1
0
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:
2010-11-17 22:25:53 +01:00
parent 54b6a377bf
commit 3c6c9a552a
8 changed files with 297 additions and 261 deletions

View File

@@ -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

View File

@@ -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):
"""

View File

@@ -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.
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)
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)
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)
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
self.files_model.refresh(fileob)
self.model.discs.currentdir = fileob
self.view['files'].grab_focus()
# TODO: synchronize with disks
return
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)

View File

@@ -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):
"""

View File

@@ -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()

View File

@@ -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

View File

@@ -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 = ''

View File

@@ -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):