diff --git a/README b/README
index 2e8b63b..ab6768b 100644
--- a/README
+++ b/README
@@ -4,15 +4,15 @@ pyGTKtalog 1.0
pyGTKtalog is Linux/FreeBSD program for indexing CD/DVD or directories on
filesystem. It is similar to gtktalog or
gwhere . There is no coincidence in name of
-application, because it's ment to be replacement (in some way) for gtktalog,
+application, because it's meant to be replacement (in some way) for gtktalog,
which seems to be dead project for years.
FEATURES
========
- scan for files in selected media
-- get/generate thumbnails from exif and other images
-- most important exif tags
+- get/generate thumbnails from EXIF and other images
+- most important EXIF tags
- add/edit description and notes
- fetch comments for images made in gThumb
- add/remove unlimited images to any file or directory
@@ -30,19 +30,20 @@ pyGTKtalog is written in python with following dependencies:
Optional modules:
-- PIL for image manipulation
+- PIL for image
+ manipulation
-Additional pyGTKtalog uses pygtkmvc by Roberto
-Cavada and EXIF module by Gene Cash (slightly updatetd to EXIF 2.2 by me) which
-are included in sources.
+Additional pyGTKtalog uses pygtkmvc by
+Roberto Cavada and EXIF module by Gene Cash (slightly updatetd to EXIF 2.2 by
+me) which are included in sources.
-pyGTKtalog extensivly uses external programs in unix spirit, however there is
-small possibility of using it Windows (probably with limitations) and quite big
-possiblity to run it on other sofisticated unix-like systems (i.e.
+pyGTKtalog extensively uses external programs in unix spirit, however there is
+small possibility of using it Windows (probably with limitations) and quite
+big possibility to run it on other sophisticated unix-like systems (i.e.
BeOS/ZETA/Haiku, QNX or MacOSX).
-INSTALATION
-===========
+INSTALLATION
+============
You don't have to install it if you don't want to. You can just change current
directory to pyGTKtalog and simply run:
@@ -80,11 +81,12 @@ For version 2.0:
- command line support: query, adding media to collection etc
- internationalization
- export to XLS
-- user definied group of tags (represented by color in cloud tag)
-- hiding specified files - configurable, like dot prefixed, cfg and manualy
- selected
+- user defined group of tags (represented by color in cloud tag)
+- hiding specified files - configurable, like dot prefixed, config files and
+ manually selected
- tests
- warning about existing image in media directory
+
Removed:
- filetypes handling (movies, images, archives, documents etc). Now it have
common, unified external "plugin" system - simple text output from command
@@ -126,4 +128,3 @@ BUGS
====
All bugs please report to Roman 'gryf' Dobosz
-
diff --git a/src/ctrls/c_main.py b/src/ctrls/c_main.py
index bc51fed..5eb66fa 100644
--- a/src/ctrls/c_main.py
+++ b/src/ctrls/c_main.py
@@ -27,7 +27,7 @@ __version__ = "1.0.1"
LICENCE = ''
import os.path
from os import popen
-from utils import deviceHelper
+from utils import device_helper
from gtkmvc import Controller
from c_config import ConfigController
@@ -526,9 +526,9 @@ class MainController(Controller):
def on_add_cd_activate(self, widget, label=None, current_id=None):
"""Add directory structure from cd/dvd disc"""
- mount = deviceHelper.volmount(self.model.config.confd['cd'])
+ mount = device_helper.volmount(self.model.config.confd['cd'])
if mount == 'ok':
- guessed_label = deviceHelper.volname(self.model.config.confd['cd'])
+ guessed_label = device_helper.volname(self.model.config.confd['cd'])
if not label:
label = Dialogs.InputDiskLabel(guessed_label).run()
if label:
@@ -541,7 +541,7 @@ class MainController(Controller):
self.model.unsaved_project = True
self.__set_title(filepath=self.model.filename, modified=True)
else:
- deviceHelper.volumount(self.model.config.confd['cd'])
+ device_helper.volumount(self.model.config.confd['cd'])
return True
else:
Dialogs.Wrn("Error mounting device - pyGTKtalog",
@@ -1373,7 +1373,7 @@ class MainController(Controller):
# umount/eject cd
ejectapp = self.model.config.confd['ejectapp']
if self.model.config.confd['eject'] and ejectapp:
- msg = deviceHelper.eject_cd(ejectapp,
+ msg = device_helper.eject_cd(ejectapp,
self.model.config.confd['cd'])
if msg != 'ok':
Dialogs.Wrn("error ejecting device - pyGTKtalog",
@@ -1381,7 +1381,7 @@ class MainController(Controller):
self.model.config.confd['cd'],
"Last eject message:\n%s" % msg)
else:
- msg = deviceHelper.volumount(self.model.config.confd['cd'])
+ msg = device_helper.volumount(self.model.config.confd['cd'])
if msg != 'ok':
Dialogs.Wrn("error unmounting device - pyGTKtalog",
"Cannot unmount device pointed to %s" %
diff --git a/src/models/m_main.py b/src/models/m_main.py
index dce1ca5..e328d6d 100644
--- a/src/models/m_main.py
+++ b/src/models/m_main.py
@@ -34,7 +34,11 @@ import gobject
from gtkmvc.model_mt import ModelMT
-from pysqlite2 import dbapi2 as sqlite
+try:
+ import sqlite3 as sqlite
+except ImportError:
+ from pysqlite2 import dbapi2 as sqlite
+
from datetime import datetime
import threading as _threading
@@ -50,6 +54,20 @@ from utils.gthumb import GthumbCommentParser
from utils.no_thumb import no_thumb as no_thumb_img
+def mangle_date(date):
+ """Return date object depending on the record type."""
+
+ if date:
+ try:
+ dateobj = datetime.fromtimestamp(date)
+ except TypeError:
+ dateobj = datetime.strptime(date, "%Y-%m-%d %H:%M:%S.%f")
+ else:
+ dateobj = datetime.fromtimestamp(0)
+
+ return dateobj
+
+
class MainModel(ModelMT):
"""Create, load, save, manipulate db file which is container for data"""
@@ -169,7 +187,6 @@ class MainModel(ModelMT):
else:
os.mkdir(path)
-
if os.path.exists(imgpath):
if not os.path.isdir(imgpath):
print "Warning:",
@@ -360,8 +377,6 @@ class MainModel(ModelMT):
res = self.db_cursor.fetchone()
if res and res[0]:
# there is such an image. going back.
- if __debug__:
- print res[0]
return
# check if file have have thumbnail. if not, make it with first
@@ -556,8 +571,6 @@ class MainModel(ModelMT):
try:
os.unlink(self.db_tmp_path)
except:
- if __debug__:
- print "Exception in removing temporary db file!"
pass
#if self.internal_dirname != None:
@@ -573,6 +586,7 @@ class MainModel(ModelMT):
self.filename = None
self.__create_temporary_db_file()
self.__connect_to_db()
+ self._set_image_path()
self.__create_database()
self.__clear_trees()
self.clear_search_tree()
@@ -592,6 +606,14 @@ class MainModel(ModelMT):
return
if filename:
+ if not '.sqlite' in filename:
+ filename += '.sqlite'
+ else:
+ filename = filename[:filename.rindex('.sqlite')] + '.sqlite'
+
+ if self.config.confd['compress']:
+ filename += '.bz2'
+
self.filename = filename
val, err = self.__compress_and_save()
if not val:
@@ -683,6 +705,7 @@ class MainModel(ModelMT):
#tar.close()
self.__connect_to_db()
+ self._set_image_path()
self.__fetch_db_into_treestore()
self.config.add_recent(filename)
self.get_tags()
@@ -758,8 +781,9 @@ class MainModel(ModelMT):
self.search_list.set_value(myiter, 3,
self.__get_file_path(row[0]))
self.search_list.set_value(myiter, 4, row[2])
- self.search_list.set_value(myiter, 5,
- datetime.fromtimestamp(row[3]))
+
+ self.search_list.set_value(myiter, 5, mangle_date(row[3]))
+
self.search_list.set_value(myiter, 6, 1)
self.search_list.set_value(myiter, 7, gtk.STOCK_DIRECTORY)
@@ -794,8 +818,7 @@ class MainModel(ModelMT):
self.search_list.set_value(myiter, 3,
self.__get_file_path(row[0]))
self.search_list.set_value(myiter, 4, row[2])
- self.search_list.set_value(myiter, 5,
- datetime.fromtimestamp(row[3]))
+ self.search_list.set_value(myiter, 5, mangle_date(row[3]))
self.search_list.set_value(myiter, 6, row[4])
if row[4] == self.FIL:
self.search_list.set_value(myiter, 7, gtk.STOCK_FILE)
@@ -825,9 +848,7 @@ class MainModel(ModelMT):
#self.__fetch_db_into_treestore()
self.unsaved_project = True
- else:
- if __debug__:
- print "m_main.py: rename(): no label defined"
+
return
def refresh_discs_tree(self):
@@ -879,8 +900,7 @@ class MainModel(ModelMT):
self.files_list.set_value(myiter, 2, row[1])
self.files_list.set_value(myiter, 3, self.__get_file_path(row[0]))
self.files_list.set_value(myiter, 4, row[2])
- self.files_list.set_value(myiter, 5,
- datetime.fromtimestamp(row[3]))
+ self.files_list.set_value(myiter, 5, mangle_date(row[3]))
self.files_list.set_value(myiter, 6, 1)
self.files_list.set_value(myiter, 7, gtk.STOCK_DIRECTORY)
@@ -923,8 +943,7 @@ class MainModel(ModelMT):
self.files_list.set_value(myiter, 2, row[1])
self.files_list.set_value(myiter, 3, self.__get_file_path(row[0]))
self.files_list.set_value(myiter, 4, row[2])
- self.files_list.set_value(myiter, 5,
- datetime.fromtimestamp(row[3]))
+ self.files_list.set_value(myiter, 5, mangle_date(row[3]))
self.files_list.set_value(myiter, 6, row[4])
if row[4] == self.FIL:
self.files_list.set_value(myiter, 7, gtk.STOCK_FILE)
@@ -954,11 +973,10 @@ class MainModel(ModelMT):
res = self.db_cursor.fetchone()
if res:
retval['fileinfo'] = {'id': file_id,
- 'date': datetime.fromtimestamp(res[1]),
- 'size': res[2], 'type': res[3]}
-
- retval['fileinfo']['disc'] = self.__get_file_root(file_id)
-
+ 'size': res[2],
+ 'type': res[3],
+ 'date': mangle_date(res[1]),
+ 'disc': self.__get_file_root(file_id)}
retval['filename'] = res[0]
if res[4]:
@@ -969,9 +987,13 @@ class MainModel(ModelMT):
if res[6]:
thumbfile = os.path.join(self.image_path, res[6] + "_t")
+ thumb2 = os.path.join(self.image_path, res[6])
if os.path.exists(thumbfile):
pix = gtk.gdk.pixbuf_new_from_file(thumbfile)
retval['thumbnail'] = thumbfile
+ elif os.path.exists(thumb2):
+ pix = gtk.gdk.pixbuf_new_from_file(thumb2)
+ retval['thumbnail'] = thumb2
sql = """SELECT id, filename FROM images
WHERE file_id = ?"""
@@ -981,8 +1003,13 @@ class MainModel(ModelMT):
self.images_store = gtk.ListStore(gobject.TYPE_INT, gtk.gdk.Pixbuf)
for im_id, filename in res:
thumbfile = os.path.join(self.image_path, filename + "_t")
+ file_, ext_ = os.path.splitext(filename)
+ thumb2 = os.path.join(self.image_path,
+ "".join([file_, "_t", ext_]))
if os.path.exists(thumbfile):
pix = gtk.gdk.pixbuf_new_from_file(thumbfile)
+ elif os.path.exists(thumb2):
+ pix = gtk.gdk.pixbuf_new_from_file(thumb2)
else:
pix = gtk.gdk.pixbuf_new_from_inline(len(no_thumb_img),
no_thumb_img, False)
@@ -1083,13 +1110,10 @@ class MainModel(ModelMT):
sql = """DELETE FROM tags_files WHERE file_id = ?"""
db_cursor.executemany(sql, generator())
- if __debug__:
- print "m_main.py: delete(): deleting:", fids
-
- if len(fids) == 1:
- arg = "(%d)" % fids[0]
- else:
- arg = str(tuple(fids))
+ #if len(fids) == 1:
+ # arg = "(%d)" % fids[0]
+ #else:
+ # arg = str(tuple(fids))
# remove thumbnails
#sql = """SELECT filename FROM thumbnails WHERE file_id IN %s""" % arg
@@ -1258,12 +1282,15 @@ class MainModel(ModelMT):
path = os.path.join(self.image_path, res[0])
if os.path.exists(path):
return path
+ path = os.path.join('/home/gryf/.pygtktalog/imgs2/', res[0])
+ if os.path.exists(path):
+ return path
return None
def update_desc_and_note(self, file_id, desc='', note=''):
"""update note and description"""
sql = """UPDATE files SET description=?, note=? WHERE id=?"""
- self.db_cursor.execute(sql, (desc, note, file_id))
+ self.db_cursor.execute(sql, (unicode(desc), unicode(note), file_id))
self.db_connection.commit()
return
@@ -1378,6 +1405,34 @@ class MainModel(ModelMT):
self.db_cursor = self.db_connection.cursor()
return
+ def _set_image_path(self):
+ """hack, hack, hack!"""
+ if not self.filename:
+ return
+
+ sql = ("select name from sqlite_master where name='config' and "
+ "type='table'")
+ if not self.db_cursor.execute(sql).fetchone():
+ return
+
+ sql = "SELECT value FROM config WHERE key = ?"
+ res = self.db_cursor.execute(sql, ("image_path", )).fetchone()
+ if not res:
+ return
+
+ if res[0] == ":same_as_db:":
+ dir_, file_ = (os.path.dirname(self.filename),
+ os.path.basename(self.filename))
+ file_base, dummy = os.path.splitext(file_)
+ self.image_path = os.path.abspath(os.path.join(dir_, file_base +
+ "_images"))
+ else:
+ self.image_path = res[0]
+ if "~" in self.image_path:
+ self.images_dir = os.path.expanduser(self.image_path)
+ if "$" in self.image_path:
+ self.images_dir = os.path.expandvars(self.image_path)
+
def __close_db_connection(self):
"""close db conection"""
@@ -1407,8 +1462,6 @@ class MainModel(ModelMT):
output_file = bz2.BZ2File(self.filename, "w")
else:
output_file = open(self.filename, "w")
- if __debug__:
- print "m_main.py: __compress_and_save(): tar open successed"
except IOError, (errno, strerror):
return False, strerror
@@ -1597,8 +1650,6 @@ class MainModel(ModelMT):
for root, dirs, files in os.walk(self.path):
count += len(files)
except:
- if __debug__:
- print 'm_main.py: os.walk in %s' % self.path
pass
if count > 0:
@@ -1630,14 +1681,14 @@ class MainModel(ModelMT):
files(parent_id, filename, filepath, date,
size, type, source)
VALUES(?,?,?,?,?,?,?)"""
- db_cursor.execute(sql, (parent_id, name, path, date, size,
- filetype, self.source))
+ db_cursor.execute(sql, (parent_id, name, path.decode("utf-8"),
+ date, size, filetype, self.source))
else:
self.discs_tree.set_value(myit, 2, gtk.STOCK_DIRECTORY)
sql = """INSERT INTO
files(parent_id, filename, filepath, date, size, type)
VALUES(?,?,?,?,?,?)"""
- db_cursor.execute(sql, (parent_id, name, path,
+ db_cursor.execute(sql, (parent_id, name, path.decode("utf-8"),
date, size, filetype))
sql = """SELECT seq FROM sqlite_sequence WHERE name='files'"""
@@ -1645,14 +1696,13 @@ class MainModel(ModelMT):
currentid = db_cursor.fetchone()[0]
self.discs_tree.set_value(myit, 0, currentid)
+
self.discs_tree.set_value(myit, 1, name)
self.discs_tree.set_value(myit, 3, parent_id)
try:
root, dirs, files = os.walk(path).next()
except:
- if __debug__:
- print "m_main.py: cannot access ", path
#return -1
return 0
@@ -1721,8 +1771,12 @@ class MainModel(ModelMT):
sql = """INSERT INTO
files(parent_id, filename, filepath, date, size, type)
VALUES(?,?,?,?,?,?)"""
- db_cursor.execute(sql, (currentid, j, current_file,
- st_mtime, st_size, self.FIL))
+ try:
+ db_cursor.execute(sql, (currentid, unicode(j),
+ unicode(current_file),
+ st_mtime, st_size, self.FIL))
+ except:
+ raise
if self.count % 32 == 0:
update = True
@@ -1812,7 +1866,10 @@ class MainModel(ModelMT):
sql = """UPDATE files SET description=?
WHERE id=?"""
- db_cursor.execute(sql, (desc, fileid))
+ db_cursor.execute(sql,
+ (unicode(desc.decode("utf8",
+ "ignore")),
+ fileid))
### end of scan
if update:
@@ -1827,18 +1884,11 @@ class MainModel(ModelMT):
return _size
if __recurse(1, self.label, self.path, 0, 0, self.DIR) == -1:
- if __debug__:
- print "m_main.py: __scan() __recurse()",
- print "interrupted self.abort = True"
self.discs_tree.remove(self.fresh_disk_iter)
db_cursor.close()
db_connection.rollback()
else:
- if __debug__:
- print "m_main.py: __scan() __recurse() goes without interrupt"
if self.currentid:
- if __debug__:
- print "m_main.py: __scan() removing old branch"
self.statusmsg = "Removing old branch..."
self.delete(self.currentid, db_cursor, db_connection)
@@ -1847,8 +1897,6 @@ class MainModel(ModelMT):
db_cursor.close()
db_connection.commit()
db_connection.close()
- if __debug__:
- print "m_main.py: __scan() time: ", (datetime.now() - timestamp)
self.busy = False
@@ -1903,13 +1951,8 @@ class MainModel(ModelMT):
gtk.STOCK_DIRECTORY)
return
- if __debug__:
- start_date = datetime.now()
# launch scanning.
get_children()
- if __debug__:
- print "m_main.py: __fetch_db_into_treestore()",
- print "tree generation time: ", (datetime.now() - start_date)
db_connection.close()
return
@@ -1944,13 +1987,8 @@ class MainModel(ModelMT):
gtk.STOCK_DIRECTORY)
return
- if __debug__:
- start_date = datetime.now()
# launch scanning.
get_children()
- if __debug__:
- print "m_main.py: __append_added_volume() tree generation time: ",
- print datetime.now() - start_date
db_connection.close()
return
diff --git a/src/utils/deviceHelper.py b/src/utils/deviceHelper.py
deleted file mode 100644
index 8ee2a52..0000000
--- a/src/utils/deviceHelper.py
+++ /dev/null
@@ -1,112 +0,0 @@
-# This Python file uses the following encoding: utf-8
-#
-# Author: Roman 'gryf' Dobosz gryf@elysium.pl
-#
-# Copyright (C) 2007 by Roman 'gryf' Dobosz
-#
-# This file is part of pyGTKtalog.
-#
-# This program is free software; you can redistribute it and/or modify
-# it under the terms of the GNU General Public License as published by
-# the Free Software Foundation; either version 2 of the License, or
-# (at your option) any later version.
-#
-# This program is distributed in the hope that it will be useful,
-# but WITHOUT ANY WARRANTY; without even the implied warranty of
-# MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
-# GNU General Public License for more details.
-#
-# You should have received a copy of the GNU General Public License
-# along with this program; if not, write to the Free Software
-# Foundation, Inc., 51 Franklin St, Fifth Floor, Boston, MA 02110-1301 USA
-
-# -------------------------------------------------------------------------
-"""
-device (cd, dvd) helper
-"""
-
-import os
-
-
-def volname(mntp):
- """read volume name from cd/dvd"""
- dev = mountpoint_to_dev(mntp)
- if dev != None:
- try:
- a = open(dev,"rb")
- a.seek(32808)
- b = a.read(32).strip()
- a.close()
- except:
- return None
- return b
- return None
-
-
-def volmount(mntp):
- """mount device, return 'ok' or error message"""
- _in,_out,_err = os.popen3("mount %s" % mntp)
- inf = _err.readlines()
- if len(inf) > 0:
- for i in inf:
- i.strip()
- return i
- else:
- return 'ok'
-
-
-def volumount(mntp):
- """mount device, return 'ok' or error message"""
- _in,_out,_err = os.popen3("umount %s" % mntp)
- inf = _err.readlines()
- if len(inf) > 0:
- for error in inf:
- error.strip()
-
- if error.strip()[:7] == 'umount:':
- return error.strip()
- return 'ok'
-
-
-def check_mount(dev):
- """Refresh the entries from fstab or mount."""
- mounts = os.popen('mount')
- for line in mounts.readlines():
- parts = line.split()
- device, txt1, mount_point, txt2, filesystem, options = parts
- if device == dev:
- return True
- return False
-
-
-def mountpoint_to_dev(mntp):
- """guess mountpoint from fstab"""
- fstab = open("/etc/fstab")
- for line in fstab.readlines():
- a = line.split()
- try:
- if a[1] == mntp and a[0][0] != '#':
- fstab.close()
- return a[0]
- except:
- pass
- fstab.close()
- return None
-
-
-def eject_cd(eject_app, cd):
- """mount device, return 'ok' or error message"""
- if len(eject_app) > 0:
- _in,_out,_err = os.popen3("%s %s" % (eject_app, cd))
- inf = _err.readlines()
- error = ''
-
- for error in inf:
- error.strip()
-
- if error !='':
- return error
-
- return 'ok'
- return "Eject program not specified"
-
diff --git a/src/utils/img.py b/src/utils/img.py
index 81f31cc..27a556a 100644
--- a/src/utils/img.py
+++ b/src/utils/img.py
@@ -26,7 +26,8 @@ from shutil import copy
from os import path
from hashlib import sha512
-import Image
+from PIL import Image
+
class Img(object):
diff --git a/src/utils/thumbnail.py b/src/utils/thumbnail.py
index e832d4b..cbfcc8e 100644
--- a/src/utils/thumbnail.py
+++ b/src/utils/thumbnail.py
@@ -29,7 +29,7 @@ from os import path
import sys
from utils import EXIF
-import Image
+from PIL import Image
class Thumbnail(object):
"""Class for generate/extract thumbnail from image file"""
@@ -56,7 +56,7 @@ class Thumbnail(object):
# rotated 90 CW
8: Image.ROTATE_90} # Rotated 90 CCW
flips = {7: Image.FLIP_LEFT_RIGHT, 5: Image.FLIP_LEFT_RIGHT}
-
+
image_file = open(self.filename, 'rb')
try:
exif = EXIF.process_file(image_file)
@@ -71,7 +71,7 @@ class Thumbnail(object):
if __debug__:
print "file", self.filename, "with hash", self.sha512, "exists"
return self.sha512, exif
-
+
if 'JPEGThumbnail' in exif:
if __debug__:
print self.filename, "exif thumb"
@@ -79,15 +79,15 @@ class Thumbnail(object):
thumb_file = open(self.thumbnail_path, 'wb')
thumb_file.write(exif_thumbnail)
thumb_file.close()
-
+
if 'Image Orientation' in exif:
orient = exif['Image Orientation'].values[0]
if orient > 1 and orient in orientations:
temp_image_path = mkstemp()[1]
-
+
thumb_image = Image.open(self.thumbnail_path)
tmp_thumb_img = thumb_image.transpose(orientations[orient])
-
+
if orient in flips:
tmp_thumb_img = tmp_thumb_img.transpose(flips[orient])
diff --git a/src/views/v_image.py b/src/views/v_image.py
index 1764194..7fe09f8 100644
--- a/src/views/v_image.py
+++ b/src/views/v_image.py
@@ -34,9 +34,8 @@ class ImageView(object):
image = gtk.Image()
image.set_from_file(image_filename)
- pixbuf = image.get_pixbuf()
- pic_width = pixbuf.get_width() + 23
- pic_height = pixbuf.get_height() + 23
+ pic_width = image.size_request()[0] + 23
+ pic_height = image.size_request()[1] + 23
screen_width = gtk.gdk.screen_width()
screen_height = gtk.gdk.screen_height()