mirror of
https://github.com/gryf/pygtktalog.git
synced 2026-03-27 06:33:30 +01:00
Compare commits
58 Commits
legacy
...
images_in_
| Author | SHA1 | Date | |
|---|---|---|---|
| de247e9bc8 | |||
| ad1703cd90 | |||
| 3e2634bc57 | |||
| 3c6c9a552a | |||
| 54b6a377bf | |||
| 22c24fbaf7 | |||
| 62ab67ecc5 | |||
| 7417b9e98e | |||
| 1cf1390567 | |||
| 5db02183a5 | |||
| 3590f90751 | |||
| 2b5b53ada1 | |||
| 7536e2c60a | |||
| 3b0cb80407 | |||
| 9769dfdb76 | |||
| dbb01acd3f | |||
| 9b7f15122d | |||
| 1cd6ad5b84 | |||
| 58c0c1ecdc | |||
| 20501fcf54 | |||
| 5e83363fe7 | |||
| 313db80101 | |||
| 3f797b0bf8 | |||
| 8d6cb75b8e | |||
| 71162da225 | |||
| 6b1fdb90e9 | |||
| c1dd854f62 | |||
| 2b47d9b869 | |||
| 53c3a444e0 | |||
| b54aa1849b | |||
| 37f06726b4 | |||
| e93b7291b8 | |||
| ca7fdf15e8 | |||
| 7b5c76f1d9 | |||
| f0f8d27d19 | |||
| b493b66ea8 | |||
| 83b9d944cf | |||
| 5ccdd8ee6f | |||
| 8013eec7d2 | |||
| 14f654251d | |||
| 8a24e30bde | |||
| bcacb75229 | |||
| d04a84c72d | |||
| e69e2300e8 | |||
| 08c38bf63d | |||
| 52b293c459 | |||
| bdf059d11f | |||
| ccec14f3ea | |||
| 36c4e7e4f2 | |||
| b0964ca031 | |||
| efaccd8902 | |||
| 434df58b16 | |||
| 56c77ae9a4 | |||
| c46d29a5bb | |||
| 5e8c33f05a | |||
| fb920f58bc | |||
| 292d290723 | |||
| 0adcdaba8d |
4
MANIFEST.in
Normal file
4
MANIFEST.in
Normal file
@@ -0,0 +1,4 @@
|
||||
include setup.py
|
||||
include pavement.py
|
||||
include paver-minilib.zip
|
||||
include pygtktalog/locale/*/*/*.mo
|
||||
198
README
198
README
@@ -1,67 +1,98 @@
|
||||
pyGTKtalog 1.0
|
||||
==================
|
||||
pyGTKtalog
|
||||
==========
|
||||
|
||||
pyGTKtalog is Linux/FreeBSD program for indexing CD/DVD or directories on
|
||||
filesystem. It is similar to gtktalog <http://www.nongnu.org/gtktalog/> or
|
||||
gwhere <http://www.gwhere.org/home.php3>. There is no coincidence in name of
|
||||
application, because it's ment to be replacement (in some way) for gtktalog,
|
||||
filesystem. It is similar to `gtktalog <http://www.nongnu.org/gtktalog/>`_ or
|
||||
`gwhere <http://www.gwhere.org/home.php3>`_. There is no coincidence in name of
|
||||
application, because it's menat to be replacement (in some way) for gtktalog,
|
||||
which seems to be dead project for years.
|
||||
|
||||
FEATURES
|
||||
========
|
||||
Current version is 1.9.
|
||||
|
||||
- scan for files in selected media
|
||||
- get/generate thumbnails from exif and other images
|
||||
- most important exif tags
|
||||
- add/edit description and notes
|
||||
- fetch comments for images made in gThumb <http://gthumb.sourceforge.net>
|
||||
- add/remove unlimited images to any file or directory
|
||||
- tagging files <http://en.wikipedia.org/wiki/Tag_%28metadata%29>
|
||||
- and more :)
|
||||
FEATURES
|
||||
--------
|
||||
|
||||
* scan for files in selected media
|
||||
* get/generate thumbnails from exif and other images
|
||||
* most important exif tags
|
||||
* add/edit description and notes
|
||||
* fetch comments for images made in `gThumb <http://gthumb.sourceforge.net>`_
|
||||
* add/remove unlimited images to any file or directory
|
||||
* `tagging files <http://en.wikipedia.org/wiki/Tag_%28metadata%29>`_
|
||||
* and more :)
|
||||
|
||||
REQUIREMENTS
|
||||
============
|
||||
------------
|
||||
|
||||
pyGTKtalog is written in python with following dependencies:
|
||||
pyGTKtalog requires python and following libraries:
|
||||
|
||||
- python 2.4 or higher
|
||||
- pygtk 2.10 or higher <http://www.pygtk.org>
|
||||
- pysqlite2 <http://pysqlite.org/> (unnecessary, if python 2.5 is used)
|
||||
* `python 2.6 <http://www.python.org/>`_
|
||||
* `pygtk 2.16 <http://www.pygtk.org>`_
|
||||
* `pygtkmvc 1.99 <http://sourceforge.net/apps/trac/pygtkmvc/wiki>`_
|
||||
* `sqlalchemy 0.6 <http://www.sqlalchemy.org>`_
|
||||
|
||||
Optional modules:
|
||||
It may work on other (lower) version of libraries, and it should work with
|
||||
higher versions of libraries.
|
||||
|
||||
- PIL <http://www.pythonware.com/products/pil/index.htm> for image manipulation
|
||||
.. note::
|
||||
|
||||
Additional pyGTKtalog uses pygtkmvc <http://pygtkmvc.sourceforge.net> by Roberto
|
||||
Cavada and EXIF module by Gene Cash (slightly updatetd to EXIF 2.2 by me) which
|
||||
are included in sources.
|
||||
Although pygtkmvc is `listed on pypi
|
||||
<http://pypi.python.org/pypi/python-gtkmvc/>`_ it may happen that you
|
||||
have to download it directly from
|
||||
`sourceforge <http://sourceforge.net/apps/trac/pygtkmvc/wiki>`_ page and
|
||||
install manually. I don't know about pygtk (I've installed it by my
|
||||
system package manager), but all the others python libraries (sqlalchemy,
|
||||
paver, nose, coverage) should be installable via `pip
|
||||
<http://pypi.python.org/pypi/pip>`_
|
||||
|
||||
Optional modules
|
||||
^^^^^^^^^^^^^^^^
|
||||
|
||||
* `PIL <http://www.pythonware.com/products/pil/index.htm>`_ for image manipulation
|
||||
|
||||
Additional pyGTKtalog uses EXIF module by Gene Cash (slightly updatetd to EXIF
|
||||
2.2 by me) which is 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.
|
||||
BeOS/ZETA/Haiku, QNX or MacOSX).
|
||||
|
||||
Programs that are used:
|
||||
* mencoder (provided by mplayer package)
|
||||
* montage, convert from ImageMagick
|
||||
|
||||
For development process following programs are used:
|
||||
|
||||
* `gettext <http://www.gnu.org/software/gettext/gettext.html>`_
|
||||
* `intltool <http://www.gnome.org/>`_
|
||||
* `nose <http://code.google.com/p/python-nose/>`_
|
||||
* `coverage <http://nedbatchelder.com/code/coverage/>`_
|
||||
* `paver <http://code.google.com/p/paver/>`__
|
||||
|
||||
INSTALATION
|
||||
===========
|
||||
-----------
|
||||
|
||||
You don't have to install it if you don't want to. You can just change current
|
||||
directory to pyGTKtalog and simply run:
|
||||
directory to pyGTKtalog and simply run::
|
||||
|
||||
./pyGTKtalog
|
||||
$ paver run
|
||||
|
||||
That's it. Alternatively, if you like to put it in more system wide place, all
|
||||
you have to do is:
|
||||
|
||||
- put pyGTKtalog directory into your destination of choice (/usr/local/share,
|
||||
/opt or ~/ is typical bet)
|
||||
- copy pyGTKtalog shell script to /usr/bin, /usr/local/bin or in
|
||||
other place, where PATH variable is pointing or you feel like.
|
||||
- then modify pyGTKtalog line 6 to match right pygtktalog.py directory
|
||||
#. put pyGTKtalog directory into your destination of choice (/usr/local/share,
|
||||
/opt or ~/ is typical bet)
|
||||
|
||||
#. copy pyGTKtalog shell script to /usr/bin, /usr/local/bin or in
|
||||
other place, where PATH variable is pointing or you feel like.
|
||||
|
||||
#. then modify pyGTKtalog line 6 to match right pygtktalog.py directory
|
||||
|
||||
Then, just run pyGTKtalog script.
|
||||
|
||||
TODO
|
||||
====
|
||||
----
|
||||
|
||||
PyGTKtalog is still under heavy development, however there is small chance to
|
||||
change structure of catalogs (and if it'll change, there will be transparent
|
||||
@@ -70,53 +101,82 @@ function to update DB schema).
|
||||
For version 1.0 there are no features to be done, just bug fixes.
|
||||
|
||||
There are still minor aims for versions 1.x to be done:
|
||||
- consolidate popup-menus with edit menu
|
||||
- add popup menu for directly removing tag from tag cloud
|
||||
- implement advanced search
|
||||
|
||||
* consolidate popup-menus with edit menu
|
||||
* add popup menu for directly removing tag from tag cloud
|
||||
* implement advanced search
|
||||
|
||||
For version 2.0:
|
||||
- Icon grid in files view
|
||||
- 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
|
||||
|
||||
* Export/Import
|
||||
* Icon grid in files view
|
||||
* 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
|
||||
* tests
|
||||
* warning about existing image in media directory
|
||||
|
||||
Removed:
|
||||
- filetypes handling (movies, images, archives, documents etc). Now it have
|
||||
|
||||
* filetypes handling (movies, images, archives, documents etc). Now it have
|
||||
common, unified external "plugin" system - simple text output from command
|
||||
line programs.
|
||||
- anime/movie
|
||||
- title
|
||||
- alt title
|
||||
- type (anime movie, movie, anime oav, anime tv series, tv series, etc)
|
||||
- cover/images
|
||||
- genre
|
||||
- lang
|
||||
- sub lang
|
||||
- release date (from - to)
|
||||
- anidb link/imdb link
|
||||
Maybe in future versions. Now text file descriptions/notes and tags have to
|
||||
be enough for good and fast information search.
|
||||
* anime/movie
|
||||
* title
|
||||
* alt title
|
||||
* type (anime movie, movie, anime oav, anime tv series, tv series, etc)
|
||||
* cover/images
|
||||
* genre
|
||||
* lang
|
||||
* sub lang
|
||||
* release date (from - to)
|
||||
* anidb link/imdb link
|
||||
|
||||
Maybe in future versions. Now text file descriptions/notes and tags have to
|
||||
be enough for good and fast information search.
|
||||
|
||||
NOTES
|
||||
=====
|
||||
-----
|
||||
|
||||
Catalog file is tared and gziped sqlite database and directories with images and
|
||||
thumbnails. If there are more images, the size of catalog file will grow. So be
|
||||
carefull with adding big images in your catalog file!
|
||||
Catalog file is plain sqlite database (optionally compressed with bzip2). All
|
||||
images are stored in ``~/.pygtktalog/images`` directory. Names for images are
|
||||
generated sha512 hash from image file itself. There is small possibility for two
|
||||
identical hash for different image files. However, no images are overwritten.
|
||||
Thumbnail filename for each image is simply concatenation of image filename in
|
||||
images directory and '_t' string.
|
||||
|
||||
There is also converter form old database to new. In fact no image are stored in
|
||||
archive with katalog. All thumnails will be lost. All images without big image
|
||||
will be lost. There ar serious changes with application design, and I decided,
|
||||
that is better to keep media unpacked on disk, instead of pack it every time
|
||||
with save and unpack with open methods. New design prevent from deleting eny
|
||||
file from media directory (placed in ~/.pygtktalog/images). Functionality for
|
||||
exporting images and corresponding db file is planned.
|
||||
There is also converter from old database to new for internal use only. In
|
||||
public release there will be no other formats so it will be useless, and
|
||||
deleted. There are some issues with converting. All thumbnails will be lost.
|
||||
All images without big image will be lost. There are serious changes with
|
||||
application design, and I decided, that is better to keep media unpacked on
|
||||
disk, instead of pack it every time with save and unpack with open methods. New
|
||||
design prevent from deleting any file from media directory (placed in
|
||||
``~/.pygtktalog/images``). Functionality for exporting images and corresponding
|
||||
db file is planned.
|
||||
|
||||
|
||||
DEVELOPMENT
|
||||
-----------
|
||||
|
||||
Several tools has been used to develop pyGTKtalog.
|
||||
|
||||
Paver
|
||||
^^^^^
|
||||
|
||||
I've choose `Paver <http://www.blueskyonmars.com/projects/paver/>`_ as make
|
||||
equivalent. Inside main project directory there is pavement.py script, which
|
||||
provides several tasks, that can be helpfull in a work with sources. Paver is
|
||||
also used to generate standard setup.py.
|
||||
|
||||
Nose
|
||||
^^^^
|
||||
|
||||
BUGS
|
||||
====
|
||||
----
|
||||
|
||||
All bugs please report to Roman 'gryf' Dobosz <roman.dobosz@gmail.com>
|
||||
All bugs please report to Roman 'gryf' Dobosz <gryf73@gmail.com>.
|
||||
|
||||
|
||||
@@ -1,5 +0,0 @@
|
||||
#!/bin/sh
|
||||
# remove ~, pyc, pyo files from current directory
|
||||
find . -name \*~ -exec rm '{}' ';'
|
||||
find . -name \*pyc -exec rm '{}' ';'
|
||||
find . -name \*pyo -exec rm '{}' ';'
|
||||
121
convert_1.x_to_2.x.py
Executable file
121
convert_1.x_to_2.x.py
Executable file
@@ -0,0 +1,121 @@
|
||||
#!/usr/bin/env python
|
||||
"""
|
||||
Project: pyGTKtalog
|
||||
Description: convert db created with v.1.x into v.2.x
|
||||
Type: tool
|
||||
Author: Roman 'gryf' Dobosz, gryf73@gmail.com
|
||||
Created: 2009-08-14
|
||||
"""
|
||||
import sys
|
||||
import os
|
||||
import bz2
|
||||
import shutil
|
||||
from tempfile import mkstemp
|
||||
from sqlite3 import dbapi2 as sqlite
|
||||
from datetime import datetime
|
||||
|
||||
# import db objects just to create schema
|
||||
from pygtktalog.dbobjects import File, Exif, Group, Gthumb
|
||||
from pygtktalog.dbobjects import Image, Tag, Thumbnail
|
||||
from pygtktalog.dbcommon import connect
|
||||
|
||||
|
||||
def create_schema(cur):
|
||||
pass
|
||||
|
||||
|
||||
def create_temporary_db_file():
|
||||
"""create temporary db file"""
|
||||
fd, fname = mkstemp()
|
||||
os.close(fd)
|
||||
return fname
|
||||
|
||||
|
||||
def connect_to_db(filename):
|
||||
"""initialize db connection and store it in class attributes"""
|
||||
db_connection = sqlite.connect(filename, \
|
||||
detect_types=sqlite.PARSE_DECLTYPES | sqlite.PARSE_COLNAMES)
|
||||
db_cursor = db_connection.cursor()
|
||||
return db_connection, db_cursor
|
||||
|
||||
|
||||
def opendb(filename=None):
|
||||
"""try to open db file"""
|
||||
db_tmp_path = create_temporary_db_file()
|
||||
|
||||
try:
|
||||
test_file = open(filename).read(15)
|
||||
except IOError:
|
||||
os.unlink(db_tmp_path)
|
||||
return False
|
||||
|
||||
if test_file == "SQLite format 3":
|
||||
db_tmp = open(db_tmp_path, "wb")
|
||||
db_tmp.write(open(filename).read())
|
||||
db_tmp.close()
|
||||
elif test_file[0:10] == "BZh91AY&SY":
|
||||
open_file = bz2.BZ2File(filename)
|
||||
try:
|
||||
curdb = open(db_tmp_path, "w")
|
||||
curdb.write(open_file.read())
|
||||
curdb.close()
|
||||
open_file.close()
|
||||
except IOError:
|
||||
# file is not bz2
|
||||
os.unlink(db_tmp_path)
|
||||
return False
|
||||
else:
|
||||
os.unlink(db_tmp_path)
|
||||
return False
|
||||
|
||||
return connect_to_db(db_tmp_path), db_tmp_path
|
||||
|
||||
if __name__ == "__main__":
|
||||
if len(sys.argv) != 3:
|
||||
print "usage: %s src_base dst_base" % sys.argv[0]
|
||||
exit()
|
||||
|
||||
result = opendb(sys.argv[1])
|
||||
if not result:
|
||||
print "unable to open src db file"
|
||||
exit()
|
||||
|
||||
(src_con, src_c), src_tmpf = result
|
||||
|
||||
shutil.copy(src_tmpf, sys.argv[2])
|
||||
|
||||
# close src db.
|
||||
src_c.close()
|
||||
src_con.close()
|
||||
os.unlink(src_tmpf)
|
||||
|
||||
# create or update shema
|
||||
connect(sys.argv[2])
|
||||
|
||||
dst_con = sqlite.connect(sys.argv[2])
|
||||
dst_c = dst_con.cursor()
|
||||
|
||||
sql = "select id, date from files"
|
||||
|
||||
for id, date in dst_c.execute(sql).fetchall():
|
||||
sql = "update files set date=? where id=?"
|
||||
if date and int(date) > 0:
|
||||
dst_c.execute(sql, (datetime.fromtimestamp(int(date)), id))
|
||||
else:
|
||||
dst_c.execute(sql, (None, id))
|
||||
|
||||
sql = "select id, date from gthumb"
|
||||
|
||||
for id, date in dst_c.execute(sql).fetchall():
|
||||
sql = "update gthumb set date=? where id=?"
|
||||
try:
|
||||
if int(date) > 0:
|
||||
dst_c.execute(sql, (datetime.fromtimestamp(int(date)), id))
|
||||
else:
|
||||
dst_c.execute(sql, (None, id))
|
||||
except:
|
||||
print id, date
|
||||
|
||||
dst_con.commit()
|
||||
dst_c.close()
|
||||
dst_con.close()
|
||||
@@ -1,213 +0,0 @@
|
||||
#!/usr/bin/python
|
||||
# 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
|
||||
|
||||
# -------------------------------------------------------------------------
|
||||
import sys
|
||||
import os
|
||||
import shutil
|
||||
import tarfile
|
||||
|
||||
try:
|
||||
import sqlite3 as sqlite
|
||||
except ImportError:
|
||||
from pysqlite2 import dbapi2 as sqlite
|
||||
from datetime import datetime
|
||||
|
||||
class OldModel(object):
|
||||
"""Create, load, save, manipulate db file which is container for data"""
|
||||
|
||||
def __init__(self):
|
||||
"""initialize"""
|
||||
self.db_cursor = None
|
||||
self.db_connection = None
|
||||
self.internal_dirname = None
|
||||
|
||||
def cleanup(self):
|
||||
"""remove temporary directory tree from filesystem"""
|
||||
self.__close_db_connection()
|
||||
if self.internal_dirname != None:
|
||||
try:
|
||||
shutil.rmtree(self.internal_dirname)
|
||||
except OSError:
|
||||
pass
|
||||
return
|
||||
|
||||
def open(self, filename=None):
|
||||
"""try to open db file"""
|
||||
self.__create_internal_dirname()
|
||||
self.filename = filename
|
||||
|
||||
try:
|
||||
tar = tarfile.open(filename, "r:gz")
|
||||
except:
|
||||
try:
|
||||
tar = tarfile.open(filename, "r")
|
||||
except:
|
||||
self.internal_dirname = None
|
||||
return False
|
||||
|
||||
os.chdir(self.internal_dirname)
|
||||
try:
|
||||
tar.extractall()
|
||||
if __debug__:
|
||||
print "OldModel 73: extracted tarfile into",
|
||||
print self.internal_dirname
|
||||
except AttributeError:
|
||||
# python 2.4 tarfile module lacks of method extractall()
|
||||
directories = []
|
||||
for tarinfo in tar:
|
||||
if tarinfo.isdir():
|
||||
# Extract directory with a safe mode, so that
|
||||
# all files below can be extracted as well.
|
||||
try:
|
||||
os.makedirs(os.path.join('.', tarinfo.name), 0700)
|
||||
except EnvironmentError:
|
||||
pass
|
||||
directories.append(tarinfo)
|
||||
else:
|
||||
tar.extract(tarinfo, '.')
|
||||
|
||||
# Reverse sort directories.
|
||||
directories.sort(lambda a, b: cmp(a.name, b.name))
|
||||
directories.reverse()
|
||||
|
||||
# Set correct owner, mtime and filemode on directories.
|
||||
for tarinfo in directories:
|
||||
try:
|
||||
os.chown(os.path.join('.', tarinfo.name),
|
||||
tarinfo.uid, tarinfo.gid)
|
||||
os.utime(os.path.join('.', tarinfo.name),
|
||||
(0, tarinfo.mtime))
|
||||
except OSError:
|
||||
if __debug__:
|
||||
print "OldModel 103: open(): setting corrext owner,",
|
||||
print "mtime etc"
|
||||
tar.close()
|
||||
self.__connect_to_db()
|
||||
return True
|
||||
|
||||
# private class functions
|
||||
def __connect_to_db(self):
|
||||
"""initialize db connection and store it in class attributes"""
|
||||
self.db_connection = sqlite.connect("%s" % \
|
||||
(self.internal_dirname + '/db.sqlite'),
|
||||
detect_types=sqlite.PARSE_DECLTYPES|sqlite.PARSE_COLNAMES)
|
||||
self.db_cursor = self.db_connection.cursor()
|
||||
return
|
||||
|
||||
def __close_db_connection(self):
|
||||
"""close db conection"""
|
||||
if self.db_cursor != None:
|
||||
self.db_cursor.close()
|
||||
self.db_cursor = None
|
||||
if self.db_connection != None:
|
||||
self.db_connection.close()
|
||||
self.db_connection = None
|
||||
return
|
||||
|
||||
def __create_internal_dirname(self):
|
||||
"""create temporary directory for working thumb/image files and
|
||||
database"""
|
||||
# TODO: change this stupid rutine into tempfile mkdtemp method
|
||||
self.cleanup()
|
||||
self.internal_dirname = "/tmp/pygtktalog%d" % \
|
||||
datetime.now().microsecond
|
||||
try:
|
||||
os.mkdir(self.internal_dirname)
|
||||
except IOError, (errno, strerror):
|
||||
print "OldModel 138: __create_internal_dirname(): ", strerror
|
||||
return
|
||||
|
||||
def setup_path():
|
||||
"""Sets up the python include paths to include needed directories"""
|
||||
import os.path
|
||||
|
||||
from src.utils.globals import TOPDIR
|
||||
sys.path = [os.path.join(TOPDIR, "src")] + sys.path
|
||||
return
|
||||
|
||||
if __name__ == "__main__":
|
||||
"""run the stuff"""
|
||||
if len(sys.argv) != 3:
|
||||
print "Usage: %s old_katalog_filename new_katalog_filename" % \
|
||||
sys.argv[0]
|
||||
print "All available pictures will be exported aswell, however",
|
||||
print "thumbnails without"
|
||||
print "images will be lost."
|
||||
sys.exit()
|
||||
|
||||
# Directory from where pygtkatalog was invoced. We need it for calculate
|
||||
# path for argument (catalog file)
|
||||
execution_dir = os.path.abspath(os.path.curdir)
|
||||
|
||||
# Directory, where this files lies. We need it to setup private source
|
||||
# paths
|
||||
libraries_dir = os.path.dirname(__file__)
|
||||
os.chdir(libraries_dir)
|
||||
|
||||
setup_path()
|
||||
|
||||
from shutil import copy
|
||||
|
||||
from utils.img import Img
|
||||
from models.m_main import MainModel as NewModel
|
||||
|
||||
model = OldModel()
|
||||
new_model = NewModel()
|
||||
if not model.open(os.path.join(execution_dir, sys.argv[1])):
|
||||
print "cannot open katalog in 1.0RC1 format"
|
||||
sys.exit()
|
||||
|
||||
model.db_cursor.execute("""create table
|
||||
images2(id INTEGER PRIMARY KEY AUTOINCREMENT,
|
||||
file_id INTEGER,
|
||||
filename TEXT);""")
|
||||
|
||||
model.db_cursor.execute("delete from thumbnails")
|
||||
result = model.db_cursor.execute("select file_id, filename from images")
|
||||
# (id, filename)
|
||||
# (4921, u'images/13/39.jpg')
|
||||
for row in result.fetchall():
|
||||
if row[1] and os.path.exists(os.path.join(model.internal_dirname,
|
||||
row[1])):
|
||||
im = Img(os.path.join(model.internal_dirname, row[1]),
|
||||
new_model.image_path)
|
||||
image = im.save()
|
||||
sql = "insert into images2(file_id, filename) values (?, ?)"
|
||||
model.db_cursor.execute(sql, (row[0], image))
|
||||
|
||||
model.db_cursor.execute("select id from thumbnails where file_id=?", (row[0], ))
|
||||
thumb = model.db_cursor.fetchone()
|
||||
if not (thumb and thumb[0]):
|
||||
sql = "insert into thumbnails(file_id, filename) values (?, ?)"
|
||||
model.db_cursor.execute(sql, (row[0], image))
|
||||
|
||||
|
||||
model.db_connection.commit()
|
||||
model.db_cursor.execute("drop table images")
|
||||
model.db_cursor.execute("alter table images2 rename to images")
|
||||
|
||||
copy(os.path.join(model.internal_dirname, 'db.sqlite'),
|
||||
os.path.join(execution_dir, sys.argv[2]))
|
||||
# remove stuff
|
||||
model.cleanup()
|
||||
375
locale/pl.po
Normal file
375
locale/pl.po
Normal file
@@ -0,0 +1,375 @@
|
||||
#
|
||||
# pygtktalog Language File
|
||||
#
|
||||
msgid ""
|
||||
msgstr ""
|
||||
"Project-Id-Version: pygtktalog\n"
|
||||
"POT-Creation-Date: 2009-08-26 22:10:33.892815\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"
|
||||
"Generated-By: slightly modified generate_pot.py\n"
|
||||
|
||||
#: pygtktalog/views/glade/main.glade.h:1
|
||||
msgid "Add CD"
|
||||
msgstr "Dodaj CD"
|
||||
|
||||
#: pygtktalog/views/glade/main.glade.h:2
|
||||
msgid "Add CD/DVD to catalog"
|
||||
msgstr "Dodaj CD/DVD do katalogu"
|
||||
|
||||
#: pygtktalog/views/glade/main.glade.h:3
|
||||
msgid "Add Dir"
|
||||
msgstr "Dodaj katalog"
|
||||
|
||||
#: pygtktalog/views/glade/main.glade.h:4
|
||||
msgid "Add _CD/DVD"
|
||||
msgstr "Dodaj _Cd/DVD"
|
||||
|
||||
#: pygtktalog/views/glade/main.glade.h:5
|
||||
msgid "Add _Directory"
|
||||
msgstr "Dodaj kata_log"
|
||||
|
||||
#: pygtktalog/views/glade/main.glade.h:6
|
||||
#: pygtktalog/views/glade/files.glade.h:1
|
||||
msgid "Add _Images"
|
||||
msgstr "Dodaj _obrazy"
|
||||
|
||||
#: pygtktalog/views/glade/main.glade.h:7
|
||||
#: pygtktalog/views/glade/files.glade.h:2
|
||||
msgid "Add _Thumbnail"
|
||||
msgstr "Dodaj miniaturÄ™"
|
||||
|
||||
#: pygtktalog/views/glade/main.glade.h:8
|
||||
#: pygtktalog/views/glade/files.glade.h:3
|
||||
msgid ""
|
||||
"Add images to file. If file have no thumbnail,\n"
|
||||
"thumbnail from first image will be generated."
|
||||
msgstr ""
|
||||
"Dodanie obrazów do pliku. Jeśli plik nie posiada miniatury,\n"
|
||||
"zostanie wygenerowana miniatura z pierwszego obrazu."
|
||||
|
||||
#: pygtktalog/views/glade/main.glade.h:10
|
||||
#: pygtktalog/views/glade/main.glade.h:6
|
||||
msgid "Cancel"
|
||||
msgstr "Analuj"
|
||||
|
||||
#: pygtktalog/views/glade/main.glade.h:11
|
||||
#: pygtktalog/views/glade/main.glade.h:7
|
||||
msgid "Catalog _statistics"
|
||||
msgstr "_Statystyka katalogu"
|
||||
|
||||
#: pygtktalog/views/glade/main.glade.h:12
|
||||
#: pygtktalog/views/glade/discs.glade.h:1
|
||||
msgid "Collapse all nodes"
|
||||
msgstr "Zwiń wszystkie gałęzie"
|
||||
|
||||
#: pygtktalog/views/glade/main.glade.h:13
|
||||
#: pygtktalog/views/glade/main.glade.h:8
|
||||
msgid "Create new catalog"
|
||||
msgstr "UtwĂłrz nowy katalog"
|
||||
|
||||
#: pygtktalog/views/glade/main.glade.h:14
|
||||
#: pygtktalog/views/glade/main.glade.h:9
|
||||
msgid "Delete all images"
|
||||
msgstr "Usuń wszystkie obrazy"
|
||||
|
||||
#: pygtktalog/views/glade/main.glade.h:15
|
||||
#: pygtktalog/views/glade/main.glade.h:10
|
||||
msgid "Delete all thumbnals"
|
||||
msgstr "Usuń wszystkie miniatury"
|
||||
|
||||
#: pygtktalog/views/glade/main.glade.h:16
|
||||
#: pygtktalog/views/glade/main.glade.h:11
|
||||
msgid "Delete tag"
|
||||
msgstr "Usuń tag"
|
||||
|
||||
#: pygtktalog/views/glade/main.glade.h:17
|
||||
#: pygtktalog/views/glade/main.glade.h:12
|
||||
msgid "Deletes all images from files in current colection"
|
||||
msgstr "Usuwa wszystkie obrazy z plikĂłw w bieĹĽÄ…cej kolekcji"
|
||||
|
||||
#: pygtktalog/views/glade/main.glade.h:18
|
||||
#: pygtktalog/views/glade/main.glade.h:13
|
||||
msgid "Discs"
|
||||
msgstr "Dyski"
|
||||
|
||||
#: pygtktalog/views/glade/main.glade.h:19
|
||||
#: pygtktalog/views/glade/details.glade.h:1
|
||||
msgid "Double click to open image"
|
||||
msgstr "Dwukrotne kliknięcie otwiera obraz"
|
||||
|
||||
#: pygtktalog/views/glade/main.glade.h:20
|
||||
#: pygtktalog/views/glade/details.glade.h:2
|
||||
msgid "EXIF"
|
||||
msgstr "EXIF"
|
||||
|
||||
#: pygtktalog/views/glade/main.glade.h:21
|
||||
#: pygtktalog/views/glade/discs.glade.h:2
|
||||
msgid "Expand all nodes"
|
||||
msgstr "Rozwiń wszystkie gałęzie"
|
||||
|
||||
#: pygtktalog/views/glade/main.glade.h:22
|
||||
#: pygtktalog/views/glade/main.glade.h:14
|
||||
msgid "Export"
|
||||
msgstr "Eksport"
|
||||
|
||||
#: pygtktalog/views/glade/main.glade.h:23
|
||||
#: pygtktalog/views/glade/details.glade.h:3
|
||||
msgid "File info"
|
||||
msgstr "Informacje o pliku"
|
||||
|
||||
#: pygtktalog/views/glade/main.glade.h:24
|
||||
#: pygtktalog/views/glade/main.glade.h:15
|
||||
msgid "Find file"
|
||||
msgstr "ZnajdĹş plik"
|
||||
|
||||
#: pygtktalog/views/glade/main.glade.h:25
|
||||
#: pygtktalog/views/glade/details.glade.h:4
|
||||
msgid "Images"
|
||||
msgstr "Obrazy"
|
||||
|
||||
#: pygtktalog/views/glade/main.glade.h:26
|
||||
#: pygtktalog/views/glade/main.glade.h:16
|
||||
msgid "Import"
|
||||
msgstr "Import"
|
||||
|
||||
#: pygtktalog/views/glade/main.glade.h:27
|
||||
#: pygtktalog/views/glade/main.glade.h:17
|
||||
msgid "Open catalog file"
|
||||
msgstr "OtwĂłrz plik katalogu"
|
||||
|
||||
#: pygtktalog/views/glade/main.glade.h:28
|
||||
#: pygtktalog/views/glade/main.glade.h:18
|
||||
msgid "Quit pyGTKtalog"
|
||||
msgstr "Zakończ pyGTKtalog"
|
||||
|
||||
#: pygtktalog/views/glade/main.glade.h:29
|
||||
#: pygtktalog/views/glade/files.glade.h:5
|
||||
msgid "Re_move Thumbnail"
|
||||
msgstr "Usuń miniaturę"
|
||||
|
||||
#: pygtktalog/views/glade/main.glade.h:30
|
||||
#: pygtktalog/views/glade/main.glade.h:19
|
||||
msgid "Recent files"
|
||||
msgstr "Ostatnio uĹĽywane pliki"
|
||||
|
||||
#: pygtktalog/views/glade/main.glade.h:31
|
||||
#: pygtktalog/views/glade/files.glade.h:6
|
||||
msgid "Rem_ove All Images"
|
||||
msgstr "Usuń wszystkie obrazy"
|
||||
|
||||
#: pygtktalog/views/glade/main.glade.h:32
|
||||
#: pygtktalog/views/glade/files.glade.h:7
|
||||
msgid "Remo_ve tag"
|
||||
msgstr "Usuń tag"
|
||||
|
||||
#: pygtktalog/views/glade/main.glade.h:33
|
||||
#: pygtktalog/views/glade/main.glade.h:20
|
||||
msgid "Save all images..."
|
||||
msgstr "Zapisz wszytkie obrazy..."
|
||||
|
||||
#: pygtktalog/views/glade/main.glade.h:34
|
||||
#: pygtktalog/views/glade/main.glade.h:21
|
||||
msgid "Save catalog"
|
||||
msgstr "Zapisz katalog"
|
||||
|
||||
#: pygtktalog/views/glade/main.glade.h:35
|
||||
#: pygtktalog/views/glade/main.glade.h:22
|
||||
msgid "Set as _thumbnail"
|
||||
msgstr "Ustaw jako miniaturÄ™"
|
||||
|
||||
#: pygtktalog/views/glade/main.glade.h:36
|
||||
#: pygtktalog/views/glade/main.glade.h:23
|
||||
msgid "Status bar"
|
||||
msgstr "Pasek stanu"
|
||||
|
||||
#: pygtktalog/views/glade/main.glade.h:37
|
||||
#: pygtktalog/views/glade/main.glade.h:24
|
||||
msgid "Tags"
|
||||
msgstr "Tagi"
|
||||
|
||||
#: pygtktalog/views/glade/main.glade.h:38
|
||||
#: pygtktalog/views/glade/main.glade.h:25
|
||||
msgid "Toolbar"
|
||||
msgstr "Pasek narzędzi"
|
||||
|
||||
#: pygtktalog/views/glade/main.glade.h:39
|
||||
#: pygtktalog/views/glade/main.glade.h:26
|
||||
msgid "_Add images"
|
||||
msgstr "Dodaj obrazy"
|
||||
|
||||
#: pygtktalog/views/glade/main.glade.h:40
|
||||
#: pygtktalog/views/glade/files.glade.h:8
|
||||
msgid "_Add tag"
|
||||
msgstr "_Dodaj tag"
|
||||
|
||||
#: pygtktalog/views/glade/main.glade.h:41
|
||||
#: pygtktalog/views/glade/main.glade.h:27
|
||||
msgid "_Catalog"
|
||||
msgstr "_Katalog"
|
||||
|
||||
#: pygtktalog/views/glade/main.glade.h:42
|
||||
#: pygtktalog/views/glade/discs.glade.h:3
|
||||
msgid "_Collapse all"
|
||||
msgstr "_Zwiń wszystko"
|
||||
|
||||
#: pygtktalog/views/glade/main.glade.h:43
|
||||
#: pygtktalog/views/glade/discs.glade.h:4
|
||||
#: pygtktalog/views/glade/files.glade.h:9
|
||||
msgid "_Delete"
|
||||
msgstr "_Usuń"
|
||||
|
||||
#: pygtktalog/views/glade/main.glade.h:44
|
||||
#: pygtktalog/views/glade/main.glade.h:28
|
||||
msgid "_Delete images"
|
||||
msgstr "Usuń obrazy"
|
||||
|
||||
#: pygtktalog/views/glade/main.glade.h:45
|
||||
#: pygtktalog/views/glade/main.glade.h:29
|
||||
#: pygtktalog/views/glade/files.glade.h:10
|
||||
#: pygtktalog/views/glade/test.glade.h:1
|
||||
msgid "_Edit"
|
||||
msgstr "_Edycja"
|
||||
|
||||
#: pygtktalog/views/glade/main.glade.h:46
|
||||
#: pygtktalog/views/glade/discs.glade.h:5
|
||||
msgid "_Expand all"
|
||||
msgstr "_Rozwiń wszystko"
|
||||
|
||||
#: pygtktalog/views/glade/main.glade.h:47 pygtktalog/views/main_menu.py:19
|
||||
#: pygtktalog/views/glade/main.glade.h:30
|
||||
#: pygtktalog/views/glade/test.glade.h:2
|
||||
msgid "_File"
|
||||
msgstr "_Plik"
|
||||
|
||||
#: pygtktalog/views/glade/main.glade.h:48
|
||||
#: pygtktalog/views/glade/main.glade.h:31
|
||||
#: pygtktalog/views/glade/test.glade.h:3
|
||||
msgid "_Help"
|
||||
msgstr "_Pomoc"
|
||||
|
||||
#: pygtktalog/views/glade/main.glade.h:49
|
||||
#: pygtktalog/views/glade/main.glade.h:32
|
||||
msgid "_Remove Thumbnail"
|
||||
msgstr "Usuń miniaturę"
|
||||
|
||||
#: pygtktalog/views/glade/main.glade.h:50
|
||||
#: pygtktalog/views/glade/discs.glade.h:6
|
||||
#: pygtktalog/views/glade/files.glade.h:11
|
||||
msgid "_Rename"
|
||||
msgstr "_Zmień nazwę"
|
||||
|
||||
#: pygtktalog/views/glade/main.glade.h:51
|
||||
#: pygtktalog/views/glade/main.glade.h:33
|
||||
msgid "_Save images to..."
|
||||
msgstr "Zapisz obrazy do..."
|
||||
|
||||
#: pygtktalog/views/glade/main.glade.h:52
|
||||
#: pygtktalog/views/glade/discs.glade.h:7
|
||||
msgid "_Statistics"
|
||||
msgstr "_Statystyka"
|
||||
|
||||
#: pygtktalog/views/glade/main.glade.h:53
|
||||
#: pygtktalog/views/glade/discs.glade.h:8
|
||||
msgid "_Update"
|
||||
msgstr "_Uaktualnij"
|
||||
|
||||
#: pygtktalog/views/glade/main.glade.h:54
|
||||
#: pygtktalog/views/glade/main.glade.h:34
|
||||
#: pygtktalog/views/glade/test.glade.h:4
|
||||
msgid "_View"
|
||||
msgstr "_Widok"
|
||||
|
||||
#: pygtktalog/views/glade/main.glade.h:55
|
||||
msgid "gtk-clear"
|
||||
msgstr ""
|
||||
|
||||
#: pygtktalog/views/glade/main.glade.h:56
|
||||
#: pygtktalog/views/glade/main.glade.h:35
|
||||
msgid "pyGTKtalog"
|
||||
msgstr "pyGTKtalog"
|
||||
|
||||
#: pygtktalog/views/glade/main.glade.h:57
|
||||
#: pygtktalog/views/glade/main.glade.h:36
|
||||
msgid "pyGTKtalog - Image"
|
||||
msgstr "pyGTKtalog - Obraz"
|
||||
|
||||
#: pygtktalog/controllers/main.py:47
|
||||
msgid "Do you really want to quit?"
|
||||
msgstr "Czy na pewno chcesz zakończyć?"
|
||||
|
||||
#: pygtktalog/controllers/main.py:48
|
||||
msgid "Current database is not saved, any changes will be lost."
|
||||
msgstr "BieĹĽÄ…cy katalog nie jest zapisany, wszelkie zmiany zostanÄ… utracone."
|
||||
|
||||
#: pygtktalog/controllers/main.py:48 pygtktalog/controllers/main.py:49
|
||||
msgid "Quit application"
|
||||
msgstr "Zakończ aplikację"
|
||||
|
||||
#: pygtktalog/models/main.py:16 pygtktalog/models/main.py:31
|
||||
msgid "Idle"
|
||||
msgstr "Bezczynny"
|
||||
|
||||
#: pygtktalog/controllers/files.py:33
|
||||
#, fuzzy
|
||||
msgid "Disc"
|
||||
msgstr "Dyski"
|
||||
|
||||
#: pygtktalog/controllers/files.py:39
|
||||
#, fuzzy
|
||||
msgid "Filename"
|
||||
msgstr "_Zmień nazwę"
|
||||
|
||||
#: pygtktalog/controllers/files.py:50
|
||||
msgid "Path"
|
||||
msgstr ""
|
||||
|
||||
#: pygtktalog/controllers/files.py:56
|
||||
msgid "Size"
|
||||
msgstr ""
|
||||
|
||||
#: pygtktalog/controllers/files.py:61
|
||||
msgid "Date"
|
||||
msgstr ""
|
||||
|
||||
#: pygtktalog/views/glade/test.glade.h:5
|
||||
msgid "gtk-about"
|
||||
msgstr ""
|
||||
|
||||
#: pygtktalog/views/glade/test.glade.h:6
|
||||
msgid "gtk-copy"
|
||||
msgstr ""
|
||||
|
||||
#: pygtktalog/views/glade/test.glade.h:7
|
||||
msgid "gtk-cut"
|
||||
msgstr ""
|
||||
|
||||
#: pygtktalog/views/glade/test.glade.h:8
|
||||
msgid "gtk-delete"
|
||||
msgstr ""
|
||||
|
||||
#: pygtktalog/views/glade/test.glade.h:9
|
||||
msgid "gtk-new"
|
||||
msgstr ""
|
||||
|
||||
#: pygtktalog/views/glade/test.glade.h:10
|
||||
msgid "gtk-open"
|
||||
msgstr ""
|
||||
|
||||
#: pygtktalog/views/glade/test.glade.h:11
|
||||
msgid "gtk-paste"
|
||||
msgstr ""
|
||||
|
||||
#: pygtktalog/views/glade/test.glade.h:12
|
||||
msgid "gtk-quit"
|
||||
msgstr ""
|
||||
|
||||
#: pygtktalog/views/glade/test.glade.h:13
|
||||
msgid "gtk-save"
|
||||
msgstr ""
|
||||
|
||||
#: pygtktalog/views/glade/test.glade.h:14
|
||||
msgid "gtk-save-as"
|
||||
msgstr ""
|
||||
370
locale/pygtktalog.pot
Normal file
370
locale/pygtktalog.pot
Normal file
@@ -0,0 +1,370 @@
|
||||
#
|
||||
# pygtktalog Language File
|
||||
#
|
||||
msgid ""
|
||||
msgstr ""
|
||||
"Project-Id-Version: pygtktalog\n"
|
||||
"POT-Creation-Date: 2009-08-26 22:10:33.892815\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"
|
||||
|
||||
#: pygtktalog/views/glade/main.glade.h:1
|
||||
msgid "Add CD"
|
||||
msgstr ""
|
||||
|
||||
#: pygtktalog/views/glade/main.glade.h:2
|
||||
msgid "Add CD/DVD to catalog"
|
||||
msgstr ""
|
||||
|
||||
#: pygtktalog/views/glade/main.glade.h:3
|
||||
msgid "Add Dir"
|
||||
msgstr ""
|
||||
|
||||
#: pygtktalog/views/glade/main.glade.h:4
|
||||
msgid "Add _CD/DVD"
|
||||
msgstr ""
|
||||
|
||||
#: pygtktalog/views/glade/main.glade.h:5
|
||||
msgid "Add _Directory"
|
||||
msgstr ""
|
||||
|
||||
#: pygtktalog/views/glade/main.glade.h:6
|
||||
#: pygtktalog/views/glade/files.glade.h:1
|
||||
msgid "Add _Images"
|
||||
msgstr ""
|
||||
|
||||
#: pygtktalog/views/glade/main.glade.h:7
|
||||
#: pygtktalog/views/glade/files.glade.h:2
|
||||
msgid "Add _Thumbnail"
|
||||
msgstr ""
|
||||
|
||||
#: pygtktalog/views/glade/main.glade.h:8
|
||||
#: pygtktalog/views/glade/files.glade.h:3
|
||||
msgid ""
|
||||
"Add images to file. If file have no thumbnail,\n"
|
||||
"thumbnail from first image will be generated."
|
||||
msgstr ""
|
||||
|
||||
#: pygtktalog/views/glade/main.glade.h:10
|
||||
#: pygtktalog/views/glade/main.glade.h:6
|
||||
msgid "Cancel"
|
||||
msgstr ""
|
||||
|
||||
#: pygtktalog/views/glade/main.glade.h:11
|
||||
#: pygtktalog/views/glade/main.glade.h:7
|
||||
msgid "Catalog _statistics"
|
||||
msgstr ""
|
||||
|
||||
#: pygtktalog/views/glade/main.glade.h:12
|
||||
#: pygtktalog/views/glade/discs.glade.h:1
|
||||
msgid "Collapse all nodes"
|
||||
msgstr ""
|
||||
|
||||
#: pygtktalog/views/glade/main.glade.h:13
|
||||
#: pygtktalog/views/glade/main.glade.h:8
|
||||
msgid "Create new catalog"
|
||||
msgstr ""
|
||||
|
||||
#: pygtktalog/views/glade/main.glade.h:14
|
||||
#: pygtktalog/views/glade/main.glade.h:9
|
||||
msgid "Delete all images"
|
||||
msgstr ""
|
||||
|
||||
#: pygtktalog/views/glade/main.glade.h:15
|
||||
#: pygtktalog/views/glade/main.glade.h:10
|
||||
msgid "Delete all thumbnals"
|
||||
msgstr ""
|
||||
|
||||
#: pygtktalog/views/glade/main.glade.h:16
|
||||
#: pygtktalog/views/glade/main.glade.h:11
|
||||
msgid "Delete tag"
|
||||
msgstr ""
|
||||
|
||||
#: pygtktalog/views/glade/main.glade.h:17
|
||||
#: pygtktalog/views/glade/main.glade.h:12
|
||||
msgid "Deletes all images from files in current colection"
|
||||
msgstr ""
|
||||
|
||||
#: pygtktalog/views/glade/main.glade.h:18
|
||||
#: pygtktalog/views/glade/main.glade.h:13
|
||||
msgid "Discs"
|
||||
msgstr ""
|
||||
|
||||
#: pygtktalog/views/glade/main.glade.h:19
|
||||
#: pygtktalog/views/glade/details.glade.h:1
|
||||
msgid "Double click to open image"
|
||||
msgstr ""
|
||||
|
||||
#: pygtktalog/views/glade/main.glade.h:20
|
||||
#: pygtktalog/views/glade/details.glade.h:2
|
||||
msgid "EXIF"
|
||||
msgstr ""
|
||||
|
||||
#: pygtktalog/views/glade/main.glade.h:21
|
||||
#: pygtktalog/views/glade/discs.glade.h:2
|
||||
msgid "Expand all nodes"
|
||||
msgstr ""
|
||||
|
||||
#: pygtktalog/views/glade/main.glade.h:22
|
||||
#: pygtktalog/views/glade/main.glade.h:14
|
||||
msgid "Export"
|
||||
msgstr ""
|
||||
|
||||
#: pygtktalog/views/glade/main.glade.h:23
|
||||
#: pygtktalog/views/glade/details.glade.h:3
|
||||
msgid "File info"
|
||||
msgstr ""
|
||||
|
||||
#: pygtktalog/views/glade/main.glade.h:24
|
||||
#: pygtktalog/views/glade/main.glade.h:15
|
||||
msgid "Find file"
|
||||
msgstr ""
|
||||
|
||||
#: pygtktalog/views/glade/main.glade.h:25
|
||||
#: pygtktalog/views/glade/details.glade.h:4
|
||||
msgid "Images"
|
||||
msgstr ""
|
||||
|
||||
#: pygtktalog/views/glade/main.glade.h:26
|
||||
#: pygtktalog/views/glade/main.glade.h:16
|
||||
msgid "Import"
|
||||
msgstr ""
|
||||
|
||||
#: pygtktalog/views/glade/main.glade.h:27
|
||||
#: pygtktalog/views/glade/main.glade.h:17
|
||||
msgid "Open catalog file"
|
||||
msgstr ""
|
||||
|
||||
#: pygtktalog/views/glade/main.glade.h:28
|
||||
#: pygtktalog/views/glade/main.glade.h:18
|
||||
msgid "Quit pyGTKtalog"
|
||||
msgstr ""
|
||||
|
||||
#: pygtktalog/views/glade/main.glade.h:29
|
||||
#: pygtktalog/views/glade/files.glade.h:5
|
||||
msgid "Re_move Thumbnail"
|
||||
msgstr ""
|
||||
|
||||
#: pygtktalog/views/glade/main.glade.h:30
|
||||
#: pygtktalog/views/glade/main.glade.h:19
|
||||
msgid "Recent files"
|
||||
msgstr ""
|
||||
|
||||
#: pygtktalog/views/glade/main.glade.h:31
|
||||
#: pygtktalog/views/glade/files.glade.h:6
|
||||
msgid "Rem_ove All Images"
|
||||
msgstr ""
|
||||
|
||||
#: pygtktalog/views/glade/main.glade.h:32
|
||||
#: pygtktalog/views/glade/files.glade.h:7
|
||||
msgid "Remo_ve tag"
|
||||
msgstr ""
|
||||
|
||||
#: pygtktalog/views/glade/main.glade.h:33
|
||||
#: pygtktalog/views/glade/main.glade.h:20
|
||||
msgid "Save all images..."
|
||||
msgstr ""
|
||||
|
||||
#: pygtktalog/views/glade/main.glade.h:34
|
||||
#: pygtktalog/views/glade/main.glade.h:21
|
||||
msgid "Save catalog"
|
||||
msgstr ""
|
||||
|
||||
#: pygtktalog/views/glade/main.glade.h:35
|
||||
#: pygtktalog/views/glade/main.glade.h:22
|
||||
msgid "Set as _thumbnail"
|
||||
msgstr ""
|
||||
|
||||
#: pygtktalog/views/glade/main.glade.h:36
|
||||
#: pygtktalog/views/glade/main.glade.h:23
|
||||
msgid "Status bar"
|
||||
msgstr ""
|
||||
|
||||
#: pygtktalog/views/glade/main.glade.h:37
|
||||
#: pygtktalog/views/glade/main.glade.h:24
|
||||
msgid "Tags"
|
||||
msgstr ""
|
||||
|
||||
#: pygtktalog/views/glade/main.glade.h:38
|
||||
#: pygtktalog/views/glade/main.glade.h:25
|
||||
msgid "Toolbar"
|
||||
msgstr ""
|
||||
|
||||
#: pygtktalog/views/glade/main.glade.h:39
|
||||
#: pygtktalog/views/glade/main.glade.h:26
|
||||
msgid "_Add images"
|
||||
msgstr ""
|
||||
|
||||
#: pygtktalog/views/glade/main.glade.h:40
|
||||
#: pygtktalog/views/glade/files.glade.h:8
|
||||
msgid "_Add tag"
|
||||
msgstr ""
|
||||
|
||||
#: pygtktalog/views/glade/main.glade.h:41
|
||||
#: pygtktalog/views/glade/main.glade.h:27
|
||||
msgid "_Catalog"
|
||||
msgstr ""
|
||||
|
||||
#: pygtktalog/views/glade/main.glade.h:42
|
||||
#: pygtktalog/views/glade/discs.glade.h:3
|
||||
msgid "_Collapse all"
|
||||
msgstr ""
|
||||
|
||||
#: pygtktalog/views/glade/main.glade.h:43
|
||||
#: pygtktalog/views/glade/discs.glade.h:4
|
||||
#: pygtktalog/views/glade/files.glade.h:9
|
||||
msgid "_Delete"
|
||||
msgstr ""
|
||||
|
||||
#: pygtktalog/views/glade/main.glade.h:44
|
||||
#: pygtktalog/views/glade/main.glade.h:28
|
||||
msgid "_Delete images"
|
||||
msgstr ""
|
||||
|
||||
#: pygtktalog/views/glade/main.glade.h:45
|
||||
#: pygtktalog/views/glade/main.glade.h:29
|
||||
#: pygtktalog/views/glade/files.glade.h:10
|
||||
#: pygtktalog/views/glade/test.glade.h:1
|
||||
msgid "_Edit"
|
||||
msgstr ""
|
||||
|
||||
#: pygtktalog/views/glade/main.glade.h:46
|
||||
#: pygtktalog/views/glade/discs.glade.h:5
|
||||
msgid "_Expand all"
|
||||
msgstr ""
|
||||
|
||||
#: pygtktalog/views/glade/main.glade.h:47 pygtktalog/views/main_menu.py:19
|
||||
#: pygtktalog/views/glade/main.glade.h:30
|
||||
#: pygtktalog/views/glade/test.glade.h:2
|
||||
msgid "_File"
|
||||
msgstr ""
|
||||
|
||||
#: pygtktalog/views/glade/main.glade.h:48
|
||||
#: pygtktalog/views/glade/main.glade.h:31
|
||||
#: pygtktalog/views/glade/test.glade.h:3
|
||||
msgid "_Help"
|
||||
msgstr ""
|
||||
|
||||
#: pygtktalog/views/glade/main.glade.h:49
|
||||
#: pygtktalog/views/glade/main.glade.h:32
|
||||
msgid "_Remove Thumbnail"
|
||||
msgstr ""
|
||||
|
||||
#: pygtktalog/views/glade/main.glade.h:50
|
||||
#: pygtktalog/views/glade/discs.glade.h:6
|
||||
#: pygtktalog/views/glade/files.glade.h:11
|
||||
msgid "_Rename"
|
||||
msgstr ""
|
||||
|
||||
#: pygtktalog/views/glade/main.glade.h:51
|
||||
#: pygtktalog/views/glade/main.glade.h:33
|
||||
msgid "_Save images to..."
|
||||
msgstr ""
|
||||
|
||||
#: pygtktalog/views/glade/main.glade.h:52
|
||||
#: pygtktalog/views/glade/discs.glade.h:7
|
||||
msgid "_Statistics"
|
||||
msgstr ""
|
||||
|
||||
#: pygtktalog/views/glade/main.glade.h:53
|
||||
#: pygtktalog/views/glade/discs.glade.h:8
|
||||
msgid "_Update"
|
||||
msgstr ""
|
||||
|
||||
#: pygtktalog/views/glade/main.glade.h:54
|
||||
#: pygtktalog/views/glade/main.glade.h:34
|
||||
#: pygtktalog/views/glade/test.glade.h:4
|
||||
msgid "_View"
|
||||
msgstr ""
|
||||
|
||||
#: pygtktalog/views/glade/main.glade.h:55
|
||||
msgid "gtk-clear"
|
||||
msgstr ""
|
||||
|
||||
#: pygtktalog/views/glade/main.glade.h:56
|
||||
#: pygtktalog/views/glade/main.glade.h:35
|
||||
msgid "pyGTKtalog"
|
||||
msgstr ""
|
||||
|
||||
#: pygtktalog/views/glade/main.glade.h:57
|
||||
#: pygtktalog/views/glade/main.glade.h:36
|
||||
msgid "pyGTKtalog - Image"
|
||||
msgstr ""
|
||||
|
||||
#: pygtktalog/controllers/main.py:47
|
||||
msgid "Do you really want to quit?"
|
||||
msgstr ""
|
||||
|
||||
#: pygtktalog/controllers/main.py:48
|
||||
msgid "Current database is not saved, any changes will be lost."
|
||||
msgstr ""
|
||||
|
||||
#: pygtktalog/controllers/main.py:48 pygtktalog/controllers/main.py:49
|
||||
msgid "Quit application"
|
||||
msgstr ""
|
||||
|
||||
#: pygtktalog/models/main.py:16 pygtktalog/models/main.py:31
|
||||
msgid "Idle"
|
||||
msgstr ""
|
||||
|
||||
#: pygtktalog/controllers/files.py:33
|
||||
msgid "Disc"
|
||||
msgstr ""
|
||||
|
||||
#: pygtktalog/controllers/files.py:39
|
||||
msgid "Filename"
|
||||
msgstr ""
|
||||
|
||||
#: pygtktalog/controllers/files.py:50
|
||||
msgid "Path"
|
||||
msgstr ""
|
||||
|
||||
#: pygtktalog/controllers/files.py:56
|
||||
msgid "Size"
|
||||
msgstr ""
|
||||
|
||||
#: pygtktalog/controllers/files.py:61
|
||||
msgid "Date"
|
||||
msgstr ""
|
||||
|
||||
#: pygtktalog/views/glade/test.glade.h:5
|
||||
msgid "gtk-about"
|
||||
msgstr ""
|
||||
|
||||
#: pygtktalog/views/glade/test.glade.h:6
|
||||
msgid "gtk-copy"
|
||||
msgstr ""
|
||||
|
||||
#: pygtktalog/views/glade/test.glade.h:7
|
||||
msgid "gtk-cut"
|
||||
msgstr ""
|
||||
|
||||
#: pygtktalog/views/glade/test.glade.h:8
|
||||
msgid "gtk-delete"
|
||||
msgstr ""
|
||||
|
||||
#: pygtktalog/views/glade/test.glade.h:9
|
||||
msgid "gtk-new"
|
||||
msgstr ""
|
||||
|
||||
#: pygtktalog/views/glade/test.glade.h:10
|
||||
msgid "gtk-open"
|
||||
msgstr ""
|
||||
|
||||
#: pygtktalog/views/glade/test.glade.h:11
|
||||
msgid "gtk-paste"
|
||||
msgstr ""
|
||||
|
||||
#: pygtktalog/views/glade/test.glade.h:12
|
||||
msgid "gtk-quit"
|
||||
msgstr ""
|
||||
|
||||
#: pygtktalog/views/glade/test.glade.h:13
|
||||
msgid "gtk-save"
|
||||
msgstr ""
|
||||
|
||||
#: pygtktalog/views/glade/test.glade.h:14
|
||||
msgid "gtk-save-as"
|
||||
msgstr ""
|
||||
228
pavement.py
Normal file
228
pavement.py
Normal file
@@ -0,0 +1,228 @@
|
||||
"""
|
||||
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', 'test/.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 test" % _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
|
||||
11
pyGTKtalog
11
pyGTKtalog
@@ -1,11 +0,0 @@
|
||||
#!/bin/sh
|
||||
# Simple shell wraper to launch pyGTKtalog
|
||||
|
||||
# change path to pygtktalog.py file full path, then you can move this shell
|
||||
# script to desired place (like /usr/bin, /usr/local/bin, ~/bin and so on)
|
||||
path_to_gtktalog_directory="."
|
||||
|
||||
# python interpreter
|
||||
python_intrpreter="/usr/bin/python"
|
||||
|
||||
exec $python_intrpreter -OO ${path_to_gtktalog_directory}/pygtktalog.py "$@"
|
||||
118
pygtktalog.py
118
pygtktalog.py
@@ -1,118 +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
|
||||
|
||||
# -------------------------------------------------------------------------
|
||||
import sys
|
||||
import os
|
||||
try:
|
||||
import gtk
|
||||
except ImportError:
|
||||
print "You need to install pyGTK v2.10.x or newer."
|
||||
sys.exit(1)
|
||||
|
||||
|
||||
def setup_path():
|
||||
"""Sets up the python include paths to include needed directories"""
|
||||
import os.path
|
||||
|
||||
from src.utils.globals import TOPDIR
|
||||
sys.path = [os.path.join(TOPDIR, "src")] + sys.path
|
||||
return
|
||||
|
||||
|
||||
def check_requirements():
|
||||
"""Checks versions and other requirements"""
|
||||
import sys
|
||||
import gtkmvc
|
||||
gtkmvc.require("1.2.0")
|
||||
|
||||
try:
|
||||
from models.m_config import ConfigModel
|
||||
except ImportError:
|
||||
print "Some fundamental files are missing.",
|
||||
print "Try runnig pyGTKtalog in his root directory"
|
||||
sys.exit(1)
|
||||
|
||||
conf = ConfigModel()
|
||||
conf.load()
|
||||
|
||||
try:
|
||||
import pygtk
|
||||
#tell pyGTK, if possible, that we want GTKv2
|
||||
pygtk.require("2.0")
|
||||
except ImportError:
|
||||
#Some distributions come with GTK2, but not pyGTK
|
||||
pass
|
||||
|
||||
try:
|
||||
from pysqlite2 import dbapi2 as sqlite
|
||||
except ImportError:
|
||||
print "pyGTKtalog uses SQLite DB.\nYou'll need to get it and the",
|
||||
print "python bindings as well.",
|
||||
print "http://www.sqlite.org"
|
||||
print "http://initd.org/tracker/pysqlite"
|
||||
sys.exit(1)
|
||||
|
||||
if conf.confd['exportxls']:
|
||||
try:
|
||||
import pyExcelerator
|
||||
except ImportError:
|
||||
print "WARNING: You'll need pyExcelerator, if you want to export",
|
||||
print "DB to XLS format."
|
||||
print "http://sourceforge.net/projects/pyexcelerator"
|
||||
|
||||
if conf.confd['thumbs'] and conf.confd['retrive']:
|
||||
try:
|
||||
import Image
|
||||
except ImportError:
|
||||
print "WARNING: You'll need Python Imaging Library (PIL), if you",
|
||||
print "want to make\nthumbnails!"
|
||||
return
|
||||
|
||||
if __name__ == "__main__":
|
||||
# Directory from where pygtkatalog was invoced. We need it for calculate
|
||||
# path for argument (catalog file)
|
||||
execution_dir = os.path.abspath(os.path.curdir)
|
||||
# Directory, where this files lies. We need it to setup private source
|
||||
# paths
|
||||
libraries_dir = os.path.dirname(__file__)
|
||||
os.chdir(libraries_dir)
|
||||
|
||||
setup_path()
|
||||
check_requirements()
|
||||
|
||||
from models.m_main import MainModel
|
||||
from ctrls.c_main import MainController
|
||||
from views.v_main import MainView
|
||||
|
||||
model = MainModel()
|
||||
if len(sys.argv) > 1:
|
||||
model.open(os.path.join(execution_dir, sys.argv[1]))
|
||||
controler = MainController(model)
|
||||
view = MainView(controler)
|
||||
|
||||
try:
|
||||
gtk.main()
|
||||
except KeyboardInterrupt:
|
||||
model.config.save()
|
||||
model.cleanup()
|
||||
gtk.main_quit
|
||||
1781
pygtktalog/EXIF.py
Normal file
1781
pygtktalog/EXIF.py
Normal file
File diff suppressed because it is too large
Load Diff
83
pygtktalog/EXIF_light_source_and_flash.patch
Normal file
83
pygtktalog/EXIF_light_source_and_flash.patch
Normal file
@@ -0,0 +1,83 @@
|
||||
diff -au EXIF.py EXIF_mine.py
|
||||
--- EXIF.py 2008-07-31 15:53:50.000000000 +0200
|
||||
+++ EXIF_mine.py 2009-02-25 18:39:48.000000000 +0100
|
||||
@@ -251,40 +251,54 @@
|
||||
2: 'CenterWeightedAverage',
|
||||
3: 'Spot',
|
||||
4: 'MultiSpot',
|
||||
- 5: 'Pattern'}),
|
||||
+ 5: 'Pattern',
|
||||
+ 6: 'Partial',
|
||||
+ 255: 'other'}),
|
||||
0x9208: ('LightSource',
|
||||
{0: 'Unknown',
|
||||
1: 'Daylight',
|
||||
2: 'Fluorescent',
|
||||
- 3: 'Tungsten',
|
||||
- 9: 'Fine Weather',
|
||||
- 10: 'Flash',
|
||||
+ 3: 'Tungsten (incandescent light)',
|
||||
+ 4: 'Flash',
|
||||
+ 9: 'Fine weather',
|
||||
+ 10: 'Cloudy weather',
|
||||
11: 'Shade',
|
||||
- 12: 'Daylight Fluorescent',
|
||||
- 13: 'Day White Fluorescent',
|
||||
- 14: 'Cool White Fluorescent',
|
||||
- 15: 'White Fluorescent',
|
||||
- 17: 'Standard Light A',
|
||||
- 18: 'Standard Light B',
|
||||
- 19: 'Standard Light C',
|
||||
+ 12: 'Daylight fluorescent (D 5700 - 7100K)',
|
||||
+ 13: 'Day white fluorescent (N 4600 - 5400K)',
|
||||
+ 14: 'Cool white fluorescent (W 3900 - 4500K)',
|
||||
+ 15: 'White fluorescent (WW 3200 - 3700K)',
|
||||
+ 17: 'Standard light A',
|
||||
+ 18: 'Standard light B',
|
||||
+ 19: 'Standard light C',
|
||||
20: 'D55',
|
||||
21: 'D65',
|
||||
22: 'D75',
|
||||
- 255: 'Other'}),
|
||||
+ 23: 'D50',
|
||||
+ 24: 'ISO studio tungsten',
|
||||
+ 255: 'other light source',}),
|
||||
0x9209: ('Flash',
|
||||
- {0: 'No',
|
||||
- 1: 'Fired',
|
||||
- 5: 'Fired (?)', # no return sensed
|
||||
- 7: 'Fired (!)', # return sensed
|
||||
- 9: 'Fill Fired',
|
||||
- 13: 'Fill Fired (?)',
|
||||
- 15: 'Fill Fired (!)',
|
||||
- 16: 'Off',
|
||||
- 24: 'Auto Off',
|
||||
- 25: 'Auto Fired',
|
||||
- 29: 'Auto Fired (?)',
|
||||
- 31: 'Auto Fired (!)',
|
||||
- 32: 'Not Available'}),
|
||||
+ {0: 'Flash did not fire',
|
||||
+ 1: 'Flash fired',
|
||||
+ 5: 'Strobe return light not detected',
|
||||
+ 7: 'Strobe return light detected',
|
||||
+ 9: 'Flash fired, compulsory flash mode',
|
||||
+ 13: 'Flash fired, compulsory flash mode, return light not detected',
|
||||
+ 15: 'Flash fired, compulsory flash mode, return light detected',
|
||||
+ 16: 'Flash did not fire, compulsory flash mode',
|
||||
+ 24: 'Flash did not fire, auto mode',
|
||||
+ 25: 'Flash fired, auto mode',
|
||||
+ 29: 'Flash fired, auto mode, return light not detected',
|
||||
+ 31: 'Flash fired, auto mode, return light detected',
|
||||
+ 32: 'No flash function',
|
||||
+ 65: 'Flash fired, red-eye reduction mode',
|
||||
+ 69: 'Flash fired, red-eye reduction mode, return light not detected',
|
||||
+ 71: 'Flash fired, red-eye reduction mode, return light detected',
|
||||
+ 73: 'Flash fired, compulsory flash mode, red-eye reduction mode',
|
||||
+ 77: 'Flash fired, compulsory flash mode, red-eye reduction mode, return light not detected',
|
||||
+ 79: 'Flash fired, compulsory flash mode, red-eye reduction mode, return light detected',
|
||||
+ 89: 'Flash fired, auto mode, red-eye reduction mode',
|
||||
+ 93: 'Flash fired, auto mode, return light not detected, red-eye reduction mode',
|
||||
+ 95: 'Flash fired, auto mode, return light detected, red-eye reduction mode'}),
|
||||
0x920A: ('FocalLength', ),
|
||||
0x9214: ('SubjectArea', ),
|
||||
0x927C: ('MakerNote', ),
|
||||
65
pygtktalog/__init__.py
Normal file
65
pygtktalog/__init__.py
Normal file
@@ -0,0 +1,65 @@
|
||||
"""
|
||||
Project: pyGTKtalog
|
||||
Description: Initialization for main module - i18n and so.
|
||||
Type: core
|
||||
Author: Roman 'gryf' Dobosz, gryf73@gmail.com
|
||||
Created: 2009-05-05
|
||||
"""
|
||||
|
||||
__version__ = "1.9.0"
|
||||
__appname__ = "pyGTKtalog"
|
||||
__copyright__ = u"\u00A9 Roman 'gryf' Dobosz"
|
||||
__summary__ = "%s is simple tool for managing file collections." % __appname__
|
||||
__web__ = "http://bitbucket.org/gryf"
|
||||
__logo_img__ = "views/pixmaps/Giant Worms.png"
|
||||
|
||||
import os
|
||||
import sys
|
||||
import locale
|
||||
import gettext
|
||||
import __builtin__
|
||||
|
||||
import gtk.glade
|
||||
|
||||
from logger import get_logger
|
||||
|
||||
|
||||
__all__ = ['controllers',
|
||||
'models',
|
||||
'views',
|
||||
'EXIF',
|
||||
'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
|
||||
0
pygtktalog/controllers/__init__.py
Normal file
0
pygtktalog/controllers/__init__.py
Normal file
18
pygtktalog/controllers/details.py
Normal file
18
pygtktalog/controllers/details.py
Normal file
@@ -0,0 +1,18 @@
|
||||
"""
|
||||
Project: pyGTKtalog
|
||||
Description: Controller for Details NoteBook
|
||||
Type: core
|
||||
Author: Roman 'gryf' Dobosz, gryf73@gmail.com
|
||||
Created: 2009-08-30
|
||||
"""
|
||||
from gtkmvc import Controller
|
||||
|
||||
|
||||
class DetailsController(Controller):
|
||||
"""
|
||||
Controller for details NoteBook.
|
||||
"""
|
||||
|
||||
def register_view(self, view):
|
||||
"""Default view registration stuff"""
|
||||
pass
|
||||
214
pygtktalog/controllers/discs.py
Normal file
214
pygtktalog/controllers/discs.py
Normal file
@@ -0,0 +1,214 @@
|
||||
"""
|
||||
Project: pyGTKtalog
|
||||
Description: Controller for Discs TreeView
|
||||
Type: core
|
||||
Author: Roman 'gryf' Dobosz, gryf73@gmail.com
|
||||
Created: 2009-08-30
|
||||
"""
|
||||
import gtk
|
||||
|
||||
from gtkmvc import Controller
|
||||
|
||||
from pygtktalog.logger import get_logger
|
||||
|
||||
LOG = get_logger("discs ctrl")
|
||||
|
||||
|
||||
class DiscsController(Controller):
|
||||
"""
|
||||
Controller for discs TreeView
|
||||
"""
|
||||
|
||||
def __init__(self, model, view):
|
||||
"""
|
||||
Initialize DiscsController
|
||||
"""
|
||||
LOG.debug(self.__init__.__doc__.strip())
|
||||
Controller.__init__(self, model, view)
|
||||
self.discs_model = self.model.discs.discs
|
||||
|
||||
def register_view(self, view):
|
||||
"""
|
||||
Do DiscTree registration
|
||||
"""
|
||||
LOG.debug(self.register_view.__doc__.strip())
|
||||
view['discs'].set_model(self.discs_model)
|
||||
|
||||
# register observers
|
||||
self.model.discs.register_observer(self)
|
||||
|
||||
# connect signals to popup menu - framework somehow omits automatic
|
||||
# signal connection for subviews which are not included to widgets
|
||||
# tree
|
||||
sigs = {'expand_all': ('activate', self.on_expand_all_activate),
|
||||
'collapse_all': ('activate', self.on_collapse_all_activate),
|
||||
'update': ('activate', self.on_update_activate),
|
||||
'rename': ('activate', self.on_rename_activate),
|
||||
'delete': ('activate', self.on_delete_activate),
|
||||
'statistics': ('activate', self.on_statistics_activate)}
|
||||
for signal in sigs:
|
||||
view.menu[signal].connect(sigs[signal][0], sigs[signal][1])
|
||||
|
||||
col = gtk.TreeViewColumn('discs_column')
|
||||
|
||||
cellpb = gtk.CellRendererPixbuf()
|
||||
cell = gtk.CellRendererText()
|
||||
|
||||
# make cell text editabe
|
||||
cell.set_property('editable', True)
|
||||
cell.connect('edited', self.on_editing_done, self.discs_model)
|
||||
# TODO: find a way how to disable default return keypress on editable
|
||||
# fields
|
||||
|
||||
col.pack_start(cellpb, False)
|
||||
col.pack_start(cell, True)
|
||||
|
||||
col.set_attributes(cellpb, stock_id=2)
|
||||
col.set_attributes(cell, text=1)
|
||||
|
||||
view['discs'].append_column(col)
|
||||
view['discs'].show()
|
||||
|
||||
# treeview signals
|
||||
def on_discs_button_press_event(self, treeview, event):
|
||||
"""
|
||||
Handle right click on discs treeview - show popup menu.
|
||||
"""
|
||||
LOG.debug(self.on_discs_button_press_event.__doc__.strip())
|
||||
pathinfo = treeview.get_path_at_pos(int(event.x), int(event.y))
|
||||
|
||||
if event.button == 3:
|
||||
if pathinfo:
|
||||
path = pathinfo[0]
|
||||
|
||||
# Make sure, that there is selected row
|
||||
sel = treeview.get_selection()
|
||||
sel.unselect_all()
|
||||
sel.select_path(path)
|
||||
|
||||
self._popup_menu(sel, event, event.button)
|
||||
else:
|
||||
self._popup_menu(None, event, event.button)
|
||||
return True
|
||||
|
||||
def on_discs_cursor_changed(self, treeview):
|
||||
"""
|
||||
Show files on right treeview, after clicking the left disc treeview.
|
||||
"""
|
||||
LOG.debug(self.on_discs_cursor_changed.__doc__.strip())
|
||||
selection = treeview.get_selection()
|
||||
path = selection.get_selected_rows()[1][0]
|
||||
self.model.files.refresh(self.discs_model.get_value(\
|
||||
self.discs_model.get_iter(path), 0))
|
||||
|
||||
def on_discs_key_release_event(self, treeview, event):
|
||||
"""
|
||||
Watch for specific keys
|
||||
"""
|
||||
LOG.debug(self.on_discs_key_release_event.__doc__.strip())
|
||||
if gtk.gdk.keyval_name(event.keyval) == 'Menu':
|
||||
LOG.debug('Menu key pressed')
|
||||
self._popup_menu(treeview.get_selection(), event, 0)
|
||||
return True
|
||||
return False
|
||||
|
||||
def on_discs_row_activated(self, treeview, path, treecolumn):
|
||||
"""
|
||||
If possible, expand or collapse branch of discs tree
|
||||
"""
|
||||
LOG.debug(self.on_discs_row_activated.__doc__.strip())
|
||||
if treeview.row_expanded(path):
|
||||
treeview.collapse_row(path)
|
||||
else:
|
||||
treeview.expand_row(path, False)
|
||||
|
||||
def on_editing_done(self, cell, path, new_text, model):
|
||||
"""
|
||||
Store changed filename text
|
||||
"""
|
||||
LOG.debug(self.on_editing_done.__doc__.strip())
|
||||
model[path][0].filename = new_text
|
||||
model[path][1] = new_text
|
||||
return
|
||||
|
||||
# popup menu signals
|
||||
def on_expand_all_activate(self, menu_item):
|
||||
"""
|
||||
Expand all
|
||||
"""
|
||||
LOG.debug(self.on_expand_all_activate.__doc__.strip())
|
||||
self.view['discs'].expand_all()
|
||||
|
||||
def on_collapse_all_activate(self, menu_item):
|
||||
"""
|
||||
Collapse all
|
||||
"""
|
||||
LOG.debug(self.on_collapse_all_activate.__doc__.strip())
|
||||
self.view['discs'].collapse_all()
|
||||
|
||||
def on_update_activate(self, menu_item):
|
||||
"""
|
||||
Trigger update specified tree entry
|
||||
"""
|
||||
LOG.debug(self.on_update_activate.__doc__.strip())
|
||||
raise NotImplementedError
|
||||
|
||||
def on_rename_activate(self, menu_item):
|
||||
"""
|
||||
Rename disk or directory
|
||||
"""
|
||||
LOG.debug(self.on_rename_activate.__doc__.strip())
|
||||
treeview = self.view['discs']
|
||||
selection = treeview.get_selection()
|
||||
path = selection.get_selected_rows()[1][0]
|
||||
treeview.set_cursor(path, treeview.get_column(0), start_editing=True)
|
||||
|
||||
def on_delete_activate(self, menu_item):
|
||||
"""
|
||||
Delete disk or directory from catalog
|
||||
"""
|
||||
LOG.debug(self.on_delete_activate.__doc__.strip())
|
||||
raise NotImplementedError
|
||||
|
||||
def on_statistics_activate(self, menu_item):
|
||||
"""
|
||||
Show statistics for selected item
|
||||
"""
|
||||
LOG.debug(self.on_statistics_activate.__doc__.strip())
|
||||
raise NotImplementedError
|
||||
|
||||
# observable properties
|
||||
def property_currentdir_value_change(self, model, old, new):
|
||||
"""
|
||||
Change of a current dir signalized by other controllers/models
|
||||
"""
|
||||
LOG.debug(self.property_currentdir_value_change.__doc__.strip())
|
||||
self._set_cursor_to_obj_position(new)
|
||||
|
||||
# private methods
|
||||
def _popup_menu(self, selection, event, button):
|
||||
"""
|
||||
Popup menu for discs treeview. Gather information from discs model,
|
||||
and trigger menu popup.
|
||||
"""
|
||||
LOG.debug(self._popup_menu.__doc__.strip())
|
||||
if selection is None:
|
||||
self.view.menu.set_menu_items_sensitivity(False)
|
||||
else:
|
||||
model, list_of_paths = selection.get_selected_rows()
|
||||
|
||||
for path in list_of_paths:
|
||||
self.view.menu.set_menu_items_sensitivity(True)
|
||||
self.view.menu.set_update_sensitivity(model.get_value(\
|
||||
model.get_iter(path), 0).parent_id == 1)
|
||||
|
||||
self.view.menu['discs_popup'].popup(None, None, None,
|
||||
button, event.time)
|
||||
|
||||
def _set_cursor_to_obj_position(self, obj):
|
||||
"""
|
||||
Set cursor/focus to specified object postion in Discs treeview.
|
||||
"""
|
||||
path = self.model.discs.find_path(obj)
|
||||
self.view['discs'].expand_to_path(path)
|
||||
self.view['discs'].set_cursor(path)
|
||||
282
pygtktalog/controllers/files.py
Normal file
282
pygtktalog/controllers/files.py
Normal file
@@ -0,0 +1,282 @@
|
||||
"""
|
||||
Project: pyGTKtalog
|
||||
Description: Controller for Files TreeView
|
||||
Type: core
|
||||
Author: Roman 'gryf' Dobosz, gryf73@gmail.com
|
||||
Created: 2009-08-30
|
||||
"""
|
||||
import gtk
|
||||
|
||||
from gtkmvc import Controller
|
||||
|
||||
from pygtktalog.pygtkutils import get_tv_item_under_cursor
|
||||
from pygtktalog.logger import get_logger
|
||||
|
||||
LOG = get_logger("files ctrl")
|
||||
|
||||
|
||||
class FilesController(Controller):
|
||||
"""
|
||||
Controller for files TreeView list.
|
||||
"""
|
||||
|
||||
def __init__(self, model, view):
|
||||
"""
|
||||
FilesController initialization
|
||||
"""
|
||||
Controller.__init__(self, model, view)
|
||||
self.DND_TARGETS = [('files_tags', 0, 69)]
|
||||
self.files_model = self.model.files
|
||||
|
||||
def register_view(self, view):
|
||||
"""
|
||||
Register view, and setup columns for files treeview
|
||||
"""
|
||||
view['files'].set_model(self.files_model.files)
|
||||
|
||||
sigs = {"add_tag": ("activate", self.on_add_tag1_activate),
|
||||
"delete_tag": ("activate", self.on_delete_tag_activate),
|
||||
"add_thumb": ("activate", self.on_add_thumb1_activate),
|
||||
"remove_thumb": ("activate", self.on_remove_thumb1_activate),
|
||||
"add_image": ("activate", self.on_add_image1_activate),
|
||||
"remove_image": ("activate", self.on_remove_image1_activate),
|
||||
"edit": ("activate", self.on_edit2_activate),
|
||||
"delete": ("activate", self.on_delete3_activate),
|
||||
"rename": ("activate", self.on_rename2_activate)}
|
||||
for signal in sigs:
|
||||
view.menu[signal].connect(sigs[signal][0], sigs[signal][1])
|
||||
|
||||
view['files'].get_selection().set_mode(gtk.SELECTION_MULTIPLE)
|
||||
|
||||
col = gtk.TreeViewColumn(_('Disc'), gtk.CellRendererText(), text=1)
|
||||
col.set_sort_column_id(1)
|
||||
col.set_resizable(True)
|
||||
col.set_visible(False)
|
||||
view['files'].append_column(col)
|
||||
|
||||
col = gtk.TreeViewColumn(_('Filename'))
|
||||
cellpb = gtk.CellRendererPixbuf()
|
||||
cell = gtk.CellRendererText()
|
||||
col.pack_start(cellpb, False)
|
||||
col.pack_start(cell, True)
|
||||
col.set_attributes(cellpb, stock_id=7)
|
||||
col.set_attributes(cell, text=2)
|
||||
col.set_sort_column_id(2)
|
||||
col.set_resizable(True)
|
||||
self.view['files'].append_column(col)
|
||||
|
||||
col = gtk.TreeViewColumn(_('Path'), gtk.CellRendererText(), text=3)
|
||||
col.set_sort_column_id(3)
|
||||
col.set_resizable(True)
|
||||
col.set_visible(False)
|
||||
self.view['files'].append_column(col)
|
||||
|
||||
col = gtk.TreeViewColumn(_('Size'), gtk.CellRendererText(), text=4)
|
||||
col.set_sort_column_id(4)
|
||||
col.set_resizable(True)
|
||||
self.view['files'].append_column(col)
|
||||
|
||||
col = gtk.TreeViewColumn(_('Date'), gtk.CellRendererText(), text=5)
|
||||
col.set_sort_column_id(5)
|
||||
col.set_resizable(True)
|
||||
self.view['files'].append_column(col)
|
||||
self.view['files'].set_search_column(2)
|
||||
|
||||
# setup d'n'd support
|
||||
self.view['files'].drag_source_set(gtk.gdk.BUTTON1_MASK,
|
||||
self.DND_TARGETS,
|
||||
gtk.gdk.ACTION_COPY)
|
||||
|
||||
# signals
|
||||
def on_files_drag_data_get(self, treeview, context, selection,
|
||||
targetType, eventTime):
|
||||
"""responce to "data get" DnD signal"""
|
||||
# get selection, and send it to the client
|
||||
if targetType == self.DND_TARGETS[0][2]:
|
||||
# get selection
|
||||
treesrl = treeview.get_selection()
|
||||
model, list_of_paths = treesrl.get_selected_rows()
|
||||
ids = []
|
||||
for path in list_of_paths:
|
||||
fid = model.get_value(model.get_iter(path), 0)
|
||||
ids.append(fid)
|
||||
string = str(tuple(ids)).replace(",)", ")")
|
||||
selection.set(selection.target, 8, string)
|
||||
|
||||
def on_files_button_press_event(self, treeview, event):
|
||||
"""
|
||||
Handle right click on files treeview - show popup menu.
|
||||
"""
|
||||
LOG.debug("Mouse button pressed")
|
||||
pathinfo = treeview.get_path_at_pos(int(event.x), int(event.y))
|
||||
|
||||
if event.button == 3: # Right mouse button. Show context menu.
|
||||
LOG.debug("It's a right button")
|
||||
if pathinfo:
|
||||
path = pathinfo[0]
|
||||
|
||||
# Make sure, that there is selected row
|
||||
sel = treeview.get_selection()
|
||||
sel.unselect_all()
|
||||
sel.select_path(path)
|
||||
|
||||
self._popup_menu(sel, event, event.button)
|
||||
else:
|
||||
self._popup_menu(None, event, event.button)
|
||||
return True
|
||||
#try:
|
||||
# selection = tree.get_selection()
|
||||
# model, list_of_paths = selection.get_selected_rows()
|
||||
#except TypeError:
|
||||
# list_of_paths = []
|
||||
|
||||
#if len(list_of_paths) == 0:
|
||||
# # try to select item under cursor
|
||||
# try:
|
||||
# path, column, x, y = tree.get_path_at_pos(int(event.x),
|
||||
# int(event.y))
|
||||
# except TypeError:
|
||||
# # failed, do not show any popup and return
|
||||
# tree.get_selection().unselect_all()
|
||||
# return False
|
||||
# selection.select_path(path[0])
|
||||
|
||||
#if len(list_of_paths) > 1:
|
||||
# self.view['add_image'].set_sensitive(False)
|
||||
# self.view['rename'].set_sensitive(False)
|
||||
# self.view['edit'].set_sensitive(False)
|
||||
#else:
|
||||
# self.view['add_image'].set_sensitive(True)
|
||||
# self.view['rename'].set_sensitive(True)
|
||||
# self.view['edit'].set_sensitive(True)
|
||||
#self.__popup_menu(event, 'files_popup')
|
||||
#return True
|
||||
LOG.debug("It's other button")
|
||||
|
||||
def on_files_cursor_changed(self, treeview):
|
||||
"""Show details of selected file/directory"""
|
||||
file_id = get_tv_item_under_cursor(treeview)
|
||||
LOG.debug("found item: %s" % file_id)
|
||||
return
|
||||
|
||||
def on_files_key_release_event(self, treeview, event):
|
||||
"""do something with pressed keys"""
|
||||
if gtk.gdk.keyval_name(event.keyval) == 'Menu':
|
||||
try:
|
||||
selection = treeview.get_selection()
|
||||
model, list_of_paths = selection.get_selected_rows()
|
||||
if not list_of_paths:
|
||||
return
|
||||
except TypeError:
|
||||
return
|
||||
self._popup_menu(selection, event, 0)
|
||||
|
||||
if gtk.gdk.keyval_name(event.keyval) == 'BackSpace':
|
||||
row, gtk_column = self.view['files'].get_cursor()
|
||||
if row and gtk_column:
|
||||
fileob = self.files_model.get_value(row)
|
||||
if fileob.parent.parent.id != 1:
|
||||
#self.files_model.refresh(fileob.parent.parent)
|
||||
# TODO: synchronize with disks
|
||||
self.model.discs.currentdir = fileob.parent.parent
|
||||
|
||||
self.view['files'].grab_focus()
|
||||
|
||||
|
||||
def on_files_row_activated(self, files_obj, row, column):
|
||||
"""
|
||||
On directory doubleclick in files listview dive into desired branch.
|
||||
"""
|
||||
|
||||
fileob = self.files_model.get_value(row=row)
|
||||
if not fileob.children:
|
||||
# ONLY directories. files are omitted.
|
||||
return
|
||||
|
||||
self.files_model.refresh(fileob)
|
||||
self.model.discs.currentdir = fileob
|
||||
self.view['files'].grab_focus()
|
||||
|
||||
# TODO: synchronize with disks
|
||||
return
|
||||
|
||||
def on_add_tag1_activate(self, menu_item):
|
||||
"""
|
||||
TODO
|
||||
"""
|
||||
LOG.debug(self.on_add_tag1_activate.__doc__.strip())
|
||||
raise NotImplementedError
|
||||
|
||||
def on_delete_tag_activate(self, menuitem):
|
||||
"""
|
||||
TODO
|
||||
"""
|
||||
LOG.debug(self.on_delete_tag_activate.__doc__.strip())
|
||||
raise NotImplementedError
|
||||
|
||||
def on_add_thumb1_activate(self, menuitem):
|
||||
"""
|
||||
TODO
|
||||
"""
|
||||
LOG.debug(self.on_add_thumb1_activate.__doc__.strip())
|
||||
raise NotImplementedError
|
||||
|
||||
def on_remove_thumb1_activate(self, menuitem):
|
||||
"""
|
||||
TODO
|
||||
"""
|
||||
LOG.debug(self.on_remove_thumb1_activate.__doc__.strip())
|
||||
raise NotImplementedError
|
||||
|
||||
def on_add_image1_activate(self, menuitem):
|
||||
"""
|
||||
TODO
|
||||
"""
|
||||
LOG.debug(self.on_add_image1_activate.__doc__.strip())
|
||||
raise NotImplementedError
|
||||
|
||||
def on_remove_image1_activate(self, menuitem):
|
||||
"""
|
||||
TODO
|
||||
"""
|
||||
LOG.debug(self.on_remove_image1_activate.__doc__.strip())
|
||||
raise NotImplementedError
|
||||
|
||||
def on_edit2_activate(self, menuitem):
|
||||
"""
|
||||
TODO
|
||||
"""
|
||||
LOG.debug(self.on_edit2_activate.__doc__.strip())
|
||||
raise NotImplementedError
|
||||
|
||||
def on_delete3_activate(self, menuitem):
|
||||
"""
|
||||
TODO
|
||||
"""
|
||||
LOG.debug(self.on_delete3_activate.__doc__.strip())
|
||||
raise NotImplementedError
|
||||
|
||||
def on_rename2_activate(self, menuitem):
|
||||
"""
|
||||
TODO
|
||||
"""
|
||||
LOG.debug(self.on_rename2_activate.__doc__.strip())
|
||||
raise NotImplementedError
|
||||
|
||||
# private methods
|
||||
def _popup_menu(self, selection, event, button):
|
||||
"""
|
||||
Popup menu for files treeview. Gather information from discs model,
|
||||
and trigger menu popup.
|
||||
"""
|
||||
LOG.debug(self._popup_menu.__doc__.strip())
|
||||
if selection is None:
|
||||
self.view.menu.set_menu_items_sensitivity(False)
|
||||
else:
|
||||
model, list_of_paths = selection.get_selected_rows()
|
||||
|
||||
for path in list_of_paths:
|
||||
self.view.menu.set_menu_items_sensitivity(True)
|
||||
|
||||
self.view.menu['files_popup'].popup(None, None, None,
|
||||
button, event.time)
|
||||
201
pygtktalog/controllers/main.py
Normal file
201
pygtktalog/controllers/main.py
Normal file
@@ -0,0 +1,201 @@
|
||||
"""
|
||||
Project: pyGTKtalog
|
||||
Description: Controller for main window
|
||||
Type: core
|
||||
Author: Roman 'gryf' Dobosz, gryf73@gmail.com
|
||||
Created: 2009-05-02
|
||||
"""
|
||||
import gtk
|
||||
|
||||
from gtkmvc import Controller
|
||||
|
||||
from pygtktalog.controllers.discs import DiscsController
|
||||
from pygtktalog.controllers.files import FilesController
|
||||
#from pygtktalog.controllers.details import DetailsController
|
||||
#from pygtktalog.controllers.tags import TagcloudController
|
||||
#from pygtktalog.dialogs import yesno, okcancel, info, warn, error
|
||||
from pygtktalog.dialogs import open_catalog, save_catalog, error, yesno, about
|
||||
from pygtktalog.logger import get_logger
|
||||
|
||||
LOG = get_logger("main controller")
|
||||
|
||||
class MainController(Controller):
|
||||
"""
|
||||
Controller for main application window
|
||||
"""
|
||||
TITLE = "pyGTKtalog"
|
||||
UNTITLED = _("untitled")
|
||||
|
||||
def __init__(self, model, view):
|
||||
"""
|
||||
Initialize MainController, add controllers for trees and details.
|
||||
"""
|
||||
LOG.debug(self.__init__.__doc__.strip())
|
||||
Controller.__init__(self, model, view)
|
||||
|
||||
# add controllers for files/tags/details components
|
||||
self.discs = DiscsController(model, view.discs)
|
||||
self.files = FilesController(model, view.files)
|
||||
#self.details = DetailsController(model, view.details)
|
||||
#self.tags = TagcloudController(model, view.tags)
|
||||
|
||||
|
||||
def register_view(self, view):
|
||||
"""
|
||||
Registration view for MainController class
|
||||
"""
|
||||
LOG.debug(self.register_view.__doc__.strip())
|
||||
LOG.debug("replace hardcoded defaults with configured!")
|
||||
view['main'].set_default_size(800, 600)
|
||||
view['hpaned1'].set_position(200)
|
||||
if not self.model.tmp_filename:
|
||||
view.set_widgets_app_sensitivity(False)
|
||||
view['main'].show()
|
||||
|
||||
# status bar
|
||||
LOG.debug("register statusbar")
|
||||
self.context_id = self.view['mainStatus'].get_context_id('status')
|
||||
self.statusbar_id = \
|
||||
self.view['mainStatus'].push(self.context_id,
|
||||
self.model.status_bar_message)
|
||||
|
||||
|
||||
# signals
|
||||
def on_main_destroy_event(self, widget, event):
|
||||
"""
|
||||
Window destroyed. Cleanup before quit.
|
||||
"""
|
||||
LOG.debug(self.on_main_destroy_event.__doc__.strip())
|
||||
self.on_quit_activate(widget)
|
||||
return True
|
||||
|
||||
def on_quit_activate(self, widget):
|
||||
"""
|
||||
Quit and save window parameters to config file
|
||||
"""
|
||||
LOG.debug(self.on_quit_activate.__doc__.strip())
|
||||
#if yesno(_("Do you really want to quit?"),
|
||||
# _("Current database is not saved, any changes will be "
|
||||
# "lost."), _("Quit application") + " - pyGTKtalog", 0):
|
||||
self.model.cleanup()
|
||||
LOG.debug("quit application")
|
||||
gtk.main_quit()
|
||||
return False
|
||||
|
||||
def on_new_activate(self, widget):
|
||||
"""
|
||||
Create new catalog file
|
||||
"""
|
||||
LOG.debug(self.on_new_activate.__doc__.strip())
|
||||
self.model.new()
|
||||
self._set_title()
|
||||
self.view.set_widgets_app_sensitivity(True)
|
||||
|
||||
def on_open_activate(self, widget):
|
||||
"""
|
||||
Open catalog file
|
||||
"""
|
||||
LOG.debug(self.on_open_activate.__doc__.strip())
|
||||
|
||||
if self.model.db_unsaved and 'confirm' in self.model.config and \
|
||||
self.model.config['confirm']:
|
||||
if not yesno(_("Current database is not saved"),
|
||||
_("Do you really want to open new file and abandon "
|
||||
"current one?"),
|
||||
_("Unsaved data")):
|
||||
LOG.debug("Cancel opening catalog file - unsaved data remain")
|
||||
return
|
||||
|
||||
initial_path = None
|
||||
#if self.model.config.recent and self.model.config.recent[0]:
|
||||
# initial_path = os.path.dirname(self.model.config.recent[0])
|
||||
|
||||
#if not path:
|
||||
path = open_catalog(path=initial_path)
|
||||
if not path:
|
||||
return
|
||||
|
||||
# cleanup files and details
|
||||
try:
|
||||
self.model.files_list.clear()
|
||||
except:
|
||||
pass
|
||||
#self.__hide_details()
|
||||
#self.view['tag_path_box'].hide()
|
||||
#buf = self.view['tag_cloud_textview'].get_buffer()
|
||||
#buf.set_text('')
|
||||
#self.view['tag_cloud_textview'].set_buffer(buf)
|
||||
|
||||
if not self.model.open(path):
|
||||
error(_("Cannot open file."),
|
||||
_("File %s cannot be open") % path,
|
||||
_("Error opening file"))
|
||||
else:
|
||||
#self.__generate_recent_menu()
|
||||
#self.__activate_ui(path)
|
||||
#self.__tag_cloud()
|
||||
self.view.set_widgets_app_sensitivity()
|
||||
self._set_title()
|
||||
|
||||
return
|
||||
|
||||
def on_about1_activate(self, widget):
|
||||
"""Show about dialog"""
|
||||
about()
|
||||
|
||||
def on_save_activate(self, widget):
|
||||
"""
|
||||
Save current catalog
|
||||
"""
|
||||
LOG.debug(self.on_save_activate.__doc__.strip())
|
||||
if not self.model.cat_fname:
|
||||
self.on_save_as_activate(widget)
|
||||
else:
|
||||
self.model.save()
|
||||
self._set_title()
|
||||
|
||||
def on_save_as_activate(self, widget):
|
||||
"""
|
||||
Save current catalog under differnet file
|
||||
"""
|
||||
LOG.debug(self.on_save_as_activate.__doc__.strip())
|
||||
initial_path = None
|
||||
#if self.model.config.recent[0]:
|
||||
# initial_path = os.path.dirname(self.model.config.recent[0])
|
||||
|
||||
path = save_catalog(path=initial_path)
|
||||
if path:
|
||||
ret, err = self.model.save(path)
|
||||
if ret:
|
||||
#self.model.config.add_recent(path)
|
||||
self._set_title()
|
||||
pass
|
||||
else:
|
||||
error(_("Cannot write file %s.") % path,
|
||||
"%s" % err,
|
||||
_("Error writing file"))
|
||||
|
||||
|
||||
# helpers
|
||||
def _set_title(self):
|
||||
"""
|
||||
Get title of the main window, to reflect state of the catalog file
|
||||
Returns:
|
||||
String with apropriate title for main form
|
||||
"""
|
||||
LOG.debug("change the title")
|
||||
|
||||
if not self.model.tmp_filename:
|
||||
LOG.debug("application has been initialized, title should"
|
||||
" be empty")
|
||||
fname = ""
|
||||
|
||||
elif not self.model.cat_fname:
|
||||
fname = self.UNTITLED
|
||||
else:
|
||||
fname = self.model.cat_fname
|
||||
|
||||
modified = self.model.db_unsaved and "*" or ""
|
||||
|
||||
self.view['main'].set_title("%s%s - %s" % (fname,
|
||||
modified, self.TITLE))
|
||||
23
pygtktalog/controllers/tags.py
Normal file
23
pygtktalog/controllers/tags.py
Normal file
@@ -0,0 +1,23 @@
|
||||
"""
|
||||
Project: pyGTKtalog
|
||||
Description: Controller for Tagcloud TextView
|
||||
Type: core
|
||||
Author: Roman 'gryf' Dobosz, gryf73@gmail.com
|
||||
Created: 2009-08-30
|
||||
"""
|
||||
from gtkmvc import Controller
|
||||
|
||||
|
||||
class TagcloudController(Controller):
|
||||
"""
|
||||
Controller for Tagcloud TextView
|
||||
"""
|
||||
|
||||
#def __init__(self, model, view):
|
||||
# """Initialize main controller"""
|
||||
# Controller.__init__(self, model, view)
|
||||
# return
|
||||
|
||||
def register_view(self, view):
|
||||
"""Default view registration stuff"""
|
||||
pass
|
||||
45
pygtktalog/dbcommon.py
Normal file
45
pygtktalog/dbcommon.py
Normal file
@@ -0,0 +1,45 @@
|
||||
"""
|
||||
Project: pyGTKtalog
|
||||
Description: Common database operations.
|
||||
Type: core
|
||||
Author: Roman 'gryf' Dobosz, gryf73@gmail.com
|
||||
Created: 2009-08-07
|
||||
"""
|
||||
from sqlalchemy import MetaData, create_engine
|
||||
from sqlalchemy.orm import sessionmaker
|
||||
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)
|
||||
Session = sessionmaker()
|
||||
|
||||
LOG = get_logger("dbcommon")
|
||||
|
||||
|
||||
def connect(filename=None):
|
||||
"""
|
||||
create engine and bind to Meta object.
|
||||
Arguments:
|
||||
@filename - string with absolute or relative path to sqlite database
|
||||
file. If None, db in-memory will be created
|
||||
"""
|
||||
|
||||
if not filename:
|
||||
filename = ':memory:'
|
||||
|
||||
LOG.info("db filename: %s" % filename)
|
||||
|
||||
connect_string = "sqlite:///%s" % filename
|
||||
engine = create_engine(connect_string)
|
||||
Meta.bind = engine
|
||||
Meta.create_all(checkfirst=True)
|
||||
|
||||
299
pygtktalog/dbobjects.py
Normal file
299
pygtktalog/dbobjects.py
Normal file
@@ -0,0 +1,299 @@
|
||||
"""
|
||||
Project: pyGTKtalog
|
||||
Description: Definition of DB objects classes. Using SQLAlchemy.
|
||||
Type: core
|
||||
Author: Roman 'gryf' Dobosz, gryf73@gmail.com
|
||||
Created: 2009-08-07
|
||||
"""
|
||||
import os
|
||||
from cStringIO import StringIO
|
||||
from hashlib import sha256
|
||||
|
||||
from sqlalchemy import Column, Table, Integer, Text, Binary, \
|
||||
DateTime, ForeignKey, Sequence
|
||||
from sqlalchemy.orm import relation, backref
|
||||
|
||||
from pygtktalog.dbcommon import Base
|
||||
from pygtktalog.thumbnail import ThumbCreator
|
||||
|
||||
|
||||
IMG_PATH = "/home/gryf/.pygtktalog/imgs/" # FIXME: should be configurable
|
||||
|
||||
tags_files = Table("tags_files", Base.metadata,
|
||||
Column("file_id", Integer, ForeignKey("files.id")),
|
||||
Column("tag_id", Integer, ForeignKey("tags.id")))
|
||||
|
||||
TYPE = {'root': 0, 'dir': 1, 'file': 2, 'link': 3}
|
||||
|
||||
class File(Base):
|
||||
__tablename__ = "files"
|
||||
id = Column(Integer, Sequence("file_id_seq"), primary_key=True)
|
||||
parent_id = Column(Integer, ForeignKey("files.id"))
|
||||
filename = Column(Text)
|
||||
filepath = Column(Text)
|
||||
date = Column(DateTime)
|
||||
size = Column(Integer)
|
||||
type = Column(Integer)
|
||||
source = Column(Integer)
|
||||
note = Column(Text)
|
||||
description = Column(Text)
|
||||
checksum = Column(Text)
|
||||
thumbnail = Column(Binary)
|
||||
|
||||
children = relation('File',
|
||||
backref=backref('parent', remote_side="File.id"),
|
||||
order_by=[type, filename])
|
||||
tags = relation("Tag", secondary=tags_files, order_by="Tag.tag")
|
||||
#thumbnail = relation("Thumbnail", backref="file")
|
||||
images = relation("Image", backref="file")
|
||||
|
||||
def __init__(self, filename=None, path=None, date=None, size=None,
|
||||
ftype=None, src=None):
|
||||
"""Create file object with empty defaults"""
|
||||
self.filename = filename
|
||||
self.filepath = path
|
||||
self.date = date
|
||||
self.size = size
|
||||
self.type = ftype
|
||||
self.source = src
|
||||
|
||||
def __repr__(self):
|
||||
return "<File('%s', %s)>" % (str(self.filename), str(self.id))
|
||||
|
||||
def generate_checksum(self):
|
||||
"""
|
||||
Generate checksum of first 10MB of the file
|
||||
"""
|
||||
if self.type != TYPE['file']:
|
||||
return
|
||||
|
||||
buf = open(os.path.join(self.filepath, self.filename)).read(10485760)
|
||||
self.checksum = sha256(buf).hexdigest()
|
||||
|
||||
def get_all_children(self):
|
||||
"""
|
||||
Return list of all node direct and indirect children
|
||||
"""
|
||||
def _recursive(node):
|
||||
children = []
|
||||
if node.children:
|
||||
for child in node.children:
|
||||
children += _recursive(child)
|
||||
if node != self:
|
||||
children.append(node)
|
||||
|
||||
return children
|
||||
|
||||
if self.children:
|
||||
return _recursive(self)
|
||||
else:
|
||||
return []
|
||||
|
||||
|
||||
class Group(Base):
|
||||
__tablename__ = "groups"
|
||||
id = Column(Integer, Sequence("group_id_seq"), primary_key=True)
|
||||
name = Column(Text)
|
||||
color = Column(Text)
|
||||
|
||||
def __init__(self, name=None, color=None):
|
||||
self.name = name
|
||||
self.color = color
|
||||
|
||||
def __repr__(self):
|
||||
return "<Group('%s', %s)>" % (str(self.name), str(self.id))
|
||||
|
||||
|
||||
class Tag(Base):
|
||||
__tablename__ = "tags"
|
||||
id = Column(Integer, Sequence("tags_id_seq"), primary_key=True)
|
||||
group_id = Column(Integer, ForeignKey("groups.id"))
|
||||
tag = Column(Text)
|
||||
group = relation('Group', backref=backref('tags', remote_side="Group.id"))
|
||||
|
||||
files = relation("File", secondary=tags_files)
|
||||
|
||||
def __init__(self, tag=None, group=None):
|
||||
self.tag = tag
|
||||
self.group = group
|
||||
|
||||
def __repr__(self):
|
||||
return "<Tag('%s', %s)>" % (str(self.tag), str(self.id))
|
||||
|
||||
|
||||
#class Thumbnail(Base):
|
||||
# __tablename__ = "thumbnails"
|
||||
# id = Column(Integer, Sequence("thumbnail_id_seq"), primary_key=True)
|
||||
# file_id = Column(Integer, ForeignKey("files.id"))
|
||||
# filename = Column(Text)
|
||||
#
|
||||
# def __init__(self, filename=None, file_obj=None):
|
||||
# self.filename = filename
|
||||
# self.file = file_obj
|
||||
# if self.filename:
|
||||
# self.save(self.filename)
|
||||
#
|
||||
# def save(self, fname):
|
||||
# """
|
||||
# Create file related thumbnail, add it to the file object.
|
||||
# """
|
||||
# new_name = sha1(str(uuid1())).hexdigest()
|
||||
# new_name = [new_name[start:start+10] for start in range(0,
|
||||
# len(new_name),
|
||||
# 10)]
|
||||
# try:
|
||||
# os.makedirs(os.path.join(IMG_PATH, *new_name[:-1]))
|
||||
# except OSError as exc:
|
||||
# if exc.errno != errno.EEXIST:
|
||||
# raise
|
||||
#
|
||||
# ext = os.path.splitext(self.filename)[1]
|
||||
# if ext:
|
||||
# new_name.append("".join([new_name.pop(), ext]))
|
||||
#
|
||||
# thumb = thumbnail.Thumbnail(self.filename)
|
||||
# thumb_tmp_name = thumb.save()
|
||||
# name, ext = os.path.splitext(new_name.pop())
|
||||
# new_name.append("".join([name, "_t", '.jpg']))
|
||||
# self.filename = os.path.sep.join(new_name)
|
||||
# shutil.move(thumb_tmp_name, os.path.join(IMG_PATH, *new_name))
|
||||
#
|
||||
# def get_copy(self):
|
||||
# """
|
||||
# Create the very same object as self with exception of id field
|
||||
# """
|
||||
# thumb = Thumbnail()
|
||||
# thumb.filename = self.filename
|
||||
# return thumb
|
||||
#
|
||||
# def __repr__(self):
|
||||
# return "<Thumbnail('%s', %s)>" % (str(self.filename), str(self.id))
|
||||
#
|
||||
|
||||
class Image(Base):
|
||||
__tablename__ = "images"
|
||||
id = Column(Integer, Sequence("images_id_seq"), primary_key=True)
|
||||
file_id = Column(Integer, ForeignKey("files.id"))
|
||||
image = Column(Binary)
|
||||
thumb = Column(Binary)
|
||||
checksum = Column(Text)
|
||||
|
||||
def __init__(self, filename=None, file_obj=None):
|
||||
self.file = file_obj
|
||||
if filename:
|
||||
self.save(filename)
|
||||
|
||||
def save(self, fname):
|
||||
"""
|
||||
Save and create coressponding thumbnail (note: it differs from file
|
||||
related thumbnail!)
|
||||
"""
|
||||
file_buffer = StringIO()
|
||||
|
||||
with open(fname) as f:
|
||||
file_buffer.write(f.read())
|
||||
|
||||
self.image = file_buffer.getvalue()
|
||||
self.checksum = sha256(file_buffer.getvalue()).hexdigest()
|
||||
|
||||
file_buffer.seek(0)
|
||||
thumb = ThumbCreator(fname).generate()
|
||||
if thumb:
|
||||
self.thumb = thumb.getvalue()
|
||||
thumb.close()
|
||||
|
||||
file_buffer.close()
|
||||
|
||||
def get_copy(self):
|
||||
"""
|
||||
Create the very same object as self with exception of id field
|
||||
"""
|
||||
img = Image()
|
||||
img.image = self.image
|
||||
img.thumb = self.thumb
|
||||
img.checksum = self.checksum
|
||||
return img
|
||||
|
||||
@property
|
||||
def fthumb(self):
|
||||
"""
|
||||
Return file-like object with thumbnail
|
||||
"""
|
||||
if self.thumb:
|
||||
buf = StringIO()
|
||||
buf.write(self.thumb)
|
||||
buf.seek(0)
|
||||
return buf
|
||||
else:
|
||||
return None
|
||||
|
||||
@property
|
||||
def fimage(self):
|
||||
"""
|
||||
Return file-like object with image
|
||||
"""
|
||||
if self.image:
|
||||
buf = StringIO()
|
||||
buf.write(self.image)
|
||||
buf.seek(0)
|
||||
return buf
|
||||
else:
|
||||
return None
|
||||
|
||||
def __repr__(self):
|
||||
return "<Image(%s)>" % str(self.id)
|
||||
|
||||
|
||||
class Exif(Base):
|
||||
__tablename__ = "exif"
|
||||
id = Column(Integer, Sequence("exif_id_seq"), primary_key=True)
|
||||
file_id = Column(Integer, ForeignKey("files.id"))
|
||||
camera = Column(Text)
|
||||
date = Column(Text)
|
||||
aperture = Column(Text)
|
||||
exposure_program = Column(Text)
|
||||
exposure_bias = Column(Text)
|
||||
iso = Column(Text)
|
||||
focal_length = Column(Text)
|
||||
subject_distance = Column(Text)
|
||||
metering_mode = Column(Text)
|
||||
flash = Column(Text)
|
||||
light_source = Column(Text)
|
||||
resolution = Column(Text)
|
||||
orientation = Column(Text)
|
||||
|
||||
def __init__(self):
|
||||
self.camera = None
|
||||
self.date = None
|
||||
self.aperture = None
|
||||
self.exposure_program = None
|
||||
self.exposure_bias = None
|
||||
self.iso = None
|
||||
self.focal_length = None
|
||||
self.subject_distance = None
|
||||
self.metering_mode = None
|
||||
self.flash = None
|
||||
self.light_source = None
|
||||
self.resolution = None
|
||||
self.orientation = None
|
||||
|
||||
def __repr__(self):
|
||||
return "<Exif('%s', %s)>" % (str(self.date), str(self.id))
|
||||
|
||||
|
||||
class Gthumb(Base):
|
||||
__tablename__ = "gthumb"
|
||||
id = Column(Integer, Sequence("gthumb_id_seq"), primary_key=True)
|
||||
file_id = Column(Integer, ForeignKey("files.id"))
|
||||
note = Column(Text)
|
||||
place = Column(Text)
|
||||
date = Column(DateTime)
|
||||
|
||||
def __init__(self, note=None, place=None, date=None):
|
||||
self.note = note
|
||||
self.place = place
|
||||
self.date = date
|
||||
|
||||
def __repr__(self):
|
||||
return "<Gthumb('%s', '%s', %s)>" % (str(self.date), str(self.place),
|
||||
str(self.id))
|
||||
231
pygtktalog/dialogs.py
Normal file
231
pygtktalog/dialogs.py
Normal file
@@ -0,0 +1,231 @@
|
||||
"""
|
||||
Project: pyGTKtalog
|
||||
Description: Simple dialogs for interact with user
|
||||
Type: helper
|
||||
Author: Roman 'gryf' Dobosz, gryf73@gmail.com
|
||||
Created: 2009-05-12
|
||||
"""
|
||||
import os
|
||||
|
||||
import gtk
|
||||
|
||||
import pygtktalog
|
||||
|
||||
|
||||
class Dialog(object):
|
||||
"""
|
||||
Show simple dialog for questions
|
||||
Returns: Bool - True, if "OK" button pressed, False otherwise.
|
||||
"""
|
||||
|
||||
def __init__(self, dialog_type, message, secondary_msg="", title=""):
|
||||
"""
|
||||
Initialize some defaults
|
||||
"""
|
||||
self.dialog = None
|
||||
self.buttons = gtk.BUTTONS_OK
|
||||
self.ok_default = False
|
||||
self.message = message
|
||||
self.secondary_msg = secondary_msg
|
||||
self.type = dialog_type
|
||||
self.title = title
|
||||
|
||||
def run(self):
|
||||
"""Show the dialog"""
|
||||
if self.dialog is None:
|
||||
self._create_dialog()
|
||||
|
||||
# Change default/focus from cancel/no to ok/yes. Suitable only for
|
||||
# Question dialog.
|
||||
# Ofcourse, if something changes in the future, this could break
|
||||
# things.
|
||||
if self.ok_default:
|
||||
# this is tricky: Ok/Yes buttons appears as first on the list, but
|
||||
# they are visibile in oposite order. This could be a bug.
|
||||
button = self.dialog.get_action_area().get_children()[0]
|
||||
button.grab_default()
|
||||
|
||||
retval = self.dialog.run()
|
||||
self.dialog.destroy()
|
||||
if retval == gtk.RESPONSE_OK or retval == gtk.RESPONSE_YES:
|
||||
return True
|
||||
return False
|
||||
|
||||
def _create_dialog(self):
|
||||
"""Create MessageDialog widgt"""
|
||||
if self.type == gtk.MESSAGE_QUESTION and \
|
||||
self.buttons not in (gtk.BUTTONS_YES_NO, gtk.BUTTONS_OK_CANCEL):
|
||||
self.buttons = gtk.BUTTONS_YES_NO
|
||||
|
||||
self.dialog = gtk.MessageDialog(buttons=self.buttons, type=self.type,
|
||||
message_format=self.message)
|
||||
self.dialog.format_secondary_text(self.secondary_msg)
|
||||
self.dialog.set_title(self.title)
|
||||
|
||||
|
||||
class ChooseFile(object):
|
||||
"""
|
||||
Common file chooser
|
||||
"""
|
||||
URI = None
|
||||
BUTTON_PAIRS = {'cancel': (gtk.STOCK_CANCEL, gtk.RESPONSE_CANCEL),
|
||||
'ok': (gtk.STOCK_OK, gtk.RESPONSE_APPLY),
|
||||
'save': (gtk.STOCK_SAVE, gtk.RESPONSE_APPLY),
|
||||
'open': (gtk.STOCK_OPEN, gtk.RESPONSE_APPLY)}
|
||||
CHOOSER_TYPES = {'open': gtk.FILE_CHOOSER_ACTION_OPEN,
|
||||
'save': gtk.FILE_CHOOSER_ACTION_SAVE}
|
||||
FILTERS = {'catalogs': {'name': "Catalog files",
|
||||
'patterns': ("*.sqlite", "*.sqlite.bz2")},
|
||||
'all': {'name': "All files", 'patterns': ("*.*",)}}
|
||||
|
||||
def __init__(self, title="", buttons=('cancel', 'ok'), path=None,
|
||||
chooser_type="open"):
|
||||
super(ChooseFile, self).__init__()
|
||||
self.path = path
|
||||
self.title = title
|
||||
self.action = self.CHOOSER_TYPES[chooser_type]
|
||||
self.buttons = []
|
||||
for button in buttons:
|
||||
self.buttons.append(self.BUTTON_PAIRS[button][0])
|
||||
self.buttons.append(self.BUTTON_PAIRS[button][1])
|
||||
self.buttons = tuple(self.buttons)
|
||||
self.confirmation = False
|
||||
self.dialog = None
|
||||
self.filters = []
|
||||
|
||||
def _mk_dialog(self):
|
||||
"""
|
||||
Create FileChooserDialog object
|
||||
"""
|
||||
self.dialog = gtk.FileChooserDialog(self.title, None, self.action,
|
||||
self.buttons)
|
||||
self.dialog.set_action(gtk.FILE_CHOOSER_ACTION_SAVE)
|
||||
self.dialog.set_default_response(gtk.RESPONSE_OK)
|
||||
self.dialog.set_do_overwrite_confirmation(self.confirmation)
|
||||
self.dialog.set_title(self.title)
|
||||
|
||||
if self.URI:
|
||||
self.dialog.set_current_folder_uri(self.URI)
|
||||
elif self.path and os.path.exists(self.path):
|
||||
self.path = "file://" + os.path.abspath(self.path)
|
||||
self.dialog.set_current_folder_uri(self.path)
|
||||
|
||||
for filtr in self._get_filters():
|
||||
self.dialog.add_filter(filtr)
|
||||
|
||||
def _get_filters(self):
|
||||
"""
|
||||
"""
|
||||
filters = []
|
||||
for filter_def in self.filters:
|
||||
filtr = gtk.FileFilter()
|
||||
filtr.set_name(self.FILTERS[filter_def]['name'])
|
||||
for pat in self.FILTERS[filter_def]['patterns']:
|
||||
filtr.add_pattern(pat)
|
||||
filters.append(filtr)
|
||||
return filters
|
||||
|
||||
def run(self):
|
||||
"""
|
||||
Show dialog, get response.
|
||||
Returns:
|
||||
|
||||
Returns: String - with filename, None otherwise.
|
||||
"""
|
||||
|
||||
if self.dialog is None:
|
||||
self._mk_dialog()
|
||||
|
||||
response = self.dialog.run()
|
||||
filename = None
|
||||
|
||||
if response == gtk.RESPONSE_APPLY:
|
||||
filename = self.dialog.get_filename()
|
||||
self.__class__.URI = self.dialog.get_current_folder_uri()
|
||||
|
||||
self.dialog.destroy()
|
||||
return filename
|
||||
|
||||
|
||||
def yesno(message, secondarymsg="", title="", default=False):
|
||||
"""Question with yes-no buttons. Returns False on 'no', True on 'yes'"""
|
||||
dialog = Dialog(gtk.MESSAGE_QUESTION, message, secondarymsg, title)
|
||||
dialog.buttons = gtk.BUTTONS_YES_NO
|
||||
dialog.ok_default = default
|
||||
return dialog.run()
|
||||
|
||||
|
||||
def okcancel(message, secondarymsg="", title="", default=False):
|
||||
"""Question with ok-cancel buttons. Returns False on 'cancel', True on
|
||||
'ok'"""
|
||||
dialog = Dialog(gtk.MESSAGE_QUESTION, message, secondarymsg, title)
|
||||
dialog.buttons = gtk.BUTTONS_OK_CANCEL
|
||||
dialog.ok_default = default
|
||||
return dialog.run()
|
||||
|
||||
|
||||
def info(message, secondarymsg="", title="", button=gtk.BUTTONS_OK):
|
||||
"""Info dialog. Button defaults to gtk.BUTTONS_OK, but can be changed with
|
||||
gtk.BUTTONS_CANCEL, gtk.BUTTONS_CLOSE or gtk.BUTTONS_NONE.
|
||||
Always returns True."""
|
||||
dialog = Dialog(gtk.MESSAGE_INFO, message, secondarymsg, title)
|
||||
dialog.buttons = button
|
||||
dialog.run()
|
||||
return True
|
||||
|
||||
|
||||
def warn(message, secondarymsg="", title="", button=gtk.BUTTONS_OK):
|
||||
"""Warning dialog. Button defaults to gtk.BUTTONS_OK, but can be changed
|
||||
with gtk.BUTTONS_CANCEL, gtk.BUTTONS_CLOSE or gtk.BUTTONS_NONE.
|
||||
Always returns True."""
|
||||
dialog = Dialog(gtk.MESSAGE_WARNING, message, secondarymsg, title)
|
||||
dialog.buttons = button
|
||||
dialog.run()
|
||||
return True
|
||||
|
||||
|
||||
def error(message, secondarymsg="", title="", button=gtk.BUTTONS_OK):
|
||||
"""Error dialog. Button defaults to gtk.BUTTONS_OK, but can be changed with
|
||||
gtk.BUTTONS_CANCEL, gtk.BUTTONS_CLOSE or gtk.BUTTONS_NONE.
|
||||
Always returns True."""
|
||||
dialog = Dialog(gtk.MESSAGE_ERROR, message, secondarymsg, title)
|
||||
dialog.buttons = button
|
||||
dialog.run()
|
||||
return True
|
||||
|
||||
|
||||
def open_catalog(title=_("Open catalog"), path=None):
|
||||
"""
|
||||
Request filename from user to open.
|
||||
Returns: string - full path and filename or None
|
||||
"""
|
||||
requester = ChooseFile(title)
|
||||
requester.filters = ['catalogs', 'all']
|
||||
return requester.run()
|
||||
|
||||
|
||||
def save_catalog(title=_("Open catalog"), path=None):
|
||||
"""
|
||||
Request filename from user for save.
|
||||
Returns: string - full path and filename or None
|
||||
"""
|
||||
requester = ChooseFile(title, chooser_type="save")
|
||||
requester.filters = ['catalogs', 'all']
|
||||
requester.confirmation = True
|
||||
return requester.run()
|
||||
|
||||
|
||||
def about():
|
||||
"""
|
||||
Show About dialog
|
||||
"""
|
||||
dialog = gtk.AboutDialog()
|
||||
dialog.set_version(pygtktalog.__version__)
|
||||
dialog.set_program_name(pygtktalog.__appname__)
|
||||
dialog.set_copyright(pygtktalog.__copyright__)
|
||||
dialog.set_comments(pygtktalog.__summary__)
|
||||
dialog.set_website(pygtktalog.__web__)
|
||||
dialog.set_logo(gtk.gdk.pixbuf_new_from_file(\
|
||||
os.path.join(os.path.dirname(__file__), pygtktalog.__logo_img__)))
|
||||
dialog.run()
|
||||
dialog.destroy()
|
||||
81
pygtktalog/logger.py
Normal file
81
pygtktalog/logger.py
Normal file
@@ -0,0 +1,81 @@
|
||||
"""
|
||||
Project: pyGTKtalog
|
||||
Description: Logging functionality
|
||||
Type: core
|
||||
Author: Roman 'gryf' Dobosz, gryf73@gmail.com
|
||||
Created: 2009-09-02
|
||||
"""
|
||||
import os
|
||||
import sys
|
||||
import logging
|
||||
|
||||
LEVEL = {'DEBUG': logging.DEBUG,
|
||||
'INFO': logging.INFO,
|
||||
'WARN': logging.WARN,
|
||||
'ERROR': logging.ERROR,
|
||||
'CRITICAL': logging.CRITICAL}
|
||||
|
||||
BLACK, RED, GREEN, YELLOW, BLUE, MAGENTA, CYAN, WHITE = range(8)
|
||||
|
||||
RESET_SEQ = "\033[0m"
|
||||
COLOR_SEQ = "\033[1;%dm"
|
||||
BOLD_SEQ = "\033[1m"
|
||||
|
||||
COLORS = {'WARNING': YELLOW,
|
||||
'INFO': GREEN,
|
||||
'DEBUG': BLUE,
|
||||
'CRITICAL': WHITE,
|
||||
'ERROR': RED}
|
||||
|
||||
|
||||
class ColoredFormatter(logging.Formatter):
|
||||
def __init__(self, msg, use_color=True):
|
||||
logging.Formatter.__init__(self, msg)
|
||||
self.use_color = use_color
|
||||
|
||||
def format(self, record):
|
||||
levelname = record.levelname
|
||||
if self.use_color and levelname in COLORS:
|
||||
levelname_color = COLOR_SEQ % (30 + COLORS[levelname]) \
|
||||
+ levelname + RESET_SEQ
|
||||
record.levelname = levelname_color
|
||||
return logging.Formatter.format(self, record)
|
||||
|
||||
|
||||
#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='DEBUG', to_file=False):
|
||||
"""
|
||||
Prepare and return log object. Standard formatting is used for all logs.
|
||||
Arguments:
|
||||
@module_name - String name for Logger object.
|
||||
@level - Log level (as string), one of DEBUG, INFO, WARN, ERROR and
|
||||
CRITICAL.
|
||||
@to_file - If True, additionally stores full log in file inside
|
||||
.pygtktalog config directory and to stderr, otherwise log
|
||||
is only redirected to stderr.
|
||||
Returns: object of logging.Logger class
|
||||
"""
|
||||
|
||||
path = os.path.join(os.path.expanduser("~"), ".pygtktalog", "app.log")
|
||||
#path = "/dev/null"
|
||||
log = logging.getLogger(module_name)
|
||||
log.setLevel(LEVEL[level])
|
||||
|
||||
console_handler = logging.StreamHandler(sys.stderr)
|
||||
console_formatter = ColoredFormatter("%(filename)s:%(lineno)s - "
|
||||
"%(levelname)s - %(message)s")
|
||||
console_handler.setFormatter(console_formatter)
|
||||
|
||||
log.addHandler(console_handler)
|
||||
|
||||
if to_file:
|
||||
file_handler = logging.FileHandler(path)
|
||||
file_formatter = logging.Formatter("%(asctime)s %(levelname)6s "
|
||||
"%(filename)s: %(lineno)s - "
|
||||
"%(message)s")
|
||||
file_handler.setFormatter(file_formatter)
|
||||
file_handler.setLevel(LEVEL[level])
|
||||
log.addHandler(file_handler)
|
||||
|
||||
return log
|
||||
22
pygtktalog/misc.py
Normal file
22
pygtktalog/misc.py
Normal file
@@ -0,0 +1,22 @@
|
||||
"""
|
||||
Project: pyGTKtalog
|
||||
Description: Misc functions used more than once in src
|
||||
Type: lib
|
||||
Author: Roman 'gryf' Dobosz, gryf73@gmail.com
|
||||
Created: 2009-04-05
|
||||
"""
|
||||
|
||||
def float_to_string(float_length):
|
||||
"""
|
||||
Parse float digit into time string
|
||||
Arguments:
|
||||
@number - digit to be converted into time.
|
||||
Returns HH:MM:SS formatted string
|
||||
"""
|
||||
hour = int(float_length / 3600)
|
||||
float_length -= hour*3600
|
||||
minutes = int(float_length / 60)
|
||||
float_length -= minutes * 60
|
||||
sec = int(float_length)
|
||||
return "%02d:%02d:%02d" % (hour, minutes, sec)
|
||||
|
||||
0
pygtktalog/models/__init__.py
Normal file
0
pygtktalog/models/__init__.py
Normal file
31
pygtktalog/models/details.py
Normal file
31
pygtktalog/models/details.py
Normal file
@@ -0,0 +1,31 @@
|
||||
"""
|
||||
Project: pyGTKtalog
|
||||
Description: Model(s) for details part of the application
|
||||
Type: core
|
||||
Author: Roman 'gryf' Dobosz, gryf73@gmail.com
|
||||
Created: 2010-11-09
|
||||
"""
|
||||
import gtk
|
||||
import gobject
|
||||
|
||||
from gtkmvc import Model
|
||||
|
||||
|
||||
class DetailsModel(Model):
|
||||
"""
|
||||
Main model for application.
|
||||
It is responsible for communicate with database objects and I/O
|
||||
operations.
|
||||
"""
|
||||
|
||||
exif = gtk.ListStore(gobject.TYPE_PYOBJECT,
|
||||
gobject.TYPE_STRING,
|
||||
gobject.TYPE_STRING,
|
||||
gobject.TYPE_STRING,
|
||||
gobject.TYPE_UINT64,
|
||||
gobject.TYPE_STRING,
|
||||
gobject.TYPE_INT,
|
||||
str)
|
||||
|
||||
__observables__ = ['exif']
|
||||
|
||||
107
pygtktalog/models/discs.py
Normal file
107
pygtktalog/models/discs.py
Normal file
@@ -0,0 +1,107 @@
|
||||
"""
|
||||
Project: pyGTKtalog
|
||||
Description: Model for discs representation
|
||||
Type: core
|
||||
Author: Roman 'gryf' Dobosz, gryf73@gmail.com
|
||||
Created: 2009-05-02
|
||||
"""
|
||||
import gtk
|
||||
import gobject
|
||||
|
||||
from gtkmvc import Model
|
||||
|
||||
from pygtktalog.dbobjects import File
|
||||
from pygtktalog.dbcommon import Session
|
||||
from pygtktalog.logger import get_logger
|
||||
|
||||
LOG = get_logger("discs model")
|
||||
|
||||
|
||||
class DiscsModel(Model):
|
||||
"""
|
||||
Model for discs representation.
|
||||
"""
|
||||
|
||||
currentdir = None
|
||||
|
||||
__observables__ = ("currentdir",)
|
||||
|
||||
def __init__(self):
|
||||
"""
|
||||
Initialization. Make some nice defaults.
|
||||
"""
|
||||
Model.__init__(self)
|
||||
self.discs = gtk.TreeStore(gobject.TYPE_PYOBJECT,
|
||||
gobject.TYPE_STRING,
|
||||
str)
|
||||
self.files_model = None
|
||||
|
||||
def clear(self):
|
||||
"""
|
||||
Make TreeStore empty
|
||||
"""
|
||||
self.discs.clear()
|
||||
|
||||
def refresh(self, session=Session()):
|
||||
"""
|
||||
Read objects from database, fill TreeStore model with discs
|
||||
information
|
||||
Arguments:
|
||||
@session current sqlalchemy.orm.session.Session object
|
||||
"""
|
||||
LOG.debug("session obj: %s" % str(session))
|
||||
dirs = session.query(File).filter(File.type == 1)
|
||||
dirs = dirs.order_by(File.filename).all()
|
||||
|
||||
def get_children(parent_id=1, iterator=None):
|
||||
"""
|
||||
Get all children of the selected parent.
|
||||
Arguments:
|
||||
@parent_id - integer with id of the parent (from db)
|
||||
@iterator - gtk.TreeIter, which points to a path inside model
|
||||
"""
|
||||
for fileob in dirs:
|
||||
if fileob.parent_id == parent_id:
|
||||
myiter = self.discs.insert_before(iterator, None)
|
||||
self.discs.set_value(myiter, 0, fileob)
|
||||
self.discs.set_value(myiter, 1, fileob.filename)
|
||||
if iterator is None:
|
||||
self.discs.set_value(myiter, 2, gtk.STOCK_CDROM)
|
||||
else:
|
||||
self.discs.set_value(myiter, 2, gtk.STOCK_DIRECTORY)
|
||||
get_children(fileob.id, myiter)
|
||||
return
|
||||
get_children()
|
||||
return True
|
||||
|
||||
def find_path(self, obj):
|
||||
"""
|
||||
Return path of specified File object (which should be the first one)
|
||||
"""
|
||||
path = None
|
||||
gtkiter = self.discs.get_iter_first()
|
||||
|
||||
def get_children(iterator):
|
||||
"""
|
||||
Iterate through entire TreeModel, and return path for specified in
|
||||
outter scope File object
|
||||
"""
|
||||
if self.discs.get_value(iterator, 0) == obj:
|
||||
return self.discs.get_path(iterator)
|
||||
|
||||
if self.discs.iter_has_child(iterator):
|
||||
path = get_children(self.discs.iter_children(iterator))
|
||||
if path:
|
||||
return path
|
||||
|
||||
iterator = self.discs.iter_next(iterator)
|
||||
if iterator is None:
|
||||
return None
|
||||
|
||||
return get_children(iterator)
|
||||
|
||||
path = get_children(gtkiter)
|
||||
LOG.debug("found path for object '%s': %s" % (str(obj), str(path)))
|
||||
return path
|
||||
|
||||
|
||||
79
pygtktalog/models/files.py
Normal file
79
pygtktalog/models/files.py
Normal file
@@ -0,0 +1,79 @@
|
||||
"""
|
||||
Project: pyGTKtalog
|
||||
Description: Model for files representation
|
||||
Type: core
|
||||
Author: Roman 'gryf' Dobosz, gryf73@gmail.com
|
||||
Created: 2010-11-12
|
||||
"""
|
||||
import gtk
|
||||
import gobject
|
||||
|
||||
from gtkmvc import Model
|
||||
|
||||
from pygtktalog.dbcommon import Session
|
||||
from pygtktalog.logger import get_logger
|
||||
|
||||
LOG = get_logger("files model")
|
||||
|
||||
|
||||
class FilesModel(Model):
|
||||
"""
|
||||
Model for files representation
|
||||
"""
|
||||
|
||||
def __init__(self):
|
||||
"""
|
||||
Initialization. Make some nice defaults.
|
||||
"""
|
||||
Model.__init__(self)
|
||||
self.files = gtk.ListStore(gobject.TYPE_PYOBJECT,
|
||||
gobject.TYPE_STRING,
|
||||
gobject.TYPE_STRING,
|
||||
gobject.TYPE_STRING,
|
||||
gobject.TYPE_UINT64,
|
||||
gobject.TYPE_STRING,
|
||||
gobject.TYPE_INT,
|
||||
str)
|
||||
self.discs_model = None
|
||||
|
||||
def clear(self):
|
||||
"""
|
||||
Cleanup ListStore model
|
||||
"""
|
||||
self.files.clear()
|
||||
|
||||
def refresh(self, fileob):
|
||||
"""
|
||||
Update files ListStore
|
||||
Arguments:
|
||||
fileob - File object
|
||||
"""
|
||||
LOG.info("found %d files for File object: %s" % (len(fileob.children),
|
||||
str(fileob)))
|
||||
self.files.clear()
|
||||
|
||||
for child in fileob.children:
|
||||
myiter = self.files.insert_before(None, None)
|
||||
self.files.set_value(myiter, 0, child)
|
||||
self.files.set_value(myiter, 1, child.parent_id \
|
||||
if child.parent_id != 1 else None)
|
||||
self.files.set_value(myiter, 2, child.filename)
|
||||
self.files.set_value(myiter, 3, child.filepath)
|
||||
self.files.set_value(myiter, 4, child.size)
|
||||
self.files.set_value(myiter, 5, child.date)
|
||||
self.files.set_value(myiter, 6, 1)
|
||||
self.files.set_value(myiter, 7, gtk.STOCK_DIRECTORY \
|
||||
if child.type == 1 else gtk.STOCK_FILE)
|
||||
|
||||
def get_value(self, row=None, fiter=None, column=0):
|
||||
"""
|
||||
TODO:
|
||||
"""
|
||||
if row:
|
||||
fiter = self.files.get_iter(row)
|
||||
if not fiter:
|
||||
LOG.error("ERROR: there is no way to determine gtk_iter object!"
|
||||
" Please specify valid row or gtk_iter!")
|
||||
return None
|
||||
|
||||
return self.files.get_value(fiter, column)
|
||||
289
pygtktalog/models/main.py
Normal file
289
pygtktalog/models/main.py
Normal file
@@ -0,0 +1,289 @@
|
||||
"""
|
||||
Project: pyGTKtalog
|
||||
Description: Model for main application
|
||||
Type: core
|
||||
Author: Roman 'gryf' Dobosz, gryf73@gmail.com
|
||||
Created: 2009-05-02
|
||||
"""
|
||||
import os
|
||||
import bz2
|
||||
from string import printable
|
||||
from tempfile import mkstemp
|
||||
|
||||
import gtk
|
||||
import gobject
|
||||
from gtkmvc import ModelMT
|
||||
from sqlalchemy import create_engine
|
||||
|
||||
from pygtktalog.dbobjects import File, Exif, Group, Gthumb
|
||||
from pygtktalog.dbobjects import Image, Tag, Thumbnail
|
||||
from pygtktalog.dbcommon import connect, Meta, Session
|
||||
from pygtktalog.logger import get_logger
|
||||
from pygtktalog.models.details import DetailsModel
|
||||
from pygtktalog.models.discs import DiscsModel
|
||||
from pygtktalog.models.files import FilesModel
|
||||
|
||||
LOG = get_logger("main model")
|
||||
|
||||
|
||||
class MainModel(ModelMT):
|
||||
"""
|
||||
Main model for application.
|
||||
It is responsible for communicate with database objects and I/O
|
||||
operations.
|
||||
"""
|
||||
status_bar_message = _("Idle")
|
||||
current_disc = None
|
||||
|
||||
__observables__ = ("status_bar_message", "current_disc")
|
||||
|
||||
def __init__(self, filename=None):
|
||||
"""
|
||||
Initialization. Make some nice defaults.
|
||||
Arguments:
|
||||
filename - String that indicates optionally compressed with bzip2
|
||||
file containing sqlite3 database.
|
||||
"""
|
||||
ModelMT.__init__(self)
|
||||
# Opened/saved database location in filesystem. Could be compressed.
|
||||
self.cat_fname = filename
|
||||
# Temporary (usually in /tmp) working database.
|
||||
self.tmp_filename = None
|
||||
self.config = {}
|
||||
# SQLAlchemy session object for internal use
|
||||
self._session = None
|
||||
# Flag indicates, that db was compressed
|
||||
# TODO: make it depend on configuration
|
||||
self.compressed = False
|
||||
|
||||
self.db_unsaved = None
|
||||
|
||||
self.discs = DiscsModel()
|
||||
self.files = FilesModel()
|
||||
|
||||
if self.cat_fname:
|
||||
self.open(self.cat_fname)
|
||||
|
||||
def open(self, filename):
|
||||
"""
|
||||
Open catalog file and read db
|
||||
Arguments:
|
||||
@filename - see MainModel __init__ docstring.
|
||||
Returns: Bool - true for success, false otherwise.
|
||||
"""
|
||||
LOG.debug("filename: '%s'", filename)
|
||||
self.unsaved_project = False
|
||||
if not os.path.exists(filename):
|
||||
LOG.warn("db file '%s' doesn't exist.", filename)
|
||||
return False
|
||||
|
||||
if not os.path.isfile(filename):
|
||||
LOG.warn("db file '%s' is not a regular file.", filename)
|
||||
return False
|
||||
|
||||
self.cat_fname = filename
|
||||
|
||||
if self._open_or_decompress():
|
||||
return self.discs.refresh(self._session)
|
||||
else:
|
||||
return False
|
||||
|
||||
def save(self, filename=None):
|
||||
"""
|
||||
Save tared directory at given catalog fielname
|
||||
Arguments:
|
||||
@filename - see MainModel __init__ docstring.
|
||||
Returns: tuple:
|
||||
Bool - true for success, false otherwise.
|
||||
String or None - error message
|
||||
"""
|
||||
|
||||
if not filename and not self.cat_fname:
|
||||
LOG.debug("no filename detected!")
|
||||
return False, None
|
||||
|
||||
if filename:
|
||||
if not '.sqlite' in filename:
|
||||
filename += '.sqlite'
|
||||
else:
|
||||
filename = filename[:filename.rindex('.sqlite')] + '.sqlite'
|
||||
|
||||
if 'compress' in self.config and self.config['compress']:
|
||||
filename += '.bz2'
|
||||
|
||||
self.cat_fname = filename
|
||||
val, err = self._compress_and_save()
|
||||
if not val:
|
||||
self.cat_fname = None
|
||||
return val, err
|
||||
|
||||
def new(self):
|
||||
"""
|
||||
Create new catalog
|
||||
"""
|
||||
self.cleanup()
|
||||
self._create_temp_db_file()
|
||||
self._create_schema()
|
||||
self.discs.clear()
|
||||
self.files.clear()
|
||||
self.db_unsaved = False
|
||||
|
||||
def cleanup(self):
|
||||
"""
|
||||
Remove temporary directory tree from filesystem
|
||||
"""
|
||||
|
||||
if self._session:
|
||||
self._session.close()
|
||||
self._session = None
|
||||
|
||||
if self.tmp_filename is None:
|
||||
return
|
||||
|
||||
try:
|
||||
os.unlink(self.tmp_filename)
|
||||
except OSError:
|
||||
LOG.error("temporary db file doesn't exists!")
|
||||
except TypeError:
|
||||
# TODO: file does not exist - create? print error message?
|
||||
LOG.error("temporary db file doesn't exists!")
|
||||
else:
|
||||
LOG.debug("file %s succesfully deleted", self.tmp_filename)
|
||||
|
||||
def _examine_file(self, filename):
|
||||
"""
|
||||
Try to recognize file.
|
||||
Arguments:
|
||||
@filename - String with full path to file to examine
|
||||
Returns: 'sql', 'bz2' or None for file sqlite, compressed with bzip2
|
||||
or other respectively
|
||||
"""
|
||||
try:
|
||||
head_of_file = open(filename).read(15)
|
||||
LOG.debug("head of file: %s", filter(lambda x: x in printable,
|
||||
head_of_file))
|
||||
except IOError:
|
||||
LOG.exception("Error opening file '%s'!", filename)
|
||||
self.cleanup()
|
||||
self.cat_fname = None
|
||||
self.tmp_filename = None
|
||||
return None
|
||||
|
||||
if head_of_file == "SQLite format 3":
|
||||
LOG.debug("File format: uncompressed sqlite")
|
||||
return 'sql'
|
||||
elif head_of_file[0:10] == "BZh91AY&SY":
|
||||
LOG.debug("File format: bzip2")
|
||||
return 'bz2'
|
||||
else:
|
||||
return None
|
||||
|
||||
def _open_or_decompress(self):
|
||||
"""
|
||||
Try to open file, which user thinks is our catalog file.
|
||||
Returns: Bool - true for success, false otherwise
|
||||
"""
|
||||
filename = os.path.abspath(self.cat_fname)
|
||||
LOG.info("catalog file: %s", filename)
|
||||
|
||||
if self._session:
|
||||
self.cleanup()
|
||||
|
||||
self._create_temp_db_file()
|
||||
LOG.debug("tmp database file: %s", str(self.tmp_filename))
|
||||
|
||||
examine = self._examine_file(filename)
|
||||
if examine == "sql":
|
||||
db_tmp = open(self.tmp_filename, "wb")
|
||||
db_tmp.write(open(filename).read())
|
||||
db_tmp.close()
|
||||
elif examine == "bz2":
|
||||
open_file = bz2.BZ2File(filename)
|
||||
try:
|
||||
db_tmp = open(self.tmp_filename, "w")
|
||||
db_tmp.write(open_file.read())
|
||||
db_tmp.close()
|
||||
open_file.close()
|
||||
except IOError:
|
||||
self.cleanup()
|
||||
self.cat_fname = None
|
||||
self.internal_dirname = None
|
||||
LOG.exception("File is probably not a bz2!")
|
||||
return False
|
||||
if self._examine_file(self.tmp_filename) is not 'sql':
|
||||
LOG.error("Error opening file '%s' - not a catalog file!",
|
||||
self.tmp_filename)
|
||||
self.cleanup()
|
||||
self.cat_fname = None
|
||||
self.tmp_filename = None
|
||||
return False
|
||||
|
||||
else:
|
||||
LOG.error("Error opening file '%s' - not a catalog file!",
|
||||
self.tmp_filename)
|
||||
self.cleanup()
|
||||
self.cat_fname = None
|
||||
self.internal_dirname = None
|
||||
return False
|
||||
|
||||
connect(os.path.abspath(self.tmp_filename))
|
||||
self._session = Session()
|
||||
LOG.debug("session obj: %s" % str(self._session))
|
||||
return True
|
||||
|
||||
def _create_temp_db_file(self):
|
||||
"""
|
||||
Create new DB file, populate schema.
|
||||
"""
|
||||
fd, self.tmp_filename = mkstemp()
|
||||
LOG.debug("new db filename: %s" % self.tmp_filename)
|
||||
# close file descriptor, otherwise it can be source of app crash!
|
||||
# http://www.logilab.org/blogentry/17873
|
||||
os.close(fd)
|
||||
|
||||
def _create_schema(self):
|
||||
"""
|
||||
"""
|
||||
self._session = Session()
|
||||
LOG.debug("session obj: %s" % str(self._session))
|
||||
|
||||
connect(os.path.abspath(self.tmp_filename))
|
||||
|
||||
root = File()
|
||||
root.id = 1
|
||||
root.filename = 'root'
|
||||
root.size = 0
|
||||
root.source = 0
|
||||
root.type = 0
|
||||
root.parent_id = 1
|
||||
|
||||
self._session.add(root)
|
||||
self._session.commit()
|
||||
|
||||
def _compress_and_save(self):
|
||||
"""
|
||||
Create (and optionaly compress) tar archive from working directory and
|
||||
write it to specified file.
|
||||
"""
|
||||
|
||||
# flush all changes
|
||||
self._session.commit()
|
||||
|
||||
try:
|
||||
if 'compress' in self.config and self.config['compress']:
|
||||
output_file = bz2.BZ2File(self.cat_fname, "w")
|
||||
else:
|
||||
output_file = open(self.cat_fname, "w")
|
||||
LOG.debug("save (and optionally compress) successed")
|
||||
|
||||
except IOError, (errno, strerror):
|
||||
LOG.error("error saving or compressing file", errno, strerror)
|
||||
return False, strerror
|
||||
|
||||
dbpath = open(self.tmp_filename)
|
||||
output_file.write(dbpath.read())
|
||||
dbpath.close()
|
||||
output_file.close()
|
||||
|
||||
self.db_unsaved = False
|
||||
return True, None
|
||||
25
pygtktalog/pygtkutils.py
Normal file
25
pygtktalog/pygtkutils.py
Normal file
@@ -0,0 +1,25 @@
|
||||
"""
|
||||
Project: pyGTKtalog
|
||||
Description: pyGTK common utility functions
|
||||
Type: tility
|
||||
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.
|
||||
Arguments:
|
||||
@treeview - gtk.TreeView
|
||||
Returns:
|
||||
Item in first column of TreeModel, which TreeView is connected with,
|
||||
None in other cases
|
||||
"""
|
||||
path, column = treeview.get_cursor()
|
||||
if path and column:
|
||||
model = treeview.get_model()
|
||||
tm_iter = model.get_iter(path)
|
||||
item_id = model.get_value(tm_iter, 0)
|
||||
return item_id
|
||||
return None
|
||||
|
||||
665
pygtktalog/scan.py
Normal file
665
pygtktalog/scan.py
Normal file
@@ -0,0 +1,665 @@
|
||||
"""
|
||||
Project: pyGTKtalog
|
||||
Description: Filesystem scan and file automation layer
|
||||
Type: core
|
||||
Author: Roman 'gryf' Dobosz, gryf73@gmail.com
|
||||
Created: 2011-03-27
|
||||
"""
|
||||
import os
|
||||
import sys
|
||||
import re
|
||||
from datetime import datetime
|
||||
import mimetypes
|
||||
|
||||
from pygtktalog.dbobjects import File, Image, TYPE
|
||||
from pygtktalog.thumbnail import ThumbCreator
|
||||
from pygtktalog.dbcommon import Session
|
||||
from pygtktalog.logger import get_logger
|
||||
from pygtktalog.video import Video
|
||||
|
||||
|
||||
LOG = get_logger(__name__)
|
||||
PAT = re.compile("(\[[^\]]*\]"
|
||||
".*\(\d\d\d\d\))"
|
||||
"\s[^\[]*\[.{8}\]"
|
||||
".[a-zA-Z0-9]*$")
|
||||
|
||||
#PAT = re.compile(r'(?P<group>\[[^\]]*\]\s)?'
|
||||
# r'(?P<title>.*)\s'
|
||||
# r'(?P<year>\(\d{4}\))\s'
|
||||
# r'(?P<kind>.*)'
|
||||
# r'(?P<checksum>\[[A-Z0-9]{8}\])'
|
||||
# r'\.(?P<extension>(avi|asf|mpeg|mpg|mp4|ogm|ogv|mkv|mov|wmv'
|
||||
# r'|rm|rmvb|flv|jpg|png|gif|nfo))\.?(conf)?$')
|
||||
|
||||
|
||||
class NoAccessError(Exception):
|
||||
pass
|
||||
|
||||
|
||||
class Scan(object):
|
||||
"""
|
||||
Retrieve and identify all files recursively on given path
|
||||
"""
|
||||
def __init__(self, path):
|
||||
"""
|
||||
Initialize
|
||||
@Arguments:
|
||||
@path - string with initial root directory to scan
|
||||
"""
|
||||
self.abort = False
|
||||
self.path = path.rstrip(os.path.sep)
|
||||
self._files = []
|
||||
self._existing_files = [] # for re-use purpose in adding
|
||||
self._existing_branch = [] # for branch storage, mainly for updating
|
||||
self._session = Session()
|
||||
self.files_count = self._get_files_count()
|
||||
self.current_count = 0
|
||||
|
||||
def add_files(self):
|
||||
"""
|
||||
Returns list, which contain object, modification date and file
|
||||
size.
|
||||
"""
|
||||
self._files = []
|
||||
self._existing_branch = []
|
||||
LOG.debug("given path: %s" % self.path)
|
||||
|
||||
# See, if file exists. If not it would raise OSError exception
|
||||
os.stat(self.path)
|
||||
|
||||
if not os.access(self.path, os.R_OK | os.X_OK) \
|
||||
or not os.path.isdir(self.path):
|
||||
raise NoAccessError("Access to %s is forbidden" % self.path)
|
||||
|
||||
directory = os.path.basename(self.path)
|
||||
path = os.path.dirname(self.path)
|
||||
|
||||
if not self._recursive(None, directory, path, 0):
|
||||
return None
|
||||
|
||||
# add only first item from _files, because it is a root of the other,
|
||||
# so other will be automatically added aswell.
|
||||
self._session.add(self._files[0])
|
||||
self._session.commit()
|
||||
return self._files
|
||||
|
||||
def update_files(self, node_id):
|
||||
"""
|
||||
Updtate DB contents of provided node.
|
||||
"""
|
||||
self.current_count = 0
|
||||
old_node = self._session.query(File).get(node_id)
|
||||
if old_node is None:
|
||||
LOG.warning("No such object in db: %s", node_id)
|
||||
return
|
||||
parent = old_node.parent
|
||||
|
||||
self._files = []
|
||||
self._existing_branch = old_node.get_all_children()
|
||||
self._existing_branch.insert(0, old_node)
|
||||
|
||||
# Break the chain of parent-children relations
|
||||
for fobj in self._existing_branch:
|
||||
fobj.parent = None
|
||||
|
||||
update_path = os.path.join(old_node.filepath, old_node.filename)
|
||||
|
||||
# refresh objects
|
||||
self._get_all_files()
|
||||
|
||||
LOG.debug("path for update: %s" % update_path)
|
||||
|
||||
# See, if file exists. If not it would raise OSError exception
|
||||
os.stat(update_path)
|
||||
|
||||
if not os.access(update_path, os.R_OK | os.X_OK) \
|
||||
or not os.path.isdir(update_path):
|
||||
raise NoAccessError("Access to %s is forbidden" % update_path)
|
||||
|
||||
directory = os.path.basename(update_path)
|
||||
path = os.path.dirname(update_path)
|
||||
|
||||
if not self._recursive(parent, directory, path, 0):
|
||||
return None
|
||||
|
||||
# update branch
|
||||
#self._session.merge(self._files[0])
|
||||
self._session.query(File).filter(File.parent==None).delete()
|
||||
|
||||
self._session.commit()
|
||||
return self._files
|
||||
|
||||
def _get_dirsize(self, path):
|
||||
"""
|
||||
Returns sum of all files under specified path (also in subdirs)
|
||||
"""
|
||||
|
||||
size = 0
|
||||
|
||||
for root, dirs, files in os.walk(path):
|
||||
for fname in files:
|
||||
try:
|
||||
size += os.stat(os.path.join(root, fname)).st_size
|
||||
except OSError:
|
||||
LOG.warning("Cannot access file "
|
||||
"%s" % os.path.join(root, fname))
|
||||
|
||||
return size
|
||||
|
||||
def _gather_information(self, fobj):
|
||||
"""
|
||||
Try to guess type and gather information about File object if possible
|
||||
"""
|
||||
mimedict = {'audio': self._audio,
|
||||
'video': self._video,
|
||||
'image': self._image}
|
||||
fp = os.path.join(fobj.filepath.encode(sys.getfilesystemencoding()),
|
||||
fobj.filename.encode(sys.getfilesystemencoding()))
|
||||
|
||||
mimeinfo = mimetypes.guess_type(fp)
|
||||
if mimeinfo[0] and mimeinfo[0].split("/")[0] in mimedict.keys():
|
||||
mimedict[mimeinfo[0].split("/")[0]](fobj, fp)
|
||||
else:
|
||||
LOG.debug("Filetype not supported " + str(mimeinfo) + " " + fp)
|
||||
pass
|
||||
|
||||
def _audio(self, fobj, filepath):
|
||||
return
|
||||
|
||||
def _image(self, fobj, filepath):
|
||||
#Thumbnail(filepath, fobj)
|
||||
return
|
||||
|
||||
def _video(self, fobj, filepath):
|
||||
"""
|
||||
Make captures for a movie. Save it under uniq name.
|
||||
"""
|
||||
result = PAT.search(fobj.filename)
|
||||
if result:
|
||||
self._check_related(fobj, result.groups()[0])
|
||||
|
||||
vid = Video(filepath)
|
||||
|
||||
fobj.description = vid.get_formatted_tags()
|
||||
|
||||
preview_fn = vid.capture()
|
||||
if preview_fn:
|
||||
Image(preview_fn, fobj)
|
||||
|
||||
def _check_related(self, fobj, pattern):
|
||||
"""
|
||||
Try to search for related files which belongs to specified File
|
||||
object and pattern. If found, additional objects are created.
|
||||
"""
|
||||
for filen in os.listdir(fobj.filepath):
|
||||
if pattern in filen and \
|
||||
os.path.splitext(filen)[1] in (".jpg", ".png", ".gif"):
|
||||
full_fname = os.path.join(fobj.filepath, filen)
|
||||
LOG.debug('found cover file: %s' % full_fname)
|
||||
|
||||
Image(full_fname, fobj)
|
||||
|
||||
if not fobj.thumbnail:
|
||||
fthumb = ThumbCreator(full_fname).generate()
|
||||
fobj.thumbnail = fthumb.getvalue()
|
||||
fthumb.close()
|
||||
|
||||
def _name_matcher(self, fpath, fname, media=False):
|
||||
"""
|
||||
Try to match special pattern to filename which may be looks like this:
|
||||
[aXXo] Batman (1989) [D3ADBEEF].avi
|
||||
[aXXo] Batman (1989) [D3ADBEEF].avi.conf
|
||||
[aXXo] Batman (1989) cover [BEEFD00D].jpg
|
||||
[aXXo] Batman (1989) cover2 [FEEDD00D].jpg
|
||||
[aXXo] Batman (1989) trailer [B00B1337].avi
|
||||
or
|
||||
Batman (1989) [D3ADBEEF].avi (and so on)
|
||||
|
||||
For media=False it will return True for filename, that matches
|
||||
pattern, and there are at least one corresponding media files (avi,
|
||||
mpg, mov and so on) _in case the filename differs from media_. This is
|
||||
usfull for not storing covers, nfo, conf files in the db.
|
||||
|
||||
For kind == 2 it will return all images and other files that should be
|
||||
gather due to video file examinig as a dict of list (conf, nfo and
|
||||
images).
|
||||
"""
|
||||
# TODO: dokonczyc to na podstawie tego cudowanego patternu u gory.
|
||||
return
|
||||
|
||||
def _get_all_files(self):
|
||||
self._existing_files = self._session.query(File).all()
|
||||
|
||||
def _mk_file(self, fname, path, parent, ftype=TYPE['file']):
|
||||
"""
|
||||
Create and return File object
|
||||
"""
|
||||
fullpath = os.path.join(path, fname)
|
||||
|
||||
fname = fname.decode(sys.getfilesystemencoding())
|
||||
path = path.decode(sys.getfilesystemencoding())
|
||||
|
||||
if ftype == TYPE['link']:
|
||||
fname = fname + " -> " + os.readlink(fullpath)
|
||||
|
||||
fob = {'filename': fname,
|
||||
'path': path,
|
||||
'ftype': ftype}
|
||||
try:
|
||||
fob['date'] = datetime.fromtimestamp(os.stat(fullpath).st_mtime)
|
||||
fob['size'] = os.stat(fullpath).st_size
|
||||
except OSError:
|
||||
# in case of dead softlink, we will have no time and size
|
||||
fob['date'] = None
|
||||
fob['size'] = 0
|
||||
|
||||
fobj = self._get_old_file(fob, ftype)
|
||||
|
||||
if fobj:
|
||||
LOG.debug("found existing file in db: %s" % str(fobj))
|
||||
fobj.size = fob['size'] # TODO: update whole tree sizes (for directories/discs)
|
||||
fobj.filepath = fob['path']
|
||||
fobj.type = fob['ftype']
|
||||
else:
|
||||
fobj = File(**fob)
|
||||
|
||||
if parent is None:
|
||||
fobj.parent_id = 1
|
||||
else:
|
||||
fobj.parent = parent
|
||||
|
||||
self._files.append(fobj)
|
||||
|
||||
return fobj
|
||||
|
||||
def _recursive(self, parent, fname, path, size):
|
||||
"""
|
||||
Do the walk through the file system
|
||||
@Arguments:
|
||||
@parent - directory File object which is parent for the current
|
||||
scope
|
||||
@fname - string that hold filename
|
||||
@path - full path for further scanning
|
||||
@size - size of the object
|
||||
"""
|
||||
if self.abort:
|
||||
return False
|
||||
|
||||
fullpath = os.path.join(path, fname)
|
||||
|
||||
parent = self._mk_file(fname, path, parent, TYPE['dir'])
|
||||
|
||||
parent.size = self._get_dirsize(fullpath)
|
||||
parent.type = TYPE['dir']
|
||||
|
||||
root, dirs, files = os.walk(fullpath).next()
|
||||
for fname in files:
|
||||
fpath = os.path.join(root, fname)
|
||||
self.current_count += 1
|
||||
LOG.debug("Processing %s [%s/%s]", fname, self.current_count,
|
||||
self.files_count)
|
||||
|
||||
result = PAT.search(fname)
|
||||
test_ = False
|
||||
|
||||
if result and os.path.splitext(fpath)[1] in ('.jpg', '.gif',
|
||||
'.png'):
|
||||
newpat = result.groups()[0]
|
||||
matching_files = []
|
||||
for fn_ in os.listdir(root):
|
||||
if newpat in fn_:
|
||||
matching_files.append(fn_)
|
||||
|
||||
if len(matching_files) > 1:
|
||||
LOG.debug('found cover "%s" in group: %s, skipping', fname,
|
||||
str(matching_files))
|
||||
test_ = True
|
||||
if test_:
|
||||
continue
|
||||
|
||||
if os.path.islink(fpath):
|
||||
fob = self._mk_file(fname, root, parent, TYPE['link'])
|
||||
else:
|
||||
fob = self._mk_file(fname, root, parent)
|
||||
existing_obj = self._object_exists(fob)
|
||||
|
||||
if existing_obj:
|
||||
fob.tags = existing_obj.tags
|
||||
fob.thumbnail = existing_obj.thumbnail
|
||||
fob.images = [img.get_copy() \
|
||||
for img in existing_obj.images] # TODO: many-to-many?
|
||||
else:
|
||||
LOG.debug("gather information for %s",
|
||||
os.path.join(root, fname))
|
||||
self._gather_information(fob)
|
||||
size += fob.size
|
||||
if fob not in self._existing_files:
|
||||
self._existing_files.append(fob)
|
||||
|
||||
for dirname in dirs:
|
||||
dirpath = os.path.join(root, dirname)
|
||||
|
||||
if not os.access(dirpath, os.R_OK|os.X_OK):
|
||||
LOG.info("Cannot access directory %s" % dirpath)
|
||||
continue
|
||||
|
||||
if os.path.islink(dirpath):
|
||||
fob = self._mk_file(dirname, root, parent, TYPE['link'])
|
||||
else:
|
||||
LOG.debug("going into %s" % os.path.join(root, dirname))
|
||||
self._recursive(parent, dirname, fullpath, size)
|
||||
|
||||
LOG.debug("size of items: %s" % parent.size)
|
||||
return True
|
||||
|
||||
def _get_old_file(self, fdict, ftype):
|
||||
"""
|
||||
Search for object with provided data in dictionary in stored branch
|
||||
(which is updating). Return such object on success, remove it from
|
||||
list.
|
||||
"""
|
||||
for index, obj in enumerate(self._existing_branch):
|
||||
if ftype == TYPE['link'] and fdict['filename'] == obj.filename:
|
||||
return self._existing_branch.pop(index)
|
||||
elif fdict['filename'] == obj.filename and \
|
||||
fdict['date'] == obj.date and \
|
||||
ftype == TYPE['file'] and \
|
||||
fdict['size'] in (obj.size, 0):
|
||||
obj = self._existing_branch.pop(index)
|
||||
obj.size = fdict['size']
|
||||
return obj
|
||||
elif fdict['filename'] == obj.filename:
|
||||
obj = self._existing_branch.pop(index)
|
||||
obj.size = fdict['date']
|
||||
return obj
|
||||
return False
|
||||
|
||||
def _object_exists(self, fobj):
|
||||
"""
|
||||
Perform check if current File object already exists in collection. If
|
||||
so, return first matching one, None otherwise.
|
||||
"""
|
||||
for efobj in self._existing_files:
|
||||
if efobj.size == fobj.size \
|
||||
and efobj.type == fobj.type \
|
||||
and efobj.date == fobj.date \
|
||||
and efobj.filename == fobj.filename:
|
||||
return efobj
|
||||
return None
|
||||
|
||||
def _get_files_count(self):
|
||||
count = 0
|
||||
for root, dirs, files in os.walk(self.path):
|
||||
count += len(files)
|
||||
LOG.debug("count of files: %s", count)
|
||||
return count
|
||||
|
||||
|
||||
class asdScan(object):
|
||||
"""
|
||||
Retrieve and identify all files recursively on given path
|
||||
"""
|
||||
def __init__(self, path, tree_model):
|
||||
self.path = path
|
||||
self.abort = False
|
||||
self.label = None
|
||||
self.DIR = None
|
||||
self.source = None
|
||||
|
||||
def scan(self):
|
||||
"""
|
||||
scan content of the given path
|
||||
"""
|
||||
self.busy = True
|
||||
|
||||
# count files in directory tree
|
||||
LOG.debug("Calculating number of files in directory tree...")
|
||||
|
||||
step = 0
|
||||
try:
|
||||
for root, dirs, files in os.walk(self.path):
|
||||
step += len(files)
|
||||
except Exception, ex:
|
||||
LOG.warning("exception on file %s: %s: %s" \
|
||||
% (self.path, ex.__class__.__name__, str(ex)))
|
||||
pass
|
||||
|
||||
step = 1 / float(step or 1)
|
||||
|
||||
self.count = 0
|
||||
|
||||
def _recurse(parent_id, name, path, date, size, filetype,
|
||||
discs_tree_iter=None):
|
||||
"""recursive scans given path"""
|
||||
if self.abort:
|
||||
return -1
|
||||
|
||||
_size = size
|
||||
|
||||
if parent_id == 1:
|
||||
sql = """INSERT INTO
|
||||
files(parent_id, filename, filepath, date,
|
||||
size, type, source)
|
||||
VALUES(?,?,?,?,?,?,?)"""
|
||||
print(sql, (parent_id, name, path, date, size,
|
||||
filetype, self.source))
|
||||
else:
|
||||
sql = """INSERT INTO
|
||||
files(parent_id, filename, filepath, date, size, type)
|
||||
VALUES(?,?,?,?,?,?)"""
|
||||
print(sql, (parent_id, name, path,
|
||||
date, size, filetype))
|
||||
|
||||
sql = """SELECT seq FROM sqlite_sequence WHERE name='files'"""
|
||||
print(sql)
|
||||
currentid = None #db_cursor.fetchone()[0]
|
||||
|
||||
try:
|
||||
root, dirs, files = os.walk(path).next()
|
||||
except:
|
||||
LOG.warning("Cannot access ", path)
|
||||
return 0
|
||||
|
||||
#############
|
||||
# directories
|
||||
for i in dirs:
|
||||
j = i #j = self.__decode_filename(i)
|
||||
current_dir = os.path.join(root, i)
|
||||
|
||||
try:
|
||||
st = os.stat(current_dir)
|
||||
st_mtime = st.st_mtime
|
||||
except OSError:
|
||||
st_mtime = 0
|
||||
|
||||
# do NOT follow symbolic links
|
||||
if os.path.islink(current_dir):
|
||||
l = self.__decode_filename(os.readlink(current_dir))
|
||||
|
||||
sql = """INSERT INTO
|
||||
files(parent_id, filename, filepath, date, size, type)
|
||||
VALUES(?,?,?,?,?,?)"""
|
||||
print(sql, (currentid, j + " -> " + l,
|
||||
current_dir, st_mtime, 0,
|
||||
self.LIN))
|
||||
dirsize = 0
|
||||
else:
|
||||
myit = None
|
||||
dirsize = _recurse(currentid, j, current_dir,
|
||||
st_mtime, 0, self.DIR, myit)
|
||||
|
||||
if dirsize == -1:
|
||||
break
|
||||
else:
|
||||
_size = _size + dirsize
|
||||
|
||||
########
|
||||
# files:
|
||||
for i in files:
|
||||
if self.abort:
|
||||
break
|
||||
|
||||
self.count = self.count + 1
|
||||
current_file = os.path.join(root, i)
|
||||
|
||||
try:
|
||||
st = os.stat(current_file)
|
||||
st_mtime = st.st_mtime
|
||||
st_size = st.st_size
|
||||
except OSError:
|
||||
st_mtime = 0
|
||||
st_size = 0
|
||||
|
||||
_size = _size + st_size
|
||||
j = i #self.__decode_filename(i)
|
||||
|
||||
# do NOT follow symbolic links
|
||||
if os.path.islink(current_file):
|
||||
l = self.__decode_filename(os.readlink(current_file))
|
||||
sql = """INSERT INTO
|
||||
files(parent_id, filename, filepath, date, size, type)
|
||||
VALUES(?,?,?,?,?,?)"""
|
||||
print(sql, (currentid, j + " -> " + l,
|
||||
current_file, st_mtime, 0,
|
||||
self.LIN))
|
||||
else:
|
||||
sql = """INSERT INTO
|
||||
files(parent_id, filename, filepath, date, size, type)
|
||||
VALUES(?,?,?,?,?,?)"""
|
||||
print(sql, (currentid, j, current_file,
|
||||
st_mtime, st_size, self.FIL))
|
||||
|
||||
if self.count % 32 == 0:
|
||||
update = True
|
||||
else:
|
||||
update = False
|
||||
|
||||
###########################
|
||||
# fetch details about files
|
||||
if self.config.confd['retrive']:
|
||||
update = True
|
||||
exif = None
|
||||
|
||||
sql = """SELECT seq FROM sqlite_sequence
|
||||
WHERE name='files'"""
|
||||
print(sql)
|
||||
fileid = 1 # dummy!
|
||||
|
||||
ext = i.split('.')[-1].lower()
|
||||
|
||||
# Video
|
||||
if ext in self.MOV:
|
||||
v = Video(current_file)
|
||||
cfn = v.capture()
|
||||
img = Img(cfn, self.image_path)
|
||||
th = img.save()
|
||||
if th:
|
||||
sql = """INSERT INTO
|
||||
thumbnails(file_id, filename)
|
||||
VALUES(?, ?)"""
|
||||
print(sql, (fileid, th + "_t"))
|
||||
sql = """INSERT INTO images(file_id, filename)
|
||||
VALUES(?, ?)"""
|
||||
print(sql, (fileid, th))
|
||||
os.unlink(cfn)
|
||||
|
||||
# Images - thumbnails and exif data
|
||||
if self.config.confd['thumbs'] and ext in self.IMG:
|
||||
thumb = Thumbnail(current_file, self.image_path)
|
||||
th, exif = thumb.save()
|
||||
if th:
|
||||
sql = """INSERT INTO
|
||||
thumbnails(file_id, filename)
|
||||
VALUES(?, ?)"""
|
||||
print(sql, (fileid, th))
|
||||
|
||||
# exif - store data in exif table
|
||||
jpg = ['jpg', 'jpeg']
|
||||
if self.config.confd['exif'] and ext in jpg:
|
||||
p = None
|
||||
if self.config.confd['thumbs'] and exif:
|
||||
p = ParseExif(exif_dict=exif)
|
||||
else:
|
||||
p = ParseExif(exif_file=current_file)
|
||||
if not p.exif_dict:
|
||||
p = None
|
||||
if p:
|
||||
p = p.parse()
|
||||
p = list(p)
|
||||
p.insert(0, fileid)
|
||||
sql = """INSERT INTO exif (file_id,
|
||||
camera,
|
||||
date,
|
||||
aperture,
|
||||
exposure_program,
|
||||
exposure_bias,
|
||||
iso,
|
||||
focal_length,
|
||||
subject_distance,
|
||||
metering_mode,
|
||||
flash,
|
||||
light_source,
|
||||
resolution,
|
||||
orientation)
|
||||
values(?,?,?,?,?,?,?,?,?,?,?,?,?,?)"""
|
||||
print(sql, (tuple(p)))
|
||||
|
||||
# gthumb - save comments from gThumb program
|
||||
if self.config.confd['gthumb']:
|
||||
gt = GthumbCommentParser(root, i)
|
||||
cmnts = gt.parse()
|
||||
if cmnts:
|
||||
sql = """insert into gthumb(file_id,
|
||||
note,
|
||||
place,
|
||||
date)
|
||||
values(?,?,?,?)"""
|
||||
print(sql, (fileid,
|
||||
cmnts['note'],
|
||||
cmnts['place'],
|
||||
cmnts['date']))
|
||||
if 'keywords' in cmnts:
|
||||
# TODO: add gthumb keywords to tags
|
||||
pass
|
||||
|
||||
# Extensions - user defined actions
|
||||
if ext in self.config.confd['extensions'].keys():
|
||||
cmd = self.config.confd['extensions'][ext]
|
||||
arg = current_file.replace('"', '\\"')
|
||||
output = os.popen(cmd % arg).readlines()
|
||||
desc = ''
|
||||
for line in output:
|
||||
desc += line
|
||||
|
||||
sql = """UPDATE files SET description=?
|
||||
WHERE id=?"""
|
||||
print(sql, (desc, fileid))
|
||||
|
||||
### end of scan
|
||||
if update:
|
||||
self.statusmsg = "Scannig: %s" % current_file
|
||||
self.progress = step * self.count
|
||||
|
||||
sql = """UPDATE files SET size=? WHERE id=?"""
|
||||
print(sql, (_size, currentid))
|
||||
if self.abort:
|
||||
return -1
|
||||
else:
|
||||
return _size
|
||||
|
||||
if _recurse(1, self.label, self.path, 0, 0, self.DIR) == -1:
|
||||
LOG.debug("interrupted self.abort = True")
|
||||
else:
|
||||
LOG.debug("recursive goes without interrupt")
|
||||
if self.currentid:
|
||||
LOG.debug("removing old branch")
|
||||
self.statusmsg = "Removing old branch..."
|
||||
self.currentid = None
|
||||
|
||||
self.busy = False
|
||||
|
||||
# refresh discs tree
|
||||
self.statusmsg = "Idle"
|
||||
self.progress = 0
|
||||
self.abort = False
|
||||
106
pygtktalog/thumbnail.py
Normal file
106
pygtktalog/thumbnail.py
Normal file
@@ -0,0 +1,106 @@
|
||||
"""
|
||||
Project: pyGTKtalog
|
||||
Description: Create thumbnail for sepcified image by its filename
|
||||
Type: lib
|
||||
Author: Roman 'gryf' Dobosz, gryf73@gmail.com
|
||||
Created: 2011-05-15
|
||||
"""
|
||||
import os
|
||||
import sys
|
||||
from cStringIO import StringIO
|
||||
from tempfile import mkstemp
|
||||
|
||||
import Image
|
||||
|
||||
from pygtktalog.logger import get_logger
|
||||
from pygtktalog import EXIF
|
||||
|
||||
|
||||
LOG = get_logger(__name__)
|
||||
|
||||
|
||||
class ThumbCreator(object):
|
||||
"""
|
||||
Class for generate/extract thumbnail from image file
|
||||
"""
|
||||
|
||||
def __init__(self, filename):
|
||||
self.thumb_x = 160
|
||||
self.thumb_y = 160
|
||||
self.filename = filename.decode(sys.getfilesystemencoding())
|
||||
self.fobj = StringIO()
|
||||
|
||||
def generate(self):
|
||||
"""
|
||||
Generate and return file-like object with thumbnail
|
||||
"""
|
||||
exif = {}
|
||||
orientations = {2: Image.FLIP_LEFT_RIGHT, # Mirrored horizontal
|
||||
3: Image.ROTATE_180, # Rotated 180
|
||||
4: Image.FLIP_TOP_BOTTOM, # Mirrored vertical
|
||||
5: Image.ROTATE_90, # Mirrored horizontal then
|
||||
# rotated 90 CCW
|
||||
6: Image.ROTATE_270, # Rotated 90 CW
|
||||
7: Image.ROTATE_270, # Mirrored horizontal then
|
||||
# rotated 90 CW
|
||||
8: Image.ROTATE_90} # Rotated 90 CCW
|
||||
flips = {7: Image.FLIP_LEFT_RIGHT, 5: Image.FLIP_LEFT_RIGHT}
|
||||
|
||||
exif = self._get_exif()
|
||||
file_desc, thumb_fn = mkstemp(suffix=".jpg")
|
||||
os.close(file_desc)
|
||||
|
||||
if 'JPEGThumbnail' not in exif:
|
||||
LOG.debug("no exif thumb for file %s; creating." % self.filename)
|
||||
thumb = self._scale_image()
|
||||
if thumb:
|
||||
thumb.save(self.fobj, "JPEG")
|
||||
else:
|
||||
LOG.debug("exif thumb for filename %s" % self.filename)
|
||||
exif_thumbnail = exif['JPEGThumbnail']
|
||||
self.fobj.write(exif_thumbnail)
|
||||
self.fobj.seek(0)
|
||||
|
||||
if 'Image Orientation' in exif:
|
||||
orient = exif['Image Orientation'].values[0]
|
||||
if orient > 1 and orient in orientations:
|
||||
thumb_image = Image.open(self.fobj)
|
||||
tmp_thumb_img = thumb_image.transpose(orientations[orient])
|
||||
|
||||
if orient in flips:
|
||||
tmp_thumb_img = tmp_thumb_img.transpose(flips[orient])
|
||||
|
||||
self.fobj.seek(0)
|
||||
self.fobj.truncate()
|
||||
tmp_thumb_img.save(self.fobj, 'JPEG')
|
||||
|
||||
return self.fobj
|
||||
|
||||
def _get_exif(self):
|
||||
"""
|
||||
Get exif (if available), return as a dict
|
||||
"""
|
||||
image_file = open(self.filename, 'rb')
|
||||
try:
|
||||
exif = EXIF.process_file(image_file)
|
||||
except Exception:
|
||||
exif = {}
|
||||
LOG.info("Exif crashed on '%s'." % self.filename)
|
||||
finally:
|
||||
image_file.close()
|
||||
|
||||
return exif
|
||||
|
||||
def _scale_image(self):
|
||||
"""
|
||||
Create thumbnail. returns image object or None
|
||||
"""
|
||||
try:
|
||||
image_thumb = Image.open(self.filename).convert('RGB')
|
||||
except:
|
||||
return None
|
||||
it_x, it_y = image_thumb.size
|
||||
if it_x > self.thumb_x or it_y > self.thumb_y:
|
||||
image_thumb.thumbnail((self.thumb_x, self.thumb_y),
|
||||
Image.ANTIALIAS)
|
||||
return image_thumb
|
||||
281
pygtktalog/video.py
Normal file
281
pygtktalog/video.py
Normal file
@@ -0,0 +1,281 @@
|
||||
"""
|
||||
Project: pyGTKtalog
|
||||
Description: Gather video file information, make "screenshot" with content
|
||||
of the movie file. Uses external tools like mplayer.
|
||||
Type: lib
|
||||
Author: Roman 'gryf' Dobosz, gryf73@gmail.com
|
||||
Created: 2009-04-04
|
||||
"""
|
||||
import os
|
||||
import shutil
|
||||
from tempfile import mkdtemp, mkstemp
|
||||
import math
|
||||
|
||||
import Image
|
||||
from pygtktalog.misc import float_to_string
|
||||
from pygtktalog.logger import get_logger
|
||||
|
||||
|
||||
LOG = get_logger("Video")
|
||||
|
||||
|
||||
class Video(object):
|
||||
"""Class for retrive midentify script output and put it in dict.
|
||||
Usually there is no need for such a detailed movie/clip information.
|
||||
Midentify script belongs to mplayer package.
|
||||
"""
|
||||
|
||||
def __init__(self, filename, out_width=1024):
|
||||
"""
|
||||
Init class instance.
|
||||
Arguments:
|
||||
@filename - Filename of a video file (required).
|
||||
@out_width - width of final image to be scaled to.
|
||||
"""
|
||||
self.filename = filename
|
||||
self.out_width = out_width
|
||||
self.tags = {}
|
||||
|
||||
output = self._get_movie_info()
|
||||
|
||||
attrs = {'ID_VIDEO_WIDTH': ['width', int],
|
||||
'ID_VIDEO_HEIGHT': ['height', int],
|
||||
# length is in seconds
|
||||
'ID_LENGTH': ['length', lambda x: int(x.split(".")[0])],
|
||||
'ID_START_TIME': ['start', self._get_start_pos],
|
||||
'ID_DEMUXER': ['container', self._return_lower],
|
||||
'ID_VIDEO_FORMAT': ['video_format', self._return_lower],
|
||||
'ID_VIDEO_CODEC': ['video_codec', self._return_lower],
|
||||
'ID_AUDIO_CODEC': ['audio_codec', self._return_lower],
|
||||
'ID_AUDIO_FORMAT': ['audio_format', self._return_lower],
|
||||
'ID_AUDIO_NCH': ['audio_no_channels', int]}
|
||||
# TODO: what about audio/subtitle language/existence?
|
||||
|
||||
for key in output:
|
||||
if key in attrs:
|
||||
self.tags[attrs[key][0]] = attrs[key][1](output[key])
|
||||
|
||||
if 'length' in self.tags and self.tags['length'] > 0:
|
||||
start = self.tags.get('start', 0)
|
||||
length = self.tags['length'] - start
|
||||
hours = length / 3600
|
||||
seconds = length - hours * 3600
|
||||
minutes = seconds / 60
|
||||
seconds -= minutes * 60
|
||||
length_str = "%02d:%02d:%02d" % (hours, minutes, seconds)
|
||||
self.tags['duration'] = length_str
|
||||
|
||||
def capture(self):
|
||||
"""
|
||||
Extract images for given video filename and montage it into one, big
|
||||
picture, similar to output from Windows Media Player thing, but without
|
||||
captions and time (who need it anyway?).
|
||||
|
||||
Returns: image filename or None
|
||||
|
||||
NOTE: You should remove returned file manually, or move it in some
|
||||
other place, otherwise it stays in filesystem.
|
||||
"""
|
||||
|
||||
if not ('length' in self.tags and 'width' in self.tags):
|
||||
# no length or width
|
||||
return None
|
||||
|
||||
if not (self.tags['length'] > 0 and self.tags['width'] > 0):
|
||||
# zero length or wight
|
||||
return None
|
||||
|
||||
# Calculate number of pictures. Base is equivalent 72 pictures for
|
||||
# 1:30:00 movie length
|
||||
scale = int(10 * math.log(self.tags['length'], math.e) - 11)
|
||||
|
||||
if scale < 1:
|
||||
return None
|
||||
|
||||
no_pictures = self.tags['length'] / scale
|
||||
|
||||
if no_pictures > 8:
|
||||
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")
|
||||
os.close(file_desc)
|
||||
self._make_captures(tempdir, no_pictures)
|
||||
self._make_montage(tempdir, image_fn, no_pictures)
|
||||
|
||||
shutil.rmtree(tempdir)
|
||||
return image_fn
|
||||
|
||||
def get_formatted_tags(self):
|
||||
"""
|
||||
Return formatted tags as a string
|
||||
"""
|
||||
out_tags = u''
|
||||
if 'container' in self.tags:
|
||||
out_tags += u"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'])
|
||||
|
||||
if 'duration' in self.tags:
|
||||
out_tags += u"Duration: %s\n" % self.tags['duration']
|
||||
|
||||
if 'video_codec' in self.tags:
|
||||
out_tags += "Video codec: %s\n" % self.tags['video_codec']
|
||||
|
||||
if 'video_format' in self.tags:
|
||||
out_tags += "Video format: %s\n" % self.tags['video_format']
|
||||
|
||||
if 'audio_codec' in self.tags:
|
||||
out_tags += "Audio codec: %s\n" % self.tags['audio_codec']
|
||||
|
||||
if 'audio_format' in self.tags:
|
||||
out_tags += "Audio format: %s\n" % self.tags['audio_format']
|
||||
|
||||
if 'audio_no_channels' in self.tags:
|
||||
out_tags += "Audio channels: %s\n" % self.tags['audio_no_channels']
|
||||
|
||||
return out_tags
|
||||
|
||||
def _get_movie_info(self):
|
||||
"""
|
||||
Gather movie file information with midentify shell command.
|
||||
Returns: dict of command output. Each dict element represents pairs:
|
||||
variable=value, for example output from midentify will be:
|
||||
|
||||
ID_VIDEO_ID=0
|
||||
ID_AUDIO_ID=1
|
||||
....
|
||||
ID_AUDIO_CODEC=mp3
|
||||
ID_EXIT=EOF
|
||||
|
||||
so method returns dict:
|
||||
|
||||
{'ID_VIDEO_ID': '0',
|
||||
'ID_AUDIO_ID': 1,
|
||||
....
|
||||
'ID_AUDIO_CODEC': 'mp3',
|
||||
'ID_EXIT': 'EOF'}
|
||||
"""
|
||||
output = os.popen('midentify "%s"' % self.filename).readlines()
|
||||
return_dict = {}
|
||||
|
||||
for line in output:
|
||||
line = line.strip()
|
||||
key = line.split('=')
|
||||
if len(key) > 1:
|
||||
return_dict[key[0]] = line.replace("%s=" % key[0], "")
|
||||
return return_dict
|
||||
|
||||
def _make_captures(self, directory, no_pictures):
|
||||
"""
|
||||
Make screens with mplayer into given directory
|
||||
Arguments:
|
||||
@directory - full output directory name
|
||||
@no_pictures - number of pictures to take
|
||||
"""
|
||||
step = float(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 -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):
|
||||
LOG.error('error capturing file from movie "%s" at position '
|
||||
'%s. Errors: %s, %s', self.filename, time, errno,
|
||||
strerror)
|
||||
|
||||
def _make_montage(self, directory, image_fn, no_pictures):
|
||||
"""
|
||||
Generate one big image from screnshots and optionally resize it. Uses
|
||||
PIL package to create output image.
|
||||
Arguments:
|
||||
@directory - source directory containing images
|
||||
@image_fn - destination final image
|
||||
@no_pictures - number of pictures
|
||||
timeit result:
|
||||
python /usr/lib/python2.6/timeit.py -n 1 -r 1 'from \
|
||||
pygtktalog.video import Video; v = Video("/home/gryf/t/a.avi"); \
|
||||
v.capture()'
|
||||
1 loops, best of 1: 18.8 sec per loop
|
||||
"""
|
||||
row_length = 4
|
||||
if no_pictures < 8:
|
||||
row_length = 2
|
||||
|
||||
if not (self.tags['width'] * row_length) > self.out_width:
|
||||
for i in [8, 6, 5]:
|
||||
if (no_pictures % i) == 0 and \
|
||||
(i * self.tags['width']) <= self.out_width:
|
||||
row_length = i
|
||||
break
|
||||
|
||||
coef = float(self.out_width - row_length - 1) / \
|
||||
(self.tags['width'] * row_length)
|
||||
if coef < 1:
|
||||
dim = (int(self.tags['width'] * coef),
|
||||
int(self.tags['height'] * coef))
|
||||
else:
|
||||
dim = int(self.tags['width']), int(self.tags['height'])
|
||||
|
||||
ifn_list = os.listdir(directory)
|
||||
ifn_list.sort()
|
||||
img_list = [Image.open(os.path.join(directory, fn)).resize(dim) \
|
||||
for fn in ifn_list]
|
||||
|
||||
rows = no_pictures / row_length
|
||||
cols = row_length
|
||||
isize = (cols * dim[0] + cols + 1,
|
||||
rows * dim[1] + rows + 1)
|
||||
|
||||
inew = Image.new('RGB', isize, (80, 80, 80))
|
||||
|
||||
for irow in range(no_pictures * row_length):
|
||||
for icol in range(row_length):
|
||||
left = 1 + icol * (dim[0] + 1)
|
||||
right = left + dim[0]
|
||||
upper = 1 + irow * (dim[1] + 1)
|
||||
lower = upper + dim[1]
|
||||
bbox = (left, upper, right, lower)
|
||||
try:
|
||||
img = img_list.pop(0)
|
||||
except:
|
||||
break
|
||||
inew.paste(img, bbox)
|
||||
inew.save(image_fn, 'JPEG')
|
||||
|
||||
def _return_lower(self, chain):
|
||||
"""
|
||||
Return lowercase version of provided string argument
|
||||
Arguments:
|
||||
@chain string to be lowered
|
||||
Returns:
|
||||
@string with lowered string
|
||||
"""
|
||||
return str(chain).lower()
|
||||
|
||||
def _get_start_pos(self, chain):
|
||||
"""
|
||||
Return integer for starting point of the movie
|
||||
"""
|
||||
try:
|
||||
return int(chain.split(".")[0])
|
||||
except:
|
||||
return 0
|
||||
|
||||
def __str__(self):
|
||||
str_out = ''
|
||||
for key in self.tags:
|
||||
str_out += "%20s: %s\n" % (key, self.tags[key])
|
||||
return str_out
|
||||
0
pygtktalog/views/__init__.py
Normal file
0
pygtktalog/views/__init__.py
Normal file
138
pygtktalog/views/glade/details.glade
Normal file
138
pygtktalog/views/glade/details.glade
Normal file
@@ -0,0 +1,138 @@
|
||||
<?xml version="1.0" encoding="UTF-8" standalone="no"?>
|
||||
<!DOCTYPE glade-interface SYSTEM "glade-2.0.dtd">
|
||||
<!--Generated with glade3 3.4.5 on Sat Aug 29 15:13:44 2009 -->
|
||||
<glade-interface>
|
||||
<widget class="GtkWindow" id="top_details">
|
||||
<child>
|
||||
<widget class="GtkNotebook" id="notebook_details">
|
||||
<property name="visible">True</property>
|
||||
<property name="can_focus">True</property>
|
||||
<property name="events">GDK_POINTER_MOTION_MASK | GDK_POINTER_MOTION_HINT_MASK | GDK_BUTTON_PRESS_MASK | GDK_BUTTON_RELEASE_MASK</property>
|
||||
<child>
|
||||
<widget class="GtkHBox" id="fileinfo">
|
||||
<property name="visible">True</property>
|
||||
<property name="events">GDK_POINTER_MOTION_MASK | GDK_POINTER_MOTION_HINT_MASK | GDK_BUTTON_PRESS_MASK | GDK_BUTTON_RELEASE_MASK</property>
|
||||
<child>
|
||||
<widget class="GtkScrolledWindow" id="scrolledwindow4">
|
||||
<property name="visible">True</property>
|
||||
<property name="can_focus">True</property>
|
||||
<property name="events">GDK_POINTER_MOTION_MASK | GDK_POINTER_MOTION_HINT_MASK | GDK_BUTTON_PRESS_MASK | GDK_BUTTON_RELEASE_MASK</property>
|
||||
<property name="hscrollbar_policy">GTK_POLICY_AUTOMATIC</property>
|
||||
<property name="vscrollbar_policy">GTK_POLICY_AUTOMATIC</property>
|
||||
<property name="shadow_type">GTK_SHADOW_IN</property>
|
||||
<child>
|
||||
<widget class="GtkTextView" id="description">
|
||||
<property name="visible">True</property>
|
||||
<property name="can_focus">True</property>
|
||||
<property name="events">GDK_POINTER_MOTION_MASK | GDK_POINTER_MOTION_HINT_MASK | GDK_BUTTON_PRESS_MASK | GDK_BUTTON_RELEASE_MASK</property>
|
||||
<property name="editable">False</property>
|
||||
<property name="wrap_mode">GTK_WRAP_WORD</property>
|
||||
<property name="left_margin">2</property>
|
||||
<property name="right_margin">2</property>
|
||||
<property name="cursor_visible">False</property>
|
||||
</widget>
|
||||
</child>
|
||||
</widget>
|
||||
</child>
|
||||
<child>
|
||||
<widget class="GtkViewport" id="thumb_box">
|
||||
<property name="events">GDK_POINTER_MOTION_MASK | GDK_POINTER_MOTION_HINT_MASK | GDK_BUTTON_PRESS_MASK | GDK_BUTTON_RELEASE_MASK</property>
|
||||
<property name="resize_mode">GTK_RESIZE_QUEUE</property>
|
||||
<child>
|
||||
<widget class="GtkImage" id="thumb">
|
||||
<property name="visible">True</property>
|
||||
<property name="events">GDK_POINTER_MOTION_MASK | GDK_POINTER_MOTION_HINT_MASK | GDK_BUTTON_PRESS_MASK | GDK_BUTTON_RELEASE_MASK</property>
|
||||
<property name="xpad">3</property>
|
||||
<property name="ypad">3</property>
|
||||
<property name="stock">gtk-missing-image</property>
|
||||
<property name="icon_size">6</property>
|
||||
</widget>
|
||||
</child>
|
||||
</widget>
|
||||
<packing>
|
||||
<property name="expand">False</property>
|
||||
<property name="fill">False</property>
|
||||
<property name="position">1</property>
|
||||
</packing>
|
||||
</child>
|
||||
</widget>
|
||||
</child>
|
||||
<child>
|
||||
<widget class="GtkLabel" id="nb_fileinfo">
|
||||
<property name="visible">True</property>
|
||||
<property name="events">GDK_POINTER_MOTION_MASK | GDK_POINTER_MOTION_HINT_MASK | GDK_BUTTON_PRESS_MASK | GDK_BUTTON_RELEASE_MASK</property>
|
||||
<property name="label" translatable="yes">File info</property>
|
||||
</widget>
|
||||
<packing>
|
||||
<property name="type">tab</property>
|
||||
<property name="tab_fill">False</property>
|
||||
</packing>
|
||||
</child>
|
||||
<child>
|
||||
<widget class="GtkScrolledWindow" id="img_container">
|
||||
<property name="can_focus">True</property>
|
||||
<property name="events">GDK_POINTER_MOTION_MASK | GDK_POINTER_MOTION_HINT_MASK | GDK_BUTTON_PRESS_MASK | GDK_BUTTON_RELEASE_MASK</property>
|
||||
<property name="hscrollbar_policy">GTK_POLICY_AUTOMATIC</property>
|
||||
<property name="vscrollbar_policy">GTK_POLICY_AUTOMATIC</property>
|
||||
<property name="shadow_type">GTK_SHADOW_IN</property>
|
||||
<child>
|
||||
<widget class="GtkIconView" id="images">
|
||||
<property name="visible">True</property>
|
||||
<property name="can_focus">True</property>
|
||||
<property name="events">GDK_POINTER_MOTION_MASK | GDK_POINTER_MOTION_HINT_MASK | GDK_BUTTON_PRESS_MASK | GDK_BUTTON_RELEASE_MASK</property>
|
||||
<property name="tooltip" translatable="yes">Double click to open image</property>
|
||||
</widget>
|
||||
</child>
|
||||
</widget>
|
||||
<packing>
|
||||
<property name="position">1</property>
|
||||
</packing>
|
||||
</child>
|
||||
<child>
|
||||
<widget class="GtkLabel" id="label3">
|
||||
<property name="visible">True</property>
|
||||
<property name="events">GDK_POINTER_MOTION_MASK | GDK_POINTER_MOTION_HINT_MASK | GDK_BUTTON_PRESS_MASK | GDK_BUTTON_RELEASE_MASK</property>
|
||||
<property name="label" translatable="yes">Images</property>
|
||||
</widget>
|
||||
<packing>
|
||||
<property name="type">tab</property>
|
||||
<property name="position">1</property>
|
||||
<property name="tab_fill">False</property>
|
||||
</packing>
|
||||
</child>
|
||||
<child>
|
||||
<widget class="GtkScrolledWindow" id="exifinfo">
|
||||
<property name="can_focus">True</property>
|
||||
<property name="events">GDK_POINTER_MOTION_MASK | GDK_POINTER_MOTION_HINT_MASK | GDK_BUTTON_PRESS_MASK | GDK_BUTTON_RELEASE_MASK</property>
|
||||
<property name="hscrollbar_policy">GTK_POLICY_AUTOMATIC</property>
|
||||
<property name="vscrollbar_policy">GTK_POLICY_AUTOMATIC</property>
|
||||
<property name="shadow_type">GTK_SHADOW_IN</property>
|
||||
<child>
|
||||
<widget class="GtkTreeView" id="exif_tree">
|
||||
<property name="visible">True</property>
|
||||
<property name="can_focus">True</property>
|
||||
<property name="events">GDK_POINTER_MOTION_MASK | GDK_POINTER_MOTION_HINT_MASK | GDK_BUTTON_PRESS_MASK | GDK_BUTTON_RELEASE_MASK</property>
|
||||
<property name="rules_hint">True</property>
|
||||
</widget>
|
||||
</child>
|
||||
</widget>
|
||||
<packing>
|
||||
<property name="position">2</property>
|
||||
</packing>
|
||||
</child>
|
||||
<child>
|
||||
<widget class="GtkLabel" id="label4">
|
||||
<property name="visible">True</property>
|
||||
<property name="events">GDK_POINTER_MOTION_MASK | GDK_POINTER_MOTION_HINT_MASK | GDK_BUTTON_PRESS_MASK | GDK_BUTTON_RELEASE_MASK</property>
|
||||
<property name="label" translatable="yes">EXIF</property>
|
||||
</widget>
|
||||
<packing>
|
||||
<property name="type">tab</property>
|
||||
<property name="position">2</property>
|
||||
<property name="tab_fill">False</property>
|
||||
</packing>
|
||||
</child>
|
||||
</widget>
|
||||
</child>
|
||||
</widget>
|
||||
</glade-interface>
|
||||
76
pygtktalog/views/glade/discs.glade
Normal file
76
pygtktalog/views/glade/discs.glade
Normal file
@@ -0,0 +1,76 @@
|
||||
<?xml version="1.0"?>
|
||||
<glade-interface>
|
||||
<!-- interface-requires gtk+ 2.6 -->
|
||||
<!-- interface-naming-policy toplevel-contextual -->
|
||||
<widget class="GtkWindow" id="top_discs">
|
||||
<child>
|
||||
<widget class="GtkTreeView" id="discs">
|
||||
<property name="visible">True</property>
|
||||
<property name="can_focus">True</property>
|
||||
<property name="headers_visible">False</property>
|
||||
<property name="rules_hint">True</property>
|
||||
<signal name="button_press_event" handler="on_discs_button_press_event"/>
|
||||
<signal name="cursor_changed" handler="on_discs_cursor_changed"/>
|
||||
<signal name="row_activated" handler="on_discs_row_activated"/>
|
||||
<signal name="key_release_event" handler="on_discs_key_release_event"/>
|
||||
</widget>
|
||||
</child>
|
||||
</widget>
|
||||
<widget class="GtkMenu" id="discs_popup">
|
||||
<child>
|
||||
<widget class="GtkMenuItem" id="expand_all">
|
||||
<property name="visible">True</property>
|
||||
<property name="tooltip" translatable="yes">Expand all nodes</property>
|
||||
<property name="label" translatable="yes">_Expand all</property>
|
||||
<property name="use_underline">True</property>
|
||||
<signal name="activate" handler="on_expand_all_activate"/>
|
||||
</widget>
|
||||
</child>
|
||||
<child>
|
||||
<widget class="GtkMenuItem" id="collapse_all">
|
||||
<property name="visible">True</property>
|
||||
<property name="tooltip" translatable="yes">Collapse all nodes</property>
|
||||
<property name="label" translatable="yes">_Collapse all</property>
|
||||
<property name="use_underline">True</property>
|
||||
<signal name="activate" handler="on_collapse_all_activate"/>
|
||||
</widget>
|
||||
</child>
|
||||
<child>
|
||||
<widget class="GtkSeparatorMenuItem" id="separator">
|
||||
<property name="visible">True</property>
|
||||
</widget>
|
||||
</child>
|
||||
<child>
|
||||
<widget class="GtkMenuItem" id="update">
|
||||
<property name="visible">True</property>
|
||||
<property name="label" translatable="yes">_Update</property>
|
||||
<property name="use_underline">True</property>
|
||||
<signal name="activate" handler="on_update_activate"/>
|
||||
</widget>
|
||||
</child>
|
||||
<child>
|
||||
<widget class="GtkMenuItem" id="rename">
|
||||
<property name="visible">True</property>
|
||||
<property name="label" translatable="yes">_Rename</property>
|
||||
<property name="use_underline">True</property>
|
||||
<signal name="activate" handler="on_rename_activate"/>
|
||||
</widget>
|
||||
</child>
|
||||
<child>
|
||||
<widget class="GtkMenuItem" id="delete">
|
||||
<property name="visible">True</property>
|
||||
<property name="label" translatable="yes">_Delete</property>
|
||||
<property name="use_underline">True</property>
|
||||
<signal name="activate" handler="on_delete_activate"/>
|
||||
</widget>
|
||||
</child>
|
||||
<child>
|
||||
<widget class="GtkMenuItem" id="statistics">
|
||||
<property name="visible">True</property>
|
||||
<property name="label" translatable="yes">_Statistics</property>
|
||||
<property name="use_underline">True</property>
|
||||
<signal name="activate" handler="on_statistics_activate"/>
|
||||
</widget>
|
||||
</child>
|
||||
</widget>
|
||||
</glade-interface>
|
||||
122
pygtktalog/views/glade/files.glade
Normal file
122
pygtktalog/views/glade/files.glade
Normal file
@@ -0,0 +1,122 @@
|
||||
<?xml version="1.0"?>
|
||||
<glade-interface>
|
||||
<!-- interface-requires gtk+ 2.6 -->
|
||||
<!-- interface-naming-policy toplevel-contextual -->
|
||||
<widget class="GtkWindow" id="top_files">
|
||||
<child>
|
||||
<widget class="GtkTreeView" id="files">
|
||||
<property name="visible">True</property>
|
||||
<property name="can_focus">True</property>
|
||||
<property name="rules_hint">True</property>
|
||||
<signal name="button_press_event" handler="on_files_button_press_event"/>
|
||||
<signal name="cursor_changed" handler="on_files_cursor_changed"/>
|
||||
<signal name="row_activated" handler="on_files_row_activated"/>
|
||||
<signal name="drag_data_get" handler="on_files_drag_data_get"/>
|
||||
<signal name="key_release_event" handler="on_files_key_release_event"/>
|
||||
</widget>
|
||||
</child>
|
||||
</widget>
|
||||
<widget class="GtkMenu" id="files_popup">
|
||||
<property name="visible">True</property>
|
||||
<property name="events">GDK_POINTER_MOTION_MASK | GDK_POINTER_MOTION_HINT_MASK | GDK_BUTTON_PRESS_MASK | GDK_BUTTON_RELEASE_MASK</property>
|
||||
<child>
|
||||
<widget class="GtkMenuItem" id="add_tag">
|
||||
<property name="visible">True</property>
|
||||
<property name="events">GDK_POINTER_MOTION_MASK | GDK_POINTER_MOTION_HINT_MASK | GDK_BUTTON_PRESS_MASK | GDK_BUTTON_RELEASE_MASK</property>
|
||||
<property name="label" translatable="yes">_Add tag</property>
|
||||
<property name="use_underline">True</property>
|
||||
<signal name="activate" handler="on_add_tag1_activate"/>
|
||||
</widget>
|
||||
</child>
|
||||
<child>
|
||||
<widget class="GtkMenuItem" id="delete_tag">
|
||||
<property name="visible">True</property>
|
||||
<property name="events">GDK_POINTER_MOTION_MASK | GDK_POINTER_MOTION_HINT_MASK | GDK_BUTTON_PRESS_MASK | GDK_BUTTON_RELEASE_MASK</property>
|
||||
<property name="label" translatable="yes">Remo_ve tag</property>
|
||||
<property name="use_underline">True</property>
|
||||
<signal name="activate" handler="on_delete_tag_activate"/>
|
||||
</widget>
|
||||
</child>
|
||||
<child>
|
||||
<widget class="GtkSeparatorMenuItem" id="separator">
|
||||
<property name="visible">True</property>
|
||||
</widget>
|
||||
</child>
|
||||
<child>
|
||||
<widget class="GtkMenuItem" id="add_thumb">
|
||||
<property name="visible">True</property>
|
||||
<property name="events">GDK_POINTER_MOTION_MASK | GDK_POINTER_MOTION_HINT_MASK | GDK_BUTTON_PRESS_MASK | GDK_BUTTON_RELEASE_MASK</property>
|
||||
<property name="label" translatable="yes">Add _Thumbnail</property>
|
||||
<property name="use_underline">True</property>
|
||||
<signal name="activate" handler="on_add_thumb1_activate"/>
|
||||
</widget>
|
||||
</child>
|
||||
<child>
|
||||
<widget class="GtkMenuItem" id="remove_thumb">
|
||||
<property name="visible">True</property>
|
||||
<property name="events">GDK_POINTER_MOTION_MASK | GDK_POINTER_MOTION_HINT_MASK | GDK_BUTTON_PRESS_MASK | GDK_BUTTON_RELEASE_MASK</property>
|
||||
<property name="label" translatable="yes">Re_move Thumbnail</property>
|
||||
<property name="use_underline">True</property>
|
||||
<signal name="activate" handler="on_remove_thumb1_activate"/>
|
||||
</widget>
|
||||
</child>
|
||||
<child>
|
||||
<widget class="GtkSeparatorMenuItem" id="separator1">
|
||||
<property name="visible">True</property>
|
||||
</widget>
|
||||
</child>
|
||||
<child>
|
||||
<widget class="GtkMenuItem" id="add_image">
|
||||
<property name="visible">True</property>
|
||||
<property name="events">GDK_POINTER_MOTION_MASK | GDK_POINTER_MOTION_HINT_MASK | GDK_BUTTON_PRESS_MASK | GDK_BUTTON_RELEASE_MASK</property>
|
||||
<property name="has_tooltip">True</property>
|
||||
<property name="tooltip" translatable="yes">Add images to file. If file have no thumbnail,
|
||||
thumbnail from first image will be generated.</property>
|
||||
<property name="label" translatable="yes">Add _Images</property>
|
||||
<property name="use_underline">True</property>
|
||||
<signal name="activate" handler="on_add_image1_activate"/>
|
||||
</widget>
|
||||
</child>
|
||||
<child>
|
||||
<widget class="GtkMenuItem" id="remove_image">
|
||||
<property name="visible">True</property>
|
||||
<property name="events">GDK_POINTER_MOTION_MASK | GDK_POINTER_MOTION_HINT_MASK | GDK_BUTTON_PRESS_MASK | GDK_BUTTON_RELEASE_MASK</property>
|
||||
<property name="label" translatable="yes">Rem_ove All Images</property>
|
||||
<property name="use_underline">True</property>
|
||||
<signal name="activate" handler="on_remove_image1_activate"/>
|
||||
</widget>
|
||||
</child>
|
||||
<child>
|
||||
<widget class="GtkSeparatorMenuItem" id="separator2">
|
||||
<property name="visible">True</property>
|
||||
</widget>
|
||||
</child>
|
||||
<child>
|
||||
<widget class="GtkMenuItem" id="edit">
|
||||
<property name="visible">True</property>
|
||||
<property name="events">GDK_POINTER_MOTION_MASK | GDK_POINTER_MOTION_HINT_MASK | GDK_BUTTON_PRESS_MASK | GDK_BUTTON_RELEASE_MASK</property>
|
||||
<property name="label" translatable="yes">_Edit</property>
|
||||
<property name="use_underline">True</property>
|
||||
<signal name="activate" handler="on_edit2_activate"/>
|
||||
</widget>
|
||||
</child>
|
||||
<child>
|
||||
<widget class="GtkMenuItem" id="delete">
|
||||
<property name="visible">True</property>
|
||||
<property name="events">GDK_POINTER_MOTION_MASK | GDK_POINTER_MOTION_HINT_MASK | GDK_BUTTON_PRESS_MASK | GDK_BUTTON_RELEASE_MASK</property>
|
||||
<property name="label" translatable="yes">_Delete</property>
|
||||
<property name="use_underline">True</property>
|
||||
<signal name="activate" handler="on_delete3_activate"/>
|
||||
</widget>
|
||||
</child>
|
||||
<child>
|
||||
<widget class="GtkMenuItem" id="rename">
|
||||
<property name="visible">True</property>
|
||||
<property name="events">GDK_POINTER_MOTION_MASK | GDK_POINTER_MOTION_HINT_MASK | GDK_BUTTON_PRESS_MASK | GDK_BUTTON_RELEASE_MASK</property>
|
||||
<property name="label" translatable="yes">_Rename</property>
|
||||
<property name="use_underline">True</property>
|
||||
<signal name="activate" handler="on_rename2_activate"/>
|
||||
</widget>
|
||||
</child>
|
||||
</widget>
|
||||
</glade-interface>
|
||||
@@ -1,7 +1,7 @@
|
||||
<?xml version="1.0" encoding="UTF-8" standalone="no"?>
|
||||
<!DOCTYPE glade-interface SYSTEM "glade-2.0.dtd">
|
||||
<!--*- mode: xml -*-->
|
||||
<?xml version="1.0"?>
|
||||
<glade-interface>
|
||||
<!-- interface-requires gtk+ 2.16 -->
|
||||
<!-- interface-naming-policy toplevel-contextual -->
|
||||
<widget class="GtkWindow" id="main">
|
||||
<property name="events">GDK_POINTER_MOTION_MASK | GDK_POINTER_MOTION_HINT_MASK | GDK_BUTTON_PRESS_MASK | GDK_BUTTON_RELEASE_MASK</property>
|
||||
<property name="title" translatable="yes">pyGTKtalog</property>
|
||||
@@ -11,6 +11,7 @@
|
||||
<widget class="GtkVBox" id="vbox1">
|
||||
<property name="visible">True</property>
|
||||
<property name="events">GDK_POINTER_MOTION_MASK | GDK_POINTER_MOTION_HINT_MASK | GDK_BUTTON_PRESS_MASK | GDK_BUTTON_RELEASE_MASK</property>
|
||||
<property name="orientation">vertical</property>
|
||||
<child>
|
||||
<widget class="GtkMenuBar" id="mainMenubar">
|
||||
<property name="visible">True</property>
|
||||
@@ -23,8 +24,8 @@
|
||||
<widget class="GtkMenu" id="file1_menu">
|
||||
<child>
|
||||
<widget class="GtkImageMenuItem" id="new1">
|
||||
<property name="visible">True</property>
|
||||
<property name="label">gtk-new</property>
|
||||
<property name="visible">True</property>
|
||||
<property name="use_underline">True</property>
|
||||
<property name="use_stock">True</property>
|
||||
<signal name="activate" handler="on_new_activate"/>
|
||||
@@ -32,8 +33,8 @@
|
||||
</child>
|
||||
<child>
|
||||
<widget class="GtkImageMenuItem" id="open1">
|
||||
<property name="visible">True</property>
|
||||
<property name="label">gtk-open</property>
|
||||
<property name="visible">True</property>
|
||||
<property name="use_underline">True</property>
|
||||
<property name="use_stock">True</property>
|
||||
<signal name="activate" handler="on_open_activate"/>
|
||||
@@ -41,8 +42,8 @@
|
||||
</child>
|
||||
<child>
|
||||
<widget class="GtkImageMenuItem" id="save1">
|
||||
<property name="visible">True</property>
|
||||
<property name="label">gtk-save</property>
|
||||
<property name="visible">True</property>
|
||||
<property name="use_underline">True</property>
|
||||
<property name="use_stock">True</property>
|
||||
<signal name="activate" handler="on_save_activate"/>
|
||||
@@ -50,8 +51,8 @@
|
||||
</child>
|
||||
<child>
|
||||
<widget class="GtkImageMenuItem" id="save_as1">
|
||||
<property name="visible">True</property>
|
||||
<property name="label">gtk-save-as</property>
|
||||
<property name="visible">True</property>
|
||||
<property name="use_underline">True</property>
|
||||
<property name="use_stock">True</property>
|
||||
<signal name="activate" handler="on_save_as_activate"/>
|
||||
@@ -62,6 +63,27 @@
|
||||
<property name="visible">True</property>
|
||||
</widget>
|
||||
</child>
|
||||
<child>
|
||||
<widget class="GtkMenuItem" id="import">
|
||||
<property name="visible">True</property>
|
||||
<property name="label" translatable="yes">Import</property>
|
||||
<property name="use_underline">True</property>
|
||||
<signal name="activate" handler="on_import_activate"/>
|
||||
</widget>
|
||||
</child>
|
||||
<child>
|
||||
<widget class="GtkMenuItem" id="export">
|
||||
<property name="visible">True</property>
|
||||
<property name="label" translatable="yes">Export</property>
|
||||
<property name="use_underline">True</property>
|
||||
<signal name="activate" handler="on_export_activate"/>
|
||||
</widget>
|
||||
</child>
|
||||
<child>
|
||||
<widget class="GtkSeparatorMenuItem" id="separator13">
|
||||
<property name="visible">True</property>
|
||||
</widget>
|
||||
</child>
|
||||
<child>
|
||||
<widget class="GtkMenuItem" id="recent_files1">
|
||||
<property name="visible">True</property>
|
||||
@@ -77,8 +99,8 @@
|
||||
</child>
|
||||
<child>
|
||||
<widget class="GtkImageMenuItem" id="quit1">
|
||||
<property name="visible">True</property>
|
||||
<property name="label">gtk-quit</property>
|
||||
<property name="visible">True</property>
|
||||
<property name="use_underline">True</property>
|
||||
<property name="use_stock">True</property>
|
||||
<signal name="activate" handler="on_quit_activate"/>
|
||||
@@ -98,12 +120,12 @@
|
||||
<widget class="GtkMenu" id="edit1_menu">
|
||||
<child>
|
||||
<widget class="GtkImageMenuItem" id="delete1">
|
||||
<property name="visible">True</property>
|
||||
<property name="label">gtk-delete</property>
|
||||
<property name="visible">True</property>
|
||||
<property name="use_underline">True</property>
|
||||
<property name="use_stock">True</property>
|
||||
<signal name="activate" handler="on_delete1_activate"/>
|
||||
<accelerator key="Delete" modifiers="" signal="activate"/>
|
||||
<accelerator key="Delete" signal="activate"/>
|
||||
</widget>
|
||||
</child>
|
||||
<child>
|
||||
@@ -113,12 +135,12 @@
|
||||
</child>
|
||||
<child>
|
||||
<widget class="GtkImageMenuItem" id="find1">
|
||||
<property name="visible">True</property>
|
||||
<property name="label">gtk-find</property>
|
||||
<property name="visible">True</property>
|
||||
<property name="use_underline">True</property>
|
||||
<property name="use_stock">True</property>
|
||||
<signal name="activate" handler="on_find_activate"/>
|
||||
<accelerator key="f" modifiers="GDK_CONTROL_MASK" signal="activate"/>
|
||||
<accelerator key="f" signal="activate" modifiers="GDK_CONTROL_MASK"/>
|
||||
</widget>
|
||||
</child>
|
||||
<child>
|
||||
@@ -128,8 +150,8 @@
|
||||
</child>
|
||||
<child>
|
||||
<widget class="GtkImageMenuItem" id="properties1">
|
||||
<property name="visible">True</property>
|
||||
<property name="label">gtk-preferences</property>
|
||||
<property name="visible">True</property>
|
||||
<property name="use_underline">True</property>
|
||||
<property name="use_stock">True</property>
|
||||
<signal name="activate" handler="on_preferences_activate"/>
|
||||
@@ -152,7 +174,7 @@
|
||||
<property name="label" translatable="yes">Add _CD/DVD</property>
|
||||
<property name="use_underline">True</property>
|
||||
<signal name="activate" handler="on_add_cd_activate"/>
|
||||
<accelerator key="e" modifiers="GDK_CONTROL_MASK" signal="activate"/>
|
||||
<accelerator key="e" signal="activate" modifiers="GDK_CONTROL_MASK"/>
|
||||
</widget>
|
||||
</child>
|
||||
<child>
|
||||
@@ -161,7 +183,7 @@
|
||||
<property name="label" translatable="yes">Add _Directory</property>
|
||||
<property name="use_underline">True</property>
|
||||
<signal name="activate" handler="on_add_directory_activate"/>
|
||||
<accelerator key="d" modifiers="GDK_CONTROL_MASK" signal="activate"/>
|
||||
<accelerator key="d" signal="activate" modifiers="GDK_CONTROL_MASK"/>
|
||||
</widget>
|
||||
</child>
|
||||
<child>
|
||||
@@ -217,17 +239,11 @@
|
||||
</child>
|
||||
<child>
|
||||
<widget class="GtkImageMenuItem" id="cancel1">
|
||||
<property name="label">gtk-cancel</property>
|
||||
<property name="visible">True</property>
|
||||
<property name="label" translatable="yes">Cancel</property>
|
||||
<property name="use_underline">True</property>
|
||||
<property name="use_stock">True</property>
|
||||
<signal name="activate" handler="on_cancel_clicked"/>
|
||||
<child internal-child="image">
|
||||
<widget class="GtkImage" id="image3">
|
||||
<property name="visible">True</property>
|
||||
<property name="stock">gtk-cancel</property>
|
||||
<property name="icon_size">1</property>
|
||||
</widget>
|
||||
</child>
|
||||
</widget>
|
||||
</child>
|
||||
</widget>
|
||||
@@ -257,33 +273,6 @@
|
||||
<signal name="activate" handler="on_status_bar1_activate"/>
|
||||
</widget>
|
||||
</child>
|
||||
<child>
|
||||
<widget class="GtkSeparatorMenuItem" id="separatormenuitem4">
|
||||
<property name="visible">True</property>
|
||||
</widget>
|
||||
</child>
|
||||
<child>
|
||||
<widget class="GtkRadioMenuItem" id="list1">
|
||||
<property name="visible">True</property>
|
||||
<property name="events">GDK_POINTER_MOTION_MASK | GDK_POINTER_MOTION_HINT_MASK | GDK_BUTTON_PRESS_MASK | GDK_BUTTON_RELEASE_MASK</property>
|
||||
<property name="tooltip" translatable="yes">Show as a list</property>
|
||||
<property name="label" translatable="yes">List</property>
|
||||
<property name="use_underline">True</property>
|
||||
<property name="active">True</property>
|
||||
<property name="draw_as_radio">True</property>
|
||||
</widget>
|
||||
</child>
|
||||
<child>
|
||||
<widget class="GtkRadioMenuItem" id="thumbnails1">
|
||||
<property name="visible">True</property>
|
||||
<property name="events">GDK_POINTER_MOTION_MASK | GDK_POINTER_MOTION_HINT_MASK | GDK_BUTTON_PRESS_MASK | GDK_BUTTON_RELEASE_MASK</property>
|
||||
<property name="tooltip" translatable="yes">Show as thumbnails</property>
|
||||
<property name="label" translatable="yes">Thumbnails</property>
|
||||
<property name="use_underline">True</property>
|
||||
<property name="draw_as_radio">True</property>
|
||||
<property name="group">list1</property>
|
||||
</widget>
|
||||
</child>
|
||||
</widget>
|
||||
</child>
|
||||
</widget>
|
||||
@@ -297,8 +286,8 @@
|
||||
<widget class="GtkMenu" id="help1_menu">
|
||||
<child>
|
||||
<widget class="GtkImageMenuItem" id="about1">
|
||||
<property name="visible">True</property>
|
||||
<property name="label">gtk-about</property>
|
||||
<property name="visible">True</property>
|
||||
<property name="use_underline">True</property>
|
||||
<property name="use_stock">True</property>
|
||||
<signal name="activate" handler="on_about1_activate"/>
|
||||
@@ -312,12 +301,13 @@
|
||||
<packing>
|
||||
<property name="expand">False</property>
|
||||
<property name="fill">False</property>
|
||||
<property name="position">0</property>
|
||||
</packing>
|
||||
</child>
|
||||
<child>
|
||||
<widget class="GtkToolbar" id="maintoolbar">
|
||||
<property name="visible">True</property>
|
||||
<property name="toolbar_style">GTK_TOOLBAR_BOTH</property>
|
||||
<property name="toolbar_style">both</property>
|
||||
<child>
|
||||
<widget class="GtkToolButton" id="tb_new">
|
||||
<property name="visible">True</property>
|
||||
@@ -327,6 +317,7 @@
|
||||
</widget>
|
||||
<packing>
|
||||
<property name="expand">False</property>
|
||||
<property name="homogeneous">True</property>
|
||||
</packing>
|
||||
</child>
|
||||
<child>
|
||||
@@ -338,6 +329,7 @@
|
||||
</widget>
|
||||
<packing>
|
||||
<property name="expand">False</property>
|
||||
<property name="homogeneous">True</property>
|
||||
</packing>
|
||||
</child>
|
||||
<child>
|
||||
@@ -349,6 +341,7 @@
|
||||
</widget>
|
||||
<packing>
|
||||
<property name="expand">False</property>
|
||||
<property name="homogeneous">True</property>
|
||||
</packing>
|
||||
</child>
|
||||
<child>
|
||||
@@ -362,7 +355,6 @@
|
||||
</widget>
|
||||
<packing>
|
||||
<property name="expand">False</property>
|
||||
<property name="homogeneous">False</property>
|
||||
</packing>
|
||||
</child>
|
||||
<child>
|
||||
@@ -376,6 +368,7 @@
|
||||
</widget>
|
||||
<packing>
|
||||
<property name="expand">False</property>
|
||||
<property name="homogeneous">True</property>
|
||||
</packing>
|
||||
</child>
|
||||
<child>
|
||||
@@ -388,6 +381,7 @@
|
||||
</widget>
|
||||
<packing>
|
||||
<property name="expand">False</property>
|
||||
<property name="homogeneous">True</property>
|
||||
</packing>
|
||||
</child>
|
||||
<child>
|
||||
@@ -399,6 +393,7 @@
|
||||
</widget>
|
||||
<packing>
|
||||
<property name="expand">False</property>
|
||||
<property name="homogeneous">True</property>
|
||||
</packing>
|
||||
</child>
|
||||
<child>
|
||||
@@ -412,7 +407,6 @@
|
||||
</widget>
|
||||
<packing>
|
||||
<property name="expand">False</property>
|
||||
<property name="homogeneous">False</property>
|
||||
</packing>
|
||||
</child>
|
||||
<child>
|
||||
@@ -426,6 +420,7 @@
|
||||
</widget>
|
||||
<packing>
|
||||
<property name="expand">False</property>
|
||||
<property name="homogeneous">True</property>
|
||||
</packing>
|
||||
</child>
|
||||
<child>
|
||||
@@ -437,18 +432,7 @@
|
||||
</widget>
|
||||
<packing>
|
||||
<property name="expand">False</property>
|
||||
</packing>
|
||||
</child>
|
||||
<child>
|
||||
<widget class="GtkToolButton" id="debugbtn">
|
||||
<property name="visible">True</property>
|
||||
<property name="label" translatable="yes">Debug</property>
|
||||
<property name="use_underline">True</property>
|
||||
<property name="stock_id">gtk-dialog-info</property>
|
||||
<signal name="clicked" handler="on_debugbtn_clicked"/>
|
||||
</widget>
|
||||
<packing>
|
||||
<property name="expand">False</property>
|
||||
<property name="homogeneous">True</property>
|
||||
</packing>
|
||||
</child>
|
||||
</widget>
|
||||
@@ -469,18 +453,24 @@
|
||||
<property name="can_focus">True</property>
|
||||
<property name="events">GDK_POINTER_MOTION_MASK | GDK_POINTER_MOTION_HINT_MASK | GDK_BUTTON_PRESS_MASK | GDK_BUTTON_RELEASE_MASK</property>
|
||||
<property name="editable">False</property>
|
||||
<property name="shadow_type">GTK_SHADOW_ETCHED_IN</property>
|
||||
<property name="shadow_type">etched-in</property>
|
||||
<property name="caps_lock_warning">False</property>
|
||||
<property name="secondary_icon_stock">gtk-clear</property>
|
||||
<property name="secondary_icon_activatable">True</property>
|
||||
<property name="secondary_icon_sensitive">True</property>
|
||||
</widget>
|
||||
<packing>
|
||||
<property name="position">0</property>
|
||||
</packing>
|
||||
</child>
|
||||
<child>
|
||||
<widget class="GtkButton" id="clear">
|
||||
<property name="label">gtk-clear</property>
|
||||
<property name="visible">True</property>
|
||||
<property name="can_focus">True</property>
|
||||
<property name="receives_default">True</property>
|
||||
<property name="events">GDK_POINTER_MOTION_MASK | GDK_POINTER_MOTION_HINT_MASK | GDK_BUTTON_PRESS_MASK | GDK_BUTTON_RELEASE_MASK</property>
|
||||
<property name="label" translatable="yes">gtk-clear</property>
|
||||
<property name="use_stock">True</property>
|
||||
<property name="response_id">0</property>
|
||||
<signal name="clicked" handler="on_clear_clicked"/>
|
||||
</widget>
|
||||
<packing>
|
||||
@@ -505,23 +495,14 @@
|
||||
<property name="can_focus">True</property>
|
||||
<property name="events">GDK_POINTER_MOTION_MASK | GDK_POINTER_MOTION_HINT_MASK | GDK_BUTTON_PRESS_MASK | GDK_BUTTON_RELEASE_MASK</property>
|
||||
<child>
|
||||
<widget class="GtkScrolledWindow" id="scrolledwindow1">
|
||||
<widget class="GtkScrolledWindow" id="scrolledwindow_discs">
|
||||
<property name="visible">True</property>
|
||||
<property name="can_focus">True</property>
|
||||
<property name="events">GDK_POINTER_MOTION_MASK | GDK_POINTER_MOTION_HINT_MASK | GDK_BUTTON_PRESS_MASK | GDK_BUTTON_RELEASE_MASK</property>
|
||||
<property name="hscrollbar_policy">GTK_POLICY_AUTOMATIC</property>
|
||||
<property name="vscrollbar_policy">GTK_POLICY_AUTOMATIC</property>
|
||||
<property name="hscrollbar_policy">automatic</property>
|
||||
<property name="vscrollbar_policy">automatic</property>
|
||||
<child>
|
||||
<widget class="GtkTreeView" id="discs">
|
||||
<property name="visible">True</property>
|
||||
<property name="can_focus">True</property>
|
||||
<property name="headers_visible">False</property>
|
||||
<property name="rules_hint">True</property>
|
||||
<signal name="button_press_event" handler="on_discs_button_press_event"/>
|
||||
<signal name="row_activated" handler="on_discs_row_activated"/>
|
||||
<signal name="cursor_changed" handler="on_discs_cursor_changed"/>
|
||||
<signal name="key_release_event" handler="on_discs_key_release_event"/>
|
||||
</widget>
|
||||
<placeholder/>
|
||||
</child>
|
||||
</widget>
|
||||
</child>
|
||||
@@ -532,8 +513,8 @@
|
||||
<property name="label" translatable="yes">Discs</property>
|
||||
</widget>
|
||||
<packing>
|
||||
<property name="type">tab</property>
|
||||
<property name="tab_fill">False</property>
|
||||
<property name="type">tab</property>
|
||||
</packing>
|
||||
</child>
|
||||
<child>
|
||||
@@ -541,23 +522,10 @@
|
||||
<property name="visible">True</property>
|
||||
<property name="can_focus">True</property>
|
||||
<property name="events">GDK_POINTER_MOTION_MASK | GDK_POINTER_MOTION_HINT_MASK | GDK_BUTTON_PRESS_MASK | GDK_BUTTON_RELEASE_MASK</property>
|
||||
<property name="hscrollbar_policy">GTK_POLICY_AUTOMATIC</property>
|
||||
<property name="vscrollbar_policy">GTK_POLICY_AUTOMATIC</property>
|
||||
<property name="hscrollbar_policy">automatic</property>
|
||||
<property name="vscrollbar_policy">automatic</property>
|
||||
<child>
|
||||
<widget class="GtkTextView" id="tag_cloud_textview">
|
||||
<property name="visible">True</property>
|
||||
<property name="can_focus">True</property>
|
||||
<property name="events">GDK_POINTER_MOTION_MASK | GDK_POINTER_MOTION_HINT_MASK | GDK_BUTTON_PRESS_MASK | GDK_BUTTON_RELEASE_MASK</property>
|
||||
<property name="editable">False</property>
|
||||
<property name="wrap_mode">GTK_WRAP_WORD</property>
|
||||
<property name="cursor_visible">False</property>
|
||||
<signal name="visibility_notify_event" handler="on_tag_cloud_textview_visibility_notify_event"/>
|
||||
<signal name="drag_leave" handler="on_tag_cloud_textview_drag_leave"/>
|
||||
<signal name="event_after" handler="on_tag_cloud_textview_event_after"/>
|
||||
<signal name="drag_motion" handler="on_tag_cloud_textview_drag_motion"/>
|
||||
<signal name="drag_data_received" handler="on_tag_cloud_textview_drag_data_received"/>
|
||||
<signal name="drag_drop" handler="on_tag_cloud_textview_drag_drop"/>
|
||||
</widget>
|
||||
<placeholder/>
|
||||
</child>
|
||||
</widget>
|
||||
<packing>
|
||||
@@ -571,9 +539,9 @@
|
||||
<property name="label" translatable="yes">Tags</property>
|
||||
</widget>
|
||||
<packing>
|
||||
<property name="type">tab</property>
|
||||
<property name="position">1</property>
|
||||
<property name="tab_fill">False</property>
|
||||
<property name="type">tab</property>
|
||||
</packing>
|
||||
</child>
|
||||
</widget>
|
||||
@@ -586,24 +554,16 @@
|
||||
<widget class="GtkVPaned" id="vpaned1">
|
||||
<property name="visible">True</property>
|
||||
<property name="can_focus">True</property>
|
||||
<property name="orientation">vertical</property>
|
||||
<child>
|
||||
<widget class="GtkScrolledWindow" id="scrolledwindow2">
|
||||
<widget class="GtkScrolledWindow" id="scrolledwindow_files">
|
||||
<property name="visible">True</property>
|
||||
<property name="can_focus">True</property>
|
||||
<property name="hscrollbar_policy">GTK_POLICY_AUTOMATIC</property>
|
||||
<property name="vscrollbar_policy">GTK_POLICY_AUTOMATIC</property>
|
||||
<property name="shadow_type">GTK_SHADOW_IN</property>
|
||||
<property name="hscrollbar_policy">automatic</property>
|
||||
<property name="vscrollbar_policy">automatic</property>
|
||||
<property name="shadow_type">in</property>
|
||||
<child>
|
||||
<widget class="GtkTreeView" id="files">
|
||||
<property name="visible">True</property>
|
||||
<property name="can_focus">True</property>
|
||||
<property name="rules_hint">True</property>
|
||||
<signal name="drag_data_get" handler="on_files_drag_data_get"/>
|
||||
<signal name="button_press_event" handler="on_files_button_press_event"/>
|
||||
<signal name="row_activated" handler="on_files_row_activated"/>
|
||||
<signal name="cursor_changed" handler="on_files_cursor_changed"/>
|
||||
<signal name="key_release_event" handler="on_files_key_release_event"/>
|
||||
</widget>
|
||||
<placeholder/>
|
||||
</child>
|
||||
</widget>
|
||||
<packing>
|
||||
@@ -612,144 +572,7 @@
|
||||
</packing>
|
||||
</child>
|
||||
<child>
|
||||
<widget class="GtkNotebook" id="notebook_details">
|
||||
<property name="visible">True</property>
|
||||
<property name="can_focus">True</property>
|
||||
<property name="events">GDK_POINTER_MOTION_MASK | GDK_POINTER_MOTION_HINT_MASK | GDK_BUTTON_PRESS_MASK | GDK_BUTTON_RELEASE_MASK</property>
|
||||
<child>
|
||||
<widget class="GtkHBox" id="fileinfo">
|
||||
<property name="visible">True</property>
|
||||
<property name="events">GDK_POINTER_MOTION_MASK | GDK_POINTER_MOTION_HINT_MASK | GDK_BUTTON_PRESS_MASK | GDK_BUTTON_RELEASE_MASK</property>
|
||||
<child>
|
||||
<widget class="GtkScrolledWindow" id="scrolledwindow4">
|
||||
<property name="visible">True</property>
|
||||
<property name="can_focus">True</property>
|
||||
<property name="events">GDK_POINTER_MOTION_MASK | GDK_POINTER_MOTION_HINT_MASK | GDK_BUTTON_PRESS_MASK | GDK_BUTTON_RELEASE_MASK</property>
|
||||
<property name="hscrollbar_policy">GTK_POLICY_AUTOMATIC</property>
|
||||
<property name="vscrollbar_policy">GTK_POLICY_AUTOMATIC</property>
|
||||
<property name="shadow_type">GTK_SHADOW_IN</property>
|
||||
<child>
|
||||
<widget class="GtkTextView" id="description">
|
||||
<property name="visible">True</property>
|
||||
<property name="can_focus">True</property>
|
||||
<property name="events">GDK_POINTER_MOTION_MASK | GDK_POINTER_MOTION_HINT_MASK | GDK_BUTTON_PRESS_MASK | GDK_BUTTON_RELEASE_MASK</property>
|
||||
<property name="editable">False</property>
|
||||
<property name="wrap_mode">GTK_WRAP_WORD</property>
|
||||
<property name="left_margin">2</property>
|
||||
<property name="right_margin">2</property>
|
||||
<property name="cursor_visible">False</property>
|
||||
</widget>
|
||||
</child>
|
||||
</widget>
|
||||
</child>
|
||||
<child>
|
||||
<widget class="GtkViewport" id="thumb_box">
|
||||
<property name="events">GDK_POINTER_MOTION_MASK | GDK_POINTER_MOTION_HINT_MASK | GDK_BUTTON_PRESS_MASK | GDK_BUTTON_RELEASE_MASK</property>
|
||||
<property name="resize_mode">GTK_RESIZE_QUEUE</property>
|
||||
<signal name="button_press_event" handler="on_thumb_box_button_press_event"/>
|
||||
<child>
|
||||
<widget class="GtkImage" id="thumb">
|
||||
<property name="visible">True</property>
|
||||
<property name="events">GDK_POINTER_MOTION_MASK | GDK_POINTER_MOTION_HINT_MASK | GDK_BUTTON_PRESS_MASK | GDK_BUTTON_RELEASE_MASK</property>
|
||||
<property name="xpad">3</property>
|
||||
<property name="ypad">3</property>
|
||||
<property name="stock">gtk-missing-image</property>
|
||||
<property name="icon_size">6</property>
|
||||
</widget>
|
||||
</child>
|
||||
</widget>
|
||||
<packing>
|
||||
<property name="expand">False</property>
|
||||
<property name="fill">False</property>
|
||||
<property name="position">1</property>
|
||||
</packing>
|
||||
</child>
|
||||
</widget>
|
||||
</child>
|
||||
<child>
|
||||
<widget class="GtkLabel" id="nb_fileinfo">
|
||||
<property name="visible">True</property>
|
||||
<property name="events">GDK_POINTER_MOTION_MASK | GDK_POINTER_MOTION_HINT_MASK | GDK_BUTTON_PRESS_MASK | GDK_BUTTON_RELEASE_MASK</property>
|
||||
<property name="label" translatable="yes">File info</property>
|
||||
</widget>
|
||||
<packing>
|
||||
<property name="type">tab</property>
|
||||
<property name="tab_fill">False</property>
|
||||
</packing>
|
||||
</child>
|
||||
<child>
|
||||
<widget class="GtkScrolledWindow" id="img_container">
|
||||
<property name="can_focus">True</property>
|
||||
<property name="events">GDK_POINTER_MOTION_MASK | GDK_POINTER_MOTION_HINT_MASK | GDK_BUTTON_PRESS_MASK | GDK_BUTTON_RELEASE_MASK</property>
|
||||
<property name="hscrollbar_policy">GTK_POLICY_AUTOMATIC</property>
|
||||
<property name="vscrollbar_policy">GTK_POLICY_AUTOMATIC</property>
|
||||
<property name="shadow_type">GTK_SHADOW_IN</property>
|
||||
<child>
|
||||
<widget class="GtkIconView" id="images">
|
||||
<property name="visible">True</property>
|
||||
<property name="can_focus">True</property>
|
||||
<property name="events">GDK_POINTER_MOTION_MASK | GDK_POINTER_MOTION_HINT_MASK | GDK_BUTTON_PRESS_MASK | GDK_BUTTON_RELEASE_MASK</property>
|
||||
<property name="tooltip" translatable="yes">Double click to open image</property>
|
||||
<signal name="button_press_event" handler="on_images_button_press_event"/>
|
||||
<signal name="item_activated" handler="on_images_item_activated"/>
|
||||
<signal name="key_release_event" handler="on_images_key_release_event"/>
|
||||
</widget>
|
||||
</child>
|
||||
</widget>
|
||||
<packing>
|
||||
<property name="position">1</property>
|
||||
</packing>
|
||||
</child>
|
||||
<child>
|
||||
<widget class="GtkLabel" id="label3">
|
||||
<property name="visible">True</property>
|
||||
<property name="events">GDK_POINTER_MOTION_MASK | GDK_POINTER_MOTION_HINT_MASK | GDK_BUTTON_PRESS_MASK | GDK_BUTTON_RELEASE_MASK</property>
|
||||
<property name="label" translatable="yes">Images</property>
|
||||
</widget>
|
||||
<packing>
|
||||
<property name="type">tab</property>
|
||||
<property name="position">1</property>
|
||||
<property name="tab_fill">False</property>
|
||||
</packing>
|
||||
</child>
|
||||
<child>
|
||||
<widget class="GtkScrolledWindow" id="exifinfo">
|
||||
<property name="can_focus">True</property>
|
||||
<property name="events">GDK_POINTER_MOTION_MASK | GDK_POINTER_MOTION_HINT_MASK | GDK_BUTTON_PRESS_MASK | GDK_BUTTON_RELEASE_MASK</property>
|
||||
<property name="hscrollbar_policy">GTK_POLICY_AUTOMATIC</property>
|
||||
<property name="vscrollbar_policy">GTK_POLICY_AUTOMATIC</property>
|
||||
<property name="shadow_type">GTK_SHADOW_IN</property>
|
||||
<child>
|
||||
<widget class="GtkTreeView" id="exif_tree">
|
||||
<property name="visible">True</property>
|
||||
<property name="can_focus">True</property>
|
||||
<property name="events">GDK_POINTER_MOTION_MASK | GDK_POINTER_MOTION_HINT_MASK | GDK_BUTTON_PRESS_MASK | GDK_BUTTON_RELEASE_MASK</property>
|
||||
<property name="headers_clickable">True</property>
|
||||
<property name="rules_hint">True</property>
|
||||
</widget>
|
||||
</child>
|
||||
</widget>
|
||||
<packing>
|
||||
<property name="position">2</property>
|
||||
</packing>
|
||||
</child>
|
||||
<child>
|
||||
<widget class="GtkLabel" id="label4">
|
||||
<property name="visible">True</property>
|
||||
<property name="events">GDK_POINTER_MOTION_MASK | GDK_POINTER_MOTION_HINT_MASK | GDK_BUTTON_PRESS_MASK | GDK_BUTTON_RELEASE_MASK</property>
|
||||
<property name="label" translatable="yes">EXIF</property>
|
||||
</widget>
|
||||
<packing>
|
||||
<property name="type">tab</property>
|
||||
<property name="position">2</property>
|
||||
<property name="tab_fill">False</property>
|
||||
</packing>
|
||||
</child>
|
||||
</widget>
|
||||
<packing>
|
||||
<property name="resize">True</property>
|
||||
<property name="shrink">True</property>
|
||||
</packing>
|
||||
<placeholder/>
|
||||
</child>
|
||||
</widget>
|
||||
<packing>
|
||||
@@ -780,6 +603,9 @@
|
||||
<property name="visible">True</property>
|
||||
<property name="has_resize_grip">False</property>
|
||||
</widget>
|
||||
<packing>
|
||||
<property name="position">0</property>
|
||||
</packing>
|
||||
</child>
|
||||
<child>
|
||||
<widget class="GtkProgressBar" id="progressbar1">
|
||||
@@ -801,63 +627,6 @@
|
||||
</widget>
|
||||
</child>
|
||||
</widget>
|
||||
<widget class="GtkMenu" id="discs_popup">
|
||||
<child>
|
||||
<widget class="GtkMenuItem" id="expand_all1">
|
||||
<property name="visible">True</property>
|
||||
<property name="tooltip" translatable="yes">Expand all nodes</property>
|
||||
<property name="label" translatable="yes">_Expand all</property>
|
||||
<property name="use_underline">True</property>
|
||||
<signal name="activate" handler="on_expand_all1_activate"/>
|
||||
</widget>
|
||||
</child>
|
||||
<child>
|
||||
<widget class="GtkMenuItem" id="collapse_all1">
|
||||
<property name="visible">True</property>
|
||||
<property name="tooltip" translatable="yes">Collapse all nodes</property>
|
||||
<property name="label" translatable="yes">_Collapse all</property>
|
||||
<property name="use_underline">True</property>
|
||||
<signal name="activate" handler="on_collapse_all1_activate"/>
|
||||
</widget>
|
||||
</child>
|
||||
<child>
|
||||
<widget class="GtkSeparatorMenuItem" id="separator4">
|
||||
<property name="visible">True</property>
|
||||
</widget>
|
||||
</child>
|
||||
<child>
|
||||
<widget class="GtkMenuItem" id="update1">
|
||||
<property name="visible">True</property>
|
||||
<property name="label" translatable="yes">_Update</property>
|
||||
<property name="use_underline">True</property>
|
||||
<signal name="activate" handler="on_update1_activate"/>
|
||||
</widget>
|
||||
</child>
|
||||
<child>
|
||||
<widget class="GtkMenuItem" id="rename1">
|
||||
<property name="visible">True</property>
|
||||
<property name="label" translatable="yes">_Rename</property>
|
||||
<property name="use_underline">True</property>
|
||||
<signal name="activate" handler="on_rename1_activate"/>
|
||||
</widget>
|
||||
</child>
|
||||
<child>
|
||||
<widget class="GtkMenuItem" id="delete2">
|
||||
<property name="visible">True</property>
|
||||
<property name="label" translatable="yes">_Delete</property>
|
||||
<property name="use_underline">True</property>
|
||||
<signal name="activate" handler="on_delete2_activate"/>
|
||||
</widget>
|
||||
</child>
|
||||
<child>
|
||||
<widget class="GtkMenuItem" id="statistics1">
|
||||
<property name="visible">True</property>
|
||||
<property name="label" translatable="yes">_Statistics</property>
|
||||
<property name="use_underline">True</property>
|
||||
<signal name="activate" handler="on_statistics1_activate"/>
|
||||
</widget>
|
||||
</child>
|
||||
</widget>
|
||||
<widget class="GtkWindow" id="win_exifinfo">
|
||||
<property name="events">GDK_POINTER_MOTION_MASK | GDK_POINTER_MOTION_HINT_MASK | GDK_BUTTON_PRESS_MASK | GDK_BUTTON_RELEASE_MASK</property>
|
||||
<child>
|
||||
@@ -870,108 +639,6 @@
|
||||
</widget>
|
||||
</child>
|
||||
</widget>
|
||||
<widget class="GtkMenu" id="files_popup">
|
||||
<property name="visible">True</property>
|
||||
<property name="events">GDK_POINTER_MOTION_MASK | GDK_POINTER_MOTION_HINT_MASK | GDK_BUTTON_PRESS_MASK | GDK_BUTTON_RELEASE_MASK</property>
|
||||
<child>
|
||||
<widget class="GtkMenuItem" id="add_tag1">
|
||||
<property name="visible">True</property>
|
||||
<property name="events">GDK_POINTER_MOTION_MASK | GDK_POINTER_MOTION_HINT_MASK | GDK_BUTTON_PRESS_MASK | GDK_BUTTON_RELEASE_MASK</property>
|
||||
<property name="label" translatable="yes">_Add tag</property>
|
||||
<property name="use_underline">True</property>
|
||||
<signal name="activate" handler="on_add_tag1_activate"/>
|
||||
</widget>
|
||||
</child>
|
||||
<child>
|
||||
<widget class="GtkMenuItem" id="delete_tag">
|
||||
<property name="visible">True</property>
|
||||
<property name="events">GDK_POINTER_MOTION_MASK | GDK_POINTER_MOTION_HINT_MASK | GDK_BUTTON_PRESS_MASK | GDK_BUTTON_RELEASE_MASK</property>
|
||||
<property name="label" translatable="yes">Remo_ve tag</property>
|
||||
<property name="use_underline">True</property>
|
||||
<signal name="activate" handler="on_delete_tag_activate"/>
|
||||
</widget>
|
||||
</child>
|
||||
<child>
|
||||
<widget class="GtkSeparatorMenuItem" id="separator11">
|
||||
<property name="visible">True</property>
|
||||
</widget>
|
||||
</child>
|
||||
<child>
|
||||
<widget class="GtkMenuItem" id="add_thumb1">
|
||||
<property name="visible">True</property>
|
||||
<property name="events">GDK_POINTER_MOTION_MASK | GDK_POINTER_MOTION_HINT_MASK | GDK_BUTTON_PRESS_MASK | GDK_BUTTON_RELEASE_MASK</property>
|
||||
<property name="label" translatable="yes">Add _Thumbnail</property>
|
||||
<property name="use_underline">True</property>
|
||||
<signal name="activate" handler="on_add_thumb1_activate"/>
|
||||
</widget>
|
||||
</child>
|
||||
<child>
|
||||
<widget class="GtkMenuItem" id="remove_thumb1">
|
||||
<property name="visible">True</property>
|
||||
<property name="events">GDK_POINTER_MOTION_MASK | GDK_POINTER_MOTION_HINT_MASK | GDK_BUTTON_PRESS_MASK | GDK_BUTTON_RELEASE_MASK</property>
|
||||
<property name="label" translatable="yes">Re_move Thumbnail</property>
|
||||
<property name="use_underline">True</property>
|
||||
<signal name="activate" handler="on_remove_thumb1_activate"/>
|
||||
</widget>
|
||||
</child>
|
||||
<child>
|
||||
<widget class="GtkSeparatorMenuItem" id="separator7">
|
||||
<property name="visible">True</property>
|
||||
</widget>
|
||||
</child>
|
||||
<child>
|
||||
<widget class="GtkMenuItem" id="add_image1">
|
||||
<property name="visible">True</property>
|
||||
<property name="events">GDK_POINTER_MOTION_MASK | GDK_POINTER_MOTION_HINT_MASK | GDK_BUTTON_PRESS_MASK | GDK_BUTTON_RELEASE_MASK</property>
|
||||
<property name="tooltip" translatable="yes">Add images to file. If file have no thumbnail,
|
||||
thumbnail from first image will be generated.</property>
|
||||
<property name="label" translatable="yes">Add _Images</property>
|
||||
<property name="use_underline">True</property>
|
||||
<signal name="activate" handler="on_add_image1_activate"/>
|
||||
</widget>
|
||||
</child>
|
||||
<child>
|
||||
<widget class="GtkMenuItem" id="remove_image1">
|
||||
<property name="visible">True</property>
|
||||
<property name="events">GDK_POINTER_MOTION_MASK | GDK_POINTER_MOTION_HINT_MASK | GDK_BUTTON_PRESS_MASK | GDK_BUTTON_RELEASE_MASK</property>
|
||||
<property name="label" translatable="yes">Rem_ove All Images</property>
|
||||
<property name="use_underline">True</property>
|
||||
<signal name="activate" handler="on_remove_image1_activate"/>
|
||||
</widget>
|
||||
</child>
|
||||
<child>
|
||||
<widget class="GtkSeparatorMenuItem" id="separator8">
|
||||
<property name="visible">True</property>
|
||||
</widget>
|
||||
</child>
|
||||
<child>
|
||||
<widget class="GtkMenuItem" id="edit2">
|
||||
<property name="visible">True</property>
|
||||
<property name="events">GDK_POINTER_MOTION_MASK | GDK_POINTER_MOTION_HINT_MASK | GDK_BUTTON_PRESS_MASK | GDK_BUTTON_RELEASE_MASK</property>
|
||||
<property name="label" translatable="yes">_Edit</property>
|
||||
<property name="use_underline">True</property>
|
||||
<signal name="activate" handler="on_edit2_activate"/>
|
||||
</widget>
|
||||
</child>
|
||||
<child>
|
||||
<widget class="GtkMenuItem" id="delete3">
|
||||
<property name="visible">True</property>
|
||||
<property name="events">GDK_POINTER_MOTION_MASK | GDK_POINTER_MOTION_HINT_MASK | GDK_BUTTON_PRESS_MASK | GDK_BUTTON_RELEASE_MASK</property>
|
||||
<property name="label" translatable="yes">_Delete</property>
|
||||
<property name="use_underline">True</property>
|
||||
<signal name="activate" handler="on_delete3_activate"/>
|
||||
</widget>
|
||||
</child>
|
||||
<child>
|
||||
<widget class="GtkMenuItem" id="rename2">
|
||||
<property name="visible">True</property>
|
||||
<property name="events">GDK_POINTER_MOTION_MASK | GDK_POINTER_MOTION_HINT_MASK | GDK_BUTTON_PRESS_MASK | GDK_BUTTON_RELEASE_MASK</property>
|
||||
<property name="label" translatable="yes">_Rename</property>
|
||||
<property name="use_underline">True</property>
|
||||
<signal name="activate" handler="on_rename2_activate"/>
|
||||
</widget>
|
||||
</child>
|
||||
</widget>
|
||||
<widget class="GtkWindow" id="image_show">
|
||||
<property name="events">GDK_POINTER_MOTION_MASK | GDK_POINTER_MOTION_HINT_MASK | GDK_BUTTON_PRESS_MASK | GDK_BUTTON_RELEASE_MASK</property>
|
||||
<property name="title" translatable="yes">pyGTKtalog - Image</property>
|
||||
22
pygtktalog/views/glade/tagcloud.glade
Normal file
22
pygtktalog/views/glade/tagcloud.glade
Normal file
@@ -0,0 +1,22 @@
|
||||
<?xml version="1.0"?>
|
||||
<glade-interface>
|
||||
<!-- interface-requires gtk+ 2.6 -->
|
||||
<!-- interface-naming-policy toplevel-contextual -->
|
||||
<widget class="GtkWindow" id="top_tags">
|
||||
<child>
|
||||
<widget class="GtkTextView" id="tag_cloud_textview">
|
||||
<property name="visible">True</property>
|
||||
<property name="can_focus">True</property>
|
||||
<property name="events">GDK_POINTER_MOTION_MASK | GDK_POINTER_MOTION_HINT_MASK | GDK_BUTTON_PRESS_MASK | GDK_BUTTON_RELEASE_MASK</property>
|
||||
<property name="wrap_mode">word</property>
|
||||
<property name="cursor_visible">False</property>
|
||||
<signal name="visibility_notify_event" handler="on_tag_cloud_textview_visibility_notify_event"/>
|
||||
<signal name="drag_motion" handler="on_tag_cloud_textview_drag_motion"/>
|
||||
<signal name="event_after" handler="on_tag_cloud_textview_event_after"/>
|
||||
<signal name="drag_drop" handler="on_tag_cloud_textview_drag_drop"/>
|
||||
<signal name="drag_leave" handler="on_tag_cloud_textview_drag_leave"/>
|
||||
<signal name="drag_data_received" handler="on_tag_cloud_textview_drag_data_received"/>
|
||||
</widget>
|
||||
</child>
|
||||
</widget>
|
||||
</glade-interface>
|
||||
210
pygtktalog/views/glade/test.glade
Normal file
210
pygtktalog/views/glade/test.glade
Normal file
@@ -0,0 +1,210 @@
|
||||
<?xml version="1.0" encoding="UTF-8" standalone="no"?>
|
||||
<!DOCTYPE glade-interface SYSTEM "glade-2.0.dtd">
|
||||
<!--Generated with glade3 3.4.5 on Fri Oct 23 18:28:53 2009 -->
|
||||
<glade-interface>
|
||||
<widget class="GtkWindow" id="window1">
|
||||
<child>
|
||||
<widget class="GtkVBox" id="vbox1">
|
||||
<property name="visible">True</property>
|
||||
<property name="orientation">GTK_ORIENTATION_VERTICAL</property>
|
||||
<property name="orientation">GTK_ORIENTATION_VERTICAL</property>
|
||||
<child>
|
||||
<widget class="GtkMenuBar" id="menubar1">
|
||||
<property name="visible">True</property>
|
||||
<child>
|
||||
<widget class="GtkMenuItem" id="menuitem1">
|
||||
<property name="visible">True</property>
|
||||
<property name="label" translatable="yes">_File</property>
|
||||
<property name="use_underline">True</property>
|
||||
<child>
|
||||
<widget class="GtkMenu" id="menu1">
|
||||
<property name="visible">True</property>
|
||||
<child>
|
||||
<widget class="GtkImageMenuItem" id="imagemenuitem1">
|
||||
<property name="visible">True</property>
|
||||
<property name="label" translatable="yes">gtk-new</property>
|
||||
<property name="use_underline">True</property>
|
||||
<property name="use_stock">True</property>
|
||||
</widget>
|
||||
</child>
|
||||
<child>
|
||||
<widget class="GtkImageMenuItem" id="imagemenuitem2">
|
||||
<property name="visible">True</property>
|
||||
<property name="label" translatable="yes">gtk-open</property>
|
||||
<property name="use_underline">True</property>
|
||||
<property name="use_stock">True</property>
|
||||
</widget>
|
||||
</child>
|
||||
<child>
|
||||
<widget class="GtkImageMenuItem" id="imagemenuitem3">
|
||||
<property name="visible">True</property>
|
||||
<property name="label" translatable="yes">gtk-save</property>
|
||||
<property name="use_underline">True</property>
|
||||
<property name="use_stock">True</property>
|
||||
</widget>
|
||||
</child>
|
||||
<child>
|
||||
<widget class="GtkImageMenuItem" id="imagemenuitem4">
|
||||
<property name="visible">True</property>
|
||||
<property name="label" translatable="yes">gtk-save-as</property>
|
||||
<property name="use_underline">True</property>
|
||||
<property name="use_stock">True</property>
|
||||
</widget>
|
||||
</child>
|
||||
<child>
|
||||
<widget class="GtkSeparatorMenuItem" id="separatormenuitem1">
|
||||
<property name="visible">True</property>
|
||||
</widget>
|
||||
</child>
|
||||
<child>
|
||||
<widget class="GtkImageMenuItem" id="imagemenuitem5">
|
||||
<property name="visible">True</property>
|
||||
<property name="label" translatable="yes">gtk-quit</property>
|
||||
<property name="use_underline">True</property>
|
||||
<property name="use_stock">True</property>
|
||||
</widget>
|
||||
</child>
|
||||
</widget>
|
||||
</child>
|
||||
</widget>
|
||||
</child>
|
||||
<child>
|
||||
<widget class="GtkMenuItem" id="menuitem2">
|
||||
<property name="visible">True</property>
|
||||
<property name="label" translatable="yes">_Edit</property>
|
||||
<property name="use_underline">True</property>
|
||||
<child>
|
||||
<widget class="GtkMenu" id="menu2">
|
||||
<property name="visible">True</property>
|
||||
<child>
|
||||
<widget class="GtkImageMenuItem" id="imagemenuitem6">
|
||||
<property name="visible">True</property>
|
||||
<property name="label" translatable="yes">gtk-cut</property>
|
||||
<property name="use_underline">True</property>
|
||||
<property name="use_stock">True</property>
|
||||
</widget>
|
||||
</child>
|
||||
<child>
|
||||
<widget class="GtkImageMenuItem" id="imagemenuitem7">
|
||||
<property name="visible">True</property>
|
||||
<property name="label" translatable="yes">gtk-copy</property>
|
||||
<property name="use_underline">True</property>
|
||||
<property name="use_stock">True</property>
|
||||
</widget>
|
||||
</child>
|
||||
<child>
|
||||
<widget class="GtkImageMenuItem" id="imagemenuitem8">
|
||||
<property name="visible">True</property>
|
||||
<property name="label" translatable="yes">gtk-paste</property>
|
||||
<property name="use_underline">True</property>
|
||||
<property name="use_stock">True</property>
|
||||
</widget>
|
||||
</child>
|
||||
<child>
|
||||
<widget class="GtkImageMenuItem" id="imagemenuitem9">
|
||||
<property name="visible">True</property>
|
||||
<property name="label" translatable="yes">gtk-delete</property>
|
||||
<property name="use_underline">True</property>
|
||||
<property name="use_stock">True</property>
|
||||
</widget>
|
||||
</child>
|
||||
</widget>
|
||||
</child>
|
||||
</widget>
|
||||
</child>
|
||||
<child>
|
||||
<widget class="GtkMenuItem" id="menuitem3">
|
||||
<property name="visible">True</property>
|
||||
<property name="label" translatable="yes">_View</property>
|
||||
<property name="use_underline">True</property>
|
||||
</widget>
|
||||
</child>
|
||||
<child>
|
||||
<widget class="GtkMenuItem" id="menuitem4">
|
||||
<property name="visible">True</property>
|
||||
<property name="label" translatable="yes">_Help</property>
|
||||
<property name="use_underline">True</property>
|
||||
<child>
|
||||
<widget class="GtkMenu" id="menu3">
|
||||
<property name="visible">True</property>
|
||||
<child>
|
||||
<widget class="GtkImageMenuItem" id="imagemenuitem10">
|
||||
<property name="visible">True</property>
|
||||
<property name="label" translatable="yes">gtk-about</property>
|
||||
<property name="use_underline">True</property>
|
||||
<property name="use_stock">True</property>
|
||||
</widget>
|
||||
</child>
|
||||
</widget>
|
||||
</child>
|
||||
</widget>
|
||||
</child>
|
||||
</widget>
|
||||
<packing>
|
||||
<property name="expand">False</property>
|
||||
</packing>
|
||||
</child>
|
||||
<child>
|
||||
<widget class="GtkHPaned" id="hpaned1">
|
||||
<property name="visible">True</property>
|
||||
<property name="can_focus">True</property>
|
||||
<child>
|
||||
<widget class="GtkViewport" id="viewport1">
|
||||
<property name="visible">True</property>
|
||||
<property name="resize_mode">GTK_RESIZE_QUEUE</property>
|
||||
<child>
|
||||
<widget class="GtkTreeView" id="treeview1">
|
||||
<property name="visible">True</property>
|
||||
<property name="can_focus">True</property>
|
||||
</widget>
|
||||
</child>
|
||||
</widget>
|
||||
<packing>
|
||||
<property name="resize">False</property>
|
||||
<property name="shrink">True</property>
|
||||
</packing>
|
||||
</child>
|
||||
<child>
|
||||
<widget class="GtkTextView" id="textview1">
|
||||
<property name="visible">True</property>
|
||||
<property name="can_focus">True</property>
|
||||
</widget>
|
||||
<packing>
|
||||
<property name="resize">True</property>
|
||||
<property name="shrink">True</property>
|
||||
</packing>
|
||||
</child>
|
||||
</widget>
|
||||
<packing>
|
||||
<property name="position">1</property>
|
||||
</packing>
|
||||
</child>
|
||||
<child>
|
||||
<widget class="GtkHBox" id="hbox2">
|
||||
<property name="visible">True</property>
|
||||
<child>
|
||||
<widget class="GtkStatusbar" id="statusbar1">
|
||||
<property name="visible">True</property>
|
||||
<property name="spacing">2</property>
|
||||
<property name="has_resize_grip">False</property>
|
||||
</widget>
|
||||
</child>
|
||||
<child>
|
||||
<widget class="GtkProgressBar" id="progressbar1">
|
||||
<property name="visible">True</property>
|
||||
</widget>
|
||||
<packing>
|
||||
<property name="expand">False</property>
|
||||
<property name="position">1</property>
|
||||
</packing>
|
||||
</child>
|
||||
</widget>
|
||||
<packing>
|
||||
<property name="expand">False</property>
|
||||
<property name="position">2</property>
|
||||
</packing>
|
||||
</child>
|
||||
</widget>
|
||||
</child>
|
||||
</widget>
|
||||
</glade-interface>
|
||||
189
pygtktalog/views/main.py
Normal file
189
pygtktalog/views/main.py
Normal file
@@ -0,0 +1,189 @@
|
||||
"""
|
||||
Project: pyGTKtalog
|
||||
Description: View for main window
|
||||
Type: core
|
||||
Author: Roman 'gryf' Dobosz, gryf73@gmail.com
|
||||
Created: 2009-05-02
|
||||
"""
|
||||
import os.path
|
||||
|
||||
from gtkmvc import View
|
||||
|
||||
|
||||
def get_glade(glade_filename):
|
||||
"""
|
||||
Return full path to specified glade file
|
||||
"""
|
||||
return os.path.join(os.path.dirname(__file__), "glade", glade_filename)
|
||||
|
||||
|
||||
class MainView(View):
|
||||
"""
|
||||
Create window from glade file. Note, that glade file is placed in view
|
||||
module under glade subdirectory.
|
||||
"""
|
||||
glade = get_glade("main.glade")
|
||||
top = "main"
|
||||
|
||||
def __init__(self, top="main"):
|
||||
"""
|
||||
Initialize view
|
||||
"""
|
||||
View.__init__(self)
|
||||
self.app_sensitive = None
|
||||
self['tag_path_box'].hide()
|
||||
|
||||
self.discs = DiscsView()
|
||||
#self['scrolledwindow_discs'].add_with_viewport(\
|
||||
# self.discs.get_top_widget())
|
||||
self['scrolledwindow_discs'].add(self.discs.get_top_widget())
|
||||
|
||||
self.files = FilesView()
|
||||
self['scrolledwindow_files'].add_with_viewport(\
|
||||
self.files.get_top_widget())
|
||||
|
||||
self.details = DetailsView()
|
||||
self['vpaned1'].add2(self.details.get_top_widget())
|
||||
|
||||
def set_widgets_scan_sensitivity(self, sensitive=True):
|
||||
"""
|
||||
Activate/deactivate selected widgets while scanning is active
|
||||
"""
|
||||
pass
|
||||
|
||||
def set_widgets_app_sensitivity(self, sensitive=True):
|
||||
"""
|
||||
Enable/disable widgets for empty application. Usefull for first run
|
||||
of an application (without any db filename as an argument).
|
||||
"""
|
||||
if self.app_sensitive is sensitive:
|
||||
return
|
||||
|
||||
for widget in ['scrolledwindow_discs', 'scrolledwindow_files',
|
||||
'tb_save', 'tb_addcd', 'tb_adddir', 'tb_find',
|
||||
'edit1', 'catalog1', 'save1', 'save_as1', 'import',
|
||||
'export']:
|
||||
self[widget].set_sensitive(sensitive)
|
||||
|
||||
# widgets from subclasses
|
||||
self.details['notebook_details'].set_sensitive(sensitive)
|
||||
|
||||
|
||||
class DiscsView(View):
|
||||
"""
|
||||
Separate Discs TreeView subview.
|
||||
"""
|
||||
glade = get_glade("discs.glade")
|
||||
top = 'discs'
|
||||
|
||||
def __init__(self):
|
||||
"""
|
||||
Initialize view
|
||||
"""
|
||||
View.__init__(self)
|
||||
self.menu = DiscsPopupView()
|
||||
|
||||
|
||||
class DiscsPopupView(View):
|
||||
"""
|
||||
Separate Discs PopUp subview.
|
||||
"""
|
||||
glade = get_glade("discs.glade")
|
||||
top = 'discs_popup'
|
||||
|
||||
def __init__(self):
|
||||
"""
|
||||
Initialize view
|
||||
"""
|
||||
View.__init__(self)
|
||||
|
||||
def set_update_sensitivity(self, state):
|
||||
"""
|
||||
Set sensitivity for 'update' popup menu item
|
||||
Arguments:
|
||||
@state - Bool, if True update menu item will be sensitive,
|
||||
otherwise not
|
||||
"""
|
||||
self['update'].set_sensitive(state)
|
||||
|
||||
def set_menu_items_sensitivity(self, state):
|
||||
"""
|
||||
Set sensitivity for couple of popup menu items, which should be
|
||||
disabled if user right-clicks on no item in treeview.
|
||||
Arguments:
|
||||
@state - Bool, if True update menu item will be sensitive,
|
||||
otherwise not
|
||||
"""
|
||||
for item in ['update', 'rename', 'delete', 'statistics']:
|
||||
self[item].set_sensitive(state)
|
||||
|
||||
|
||||
class FilesView(View):
|
||||
"""
|
||||
Separate subview of Files TreeView as a table.
|
||||
"""
|
||||
glade = get_glade("files.glade")
|
||||
top = 'files'
|
||||
|
||||
def __init__(self):
|
||||
"""
|
||||
Initialize view
|
||||
"""
|
||||
View.__init__(self)
|
||||
self.menu = FilesPopupView()
|
||||
|
||||
|
||||
class FilesPopupView(View):
|
||||
"""
|
||||
Separate Files PopUp subview.
|
||||
"""
|
||||
glade = get_glade("files.glade")
|
||||
top = 'files_popup'
|
||||
|
||||
def __init__(self):
|
||||
"""
|
||||
Initialize view
|
||||
"""
|
||||
View.__init__(self)
|
||||
|
||||
def set_menu_items_sensitivity(self, state):
|
||||
"""
|
||||
Set sensitivity for couple of popup menu items, which should be
|
||||
disabled if user right-clicks on no item in treeview.
|
||||
Arguments:
|
||||
@state - Bool, if True update menu item will be sensitive,
|
||||
otherwise not
|
||||
"""
|
||||
for item in ["add_tag", "delete_tag", "add_thumb", "remove_thumb",
|
||||
"add_image", "remove_image", "edit", "delete", "rename"]:
|
||||
self[item].set_sensitive(state)
|
||||
|
||||
|
||||
class TagcloudView(View):
|
||||
"""
|
||||
Textview subview with clickable tags.
|
||||
"""
|
||||
glade = get_glade("tagcloud.glade")
|
||||
top = 'tag_cloud_textview'
|
||||
|
||||
def __init__(self):
|
||||
"""
|
||||
Initialize view
|
||||
"""
|
||||
View.__init__(self)
|
||||
|
||||
|
||||
class DetailsView(View):
|
||||
"""
|
||||
Notebook subview containing tabs with details and possibly Exif, images
|
||||
assocated with object and alternatively thumbnail.
|
||||
"""
|
||||
glade = get_glade("details.glade")
|
||||
top = 'notebook_details'
|
||||
|
||||
def __init__(self):
|
||||
"""
|
||||
Initialize view
|
||||
"""
|
||||
View.__init__(self)
|
||||
|
||||
36
pygtktalog/views/main_menu.py
Normal file
36
pygtktalog/views/main_menu.py
Normal file
@@ -0,0 +1,36 @@
|
||||
"""
|
||||
Project: pyGTKtalog
|
||||
Description: Menu for the main window
|
||||
Type: interface
|
||||
Author: Roman 'gryf' Dobosz, gryf73@gmail.com
|
||||
Created: 2010-03-14 21:31:57
|
||||
"""
|
||||
|
||||
import gtk
|
||||
|
||||
from gtkmvc import View
|
||||
|
||||
|
||||
class MainMenu(View):
|
||||
def __init__(self):
|
||||
View.__init__(self)
|
||||
self['mainMenu'] = gtk.MenuBar()
|
||||
|
||||
self['file_menu'] = gtk.MenuItem(_("_File"))
|
||||
|
||||
accel_group = gtk.AccelGroup()
|
||||
menu_items = (("/_File", None, None, 0, "<Branch>"),
|
||||
("/File/_New", "<control>N", None, 0, None),
|
||||
("/File/_Open", "<control>O", None, 0, None),
|
||||
("/File/_Save", "<control>S", None, 0, None),
|
||||
("/File/Save _As", None, None, 0, None),
|
||||
("/File/sep1", None, None, 0, "<Separator>"),
|
||||
("/File/Quit", "<control>Q", gtk.main_quit, 0, None),
|
||||
("/_Options", None, None, 0, "<Branch>"),
|
||||
("/Options/Test", None, None, 0, None),
|
||||
("/_Help", None, None, 0, "<LastBranch>"),
|
||||
("/_Help/About", None, None, 0, None),)
|
||||
item_factory = gtk.ItemFactory(gtk.MenuBar, "<main>", accel_group)
|
||||
item_factory.create_items(menu_items)
|
||||
|
||||
|
||||
18
pygtktalog/views/main_toolbar.py
Normal file
18
pygtktalog/views/main_toolbar.py
Normal file
@@ -0,0 +1,18 @@
|
||||
"""
|
||||
Project: pyGTKtalog
|
||||
Description: Toolbar for the main window
|
||||
Type: interface
|
||||
Author: Roman 'gryf' Dobosz, gryf73@gmail.com
|
||||
Created: 2010-04-20 18:47:49
|
||||
"""
|
||||
|
||||
import gtk
|
||||
|
||||
from gtkmvc import View
|
||||
|
||||
|
||||
class ToolBar(View):
|
||||
def __init__(self):
|
||||
View.__init__(self)
|
||||
self['maintoolbar'] = gtk.Toolbar()
|
||||
|
||||
@@ -22,27 +22,26 @@
|
||||
|
||||
# -------------------------------------------------------------------------
|
||||
|
||||
__version__ = "1.0 RC2"
|
||||
LICENCE = open('LICENCE').read()
|
||||
|
||||
import os.path
|
||||
from os import popen
|
||||
from utils import deviceHelper
|
||||
from gtkmvc import Controller
|
||||
|
||||
from c_config import ConfigController
|
||||
from views.v_config import ConfigView
|
||||
|
||||
from c_search import SearchController
|
||||
from views.v_search import SearchView
|
||||
|
||||
import views.v_dialogs as Dialogs
|
||||
|
||||
from views.v_image import ImageView
|
||||
|
||||
import gtk
|
||||
import pango
|
||||
|
||||
from gtkmvc import Controller
|
||||
|
||||
from lib import device_helper
|
||||
from lib.globs import APPL_VERSION
|
||||
from c_config import ConfigController
|
||||
from views.v_config import ConfigView
|
||||
from c_search import SearchController
|
||||
from views.v_search import SearchView
|
||||
import views.v_dialogs as Dialogs
|
||||
from views.v_image import ImageView
|
||||
|
||||
|
||||
|
||||
class MainController(Controller):
|
||||
"""Controller for main application window"""
|
||||
scan_cd = False
|
||||
@@ -253,7 +252,7 @@ class MainController(Controller):
|
||||
except TypeError:
|
||||
if __debug__:
|
||||
print "c_main.py: on_edit2_activate(): 0",
|
||||
print "zaznaczonych wierszy"
|
||||
print "selected rows"
|
||||
return
|
||||
|
||||
val = self.model.get_file_info(id)
|
||||
@@ -282,11 +281,9 @@ class MainController(Controller):
|
||||
|
||||
def on_remove_thumb1_activate(self, menu_item):
|
||||
if self.model.config.confd['delwarn']:
|
||||
title = 'Delete thumbnails'
|
||||
question = 'Delete thumbnails?'
|
||||
description = "Thumbnails for selected items will be permanently"
|
||||
description += " removed from catalog."
|
||||
obj = Dialogs.Qst(title, question, description)
|
||||
obj = Dialogs.Qst(_("Delete thumbnails"), _("Delete thumbnails?"),
|
||||
_("Thumbnails for selected items will be "
|
||||
"permanently removed from catalog."))
|
||||
if not obj.run():
|
||||
return
|
||||
try:
|
||||
@@ -308,11 +305,9 @@ class MainController(Controller):
|
||||
|
||||
def on_remove_image1_activate(self, menu_item):
|
||||
if self.model.config.confd['delwarn']:
|
||||
title = 'Delete images'
|
||||
question = 'Delete all images?'
|
||||
description = 'All images for selected items will be permanently'
|
||||
description += ' removed from catalog.'
|
||||
obj = Dialogs.Qst(title, question, description)
|
||||
obj = Dialogs.Qst(_("Delete images"), _("Delete all images?"),
|
||||
_("All images for selected items will be "
|
||||
"permanently removed from catalog."))
|
||||
if not obj.run():
|
||||
return
|
||||
try:
|
||||
@@ -344,8 +339,8 @@ class MainController(Controller):
|
||||
else:
|
||||
ImageView(img)
|
||||
else:
|
||||
Dialogs.Inf("Image view", "No Image",
|
||||
"Image file does not exist.")
|
||||
Dialogs.Inf(_("Image view"), _("No Image"),
|
||||
_("Image file does not exist."))
|
||||
|
||||
def on_rename1_activate(self, widget):
|
||||
model, iter = self.view['discs'].get_selection().get_selected()
|
||||
@@ -493,11 +488,10 @@ class MainController(Controller):
|
||||
# check if any unsaved project is on go.
|
||||
if self.model.unsaved_project and \
|
||||
self.model.config.confd['confirmquit']:
|
||||
title = 'Quit application - pyGTKtalog'
|
||||
question = 'Do you really want to quit?'
|
||||
description = "Current database is not saved, any changes will "
|
||||
description += "be lost."
|
||||
if not Dialogs.Qst(title, question, description).run():
|
||||
if not Dialogs.Qst(_("Quit application") + " - pyGTKtalog",
|
||||
_("Do you really want to quit?"),
|
||||
_("Current database is not saved, any changes "
|
||||
"will be lost.")).run():
|
||||
return
|
||||
self.__store_settings()
|
||||
self.model.cleanup()
|
||||
@@ -507,10 +501,10 @@ class MainController(Controller):
|
||||
def on_new_activate(self, widget):
|
||||
"""Create new database file"""
|
||||
if self.model.unsaved_project:
|
||||
title = 'Unsaved data - pyGTKtalog'
|
||||
question = "Do you want to abandon changes?"
|
||||
desc = "Current database is not saved, any changes will be lost."
|
||||
if not Dialogs.Qst(title, question, desc).run():
|
||||
if not Dialogs.Qst(_("Unsaved data") + " - pyGTKtalog",
|
||||
_("Do you want to abandon changes?"),
|
||||
_("Current database is not saved, any changes "
|
||||
"will be lost.")).run():
|
||||
return
|
||||
self.model.new()
|
||||
|
||||
@@ -526,9 +520,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'])
|
||||
if mount == 'ok':
|
||||
guessed_label = deviceHelper.volname(self.model.config.confd['cd'])
|
||||
mount, msg = device_helper.volmount(self.model.config.confd['cd'])
|
||||
if mount:
|
||||
guessed_label = device_helper.volname(self.model.config.confd['cd'])
|
||||
if not label:
|
||||
label = Dialogs.InputDiskLabel(guessed_label).run()
|
||||
if label:
|
||||
@@ -541,13 +535,13 @@ 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",
|
||||
"Cannot mount device pointed to %s" %
|
||||
Dialogs.Wrn(_("Error mounting device") + " - pyGTKtalog",
|
||||
_("Cannot mount device pointed to %s") %
|
||||
self.model.config.confd['cd'],
|
||||
"Last mount message:\n%s" % mount)
|
||||
_("Last mount message:\n%s") % msg)
|
||||
return False
|
||||
|
||||
def on_add_directory_activate(self, widget, path=None, label=None,
|
||||
@@ -571,7 +565,7 @@ class MainController(Controller):
|
||||
# NOTE: about
|
||||
def on_about1_activate(self, widget):
|
||||
"""Show about dialog"""
|
||||
Dialogs.Abt("pyGTKtalog", __version__, "About",
|
||||
Dialogs.Abt("pyGTKtalog", "%d.%d.%d" % APPL_VERSION, "About",
|
||||
["Roman 'gryf' Dobosz"], LICENCE)
|
||||
return
|
||||
|
||||
@@ -600,6 +594,7 @@ class MainController(Controller):
|
||||
|
||||
def on_save_activate(self, widget):
|
||||
"""Save catalog to file"""
|
||||
# FIXME: Traceback when recent is null
|
||||
if self.model.filename:
|
||||
self.model.save()
|
||||
self.__set_title(filepath=self.model.filename)
|
||||
@@ -619,8 +614,8 @@ class MainController(Controller):
|
||||
self.model.config.add_recent(path)
|
||||
self.__set_title(filepath=path)
|
||||
else:
|
||||
Dialogs.Err("Error writing file - pyGTKtalog",
|
||||
"Cannot write file %s." % path, "%s" % err)
|
||||
Dialogs.Err(_("Error writing file") + " - pyGTKtalog",
|
||||
_("Cannot write file %s.") % path, "%s" % err)
|
||||
|
||||
def on_stat1_activate(self, menu_item, selected_id=None):
|
||||
data = self.model.get_stats(selected_id)
|
||||
@@ -641,9 +636,9 @@ class MainController(Controller):
|
||||
"""Open catalog file"""
|
||||
confirm = self.model.config.confd['confirmabandon']
|
||||
if self.model.unsaved_project and confirm:
|
||||
obj = Dialogs.Qst('Unsaved data - pyGTKtalog',
|
||||
'There is not saved database',
|
||||
'Pressing "Ok" will abandon catalog.')
|
||||
obj = Dialogs.Qst(_("Unsaved data") + " - pyGTKtalog",
|
||||
_("There is not saved database"),
|
||||
_("Pressing <b>Ok</b> will abandon catalog."))
|
||||
if not obj.run():
|
||||
return
|
||||
|
||||
@@ -667,8 +662,8 @@ class MainController(Controller):
|
||||
|
||||
if path:
|
||||
if not self.model.open(path):
|
||||
Dialogs.Err("Error opening file - pyGTKtalog",
|
||||
"Cannot open file %s." % path)
|
||||
Dialogs.Err(_("Error opening file") + " - pyGTKtalog",
|
||||
_("Cannot open file %s.") % path)
|
||||
else:
|
||||
self.__generate_recent_menu()
|
||||
self.__activate_ui(path)
|
||||
@@ -734,13 +729,14 @@ class MainController(Controller):
|
||||
"""delete selected images (with thumbnails)"""
|
||||
list_of_paths = self.view['images'].get_selected_items()
|
||||
if not list_of_paths:
|
||||
Dialogs.Inf("Delete images", "No images selected",
|
||||
"You have to select at least one image to delete.")
|
||||
Dialogs.Inf(_("Delete images"), _("No images selected"),
|
||||
_("You have to select at least one image to delete."))
|
||||
return
|
||||
|
||||
if self.model.config.confd['delwarn']:
|
||||
obj = Dialogs.Qst('Delete images', 'Delete selected images?',
|
||||
'Selected images will be permanently removed from catalog.')
|
||||
obj = Dialogs.Qst(_("Delete images"), _("Delete selected images?"),
|
||||
_("Selected images will be permanently removed"
|
||||
" from catalog."))
|
||||
if not obj.run():
|
||||
return
|
||||
|
||||
@@ -769,7 +765,7 @@ class MainController(Controller):
|
||||
|
||||
def on_img_save_activate(self, menu_item):
|
||||
"""export images (not thumbnails) into desired direcotry"""
|
||||
dialog = Dialogs.SelectDirectory("Choose directory to save images")
|
||||
dialog = Dialogs.SelectDirectory(_("Choose directory to save images"))
|
||||
filepath = dialog.run()
|
||||
|
||||
if not filepath:
|
||||
@@ -794,14 +790,13 @@ class MainController(Controller):
|
||||
count += 1
|
||||
|
||||
if count > 0:
|
||||
Dialogs.Inf("Save images",
|
||||
"%d images was succsefully saved." % count,
|
||||
"Images are placed in directory:\n%s." % filepath)
|
||||
Dialogs.Inf(_("Save images"),
|
||||
_("%d images was succsefully saved.") % count,
|
||||
_("Images are placed in directory:\n%s.") % filepath)
|
||||
else:
|
||||
description = "Images probably don't have real images - only"
|
||||
description += " thumbnails."
|
||||
Dialogs.Inf("Save images",
|
||||
"No images was saved.",
|
||||
description = _("Images probably don't have real images - only"
|
||||
" thumbnails.")
|
||||
Dialogs.Inf(_("Save images"), _("No images was saved."),
|
||||
description)
|
||||
return
|
||||
|
||||
@@ -810,12 +805,12 @@ class MainController(Controller):
|
||||
list_of_paths = self.view['images'].get_selected_items()
|
||||
|
||||
if not list_of_paths:
|
||||
Dialogs.Inf("Set thumbnail", "No image selected",
|
||||
"You have to select one image to set as thumbnail.")
|
||||
Dialogs.Inf(_("Set thumbnail"), _("No image selected"),
|
||||
_("You have to select one image to set as thumbnail."))
|
||||
return
|
||||
if len(list_of_paths) >1:
|
||||
Dialogs.Inf("Set thumbnail", "To many images selected",
|
||||
"You have to select one image to set as thumbnail.")
|
||||
Dialogs.Inf(_("Set thumbnail"), _("To many images selected"),
|
||||
_("You have to select one image to set as thumbnail."))
|
||||
return
|
||||
|
||||
model = self.view['images'].get_model()
|
||||
@@ -867,9 +862,15 @@ class MainController(Controller):
|
||||
|
||||
self.__popup_menu(event)
|
||||
|
||||
def on_expand_all1_activate(self, menu_item):
|
||||
self.view['discs'].expand_all()
|
||||
return
|
||||
def on_export_activate(self, menu_item):
|
||||
"""export db file and coressponding images to tar.bz2 archive"""
|
||||
dialog = Dialogs.ChooseFilename(None, _("Choose export file"))
|
||||
filepath = dialog.run()
|
||||
|
||||
if not filepath:
|
||||
return
|
||||
|
||||
# TODO: dokończyć ten shit
|
||||
|
||||
def on_collapse_all1_activate(self, menu_item):
|
||||
self.view['discs'].collapse_all()
|
||||
@@ -1009,8 +1010,8 @@ class MainController(Controller):
|
||||
def on_delete_tag_activate(self, menu_item):
|
||||
ids = self.__get_tv_selection_ids(self.view['files'])
|
||||
if not ids:
|
||||
Dialogs.Inf("Remove tags", "No files selected",
|
||||
"You have to select some files first.")
|
||||
Dialogs.Inf(_("Remove tags"), _("No files selected"),
|
||||
_("You have to select some files first."))
|
||||
return
|
||||
|
||||
tags = self.model.get_tags_by_file_id(ids)
|
||||
@@ -1018,8 +1019,9 @@ class MainController(Controller):
|
||||
d = Dialogs.TagsRemoveDialog(tags)
|
||||
retcode, retval = d.run()
|
||||
if retcode=="ok" and not retval:
|
||||
Dialogs.Inf("Remove tags", "No tags selected",
|
||||
"You have to select any tag to remove from files.")
|
||||
Dialogs.Inf(_("Remove tags"), _("No tags selected"),
|
||||
_("You have to select any tag to remove from"
|
||||
" files."))
|
||||
return
|
||||
elif retcode == "ok" and retval:
|
||||
self.model.delete_tags(ids, retval)
|
||||
@@ -1044,7 +1046,7 @@ class MainController(Controller):
|
||||
|
||||
def on_add_image1_activate(self, menu_item):
|
||||
dialog = Dialogs.LoadImageFile(True)
|
||||
msg = "Don't copy images. Generate only thumbnails."
|
||||
msg = _("Don't copy images. Generate only thumbnails.")
|
||||
toggle = gtk.CheckButton(msg)
|
||||
toggle.show()
|
||||
dialog.dialog.set_extra_widget(toggle)
|
||||
@@ -1108,15 +1110,15 @@ class MainController(Controller):
|
||||
return
|
||||
|
||||
if not selected_iter:
|
||||
Dialogs.Inf("Delete disc", "No disc selected",
|
||||
"You have to select disc first before you " +\
|
||||
"can delete it")
|
||||
Dialogs.Inf(_("Delete disc"), _("No disc selected"),
|
||||
_("You have to select disc first before you "
|
||||
"can delete it"))
|
||||
return
|
||||
|
||||
if self.model.config.confd['delwarn']:
|
||||
name = model.get_value(selected_iter, 1)
|
||||
obj = Dialogs.Qst('Delete %s' % name, 'Delete %s?' % name,
|
||||
'Object will be permanently removed.')
|
||||
obj = Dialogs.Qst(_("Delete %s") % name, _("Delete %s?") % name,
|
||||
_("Object will be permanently removed."))
|
||||
if not obj.run():
|
||||
return
|
||||
|
||||
@@ -1159,14 +1161,14 @@ class MainController(Controller):
|
||||
return
|
||||
|
||||
if not list_of_paths:
|
||||
Dialogs.Inf("Delete files", "No files selected",
|
||||
"You have to select at least one file to delete.")
|
||||
Dialogs.Inf(_("Delete files"), _("No files selected"),
|
||||
_("You have to select at least one file to delete."))
|
||||
return
|
||||
|
||||
if self.model.config.confd['delwarn']:
|
||||
description = "Selected files and directories will be "
|
||||
description += "permanently\n removed from catalog."
|
||||
obj = Dialogs.Qst("Delete files", "Delete files?", description)
|
||||
obj = Dialogs.Qst(_("Delete files"), _("Delete files?"),
|
||||
_("Selected files and directories will be "
|
||||
"permanently\n removed from catalog."))
|
||||
if not obj.run():
|
||||
return
|
||||
|
||||
@@ -1207,10 +1209,9 @@ class MainController(Controller):
|
||||
|
||||
def on_th_delete_activate(self, menu_item):
|
||||
if self.model.config.confd['delwarn']:
|
||||
title = 'Delete thumbnail'
|
||||
question = 'Delete thumbnail?'
|
||||
dsc = "Current thumbnail will be permanently removed from catalog."
|
||||
obj = Dialogs.Qst(title, question, dsc)
|
||||
obj = Dialogs.Qst(_("Delete thumbnail"), _("Delete thumbnail?"),
|
||||
_("Current thumbnail will be permanently removed"
|
||||
" from catalog."))
|
||||
if not obj.run():
|
||||
return
|
||||
path, column = self.view['files'].get_cursor()
|
||||
@@ -1354,20 +1355,22 @@ 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",
|
||||
"Cannot eject device pointed to %s" %
|
||||
title = _("Error ejecting device")
|
||||
Dialogs.Wrn(title + " - pyGTKtalog",
|
||||
_("Cannot eject device pointed to %s") %
|
||||
self.model.config.confd['cd'],
|
||||
"Last eject message:\n%s" % msg)
|
||||
_("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" %
|
||||
title = _("Error unmounting device")
|
||||
Dialogs.Wrn(title + " - pyGTKtalog",
|
||||
_("Cannot unmount device pointed to %s") %
|
||||
self.model.config.confd['cd'],
|
||||
"Last umount message:\n%s" % msg)
|
||||
_("Last umount message:\n%s") % msg)
|
||||
return
|
||||
|
||||
def property_progress_value_change(self, model, old, new):
|
||||
|
||||
86
src/lib/device_helper.py
Normal file
86
src/lib/device_helper.py
Normal file
@@ -0,0 +1,86 @@
|
||||
"""
|
||||
Project: pyGTKtalog
|
||||
Description: Simple functions for device management.
|
||||
Type: lib
|
||||
Author: Roman 'gryf' Dobosz, gryf73@gmail.com
|
||||
Created: 2008-12-15
|
||||
"""
|
||||
import os
|
||||
import locale
|
||||
import gettext
|
||||
|
||||
from src.lib.globs import APPL_SHORT_NAME
|
||||
|
||||
locale.setlocale(locale.LC_ALL, '')
|
||||
gettext.install(APPL_SHORT_NAME, 'locale', unicode=True)
|
||||
|
||||
def volname(mntp):
|
||||
"""read volume name from cd/dvd"""
|
||||
dev = mountpoint_to_dev(mntp)
|
||||
label = None
|
||||
if dev != None:
|
||||
try:
|
||||
disk = open(dev, "rb")
|
||||
disk.seek(32808)
|
||||
label = disk.read(32).strip()
|
||||
disk.close()
|
||||
except IOError:
|
||||
return None
|
||||
return label
|
||||
|
||||
def volmount(mntp):
|
||||
"""
|
||||
Mount device.
|
||||
@param mountpoint
|
||||
@returns tuple with bool status of mount, and string with error message
|
||||
"""
|
||||
_in, _out, _err = os.popen3("mount %s" % mntp)
|
||||
inf = _err.readlines()
|
||||
if len(inf) > 0:
|
||||
return False, inf[0].strip()
|
||||
else:
|
||||
return True, ''
|
||||
|
||||
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:
|
||||
return inf[0].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 = parts
|
||||
if device[0] == dev:
|
||||
return True
|
||||
return False
|
||||
|
||||
def mountpoint_to_dev(mntp):
|
||||
"""guess device name by mountpoint from fstab"""
|
||||
fstab = open("/etc/fstab")
|
||||
device = None
|
||||
for line in fstab.readlines():
|
||||
output = line.split()
|
||||
# lengtht of single valid fstab line is at least 5
|
||||
if len(output) > 5 and output[1] == mntp and output[0][0] != '#':
|
||||
device = output[0]
|
||||
|
||||
fstab.close()
|
||||
return device
|
||||
|
||||
def eject_cd(eject_app, cdrom):
|
||||
"""mount device, return 'ok' or error message"""
|
||||
if len(eject_app) > 0:
|
||||
_in, _out, _err = os.popen3("%s %s" % (eject_app, cdrom))
|
||||
inf = _err.readlines()
|
||||
|
||||
if len(inf) > 0 and inf[0].strip() != '':
|
||||
return inf[0].strip()
|
||||
|
||||
return 'ok'
|
||||
return _("Eject program not specified")
|
||||
|
||||
@@ -25,15 +25,17 @@
|
||||
import os.path
|
||||
import sys
|
||||
|
||||
if sys.argv[0]: top_dir = os.path.dirname(os.path.abspath(sys.argv[0]))
|
||||
else: top_dir = "."
|
||||
if sys.argv[0]:
|
||||
top_dir = os.path.dirname(os.path.abspath(sys.argv[0]))
|
||||
else:
|
||||
top_dir = "."
|
||||
|
||||
# ----------------------------------------------------------------------
|
||||
TOPDIR = top_dir
|
||||
RESOURCES_DIR = os.path.join(TOPDIR, "resources")
|
||||
GLADE_DIR = os.path.join(RESOURCES_DIR, "glade")
|
||||
STYLES_DIR = os.path.join(RESOURCES_DIR, "styles")
|
||||
APPL_SHORT_NAME = "pycolector"
|
||||
APPL_VERSION = (1, 0, 0)
|
||||
APPL_SHORT_NAME = "pygtktalog"
|
||||
APPL_VERSION = (1, 0, 2)
|
||||
# ----------------------------------------------------------------------
|
||||
|
||||
@@ -27,7 +27,7 @@ import os
|
||||
from datetime import date
|
||||
|
||||
class GthumbCommentParser(object):
|
||||
|
||||
"""Read and return comments created eith gThumb program"""
|
||||
def __init__(self, image_path, image_filename):
|
||||
self.path = image_path
|
||||
self.filename = image_filename
|
||||
@@ -36,16 +36,16 @@ class GthumbCommentParser(object):
|
||||
"""Return dictionary with apropriate fields, or None if no comment
|
||||
available"""
|
||||
try:
|
||||
gf = gzip.open(os.path.join(self.path,
|
||||
'.comments', self.filename + '.xml'))
|
||||
gzf = gzip.open(os.path.join(self.path, '.comments',
|
||||
self.filename + '.xml'))
|
||||
except:
|
||||
return None
|
||||
|
||||
try:
|
||||
xml = gf.read()
|
||||
gf.close()
|
||||
xml = gzf.read()
|
||||
gzf.close()
|
||||
except:
|
||||
gf.close()
|
||||
gzf.close()
|
||||
return None
|
||||
|
||||
if not xml:
|
||||
@@ -23,11 +23,9 @@
|
||||
# -------------------------------------------------------------------------
|
||||
|
||||
from shutil import copy
|
||||
from os import path, mkdir
|
||||
from os import path
|
||||
from hashlib import sha512
|
||||
from tempfile import mkstemp
|
||||
|
||||
from utils import EXIF
|
||||
import Image
|
||||
|
||||
class Img(object):
|
||||
@@ -46,14 +44,14 @@ class Img(object):
|
||||
"""Save image and asociated thumbnail into specific directory structure
|
||||
returns filename for image"""
|
||||
|
||||
|
||||
image_filename = path.join(self.base, self.sha512)
|
||||
thumbnail = path.join(self.base, self.sha512 + "_t")
|
||||
|
||||
# check wheter image already exists
|
||||
if path.exists(image_filename) and path.exists(thumbnail):
|
||||
if __debug__:
|
||||
print "image", self.filename, "with hash", self.sha512, "already exist"
|
||||
print "image", self.filename, "with hash",
|
||||
print self.sha512, "already exist"
|
||||
return self.sha512
|
||||
|
||||
if not path.exists(thumbnail):
|
||||
83
src/lib/thumbnail.py
Normal file
83
src/lib/thumbnail.py
Normal file
@@ -0,0 +1,83 @@
|
||||
"""
|
||||
Project: pyGTKtalog
|
||||
Description: Thumbnail helper
|
||||
Type: library
|
||||
Author: Roman 'gryf' Dobosz, gryf73@gmail.com
|
||||
Created: 2012-02-19
|
||||
"""
|
||||
from hashlib import sha256
|
||||
from cStringIO import StringIO
|
||||
|
||||
from lib import EXIF
|
||||
import Image
|
||||
|
||||
|
||||
class Thumbnail(object):
|
||||
"""Class for generate/extract thumbnail from image file"""
|
||||
|
||||
def __init__(self, fp, base=''):
|
||||
self.thumb_x = 160
|
||||
self.thumb_y = 160
|
||||
self.base = base
|
||||
self.sha256 = sha256(fp.read(10485760)).hexdigest()
|
||||
fp.seek(0)
|
||||
self.fp = fp
|
||||
|
||||
def save(self):
|
||||
"""Save thumbnail into specific directory structure
|
||||
return exif obj and fp to thumbnail"""
|
||||
exif = {}
|
||||
orientations = {2: Image.FLIP_LEFT_RIGHT, # Mirrored horizontal
|
||||
3: Image.ROTATE_180, # Rotated 180
|
||||
4: Image.FLIP_TOP_BOTTOM, # Mirrored vertical
|
||||
5: Image.ROTATE_90, # Mirrored horizontal then
|
||||
# rotated 90 CCW
|
||||
6: Image.ROTATE_270, # Rotated 90 CW
|
||||
7: Image.ROTATE_270, # Mirrored horizontal then
|
||||
# rotated 90 CW
|
||||
8: Image.ROTATE_90} # Rotated 90 CCW
|
||||
flips = {7: Image.FLIP_LEFT_RIGHT, 5: Image.FLIP_LEFT_RIGHT}
|
||||
|
||||
try:
|
||||
exif = EXIF.process_file(self.fp)
|
||||
except:
|
||||
self.fp.seek(0)
|
||||
|
||||
thumb_file = StringIO()
|
||||
if 'JPEGThumbnail' in exif:
|
||||
thumb_file.write(exif['JPEGThumbnail'])
|
||||
|
||||
if 'Image Orientation' in exif:
|
||||
orient = exif['Image Orientation'].values[0]
|
||||
if orient > 1 and orient in orientations:
|
||||
tmp_thumb_img = StringIO()
|
||||
|
||||
thumb_image = Image.open(self.fp)
|
||||
tmp_thumb_img = thumb_image.transpose(orientations[orient])
|
||||
|
||||
if orient in flips:
|
||||
tmp_thumb_img = tmp_thumb_img.transpose(flips[orient])
|
||||
|
||||
if tmp_thumb_img:
|
||||
thumb_file.seek(0)
|
||||
tmp_thumb_img.save(thumb_file, 'JPEG')
|
||||
tmp_thumb_img.close()
|
||||
|
||||
else:
|
||||
thumb = self.__scale_image()
|
||||
if thumb:
|
||||
thumb.save(self.thumbnail_path, "JPEG")
|
||||
|
||||
return exif, thumb_file
|
||||
|
||||
def __scale_image(self):
|
||||
"""create thumbnail. returns image object or None"""
|
||||
try:
|
||||
image_thumb = Image.open(self.fp).convert('RGB')
|
||||
except:
|
||||
return None
|
||||
it_x, it_y = image_thumb.size
|
||||
if it_x > self.thumb_x or it_y > self.thumb_y:
|
||||
image_thumb.thumbnail((self.thumb_x, self.thumb_y),
|
||||
Image.ANTIALIAS)
|
||||
return image_thumb
|
||||
@@ -27,28 +27,27 @@ import sys
|
||||
import shutil
|
||||
import bz2
|
||||
import math
|
||||
import sqlite3 as sqlite
|
||||
from tempfile import mkstemp
|
||||
from datetime import datetime
|
||||
import threading as _threading
|
||||
|
||||
import gtk
|
||||
import gobject
|
||||
|
||||
from gtkmvc.model_mt import ModelMT
|
||||
|
||||
from pysqlite2 import dbapi2 as sqlite
|
||||
from datetime import datetime
|
||||
|
||||
import threading as _threading
|
||||
|
||||
from m_config import ConfigModel
|
||||
|
||||
try:
|
||||
from utils.thumbnail import Thumbnail
|
||||
from utils.img import Img
|
||||
from lib.thumbnail import Thumbnail
|
||||
from lib.img import Img
|
||||
except:
|
||||
pass
|
||||
from utils.parse_exif import ParseExif
|
||||
from utils.gthumb import GthumbCommentParser
|
||||
from lib.parse_exif import ParseExif
|
||||
from lib.gthumb import GthumbCommentParser
|
||||
|
||||
from utils.no_thumb import no_thumb as no_thumb_img
|
||||
from lib.no_thumb import no_thumb as no_thumb_img
|
||||
from lib.video import Video
|
||||
|
||||
class MainModel(ModelMT):
|
||||
"""Create, load, save, manipulate db file which is container for data"""
|
||||
@@ -87,6 +86,7 @@ class MainModel(ModelMT):
|
||||
# images extensions - only for PIL and EXIF
|
||||
IMG = ['jpg', 'jpeg', 'gif', 'png', 'tif', 'tiff', 'tga', 'pcx', 'bmp',
|
||||
'xbm', 'xpm', 'jp2', 'jpx', 'pnm']
|
||||
MOV = ['avi', 'mpg', 'mpeg', 'mkv', 'wmv', 'ogm', 'mov']
|
||||
|
||||
def __init__(self):
|
||||
"""initialize"""
|
||||
@@ -592,6 +592,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:
|
||||
@@ -1742,6 +1750,23 @@ class MainModel(ModelMT):
|
||||
|
||||
ext = i.split('.')[-1].lower()
|
||||
|
||||
# Video
|
||||
if ext in self.MOV:
|
||||
#import rpdb2; rpdb2.start_embedded_debugger('pass')
|
||||
v = Video(current_file)
|
||||
cfn = v.capture()
|
||||
img = Img(cfn, self.image_path)
|
||||
th = img.save()
|
||||
if th:
|
||||
sql = """INSERT INTO
|
||||
thumbnails(file_id, filename)
|
||||
VALUES(?, ?)"""
|
||||
db_cursor.execute(sql, (fileid, th+"_t"))
|
||||
sql = """INSERT INTO images(file_id, filename)
|
||||
VALUES(?, ?)"""
|
||||
db_cursor.execute(sql, (fileid, th))
|
||||
os.unlink(cfn)
|
||||
|
||||
# Images - thumbnails and exif data
|
||||
if self.config.confd['thumbs'] and ext in self.IMG:
|
||||
thumb = Thumbnail(current_file, self.image_path)
|
||||
|
||||
1214
src/utils/EXIF.py
1214
src/utils/EXIF.py
File diff suppressed because it is too large
Load Diff
@@ -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"
|
||||
|
||||
@@ -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"
|
||||
|
||||
@@ -1,116 +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
|
||||
|
||||
# -------------------------------------------------------------------------
|
||||
|
||||
from tempfile import mkstemp
|
||||
from hashlib import sha512
|
||||
from shutil import move
|
||||
from os import path
|
||||
import sys
|
||||
|
||||
from utils import EXIF
|
||||
import Image
|
||||
|
||||
class Thumbnail(object):
|
||||
"""Class for generate/extract thumbnail from image file"""
|
||||
|
||||
def __init__(self, filename=None, base=''):
|
||||
self.thumb_x = 160
|
||||
self.thumb_y = 160
|
||||
self.filename = filename
|
||||
self.base = base
|
||||
self.sha512 = sha512(open(filename).read()).hexdigest()
|
||||
self.thumbnail_path = path.join(self.base, self.sha512 + "_t")
|
||||
|
||||
def save(self):
|
||||
"""Save thumbnail into specific directory structure
|
||||
return filename base and exif object or None"""
|
||||
exif = {}
|
||||
orientations = {2: Image.FLIP_LEFT_RIGHT, # Mirrored horizontal
|
||||
3: Image.ROTATE_180, # Rotated 180
|
||||
4: Image.FLIP_TOP_BOTTOM, # Mirrored vertical
|
||||
5: Image.ROTATE_90, # Mirrored horizontal then
|
||||
# rotated 90 CCW
|
||||
6: Image.ROTATE_270, # Rotated 90 CW
|
||||
7: Image.ROTATE_270, # Mirrored horizontal then
|
||||
# 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)
|
||||
except:
|
||||
if __debug__:
|
||||
print "exception", sys.exc_info()[0], "raised with file:"
|
||||
print self.filename
|
||||
finally:
|
||||
image_file.close()
|
||||
|
||||
if path.exists(self.thumbnail_path):
|
||||
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"
|
||||
exif_thumbnail = exif['JPEGThumbnail']
|
||||
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])
|
||||
|
||||
if tmp_thumb_img:
|
||||
tmp_thumb_img.save(temp_image_path, 'JPEG')
|
||||
move(temp_image_path, self.thumbnail_path)
|
||||
return self.sha512, exif
|
||||
else:
|
||||
if __debug__:
|
||||
print self.filename, "no exif thumb"
|
||||
thumb = self.__scale_image()
|
||||
if thumb:
|
||||
thumb.save(self.thumbnail_path, "JPEG")
|
||||
return self.sha512, exif
|
||||
return None, exif
|
||||
|
||||
def __scale_image(self):
|
||||
"""create thumbnail. returns image object or None"""
|
||||
try:
|
||||
image_thumb = Image.open(self.filename).convert('RGB')
|
||||
except:
|
||||
return None
|
||||
it_x, it_y = image_thumb.size
|
||||
if it_x > self.thumb_x or it_y > self.thumb_y:
|
||||
image_thumb.thumbnail((self.thumb_x, self.thumb_y), Image.ANTIALIAS)
|
||||
return image_thumb
|
||||
@@ -24,11 +24,11 @@
|
||||
|
||||
from gtkmvc import View
|
||||
import os.path
|
||||
import utils.globals
|
||||
import lib.globs
|
||||
|
||||
class ConfigView(View):
|
||||
"""Preferences window from glade file """
|
||||
GLADE = os.path.join(utils.globals.GLADE_DIR, "config.glade")
|
||||
GLADE = os.path.join(lib.globs.GLADE_DIR, "config.glade")
|
||||
|
||||
def __init__(self, ctrl):
|
||||
View.__init__(self, ctrl, self.GLADE)
|
||||
|
||||
@@ -25,7 +25,7 @@
|
||||
import gtk
|
||||
import gobject
|
||||
import os
|
||||
import utils.globals
|
||||
import lib.globs
|
||||
|
||||
class Qst(object):
|
||||
"""Show simple dialog for questions
|
||||
@@ -128,7 +128,7 @@ class InputDiskLabel(object):
|
||||
"""Sepcific dialog for quering user for a disc label"""
|
||||
|
||||
def __init__(self, label=""):
|
||||
self.gladefile = os.path.join(utils.globals.GLADE_DIR, "dialogs.glade")
|
||||
self.gladefile = os.path.join(lib.globs.GLADE_DIR, "dialogs.glade")
|
||||
self.label = ""
|
||||
if label!= None:
|
||||
self.label = label
|
||||
@@ -148,7 +148,7 @@ class InputNewName(object):
|
||||
"""Sepcific dialog for quering user for a disc label"""
|
||||
|
||||
def __init__(self, name=""):
|
||||
self.gladefile = os.path.join(utils.globals.GLADE_DIR, "dialogs.glade")
|
||||
self.gladefile = os.path.join(lib.globs.GLADE_DIR, "dialogs.glade")
|
||||
self.label = ""
|
||||
self.name = name
|
||||
|
||||
@@ -169,7 +169,7 @@ class PointDirectoryToAdd(object):
|
||||
URI="file://"+os.path.abspath(os.path.curdir)
|
||||
|
||||
def __init__(self,volname='',dirname=''):
|
||||
self.gladefile = os.path.join(utils.globals.GLADE_DIR, "dialogs.glade")
|
||||
self.gladefile = os.path.join(lib.globs.GLADE_DIR, "dialogs.glade")
|
||||
self.gladexml = gtk.glade.XML(self.gladefile, "addDirDialog")
|
||||
self.volname = self.gladexml.get_widget("dirvolname")
|
||||
self.volname.set_text(volname)
|
||||
@@ -257,15 +257,15 @@ class SelectDirectory(object):
|
||||
dialog.destroy()
|
||||
return retval
|
||||
|
||||
class ChooseDBFilename(object):
|
||||
"""Sepcific dialog for quering user for selecting filename for database"""
|
||||
class ChooseFilename(object):
|
||||
"""Dialog for quering user for selecting filename"""
|
||||
|
||||
URI=None
|
||||
|
||||
def __init__(self, path=None):
|
||||
def __init__(self, path=None, title=''):
|
||||
self.path = path
|
||||
self.dialog = gtk.FileChooserDialog(
|
||||
title="Save catalog as...",
|
||||
title="",
|
||||
action=gtk.FILE_CHOOSER_ACTION_SAVE,
|
||||
buttons=(
|
||||
gtk.STOCK_CANCEL,
|
||||
@@ -276,7 +276,44 @@ class ChooseDBFilename(object):
|
||||
self.dialog.set_action(gtk.FILE_CHOOSER_ACTION_SAVE)
|
||||
self.dialog.set_default_response(gtk.RESPONSE_OK)
|
||||
self.dialog.set_do_overwrite_confirmation(True)
|
||||
self.dialog.set_title('Save catalog to file...')
|
||||
self.dialog.set_title(title)
|
||||
|
||||
f = gtk.FileFilter()
|
||||
f.set_name("Catalog files")
|
||||
f.add_pattern("*.sqlite")
|
||||
f.add_pattern("*.sqlite.bz2")
|
||||
self.dialog.add_filter(f)
|
||||
f = gtk.FileFilter()
|
||||
f.set_name("All files")
|
||||
f.add_pattern("*.*")
|
||||
self.dialog.add_filter(f)
|
||||
|
||||
def run(self):
|
||||
if self.URI:
|
||||
self.dialog.set_current_folder_uri(self.URI)
|
||||
elif self.path and os.path.exists(self.path):
|
||||
self.path = "file://"+os.path.abspath(self.path)
|
||||
self.dialog.set_current_folder_uri(self.path)
|
||||
|
||||
response = self.dialog.run()
|
||||
if response == gtk.RESPONSE_OK:
|
||||
filename = self.dialog.get_filename()
|
||||
self.__class__.URI = self.dialog.get_current_folder_uri()
|
||||
self.dialog.destroy()
|
||||
return filename
|
||||
else:
|
||||
self.dialog.destroy()
|
||||
return None
|
||||
pass
|
||||
|
||||
class ChooseDBFilename(ChooseFilename):
|
||||
"""Sepcific dialog for quering user for selecting filename for database"""
|
||||
|
||||
URI=None
|
||||
|
||||
def __init__(self, path=None):
|
||||
ChooseFilename.__init__(self)
|
||||
self.dialog.set_title('Save catalog as...')
|
||||
|
||||
f = gtk.FileFilter()
|
||||
f.set_name("Catalog files")
|
||||
@@ -298,11 +335,6 @@ class ChooseDBFilename(object):
|
||||
response = self.dialog.run()
|
||||
if response == gtk.RESPONSE_OK:
|
||||
filename = self.dialog.get_filename()
|
||||
print filename, ' do ',
|
||||
if filename[-11:].lower() != '.sqlite.bz2' and \
|
||||
filename[-7:].lower() != '.sqlite':
|
||||
filename = filename + '.sqlite.bz2'
|
||||
print filename
|
||||
self.__class__.URI = self.dialog.get_current_folder_uri()
|
||||
self.dialog.destroy()
|
||||
return filename
|
||||
@@ -454,7 +486,7 @@ class StatsDialog(object):
|
||||
"""Sepcific dialog for display stats"""
|
||||
|
||||
def __init__(self, values={}):
|
||||
self.gladefile = os.path.join(utils.globals.GLADE_DIR, "dialogs.glade")
|
||||
self.gladefile = os.path.join(lib.globs.GLADE_DIR, "dialogs.glade")
|
||||
self.values = values
|
||||
|
||||
def run(self):
|
||||
@@ -497,7 +529,7 @@ class TagsDialog(object):
|
||||
"""Sepcific dialog for display stats"""
|
||||
|
||||
def __init__(self):
|
||||
self.gladefile = os.path.join(utils.globals.GLADE_DIR, "dialogs.glade")
|
||||
self.gladefile = os.path.join(lib.globs.GLADE_DIR, "dialogs.glade")
|
||||
|
||||
def run(self):
|
||||
gladexml = gtk.glade.XML(self.gladefile, "tagsDialog")
|
||||
@@ -516,7 +548,7 @@ class TagsRemoveDialog(object):
|
||||
"""Sepcific dialog for display stats"""
|
||||
|
||||
def __init__(self, tag_dict=None):
|
||||
self.gladefile = os.path.join(utils.globals.GLADE_DIR, "dialogs.glade")
|
||||
self.gladefile = os.path.join(lib.globs.GLADE_DIR, "dialogs.glade")
|
||||
self.tag_dict = tag_dict
|
||||
|
||||
def run(self):
|
||||
@@ -585,7 +617,7 @@ class EditDialog(object):
|
||||
"""Sepcific dialog for display stats"""
|
||||
|
||||
def __init__(self, values={}):
|
||||
self.gladefile = os.path.join(utils.globals.GLADE_DIR, "dialogs.glade")
|
||||
self.gladefile = os.path.join(lib.globs.GLADE_DIR, "dialogs.glade")
|
||||
self.values = values
|
||||
|
||||
def run(self):
|
||||
|
||||
@@ -23,14 +23,14 @@
|
||||
# -------------------------------------------------------------------------
|
||||
|
||||
import os.path
|
||||
import utils.globals
|
||||
import lib.globs
|
||||
from gtkmvc import View
|
||||
|
||||
class MainView(View):
|
||||
"""This handles only the graphical representation of the
|
||||
application. The widgets set is loaded from glade file"""
|
||||
|
||||
GLADE = os.path.join(utils.globals.GLADE_DIR, "main.glade")
|
||||
GLADE = os.path.join(lib.globs.GLADE_DIR, "main.glade")
|
||||
|
||||
def __init__(self, ctrl):
|
||||
View.__init__(self, ctrl, self.GLADE)
|
||||
|
||||
@@ -24,12 +24,12 @@
|
||||
|
||||
from gtkmvc import View
|
||||
import os.path
|
||||
import utils.globals
|
||||
import lib.globs
|
||||
|
||||
class SearchView(View):
|
||||
"""Search window from glade file """
|
||||
|
||||
GLADE = os.path.join(utils.globals.GLADE_DIR, "search.glade")
|
||||
GLADE = os.path.join(lib.globs.GLADE_DIR, "search.glade")
|
||||
|
||||
def __init__(self, ctrl):
|
||||
View.__init__(self, ctrl, self.GLADE)
|
||||
|
||||
0
test/unit/__init__.py
Normal file
0
test/unit/__init__.py
Normal file
28
test/unit/dbcommon_test.py
Normal file
28
test/unit/dbcommon_test.py
Normal file
@@ -0,0 +1,28 @@
|
||||
"""
|
||||
Project: pyGTKtalog
|
||||
Description: Tests for DataBase class.
|
||||
Type: test
|
||||
Author: Roman 'gryf' Dobosz, gryf73@gmail.com
|
||||
Created: 2009-07-19
|
||||
"""
|
||||
import unittest
|
||||
import os
|
||||
|
||||
from pygtktalog.dbcommon import connect, Meta, Session, Base
|
||||
|
||||
|
||||
class TestDataBase(unittest.TestCase):
|
||||
"""
|
||||
Class responsible for database connection and schema creation
|
||||
"""
|
||||
|
||||
def test_connect(self):
|
||||
"""
|
||||
Test connection to database. Memory and file method will be tested.
|
||||
"""
|
||||
connect(":memory:")
|
||||
|
||||
|
||||
if __name__ == "__main__":
|
||||
os.chdir(os.path.join(os.path.abspath(os.path.dirname(__file__)), "../"))
|
||||
unittest.main()
|
||||
77
test/unit/dialogs_test.py
Normal file
77
test/unit/dialogs_test.py
Normal file
@@ -0,0 +1,77 @@
|
||||
"""
|
||||
Project: pyGTKtalog
|
||||
Description: Test simple dialogs
|
||||
Type: test
|
||||
Author: Roman 'gryf' Dobosz, gryf73@gmail.com
|
||||
Created: 2009-05-19
|
||||
"""
|
||||
import unittest
|
||||
import os
|
||||
|
||||
import gtk
|
||||
|
||||
from pygtktalog.dialogs import Dialog, yesno, okcancel, info, warn, error
|
||||
|
||||
|
||||
class MessageDialogMock(gtk.MessageDialog):
|
||||
"""Mock class for MessageDialog, which shouldn't be displayed in a test"""
|
||||
|
||||
def run(self):
|
||||
"""Carefull! only for MESSAGE_INFO return value is RESPONSE_OK!"""
|
||||
if self.get_property('message-type') == gtk.MESSAGE_INFO:
|
||||
return gtk.RESPONSE_OK
|
||||
else:
|
||||
return gtk.RESPONSE_CANCEL
|
||||
|
||||
class TestDialog(unittest.TestCase):
|
||||
"""Tests for Dialog class"""
|
||||
|
||||
def test_dialog_create(self):
|
||||
"""Test dialog creation and run method"""
|
||||
# overwrite MessageDialog class in gtk module
|
||||
gtk.MessageDialog = MessageDialogMock
|
||||
dialog = Dialog(gtk.MESSAGE_INFO, 'msg', 'secondarymsg', 'title')
|
||||
self.assertTrue(dialog.buttons == gtk.BUTTONS_OK, "dialog should have"
|
||||
" gtk.BUTTONS_OK")
|
||||
self.assertTrue(dialog.run(), "dialog should return True")
|
||||
|
||||
dialog = Dialog(gtk.MESSAGE_QUESTION, 'msg', 'secondarymsg', 'title')
|
||||
self.assertFalse(dialog.run(), "dialog should return False")
|
||||
# NOTE: dialog should be run before test against buttons attribute
|
||||
self.assertTrue(dialog.buttons == gtk.BUTTONS_YES_NO,
|
||||
"dialog should have gtk.BUTTONS_YES_NO")
|
||||
|
||||
dialog = Dialog(gtk.MESSAGE_QUESTION, 'msg', 'secondarymsg', 'title')
|
||||
dialog.buttons = gtk.BUTTONS_OK
|
||||
dialog.ok_default = True
|
||||
self.assertFalse(dialog.run(), "dialog should return True")
|
||||
|
||||
def test_error(self):
|
||||
"""Test error function"""
|
||||
result = error('msg', 'secondarymsg', 'title')
|
||||
self.assertTrue(result, "Should return True")
|
||||
|
||||
def test_warn(self):
|
||||
"""Test warn function"""
|
||||
result = warn('msg', 'secondarymsg', 'title')
|
||||
self.assertTrue(result, "Should return True")
|
||||
|
||||
def test_info(self):
|
||||
"""Test info function"""
|
||||
result = info('msg', 'secondarymsg', 'title')
|
||||
self.assertTrue(result, "Should return True")
|
||||
|
||||
def test_yesno(self):
|
||||
"""Test yesno function"""
|
||||
result = yesno('msg', 'secondarymsg', 'title')
|
||||
self.assertFalse(result, "Should return False")
|
||||
|
||||
def test_okcancel(self):
|
||||
"""Test yesno function"""
|
||||
result = okcancel('msg', 'secondarymsg', 'title')
|
||||
self.assertFalse(result, "Should return False")
|
||||
|
||||
|
||||
if __name__ == "__main__":
|
||||
os.chdir(os.path.join(os.path.abspath(os.path.dirname(__file__)), "../"))
|
||||
unittest.main()
|
||||
8
test/unit/main_view_test.py
Normal file
8
test/unit/main_view_test.py
Normal file
@@ -0,0 +1,8 @@
|
||||
"""
|
||||
Project: pyGTKtalog
|
||||
Description: Tests for main view class.
|
||||
Type: test
|
||||
Author: Roman 'gryf' Dobosz, gryf73@gmail.com
|
||||
Created: 2010-03-14
|
||||
"""
|
||||
|
||||
33
test/unit/misc_test.py
Normal file
33
test/unit/misc_test.py
Normal file
@@ -0,0 +1,33 @@
|
||||
"""
|
||||
Project: pyGTKtalog
|
||||
Description: Tests for misc functions.
|
||||
Type: test
|
||||
Author: Roman 'gryf' Dobosz, gryf73@gmail.com
|
||||
Created: 2009-04-09
|
||||
"""
|
||||
import unittest
|
||||
import os
|
||||
|
||||
import pygtktalog.misc as pgtkmisc
|
||||
|
||||
|
||||
class TestMiscModule(unittest.TestCase):
|
||||
"""
|
||||
Tests functions from misc module
|
||||
"""
|
||||
|
||||
def test_float_to_string(self):
|
||||
"""
|
||||
test conversion between digits to formated output
|
||||
"""
|
||||
self.assertEqual(pgtkmisc.float_to_string(10), '00:00:10')
|
||||
self.assertEqual(pgtkmisc.float_to_string(76), '00:01:16')
|
||||
self.assertEqual(pgtkmisc.float_to_string(22222), '06:10:22')
|
||||
self.assertRaises(TypeError, pgtkmisc.float_to_string)
|
||||
self.assertRaises(TypeError, pgtkmisc.float_to_string, None)
|
||||
self.assertRaises(TypeError, pgtkmisc.float_to_string, '10')
|
||||
|
||||
|
||||
if __name__ == "__main__":
|
||||
os.chdir(os.path.join(os.path.abspath(os.path.dirname(__file__)), "../"))
|
||||
unittest.main()
|
||||
124
test/unit/scan_test.py
Normal file
124
test/unit/scan_test.py
Normal file
@@ -0,0 +1,124 @@
|
||||
"""
|
||||
Project: pyGTKtalog
|
||||
Description: Tests for scan files.
|
||||
Type: test
|
||||
Author: Roman 'gryf' Dobosz, gryf73@gmail.com
|
||||
Created: 2011-03-26
|
||||
"""
|
||||
import os
|
||||
import unittest
|
||||
|
||||
from pygtktalog import scan
|
||||
from pygtktalog.dbobjects import File
|
||||
from pygtktalog.dbcommon import connect, Session
|
||||
|
||||
|
||||
TEST_DIR = "/home/share/_test_/test_dir"
|
||||
TEST_DIR_PERMS = "/home/share/_test_/test_dir_permissions/"
|
||||
|
||||
class TestScan(unittest.TestCase):
|
||||
"""
|
||||
Test cases for scan functionality
|
||||
|
||||
1. execution scan function:
|
||||
1.1 simple case - should pass
|
||||
1.2 non-existent directory passed
|
||||
1.3 file passed
|
||||
1.4 directory has permission that forbids file listing
|
||||
|
||||
2. rescan directory; looking for changes
|
||||
2.0 don't touch records for changed files (same directories, same
|
||||
filename, same type and size)
|
||||
2.1 search for files of the same type, same size.
|
||||
2.2 change parent node for moved files (don't insert new)
|
||||
|
||||
3. adding new directory tree which contains same files like already stored
|
||||
in the database
|
||||
"""
|
||||
def setUp(self):
|
||||
connect()
|
||||
root = File()
|
||||
root.id = 1
|
||||
root.filename = 'root'
|
||||
root.size = 0
|
||||
root.source = 0
|
||||
root.type = 0
|
||||
root.parent_id = 1
|
||||
|
||||
sess = Session()
|
||||
sess.add(root)
|
||||
sess.commit()
|
||||
|
||||
def test_happy_scenario(self):
|
||||
"""
|
||||
make scan, count items
|
||||
"""
|
||||
scanob = scan.Scan(os.path.abspath(os.path.join(__file__,
|
||||
"../../../mocks")))
|
||||
scanob = scan.Scan(TEST_DIR)
|
||||
result_list = scanob.add_files()
|
||||
self.assertEqual(len(result_list), 143)
|
||||
self.assertEqual(len(result_list[0].children), 8)
|
||||
# check soft links
|
||||
self.assertEqual(len([x for x in result_list if x.type == 3]), 2)
|
||||
|
||||
def test_wrong_and_nonexistent(self):
|
||||
"""
|
||||
Check for accessing non existent directory, regular file instead of
|
||||
the directory, or file.directory with no access to it.
|
||||
"""
|
||||
scanobj = scan.Scan('/nonexistent_directory_')
|
||||
self.assertRaises(OSError, scanobj.add_files)
|
||||
|
||||
scanobj.path = '/root'
|
||||
self.assertRaises(scan.NoAccessError, scanobj.add_files)
|
||||
|
||||
scanobj.path = '/bin/sh'
|
||||
self.assertRaises(scan.NoAccessError, scanobj.add_files)
|
||||
|
||||
# dir contains some non accessable items. Should just pass, and on
|
||||
# logs should be messages about it
|
||||
scanobj.path = TEST_DIR_PERMS
|
||||
scanobj.add_files()
|
||||
|
||||
def test_abort_functionality(self):
|
||||
scanobj = scan.Scan(TEST_DIR)
|
||||
scanobj.abort = True
|
||||
self.assertEqual(None, scanobj.add_files())
|
||||
|
||||
def test_double_scan(self):
|
||||
"""
|
||||
Do the scan twice.
|
||||
"""
|
||||
ses = Session()
|
||||
self.assertEqual(len(ses.query(File).all()), 1)
|
||||
|
||||
scanob = scan.Scan(TEST_DIR)
|
||||
scanob.add_files()
|
||||
|
||||
# note: we have 144 elements in db, because of root element
|
||||
self.assertEqual(len(ses.query(File).all()), 144)
|
||||
|
||||
scanob2 = scan.Scan(TEST_DIR)
|
||||
scanob2.add_files()
|
||||
# it is perfectly ok, since we don't update collection, but just added
|
||||
# same directory twice.
|
||||
self.assertEqual(len(ses.query(File).all()), 287)
|
||||
file_ob = scanob._files[2]
|
||||
file2_ob = scanob2._files[2]
|
||||
|
||||
# File objects are different
|
||||
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)
|
||||
|
||||
# they are different objects
|
||||
self.assertTrue(file_ob.images[0] is not file2_ob.images[0])
|
||||
|
||||
ses.close()
|
||||
|
||||
if __name__ == "__main__":
|
||||
os.chdir(os.path.join(os.path.abspath(os.path.dirname(__file__)), "../"))
|
||||
unittest.main()
|
||||
133
test/unit/video_test.py
Normal file
133
test/unit/video_test.py
Normal file
@@ -0,0 +1,133 @@
|
||||
"""
|
||||
Project: pyGTKtalog
|
||||
Description: Tests for Video class.
|
||||
Type: test
|
||||
Author: Roman 'gryf' Dobosz, gryf73@gmail.com
|
||||
Created: 2008-12-15
|
||||
"""
|
||||
import unittest
|
||||
import os
|
||||
|
||||
from pygtktalog.video import Video
|
||||
|
||||
|
||||
class TestVideo(unittest.TestCase):
|
||||
"""Class for retrive midentify script output and put it in dict.
|
||||
Usually there is no need for such a detailed movie/clip information.
|
||||
Video script belongs to mplayer package.
|
||||
"""
|
||||
|
||||
def test_avi(self):
|
||||
"""test mock avi file, should return dict with expected values"""
|
||||
avi = Video("mocks/m.avi")
|
||||
self.assertTrue(len(avi.tags) != 0, "result should have lenght > 0")
|
||||
self.assertEqual(avi.tags['audio_format'], '85')
|
||||
self.assertEqual(avi.tags['width'], 128)
|
||||
self.assertEqual(avi.tags['audio_no_channels'], 2)
|
||||
self.assertEqual(avi.tags['height'], 96)
|
||||
self.assertEqual(avi.tags['video_format'], 'xvid')
|
||||
self.assertEqual(avi.tags['length'], 4)
|
||||
self.assertEqual(avi.tags['audio_codec'], 'mp3')
|
||||
self.assertEqual(avi.tags['video_codec'], 'ffodivx')
|
||||
self.assertEqual(avi.tags['duration'], '00:00:04')
|
||||
self.assertEqual(avi.tags['container'], 'avi')
|
||||
|
||||
def test_avi2(self):
|
||||
"""test another mock avi file, should return dict with expected
|
||||
values"""
|
||||
avi = Video("mocks/m1.avi")
|
||||
self.assertTrue(len(avi.tags) != 0, "result should have lenght > 0")
|
||||
self.assertEqual(avi.tags['audio_format'], '85')
|
||||
self.assertEqual(avi.tags['width'], 128)
|
||||
self.assertEqual(avi.tags['audio_no_channels'], 2)
|
||||
self.assertEqual(avi.tags['height'], 96)
|
||||
self.assertEqual(avi.tags['video_format'], 'h264')
|
||||
self.assertEqual(avi.tags['length'], 4)
|
||||
self.assertEqual(avi.tags['audio_codec'], 'mp3')
|
||||
self.assertEqual(avi.tags['video_codec'], 'ffh264')
|
||||
self.assertEqual(avi.tags['duration'], '00:00:04')
|
||||
self.assertEqual(avi.tags['container'], 'avi')
|
||||
|
||||
def test_mkv(self):
|
||||
"""test mock mkv file, should return dict with expected values"""
|
||||
avi = Video("mocks/m.mkv")
|
||||
self.assertTrue(len(avi.tags) != 0, "result should have lenght > 0")
|
||||
self.assertEqual(avi.tags['audio_format'], '8192')
|
||||
self.assertEqual(avi.tags['width'], 128)
|
||||
self.assertTrue(avi.tags['audio_no_channels'] in (1, 2))
|
||||
self.assertEqual(avi.tags['height'], 96)
|
||||
self.assertEqual(avi.tags['video_format'], 'mp4v')
|
||||
self.assertEqual(avi.tags['length'], 4)
|
||||
self.assertTrue(avi.tags['audio_codec'] in ('a52', 'ffac3'))
|
||||
self.assertEqual(avi.tags['video_codec'], 'ffodivx')
|
||||
self.assertEqual(avi.tags['duration'], '00:00:04')
|
||||
self.assertTrue(avi.tags['container'] in ('mkv', 'lavfpref'))
|
||||
|
||||
def test_mpg(self):
|
||||
"""test mock mpg file, should return dict with expected values"""
|
||||
avi = Video("mocks/m.mpg")
|
||||
self.assertTrue(len(avi.tags) != 0, "result should have lenght > 0")
|
||||
self.assertFalse(avi.tags.has_key('audio_format'))
|
||||
self.assertEqual(avi.tags['width'], 128)
|
||||
self.assertFalse(avi.tags.has_key('audio_no_channels'))
|
||||
self.assertEqual(avi.tags['height'], 96)
|
||||
self.assertEqual(avi.tags['video_format'], '0x10000001')
|
||||
self.assertFalse(avi.tags.has_key('lenght'))
|
||||
self.assertFalse(avi.tags.has_key('audio_codec'))
|
||||
self.assertEqual(avi.tags['video_codec'], 'ffmpeg1')
|
||||
self.assertFalse(avi.tags.has_key('duration'))
|
||||
self.assertEqual(avi.tags['container'], 'mpeges')
|
||||
|
||||
def test_ogm(self):
|
||||
"""test mock ogm file, should return dict with expected values"""
|
||||
avi = Video("mocks/m.ogm")
|
||||
self.assertTrue(len(avi.tags) != 0, "result should have lenght > 0")
|
||||
self.assertEqual(avi.tags['audio_format'], '8192')
|
||||
self.assertEqual(avi.tags['width'], 160)
|
||||
self.assertTrue(avi.tags['audio_no_channels'] in (1, 2))
|
||||
self.assertEqual(avi.tags['height'], 120)
|
||||
self.assertEqual(avi.tags['video_format'], 'h264')
|
||||
self.assertEqual(avi.tags['length'], 4)
|
||||
self.assertTrue(avi.tags['audio_codec'] in ('a52', 'ffac3'))
|
||||
self.assertEqual(avi.tags['video_codec'], 'ffh264')
|
||||
self.assertEqual(avi.tags['duration'], '00:00:04')
|
||||
self.assertTrue(avi.tags['container'] in ('ogg', 'lavfpref'))
|
||||
|
||||
def test_capture(self):
|
||||
"""test capture with some small movie and play a little with tags"""
|
||||
avi = Video("mocks/m.avi")
|
||||
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.9, 0)
|
||||
os.unlink(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)
|
||||
|
||||
avi.tags['length'] = 4
|
||||
|
||||
avi.tags['width'] = 0
|
||||
self.assertTrue(avi.capture() is None)
|
||||
|
||||
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)
|
||||
|
||||
self.assertTrue(len(str(avi)) > 0)
|
||||
|
||||
|
||||
if __name__ == "__main__":
|
||||
os.chdir(os.path.join(os.path.abspath(os.path.dirname(__file__)), "../"))
|
||||
unittest.main()
|
||||
Reference in New Issue
Block a user