#!/usr/bin/python # -*- encoding: utf-8 -*- # # film-exif - Updates EXIF for film picture from digiKam tags # # Usage: film-exif [--db /path/to/digikam3.db] JPG_files # If the database file is not given, use the one in the current directory # See comments below and set variables according to your digiKam tags tree. # # Copyright (C) Miguel Ángel Vilela # # 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. import os import sys import getopt import sqlite3 import commands import datetime # Only tags under these parents will be considered # (leave empty to consider all tags) Parents = ('EXIF: cuerpo', 'EXIF: objetivo', 'EXIF: pelicula', 'EXIF: enfoque', 'EXIF: medicion', 'EXIF: programa', 'EXIF: exposicion') # These tags has always the same value StaticMap = { "Exif.Image.Artist": "Miguel Ángel Vilela (miguev.net)".decode('utf-8'), "Exif.Image.Copyright": "© Miguel Ángel Vilela (miguev.net)".decode('utf-8'), "Exif.Photo.UserComment": "http://www.flickr.com/photos/miguev/", } # Digikam tags to EXIF tags mapping. TagMap = { # Camera body: AF/MF Matrix/Spot/Centered # Exif.Image.Make Ascii # Exif.Image.Model Ascii # Exif.Photo.ExposureProgram Short 1 1 + idx(Manual,Auto,Aperture priority,Shutter priority,Creative program,Action program,Portrait mode,Landscape mode) # Exif.Photo.MeteringMode Short 1 1 + idx(Average,Center weighted average,Spot,Multi-spot,Multi-segment,Partial) # Exif.Nikon3.Focus Ascii 7 MANUAL/AF-S/AF-C "Nikon FM-2n": { "Exif.Image.Make": "NIKON CORPORATION", "Exif.Image.Model": "NIKON FM-2n", "Exif.Photo.ExposureProgram": "1", "Exif.Photo.MeteringMode": "2", "Exif.Nikon3.Focus": "MANUAL", }, "Olympus OM-1": { "Exif.Image.Make": "OLYMPUS IMAGING CORP.", "Exif.Image.Model": "OM-1", "Exif.Photo.ExposureProgram": "1", "Exif.Photo.MeteringMode": "2", "Exif.Nikon3.Focus": "MANUAL", }, "Nikon F90X": { "Exif.Image.Make": "NIKON CORPORATION", "Exif.Image.Model": "NIKON F90X", }, "Chinon MA-8": { "Exif.Image.Make": "CHINON CORPORATION", "Exif.Image.Model": "CHINON MA-8", "Exif.Photo.ExposureProgram": "1", "Exif.Photo.MeteringMode": "2", "Exif.Nikon3.Focus": "MANUAL", }, "Samsung Vega 70D": { "Exif.Image.Make": "Samsung Techwin", "Exif.Image.Model": "Vega 70D", "Exif.Photo.FocalLength": "350/10", "Exif.Photo.FocalLengthIn35mmFilm": "35" , "Exif.Photo.MaxApertureValue" : "35/10", "Exif.Photo.FNumber": "80/10", "Exif.Photo.ExposureTime": "1/60", "Exif.Photo.Flash": "0", "Exif.Photo.ExposureProgram": "2", "Exif.Photo.MeteringMode": "2", "Exif.Nikon3.Focus": "AF-S", "Exif.Nikon3.Lens": "350/10 1050/10 35/10 56/10", }, # Lens: # Exif.Photo.FocalLengthIn35mmFilm Short 1 len # Exif.Photo.FocalLength Rational 1 len x 10/10 # Exif.Photo.MaxApertureValue Rational 1 FM x 10/10 # Exif.Nikon3.Lens Rational 4 (wide tele Fw Ft) x 10/10 # Exif.Nikon3.LensType Byte 1 2 ^ idx(MF,D,G,VR) "Chinon 28mm 1:3.5": { # 28-70mm set at 28mm "Exif.Photo.FocalLengthIn35mmFilm" : "28", "Exif.Photo.FocalLength" : "280/10", "Exif.Photo.MaxApertureValue" : "35/10", "Exif.Nikon3.Lens": "280/10 700/10 35/10 45/10", }, "Chinon 50mm 1:4": { # 28-70mm set at 50mm "Exif.Photo.FocalLengthIn35mmFilm" : "50", "Exif.Photo.FocalLength" : "500/10", "Exif.Photo.MaxApertureValue" : "40/10", "Exif.Nikon3.Lens": "280/10 700/10 35/10 45/10", }, "Chinon 70mm 1:4.5": { # 28-70mm set at 70mm "Exif.Photo.FocalLengthIn35mmFilm" : "70", "Exif.Photo.FocalLength" : "700/10", "Exif.Photo.MaxApertureValue" : "45/10", "Exif.Nikon3.Lens": "280/10 700/10 35/10 45/10", }, "Chinon 28-70mm 1:3.5-4.5": { "Exif.Nikon3.Lens": "280/10 700/10 35/10 45/10", }, # Nikkor lenses for Nikon "AF Nikkor 20mm 1:2.8D": { "Exif.Photo.FocalLengthIn35mmFilm" : "20", "Exif.Photo.FocalLength" : "200/10", "Exif.Photo.MaxApertureValue" : "28/10", "Exif.Nikon3.Lens": "200/10 200/10 28/10 28/10", "Exif.Nikon3.LensType": "2", }, "AF Nikkor 28mm 1:2.8": { "Exif.Photo.FocalLengthIn35mmFilm" : "28", "Exif.Photo.FocalLength" : "280/10", "Exif.Photo.MaxApertureValue" : "28/10", "Exif.Nikon3.Lens": "280/10 280/10 28/10 28/10", "Exif.Nikon3.LensType": "0", }, "AF Nikkor 35mm 1:3.5D": { # 35-105mm set at 35mm "Exif.Photo.FocalLengthIn35mmFilm" : "35", "Exif.Photo.FocalLength" : "350/10", "Exif.Photo.MaxApertureValue" : "35/10", "Exif.Nikon3.Lens": "350/10 1050/10 35/10 56/10", "Exif.Nikon3.LensType": "2", }, "AF Nikkor 50mm 1:1.4D": { "Exif.Photo.FocalLengthIn35mmFilm" : "50", "Exif.Photo.FocalLength" : "500/10", "Exif.Photo.MaxApertureValue" : "14/10", "Exif.Nikon3.Lens": "500/10 500/10 14/10 14/10", "Exif.Nikon3.LensType": "2", }, "AF Nikkor 85mm 1:1.8": { "Exif.Photo.FocalLengthIn35mmFilm" : "85", "Exif.Photo.FocalLength" : "850/10", "Exif.Photo.MaxApertureValue" : "18/10", "Exif.Nikon3.Lens": "850/10 850/10 18/10 18/10", "Exif.Nikon3.LensType": "0", }, "AF Nikkor 105mm 1:5.6D": { # 35-105mm set at 105mm "Exif.Photo.FocalLengthIn35mmFilm" : "105", "Exif.Photo.FocalLength" : "1050/10", "Exif.Photo.MaxApertureValue" : "56/10", "Exif.Nikon3.Lens": "350/10 1050/10 35/10 56/10", "Exif.Nikon3.LensType": "2", }, "AF Nikkor 35-105mm 1:3.5-4.5D": { "Exif.Nikon3.Lens": "350/10 1050/10 35/10 56/10", "Exif.Nikon3.LensType": "2", }, # 2nd brand lenses for Nikon (Hanimex, Sigma, Tokina) "Hanimex 135mm 1:2.8": { "Exif.Photo.FocalLengthIn35mmFilm" : "135", "Exif.Photo.FocalLength" : "1350/10", "Exif.Photo.MaxApertureValue" : "28/10", "Exif.Nikon3.Lens": "1350/10 1350/10 28/10 28/10", "Exif.Nikon3.LensType": "1", }, "Sigma Macro 50mm 1:2.8 DG EX": { "Exif.Photo.FocalLengthIn35mmFilm" : "50", "Exif.Photo.FocalLength" : "500/10", "Exif.Photo.MaxApertureValue" : "28/10", "Exif.Nikon3.Lens": "500/10 500/10 28/10 28/10", "Exif.Nikon3.LensType": "2", }, "Tokina 12-24mm 1:4": { # 12-24mm at 18mm "Exif.Photo.FocalLengthIn35mmFilm" : "18", "Exif.Photo.FocalLength" : "180/10", "Exif.Photo.MaxApertureValue" : "40/10", "Exif.Nikon3.Lens": "120/10 240/10 40/10 40/10", "Exif.Nikon3.LensType": "6", }, "Tokina 12-24mm 1:4": { # 12-24mm at 20mm "Exif.Photo.FocalLengthIn35mmFilm" : "20", "Exif.Photo.FocalLength" : "200/10", "Exif.Photo.MaxApertureValue" : "40/10", "Exif.Nikon3.Lens": "120/10 240/10 40/10 40/10", "Exif.Nikon3.LensType": "6", }, "Tokina 12-24mm 1:4": { # 12-24mm at 24mm "Exif.Photo.FocalLengthIn35mmFilm" : "24", "Exif.Photo.FocalLength" : "240/10", "Exif.Photo.MaxApertureValue" : "40/10", "Exif.Nikon3.Lens": "120/10 240/10 40/10 40/10", "Exif.Nikon3.LensType": "6", }, "Tokina 12-24mm 1:4": { "Exif.Photo.MaxApertureValue" : "40/10", "Exif.Nikon3.Lens": "120/10 240/10 40/10 40/10", "Exif.Nikon3.LensType": "6", }, # Zuiko lenses for Olympus OM-1 "Zuiko 24mm 1:2.8": { "Exif.Photo.FocalLengthIn35mmFilm" : "24", "Exif.Photo.FocalLength" : "240/10", "Exif.Photo.MaxApertureValue" : "28/10", "Exif.Nikon3.Lens": "240/10 240/10 28/10 28/10", "Exif.Nikon3.LensType": "1", }, "Zuiko 35mm 1:2.8": { "Exif.Photo.FocalLengthIn35mmFilm" : "35", "Exif.Photo.FocalLength" : "350/10", "Exif.Photo.MaxApertureValue" : "28/10", "Exif.Nikon3.Lens": "350/10 350/10 28/10 28/10", "Exif.Nikon3.LensType": "1", }, "Zuiko Micro 50mm 1:3.5": { "Exif.Photo.FocalLengthIn35mmFilm" : "50", "Exif.Photo.FocalLength" : "500/10", "Exif.Photo.MaxApertureValue" : "35/10", "Exif.Nikon3.Lens": "500/10 500/10 35/10 35/10", "Exif.Nikon3.LensType": "1", }, "Zuiko 100mm 1:2.8": { "Exif.Photo.FocalLengthIn35mmFilm" : "100", "Exif.Photo.FocalLength" : "1000/10", "Exif.Photo.MaxApertureValue" : "28/10", "Exif.Nikon3.Lens": "1000/10 1000/10 28/10 28/10", "Exif.Nikon3.LensType": "1", }, # Film: # Exif.Photo.ISOSpeedRatings Short 1 ISO # Exif.Nikon3.ISOSpeed Short 2 0 ISO "Kodak VPS 160": { "Exif.Photo.ISOSpeedRatings": "160", "Exif.Nikon3.ISOSpeed": "0 160", }, "Agfa NPS 160": { "Exif.Photo.ISOSpeedRatings": "160", "Exif.Nikon3.ISOSpeed": "0 160", }, "Fujifilm Superia X-tra 400": { "Exif.Photo.ISOSpeedRatings": "400", "Exif.Nikon3.ISOSpeed": "0 400", }, "Fujifilm Reala 100": { "Exif.Photo.ISOSpeedRatings": "100", "Exif.Nikon3.ISOSpeed": "0 100", }, "Fujifilm Sensia 100": { "Exif.Photo.ISOSpeedRatings": "100", "Exif.Nikon3.ISOSpeed": "0 100", }, "Fujifilm PRO 160": { "Exif.Photo.ISOSpeedRatings": "160", "Exif.Nikon3.ISOSpeed": "0 160", }, "Kodak T-Max 100": { "Exif.Photo.ISOSpeedRatings": "100", "Exif.Nikon3.ISOSpeed": "0 100", }, "Kodak T-Max 400": { "Exif.Photo.ISOSpeedRatings": "400", "Exif.Nikon3.ISOSpeed": "0 400", }, "Kodak T-Max 3200": { "Exif.Photo.ISOSpeedRatings": "3200", "Exif.Nikon3.ISOSpeed": "0 3200", }, "Ilford HP5 400": { "Exif.Photo.ISOSpeedRatings": "400", "Exif.Nikon3.ISOSpeed": "0 400", }, "Ilford HP5+ 125": { "Exif.Photo.ISOSpeedRatings": "125", "Exif.Nikon3.ISOSpeed": "0 125", }, # Focus mode # Exif.Nikon3.Focus Ascii 7 MANUAL/AF-S/AF-C "AF-C" : { "Exif.Nikon3.Focus": "AF-C", }, "AF-S" : { "Exif.Nikon3.Focus": "AF-S", }, "Manual" : { "Exif.Nikon3.Focus": "MANUAL", }, # Metering mode # Exif.Photo.MeteringMode Short 1 1 + idx(Average,Center weighted average,Spot,Multi-spot,Multi-segment,Partial) "Matrix" : { "Exif.Photo.MeteringMode": "5", }, "Ponderada" : { "Exif.Photo.MeteringMode": "2", }, "Spot" : { "Exif.Photo.MeteringMode": "3", }, # Program used # Exif.Photo.ExposureProgram Short 1 1 + idx(Manual,Auto,Aperture priority,Shutter priority,Creative program,Action program,Portrait mode,Landscape mode) "Auto" : { "Exif.Photo.ExposureProgram": "2", }, "Deportes" : { "Exif.Photo.ExposureProgram": "6", }, "Manual" : { "Exif.Photo.ExposureProgram": "1", }, "Paisaje" : { "Exif.Photo.ExposureProgram": "8", }, "Prioridad de apertura" : { "Exif.Photo.ExposureProgram": "3", }, "Prioridad de velocidad" : { "Exif.Photo.ExposureProgram": "4", }, "Retrato" : { "Exif.Photo.ExposureProgram": "7", }, # Exposure values # Exif.Photo.FNumber Rational 1 Fn x 10/10 # Exif.Photo.ExposureTime Rational 1 1/exp x 10/10 "F1.4": { "Exif.Photo.FNumber": "14/10", }, "F2": { "Exif.Photo.FNumber": "20/10", }, "F2.8": { "Exif.Photo.FNumber": "28/10", }, "F4": { "Exif.Photo.FNumber": "40/10", }, "F5.6": { "Exif.Photo.FNumber": "56/10", }, "F8": { "Exif.Photo.FNumber": "80/10", }, "F11": { "Exif.Photo.FNumber": "110/10", }, "F16": { "Exif.Photo.FNumber": "160/10", }, "F22": { "Exif.Photo.FNumber": "220/10", }, "F32": { "Exif.Photo.FNumber": "320/10", }, "1000 ms": { "Exif.Photo.ExposureTime": "10/10", }, "0500 ms": { "Exif.Photo.ExposureTime": "10/20", }, "0250 ms": { "Exif.Photo.ExposureTime": "10/40", }, "0125 ms": { "Exif.Photo.ExposureTime": "10/80", }, "0064 ms": { "Exif.Photo.ExposureTime": "10/150", }, "0032 ms": { "Exif.Photo.ExposureTime": "10/300", }, "0016 ms": { "Exif.Photo.ExposureTime": "10/600", }, "0008 ms": { "Exif.Photo.ExposureTime": "10/1250", }, "0004 ms": { "Exif.Photo.ExposureTime": "10/2500", }, "0002 ms": { "Exif.Photo.ExposureTime": "10/5000", }, "0001 ms": { "Exif.Photo.ExposureTime": "10/10000", }, "0000.5 ms": { "Exif.Photo.ExposureTime": "10/20000", }, "0000.25 ms": { "Exif.Photo.ExposureTime": "10/40000", }, "0000.125 ms": { "Exif.Photo.ExposureTime": "10/80000", }, } # Get the DB file name and open it dbfile = 'digikam3.db' opts, args = getopt.getopt(sys.argv[1:], None, ["db="]) for opt, arg in opts: if opt == '--db': dbfile = arg db = sqlite3.connect(dbfile) cur = db.cursor() # Get all relevant tags from parents IDs query = "select id from Tags" if Parents: query += " where name in %s" % str(Parents) cur.execute(query) parents_ids = tuple([r[0] for r in cur.fetchall()]) query = "select id, name from Tags where pid in %s" % str(parents_ids) cur.execute(query) tags = dict(cur.fetchall()) #print '"' + '"\n"'.join(tags.values()) + '"' # Get all relevant images #print args images_path = dict([(os.path.basename(img), img) for img in args]) names = tuple(images_path.keys()) + ('None',) query = "select id, name from Images where name in %s" % str(names) cur.execute(query) images = dict(cur.fetchall()) # Get the relationship between this images and each tag query = "select imageid, tagid from ImageTags where imageid in %s and tagid in %s" \ % (str(tuple(images.keys()) + ('None',)), str(tuple(tags.keys()) + ('None',))) #print query cur.execute(query) image_tags = dict([(key, []) for key in images.keys()]) for key, value in cur.fetchall(): image_tags[key].append(value) #for id, name in images.iteritems(): # print "%s has tags %s" % (name, ', '.join([tags[i] for i in image_tags[id]])) # Assign EXIF commands to each digiKam tag exif_cmds = dict([(id, []) for id in tags.keys()]) for tid, tname in tags.iteritems(): for etag, value in TagMap[tname].iteritems(): exif_cmds[tid].append("set %s %s" % (etag, value)) # Show EXIF commands #for tid, tname in tags.iteritems(): # print "digiKam tag: %s\n %s" % (tname, "\n ".join(exif_cmds[tid])) # Set EXIF tags on images for iid, iname in images.iteritems(): cmds = ["set %s %s" % item for item in StaticMap.iteritems()] for tid in image_tags[iid]: cmds.extend(exif_cmds[tid]) status, output = commands.getstatusoutput("""exiv2 -pv '%s' | grep '^0x0132' | awk '{print $6 " " $7}'""" % images_path[iname]) if status == 0: cmds.append("set Exif.Photo.DateTimeOriginal %s" % output) else: status, output = commands.getstatusoutput("""exiv2 -pv '%s' | grep '^0x9004' | awk '{print $6 " " $7}'""" % images_path[iname]) if status == 0: cmds.append("set Exif.Photo.DateTimeOriginal %s" % output) else: try: fstat = os.stat(images_path[iname]) cmds.append("set Exif.Photo.DateTimeOriginal %s" % datetime.datetime.fromtimestamp( fstat[-2]).strftime('%Y:%m:%d %H:%M:%S')) except: pass print "Setting %d EXIF tags for '%s'" % (len(cmds), images_path[iname]) #print "%s\n %s" % (iname, "\n ".join(cmds)) exiv2_cmd = "exiv2 -k %s '%s'" % (' '.join(['-M"%s"' % c for c in cmds]), images_path[iname].decode('utf-8')) #print exiv2_cmd status, output = commands.getstatusoutput(exiv2_cmd.encode('utf-8')) if status != 0: print "Command failed:\n\n\t%s\n\n\t%s\n\n" % (exiv2_cmd, output)