# -*- coding: utf-8 -*- #!/usr/bin/python # # Gramps - a GTK+/GNOME based genealogy program # # Copyright (C) 2009-2011 Rob G. Healey # # This program is free software; you can redistribute it and/or modify # it under the terms of the GNU General Public License as published by # the Free Software Foundation; either version 2 of the License, or # (at your option) any later version. # # This program is distributed in the hope that it will be useful, # but WITHOUT ANY WARRANTY; without even the implied warranty of # MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the # GNU General Public License for more details. # # You should have received a copy of the GNU General Public License # along with this program; if not, write to the Free Software # Foundation, Inc., 59 Temple Place, Suite 330, Boston, MA 02111-1307 USA # $Id$ # ***************************************************************************** # Python Modules # ***************************************************************************** import os import datetime import calendar import time from PIL import Image # abilty to escape certain characters from output... from xml.sax.saxutils import escape as _html_escape from itertools import chain from decimal import Decimal, getcontext getcontext().prec = 6 from fractions import Fraction import subprocess # ----------------------------------------------------------------------------- # GTK modules # ----------------------------------------------------------------------------- import gtk # ----------------------------------------------------------------------------- # GRAMPS modules # ----------------------------------------------------------------------------- import GrampsDisplay from gen.ggettext import gettext as _ from DateHandler import displayer as _dd from DateHandler import parser as _dp from gen.plug import Gramplet from gui.widgets import ValidatableMaskedEntry from Errors import ValidationError from QuestionDialog import WarningDialog, QuestionDialog, OptionDialog import gen.lib import gen.mime import Utils from PlaceUtils import conv_lat_lon from gen.db import DbTxn from ListModel import ListModel import pyexiv2 # v0.1 has a different API to v0.2 and above if hasattr(pyexiv2, 'version_info'): OLD_API = False else: # version_info attribute does not exist prior to v0.2.0 OLD_API = True #------------------------------------------------ # support helpers #------------------------------------------------ def _format_datetime(exif_dt): """ Convert a python datetime object into a string for display, using the standard Gramps date format. """ if type(exif_dt) is not datetime.datetime: return '' date_part = gen.lib.date.Date() date_part.set_yr_mon_day(exif_dt.year, exif_dt.month, exif_dt.day) date_str = _dd.display(date_part) time_str = _('%(hr)02d:%(min)02d:%(sec)02d') % {'hr': exif_dt.hour, 'min': exif_dt.minute, 'sec': exif_dt.second} return _('%(date)s %(time)s') % {'date': date_str, 'time': time_str} def _format_gps(tag_value): """ Convert a (degrees, minutes, seconds) tuple into a string for display. """ return "%d° %02d' %05.2f\"" % (tag_value[0], tag_value[1], tag_value[2]) def _parse_datetime(value): """ Parse date and time and return a datetime object. """ value = value.rstrip() if not value: return None if value.find(u':') >= 0: # Time part present if value.find(u' ') >= 0: # Both date and time part date_text, time_text = value.rsplit(u' ', 1) else: # Time only date_text = u'' time_text = value else: # Date only date_text = value time_text = u'00:00:00' date_part = _dp.parse(date_text) try: time_part = time.strptime(time_text, "%H:%M:%S") except ValueError: time_part = None if date_part.get_modifier() == Date.MOD_NONE and time_part is not None: return datetime(date_part.get_year(), date_part.get_month(), date_part.get_day(), time_part.tm_hour, time_part.tm_min, time_part.tm_sec) else: return None # ----------------------------------------------------------------------------- # Constants # ----------------------------------------------------------------------------- # available image types for exiv2 and pyexiv2 _vtypes = [".jpeg", ".jpg", ".jfif", ".exv", ".dng", ".bmp", ".nef", ".png", ".psd", ".jp2", ".pef", ".srw", ".pgf", ".tiff"] _vtypes.sort() _VALIDIMAGEMAP = dict( (index, imgtype_) for index, imgtype_ in enumerate(_vtypes) ) # valid converting types for PIL.Image... _validconvert = list( (_("-- Image Types --"), ".bmp", ".gif", ".jpg", ".msp", ".pcx", ".png", ".ppm", ".tiff", ".xbm") ) DESCRIPTION = _("Description") ORIGIN = _("Origin") IMAGE = _('Image') CAMERA = _('Camera') GPS = _('GPS') ADVANCED = _("Advanced") # All of the exiv2 tag reference... TAGS_ = [ # Description subclass... (DESCRIPTION, 'Exif.Image.Artist', None, None), (DESCRIPTION, 'Exif.Image.Copyright', None, None), (DESCRIPTION, 'Exif.Photo.DateTimeOriginal', None, _format_datetime), (DESCRIPTION, 'Exif.Photo.DateTimeDigitized', None, _format_datetime), (DESCRIPTION, 'Exif.Image.Rating', None, None), # Origin subclass... (ORIGIN, 'Exif.Image.Software', None, None), (ORIGIN, 'Xmp.MicrosoftPhoto.DateAcquired', None, None), (ORIGIN, 'Exif.Image.TimeZoneOffset', None, None), (ORIGIN, 'Exif.Image.SubjectDistance', None, None), # Image subclass... (IMAGE, 'Exif.Image.ImageDescription', None, None), (IMAGE, 'Exif.Photo.DateTimeOriginal', None, _format_datetime), (IMAGE, 'Exif.Photo.PixelXDimension', None, None), (IMAGE, 'Exif.Photo.PixelYDimension', None, None), (IMAGE, 'Exif.Image.Compression', None, None), (IMAGE, 'Exif.Image.DocumentName', None, None), (IMAGE, 'Exif.Image.Orientation', None, None), (IMAGE, 'Exif.Image.ImageID', None, None), (IMAGE, 'Exif.Photo.ExifVersion', None, None), # Camera subclass... (CAMERA, 'Exif.Image.Make', None, None), (CAMERA, 'Exif.Image.Model', None, None), (CAMERA, 'Exif.Photo.FNumber', None, None), (CAMERA, 'Exif.Photo.ExposureTime', None, None), (CAMERA, 'Exif.Photo.ISOSpeedRatings', None, None), (CAMERA, 'Exif.Photo.FocalLength', None, None), (CAMERA, 'Exif.Photo.MeteringMode', None, None), (CAMERA, 'Exif.Photo.Flash', None, None), (CAMERA, 'Exif.Image.SelfTimerMode', None, None), (CAMERA, 'Exif.Image.CameraSerialNumber', None, None), # GPS subclass... (GPS, 'Exif.GPSInfo.GPSLatitude', 'Exif.GPSInfo.GPSLatitudeRef', _format_gps), (GPS, 'Exif.GPSInfo.GPSLongitude', 'Exif.GPSInfo.GPSLongitudeRef', _format_gps), (GPS, 'Exif.GPSInfo.GPSAltitude', 'Exif.GPSInfo.GPSAltitudeRef', None), (GPS, 'Exif.Image.GPSTag', None, None), (GPS, 'Exif.GPSInfo.GPSTimeStamp', None, _format_gps), (GPS, 'Exif.GPSInfo.GPSSatellites', None, None), # Advanced subclass... (ADVANCED, 'Xmp.MicrosoftPhoto.LensManufacturer', None, None), (ADVANCED, 'Xmp.MicrosoftPhoto.LensModel', None, None), (ADVANCED, 'Xmp.MicrosoftPhoto.FlashManufacturer', None, None), (ADVANCED, 'Xmp.MicrosoftPhoto.FlashModel', None, None), (ADVANCED, 'Xmp.MicrosoftPhoto.CameraSerialNumber', None, None), (ADVANCED, 'Exif.Photo.Contrast', None, None), (ADVANCED, 'Exif.Photo.LightSource', None, None), (ADVANCED, 'Exif.Photo.ExposureProgram', None, None), (ADVANCED, 'Exif.Photo.Saturation', None, None), (ADVANCED, 'Exif.Photo.Sharpness', None, None), (ADVANCED, 'Exif.Photo.WhiteBalance', None, None), (ADVANCED, 'Exif.Image.ExifTag', None, None), (ADVANCED, 'Exif.Image.BatteryLevel', None, None), (ADVANCED, 'Exif.Image.XPKeywords', None, None), (ADVANCED, 'Exif.Image.XPComment', None, None), (ADVANCED, 'Exif.Image.XPSubject', None, None) ] # set up Exif keys for Image Exif metadata keypairs... _DATAMAP = { "Exif.Image.ImageDescription" : "Description", "Exif.Image.DateTime" : "Modified", "Exif.Image.Artist" : "Artist", "Exif.Image.Copyright" : "Copyright", "Exif.Photo.DateTimeOriginal" : "Original", "Exif.Photo.DateTimeDigitized" : "Digitized", "Exif.GPSInfo.GPSLatitudeRef" : "LatitudeRef", "Exif.GPSInfo.GPSLatitude" : "Latitude", "Exif.GPSInfo.GPSLongitudeRef" : "LongitudeRef", "Exif.GPSInfo.GPSLongitude" : "Longitude", "Exif.GPSInfo.GPSAltitudeRef" : "AltitudeRef", "Exif.GPSInfo.GPSAltitude" : "Altitude"} _DATAMAP = dict((key, val) for key, val in _DATAMAP.items() ) _DATAMAP.update( (val, key) for key, val in _DATAMAP.items() ) # define tooltips for all data entry fields... _TOOLTIPS = { # Description... "Description" : _("Provide a short descripion for this image."), # Last Change/ Modify Date/ Time... "Modified" : _("This is the date/ time that the image was last changed/ modified.\n" "Example: 2011-05-24 14:30:00"), # Artist... "Artist" : _("Enter the Artist/ Author of this image. The person's name or " "the company who is responsible for the creation of this image."), # Copyright... "Copyright" : _("Enter the copyright information for this image. \n"), # Original Date/ Time... "Original" : _("The original date/ time when the image was first created/ taken as in a photograph.\n" "Example: 1830-01-1 09:30:59"), # GPS Latitude coordinates... "Latitude" : _("Enter the Latitude GPS coordinates for this image,\n" "Example: 43.722965, 43 43 22 N, 38° 38′ 03″ N, 38 38 3"), # GPS Longitude coordinates... "Longitude" : _("Enter the Longitude GPS coordinates for this image,\n" "Example: 10.396378, 10 23 46 E, 105° 6′ 6″ W, -105 6 6"), # GPS Altitude (in meters)... "Altitude" : _("This is the measurement of Above or Below Sea Level. It is measured in meters." "Example: 200.558, -200.558") } _TOOLTIPS = dict( (key, tooltip) for key, tooltip in _TOOLTIPS.items() ) # define tooltips for all buttons... # common buttons for all images... _BUTTONTIPS = { # Wiki Help button... "Help" : _("Displays the Gramps Wiki Help page for 'Edit Image Exif Metadata' " "in your web browser."), # Edit screen button... "Edit" : _("This will open up a new window to allow you to edit/ modify " "this image's Exif metadata.\n It will also allow you to be able to " "Save the modified metadata."), # Thumbnail Viewing Window button... "Thumbnail" : _("Will produce a Popup window showing a Thumbnail Viewing Area"), # Image Type button... "ImageType" : _("Select from a drop- down box the image file type that you " "would like to convert your non- Exiv2 compatible media object to."), # Convert to different image type... "Convert" : _("If your image is not of an image type that can have " "Exif metadata read/ written to/from, convert it to a type that can?"), # Delete/ Erase/ Wipe Exif metadata button... "Delete" : _("WARNING: This will completely erase all Exif metadata " "from this image! Are you sure that you want to do this?") } # ------------------------------------------------------------------------ # # 'Edit Image Exif metadata' Class # # ------------------------------------------------------------------------ class EditExifMetadata(Gramplet): """ Special symbols... degrees symbol = [Ctrl] [Shift] u \00b0 minutes symbol = \2032 seconds symbol = \2033 """ def init(self): self.exif_widgets = {} self.dates = {} self.coordinates = {} self.image_path = False self.orig_image = False self.plugin_image = False vbox = self.__build_gui() self.connect_signal("Media", self.update) self.connect_signal("media-update", self.update) self.gui.get_container_widget().remove(self.gui.textview) self.gui.get_container_widget().add_with_viewport(vbox) def __build_gui(self): """ will display all exif metadata and all buttons. """ main_vbox = gtk.VBox(False, 0) main_vbox.set_border_width(10) # Displays the file name... medialabel = gtk.HBox(False) label = self.__create_label("MediaLabel", False, False, False) medialabel.pack_start(label, expand =False) main_vbox.pack_start(medialabel, expand =False, fill =False, padding =2) label.show() # Displays mime type information... mimetype = gtk.HBox(False) label = self.__create_label("MimeType", False, False, False) mimetype.pack_start(label, expand =False) main_vbox.pack_start(mimetype, expand =False, fill =False, padding =2) label.show() # image dimensions... imagesize = gtk.HBox(False) label = self.__create_label("ImageSize", False, False, False) imagesize.pack_start(label, expand =False, fill =False, padding =0) main_vbox.pack_start(imagesize, expand =False, fill =True, padding =5) label.hide() # Displays all plugin messages... messagearea = gtk.HBox(False) label = self.__create_label("MessageArea", False, False, False) messagearea.pack_start(label, expand =False) main_vbox.pack_start(messagearea, expand =False, fill =False, padding =2) label.show() # Separator line before the buttons... main_vbox.pack_start(gtk.HSeparator(), expand =False, fill =True, padding =0) # Thumbnail, ImageType, and Convert buttons... new_hbox = gtk.HBox() main_vbox.pack_start(new_hbox, expand =False, fill =True, padding =5) new_hbox.show() # Thumbnail button... event_box = gtk.EventBox() new_hbox.pack_start(event_box, expand =False, fill =True, padding =5) event_box.show() button = self.__create_button( "Thumbnail", _("Thumbnail"), [self.thumbnail_view]) event_box.add(button) button.show() # Image Type... event_box = gtk.EventBox() event_box.set_size_request(150, 30) new_hbox.pack_start(event_box, expand =False, fill =True, padding =5) event_box.show() combo_box = gtk.combo_box_new_text() combo_box.set_active(0) combo_box.set_sensitive(False) event_box.add(combo_box) self.exif_widgets["ImageType"] = combo_box combo_box.show() # Convert button... event_box = gtk.EventBox() new_hbox.pack_start(event_box, expand =False, fill =True, padding =5) event_box.show() button = self.__create_button( "Convert", False, [self.__convert_dialog], gtk.STOCK_CONVERT) event_box.add(button) button.show() # Connect the changed signal to ImageType... self.exif_widgets["ImageType"].connect("changed", self.changed_cb) # Help, Edit, and Delete horizontal box hed_box = gtk.HButtonBox() hed_box.set_layout(gtk.BUTTONBOX_START) main_vbox.pack_start(hed_box, expand =False, fill =True, padding =5) # Help button... hed_box.add( self.__create_button( "Help", False, [self.__help_page], gtk.STOCK_HELP, True) ) # Edit button... hed_box.add( self.__create_button( "Edit", False, [self.display_edit_window], gtk.STOCK_EDIT) ) # Delete All Metadata button... hed_box.add(self.__create_button( "Delete", False, [self.__wipe_dialog], gtk.STOCK_DELETE) ) new_vbox = self.__build_shaded_gui() main_vbox.pack_start(new_vbox, expand =False, fill =False, padding =10) # number of key/value pairs shown... label = self.__create_label("Total", False, False, False) main_vbox.pack_start(label, expand =False, fill =False, padding =10) label.show() main_vbox.show_all() return main_vbox def __build_shaded_gui(self): """ Build the GUI interface. """ top = gtk.TreeView() titles = [(_('Key'), 1, 160), (_('Value'), 2, 290)] self.model = ListModel(top, titles, list_mode="tree") return top def db_changed(self): self.dbstate.db.connect('media-update', self.update) self.connect_signal('Media', self.update) self.update() def main(self): # return false finishes """ get the active media, mime type, and reads the image metadata *** disable all buttons at first, then activate as needed... # Help will never be disabled... """ db = self.dbstate.db # self.update() # display all button tooltips only... # 1st argument is for Fields, 2nd argument is for Buttons... self._setup_widget_tips(fields =False, buttons =True) # clears all labels and display area... for widgetname_ in ["MediaLabel", "MimeType", "ImageSize", "MessageArea", "Total"]: self.exif_widgets[widgetname_].set_text("") self.model.clear() self.sections = {} # deactivate Convert and ImageType buttons... self.deactivate_buttons(["Convert", "ImageType"]) # set Message Ares to Select... self.exif_widgets["MessageArea"].set_text(_("Select an image to begin...")) active_handle = self.get_active("Media") if not active_handle: self.set_has_data(False) return # get image from database... self.orig_image = db.get_object_from_handle(active_handle) if not self.orig_image: self.set_has_data(False) return # get file path and attempt to find it? self.image_path = Utils.media_path_full(db, self.orig_image.get_path() ) if not os.path.isfile(self.image_path): self.set_has_data(False) return # display file description/ title... self.exif_widgets["MediaLabel"].set_text(_html_escape(self.orig_image.get_description() ) ) # Mime type information... mime_type = self.orig_image.get_mime_type() self.exif_widgets["MimeType"].set_text("%s, %s" % ( mime_type, gen.mime.get_description(mime_type) ) ) # get dirpath/ basename, and extension... self._filepath_fname, self.extension = os.path.splitext(self.image_path) # check image read privileges... _readable = os.access(self.image_path, os.R_OK) if not _readable: self.exif_widgets["MessageArea"].set_text(_("Image is NOT readable,\n" "Please choose a different image...")) return # check image write privileges... _writable = os.access(self.image_path, os.W_OK) if not _writable: self.exif_widgets["MessageArea"].set_text(_("Image is NOT writable,\n" "You will NOT be able to save Exif metadata....")) self.deactivate_buttons(["Edit"]) # if image file type is not an Exiv2 acceptable image type, offer to convert it.... if self.extension not in _VALIDIMAGEMAP.values(): self.activate_buttons(["ImageType"]) imageconvert_ = _validconvert if self.extension in imageconvert_: imageconvert_.remove(self.extension) self._VCONVERTMAP = dict( (index, imgtype_) for index, imgtype_ in enumerate(imageconvert_) ) for imgtype_ in self._VCONVERTMAP.values(): self.exif_widgets["ImageType"].append_text(imgtype_) self.exif_widgets["ImageType"].set_active(0) else: self.activate_buttons(["Edit"]) # determine if it is a mime image object? if mime_type: if mime_type.startswith("image"): # creates, and reads the plugin image instance... self.plugin_image = self.setup_image(self.image_path) if self.plugin_image: if OLD_API: # prior to pyexiv2-0.2.0 try: ttype, tdata = self.plugin_image.getThumbnailData() width, height = tdata.dimensions thumbnail = True except (IOError, OSError): thumbnail = False else: # pyexiv2-0.2.0 and above try: previews = self.plugin_image.previews thumbnail = True if not previews: thumbnail = False except (IOError, OSError): thumbnail = False # get image width and height... self.exif_widgets["ImageSize"].show() width, height = self.plugin_image.dimensions self.exif_widgets["ImageSize"].set_text(_("Image Size : %04d x %04d pixels") % (width, height)) # if a thumbnail exists, then activate the buttton? if thumbnail: self.activate_buttons(["Thumbnail"]) # Activate the Edit button... self.activate_buttons(["Edit"]) # display all exif metadata... self.__display_exif_tags() # has mime, but not an image... else: self.exif_widgets["MessageArea"].set_text(_("Please choose a different image...")) return # has no mime... else: self.exif_widgets["MessageArea"].set_text(_("Please choose a different image...")) return def __display_exif_tags(self, metadatatags_ =None): """ Display the exif tags. """ metadatatags_ = _get_exif_keypairs(self.plugin_image) if not metadatatags_: self.exif_widgets["MessageArea"].set_text(_("No Exif metadata for this image...")) self.set_has_data(False) return if OLD_API: # prior to v0.2.0 try: self.plugin_image.readMetadata() has_metadata = True except (IOError, OSError): has_metadata = False if has_metadata: for section, key, key2, func in TAGS_: if key in metadatatags_: if section not in self.sections: node = self.model.add([section, '']) self.sections[section] = node else: node = self.sections[section] label = self.plugin_image.tagDetails(key)[0] if func: human_value = func(self.plugin_image[key]) else: human_value = self.plugin_image.interpretedExifValue(key) if key2: human_value += ' ' + self.plugin_image.interpretedExifValue(key2) self.model.add((label, human_value), node =node) self.model.tree.expand_all() else: # v0.2.0 and above try: self.plugin_image.read() has_metadata = True except (IOError, OSError): has_metadata = False if has_metadata: for section, key, key2, func in TAGS_: if key in metadatatags_: if section not in self.sections: node = self.model.add([section, '']) self.sections[section] = node else: node = self.sections[section] tag = self.plugin_image[key] if func: human_value = func(tag.value) else: human_value = tag.human_value if key2: human_value += ' ' + self.plugin_image[key2].human_value self.model.add((tag.label, human_value), node =node) self.model.tree.expand_all() # update has_data functionality... self.set_has_data(self.model.count > 0) # activate these buttons... self.activate_buttons(["Delete"]) def changed_cb(self, object): """ will show the Convert Button once an Image Type has been selected, and if image extension is not an Exiv2- compatible image? """ # get convert image type and check it? ext_value = self.exif_widgets["ImageType"].get_active() if (self.extension not in _VALIDIMAGEMAP.values() and ext_value >= 1): # if Convert button is not active, set it to active state # so that the user may convert this image? if not self.exif_widgets["Convert"].get_sensitive(): self.activate_buttons(["Convert"]) def _setup_widget_tips(self, fields =None, buttons =None): """ set up widget tooltips... * data fields * buttons """ # if True, setup tooltips for all Data Entry Fields... if fields: for widget, tooltip in _TOOLTIPS.items(): self.exif_widgets[widget].set_tooltip_text(tooltip) # if True, setup tooltips for all Buttons... if buttons: for widget, tooltip in _BUTTONTIPS.items(): self.exif_widgets[widget].set_tooltip_text(tooltip) def setup_image(self, full_path): """ This will: * create the plugin image instance if needed, * setup the tooltips for the data fields, * setup the tooltips for the buttons, """ if OLD_API: # prior to pyexiv2-0.2.0 metadata = pyexiv2.Image(full_path) try: metadata.readMetadata() except (IOError, OSError): self.set_has_data(False) return else: # pyexiv2-0.2.0 and above metadata = pyexiv2.ImageMetadata(full_path) try: metadata.read() except (IOError, OSError): self.set_has_data(False) return return metadata def update_has_data(self): active_handle = self.get_active('Media') active = self.dbstate.db.get_object_from_handle(active_handle) self.set_has_data(self.get_has_data(active)) def get_has_data(self, media): """ Return True if the gramplet has data, else return False. """ db = self.dbstate.db if media is None: return False full_path = Utils.media_path_full(db, media.get_path() ) if not os.path.isfile(full_path): return False if OLD_API: # prior to pyexiv2-0.2.0 metadata = pyexiv2.Image(full_path) try: metadata.readMetadata() except (IOError, OSError): return False else: # pyexiv2-0.2.0 and above metadata = pyexiv2.ImageMetadata(full_path) try: metadata.read() except (IOError, OSError): return False # update image Exif metadata... mediadatatags_ = _get_exif_keypairs(self.plugin_image) if mediadatatags_: return True return False def __create_button(self, pos, text, callback =[], icon =False, sensitive =False): """ creates and returns a button for display """ if (icon and not text): button = gtk.Button(stock =icon) else: button = gtk.Button(text) if callback is not []: for call_ in callback: button.connect("clicked", call_) # attach a addon widget to the button for manipulation... self.exif_widgets[pos] = button if not sensitive: button.set_sensitive(False) else: button.set_sensitive(True) return button def __create_label(self, widget, text, width, height, wrap =True): """ creates a label for this addon. """ label = gtk.Label() label.set_alignment(0.0, 0.0) if wrap: label.set_line_wrap(True) if (width and height): label.set_size_request(width, height) if text: label.set_text(text) if widget: self.exif_widgets[widget] = label return label def thumbnail_view(self, object): """ will allow a display area for a thumbnail pop-up window. """ tip = _("Click Close to close this Thumbnail View Area.") self.tbarea = gtk.Window(gtk.WINDOW_TOPLEVEL) self.tbarea.tooltip = tip self.tbarea.set_title(_("Thumbnail View Area")) self.tbarea.set_default_size((width + 40), (height + 40)) self.tbarea.set_border_width(10) self.tbarea.connect('destroy', lambda w: self.tbarea.destroy() ) pbloader, width, height = self.__get_thumbnail_data() new_vbox = self.build_thumbnail_gui(pbloader, width, height) self.tbarea.add(new_vbox) self.tbarea.show() def __get_thumbnail_data(self): """ returns the thumbnail width and height from the active media object if there is any? """ dirpath, filename = os.path.split(self.image_path) pbloader, width, height = [False]*3 if OLD_API: # prior to pyexiv2-0.2.0 try: ttype, tdata = self.plugin_image.getThumbnailData() width, height = tdata.dimensions except (IOError, OSError): print(_('Error: %s does not contain an EXIF thumbnail.') % filename) self.close_window(self.tbarea) # Create a GTK pixbuf loader to read the thumbnail data pbloader = gtk.gdk.PixbufLoader() pbloader.write(tdata) else: # pyexiv2-0.2.0 and above try: previews = self.plugin_image.previews if not previews: print(_('Error: %s does not contain an EXIF thumbnail.') % filename) self.close_window(self.tbarea) # Get the largest preview available... preview = previews[-1] width, height = preview.dimensions except (IOError, OSError): print(_('Error: %s does not contain an EXIF thumbnail.') % filename) self.close_window(self.tbarea) # Create a GTK pixbuf loader to read the thumbnail data pbloader = gtk.gdk.PixbufLoader() pbloader.write(preview.data) return pbloader, width, height def build_thumbnail_gui(self, pbloader, width, height): """ builds the thumbnail viewing area. """ main_vbox = gtk.VBox() main_vbox.set_size_request((width - 30), (height - 30)) hbox = gtk.HBox(False, 0) main_vbox.pack_start(hbox, expand =False, fill =False, padding =5) hbox.show() # Get the resulting pixbuf and build an image to be displayed... pixbuf = pbloader.get_pixbuf() pbloader.close() imgwidget = gtk.Image() imgwidget.set_from_pixbuf(pixbuf) hbox.pack_start(imgwidget, expand = False, fill =True, padding =0) imgwidget.show() main_vbox.show_all() return main_vbox def __convert_dialog(self, object): """ Handles the Convert question Dialog... """ # Convert and delete original file or just convert... OptionDialog(_("Edit Image Exif Metadata"), _("WARNING: You are about to convert this " "image into a .jpeg image. Are you sure that you want to do this?"), _("Convert and Delete"), self.__convert_delete, _("Convert"), self.__convert_only) def __convert_copy(self, full_path =None): """ Will attempt to convert an image to jpeg if it is not? """ if full_path is None: full_path = self.image_path # get image filepath and its filename... filepath, basename = os.path.split(self._filepath_fname) # get extension selected for converting this image... ext_type = self.exif_widgets["ImageType"].get_active() if ext_type >= 1: basename += self._VCONVERTMAP[ext_type] # new file name and dirpath... dest_file = os.path.join(filepath, basename) # open source image file... im = Image.open(full_path) im.save(dest_file) # identify pyexiv2 source image file... if OLD_API: # prior to pyexiv2-0.2.0... src_meta = pyexiv2.Image(full_path) src_meta.readMetadata() else: src_meta = pyexiv2.ImageMetadata(full_path) src_meta.read() # check to see if source image file has any Exif metadata? if _get_exif_keypairs(src_meta): if OLD_API: # Identify the destination image file... dest_meta = pyexiv2.Image(dest_file) dest_meta.readMetadata() # copy source metadata to destination file... src_meta.copy(dest_meta, comment =False) dest_meta.writeMetadata() else: # pyexiv2-0.2.0 and above... # Identify the destination image file... dest_meta = pyexiv2.ImageMetadata(dest_file) dest_meta.read() # copy source metadata to destination file... src_meta.copy(dest_meta, comment =False) dest_meta.write() return dest_file def __convert_delete(self, full_path =None): """ will convert an image file and delete original non-jpeg image. """ if full_path is None: full_path = self.image_path # Convert image and copy over it's Exif metadata (if any?) newfilepath = self.__convert_copy(full_path) if newfilepath: # delete original file from this computer and set new filepath... try: os.remove(full_path) delete_results = True except (IOError, OSError): delete_results = False if delete_results: # check for new destination and if source image file is removed? if (os.path.isfile(newfilepath) and not os.path.isfile(full_path) ): self.__update_media_path(newfilepath) else: self.exif_widgets["MessageArea"].set_text(_("There has been an error, " "Please check your source and destination file paths...")) # notify user about the convert, delete, and new filepath... self.exif_widgets["MessageArea"].set_text(_("Your image has been " "converted and the original file has been deleted, and " "the full path has been updated!")) else: self.exif_widgets["MessageArea"].set_text(_("There was an error in " "deleting the original file. You will need to delete it yourself!")) def __convert_only(self, full_path =None): """ This will only convert the file and update the media object path. """ if full_path is None: full_path = self.image_path # the convert was sucessful, then update media path? newfilepath = self.__convert_copy(full_path) if newfilepath: # update the media object path... self.__update_media_path(newfilepath) else: self.exif_widgets["MessageArea"].set_text(_("There was an error " "in converting your image file.")) def __update_media_path(self, newfilepath =None): """ update the media object's media path. """ if newfilepath: db = self.dbstate.db # begin database tranaction to save media object new path... with DbTxn(_("Media Path Update"), db) as trans: self.orig_image.set_path(newfilepath) db.commit_media_object(self.orig_image, trans) db.request_rebuild() else: self.exif_widgets["MessageArea"].set_text(_("There has been an " "error in updating the image file's path!")) def __help_page(self, addonwiki =None): """ will bring up a Wiki help page. """ addonwiki = 'Edit Image Exif Metadata' GrampsDisplay.help(webpage =addonwiki) def activate_buttons(self, ButtonList): """ Enable/ activate the buttons that are in ButtonList """ for widgetname_ in ButtonList: self.exif_widgets[widgetname_].set_sensitive(True) def deactivate_buttons(self, ButtonList): """ disable/ de-activate buttons in ButtonList *** if All, then disable ALL buttons in the current display... """ for widgetname_ in ButtonList: self.exif_widgets[widgetname_].set_sensitive(False) def active_buttons(self, obj): """ will handle the toggle action of the Edit button. If there is no Exif metadata, then the data fields are connected to the 'changed' signal to be able to activate the Edit button once data has been entered into the data fields... Activate these buttons once info has been entered into the data fields... """ if not self.exif_widgets["Edit"].get_sensitive(): self.activate_buttons(["Edit"]) # set Message Area to Entering Data... self.exif_widgets["MessageArea"].set_text(_("Entering data...")) if EXIV2_FOUND_: if not self.exif_widgets["Delete"].get_sensitive(): self.activate_buttons(["Delete"]) # if Save is in the list of button tooltips, then check it too? if "Save" in _BUTTONTIPS.keys(): if not self.exif_widgets["Save"].get_sensitive(): self.activate_buttons(["Save"]) def display_edit_window(self, object): """ creates the editing area fields. """ tip = _("Click the close button when you are finished modifying this " "image's Exif metadata.") self.edtarea = gtk.Window(gtk.WINDOW_TOPLEVEL) self.edtarea.tooltip = tip self.edtarea.set_title( self.orig_image.get_description() ) self.edtarea.set_default_size(525, 582) self.edtarea.set_border_width(10) self.edtarea.connect("destroy", lambda w: self.edtarea.destroy() ) # create a new scrolled window. scrollwindow = gtk.ScrolledWindow() scrollwindow.set_border_width(10) # scrollwindow.set_size_request(520, 500) scrollwindow.set_policy(gtk.POLICY_AUTOMATIC, gtk.POLICY_AUTOMATIC) self.edtarea.add(scrollwindow) scrollwindow.show() vbox = self.__build_edit_gui() scrollwindow.add_with_viewport(vbox) self.edtarea.show() # display all fields and button tooltips... # need to add Save and Close over here... _BUTTONTIPS.update( (key, tip) for key, tip in { # Add the Save button... "Save" : _("Saves a copy of the data fields into the image's Exif metadata."), # Add the Close button... "Close" : _("Closes this popup Edit window.\n" "WARNING: This action will NOT Save any changes/ modification made to this " "image's Exif metadata."), # Clear button... "Clear" : _("This button will clear all of the data fields shown here."), # Re- display the data fields button... "Copy" : _("Re -display the data fields that were cleared from the Edit Area."), # Convert 2 Decimal button... "Decimal" : _("Convert GPS Latitude/ Longitude coordinates to Decimal representation."), # Convert 2 Degrees, Minutes, Seconds button... "DMS" : _("Convert GPS Latitude/ Longitude coordinates to " "(Degrees, Minutes, Seconds) Representation.") }.items() ) # True, True -- all data fields and button tooltips will be displayed... self._setup_widget_tips(fields =True, buttons = True) # display all data fields and their values... self.EditArea(self.plugin_image) def __build_edit_gui(self): """ will build the edit screen ... """ main_vbox = gtk.VBox() main_vbox.set_border_width(10) main_vbox.set_size_request(480, 480) label = self.__create_label("Edit:Message", False, False, False) main_vbox.pack_start(label, expand =False, fill =False, padding =0) label.show() # create the data fields... # ***Label/ Title, Description, Artist, and Copyright gen_frame = gtk.Frame(_("General Data")) gen_frame.set_size_request(470, 170) main_vbox.pack_start(gen_frame, expand =False, fill =True, padding =10) gen_frame.show() new_vbox = gtk.VBox(False, 0) gen_frame.add(new_vbox) new_vbox.show() for widget, text in [ ("Description", _("Description :") ), ("Artist", _("Artist :") ), ("Copyright", _("Copyright :") ) ]: new_hbox = gtk.HBox(False, 0) new_vbox.pack_start(new_hbox, expand =False, fill =False, padding =5) new_hbox.show() label = self.__create_label(False, text, width =90, height =25) new_hbox.pack_start(label, expand =False, fill =False, padding =0) label.show() event_box = gtk.EventBox() event_box.set_size_request(360, 30) new_hbox.pack_start(event_box, expand =False, fill =False, padding =0) event_box.show() entry = gtk.Entry(max =100) event_box.add(entry) self.exif_widgets[widget] = entry entry.show() # iso format: Year, Month, Day spinners... datetime_frame = gtk.Frame(_("Date/ Time")) datetime_frame.set_size_request(470, 110) main_vbox.pack_start(datetime_frame, expand =False, fill =False, padding =0) datetime_frame.show() new_vbox = gtk.VBox(False, 0) new_vbox.set_border_width(5) datetime_frame.add(new_vbox) new_vbox.show() new_hbox = gtk.HBox(False, 0) new_vbox.pack_start(new_hbox, expand =False, fill =False, padding =0) new_hbox.show() for widget, text in [ ("Original", _("Original Date/ Time :") ), ("Modified", _("Last Changed :") ) ]: vbox2 = gtk.VBox(False, 0) new_hbox.pack_start(vbox2, expand =False, fill =False, padding =5) vbox2.show() label = self.__create_label(widget, text, width =150, height =25) vbox2.pack_start(label, expand =False, fill =False, padding =0) label.show() event_box = gtk.EventBox() event_box.set_size_request(215, 30) vbox2.pack_start(event_box, expand =False, fill =False, padding =0) event_box.show() entry = ValidatableMaskedEntry() entry.connect('validate', self.validate_datetime, widget) # entry.connect('content-changed', self.set_datetime, widget) event_box.add(entry) self.exif_widgets[widget] = entry entry.show() self.dates[widget] = None # if there is text in the modified Date/ Time field, disable editing... if self.exif_widgets["Modified"].get_text(): self.exif_widgets["Modified"].set_editable(False) # GPS coordinates... latlong_frame = gtk.Frame(_("Latitude/ Longitude/ Altitude GPS coordinates")) latlong_frame.set_size_request(470, 125) main_vbox.pack_start(latlong_frame, expand =False, fill =False, padding =0) latlong_frame.show() new_vbox = gtk.VBox(False, 0) latlong_frame.add(new_vbox) new_vbox.show() new_hbox = gtk.HBox(False, 0) new_vbox.pack_start(new_hbox, expand =False, fill =False, padding =0) new_hbox.show() for widget, text in [ ("Latitude", _("Latitude :") ), ("Longitude", _("Longitude :") ), ("Altitude", _("Altitude :") ) ]: vbox2 = gtk.VBox(False, 0) new_hbox.pack_start(vbox2, expand =False, fill =False, padding =5) vbox2.show() label = self.__create_label(widget, text, width =90, height =25) vbox2.pack_start(label, expand =False, fill =False, padding =0) label.show() event_box = gtk.EventBox() event_box.set_size_request(141, 30) vbox2.pack_start(event_box, expand =False, fill =False, padding =0) event_box.show() entry = ValidatableMaskedEntry() entry.connect('validate', self.validate_coordinate, widget) event_box.add(entry) self.exif_widgets[widget] = entry entry.show() # add an empty row for spacing... new_hbox = gtk.HBox(False, 0) new_vbox.pack_start(new_hbox, expand =False, fill =False, padding =5) new_hbox.show() new_hbox = gtk.HBox(False, 0) new_vbox.pack_start(new_hbox, expand =False, fill =False, padding =0) new_hbox.show() label = self.__create_label( False, _("Convert GPS :"), 100, 25) new_hbox.pack_start(label, expand =False, fill =False, padding =0) label.show() # Convert2decimal and DMS buttons... decdms_box = gtk.HButtonBox() decdms_box.set_layout(gtk.BUTTONBOX_END) new_vbox.pack_end(decdms_box, expand =False, fill =False, padding =0) decdms_box.show() # Decimal button... decdms_box.add(self.__create_button( "Decimal", _("Decimal"), [self.__decimalbutton], False, True) ) # Degrees, Minutes, Seconds button... decdms_box.add(self.__create_button( "DMS", _("Deg., Mins., Secs."), [self.__dmsbutton], False, True) ) # Help, Save, Clear, Copy, and Close horizontal box hsccc_box = gtk.HButtonBox() hsccc_box.set_layout(gtk.BUTTONBOX_START) main_vbox.pack_start(hsccc_box, expand =False, fill =False, padding =10) hsccc_box.show() # Help button... hsccc_box.add(self.__create_button( "Help", False, [self.__help_page], gtk.STOCK_HELP, True) ) # Save button... hsccc_box.add(self.__create_button( "Save", False, [self.save_metadata, self.update, self.__display_exif_tags], gtk.STOCK_SAVE, True) ) # Clear button... hsccc_box.add(self.__create_button( "Clear", False, [self.clear_metadata], gtk.STOCK_CLEAR, True) ) # Re -display the edit area button... hsccc_box.add(self.__create_button( "Copy", False, [self.EditArea], gtk.STOCK_COPY, True) ) # Close button... hsccc_box.add(self.__create_button( "Close", False, [lambda w: self.edtarea.destroy() ], gtk.STOCK_CLOSE, True) ) main_vbox.show_all() return main_vbox def set_datetime(self, widget, field): """ Parse date and time from text entry """ value = _parse_datetime(unicode(widget.get_text())) if value is not None: self.dates[field] = "%04d:%02d:%02d %02d:%02d:%02d" % ( value.year, value.month, value.day, value.hour, value.minute, value.second) else: self.dates[field] = None def validate_datetime(self, widget, data, field): """ Validate current date and time in text entry """ if self.dates[field] is None: return ValidationError(_('Bad Date/Time')) def validate_coordinate(self, widget, data, field): """ Validate current latitude or longitude in text entry """ if field == "Latitude" and not conv_lat_lon(data, "0", "ISO-D"): return ValidationError(_(u"Invalid latitude (syntax: 18\u00b09'") + _('48.21"S, -18.2412 or -18:9:48.21)')) if field == "Longitude" and not conv_lat_lon("0", data, "ISO-D"): return ValidationError(_(u"Invalid longitude (syntax: 18\u00b09'") + _('48.21"E, -18.2412 or -18:9:48.21)')) def __wipe_dialog(self, object): """ Handles the Delete Dialog... """ QuestionDialog(_("Edit Image Exif Metadata"), _("WARNING! You are about to completely " "delete the Exif metadata from this image?"), _("Delete"), self.strip_metadata) def _get_value(self, keytag_): """ gets the value from the Exif Key, and returns it... @param: keytag_ -- image metadata key """ if OLD_API: keyvalue_ = self.plugin_image[keytag_] else: try: valu_ = self.plugin_image.__getitem__(keytag_) keyvalue_ = valu_.value except (KeyError, ValueError, AttributeError): keyvalue_ = False return keyvalue_ def clear_metadata(self, object): """ clears all data fields to nothing """ for widget in _TOOLTIPS.keys(): self.exif_widgets[widget].set_text("") def EditArea(self, mediadatatags_ =None): """ displays the image Exif metadata in the Edit Area... """ mediadatatags_ = _get_exif_keypairs(self.plugin_image) if mediadatatags_: mediadatatags_ = [keytag_ for keytag_ in mediadatatags_ if keytag_ in _DATAMAP] for keytag_ in mediadatatags_: widgetname_ = _DATAMAP[keytag_] tagValue = self._get_value(keytag_) if tagValue: if widgetname_ in ["Description", "Artist", "Copyright"]: self.exif_widgets[widgetname_].set_text(tagValue) # Last Changed/ Modified... elif widgetname_ in ["Modified", "Original"]: use_date = _format_datetime(tagValue) if use_date: self.exif_widgets[widgetname_].set_text(use_date) self.exif_widgets["Modified"].set_editable(False) else: self.exif_widgets["Modified"].set_editable(True) # LatitudeRef, Latitude, LongitudeRef, Longitude... elif widgetname_ == "Latitude": latitude, longitude = tagValue, self._get_value(_DATAMAP["Longitude"]) # if latitude and longitude exist, display them? if (latitude and longitude): # split latitude metadata into (degrees, minutes, and seconds) latdeg, latmin, latsec = rational_to_dms(latitude) # split longitude metadata into degrees, minutes, and seconds longdeg, longmin, longsec = rational_to_dms(longitude) # check to see if we have valid GPS coordinates? latfail = any(coords == False for coords in [latdeg, latmin, latsec]) longfail = any(coords == False for coords in [longdeg, longmin, longsec]) if (not latfail and not longfail): # Latitude Direction Reference LatRef = self._get_value(_DATAMAP["LatitudeRef"] ) # Longitude Direction Reference LongRef = self._get_value(_DATAMAP["LongitudeRef"] ) # set display for Latitude GPS coordinates latitude = """%s° %s′ %s″ %s""" % (latdeg, latmin, latsec, LatRef) self.exif_widgets["Latitude"].set_text(latitude) # set display for Longitude GPS coordinates longitude = """%s° %s′ %s″ %s""" % (longdeg, longmin, longsec, LongRef) self.exif_widgets["Longitude"].set_text(longitude) # latitude, longitude = self.__convert2dms(self.plugin_image) self.exif_widgets["Latitude"].validate() self.exif_widgets["Longitude"].validate() elif widgetname_ == "Altitude": altitude = tagValue AltitudeRef = self._get_value(_DATAMAP["AltitudeRef"]) if (altitude and AltitudeRef): altitude = convert_value(altitude) if altitude: if AltitudeRef == "1": altitude = "-" + altitude self.exif_widgets[widgetname_].set_text(altitude) else: # set Edit Message Area to None... self.exif_widgets["Edit:Message"].set_text(_("There is NO Exif metadata for this image.")) for widget in _TOOLTIPS.keys(): # once the user types in that field, # the Edit, Clear, and Delete buttons will become active... self.exif_widgets[widget].connect("changed", self.active_buttons) def _set_value(self, keytag_, keyvalue_): """ sets the value for the metadata keytag_s """ tagclass_ = keytag_[0:4] if OLD_API: self.plugin_image[keytag_] = keyvalue_ else: try: self.plugin_image.__setitem__(keytag_, keyvalue_) except KeyError: if tagclass_ == "Exif": self.plugin_image[keytag_] = pyexiv2.ExifTag(keytag_, keyvalue_) elif tagclass_ == "Xmp.": self.plugin_image[keytag_] = pyexiv2.XmpTag(keytag_, keyvalue_) elif tagclass_ == "Iptc": self.plugin_image[keytag_] = IptcTag(keytag_, keyvalue_) except (ValueError, AttributeError): pass def write_metadata(self, plugininstance): """ writes the Exif metadata to the image. OLD_API -- prior to pyexiv2-0.2.0 -- pyexiv2-0.2.0 and above... """ if OLD_API: plugininstance.writeMetadata() else: plugininstance.write() def close_window(self, widgetWindow): """ closes the window title by widgetWindow. """ lambda w: widgetWindow.destroy() def convert_format(self, latitude, longitude, format): """ Convert GPS coordinates into a specified format. """ if (not latitude and not longitude): return [False]*2 latitude, longitude = conv_lat_lon( unicode(latitude), unicode(longitude), format) return latitude, longitude def __decimalbutton(self): latitude = self.exif_widgets["Latitude"].get_text() longitude = self.exif_widgets["Longitude"].get_text() latitude, longitude = self.__convert2decimal(latitude, longitude, True) def __dmsbutton(self): latitude = self.exif_widgets["Latitude"].get_text() longitude = self.exif_widgets["Longitude"].get_text() latitude, longitude = self.__convert2dms(latitude, longitude, True) def __convert2decimal(self, latitude =False, longitude =False, display =False): """ will convert a decimal GPS coordinates into decimal format. """ if (not latitude and not longitude): return [False]*2 latitude, longitude = self.convert_format(latitude, longitude, "D.D8") # display the results only if the convert gps buttons are pressed... if display: self.exif_widgets["Latitude"].set_text(latitude) self.exif_widgets["Longitude"].set_text(longitude) return latitude, longitude def __convert2dms(self, latitude =False, longitude =False, display =True): """ will convert a decimal GPS coordinates into degrees, minutes, seconds for display only """ if (not latitude and not longitude): return [False]*2 latitude, longitude = self.convert_format(latitude, longitude, "DEG-:") # display the results only if the convert gps buttons are pressed... if display: self.exif_widgets["Latitude"].set_text(latitude) self.exif_widgets["Longitude"].set_text(longitude) return latitude, longitude def save_metadata(self, datatags_ =None): """ gets the information from the plugin data fields and sets the keytag_ = keyvalue image metadata """ db = self.dbstate.db # get a copy of all the widgets... datatags_ = ( (widget, self.exif_widgets[widget].get_text() ) for widget in _TOOLTIPS.keys() ) for widgetname_, widgetvalue_ in datatags_: # Exif Label, Description, Artist, Copyright... if widgetname_ in ["Description", "Artist", "Copyright"]: self._set_value(_DATAMAP[widgetname_], widgetvalue_) # Modify Date/ Time... elif widgetname_ == "Modified": date1 = self.dates["Modified"] widgetvalue_ = date1 if date1 is not None else datetime.datetime.now() self._set_value(_DATAMAP[widgetname_], widgetvalue_) # display modified date in its cell... displayed = _format_datetime(widgetvalue_) if displayed: self.exif_widgets[widgetname_].set_text(displayed) # Original Date/ Time... elif widgetname_ == "Original": widgetvalue_ = self.dates["Original"] if widgetvalue_ is not None: self._set_value(_DATAMAP[widgetname_], widgetvalue_) # modify the media object date if it is not already set? mediaobj_date = self.orig_image.get_date_object() if mediaobj_date.is_empty(): objdate = gen.lib.date.Date() try: objdate.set_yr_mon_day(widgetvalue_.get_year(), widgetvalue_.get_month(), widgetvalue_.get_day() ) gooddate = True except ValueError: gooddate = False if gooddate: # begin database tranaction to save media object's date... with DbTxn(_("Create Date Object"), db) as trans: self.orig_image.set_date_object(objdate) db.disable_signals() db.commit_media_object(self.orig_image, trans) db.enable_signals() db.request_rebuild() # Latitude/ Longitude... elif widgetname_ == "Latitude": latitude = self.exif_widgets["Latitude"].get_text() longitude = self.exif_widgets["Longitude"].get_text() if (latitude and longitude): if (latitude.count(" ") == longitude.count(" ") == 0): latitude, longitude = self.__convert2dms(latitude, longitude) # remove symbols before saving... latitude, longitude = _removesymbolsb4saving(latitude, longitude) # split Latitude/ Longitude into its (d, m, s, dir)... latitude = coordinate_splitup(latitude) longitude = coordinate_splitup(longitude) if "N" in latitude: latref = "N" elif "S" in latitude: latref = "S" latitude.remove(latref) if "E" in longitude: longref = "E" elif "W" in longitude: longref = "W" longitude.remove(longref) # convert Latitude/ Longitude into pyexiv2.Rational()... latitude = coords_to_rational(latitude) longitude = coords_to_rational(longitude) # save LatitudeRef/ Latitude... self._set_value(_DATAMAP["LatitudeRef"], latref) self._set_value(_DATAMAP["Latitude"], latitude) # save LongitudeRef/ Longitude... self._set_value(_DATAMAP["LongitudeRef"], longref) self._set_value(_DATAMAP["Longitude"], longitude) # Altitude, and Altitude Reference... elif widgetname_ == "Altitude": if widgetvalue_: if "-" in widgetvalue_: widgetvalue_= widgetvalue_.replace("-", "") altituderef = "1" else: altituderef = "0" # convert altitude to pyexiv2.Rational for saving... widgetvalue_ = altitude_to_rational(widgetvalue_) self._set_value(_DATAMAP["AltitudeRef"], altituderef) self._set_value(_DATAMAP[widgetname_], widgetvalue_) # writes all Exif Metadata to image even if the fields are all empty so as to remove the value... self.write_metadata(self.plugin_image) if datatags_: # set Message Area to Saved... self.exif_widgets["Edit:Message"].set_text(_("Saving Exif metadata to this image...")) else: # set Edit Message to Cleared... self.exif_widgets["Edit:Message"].set_text(_("All Exif metadata has been cleared...")) def strip_metadata(self, mediadatatags =None): """ Will completely and irrevocably erase all Exif metadata from this image. """ # make sure the image has Exif metadata... mediadatatags_ = _get_exif_keypairs(self.plugin_image) if not mediadatatags_: return if EXIV2_FOUND_: try: erase = subprocess.check_call( [EXIV2_FOUND_, "delete", self.image_path] ) erase_results = str(erase) except subprocess.CalledProcessError: erase_results = False else: if mediadatatags_: for keytag_ in mediadatatags_: del self.plugin_image[keytag_] erase_results = True # write wiped metadata to image... self.write_metadata(self.plugin_image) if erase_results: for widget in ["MediaLabel", "MimeType", "ImageSize", "MessageArea", "Total"]: self.exif_widgets[widget].set_text("") # Clear the Viewing Area... self.model.clear() self.exif_widgets["MessageArea"].set_text(_("All Exif metadata " "has been deleted from this image...")) self.update() else: self.exif_widgets["MessageArea"].set_text(_("There was an error " "in stripping the Exif metadata from this image...")) def _removesymbolsb4saving(latitude, longitude): """ will recieve a DMS with symbols and return it without them @param: latitude -- Latitude GPS coordinates @param: longitude -- GPS Longitude coordinates """ # check to see if latitude/ longitude exist? if (latitude and longitude): # remove degrees, minutes, seconds symbols if they exist in # Latitude/ Longitude... for symbol in ["°", "#", "먊", "′", "'", '″', '"']: if symbol in latitude: latitude = latitude.replace(symbol, "") if symbol in longitude: longitude = longitude.replace(symbol, "") return latitude, longitude def string_to_rational(coordinate): """ convert string to rational variable for GPS """ if coordinate: if '.' in coordinate: value1, value2 = coordinate.split('.') return pyexiv2.Rational(int(float(value1 + value2)), 10**len(value2)) else: return pyexiv2.Rational(int(coordinate), 1) def coordinate_splitup(coordinates): """ will split up the coordinates into a list """ # Latitude, Longitude... if (":" in coordinates and coordinates.find(" ") == -1): return [(coordinate) for coordinate in coordinates.split(":")] elif (" " in coordinates and coordinates.find(":") == -1): return [(coordinate) for coordinate in coordinates.split(" ")] return None def coords_to_rational(coordinates): """ returns the rational equivalent for (degrees, minutes, seconds)... """ return [string_to_rational(coordinate) for coordinate in coordinates] def altitude_to_rational(altitude): return [string_to_rational(altitude)] def convert_value(value): """ will take a value from the coordinates and return its value """ if isinstance(value, (Fraction, pyexiv2.Rational)): return str((Decimal(value.numerator) / Decimal(value.denominator))) def rational_to_dms(coordinates): """ takes a rational set of coordinates and returns (degrees, minutes, seconds) [Fraction(40, 1), Fraction(0, 1), Fraction(1079, 20)] """ # coordinates look like: # [Rational(38, 1), Rational(38, 1), Rational(150, 50)] # or [Fraction(38, 1), Fraction(38, 1), Fraction(318, 100)] return [convert_value(coordinate) for coordinate in coordinates] def _get_exif_keypairs(plugin_image): """ Will be used to retrieve and update the Exif metadata from the image. """ if not plugin_image: return False mediadatatags_ = [keytag_ for keytag_ in (plugin_image.exifKeys() if OLD_API else chain( plugin_image.exif_keys, plugin_image.xmp_keys, plugin_image.iptc_keys) ) ] return mediadatatags_