+++ /dev/null
-package tim.prune.save;
-
-import java.awt.BorderLayout;
-import java.awt.Dimension;
-import java.awt.FlowLayout;
-import java.awt.Frame;
-import java.awt.event.ActionEvent;
-import java.awt.event.ActionListener;
-import java.io.File;
-
-import javax.swing.BorderFactory;
-import javax.swing.BoxLayout;
-import javax.swing.JButton;
-import javax.swing.JCheckBox;
-import javax.swing.JDialog;
-import javax.swing.JLabel;
-import javax.swing.JOptionPane;
-import javax.swing.JPanel;
-import javax.swing.JProgressBar;
-import javax.swing.JScrollPane;
-import javax.swing.JTable;
-
-import tim.prune.ExternalTools;
-import tim.prune.I18nManager;
-import tim.prune.UpdateMessageBroker;
-import tim.prune.config.Config;
-import tim.prune.data.Coordinate;
-import tim.prune.data.DataPoint;
-import tim.prune.data.Photo;
-import tim.prune.data.PhotoList;
-
-/**
- * Class to call Exiftool to save coordinate information in jpg files
- */
-public class ExifSaver implements Runnable
-{
- private Frame _parentFrame = null;
- private JDialog _dialog = null;
- private JButton _okButton = null;
- private JCheckBox _overwriteCheckbox = null;
- private JCheckBox _forceCheckbox = null;
- private JProgressBar _progressBar = null;
- private PhotoTableModel _photoTableModel = null;
- private boolean _saveCancelled = false;
-
-
- // To preserve timestamps of file use parameter -P
- // To overwrite file (careful!) use parameter -overwrite_original_in_place
-
- // To read all GPS tags, use -GPS:All
- // To delete all GPS tags, use -GPS:All=
-
- // To set Altitude, use -GPSAltitude= and -GPSAltitudeRef=
- // To set Latitude, use -GPSLatitude= and -GPSLatitudeRef=
-
- // To delete all tags with overwrite: exiftool -P -overwrite_original_in_place -GPS:All= <filename>
-
- // To set altitude with overwrite: exiftool -P -overwrite_original_in_place -GPSAltitude=1234 -GPSAltitudeRef='Above Sea Level' <filename>
- // (setting altitude ref to 0 doesn't work)
- // To set latitude with overwrite: exiftool -P -overwrite_original_in_place -GPSLatitude='12 34 56.78' -GPSLatitudeRef=N <filename>
- // (latitude as space-separated deg min sec, reference as either N or S)
- // Same for longitude, reference E or W
-
-
- /**
- * Constructor
- * @param inParentFrame parent frame
- */
- public ExifSaver(Frame inParentFrame)
- {
- _parentFrame = inParentFrame;
- }
-
-
- /**
- * Save exif information to all photos in the list
- * whose coordinate information has changed since loading
- * @param inPhotoList list of photos to save
- * @return true if saved
- */
- public boolean saveExifInformation(PhotoList inPhotoList)
- {
- // Check if external exif tool can be called
- boolean exifToolInstalled = ExternalTools.isToolInstalled(ExternalTools.TOOL_EXIFTOOL);
- if (!exifToolInstalled)
- {
- // show warning
- int answer = JOptionPane.showConfirmDialog(_dialog, I18nManager.getText("dialog.saveexif.noexiftool"),
- I18nManager.getText("dialog.saveexif.title"),
- JOptionPane.YES_NO_OPTION, JOptionPane.WARNING_MESSAGE);
- if (answer == JOptionPane.NO_OPTION || answer == JOptionPane.CLOSED_OPTION)
- {
- return false;
- }
- }
- // Make model and add all photos to it
- _photoTableModel = new PhotoTableModel(inPhotoList.getNumPhotos());
- for (int i=0; i<inPhotoList.getNumPhotos(); i++)
- {
- Photo photo = inPhotoList.getPhoto(i);
- PhotoTableEntry entry = new PhotoTableEntry(photo);
- _photoTableModel.addPhotoInfo(entry);
- }
- // Check if there are any modified photos to save
- if (_photoTableModel.getNumSaveablePhotos() < 1)
- {
- JOptionPane.showMessageDialog(_parentFrame, I18nManager.getText("dialog.saveexif.nothingtosave"),
- I18nManager.getText("dialog.saveexif.title"), JOptionPane.WARNING_MESSAGE);
- return false;
- }
- // Construct dialog
- _dialog = new JDialog(_parentFrame, I18nManager.getText("dialog.saveexif.title"), true);
- _dialog.setLocationRelativeTo(_parentFrame);
- _dialog.setDefaultCloseOperation(JDialog.DISPOSE_ON_CLOSE);
- _dialog.getContentPane().add(makeDialogComponents());
- _dialog.pack();
- // set progress bar and show dialog
- _progressBar.setVisible(false);
- _dialog.setVisible(true);
- return true;
- }
-
-
- /**
- * Put together the dialog components for adding to the gui
- * @return panel containing all gui components
- */
- private JPanel makeDialogComponents()
- {
- JPanel panel = new JPanel();
- panel.setLayout(new BorderLayout());
- // Label at top
- JLabel topLabel = new JLabel(I18nManager.getText("dialog.saveexif.intro"));
- topLabel.setBorder(BorderFactory.createEmptyBorder(8, 6, 5, 6));
- panel.add(topLabel, BorderLayout.NORTH);
- // centre panel with most controls
- JPanel centrePanel = new JPanel();
- centrePanel.setLayout(new BorderLayout());
- // table panel with table and checkbox
- JPanel tablePanel = new JPanel();
- tablePanel.setLayout(new BorderLayout());
- JTable photoTable = new JTable(_photoTableModel);
- JScrollPane scrollPane = new JScrollPane(photoTable);
- scrollPane.setPreferredSize(new Dimension(300, 160));
- tablePanel.add(scrollPane, BorderLayout.CENTER);
- // Pair of checkboxes
- JPanel checkPanel = new JPanel();
- checkPanel.setLayout(new BoxLayout(checkPanel, BoxLayout.Y_AXIS));
- _overwriteCheckbox = new JCheckBox(I18nManager.getText("dialog.saveexif.overwrite"));
- _overwriteCheckbox.setSelected(false);
- checkPanel.add(_overwriteCheckbox);
- _forceCheckbox = new JCheckBox(I18nManager.getText("dialog.saveexif.force"));
- _forceCheckbox.setSelected(false);
- checkPanel.add(_forceCheckbox);
- tablePanel.add(checkPanel, BorderLayout.SOUTH);
- centrePanel.add(tablePanel, BorderLayout.CENTER);
- // progress bar below main controls
- _progressBar = new JProgressBar(0, 100);
- centrePanel.add(_progressBar, BorderLayout.SOUTH);
- panel.add(centrePanel, BorderLayout.CENTER);
- // Right-hand panel with select all, none buttons
- JPanel rightPanel = new JPanel();
- rightPanel.setLayout(new BoxLayout(rightPanel, BoxLayout.Y_AXIS));
- JButton selectAllButton = new JButton(I18nManager.getText("button.selectall"));
- selectAllButton.addActionListener(new ActionListener() {
- public void actionPerformed(ActionEvent e)
- {
- selectPhotos(true);
- }
- });
- rightPanel.add(selectAllButton);
- JButton selectNoneButton = new JButton(I18nManager.getText("button.selectnone"));
- selectNoneButton.addActionListener(new ActionListener() {
- public void actionPerformed(ActionEvent e)
- {
- selectPhotos(false);
- }
- });
- rightPanel.add(selectNoneButton);
- panel.add(rightPanel, BorderLayout.EAST);
- // Lower panel with ok and cancel buttons
- JPanel buttonPanel = new JPanel();
- buttonPanel.setLayout(new FlowLayout(FlowLayout.RIGHT));
- _okButton = new JButton(I18nManager.getText("button.ok"));
- _okButton.addActionListener(new ActionListener() {
- public void actionPerformed(ActionEvent e)
- {
- // disable ok button
- _okButton.setEnabled(false);
- // start new thread to do save
- new Thread(ExifSaver.this).start();
- }
- });
- buttonPanel.add(_okButton);
- JButton cancelButton = new JButton(I18nManager.getText("button.cancel"));
- cancelButton.addActionListener(new ActionListener() {
- public void actionPerformed(ActionEvent e)
- {
- _saveCancelled = true;
- _dialog.dispose();
- }
- });
- buttonPanel.add(cancelButton);
- panel.add(buttonPanel, BorderLayout.SOUTH);
- return panel;
- }
-
-
- /**
- * Select all or select none
- * @param inSelected true to select all photos, false to deselect all
- */
- private void selectPhotos(boolean inSelected)
- {
- int numPhotos = _photoTableModel.getRowCount();
- for (int i=0; i<numPhotos; i++)
- {
- _photoTableModel.getPhotoTableEntry(i).setSaveFlag(inSelected);
- }
- _photoTableModel.fireTableDataChanged();
- }
-
-
- /**
- * Run method for saving in separate thread
- */
- public void run()
- {
- _saveCancelled = false;
- PhotoTableEntry entry = null;
- Photo photo = null;
- int numPhotos = _photoTableModel.getRowCount();
- _progressBar.setMaximum(numPhotos);
- _progressBar.setValue(0);
- _progressBar.setVisible(true);
- boolean overwriteFlag = _overwriteCheckbox.isSelected();
- int numSaved = 0, numFailed = 0, numForced = 0;
- // Loop over all photos in list
- for (int i=0; i<numPhotos; i++)
- {
- entry = _photoTableModel.getPhotoTableEntry(i);
- if (entry != null && entry.getSaveFlag() && !_saveCancelled)
- {
- // Only look at photos which are selected and whose status has changed since load
- photo = entry.getPhoto();
- if (photo != null && photo.isModified())
- {
- // Increment counter if save successful
- if (savePhoto(photo, overwriteFlag, false)) {
- numSaved++;
- }
- else {
- if (_forceCheckbox.isSelected() && savePhoto(photo, overwriteFlag, true))
- {
- numForced++;
- }
- else {
- numFailed++;
- }
- }
- }
- }
- // update progress bar
- _progressBar.setValue(i + 1);
- }
- _progressBar.setVisible(false);
- // Show confirmation
- UpdateMessageBroker.informSubscribers(I18nManager.getTextWithNumber("confirm.saveexif.ok", numSaved));
- if (numFailed > 0)
- {
- JOptionPane.showMessageDialog(_parentFrame,
- I18nManager.getTextWithNumber("error.saveexif.failed", numFailed),
- I18nManager.getText("dialog.saveexif.title"), JOptionPane.ERROR_MESSAGE);
- }
- if (numForced > 0)
- {
- JOptionPane.showMessageDialog(_parentFrame,
- I18nManager.getTextWithNumber("error.saveexif.forced", numForced),
- I18nManager.getText("dialog.saveexif.title"), JOptionPane.WARNING_MESSAGE);
- }
- // close dialog, all finished
- _dialog.dispose();
- }
-
-
- /**
- * Save the details for the given photo
- * @param inPhoto Photo object
- * @param inOverwriteFlag true to overwrite file, false otherwise
- * @param inForceFlag true to force write, ignoring minor errors
- * @return true if details saved ok
- */
- private boolean savePhoto(Photo inPhoto, boolean inOverwriteFlag, boolean inForceFlag)
- {
- // If photos don't have a file, then can't save them
- if (inPhoto.getFile() == null) {
- return false;
- }
- // Check whether photo file still exists
- if (!inPhoto.getFile().exists())
- {
- // photo file doesn't exist any more
- JOptionPane.showMessageDialog(_parentFrame,
- I18nManager.getText("error.saveexif.filenotfound") + " : " + inPhoto.getFile().getAbsolutePath(),
- I18nManager.getText("dialog.saveexif.title"), JOptionPane.ERROR_MESSAGE);
- return false;
- }
- // Warn if file read-only and selected to overwrite
- if (inOverwriteFlag && !inPhoto.getFile().canWrite())
- {
- // eek, can't overwrite file
- int answer = JOptionPane.showConfirmDialog(_parentFrame,
- I18nManager.getText("error.saveexif.cannotoverwrite1") + " " + inPhoto.getFile().getAbsolutePath()
- + " " + I18nManager.getText("error.saveexif.cannotoverwrite2"),
- I18nManager.getText("dialog.saveexif.title"),
- JOptionPane.YES_NO_OPTION, JOptionPane.ERROR_MESSAGE);
- if (answer == JOptionPane.YES_OPTION)
- {
- // don't overwrite this image but write to copy
- inOverwriteFlag = false;
- }
- else
- {
- // don't do anything with this file
- return false;
- }
- }
- String[] command = null;
- if (inPhoto.getCurrentStatus() == Photo.Status.NOT_CONNECTED)
- {
- // Photo is no longer connected, so delete gps tags
- command = getDeleteGpsExifTagsCommand(inPhoto.getFile(), inOverwriteFlag);
- }
- else
- {
- // Photo is now connected, so write new gps tags
- command = getWriteGpsExifTagsCommand(inPhoto.getFile(), inPhoto.getDataPoint(), inOverwriteFlag, inForceFlag);
- }
- // Execute exif command
- boolean saved = false;
- try
- {
- Process process = Runtime.getRuntime().exec(command);
- // Wait for process to finish so not too many run in parallel
- try {
- process.waitFor();
- }
- catch (InterruptedException ie) {}
- saved = (process.exitValue() == 0);
- }
- catch (Exception e)
- {
- // show error message
- JOptionPane.showMessageDialog(_parentFrame, "Exception: '" + e.getClass().getName() + "' : "
- + e.getMessage(), I18nManager.getText("dialog.saveexif.title"), JOptionPane.ERROR_MESSAGE);
- }
- return saved;
- }
-
-
- /**
- * Create the command to delete the gps exif tags from the specified file
- * @param inFile file from which to delete tags
- * @param inOverwrite true to overwrite file, false to create copy
- * @return external command to delete gps tags
- */
- private static String[] getDeleteGpsExifTagsCommand(File inFile, boolean inOverwrite)
- {
- // Make a string array to construct the command and its parameters
- String[] result = new String[inOverwrite?5:4];
- result[0] = Config.getConfigString(Config.KEY_EXIFTOOL_PATH);
- result[1] = "-P";
- if (inOverwrite) {result[2] = " -overwrite_original_in_place";}
- // remove all gps tags
- int paramOffset = inOverwrite?3:2;
- result[paramOffset] = "-GPS:All=";
- result[paramOffset + 1] = inFile.getAbsolutePath();
- return result;
- }
-
-
- /**
- * Create the comand to write the gps exif tags to the specified file
- * @param inFile file to which to write the tags
- * @param inPoint DataPoint object containing coordinate information
- * @param inOverwrite true to overwrite file, false to create copy
- * @param inForce true to force write, ignoring minor errors
- * @return external command to write gps tags
- */
- private static String[] getWriteGpsExifTagsCommand(File inFile, DataPoint inPoint,
- boolean inOverwrite, boolean inForce)
- {
- // Make a string array to construct the command and its parameters
- String[] result = new String[(inOverwrite?10:9) + (inForce?1:0)];
- result[0] = Config.getConfigString(Config.KEY_EXIFTOOL_PATH);
- result[1] = "-P";
- if (inOverwrite) {result[2] = "-overwrite_original_in_place";}
- int paramOffset = inOverwrite?3:2;
- if (inForce) {
- result[paramOffset] = "-m";
- paramOffset++;
- }
- // To set latitude : -GPSLatitude='12 34 56.78' -GPSLatitudeRef='N'
- // (latitude as space-separated deg min sec, reference as either N or S)
- result[paramOffset] = "-GPSLatitude='" + inPoint.getLatitude().output(Coordinate.FORMAT_DEG_MIN_SEC_WITH_SPACES)
- + "'";
- result[paramOffset + 1] = "-GPSLatitudeRef=" + inPoint.getLatitude().output(Coordinate.FORMAT_CARDINAL);
- // same for longitude with space-separated deg min sec, reference as either E or W
- result[paramOffset + 2] = "-GPSLongitude='" + inPoint.getLongitude().output(Coordinate.FORMAT_DEG_MIN_SEC_WITH_SPACES)
- + "'";
- result[paramOffset + 3] = "-GPSLongitudeRef=" + inPoint.getLongitude().output(Coordinate.FORMAT_CARDINAL);
- // add altitude if it has it
- result[paramOffset + 4] = "-GPSAltitude="
- + (inPoint.hasAltitude()?inPoint.getAltitude().getMetricValue():0);
- result[paramOffset + 5] = "-GPSAltitudeRef='Above Sea Level'";
- // add the filename to modify
- result[paramOffset + 6] = inFile.getAbsolutePath();
- return result;
- }
-}