summaryrefslogtreecommitdiff
path: root/rapid/metadataphoto.py
diff options
context:
space:
mode:
authorJulien Valroff <julien@kirya.net>2011-03-25 22:07:02 +0100
committerJulien Valroff <julien@kirya.net>2011-03-25 22:07:02 +0100
commit59350657b70c3c2bb9e1cf90817bb4607a00e4fe (patch)
tree4eb598ac0235c9cf1614eaf9e33b1d7d290e9e3d /rapid/metadataphoto.py
parent478f6858602c736b67d420cbf198aeab3313cc23 (diff)
parent6866d4a5b74779f087b8e44148a49163d8b7327b (diff)
Merge commit 'upstream/0.4.0_alpha1' into experimental
Diffstat (limited to 'rapid/metadataphoto.py')
-rwxr-xr-xrapid/metadataphoto.py426
1 files changed, 426 insertions, 0 deletions
diff --git a/rapid/metadataphoto.py b/rapid/metadataphoto.py
new file mode 100755
index 0000000..8a760c4
--- /dev/null
+++ b/rapid/metadataphoto.py
@@ -0,0 +1,426 @@
+#!/usr/bin/python
+# -*- coding: latin1 -*-
+
+### Copyright (C) 2007-10 Damon Lynch <damonlynch@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
+
+import re
+import datetime
+import sys
+#~ import subprocess
+import config
+import types
+import time
+
+try:
+ import pyexiv2
+except ImportError:
+ sys.stderr.write("You need to install pyexiv2, the python binding for exiv2, to run this program.\n" )
+ sys.exit(1)
+
+
+
+def __version_info(version):
+ if not version:
+ return ''
+ else:
+ v = ''
+ for i in version:
+ v += '.%s' % i
+ return v[1:]
+
+def pyexiv2_version_info():
+ return __version_info(pyexiv2.version_info)
+
+def exiv2_version_info():
+ return __version_info(pyexiv2.exiv2_version_info)
+
+
+class MetaData(pyexiv2.metadata.ImageMetadata):
+ """
+ Class providing human readable access to image metadata
+
+ """
+
+ def aperture(self, missing=''):
+ """
+ Returns in string format the floating point value of the image's aperture.
+
+ Returns missing if the metadata value is not present.
+ """
+
+ try:
+
+ a = self["Exif.Photo.FNumber"].value
+
+ a = float(a.numerator) / float(a.denominator)
+ return "%.1f" % a
+ except:
+ return missing
+
+ def iso(self, missing=''):
+ """
+ Returns in string format the integer value of the image's ISO.
+
+ Returns missing if the metadata value is not present.
+ """
+ try:
+ return self["Exif.Photo.ISOSpeedRatings"].human_value
+ except:
+ return missing
+
+ def exposure_time(self, alternativeFormat=False, missing=''):
+ """
+ Returns in string format the exposure time of the image.
+
+ Returns missing if the metadata value is not present.
+
+ alternativeFormat is useful if the value is going to be used in a
+ purpose where / is an invalid character, e.g. file system names.
+
+ alternativeFormat is False:
+ For exposures less than one second, the result is formatted as a
+ fraction e.g. 1/125
+ For exposures greater than or equal to one second, the value is
+ formatted as an integer e.g. 30
+
+ alternativeFormat is True:
+ For exposures less than one second, the result is formatted as an
+ integer e.g. 125
+ For exposures less than one second but more than or equal to
+ one tenth of a second, the result is formatted as an integer
+ e.g. 3 representing 3/10 of a second
+ For exposures greater than or equal to one second, the value is
+ formatted as an integer with a trailing s e.g. 30s
+ """
+
+ try:
+
+ e = self["Exif.Photo.ExposureTime"].value
+
+ e0 = int(e.numerator)
+ e1 = int(e.denominator)
+
+ if e1 > e0:
+ if alternativeFormat:
+ if e0 == 1:
+ return str(e1)
+ else:
+ return str(e0)
+ else:
+ return "%s/%s" % (e0,e1)
+ elif e0 > e1:
+ e = float(e0) / e1
+ if alternativeFormat:
+ return "%.0fs" % e
+ else:
+ return "%.0f" % e
+ else:
+ return "1s"
+ except:
+ return missing
+
+ def focal_length(self, missing=''):
+ """
+ Returns in string format the focal length of the lens used to record the image.
+
+ Returns missing if the metadata value is not present.
+ """
+ try:
+ f = self["Exif.Photo.FocalLength"].value
+ f0 = float(f.numerator)
+ f1 = float(f.denominator)
+
+ return "%.0f" % (f0 / f1)
+ except:
+ return missing
+
+
+ def camera_make(self, missing=''):
+ """
+ Returns in string format the camera make (manufacturer) used to record the image.
+
+ Returns missing if the metadata value is not present.
+ """
+ try:
+ return self["Exif.Image.Make"].value.strip()
+ except:
+ return missing
+
+ def camera_model(self, missing=''):
+ """
+ Returns in string format the camera model used to record the image.
+
+ Returns missing if the metadata value is not present.
+ """
+ try:
+ return self["Exif.Image.Model"].value.strip()
+ except:
+ return missing
+
+ def camera_serial(self, missing=''):
+ try:
+ keys = self.exif_keys
+ if 'Exif.Canon.SerialNumber' in keys:
+ v = self['Exif.Canon.SerialNumber'].raw_value
+ elif 'Exif.Nikon3.SerialNumber' in keys:
+ v = self['Exif.Nikon3.SerialNumber'].raw_value
+ elif 'Exif.OlympusEq.SerialNumber' in keys:
+ v = self['Exif.OlympusEq.SerialNumber'].raw_value
+ elif 'Exif.Olympus.SerialNumber' in keys:
+ v = self['Exif.Olympus.SerialNumber'].raw_value
+ elif 'Exif.Olympus.SerialNumber2' in keys:
+ v = self['Exif.Olympus.SerialNumber2'].raw_value
+ elif 'Exif.Panasonic.SerialNumber' in keys:
+ v = self['Exif.Panasonic.SerialNumber'].raw_value
+ elif 'Exif.Fujifilm.SerialNumber' in keys:
+ v = self['Exif.Fujifilm.SerialNumber'].raw_value
+ elif 'Exif.Image.CameraSerialNumber' in keys:
+ v = self['Exif.Image.CameraSerialNumber'].raw_value
+ else:
+ return missing
+ v = str(v) # probably not necessary, but just in case
+ return v.strip()
+ except:
+ return missing
+
+ def shutter_count(self, missing=''):
+ try:
+ keys = self.exif_keys
+ if 'Exif.Nikon3.ShutterCount' in keys:
+ v = self['Exif.Nikon3.ShutterCount'].raw_value
+ elif 'Exif.Canon.FileNumber' in keys:
+ v = self['Exif.Canon.FileNumber'].raw_value
+ elif 'Exif.Canon.ImageNumber' in keys:
+ v = self['Exif.Canon.ImageNumber'].raw_value
+ else:
+ return missing
+ return str(v)
+ except:
+ return missing
+
+ def owner_name(self, missing=''):
+ """ returns camera name recorded by select Canon cameras"""
+ try:
+ return self['Exif.Canon.OwnerName'].value.strip()
+ except:
+ return missing
+
+ def short_camera_model(self, includeCharacters = '', missing=''):
+ """
+ Returns in shorterned string format the camera model used to record the image.
+
+ Returns missing if the metadata value is not present.
+
+ The short format is determined by the first occurrence of a digit in the
+ camera model, including all alphaNumeric characters before and after
+ that digit up till a non-alphanumeric character, but with these interventions:
+
+ 1. Canon "Mark" designations are shortened prior to conversion.
+ 2. Names like "Canon EOS DIGITAL REBEL XSi" do not have a number and must
+ and treated differently (see below)
+
+ Examples:
+ Canon EOS 300D DIGITAL -> 300D
+ Canon EOS 5D -> 5D
+ Canon EOS 5D Mark II -> 5DMkII
+ NIKON D2X -> D2X
+ NIKON D70 -> D70
+ X100,D540Z,C310Z -> X100
+ Canon EOS DIGITAL REBEL XSi -> XSi
+ Canon EOS Digital Rebel XS -> XS
+ Canon EOS Digital Rebel XTi -> XTi
+ Canon EOS Kiss Digital X -> Digital
+ Canon EOS Digital Rebel XT -> XT
+ EOS Kiss Digital -> Digital
+ Canon Digital IXUS Wireless -> Wireless
+ Canon Digital IXUS i zoom -> zoom
+ Canon EOS Kiss Digital N -> N
+ Canon Digital IXUS IIs -> IIs
+ IXY Digital L -> L
+ Digital IXUS i -> i
+ IXY Digital -> Digital
+ Digital IXUS -> IXUS
+
+ The optional includeCharacters allows additional characters to appear
+ before and after the digits.
+ Note: special includeCharacters MUST be escaped as per syntax of a
+ regular expressions (see documentation for module re)
+
+ Examples:
+
+ includeCharacters = '':
+ DSC-P92 -> P92
+ includeCharacters = '\-':
+ DSC-P92 -> DSC-P92
+
+ If a digit is not found in the camera model, the last word is returned.
+
+ Note: assume exif values are in ENGLISH, regardless of current platform
+ """
+ m = self.camera_model()
+ m = m.replace(' Mark ', 'Mk')
+ if m:
+ s = r"(?:[^a-zA-Z0-9%s]?)(?P<model>[a-zA-Z0-9%s]*\d+[a-zA-Z0-9%s]*)"\
+ % (includeCharacters, includeCharacters, includeCharacters)
+ r = re.search(s, m)
+ if r:
+ return r.group("model")
+ else:
+ head, space, model = m.strip().rpartition(' ')
+ return model
+ else:
+ return missing
+
+
+ def date_time(self, missing=''):
+ """
+ Returns in python datetime format the date and time the image was
+ recorded.
+
+ Trys to get value from exif key "Exif.Photo.DateTimeOriginal".
+ If that does not exist, trys key "Exif.Image.DateTime"
+
+ Returns missing either metadata value is not present.
+ """
+ try:
+ if "Exif.Photo.DateTimeOriginal" in self.exif_keys:
+ v = self["Exif.Photo.DateTimeOriginal"].value
+ else:
+ v = self["Exif.Image.DateTime"].value
+
+ return v
+ except:
+ return missing
+
+ def time_stamp(self, missing=''):
+ dt = self.date_time(missing=None)
+ if not dt is None:
+ try:
+ t = dt.timetuple()
+ ts = time.mktime(t)
+ except:
+ ts = missing
+ else:
+ ts = missing
+ return ts
+
+ def sub_seconds(self, missing='00'):
+ """ returns the subsecond the image was taken, as recorded by the camera"""
+ try:
+ return str(self["Exif.Photo.SubSecTimeOriginal"].value)
+ except:
+ return missing
+
+ def orientation(self, missing=''):
+ """
+ Returns the orientation of the image, as recorded by the camera
+ Return type int
+ """
+ try:
+ v = self['Exif.Image.Orientation'].value
+ if isinstance(v, types.StringType):
+ v = int(v)
+ return v
+ except:
+ return missing
+
+
+class DummyMetaData(MetaData):
+ """
+ Class which gives metadata values for an imaginary image.
+
+ Useful for displaying in preference examples etc. when no image is ready to
+ be downloaded.
+
+ See MetaData class for documentation of class methods.
+ """
+
+ def __init__(self):
+ pass
+
+ def readMetadata(self):
+ pass
+
+ def aperture(self, missing=''):
+ return "2.0"
+
+ def iso(self, missing=''):
+ return "100"
+
+ def exposure_time(self, alternativeFormat=False, missing=''):
+ if alternativeFormat:
+ return "4000"
+ else:
+ return "1/4000"
+
+ def focal_length(self, missing=''):
+ return "135"
+
+ def camera_make(self, missing=''):
+ return "Canon"
+
+ def camera_model(self, missing=''):
+ return "Canon EOS 5D"
+
+ def short_camera_model(self, includeCharacters = '', missing=''):
+ return "5D"
+
+ def camera_serial(self, missing=''):
+ return '730402168'
+
+ def shutter_count(self, missing=''):
+ return '387'
+
+ def owner_name(self, missing=''):
+ return 'Photographer Name'
+
+ def date_time(self, missing=''):
+ return datetime.datetime.now()
+
+ def subSeconds(self, missing='00'):
+ return '57'
+
+ def orientation(self, missing=''):
+ return 1
+
+if __name__ == '__main__':
+ import sys
+
+
+ if (len(sys.argv) != 2):
+ print 'Usage: ' + sys.argv[0] + ' path/to/photo/containing/metadata'
+ m = DummyMetaData()
+
+ else:
+ m = MetaData(sys.argv[1])
+ m.read()
+
+ print "f"+ m.aperture('missing ')
+ print "ISO " + m.iso('missing ')
+ print m.exposure_time(missing='missing ') + " sec"
+ print m.exposure_time(alternativeFormat=True, missing='missing ')
+ print m.focal_length('missing ') + "mm"
+ print m.camera_make()
+ print m.camera_model()
+ print m.short_camera_model()
+ print m.short_camera_model(includeCharacters = "\-")
+ print m.date_time()
+ print m.orientation()
+ print 'Serial number:', m.camera_serial()
+ print 'Shutter count:', m.shutter_count()
+ print 'Subseconds:', m.sub_seconds()
+