1#!/usr/bin/env python3 2 3# Copyright (C) 2007-2020 Damon Lynch <damonlynch@gmail.com> 4 5# This file is part of Rapid Photo Downloader. 6# 7# Rapid Photo Downloader is free software: you can redistribute it and/or 8# modify it under the terms of the GNU General Public License as published by 9# the Free Software Foundation, either version 3 of the License, or 10# (at your option) any later version. 11# 12# Rapid Photo Downloader is distributed in the hope that it will be useful, 13# but WITHOUT ANY WARRANTY; without even the implied warranty of 14# MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the 15# GNU General Public License for more details. 16# 17# You should have received a copy of the GNU General Public License 18# along with Rapid Photo Downloader. If not, 19# see <http://www.gnu.org/licenses/>. 20 21# Special key in each dictionary which specifies the order of elements. 22# It is very important to have a consistent and rational order when displaying 23# these prefs to the user, and dictionaries are unsorted. 24 25__author__ = 'Damon Lynch' 26__copyright__ = "Copyright 2007-2020, Damon Lynch" 27 28import os 29from collections import OrderedDict 30from typing import List, Optional, Tuple 31 32 33# PLEASE NOTE: these values are duplicated in a dummy class whose function 34# is to have them put into the translation template. If you change the values below 35# then you MUST change the value in class i18TranslateMeThanks as well!! 36 37# *** Level 0, i.e. first column of values presented to user 38DATE_TIME = 'Date time' 39TEXT = 'Text' 40FILENAME = 'Filename' 41METADATA = 'Metadata' 42SEQUENCES = 'Sequences' 43JOB_CODE = 'Job code' 44 45SEPARATOR = os.sep 46 47# *** Level 1, i.e. second column of values presented to user 48 49# Date time 50IMAGE_DATE = 'Image date' 51TODAY = 'Today' 52YESTERDAY = 'Yesterday' 53VIDEO_DATE = 'Video date' 54DOWNLOAD_TIME = 'Download time' 55 56# File name 57NAME = 'Name' 58IMAGE_NUMBER = 'Image number' 59VIDEO_NUMBER = 'Video number' 60 61# pre 0.9.0a4 File name values: NAME_EXTENSION, EXTENSION 62NAME_EXTENSION = 'Name + extension' 63 64# however extension is used for subfolder generation in all versions 65EXTENSION = 'Extension' 66 67 68# Metadata 69APERTURE = 'Aperture' 70ISO = 'ISO' 71EXPOSURE_TIME = 'Exposure time' 72FOCAL_LENGTH = 'Focal length' 73CAMERA_MAKE = 'Camera make' 74CAMERA_MODEL = 'Camera model' 75SHORT_CAMERA_MODEL = 'Short camera model' 76SHORT_CAMERA_MODEL_HYPHEN = 'Hyphenated short camera model' 77SERIAL_NUMBER = 'Serial number' 78SHUTTER_COUNT = 'Shutter count' 79# Currently the only file number is Exif.CanonFi.FileNumber, 80# which is in the format xxx-yyyy, where xxx is the folder and yyyy the image 81FILE_NUMBER = 'File number' 82OWNER_NAME = 'Owner name' 83COPYRIGHT = 'Copyright' 84ARTIST = 'Artist' 85 86# Video metadata 87CODEC = 'Codec' 88WIDTH = 'Width' 89HEIGHT = 'Height' 90FPS = 'Frames Per Second' 91LENGTH = 'Length' 92 93# Image sequences 94DOWNLOAD_SEQ_NUMBER = 'Downloads today' 95SESSION_SEQ_NUMBER = 'Session number' 96SUBFOLDER_SEQ_NUMBER = 'Subfolder number' 97STORED_SEQ_NUMBER = 'Stored number' 98SEQUENCE_LETTER = 'Sequence letter' 99 100# *** Level 2, i.e. third and final column of values presented to user 101 102# Image number 103IMAGE_NUMBER_ALL = 'All digits' 104IMAGE_NUMBER_1 = 'Last digit' 105IMAGE_NUMBER_2 = 'Last 2 digits' 106IMAGE_NUMBER_3 = 'Last 3 digits' 107IMAGE_NUMBER_4 = 'Last 4 digits' 108 109# Case 110ORIGINAL_CASE = "Original Case" 111UPPERCASE = "UPPERCASE" 112LOWERCASE = "lowercase" 113 114# Sequence number 115SEQUENCE_NUMBER_1 = "One digit" 116SEQUENCE_NUMBER_2 = "Two digits" 117SEQUENCE_NUMBER_3 = "Three digits" 118SEQUENCE_NUMBER_4 = "Four digits" 119SEQUENCE_NUMBER_5 = "Five digits" 120SEQUENCE_NUMBER_6 = "Six digits" 121SEQUENCE_NUMBER_7 = "Seven digits" 122 123# File number 124FILE_NUMBER_FOLDER = "Folder only" 125FILE_NUMBER_ALL = "Folder and file" 126 127# Now, define dictionaries and lists of valid combinations of preferences. 128 129# Level 2 130 131# Date 132 133SUBSECONDS = 'Subseconds' 134 135# NOTE 1: if changing LIST_DATE_TIME_L2, you MUST update the default 136# subfolder preference immediately below 137# NOTE 2: if changing LIST_DATE_TIME_L2, you MUST also update 138# DATE_TIME_CONVERT below 139# NOTE 3: if changing LIST_DATE_TIME_L2, you MUST also update 140# PHOTO_SUBFOLDER_MENU_DEFAULTS_CONV 141LIST_DATE_TIME_L2 = [ 142 'YYYYMMDD', # 0 143 'YYYY-MM-DD', 144 'YYYY_MM_DD', # 2 145 'YYMMDD', 146 'YY-MM-DD', # 4 147 'YY_MM_DD', 148 'MMDDYYYY', # 6 149 'MMDDYY', 150 'MMDD', # 8 151 'DDMMYYYY', 152 'DDMMYY', # 10 153 'YYYY', 154 'YY', # 12 155 'MM', 156 'DD', # 14 157 'Month (full)', 158 'Month (abbreviated)', # 16 159 'Weekday (full)', 160 'Weekday (abbreviated)', # 18 161 'HHMMSS', 162 'HHMM', # 20 163 'HH-MM-SS', 164 'HH-MM', # 22 165 'HH', 166 'MM (minutes)', # 24 167 'SS' 168] 169 170LIST_IMAGE_DATE_TIME_L2 = LIST_DATE_TIME_L2 + [SUBSECONDS] 171 172DEFAULT_SUBFOLDER_PREFS = [ 173 DATE_TIME, IMAGE_DATE, LIST_DATE_TIME_L2[11], '/', '', '', DATE_TIME, IMAGE_DATE, 174 LIST_DATE_TIME_L2[0] 175] 176DEFAULT_VIDEO_SUBFOLDER_PREFS = [ 177 DATE_TIME, VIDEO_DATE, LIST_DATE_TIME_L2[11], '/', '', '', DATE_TIME, 178 VIDEO_DATE, LIST_DATE_TIME_L2[0] 179] 180 181DEFAULT_PHOTO_RENAME_PREFS = [FILENAME, NAME, ORIGINAL_CASE] 182DEFAULT_VIDEO_RENAME_PREFS = [FILENAME, NAME, ORIGINAL_CASE] 183 184 185class i18TranslateMeThanks: 186 """ this class is never used in actual running code 187 Its purpose is to have these values inserted into the program's i18n template file 188 189 """ 190 191 def __init__(self): 192 _('Date time') 193 _('Text') 194 _('Filename') 195 _('Metadata') 196 _('Sequences') 197 # Translators: for an explanation of what this means, 198 # see http://damonlynch.net/rapid/documentation/index.html#jobcode 199 _('Job code') 200 _('Image date') 201 _('Video date') 202 _('Today') 203 _('Yesterday') 204 # Translators: Download time is the time and date that the download started (when the 205 # user clicked the Download button) 206 _('Download time') 207 # Translators: for an explanation of what this means, 208 # see http://damonlynch.net/rapid/documentation/index.html#renamefilename 209 _('Name') 210 # Translators: for an explanation of what this means, 211 # see http://damonlynch.net/rapid/documentation/index.html#renamefilename 212 _('Extension') 213 # Translators: for an explanation of what this means, 214 # see http://damonlynch.net/rapid/documentation/index.html#renamefilename 215 _('Image number') 216 _('Video number') 217 # Translators: for an explanation of what this means, 218 # see http://damonlynch.net/rapid/documentation/index.html#renamemetadata 219 _('Aperture') 220 # Translators: for an explanation of what this means, 221 # see http://damonlynch.net/rapid/documentation/index.html#renamemetadata 222 _('ISO') 223 # Translators: for an explanation of what this means, 224 # see http://damonlynch.net/rapid/documentation/index.html#renamemetadata 225 _('Exposure time') 226 # Translators: for an explanation of what this means, 227 # see http://damonlynch.net/rapid/documentation/index.html#renamemetadata 228 _('Focal length') 229 # Translators: for an explanation of what this means, 230 # see http://damonlynch.net/rapid/documentation/index.html#renamemetadata 231 _('Camera make') 232 # Translators: for an explanation of what this means, 233 # see http://damonlynch.net/rapid/documentation/index.html#renamemetadata 234 _('Camera model') 235 # Translators: for an explanation of what this means, 236 # see http://damonlynch.net/rapid/documentation/index.html#renamemetadata 237 _('Short camera model') 238 # Translators: for an explanation of what this means, 239 # see http://damonlynch.net/rapid/documentation/index.html#renamemetadata 240 _('Hyphenated short camera model') 241 # Translators: for an explanation of what this means, 242 # see http://damonlynch.net/rapid/documentation/index.html#renamemetadata 243 _('Serial number') 244 # Translators: for an explanation of what this means, 245 # see http://damonlynch.net/rapid/documentation/index.html#renamemetadata 246 _('Shutter count') 247 # File number currently refers to the Exif value Exif.Canon.FileNumber 248 _('File number') 249 # Only the folder component of the Exif.Canon.FileNumber value 250 _('Folder only') 251 # The folder and file component of the Exif.Canon.FileNumber value 252 _('Folder and file') 253 # Translators: for an explanation of what this means, 254 # see http://damonlynch.net/rapid/documentation/index.html#renamemetadata 255 _('Owner name') 256 _('Codec') 257 _('Width') 258 _('Height') 259 _('Length') 260 _('Frames Per Second') 261 _('Artist') 262 _('Copyright') 263 # Translators: for an explanation of what this means, 264 # see http://damonlynch.net/rapid/documentation/index.html#sequencenumbers 265 _('Downloads today') 266 # Translators: for an explanation of what this means, 267 # see http://damonlynch.net/rapid/documentation/index.html#sequencenumbers 268 _('Session number') 269 # Translators: for an explanation of what this means, 270 # see http://damonlynch.net/rapid/documentation/index.html#sequencenumbers 271 _('Subfolder number') 272 # Translators: for an explanation of what this means, 273 # see http://damonlynch.net/rapid/documentation/index.html#sequencenumbers 274 _('Stored number') 275 # Translators: for an explanation of what this means, 276 # see http://damonlynch.net/rapid/documentation/index.html#sequenceletters 277 _('Sequence letter') 278 # Translators: for an explanation of what this means, 279 # see http://damonlynch.net/rapid/documentation/index.html#renamefilename 280 _('All digits') 281 # Translators: for an explanation of what this means, 282 # see http://damonlynch.net/rapid/documentation/index.html#renamefilename 283 _('Last digit') 284 # Translators: for an explanation of what this means, 285 # see http://damonlynch.net/rapid/documentation/index.html#renamefilename 286 _('Last 2 digits') 287 # Translators: for an explanation of what this means, 288 # see http://damonlynch.net/rapid/documentation/index.html#renamefilename 289 _('Last 3 digits') 290 # Translators: for an explanation of what this means, 291 # see http://damonlynch.net/rapid/documentation/index.html#renamefilename 292 _('Last 4 digits') 293 # Translators: please not the capitalization of this text, and keep it the same if your 294 # language features capitalization 295 _("Original Case") 296 # Translators: please not the capitalization of this text, and keep it the same if your 297 # language features capitalization 298 _("UPPERCASE") 299 # Translators: please not the capitalization of this text, and keep it the same if your 300 # language features capitalization 301 _("lowercase") 302 _("One digit") 303 _("Two digits") 304 _("Three digits") 305 _("Four digits") 306 _("Five digits") 307 _("Six digits") 308 _("Seven digits") 309 # Translators: for an explanation of what this means, 310 # see http://damonlynch.net/rapid/documentation/index.html#renamedateandtime 311 _('Subseconds') 312 # Translators: for an explanation of what this means, 313 # see http://damonlynch.net/rapid/documentation/index.html#renamedateandtime 314 _('YYYYMMDD') 315 # Translators: for an explanation of what this means, 316 # see http://damonlynch.net/rapid/documentation/index.html#renamedateandtime 317 _('YYYY-MM-DD') 318 # Translators: for an explanation of what this means, 319 # see http://damonlynch.net/rapid/documentation/index.html#renamedateandtime 320 _('YYYY_MM_DD') 321 # Translators: for an explanation of what this means, 322 # see http://damonlynch.net/rapid/documentation/index.html#renamedateandtime 323 _('YYMMDD') 324 # Translators: for an explanation of what this means, 325 # see http://damonlynch.net/rapid/documentation/index.html#renamedateandtime 326 _('YY-MM-DD') 327 # Translators: for an explanation of what this means, 328 # see http://damonlynch.net/rapid/documentation/index.html#renamedateandtime 329 _('YY_MM_DD') 330 # Translators: for an explanation of what this means, 331 # see http://damonlynch.net/rapid/documentation/index.html#renamedateandtime 332 _('MMDDYYYY') 333 # Translators: for an explanation of what this means, 334 # see http://damonlynch.net/rapid/documentation/index.html#renamedateandtime 335 _('MMDDYY') 336 # Translators: for an explanation of what this means, 337 # see http://damonlynch.net/rapid/documentation/index.html#renamedateandtime 338 _('MMDD') 339 # Translators: for an explanation of what this means, 340 # see http://damonlynch.net/rapid/documentation/index.html#renamedateandtime 341 _('DDMMYYYY') 342 # Translators: for an explanation of what this means, 343 # see http://damonlynch.net/rapid/documentation/index.html#renamedateandtime 344 _('DDMMYY') 345 # Translators: for an explanation of what this means, 346 # see http://damonlynch.net/rapid/documentation/index.html#renamedateandtime 347 _('YYYY') 348 # Translators: for an explanation of what this means, 349 # see http://damonlynch.net/rapid/documentation/index.html#renamedateandtime 350 _('YY') 351 # Translators: for an explanation of what this means, 352 # see http://damonlynch.net/rapid/documentation/index.html#renamedateandtime 353 _('MM') 354 # Translators: for an explanation of what this means, 355 # see http://damonlynch.net/rapid/documentation/index.html#renamedateandtime 356 _('DD') 357 # Translators: for an explanation of what this means, 358 # see http://damonlynch.net/rapid/documentation/index.html#renamedateandtime 359 _('Month (full)'), 360 # Translators: for an explanation of what this means, 361 # see http://damonlynch.net/rapid/documentation/index.html#renamedateandtime 362 _('Month (abbreviated)'), 363 # Translators: for an explanation of what this means, 364 # see http://damonlynch.net/rapid/documentation/index.html#renamedateandtime 365 _('Weekday (full)') 366 # Translators: for an explanation of what this means, 367 # see http://damonlynch.net/rapid/documentation/index.html#renamedateandtime 368 _('Weekday (abbreviated)') 369 # Translators: for an explanation of what this means, 370 # see http://damonlynch.net/rapid/documentation/index.html#renamedateandtime 371 _('HHMMSS') 372 # Translators: for an explanation of what this means, 373 # see http://damonlynch.net/rapid/documentation/index.html#renamedateandtime 374 _('HHMM') 375 # Translators: for an explanation of what this means, 376 # see http://damonlynch.net/rapid/documentation/index.html#renamedateandtime 377 _('HH-MM-SS') 378 # Translators: for an explanation of what this means, 379 # see http://damonlynch.net/rapid/documentation/index.html#renamedateandtime 380 _('HH-MM') 381 # Translators: for an explanation of what this means, 382 # see http://damonlynch.net/rapid/documentation/index.html#renamedateandtime 383 _('HH') 384 # Translators: for an explanation of what this means, 385 # see http://damonlynch.net/rapid/documentation/index.html#renamedateandtime 386 _('MM (minutes)') 387 # Translators: for an explanation of what this means, 388 # see http://damonlynch.net/rapid/documentation/index.html#renamedateandtime 389 _('SS') 390 391 # Convenience values for python datetime conversion using values in 392 393 394# Default subfolder options that appear in drop-down menu in Destination views 395# Any change to PHOTO_SUBFOLDER_MENU_DEFAULTS must also be reflected in 396# PHOTO_SUBFOLDER_MENU_DEFAULTS_CONV 397 398# The following values will be displayed in the menu after an os.sep.join() operation 399 400PHOTO_SUBFOLDER_MENU_DEFAULTS = ( 401 (_('Date'), _('YYYY'), _('YYYYMMDD')), 402 (_('Date (hyphens)'), _('YYYY'), _('YYYY-MM-DD')), 403 (_('Date (underscores)'), _('YYYY'), _('YYYY_MM_DD')), 404 (_('Date and Job Code'), _('YYYY'), _('YYYYMM_Job Code')), 405 (_('Date and Job Code Subfolder'), _('YYYY'), _('YYYYMM'), _('Job Code')) 406) 407 408# Any change to PHOTO_SUBFOLDER_MENU_DEFAULTS_CONV must also be reflected in 409# PHOTO_SUBFOLDER_MENU_DEFAULTS 410 411PHOTO_SUBFOLDER_MENU_DEFAULTS_CONV = ( 412 # 0 413 [DATE_TIME, IMAGE_DATE, LIST_DATE_TIME_L2[11], 414 '/', '', '', 415 DATE_TIME, IMAGE_DATE, LIST_DATE_TIME_L2[0] 416 ], 417 # 1 418 [DATE_TIME, IMAGE_DATE, LIST_DATE_TIME_L2[11], 419 '/', '', '', 420 DATE_TIME, IMAGE_DATE, LIST_DATE_TIME_L2[1] 421 ], 422 # 2 423 [DATE_TIME, IMAGE_DATE, LIST_DATE_TIME_L2[11], 424 '/', '', '', 425 DATE_TIME, IMAGE_DATE, LIST_DATE_TIME_L2[2] 426 ], 427 # 3 428 [DATE_TIME, IMAGE_DATE, LIST_DATE_TIME_L2[11], 429 '/', '', '', 430 DATE_TIME, IMAGE_DATE, LIST_DATE_TIME_L2[11], 431 DATE_TIME, IMAGE_DATE, LIST_DATE_TIME_L2[13], 432 TEXT, '_', '', 433 JOB_CODE, '', ''], 434 # 4 435 [DATE_TIME, IMAGE_DATE, LIST_DATE_TIME_L2[11], 436 '/', '', '', 437 DATE_TIME, IMAGE_DATE, LIST_DATE_TIME_L2[11], 438 DATE_TIME, IMAGE_DATE, LIST_DATE_TIME_L2[13], 439 '/', '', '', 440 JOB_CODE, '', '', 441 ], 442) 443 444PHOTO_RENAME_MENU_DEFAULTS = ( 445 (_('Original Filename'), 'IMG_1234'), 446 (_('Date-Time and Downloads today'), _('YYYYMMDD-HHMM-1')), 447 (_('Date and Downloads today'), _('YYYYMMDD-1')), 448 (_('Date-Time and Image number'), _('YYYYMMDD-1234')), 449 (_('Date-Time and Job Code'), _('YYYYMMDD-HHMM-Job Code-1')), 450 (_('Date and Job Code'), _('YYYYMMDD-Job Code-1')) 451) 452 453PHOTO_RENAME_MENU_DEFAULTS_CONV = ( 454 # 0 Original Filename 455 [FILENAME, NAME, ORIGINAL_CASE], 456 # 1 Date-Time and Downloads today 457 [ 458 DATE_TIME, IMAGE_DATE, LIST_DATE_TIME_L2[0], 459 TEXT, '-', '', 460 DATE_TIME, IMAGE_DATE, LIST_DATE_TIME_L2[20], 461 TEXT, '-', '', 462 SEQUENCES, DOWNLOAD_SEQ_NUMBER, SEQUENCE_NUMBER_1 463 ], 464 # 2 Date and Downloads today 465 [ 466 DATE_TIME, IMAGE_DATE, LIST_DATE_TIME_L2[0], 467 TEXT, '-', '', 468 SEQUENCES, DOWNLOAD_SEQ_NUMBER, SEQUENCE_NUMBER_1 469 ], 470 # 3 Date-Time and Image number 471 [ 472 DATE_TIME, IMAGE_DATE, LIST_DATE_TIME_L2[0], 473 TEXT, '-', '', 474 DATE_TIME, IMAGE_DATE, LIST_DATE_TIME_L2[20], 475 TEXT, '-', '', 476 FILENAME, IMAGE_NUMBER, IMAGE_NUMBER_ALL 477 ], 478 # 4 Date-Time and Job Code 479 [ 480 DATE_TIME, IMAGE_DATE, LIST_DATE_TIME_L2[0], 481 TEXT, '-', '', 482 DATE_TIME, IMAGE_DATE, LIST_DATE_TIME_L2[20], 483 TEXT, '-', '', 484 JOB_CODE, '', '', 485 TEXT, '-', '', 486 SEQUENCES, DOWNLOAD_SEQ_NUMBER, SEQUENCE_NUMBER_1 487 ], 488 # 5 Date and Job Code 489 [ 490 DATE_TIME, IMAGE_DATE, LIST_DATE_TIME_L2[0], 491 TEXT, '-', '', 492 JOB_CODE, '', '', 493 TEXT, '-', '', 494 SEQUENCES, DOWNLOAD_SEQ_NUMBER, SEQUENCE_NUMBER_1 495 ] 496) 497 498# See notes above regarding keeping values in sync 499VIDEO_SUBFOLDER_MENU_DEFAULTS = PHOTO_SUBFOLDER_MENU_DEFAULTS 500VIDEO_SUBFOLDER_MENU_DEFAULTS_CONV = ( 501 # 0 502 [ 503 DATE_TIME, VIDEO_DATE, LIST_DATE_TIME_L2[11], 504 SEPARATOR, '', '', 505 DATE_TIME, VIDEO_DATE, LIST_DATE_TIME_L2[0] 506 ], 507 # 1 508 [ 509 DATE_TIME, VIDEO_DATE, LIST_DATE_TIME_L2[11], 510 SEPARATOR, '', '', 511 DATE_TIME, VIDEO_DATE, LIST_DATE_TIME_L2[1] 512 ], 513 # 2 514 [ 515 DATE_TIME, VIDEO_DATE, LIST_DATE_TIME_L2[11], 516 SEPARATOR, '', '', 517 DATE_TIME, VIDEO_DATE, LIST_DATE_TIME_L2[2] 518 ], 519 # 3 520 [ 521 DATE_TIME, VIDEO_DATE, LIST_DATE_TIME_L2[11], 522 SEPARATOR, '', '', 523 DATE_TIME, VIDEO_DATE, LIST_DATE_TIME_L2[11], 524 DATE_TIME, VIDEO_DATE, LIST_DATE_TIME_L2[13], 525 TEXT, '_', '', 526 JOB_CODE, '', '' 527 ], 528 # 4 529 [ 530 DATE_TIME, VIDEO_DATE, LIST_DATE_TIME_L2[11], 531 SEPARATOR, '', '', 532 DATE_TIME, VIDEO_DATE, LIST_DATE_TIME_L2[11], 533 DATE_TIME, VIDEO_DATE, LIST_DATE_TIME_L2[13], 534 SEPARATOR, '', '', 535 JOB_CODE, '', '', 536 ], 537) 538 539VIDEO_RENAME_MENU_DEFAULTS = ( 540 (_('Original Filename'), 'MVI_1234'), 541 (_('Date-Time and Downloads today'), _('YYYYMMDD-HHMM-1')), 542 (_('Date and Downloads today'), _('YYYYMMDD-1')), 543 (_('Date-Time and Video number'), _('YYYYMMDD_1234')), 544 (_('Date-Time and Job Code'), _('YYYYMMDD-HHMM-Job Code-1')), 545 (_('Date and Job Code'), _('YYYYMMDD-Job Code-1')), 546 (_('Resolution'), _('YYYYMMDD-HHMM-1-1920x1080')) 547) 548 549VIDEO_RENAME_MENU_DEFAULTS_CONV = ( 550 # 0 Original Filename 551 [FILENAME, NAME, ORIGINAL_CASE], 552 # 1 Date-Time and Downloads today 553 [ 554 DATE_TIME, VIDEO_DATE, LIST_DATE_TIME_L2[0], 555 TEXT, '-', '', 556 DATE_TIME, VIDEO_DATE, LIST_DATE_TIME_L2[20], 557 TEXT, '-', '', 558 SEQUENCES, DOWNLOAD_SEQ_NUMBER, SEQUENCE_NUMBER_1 559 ], 560 # 2 Date and Downloads today 561 [ 562 DATE_TIME, VIDEO_DATE, LIST_DATE_TIME_L2[0], 563 TEXT, '-', '', 564 SEQUENCES, DOWNLOAD_SEQ_NUMBER, SEQUENCE_NUMBER_1 565 ], 566 # 3 Date-Time and Image number 567 [ 568 DATE_TIME, VIDEO_DATE, LIST_DATE_TIME_L2[0], 569 TEXT, '-', '', 570 DATE_TIME, VIDEO_DATE, LIST_DATE_TIME_L2[20], 571 TEXT, '-', '', 572 FILENAME, VIDEO_NUMBER, IMAGE_NUMBER_ALL 573 ], 574 # 4 Date-Time and Job Code 575 [ 576 DATE_TIME, VIDEO_DATE, LIST_DATE_TIME_L2[0], 577 TEXT, '-', '', 578 DATE_TIME, VIDEO_DATE, LIST_DATE_TIME_L2[20], 579 TEXT, '-', '', 580 JOB_CODE, '', '', 581 TEXT, '-', '', 582 SEQUENCES, DOWNLOAD_SEQ_NUMBER, SEQUENCE_NUMBER_1 583 ], 584 # 5 Date and Job Code 585 [ 586 DATE_TIME, VIDEO_DATE, LIST_DATE_TIME_L2[0], 587 TEXT, '-', '', 588 JOB_CODE, '', '', 589 TEXT, '-', '', 590 SEQUENCES, DOWNLOAD_SEQ_NUMBER, SEQUENCE_NUMBER_1 591 ], 592 # 6 Resolution 593 [ 594 DATE_TIME, VIDEO_DATE, LIST_DATE_TIME_L2[0], 595 TEXT, '-', '', 596 DATE_TIME, VIDEO_DATE, LIST_DATE_TIME_L2[20], 597 TEXT, '-', '', 598 SEQUENCES, DOWNLOAD_SEQ_NUMBER, SEQUENCE_NUMBER_1, 599 TEXT, '-', '', 600 METADATA, WIDTH, '', 601 TEXT, 'x', '', 602 METADATA, HEIGHT, '' 603 ] 604) 605 606# See notes above regarding keeping values in sync 607DATE_TIME_CONVERT = [ 608 '%Y%m%d', # 0 609 '%Y-%m-%d', 610 '%Y_%m_%d', # 2 611 '%y%m%d', 612 '%y-%m-%d', # 4 613 '%y_%m_%d', 614 '%m%d%Y', # 6 615 '%m%d%y', 616 '%m%d', # 8 617 '%d%m%Y', 618 '%d%m%y', # 10 619 '%Y', 620 '%y', # 12 621 '%m', 622 '%d', # 14 623 '%B', 624 '%b', # 16 625 '%A', 626 '%a', # 18 627 '%H%M%S', 628 '%H%M', # 20 629 '%H-%M-%S', 630 '%H-%M', # 22 631 '%H', 632 '%M', # 24 633 '%S' 634] 635 636LIST_IMAGE_NUMBER_L2 = [ 637 IMAGE_NUMBER_ALL, IMAGE_NUMBER_1, IMAGE_NUMBER_2, IMAGE_NUMBER_3, IMAGE_NUMBER_4 638] 639 640LIST_CASE_L2 = [ORIGINAL_CASE, UPPERCASE, LOWERCASE] 641 642LIST_SEQUENCE_LETTER_L2 = [ 643 UPPERCASE, 644 LOWERCASE 645] 646 647LIST_SEQUENCE_NUMBERS_L2 = [ 648 SEQUENCE_NUMBER_1, 649 SEQUENCE_NUMBER_2, 650 SEQUENCE_NUMBER_3, 651 SEQUENCE_NUMBER_4, 652 SEQUENCE_NUMBER_5, 653 SEQUENCE_NUMBER_6, 654 SEQUENCE_NUMBER_7, 655] 656 657LIST_SHUTTER_COUNT_L2 = [ 658 SEQUENCE_NUMBER_3, 659 SEQUENCE_NUMBER_4, 660 SEQUENCE_NUMBER_5, 661 SEQUENCE_NUMBER_6, 662] 663FILE_NUMBER_L2 = [ 664 FILE_NUMBER_FOLDER, 665 FILE_NUMBER_ALL 666] 667 668# Level 1 669 670DICT_DATE_TIME_L1 = OrderedDict( 671 [ 672 (IMAGE_DATE, LIST_IMAGE_DATE_TIME_L2), 673 (TODAY, LIST_DATE_TIME_L2), 674 (YESTERDAY, LIST_DATE_TIME_L2), 675 (DOWNLOAD_TIME, LIST_DATE_TIME_L2), 676 ] 677) 678 679VIDEO_DICT_DATE_TIME_L1 = OrderedDict( 680 [ 681 (VIDEO_DATE, LIST_IMAGE_DATE_TIME_L2), 682 (TODAY, LIST_DATE_TIME_L2), 683 (YESTERDAY, LIST_DATE_TIME_L2), 684 (DOWNLOAD_TIME, LIST_DATE_TIME_L2), 685 ] 686) 687 688DICT_FILENAME_L1 = OrderedDict( 689 [ 690 (NAME, LIST_CASE_L2), 691 (IMAGE_NUMBER, LIST_IMAGE_NUMBER_L2), 692 ] 693) 694 695# pre 0.9.0a4 values for DICT_FILENAME_L1: 696#(NAME_EXTENSION, LIST_CASE_L2), 697# (EXTENSION, LIST_CASE_L2), 698 699DICT_VIDEO_FILENAME_L1 = OrderedDict( 700 [ 701 (NAME, LIST_CASE_L2), 702 (VIDEO_NUMBER, LIST_IMAGE_NUMBER_L2), 703 ] 704) 705 706# pre 0.9.0a4 values for DICT_VIDEO_FILENAME_L1: 707# (NAME_EXTENSION, LIST_CASE_L2), 708# (EXTENSION, LIST_CASE_L2), 709 710DICT_SUBFOLDER_FILENAME_L1 = { 711 EXTENSION: LIST_CASE_L2, 712} 713 714DICT_METADATA_L1 = OrderedDict( 715 [ 716 (APERTURE, None), 717 (ISO, None), 718 (EXPOSURE_TIME, None), 719 (FOCAL_LENGTH, None), 720 (CAMERA_MAKE, LIST_CASE_L2), 721 (CAMERA_MODEL, LIST_CASE_L2), 722 (SHORT_CAMERA_MODEL, LIST_CASE_L2), 723 (SHORT_CAMERA_MODEL_HYPHEN, LIST_CASE_L2), 724 (SERIAL_NUMBER, None), 725 (SHUTTER_COUNT, LIST_SHUTTER_COUNT_L2), 726 (FILE_NUMBER, FILE_NUMBER_L2), 727 (OWNER_NAME, LIST_CASE_L2), 728 (ARTIST, LIST_CASE_L2), 729 (COPYRIGHT, LIST_CASE_L2), 730 ] 731) 732 733DICT_VIDEO_METADATA_L1 = OrderedDict( 734 [ 735 (CODEC, LIST_CASE_L2), 736 (WIDTH, None), 737 (HEIGHT, None), 738 (LENGTH, None), 739 (FPS, None), 740 ] 741) 742 743DICT_SEQUENCE_L1 = OrderedDict( 744 [ 745 (DOWNLOAD_SEQ_NUMBER, LIST_SEQUENCE_NUMBERS_L2), 746 (STORED_SEQ_NUMBER, LIST_SEQUENCE_NUMBERS_L2), 747 (SESSION_SEQ_NUMBER, LIST_SEQUENCE_NUMBERS_L2), 748 (SEQUENCE_LETTER, LIST_SEQUENCE_LETTER_L2), 749 ] 750) 751 752LIST_SEQUENCE_L1 = list(DICT_SEQUENCE_L1.keys()) 753 754# Level 0 755 756DICT_IMAGE_RENAME_L0 = OrderedDict( 757 [ 758 (DATE_TIME, DICT_DATE_TIME_L1), 759 (TEXT, None), 760 (FILENAME, DICT_FILENAME_L1), 761 (METADATA, DICT_METADATA_L1), 762 (SEQUENCES, DICT_SEQUENCE_L1), 763 (JOB_CODE, None), 764 ] 765) 766 767DICT_VIDEO_RENAME_L0 = OrderedDict( 768 [ 769 (DATE_TIME, VIDEO_DICT_DATE_TIME_L1), 770 (TEXT, None), 771 (FILENAME, DICT_VIDEO_FILENAME_L1), 772 (METADATA, DICT_VIDEO_METADATA_L1), 773 (SEQUENCES, DICT_SEQUENCE_L1), 774 (JOB_CODE, None), 775 ] 776) 777 778DICT_SUBFOLDER_L0 = OrderedDict( 779 [ 780 (DATE_TIME, DICT_DATE_TIME_L1), 781 (TEXT, None), 782 (FILENAME, DICT_SUBFOLDER_FILENAME_L1), 783 (METADATA, DICT_METADATA_L1), 784 (SEPARATOR, None), 785 (JOB_CODE, None), 786 ] 787) 788 789DICT_VIDEO_SUBFOLDER_L0 = OrderedDict( 790 [ 791 (DATE_TIME, VIDEO_DICT_DATE_TIME_L1), 792 (TEXT, None), 793 (FILENAME, DICT_SUBFOLDER_FILENAME_L1), 794 (METADATA, DICT_VIDEO_METADATA_L1), 795 (SEPARATOR, None), 796 (JOB_CODE, None), 797 ] 798) 799 800# preference elements that require metadata 801# note there is no need to specify lower level elements if a higher level 802# element is necessary for them to be present to begin with 803METADATA_ELEMENTS = [METADATA, IMAGE_DATE] 804 805# preference elements that are sequence numbers or letters 806SEQUENCE_ELEMENTS = [ 807 DOWNLOAD_SEQ_NUMBER, 808 SESSION_SEQ_NUMBER, 809 SUBFOLDER_SEQ_NUMBER, 810 STORED_SEQ_NUMBER, 811 SEQUENCE_LETTER 812] 813 814# preference elements that do not require metadata and are not fixed 815# as above, there is no need to specify lower level elements if a higher level 816# element is necessary for them to be present to begin with 817DYNAMIC_NON_METADATA_ELEMENTS = [TODAY, YESTERDAY, FILENAME] + SEQUENCE_ELEMENTS 818 819PHOTO_RENAME_COMPLEX = [ 820 'Date time', 'Image date', 'YYYYMMDD', 'Text', '-', '', 'Date time', 'Image date', 'HHMM', 821 'Text', '-', '', 'Sequences', 'Downloads today', 'One digit', 'Text', '-iso', '', 'Metadata', 822 'ISO', '', 'Text', '-f', '', 'Metadata', 'Aperture', '', 'Text', '-', '', 'Metadata', 823 'Focal length', '', 'Text', 'mm-', '', 'Metadata', 'Exposure time', '' 824] 825PHOTO_RENAME_SIMPLE = [ 826 'Date time', 'Image date', 'YYYYMMDD', 'Text', '-', '', 'Date time', 'Image date', 'HHMM', 827 'Text', '-', '', 'Sequences', 'Downloads today', 'One digit' 828] 829 830VIDEO_RENAME_SIMPLE = [x if x != 'Image date' else 'Video date' for x in PHOTO_RENAME_SIMPLE] 831 832JOB_CODE_RENAME_TEST = ['Job code', '', '', 'Sequences', 'Downloads today', 'One digit'] 833 834 835def upgrade_pre090a4_rename_pref(pref_list: List[str]) -> Tuple[List[str], str]: 836 r""" 837 Upgrade photo and video rename preference list 838 839 :param pref_list: pref list to upgrade 840 :return: tuple of new pref list, and if found, the case to be used for the 841 extension 842 843 >>> upgrade_pre090a4_rename_pref([FILENAME, NAME_EXTENSION, ORIGINAL_CASE]) 844 (['Filename', 'Name', 'Original Case'], 'Original Case') 845 >>> upgrade_pre090a4_rename_pref(PHOTO_RENAME_SIMPLE + [FILENAME, EXTENSION, LOWERCASE]) 846 ... # doctest: +NORMALIZE_WHITESPACE 847 (['Date time', 'Image date', 'YYYYMMDD', 848 'Text', '-', '', 849 'Date time', 'Image date', 'HHMM', 850 'Text', '-', '', 851 'Sequences', 'Downloads today', 'One digit'], 'lowercase') 852 >>> upgrade_pre090a4_rename_pref(PHOTO_RENAME_COMPLEX + [FILENAME, EXTENSION, UPPERCASE]) 853 ... # doctest: +NORMALIZE_WHITESPACE 854 (['Date time', 'Image date', 'YYYYMMDD', 'Text', '-', '', 855 'Date time', 'Image date', 'HHMM', 'Text', '-', '', 'Sequences', 856 'Downloads today', 'One digit', 'Text', '-iso', '', 857 'Metadata', 'ISO', '', 'Text', '-f', '', 'Metadata', 858 'Aperture', '', 'Text', '-', '', 'Metadata', 'Focal length', '', 859 'Text', 'mm-', '', 'Metadata', 'Exposure time', ''], 'UPPERCASE') 860 >>> upgrade_pre090a4_rename_pref([FILENAME, NAME, LOWERCASE]) 861 (['Filename', 'Name', 'lowercase'], None) 862 863 """ 864 if not pref_list: 865 return (pref_list, None) 866 867 # get extension case from last value 868 if pref_list[-2] in (NAME_EXTENSION, EXTENSION): 869 case = pref_list[-1] 870 else: 871 case = None 872 873 new_pref_list = [] 874 for idx in range(0, len(pref_list), 3): 875 l1 = pref_list[idx + 1] 876 if l1 != EXTENSION: 877 if l1 == NAME_EXTENSION: 878 l1 = NAME 879 new_pref_list.extend([pref_list[idx], l1, pref_list[idx + 2]]) 880 return new_pref_list, case 881 882 883class PrefError(Exception): 884 """ base class """ 885 886 def __init__(self): 887 super().__init__() 888 self.msg = '' 889 890 def unpackList(self, l: List[str]): 891 """ 892 Make the preferences presentable to the user 893 """ 894 return ', '.join("'{}'".format(i) for i in l) 895 896 def __str__(self): 897 return self.msg 898 899 900class PrefKeyError(PrefError): 901 def __init__(self, error): 902 super().__init__() 903 value = error[0] 904 expectedValues = self.unpackList(error[1]) 905 self.msg = "Preference key '%(key)s' is invalid.\nExpected one of %(value)s" % { 906 'key': value, 'value': expectedValues} 907 908 909class PrefValueInvalidError(PrefKeyError): 910 def __init__(self, error): 911 super().__init__(error) 912 value = error[0] 913 self.msg = "Preference value '%(value)s' is invalid" % {'value': value} 914 915 916class PrefLengthError(PrefError): 917 def __init__(self, error): 918 super().__init__() 919 self.msg = "These preferences are not well formed:" + "\n %s" % self.unpackList(error) 920 921 922class PrefValueKeyComboError(PrefError): 923 def __init__(self, error): 924 super().__init__() 925 self.msg = error 926 927 928def check_pref_valid(pref_defn, prefs, modulo=3) -> bool: 929 """ 930 Checks to see if user preferences are valid according to their 931 definition. Raises appropriate exception if an error is found. 932 933 :param prefs: list of preferences 934 :param pref_defn: is a Dict specifying what is valid 935 :param modulo: how many list elements are equivalent to one line 936 of preferences. 937 :return: True if prefs match with pref_defn 938 """ 939 940 if (len(prefs) % modulo != 0) or not prefs: 941 raise PrefLengthError(prefs) 942 else: 943 for i in range(0, len(prefs), modulo): 944 _check_pref_valid(pref_defn, prefs[i:i + modulo]) 945 946 return True 947 948 949def _check_pref_valid(pref_defn, prefs): 950 key = prefs[0] 951 value = prefs[1] 952 953 if key in pref_defn: 954 955 next_pref_defn = pref_defn[key] 956 957 if value is None: 958 # value should never be None, at any time 959 raise PrefValueInvalidError((None, next_pref_defn)) 960 961 if next_pref_defn and not value: 962 raise PrefValueInvalidError((value, next_pref_defn)) 963 964 if isinstance(next_pref_defn, dict): 965 return _check_pref_valid(next_pref_defn, prefs[1:]) 966 else: 967 if isinstance(next_pref_defn, list): 968 result = value in next_pref_defn 969 if not result: 970 raise PrefValueInvalidError((value, next_pref_defn)) 971 return True 972 elif not next_pref_defn: 973 return True 974 else: 975 result = next_pref_defn == value 976 if not result: 977 raise PrefValueInvalidError((value, next_pref_defn)) 978 return True 979 else: 980 raise PrefKeyError((key, list(pref_defn.keys()))) 981 982 983def filter_subfolder_prefs(pref_list: List[str], 984 pref_colors: Optional[List[str]]=None) \ 985 -> Tuple[bool, List[str], Optional[List[str]]]: 986 """ 987 Filters out extraneous preference choices. 988 989 :param pref_list: the list of user specified preferences 990 :param pref_colors: optional list of colors associated with displaying the 991 generated sample name while editing the preferences 992 :return: bool indicating whether list changed, the pref list, and optionally the 993 list of colors 994 """ 995 996 prefs_changed = False 997 continue_check = True 998 while continue_check and pref_list: 999 continue_check = False 1000 if pref_list[0] == SEPARATOR: 1001 # subfolder preferences should not start with a / 1002 pref_list = pref_list[3:] 1003 if pref_colors is not None: 1004 pref_colors = pref_colors[1:] 1005 prefs_changed = True 1006 continue_check = True 1007 elif pref_list[-3] == SEPARATOR: 1008 # subfolder preferences should not end with a / 1009 pref_list = pref_list[:-3] 1010 if pref_colors is not None: 1011 pref_colors = pref_colors[:-1] 1012 continue_check = True 1013 prefs_changed = True 1014 else: 1015 for i in range(0, len(pref_list) - 3, 3): 1016 if pref_list[i] == SEPARATOR and pref_list[i + 3] == SEPARATOR: 1017 # subfolder preferences should not contain two /s side by side 1018 continue_check = True 1019 prefs_changed = True 1020 # note we are messing with the contents of the pref list, 1021 # must exit loop and try again 1022 pref_list = pref_list[:i] + pref_list[i + 3:] 1023 if pref_colors is not None: 1024 pref_colors = pref_colors[:i//3] + pref_colors[i//3 + 1:] 1025 break 1026 1027 return (prefs_changed, pref_list, pref_colors) 1028