From 5168fdb07d6dc2b77f0ef9c7502940ce4a02e9aa Mon Sep 17 00:00:00 2001 From: Julien Valroff Date: Fri, 8 Apr 2011 07:12:47 +0200 Subject: Imported Upstream version 0.3.6 --- rapid/renamesubfolderprefs.py | 1695 +++++++++++++++++++++++++++++++++++++++++ 1 file changed, 1695 insertions(+) create mode 100644 rapid/renamesubfolderprefs.py (limited to 'rapid/renamesubfolderprefs.py') diff --git a/rapid/renamesubfolderprefs.py b/rapid/renamesubfolderprefs.py new file mode 100644 index 0000000..d538030 --- /dev/null +++ b/rapid/renamesubfolderprefs.py @@ -0,0 +1,1695 @@ +#!/usr/bin/python +# -*- coding: latin1 -*- + +### Copyright (C) 2007, 2008, 2009, 2010 Damon Lynch + +### 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., 59 Temple Place, Suite 330, Boston, MA 02111-1307 USA + +""" Define and test preferences for use in PlusMinus tables. + +These are displayed to the user as a series of rows in the user +preferences dialog window. + +Preferences for subfolders and image renaming are defined below +in dictionaries and lists. This makes it easier for checking validity and +creating combo boxes. + +There are 3 levels: 0, 1 and 2, which specify the depth of the pref value. +Level 0 is the topmost level, and corresponds to the first entry in the +row of preferences the user sees in the preferences dialog window. + +Custom exceptions are defined to handle invalid preferences. + +The user's actual preferences, on the other hand, are stored in flat lists. +Each list has members which are a multiple of 3 in length. +Each group of 3 members is equal to one line of preferences in the plus minus +table. +""" +#needed for python 2.5, unneeded for python 2.6 +from __future__ import with_statement + +import string + +import os +import re +import sys + +import gtk.gdk as gdk + +try: + import pygtk + pygtk.require("2.0") +except: + pass +try: + import gtk +except: + sys.exit(1) + +from common import Configi18n +global _ +_ = Configi18n._ + +import datetime + +import config + +from common import pythonifyVersion +import problemnotification as pn + + +# Special key in each dictionary which specifies the order of elements. +# It is very important to have a consistent and rational order when displaying +# these prefs to the user, and dictionaries are unsorted. + +ORDER_KEY = "__order__" + +# PLEASE NOTE: these values are duplicated in a dummy class whose function +# is to have them put into the translation template. If you change the values below +# then you MUST change the value in class i18TranslateMeThanks as well!! + +# *** Level 0 +DATE_TIME = 'Date time' +TEXT = 'Text' +FILENAME = 'Filename' +METADATA = 'Metadata' +SEQUENCES = 'Sequences' +JOB_CODE = 'Job code' + +SEPARATOR = os.sep + +# *** Level 1 + +# Date time +IMAGE_DATE = 'Image date' +TODAY = 'Today' +YESTERDAY = 'Yesterday' +VIDEO_DATE = 'Video date' +DOWNLOAD_TIME = 'Download time' + +# File name +NAME_EXTENSION = 'Name + extension' +NAME = 'Name' +EXTENSION = 'Extension' +IMAGE_NUMBER = 'Image number' +VIDEO_NUMBER = 'Video number' + +# Metadata +APERTURE = 'Aperture' +ISO = 'ISO' +EXPOSURE_TIME = 'Exposure time' +FOCAL_LENGTH = 'Focal length' +CAMERA_MAKE = 'Camera make' +CAMERA_MODEL = 'Camera model' +SHORT_CAMERA_MODEL = 'Short camera model' +SHORT_CAMERA_MODEL_HYPHEN = 'Hyphenated short camera model' +SERIAL_NUMBER = 'Serial number' +SHUTTER_COUNT = 'Shutter count' +OWNER_NAME = 'Owner name' + +# Video metadata +CODEC = 'Codec' +WIDTH = 'Width' +HEIGHT = 'Height' +FPS = 'Frames Per Second' +LENGTH = 'Length' + +#Image sequences +DOWNLOAD_SEQ_NUMBER = 'Downloads today' +SESSION_SEQ_NUMBER = 'Session number' +SUBFOLDER_SEQ_NUMBER = 'Subfolder number' +STORED_SEQ_NUMBER = 'Stored number' + +SEQUENCE_LETTER = 'Sequence letter' + + + +# *** Level 2 + +# Image number +IMAGE_NUMBER_ALL = 'All digits' +IMAGE_NUMBER_1 = 'Last digit' +IMAGE_NUMBER_2 = 'Last 2 digits' +IMAGE_NUMBER_3 = 'Last 3 digits' +IMAGE_NUMBER_4 = 'Last 4 digits' + + +# Case +ORIGINAL_CASE = "Original Case" +UPPERCASE = "UPPERCASE" +LOWERCASE = "lowercase" + +# Sequence number +SEQUENCE_NUMBER_1 = "One digit" +SEQUENCE_NUMBER_2 = "Two digits" +SEQUENCE_NUMBER_3 = "Three digits" +SEQUENCE_NUMBER_4 = "Four digits" +SEQUENCE_NUMBER_5 = "Five digits" +SEQUENCE_NUMBER_6 = "Six digits" +SEQUENCE_NUMBER_7 = "Seven digits" + + +# Now, define dictionaries and lists of valid combinations of preferences. + +# Level 2 + +# Date + +SUBSECONDS = 'Subseconds' + +# ****** NOTE 1: if changing LIST_DATE_TIME_L2, you MUST update the default subfolder preference below ***** +# ****** NOTE 2: if changing LIST_DATE_TIME_L2, you MUST update DATE_TIME_CONVERT below ***** +LIST_DATE_TIME_L2 = ['YYYYMMDD', 'YYYY-MM-DD','YYMMDD', 'YY-MM-DD', + 'MMDDYYYY', 'MMDDYY', 'MMDD', + 'DDMMYYYY', 'DDMMYY', 'YYYY', 'YY', + 'MM', 'DD', + 'HHMMSS', 'HHMM', 'HH-MM-SS', 'HH-MM', 'HH', 'MM (minutes)', 'SS'] + + +LIST_IMAGE_DATE_TIME_L2 = LIST_DATE_TIME_L2 + [SUBSECONDS] + +DEFAULT_SUBFOLDER_PREFS = [DATE_TIME, IMAGE_DATE, LIST_DATE_TIME_L2[9], '/', '', '', DATE_TIME, IMAGE_DATE, LIST_DATE_TIME_L2[0]] +DEFAULT_VIDEO_SUBFOLDER_PREFS = [DATE_TIME, VIDEO_DATE, LIST_DATE_TIME_L2[9], '/', '', '', DATE_TIME, VIDEO_DATE, LIST_DATE_TIME_L2[0]] + +class i18TranslateMeThanks: + """ this class is never used in actual running code + It's purpose is to have these values inserted into the program's i18n template file + + """ + def __init__(self): + _('Date time') + _('Text') + _('Filename') + _('Metadata') + _('Sequences') + # Translators: for an explanation of what this means, see http://damonlynch.net/rapid/documentation/index.html#jobcode + _('Job code') + _('Image date') + _('Video date') + _('Today') + _('Yesterday') + # Translators: Download time is the time and date that the download started (when the user clicked the Download button) + _('Download time') + # Translators: for an explanation of what this means, see http://damonlynch.net/rapid/documentation/index.html#renamefilename + _('Name + extension') + # Translators: for an explanation of what this means, see http://damonlynch.net/rapid/documentation/index.html#renamefilename + _('Name') + # Translators: for an explanation of what this means, see http://damonlynch.net/rapid/documentation/index.html#renamefilename + _('Extension') + # Translators: for an explanation of what this means, see http://damonlynch.net/rapid/documentation/index.html#renamefilename + _('Image number') + _('Video number') + # Translators: for an explanation of what this means, see http://damonlynch.net/rapid/documentation/index.html#renamemetadata + _('Aperture') + # Translators: for an explanation of what this means, see http://damonlynch.net/rapid/documentation/index.html#renamemetadata + _('ISO') + # Translators: for an explanation of what this means, see http://damonlynch.net/rapid/documentation/index.html#renamemetadata + _('Exposure time') + # Translators: for an explanation of what this means, see http://damonlynch.net/rapid/documentation/index.html#renamemetadata + _('Focal length') + # Translators: for an explanation of what this means, see http://damonlynch.net/rapid/documentation/index.html#renamemetadata + _('Camera make') + # Translators: for an explanation of what this means, see http://damonlynch.net/rapid/documentation/index.html#renamemetadata + _('Camera model') + # Translators: for an explanation of what this means, see http://damonlynch.net/rapid/documentation/index.html#renamemetadata + _('Short camera model') + # Translators: for an explanation of what this means, see http://damonlynch.net/rapid/documentation/index.html#renamemetadata + _('Hyphenated short camera model') + # Translators: for an explanation of what this means, see http://damonlynch.net/rapid/documentation/index.html#renamemetadata + _('Serial number') + # Translators: for an explanation of what this means, see http://damonlynch.net/rapid/documentation/index.html#renamemetadata + _('Shutter count') + # Translators: for an explanation of what this means, see http://damonlynch.net/rapid/documentation/index.html#renamemetadata + _('Owner name') + _('Codec') + _('Width') + _('Height') + _('Length') + _('Frames Per Second') + # Translators: for an explanation of what this means, see http://damonlynch.net/rapid/documentation/index.html#sequencenumbers + _('Downloads today') + # Translators: for an explanation of what this means, see http://damonlynch.net/rapid/documentation/index.html#sequencenumbers + _('Session number') + # Translators: for an explanation of what this means, see http://damonlynch.net/rapid/documentation/index.html#sequencenumbers + _('Subfolder number') + # Translators: for an explanation of what this means, see http://damonlynch.net/rapid/documentation/index.html#sequencenumbers + _('Stored number') + # Translators: for an explanation of what this means, see http://damonlynch.net/rapid/documentation/index.html#sequenceletters + _('Sequence letter') + # Translators: for an explanation of what this means, see http://damonlynch.net/rapid/documentation/index.html#renamefilename + _('All digits') + # Translators: for an explanation of what this means, see http://damonlynch.net/rapid/documentation/index.html#renamefilename + _('Last digit') + # Translators: for an explanation of what this means, see http://damonlynch.net/rapid/documentation/index.html#renamefilename + _('Last 2 digits') + # Translators: for an explanation of what this means, see http://damonlynch.net/rapid/documentation/index.html#renamefilename + _('Last 3 digits') + # Translators: for an explanation of what this means, see http://damonlynch.net/rapid/documentation/index.html#renamefilename + _('Last 4 digits') + # Translators: please not the capitalization of this text, and keep it the same if your language features capitalization + _("Original Case") + # Translators: please not the capitalization of this text, and keep it the same if your language features capitalization + _("UPPERCASE") + # Translators: please not the capitalization of this text, and keep it the same if your language features capitalization + _("lowercase") + _("One digit") + _("Two digits") + _("Three digits") + _("Four digits") + _("Five digits") + _("Six digits") + _("Seven digits") + # Translators: for an explanation of what this means, see http://damonlynch.net/rapid/documentation/index.html#renamedateandtime + _('Subseconds') + # Translators: for an explanation of what this means, see http://damonlynch.net/rapid/documentation/index.html#renamedateandtime + _('YYYYMMDD') + # Translators: for an explanation of what this means, see http://damonlynch.net/rapid/documentation/index.html#renamedateandtime + _('YYYY-MM-DD') + # Translators: for an explanation of what this means, see http://damonlynch.net/rapid/documentation/index.html#renamedateandtime + _('YYMMDD') + # Translators: for an explanation of what this means, see http://damonlynch.net/rapid/documentation/index.html#renamedateandtime + _('YY-MM-DD') + # Translators: for an explanation of what this means, see http://damonlynch.net/rapid/documentation/index.html#renamedateandtime + _('MMDDYYYY') + # Translators: for an explanation of what this means, see http://damonlynch.net/rapid/documentation/index.html#renamedateandtime + _('MMDDYY') + # Translators: for an explanation of what this means, see http://damonlynch.net/rapid/documentation/index.html#renamedateandtime + _('MMDD') + # Translators: for an explanation of what this means, see http://damonlynch.net/rapid/documentation/index.html#renamedateandtime + _('DDMMYYYY') + # Translators: for an explanation of what this means, see http://damonlynch.net/rapid/documentation/index.html#renamedateandtime + _('DDMMYY') + # Translators: for an explanation of what this means, see http://damonlynch.net/rapid/documentation/index.html#renamedateandtime + _('YYYY') + # Translators: for an explanation of what this means, see http://damonlynch.net/rapid/documentation/index.html#renamedateandtime + _('YY') + # Translators: for an explanation of what this means, see http://damonlynch.net/rapid/documentation/index.html#renamedateandtime + _('MM') + # Translators: for an explanation of what this means, see http://damonlynch.net/rapid/documentation/index.html#renamedateandtime + _('DD') + # Translators: for an explanation of what this means, see http://damonlynch.net/rapid/documentation/index.html#renamedateandtime + _('HHMMSS') + # Translators: for an explanation of what this means, see http://damonlynch.net/rapid/documentation/index.html#renamedateandtime + _('HHMM') + # Translators: for an explanation of what this means, see http://damonlynch.net/rapid/documentation/index.html#renamedateandtime + _('HH-MM-SS') + # Translators: for an explanation of what this means, see http://damonlynch.net/rapid/documentation/index.html#renamedateandtime + _('HH-MM') + # Translators: for an explanation of what this means, see http://damonlynch.net/rapid/documentation/index.html#renamedateandtime + _('HH') + # Translators: for an explanation of what this means, see http://damonlynch.net/rapid/documentation/index.html#renamedateandtime + _('MM (minutes)') + # Translators: for an explanation of what this means, see http://damonlynch.net/rapid/documentation/index.html#renamedateandtime + _('SS') + + +# Convenience values for python datetime conversion using values in +# LIST_DATE_TIME_L2. Obviously the two must remain synchronized. + +DATE_TIME_CONVERT = ['%Y%m%d', '%Y-%m-%d','%y%m%d', '%y-%m-%d', + '%m%d%Y', '%m%d%y', '%m%d', + '%d%m%Y', '%d%m%y', '%Y', '%y', + '%m', '%d', + '%H%M%S', '%H%M', '%H-%M-%S', '%H-%M', + '%H', '%M', '%S'] + + +LIST_IMAGE_NUMBER_L2 = [IMAGE_NUMBER_ALL, IMAGE_NUMBER_1, IMAGE_NUMBER_2, + IMAGE_NUMBER_3, IMAGE_NUMBER_4] + + +LIST_CASE_L2 = [ORIGINAL_CASE, UPPERCASE, LOWERCASE] + +LIST_SEQUENCE_LETTER_L2 = [ + UPPERCASE, + LOWERCASE + ] + + + +LIST_SEQUENCE_NUMBERS_L2 = [ + SEQUENCE_NUMBER_1, + SEQUENCE_NUMBER_2, + SEQUENCE_NUMBER_3, + SEQUENCE_NUMBER_4, + SEQUENCE_NUMBER_5, + SEQUENCE_NUMBER_6, + SEQUENCE_NUMBER_7, + ] + + + +LIST_SHUTTER_COUNT_L2 = [ + SEQUENCE_NUMBER_3, + SEQUENCE_NUMBER_4, + SEQUENCE_NUMBER_5, + SEQUENCE_NUMBER_6, + ] + +# Level 1 +LIST_DATE_TIME_L1 = [IMAGE_DATE, TODAY, YESTERDAY, DOWNLOAD_TIME] +LIST_VIDEO_DATE_TIME_L1 = [VIDEO_DATE, TODAY, YESTERDAY, DOWNLOAD_TIME] + +DICT_DATE_TIME_L1 = { + IMAGE_DATE: LIST_IMAGE_DATE_TIME_L2, + TODAY: LIST_DATE_TIME_L2, + YESTERDAY: LIST_DATE_TIME_L2, + DOWNLOAD_TIME: LIST_DATE_TIME_L2, + ORDER_KEY: LIST_DATE_TIME_L1 + } + +VIDEO_DICT_DATE_TIME_L1 = { + VIDEO_DATE: LIST_IMAGE_DATE_TIME_L2, + TODAY: LIST_DATE_TIME_L2, + YESTERDAY: LIST_DATE_TIME_L2, + DOWNLOAD_TIME: LIST_DATE_TIME_L2, + ORDER_KEY: LIST_VIDEO_DATE_TIME_L1 + } + + +LIST_FILENAME_L1 = [NAME_EXTENSION, NAME, EXTENSION, IMAGE_NUMBER] + +DICT_FILENAME_L1 = { + NAME_EXTENSION: LIST_CASE_L2, + NAME: LIST_CASE_L2, + EXTENSION: LIST_CASE_L2, + IMAGE_NUMBER: LIST_IMAGE_NUMBER_L2, + ORDER_KEY: LIST_FILENAME_L1 + } + +LIST_VIDEO_FILENAME_L1 = [NAME_EXTENSION, NAME, EXTENSION, VIDEO_NUMBER] + +DICT_VIDEO_FILENAME_L1 = { + NAME_EXTENSION: LIST_CASE_L2, + NAME: LIST_CASE_L2, + EXTENSION: LIST_CASE_L2, + VIDEO_NUMBER: LIST_IMAGE_NUMBER_L2, + ORDER_KEY: LIST_VIDEO_FILENAME_L1 + } + + +LIST_SUBFOLDER_FILENAME_L1 = [EXTENSION] + +DICT_SUBFOLDER_FILENAME_L1 = { + EXTENSION: LIST_CASE_L2, + ORDER_KEY: LIST_SUBFOLDER_FILENAME_L1 +} + +LIST_METADATA_L1 = [APERTURE, ISO, EXPOSURE_TIME, FOCAL_LENGTH, + CAMERA_MAKE, CAMERA_MODEL, + SHORT_CAMERA_MODEL, + SHORT_CAMERA_MODEL_HYPHEN, + SERIAL_NUMBER, + SHUTTER_COUNT, + OWNER_NAME] + +LIST_VIDEO_METADATA_L1 = [CODEC, WIDTH, HEIGHT, LENGTH, FPS] + +DICT_METADATA_L1 = { + APERTURE: None, + ISO: None, + EXPOSURE_TIME: None, + FOCAL_LENGTH: None, + CAMERA_MAKE: LIST_CASE_L2, + CAMERA_MODEL: LIST_CASE_L2, + SHORT_CAMERA_MODEL: LIST_CASE_L2, + SHORT_CAMERA_MODEL_HYPHEN: LIST_CASE_L2, + SERIAL_NUMBER: None, + SHUTTER_COUNT: LIST_SHUTTER_COUNT_L2, + OWNER_NAME: LIST_CASE_L2, + ORDER_KEY: LIST_METADATA_L1 + } + +DICT_VIDEO_METADATA_L1 = { + CODEC: LIST_CASE_L2, + WIDTH: None, + HEIGHT: None, + LENGTH: None, + FPS: None, + ORDER_KEY: LIST_VIDEO_METADATA_L1 + } + +LIST_SEQUENCE_L1 = [ + DOWNLOAD_SEQ_NUMBER, + STORED_SEQ_NUMBER, + SESSION_SEQ_NUMBER, + SEQUENCE_LETTER + ] + +DICT_SEQUENCE_L1 = { + DOWNLOAD_SEQ_NUMBER: LIST_SEQUENCE_NUMBERS_L2, + STORED_SEQ_NUMBER: LIST_SEQUENCE_NUMBERS_L2, + SESSION_SEQ_NUMBER: LIST_SEQUENCE_NUMBERS_L2, + SEQUENCE_LETTER: LIST_SEQUENCE_LETTER_L2, + ORDER_KEY: LIST_SEQUENCE_L1 + } + + +# Level 0 + + +LIST_IMAGE_RENAME_L0 = [DATE_TIME, TEXT, FILENAME, METADATA, + SEQUENCES, JOB_CODE] + +LIST_VIDEO_RENAME_L0 = LIST_IMAGE_RENAME_L0 + + +DICT_IMAGE_RENAME_L0 = { + DATE_TIME: DICT_DATE_TIME_L1, + TEXT: None, + FILENAME: DICT_FILENAME_L1, + METADATA: DICT_METADATA_L1, + SEQUENCES: DICT_SEQUENCE_L1, + JOB_CODE: None, + ORDER_KEY: LIST_IMAGE_RENAME_L0 + } + +DICT_VIDEO_RENAME_L0 = { + DATE_TIME: VIDEO_DICT_DATE_TIME_L1, + TEXT: None, + FILENAME: DICT_VIDEO_FILENAME_L1, + METADATA: DICT_VIDEO_METADATA_L1, + SEQUENCES: DICT_SEQUENCE_L1, + JOB_CODE: None, + ORDER_KEY: LIST_VIDEO_RENAME_L0 + } + +LIST_SUBFOLDER_L0 = [DATE_TIME, TEXT, FILENAME, METADATA, JOB_CODE, SEPARATOR] + +DICT_SUBFOLDER_L0 = { + DATE_TIME: DICT_DATE_TIME_L1, + TEXT: None, + FILENAME: DICT_SUBFOLDER_FILENAME_L1, + METADATA: DICT_METADATA_L1, + JOB_CODE: None, + SEPARATOR: None, + ORDER_KEY: LIST_SUBFOLDER_L0 + } + +LIST_VIDEO_SUBFOLDER_L0 = [DATE_TIME, TEXT, FILENAME, METADATA, JOB_CODE, SEPARATOR] + +DICT_VIDEO_SUBFOLDER_L0 = { + DATE_TIME: VIDEO_DICT_DATE_TIME_L1, + TEXT: None, + FILENAME: DICT_SUBFOLDER_FILENAME_L1, + METADATA: DICT_VIDEO_METADATA_L1, + JOB_CODE: None, + SEPARATOR: None, + ORDER_KEY: LIST_VIDEO_SUBFOLDER_L0 + } + +# preference elements that require metadata +# note there is no need to specify lower level elements if a higher level +# element is necessary for them to be present to begin with +METADATA_ELEMENTS = [METADATA, IMAGE_DATE] + +# preference elements that are sequence numbers or letters +SEQUENCE_ELEMENTS = [ + DOWNLOAD_SEQ_NUMBER, + SESSION_SEQ_NUMBER, + SUBFOLDER_SEQ_NUMBER, + STORED_SEQ_NUMBER, + SEQUENCE_LETTER] + +# preference elements that do not require metadata and are not fixed +# as above, there is no need to specify lower level elements if a higher level +# element is necessary for them to be present to begin with +DYNAMIC_NON_METADATA_ELEMENTS = [ + TODAY, YESTERDAY, + FILENAME] + SEQUENCE_ELEMENTS + + + +#the following is what the preferences looked in older versions of the program +#they are here for reference, and for checking the validity of preferences + +USER_INPUT = 'User' + +DOWNLOAD_SEQ_NUMBER_V_0_0_8_B7 = 'Downloads today' +SESSION_SEQ_NUMBER_V_0_0_8_B7 = 'Session sequence number' +SUBFOLDER_SEQ_NUMBER_V_0_0_8_B7 = 'Subfolder sequence number' +STORED_SEQ_NUMBER_V_0_0_8_B7 = 'Stored sequence number' +SEQUENCE_LETTER_V_0_0_8_B7 = 'Sequence letter' + +LIST_SEQUENCE_NUMBERS_L1_L2_V_0_0_8_B7 = [ + SEQUENCE_NUMBER_1, + SEQUENCE_NUMBER_2, + SEQUENCE_NUMBER_3, + SEQUENCE_NUMBER_4, + SEQUENCE_NUMBER_5, + SEQUENCE_NUMBER_6, + ] + +DICT_SEQUENCE_NUMBERS_L1_L2_V_0_0_8_B7 = { + SEQUENCE_NUMBER_1: None, + SEQUENCE_NUMBER_2: None, + SEQUENCE_NUMBER_3: None, + SEQUENCE_NUMBER_4: None, + SEQUENCE_NUMBER_5: None, + SEQUENCE_NUMBER_6: None, + ORDER_KEY: LIST_SEQUENCE_NUMBERS_L1_L2_V_0_0_8_B7 + } + +LIST_SEQUENCE_L1_V_0_0_8_B7 = [USER_INPUT] + +DICT_SEQUENCE_L1_V_0_0_8_B7 = { + USER_INPUT: DICT_SEQUENCE_NUMBERS_L1_L2_V_0_0_8_B7, + ORDER_KEY: LIST_SEQUENCE_L1_V_0_0_8_B7 + } + +LIST_SEQUENCE_LETTER_L1_L1_V_0_0_8_B7 = [ + UPPERCASE, + LOWERCASE + ] + +DICT_SEQUENCE_LETTER_L1_V_0_0_8_B7 = { + UPPERCASE: None, + LOWERCASE: None, + ORDER_KEY: LIST_SEQUENCE_LETTER_L1_L1_V_0_0_8_B7 + } + +LIST_IMAGE_RENAME_L0_V_0_0_8_B7 = [DATE_TIME, TEXT, FILENAME, METADATA, + DOWNLOAD_SEQ_NUMBER_V_0_0_8_B7, + SESSION_SEQ_NUMBER_V_0_0_8_B7, + SEQUENCE_LETTER_V_0_0_8_B7] + +DICT_IMAGE_RENAME_L0_V_0_0_8_B7 = { + DATE_TIME: DICT_DATE_TIME_L1, + TEXT: None, + FILENAME: DICT_FILENAME_L1, + METADATA: DICT_METADATA_L1, + DOWNLOAD_SEQ_NUMBER_V_0_0_8_B7: None, + SESSION_SEQ_NUMBER_V_0_0_8_B7: None, + SEQUENCE_LETTER_V_0_0_8_B7: DICT_SEQUENCE_LETTER_L1_V_0_0_8_B7, + ORDER_KEY: LIST_IMAGE_RENAME_L0_V_0_0_8_B7 + } + +PREVIOUS_IMAGE_RENAME= { + '0.0.8~b7': DICT_IMAGE_RENAME_L0_V_0_0_8_B7, + } + + +# Functions to work with above data + +def _getPrevPrefs(oldDefs, currentDefs, previousVersion): + k = oldDefs.keys() + # if there were other defns, we'd need to figure out which one + # but currently, there are no others + # there will be in future, and this code wil be updated then + version_change = pythonifyVersion(k[0]) + if pythonifyVersion(previousVersion) <= version_change: + return oldDefs[k[0]] + else: + return currentDefs + +def _upgradePreferencesToCurrent(prefs, previousVersion): + """ checks to see if preferences should be upgraded + + returns True if they were upgraded, and the new prefs + + VERY IMPORTANT: the new prefs will be a new list, not an inplace + modification of the existing preferences! Otherwise, the check on + assignment in the prefs.py __setattr__ will not work as expected!! + """ + upgraded = False + # code to upgrade from <= 0.0.8~b7 to >= 0.0.8~b8 + p = [] + for i in range(0, len(prefs), 3): + if prefs[i] in [SEQUENCE_LETTER_V_0_0_8_B7, SESSION_SEQ_NUMBER_V_0_0_8_B7]: + upgraded = True + p.append(SEQUENCES) + if prefs[i] == SEQUENCE_LETTER_V_0_0_8_B7: + p.append(SEQUENCE_LETTER) + p.append(prefs[i+1]) + else: + p.append(SESSION_SEQ_NUMBER) + p.append(prefs[i+2]) + else: + p += prefs[i:i+3] + + assert(len(prefs)==len(p)) + return (upgraded, p) + + +def upgradePreferencesToCurrent(imageRenamePrefs, subfolderPrefs, previousVersion): + """Upgrades user preferences to current version + + returns True if the preferences were upgraded""" + + # only check image rename, for now.... + upgraded, imageRenamePrefs = _upgradePreferencesToCurrent(imageRenamePrefs, previousVersion) + return (upgraded, imageRenamePrefs , subfolderPrefs) + + +def usesJobCode(prefs): + """ Returns True if the preferences contain a job code, else returns False""" + for i in range(0, len(prefs), 3): + if prefs[i] == JOB_CODE: + return True + return False + +def checkPreferencesForValidity(imageRenamePrefs, subfolderPrefs, videoRenamePrefs, videoSubfolderPrefs, version=config.version): + """ + Checks preferences for validity (called at program startup) + + Returns true if the passed in preferences are valid, else returns False + """ + + if version == config.version: + try: + tests = ((imageRenamePrefs, ImageRenamePreferences), + (subfolderPrefs, SubfolderPreferences), + (videoRenamePrefs, VideoRenamePreferences), + (videoSubfolderPrefs, VideoSubfolderPreferences)) + for i, Prefs in tests: + p = Prefs(i, None) + p.checkPrefsForValidity() + except: + return False + return True + else: + defn = _getPrevPrefs(PREVIOUS_IMAGE_RENAME, DICT_IMAGE_RENAME_L0, version) + try: + checkPreferenceValid(defn, imageRenamePrefs) + checkPreferenceValid(DICT_SUBFOLDER_L0, subfolderPrefs) + checkPreferenceValid(DICT_VIDEO_SUBFOLDER_L0, videoSubfolderPrefs) + checkPreferenceValid(DICT_VIDEO_RENAME_L0, videoRenamePrefs) + except: + return False + return True + +def checkPreferenceValid(prefDefinition, prefs, modulo=3): + """ + Checks to see if prefs are valid according to definition. + + prefs is a list of preferences. + prefDefinition is a Dict specifying what is valid. + modulo is how many list elements are equivalent to one line of preferences. + + Returns True if prefs match with prefDefinition, + else raises appropriate error. + """ + + if (len(prefs) % modulo <> 0) or not prefs: + raise PrefLengthError(prefs) + else: + for i in range(0, len(prefs), modulo): + _checkPreferenceValid(prefDefinition, prefs[i:i+modulo]) + + return True + +def _checkPreferenceValid(prefDefinition, prefs): + + key = prefs[0] + value = prefs[1] + + + if prefDefinition.has_key(key): + + nextPrefDefinition = prefDefinition[key] + + if value == None: + # value should never be None, at any time + raise PrefValueInvalidError((None, nextPrefDefinition)) + + if nextPrefDefinition and not value: + raise PrefValueInvalidError((value, nextPrefDefinition)) + + if type(nextPrefDefinition) == type({}): + return _checkPreferenceValid(nextPrefDefinition, prefs[1:]) + else: + if type(nextPrefDefinition) == type([]): + result = value in nextPrefDefinition + if not result: + raise PrefValueInvalidError((value, nextPrefDefinition)) + return True + elif not nextPrefDefinition: + return True + else: + result = nextPrefDefinition == value + if not result: + raise PrefKeyValue((value, nextPrefDefinition)) + return True + else: + raise PrefKeyError((key, prefDefinition[ORDER_KEY])) + +def filterSubfolderPreferences(prefList): + """ + Filters out extraneous preference choices + """ + prefs_changed = False + continueCheck = True + while continueCheck and prefList: + continueCheck = False + if prefList[0] == SEPARATOR: + # Subfolder preferences should not start with a / + prefList = prefList[3:] + prefs_changed = True + continueCheck = True + elif prefList[-3] == SEPARATOR: + # Subfolder preferences should not end with a / + prefList = prefList[:-3] + continueCheck = True + prefs_changed = True + else: + for i in range(0, len(prefList) - 3, 3): + if prefList[i] == SEPARATOR and prefList[i+3] == SEPARATOR: + # Subfolder preferences should not contain two /s side by side + continueCheck = True + prefs_changed = True + # note we are messing with the contents of the pref list, + # must exit loop and try again + prefList = prefList[:i] + prefList[i+3:] + break + + return (prefs_changed, prefList) + + +class PrefError(Exception): + """ base class """ + def unpackList(self, l): + """ + Make the preferences presentable to the user + """ + + s = '' + for i in l: + if i <> ORDER_KEY: + s += "'" + i + "', " + return s[:-2] + + def __str__(self): + return self.msg + +class PrefKeyError(PrefError): + def __init__(self, error): + value = error[0] + expectedValues = self.unpackList(error[1]) + self.msg = _("Preference key '%(key)s' is invalid.\nExpected one of %(value)s") % { + 'key': value, 'value': expectedValues} + + +class PrefValueInvalidError(PrefKeyError): + def __init__(self, error): + value = error[0] + self.msg = _("Preference value '%(value)s' is invalid") % {'value': value} + +class PrefLengthError(PrefError): + def __init__(self, error): + self.msg = _("These preferences are not well formed:") + "\n %s" % self.unpackList(error) + +class PrefValueKeyComboError(PrefError): + def __init__(self, error): + self.msg = error + + +def convertDateForStrftime(dateTimeUserChoice): + try: + return DATE_TIME_CONVERT[LIST_DATE_TIME_L2.index(dateTimeUserChoice)] + except: + raise PrefValueInvalidError(dateTimeUserChoice) + + +class Comboi18n(gtk.ComboBox): + """ very simple i18n version of the venerable combo box + with one column displayed to the user. + + This combo box has two columns: + 1. the first contains the actual value and is invisible + 2. the second contains the translation of the first column, and this is what + the users sees + """ + def __init__(self): + liststore = gtk.ListStore(str, str) + gtk.ComboBox.__init__(self, liststore) + cell = gtk.CellRendererText() + self.pack_start(cell, True) + self.add_attribute(cell, 'text', 1) + # must name the combo box on pygtk used in Ubuntu 11.04, Fedora 15, etc. + self.set_name('GtkComboBox') + + def append_text(self, text): + model = self.get_model() + model.append((text, _(text))) + + def get_active_text(self): + model = self.get_model() + active = self.get_active() + if active < 0: + return None + return model[active][0] + +class ImageRenamePreferences: + def __init__(self, prefList, parent, fileSequenceLock=None, sequences=None): + """ + Exception raised if preferences are invalid. + + This should be caught by calling class.""" + + self.parent = parent + self.prefList = prefList + + # use variables for determining sequence numbers + # there are two possibilities: + # 1. this code is being called while being run from within a copy photos process + # 2. it's being called from within the preferences dialog window + + self.fileSequenceLock = fileSequenceLock + self.sequences = sequences + + self.job_code = '' + + # derived classes will have their own definitions, do not overwrite + if not hasattr(self, "prefsDefnL0"): + self.prefsDefnL0 = DICT_IMAGE_RENAME_L0 + self.defaultPrefs = [FILENAME, NAME_EXTENSION, ORIGINAL_CASE] + self.defaultRow = self.defaultPrefs + self.stripForwardSlash = True + self.L1DateCheck = IMAGE_DATE #used in _getDateComponent() + self.component = pn.FILENAME_COMPONENT + + + def initializeProblem(self, problem): + """ + Set the problem tracker used in name generation + """ + self.problem = problem + + def getProblems(self): + """ + Returns Problem class if there were problems, else returns None. + """ + if self.problem.has_problem(): + return self.problem + else: + return None + + def checkPrefsForValidity(self): + """ + Checks image preferences validity + """ + + return checkPreferenceValid(self.prefsDefnL0, self.prefList) + + def formatPreferencesForPrettyPrint(self): + """ returns a string useful for printing the preferences""" + + v = '' + + for i in range(0, len(self.prefList), 3): + if (self.prefList[i+1] or self.prefList[i+2]): + c = ':' + else: + c = '' + s = "%s%s " % (self.prefList[i], c) + + if self.prefList[i+1]: + s = "%s%s" % (s, self.prefList[i+1]) + if self.prefList[i+2]: + s = "%s (%s)" % (s, self.prefList[i+2]) + + v += s + "\n" + return v + + + def setJobCode(self, job_code): + self.job_code = job_code + + def setDownloadStartTime(self, download_start_time): + self.download_start_time = download_start_time + + def _getDateComponent(self): + """ + Returns portion of new image / subfolder name based on date time. + If the date is missing, will attempt to use the fallback date. + """ + + # step 1: get the correct value from metadata + if self.L1 == self.L1DateCheck: + if self.L2 == SUBSECONDS: + d = self.metadata.subSeconds() + if d == '00': + self.problem.add_problem(self.component, pn.MISSING_METADATA, _(self.L2)) + return '' + else: + return d + else: + d = self.metadata.dateTime(missing=None) + + elif self.L1 == TODAY: + d = datetime.datetime.now() + elif self.L1 == YESTERDAY: + delta = datetime.timedelta(days = 1) + d = datetime.datetime.now() - delta + elif self.L1 == DOWNLOAD_TIME: + d = self.download_start_time + else: + raise("Date options invalid") + + # step 2: handle a missing value + if not d: + if self.fallback_date: + try: + d = datetime.datetime.fromtimestamp(self.fallback_date) + except: + self.problem.add_problem(self.component, pn.INVALID_DATE_TIME, '') + return '' + else: + self.problem.add_problem(self.component, pn.MISSING_METADATA, _(self.L1)) + return '' + + try: + return d.strftime(convertDateForStrftime(self.L2)) + except: + self.problem.add_problem(self.component, pn.INVALID_DATE_TIME, d) + return '' + + def _getFilenameComponent(self): + """ + Returns portion of new image / subfolder name based on the file name + """ + + name, extension = os.path.splitext(self.existingFilename) + + if self.L1 == NAME_EXTENSION: + filename = self.existingFilename + elif self.L1 == NAME: + filename = name + elif self.L1 == EXTENSION: + if extension: + if not self.stripInitialPeriodFromExtension: + # keep the period / dot of the extension, so the user does not + # need to manually specify it + filename = extension + else: + # having the period when this is used as a part of a subfolder name + # is a bad idea! + filename = extension[1:] + else: + self.problem.add_problem(self.component, pn.MISSING_FILE_EXTENSION) + return "" + elif self.L1 == IMAGE_NUMBER or self.L1 == VIDEO_NUMBER: + n = re.search("(?P[0-9]+$)", name) + if not n: + self.problem.add_problem(self.component, pn.MISSING_IMAGE_NUMBER) + return '' + else: + image_number = n.group("image_number") + + if self.L2 == IMAGE_NUMBER_ALL: + filename = image_number + elif self.L2 == IMAGE_NUMBER_1: + filename = image_number[-1] + elif self.L2 == IMAGE_NUMBER_2: + filename = image_number[-2:] + elif self.L2 == IMAGE_NUMBER_3: + filename = image_number[-3:] + elif self.L2 == IMAGE_NUMBER_4: + filename = image_number[-4:] + else: + raise TypeError("Incorrect filename option") + + if self.L2 == UPPERCASE: + filename = filename.upper() + elif self.L2 == LOWERCASE: + filename = filename.lower() + + return filename + + def _getMetadataComponent(self): + """ + Returns portion of new image / subfolder name based on the metadata + + Note: date time metadata found in _getDateComponent() + """ + + if self.L1 == APERTURE: + v = self.metadata.aperture() + elif self.L1 == ISO: + v = self.metadata.iso() + elif self.L1 == EXPOSURE_TIME: + v = self.metadata.exposureTime(alternativeFormat=True) + elif self.L1 == FOCAL_LENGTH: + v = self.metadata.focalLength() + elif self.L1 == CAMERA_MAKE: + v = self.metadata.cameraMake() + elif self.L1 == CAMERA_MODEL: + v = self.metadata.cameraModel() + elif self.L1 == SHORT_CAMERA_MODEL: + v = self.metadata.shortCameraModel() + elif self.L1 == SHORT_CAMERA_MODEL_HYPHEN: + v = self.metadata.shortCameraModel(includeCharacters = "\-") + elif self.L1 == SERIAL_NUMBER: + v = self.metadata.cameraSerial() + elif self.L1 == SHUTTER_COUNT: + v = self.metadata.shutterCount() + if v: + v = int(v) + padding = LIST_SHUTTER_COUNT_L2.index(self.L2) + 3 + formatter = '%0' + str(padding) + "i" + v = formatter % v + + elif self.L1 == OWNER_NAME: + v = self.metadata.ownerName() + else: + raise TypeError("Invalid metadata option specified") + if self.L1 in [CAMERA_MAKE, CAMERA_MODEL, SHORT_CAMERA_MODEL, + SHORT_CAMERA_MODEL_HYPHEN, OWNER_NAME]: + if self.L2 == UPPERCASE: + v = v.upper() + elif self.L2 == LOWERCASE: + v = v.lower() + if not v: + self.problem.add_problem(self.component, pn.MISSING_METADATA, _(self.L1)) + return v + + + def _formatSequenceNo(self, value, amountToPad): + padding = LIST_SEQUENCE_NUMBERS_L2.index(amountToPad) + 1 + formatter = '%0' + str(padding) + "i" + return formatter % value + + + def _calculateLetterSequence(self, sequence): + + def _letters(x): + """ + Adapted from algorithm at http://en.wikipedia.org/wiki/Hexavigesimal + """ + v = '' + while x > 25: + r = x % 26 + x= x / 26 - 1 + v = string.lowercase[r] + v + v = string.lowercase[x] + v + + return v + + + v = _letters(sequence) + if self.L2 == UPPERCASE: + v = v.upper() + + return v + + def _getSubfolderSequenceNo(self): + """ + Add a sequence number to the filename + + * Sequence numbering is per subfolder + * Assume the user might actually have a (perhaps odd) reason to have more + than one subfolder sequence number in the same file name + """ + + self.subfolderSeqNoInstanceInFilename += 1 + + if self.downloadSubfolder: + subfolder = self.downloadSubfolder + str(self.subfolderSeqNoInstanceInFilename) + else: + subfolder = "__subfolder__" + str(self.subfolderSeqNoInstanceInFilename) + + if self.fileSequenceLock: + with self.fileSequenceLock: + v = self.sequenceNos.calculate(subfolder) + v = self.formatSequenceNo(v, self.L1) + else: + v = self.sequenceNos.calculate(subfolder) + v = self.formatSequenceNo(v, self.L1) + + return v + + def _getSessionSequenceNo(self): + return self._formatSequenceNo(self.sequences.getSessionSequenceNoUsingCounter(self.sequenceCounter), self.L2) + + def _getDownloadsTodaySequenceNo(self): + return self._formatSequenceNo(self.sequences.getDownloadsTodayUsingCounter(self.sequenceCounter), self.L2) + + + def _getStoredSequenceNo(self): + return self._formatSequenceNo(self.sequences.getStoredSequenceNoUsingCounter(self.sequenceCounter), self.L2) + + def _getSequenceLetter(self): + return self._calculateLetterSequence(self.sequences.getSequenceLetterUsingCounter(self.sequenceCounter)) + + + def _getSequencesComponent(self): + if self.L1 == DOWNLOAD_SEQ_NUMBER: + return self._getDownloadsTodaySequenceNo() + elif self.L1 == SESSION_SEQ_NUMBER: + return self._getSessionSequenceNo() + elif self.L1 == SUBFOLDER_SEQ_NUMBER: + return self._getSubfolderSequenceNo() + elif self.L1 == STORED_SEQ_NUMBER: + return self._getStoredSequenceNo() + elif self.L1 == SEQUENCE_LETTER: + return self._getSequenceLetter() + + def _getComponent(self): + try: + if self.L0 == DATE_TIME: + return self._getDateComponent() + elif self.L0 == TEXT: + return self.L1 + elif self.L0 == FILENAME: + return self._getFilenameComponent() + elif self.L0 == METADATA: + return self._getMetadataComponent() + elif self.L0 == SEQUENCES: + return self._getSequencesComponent() + elif self.L0 == JOB_CODE: + return self.job_code + elif self.L0 == SEPARATOR: + return os.sep + except: + self.problem.add_problem(self.component, pn.ERROR_IN_GENERATION, _(self.L0)) + return '' + + def _getValuesFromList(self): + for i in range(0, len(self.prefList), 3): + yield (self.prefList[i], self.prefList[i+1], self.prefList[i+2]) + + + def _generateName(self, metadata, existingFilename, stripCharacters, subfolder, stripInitialPeriodFromExtension, sequence, fallback_date): + self.metadata = metadata + self.existingFilename = existingFilename + self.stripInitialPeriodFromExtension = stripInitialPeriodFromExtension + self.fallback_date = fallback_date + + name = '' + + #the subfolder in which the image will be downloaded to + self.downloadSubfolder = subfolder + + self.sequenceCounter = sequence + + for self.L0, self.L1, self.L2 in self._getValuesFromList(): + v = self._getComponent() + if v: + name += v + + if stripCharacters: + for c in r'\:*?"<>|': + name = name.replace(c, '') + + if self.stripForwardSlash: + name = name.replace('/', '') + + name = name.strip() + + return name + + def generateNameUsingPreferences(self, metadata, existingFilename=None, + stripCharacters = False, subfolder=None, + stripInitialPeriodFromExtension=False, + sequencesPreliminary = True, + sequence_to_use = None, + fallback_date = None): + """ + Generate a filename for the photo or video in string format based on user preferences. + + Returns the name in string format + + Any problems encountered during the generation of the name can be accessed + through the method getProblems() + """ + + if self.sequences: + if sequence_to_use is not None: + sequence = sequence_to_use + elif sequencesPreliminary: + sequence = self.sequences.getPrelimSequence() + else: + sequence = self.sequences.getFinalSequence() + else: + sequence = 0 + + return self._generateName(metadata, existingFilename, stripCharacters, subfolder, + stripInitialPeriodFromExtension, sequence, fallback_date) + + def generateNameSequencePossibilities(self, metadata, existingFilename, + stripCharacters=False, subfolder=None, + stripInitialPeriodFromExtension=False): + + """ Generates the possible image names using the sequence numbers / letter possibilities""" + + for sequence in self.sequences.getSequencePossibilities(): + yield self._generateName(metadata, existingFilename, stripCharacters, subfolder, + stripInitialPeriodFromExtension, sequence) + + def filterPreferences(self): + """ + Filters out extraneous preference choices + Expected to be implemented in derived classes when needed + """ + pass + + def needImageMetaDataToCreateUniqueName(self): + """ + Returns True if an image's metadata is essential to properly generate a unique image name + + Image names should be unique. Some images may not have metadata. If + only non-dynamic components make up the rest of an image name + (e.g. text specified by the user), then relying on metadata will likely + produce duplicate names. + + File extensions are not considered dynamic. + + This is NOT a general test to see if unique filenames can be generated. It is a test + to see if an image's metadata is needed. + """ + hasMD = hasDynamic = False + + for e in METADATA_ELEMENTS: + if e in self.prefList: + hasMD = True + break + + if hasMD: + for e in DYNAMIC_NON_METADATA_ELEMENTS: + if e in self.prefList: + if e == FILENAME and (NAME_EXTENSION in self.prefList or + NAME in self.prefList or + IMAGE_NUMBER in self.prefList): + hasDynamic = True + break + + if hasMD and not hasDynamic: + return True + else: + return False + + def usesSequenceElements(self): + """ Returns true if any sequence numbers or letters are used to generate the filename """ + + for e in SEQUENCE_ELEMENTS: + if e in self.prefList: + return True + + return False + + def usesTheSequenceElement(self, e): + """ Returns true if a stored sequence number is used to generate the filename """ + return e in self.prefList + + + def _createCombo(self, choices): + combobox = Comboi18n() + for text in choices: + combobox.append_text(text) + return combobox + + def getDefaultRow(self): + """ + returns a list of default widgets + """ + return self.getWidgetsBasedOnUserSelection(self.defaultRow) + + def _getPreferenceWidgets(self, prefDefinition, prefs, widgets): + key = prefs[0] + value = prefs[1] + + # supply a default value if the user has not yet chosen a value! + if not key: + key = prefDefinition[ORDER_KEY][0] + + if not key in prefDefinition: + raise PrefKeyError((key, prefDefinition.keys())) + + + list0 = prefDefinition[ORDER_KEY] + + # the first widget will always be a combo box + widget0 = self._createCombo(list0) + widget0.set_active(list0.index(key)) + + widgets.append(widget0) + + if key == TEXT: + widget1 = gtk.Entry() + widget1.set_text(value) + + widgets.append(widget1) + widgets.append(None) + return + elif key in [SEPARATOR, JOB_CODE]: + widgets.append(None) + widgets.append(None) + return + else: + nextPrefDefinition = prefDefinition[key] + if type(nextPrefDefinition) == type({}): + return self._getPreferenceWidgets(nextPrefDefinition, + prefs[1:], + widgets) + else: + if type(nextPrefDefinition) == type([]): + widget1 = self._createCombo(nextPrefDefinition) + if not value: + value = nextPrefDefinition[0] + try: + widget1.set_active(nextPrefDefinition.index(value)) + except: + raise PrefValueInvalidError((value, nextPrefDefinition)) + + widgets.append(widget1) + else: + widgets.append(None) + + def getWidgetsBasedOnPreferences(self): + """ + Yields a list of widgets and their callbacks based on the users preferences. + + This list is equivalent to one row of preferences when presented to the + user in the Plus Minus Table. + """ + + for L0, L1, L2 in self._getValuesFromList(): + prefs = [L0, L1, L2] + widgets = [] + self._getPreferenceWidgets(self.prefsDefnL0, prefs, widgets) + yield widgets + + + def getWidgetsBasedOnUserSelection(self, selection): + """ + Returns a list of widgets and their callbacks based on what the user has selected. + + Selection is the values the user has chosen thus far in comboboxes. + It determines the contents of the widgets returned. + It should be a list of three values, with None for values not chosen. + For values which are None, the first value in the preferences + definition is chosen. + + """ + widgets = [] + + self._getPreferenceWidgets(self.prefsDefnL0, selection, widgets) + return widgets + +def getVideoMetadataComponent(video): + """ + Returns portion of video / subfolder name based on the metadata + + This is outside of a class definition because of the inheritence + hierarchy. + """ + + problem = None + if video.L1 == CODEC: + v = video.metadata.codec() + elif video.L1 == WIDTH: + v = video.metadata.width() + elif video.L1 == HEIGHT: + v = video.metadata.height() + elif video.L1 == FPS: + v = video.metadata.framesPerSecond() + elif video.L1 == LENGTH: + v = video.metadata.length() + else: + raise TypeError("Invalid metadata option specified") + if video.L1 in [CODEC]: + if video.L2 == UPPERCASE: + v = v.upper() + elif video.L2 == LOWERCASE: + v = v.lower() + if not v: + video.problem.add_problem(video.component, pn.MISSING_METADATA, _(video.L1)) + return v + +class VideoRenamePreferences(ImageRenamePreferences): + def __init__(self, prefList, parent, fileSequenceLock=None, sequences=None): + self.prefsDefnL0 = DICT_VIDEO_RENAME_L0 + self.defaultPrefs = [FILENAME, NAME_EXTENSION, ORIGINAL_CASE] + self.defaultRow = self.defaultPrefs + self.stripForwardSlash = True + self.L1DateCheck = VIDEO_DATE + self.component = pn.FILENAME_COMPONENT + ImageRenamePreferences.__init__(self, prefList, parent, fileSequenceLock, sequences) + + def _getMetadataComponent(self): + """ + Returns portion of video / subfolder name based on the metadata + + Note: date time metadata found in _getDateComponent() + """ + return getVideoMetadataComponent(self) + + +class SubfolderPreferences(ImageRenamePreferences): + def __init__(self, prefList, parent): + self.prefsDefnL0 = DICT_SUBFOLDER_L0 + self.defaultPrefs = DEFAULT_SUBFOLDER_PREFS + self.defaultRow = [DATE_TIME, IMAGE_DATE, LIST_DATE_TIME_L2[0]] + self.stripForwardSlash = False + self.L1DateCheck = IMAGE_DATE + self.component = pn.SUBFOLDER_COMPONENT + ImageRenamePreferences.__init__(self, prefList, parent) + + self.stripExtraneousWhiteSpace = re.compile(r'\s*%s\s*' % os.sep) + + def generateNameUsingPreferences(self, photo, existingFilename=None, + stripCharacters = False, fallback_date = None): + """ + Generate a filename for the photo in string format based on user prefs. + + Returns a tuple of two strings: + - the name + - any problems generating the name. If blank, there were no problems + """ + + subfolders = ImageRenamePreferences.generateNameUsingPreferences( + self, photo, + existingFilename, stripCharacters, + stripInitialPeriodFromExtension=True, + fallback_date=fallback_date) + # subfolder value must never start with a separator, or else any + # os.path.join function call will fail to join a subfolder to its + # parent folder + if subfolders: + if subfolders[0] == os.sep: + subfolders = subfolders[1:] + + # remove any spaces before and after a directory name + if subfolders and stripCharacters: + subfolders = self.stripExtraneousWhiteSpace.sub(os.sep, subfolders) + + return subfolders + + def filterPreferences(self): + filtered, prefList = filterSubfolderPreferences(self.prefList) + if filtered: + self.prefList = prefList + + def needMetaDataToCreateUniqueName(self): + """ + Returns True if metadata is essential to properly generate subfolders + + This will be the case if the only components are metadata and separators + """ + + for e in self.prefList: + if (not e) and ((e not in METADATA_ELEMENTS) or (e <> SEPARATOR)): + return True + + return False + + + + + def checkPrefsForValidity(self): + """ + Checks subfolder preferences validity above and beyond image name checks. + + See parent method for full description. + + Subfolders have additional requirments to that of image names. + """ + v = ImageRenamePreferences.checkPrefsForValidity(self) + if v: + # peform additional checks: + # 1. do not start with a separator + # 2. do not end with a separator + # 3. do not have two separators in a row + # these three rules will ensure something else other than a + # separator is specified + L1s = [] + for i in range(0, len(self.prefList), 3): + L1s.append(self.prefList[i]) + + if L1s[0] == SEPARATOR: + raise PrefValueKeyComboError(_("Subfolder preferences should not start with a %s") % os.sep) + elif L1s[-1] == SEPARATOR: + raise PrefValueKeyComboError(_("Subfolder preferences should not end with a %s") % os.sep) + else: + for i in range(len(L1s) - 1): + if L1s[i] == SEPARATOR and L1s[i+1] == SEPARATOR: + raise PrefValueKeyComboError(_("Subfolder preferences should not contain two %s one after the other") % os.sep) + return v + + + +class VideoSubfolderPreferences(SubfolderPreferences): + def __init__(self, prefList, parent): + SubfolderPreferences.__init__(self, prefList, parent) + self.prefsDefnL0 = DICT_VIDEO_SUBFOLDER_L0 + self.defaultPrefs = DEFAULT_VIDEO_SUBFOLDER_PREFS + self.defaultRow = [DATE_TIME, VIDEO_DATE, LIST_DATE_TIME_L2[0]] + self.L1DateCheck = VIDEO_DATE + self.component = pn.SUBFOLDER_COMPONENT + + def _getMetadataComponent(self): + """ + Returns portion of video / subfolder name based on the metadata + + Note: date time metadata found in _getDateComponent() + """ + return getVideoMetadataComponent(self) + +class Sequences: + """ + Holds sequence numbers and letters used in generating filenames. + The same instance of this class is shared among all threads. + """ + def __init__(self, downloadsToday, storedSequenceNo): + self.subfolderSequenceNo = {} + self.sessionSequenceNo = 1 + self.sequenceLetter = 0 + + self.setUseOfSequenceElements(False, False) + + self.assignedSequenceCounter = 1 + self.reset(downloadsToday, storedSequenceNo) + + def setUseOfSequenceElements(self, usesSessionSequenceNo, usesSequenceLetter): + self.usesSessionSequenceNo = usesSessionSequenceNo + self.usesSequenceLetter = usesSequenceLetter + + def reset(self, downloadsToday, storedSequenceNo): + self.downloadsToday = downloadsToday + self.downloadsTodayOffset = 0 + self.storedSequenceNo = storedSequenceNo + if self.usesSessionSequenceNo: + self.sessionSequenceNo = self.sessionSequenceNo + self.assignedSequenceCounter - 1 + if self.usesSequenceLetter: + self.sequenceLetter = self.sequenceLetter + self.assignedSequenceCounter - 1 + self.doNotAddToPool = False + self.pool = [] + self.poolSequenceCounter = 0 + self.assignedSequenceCounter = 1 + + def getPrelimSequence(self): + if self.doNotAddToPool: + self.doNotAddToPool = False + else: + # increment pool sequence number + self.poolSequenceCounter += 1 + self.pool.append(self.poolSequenceCounter) + + return self.poolSequenceCounter + + def getFinalSequence(self): + # get oldest queue value + # remove from queue or flag it should be removed + + return self.assignedSequenceCounter + + def getSequencePossibilities(self): + for i in self.pool: + yield i + + def getSessionSequenceNo(self): + return self.sessionSequenceNo + self.assignedSequenceCounter - 1 + + def getSessionSequenceNoUsingCounter(self, counter): + return self.sessionSequenceNo + counter - 1 + + def setSessionSequenceNo(self, value): + self.sessionSequenceNo = value + + def setStoredSequenceNo(self, value): + self.storedSequenceNo = value + + def getDownloadsTodayUsingCounter(self, counter): + return self.downloadsToday + counter - self.downloadsTodayOffset + + def setDownloadsToday(self, value): + self.downloadsToday = value + self.downloadsTodayOffset = self.assignedSequenceCounter - 1 + + def getStoredSequenceNoUsingCounter(self, counter): + return self.storedSequenceNo + counter + + def getSequenceLetterUsingCounter(self, counter): + return self.sequenceLetter + counter - 1 + + def imageCopyFailed(self): + self.doNotAddToPool = True + + def imageCopySucceeded(self): + self.increment() + + def increment(self): + assert(self.assignedSequenceCounter == self.pool[0]) + self.assignedSequenceCounter += 1 + self.pool = self.pool[1:] + #assert(len(self.pool) > 0) + + + + +if __name__ == '__main__': + import sys + import os.path + from metadata import MetaData + + if False: + if (len(sys.argv) != 2): + print 'Usage: ' + sys.argv[0] + ' path/to/photo/containing/metadata' + sys.exit(1) + else: + p0 = [FILENAME, NAME_EXTENSION, ORIGINAL_CASE] + p1 = [FILENAME, NAME_EXTENSION, LOWERCASE] + p2 = [METADATA, APERTURE, None] + p3 = [FILENAME, IMAGE_NUMBER, IMAGE_NUMBER_ALL] + p4 = [METADATA, CAMERA_MODEL, ORIGINAL_CASE] + p5 = [TEXT, '-', None] + p6 = [TEXT, 'Job', None] + + p = [p0, p1, p2, p3, p4] + p = [p6 + p5 + p2 + p5 + p3] + + d0 = [DATE_TIME, IMAGE_DATE, 'YYYYMMDD'] + d1 = [DATE_TIME, IMAGE_DATE, 'HHMMSS'] + d2 = [DATE_TIME, IMAGE_DATE, SUBSECONDS] + + d = [d0 + d1 + d2] + + fullpath = sys.argv[1] + path, filename = os.path.split(fullpath) + + m = MetaData(fullpath) + m.readMetadata() + + for pref in p: + i = ImageRenamePreferences(pref, None) + print i.generateNameUsingPreferences(m, filename) + + for pref in d: + i = ImageRenamePreferences(pref, None) + print i.generateNameUsingPreferences(m, filename) + else: + prefs = [SEQUENCES, SESSION_SEQ_NUMBER, SEQUENCE_NUMBER_3] +# prefs = ['Filename2', NAME_EXTENSION, UPPERCASE] + print checkPreferenceValid(DICT_IMAGE_RENAME_L0, prefs) -- cgit v1.2.3