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:
* `python 2.7`_
* `sqlalchemy 1.0`_
* `pygtk 2.24`_ (only for ``gtktalog.py``)
* `python 3`_, tested on python 3.6
* `sqlalchemy 1.2`_ or higher
* `pygtk 2.24`_ (only for ``gtktalog.py``, will not work with python3)
* `pillow`_ for image manipulation
* `exifread`_ for parse EXIF information
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
GTK3.
higher versions of libraries. GTK3 support will follow.
pyGTKtalog extensively uses external programs in unix spirit, however there is
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/
.. _pillow: https://python-pillow.org/
.. _pygtk 2.24: http://www.pygtk.org
.. _python 2.7: http://www.python.org/
.. _sqlalchemy 1.0: http://www.sqlalchemy.org
.. _python 3: http://www.python.org/
.. _sqlalchemy 1.2: http://www.sqlalchemy.org
.. _tagging files: http://en.wikipedia.org/wiki/tag_%28metadata%29
.. _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
Created: 2009-05-05
"""
__version__ = "2.0.0"
__version__ = "3.0.0"
__appname__ = "pyGTKtalog"
__copyright__ = u"\u00A9 Roman 'gryf' Dobosz"
__copyright__ = "\u00A9 Roman 'gryf' Dobosz"
__summary__ = "%s is simple tool for managing file collections." % __appname__
__web__ = "http://github.com/gryf/pygtktalog"
import os
import sys
import locale
import gettext
import __builtin__
from pygtktalog.logger import get_logger
__all__ = ['dbcommon',
'dbobjects',
'dialogs',
'logger',
'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
# 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
Meta = MetaData()
Base = declarative_base(metadata=Meta)

View File

@@ -63,8 +63,7 @@ class File(Base):
self.source = src
def __repr__(self):
return "<File('%s', %s)>" % (self.filename.encode('utf-8'),
str(self.id))
return "<File('%s', %s)>" % (self.filename, str(self.id))
def get_all_children(self):
"""
@@ -149,8 +148,8 @@ class Thumbnail(Base):
if not os.path.exists(os.path.join(img_path, *new_name)):
shutil.move(thumb, os.path.join(img_path, *new_name))
else:
LOG.info("Thumbnail already exists (%s: %s)" % \
(fname, "/".join(new_name)))
LOG.info("Thumbnail already exists (%s: %s)",
fname, "/".join(new_name))
os.unlink(thumb)
def __repr__(self):
@@ -203,7 +202,6 @@ class Image(Base):
else:
LOG.info("Thumbnail already generated %s" % "/".join(new_name))
def get_copy(self):
"""
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):
"""Create menu/toolbar using uimanager."""
actions = [('File', None, '_File'),
('New', gtk.STOCK_NEW, '_New', None, 'Create new catalog', self.on_new),
('Open', gtk.STOCK_OPEN, '_Open', None, '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),
('New', gtk.STOCK_NEW, '_New', None,
'Create new catalog', self.on_new),
('Open', gtk.STOCK_OPEN, '_Open', None,
'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),
('Export', None, '_Export', None, None, self.on_export),
('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'),
('Delete', gtk.STOCK_DELETE, '_Delete', None, None, self.on_delete),
('Find', gtk.STOCK_FIND, '_Find', None, 'Find file', self.on_find),
('Delete', gtk.STOCK_DELETE, '_Delete', None, None,
self.on_delete),
('Find', gtk.STOCK_FIND, '_Find', None, 'Find file',
self.on_find),
('Preferences', gtk.STOCK_PREFERENCES, '_Preferences'),
('Catalog', None, '_Catalog'),
('Add_CD', gtk.STOCK_CDROM, '_Add CD', None, 'Add CD/DVD/BR to catalog'),
('Add_Dir', gtk.STOCK_DIRECTORY, '_Add Dir', None, 'Add directory to catalog'),
('Add_CD', gtk.STOCK_CDROM, '_Add CD', None,
'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_thumbnails', None, '_Delete all thumbnails'),
('Save_all_images', None, '_Save all images…'),
@@ -244,5 +253,5 @@ class MainWindow(object):
def run():
gui = MainWindow()
MainWindow()
gtk.mainloop()

View File

@@ -27,6 +27,7 @@ COLORS = {'WARNING': YELLOW,
'CRITICAL': WHITE,
'ERROR': RED}
def cprint(txt, color):
color_map = {"black": BLACK,
"red": RED,
@@ -36,7 +37,7 @@ def cprint(txt, color):
"magenta": MAGENTA,
"cyan": CYAN,
"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):
@@ -58,13 +59,11 @@ class ColoredFormatter(logging.Formatter):
record.levelname = levelname_color
return logging.Formatter.format(self, record)
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='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.
Arguments:
@@ -83,11 +82,9 @@ def get_logger(module_name, level='INFO', to_file=True, to_console=True):
log.setLevel(LEVEL[level])
if to_console:
#path = "/dev/null"
console_handler = logging.StreamHandler(sys.stderr)
console_formatter = ColoredFormatter("%(filename)s:%(lineno)s - "
"%(levelname)s - %(message)s")
"%(levelname)s - %(message)s")
console_handler.setFormatter(console_formatter)
log.addHandler(console_handler)

View File

@@ -29,6 +29,7 @@ def float_to_string(float_length):
sec = int(float_length)
return "%02d:%02d:%02d" % (hour, minutes, sec)
def calculate_image_path(dbpath=None, create=False):
"""Calculate image path out of provided path or using current connection"""
if not dbpath:
@@ -50,7 +51,7 @@ def calculate_image_path(dbpath=None, create=False):
if not os.path.exists(images_dir):
try:
os.mkdir(images_dir)
except OSError, err:
except OSError as err:
if err.errno != errno.EEXIST:
raise
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)
def mk_paths(fname, img_path):
"""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 = [new_path[i:i + 2] for i in range(0, len(new_path), 2)]

View File

@@ -1,11 +1,12 @@
"""
Project: pyGTKtalog
Description: pyGTK common utility functions
Type: tility
Type: utility
Author: Roman 'gryf' Dobosz, gryf73@gmail.com
Created: 2010-11-07 13:30:37
"""
def get_tv_item_under_cursor(treeview):
"""
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)
return item_id
return None

View File

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

View File

@@ -105,7 +105,7 @@ class ThumbCreator(object):
"""
try:
image_thumb = Image.open(self.filename).convert('RGB')
except:
except Exception:
return None
it_x, it_y = image_thumb.size
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
Created: 2009-04-04
"""
import math
import os
import shutil
from tempfile import mkdtemp, mkstemp
import math
import tempfile
from PIL import Image
from pygtktalog.misc import float_to_string
from pygtktalog.logger import get_logger
@@ -49,7 +50,7 @@ class Video(object):
'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?
# TODO: what about audio/subtitle language/existence?
for key in output:
if key in attrs:
@@ -58,9 +59,9 @@ class Video(object):
if 'length' in self.tags and self.tags['length'] > 0:
start = self.tags.get('start', 0)
length = self.tags['length'] - start
hours = length / 3600
hours = length // 3600
seconds = length - hours * 3600
minutes = seconds / 60
minutes = seconds // 60
seconds -= minutes * 60
length_str = "%02d:%02d:%02d" % (hours, minutes, seconds)
self.tags['duration'] = length_str
@@ -92,16 +93,16 @@ class Video(object):
if scale < 1:
return None
no_pictures = self.tags['length'] / scale
no_pictures = self.tags['length'] // scale
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:
# for really short movies
no_pictures = 4
tempdir = mkdtemp()
file_desc, image_fn = mkstemp(suffix=".jpg")
tempdir = tempfile.mkdtemp()
file_desc, image_fn = tempfile.mkstemp(suffix=".jpg")
os.close(file_desc)
self._make_captures(tempdir, no_pictures)
self._make_montage(tempdir, image_fn, no_pictures)
@@ -113,16 +114,16 @@ class Video(object):
"""
Return formatted tags as a string
"""
out_tags = u''
out_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:
out_tags += u"Resolution: %sx%s\n" % (self.tags['width'],
self.tags['height'])
out_tags += "Resolution: %sx%s\n" % (self.tags['width'],
self.tags['height'])
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:
out_tags += "Video codec: %s\n" % self.tags['video_codec']
@@ -178,20 +179,21 @@ class Video(object):
@directory - full output directory name
@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
for dummy in range(1, no_pictures + 1):
current_time += step
time = float_to_string(current_time)
cmd = "mplayer \"%s\" -ao null -brightness 0 -hue 0 " \
"-saturation 0 -contrast 0 -mc 0 -vf-clr -vo jpeg:outdir=\"%s\" -ss %s" \
" -frames 1 2>/dev/null"
cmd = ('mplayer "%s" -ao null -brightness 0 -hue 0 '
'-saturation 0 -contrast 0 -mc 0 -vf-clr '
'-vo jpeg:outdir="%s" -ss %s -frames 1 2>/dev/null')
os.popen(cmd % (self.filename, directory, time)).readlines()
try:
shutil.move(os.path.join(directory, "00000001.jpg"),
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 '
'%s. Errors: %s, %s', self.filename, time, errno,
strerror)
@@ -216,13 +218,13 @@ class Video(object):
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:
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)
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))
@@ -231,10 +233,10 @@ class Video(object):
ifn_list = os.listdir(directory)
ifn_list.sort()
img_list = [Image.open(os.path.join(directory, fn)).resize(dim) \
for fn in ifn_list]
img_list = [Image.open(os.path.join(directory, fn)).resize(dim)
for fn in ifn_list]
rows = no_pictures / row_length
rows = no_pictures // row_length
cols = row_length
isize = (cols * dim[0] + cols + 1,
rows * dim[1] + rows + 1)
@@ -250,7 +252,7 @@ class Video(object):
bbox = (left, upper, right, lower)
try:
img = img_list.pop(0)
except:
except Exception:
break
inew.paste(img, bbox)
inew.save(image_fn, 'JPEG')
@@ -271,7 +273,7 @@ class Video(object):
"""
try:
return int(chain.split(".")[0])
except:
except Exception:
return 0
def __str__(self):

View File

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

View File

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

View File

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

View File

@@ -23,7 +23,7 @@ def populate_with_mock_files(dir_):
files_no = 0
for file_ in files1:
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
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'))
for file_ in files2:
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
return files_no
@@ -178,8 +178,8 @@ class TestScan(unittest.TestCase):
self.assertTrue(file_ob is not file2_ob)
# While Image objects points to the same file
self.assertTrue(file_ob.images[0].filename == \
file2_ob.images[0].filename)
self.assertTrue(file_ob.images[0].filename ==
file2_ob.images[0].filename)
# they are different objects
self.assertTrue(file_ob.images[0] is not file2_ob.images[0])

View File

@@ -7,6 +7,8 @@
"""
import os
import unittest
from unittest import mock
import io
import PIL
@@ -130,7 +132,7 @@ ID_AUDIO_RATE=22050
ID_AUDIO_NCH=1
ID_AUDIO_CODEC=ffac3
ID_EXIT=EOF""",
"m.wmv":"""ID_AUDIO_ID=1
"m.wmv": """ID_AUDIO_ID=1
ID_VIDEO_ID=2
ID_FILENAME=m.wmv
ID_DEMUXER=asf
@@ -198,6 +200,7 @@ class Readlines(object):
def readlines(self):
return self.data.split('\n')
def mock_popen(command):
key = None
if 'midentify' in command:
@@ -205,22 +208,25 @@ def mock_popen(command):
elif 'jpeg:outdir' in command:
# simulate capture for mplayer
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:
img.save(fobj)
return Readlines(key)
os.popen = mock_popen
# os.popen = mock_popen
class TestVideo(unittest.TestCase):
"""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"""
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.assertEqual(avi.tags['audio_format'], '85')
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['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
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.assertEqual(avi.tags['audio_format'], '85')
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['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"""
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.assertEqual(mkv.tags['audio_format'], '8192')
self.assertEqual(mkv.tags['width'], 128)
@@ -264,24 +276,30 @@ class TestVideo(unittest.TestCase):
self.assertEqual(mkv.tags['duration'], '00:00:04')
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"""
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.assertFalse(mpg.tags.has_key('audio_format'))
self.assertFalse('audio_format' in mpg.tags)
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['video_format'], '0x10000001')
self.assertFalse(mpg.tags.has_key('lenght'))
self.assertFalse(mpg.tags.has_key('audio_codec'))
self.assertFalse('lenght' in mpg.tags)
self.assertFalse('audio_codec' in mpg.tags)
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')
def test_ogm(self):
@mock.patch('os.popen')
def test_ogm(self, popen):
"""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.assertEqual(ogm.tags['audio_format'], '8192')
self.assertEqual(ogm.tags['width'], 160)
@@ -294,9 +312,12 @@ class TestVideo(unittest.TestCase):
self.assertEqual(ogm.tags['duration'], '00:00:04')
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"""
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.assertEqual(wmv.tags['audio_format'], '353')
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['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"""
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.assertEqual(mp4.tags['audio_format'], 'mp4a')
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['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"""
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()
self.assertTrue(filename != None)
self.assertTrue(os.path.exists(filename))
file_size = os.stat(filename)[6]
self.assertAlmostEqual(file_size/10000.0, 0.151, 0)
os.unlink(filename)
self.assertIsNotNone(filename)
for length in (480, 380, 4):
avi.tags['length'] = length
filename = avi.capture()
self.assertTrue(filename is not None)
os.unlink(filename)
avi.tags['length'] = 3
self.assertTrue(avi.capture() is None)
@@ -351,7 +385,6 @@ class TestVideo(unittest.TestCase):
avi.tags['width'] = 1025
filename = avi.capture()
self.assertTrue(filename is not None)
os.unlink(filename)
del(avi.tags['length'])
self.assertTrue(avi.capture() is None)

View File

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