From c6d7418ace434c54a373baceac538211ad11f62c Mon Sep 17 00:00:00 2001 From: "James E. Blair" Date: Sun, 11 Oct 2009 11:47:40 -0700 Subject: Initial checkin. --- exifilm.py | 383 +++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++ 1 file changed, 383 insertions(+) create mode 100644 exifilm.py (limited to 'exifilm.py') diff --git a/exifilm.py b/exifilm.py new file mode 100644 index 0000000..17db62d --- /dev/null +++ b/exifilm.py @@ -0,0 +1,383 @@ +#!/usr/bin/python + +# ExiFilm -- Add film exposure metadata to EXIF tags of digital images +# Copyright (C) 2009 James E. Blair +# +# 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 3 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, see . + +import pygtk +pygtk.require('2.0') +import gtk, gobject +import gtk.glade +import gtk.keysyms +from gtk import gdk + +import os, sys +import pyexiv2 +import datetime +import decimal, fractions +import re + +HALF_STOP_SCALE = ['1', '1.2', '1.4', '1.7', '2', '2.4', '2.8', '3.3', '4', '4.8', + '5.6', '6.7', '8', '9.5', '11', '13', '16', '19', '22', '27', + '32', '38', '45', '54', '64', '76', '91'] + + +THIRD_STOP_SCALE = ['1', '1.1', '1.2', '1.4', '1.6', '1.8', '2', '2.2', '2.5', + '2.8', '3.2', '3.5', '4', '4.5', '5.0', '5.6', '6.3', '7.1', + '8', '9', '10', '11', '13', '14', '16', '18', '20', '22', + '25', '29', '32', '36', '40', '45', '51', '57', '64', '72', + '81', '91'] + +CAMERA_MOVEMENTS = [ + 'rising', + 'falling', + 'forward tilt', + 'backward tilt', + 'left shift', + 'right shift', + 'left swing', + 'right swing', + ] + +ID = 'ID' +FILM = 'Film' +CARRIER = 'Carrier' +PROCESS = 'Process' +FRONT_MOVEMENTS = 'Front movements' +REAR_MOVEMENTS = 'Rear movements' + +PRIVATE_KEYS = [ + ID, + FILM, + CARRIER, + PROCESS, + FRONT_MOVEMENTS, + REAR_MOVEMENTS, + ] + +ISO_RE = re.compile(r'\d\d+') + +def to_rational(v): + if '/' in v: + n,d = v.split('/') + else: + n,d = (v,1) + return pyexiv2.Rational(int(n), int(d)) + +def from_rational(v): + if not v: return v + n,d = str(v).split('/') + if d == '1': + return str(n) + return str(v) + +def from_fstop(v): + if '/' in v: + whole, frac = v.split(' ') + n,d = frac.split('/') + if d == '2': scale = HALF_STOP_SCALE + if d == '3': scale = THIRD_STOP_SCALE + i = scale.index(whole) + i += int(n) + f = fractions.Fraction.from_decimal(decimal.Decimal(str(scale[i]))) + return str(f) + f = fractions.Fraction.from_decimal(decimal.Decimal(v)) + return str(f) + +def to_fstop(v): + if '/' in v: + n,d = v.split('/') + return str(float(n)/float(d)) + return v + +def encode_comments(d, comments): + ret = '' + for k in PRIVATE_KEYS: + if k in d.keys(): + ret += '%s: %s\n' % (k, d[k]) + ret += '\n'+comments + return ret + +def decode_comments(comments): + d = {} + ret = '' + lines = comments.split('\n') + while lines: + line = lines.pop(0).strip() + if not line: break + if ':' in line: + k,v = line.split(':', 1) + d[k.strip()] = v.strip() + else: + ret += line+'\n' + break + return d, ret+'\n'.join(lines) + +class ExiFilm(object): + + def __init__(self): + self.image = None + self.xml = gtk.glade.XML('exifilm.glade', 'main_window') + + self.file_combo = self.xml.get_widget('file') + + self.id_entry = self.xml.get_widget('id') + self.film_entry = self.xml.get_widget('film') + self.carrier_entry = self.xml.get_widget('carrier') + self.process_entry = self.xml.get_widget('process') + self.lens_entry = self.xml.get_widget('lens') + self.aperture_entry = self.xml.get_widget('aperture') + self.shutter_entry = self.xml.get_widget('shutter') + + self.front_button = self.xml.get_widget('front') + self.rear_button = self.xml.get_widget('rear') + + self.description_entry = self.xml.get_widget('description') + self.date_button = self.xml.get_widget('date') + + self.comments_text = self.xml.get_widget('comments') + self.comments_buffer = gtk.TextBuffer() + self.comments_text.set_buffer(self.comments_buffer) + + # Keep the last selected date to provide a sane default + self.lastdate = None + + dic = { + 'on_main_window_destroy': self.window_closed, + 'on_main_window_key_press_event': self.key_press, + 'on_date_clicked': self.date_clicked, + 'on_front_clicked': self.front_clicked, + 'on_rear_clicked': self.rear_clicked, + 'on_file_changed': self.file_changed, + } + + self.xml.signal_autoconnect (dic) + + self.file_store = gtk.ListStore(gobject.TYPE_STRING) + cell = gtk.CellRendererText() + self.file_combo.pack_start(cell, True) + self.file_combo.add_attribute(cell, 'text', 0) + self.file_combo.set_model(self.file_store) + + self.filename = None + first = None + self.directory = '.' + if len(sys.argv)>1: + self.directory = sys.argv[1] + files = os.listdir(self.directory) + files.sort() + for fn in files: + if not (fn.lower().endswith('.jpeg') or + fn.lower().endswith('.jpg')): + continue + if not first: first = fn + self.file_store.append((fn,)) + if first: + self.file_combo.set_active(0) + + def file_changed(self, widget, data=None): + if self.filename: + self.save() + i = self.file_combo.get_active() + fn = self.file_store[i][0] + self.load(fn) + self.id_entry.grab_focus() + + def key_press(self, widget, data=None): + if data.keyval == gtk.keysyms.Page_Up: + i = self.file_combo.get_active() + if i>0: + self.file_combo.set_active(i-1) + return True + + if data.keyval == gtk.keysyms.Page_Down: + i = self.file_combo.get_active() + if i1 and sys.argv[1][0]=='-': + print "Usage: %s [PATH]" % sys.argv[0] + print + print " PATH is a directory with JPEG files to edit." + print " PATH defaults to the current directory." + else: + main() -- cgit v1.2.3