1
0
mirror of https://github.com/gryf/pygtktalog.git synced 2025-12-17 11:30:19 +01:00

changes in tests, upgrade to new EXIF module, etc

This commit is contained in:
2009-04-07 19:40:15 +00:00
parent fb920f58bc
commit 5e8c33f05a
14 changed files with 1241 additions and 741 deletions

5
README
View File

@@ -24,9 +24,8 @@ REQUIREMENTS
pyGTKtalog is written in python with following dependencies: pyGTKtalog is written in python with following dependencies:
- python 2.4 or higher - python 2.5 or higher
- pygtk 2.10 or higher <http://www.pygtk.org> - pygtk 2.12 or higher <http://www.pygtk.org>
- pysqlite2 <http://pysqlite.org/> (unnecessary, if python 2.5 is used)
Optional modules: Optional modules:

View File

@@ -1,110 +1,62 @@
# This Python file uses the following encoding: utf-8 """
# Project: pyGTKtalog
# Author: Roman 'gryf' Dobosz gryf@elysium.pl Description: Application main launch file.
# Type: core
# Copyright (C) 2007 by Roman 'gryf' Dobosz Author: Roman 'gryf' Dobosz, gryf73@gmail.com
# Created: 2007-05-01
# This file is part of pyGTKtalog. """
#
# 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., 51 Franklin St, Fifth Floor, Boston, MA 02110-1301 USA
# -------------------------------------------------------------------------
import sys import sys
import os import os
try: import locale
import gtk import gettext
except ImportError:
print "You need to install pyGTK v2.10.x or newer."
raise
def setup_path(): import gtk
"""Sets up the python include paths to include needed directories""" import pygtk
import os.path pygtk.require("2.0")
from src.lib.globs import TOPDIR import gtkmvc
sys.path = [os.path.join(TOPDIR, "src")] + sys.path gtkmvc.require("1.2.2")
return
from src.lib.globs import TOPDIR
from src.lib.globs import APPL_SHORT_NAME
sys.path = [os.path.join(TOPDIR, "src")] + sys.path
from models.m_config import ConfigModel
from models.m_main import MainModel
from ctrls.c_main import MainController
from views.v_main import MainView
def check_requirements(): def check_requirements():
"""Checks versions and other requirements""" """Checks versions and other requirements"""
import sys
import gtkmvc
gtkmvc.require("1.2.0")
try:
from models.m_config import ConfigModel
except ImportError:
print "Some fundamental files are missing.",
print "Try runnig pyGTKtalog in his root directory"
raise
conf = ConfigModel() conf = ConfigModel()
conf.load() conf.load()
try:
import pygtk
#tell pyGTK, if possible, that we want GTKv2
pygtk.require("2.0")
except ImportError:
#Some distributions come with GTK2, but not pyGTK
pass
try:
import sqlite3 as sqlite
except ImportError:
try:
from pysqlite2 import dbapi2 as sqlite
except ImportError:
print "pyGTKtalog uses SQLite DB.\nYou'll need to get it and the",
print "python bindings as well.",
print "http://www.sqlite.org"
print "http://initd.org/tracker/pysqlite"
print "Alternatively install python 2.5 or higher"
raise
if conf.confd['exportxls']:
try:
import pyExcelerator
except ImportError:
print "WARNING: You'll need pyExcelerator, if you want to export",
print "DB to XLS format."
print "http://sourceforge.net/projects/pyexcelerator"
if conf.confd['thumbs'] and conf.confd['retrive']: if conf.confd['thumbs'] and conf.confd['retrive']:
try: try:
import Image import Image
except ImportError: except ImportError:
print "WARNING: You'll need Python Imaging Library (PIL), if you", print _("WARNING: You'll need Python Imaging Library (PIL), if "
print "want to make\nthumbnails!" "you want to make thumbnails!")
raise
return return
if __name__ == "__main__": def run():
"""Create model, controller and view and launch it."""
# Directory from where pygtkatalog was invoced. We need it for calculate # Directory from where pygtkatalog was invoced. We need it for calculate
# path for argument (catalog file) # path for argument (catalog file)
execution_dir = os.path.abspath(os.path.curdir) execution_dir = os.path.abspath(os.path.curdir)
# Directory, where this files lies. We need it to setup private source # Directory, where this files lies. We need it to setup private source
# paths # paths
libraries_dir = os.path.dirname(__file__) libraries_dir = os.path.dirname(__file__)
os.chdir(libraries_dir) if libraries_dir:
os.chdir(libraries_dir)
setup_path() # Setup i18n
#check_requirements() locale.setlocale(locale.LC_ALL, '')
gettext.install(APPL_SHORT_NAME, 'locale', unicode=True)
from models.m_main import MainModel check_requirements()
from ctrls.c_main import MainController
from views.v_main import MainView
model = MainModel() model = MainModel()
if len(sys.argv) > 1: if len(sys.argv) > 1:
@@ -115,6 +67,9 @@ if __name__ == "__main__":
try: try:
gtk.main() gtk.main()
except KeyboardInterrupt: except KeyboardInterrupt:
#model.config.save() model.config.save()
#model.cleanup() model.cleanup()
gtk.main_quit gtk.main_quit
if __name__ == "__main__":
run()

View File

@@ -252,7 +252,7 @@ class MainController(Controller):
except TypeError: except TypeError:
if __debug__: if __debug__:
print "c_main.py: on_edit2_activate(): 0", print "c_main.py: on_edit2_activate(): 0",
print "zaznaczonych wierszy" print "selected rows"
return return
val = self.model.get_file_info(id) val = self.model.get_file_info(id)
@@ -281,11 +281,9 @@ class MainController(Controller):
def on_remove_thumb1_activate(self, menu_item): def on_remove_thumb1_activate(self, menu_item):
if self.model.config.confd['delwarn']: if self.model.config.confd['delwarn']:
title = 'Delete thumbnails' obj = Dialogs.Qst(_("Delete thumbnails"), _("Delete thumbnails?"),
question = 'Delete thumbnails?' _("Thumbnails for selected items will be "
description = "Thumbnails for selected items will be permanently" "permanently removed from catalog."))
description += " removed from catalog."
obj = Dialogs.Qst(title, question, description)
if not obj.run(): if not obj.run():
return return
try: try:
@@ -307,11 +305,9 @@ class MainController(Controller):
def on_remove_image1_activate(self, menu_item): def on_remove_image1_activate(self, menu_item):
if self.model.config.confd['delwarn']: if self.model.config.confd['delwarn']:
title = 'Delete images' obj = Dialogs.Qst(_("Delete images"), _("Delete all images?"),
question = 'Delete all images?' _("All images for selected items will be "
description = 'All images for selected items will be permanently' "permanently removed from catalog."))
description += ' removed from catalog.'
obj = Dialogs.Qst(title, question, description)
if not obj.run(): if not obj.run():
return return
try: try:
@@ -343,8 +339,8 @@ class MainController(Controller):
else: else:
ImageView(img) ImageView(img)
else: else:
Dialogs.Inf("Image view", "No Image", Dialogs.Inf(_("Image view"), _("No Image"),
"Image file does not exist.") _("Image file does not exist."))
def on_rename1_activate(self, widget): def on_rename1_activate(self, widget):
model, iter = self.view['discs'].get_selection().get_selected() model, iter = self.view['discs'].get_selection().get_selected()
@@ -492,11 +488,10 @@ class MainController(Controller):
# check if any unsaved project is on go. # check if any unsaved project is on go.
if self.model.unsaved_project and \ if self.model.unsaved_project and \
self.model.config.confd['confirmquit']: self.model.config.confd['confirmquit']:
title = 'Quit application - pyGTKtalog' if not Dialogs.Qst(_("Quit application") + " - pyGTKtalog",
question = 'Do you really want to quit?' _("Do you really want to quit?"),
description = "Current database is not saved, any changes will " _("Current database is not saved, any changes "
description += "be lost." "will be lost.")).run():
if not Dialogs.Qst(title, question, description).run():
return return
self.__store_settings() self.__store_settings()
self.model.cleanup() self.model.cleanup()
@@ -506,10 +501,10 @@ class MainController(Controller):
def on_new_activate(self, widget): def on_new_activate(self, widget):
"""Create new database file""" """Create new database file"""
if self.model.unsaved_project: if self.model.unsaved_project:
title = 'Unsaved data - pyGTKtalog' if not Dialogs.Qst(_("Unsaved data") + " - pyGTKtalog",
question = "Do you want to abandon changes?" _("Do you want to abandon changes?"),
desc = "Current database is not saved, any changes will be lost." _("Current database is not saved, any changes "
if not Dialogs.Qst(title, question, desc).run(): "will be lost.")).run():
return return
self.model.new() self.model.new()
@@ -525,8 +520,8 @@ class MainController(Controller):
def on_add_cd_activate(self, widget, label=None, current_id=None): def on_add_cd_activate(self, widget, label=None, current_id=None):
"""Add directory structure from cd/dvd disc""" """Add directory structure from cd/dvd disc"""
mount = device_helper.volmount(self.model.config.confd['cd']) mount, msg = device_helper.volmount(self.model.config.confd['cd'])
if mount == 'ok': if mount:
guessed_label = device_helper.volname(self.model.config.confd['cd']) guessed_label = device_helper.volname(self.model.config.confd['cd'])
if not label: if not label:
label = Dialogs.InputDiskLabel(guessed_label).run() label = Dialogs.InputDiskLabel(guessed_label).run()
@@ -543,10 +538,10 @@ class MainController(Controller):
device_helper.volumount(self.model.config.confd['cd']) device_helper.volumount(self.model.config.confd['cd'])
return True return True
else: else:
Dialogs.Wrn("Error mounting device - pyGTKtalog", Dialogs.Wrn(_("Error mounting device") + " - pyGTKtalog",
"Cannot mount device pointed to %s" % _("Cannot mount device pointed to %s") %
self.model.config.confd['cd'], self.model.config.confd['cd'],
"Last mount message:\n%s" % mount) _("Last mount message:\n%s") % msg)
return False return False
def on_add_directory_activate(self, widget, path=None, label=None, def on_add_directory_activate(self, widget, path=None, label=None,
@@ -599,6 +594,7 @@ class MainController(Controller):
def on_save_activate(self, widget): def on_save_activate(self, widget):
"""Save catalog to file""" """Save catalog to file"""
# FIXME: Traceback when recent is null
if self.model.filename: if self.model.filename:
self.model.save() self.model.save()
self.__set_title(filepath=self.model.filename) self.__set_title(filepath=self.model.filename)
@@ -618,8 +614,8 @@ class MainController(Controller):
self.model.config.add_recent(path) self.model.config.add_recent(path)
self.__set_title(filepath=path) self.__set_title(filepath=path)
else: else:
Dialogs.Err("Error writing file - pyGTKtalog", Dialogs.Err(_("Error writing file") + " - pyGTKtalog",
"Cannot write file %s." % path, "%s" % err) _("Cannot write file %s.") % path, "%s" % err)
def on_stat1_activate(self, menu_item, selected_id=None): def on_stat1_activate(self, menu_item, selected_id=None):
data = self.model.get_stats(selected_id) data = self.model.get_stats(selected_id)
@@ -640,9 +636,9 @@ class MainController(Controller):
"""Open catalog file""" """Open catalog file"""
confirm = self.model.config.confd['confirmabandon'] confirm = self.model.config.confd['confirmabandon']
if self.model.unsaved_project and confirm: if self.model.unsaved_project and confirm:
obj = Dialogs.Qst('Unsaved data - pyGTKtalog', obj = Dialogs.Qst(_("Unsaved data") + " - pyGTKtalog",
'There is not saved database', _("There is not saved database"),
'Pressing "Ok" will abandon catalog.') _("Pressing <b>Ok</b> will abandon catalog."))
if not obj.run(): if not obj.run():
return return
@@ -666,8 +662,8 @@ class MainController(Controller):
if path: if path:
if not self.model.open(path): if not self.model.open(path):
Dialogs.Err("Error opening file - pyGTKtalog", Dialogs.Err(_("Error opening file") + " - pyGTKtalog",
"Cannot open file %s." % path) _("Cannot open file %s.") % path)
else: else:
self.__generate_recent_menu() self.__generate_recent_menu()
self.__activate_ui(path) self.__activate_ui(path)
@@ -733,13 +729,14 @@ class MainController(Controller):
"""delete selected images (with thumbnails)""" """delete selected images (with thumbnails)"""
list_of_paths = self.view['images'].get_selected_items() list_of_paths = self.view['images'].get_selected_items()
if not list_of_paths: if not list_of_paths:
Dialogs.Inf("Delete images", "No images selected", Dialogs.Inf(_("Delete images"), _("No images selected"),
"You have to select at least one image to delete.") _("You have to select at least one image to delete."))
return return
if self.model.config.confd['delwarn']: if self.model.config.confd['delwarn']:
obj = Dialogs.Qst('Delete images', 'Delete selected images?', obj = Dialogs.Qst(_("Delete images"), _("Delete selected images?"),
'Selected images will be permanently removed from catalog.') _("Selected images will be permanently removed"
" from catalog."))
if not obj.run(): if not obj.run():
return return
@@ -768,7 +765,7 @@ class MainController(Controller):
def on_img_save_activate(self, menu_item): def on_img_save_activate(self, menu_item):
"""export images (not thumbnails) into desired direcotry""" """export images (not thumbnails) into desired direcotry"""
dialog = Dialogs.SelectDirectory("Choose directory to save images") dialog = Dialogs.SelectDirectory(_("Choose directory to save images"))
filepath = dialog.run() filepath = dialog.run()
if not filepath: if not filepath:
@@ -793,14 +790,13 @@ class MainController(Controller):
count += 1 count += 1
if count > 0: if count > 0:
Dialogs.Inf("Save images", Dialogs.Inf(_("Save images"),
"%d images was succsefully saved." % count, _("%d images was succsefully saved.") % count,
"Images are placed in directory:\n%s." % filepath) _("Images are placed in directory:\n%s.") % filepath)
else: else:
description = "Images probably don't have real images - only" description = _("Images probably don't have real images - only"
description += " thumbnails." " thumbnails.")
Dialogs.Inf("Save images", Dialogs.Inf(_("Save images"), _("No images was saved."),
"No images was saved.",
description) description)
return return
@@ -809,12 +805,12 @@ class MainController(Controller):
list_of_paths = self.view['images'].get_selected_items() list_of_paths = self.view['images'].get_selected_items()
if not list_of_paths: if not list_of_paths:
Dialogs.Inf("Set thumbnail", "No image selected", Dialogs.Inf(_("Set thumbnail"), _("No image selected"),
"You have to select one image to set as thumbnail.") _("You have to select one image to set as thumbnail."))
return return
if len(list_of_paths) >1: if len(list_of_paths) >1:
Dialogs.Inf("Set thumbnail", "To many images selected", Dialogs.Inf(_("Set thumbnail"), _("To many images selected"),
"You have to select one image to set as thumbnail.") _("You have to select one image to set as thumbnail."))
return return
model = self.view['images'].get_model() model = self.view['images'].get_model()
@@ -872,7 +868,7 @@ class MainController(Controller):
def on_export_activate(self, menu_item): def on_export_activate(self, menu_item):
"""export db file and coressponding images to tar.bz2 archive""" """export db file and coressponding images to tar.bz2 archive"""
dialog = Dialogs.ChooseFilename(None, "Choose export file") dialog = Dialogs.ChooseFilename(None, _("Choose export file"))
filepath = dialog.run() filepath = dialog.run()
if not filepath: if not filepath:
@@ -1018,8 +1014,8 @@ class MainController(Controller):
def on_delete_tag_activate(self, menu_item): def on_delete_tag_activate(self, menu_item):
ids = self.__get_tv_selection_ids(self.view['files']) ids = self.__get_tv_selection_ids(self.view['files'])
if not ids: if not ids:
Dialogs.Inf("Remove tags", "No files selected", Dialogs.Inf(_("Remove tags"), _("No files selected"),
"You have to select some files first.") _("You have to select some files first."))
return return
tags = self.model.get_tags_by_file_id(ids) tags = self.model.get_tags_by_file_id(ids)
@@ -1027,8 +1023,9 @@ class MainController(Controller):
d = Dialogs.TagsRemoveDialog(tags) d = Dialogs.TagsRemoveDialog(tags)
retcode, retval = d.run() retcode, retval = d.run()
if retcode=="ok" and not retval: if retcode=="ok" and not retval:
Dialogs.Inf("Remove tags", "No tags selected", Dialogs.Inf(_("Remove tags"), _("No tags selected"),
"You have to select any tag to remove from files.") _("You have to select any tag to remove from"
" files."))
return return
elif retcode == "ok" and retval: elif retcode == "ok" and retval:
self.model.delete_tags(ids, retval) self.model.delete_tags(ids, retval)
@@ -1053,7 +1050,7 @@ class MainController(Controller):
def on_add_image1_activate(self, menu_item): def on_add_image1_activate(self, menu_item):
dialog = Dialogs.LoadImageFile(True) dialog = Dialogs.LoadImageFile(True)
msg = "Don't copy images. Generate only thumbnails." msg = _("Don't copy images. Generate only thumbnails.")
toggle = gtk.CheckButton(msg) toggle = gtk.CheckButton(msg)
toggle.show() toggle.show()
dialog.dialog.set_extra_widget(toggle) dialog.dialog.set_extra_widget(toggle)
@@ -1117,15 +1114,15 @@ class MainController(Controller):
return return
if not selected_iter: if not selected_iter:
Dialogs.Inf("Delete disc", "No disc selected", Dialogs.Inf(_("Delete disc"), _("No disc selected"),
"You have to select disc first before you " +\ _("You have to select disc first before you "
"can delete it") "can delete it"))
return return
if self.model.config.confd['delwarn']: if self.model.config.confd['delwarn']:
name = model.get_value(selected_iter, 1) name = model.get_value(selected_iter, 1)
obj = Dialogs.Qst('Delete %s' % name, 'Delete %s?' % name, obj = Dialogs.Qst(_("Delete %s") % name, _("Delete %s?") % name,
'Object will be permanently removed.') _("Object will be permanently removed."))
if not obj.run(): if not obj.run():
return return
@@ -1168,14 +1165,14 @@ class MainController(Controller):
return return
if not list_of_paths: if not list_of_paths:
Dialogs.Inf("Delete files", "No files selected", Dialogs.Inf(_("Delete files"), _("No files selected"),
"You have to select at least one file to delete.") _("You have to select at least one file to delete."))
return return
if self.model.config.confd['delwarn']: if self.model.config.confd['delwarn']:
description = "Selected files and directories will be " obj = Dialogs.Qst(_("Delete files"), _("Delete files?"),
description += "permanently\n removed from catalog." _("Selected files and directories will be "
obj = Dialogs.Qst("Delete files", "Delete files?", description) "permanently\n removed from catalog."))
if not obj.run(): if not obj.run():
return return
@@ -1216,10 +1213,9 @@ class MainController(Controller):
def on_th_delete_activate(self, menu_item): def on_th_delete_activate(self, menu_item):
if self.model.config.confd['delwarn']: if self.model.config.confd['delwarn']:
title = 'Delete thumbnail' obj = Dialogs.Qst(_("Delete thumbnail"), _("Delete thumbnail?"),
question = 'Delete thumbnail?' _("Current thumbnail will be permanently removed"
dsc = "Current thumbnail will be permanently removed from catalog." " from catalog."))
obj = Dialogs.Qst(title, question, dsc)
if not obj.run(): if not obj.run():
return return
path, column = self.view['files'].get_cursor() path, column = self.view['files'].get_cursor()
@@ -1366,17 +1362,19 @@ class MainController(Controller):
msg = device_helper.eject_cd(ejectapp, msg = device_helper.eject_cd(ejectapp,
self.model.config.confd['cd']) self.model.config.confd['cd'])
if msg != 'ok': if msg != 'ok':
Dialogs.Wrn("error ejecting device - pyGTKtalog", title = _("Error ejecting device")
"Cannot eject device pointed to %s" % Dialogs.Wrn(title + " - pyGTKtalog",
_("Cannot eject device pointed to %s") %
self.model.config.confd['cd'], self.model.config.confd['cd'],
"Last eject message:\n%s" % msg) _("Last eject message:\n%s") % msg)
else: else:
msg = device_helper.volumount(self.model.config.confd['cd']) msg = device_helper.volumount(self.model.config.confd['cd'])
if msg != 'ok': if msg != 'ok':
Dialogs.Wrn("error unmounting device - pyGTKtalog", title = _("Error unmounting device")
"Cannot unmount device pointed to %s" % Dialogs.Wrn(title + " - pyGTKtalog",
_("Cannot unmount device pointed to %s") %
self.model.config.confd['cd'], self.model.config.confd['cd'],
"Last umount message:\n%s" % msg) _("Last umount message:\n%s") % msg)
return return
def property_progress_value_change(self, model, old, new): def property_progress_value_change(self, model, old, new):

File diff suppressed because it is too large Load Diff

View File

@@ -1,32 +1,23 @@
# This Python file uses the following encoding: utf-8 """
# Project: pyGTKtalog
# Author: Roman 'gryf' Dobosz gryf@elysium.pl Description: Simple functions for device management.
# Type: lib
# Copyright (C) 2007 by Roman 'gryf' Dobosz Author: Roman 'gryf' Dobosz, gryf73@gmail.com
# Created: 2008-12-15
# This file is part of pyGTKtalog. """
#
# 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., 51 Franklin St, Fifth Floor, Boston, MA 02110-1301 USA
# -------------------------------------------------------------------------
import os import os
import locale
import gettext
from src.lib.globs import APPL_SHORT_NAME
locale.setlocale(locale.LC_ALL, '')
gettext.install(APPL_SHORT_NAME, 'locale', unicode=True)
def volname(mntp): def volname(mntp):
"""read volume name from cd/dvd""" """read volume name from cd/dvd"""
dev = mountpoint_to_dev(mntp) dev = mountpoint_to_dev(mntp)
label = None
if dev != None: if dev != None:
try: try:
disk = open(dev, "rb") disk = open(dev, "rb")
@@ -35,17 +26,20 @@ def volname(mntp):
disk.close() disk.close()
except IOError: except IOError:
return None return None
return label return label
return None
def volmount(mntp): def volmount(mntp):
"""mount device, return 'ok' or error message""" """
Mount device.
@param mountpoint
@returns tuple with bool status of mount, and string with error message
"""
_in, _out, _err = os.popen3("mount %s" % mntp) _in, _out, _err = os.popen3("mount %s" % mntp)
inf = _err.readlines() inf = _err.readlines()
if len(inf) > 0: if len(inf) > 0:
return inf[0].strip() return False, inf[0].strip()
else: else:
return 'ok' return True, ''
def volumount(mntp): def volumount(mntp):
"""mount device, return 'ok' or error message""" """mount device, return 'ok' or error message"""
@@ -88,5 +82,5 @@ def eject_cd(eject_app, cdrom):
return inf[0].strip() return inf[0].strip()
return 'ok' return 'ok'
return "Eject program not specified" return _("Eject program not specified")

View File

@@ -35,7 +35,7 @@ TOPDIR = top_dir
RESOURCES_DIR = os.path.join(TOPDIR, "resources") RESOURCES_DIR = os.path.join(TOPDIR, "resources")
GLADE_DIR = os.path.join(RESOURCES_DIR, "glade") GLADE_DIR = os.path.join(RESOURCES_DIR, "glade")
STYLES_DIR = os.path.join(RESOURCES_DIR, "styles") STYLES_DIR = os.path.join(RESOURCES_DIR, "styles")
APPL_SHORT_NAME = "pygtktalog2" APPL_SHORT_NAME = "pygtktalog"
APPL_VERSION = (1, 0, 2) APPL_VERSION = (1, 0, 2)
# ---------------------------------------------------------------------- # ----------------------------------------------------------------------

View File

@@ -27,7 +27,7 @@ import os
from datetime import date from datetime import date
class GthumbCommentParser(object): class GthumbCommentParser(object):
"""Read and return comments created eith gThumb program"""
def __init__(self, image_path, image_filename): def __init__(self, image_path, image_filename):
self.path = image_path self.path = image_path
self.filename = image_filename self.filename = image_filename
@@ -36,16 +36,16 @@ class GthumbCommentParser(object):
"""Return dictionary with apropriate fields, or None if no comment """Return dictionary with apropriate fields, or None if no comment
available""" available"""
try: try:
gf = gzip.open(os.path.join(self.path, gzf = gzip.open(os.path.join(self.path, '.comments',
'.comments', self.filename + '.xml')) self.filename + '.xml'))
except: except:
return None return None
try: try:
xml = gf.read() xml = gzf.read()
gf.close() gzf.close()
except: except:
gf.close() gzf.close()
return None return None
if not xml: if not xml:

View File

@@ -44,7 +44,6 @@ class Img(object):
"""Save image and asociated thumbnail into specific directory structure """Save image and asociated thumbnail into specific directory structure
returns filename for image""" returns filename for image"""
image_filename = path.join(self.base, self.sha512) image_filename = path.join(self.base, self.sha512)
thumbnail = path.join(self.base, self.sha512 + "_t") thumbnail = path.join(self.base, self.sha512 + "_t")

View File

@@ -1,7 +1,12 @@
# This Python file uses the following encoding: utf-8 """
Project: pyGTKtalog
Description: Gather video file information. Uses external tools.
Type: lib
Author: Roman 'gryf' Dobosz, gryf73@gmail.com
Created: 2008-12-15
"""
from os import popen from os import popen
from sys import argv, exit import sys
class Midentify(object): class Midentify(object):
"""Class for retrive midentify script output and put it in dict. """Class for retrive midentify script output and put it in dict.
@@ -16,48 +21,41 @@ class Midentify(object):
def get_data(self): def get_data(self):
"""return dict with clip information""" """return dict with clip information"""
output = popen("midentify \"%s\"" % self.filename).readlines() output = popen('midentify "%s"' % self.filename).readlines()
attrs = {'ID_VIDEO_WIDTH': ['width', int],
'ID_VIDEO_HEIGHT': ['height', int],
'ID_LENGTH': ['length', lambda x: int(x.split(".")[0])],
# length is in seconds
'ID_DEMUXER': ['container', str],
'ID_VIDEO_FORMAT': ['video_format', str],
'ID_VIDEO_CODEC': ['video_codec', str],
'ID_AUDIO_CODEC': ['audio_codec', str],
'ID_AUDIO_FORMAT': ['audio_format', str],
'ID_AUDIO_NCH': ['audio_no_channels', int],}
for line in output: for line in output:
line = line.strip() line = line.strip()
if "ID_VIDEO_WIDTH" in line: for attr in attrs:
self.tags['width'] = line.replace("ID_VIDEO_WIDTH=", "") if attr in line:
elif "ID_VIDEO_HEIGHT" in line: self.tags[attrs[attr][0]] = \
self.tags['height'] = line.replace("ID_VIDEO_HEIGHT=", "") attrs[attr][1](line.replace("%s=" % attr, ""))
elif "ID_LENGTH" in line:
length = line.replace("ID_LENGTH=", "") if 'length' in self.tags:
if "." in length: if self.tags['length'] > 0:
length = length.split(".")[0] hours = self.tags['length'] / 3600
seconds = int(length) seconds = self.tags['length'] - hours * 3600
if seconds > 0: minutes = seconds / 60
hours = seconds / 3600 seconds -= minutes * 60
seconds -= hours * 3600 length_str = "%02d:%02d:%02d" % (hours, minutes, seconds)
minutes = seconds / 60 self.tags['duration'] = length_str
seconds -= minutes * 60
self.tags['length'] = length
length_str = "%02d:%02d:%02d" % (hours, minutes, seconds)
self.tags['duration'] = length_str
elif "ID_DEMUXER" in line:
self.tags['container'] = line.replace("ID_DEMUXER=", "")
elif "ID_VIDEO_FORMAT" in line:
self.tags['video_format'] = line.replace("ID_VIDEO_FORMAT=", "")
elif "ID_VIDEO_CODEC" in line:
self.tags['video_codec'] = line.replace("ID_VIDEO_CODEC=", "")
elif "ID_AUDIO_CODEC" in line:
self.tags['audio_codec'] = line.replace("ID_AUDIO_CODEC=", "")
elif "ID_AUDIO_FORMAT" in line:
self.tags['audio_format'] = line.replace("ID_AUDIO_FORMAT=", "")
elif "ID_AUDIO_NCH" in line:
self.tags['audio_no_channels'] = line.replace("ID_AUDIO_NCH=",
"")
return self.tags return self.tags
if __name__ == "__main__": if __name__ == "__main__":
"""run as standalone script""" if len(sys.argv) < 2:
if len(argv) < 2: print "usage: %s filename" % sys.argv[0]
print "usage: %s filename" % argv[0] sys.exit()
exit()
for arg in argv[1:]: for arg in sys.argv[1:]:
mid = Midentify(arg) mid = Midentify(arg)
print mid.get_data() print mid.get_data()

View File

@@ -27,23 +27,17 @@ import sys
import shutil import shutil
import bz2 import bz2
import math import math
import sqlite3 as sqlite
from tempfile import mkstemp from tempfile import mkstemp
from datetime import datetime
import threading as _threading
import gtk import gtk
import gobject import gobject
from gtkmvc.model_mt import ModelMT from gtkmvc.model_mt import ModelMT
try:
import sqlite3 as sqlite
except ImportError:
from pysqlite2 import dbapi2 as sqlite
from datetime import datetime
import threading as _threading
from m_config import ConfigModel from m_config import ConfigModel
try: try:
from lib.thumbnail import Thumbnail from lib.thumbnail import Thumbnail
from lib.img import Img from lib.img import Img
@@ -53,6 +47,7 @@ from lib.parse_exif import ParseExif
from lib.gthumb import GthumbCommentParser from lib.gthumb import GthumbCommentParser
from lib.no_thumb import no_thumb as no_thumb_img from lib.no_thumb import no_thumb as no_thumb_img
from lib.video import Video
class MainModel(ModelMT): class MainModel(ModelMT):
"""Create, load, save, manipulate db file which is container for data""" """Create, load, save, manipulate db file which is container for data"""
@@ -91,6 +86,7 @@ class MainModel(ModelMT):
# images extensions - only for PIL and EXIF # images extensions - only for PIL and EXIF
IMG = ['jpg', 'jpeg', 'gif', 'png', 'tif', 'tiff', 'tga', 'pcx', 'bmp', IMG = ['jpg', 'jpeg', 'gif', 'png', 'tif', 'tiff', 'tga', 'pcx', 'bmp',
'xbm', 'xpm', 'jp2', 'jpx', 'pnm'] 'xbm', 'xpm', 'jp2', 'jpx', 'pnm']
MOV = ['avi', 'mpg', 'mpeg', 'mkv', 'wmv', 'ogm', 'mov']
def __init__(self): def __init__(self):
"""initialize""" """initialize"""
@@ -1754,6 +1750,23 @@ class MainModel(ModelMT):
ext = i.split('.')[-1].lower() ext = i.split('.')[-1].lower()
# Video
if ext in self.MOV:
#import rpdb2; rpdb2.start_embedded_debugger('pass')
v = Video(current_file)
cfn = v.capture()
img = Img(cfn, self.image_path)
th = img.save()
if th:
sql = """INSERT INTO
thumbnails(file_id, filename)
VALUES(?, ?)"""
db_cursor.execute(sql, (fileid, th+"_t"))
sql = """INSERT INTO images(file_id, filename)
VALUES(?, ?)"""
db_cursor.execute(sql, (fileid, th))
os.unlink(cfn)
# Images - thumbnails and exif data # Images - thumbnails and exif data
if self.config.confd['thumbs'] and ext in self.IMG: if self.config.confd['thumbs'] and ext in self.IMG:
thumb = Thumbnail(current_file, self.image_path) thumb = Thumbnail(current_file, self.image_path)

View File

@@ -1,53 +1,27 @@
#!/usr/bin/env python """
# This Python file uses the following encoding: utf-8 Project: pyGTKtalog
# Description: Test harvester and runner.
# Author: Roman 'gryf' Dobosz gryf@elysium.pl Type: exec
# Author: Roman 'gryf' Dobosz, gryf73@gmail.com
# Copyright (C) 2007 by Roman 'gryf' Dobosz Created: 2008-12-15
# """
# This file is part of pyGTKtalog.
#
# 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., 51 Franklin St, Fifth Floor, Boston, MA 02110-1301 USA
# -------------------------------------------------------------------------
import sys import sys
import unittest import unittest
from os import path, chdir from os import path, chdir
import glob import glob
def my_import(module, name):
"""import replacement"""
mod = __import__(module, {}, {}, [name])
components = name.split('.')
for comp in components[1:]:
mod = getattr(mod, comp)
return mod
def setup_path(): def setup_path():
"""Sets up the python include paths to include needed directories""" """Sets up the python include paths to include needed directories"""
this_path = path.abspath(path.dirname(__file__)) this_path = path.abspath(path.dirname(__file__))
sys.path = [path.join(this_path, "../../src")] + sys.path sys.path = [path.join(this_path, "../../src")] + sys.path
sys.path = [path.join(this_path, "../../src/test")] + sys.path
sys.path = [path.join(this_path, "../../src/test/unit")] + sys.path sys.path = [path.join(this_path, "../../src/test/unit")] + sys.path
return return
def build_suite(): def build_suite():
"""build suite test from files in unit directory""" """Build suite test from files in unit directory. Filenames with test
suites should always end with "_test.py"."""
modules = [] modules = []
classes = []
for fname in glob.glob1('unit', '*_test.py'): for fname in glob.glob1('unit', '*_test.py'):
class_name = fname[:-8] class_name = fname[:-8]
if "_" in class_name: if "_" in class_name:
@@ -59,7 +33,6 @@ def build_suite():
class_name = "Test" + class_name.capitalize() class_name = "Test" + class_name.capitalize()
modules.append(fname[:-3]) modules.append(fname[:-3])
classes.append(class_name)
modules = map(__import__, modules) modules = map(__import__, modules)
load = unittest.defaultTestLoader.loadTestsFromModule load = unittest.defaultTestLoader.loadTestsFromModule

View File

@@ -1,9 +1,15 @@
# This Python file uses the following encoding: utf-8 """
Project: pyGTKtalog
Description: This is simple dummy test for... testing purposes :)
Type: test
Author: Roman 'gryf' Dobosz, gryf73@gmail.com
Created: 2008-12-15
"""
import unittest import unittest
class TestDummy(unittest.TestCase): class TestDummy(unittest.TestCase):
"""Fake test class""" """Fake test class"""
def test_dummyMethod(self): def test_dummy_method(self):
"""Test simple assertion""" """Test simple assertion"""
self.assertTrue(True) self.assertTrue(True)

View File

@@ -1,9 +1,15 @@
# This Python file uses the following encoding: utf-8 """
Project: pyGTKtalog
Description: This is another dummy test.
Type: test
Author: Roman 'gryf' Dobosz, gryf73@gmail.com
Created: 2008-12-15
"""
import unittest import unittest
class TestFooBar(unittest.TestCase): class TestFooBar(unittest.TestCase):
"""Fake test class""" """Fake test class"""
def test_dummyMethod(self): def test_dummy_method(self):
"""Test simple assertion""" """Test simple assertion"""
self.assertTrue(True) self.assertTrue(True)

View File

@@ -1,4 +1,10 @@
# This Python file uses the following encoding: utf-8 """
Project: pyGTKtalog
Description: Tests for Midentify class.
Type: test
Author: Roman 'gryf' Dobosz, gryf73@gmail.com
Created: 2008-12-15
"""
import unittest import unittest
from lib.midentify import Midentify from lib.midentify import Midentify
@@ -8,82 +14,82 @@ class TestMidentify(unittest.TestCase):
Midentify script belongs to mplayer package. Midentify script belongs to mplayer package.
""" """
def test_testAvi(self): def test_avi(self):
"""test mock avi file, should return dict with expected values""" """test mock avi file, should return dict with expected values"""
avi = Midentify("mocks/m.avi") avi = Midentify("mocks/m.avi")
result_dict = avi.get_data() result_dict = avi.get_data()
self.assertTrue(len(result_dict) != 0, "result should have lenght > 0") self.assertTrue(len(result_dict) != 0, "result should have lenght > 0")
self.assertEqual(result_dict['audio_format'], '85') self.assertEqual(result_dict['audio_format'], '85')
self.assertEqual(result_dict['width'], '128') self.assertEqual(result_dict['width'], 128)
self.assertEqual(result_dict['audio_no_channels'], '2') self.assertEqual(result_dict['audio_no_channels'], 2)
self.assertEqual(result_dict['height'], '96') self.assertEqual(result_dict['height'], 96)
self.assertEqual(result_dict['video_format'], 'XVID') self.assertEqual(result_dict['video_format'], 'XVID')
self.assertEqual(result_dict['length'], '4') self.assertEqual(result_dict['length'], 4)
self.assertEqual(result_dict['audio_codec'], 'mp3') self.assertEqual(result_dict['audio_codec'], 'mp3')
self.assertEqual(result_dict['video_codec'], 'ffodivx') self.assertEqual(result_dict['video_codec'], 'ffodivx')
self.assertEqual(result_dict['duration'], '00:00:04') self.assertEqual(result_dict['duration'], '00:00:04')
self.assertEqual(result_dict['container'], 'avi') self.assertEqual(result_dict['container'], 'avi')
def test_testAvi2(self): def test_avi2(self):
"""test another mock avi file, should return dict with expected """test another mock avi file, should return dict with expected
values""" values"""
avi = Midentify("mocks/m1.avi") avi = Midentify("mocks/m1.avi")
result_dict = avi.get_data() result_dict = avi.get_data()
self.assertTrue(len(result_dict) != 0, "result should have lenght > 0") self.assertTrue(len(result_dict) != 0, "result should have lenght > 0")
self.assertEqual(result_dict['audio_format'], '85') self.assertEqual(result_dict['audio_format'], '85')
self.assertEqual(result_dict['width'], '128') self.assertEqual(result_dict['width'], 128)
self.assertEqual(result_dict['audio_no_channels'], '2') self.assertEqual(result_dict['audio_no_channels'], 2)
self.assertEqual(result_dict['height'], '96') self.assertEqual(result_dict['height'], 96)
self.assertEqual(result_dict['video_format'], 'H264') self.assertEqual(result_dict['video_format'], 'H264')
self.assertEqual(result_dict['length'], '4') self.assertEqual(result_dict['length'], 4)
self.assertEqual(result_dict['audio_codec'], 'mp3') self.assertEqual(result_dict['audio_codec'], 'mp3')
self.assertEqual(result_dict['video_codec'], 'ffh264') self.assertEqual(result_dict['video_codec'], 'ffh264')
self.assertEqual(result_dict['duration'], '00:00:04') self.assertEqual(result_dict['duration'], '00:00:04')
self.assertEqual(result_dict['container'], 'avi') self.assertEqual(result_dict['container'], 'avi')
def test_testMkv(self): def test_mkv(self):
"""test mock mkv file, should return dict with expected values""" """test mock mkv file, should return dict with expected values"""
avi = Midentify("mocks/m.mkv") avi = Midentify("mocks/m.mkv")
result_dict = avi.get_data() result_dict = avi.get_data()
self.assertTrue(len(result_dict) != 0, "result should have lenght > 0") self.assertTrue(len(result_dict) != 0, "result should have lenght > 0")
self.assertEqual(result_dict['audio_format'], '8192') self.assertEqual(result_dict['audio_format'], '8192')
self.assertEqual(result_dict['width'], '128') self.assertEqual(result_dict['width'], 128)
self.assertEqual(result_dict['audio_no_channels'], '2') self.assertEqual(result_dict['audio_no_channels'], 2)
self.assertEqual(result_dict['height'], '96') self.assertEqual(result_dict['height'], 96)
self.assertEqual(result_dict['video_format'], 'mp4v') self.assertEqual(result_dict['video_format'], 'mp4v')
self.assertEqual(result_dict['length'], '4') self.assertEqual(result_dict['length'], 4)
self.assertEqual(result_dict['audio_codec'], 'a52') self.assertEqual(result_dict['audio_codec'], 'a52')
self.assertEqual(result_dict['video_codec'], 'ffodivx') self.assertEqual(result_dict['video_codec'], 'ffodivx')
self.assertEqual(result_dict['duration'], '00:00:04') self.assertEqual(result_dict['duration'], '00:00:04')
self.assertEqual(result_dict['container'], 'mkv') self.assertEqual(result_dict['container'], 'mkv')
def test_testMpg(self): def test_mpg(self):
"""test mock mpg file, should return dict with expected values""" """test mock mpg file, should return dict with expected values"""
avi = Midentify("mocks/m.mpg") avi = Midentify("mocks/m.mpg")
result_dict = avi.get_data() result_dict = avi.get_data()
self.assertTrue(len(result_dict) != 0, "result should have lenght > 0") self.assertTrue(len(result_dict) != 0, "result should have lenght > 0")
self.assertFalse(result_dict.has_key('audio_format')) self.assertFalse(result_dict.has_key('audio_format'))
self.assertEqual(result_dict['width'], '128') self.assertEqual(result_dict['width'], 128)
self.assertFalse(result_dict.has_key('audio_no_channels')) self.assertFalse(result_dict.has_key('audio_no_channels'))
self.assertEqual(result_dict['height'], '96') self.assertEqual(result_dict['height'], 96)
self.assertEqual(result_dict['video_format'], '0x10000001') self.assertEqual(result_dict['video_format'], '0x10000001')
self.assertFalse(result_dict.has_key('lenght')) self.assertFalse(result_dict.has_key('lenght'))
self.assertFalse(result_dict.has_key('audio_codec')) self.assertFalse(result_dict.has_key('audio_codec'))
self.assertEqual(result_dict['video_codec'], 'mpegpes') self.assertEqual(result_dict['video_codec'], 'ffmpeg1')
self.assertFalse(result_dict.has_key('duration')) self.assertFalse(result_dict.has_key('duration'))
self.assertEqual(result_dict['container'], 'mpeges') self.assertEqual(result_dict['container'], 'mpeges')
def test_testOgm(self): def test_ogm(self):
"""test mock ogm file, should return dict with expected values""" """test mock ogm file, should return dict with expected values"""
avi = Midentify("mocks/m.ogm") avi = Midentify("mocks/m.ogm")
result_dict = avi.get_data() result_dict = avi.get_data()
self.assertTrue(len(result_dict) != 0, "result should have lenght > 0") self.assertTrue(len(result_dict) != 0, "result should have lenght > 0")
self.assertEqual(result_dict['audio_format'], '8192') self.assertEqual(result_dict['audio_format'], '8192')
self.assertEqual(result_dict['width'], '160') self.assertEqual(result_dict['width'], 160)
self.assertEqual(result_dict['audio_no_channels'], '2') self.assertEqual(result_dict['audio_no_channels'], 2)
self.assertEqual(result_dict['height'], '120') self.assertEqual(result_dict['height'], 120)
self.assertEqual(result_dict['video_format'], 'H264') self.assertEqual(result_dict['video_format'], 'H264')
self.assertEqual(result_dict['length'], '4') self.assertEqual(result_dict['length'], 4)
self.assertEqual(result_dict['audio_codec'], 'a52') self.assertEqual(result_dict['audio_codec'], 'a52')
self.assertEqual(result_dict['video_codec'], 'ffh264') self.assertEqual(result_dict['video_codec'], 'ffh264')
self.assertEqual(result_dict['duration'], '00:00:04') self.assertEqual(result_dict['duration'], '00:00:04')