diff --git a/README b/README index 3f46e64..626533a 100644 --- a/README +++ b/README @@ -61,7 +61,7 @@ For version 1.0 following aims have to be done: - files properties x thumbnail x description - - exif information + x exif information - keywords (tags) - gthumb integration x adding images diff --git a/resources/glade/main.glade b/resources/glade/main.glade index 1b287cb..67aa83c 100644 --- a/resources/glade/main.glade +++ b/resources/glade/main.glade @@ -722,61 +722,21 @@ - + + True GDK_POINTER_MOTION_MASK | GDK_POINTER_MOTION_HINT_MASK | GDK_BUTTON_PRESS_MASK | GDK_BUTTON_RELEASE_MASK + GTK_POLICY_AUTOMATIC + GTK_POLICY_AUTOMATIC + GTK_SHADOW_IN - + + True True GDK_POINTER_MOTION_MASK | GDK_POINTER_MOTION_HINT_MASK | GDK_BUTTON_PRESS_MASK | GDK_BUTTON_RELEASE_MASK - - - True - True - GDK_POINTER_MOTION_MASK | GDK_POINTER_MOTION_HINT_MASK | GDK_BUTTON_PRESS_MASK | GDK_BUTTON_RELEASE_MASK - GTK_POLICY_AUTOMATIC - GTK_POLICY_AUTOMATIC - GTK_SHADOW_IN - - - True - True - GDK_POINTER_MOTION_MASK | GDK_POINTER_MOTION_HINT_MASK | GDK_BUTTON_PRESS_MASK | GDK_BUTTON_RELEASE_MASK - True - - - - - False - True - - - - - + True + True - - - True - GDK_POINTER_MOTION_MASK | GDK_POINTER_MOTION_HINT_MASK | GDK_BUTTON_PRESS_MASK | GDK_BUTTON_RELEASE_MASK - GTK_POLICY_AUTOMATIC - GTK_POLICY_AUTOMATIC - - - True - True - GDK_POINTER_MOTION_MASK | GDK_POINTER_MOTION_HINT_MASK | GDK_BUTTON_PRESS_MASK | GDK_BUTTON_RELEASE_MASK - True - - - - - 1 - - - - - 2 @@ -786,7 +746,7 @@ True GDK_POINTER_MOTION_MASK | GDK_POINTER_MOTION_HINT_MASK | GDK_BUTTON_PRESS_MASK | GDK_BUTTON_RELEASE_MASK - page 3 + EXIF tab diff --git a/src/ctrls/c_main.py b/src/ctrls/c_main.py index 27b83f1..6094e8f 100644 --- a/src/ctrls/c_main.py +++ b/src/ctrls/c_main.py @@ -111,6 +111,7 @@ class MainController(Controller): # initialize treeviews self.__setup_disc_treeview() self.__setup_files_treeview() + self.__setup_exif_treeview() # in case passing catalog filename in command line, unlock gui if self.model.filename != None: @@ -167,11 +168,9 @@ class MainController(Controller): try: path, column = self.view['discs'].get_cursor() - print path iter = model.get_iter(path) self.model.get_root_entries(model.get_value(iter, 0)) except TypeError: - print "zuo" self.model.get_root_entries(1) return return @@ -351,21 +350,6 @@ class MainController(Controller): self.view['update1'].set_sensitive(False) self.__popup_menu(event) - # elif event.button == 1: # Left click - # """Show files on right treeview, after clicking the left disc treeview.""" - # model = self.view['discs'].get_model() - # selected_item = self.model.discs_tree.get_value(self.model.discs_tree.get_iter(path),0) - # if __debug__: - # print "c_main.py, on_discs_cursor_changed()",selected_item - # self.model.get_root_entries(selected_item) - # - # self.view['details'].show() - # txt = self.model.get_file_info(selected_item) - # buf = self.view['details'].get_buffer() - # buf.set_text(txt) - # self.view['details'].set_buffer(buf) - # return - def on_expand_all1_activate(self, menuitem): self.view['discs'].expand_all() return @@ -402,19 +386,10 @@ class MainController(Controller): return True def on_files_cursor_changed(self, treeview): - """Show details of selected file""" + """Show details of selected file/directory""" model, paths = treeview.get_selection().get_selected_rows() try: itera = model.get_iter(paths[0]) - #if model.get_value(itera,4) == 1: - # #directory, do nothin', just turn off view - # '''self.view['details'].hide() - # buf = self.view['details'].get_buffer() - # buf.set_text('') - # self.view['details'].set_buffer(buf)''' - #else: - #file, show what you got. - #self.details.get_top_widget() iter = model.get_iter(treeview.get_cursor()[0]) selected_item = self.model.files_list.get_value(iter, 0) self.__get_item_info(selected_item) @@ -535,8 +510,6 @@ class MainController(Controller): def on_delete2_activate(self, menu_item): try: - #path, column = self.view['discs'].get_cursor() - #selected_iter = model.get_iter(path) selection = self.view['discs'].get_selection() model, selected_iter = selection.get_selected() except: @@ -562,7 +535,6 @@ class MainController(Controller): path = (row, ) # delete from db - print current_id self.model.delete(current_id) # refresh files treeview @@ -819,26 +791,6 @@ class MainController(Controller): self.view['images'].set_pixbuf_column(1) return - def __setup_tags_treeview(self): - """Setup TreeView discs widget as tree.""" - self.view['tags'].set_model(self.model.tagsTree) - - c = gtk.TreeViewColumn('Filename') - - # one row contains image and text - cellpb = gtk.CellRendererPixbuf() - cell = gtk.CellRendererText() - c.pack_start(cellpb, False) - c.pack_start(cell, True) - c.set_attributes(cellpb, stock_id=2) - c.set_attributes(cell, text=1) - - self.view['discs'].append_column(c) - - # registration of treeview signals: - - return - def __setup_files_treeview(self): """Setup TreeView files widget, as columned list.""" self.view['files'].set_model(self.model.files_list) @@ -866,14 +818,20 @@ class MainController(Controller): c.set_sort_column_id(3) c.set_resizable(True) self.view['files'].append_column(c) + return - #c = gtk.TreeViewColumn('Category',gtk.CellRendererText(), text=5) - #c.set_sort_column_id(5) - #c.set_resizable(True) - #self.view['files'].append_column(c) + def __setup_exif_treeview(self): + self.view['exif_tree'].set_model(self.model.exif_list) - # registration of treeview signals: + c = gtk.TreeViewColumn('EXIF key',gtk.CellRendererText(), text=0) + c.set_sort_column_id(0) + c.set_resizable(True) + self.view['exif_tree'].append_column(c) + c = gtk.TreeViewColumn('EXIF value',gtk.CellRendererText(), text=1) + c.set_sort_column_id(1) + c.set_resizable(True) + self.view['exif_tree'].append_column(c) return def __abort(self): @@ -962,6 +920,12 @@ class MainController(Controller): else: self.view['img_container'].hide() + if set.has_key('exif'): + self.view['exif_tree'].set_model(self.model.exif_list) + self.view['exifinfo'].show() + else: + self.view['exifinfo'].hide() + if set.has_key('thumbnail'): self.view['thumb'].set_from_file(set['thumbnail']) self.view['thumb'].show() diff --git a/src/models/m_main.py b/src/models/m_main.py index 85545d3..058e20e 100644 --- a/src/models/m_main.py +++ b/src/models/m_main.py @@ -47,6 +47,7 @@ from m_config import ConfigModel from m_details import DetailsModel from utils.thumbnail import Thumbnail from utils.img import Img +from utils.parse_exif import ParseExif class MainModel(ModelMT): """Create, load, save, manipulate db file which is container for data""" @@ -63,6 +64,20 @@ class MainModel(ModelMT): CD = 1 # sorce: cd/dvd DR = 2 # source: filesystem + EXIF_DICT= {0: 'Camera', + 1: 'Date', + 2: 'Aperture', + 3: 'Exposure program', + 4: 'Exposure bias', + 5: 'ISO', + 6: 'Focal length', + 7: 'Subject distance', + 8: 'Metering mode', + 9: 'Flash', + 10: 'Light source', + 11: 'Resolution', + 12: 'Orientation'} + # images extensions - only for PIL and EXIF IMG = ['jpg', 'jpeg', 'gif', 'png', 'tif', 'tiff', 'tga', 'pcx', 'bmp', 'xbm', 'xpm', 'jp2', 'jpx', 'pnm'] @@ -89,9 +104,13 @@ class MainModel(ModelMT): gobject.TYPE_UINT64, gobject.TYPE_STRING, gobject.TYPE_INT, gobject.TYPE_STRING, str) - # iconview store - image id, pixbuffer + # iconview store - id, pixbuffer self.images_store = gtk.ListStore(gobject.TYPE_INT, gtk.gdk.Pixbuf) + # exif liststore - id, exif key, exif value + self.exif_list = gtk.ListStore(gobject.TYPE_INT, gobject.TYPE_STRING, + gobject.TYPE_STRING) + # tag cloud array element is a dict with 4 keys: # elem = {'id': str(id), 'name': tagname, 'size': size, 'color': color} # where color is in one of format: @@ -334,6 +353,24 @@ class MainModel(ModelMT): pix = gtk.gdk.pixbuf_new_from_file(im) self.images_store.append([id, pix]) retval['images'] = True + + sql = """SELECT camera, date, aperture, exposure_program, + exposure_bias, iso, focal_length, subject_distance, metering_mode, + flash, light_source, resolution, orientation + from exif + WHERE file_id = ?""" + + self.db_cursor.execute(sql, (id,)) + set = self.db_cursor.fetchone() + if set: + self.exif_list = gtk.ListStore(gobject.TYPE_STRING, + gobject.TYPE_STRING) + for key in self.EXIF_DICT: + myiter = self.exif_list.insert_before(None, None) + self.exif_list.set_value(myiter, 0, self.EXIF_DICT[key]) + self.exif_list.set_value(myiter, 1, set[key]) + + retval['exif'] = True return retval def get_source(self, path): @@ -627,17 +664,33 @@ class MainModel(ModelMT): tag_id INTEGER);""") self.db_cursor.execute("""create table groups(id INTEGER PRIMARY KEY AUTOINCREMENT, - name TEXT, - color TEXT);""") + name TEXT, + color TEXT);""") self.db_cursor.execute("""create table thumbnails(id INTEGER PRIMARY KEY AUTOINCREMENT, - file_id INTEGER, - filename TEXT);""") + file_id INTEGER, + filename TEXT);""") self.db_cursor.execute("""create table images(id INTEGER PRIMARY KEY AUTOINCREMENT, - file_id INTEGER, - thumbnail TEXT, - filename TEXT);""") + file_id INTEGER, + thumbnail TEXT, + filename TEXT);""") + self.db_cursor.execute("""create table + exif(id INTEGER PRIMARY KEY AUTOINCREMENT, + file_id INTEGER, + camera TEXT, + date TEXT, + aperture TEXT, + exposure_program TEXT, + exposure_bias TEXT, + iso TEXT, + focal_length TEXT, + subject_distance TEXT, + metering_mode TEXT, + flash TEXT, + light_source TEXT, + resolution TEXT, + orientation TEXT);""") self.db_cursor.execute("insert into files values(1, 1, 'root', null, 0, 0, 0, 0, null, null);") self.db_cursor.execute("insert into groups values(1, 'default', 'black');") @@ -802,6 +855,8 @@ class MainModel(ModelMT): # fetch details about files if self.config.confd['retrive']: update = True + exif = None + sql = """select seq FROM sqlite_sequence WHERE name='files'""" db_cursor.execute(sql) fileid = db_cursor.fetchone()[0] @@ -816,9 +871,36 @@ class MainModel(ModelMT): db_cursor.execute(sql, (fileid, tpath.split(self.internal_dirname)[1][1:])) - if self.config.confd['exif']: - # TODO: exif implementation - pass + # exif - stroe data in exif table + if self.config.confd['exif'] and ext in ['jpg', 'jpeg']: + p = None + if self.config.confd['thumbs'] and exif: + p = ParseExif(exif_dict=exif) + else: + p = ParseExif(exif_file=current_file) + if not p.exif_dict: + p = None + if p: + p = p.parse() + p = list(p) + p.insert(0, fileid) + sql = """INSERT INTO exif (file_id, + camera, + date, + aperture, + exposure_program, + exposure_bias, + iso, + focal_length, + subject_distance, + metering_mode, + flash, + light_source, + resolution, + orientation) + values(?,?,?,?,?,?,?,?,?,?,?,?,?,?)""" + db_cursor.execute(sql, (tuple(p))) + # Extensions - user defined actions if ext in self.config.confd['extensions'].keys(): diff --git a/src/utils/parse_exif.py b/src/utils/parse_exif.py new file mode 100644 index 0000000..2dda618 --- /dev/null +++ b/src/utils/parse_exif.py @@ -0,0 +1,107 @@ +import re +import EXIF +class ParseExif(object): + def __init__(self, exif_dict=None, exif_file=None): + self.camera = None + self.date = None + self.aperture = None + self.exposure_program = None + self.exposure_bias = None + self.iso = None + self.focal_length = None + self.subject_distance = None + self.metering_mode = None + self.flash = None + self.light_source = None + self.resolution = None + self.orientation = None + self.exif_dict = exif_dict + if not self.exif_dict: + try: + f = open(exif_file, 'rb') + e = EXIF.process_file(f) + if len(e.keys()) >0: + self.exif_dict = e + f.close() + except: + pass + def parse(self): + try: + self.camera = "%s" % self.exif_dict['Image Make'] + self.camera = self.camera.strip() + except: pass + try: + model = "%s" % self.exif_dict['Image Model'] + self.camera += ", " + model.strip() + except: pass + + try: + self.date = "%s" % self.exif_dict['EXIF DateTimeOriginal'] + p = re.compile('[\d,:]+') + if not p.match(self.date): + self.date = None + except: pass + + try: + self.aperture = "%s" % self.exif_dict['EXIF FNumber'] + if len(self.aperture.split("/")) == 2: + self.aperture += '.' + self.aperture = "%.1f" % eval(self.aperture) + self.aperture = "f/%.1f" % float(self.aperture) + self.aperture = self.aperture.replace('.',',') + except: pass + + try: self.exposure_program = "%s" % self.exif_dict['EXIF ExposureProgram'] + except: pass + + try: + self.exposure_bias = "%s" % self.exif_dict['EXIF ExposureBiasValue'] + if len(self.exposure_bias.split("/")) == 2: + self.exposure_bias += '.' + self.exposure_bias = "%.1f" % eval(self.exposure_bias) + self.exposure_bias = "%.1f" % float(self.exposure_bias) + self.exposure_bias = self.exposure_bias.replace('.',',') + except: pass + + try: self.iso = "%s" % self.exif_dict['EXIF ISOSpeedRatings'] + except: pass + + try: + self.focal_length = "%s" % self.exif_dict['EXIF FocalLength'] + if len(self.focal_length.split("/")) == 2: + self.focal_length += '.' + self.focal_length = "%.2f" % eval(self.focal_length) + self.focal_length = "%.2f mm" % float(self.focal_length) + self.focal_length = self.focal_length.replace('.',',') + except: pass + + try: + self.subject_distance = "%s" % self.exif_dict['EXIF SubjectDistance'] + if len(self.subject_distance.split("/")) == 2: + self.subject_distance += '.' + self.subject_distance = "%.3f" % eval(self.subject_distance) + self.subject_distance = "%.3f m" % float(self.subject_distance) + self.subject_distance = self.subject_distance.replace('.',',') + except: pass + + try: self.metering_mode = "%s" % self.exif_dict['EXIF MeteringMode'] + except: pass + + try: self.flash = "%s" % self.exif_dict['EXIF Flash'] + except: pass + + try: self.light_source = "%s" % self.exif_dict['EXIF LightSource'] + except: pass + + try: self.resolution = "%s" % self.exif_dict['Image XResolution'] + except: pass + try: self.resolution = self.resolution + " x %s" % self.exif_dict['Image YResolution'] + except: pass + try: self.resolution = self.resolution + " (%s)" % self.exif_dict['Image ResolutionUnit'] + except: pass + + try: self.orientation = "%s" % self.exif_dict['Image Orientation'] + except: pass + + return (self.camera, self.date, self.aperture, self.exposure_program, self.exposure_bias, self.iso, self.focal_length, self.subject_distance, self.metering_mode, self.flash, self.light_source, self.resolution, self.orientation) + #print self.date #self.camera, self.date, self.aperture, self.exposure_program, self.exposure_bias, self.iso, self.focal_length, self.subject_distance, self.metering_mode, self.flash, self.light_source, self.resolution, self.flash, self.orientation