mirror of
https://github.com/gryf/pygtktalog.git
synced 2025-12-17 11:30:19 +01:00
Added basic Python3 support
This commit is contained in:
13
README.rst
13
README.rst
@@ -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
|
||||||
|
|||||||
@@ -6,19 +6,15 @@
|
|||||||
Created: 2009-05-05
|
Created: 2009-05-05
|
||||||
"""
|
"""
|
||||||
|
|
||||||
__version__ = "2.0.0"
|
__version__ = "3.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 os
|
||||||
import sys
|
|
||||||
import locale
|
import locale
|
||||||
import gettext
|
import gettext
|
||||||
import __builtin__
|
|
||||||
|
|
||||||
from pygtktalog.logger import get_logger
|
|
||||||
|
|
||||||
|
|
||||||
__all__ = ['dbcommon',
|
__all__ = ['dbcommon',
|
||||||
@@ -49,10 +45,10 @@ except locale.Error:
|
|||||||
# module.textdomain(GETTEXT_DOMAIN)
|
# module.textdomain(GETTEXT_DOMAIN)
|
||||||
|
|
||||||
# register the gettext function for the whole interpreter as "_"
|
# register the gettext function for the whole interpreter as "_"
|
||||||
__builtin__._ = gettext.gettext
|
_ = gettext.gettext
|
||||||
|
|
||||||
# wrap errors into usefull message
|
# # wrap errors into usefull message
|
||||||
#def log_exception(exc_type, exc_val, traceback):
|
# def log_exception(exc_type, exc_val, traceback):
|
||||||
# get_logger(__name__).error(exc_val)
|
# get_logger(__name__).error(exc_val)
|
||||||
#
|
#
|
||||||
#sys.excepthook = log_exception
|
# sys.excepthook = log_exception
|
||||||
|
|||||||
@@ -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):
|
||||||
"""
|
"""
|
||||||
|
|||||||
@@ -36,7 +36,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):
|
||||||
|
|||||||
@@ -50,7 +50,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):
|
||||||
@@ -60,7 +60,7 @@ def calculate_image_path(dbpath=None, create=False):
|
|||||||
|
|
||||||
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)]
|
||||||
|
|||||||
@@ -155,7 +155,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")
|
||||||
@@ -199,8 +199,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]:
|
||||||
@@ -287,13 +286,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 +372,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]
|
||||||
|
|||||||
@@ -92,10 +92,10 @@ 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
|
||||||
@@ -113,16 +113,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,7 +178,7 @@ 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
|
||||||
@@ -191,7 +191,8 @@ class Video(object):
|
|||||||
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)
|
||||||
@@ -234,7 +235,7 @@ class Video(object):
|
|||||||
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)
|
||||||
|
|||||||
@@ -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,19 +273,19 @@ 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"""
|
||||||
@@ -342,9 +342,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)
|
||||||
|
|||||||
5
setup.py
5
setup.py
@@ -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',
|
||||||
|
|||||||
Reference in New Issue
Block a user