summaryrefslogtreecommitdiff
path: root/raphodo/generatenameconfig.py
diff options
context:
space:
mode:
Diffstat (limited to 'raphodo/generatenameconfig.py')
-rw-r--r--raphodo/generatenameconfig.py945
1 files changed, 945 insertions, 0 deletions
diff --git a/raphodo/generatenameconfig.py b/raphodo/generatenameconfig.py
new file mode 100644
index 0000000..1091cb7
--- /dev/null
+++ b/raphodo/generatenameconfig.py
@@ -0,0 +1,945 @@
+#!/usr/bin/env python3
+
+# Copyright (C) 2007-2016 Damon Lynch <damonlynch@gmail.com>
+
+# This file is part of Rapid Photo Downloader.
+#
+# Rapid Photo Downloader 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 3 of the License, or
+# (at your option) any later version.
+#
+# Rapid Photo Downloader 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 Rapid Photo Downloader. If not,
+# see <http://www.gnu.org/licenses/>.
+
+# 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.
+
+__author__ = 'Damon Lynch'
+__copyright__ = "Copyright 2007-2016, Damon Lynch"
+
+import os
+from collections import OrderedDict
+from typing import List, Optional, Tuple
+
+from gettext import gettext as _
+
+# 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, i.e. first column of values presented to user
+DATE_TIME = 'Date time'
+TEXT = 'Text'
+FILENAME = 'Filename'
+METADATA = 'Metadata'
+SEQUENCES = 'Sequences'
+JOB_CODE = 'Job code'
+
+SEPARATOR = os.sep
+
+# *** Level 1, i.e. second column of values presented to user
+
+# Date time
+IMAGE_DATE = 'Image date'
+TODAY = 'Today'
+YESTERDAY = 'Yesterday'
+VIDEO_DATE = 'Video date'
+DOWNLOAD_TIME = 'Download time'
+
+# File name
+NAME = 'Name'
+IMAGE_NUMBER = 'Image number'
+VIDEO_NUMBER = 'Video number'
+
+# pre 0.9.0a4 File name values: NAME_EXTENSION, EXTENSION
+NAME_EXTENSION = 'Name + extension'
+
+# however extension is used for subfolder generation in all versions
+EXTENSION = 'Extension'
+
+
+# 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'
+# Currently the only file number is Exif.CanonFi.FileNumber,
+# which is in the format xxx-yyyy, where xxx is the folder and yyyy the image
+FILE_NUMBER = 'File number'
+OWNER_NAME = 'Owner name'
+COPYRIGHT = 'Copyright'
+ARTIST = 'Artist'
+
+# 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, i.e. third and final column of values presented to user
+
+# 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"
+
+# File number
+FILE_NUMBER_FOLDER = "Folder only"
+FILE_NUMBER_ALL = "Folder and file"
+
+# 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 immediately below
+# NOTE 2: if changing LIST_DATE_TIME_L2, you MUST also update
+# DATE_TIME_CONVERT below
+# NOTE 3: if changing LIST_DATE_TIME_L2, you MUST also update
+# PHOTO_SUBFOLDER_MENU_DEFAULTS_CONV
+LIST_DATE_TIME_L2 = [
+ 'YYYYMMDD', # 0
+ 'YYYY-MM-DD',
+ 'YYYY_MM_DD', # 2
+ 'YYMMDD',
+ 'YY-MM-DD', # 4
+ 'YY_MM_DD',
+ 'MMDDYYYY', # 6
+ 'MMDDYY',
+ 'MMDD', # 8
+ 'DDMMYYYY',
+ 'DDMMYY', # 10
+ 'YYYY',
+ 'YY', # 12
+ 'MM',
+ 'DD', # 14
+ 'Month (full)',
+ 'Month (abbreviated)', # 16
+ 'HHMMSS',
+ 'HHMM', # 18
+ 'HH-MM-SS',
+ 'HH-MM', # 20
+ 'HH',
+ 'MM (minutes)', # 22
+ 'SS'
+]
+
+LIST_IMAGE_DATE_TIME_L2 = LIST_DATE_TIME_L2 + [SUBSECONDS]
+
+DEFAULT_SUBFOLDER_PREFS = [DATE_TIME, IMAGE_DATE, LIST_DATE_TIME_L2[11], '/',
+ '', '', DATE_TIME, IMAGE_DATE, LIST_DATE_TIME_L2[0]]
+DEFAULT_VIDEO_SUBFOLDER_PREFS = [DATE_TIME, VIDEO_DATE, LIST_DATE_TIME_L2[
+ 11], '/', '', '', DATE_TIME, VIDEO_DATE, LIST_DATE_TIME_L2[0]]
+
+DEFAULT_PHOTO_RENAME_PREFS = [FILENAME, NAME, ORIGINAL_CASE]
+DEFAULT_VIDEO_RENAME_PREFS = [FILENAME, NAME, ORIGINAL_CASE]
+
+class i18TranslateMeThanks:
+ """ this class is never used in actual running code
+ Its 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')
+ # 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')
+ # File number currently refers to the Exif value Exif.Canon.FileNumber
+ _('File number')
+ # Only the folder component of the Exif.Canon.FileNumber value
+ _('Folder only')
+ # The folder and file component of the Exif.Canon.FileNumber value
+ _('Folder and file')
+ # 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')
+ _('Artist')
+ _('Copyright')
+ # 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
+ _('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
+ _('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
+ _('Month (full)'),
+ # Translators: for an explanation of what this means,
+ # see http://damonlynch.net/rapid/documentation/index.html#renamedateandtime
+ _('Month (abbreviated)'),
+ # 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
+
+
+# Default subfolder options that appear in drop-down menu in Destination views
+# Any change to PHOTO_SUBFOLDER_MENU_DEFAULTS must also be reflected in
+# PHOTO_SUBFOLDER_MENU_DEFAULTS_CONV
+
+# The following values will be displayed in the menu after an os.sep.join() operation
+
+PHOTO_SUBFOLDER_MENU_DEFAULTS = (
+ (_('Date'), _('YYYY'), _('YYYYMMDD')),
+ (_('Date (hyphens)'), _('YYYY'), _('YYYY-MM-DD')),
+ (_('Date (underscores)'), _('YYYY'), _('YYYY_MM_DD')),
+ (_('Date and Job Code'), _('YYYY'), _('YYYYMM_Job Code')),
+ (_('Date and Job Code Subfolder'), _('YYYY'), _('YYYYMM'), _('Job Code'))
+)
+
+# Any change to PHOTO_SUBFOLDER_MENU_DEFAULTS_CONV must also be reflected in
+# PHOTO_SUBFOLDER_MENU_DEFAULTS
+
+PHOTO_SUBFOLDER_MENU_DEFAULTS_CONV = (
+ # 0
+ [DATE_TIME, IMAGE_DATE, LIST_DATE_TIME_L2[11],
+ '/', '', '',
+ DATE_TIME, IMAGE_DATE, LIST_DATE_TIME_L2[0]
+ ],
+ # 1
+ [DATE_TIME, IMAGE_DATE, LIST_DATE_TIME_L2[11],
+ '/', '', '',
+ DATE_TIME, IMAGE_DATE, LIST_DATE_TIME_L2[1]
+ ],
+ # 2
+ [DATE_TIME, IMAGE_DATE, LIST_DATE_TIME_L2[11],
+ '/', '', '',
+ DATE_TIME, IMAGE_DATE, LIST_DATE_TIME_L2[2]
+ ],
+ # 3
+ [DATE_TIME, IMAGE_DATE, LIST_DATE_TIME_L2[11],
+ '/', '', '',
+ DATE_TIME, IMAGE_DATE, LIST_DATE_TIME_L2[11],
+ DATE_TIME, IMAGE_DATE, LIST_DATE_TIME_L2[13],
+ TEXT, '_', '',
+ JOB_CODE, '', ''],
+ # 4
+ [DATE_TIME, IMAGE_DATE, LIST_DATE_TIME_L2[11],
+ '/', '', '',
+ DATE_TIME, IMAGE_DATE, LIST_DATE_TIME_L2[11],
+ DATE_TIME, IMAGE_DATE, LIST_DATE_TIME_L2[13],
+ '/', '', '',
+ JOB_CODE, '', '',
+ ],
+)
+
+PHOTO_RENAME_MENU_DEFAULTS = (
+ (_('Original Filename'), 'IMG_1234'),
+ (_('Date-Time and Downloads today'), _('YYYYMMDD-HHMM-1')),
+ (_('Date and Downloads today'), _('YYYYMMDD-1')),
+ (_('Date-Time and Image number'), _('YYYYMMDD-1234')),
+ (_('Date-Time and Job Code'), _('YYYYMMDD-HHMM-Job Code-1')),
+ (_('Date and Job Code'), _('YYYYMMDD-Job Code-1'))
+)
+
+PHOTO_RENAME_MENU_DEFAULTS_CONV = (
+ # 0 Original Filename
+ [FILENAME, NAME, ORIGINAL_CASE],
+ # 1 Date-Time and Downloads today
+ [DATE_TIME, IMAGE_DATE, LIST_DATE_TIME_L2[0],
+ TEXT, '-', '',
+ DATE_TIME, IMAGE_DATE, LIST_DATE_TIME_L2[18],
+ TEXT, '-', '',
+ SEQUENCES, DOWNLOAD_SEQ_NUMBER, SEQUENCE_NUMBER_1],
+ # 2 Date and Downloads today
+ [DATE_TIME, IMAGE_DATE, LIST_DATE_TIME_L2[0],
+ TEXT, '-', '',
+ SEQUENCES, DOWNLOAD_SEQ_NUMBER, SEQUENCE_NUMBER_1],
+ # 3 Date-Time and Image number
+ [DATE_TIME, IMAGE_DATE, LIST_DATE_TIME_L2[0],
+ TEXT, '-', '',
+ DATE_TIME, IMAGE_DATE, LIST_DATE_TIME_L2[18],
+ TEXT, '-', '',
+ FILENAME, IMAGE_NUMBER, IMAGE_NUMBER_ALL],
+ # 4 Date-Time and Job Code
+ [DATE_TIME, IMAGE_DATE, LIST_DATE_TIME_L2[0],
+ TEXT, '-', '',
+ DATE_TIME, IMAGE_DATE, LIST_DATE_TIME_L2[18],
+ TEXT, '-', '',
+ JOB_CODE, '', '',
+ TEXT, '-', '',
+ SEQUENCES, DOWNLOAD_SEQ_NUMBER, SEQUENCE_NUMBER_1],
+ # 5 Date and Job Code
+ [DATE_TIME, IMAGE_DATE, LIST_DATE_TIME_L2[0],
+ TEXT, '-', '',
+ JOB_CODE, '', '',
+ TEXT, '-', '',
+ SEQUENCES, DOWNLOAD_SEQ_NUMBER, SEQUENCE_NUMBER_1]
+)
+
+# See notes above regarding keeping values in sync
+VIDEO_SUBFOLDER_MENU_DEFAULTS = PHOTO_SUBFOLDER_MENU_DEFAULTS
+VIDEO_SUBFOLDER_MENU_DEFAULTS_CONV = (
+ # 0
+ [DATE_TIME, VIDEO_DATE, LIST_DATE_TIME_L2[11],
+ SEPARATOR, '', '',
+ DATE_TIME, VIDEO_DATE, LIST_DATE_TIME_L2[0]
+ ],
+ # 1
+ [DATE_TIME, VIDEO_DATE, LIST_DATE_TIME_L2[11],
+ SEPARATOR, '', '',
+ DATE_TIME, VIDEO_DATE, LIST_DATE_TIME_L2[1]
+ ],
+ # 2
+ [DATE_TIME, VIDEO_DATE, LIST_DATE_TIME_L2[11],
+ SEPARATOR, '', '',
+ DATE_TIME, VIDEO_DATE, LIST_DATE_TIME_L2[2]
+ ],
+ # 3
+ [DATE_TIME, VIDEO_DATE, LIST_DATE_TIME_L2[11],
+ SEPARATOR, '', '',
+ DATE_TIME, VIDEO_DATE, LIST_DATE_TIME_L2[11],
+ DATE_TIME, VIDEO_DATE, LIST_DATE_TIME_L2[13],
+ TEXT, '_', '',
+ JOB_CODE, '', ''],
+ # 4
+ [DATE_TIME, VIDEO_DATE, LIST_DATE_TIME_L2[11],
+ SEPARATOR, '', '',
+ DATE_TIME, VIDEO_DATE, LIST_DATE_TIME_L2[11],
+ DATE_TIME, VIDEO_DATE, LIST_DATE_TIME_L2[13],
+ SEPARATOR, '', '',
+ JOB_CODE, '', '',
+ ],
+)
+
+VIDEO_RENAME_MENU_DEFAULTS = (
+ (_('Original Filename'), 'MVI_1234'),
+ (_('Date-Time and Downloads today'), _('YYYYMMDD-HHMM-1')),
+ (_('Date and Downloads today'), _('YYYYMMDD-1')),
+ (_('Date-Time and Video number'), _('YYYYMMDD_1234')),
+ (_('Date-Time and Job Code'), _('YYYYMMDD-HHMM-Job Code-1')),
+ (_('Date and Job Code'), _('YYYYMMDD-Job Code-1')),
+ (_('Resolution'), _('YYYYMMDD-HHMM-1-1920x1080'))
+)
+
+VIDEO_RENAME_MENU_DEFAULTS_CONV = (
+ # 0 Original Filename
+ [FILENAME, NAME, ORIGINAL_CASE],
+ # 1 Date-Time and Downloads today
+ [DATE_TIME, VIDEO_DATE, LIST_DATE_TIME_L2[0],
+ TEXT, '-', '',
+ DATE_TIME, VIDEO_DATE, LIST_DATE_TIME_L2[18],
+ TEXT, '-', '',
+ SEQUENCES, DOWNLOAD_SEQ_NUMBER, SEQUENCE_NUMBER_1],
+ # 2 Date and Downloads today
+ [DATE_TIME, VIDEO_DATE, LIST_DATE_TIME_L2[0],
+ TEXT, '-', '',
+ SEQUENCES, DOWNLOAD_SEQ_NUMBER, SEQUENCE_NUMBER_1],
+ # 3 Date-Time and Image number
+ [DATE_TIME, VIDEO_DATE, LIST_DATE_TIME_L2[0],
+ TEXT, '-', '',
+ DATE_TIME, VIDEO_DATE, LIST_DATE_TIME_L2[18],
+ TEXT, '-', '',
+ FILENAME, VIDEO_NUMBER, IMAGE_NUMBER_ALL],
+ # 4 Date-Time and Job Code
+ [DATE_TIME, VIDEO_DATE, LIST_DATE_TIME_L2[0],
+ TEXT, '-', '',
+ DATE_TIME, VIDEO_DATE, LIST_DATE_TIME_L2[18],
+ TEXT, '-', '',
+ JOB_CODE, '', '',
+ TEXT, '-', '',
+ SEQUENCES, DOWNLOAD_SEQ_NUMBER, SEQUENCE_NUMBER_1],
+ # 5 Date and Job Code
+ [DATE_TIME, VIDEO_DATE, LIST_DATE_TIME_L2[0],
+ TEXT, '-', '',
+ JOB_CODE, '', '',
+ TEXT, '-', '',
+ SEQUENCES, DOWNLOAD_SEQ_NUMBER, SEQUENCE_NUMBER_1],
+ # 6 Resolution
+ [DATE_TIME, VIDEO_DATE, LIST_DATE_TIME_L2[0],
+ TEXT, '-', '',
+ DATE_TIME, VIDEO_DATE, LIST_DATE_TIME_L2[18],
+ TEXT, '-', '',
+ SEQUENCES, DOWNLOAD_SEQ_NUMBER, SEQUENCE_NUMBER_1,
+ TEXT, '-', '',
+ METADATA, WIDTH, '',
+ TEXT, 'x', '',
+ METADATA, HEIGHT, '']
+)
+
+# See notes above regarding keeping values in sync
+DATE_TIME_CONVERT = ['%Y%m%d', '%Y-%m-%d', '%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', '%B', '%b',
+ '%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,
+]
+FILE_NUMBER_L2 = [
+ FILE_NUMBER_FOLDER,
+ FILE_NUMBER_ALL
+]
+
+# Level 1
+
+DICT_DATE_TIME_L1 = OrderedDict([
+ (IMAGE_DATE, LIST_IMAGE_DATE_TIME_L2),
+ (TODAY, LIST_DATE_TIME_L2),
+ (YESTERDAY, LIST_DATE_TIME_L2),
+ (DOWNLOAD_TIME, LIST_DATE_TIME_L2),
+])
+
+VIDEO_DICT_DATE_TIME_L1 = OrderedDict([
+ (VIDEO_DATE, LIST_IMAGE_DATE_TIME_L2),
+ (TODAY, LIST_DATE_TIME_L2),
+ (YESTERDAY, LIST_DATE_TIME_L2),
+ (DOWNLOAD_TIME, LIST_DATE_TIME_L2),
+])
+
+DICT_FILENAME_L1 = OrderedDict([
+ (NAME, LIST_CASE_L2),
+ (IMAGE_NUMBER, LIST_IMAGE_NUMBER_L2),
+])
+
+# pre 0.9.0a4 values for DICT_FILENAME_L1:
+#(NAME_EXTENSION, LIST_CASE_L2),
+# (EXTENSION, LIST_CASE_L2),
+
+DICT_VIDEO_FILENAME_L1 = OrderedDict([
+ (NAME, LIST_CASE_L2),
+ (VIDEO_NUMBER, LIST_IMAGE_NUMBER_L2),
+])
+
+# pre 0.9.0a4 values for DICT_VIDEO_FILENAME_L1:
+# (NAME_EXTENSION, LIST_CASE_L2),
+# (EXTENSION, LIST_CASE_L2),
+
+DICT_SUBFOLDER_FILENAME_L1 = {
+ EXTENSION: LIST_CASE_L2,
+}
+
+DICT_METADATA_L1 = OrderedDict([
+ (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),
+ (FILE_NUMBER, FILE_NUMBER_L2),
+ (OWNER_NAME, LIST_CASE_L2),
+ (ARTIST, LIST_CASE_L2),
+ (COPYRIGHT, LIST_CASE_L2),
+])
+
+DICT_VIDEO_METADATA_L1 = OrderedDict([
+ (CODEC, LIST_CASE_L2),
+ (WIDTH, None),
+ (HEIGHT, None),
+ (LENGTH, None),
+ (FPS, None),
+])
+
+DICT_SEQUENCE_L1 = OrderedDict([
+ (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),
+])
+
+LIST_SEQUENCE_L1 = list(DICT_SEQUENCE_L1.keys())
+
+# Level 0
+
+DICT_IMAGE_RENAME_L0 = OrderedDict([
+ (DATE_TIME, DICT_DATE_TIME_L1),
+ (TEXT, None),
+ (FILENAME, DICT_FILENAME_L1),
+ (METADATA, DICT_METADATA_L1),
+ (SEQUENCES, DICT_SEQUENCE_L1),
+ (JOB_CODE, None),
+])
+
+DICT_VIDEO_RENAME_L0 = OrderedDict([
+ (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),
+])
+
+DICT_SUBFOLDER_L0 = OrderedDict([
+ (DATE_TIME, DICT_DATE_TIME_L1),
+ (TEXT, None),
+ (FILENAME, DICT_SUBFOLDER_FILENAME_L1),
+ (METADATA, DICT_METADATA_L1),
+ (SEPARATOR, None),
+ (JOB_CODE, None),
+])
+
+DICT_VIDEO_SUBFOLDER_L0 = OrderedDict([
+ (DATE_TIME, VIDEO_DICT_DATE_TIME_L1),
+ (TEXT, None),
+ (FILENAME, DICT_SUBFOLDER_FILENAME_L1),
+ (METADATA, DICT_VIDEO_METADATA_L1),
+ (SEPARATOR, None),
+ (JOB_CODE, None),
+])
+
+# 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
+
+PHOTO_RENAME_COMPLEX = ['Date time', 'Image date', 'YYYYMMDD', 'Text', '-', '',
+ 'Date time', 'Image date', 'HHMM', 'Text', '-', '', 'Sequences',
+ 'Downloads today', 'One digit', 'Text', '-iso', '',
+ 'Metadata', 'ISO', '', 'Text', '-f', '', 'Metadata',
+ 'Aperture', '', 'Text', '-', '', 'Metadata', 'Focal length', '',
+ 'Text', 'mm-', '', 'Metadata', 'Exposure time', '']
+
+PHOTO_RENAME_SIMPLE = ['Date time', 'Image date', 'YYYYMMDD', 'Text', '-', '',
+ 'Date time', 'Image date', 'HHMM', 'Text', '-', '', 'Sequences',
+ 'Downloads today', 'One digit']
+
+VIDEO_RENAME_SIMPLE = [x if x != 'Image date' else 'Video date' for x in PHOTO_RENAME_SIMPLE]
+
+JOB_CODE_RENAME_TEST = ['Job code', '', '', 'Sequences',
+ 'Downloads today', 'One digit']
+
+
+def upgrade_pre090a4_rename_pref(pref_list: List[str]) -> Tuple[List[str], str]:
+ r"""
+ Upgrade photo and video rename preference list
+
+ :param pref_list: pref list to upgrade
+ :return: tuple of new pref list, and if found, the case to be used for the
+ extension
+
+ >>> upgrade_pre090a4_rename_pref([FILENAME, NAME_EXTENSION, ORIGINAL_CASE])
+ (['Filename', 'Name', 'Original Case'], 'Original Case')
+ >>> upgrade_pre090a4_rename_pref(PHOTO_RENAME_SIMPLE + [FILENAME, EXTENSION, LOWERCASE])
+ ... # doctest: +NORMALIZE_WHITESPACE
+ (['Date time', 'Image date', 'YYYYMMDD',
+ 'Text', '-', '',
+ 'Date time', 'Image date', 'HHMM',
+ 'Text', '-', '',
+ 'Sequences', 'Downloads today', 'One digit'], 'lowercase')
+ >>> upgrade_pre090a4_rename_pref(PHOTO_RENAME_COMPLEX + [FILENAME, EXTENSION, UPPERCASE])
+ ... # doctest: +NORMALIZE_WHITESPACE
+ (['Date time', 'Image date', 'YYYYMMDD', 'Text', '-', '',
+ 'Date time', 'Image date', 'HHMM', 'Text', '-', '', 'Sequences',
+ 'Downloads today', 'One digit', 'Text', '-iso', '',
+ 'Metadata', 'ISO', '', 'Text', '-f', '', 'Metadata',
+ 'Aperture', '', 'Text', '-', '', 'Metadata', 'Focal length', '',
+ 'Text', 'mm-', '', 'Metadata', 'Exposure time', ''], 'UPPERCASE')
+ >>> upgrade_pre090a4_rename_pref([FILENAME, NAME, LOWERCASE])
+ (['Filename', 'Name', 'lowercase'], None)
+
+ """
+ if not pref_list:
+ return (pref_list, None)
+
+ # get extension case from last value
+ if pref_list[-2] in (NAME_EXTENSION, EXTENSION):
+ case = pref_list[-1]
+ else:
+ case = None
+
+ new_pref_list = []
+ for idx in range(0, len(pref_list), 3):
+ l1 = pref_list[idx + 1]
+ if l1 != EXTENSION:
+ if l1 == NAME_EXTENSION:
+ l1 = NAME
+ new_pref_list.extend([pref_list[idx], l1, pref_list[idx + 2]])
+ return new_pref_list, case
+
+
+class PrefError(Exception):
+ """ base class """
+
+ def __init__(self):
+ super().__init__()
+ self.msg = ''
+
+ def unpackList(self, l: List[str]):
+ """
+ Make the preferences presentable to the user
+ """
+ return ', '.join("'{}'".format(i) for i in l)
+
+ def __str__(self):
+ return self.msg
+
+
+class PrefKeyError(PrefError):
+ def __init__(self, error):
+ super().__init__()
+ 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):
+ super().__init__(error)
+ value = error[0]
+ self.msg = "Preference value '%(value)s' is invalid" % {'value': value}
+
+
+class PrefLengthError(PrefError):
+ def __init__(self, error):
+ super().__init__()
+ self.msg = "These preferences are not well formed:" + "\n %s" % self.unpackList(error)
+
+
+class PrefValueKeyComboError(PrefError):
+ def __init__(self, error):
+ super().__init__()
+ self.msg = error
+
+
+def check_pref_valid(pref_defn, prefs, modulo=3) -> bool:
+ """
+ Checks to see if user preferences are valid according to their
+ definition. Raises appropriate exception if an error is found.
+
+ :param prefs: list of preferences
+ :param pref_defn: is a Dict specifying what is valid
+ :param modulo: how many list elements are equivalent to one line
+ of preferences.
+ :return: True if prefs match with pref_defn
+ """
+
+ if (len(prefs) % modulo != 0) or not prefs:
+ raise PrefLengthError(prefs)
+ else:
+ for i in range(0, len(prefs), modulo):
+ _check_pref_valid(pref_defn, prefs[i:i + modulo])
+
+ return True
+
+
+def _check_pref_valid(pref_defn, prefs):
+ key = prefs[0]
+ value = prefs[1]
+
+ if key in pref_defn:
+
+ next_pref_defn = pref_defn[key]
+
+ if value is None:
+ # value should never be None, at any time
+ raise PrefValueInvalidError((None, next_pref_defn))
+
+ if next_pref_defn and not value:
+ raise PrefValueInvalidError((value, next_pref_defn))
+
+ if isinstance(next_pref_defn, dict):
+ return _check_pref_valid(next_pref_defn, prefs[1:])
+ else:
+ if isinstance(next_pref_defn, list):
+ result = value in next_pref_defn
+ if not result:
+ raise PrefValueInvalidError((value, next_pref_defn))
+ return True
+ elif not next_pref_defn:
+ return True
+ else:
+ result = next_pref_defn == value
+ if not result:
+ raise PrefValueInvalidError((value, next_pref_defn))
+ return True
+ else:
+ raise PrefKeyError((key, list(pref_defn.keys())))
+
+
+def filter_subfolder_prefs(pref_list: List[str],
+ pref_colors: Optional[List[str]]=None) \
+ -> Tuple[bool, List[str], Optional[List[str]]]:
+ """
+ Filters out extraneous preference choices.
+
+ :param pref_list: the list of user specified preferences
+ :param pref_colors: optional list of colors associated with displaying the
+ generated sample name while editing the preferences
+ :return: bool indicating whether list changed, the pref list, and optionally the
+ list of colors
+ """
+
+ prefs_changed = False
+ continue_check = True
+ while continue_check and pref_list:
+ continue_check = False
+ if pref_list[0] == SEPARATOR:
+ # subfolder preferences should not start with a /
+ pref_list = pref_list[3:]
+ if pref_colors is not None:
+ pref_colors = pref_colors[1:]
+ prefs_changed = True
+ continue_check = True
+ elif pref_list[-3] == SEPARATOR:
+ # subfolder preferences should not end with a /
+ pref_list = pref_list[:-3]
+ if pref_colors is not None:
+ pref_colors = pref_colors[:-1]
+ continue_check = True
+ prefs_changed = True
+ else:
+ for i in range(0, len(pref_list) - 3, 3):
+ if pref_list[i] == SEPARATOR and pref_list[i + 3] == SEPARATOR:
+ # subfolder preferences should not contain two /s side by side
+ continue_check = True
+ prefs_changed = True
+ # note we are messing with the contents of the pref list,
+ # must exit loop and try again
+ pref_list = pref_list[:i] + pref_list[i + 3:]
+ if pref_colors is not None:
+ pref_colors = pref_colors[:i//3] + pref_colors[i//3 + 1:]
+ break
+
+ return (prefs_changed, pref_list, pref_colors)