diff options
Diffstat (limited to 'rapid/generatename.py')
-rw-r--r-- | rapid/generatename.py | 480 |
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 |