1
0
mirror of https://github.com/gryf/pygtktalog.git synced 2026-03-26 22:03:30 +01:00

4 Commits

Author SHA1 Message Date
3141add678 Another portion of fixes.
In this patch, most notable changes are:
- use python3 exclusively for tox
- fix up broken tests
- a couple of style issues fixes here and there
2019-02-24 19:53:35 +01:00
dadeebe8a1 Removing outdated pavement script
All tasks regarding build distro packages should be recreated either
using setup.py/distutils or paver again, but for now, there is no way to
have pavement at its current state.
2019-02-24 19:50:35 +01:00
6c6f01781a Several small fixes, mostly for style issues 2019-02-24 18:25:34 +01:00
07690f9c94 Added basic Python3 support 2019-02-24 13:21:51 +01:00
18 changed files with 191 additions and 426 deletions

View File

@@ -44,15 +44,14 @@ Requirements
pyGTKtalog requires python and following libraries: pyGTKtalog requires python and following libraries:
* `python 2.7`_ * `python 3`_, tested on python 3.6
* `sqlalchemy 1.0`_ * `sqlalchemy 1.2`_ or higher
* `pygtk 2.24`_ (only for ``gtktalog.py``) * `pygtk 2.24`_ (only for ``gtktalog.py``, will not work with python3)
* `pillow`_ for image manipulation * `pillow`_ for image manipulation
* `exifread`_ for parse EXIF information * `exifread`_ for parse EXIF information
It may work on other (lower) version of libraries, and it should work with It may work on other (lower) version of libraries, and it should work with
higher versions of libraries, although it will not work on Python 3 yet, nor higher versions of libraries. GTK3 support will follow.
GTK3.
pyGTKtalog extensively uses external programs in unix spirit, however there is pyGTKtalog extensively uses external programs in unix spirit, however there is
small possibility of using it Windows (probably with limitations) and quite big small possibility of using it Windows (probably with limitations) and quite big
@@ -148,7 +147,7 @@ file in top-level directory.
.. _paver: https://pythonhosted.org/paver/ .. _paver: https://pythonhosted.org/paver/
.. _pillow: https://python-pillow.org/ .. _pillow: https://python-pillow.org/
.. _pygtk 2.24: http://www.pygtk.org .. _pygtk 2.24: http://www.pygtk.org
.. _python 2.7: http://www.python.org/ .. _python 3: http://www.python.org/
.. _sqlalchemy 1.0: http://www.sqlalchemy.org .. _sqlalchemy 1.2: http://www.sqlalchemy.org
.. _tagging files: http://en.wikipedia.org/wiki/tag_%28metadata%29 .. _tagging files: http://en.wikipedia.org/wiki/tag_%28metadata%29
.. _tox: https://testrun.org/tox .. _tox: https://testrun.org/tox

View File

@@ -1,228 +0,0 @@
"""
Project: pyGTKtalog
Description: Makefile and setup.py replacement. Used python packages -
paver, nosetests. External commands - xgettext, intltool-extract, hg,
grep.
Type: management
Author: Roman 'gryf' Dobosz, gryf73@gmail.com
Created: 2009-05-07
"""
import os
import sys
import shutil
from datetime import datetime
from paver.easy import sh, dry, call_task, options, Bunch
from paver.tasks import task, needs, help, cmdopts
from paver.setuputils import setup
from paver.misctasks import generate_setup, minilib
import paver.doctools
try:
from pylint import lint
HAVE_LINT = True
except ImportError:
HAVE_LINT = False
PO_HEADER = """#
# pygtktalog Language File
#
msgid ""
msgstr ""
"Project-Id-Version: pygtktalog\\n"
"POT-Creation-Date: %(time)s\\n"
"Last-Translator: Roman Dobosz<gryf73@gmail.com>\\n"
"MIME-Version: 1.0\\n"
"Content-Type: text/plain; charset=UTF-8\\n"
"Content-Transfer-Encoding: utf-8\\n"
"""
REV = os.popen("hg sum 2>/dev/null|grep ^Revis|cut -d ' ' -f 2").readlines()
if REV:
REV = "r" + REV[0].strip()
else:
REV = '0'
LOCALES = {'pl': 'pl_PL.utf8', 'en': 'en_EN'}
POTFILE = 'locale/pygtktalog.pot'
# distutil/setuptool setup method.
setup(
name='pyGTKtalog',
version='1.99.%s' % REV,
long_description='pyGTKtalog is application similar to WhereIsIT, '
'for collecting information on files from CD/DVD '
'or directories.',
description='pyGTKtalog is a file indexing tool written in pyGTK',
author='Roman Dobosz',
author_email='gryf73@gmail.com',
url='http://google.com',
platforms=['Linux', 'BSD'],
license='GNU General Public License (GPL)',
classifiers=['Development Status :: 2 - Pre-Alpha',
'Environment :: X11 Applications :: GTK',
'Intended Audience :: End Users/Desktop',
'License :: OSI Approved :: GNU General Public License '
'(GPL)',
'Natural Language :: English',
'Natural Language :: Polish',
'Operating System :: POSIX :: Linux',
'Programming Language :: Python',
'Topic :: Desktop Environment',
'Topic :: Utilities'],
include_package_data=True,
exclude_package_data={'': ['*.patch']},
packages=["pygtktalog"],
scripts=['bin/gtktalog.py'],
test_suite='nose.collector'
)
options(sphinx=Bunch(builddir="build", sourcedir="source"))
@task
@needs(['locale_gen', 'minilib', 'generate_setup'])
def sdist():
"""sdist with message catalogs"""
call_task("setuptools.command.sdist")
@task
@needs(['locale_gen'])
def build():
"""build with message catalogs"""
call_task("setuptools.command.build")
@task
def clean():
"""remove 'pyo', 'pyc', 'h' and '~' files"""
# clean *.pyc, *.pyo and jEdit backup files *~
for root, dummy, files in os.walk("."):
for fname in files:
if fname.endswith(".pyc") or fname.endswith(".pyo") or \
fname.endswith("~") or fname.endswith(".h") or \
fname == '.coverage':
fdel = os.path.join(root, fname)
os.unlink(fdel)
print "deleted", fdel
@task
@needs(["clean"])
def distclean():
"""make clean, and remove any dist/build/egg stuff from project"""
for dirname in [os.path.join('pygtktalog', 'locale'), 'build', 'dist',
'pyGTKtalog.egg-info']:
if os.path.exists(dirname):
shutil.rmtree(dirname, ignore_errors=True)
print "removed directory", dirname
for filename in ['paver-minilib.zip', 'setup.py', 'tests/.coverage']:
if os.path.exists(filename):
os.unlink(filename)
print "deleted", filename
@task
def run():
"""run application"""
sh("PYTHONPATH=%s:$PYTHONPATH bin/gtktalog.py" % _setup_env())
#import gtktalog
#gtktalog.run()
@task
def pot():
"""generate 'pot' file out of python/glade files"""
if not os.path.exists('locale'):
os.mkdir('locale')
if not os.path.exists(POTFILE):
fname = open(POTFILE, "w")
fname.write(PO_HEADER % {'time': datetime.now()})
cmd = "xgettext --omit-header -k_ -kN_ -j -o %s %s"
cmd_glade = "intltool-extract --type=gettext/glade %s"
for walk_tuple in os.walk("pygtktalog"):
root = walk_tuple[0]
for fname in walk_tuple[2]:
if fname.endswith(".py"):
sh(cmd % (POTFILE, os.path.join(root, fname)))
elif fname.endswith(".glade"):
sh(cmd_glade % os.path.join(root, fname))
sh(cmd % (POTFILE, os.path.join(root, fname + ".h")))
@task
@needs(['pot'])
def locale_merge():
"""create or merge if exists 'po' translation files"""
potfile = os.path.join('locale', 'pygtktalog.pot')
for lang in LOCALES:
msg_catalog = os.path.join('locale', "%s.po" % lang)
if os.path.exists(msg_catalog):
sh('msgmerge -U %s %s' % (msg_catalog, potfile))
else:
shutil.copy(potfile, msg_catalog)
@task
@needs(['locale_merge'])
def locale_gen():
"""generate message catalog file for available locale files"""
full_path = os.path.join('pygtktalog', 'locale')
if not os.path.exists(full_path):
os.mkdir(full_path)
for lang in LOCALES:
lang_path = full_path
for dirname in [lang, 'LC_MESSAGES']:
lang_path = os.path.join(lang_path, dirname)
if not os.path.exists(lang_path):
os.mkdir(lang_path)
catalog_file = os.path.join(lang_path, 'pygtktalog.mo')
msg_catalog = os.path.join('locale', "%s.po" % lang)
sh('msgfmt %s -o %s' % (msg_catalog, catalog_file))
if HAVE_LINT:
@task
def pylint():
'''Check the module you're building with pylint.'''
pylintopts = ['pygtktalog']
dry('pylint %s' % (" ".join(pylintopts)), lint.Run, pylintopts)
@task
@cmdopts([('coverage', 'c', 'display coverage information')])
def test(options):
"""run unit tests"""
cmd = "PYTHONPATH=%s:$PYTHONPATH nosetests -w tests" % _setup_env()
if hasattr(options.test, 'coverage'):
cmd += " --with-coverage --cover-package pygtktalog"
os.system(cmd)
@task
@needs(['locale_gen'])
def runpl():
"""run application with pl_PL localization. This is just for my
convenience"""
os.environ['LC_ALL'] = 'pl_PL.utf8'
run()
def _setup_env():
"""Helper function to set up paths"""
# current directory
this_path = os.path.dirname(os.path.abspath(__file__))
if this_path not in sys.path:
sys.path.insert(0, this_path)
return this_path

View File

@@ -5,54 +5,14 @@
Author: Roman 'gryf' Dobosz, gryf73@gmail.com Author: Roman 'gryf' Dobosz, gryf73@gmail.com
Created: 2009-05-05 Created: 2009-05-05
""" """
__version__ = "3.0.0"
__version__ = "2.0.0"
__appname__ = "pyGTKtalog" __appname__ = "pyGTKtalog"
__copyright__ = u"\u00A9 Roman 'gryf' Dobosz" __copyright__ = "\u00A9 Roman 'gryf' Dobosz"
__summary__ = "%s is simple tool for managing file collections." % __appname__ __summary__ = "%s is simple tool for managing file collections." % __appname__
__web__ = "http://github.com/gryf/pygtktalog" __web__ = "http://github.com/gryf/pygtktalog"
import os
import sys
import locale
import gettext
import __builtin__
from pygtktalog.logger import get_logger
__all__ = ['dbcommon', __all__ = ['dbcommon',
'dbobjects', 'dbobjects',
'dialogs', 'dialogs',
'logger', 'logger',
'misc'] 'misc']
GETTEXT_DOMAIN = 'pygtktalog'
# There should be message catalogs in "locale" directory placed by setup.py
# script. If there is no such directory, let's assume that message catalogs are
# placed in system wide location such as /usr/share/locale by Linux
# distribution package maintainer.
LOCALE_PATH = os.path.join(os.path.abspath(os.path.dirname(__file__)),
'locale')
try:
locale.setlocale(locale.LC_ALL, '')
except locale.Error:
# unknown locale string, fallback to C
locale.setlocale(locale.LC_ALL, 'C')
# for module in gtk.glade, gettext:
# if os.path.exists(LOCALE_PATH):
# module.bindtextdomain(GETTEXT_DOMAIN, LOCALE_PATH)
# else:
# module.bindtextdomain(GETTEXT_DOMAIN)
# module.textdomain(GETTEXT_DOMAIN)
# register the gettext function for the whole interpreter as "_"
__builtin__._ = gettext.gettext
# wrap errors into usefull message
#def log_exception(exc_type, exc_val, traceback):
# get_logger(__name__).error(exc_val)
#
#sys.excepthook = log_exception

View File

@@ -12,11 +12,6 @@ from sqlalchemy.ext.declarative import declarative_base
from pygtktalog.logger import get_logger from pygtktalog.logger import get_logger
# setup SQLAlchemy logging facility
# TODO: Logger("sqlalchemy")
# or maybe it will be better to separate sqlalchemy stuff from application
#get_logger("sqlalchemy", 'INFO')
# Prepare SQLAlchemy objects # Prepare SQLAlchemy objects
Meta = MetaData() Meta = MetaData()
Base = declarative_base(metadata=Meta) Base = declarative_base(metadata=Meta)

View File

@@ -63,8 +63,7 @@ class File(Base):
self.source = src self.source = src
def __repr__(self): def __repr__(self):
return "<File('%s', %s)>" % (self.filename.encode('utf-8'), return "<File('%s', %s)>" % (self.filename, str(self.id))
str(self.id))
def get_all_children(self): def get_all_children(self):
""" """
@@ -149,8 +148,8 @@ class Thumbnail(Base):
if not os.path.exists(os.path.join(img_path, *new_name)): if not os.path.exists(os.path.join(img_path, *new_name)):
shutil.move(thumb, os.path.join(img_path, *new_name)) shutil.move(thumb, os.path.join(img_path, *new_name))
else: else:
LOG.info("Thumbnail already exists (%s: %s)" % \ LOG.info("Thumbnail already exists (%s: %s)",
(fname, "/".join(new_name))) fname, "/".join(new_name))
os.unlink(thumb) os.unlink(thumb)
def __repr__(self): def __repr__(self):
@@ -203,7 +202,6 @@ class Image(Base):
else: else:
LOG.info("Thumbnail already generated %s" % "/".join(new_name)) LOG.info("Thumbnail already generated %s" % "/".join(new_name))
def get_copy(self): def get_copy(self):
""" """
Create the very same object as self with exception of id field Create the very same object as self with exception of id field

View File

@@ -138,21 +138,30 @@ class MainWindow(object):
def _setup_menu_toolbar(self, vbox): def _setup_menu_toolbar(self, vbox):
"""Create menu/toolbar using uimanager.""" """Create menu/toolbar using uimanager."""
actions = [('File', None, '_File'), actions = [('File', None, '_File'),
('New', gtk.STOCK_NEW, '_New', None, 'Create new catalog', self.on_new), ('New', gtk.STOCK_NEW, '_New', None,
('Open', gtk.STOCK_OPEN, '_Open', None, 'Open catalog file', self.on_open), 'Create new catalog', self.on_new),
('Save', gtk.STOCK_SAVE, '_Save', None, 'Save catalog file', self.on_save), ('Open', gtk.STOCK_OPEN, '_Open', None,
('Save As', gtk.STOCK_SAVE_AS, '_Save As', None, None, self.on_save), 'Open catalog file', self.on_open),
('Save', gtk.STOCK_SAVE, '_Save', None,
'Save catalog file', self.on_save),
('Save As', gtk.STOCK_SAVE_AS,
'_Save As', None, None, self.on_save),
('Import', None, '_Import', None, None, self.on_import), ('Import', None, '_Import', None, None, self.on_import),
('Export', None, '_Export', None, None, self.on_export), ('Export', None, '_Export', None, None, self.on_export),
('Recent', None, '_Recent files'), ('Recent', None, '_Recent files'),
('Quit', gtk.STOCK_QUIT, '_Quit', None, 'Quit the Program', self.on_quit), ('Quit', gtk.STOCK_QUIT, '_Quit', None,
'Quit the Program', self.on_quit),
('Edit', None, '_Edit'), ('Edit', None, '_Edit'),
('Delete', gtk.STOCK_DELETE, '_Delete', None, None, self.on_delete), ('Delete', gtk.STOCK_DELETE, '_Delete', None, None,
('Find', gtk.STOCK_FIND, '_Find', None, 'Find file', self.on_find), self.on_delete),
('Find', gtk.STOCK_FIND, '_Find', None, 'Find file',
self.on_find),
('Preferences', gtk.STOCK_PREFERENCES, '_Preferences'), ('Preferences', gtk.STOCK_PREFERENCES, '_Preferences'),
('Catalog', None, '_Catalog'), ('Catalog', None, '_Catalog'),
('Add_CD', gtk.STOCK_CDROM, '_Add CD', None, 'Add CD/DVD/BR to catalog'), ('Add_CD', gtk.STOCK_CDROM, '_Add CD', None,
('Add_Dir', gtk.STOCK_DIRECTORY, '_Add Dir', None, 'Add directory to catalog'), 'Add CD/DVD/BR to catalog'),
('Add_Dir', gtk.STOCK_DIRECTORY, '_Add Dir', None,
'Add directory to catalog'),
('Delete_all_images', None, '_Delete all images'), ('Delete_all_images', None, '_Delete all images'),
('Delete_all_thumbnails', None, '_Delete all thumbnails'), ('Delete_all_thumbnails', None, '_Delete all thumbnails'),
('Save_all_images', None, '_Save all images…'), ('Save_all_images', None, '_Save all images…'),
@@ -244,5 +253,5 @@ class MainWindow(object):
def run(): def run():
gui = MainWindow() MainWindow()
gtk.mainloop() gtk.mainloop()

View File

@@ -27,6 +27,7 @@ COLORS = {'WARNING': YELLOW,
'CRITICAL': WHITE, 'CRITICAL': WHITE,
'ERROR': RED} 'ERROR': RED}
def cprint(txt, color): def cprint(txt, color):
color_map = {"black": BLACK, color_map = {"black": BLACK,
"red": RED, "red": RED,
@@ -36,7 +37,7 @@ def cprint(txt, color):
"magenta": MAGENTA, "magenta": MAGENTA,
"cyan": CYAN, "cyan": CYAN,
"white": WHITE} "white": WHITE}
print COLOR_SEQ % (30 + color_map[color]) + txt + RESET_SEQ print(COLOR_SEQ % (30 + color_map[color]) + txt + RESET_SEQ)
class DummyFormater(logging.Formatter): class DummyFormater(logging.Formatter):
@@ -58,13 +59,11 @@ class ColoredFormatter(logging.Formatter):
record.levelname = levelname_color record.levelname = levelname_color
return logging.Formatter.format(self, record) return logging.Formatter.format(self, record)
log_obj = None log_obj = None
#def get_logger(module_name, level='INFO', to_file=False):
#def get_logger(module_name, level='DEBUG', to_file=True):
def get_logger(module_name, level='INFO', to_file=True, to_console=True): def get_logger(module_name, level='INFO', to_file=True, to_console=True):
# def get_logger(module_name, level='DEBUG', to_file=True, to_console=True):
#def get_logger(module_name, level='DEBUG', to_file=False):
""" """
Prepare and return log object. Standard formatting is used for all logs. Prepare and return log object. Standard formatting is used for all logs.
Arguments: Arguments:
@@ -83,11 +82,9 @@ def get_logger(module_name, level='INFO', to_file=True, to_console=True):
log.setLevel(LEVEL[level]) log.setLevel(LEVEL[level])
if to_console: if to_console:
#path = "/dev/null"
console_handler = logging.StreamHandler(sys.stderr) console_handler = logging.StreamHandler(sys.stderr)
console_formatter = ColoredFormatter("%(filename)s:%(lineno)s - " console_formatter = ColoredFormatter("%(filename)s:%(lineno)s - "
"%(levelname)s - %(message)s") "%(levelname)s - %(message)s")
console_handler.setFormatter(console_formatter) console_handler.setFormatter(console_formatter)
log.addHandler(console_handler) log.addHandler(console_handler)

View File

@@ -29,6 +29,7 @@ def float_to_string(float_length):
sec = int(float_length) sec = int(float_length)
return "%02d:%02d:%02d" % (hour, minutes, sec) return "%02d:%02d:%02d" % (hour, minutes, sec)
def calculate_image_path(dbpath=None, create=False): def calculate_image_path(dbpath=None, create=False):
"""Calculate image path out of provided path or using current connection""" """Calculate image path out of provided path or using current connection"""
if not dbpath: if not dbpath:
@@ -50,7 +51,7 @@ def calculate_image_path(dbpath=None, create=False):
if not os.path.exists(images_dir): if not os.path.exists(images_dir):
try: try:
os.mkdir(images_dir) os.mkdir(images_dir)
except OSError, err: except OSError as err:
if err.errno != errno.EEXIST: if err.errno != errno.EEXIST:
raise raise
elif not os.path.exists(images_dir): elif not os.path.exists(images_dir):
@@ -58,9 +59,10 @@ def calculate_image_path(dbpath=None, create=False):
return os.path.abspath(images_dir) return os.path.abspath(images_dir)
def mk_paths(fname, img_path): def mk_paths(fname, img_path):
"""Make path for provided pathname by calculating crc32 out of file""" """Make path for provided pathname by calculating crc32 out of file"""
with open(fname) as fobj: with open(fname, 'r+b') as fobj:
new_path = "%x" % (crc32(fobj.read(10*1024*1024)) & 0xffffffff) new_path = "%x" % (crc32(fobj.read(10*1024*1024)) & 0xffffffff)
new_path = [new_path[i:i + 2] for i in range(0, len(new_path), 2)] new_path = [new_path[i:i + 2] for i in range(0, len(new_path), 2)]

View File

@@ -1,11 +1,12 @@
""" """
Project: pyGTKtalog Project: pyGTKtalog
Description: pyGTK common utility functions Description: pyGTK common utility functions
Type: tility Type: utility
Author: Roman 'gryf' Dobosz, gryf73@gmail.com Author: Roman 'gryf' Dobosz, gryf73@gmail.com
Created: 2010-11-07 13:30:37 Created: 2010-11-07 13:30:37
""" """
def get_tv_item_under_cursor(treeview): def get_tv_item_under_cursor(treeview):
""" """
Get item (most probably id of the row) form tree view under cursor. Get item (most probably id of the row) form tree view under cursor.
@@ -22,4 +23,3 @@ def get_tv_item_under_cursor(treeview):
item_id = model.get_value(tm_iter, 0) item_id = model.get_value(tm_iter, 0)
return item_id return item_id
return None return None

View File

@@ -6,7 +6,6 @@
Created: 2011-03-27 Created: 2011-03-27
""" """
import os import os
import sys
import re import re
from datetime import datetime from datetime import datetime
import mimetypes import mimetypes
@@ -26,7 +25,6 @@ RE_FN_START = re.compile(r'(?P<fname_start>'
r'(\[[A-Fa-f0-9]{8}\])\..*') r'(\[[A-Fa-f0-9]{8}\])\..*')
class NoAccessError(Exception): class NoAccessError(Exception):
"""No access exception""" """No access exception"""
pass pass
@@ -114,7 +112,7 @@ class Scan(object):
# number of objects to retrieve at once. Limit is 999. Let's do a # number of objects to retrieve at once. Limit is 999. Let's do a
# little bit below. # little bit below.
num = 900 num = 900
steps = len(all_ids) / num + 1 steps = len(all_ids) // num + 1
for step in range(steps): for step in range(steps):
all_obj.extend(self._session all_obj.extend(self._session
.query(File) .query(File)
@@ -155,7 +153,7 @@ class Scan(object):
# in case of such, better get me a byte string. It is not perfect # in case of such, better get me a byte string. It is not perfect
# though, since it WILL crash if the update_path would contain some # though, since it WILL crash if the update_path would contain some
# unconvertable characters. # unconvertable characters.
update_path = update_path.encode("utf-8") update_path = update_path
# refresh objects # refresh objects
LOG.debug("Refreshing objects") LOG.debug("Refreshing objects")
@@ -181,8 +179,8 @@ class Scan(object):
# self._session.merge(self._files[0]) # self._session.merge(self._files[0])
LOG.debug("Deleting objects whitout parent: %s", LOG.debug("Deleting objects whitout parent: %s",
str(self._session.query(File) str(self._session.query(File)
.filter(File.parent==None).all())) .filter(File.parent.is_(None)).all()))
self._session.query(File).filter(File.parent==None).delete() self._session.query(File).filter(File.parent.is_(None)).delete()
self._session.commit() self._session.commit()
return self._files return self._files
@@ -199,8 +197,7 @@ class Scan(object):
'.ogm': 'video', '.ogm': 'video',
'.ogv': 'video'} '.ogv': 'video'}
fp = os.path.join(fobj.filepath.encode(sys.getfilesystemencoding()), fp = os.path.join(fobj.filepath, fobj.filename)
fobj.filename.encode(sys.getfilesystemencoding()))
mimeinfo = mimetypes.guess_type(fp) mimeinfo = mimetypes.guess_type(fp)
if mimeinfo[0]: if mimeinfo[0]:
@@ -208,7 +205,7 @@ class Scan(object):
ext = os.path.splitext(fp)[1] ext = os.path.splitext(fp)[1]
if mimeinfo and mimeinfo in mimedict.keys(): if mimeinfo and mimeinfo in mimedict:
mimedict[mimeinfo](fobj, fp) mimedict[mimeinfo](fobj, fp)
elif ext and ext in extdict: elif ext and ext in extdict:
mimedict[extdict[ext]](fobj, fp) mimedict[extdict[ext]](fobj, fp)
@@ -287,13 +284,8 @@ class Scan(object):
""" """
fullpath = os.path.join(path, fname) fullpath = os.path.join(path, fname)
fname = fname.decode(sys.getfilesystemencoding(),
errors="replace")
path = path.decode(sys.getfilesystemencoding(),
errors="replace")
if ftype == TYPE['link']: if ftype == TYPE['link']:
fname = fname + " -> " + os.readlink(fullpath).decode('utf-8') fname = fname + " -> " + os.readlink(fullpath)
fob = {'filename': fname, fob = {'filename': fname,
'path': path, 'path': path,
@@ -378,7 +370,7 @@ class Scan(object):
LOG.info("Scanning `%s' [%s/%s]", fullpath, self.current_count, LOG.info("Scanning `%s' [%s/%s]", fullpath, self.current_count,
self.files_count) self.files_count)
root, dirs, files = os.walk(fullpath).next() root, dirs, files = next(os.walk(fullpath))
for fname in files: for fname in files:
fpath = os.path.join(root, fname) fpath = os.path.join(root, fname)
extension = os.path.splitext(fname)[1] extension = os.path.splitext(fname)[1]
@@ -423,7 +415,7 @@ class Scan(object):
for dirname in dirs: for dirname in dirs:
dirpath = os.path.join(root, dirname) dirpath = os.path.join(root, dirname)
if not os.access(dirpath, os.R_OK|os.X_OK): if not os.access(dirpath, os.R_OK | os.X_OK):
LOG.info("Cannot access directory %s", dirpath) LOG.info("Cannot access directory %s", dirpath)
continue continue
@@ -481,8 +473,8 @@ class Scan(object):
def _set_image_path(self): def _set_image_path(self):
"""Get or calculate the images path""" """Get or calculate the images path"""
image_path = self._session.query(Config) \ image_path = (self._session.query(Config)
.filter(Config.key=="image_path").one() .filter(Config.key == "image_path")).one()
if image_path.value == ":same_as_db:": if image_path.value == ":same_as_db:":
image_path = pygtktalog.misc.calculate_image_path() image_path = pygtktalog.misc.calculate_image_path()
else: else:
@@ -507,4 +499,3 @@ def _get_dirsize(path):
os.path.join(root, fname)) os.path.join(root, fname))
LOG.debug("_get_dirsize, %s: %d", path, size) LOG.debug("_get_dirsize, %s: %d", path, size)
return size return size

View File

@@ -105,7 +105,7 @@ class ThumbCreator(object):
""" """
try: try:
image_thumb = Image.open(self.filename).convert('RGB') image_thumb = Image.open(self.filename).convert('RGB')
except: except Exception:
return None return None
it_x, it_y = image_thumb.size it_x, it_y = image_thumb.size
if it_x > self.thumb_x or it_y > self.thumb_y: if it_x > self.thumb_x or it_y > self.thumb_y:

View File

@@ -6,12 +6,13 @@
Author: Roman 'gryf' Dobosz, gryf73@gmail.com Author: Roman 'gryf' Dobosz, gryf73@gmail.com
Created: 2009-04-04 Created: 2009-04-04
""" """
import math
import os import os
import shutil import shutil
from tempfile import mkdtemp, mkstemp import tempfile
import math
from PIL import Image from PIL import Image
from pygtktalog.misc import float_to_string from pygtktalog.misc import float_to_string
from pygtktalog.logger import get_logger from pygtktalog.logger import get_logger
@@ -49,7 +50,7 @@ class Video(object):
'ID_AUDIO_CODEC': ['audio_codec', self._return_lower], 'ID_AUDIO_CODEC': ['audio_codec', self._return_lower],
'ID_AUDIO_FORMAT': ['audio_format', self._return_lower], 'ID_AUDIO_FORMAT': ['audio_format', self._return_lower],
'ID_AUDIO_NCH': ['audio_no_channels', int]} 'ID_AUDIO_NCH': ['audio_no_channels', int]}
# TODO: what about audio/subtitle language/existence? # TODO: what about audio/subtitle language/existence?
for key in output: for key in output:
if key in attrs: if key in attrs:
@@ -58,9 +59,9 @@ class Video(object):
if 'length' in self.tags and self.tags['length'] > 0: if 'length' in self.tags and self.tags['length'] > 0:
start = self.tags.get('start', 0) start = self.tags.get('start', 0)
length = self.tags['length'] - start length = self.tags['length'] - start
hours = length / 3600 hours = length // 3600
seconds = length - hours * 3600 seconds = length - hours * 3600
minutes = seconds / 60 minutes = seconds // 60
seconds -= minutes * 60 seconds -= minutes * 60
length_str = "%02d:%02d:%02d" % (hours, minutes, seconds) length_str = "%02d:%02d:%02d" % (hours, minutes, seconds)
self.tags['duration'] = length_str self.tags['duration'] = length_str
@@ -92,16 +93,16 @@ class Video(object):
if scale < 1: if scale < 1:
return None return None
no_pictures = self.tags['length'] / scale no_pictures = self.tags['length'] // scale
if no_pictures > 8: if no_pictures > 8:
no_pictures = (no_pictures / 8) * 8 # only multiple of 8, please. no_pictures = (no_pictures // 8) * 8 # only multiple of 8, please.
else: else:
# for really short movies # for really short movies
no_pictures = 4 no_pictures = 4
tempdir = mkdtemp() tempdir = tempfile.mkdtemp()
file_desc, image_fn = mkstemp(suffix=".jpg") file_desc, image_fn = tempfile.mkstemp(suffix=".jpg")
os.close(file_desc) os.close(file_desc)
self._make_captures(tempdir, no_pictures) self._make_captures(tempdir, no_pictures)
self._make_montage(tempdir, image_fn, no_pictures) self._make_montage(tempdir, image_fn, no_pictures)
@@ -113,16 +114,16 @@ class Video(object):
""" """
Return formatted tags as a string Return formatted tags as a string
""" """
out_tags = u'' out_tags = ''
if 'container' in self.tags: if 'container' in self.tags:
out_tags += u"Container: %s\n" % self.tags['container'] out_tags += "Container: %s\n" % self.tags['container']
if 'width' in self.tags and 'height' in self.tags: if 'width' in self.tags and 'height' in self.tags:
out_tags += u"Resolution: %sx%s\n" % (self.tags['width'], out_tags += "Resolution: %sx%s\n" % (self.tags['width'],
self.tags['height']) self.tags['height'])
if 'duration' in self.tags: if 'duration' in self.tags:
out_tags += u"Duration: %s\n" % self.tags['duration'] out_tags += "Duration: %s\n" % self.tags['duration']
if 'video_codec' in self.tags: if 'video_codec' in self.tags:
out_tags += "Video codec: %s\n" % self.tags['video_codec'] out_tags += "Video codec: %s\n" % self.tags['video_codec']
@@ -178,20 +179,21 @@ class Video(object):
@directory - full output directory name @directory - full output directory name
@no_pictures - number of pictures to take @no_pictures - number of pictures to take
""" """
step = float(self.tags['length'] / (no_pictures + 1)) step = self.tags['length'] / (no_pictures + 1)
current_time = 0 current_time = 0
for dummy in range(1, no_pictures + 1): for dummy in range(1, no_pictures + 1):
current_time += step current_time += step
time = float_to_string(current_time) time = float_to_string(current_time)
cmd = "mplayer \"%s\" -ao null -brightness 0 -hue 0 " \ cmd = ('mplayer "%s" -ao null -brightness 0 -hue 0 '
"-saturation 0 -contrast 0 -mc 0 -vf-clr -vo jpeg:outdir=\"%s\" -ss %s" \ '-saturation 0 -contrast 0 -mc 0 -vf-clr '
" -frames 1 2>/dev/null" '-vo jpeg:outdir="%s" -ss %s -frames 1 2>/dev/null')
os.popen(cmd % (self.filename, directory, time)).readlines() os.popen(cmd % (self.filename, directory, time)).readlines()
try: try:
shutil.move(os.path.join(directory, "00000001.jpg"), shutil.move(os.path.join(directory, "00000001.jpg"),
os.path.join(directory, "picture_%s.jpg" % time)) os.path.join(directory, "picture_%s.jpg" % time))
except IOError, (errno, strerror): except IOError as exc:
errno, strerror = exc.args
LOG.error('error capturing file from movie "%s" at position ' LOG.error('error capturing file from movie "%s" at position '
'%s. Errors: %s, %s', self.filename, time, errno, '%s. Errors: %s, %s', self.filename, time, errno,
strerror) strerror)
@@ -216,13 +218,13 @@ class Video(object):
if not (self.tags['width'] * row_length) > self.out_width: if not (self.tags['width'] * row_length) > self.out_width:
for i in [8, 6, 5]: for i in [8, 6, 5]:
if (no_pictures % i) == 0 and \ if ((no_pictures % i) == 0 and
(i * self.tags['width']) <= self.out_width: (i * self.tags['width']) <= self.out_width):
row_length = i row_length = i
break break
coef = float(self.out_width - row_length - 1) / \ coef = (float(self.out_width - row_length - 1) /
(self.tags['width'] * row_length) (self.tags['width'] * row_length))
if coef < 1: if coef < 1:
dim = (int(self.tags['width'] * coef), dim = (int(self.tags['width'] * coef),
int(self.tags['height'] * coef)) int(self.tags['height'] * coef))
@@ -231,10 +233,10 @@ class Video(object):
ifn_list = os.listdir(directory) ifn_list = os.listdir(directory)
ifn_list.sort() ifn_list.sort()
img_list = [Image.open(os.path.join(directory, fn)).resize(dim) \ img_list = [Image.open(os.path.join(directory, fn)).resize(dim)
for fn in ifn_list] for fn in ifn_list]
rows = no_pictures / row_length rows = no_pictures // row_length
cols = row_length cols = row_length
isize = (cols * dim[0] + cols + 1, isize = (cols * dim[0] + cols + 1,
rows * dim[1] + rows + 1) rows * dim[1] + rows + 1)
@@ -250,7 +252,7 @@ class Video(object):
bbox = (left, upper, right, lower) bbox = (left, upper, right, lower)
try: try:
img = img_list.pop(0) img = img_list.pop(0)
except: except Exception:
break break
inew.paste(img, bbox) inew.paste(img, bbox)
inew.save(image_fn, 'JPEG') inew.save(image_fn, 'JPEG')
@@ -271,7 +273,7 @@ class Video(object):
""" """
try: try:
return int(chain.split(".")[0]) return int(chain.split(".")[0])
except: except Exception:
return 0 return 0
def __str__(self): def __str__(self):

View File

@@ -3,7 +3,6 @@
Fast and ugly CLI interface for pyGTKtalog Fast and ugly CLI interface for pyGTKtalog
""" """
import argparse import argparse
import errno
import os import os
import re import re
import sys import sys
@@ -24,6 +23,7 @@ BOLD_SEQ = '\033[1m'
LOG = logger.get_logger(__name__) LOG = logger.get_logger(__name__)
def colorize(txt, color): def colorize(txt, color):
"""Pretty print with colors to console.""" """Pretty print with colors to console."""
color_map = {'black': BLACK, color_map = {'black': BLACK,
@@ -40,7 +40,7 @@ def colorize(txt, color):
def asserdb(func): def asserdb(func):
def wrapper(args): def wrapper(args):
if not os.path.exists(args.db): if not os.path.exists(args.db):
print colorize("File `%s' does not exists!" % args.db, 'red') print(colorize("File `%s' does not exists!" % args.db, 'red'))
sys.exit(1) sys.exit(1)
func(args) func(args)
return wrapper return wrapper
@@ -151,7 +151,7 @@ class Iface(object):
node = self.root node = self.root
msg = "Content of path `/':" msg = "Content of path `/':"
print colorize(msg, 'white') print(colorize(msg, 'white'))
if recursive: if recursive:
items = self._walk(node) items = self._walk(node)
@@ -168,7 +168,7 @@ class Iface(object):
else: else:
filenames = sorted(items.keys()) filenames = sorted(items.keys())
print '\n'.join(filenames) print('\n'.join(filenames))
def update(self, path, dir_to_update=None): def update(self, path, dir_to_update=None):
""" """
@@ -179,8 +179,8 @@ class Iface(object):
self.root = self.root.filter(dbo.File.type == dbo.TYPE['root']).first() self.root = self.root.filter(dbo.File.type == dbo.TYPE['root']).first()
node = self._resolve_path(path) node = self._resolve_path(path)
if node == self.root: if node == self.root:
print colorize('Cannot update entire db, since root was provided ' print(colorize('Cannot update entire db, since root was provided '
'as path.', 'red') 'as path.', 'red'))
return return
if not dir_to_update: if not dir_to_update:
@@ -189,8 +189,8 @@ class Iface(object):
if not os.path.exists(dir_to_update): if not os.path.exists(dir_to_update):
raise OSError("Path to updtate doesn't exists: %s", dir_to_update) raise OSError("Path to updtate doesn't exists: %s", dir_to_update)
print colorize("Updating node `%s' against directory " print(colorize("Updating node `%s' against directory "
"`%s'" % (path, dir_to_update), 'white') "`%s'" % (path, dir_to_update), 'white'))
if not self.dry_run: if not self.dry_run:
scanob = scan.Scan(dir_to_update) scanob = scan.Scan(dir_to_update)
# scanob.update_files(node.id) # scanob.update_files(node.id)
@@ -215,8 +215,8 @@ class Iface(object):
self.sess.add(config) self.sess.add(config)
self.sess.commit() self.sess.commit()
print colorize("Creating new db against directory `%s'" % dir_to_add, print(colorize("Creating new db against directory `%s'" % dir_to_add,
'white') 'white'))
if not self.dry_run: if not self.dry_run:
if data_dir == ':same_as_db:': if data_dir == ':same_as_db:':
misc.calculate_image_path(None, True) misc.calculate_image_path(None, True)
@@ -234,7 +234,7 @@ class Iface(object):
if not os.path.exists(dir_to_add): if not os.path.exists(dir_to_add):
raise OSError("Path to add doesn't exists: %s", dir_to_add) raise OSError("Path to add doesn't exists: %s", dir_to_add)
print colorize("Adding directory `%s'" % dir_to_add, 'white') print(colorize("Adding directory `%s'" % dir_to_add, 'white'))
if not self.dry_run: if not self.dry_run:
scanob = scan.Scan(dir_to_add) scanob = scan.Scan(dir_to_add)
scanob.add_files() scanob.add_files()
@@ -273,30 +273,29 @@ class Iface(object):
result = [] result = []
for word in search_words: for word in search_words:
phrase = u'%%%s%%' % word.decode('utf-8') phrase = u'%%%s%%' % word
query = query.filter(dbo.File.filename.like(phrase)) query = query.filter(dbo.File.filename.like(phrase))
for item in query.all(): for item in query.all():
result.append(self._get_full_path(item)) result.append(self._get_full_path(item))
if not result: if not result:
print "No results for `%s'" % ' '.join(search_words) print("No results for `%s'" % ' '.join(search_words))
return return
result.sort() result.sort()
for item in result: for item in result:
print self._annotate(item, search_words) print(self._annotate(item, search_words))
def fsck(self): def fsck(self):
"""Fsck orphaned images/thumbs""" """Fsck orphaned images/thumbs"""
image_path = self.sess.query(dbo.Config).\ image_path = (self.sess.query(dbo.Config)
filter(dbo.Config.key=='image_path').one().value .filter(dbo.Config.key == 'image_path')).one().value
if image_path == ':same_as_db:': if image_path == ':same_as_db:':
image_path = misc.calculate_image_path(None, False) image_path = misc.calculate_image_path(None, False)
files_to_remove = [] files_to_remove = []
obj_to_remove = []
# remove images/thumbnails which doesn't have file relation # remove images/thumbnails which doesn't have file relation
for name, obj in (("images", dbo.Image), for name, obj in (("images", dbo.Image),
@@ -318,20 +317,20 @@ class Iface(object):
fname).lstrip('/') fname).lstrip('/')
if '_t' in fname: if '_t' in fname:
obj = self.sess.query(dbo.Thumbnail)\ obj = (self.sess.query(dbo.Thumbnail)
.filter(dbo.Thumbnail.filename==fname_).all() .filter(dbo.Thumbnail.filename == fname_)).all()
if obj: if obj:
continue continue
obj = self.sess.query(dbo.Image)\ obj = (self.sess.query(dbo.Image)
.filter(dbo.Image.filename==\ .filter(dbo.Image.filename ==
fname_.replace('_t.', '.')).all() fname_.replace('_t.', '.'))).all()
if obj: if obj:
continue continue
else: else:
obj = self.sess.query(dbo.Image)\ obj = (self.sess.query(dbo.Image)
.filter(dbo.Image.filename==fname_).all() .filter(dbo.Image.filename == fname_)).all()
if obj: if obj:
continue continue
@@ -342,9 +341,9 @@ class Iface(object):
sys.stdout.flush() sys.stdout.flush()
if self.dry_run: if self.dry_run:
print "Following files are not associated to any items in the DB:" print("Following files are not associated to any items in the DB:")
for filename in sorted(files_to_remove): for filename in sorted(files_to_remove):
print filename print(filename)
self.sess.rollback() self.sess.rollback()
else: else:
_remove_files(image_path, files_to_remove) _remove_files(image_path, files_to_remove)
@@ -417,13 +416,13 @@ def add_dir(args):
obj.close() obj.close()
@asserdb
def create_db(args): def create_db(args):
"""List""" """List"""
obj = Iface(args.db, args.pretend, args.debug) obj = Iface(args.db, args.pretend, args.debug)
obj.create(args.dir_to_add, args.imagedir) obj.create(args.dir_to_add, args.imagedir)
obj.close() obj.close()
@asserdb @asserdb
def search(args): def search(args):
"""Find""" """Find"""
@@ -431,6 +430,7 @@ def search(args):
obj.find(args.search_words) obj.find(args.search_words)
obj.close() obj.close()
@asserdb @asserdb
def cleanup(args): def cleanup(args):
"""Cleanup""" """Cleanup"""
@@ -439,7 +439,6 @@ def cleanup(args):
obj.close() obj.close()
def main(): def main():
"""Main""" """Main"""
parser = argparse.ArgumentParser() parser = argparse.ArgumentParser()
@@ -472,7 +471,7 @@ def main():
create.add_argument('dir_to_add') create.add_argument('dir_to_add')
create.add_argument('-i', '--imagedir', help="Directory where to put " create.add_argument('-i', '--imagedir', help="Directory where to put "
"images for the database. Popular, but deprecated " "images for the database. Popular, but deprecated "
"choice is `~/.pygtktalog/images'. Currnet default " "choice is `~/.pygtktalog/images'. Current default "
"is special string `:same_as_db:' which will try to " "is special string `:same_as_db:' which will try to "
"create directory with the same name as the db with " "create directory with the same name as the db with "
"data suffix", default=':same_as_db:') "data suffix", default=':same_as_db:')
@@ -510,7 +509,12 @@ def main():
fsck.set_defaults(func=cleanup) fsck.set_defaults(func=cleanup)
args = parser.parse_args() args = parser.parse_args()
args.func(args)
if 'func' in args:
args.func(args)
else:
parser.print_help()
if __name__ == '__main__': if __name__ == '__main__':
main() main()

View File

@@ -64,5 +64,6 @@ def main():
app = App(db) app = App(db)
app.run() app.run()
if __name__ == "__main__": if __name__ == "__main__":
main() main()

View File

@@ -7,7 +7,7 @@ from distutils.core import setup
setup(name='pygtktalog', setup(name='pygtktalog',
packages=['pygtktalog'], packages=['pygtktalog'],
version='2.0', version='3.0',
description='Catalog application with GTK interface', description='Catalog application with GTK interface',
author='Roman Dobosz', author='Roman Dobosz',
author_email='gryf73@gmail.com', author_email='gryf73@gmail.com',
@@ -18,7 +18,8 @@ setup(name='pygtktalog',
scripts=['scripts/cmdcatalog.py'], scripts=['scripts/cmdcatalog.py'],
classifiers=['Programming Language :: Python :: 2', classifiers=['Programming Language :: Python :: 2',
'Programming Language :: Python :: 2.7', 'Programming Language :: Python :: 2.7',
'Programming Language :: Python :: 2 :: Only', 'Programming Language :: Python :: 3',
'Programming Language :: Python :: 3.6',
'Development Status :: 4 - Beta', 'Development Status :: 4 - Beta',
'Environment :: Console', 'Environment :: Console',
'Intended Audience :: End Users/Desktop', 'Intended Audience :: End Users/Desktop',

View File

@@ -23,7 +23,7 @@ def populate_with_mock_files(dir_):
files_no = 0 files_no = 0
for file_ in files1: for file_ in files1:
with open(os.path.join(dir_, file_), "wb") as fobj: with open(os.path.join(dir_, file_), "wb") as fobj:
fobj.write("\xde\xad\xbe\xef" * len(file_)) fobj.write(b"\xde\xad\xbe\xef" * len(file_))
files_no += 1 files_no += 1
os.symlink(os.path.join(dir_, files1[-1]), os.path.join(dir_, 'link.jpg')) os.symlink(os.path.join(dir_, files1[-1]), os.path.join(dir_, 'link.jpg'))
@@ -32,7 +32,7 @@ def populate_with_mock_files(dir_):
os.mkdir(os.path.join(dir_, 'directory')) os.mkdir(os.path.join(dir_, 'directory'))
for file_ in files2: for file_ in files2:
with open(os.path.join(dir_, 'directory', file_), "wb") as fobj: with open(os.path.join(dir_, 'directory', file_), "wb") as fobj:
fobj.write("\xfe\xad\xfa\xce" * len(file_)) fobj.write(b"\xfe\xad\xfa\xce" * len(file_))
files_no += 1 files_no += 1
return files_no return files_no
@@ -178,8 +178,8 @@ class TestScan(unittest.TestCase):
self.assertTrue(file_ob is not file2_ob) self.assertTrue(file_ob is not file2_ob)
# While Image objects points to the same file # While Image objects points to the same file
self.assertTrue(file_ob.images[0].filename == \ self.assertTrue(file_ob.images[0].filename ==
file2_ob.images[0].filename) file2_ob.images[0].filename)
# they are different objects # they are different objects
self.assertTrue(file_ob.images[0] is not file2_ob.images[0]) self.assertTrue(file_ob.images[0] is not file2_ob.images[0])

View File

@@ -7,6 +7,8 @@
""" """
import os import os
import unittest import unittest
from unittest import mock
import io
import PIL import PIL
@@ -130,7 +132,7 @@ ID_AUDIO_RATE=22050
ID_AUDIO_NCH=1 ID_AUDIO_NCH=1
ID_AUDIO_CODEC=ffac3 ID_AUDIO_CODEC=ffac3
ID_EXIT=EOF""", ID_EXIT=EOF""",
"m.wmv":"""ID_AUDIO_ID=1 "m.wmv": """ID_AUDIO_ID=1
ID_VIDEO_ID=2 ID_VIDEO_ID=2
ID_FILENAME=m.wmv ID_FILENAME=m.wmv
ID_DEMUXER=asf ID_DEMUXER=asf
@@ -198,6 +200,7 @@ class Readlines(object):
def readlines(self): def readlines(self):
return self.data.split('\n') return self.data.split('\n')
def mock_popen(command): def mock_popen(command):
key = None key = None
if 'midentify' in command: if 'midentify' in command:
@@ -205,22 +208,25 @@ def mock_popen(command):
elif 'jpeg:outdir' in command: elif 'jpeg:outdir' in command:
# simulate capture for mplayer # simulate capture for mplayer
img_dir = command.split('"')[-2] img_dir = command.split('"')[-2]
img = PIL.Image.new('RGBA', (320, 200)) img = PIL.Image.new('RGB', (320, 200))
with open(os.path.join(img_dir, "00000001.jpg"), "wb") as fobj: with open(os.path.join(img_dir, "00000001.jpg"), "wb") as fobj:
img.save(fobj) img.save(fobj)
return Readlines(key) return Readlines(key)
os.popen = mock_popen # os.popen = mock_popen
class TestVideo(unittest.TestCase): class TestVideo(unittest.TestCase):
"""test class for retrive midentify script output""" """test class for retrive midentify script output"""
def test_avi(self): @mock.patch('os.popen')
def test_avi(self, popen):
"""test mock avi file, should return dict with expected values""" """test mock avi file, should return dict with expected values"""
avi = Video("m.avi") fname = "m.avi"
popen.return_value = io.StringIO(DATA[fname])
avi = Video(fname)
self.assertTrue(len(avi.tags) != 0, "result should have lenght > 0") self.assertTrue(len(avi.tags) != 0, "result should have lenght > 0")
self.assertEqual(avi.tags['audio_format'], '85') self.assertEqual(avi.tags['audio_format'], '85')
self.assertEqual(avi.tags['width'], 128) self.assertEqual(avi.tags['width'], 128)
@@ -233,10 +239,13 @@ class TestVideo(unittest.TestCase):
self.assertEqual(avi.tags['duration'], '00:00:04') self.assertEqual(avi.tags['duration'], '00:00:04')
self.assertEqual(avi.tags['container'], 'avi') self.assertEqual(avi.tags['container'], 'avi')
def test_avi2(self): @mock.patch('os.popen')
def test_avi2(self, popen):
"""test another mock avi file, should return dict with expected """test another mock avi file, should return dict with expected
values""" values"""
avi = Video("m1.avi") fname = "m1.avi"
popen.return_value = io.StringIO(DATA[fname])
avi = Video(fname)
self.assertTrue(len(avi.tags) != 0, "result should have lenght > 0") self.assertTrue(len(avi.tags) != 0, "result should have lenght > 0")
self.assertEqual(avi.tags['audio_format'], '85') self.assertEqual(avi.tags['audio_format'], '85')
self.assertEqual(avi.tags['width'], 128) self.assertEqual(avi.tags['width'], 128)
@@ -249,9 +258,12 @@ class TestVideo(unittest.TestCase):
self.assertEqual(avi.tags['duration'], '00:00:04') self.assertEqual(avi.tags['duration'], '00:00:04')
self.assertEqual(avi.tags['container'], 'avi') self.assertEqual(avi.tags['container'], 'avi')
def test_mkv(self): @mock.patch('os.popen')
def test_mkv(self, popen):
"""test mock mkv file, should return dict with expected values""" """test mock mkv file, should return dict with expected values"""
mkv = Video("m.mkv") fname = "m.mkv"
popen.return_value = io.StringIO(DATA[fname])
mkv = Video(fname)
self.assertTrue(len(mkv.tags) != 0, "result should have lenght > 0") self.assertTrue(len(mkv.tags) != 0, "result should have lenght > 0")
self.assertEqual(mkv.tags['audio_format'], '8192') self.assertEqual(mkv.tags['audio_format'], '8192')
self.assertEqual(mkv.tags['width'], 128) self.assertEqual(mkv.tags['width'], 128)
@@ -264,24 +276,30 @@ class TestVideo(unittest.TestCase):
self.assertEqual(mkv.tags['duration'], '00:00:04') self.assertEqual(mkv.tags['duration'], '00:00:04')
self.assertTrue(mkv.tags['container'] in ('mkv', 'lavfpref')) self.assertTrue(mkv.tags['container'] in ('mkv', 'lavfpref'))
def test_mpg(self): @mock.patch('os.popen')
def test_mpg(self, popen):
"""test mock mpg file, should return dict with expected values""" """test mock mpg file, should return dict with expected values"""
mpg = Video("m.mpg") fname = "m.mpg"
popen.return_value = io.StringIO(DATA[fname])
mpg = Video(fname)
self.assertTrue(len(mpg.tags) != 0, "result should have lenght > 0") self.assertTrue(len(mpg.tags) != 0, "result should have lenght > 0")
self.assertFalse(mpg.tags.has_key('audio_format')) self.assertFalse('audio_format' in mpg.tags)
self.assertEqual(mpg.tags['width'], 128) self.assertEqual(mpg.tags['width'], 128)
self.assertFalse(mpg.tags.has_key('audio_no_channels')) self.assertFalse('audio_no_channels' in mpg.tags)
self.assertEqual(mpg.tags['height'], 96) self.assertEqual(mpg.tags['height'], 96)
self.assertEqual(mpg.tags['video_format'], '0x10000001') self.assertEqual(mpg.tags['video_format'], '0x10000001')
self.assertFalse(mpg.tags.has_key('lenght')) self.assertFalse('lenght' in mpg.tags)
self.assertFalse(mpg.tags.has_key('audio_codec')) self.assertFalse('audio_codec' in mpg.tags)
self.assertEqual(mpg.tags['video_codec'], 'ffmpeg1') self.assertEqual(mpg.tags['video_codec'], 'ffmpeg1')
self.assertFalse(mpg.tags.has_key('duration')) self.assertFalse('duration' in mpg.tags)
self.assertEqual(mpg.tags['container'], 'mpeges') self.assertEqual(mpg.tags['container'], 'mpeges')
def test_ogm(self): @mock.patch('os.popen')
def test_ogm(self, popen):
"""test mock ogm file, should return dict with expected values""" """test mock ogm file, should return dict with expected values"""
ogm = Video("m.ogm") fname = "m.ogm"
popen.return_value = io.StringIO(DATA[fname])
ogm = Video(fname)
self.assertTrue(len(ogm.tags) != 0, "result should have lenght > 0") self.assertTrue(len(ogm.tags) != 0, "result should have lenght > 0")
self.assertEqual(ogm.tags['audio_format'], '8192') self.assertEqual(ogm.tags['audio_format'], '8192')
self.assertEqual(ogm.tags['width'], 160) self.assertEqual(ogm.tags['width'], 160)
@@ -294,9 +312,12 @@ class TestVideo(unittest.TestCase):
self.assertEqual(ogm.tags['duration'], '00:00:04') self.assertEqual(ogm.tags['duration'], '00:00:04')
self.assertTrue(ogm.tags['container'] in ('ogg', 'lavfpref')) self.assertTrue(ogm.tags['container'] in ('ogg', 'lavfpref'))
def test_wmv(self): @mock.patch('os.popen')
def test_wmv(self, popen):
"""test mock wmv file, should return dict with expected values""" """test mock wmv file, should return dict with expected values"""
wmv = Video("m.wmv") fname = "m.wmv"
popen.return_value = io.StringIO(DATA[fname])
wmv = Video(fname)
self.assertTrue(len(wmv.tags) != 0, "result should have lenght > 0") self.assertTrue(len(wmv.tags) != 0, "result should have lenght > 0")
self.assertEqual(wmv.tags['audio_format'], '353') self.assertEqual(wmv.tags['audio_format'], '353')
self.assertEqual(wmv.tags['width'], 852) self.assertEqual(wmv.tags['width'], 852)
@@ -309,9 +330,12 @@ class TestVideo(unittest.TestCase):
self.assertEqual(wmv.tags['duration'], '01:17:32') self.assertEqual(wmv.tags['duration'], '01:17:32')
self.assertEqual(wmv.tags['container'], 'asf') self.assertEqual(wmv.tags['container'], 'asf')
def test_mp4(self): @mock.patch('os.popen')
def test_mp4(self, popen):
"""test mock mp4 file, should return dict with expected values""" """test mock mp4 file, should return dict with expected values"""
mp4 = Video("m.mp4") fname = "m.mp4"
popen.return_value = io.StringIO(DATA[fname])
mp4 = Video(fname)
self.assertTrue(len(mp4.tags) != 0, "result should have lenght > 0") self.assertTrue(len(mp4.tags) != 0, "result should have lenght > 0")
self.assertEqual(mp4.tags['audio_format'], 'mp4a') self.assertEqual(mp4.tags['audio_format'], 'mp4a')
self.assertEqual(mp4.tags['width'], 720) self.assertEqual(mp4.tags['width'], 720)
@@ -324,21 +348,31 @@ class TestVideo(unittest.TestCase):
self.assertEqual(mp4.tags['duration'], '00:01:09') self.assertEqual(mp4.tags['duration'], '00:01:09')
self.assertEqual(mp4.tags['container'], 'lavfpref') self.assertEqual(mp4.tags['container'], 'lavfpref')
def test_capture(self): @mock.patch('shutil.move')
@mock.patch('pygtktalog.video.Image')
@mock.patch('os.listdir')
@mock.patch('shutil.rmtree')
@mock.patch('os.close')
@mock.patch('tempfile.mkstemp')
@mock.patch('tempfile.mkdtemp')
@mock.patch('os.popen')
def test_capture(self, popen, mkdtemp, mkstemp, fclose, rmtree, listdir,
img, move):
"""test capture with some small movie and play a little with tags""" """test capture with some small movie and play a little with tags"""
avi = Video("m.avi") fname = 'm.avi'
popen.return_value = io.StringIO(DATA[fname])
mkdtemp.return_value = '/tmp'
mkstemp.return_value = (10, 'foo.jpg')
listdir.return_value = ['a.jpg', 'b.jpg', 'c.jpg', 'd.jpg']
avi = Video(fname)
filename = avi.capture() filename = avi.capture()
self.assertTrue(filename != None) self.assertIsNotNone(filename)
self.assertTrue(os.path.exists(filename))
file_size = os.stat(filename)[6]
self.assertAlmostEqual(file_size/10000.0, 0.151, 0)
os.unlink(filename)
for length in (480, 380, 4): for length in (480, 380, 4):
avi.tags['length'] = length avi.tags['length'] = length
filename = avi.capture() filename = avi.capture()
self.assertTrue(filename is not None) self.assertTrue(filename is not None)
os.unlink(filename)
avi.tags['length'] = 3 avi.tags['length'] = 3
self.assertTrue(avi.capture() is None) self.assertTrue(avi.capture() is None)
@@ -351,7 +385,6 @@ class TestVideo(unittest.TestCase):
avi.tags['width'] = 1025 avi.tags['width'] = 1025
filename = avi.capture() filename = avi.capture()
self.assertTrue(filename is not None) self.assertTrue(filename is not None)
os.unlink(filename)
del(avi.tags['length']) del(avi.tags['length'])
self.assertTrue(avi.capture() is None) self.assertTrue(avi.capture() is None)

View File

@@ -1,9 +1,10 @@
[tox] [tox]
envlist = cleanup,py27,pep8 envlist = cleanup,py3,pep8
usedevelop = True usedevelop = True
[testenv] [testenv]
basepython = python3
usedevelop=True usedevelop=True
setenv = COVERAGE_FILE = .coverage setenv = COVERAGE_FILE = .coverage
commands = py.test --cov=pygtktalog --cov-report=term-missing commands = py.test --cov=pygtktalog --cov-report=term-missing