1# Copyright (C) 2007-2020 Damon Lynch <damonlynch@gmail.com>
2
3# This file is part of Rapid Photo Downloader.
4#
5# Rapid Photo Downloader is free software: you can redistribute it and/or
6# modify
7# it under the terms of the GNU General Public License as published by
8# the Free Software Foundation, either version 3 of the License, or
9# (at your option) any later version.
10#
11# Rapid Photo Downloader is distributed in the hope that it will be useful,
12# but WITHOUT ANY WARRANTY; without even the implied warranty of
13# MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the
14# GNU General Public License for more details.
15#
16# You should have received a copy of the GNU General Public License
17# along with Rapid Photo Downloader.  If not,
18# see <http://www.gnu.org/licenses/>.
19
20__author__ = 'Damon Lynch'
21__copyright__ = "Copyright 2007-2020, Damon Lynch"
22
23from enum import (Enum, IntEnum)
24from PyQt5.QtCore import Qt
25from PyQt5.QtGui import QFont, QFontMetrics, QColor
26
27PROGRAM_NAME = "Rapid Photo Downloader"
28logfile_name = 'rapid-photo-downloader.log'
29
30remote_versions_file = 'https://www.damonlynch.net/rapid/version.json'
31
32# If set to True, the ability to check for a new version will be removed
33# from the user interface and disabled in program logic.
34disable_version_check = False
35
36
37class CheckNewVersionDialogResult(IntEnum):
38    download = 1
39    do_not_download = 2
40    skip = 3
41    open_website = 4
42
43
44class CheckNewVersionDialogState(IntEnum):
45    check = 1
46    prompt_for_download = 2
47    open_website = 3
48    failed_to_contact = 4
49    have_latest_version = 5
50
51
52class ConflictResolution(IntEnum):
53    skip = 1
54    add_identifier = 2
55
56
57class ErrorType(Enum):
58    critical_error = 1
59    serious_error = 2
60    warning = 3
61
62
63class PresetPrefType(Enum):
64    preset_photo_subfolder = 1
65    preset_video_subfolder = 2
66    preset_photo_rename = 3
67    preset_video_rename = 4
68
69
70class PresetClass(Enum):
71    builtin = 1
72    custom = 2
73    new_preset = 3
74    remove_all = 4
75    update_preset = 5
76    edited = 6
77    start_editor = 7
78
79
80class DownloadStatus(Enum):
81    # going to try to download it
82    download_pending = 1
83
84    # downloaded successfully
85    downloaded = 2
86
87    # downloaded ok but there was a warning
88    downloaded_with_warning = 3
89
90    # downloaded ok, but the file was not backed up, or had a problem
91    # (overwrite or duplicate)
92    backup_problem = 4
93
94    # has not yet been downloaded (but might be if the user chooses)
95    not_downloaded = 5
96
97    # tried to download but failed, and the backup failed or had an error
98    download_and_backup_failed = 6
99
100    # tried to download but failed
101    download_failed = 7
102
103
104Downloaded = (DownloadStatus.downloaded,
105              DownloadStatus.downloaded_with_warning,
106              DownloadStatus.backup_problem)
107
108
109DownloadWarning = {DownloadStatus.downloaded_with_warning, DownloadStatus.backup_problem}
110DownloadFailure = {DownloadStatus.download_and_backup_failed, DownloadStatus.download_failed}
111
112
113download_status_error_severity = {
114    DownloadStatus.downloaded_with_warning: ErrorType.warning,
115    DownloadStatus.backup_problem: ErrorType.serious_error,
116    DownloadStatus.download_and_backup_failed: ErrorType.serious_error,
117    DownloadStatus.download_failed: ErrorType.serious_error
118}
119
120
121DownloadUpdateMilliseconds = 1000
122DownloadUpdateSeconds = DownloadUpdateMilliseconds / 1000
123# How many seconds to delay showing the time remaining and download speed
124ShowTimeAndSpeedDelay = 8.0
125
126
127class RightSideButton(IntEnum):
128    destination = 0
129    rename = 1
130    jobcode = 2
131    backup = 3
132
133
134class ThumbnailCacheStatus(Enum):
135    not_ready = 1
136    orientation_unknown = 2
137    ready = 3
138    fdo_256_ready = 4
139    generation_failed = 5
140
141
142class ThumbnailCacheDiskStatus(Enum):
143    found = 1
144    not_found = 2
145    failure = 3
146    unknown = 4
147
148
149class ThumbnailCacheOrigin(Enum):
150    thumbnail_cache = 1
151    fdo_cache = 2
152
153
154class DisplayingFilesOfType(Enum):
155    photos = 1
156    videos = 2
157    photos_and_videos = 3
158
159
160BackupLocationType = DisplayingFilesOfType
161BackupFailureType = DisplayingFilesOfType
162DownloadingFileTypes = DisplayingFilesOfType
163
164
165class DestinationDisplayType(Enum):
166    folder_only = 1
167    usage_only = 2
168    folders_and_usage = 3
169
170
171class ExifSource(Enum):
172    raw_bytes = 1
173    app1_segment = 2
174    actual_file = 3
175
176
177class DestinationDisplayMousePos(Enum):
178    normal = 1
179    menu = 2
180
181
182class DestinationDisplayTooltipState(Enum):
183    menu = 1
184    path = 2
185    storage_space = 3
186
187
188class DeviceType(Enum):
189    camera = 1
190    volume = 2
191    path = 3
192
193
194class BackupDeviceType:
195    volume = 1
196    path = 2
197
198
199class DeviceState(Enum):
200    pre_scan = 1
201    scanning = 2
202    idle = 3
203    thumbnailing = 4
204    downloading = 5
205    finished = 6
206
207
208class FileType(IntEnum):
209    photo = 1
210    video = 2
211
212
213class FileExtension(Enum):
214    raw = 1
215    jpeg = 2
216    heif = 3
217    other_photo = 4
218    video = 5
219    audio = 6
220    unknown = 7
221
222
223class FileSortPriority(IntEnum):
224    high = 1
225    low = 2
226
227
228class KnownDeviceType(IntEnum):
229    volume_whitelist = 1
230    volume_blacklist = 2
231    camera_blacklist = 3
232
233
234class RenameAndMoveStatus(Enum):
235    download_started = 1
236    download_completed = 2
237
238
239class BackupStatus(Enum):
240    backup_started = 1
241    backup_completed = 2
242
243
244class ThumbnailSize(IntEnum):
245    width = 160
246    height = 120
247
248
249class ApplicationState(Enum):
250    normal = 1
251    exiting = 2
252
253
254class Show(IntEnum):
255    all = 1
256    new_only = 2
257
258
259class Sort(IntEnum):
260    modification_time = 1
261    checked_state = 2
262    filename = 3
263    extension = 4
264    file_type = 5
265    device = 6
266
267
268class JobCodeSort(IntEnum):
269    last_used = 1
270    code = 2
271
272
273Checked_Status = {
274    Qt.Checked: 'checked',
275    Qt.Unchecked: 'unchecked',
276    Qt.PartiallyChecked: 'partially checked'
277}
278
279
280class Roles(IntEnum):
281    previously_downloaded = Qt.UserRole
282    extension = Qt.UserRole + 1
283    download_status = Qt.UserRole + 2
284    has_audio = Qt.UserRole + 3
285    secondary_attribute = Qt.UserRole + 4
286    path = Qt.UserRole + 5
287    uri = Qt.UserRole + 6
288    camera_memory_card = Qt.UserRole + 7
289    scan_id = Qt.UserRole + 8
290    device_details = Qt.UserRole + 9
291    storage = Qt.UserRole + 10
292    mtp = Qt.UserRole + 11
293    is_camera = Qt.UserRole + 12
294    sort_extension = Qt.UserRole + 13
295    filename = Qt.UserRole + 14
296    highlight = Qt.UserRole + 16
297    folder_preview = Qt.UserRole + 17
298    download_subfolder = Qt.UserRole + 18
299    device_type = Qt.UserRole + 19
300    download_statuses = Qt.UserRole + 20
301    job_code = Qt.UserRole + 21
302    uids = Qt.UserRole + 22
303
304
305class ExtractionTask(Enum):
306    undetermined = 1
307    bypass = 2
308    load_file_directly = 3
309    load_file_and_exif_directly = 4
310    load_file_directly_metadata_from_secondary = 5
311    load_from_bytes = 6
312    load_from_bytes_metadata_from_temp_extract = 7
313    load_from_exif = 8
314    extract_from_file = 9
315    extract_from_file_and_load_metadata = 10
316    load_from_exif_buffer = 11
317    load_heif_directly = 12
318    load_heif_and_exif_directly = 13
319
320
321class ExtractionProcessing(Enum):
322    resize = 1
323    orient = 2
324    strip_bars_photo = 3
325    strip_bars_video = 4
326    add_film_strip = 5
327
328
329# Approach device uses to store timestamps
330# i.e. whether assumes are located in utc timezone or local
331class DeviceTimestampTZ(Enum):
332    undetermined = 1
333    unknown = 2
334    is_utc = 3
335    is_local = 4
336
337
338class CameraErrorCode(Enum):
339    inaccessible = 1
340    locked = 2
341    read = 3
342    write = 4
343
344
345class ViewRowType(Enum):
346    header = 1
347    content = 2
348
349
350class Align(Enum):
351    top = 1
352    bottom = 2
353
354
355class NameGenerationType(Enum):
356    photo_name = 1
357    video_name = 2
358    photo_subfolder = 3
359    video_subfolder = 4
360
361
362class CustomColors(Enum):
363    color1 = '#7a9c38'  # green
364    color2 = '#cb493f'  # red
365    color3 = '#d17109'  # orange
366    color4 = '#4D8CDC'  # blue
367    color5 = '#5f6bfe'  # purple
368    color6 = '#6d7e90'  # greyish
369    color7 = '#ffff00'  # bright yellow
370
371
372PaleGray = '#d7d6d5'
373DarkGray = '#35322f'
374MediumGray = '#5d5b59'
375DoubleDarkGray = '#1e1b18'
376
377
378ExtensionColorDict = {
379    FileExtension.raw: CustomColors.color1,
380    FileExtension.video: CustomColors.color2,
381    FileExtension.jpeg: CustomColors.color4,
382    FileExtension.heif: CustomColors.color5,
383    FileExtension.other_photo: CustomColors.color5
384}
385
386
387def extensionColor(ext_type: FileExtension) -> QColor:
388    try:
389        return QColor(ExtensionColorDict[ext_type].value)
390    except KeyError:
391        return QColor(0, 0, 0)
392
393
394FileTypeColorDict = {
395    FileType.photo: CustomColors.color1,
396    FileType.video: CustomColors.color2
397}
398
399
400def fileTypeColor(file_type: FileType) -> QColor:
401    try:
402        return QColor(FileTypeColorDict[file_type].value)
403    except KeyError:
404        return QColor(CustomColors.color3.value)
405
406
407# Position of preference values in file renaming and subfolder generation editor:
408class PrefPosition(Enum):
409    on_left = 1
410    at = 2
411    on_left_and_at = 3
412    positioned_in = 4
413    not_here = 5
414
415
416# Values in minutes:
417proximity_time_steps = [5, 10, 15, 30, 45, 60, 90, 120, 180, 240, 480, 960, 1440]
418
419
420class TemporalProximityState(Enum):
421    empty = 1
422    pending = 2  # e.g. 2 devices scanning, only 1 scan finished
423    generating = 3
424    regenerate = 4
425    generated = 5
426    ctime_rebuild = 6
427    ctime_rebuild_proceed = 7
428
429
430class StandardFileLocations(Enum):
431    home = 1
432    desktop = 2
433    file_system = 3
434    documents = 4
435    music = 5
436    pictures = 6
437    videos = 7
438    downloads = 8
439
440
441
442max_remembered_destinations = 10
443
444ThumbnailBackgroundName = MediumGray
445EmptyViewHeight = 20
446
447DeviceDisplayPadding = 6
448DeviceShadingIntensity = 104
449
450# How many steps with which to highlight thumbnail cells
451FadeSteps = 20
452FadeMilliseconds = 700
453
454
455# horizontal and vertical margin for thumbnail rectangles
456thumbnail_margin = 10
457
458
459def minPanelWidth() -> int:
460    """
461    Minimum width of panels on left and right side of main window.
462
463    Derived from standard font size.
464
465    :return: size in pixels
466    """
467
468    return int(QFontMetrics(QFont()).height() * 13.5)
469
470
471def minFileSystemViewHeight() -> int:
472    """
473    Minimum height of file system views on left and right side of main window.
474
475    Derived from standard font size.
476
477    :return: size in pixels
478    """
479
480    return QFontMetrics(QFont()).height() * 7
481
482
483def minGridColumnWidth() -> int:
484    return int(QFontMetrics(QFont()).height() * 1.3333333333333333)
485
486
487def standardProgressBarWidth() -> int:
488    return int(QFontMetrics(QFont()).height() * 20)
489
490
491# Be sure to update gvfs_controls_mounts() if updating this
492class Desktop(Enum):
493    gnome = 1
494    unity = 2
495    cinnamon = 3
496    kde = 4
497    xfce = 5
498    mate = 6
499    lxde = 7
500    lxqt = 8
501    ubuntugnome = 9
502    popgnome = 10
503    deepin = 11
504    zorin = 12
505    ukui = 13
506    pantheon = 14
507    unknown = 15
508
509
510class FileManagerType(Enum):
511    regular = 1
512    select = 2
513    dir_only_uri = 3
514    show_item = 4
515    show_items = 5
516
517
518FileManagerBehavior = dict(
519    nautilus=FileManagerType.select,
520    dolphin=FileManagerType.select,
521    caja=FileManagerType.dir_only_uri,
522    thunar=FileManagerType.dir_only_uri,
523    nemo=FileManagerType.regular,
524    pcmanfm=FileManagerType.dir_only_uri,
525    peony=FileManagerType.show_items,
526)
527FileManagerBehavior['pcmanfm-qt'] = FileManagerType.dir_only_uri
528FileManagerBehavior['dde-file-manager'] = FileManagerType.show_item
529FileManagerBehavior['io.elementary.files'] = FileManagerType.regular
530
531
532DefaultFileBrowserFallback = dict(
533    gnome='nautilus',
534    ubuntugnome='nautilus',
535    popgnome='nautilus',
536    unity='nautilus',
537    kde='dolphin',
538    cinnamon='nemo',
539    mate='caja',
540    xfce='thunar',
541    lxde='pcmanfm',
542    lxqt='pcmanfm-qt',
543    deepin='dde-file-manager',
544    kylin='peony',
545    pantheon='io.elementary.files',
546)
547
548
549# Sync with value in install.py
550class Distro(Enum):
551    debian = 1
552    ubuntu = 2
553    fedora = 3
554    neon = 4
555    linuxmint = 5
556    zorin = 6
557    arch = 7
558    opensuse = 8
559    manjaro = 9
560    galliumos = 10
561    peppermint = 11
562    elementary = 13
563    centos = 14
564    centos7 = 15
565    gentoo = 16
566    deepin = 17
567    kylin = 18
568    popos = 19
569    unknown = 20
570
571
572orientation_offset = dict(
573    arw=106,
574    cr2=126,
575    cr3=60000,  # assuming ExifTool (exiv2 >= 0.28 required for CR3)
576    dcr=7684,
577    dng=144,
578    mef=144,
579    mrw=152580,
580    nef=144,
581    nrw=94,
582    orf=132,
583    pef=118,
584    raf=208,
585    raw=742404,
586    rw2=1004548,
587    sr2=82,
588    srw=46
589)
590orientation_offset['3fr'] = 132
591
592orientation_offset_exiftool = dict(
593    arw=350,
594    cr2=320,
595    cr3=60000,
596    crw=20,
597    dcr=8196,
598    dng=644,
599    iiq=20,
600    mef=376,
601    mrw=152580,
602    nef=392,
603    nrw=94,
604    orf=6148,
605    pef=332,
606    raf=70660,
607    raw=548,
608    rw2=709636,
609    sr2=276,
610    srw=126
611)
612orientation_offset_exiftool['3fr'] = 376
613
614datetime_offset = dict(
615    arw=1540,
616    cr2=1028,
617    cr3=60000,  # assuming ExifTool
618    dng=119812,
619    mef=772,
620    mrw=152580,
621    nef=14340,
622    nrw=1540,
623    orf=6660,
624    pef=836,
625    raf=1796,
626    raw=964,
627    rw2=3844,
628    sr2=836,
629    srw=508,
630    mts=5000,
631    m2t=5000,
632    m2ts=5000,
633    mp4=50000,
634    avi=50000,
635    mov=250000,
636)
637datetime_offset['3fr'] = 1540
638datetime_offset['3gp'] = 5000
639
640datetime_offset_exiftool = dict(
641    arw=1540,
642    cr2=1000,  # varies widely :-/
643    cr3=60000,
644    crw=20,
645    dng=3000,  # varies widely :-/
646    mef=772,
647    mrw=152580,
648    nef=13316,
649    nrw=488,
650    orf=7172,
651    pef=836,
652    raf=70660,
653    raw=932,
654    rw2=709636,
655    sr2=836,
656    srw=496,
657    x3f=69220070,
658    mts=5000,
659    m2t=5000,
660    m2ts=5000,
661    mp4=50000,
662    avi=50000,
663    mov=250000,
664)
665datetime_offset_exiftool['3fr'] = 1042
666datetime_offset_exiftool['3gp'] = 5000
667
668all_tags_offset = dict(
669    arw=1848,
670    cr2=94622,
671    cr3=60000,  # assuming ExifTool
672    dng=143774,
673    mef=965,
674    mrw=183096,
675    nef=1126814,
676    nrw=1848,
677    orf=812242,
678    pef=1042,
679    raf=13522,
680    raw=890885,
681    rw2=1205458,
682    sr2=1080,
683    srw=614,
684)
685all_tags_offset['3fr'] = 1848
686
687all_tags_offset_exiftool = dict(
688    arw=1540,
689    cr2=104453,
690    cr3=60000,
691    dng=143774,
692    dcr=10450,
693    mef=965,
694    mrw=183096,
695    nef=77213623,
696    nrw=1848,
697    orf=29113613,
698    pef=183096,
699    raf=84792,
700    raw=890885,
701    rw2=1205458,
702    sr2=1080,
703    srw=222418,
704    x3f=7380128,
705    mp4=130000,
706    mts=1300000,
707    mt2=1300000,
708    m2ts=1300000,
709    avi=50000,
710    mov=250000
711)
712all_tags_offset_exiftool['3fr'] = 1042
713
714thumbnail_offset = dict(
715    jpg=100000,
716    jpeg=100000,
717    dng=100000,
718    avi=500000,
719    mod=500000,
720    mov=2000000,
721    mp4=2000000,
722    mts=600000,
723    m2t=600000,
724    mpg=500000,
725    mpeg=500000,
726    tod=500000,
727)
728
729# Repeat video information here
730thumbnail_offset_exiftool = dict(
731    cr2=694277,
732    cr3=45470,
733    mrw=84792,
734    nef=77213623,
735    nrw=45470,
736    raf=84792,
737    raw=890885,
738    rw2=1205458,
739    sr2=222418,
740    srw=812242,
741    avi=500000,
742    mod=500000,
743    mov=2000000,
744    mp4=2000000,
745    mts=600000,
746    m2t=600000,
747    mpg=500000,
748    mpeg=500000,
749    tod=500000,
750)
751
752
753
754class RememberThisMessage(Enum):
755    remember_choice = 1
756    do_not_ask_again = 2
757    do_not_warn_again = 3
758    do_not_warn_again_about_missing_libraries = 4
759
760
761class RememberThisButtons(Enum):
762    yes_no = 1
763    ok = 2
764
765
766class CompletedDownloads(IntEnum):
767    keep = 1
768    clear = 2
769    prompt = 3
770
771
772class TreatRawJpeg(IntEnum):
773    one_photo = 1
774    two_photos = 2
775
776
777class MarkRawJpeg(IntEnum):
778    no_jpeg = 1
779    no_raw = 2
780    both = 3
781
782
783# see https://developer.mozilla.org/en-US/docs/Mozilla/Localization/Localization_and_Plurals
784class Plural(Enum):
785    zero = 1
786    two_form_single = 2
787    two_form_plural = 3
788
789
790class ScalingAction(Enum):
791    turned_on = 1
792    not_set = 2
793    already_set = 3
794
795
796class ScalingDetected(Enum):
797    Qt = 1
798    Xsetting = 2
799    Qt_and_Xsetting = 3
800    undetected = 4
801
802
803# Use the character . to for download_name and path to indicate the user manually marked a
804# file as previously downloaded
805manually_marked_previously_downloaded = '.'
806
807