From 56c77ae9a402bb4f0bcadbc02e862683f59713c9 Mon Sep 17 00:00:00 2001 From: gryf Date: Tue, 7 Apr 2009 20:25:00 +0000 Subject: [PATCH] added EXIF patch and misc in lib removed some cruft --- katalog_convert_1.0rc_to_1.0.py | 213 ---------------------- runtests.sh | 4 + src/lib/EXIF.py | 64 ++++--- src/lib/EXIF_light_source_and_flash.patch | 83 +++++++++ src/lib/midentify.py | 61 ------- src/lib/misc.py | 22 +++ src/test/unit/midentify_test.py | 99 ---------- 7 files changed, 148 insertions(+), 398 deletions(-) delete mode 100755 katalog_convert_1.0rc_to_1.0.py create mode 100755 runtests.sh create mode 100644 src/lib/EXIF_light_source_and_flash.patch delete mode 100644 src/lib/midentify.py create mode 100644 src/lib/misc.py delete mode 100644 src/test/unit/midentify_test.py diff --git a/katalog_convert_1.0rc_to_1.0.py b/katalog_convert_1.0rc_to_1.0.py deleted file mode 100755 index 6020de4..0000000 --- a/katalog_convert_1.0rc_to_1.0.py +++ /dev/null @@ -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.lib.globs 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 lib.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() diff --git a/runtests.sh b/runtests.sh new file mode 100755 index 0000000..cd0f200 --- /dev/null +++ b/runtests.sh @@ -0,0 +1,4 @@ +#!/bin/sh +# run unittests +cd src/test +python run_tests.py diff --git a/src/lib/EXIF.py b/src/lib/EXIF.py index ed4192a..64bf40d 100644 --- a/src/lib/EXIF.py +++ b/src/lib/EXIF.py @@ -251,40 +251,54 @@ EXIF_TAGS = { 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', ), diff --git a/src/lib/EXIF_light_source_and_flash.patch b/src/lib/EXIF_light_source_and_flash.patch new file mode 100644 index 0000000..ec933c4 --- /dev/null +++ b/src/lib/EXIF_light_source_and_flash.patch @@ -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', ), diff --git a/src/lib/midentify.py b/src/lib/midentify.py deleted file mode 100644 index b054a9d..0000000 --- a/src/lib/midentify.py +++ /dev/null @@ -1,61 +0,0 @@ -""" - Project: pyGTKtalog - Description: Gather video file information. Uses external tools. - Type: lib - Author: Roman 'gryf' Dobosz, gryf73@gmail.com - Created: 2008-12-15 -""" -from os import popen -import sys - -class Midentify(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): - """Init class instance. Filename of a video file is required.""" - self.filename = filename - self.tags = {} - - def get_data(self): - """return dict with clip information""" - output = popen('midentify "%s"' % self.filename).readlines() - - attrs = {'ID_VIDEO_WIDTH': ['width', int], - 'ID_VIDEO_HEIGHT': ['height', int], - 'ID_LENGTH': ['length', lambda x: int(x.split(".")[0])], - # length is in seconds - 'ID_DEMUXER': ['container', str], - 'ID_VIDEO_FORMAT': ['video_format', str], - 'ID_VIDEO_CODEC': ['video_codec', str], - 'ID_AUDIO_CODEC': ['audio_codec', str], - 'ID_AUDIO_FORMAT': ['audio_format', str], - 'ID_AUDIO_NCH': ['audio_no_channels', int],} - - for line in output: - line = line.strip() - for attr in attrs: - if attr in line: - self.tags[attrs[attr][0]] = \ - attrs[attr][1](line.replace("%s=" % attr, "")) - - if 'length' in self.tags: - if self.tags['length'] > 0: - hours = self.tags['length'] / 3600 - seconds = self.tags['length'] - hours * 3600 - minutes = seconds / 60 - seconds -= minutes * 60 - length_str = "%02d:%02d:%02d" % (hours, minutes, seconds) - self.tags['duration'] = length_str - return self.tags - -if __name__ == "__main__": - if len(sys.argv) < 2: - print "usage: %s filename" % sys.argv[0] - sys.exit() - - for arg in sys.argv[1:]: - mid = Midentify(arg) - print mid.get_data() diff --git a/src/lib/misc.py b/src/lib/misc.py new file mode 100644 index 0000000..a620a83 --- /dev/null +++ b/src/lib/misc.py @@ -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: 2008-12-15 +""" + +def float_to_string(float_length): + """ + Parse float digit into time string + Arguments: + @string - width of generated image. If actual image width + exceeds this number scale is performed. + 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) diff --git a/src/test/unit/midentify_test.py b/src/test/unit/midentify_test.py deleted file mode 100644 index 7071bbd..0000000 --- a/src/test/unit/midentify_test.py +++ /dev/null @@ -1,99 +0,0 @@ -""" - Project: pyGTKtalog - Description: Tests for Midentify class. - Type: test - Author: Roman 'gryf' Dobosz, gryf73@gmail.com - Created: 2008-12-15 -""" -import unittest -from lib.midentify import Midentify - -class TestMidentify(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. - Midentify script belongs to mplayer package. - """ - - def test_avi(self): - """test mock avi file, should return dict with expected values""" - avi = Midentify("mocks/m.avi") - result_dict = avi.get_data() - self.assertTrue(len(result_dict) != 0, "result should have lenght > 0") - self.assertEqual(result_dict['audio_format'], '85') - self.assertEqual(result_dict['width'], 128) - self.assertEqual(result_dict['audio_no_channels'], 2) - self.assertEqual(result_dict['height'], 96) - self.assertEqual(result_dict['video_format'], 'XVID') - self.assertEqual(result_dict['length'], 4) - self.assertEqual(result_dict['audio_codec'], 'mp3') - self.assertEqual(result_dict['video_codec'], 'ffodivx') - self.assertEqual(result_dict['duration'], '00:00:04') - self.assertEqual(result_dict['container'], 'avi') - - def test_avi2(self): - """test another mock avi file, should return dict with expected - values""" - avi = Midentify("mocks/m1.avi") - result_dict = avi.get_data() - self.assertTrue(len(result_dict) != 0, "result should have lenght > 0") - self.assertEqual(result_dict['audio_format'], '85') - self.assertEqual(result_dict['width'], 128) - self.assertEqual(result_dict['audio_no_channels'], 2) - self.assertEqual(result_dict['height'], 96) - self.assertEqual(result_dict['video_format'], 'H264') - self.assertEqual(result_dict['length'], 4) - self.assertEqual(result_dict['audio_codec'], 'mp3') - self.assertEqual(result_dict['video_codec'], 'ffh264') - self.assertEqual(result_dict['duration'], '00:00:04') - self.assertEqual(result_dict['container'], 'avi') - - def test_mkv(self): - """test mock mkv file, should return dict with expected values""" - avi = Midentify("mocks/m.mkv") - result_dict = avi.get_data() - self.assertTrue(len(result_dict) != 0, "result should have lenght > 0") - self.assertEqual(result_dict['audio_format'], '8192') - self.assertEqual(result_dict['width'], 128) - self.assertEqual(result_dict['audio_no_channels'], 2) - self.assertEqual(result_dict['height'], 96) - self.assertEqual(result_dict['video_format'], 'mp4v') - self.assertEqual(result_dict['length'], 4) - self.assertEqual(result_dict['audio_codec'], 'a52') - self.assertEqual(result_dict['video_codec'], 'ffodivx') - self.assertEqual(result_dict['duration'], '00:00:04') - self.assertEqual(result_dict['container'], 'mkv') - - def test_mpg(self): - """test mock mpg file, should return dict with expected values""" - avi = Midentify("mocks/m.mpg") - result_dict = avi.get_data() - self.assertTrue(len(result_dict) != 0, "result should have lenght > 0") - self.assertFalse(result_dict.has_key('audio_format')) - self.assertEqual(result_dict['width'], 128) - self.assertFalse(result_dict.has_key('audio_no_channels')) - self.assertEqual(result_dict['height'], 96) - self.assertEqual(result_dict['video_format'], '0x10000001') - self.assertFalse(result_dict.has_key('lenght')) - self.assertFalse(result_dict.has_key('audio_codec')) - self.assertEqual(result_dict['video_codec'], 'ffmpeg1') - self.assertFalse(result_dict.has_key('duration')) - self.assertEqual(result_dict['container'], 'mpeges') - - def test_ogm(self): - """test mock ogm file, should return dict with expected values""" - avi = Midentify("mocks/m.ogm") - result_dict = avi.get_data() - self.assertTrue(len(result_dict) != 0, "result should have lenght > 0") - self.assertEqual(result_dict['audio_format'], '8192') - self.assertEqual(result_dict['width'], 160) - self.assertEqual(result_dict['audio_no_channels'], 2) - self.assertEqual(result_dict['height'], 120) - self.assertEqual(result_dict['video_format'], 'H264') - self.assertEqual(result_dict['length'], 4) - self.assertEqual(result_dict['audio_codec'], 'a52') - self.assertEqual(result_dict['video_codec'], 'ffh264') - self.assertEqual(result_dict['duration'], '00:00:04') - self.assertEqual(result_dict['container'], 'ogg') - -if __name__ == "__main__": - unittest.main()