Files
gramps/src/plugins/gramplet/EditExifMetadata.py

1794 lines
66 KiB
Python
Raw Blame History

This file contains ambiguous Unicode characters
This file contains Unicode characters that might be confused with other characters. If you think that this is intentional, you can safely ignore this warning. Use the Escape button to reveal them.
# -*- coding: utf-8 -*-
#!/usr/bin/python
#
# Gramps - a GTK+/GNOME based genealogy program
#
# Copyright (C) 2009-2011 Rob G. Healey <robhealey1@gmail.com>
#
# 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, sys
from datetime import datetime, date
import calendar, time
# abilty to escape certain characters from output...
from xml.sax.saxutils import escape as _html_escape
from itertools import chain
from decimal import *
getcontext().prec = 4
from fractions import Fraction
import subprocess
# -----------------------------------------------------------------------------
# GTK modules
# -----------------------------------------------------------------------------
import gtk
# -----------------------------------------------------------------------------
# GRAMPS modules
# -----------------------------------------------------------------------------
import GrampsDisplay
from QuestionDialog import WarningDialog, QuestionDialog, OptionDialog
from gen.ggettext import gettext as _
from gen.plug import Gramplet
from DateHandler import displayer as _dd
import gen.lib
import Utils
from PlaceUtils import conv_lat_lon
from ListModel import ListModel, NOSORT
#####################################################################
# Check for pyexiv2 library...
#####################################################################
# pyexiv2 download page (C) Olivier Tilloy
_DOWNLOAD_LINK = "http://tilloy.net/dev/pyexiv2/download.html"
# make sure the pyexiv2 library is installed and at least a minimum version
software_version = False
Min_VERSION = (0, 1, 3)
Min_VERSION_str = "pyexiv2-%d.%d.%d" % Min_VERSION
Pref_VERSION_str = "pyexiv2-%d.%d.%d" % (0, 3, 0)
# to be able for people that have pyexiv2-0.1.3 to be able to use this addon also...
LesserVersion = False
try:
import pyexiv2
software_version = pyexiv2.version_info
except ImportError, msg:
WarningDialog(_("You need to install, %s or greater, for this addon to work...\n"
"I would recommend installing, %s, and it may be downloaded from here: \n%s") % (
Min_VERSION_str, Pref_VERSION_str, _DOWNLOAD_LINK), str(msg))
raise Exception(_("Failed to load 'Edit Image Exif Metadata'..."))
# This only happends if the user has pyexiv2-0.1.3 installed on their computer...
except AttributeError:
LesserVersion = True
# the library is either not installed or does not meet minimum required version for this addon....
if (software_version and (software_version < Min_VERSION)):
msg = _("The minimum required version for pyexiv2 must be %s \n"
"or greater. Or you do not have the python library installed yet. "
"You may download it from here: %s\n\n I recommend getting, %s") % (
Min_VERSION_str, _DOWNLOAD_LINK, Pref_VERSION_str)
WarningDialog(msg)
raise Exception(msg)
# *******************************************************************
# Determine if we have access to outside Programs
#
# The programs are ImageMagick, and jhead
# * ImageMagick -- Convert and Delete all Exif metadata...
# * jhead -- re-initialize a jpeg, and other features...
# * del/ rm -- delete old files from the convert command...
#********************************************************************
# Windows 32bit systems
system_platform = os.sys.platform
if system_platform == "win32":
_MAGICK_FOUND = "convert.exe" if Utils.search_for("convert.exe") else False
_JHEAD_FOUND = "jhead.exe" if Utils.search_for("jhead.exe") else False
_DEL_FOUND = "del.exe" if Utils.search_for("del.exe") else False
elif system_platform == "linux2":
_MAGICK_FOUND = "convert" if Utils.search_for("convert") else False
_JHEAD_FOUND = "jhead" if Utils.search_for("jhead") else False
_DEL_FOUND = "rm" if Utils.search_for("rm") else False
else:
_MAGICK_FOUND = "convert" if Utils.search_for("convert") else False
_JHEAD_FOUND = "jhead" if Utils.search_for("jhead") else False
_DEL_FOUND = "del" if Utils.search_for("del") else False
# if external programs are not found, let the user know about the missing functionality?
if not _MAGICK_FOUND:
print(_("ImageMagick's convert program was not found on this computer.\n"
"You may download it from here: %s...") % (
"http://www.imagemagick.org/script/index.php"))
if not _JHEAD_FOUND:
print(_("Jhead program was not found on this computer.\n"
"You may download it from: %s...") % (
"http://www.sentex.net/~mwandel/jhead/"))
# -----------------------------------------------------------------------------
# Constants
# -----------------------------------------------------------------------------
# available image types for exiv2 and pyexiv2
_vtypes = [".jpeg", ".jpg", ".jfif", ".exv", ".tiff", ".dng", ".nef", ".pef", ".pgf",
".png", ".psd", ".jp2"]
# set up Exif keys for Image.exif_keys
_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"}
_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" : _("Provide a short descripion for this image."),
"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" : _("Enter the copyright information for this image. \n"),
"Year" : _("Enter the year for the date of this image.\n"
"Example: 1826 - 2100, You can either spin the up and down arrows by clicking on them or enter it manually."),
"Month" : _("Enter the month for the date of this image.\n"
"Example: 0 - 12, You can either spin the up and down arrows by clicking on them or enter it manually."),
"Day" : _("Enter the day for the date of this image.\n"
"Example: 1 - 31, You can either spin the up and down arrows by clicking on them or enter it manually."),
"Hour" : _("Enter the hour for the time of this image.\n"
"Example: 0 - 23, You can either spin the up and down arrows by clicking on them or enter it manually.\n\n"
"The hour is represented in 24-hour format."),
"Minutes" : _("Enter the minutes for the time of this image.\n"
"Example: 0 - 59, You can either spin the up and down arrows by clicking on them or enter it manually."),
"Seconds" : _("Enter the seconds for the time of this image.\n"
"Example: 0 - 59, You can either spin the up and down arrows by clicking on them or enter it manually."),
"DateDisplay" : _("This date is the Original Date/ Time of the image.\n"
"The date and time it was taken/ or created."),
"Latitude" : _("Enter the GPS Latitude Coordinates for this image,\n"
"Example: 43.722965, 43 43 22 N, 38° 38 03″ N, 38 38 3"),
"Longitude" : _("Enter the GPS Longitude Coordinates for your image,\n"
"Example: 10.396378, 10 23 46 E, 105° 6 6″ W, -105 6 6") }.items()
# define tooltips for all buttons...
_BUTTONTIPS = {
# Clear Edit Area button...
"Clear" : _("Clears the Exif metadata from the Edit area."),
# Calendar date select button...
"Popup:Select" : _("Allows you to select a date from a Popup window Calendar. \n"
"Warning: You will still need to edit the time..."),
# Thumbnail Viewing Window button...
"ThumbnailView" : _("Will produce a Popup window showing a Thumbnail Viewing Area"),
# Wiki Help button...
"Help" : _("Displays the Gramps Wiki Help page for 'Edit Image Exif Metadata' "
"in your web browser."),
# Advanced Display Window button...
"Advanced" : _("Will pop open a window with all of the Exif metadata Key/alue pairs."),
# Save Exif Metadata button...
"Save" : _("Saves/ writes the Exif metadata to this image.\n"
"WARNING: Exif metadata will be erased if you save a blank entry field...") }
# if ImageMagick is installed on this computer then, add button tooltips for Convert button...
if _MAGICK_FOUND:
_BUTTONTIPS.update( {
# Convert to .Jpeg button...
"Convert" : _("If your image is not a .jpg image, convert it to a .jpg image?") } )
# if ImageMagick's "convert" or jhead is installed, add this button tooltip...
if _MAGICK_FOUND or _JHEAD_FOUND:
_BUTTONTIPS.update( {
# 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?") } )
def _help_page(obj):
"""
will bring up a Wiki help page.
"""
GrampsDisplay.help(webpage = "Edit Image Exif Metadata")
# ------------------------------------------------------------------------
# Gramplet class
# ------------------------------------------------------------------------
class EditExifMetadata(Gramplet):
def init(self):
self.exif_column_width = 12
self.exif_widgets = {}
# set all dirty variables to False to begin this addon...
self._dirty = False
self.orig_image = False
self.image_path = False
self.plugin_image = False
vbox = self.build_gui()
self.connect_signal("Media", self.update)
self.gui.get_container_widget().remove(self.gui.textview)
self.gui.get_container_widget().add_with_viewport(vbox)
vbox.show_all()
# setup widget tooltips for all fields and buttons...
# so the user has them available even before clicking on anything...
_setup_widget_tips(self.exif_widgets)
def build_gui(self):
"""
creates the editing area fields.
"""
main_vbox = gtk.VBox(False, 0)
# Displays the file name...
medialabel = gtk.HBox(False)
label = gtk.Label()
label.set_alignment(0.0, 0.0)
label.set_line_wrap(True)
self.exif_widgets["MediaLabel"] = label
medialabel.pack_start(self.exif_widgets["MediaLabel"], expand =False)
main_vbox.pack_start(medialabel, expand =False)
# Displays mime type information...
mimetype = gtk.HBox(False)
label = gtk.Label()
label.set_alignment(0.0, 0.0)
label.set_line_wrap(True)
self.exif_widgets["MimeType"] = label
mimetype.pack_start(self.exif_widgets["MimeType"], expand =False)
main_vbox.pack_start(mimetype, expand =False)
# Displays all plugin messages...
messagearea = gtk.HBox(False)
label = gtk.Label()
label.set_alignment(0.5, 0.0)
label.set_line_wrap(True)
self.exif_widgets["MessageArea"] = label
messagearea.pack_start(self.exif_widgets["MessageArea"], expand =False)
main_vbox.pack_start(messagearea, expand =False)
# Clear, Thumbnail View, Convert horizontal box
ctc_box = gtk.HButtonBox()
ctc_box.set_layout(gtk.BUTTONBOX_START)
# Clear button...
ctc_box.add( self.__create_button(
"Clear", False, [self.clear_metadata], gtk.STOCK_CLEAR) )
# ThumbnailView View button...
ctc_box.add(self.__create_button(
"ThumbnailView", _("Thumbnail(s)"), [self.thumbnail_view] ) )
# is ImageMagick installed?
if _MAGICK_FOUND:
# Convert button...
ctc_box.add(self.__create_button(
"Convert", False, [self.__convert_dialog], gtk.STOCK_CONVERT) )
main_vbox.pack_start(ctc_box, expand =False, fill =False, padding =10)
# create the data fields and button:
# ***Description, Artist, Copyright, and Calendar date...
for items in [
# Image Description
("Description", _("Description"), None, False, [], True, 0),
# Artist field
("Artist", _("Artist"), None, False, [], True, 0),
# copyright field
("Copyright", _("Copyright"), None, False, [], True, 0),
# calendar date clickable entry...
("Popup", "", None, True,
[("Select", _("Select Date"), "button", self.select_date)],
True, 0) ]:
pos, text, choices, readonly, callback, dirty, default = items
row = self.make_row(pos, text, choices, readonly, callback, dirty, default)
main_vbox.pack_start(row, False)
# get current date and time from system...
now = time.localtime()
# iso format: Year, Month, Day spinners...
date_frame = gtk.Frame(_("Creation Date"))
main_vbox.pack_start(date_frame, expand =True, fill =True, padding =0)
new_vbox = gtk.VBox(False, 0)
new_vbox.set_border_width(5)
date_frame.add(new_vbox)
new_hbox = gtk.HBox(False, 0)
new_vbox.pack_start(new_hbox, expand =True, fill =True, padding =5)
vbox2 = gtk.VBox(False, 0)
new_hbox.pack_start(vbox2, expand =True, fill =True, padding =5)
label = gtk.Label(_("Year :"))
label.set_alignment(0, 0.5)
vbox2.pack_start(label, expand =False, fill =True, padding =0)
adj = gtk.Adjustment(value=now[0], lower=1826, upper=2100, step_incr=1, page_incr=100)
vbox2.pack_start(self.__create_spinner(
"Year", adj, False, False), expand =False, fill =True, padding =0)
vbox2 = gtk.VBox(False, 0)
new_hbox.pack_start(vbox2, expand =True, fill =True, padding =5)
label = gtk.Label(_("Month :"))
label.set_alignment(0, 0.5)
vbox2.pack_start(label, expand =False, fill =True, padding =0)
adj = gtk.Adjustment(value=now[1], lower=1, upper=12, step_incr=1, page_incr=5)
vbox2.pack_start(self.__create_spinner(
"Month", adj), expand =False, fill =True, padding =0)
vbox2 = gtk.VBox(False, 0)
new_hbox.pack_start(vbox2, expand =True, fill =True, padding =5)
label = gtk.Label(_("Day :"))
label.set_alignment(0, 0.5)
vbox2.pack_start(label, expand =False, fill =True, padding =0)
adj = gtk.Adjustment(value=now[2], lower=1, upper=31, step_incr=1, page_incr=5)
vbox2.pack_start(self.__create_spinner(
"Day", adj), expand =False, fill =True, padding =0)
# Hour, Minutes, Seconds spinners...
time_frame = gtk.Frame(_("Creation Time"))
main_vbox.pack_start(time_frame, expand =True, fill =True, padding =0)
new_vbox = gtk.VBox(False, 0)
new_vbox.set_border_width(5)
time_frame.add(new_vbox)
new_hbox = gtk.HBox(False, 0)
new_vbox.pack_start(new_hbox, expand =True, fill =True, padding =5)
vbox2 = gtk.VBox(False, 0)
new_hbox.pack_start(vbox2, expand =True, fill =True, padding =5)
label = gtk.Label(_("Hour :"))
label.set_alignment(0, 0.5)
vbox2.pack_start(label, expand =False, fill =True, padding =0)
adj = gtk.Adjustment(value=now[3], lower=0, upper=23, step_incr=1, page_incr=5)
vbox2.pack_start(self.__create_spinner(
"Hour", adj), expand =False, fill =True, padding =0)
vbox2 = gtk.VBox(False, 0)
new_hbox.pack_start(vbox2, expand =True, fill =True, padding =5)
label = gtk.Label(_("Minutes :"))
label.set_alignment(0, 0.5)
vbox2.pack_start(label, expand =False, fill =True, padding =0)
adj = gtk.Adjustment(value=now[4], lower=0, upper=59, step_incr=1, page_incr=5)
vbox2.pack_start(self.__create_spinner(
"Minutes", adj), expand =False, fill =True, padding =0)
vbox2 = gtk.VBox(False, 0)
new_hbox.pack_start(vbox2, expand =True, fill =True, padding =5)
label = gtk.Label(_("Seconds :"))
label.set_alignment(0, 0.5)
vbox2.pack_start(label, expand =False, fill =True, padding =0)
adj = gtk.Adjustment(value=now[5], lower=0, upper=59, step_incr=1, page_incr=5)
vbox2.pack_start(self.__create_spinner(
"Seconds", adj), expand =False, fill =True, padding =0)
# GPS Latitude/ Longitude Coordinates...
for items in [
# Original Date Displayed...
("DateDisplay", _("Datestamp"), None, True, [], True, 0),
# GPS Latitude Reference and Latitude...
("Latitude", _("Latitude"), None, False, [], True, 0),
# GPS Longitude Reference and Longitude...
("Longitude", _("Longitude"), None, False, [], True, 0) ]:
pos, text, choices, readonly, callback, dirty, default = items
row = self.make_row(pos, text, choices, readonly, callback, dirty, default)
main_vbox.pack_start(row, False)
# Help, Save, Delete horizontal box
hasd_box = gtk.HButtonBox()
hasd_box.set_layout(gtk.BUTTONBOX_START)
main_vbox.pack_start(hasd_box, expand =False, fill =False, padding =10)
# Help button...
hasd_box.add( self.__create_button(
"Help", False, [_help_page], gtk.STOCK_HELP, True) )
# Save button...
hasd_box.add( self.__create_button(
"Save", False, [self.__save_dialog], gtk.STOCK_SAVE) )
# Advanced View Area button...
hasd_box.add( self.__create_button(
"Advanced", _("Advanced"), [self.advanced_view] ) )
if _MAGICK_FOUND:
# Delete All Metadata button...
hasd_box.add(self.__create_button(
"Delete", False, [self.__wipe_dialog], gtk.STOCK_DELETE ) )
return main_vbox
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)
if not sensitive:
button.set_sensitive(False)
self.exif_widgets[pos] = button
return button
def __create_spinner(self, pos, adjustment, climb =True, wrap =True):
"""
Creates and returns the Date/ Time spinners...
"""
if climb:
spin_button = gtk.SpinButton(adjustment, climb_rate =0.0, digits =0)
else:
spin_button = gtk.SpinButton(adjustment, climb_rate =1.0, digits =0)
spin_button.set_wrap(wrap)
spin_button.set_numeric(True)
spin_button.update()
self.exif_widgets[pos] = spin_button
return spin_button
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 LesserVersion: # 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 activate_buttons(self, ButtonList):
"""
Enable/ activate the buttons that are in ButtonList
"""
for ButtonName in ButtonList:
self.exif_widgets[ButtonName].set_sensitive(True)
def deactivate_buttons(self, ButtonList):
"""
disable/ de-activate buttons in ButtonList
"""
for ButtonName in ButtonList:
self.exif_widgets[ButtonName].set_sensitive(False)
def active_buttons(self, obj):
"""
will handle the toggle action of the Save button.
If there is no Exif metadata, then the data fields are connected to the
'changed' signal to be able to activate the Save 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["Save"].get_sensitive():
self.activate_buttons(["Save"])
# set Message Area to Entering Data...
self.exif_widgets["MessageArea"].set_text(_("Entering data..."))
if not self.exif_widgets["Clear"].get_sensitive():
self.activate_buttons(["Clear"])
if _MAGICK_FOUND:
if not self.exif_widgets["Delete"].get_sensitive():
self.activate_buttons(["Delete"])
def main(self): # return false finishes
"""
get the active media, mime type, and reads the image metadata
"""
db = self.dbstate.db
# clear Edit Area and Labels...
self.clear_metadata(self.orig_image)
active_handle = self.get_active("Media")
if not active_handle:
self.set_has_data(False)
return
self.orig_image = db.get_object_from_handle(active_handle)
self.image_path = Utils.media_path_full(db, self.orig_image.get_path() )
if (not self.orig_image or not os.path.isfile(self.image_path)):
self.exif_widgets["MessageArea"].set_text(_("Image is either missing or deleted,\n"
"Please choose a different image..."))
return
# 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...."))
# 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(mime_type)
# disable all data fields and buttons if NOT an exiv2 image type?
basename, self.extension = os.path.splitext(self.image_path)
_setup_datafields_buttons(self.extension, self.exif_widgets)
# determine if it is a mime image object?
if mime_type:
if mime_type.startswith("image"):
# Checks to make sure that ImageMagick is installed on this computer and
# the image is NOT a (".jpeg", ".jfif", or ".jpg") image...
# if not, then activate the Convert button?
if (_MAGICK_FOUND and self.extension not in [".jpeg", ".jpg", ".jfif"] ):
self.activate_buttons(["Convert"])
# creates, and reads the plugin image instance...
self.plugin_image = self.setup_image(self.image_path)
# Check for ThumbnailViews...
previews = self.plugin_image.previews
if (len(previews) > 0):
self.activate_buttons(["ThumbnailView"])
# displays the imge Exif metadata into selected data fields...
self.EditArea(self.orig_image)
else:
self.exif_widgets["MessageArea"].set_text(_("Please choose a different image..."))
return
else:
self.exif_widgets["MessageArea"].set_text(_("Please choose a different image..."))
return
def __convert_dialog(self, obj):
"""
Handles the Convert question Dialog...
"""
# if ImageMagick and delete has been found on this computer?
if (_MAGICK_FOUND and _DEL_FOUND):
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 original"), self.convertdelete,
_("Convert"), self.convert2Jpeg)
# is ImageMagick is installed?
elif _MAGICK_FOUND:
QuestionDialog(_("Edit Image Exif Metadata"), _("Convert this image to a .jpeg image?"),
_("Convert"), self.convert2Jpeg)
def __save_dialog(self, obj):
"""
Handles the Save question Dialog...
"""
QuestionDialog(_("Edit Image Exif Metadata"), _("Save Exif metadata to this image?"),
_("Save"), self.save_metadata)
def __wipe_dialog(self, obj):
"""
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 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 LesserVersion: # 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 make_row(self, pos, text, choices=None, readonly=False, callback_list=[],
mark_dirty=False, default=0):
# Edit Image Exif Metadata
row = gtk.HBox()
label = gtk.Label()
if readonly:
label.set_text("<b>%s</b>" % text)
label.set_width_chars(self.exif_column_width)
label.set_use_markup(True)
self.exif_widgets[pos] = gtk.Label()
self.exif_widgets[pos].set_alignment(0.0, 0.5)
self.exif_widgets[pos].set_use_markup(True)
label.set_alignment(0.0, 0.5)
row.pack_start(label, False)
row.pack_start(self.exif_widgets[pos], False)
else:
label.set_text("%s: " % text)
label.set_width_chars(self.exif_column_width)
label.set_alignment(1.0, 0.5)
if choices == None:
self.exif_widgets[pos] = gtk.Entry()
if mark_dirty:
self.exif_widgets[pos].connect("changed", self._mark_dirty)
row.pack_start(label, False)
row.pack_start(self.exif_widgets[pos], True)
else:
eventBox = gtk.EventBox()
self.exif_widgets[pos] = gtk.combo_box_new_text()
eventBox.add(self.exif_widgets[pos])
for add_type in choices:
self.exif_widgets[pos].append_text(add_type)
self.exif_widgets[pos].set_active(default)
if mark_dirty:
self.exif_widgets[pos].connect("changed", self._mark_dirty)
row.pack_start(label, False)
row.pack_start(eventBox, True)
for name, text, cbtype, callback in callback_list:
if cbtype == "button":
label = gtk.Label()
label.set_text(text)
self.exif_widgets[pos + ":" + name + ":Label"] = label
row.pack_start(label, False)
icon = gtk.STOCK_EDIT
size = gtk.ICON_SIZE_MENU
button = gtk.Button()
image = gtk.Image()
image.set_from_stock(icon, size)
button.add(image)
button.set_relief(gtk.RELIEF_NONE)
button.connect("clicked", callback)
self.exif_widgets[pos + ":" + name] = button
row.pack_start(button, False)
elif cbtype == "checkbox":
button = gtk.CheckButton(text)
button.set_active(True)
button.connect("clicked", callback)
self.exif_widgets[pos + ":" + name] = button
row.pack_start(button, False)
row.show_all()
return row
def _mark_dirty(self, obj):
pass
def _get_value(self, KeyTag):
"""
gets the value from the Exif Key, and returns it...
@param: KeyTag -- image metadata key
"""
KeyValue = ""
if LesserVersion:
KeyValue = self.plugin_image[KeyTag]
else:
try:
KeyValue = self.plugin_image[KeyTag].value
except (KeyError, ValueError, AttributeError):
pass
return KeyValue
def clear_metadata(self, obj, cleartype = "All"):
"""
clears all data fields to nothing
@param: cleartype --
"Date" = clears only Date entry fields
"All" = clears all data fields
"""
# clear all data fields
if cleartype == "All":
for widgetsName in ["MediaLabel", "MimeType", "MessageArea", "DateDisplay",
"Description", "Artist", "Copyright", "Latitude", "Longitude"]:
self.exif_widgets[widgetsName].set_text("")
# clear only the date/ time field
else:
self.exif_widgets["MessageArea"].set_text("")
# De-activate the buttons except for Help...
self.deactivate_buttons(["Clear", "ThumbnailView", "Save", "Advanced"])
if _MAGICK_FOUND:
self.deactivate_buttons(["Convert"])
if (_MAGICK_FOUND or _HEAD_FOUND):
self.deactivate_buttons(["Delete"])
# set Message Area to Select...
self.exif_widgets["MessageArea"].set_text(_("Select an image to begin..."))
def EditArea(self, obj):
"""
displays the image Exif metadata in the Edit Area...
"""
# Retrieves all metadata key pairs from this image...
MediaDataTags = _get_exif_keypairs(self.plugin_image)
# if no Exif metadata, disable the has_data() functionality?
if MediaDataTags:
# update has_data() functionality...
self.set_has_data(True)
# activate Clear, Save, and Advanced button...
self.activate_buttons(["Clear", "Save", "Advanced"])
# Activate Delete button if ImageMagick or jhead is found?...
if (_MAGICK_FOUND or _JHEAD_FOUND):
self.activate_buttons(["Delete"])
imageKeyTags = [KeyTag for KeyTag in MediaDataTags if KeyTag in _DATAMAP]
# set Message Area to Copying...
self.exif_widgets["MessageArea"].set_text(_("Copying Exif metadata to the Edit Area..."))
for KeyTag in imageKeyTags:
# name for matching to exif_widgets
widgetsName = _DATAMAP[KeyTag]
tagValue = self._get_value(KeyTag)
if tagValue:
if widgetsName in ["Description", "Artist", "Copyright"]:
self.exif_widgets[widgetsName].set_text(tagValue)
# Last Changed/ Modified...
elif widgetsName == "Modified":
use_date = self._get_value(_DATAMAP["Modified"])
use_date = _process_datetime(use_date) if use_date else False
if use_date:
self.exif_widgets["MessageArea"].set_text(
_("Last Changed: %s") % use_date)
# Original Creation Date/ Time...
elif widgetsName == "Original":
use_date = ( self._get_value(_DATAMAP["Original"]) or
self._get_value(_DATAMAP["Digitized"]) )
if use_date:
if isinstance(use_date, str):
use_date = _get_date_format(use_date)
if use_date:
year, month, day, hour, minutes, seconds = use_date[0:6]
elif isinstance(use_date, datetime):
year, month, day = use_date.year, use_date.month, use_date.day
hour, minutes, seconds = use_date.hour, use_date.minute, use_date.second
else:
year = False
if year:
# update Date/ Time spin buttons...
self.update_spinners(year, month, day, hour, minutes, seconds)
use_date = False
if year < 1900:
use_date = "%04d-%s-%02d %02d:%02d:%02d" % (
year, _dd.long_months[month], day, hour, minutes, seconds)
else:
use_date = datetime(year, month, day, hour, minutes, seconds)
if use_date:
if isinstance(use_date, datetime):
use_date = _format_datetime(use_date)
self.exif_widgets["DateDisplay"].set_text(use_date)
# LatitudeRef, Latitude, LongitudeRef, Longitude...
elif widgetsName == "Latitude":
latitude = self._get_value(KeyTag)
longitude = 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
self.exif_widgets["Latitude"].set_text(
"""%s° %s %s%s""" % (latdeg, latmin, latsec, LatRef) )
# set display for Longitude GPS Coordinates
self.exif_widgets["Longitude"].set_text(
"""%s° %s %s%s""" % (longdeg, longmin, longsec, LongRef) )
else:
# set Message Area to None...
self.exif_widgets["MessageArea"].set_text(_("There is NO Exif metadata for this image yet..."))
for widget, tooltip in _TOOLTIPS:
if widget is not "Modified":
self.exif_widgets[widget].connect("changed", self.active_buttons)
def convertdelete(self):
"""
will convert2Jpeg and delete original non-jpeg image.
"""
self.convert2Jpeg()
if system_platform == "linux2":
delete = subprocess.check_call( [_DEL_FOUND, "-rf", self.image_path] )
else:
delete = subprocess.check_call( [_DEL_FOUND, "-y", self.image_path] )
delete_result = str(delete)
if delete_result:
self.exif_widgets["MessageArea"].set_text(_("Image has been converted to a .jpg image,\n"
"and original image has been deleted!"))
def convert2Jpeg(self):
"""
Will attempt to convert an image to jpeg if it is not?
"""
filepath, basename = os.path.split(self.image_path)
basename, oldext = os.path.splitext(self.image_path)
newextension = ".jpeg"
convert = subprocess.check_call(["convert", self.image_path,
os.path.join(filepath, basename + newextension) ] )
if str(convert):
# set Message Area to Convert...
self.exif_widgets["MessageArea"].set_text(_("Converting image,\n"
"You will need to delete the original image file..."))
self.deactivate_buttons(["Convert"])
def _set_value(self, KeyTag, KeyValue):
"""
sets the value for the metadata KeyTags
"""
if LesserVersion:
self.plugin_image[KeyTag] = KeyValue
else:
if "Exif" in KeyTag:
try: # tag is being modified...
self.plugin_image[KeyTag].value = KeyValue
except KeyError: # tag is being created...
self.plugin_image[KeyTag] = pyexiv2.ExifTag(KeyTag, KeyValue)
except (ValueError, AttributeError): # there is an error
# with either KeyTag or KeyValue
pass
elif "Xmp" in KeyTag:
try:
self.plugin_image[KeyTag].value = KeyValue
except KeyError:
self.plugin_image[KeyTag] = pyexiv2.XmpTag(KeyTag, KeyValue)
except (ValueError, AttributeError):
pass
else:
try:
self.plugin_image[KeyTag].value = KeyValue
except KeyError:
self.plugin_image[KeyTag] = pyexiv2.IptcTag(KeyTag, KeyValue)
except (ValueError, AttributeError):
pass
def write_metadata(self, plugininstance):
"""
writes the Exif metadata to the image.
LesserVersion -- prior to pyexiv2-0.2.0
-- pyexiv2-0.2.0 and above...
"""
if LesserVersion:
plugininstance.writeMetadata()
else:
plugininstance.write()
# -------------------------------------------------------------------
# GPS Coordinates functions
# -------------------------------------------------------------------
def addsymbols2gps(self, latitude =False, longitude =False):
"""
converts a degrees, minutes, seconds representation of Latitude/ Longitude
without their symbols to having them...
@param: latitude -- Latitude GPS Coordinates
@param: longitude -- Longitude GPS Coordinates
"""
LatitudeRef, LongitudeRef = "N", "E"
# check to see if Latitude/ Longitude exits?
if (latitude and longitude):
if (latitude.count(".") == 1 and longitude.count(".") == 1):
self.convert2dms(self.plugin_image)
# get Latitude/ Longitude from data fields
# after the conversion
latitude = self.exif_widgets["Latitude"].get_text()
longitude = self.exif_widgets["Longitude"].get_text()
# add DMS symbols if necessary?
# the conversion to decimal format, require the DMS symbols
elif ( (latitude.count("°") == 0 and longitude.count("°") == 0) and
(latitude.count("") == 0 and longitude.count("") == 0) and
(latitude.count('') == 0 and longitude.count('') == 0) ):
# is there a direction element here?
if (latitude.count("N") == 1 or latitude.count("S") == 1):
latdeg, latmin, latsec, LatitudeRef = latitude.split(" ", 3)
else:
atitudeRef = "N"
latdeg, latmin, latsec = latitude.split(" ", 2)
if latdeg[0] == "-":
latdeg = latdeg.replace("-", "")
LatitudeRef = "S"
# is there a direction element here?
if (longitude.count("E") == 1 or longitude.count("W") == 1):
longdeg, longmin, longsec, LongitudeRef = longitude.split(" ", 3)
else:
ongitudeRef = "E"
longdeg, longmin, longsec = longitude.split(" ", 2)
if longdeg[0] == "-":
longdeg = longdeg.replace("-", "")
LongitudeRef = "W"
latitude = """%s° %s %s%s""" % (latdeg, latmin, latsec, LatitudeRef)
longitude = """%s° %s %s%s""" % (longdeg, longmin, longsec, LongitudeRef)
return latitude, longitude
def convert2decimal(self, obj):
"""
will convert a decimal GPS Coordinates into decimal format.
@param: latitude -- GPS Latitude Coordinates from data field...
@param: longitude -- GPS Longitude Coordinates from data field...
"""
# get Latitude/ Longitude from the data fields
latitude = self.exif_widgets["Latitude"].get_text()
longitude = self.exif_widgets["Longitude"].get_text()
# if latitude and longitude exist?
if (latitude and longitude):
# is Latitude/ Longitude are in DMS format?
if (latitude.count(" ") >= 2 and longitude.count(" ") >= 2):
# add DMS symbols if necessary?
# the conversion to decimal format, require the DMS symbols
if ( (latitude.count("°") == 0 and longitude.count("°") == 0) and
(latitude.count("") == 0 and longitude.count("") == 0) and
(latitude.count('') == 0 and longitude.count('') == 0) ):
latitude, longitude = self.addsymbols2gps(latitude, longitude)
# convert degrees, minutes, seconds w/ symbols to an 8 point decimal
latitude, longitude = conv_lat_lon( unicode(latitude),
unicode(longitude), "D.D8")
self.exif_widgets["Latitude"].set_text(latitude)
self.exif_widgets["Longitude"].set_text(longitude)
def convert2dms(self, obj):
"""
will convert a decimal GPS Coordinates into degrees, minutes, seconds
for display only
"""
# get Latitude/ Longitude from the data fields
latitude = self.exif_widgets["Latitude"].get_text()
longitude = self.exif_widgets["Longitude"].get_text()
# if Latitude/ Longitude exists?
if (latitude and longitude):
# if coordinates are in decimal format?
if (latitude.count(".") == 1 and longitude.count(".") == 1):
# convert latitude and longitude to a DMS with separator of ":"
latitude, longitude = conv_lat_lon(latitude, longitude, "DEG-:")
# remove negative symbol if there is one?
LatitudeRef = "N"
if latitude[0] == "-":
latitude = latitude.replace("-", "")
LatitudeRef = "S"
latdeg, latmin, latsec = latitude.split(":", 2)
# remove negative symbol if there is one?
LongitudeRef = "E"
if longitude[0] == "-":
longitude = longitude.replace("-", "")
LongitudeRef = "W"
longdeg, longmin, longsec = longitude.split(":", 2)
self.exif_widgets["Latitude"].set_text(
"""%s° %s %s%s""" % (latdeg, latmin, latsec, LatitudeRef) )
self.exif_widgets["Longitude"].set_text(
"""%s° %s %s%s""" % (longdeg, longmin, longsec, LongitudeRef) )
def advanced_view(self, obj):
"""
Pups up a window with all of the Exif metadata available...
"""
tip = _("Click the close button when you are finished.")
advarea = gtk.Window(gtk.WINDOW_TOPLEVEL)
advarea.tooltip = tip
advarea.set_title(_("Complete Exif, Xmp, and Iptc metadata"))
advarea.set_default_size(300, 450)
advarea.set_border_width(10)
advarea.connect('destroy', lambda advarea: advarea.destroy() )
vbox = self.build_advanced()
vbox.show()
advarea.add(vbox)
advarea.show()
# Update the image Exif metadata...
MediaDataTags = _get_exif_keypairs(self.plugin_image)
if LesserVersion: # prior to pyexiv2-0.2.0
for KeyTag in MediaDataTags:
label = self.plugin_image.tagDetails(KeyTag)[0]
if KeyTag in ("Exif.Image.DateTime",
"Exif.Photo.DateTimeOriginal",
"Exif.Photo.DateTimeDigitized"):
human_value = _format_datetime(self.plugin_image[KeyTag])
else:
human_value = self.plugin_image.interpretedExifValue(KeyTag)
self.model.add((label, human_value))
else: # pyexiv2-0.2.0 and above
for KeyTag in MediaDataTags:
tag = self.plugin_image[KeyTag]
if KeyTag in ("Exif.Image.DateTime",
"Exif.Photo.DateTimeOriginal",
"Exif.Photo.DateTimeDigitized"):
label = tag.label
human_value = _format_datetime(tag.value)
elif ("Xmp" in KeyTag or "Iptc" in KeyTag):
label = KeyTag
human_value = tag.value
else:
label = tag.label
human_value = tag.human_value
self.model.add((label, human_value))
def build_advanced(self):
"""
Build the GUI interface.
"""
top = gtk.TreeView()
titles = [(_('Key'), 1, 250),
(_('Value'), 2, 350)]
self.model = ListModel(top, titles)
return top
def save_metadata(self):
"""
gets the information from the plugin data fields
and sets the KeyTag = keyvalue image metadata
"""
# determine if there has been something entered in the data fields?
datatags = (
len(self.exif_widgets["Description"].get_text() ) +
len(self.exif_widgets["Artist"].get_text() ) +
len(self.exif_widgets["Copyright"].get_text() ) +
len(self.exif_widgets["Latitude"].get_text() ) +
len(self.exif_widgets["Longitude"].get_text() ) )
# Description data field...
description = self.exif_widgets["Description"].get_text()
self._set_value(_DATAMAP["Description"], description)
# Modify Date/ Time... not a data field, but saved anyway...
modified = datetime.now()
self._set_value(_DATAMAP["Modified"], modified)
# display modified Date/ Time...
self.exif_widgets["MessageArea"].set_text(_("Last Changed: %s") % _format_datetime(modified) )
# Artist/ Author data field...
artist = self.exif_widgets["Artist"].get_text()
self._set_value(_DATAMAP["Artist"], artist)
# Copyright data field...
copyright = self.exif_widgets["Copyright"].get_text()
self._set_value(_DATAMAP["Copyright"], copyright)
# Original Date/ Time
year = _get_spin_value("Year", self.exif_widgets)
month = _get_spin_value("Month", self.exif_widgets)
day = _get_spin_value("Day", self.exif_widgets)
hour = _get_spin_value("Hour", self.exif_widgets)
minutes = _get_spin_value("Minutes", self.exif_widgets)
seconds = _get_spin_value("Seconds", self.exif_widgets)
use_date = False
if year < 1900:
use_date = "%04d-%s-%02d %02d:%02d:%02d" % (year, _dd.long_months[month], day,
hour, minutes, seconds)
else:
use_date = datetime(year, month, day, hour, minutes, seconds)
if use_date:
self._set_value(_DATAMAP["Original"], use_date)
# display it also...
if isinstance(use_date, datetime):
use_date = _format_datetime(use_date)
self.exif_widgets["DateDisplay"].set_text(use_date)
# Latitude/ Longitude data fields
latitude = self.exif_widgets["Latitude"].get_text()
longitude = self.exif_widgets["Longitude"].get_text()
# check to see if Latitude/ Longitude exists?
if (latitude and longitude):
# complete some error checking to prevent crashes...
# if "?" character exist, remove it?
if "?" in latitude:
latitude = latitude.replace("?", "")
if "?" in longitude:
longitude = longitude.replace("?", "")
# if "," character exists, remove it?
if "," in latitude:
latitude = latitude.replace(",", "")
if "," in longitude:
longitude = longitude.replace(",", "")
# if it is in decimal format, convert it to DMS?
# if not, then do nothing?
self.convert2dms(self.plugin_image)
# get Latitude/ Longitude from the data fields
latitude = self.exif_widgets["Latitude"].get_text()
longitude = self.exif_widgets["Longitude"].get_text()
# will add (degrees, minutes, seconds) symbols if needed?
# if not, do nothing...
latitude, longitude = self.addsymbols2gps(latitude, longitude)
# set up display
self.exif_widgets["Latitude"].set_text(latitude)
self.exif_widgets["Longitude"].set_text(longitude)
LatitudeRef = " N"
if "S" in latitude:
LatitudeRef = " S"
latitude = latitude.replace(LatitudeRef, "")
LatitudeRef = LatitudeRef.replace(" ", "")
LongitudeRef = " E"
if "W" in longitude:
LongitudeRef = " W"
longitude = longitude.replace(LongitudeRef, "")
LongitudeRef = LongitudeRef.replace(" ", "")
# remove symbols for saving Latitude/ Longitude GPS Coordinates
latitude, longitude = _removesymbols4saving(latitude, longitude)
# convert (degrees, minutes, seconds) to Rational for saving
self._set_value(_DATAMAP["LatitudeRef"], LatitudeRef)
self._set_value(_DATAMAP["Latitude"], coords_to_rational(latitude))
# convert (degrees, minutes, seconds) to Rational for saving
self._set_value(_DATAMAP["LongitudeRef"], LongitudeRef)
self._set_value(_DATAMAP["Longitude"], coords_to_rational(longitude))
if datatags:
# set Message Area to Saved...
self.exif_widgets["MessageArea"].set_text(_("Saving Exif metadata to this image..."))
else:
# set Message Area to Cleared...
self.exif_widgets["MessageArea"].set_text(_("Image Exif metadata has been cleared "
"from this image..."))
# writes all Exif Metadata to image even if the fields are all empty...
self.write_metadata(self.plugin_image)
def strip_metadata(self):
"""
Will completely and irrevocably erase all Exif metadata from this image.
"""
# update the image Exif metadata...
MediaDataTags = _get_exif_keypairs(self.plugin_image)
if not MediaDataTags:
return
if _MAGICK_FOUND:
erase = subprocess.check_call( ["convert", self.image_path, "-strip", self.image_path] )
erase_results = str(erase)
elif (_JHEAD_FOUND and self.extension in [".jpeg", ".jfif", ".jpg"]):
erase = subprocess.check_call( ["jhead", "-purejpeg", self.image_path] )
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:
# set Message Area for deleting...
self.exif_widgets["MessageArea"].set_text(_("Deleting all Exif metadata..."))
# Clear the Edit Areas
self.clear_metadata(self.plugin_image)
# set Message Area to Delete...
self.exif_widgets["MessageArea"].set_text(_("All Exif metadata has been "
"deleted from this image..."))
self.update()
def select_date(self, obj):
"""
will allow you to choose a date from the calendar widget
"""
tip = _("Double click a day to return the date.")
self.app = gtk.Window(gtk.WINDOW_TOPLEVEL)
self.app.tooltip = tip
self.app.set_title(_("Select Date"))
self.app.set_default_size(450, 200)
self.app.set_border_width(10)
self.exif_widgets["Calendar"] = gtk.Calendar()
self.exif_widgets["Calendar"].connect('day-selected-double-click', self.double_click)
self.app.add(self.exif_widgets["Calendar"])
self.exif_widgets["Calendar"].show()
self.app.show()
def double_click(self, obj):
"""
receives double-clicked and returns the selected date
widget
"""
now = time.localtime()
year, month, day = self.exif_widgets["Calendar"].get_date()
# month has to be increased by one to make it work correctly...
month += 1
hour, minutes, seconds = now[3:6]
# close this window
self.app.destroy()
# update Date/ Time spin buttons...
self.update_spinners(year, month, day, hour, minutes, seconds)
def update_spinners(self, syear, smonth, day, hour, minutes, seconds):
"""
update Date/ Time spinners.
"""
# split the date/ time into its six pieces...
datetimevalues = {
"Year" : syear,
"Month" : smonth,
"Day" : day,
"Hour" : hour,
"Minutes" : minutes,
"Seconds" : seconds}.items()
for widget, value in datetimevalues:
# make sure that the amount of days for that year and month is not > than the number of days selected...
if widget == "Day":
numdays = [0] + [calendar.monthrange(year, month)[1] for year in [syear]
for month in range(1, 13) ]
if value > numdays[smonth]:
value = numdays[smonth]
# set the date/ time spin buttons...
self.exif_widgets[widget].set_value(value)
def thumbnail_view(self, obj):
"""
will allow a display area for a thumbnail pop-up window.
"""
tip = _("Click Close to close this ThumbnailView Viewing Area.")
tbarea = gtk.Window(gtk.WINDOW_TOPLEVEL)
tbarea.tooltip = tip
tbarea.set_title(_("ThumbnailView Viewing Area"))
tbarea.set_default_size(250, 200)
tbarea.set_border_width(10)
tbarea.connect('destroy', lambda tbarea: tbarea.destroy() )
# extract the thumbnail data
previews = self.plugin_image.previews
if not previews:
print(_("This image doesn't contain any ThumbnailViews..."))
tbarea.destroy()
else:
# Get the largest preview available...
preview = previews[-1]
# Create a pixbuf loader to read the thumbnail data...
pbloader = gtk.gdk.PixbufLoader()
pbloader.write(preview.data)
# 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)
# Show the application's main window...
tbarea.add(imgwidget)
imgwidget.show()
tbarea.show()
def _setup_datafields_buttons(extension, exif_widgets):
"""
disable all data fields and buttons...
* if file extension is NOT an exiv2 image type?
"""
# _vtypes is a list of valid exiv2 image types...
goodextension = (extension in _vtypes)
# Modified and DateDisplay are gtk.Label(), which does NOT have ability to set editable or not...
for widget, tooltip in _TOOLTIPS:
if not widget in ["Modified", "DateDisplay"]:
exif_widgets[widget].set_editable(goodextension)
# Do NOT disable the Help button...
for widget, tooltip in _BUTTONTIPS.items():
if (widget is not "Help" and not goodextension):
exif_widgets[widget].set_sensitive(False)
def _get_spin_value(pos, exif_widgets):
"""
will retrieve the spinner's value and format it as two digit integer...
"""
return exif_widgets[pos].get_value_as_int()
def _setup_widget_tips(exif_widgets):
"""
set up widget tooltips...
* data fields
* buttons
"""
# add tooltips for the data entry fields...
for widget, tooltip in _TOOLTIPS:
exif_widgets[widget].set_tooltip_text(tooltip)
# add tooltips for the buttons...
for widget, tooltip in _BUTTONTIPS.items():
exif_widgets[widget].set_tooltip_text(tooltip)
def _get_exif_keypairs(plugin_image):
"""
Will be used to retrieve and update the Exif metadata from the image.
"""
MediaDataTags = False
if plugin_image:
if LesserVersion: # prior to pyexiv2-0.2.0
# get all KeyTags for this image for diplay only...
MediaDataTags = [KeyTag for KeyTag in chain(
plugin_image.exifKeys(), plugin_image.xmpKeys(),
plugin_image.iptcKeys() ) ]
else: # pyexiv2-0.2.0 and above
# get all KeyTags for this image for diplay only...
MediaDataTags = [KeyTag for KeyTag in chain(
plugin_image.exif_keys, plugin_image.xmp_keys,
plugin_image.iptc_keys) ]
return MediaDataTags
def string_to_rational(coordinate):
"""
convert string to rational variable for GPS
"""
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 coords_to_rational(Coordinates):
"""
returns the GPS coordinates to Latitude/ Longitude
"""
return [string_to_rational(coordinate) for coordinate in Coordinates.split(" ")]
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 _removesymbols4saving(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 symbol if it exist?
latitude = latitude.replace("°", "")
longitude = longitude.replace("°", "")
# remove minutes symbol if it exist?
latitude = latitude.replace("", "")
longitude = longitude.replace("", "")
# remove seconds symbol if it exist?
latitude = latitude.replace('', "")
longitude = longitude.replace('', "")
return latitude, longitude
def rational_to_dms(coords):
"""
takes a rational set of coordinates and returns (degrees, minutes, seconds)
[Fraction(40, 1), Fraction(0, 1), Fraction(1079, 20)]
"""
deg, min, sec = False, False, False
# coordinates look like:
# [Rational(38, 1), Rational(38, 1), Rational(150, 50)]
# or [Fraction(38, 1), Fraction(38, 1), Fraction(318, 100)]
if isinstance(coords, list):
if len(coords) == 3:
return [convert_value(coordinate) for coordinate in coords]
return deg, min, sec
def _format_datetime(exif_date):
"""
Convert a python datetime object into a string for display, using the
standard Gramps date format.
"""
if not isinstance(exif_date, datetime):
return ""
date_part = gen.lib.Date()
date_part.set_yr_mon_day(exif_date.year, exif_date.month, exif_date.day)
date_str = _dd.display(date_part)
time_str = exif_date.strftime('%H:%M:%S')
return _('%(date)s %(time)s') % {'date': date_str, 'time': time_str}
def _get_date_format(datestr):
"""
attempt to retrieve date format from date string
"""
# attempt to determine the dateformat of the variable passed to it...
tmpDate = False
for dateformat in ["%Y-%m-%d %H:%M:%S", "%Y %m %d %H:%M:%S",
"%Y-%b-%d %H:%M:%S", "%Y %b %d %H:%M:%S",
"%Y-%B-%d %H:%M:%S", "%Y %B %d %H:%M:%S",
"%d-%m-%Y %H:%M:%S", "%d %m %Y %H:%M:%S",
"%d-%b-%Y %H:%M:%S", "%d %b %Y %H:%M:%S",
"%d-%B-%Y %H:%M:%S", "%d %B %Y %H:%M:%S",
"%m-%d-%Y %H:%M:%S", "%m %d %Y %H:%M:%S",
"%b-%d-%Y %H:%M:%S", "%b %d %Y %H:%M:%S",
"%B-%d-%Y %H:%M:%S", "%B %d %Y %H:%M:%S"]:
# find date string format
try:
tmpDate = time.strptime(datestr, dateformat)
break
# datestring format not found...
except ValueError:
pass
return tmpDate
def _create_datetime(pyear, pmonth, day, hour, minutes, seconds):
"""
will create and retrun a str or datetime from (
year, month, day, hour, minutes, and seconds) ...
if the year is less than 1900, then it will return a string representation...
"""
# do some error trapping...
if pmonth > 12:
pmonth = 12
elif pmonth <= 0:
pmonth = 1
if hour >= 24:
hour = 23
elif hour < 0:
hour = 0
if minutes > 59:
minutes = 59
elif minutes < 0:
minutes = 0
if seconds > 59:
seconds = 59
elif seconds < 0:
seconds = 0
# get the number of days in year for all months
numdays = [0] + [calendar.monthrange(year, month)[1] for year in [pyear] for month in range(1, 13) ]
if day > numdays[pmonth]:
day = numdays[pmonth]
elif day <= 0:
day = 1
if pyear < 1900:
try:
tmpDate = "%04d-%s-%02d %02d:%02d:%02d" % (pyear, _dd.long_months[pmonth], day,
hour, minutes, seconds)
except ValueError:
tmpDate = False
else:
try:
tmpDate = datetime(pyear, pmonth, day, hour, minutes, seconds)
except ValueError:
tmpDate = False
if tmpDate is False:
tmpDate = ""
return tmpDate
def _process_datetime(tmpDate, exif_type =True):
"""
will attempt to parse the date/ time Exif metadata entry into its pieces...
(year, month, day, hour, minutes, seconds)
"""
if not tmpDate:
return False
datetype = type(tmpDate)
# if variable is already in datetime.datetime() format, return it?
if datetype == datetime:
pyear, pmonth, day = tmpDate.year, tmpDate.month, tmpDate.day
hour, minutes, seconds = tmpDate.hour, tmpDate.minute, tmpDate.second
elif any(datetype == value for value in [date, gen.lib.date.Date, list] ):
hour, minutes, seconds = time.localtime()[3:6]
# datetime.date format
if isinstance(tmpDate, date):
pyear, pmonth, day = tmpDate.year, tmpDate.month, tmpDate.day
# gen.lib.date.Date format
elif isinstance(tmpDate, gen.lib.date.Date):
pyear, pmonth, day = tmpDate.get_year(), tmpDate.get_month(), tmpDate.get_day()
# list format
else:
pyear, pmonth, day = tmpDate[0].year, tmpDate[0].month, tmpDate[0].day
# string format...
elif datetype == str:
datestr = _get_date_format(tmpDate)
if datestr is not False:
pyear, pmonth, day, hour, minutes, seconds = datestr[0:6]
else:
pyear, pmonth, day, hour, minutes, seconds = [False]*6
if (not pyear and not pmonth):
tmpDate = False
else:
# create datetime...
tmpDate = _create_datetime(pyear, pmonth, day, hour, minutes, seconds)
if tmpDate is not False:
if isinstance(tmpDate, datetime):
# for display only...
# make datetime displayed as user has set in preferences...
if exif_type:
tmpDate = _format_datetime(tmpDate)
return tmpDate