summaryrefslogtreecommitdiff
path: root/rapid/generatename.py
diff options
context:
space:
mode:
authorJulien Valroff <julien@kirya.net>2011-04-16 16:39:15 +0200
committerJulien Valroff <julien@kirya.net>2011-04-16 16:39:15 +0200
commit75b28642dd41fb4a7925b42cb24274de90a4f52c (patch)
treeee6d94212855a1fdaf649e0c6d509951164edc96 /rapid/generatename.py
parent5168fdb07d6dc2b77f0ef9c7502940ce4a02e9aa (diff)
Imported Upstream version 0.4.0~beta1upstream/0.4.0_beta1
Diffstat (limited to 'rapid/generatename.py')
-rw-r--r--rapid/generatename.py480
1 files changed, 480 insertions, 0 deletions
diff --git a/rapid/generatename.py b/rapid/generatename.py
new file mode 100644
index 0000000..e904a70
--- /dev/null
+++ b/rapid/generatename.py
@@ -0,0 +1,480 @@
+#!/usr/bin/python
+# -*- coding: latin1 -*-
+
+### Copyright (C) 2007, 2008, 2009, 2010, 2011 Damon Lynch <damonlynch@gmail.com>
+
+### 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
+
+import os, re, datetime, string, collections
+
+import multiprocessing
+import logging
+logger = multiprocessing.get_logger()
+
+import problemnotification as pn
+
+from generatenameconfig import *
+
+from gettext import gettext as _
+
+
+def convert_date_for_strftime(datetime_user_choice):
+ try:
+ return DATE_TIME_CONVERT[LIST_DATE_TIME_L2.index(datetime_user_choice)]
+ except:
+ raise PrefValueInvalidError(datetime_user_choice)
+
+
+class PhotoName:
+ """
+ Generate the name of a photo. Used as a base class for generating names
+ of videos, as well as subfolder names for both file types
+ """
+
+ def __init__(self, pref_list):
+ self.pref_list = pref_list
+
+
+ # Some of the next values are overwritten in derived classes
+ self.strip_initial_period_from_extension = False
+ self.strip_forward_slash = True
+ self.L1_date_check = IMAGE_DATE #used in _get_date_component()
+ self.component = pn.FILENAME_COMPONENT #used in error reporting
+
+
+ def _get_values_from_pref_list(self):
+ for i in range(0, len(self.pref_list), 3):
+ yield (self.pref_list[i], self.pref_list[i+1], self.pref_list[i+2])
+
+ def _get_date_component(self):
+ """
+ Returns portion of new file / 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.L1_date_check:
+ if self.L2 == SUBSECONDS:
+ d = self.rpd_file.metadata.sub_seconds()
+ if d == '00':
+ self.rpd_file.problem.add_problem(self.component, pn.MISSING_METADATA, _(self.L2))
+ return ''
+ else:
+ return d
+ else:
+ d = self.rpd_file.metadata.date_time(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.rpd_file.download_start_time
+ else:
+ raise("Date options invalid")
+
+ # step 2: if have a value, try to convert it to string format
+ if d:
+ try:
+ return d.strftime(convert_date_for_strftime(self.L2))
+ except:
+ logger.warning("Exif date time value appears invalid for file %s", self.rpd_file.full_file_name)
+
+ # step 3: handle a missing value using file modification time
+ if self.rpd_file.modification_time:
+ try:
+ d = datetime.datetime.fromtimestamp(self.rpd_file.modification_time)
+ except:
+ self.rpd_file.add_problem(self.component, pn.INVALID_DATE_TIME, '')
+ logger.error("Both file modification time and metadata date & time are invalid for file %s", self.rpd_file.full_file_name)
+ return ''
+ else:
+ self.rpd_file.add_problem(self.component, pn.MISSING_METADATA, _(self.L1))
+ return ''
+
+ try:
+ return d.strftime(convert_date_for_strftime(self.L2))
+ except:
+ self.rpd_file.add_problem(self.component, pn.INVALID_DATE_TIME, d)
+ logger.error("Both file modification time and metadata date & time are invalid for file %s", self.rpd_file.full_file_name)
+ return ''
+
+ def _get_filename_component(self):
+ """
+ Returns portion of new file / subfolder name based on the file name
+ """
+
+ name, extension = os.path.splitext(self.rpd_file.name)
+
+ if self.L1 == NAME_EXTENSION:
+ filename = self.rpd_file.name
+ elif self.L1 == NAME:
+ filename = name
+ elif self.L1 == EXTENSION:
+ if extension:
+ if not self.strip_initial_period_from_extension:
+ # 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 when it is at the start!
+ filename = extension[1:]
+ else:
+ self.rpd_file.add_problem(self.component, pn.MISSING_FILE_EXTENSION)
+ return ""
+ elif self.L1 == IMAGE_NUMBER or self.L1 == VIDEO_NUMBER:
+ n = re.search("(?P<image_number>[0-9]+$)", name)
+ if not n:
+ self.rpd_file.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 _get_metadata_component(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.rpd_file.metadata.aperture()
+ elif self.L1 == ISO:
+ v = self.rpd_file.metadata.iso()
+ elif self.L1 == EXPOSURE_TIME:
+ v = self.rpd_file.metadata.exposure_time(alternativeFormat=True)
+ elif self.L1 == FOCAL_LENGTH:
+ v = self.rpd_file.metadata.focal_length()
+ elif self.L1 == CAMERA_MAKE:
+ v = self.rpd_file.metadata.camera_make()
+ elif self.L1 == CAMERA_MODEL:
+ v = self.rpd_file.metadata.camera_model()
+ elif self.L1 == SHORT_CAMERA_MODEL:
+ v = self.rpd_file.metadata.short_camera_model()
+ elif self.L1 == SHORT_CAMERA_MODEL_HYPHEN:
+ v = self.rpd_file.metadata.short_camera_model(includeCharacters = "\-")
+ elif self.L1 == SERIAL_NUMBER:
+ v = self.rpd_file.metadata.camera_serial()
+ elif self.L1 == SHUTTER_COUNT:
+ v = self.rpd_file.metadata.shutter_count()
+ 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.rpd_file.metadata.owner_name()
+ 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.rpd_file.add_problem(self.component, pn.MISSING_METADATA, _(self.L1))
+ return v
+
+ def _calculate_letter_sequence(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 _format_sequence_no(self, value, amountToPad):
+ padding = LIST_SEQUENCE_NUMBERS_L2.index(amountToPad) + 1
+ formatter = '%0' + str(padding) + "i"
+ return formatter % value
+
+ def _get_downloads_today(self):
+ return self._format_sequence_no(self.rpd_file.sequences.get_downloads_today(), self.L2)
+
+ def _get_session_sequence_no(self):
+ return self._format_sequence_no(self.rpd_file.sequences.get_session_sequence_no(), self.L2)
+
+ def _get_stored_sequence_no(self):
+ return self._format_sequence_no(self.rpd_file.sequences.get_stored_sequence_no(), self.L2)
+
+ def _get_sequence_letter(self):
+ return self._calculate_letter_sequence(self.rpd_file.sequences.get_sequence_letter())
+
+ def _get_sequences_component(self):
+ if self.L1 == DOWNLOAD_SEQ_NUMBER:
+ return self._get_downloads_today()
+ elif self.L1 == SESSION_SEQ_NUMBER:
+ return self._get_session_sequence_no()
+ elif self.L1 == STORED_SEQ_NUMBER:
+ return self._get_stored_sequence_no()
+ elif self.L1 == SEQUENCE_LETTER:
+ return self._get_sequence_letter()
+
+
+ #~ elif self.L1 == SUBFOLDER_SEQ_NUMBER:
+ #~ return self._getSubfolderSequenceNo()
+
+
+
+ def _get_component(self):
+ try:
+ if self.L0 == DATE_TIME:
+ return self._get_date_component()
+ elif self.L0 == TEXT:
+ return self.L1
+ elif self.L0 == FILENAME:
+ return self._get_filename_component()
+ elif self.L0 == METADATA:
+ return self._get_metadata_component()
+ elif self.L0 == SEQUENCES:
+ return self._get_sequences_component()
+ elif self.L0 == JOB_CODE:
+ return self.rpd_file.job_code
+ elif self.L0 == SEPARATOR:
+ return os.sep
+ except:
+ self.rpd_file.add_problem(self.component, pn.ERROR_IN_GENERATION, _(self.L0))
+ return ''
+
+
+ def generate_name(self, rpd_file):
+ self.rpd_file = rpd_file
+
+ name = ''
+
+ for self.L0, self.L1, self.L2 in self._get_values_from_pref_list():
+ v = self._get_component()
+ if v:
+ name += v
+
+ if self.rpd_file.strip_characters:
+ for c in r'\:*?"<>|':
+ name = name.replace(c, '')
+
+ if self.strip_forward_slash:
+ name = name.replace('/', '')
+
+ name = name.strip()
+
+ return name
+
+
+
+
+class VideoName(PhotoName):
+ def __init__(self, pref_list):
+ PhotoName.__init__(self, pref_list)
+ self.L1_date_check = VIDEO_DATE #used in _get_date_component()
+
+ def _get_metadata_component(self):
+ """
+ Returns portion of video / subfolder name based on the metadata
+
+ Note: date time metadata found in _getDateComponent()
+ """
+ return get_video_metadata_component(self)
+
+class PhotoSubfolder(PhotoName):
+ """
+ Generate subfolder names for photo files
+ """
+
+ def __init__(self, pref_list):
+ self.pref_list = pref_list
+
+ self.strip_extraneous_white_space = re.compile(r'\s*%s\s*' % os.sep)
+ self.strip_initial_period_from_extension = True
+ self.strip_forward_slash = False
+ self.L1_date_check = IMAGE_DATE #used in _get_date_component()
+ self.component = pn.SUBFOLDER_COMPONENT #used in error reporting
+
+ def generate_name(self, rpd_file):
+
+ subfolders = PhotoName.generate_name(self, rpd_file)
+
+ # 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 self.rpd_file.strip_characters:
+ subfolders = self.strip_extraneous_white_space.sub(os.sep, subfolders)
+
+ return subfolders
+
+
+
+
+class VideoSubfolder(PhotoSubfolder):
+ """
+ Generate subfolder names for video files
+ """
+
+ def __init__(self, pref_list):
+ PhotoSubfolder.__init__(self, pref_list)
+ self.L1_date_check = VIDEO_DATE #used in _get_date_component()
+
+
+ def _get_metadata_component(self):
+ """
+ Returns portion of video / subfolder name based on the metadata
+
+ Note: date time metadata found in _getDateComponent()
+ """
+ return get_video_metadata_component(self)
+
+def get_video_metadata_component(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.rpd_file.metadata.codec()
+ elif video.L1 == WIDTH:
+ v = video.rpd_file.metadata.width()
+ elif video.L1 == HEIGHT:
+ v = video.rpd_file.metadata.height()
+ elif video.L1 == FPS:
+ v = video.rpd_file.metadata.frames_per_second()
+ elif video.L1 == LENGTH:
+ v = video.rpd_file.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.rpd_file.add_problem(video.component, pn.MISSING_METADATA, _(video.L1))
+ return v
+
+class Sequences:
+ """
+ Holds sequence numbers and letters used in generating filenames.
+ """
+ def __init__(self, downloads_today_tracker, stored_sequence_no):
+ self.session_sequence_no = 0
+ self.sequence_letter = -1
+ self.downloads_today_tracker = downloads_today_tracker
+ self.stored_sequence_no = stored_sequence_no
+ self.matched_sequences = None
+
+ def set_matched_sequence_value(self, matched_sequences):
+ self.matched_sequences = matched_sequences
+
+ def get_session_sequence_no(self):
+ if self.matched_sequences is not None:
+ return self.matched_sequences.session_sequence_no
+ else:
+ return self._get_session_sequence_no()
+
+ def _get_session_sequence_no(self):
+ return self.session_sequence_no + 1
+
+ def get_sequence_letter(self):
+ if self.matched_sequences is not None:
+ return self.matched_sequences.sequence_letter
+ else:
+ return self._get_sequence_letter()
+
+ def _get_sequence_letter(self):
+ return self.sequence_letter + 1
+
+ def increment(self, uses_session_sequece_no, uses_sequence_letter):
+ if uses_session_sequece_no:
+ self.session_sequence_no += 1
+ if uses_sequence_letter:
+ self.sequence_letter += 1
+
+ def get_downloads_today(self):
+ if self.matched_sequences is not None:
+ return self.matched_sequences.downloads_today
+ else:
+ return self._get_downloads_today()
+
+ def _get_downloads_today(self):
+ v = self.downloads_today_tracker.get_downloads_today()
+ if v == -1:
+ return 1
+ else:
+ return v + 1
+
+ def get_stored_sequence_no(self):
+ if self.matched_sequences is not None:
+ return self.matched_sequences.stored_sequence_no
+ else:
+ return self._get_stored_sequence_no()
+
+ def _get_stored_sequence_no(self):
+ # Must add 1 to the value, for historic reasons (that is how it used
+ # to work)
+ return self.stored_sequence_no + 1
+
+ def create_matched_sequences(self):
+ sequences = collections.namedtuple(
+ 'AssignedSequences',
+ 'session_sequence_no sequence_letter downloads_today stored_sequence_no'
+ )
+ sequences.session_sequence_no = self._get_session_sequence_no()
+ sequences.sequence_letter = self._get_sequence_letter()
+ sequences.downloads_today = self._get_downloads_today()
+ sequences.stored_sequence_no = self._get_stored_sequence_no()
+ return sequences