From fe5bb49fa498a2308326d9009039fafb9722a3a0 Mon Sep 17 00:00:00 2001 From: gryf Date: Wed, 26 Mar 2008 06:31:35 +0000 Subject: [PATCH] * Added thumbnals generating for images. * Lots of fixes. --- resources/glade/config.glade | 133 ++++++++-------- resources/glade/main.glade | 7 +- src/ctrls/c_config.py | 53 ++++++- src/models/m_config.py | 76 +++++----- src/models/m_main.py | 285 +++++++++++++++++++++++++++++------ 5 files changed, 398 insertions(+), 156 deletions(-) diff --git a/resources/glade/config.glade b/resources/glade/config.glade index e763ee7..506f255 100644 --- a/resources/glade/config.glade +++ b/resources/glade/config.glade @@ -89,16 +89,57 @@ 3 3 - + True 0 - Eject program: - ejt_entry + Mount point: + mnt_entry + + + + + + + 100 + True + True + + + 1 + 2 + + + + + + True + True + Browse... + True + 0 + + + + + 2 + 3 + + + + + + + 100 + True + True + + + 1 + 2 1 2 - @@ -122,56 +163,15 @@ - - 100 - True - True - - - 1 - 2 - 1 - 2 - - - - - - True - True - Browse... - True - 0 - - - - - 2 - 3 - - - - - - - 100 - True - True - - - 1 - 2 - - - - - + True 0 - Mount point: - mnt_entry + Eject program: + ejt_entry + 1 + 2 @@ -554,7 +554,19 @@ Movies extesions 2 3 - + + True + GDK_POINTER_MOTION_MASK | GDK_POINTER_MOTION_HINT_MASK | GDK_BUTTON_PRESS_MASK | GDK_BUTTON_RELEASE_MASK + Extension: + GTK_JUSTIFY_RIGHT + ext_entry + + + GTK_FILL + + + + True True GDK_POINTER_MOTION_MASK | GDK_POINTER_MOTION_HINT_MASK | GDK_BUTTON_PRESS_MASK | GDK_BUTTON_RELEASE_MASK @@ -564,18 +576,6 @@ Movies extesions 2 - - - True - GDK_POINTER_MOTION_MASK | GDK_POINTER_MOTION_HINT_MASK | GDK_BUTTON_PRESS_MASK | GDK_BUTTON_RELEASE_MASK - Extension: - GTK_JUSTIFY_RIGHT - ext_add - - - GTK_FILL - - False @@ -589,23 +589,25 @@ Movies extesions 3 GTK_BUTTONBOX_END - + True True True GDK_POINTER_MOTION_MASK | GDK_POINTER_MOTION_HINT_MASK | GDK_BUTTON_PRESS_MASK | GDK_BUTTON_RELEASE_MASK Add 0 + - + True True True GDK_POINTER_MOTION_MASK | GDK_POINTER_MOTION_HINT_MASK | GDK_BUTTON_PRESS_MASK | GDK_BUTTON_RELEASE_MASK Delete 0 + 1 @@ -630,7 +632,6 @@ Movies extesions True True GDK_POINTER_MOTION_MASK | GDK_POINTER_MOTION_HINT_MASK | GDK_BUTTON_PRESS_MASK | GDK_BUTTON_RELEASE_MASK - True True diff --git a/resources/glade/main.glade b/resources/glade/main.glade index 9cab256..e80e8a0 100644 --- a/resources/glade/main.glade +++ b/resources/glade/main.glade @@ -233,11 +233,9 @@ - + + True - GDK_POINTER_MOTION_MASK | GDK_POINTER_MOTION_HINT_MASK | GDK_BUTTON_PRESS_MASK | GDK_BUTTON_RELEASE_MASK - menuitem1 - True @@ -259,6 +257,7 @@ Thumbnails True True + list1 diff --git a/src/ctrls/c_config.py b/src/ctrls/c_config.py index 61ce782..ef9d3d2 100644 --- a/src/ctrls/c_config.py +++ b/src/ctrls/c_config.py @@ -22,6 +22,7 @@ # ------------------------------------------------------------------------- from gtkmvc import Controller +import views.v_dialogs as Dialogs import gtk @@ -66,7 +67,7 @@ class ConfigController(Controller): # initialize models for files extensions self.view['ext_choose'].set_model(self.model.ext_list) self.view['ext_choose'].set_active(0) - + self.view['extension_tree'].get_selection().set_mode(gtk.SELECTION_MULTIPLE) self.view['config'].show(); return # Podłącz sygnały: @@ -125,6 +126,50 @@ class ConfigController(Controller): return def on_ext_choose_changed(self, widget): + self.__setup_extension_tree() + self.view['ext_entry'].set_text('') + return + + def on_ext_add_clicked(self, widget): + ext = self.view['ext_entry'].get_text().lower() + if len(ext) == 0: + Dialogs.Err("Config - pyGTKtalog", "Error", "Extension is empty") + return + if self.view['ext_choose'].get_active() == 0: + if ext not in self.model.confd['img_ext']: + self.model.confd['img_ext'].append(ext) + self.model.refresh_ext('img_ext') + else: + self.model.confd['mov_ext'].append(ext) + + self.__setup_extension_tree() + return + + def on_ext_del_clicked(self, widget): + model, selection = self.view['extension_tree'].get_selection().get_selected_rows() + if len(selection) == 0: + Dialogs.Err("Config - pyGTKtalog", "Error", "No item selected") + return + elif len(selection) == 1: + sufix = '' + else: + sufix = "s" + + if self.model.confd['delwarn']: + obj = Dialogs.Qst('Delete extension%s' % sufix, + 'Delete extension%s?' % sufix, + 'Object%s will be permanently removed.' % sufix) + if not obj.run(): + return + + if self.view['ext_choose'].get_active() == 0: + n = 'img_ext' + else: + n = 'mov_ext' + + for i in selection: + self.model.confd[n].remove(model.get_value(model.get_iter(i), 0)) + self.__setup_extension_tree() return @@ -132,9 +177,11 @@ class ConfigController(Controller): # private controller methods def __setup_extension_tree(self): if self.view['ext_choose'].get_active() == 0: - self.view['extension_tree'].set_model(self.model.images_tree) + self.model.refresh_ext('img_ext') else: - self.view['extension_tree'].set_model(self.model.movies_tree) + self.model.refresh_ext('mov_ext') + + self.view['extension_tree'].set_model(self.model.ext_tree) for i in self.view['extension_tree'].get_columns(): self.view['extension_tree'].remove_column(i) diff --git a/src/models/m_config.py b/src/models/m_config.py index e7218f3..fa40065 100644 --- a/src/models/m_config.py +++ b/src/models/m_config.py @@ -56,33 +56,39 @@ class ConfigModel(Model): __properties__ = {} - extensions_list = ['Images extensions', 'Movies extesions'] + filetype_list = ['Images', 'Movies'] confd = { - 'savewin' : True, - 'savepan' : True, - 'wx' : 800, - 'wy' : 600, - 'h' : 200, - 'v' : 300, - 'exportxls' : False, - 'cd' : '/cdrom', - 'ejectapp' : 'eject -r', - 'eject' : True, - 'thumbs': False, - 'gthumb':False, - 'exif':False, - 'confirmquit':True, - 'mntwarn':True, - 'confirmabandon':True, + 'savewin': True, + 'savepan': True, + 'wx': 800, + 'wy': 600, + 'h': 200, + 'v': 300, + 'eject': True, + 'compress': True, + + 'exportxls': False, + + 'confirmquit': True, + 'confirmabandon': True, + 'mntwarn': True, + 'delwarn': True, + + 'cd': '/cdrom', + 'ejectapp': 'eject -r', + + 'retrive': False, + + 'thumbs': True, + 'exif': True, + 'gthumb': False, + + 'mov_ext': ['avi', 'mkv', 'mpg', 'mpeg', 'wmv'], + 'img_ext': ['bmp', 'gif', 'jpg', 'jpeg', 'png'], + 'showtoolbar':True, 'showstatusbar':True, - 'delwarn':True, - 'compress':True, - 'compress':True, - 'retrive':False, - 'mov_ext':['mkv', 'avi', 'ogg', 'mpg', 'wmv', 'mp4', 'mpeg'], - 'img_ext':['jpg','jpeg','png','gif','bmp','tga','tif','tiff','ilbm','iff','pcx'], } dictconf = { @@ -141,27 +147,27 @@ class ConfigModel(Model): def __init__(self): Model.__init__(self) self.category_tree = gtk.ListStore(gobject.TYPE_STRING) - self.images_tree = gtk.ListStore(gobject.TYPE_STRING) - self.confd['img_ext'].sort() - for i in self.confd['img_ext']: - myiter = self.images_tree.insert_before(None,None) - self.images_tree.set_value(myiter,0,i) - self.movies_tree = gtk.ListStore(gobject.TYPE_STRING) - self.confd['mov_ext'].sort() - for i in self.confd['mov_ext']: - myiter = self.movies_tree.insert_before(None,None) - self.movies_tree.set_value(myiter,0,i) - self.ext_list = gtk.ListStore(gobject.TYPE_STRING) - for i in self.extensions_list: + for i in self.filetype_list: myiter = self.ext_list.insert_before(None,None) self.ext_list.set_value(myiter,0,i) + + self.refresh_ext('img_ext') return + + + def refresh_ext(self, key): + self.ext_tree = gtk.ListStore(gobject.TYPE_STRING) + self.confd[key].sort() + for i in self.confd[key]: + myiter = self.ext_tree.insert_before(None,None) + self.ext_tree.set_value(myiter,0,i) def save(self): try: os.lstat("%s/.pygtktalog" % self.path) except: + print "Saving preferences to %s/.pygtktalog" % self.path if __debug__: print "m_config.py: save() Saving preferences to %s/.pygtktalog" % self.path newIni = Ini() diff --git a/src/models/m_main.py b/src/models/m_main.py index 2448926..ff4251c 100644 --- a/src/models/m_main.py +++ b/src/models/m_main.py @@ -55,9 +55,159 @@ from m_config import ConfigModel from m_details import DetailsModel class Thumbnail(object): - def __init__(self, *args): - self.x = None - self.y = None + def __init__(self, filename=None, x=160, y=120, root='thumbnails', base=''): + self.root = root + self.x = x + self.y = y + self.filename = filename + self.base = base + + def save(self, image_id): + """Save thumbnail into specific directory structure + return full path to the file and exif object or None""" + filepath = os.path.join(self.base, self.__get_and_make_path(image_id)) + f = open(self.filename, 'rb') + exif = None + returncode = -1 + try: + exif = EXIF.process_file(f) + f.close() + if exif.has_key('JPEGThumbnail'): + thumbnail = exif['JPEGThumbnail'] + f = open(filepath,'wb') + f.write(thumbnail) + f.close() + if exif.has_key('Image Orientation'): + orientation = exif['Image Orientation'].values[0] + if orientation > 1: + t = "/tmp/thumb%d.jpg" % datetime.now().microsecond + im_in = Image.open(filepath) + im_out = None + if orientation == 8: + # Rotated 90 CCW + im_out = im_in.transpose(Image.ROTATE_90) + elif orientation == 6: + # Rotated 90 CW + im_out = im_in.transpose(Image.ROTATE_270) + elif orientation == 3: + # Rotated 180 + im_out = im_in.transpose(Image.ROTATE_180) + elif orientation == 2: + # Mirrored horizontal + im_out = im_in.transpose(Image.FLIP_LEFT_RIGHT) + elif orientation == 4: + # Mirrored vertical + im_out = im_in.transpose(Image.FLIP_TOP_BOTTOM) + elif orientation == 5: + # Mirrored horizontal then rotated 90 CCW + im_out = im_in.transpose(Image.FLIP_LEFT_RIGHT).transpose(Image.ROTATE_90) + elif orientation == 7: + # Mirrored horizontal then rotated 90 CW + im_out = im_in.transpose(Image.FLIP_LEFT_RIGHT).transpose(Image.ROTATE_270) + + if im_out: + im_out.save(t, 'JPEG') + shutil.move(t, filepath) + else: + f.close() + returncode = 0 + else: + im = self.__scale_image(True) + if im: + im.save(filepath, "JPEG") + returncode = 1 + except: + f.close() + im = self.__scale_image(True) + if im: + im.save(filepath, "JPEG") + returncode = 2 + return filepath, exif, returncode + + # private class functions + def __get_and_make_path(self, img_id): + """Make directory structure regards of id + and return filepath WITHOUT extension""" + t = os.path.join(self.base, self.root) + try: os.mkdir(t) + except: pass + h = hex(img_id) + if len(h[2:])>6: + try: os.mkdir(os.path.join(t, h[2:4])) + except: pass + try: os.mkdir(os.path.join(t, h[2:4], h[4:6])) + except: pass + path = os.path.join(t, h[2:4], h[4:6], h[6:8]) + try: os.mkdir(path) + except: pass + img = "%s.%s" % (h[8:], 'jpg') + elif len(h[2:])>4: + try: os.mkdir(os.path.join(t, h[2:4])) + except: pass + path = os.path.join(t, h[2:4], h[4:6]) + try: os.mkdir(path) + except: pass + img = "%s.%s" % (h[6:], 'jpg') + elif len(h[2:])>2: + path = os.path.join(t, h[2:4]) + try: os.mkdir(path) + except: pass + img = "%s.%s" %(h[4:], 'jpg') + else: + path = t + img = "%s.%s" %(h[2:], 'jpg') + return(os.path.join(t, img)) + + def __scale_image(self, factor=False): + """generate scaled Image object for given file + args: + factor - if False, adjust height into self.y + if True, use self.x for scale portrait pictures height. + returns Image object, or False + """ + try: + im = Image.open(self.filename).convert('RGB') + except: + return False + x, y = im.size + + if x > self.x or y > self.y: + if x==y: + # square + imt = im.resize((self.y, self.y), Image.ANTIALIAS) + elif x > y: + # landscape + if int(y/(x/float(self.x))) > self.y: + # landscape image: height is non standard + self.x1 = int(float(self.y) * self.y / self.x) + if float(self.y) * self.y / self.x - self.x1 > 0.49: + self.x1 += 1 + imt = im.resize(((int(x/(y/float(self.y))),self.y)),Image.ANTIALIAS) + elif x/self.x==y/self.y: + # aspect ratio ok + imt = im.resize((self.x, self.y), Image.ANTIALIAS) + else: + imt = im.resize((self.x,int(y/(x/float(self.x)))), 1) + else: + # portrait + if factor: + if y>self.x: + imt = im.resize(((int(x/(y/float(self.x))),self.x)),Image.ANTIALIAS) + else: + imt = im + else: + self.x1 = int(float(self.y) * self.y / self.x) + if float(self.y) * self.y / self.x - self.x1 > 0.49: + self.x1 += 1 + + if x/self.x1==y/self.y: + # aspect ratio ok + imt = im.resize((self.x1,self.y),Image.ANTIALIAS) + else: + imt = im.resize(((int(x/(y/float(self.y))),self.y)),Image.ANTIALIAS) + return imt + else: + return im class Picture(object): def __init__(self, *args): @@ -163,7 +313,39 @@ class MainModel(ModelMT): return os.chdir(self.internal_dirname) - tar.extractall() + try: + tar.extractall() + except AttributeError: + # python's 2.4 tarfile module lacks of method extractall() + directories = [] + for tarinfo in tar: + if tarinfo.isdir(): + # Extract directory with a safe mode, so that + # all files below can be extracted as well. + try: + os.makedirs(os.path.join(path, tarinfo.name), 0777) + except EnvironmentError: + pass + directories.append(tarinfo) + else: + tar.extract(tarinfo, '.') + + # Reverse sort directories. + directories.sort(lambda a, b: cmp(a.name, b.name)) + directories.reverse() + + # Set correct owner, mtime and filemode on directories. + for tarinfo in directories: + path = os.path.join(path, tarinfo.name) + try: + os.chown(tarinfo, '.') + os.utime(tarinfo, '.') + os.chmod(tarinfo, '.') + except ExtractError, e: + if tar.errorlevel > 1: + raise + else: + tar._dbg(1, "tarfile: %s" % e) tar.close() self.__connect_to_db() @@ -218,9 +400,12 @@ class MainModel(ModelMT): self.files_list.set_value(myiter, 6, gtk.STOCK_DIRECTORY) # all the rest - self.db_cursor.execute("SELECT id, filename, size, date, type \ - FROM files WHERE parent_id=? AND type!=1 \ - ORDER BY filename", (id,)) + self.db_cursor.execute("SELECT f.id, f.filename, f.size, f.date, \ + f.type, f.filetype, t.filename \ + FROM files f \ + LEFT JOIN thumbnails t ON f.id = t.file_id \ + WHERE f.parent_id=? AND f.type!=1 \ + ORDER BY f.filename", (id,)) data = self.db_cursor.fetchall() for ch in data: myiter = self.files_list.insert_before(None, None) @@ -231,7 +416,10 @@ class MainModel(ModelMT): self.files_list.set_value(myiter, 4, ch[4]) self.files_list.set_value(myiter, 5, 'kategoria srategoria') if ch[4] == self.FIL: - self.files_list.set_value(myiter, 6, gtk.STOCK_FILE) + if ch[5] == self.F_IMG and ch[6] != None: + self.files_list.set_value(myiter, 6, gtk.STOCK_FILE) + else: + self.files_list.set_value(myiter, 6, gtk.STOCK_FILE) elif ch[4] == self.LIN: self.files_list.set_value(myiter, 6, gtk.STOCK_INDEX) return @@ -372,6 +560,10 @@ class MainModel(ModelMT): thumb_x INTEGER, thumb_y INTEGER, thumb_mode TEXT);""") + self.db_cursor.execute("""create table + thumbnails(id INTEGER PRIMARY KEY AUTOINCREMENT, + file_id INTEGER, + filename TEXT);""") self.db_cursor.execute("insert into files values(1, 1, 'root', null, \ 0, 0, 0, 0, null, null, null, null);") @@ -388,10 +580,6 @@ class MainModel(ModelMT): timestamp = datetime.now() - # TODO: file types has to be moved to configuration model - mov_ext = ('mkv', 'avi', 'ogg', 'mpg', 'wmv', 'mp4', 'mpeg') - img_ext = ('jpg','jpeg','png','gif','bmp','tga','tif','tiff','ilbm','iff','pcx') - # count files in directory tree count = 0 self.statusmsg = "Calculating number of files in directory tree..." @@ -409,6 +597,7 @@ class MainModel(ModelMT): step = 1.0/count else: step = 1.0 + self.count = 0 # guess filesystem encoding @@ -424,7 +613,7 @@ class MainModel(ModelMT): _size = size - myit = self.discs_tree.append(discs_tree_iter,None) + myit = self.discs_tree.append(discs_tree_iter, None) if parent_id == 1: self.fresh_disk_iter = myit @@ -453,7 +642,7 @@ class MainModel(ModelMT): self.discs_tree.set_value(myit,3,parent_id) try: - root,dirs,files = os.walk(path).next() + root, dirs, files = os.walk(path).next() except: if __debug__: print "m_main.py: cannot access ", path @@ -501,42 +690,15 @@ class MainModel(ModelMT): if self.abort: break self.count = self.count + 1 + current_path = os.path.join(root,i) try: - st = os.stat(os.path.join(root,i)) + st = os.stat(current_path) st_mtime = st.st_mtime st_size = st.st_size except OSError: st_mtime = 0 st_size = 0 - ### TODO: scan files - if self.config.confd['retrive']: - pass - #if i.split('.').[-1].lower() in mov_ext: - # # video only - # info = filetypeHelper.guess_video(os.path.join(root,i)) - - - if i.split('.')[-1].lower() in img_ext: - exif_info = EXIF.process_file(open(os.path.join(path,i), - 'rb')) - if exif_info.has_key('JPEGThumbnail'): - print "%s got thumbnail" % i - else: - print "%s has not got thumbnail" % i - - # pass - ### end of scan - - ### progress/status - # if wobj.sbid != 0: - # wobj.status.remove(wobj.sbSearchCId, wobj.sbid) - if self.count % 32 == 0: - self.statusmsg = "Scannig: %s" % (os.path.join(root,i)) - self.progress = step * self.count - # # PyGTK FAQ entry 23.20 - # while gtk.events_pending(): gtk.main_iteration() - _size = _size + st_size j = i if self.fsenc: @@ -548,7 +710,36 @@ class MainModel(ModelMT): """ db_cursor.execute(sql, (currentid, j, os.path.join(path,i), st_mtime, st_size, self.FIL)) - + + if self.count % 32 == 0: + update = True + else: + update = False + + ########################### + # fetch details about files + if self.config.confd['retrive']: + update = True + sql = """select seq FROM sqlite_sequence WHERE name='files'""" + db_cursor.execute(sql) + fileid=db_cursor.fetchone()[0] + if i.split('.')[-1].lower() in self.config.confd['img_ext']: + sql = """UPDATE files set filetype = ? where id = ?""" + db_cursor.execute(sql, (self.F_IMG, fileid)) + if self.config.confd['thumbs']: + path, exif, ret_code = Thumbnail(current_path, base=self.internal_dirname).save(fileid) + if ret_code != -1: + sql = """insert into thumbnails(file_id, filename) values (?, ?)""" + db_cursor.execute(sql, (fileid, path.split(self.internal_dirname)[1][1:])) + + #if i.split('.').[-1].lower() in mov_ext: + # # video only + # info = filetypeHelper.guess_video(os.path.join(root,i)) + ### end of scan + if update: + self.statusmsg = "Scannig: %s" % current_path + self.progress = step * self.count + sql = """update files set size=? where id=?""" db_cursor.execute(sql,(_size, currentid)) if self.abort: @@ -628,8 +819,7 @@ class MainModel(ModelMT): # launch scanning. get_children() if __debug__: - print "m_main.py: __fetch_db_into_treestore() tree generation time: ", - (datetime.now() - start_date) + print "m_main.py: __fetch_db_into_treestore() tree generation time: ", (datetime.now() - start_date) db_connection.close() return @@ -697,8 +887,7 @@ class MainModel(ModelMT): # launch scanning. get_children() if __debug__: - print "m_main.py: __fetch_db_into_treestore() tree generation time: ", - (datetime.now() - start_date) + print "m_main.py: __fetch_db_into_treestore() tree generation time: ", (datetime.now() - start_date) db_connection.close() return