mirror of
https://github.com/gryf/pygtktalog.git
synced 2025-12-17 11:30:19 +01:00
Added observable property for discs current directory,
Clean up dialogs a bit Some additional variables in init (mainly for about() stuff) Rearrange video module for usage PIL, which is slightly faster than ImageMagick utils
This commit is contained in:
@@ -7,6 +7,11 @@
|
||||
"""
|
||||
|
||||
__version__ = "1.9.0"
|
||||
__appname__ = "pyGTKtalog"
|
||||
__copyright__ = u"\u00A9 Roman 'gryf' Dobosz"
|
||||
__summary__ = "%s is simple tool for managing file collections." % __appname__
|
||||
__web__ = "http://bitbucket.org/gryf"
|
||||
__logo_img__ = "views/pixmaps/Giant Worms.png"
|
||||
|
||||
import os
|
||||
import locale
|
||||
|
||||
@@ -18,19 +18,24 @@ class DiscsController(Controller):
|
||||
"""
|
||||
Controller for discs TreeView
|
||||
"""
|
||||
|
||||
def __init__(self, model, view):
|
||||
"""
|
||||
Initialize DiscsController
|
||||
"""
|
||||
LOG.debug(self.__init__.__doc__.strip())
|
||||
Controller.__init__(self, model, view)
|
||||
self.discs_model = self.model.discs.discs
|
||||
|
||||
def register_view(self, view):
|
||||
"""
|
||||
Do DiscTree registration
|
||||
"""
|
||||
LOG.debug(self.register_view.__doc__.strip())
|
||||
view['discs'].set_model(self.model.discs)
|
||||
view['discs'].set_model(self.discs_model)
|
||||
|
||||
# register observers
|
||||
self.model.discs.register_observer(self)
|
||||
|
||||
# connect signals to popup menu - framework somehow omits automatic
|
||||
# signal connection for subviews which are not included to widgets
|
||||
@@ -51,7 +56,7 @@ class DiscsController(Controller):
|
||||
|
||||
# make cell text editabe
|
||||
cell.set_property('editable', True)
|
||||
cell.connect('edited', self.on_editing_done, self.model.discs)
|
||||
cell.connect('edited', self.on_editing_done, self.discs_model)
|
||||
# TODO: find a way how to disable default return keypress on editable
|
||||
# fields
|
||||
|
||||
@@ -93,8 +98,8 @@ class DiscsController(Controller):
|
||||
LOG.debug(self.on_discs_cursor_changed.__doc__.strip())
|
||||
selection = treeview.get_selection()
|
||||
path = selection.get_selected_rows()[1][0]
|
||||
self.model.update_files(self.model.discs.get_value(\
|
||||
self.model.discs.get_iter(path), 0))
|
||||
self.model.files.refresh(self.discs_model.get_value(\
|
||||
self.discs_model.get_iter(path), 0))
|
||||
|
||||
def on_discs_key_release_event(self, treeview, event):
|
||||
"""
|
||||
@@ -172,6 +177,13 @@ class DiscsController(Controller):
|
||||
LOG.debug(self.on_statistics_activate.__doc__.strip())
|
||||
raise NotImplementedError
|
||||
|
||||
# observable properties
|
||||
def property_currentdir_value_change(self, model, old, new):
|
||||
"""
|
||||
Change of a current dir signalized by other controllers/models
|
||||
"""
|
||||
LOG.debug(self.property_currentdir_value_change.__doc__.strip())
|
||||
|
||||
# private methods
|
||||
def _popup_menu(self, selection, event, button):
|
||||
"""
|
||||
|
||||
@@ -26,13 +26,13 @@ class FilesController(Controller):
|
||||
"""
|
||||
Controller.__init__(self, model, view)
|
||||
self.DND_TARGETS = [('files_tags', 0, 69)]
|
||||
|
||||
self.files_model = self.model.files
|
||||
|
||||
def register_view(self, view):
|
||||
"""
|
||||
Register view, and setup columns for files treeview
|
||||
"""
|
||||
view['files'].set_model(self.model.files)
|
||||
view['files'].set_model(self.files_model.files)
|
||||
|
||||
sigs = {"add_tag": ("activate", self.on_add_tag1_activate),
|
||||
"delete_tag": ("activate", self.on_delete_tag_activate),
|
||||
@@ -46,7 +46,6 @@ class FilesController(Controller):
|
||||
for signal in sigs:
|
||||
view.menu[signal].connect(sigs[signal][0], sigs[signal][1])
|
||||
|
||||
|
||||
view['files'].get_selection().set_mode(gtk.SELECTION_MULTIPLE)
|
||||
|
||||
col = gtk.TreeViewColumn(_('Disc'), gtk.CellRendererText(), text=1)
|
||||
@@ -108,10 +107,11 @@ class FilesController(Controller):
|
||||
"""
|
||||
Handle right click on files treeview - show popup menu.
|
||||
"""
|
||||
LOG.debug(self.on_files_button_press_event.__doc__.strip())
|
||||
LOG.debug("Mouse button pressed")
|
||||
pathinfo = treeview.get_path_at_pos(int(event.x), int(event.y))
|
||||
|
||||
if event.button == 3: # Right mouse button. Show context menu.
|
||||
if event.button == 3: # Right mouse button. Show context menu.
|
||||
LOG.debug("It's a right button")
|
||||
if pathinfo:
|
||||
path = pathinfo[0]
|
||||
|
||||
@@ -151,6 +151,7 @@ class FilesController(Controller):
|
||||
# self.view['edit'].set_sensitive(True)
|
||||
#self.__popup_menu(event, 'files_popup')
|
||||
#return True
|
||||
LOG.debug("It's other button")
|
||||
|
||||
def on_files_cursor_changed(self, treeview):
|
||||
"""Show details of selected file/directory"""
|
||||
@@ -171,75 +172,96 @@ class FilesController(Controller):
|
||||
self._popup_menu(selection, event, 0)
|
||||
|
||||
if gtk.gdk.keyval_name(event.keyval) == 'BackSpace':
|
||||
d_path, d_column = self.view['discs'].get_cursor()
|
||||
if d_path and d_column:
|
||||
# easy way
|
||||
model = self.view['discs'].get_model()
|
||||
child_iter = model.get_iter(d_path)
|
||||
parent_iter = model.iter_parent(child_iter)
|
||||
if parent_iter:
|
||||
self.view['discs'].set_cursor(model.get_path(parent_iter))
|
||||
else:
|
||||
# hard way
|
||||
f_model = treeview.get_model()
|
||||
first_iter = f_model.get_iter_first()
|
||||
first_child_value = f_model.get_value(first_iter, 0)
|
||||
# get two steps up
|
||||
val = self.model.get_parent_id(first_child_value)
|
||||
parent_value = self.model.get_parent_id(val)
|
||||
iter = self.model.discs_tree.get_iter_first()
|
||||
while iter:
|
||||
current_value = self.model.discs_tree.get_value(iter,
|
||||
0)
|
||||
if current_value == parent_value:
|
||||
path = self.model.discs_tree.get_path(iter)
|
||||
self.view['discs'].set_cursor(path)
|
||||
iter = None
|
||||
else:
|
||||
iter = self.model.discs_tree.iter_next(iter)
|
||||
#if gtk.gdk.keyval_name(event.keyval) == 'Delete':
|
||||
# for file_id in self.__get_tv_selection_ids(treeview):
|
||||
# self.main.delete(file_id)
|
||||
row, gtk_column = self.view['files'].get_cursor()
|
||||
if row and gtk_column:
|
||||
fileob = self.files_model.get_value(row)
|
||||
if fileob.parent.parent.id != 1:
|
||||
self.files_model.refresh(fileob.parent.parent)
|
||||
# TODO: synchronize with disks
|
||||
self.model.discs.currentdir = fileob.parent.parent
|
||||
|
||||
self.view['files'].grab_focus()
|
||||
|
||||
#ids = self.__get_tv_selection_ids(self.view['files'])
|
||||
|
||||
def on_files_row_activated(self, files_obj, row, column):
|
||||
"""On directory doubleclick in files listview dive into desired
|
||||
branch."""
|
||||
f_iter = self.model.files_list.get_iter(row)
|
||||
current_id = self.model.files_list.get_value(f_iter, 0)
|
||||
"""
|
||||
On directory doubleclick in files listview dive into desired branch.
|
||||
"""
|
||||
|
||||
if self.model.files_list.get_value(f_iter, 6) == 1:
|
||||
fileob = self.files_model.get_value(row=row)
|
||||
if not fileob.children:
|
||||
# ONLY directories. files are omitted.
|
||||
self.__set_files_hiden_columns_visible(False)
|
||||
self.model.get_root_entries(current_id)
|
||||
return
|
||||
|
||||
d_path, d_column = self.view['discs'].get_cursor()
|
||||
if d_path:
|
||||
if not self.view['discs'].row_expanded(d_path):
|
||||
self.view['discs'].expand_row(d_path, False)
|
||||
self.files_model.refresh(fileob)
|
||||
self.model.discs.currentdir = fileob
|
||||
self.view['files'].grab_focus()
|
||||
|
||||
discs_model = self.model.discs_tree
|
||||
iterator = discs_model.get_iter(d_path)
|
||||
new_iter = discs_model.iter_children(iterator)
|
||||
if new_iter:
|
||||
while new_iter:
|
||||
current_value = discs_model.get_value(new_iter, 0)
|
||||
if current_value == current_id:
|
||||
path = discs_model.get_path(new_iter)
|
||||
self.view['discs'].set_cursor(path)
|
||||
new_iter = discs_model.iter_next(new_iter)
|
||||
# TODO: synchronize with disks
|
||||
return
|
||||
|
||||
def on_add_tag1_activate(self, menu_item): pass
|
||||
def on_delete_tag_activate(self, menuitem): pass
|
||||
def on_add_thumb1_activate(self, menuitem): pass
|
||||
def on_remove_thumb1_activate(self, menuitem): pass
|
||||
def on_add_image1_activate(self, menuitem): pass
|
||||
def on_remove_image1_activate(self, menuitem): pass
|
||||
def on_edit2_activate(self, menuitem): pass
|
||||
def on_delete3_activate(self, menuitem): pass
|
||||
def on_rename2_activate(self, menuitem): pass
|
||||
def on_add_tag1_activate(self, menu_item):
|
||||
"""
|
||||
TODO
|
||||
"""
|
||||
LOG.debug(self.on_add_tag1_activate.__doc__.strip())
|
||||
raise NotImplementedError
|
||||
|
||||
def on_delete_tag_activate(self, menuitem):
|
||||
"""
|
||||
TODO
|
||||
"""
|
||||
LOG.debug(self.on_delete_tag_activate.__doc__.strip())
|
||||
raise NotImplementedError
|
||||
|
||||
def on_add_thumb1_activate(self, menuitem):
|
||||
"""
|
||||
TODO
|
||||
"""
|
||||
LOG.debug(self.on_add_thumb1_activate.__doc__.strip())
|
||||
raise NotImplementedError
|
||||
|
||||
def on_remove_thumb1_activate(self, menuitem):
|
||||
"""
|
||||
TODO
|
||||
"""
|
||||
LOG.debug(self.on_remove_thumb1_activate.__doc__.strip())
|
||||
raise NotImplementedError
|
||||
|
||||
def on_add_image1_activate(self, menuitem):
|
||||
"""
|
||||
TODO
|
||||
"""
|
||||
LOG.debug(self.on_add_image1_activate.__doc__.strip())
|
||||
raise NotImplementedError
|
||||
|
||||
def on_remove_image1_activate(self, menuitem):
|
||||
"""
|
||||
TODO
|
||||
"""
|
||||
LOG.debug(self.on_remove_image1_activate.__doc__.strip())
|
||||
raise NotImplementedError
|
||||
|
||||
def on_edit2_activate(self, menuitem):
|
||||
"""
|
||||
TODO
|
||||
"""
|
||||
LOG.debug(self.on_edit2_activate.__doc__.strip())
|
||||
raise NotImplementedError
|
||||
|
||||
def on_delete3_activate(self, menuitem):
|
||||
"""
|
||||
TODO
|
||||
"""
|
||||
LOG.debug(self.on_delete3_activate.__doc__.strip())
|
||||
raise NotImplementedError
|
||||
|
||||
def on_rename2_activate(self, menuitem):
|
||||
"""
|
||||
TODO
|
||||
"""
|
||||
LOG.debug(self.on_rename2_activate.__doc__.strip())
|
||||
raise NotImplementedError
|
||||
|
||||
# private methods
|
||||
def _popup_menu(self, selection, event, button):
|
||||
@@ -258,4 +280,3 @@ class FilesController(Controller):
|
||||
|
||||
self.view.menu['files_popup'].popup(None, None, None,
|
||||
button, event.time)
|
||||
|
||||
|
||||
@@ -14,12 +14,8 @@ from pygtktalog.controllers.files import FilesController
|
||||
#from pygtktalog.controllers.details import DetailsController
|
||||
#from pygtktalog.controllers.tags import TagcloudController
|
||||
#from pygtktalog.dialogs import yesno, okcancel, info, warn, error
|
||||
from pygtktalog.dialogs import open_catalog, save_catalog, error, yesno
|
||||
from pygtktalog.dialogs import About # TODO: how about make it like a functions above?
|
||||
from pygtktalog.dialogs import open_catalog, save_catalog, error, yesno, about
|
||||
from pygtktalog.logger import get_logger
|
||||
from pygtktalog import __version__
|
||||
# although it seems to be unused, it is necessary, because it contains
|
||||
# definitions for additional connectable widgets for observers.
|
||||
|
||||
LOG = get_logger("main controller")
|
||||
|
||||
@@ -56,16 +52,13 @@ class MainController(Controller):
|
||||
view.set_widgets_app_sensitivity(False)
|
||||
view['main'].show()
|
||||
|
||||
def register_adapters(self):
|
||||
"""
|
||||
progress bar/status bar adapters goes here
|
||||
"""
|
||||
LOG.debug(self.register_adapters.__doc__.strip())
|
||||
#title_ad = Adapter(self.model, "cat_fname")
|
||||
#title_ad.connect_widget(self.view["main"],
|
||||
# setter=lambda w,v: \
|
||||
# w.set_title(self._get_title()))
|
||||
pass
|
||||
# status bar
|
||||
LOG.debug("register statusbar")
|
||||
self.context_id = self.view['mainStatus'].get_context_id('status')
|
||||
self.statusbar_id = \
|
||||
self.view['mainStatus'].push(self.context_id,
|
||||
self.model.status_bar_message)
|
||||
|
||||
|
||||
# signals
|
||||
def on_main_destroy_event(self, widget, event):
|
||||
@@ -95,8 +88,6 @@ class MainController(Controller):
|
||||
"""
|
||||
LOG.debug(self.on_new_activate.__doc__.strip())
|
||||
self.model.new()
|
||||
self.view.discs['discs'].set_model(self.model.discs)
|
||||
self.view.files['files'].set_model(self.model.files)
|
||||
self._set_title()
|
||||
self.view.set_widgets_app_sensitivity(True)
|
||||
|
||||
@@ -150,8 +141,7 @@ class MainController(Controller):
|
||||
|
||||
def on_about1_activate(self, widget):
|
||||
"""Show about dialog"""
|
||||
About("pyGTKtalog", "%s" % __version__, "About",
|
||||
["Roman 'gryf' Dobosz"], '')
|
||||
about()
|
||||
|
||||
def on_save_activate(self, widget):
|
||||
"""
|
||||
|
||||
@@ -9,6 +9,8 @@ import os
|
||||
|
||||
import gtk
|
||||
|
||||
import pygtktalog
|
||||
|
||||
|
||||
class Dialog(object):
|
||||
"""
|
||||
@@ -58,23 +60,6 @@ class Dialog(object):
|
||||
self.dialog.format_secondary_text(self.secondary_msg)
|
||||
self.dialog.set_title(self.title)
|
||||
|
||||
class About(object):
|
||||
"""
|
||||
Show About dialog
|
||||
"""
|
||||
def __init__(self, name=None, ver="", title="", authors=[], licence=""):
|
||||
self.dialog = gtk.AboutDialog()
|
||||
self.dialog.set_title(title)
|
||||
self.dialog.set_version(ver)
|
||||
self.dialog.set_license(licence)
|
||||
self.dialog.set_name(name)
|
||||
self.dialog.set_authors(authors)
|
||||
self.dialog.connect('response',
|
||||
lambda dialog, response: self.dialog.destroy())
|
||||
self.dialog.show()
|
||||
|
||||
# TODO: finish this, re-use Dialog class instead of copy/paste of old classes!
|
||||
# def about(name, version, )
|
||||
|
||||
class ChooseFile(object):
|
||||
"""
|
||||
@@ -97,7 +82,7 @@ class ChooseFile(object):
|
||||
self.path = path
|
||||
self.title = title
|
||||
self.action = self.CHOOSER_TYPES[chooser_type]
|
||||
self.buttons=[]
|
||||
self.buttons = []
|
||||
for button in buttons:
|
||||
self.buttons.append(self.BUTTON_PAIRS[button][0])
|
||||
self.buttons.append(self.BUTTON_PAIRS[button][1])
|
||||
@@ -120,7 +105,7 @@ class ChooseFile(object):
|
||||
if self.URI:
|
||||
self.dialog.set_current_folder_uri(self.URI)
|
||||
elif self.path and os.path.exists(self.path):
|
||||
self.path = "file://"+os.path.abspath(self.path)
|
||||
self.path = "file://" + os.path.abspath(self.path)
|
||||
self.dialog.set_current_folder_uri(self.path)
|
||||
|
||||
for filtr in self._get_filters():
|
||||
@@ -167,6 +152,7 @@ def yesno(message, secondarymsg="", title="", default=False):
|
||||
dialog.ok_default = default
|
||||
return dialog.run()
|
||||
|
||||
|
||||
def okcancel(message, secondarymsg="", title="", default=False):
|
||||
"""Question with ok-cancel buttons. Returns False on 'cancel', True on
|
||||
'ok'"""
|
||||
@@ -175,6 +161,7 @@ def okcancel(message, secondarymsg="", title="", default=False):
|
||||
dialog.ok_default = default
|
||||
return dialog.run()
|
||||
|
||||
|
||||
def info(message, secondarymsg="", title="", button=gtk.BUTTONS_OK):
|
||||
"""Info dialog. Button defaults to gtk.BUTTONS_OK, but can be changed with
|
||||
gtk.BUTTONS_CANCEL, gtk.BUTTONS_CLOSE or gtk.BUTTONS_NONE.
|
||||
@@ -184,6 +171,7 @@ def info(message, secondarymsg="", title="", button=gtk.BUTTONS_OK):
|
||||
dialog.run()
|
||||
return True
|
||||
|
||||
|
||||
def warn(message, secondarymsg="", title="", button=gtk.BUTTONS_OK):
|
||||
"""Warning dialog. Button defaults to gtk.BUTTONS_OK, but can be changed
|
||||
with gtk.BUTTONS_CANCEL, gtk.BUTTONS_CLOSE or gtk.BUTTONS_NONE.
|
||||
@@ -193,6 +181,7 @@ def warn(message, secondarymsg="", title="", button=gtk.BUTTONS_OK):
|
||||
dialog.run()
|
||||
return True
|
||||
|
||||
|
||||
def error(message, secondarymsg="", title="", button=gtk.BUTTONS_OK):
|
||||
"""Error dialog. Button defaults to gtk.BUTTONS_OK, but can be changed with
|
||||
gtk.BUTTONS_CANCEL, gtk.BUTTONS_CLOSE or gtk.BUTTONS_NONE.
|
||||
@@ -202,6 +191,7 @@ def error(message, secondarymsg="", title="", button=gtk.BUTTONS_OK):
|
||||
dialog.run()
|
||||
return True
|
||||
|
||||
|
||||
def open_catalog(title=_("Open catalog"), path=None):
|
||||
"""
|
||||
Request filename from user to open.
|
||||
@@ -211,6 +201,7 @@ def open_catalog(title=_("Open catalog"), path=None):
|
||||
requester.filters = ['catalogs', 'all']
|
||||
return requester.run()
|
||||
|
||||
|
||||
def save_catalog(title=_("Open catalog"), path=None):
|
||||
"""
|
||||
Request filename from user for save.
|
||||
@@ -220,3 +211,19 @@ def save_catalog(title=_("Open catalog"), path=None):
|
||||
requester.filters = ['catalogs', 'all']
|
||||
requester.confirmation = True
|
||||
return requester.run()
|
||||
|
||||
|
||||
def about():
|
||||
"""
|
||||
Show About dialog
|
||||
"""
|
||||
dialog = gtk.AboutDialog()
|
||||
dialog.set_version(pygtktalog.__version__)
|
||||
dialog.set_program_name(pygtktalog.__appname__)
|
||||
dialog.set_copyright(pygtktalog.__copyright__)
|
||||
dialog.set_comments(pygtktalog.__summary__)
|
||||
dialog.set_website(pygtktalog.__web__)
|
||||
dialog.set_logo(gtk.gdk.pixbuf_new_from_file(\
|
||||
os.path.join(os.path.dirname(__file__), pygtktalog.__logo_img__)))
|
||||
dialog.run()
|
||||
dialog.destroy()
|
||||
|
||||
@@ -19,6 +19,9 @@ from pygtktalog.dbobjects import File, Exif, Group, Gthumb
|
||||
from pygtktalog.dbobjects import Image, Tag, Thumbnail
|
||||
from pygtktalog.dbcommon import connect, Meta, Session
|
||||
from pygtktalog.logger import get_logger
|
||||
from pygtktalog.models.details import DetailsModel
|
||||
from pygtktalog.models.discs import DiscsModel
|
||||
from pygtktalog.models.files import FilesModel
|
||||
|
||||
LOG = get_logger("main model")
|
||||
|
||||
@@ -55,38 +58,12 @@ class MainModel(ModelMT):
|
||||
|
||||
self.db_unsaved = None
|
||||
|
||||
self.discs = None
|
||||
self.files = None
|
||||
|
||||
self._init_discs()
|
||||
self._init_files()
|
||||
self.discs = DiscsModel()
|
||||
self.files = FilesModel()
|
||||
|
||||
if self.cat_fname:
|
||||
self.open(self.cat_fname)
|
||||
|
||||
|
||||
def _init_discs(self):
|
||||
"""
|
||||
Create TreeStore model for the discs
|
||||
"""
|
||||
self.discs = gtk.TreeStore(gobject.TYPE_PYOBJECT,
|
||||
gobject.TYPE_STRING,
|
||||
str)
|
||||
|
||||
|
||||
def _init_files(self):
|
||||
"""
|
||||
Create ListStore model for the diles
|
||||
"""
|
||||
self.files = gtk.ListStore(gobject.TYPE_PYOBJECT,
|
||||
gobject.TYPE_STRING,
|
||||
gobject.TYPE_STRING,
|
||||
gobject.TYPE_STRING,
|
||||
gobject.TYPE_UINT64,
|
||||
gobject.TYPE_STRING,
|
||||
gobject.TYPE_INT,
|
||||
str)
|
||||
|
||||
def open(self, filename):
|
||||
"""
|
||||
Open catalog file and read db
|
||||
@@ -103,7 +80,7 @@ class MainModel(ModelMT):
|
||||
self.cat_fname = filename
|
||||
|
||||
if self._open_or_decompress():
|
||||
return self._populate_discs_from_db()
|
||||
return self.discs.refresh(self._session)
|
||||
else:
|
||||
return False
|
||||
|
||||
@@ -143,8 +120,8 @@ class MainModel(ModelMT):
|
||||
self.cleanup()
|
||||
self._create_temp_db_file()
|
||||
self._create_schema()
|
||||
self._init_discs()
|
||||
self._init_files()
|
||||
self.discs.clear()
|
||||
self.files.clear()
|
||||
self.db_unsaved = False
|
||||
|
||||
def cleanup(self):
|
||||
@@ -247,6 +224,7 @@ class MainModel(ModelMT):
|
||||
|
||||
connect(os.path.abspath(self.tmp_filename))
|
||||
self._session = Session()
|
||||
LOG.debug("session obj: %s" % str(self._session))
|
||||
return True
|
||||
|
||||
def _create_temp_db_file(self):
|
||||
@@ -263,6 +241,7 @@ class MainModel(ModelMT):
|
||||
"""
|
||||
"""
|
||||
self._session = Session()
|
||||
LOG.debug("session obj: %s" % str(self._session))
|
||||
|
||||
connect(os.path.abspath(self.tmp_filename))
|
||||
|
||||
@@ -277,60 +256,6 @@ class MainModel(ModelMT):
|
||||
self._session.add(root)
|
||||
self._session.commit()
|
||||
|
||||
def _populate_discs_from_db(self):
|
||||
"""
|
||||
Read objects from database, fill TreeStore model with discs
|
||||
information
|
||||
"""
|
||||
dirs = self._session.query(File).filter(File.type == 1)
|
||||
dirs = dirs.order_by(File.filename).all()
|
||||
|
||||
def get_children(parent_id=1, iterator=None):
|
||||
"""
|
||||
Get all children of the selected parent.
|
||||
Arguments:
|
||||
@parent_id - integer with id of the parent (from db)
|
||||
@iterator - gtk.TreeIter, which points to a path inside model
|
||||
"""
|
||||
for fileob in dirs:
|
||||
if fileob.parent_id == parent_id:
|
||||
myiter = self.discs.insert_before(iterator, None)
|
||||
self.discs.set_value(myiter, 0, fileob)
|
||||
self.discs.set_value(myiter, 1, fileob.filename)
|
||||
if iterator is None:
|
||||
self.discs.set_value(myiter, 2, gtk.STOCK_CDROM)
|
||||
else:
|
||||
self.discs.set_value(myiter, 2, gtk.STOCK_DIRECTORY)
|
||||
get_children(fileob.id, myiter)
|
||||
return
|
||||
get_children()
|
||||
|
||||
return True
|
||||
|
||||
def update_files(self, fileob):
|
||||
"""
|
||||
Update files ListStore
|
||||
Arguments:
|
||||
fileob - File object
|
||||
"""
|
||||
LOG.info("found %d files for File object: %s" % (len(fileob.children),
|
||||
str(fileob)))
|
||||
|
||||
self.files.clear()
|
||||
|
||||
for child in fileob.children:
|
||||
myiter = self.files.insert_before(None, None)
|
||||
self.files.set_value(myiter, 0, child.id)
|
||||
self.files.set_value(myiter, 1, child.parent_id \
|
||||
if child.parent_id!=1 else None)
|
||||
self.files.set_value(myiter, 2, child.filename)
|
||||
self.files.set_value(myiter, 3, child.filepath)
|
||||
self.files.set_value(myiter, 4, child.size)
|
||||
self.files.set_value(myiter, 5, child.date)
|
||||
self.files.set_value(myiter, 6, 1)
|
||||
self.files.set_value(myiter, 7, gtk.STOCK_DIRECTORY \
|
||||
if child.type==1 else gtk.STOCK_FILE)
|
||||
|
||||
def _compress_and_save(self):
|
||||
"""
|
||||
Create (and optionaly compress) tar archive from working directory and
|
||||
|
||||
@@ -33,17 +33,17 @@ class Video(object):
|
||||
self.out_width = out_width
|
||||
self.tags = {}
|
||||
|
||||
output = self.__get_movie_info()
|
||||
output = self._get_movie_info()
|
||||
|
||||
attrs = {'ID_VIDEO_WIDTH': ['width', int],
|
||||
'ID_VIDEO_HEIGHT': ['height', int],
|
||||
# length is in seconds
|
||||
'ID_LENGTH': ['length', lambda x: int(x.split(".")[0])],
|
||||
'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_DEMUXER': ['container', self._return_lower],
|
||||
'ID_VIDEO_FORMAT': ['video_format', self._return_lower],
|
||||
'ID_VIDEO_CODEC': ['video_codec', self._return_lower],
|
||||
'ID_AUDIO_CODEC': ['audio_codec', self._return_lower],
|
||||
'ID_AUDIO_FORMAT': ['audio_format', self._return_lower],
|
||||
'ID_AUDIO_NCH': ['audio_no_channels', int],}
|
||||
# TODO: what about audio/subtitle language/existence?
|
||||
|
||||
@@ -97,14 +97,15 @@ class Video(object):
|
||||
tempdir = mkdtemp()
|
||||
file_desc, image_fn = mkstemp()
|
||||
os.close(file_desc)
|
||||
self.__make_captures(tempdir, no_pictures)
|
||||
self.__make_montage(tempdir, image_fn, no_pictures, self.out_width)
|
||||
self._make_captures(tempdir, no_pictures)
|
||||
#self._make_montage(tempdir, image_fn, no_pictures)
|
||||
self._make_montage3(tempdir, image_fn, no_pictures)
|
||||
|
||||
shutil.rmtree(tempdir)
|
||||
return image_fn
|
||||
|
||||
|
||||
def __get_movie_info(self):
|
||||
def _get_movie_info(self):
|
||||
"""
|
||||
Gather movie file information with midentify shell command.
|
||||
Returns: dict of command output. Each dict element represents pairs:
|
||||
@@ -134,7 +135,7 @@ class Video(object):
|
||||
return_dict[key[0]] = line.replace("%s=" % key[0], "")
|
||||
return return_dict
|
||||
|
||||
def __make_captures(self, directory, no_pictures):
|
||||
def _make_captures(self, directory, no_pictures):
|
||||
"""
|
||||
Make screens with mplayer into given directory
|
||||
Arguments:
|
||||
@@ -154,13 +155,125 @@ class Video(object):
|
||||
shutil.move(os.path.join(directory, "00000001.jpg"),
|
||||
os.path.join(directory, "picture_%s.jpg" % time))
|
||||
|
||||
def __make_montage2(self, directory, image_fn, no_pictures):
|
||||
def _make_montage2(self, directory, image_fn, no_pictures):
|
||||
"""
|
||||
Generate one big image from screnshots and optionally resize it.
|
||||
Generate one big image from screnshots and optionally resize it. Use
|
||||
external tools from ImageMagic package to arrange and compose final
|
||||
image. First, images are prescaled, before they will be montaged.
|
||||
|
||||
Arguments:
|
||||
@directory - source directory containing images
|
||||
@image_fn - destination final image
|
||||
@no_pictures - number of pictures
|
||||
timeit result:
|
||||
python /usr/lib/python2.6/timeit.py -n 1 -r 1 'from pygtktalog.video import Video; v = Video("/home/gryf/t/a.avi"); v.capture()'
|
||||
1 loops, best of 1: 25 sec per loop
|
||||
"""
|
||||
row_length = 4
|
||||
if no_pictures < 8:
|
||||
row_length = 2
|
||||
|
||||
if not (self.tags['width'] * row_length) > self.out_width:
|
||||
for i in (8, 6, 5):
|
||||
if (no_pictures % i) == 0 and \
|
||||
(i * self.tags['width']) <= self.out_width:
|
||||
row_length = i
|
||||
break
|
||||
|
||||
coef = float(self.out_width - row_length * 4) / (self.tags['width'] * row_length)
|
||||
scaled_width = int(self.tags['width'] * coef)
|
||||
|
||||
# scale images
|
||||
for fname in os.listdir(directory):
|
||||
cmd = "convert -scale %d %s %s_s.jpg"
|
||||
os.popen(cmd % (scaled_width, os.path.join(directory, fname),
|
||||
os.path.join(directory, fname))).readlines()
|
||||
shutil.move(os.path.join(directory, fname + "_s.jpg"),
|
||||
os.path.join(directory, fname))
|
||||
|
||||
|
||||
tile = "%dx%d" % (row_length, no_pictures / row_length)
|
||||
|
||||
_curdir = os.path.abspath(os.path.curdir)
|
||||
os.chdir(directory)
|
||||
|
||||
# composite pictures
|
||||
# readlines trick will make to wait for process end
|
||||
cmd = "montage -tile %s -geometry +2+2 picture_*.jpg montage.jpg"
|
||||
os.popen(cmd % tile).readlines()
|
||||
|
||||
shutil.move(os.path.join(directory, 'montage.jpg'), image_fn)
|
||||
os.chdir(_curdir)
|
||||
|
||||
def _make_montage3(self, directory, image_fn, no_pictures):
|
||||
"""
|
||||
Generate one big image from screnshots and optionally resize it. Uses
|
||||
PIL package to create output image.
|
||||
Arguments:
|
||||
@directory - source directory containing images
|
||||
@image_fn - destination final image
|
||||
@no_pictures - number of pictures
|
||||
timeit result:
|
||||
python /usr/lib/python2.6/timeit.py -n 1 -r 1 'from pygtktalog.video import Video; v = Video("/home/gryf/t/a.avi"); v.capture()'
|
||||
1 loops, best of 1: 18.8 sec per loop
|
||||
"""
|
||||
scale = False
|
||||
row_length = 4
|
||||
if no_pictures < 8:
|
||||
row_length = 2
|
||||
|
||||
if not (self.tags['width'] * row_length) > self.out_width:
|
||||
for i in [8, 6, 5]:
|
||||
if (no_pictures % i) == 0 and \
|
||||
(i * self.tags['width']) <= self.out_width:
|
||||
row_length = i
|
||||
break
|
||||
|
||||
coef = float(self.out_width - row_length - 1) / (self.tags['width'] * row_length)
|
||||
if coef < 1:
|
||||
dim = int(self.tags['width'] * coef), int(self.tags['height'] * coef)
|
||||
else:
|
||||
dim = int(self.tags['width']), int(self.tags['height'])
|
||||
|
||||
ifn_list = os.listdir(directory)
|
||||
ifn_list.sort()
|
||||
img_list = [Image.open(os.path.join(directory, fn)).resize(dim) \
|
||||
for fn in ifn_list]
|
||||
|
||||
rows = no_pictures / row_length
|
||||
cols = row_length
|
||||
isize = (cols * dim[0] + cols + 1,
|
||||
rows * dim[1] + rows + 1)
|
||||
|
||||
inew = Image.new('RGB', isize, (80, 80, 80))
|
||||
|
||||
for irow in range(no_pictures * row_length):
|
||||
for icol in range(row_length):
|
||||
left = 1 + icol*(dim[0] + 1)
|
||||
right = left + dim[0]
|
||||
upper = 1 + irow * (dim[1] + 1)
|
||||
lower = upper + dim[1]
|
||||
bbox = (left, upper, right, lower)
|
||||
try:
|
||||
img = img_list.pop(0)
|
||||
except:
|
||||
break
|
||||
inew.paste(img, bbox)
|
||||
inew.save(image_fn, 'JPEG')
|
||||
|
||||
def _make_montage(self, directory, image_fn, no_pictures):
|
||||
"""
|
||||
Generate one big image from screnshots and optionally resize it. Use
|
||||
external tools from ImageMagic package to arrange and compose final
|
||||
image.
|
||||
|
||||
Arguments:
|
||||
@directory - source directory containing images
|
||||
@image_fn - destination final image
|
||||
@no_pictures - number of pictures
|
||||
timeit result:
|
||||
python /usr/lib/python2.6/timeit.py -n 1 -r 1 'from pygtktalog.video import Video; v = Video("/home/gryf/t/a.avi"); v.capture()'
|
||||
1 loops, best of 1: 32.5 sec per loop
|
||||
"""
|
||||
scale = False
|
||||
row_length = 4
|
||||
@@ -181,52 +294,6 @@ class Video(object):
|
||||
_curdir = os.path.abspath(os.path.curdir)
|
||||
os.chdir(directory)
|
||||
|
||||
# composite pictures
|
||||
# readlines trick will make to wait for process end
|
||||
#cmd = "montage -tile %s -geometry +2+2 picture_*.jpg montage.jpg"
|
||||
imgs = [Image.open(fn).resize((photow,photoh)) for fn in fnames]
|
||||
|
||||
os.popen(cmd % tile).readlines()
|
||||
|
||||
# scale it to minimum 'modern' width: 1024
|
||||
if scale:
|
||||
cmd = "convert -scale %s montage.jpg montage_scaled.jpg"
|
||||
os.popen(cmd % out_width).readlines()
|
||||
shutil.move(os.path.join(directory, 'montage_scaled.jpg'),
|
||||
image_fn)
|
||||
else:
|
||||
shutil.move(os.path.join(directory, 'montage.jpg'),
|
||||
image_fn)
|
||||
|
||||
os.chdir(_curdir)
|
||||
|
||||
def __make_montage(self, directory, image_fn, no_pictures):
|
||||
"""
|
||||
Generate one big image from screnshots and optionally resize it.
|
||||
Arguments:
|
||||
@directory - source directory containing images
|
||||
@image_fn - destination final image
|
||||
@no_pictures - number of pictures
|
||||
"""
|
||||
scale = False
|
||||
row_length = 4
|
||||
if no_pictures < 8:
|
||||
row_length = 2
|
||||
|
||||
if (self.tags['width'] * row_length) > self.out_width:
|
||||
scale = True
|
||||
else:
|
||||
for i in [8, 6, 5]:
|
||||
if (no_pictures % i) == 0 and \
|
||||
(i * self.tags['width']) <= self.ut_width:
|
||||
row_length = i
|
||||
break
|
||||
|
||||
tile = "%dx%d" % (row_length, no_pictures / row_length)
|
||||
|
||||
_curdir = os.path.abspath(os.path.curdir)
|
||||
os.chdir(directory)
|
||||
|
||||
# composite pictures
|
||||
# readlines trick will make to wait for process end
|
||||
cmd = "montage -tile %s -geometry +2+2 picture_*.jpg montage.jpg"
|
||||
@@ -244,6 +311,15 @@ class Video(object):
|
||||
|
||||
os.chdir(_curdir)
|
||||
|
||||
def _return_lower(self, chain):
|
||||
"""
|
||||
Return lowercase version of provided string argument
|
||||
Arguments:
|
||||
@chain string to be lowered
|
||||
Returns:
|
||||
@string with lowered string
|
||||
"""
|
||||
return str(chain).lower()
|
||||
|
||||
def __str__(self):
|
||||
str_out = ''
|
||||
|
||||
@@ -25,7 +25,7 @@ class TestVideo(unittest.TestCase):
|
||||
self.assertEqual(avi.tags['width'], 128)
|
||||
self.assertEqual(avi.tags['audio_no_channels'], 2)
|
||||
self.assertEqual(avi.tags['height'], 96)
|
||||
self.assertEqual(avi.tags['video_format'], 'XVID')
|
||||
self.assertEqual(avi.tags['video_format'], 'xvid')
|
||||
self.assertEqual(avi.tags['length'], 4)
|
||||
self.assertEqual(avi.tags['audio_codec'], 'mp3')
|
||||
self.assertEqual(avi.tags['video_codec'], 'ffodivx')
|
||||
@@ -41,7 +41,7 @@ class TestVideo(unittest.TestCase):
|
||||
self.assertEqual(avi.tags['width'], 128)
|
||||
self.assertEqual(avi.tags['audio_no_channels'], 2)
|
||||
self.assertEqual(avi.tags['height'], 96)
|
||||
self.assertEqual(avi.tags['video_format'], 'H264')
|
||||
self.assertEqual(avi.tags['video_format'], 'h264')
|
||||
self.assertEqual(avi.tags['length'], 4)
|
||||
self.assertEqual(avi.tags['audio_codec'], 'mp3')
|
||||
self.assertEqual(avi.tags['video_codec'], 'ffh264')
|
||||
@@ -54,14 +54,14 @@ class TestVideo(unittest.TestCase):
|
||||
self.assertTrue(len(avi.tags) != 0, "result should have lenght > 0")
|
||||
self.assertEqual(avi.tags['audio_format'], '8192')
|
||||
self.assertEqual(avi.tags['width'], 128)
|
||||
self.assertEqual(avi.tags['audio_no_channels'], 2)
|
||||
self.assertTrue(avi.tags['audio_no_channels'] in (1, 2))
|
||||
self.assertEqual(avi.tags['height'], 96)
|
||||
self.assertEqual(avi.tags['video_format'], 'mp4v')
|
||||
self.assertEqual(avi.tags['length'], 4)
|
||||
self.assertEqual(avi.tags['audio_codec'], 'a52')
|
||||
self.assertTrue(avi.tags['audio_codec'] in ('a52', 'ffac3'))
|
||||
self.assertEqual(avi.tags['video_codec'], 'ffodivx')
|
||||
self.assertEqual(avi.tags['duration'], '00:00:04')
|
||||
self.assertEqual(avi.tags['container'], 'mkv')
|
||||
self.assertTrue(avi.tags['container'] in ('mkv', 'lavfpref'))
|
||||
|
||||
def test_mpg(self):
|
||||
"""test mock mpg file, should return dict with expected values"""
|
||||
@@ -84,14 +84,14 @@ class TestVideo(unittest.TestCase):
|
||||
self.assertTrue(len(avi.tags) != 0, "result should have lenght > 0")
|
||||
self.assertEqual(avi.tags['audio_format'], '8192')
|
||||
self.assertEqual(avi.tags['width'], 160)
|
||||
self.assertEqual(avi.tags['audio_no_channels'], 2)
|
||||
self.assertTrue(avi.tags['audio_no_channels'] in (1, 2))
|
||||
self.assertEqual(avi.tags['height'], 120)
|
||||
self.assertEqual(avi.tags['video_format'], 'H264')
|
||||
self.assertEqual(avi.tags['video_format'], 'h264')
|
||||
self.assertEqual(avi.tags['length'], 4)
|
||||
self.assertEqual(avi.tags['audio_codec'], 'a52')
|
||||
self.assertTrue(avi.tags['audio_codec'] in ('a52', 'ffac3'))
|
||||
self.assertEqual(avi.tags['video_codec'], 'ffh264')
|
||||
self.assertEqual(avi.tags['duration'], '00:00:04')
|
||||
self.assertEqual(avi.tags['container'], 'ogg')
|
||||
self.assertTrue(avi.tags['container'] in ('ogg', 'lavfpref'))
|
||||
|
||||
def test_capture(self):
|
||||
"""test capture with some small movie and play a little with tags"""
|
||||
@@ -100,7 +100,7 @@ class TestVideo(unittest.TestCase):
|
||||
self.assertTrue(filename != None)
|
||||
self.assertTrue(os.path.exists(filename))
|
||||
file_size = os.stat(filename)[6]
|
||||
self.assertEqual(file_size, 9075)
|
||||
self.assertAlmostEqual(file_size/10000.0, 0.9, 0)
|
||||
os.unlink(filename)
|
||||
|
||||
for length in (480, 380, 4):
|
||||
|
||||
Reference in New Issue
Block a user