diff --git a/CHANGELOG.md b/CHANGELOG.md index ec0d2c85..863dce6f 100644 --- a/CHANGELOG.md +++ b/CHANGELOG.md @@ -4,6 +4,39 @@ All notable changes to this project will be documented in this file. Dates are d Generated by [`auto-changelog`](https://github.com/CookPete/auto-changelog). +#### [v0.45.8](https://github.com/RhetTbull/osxphotos/compare/v0.45.6...v0.45.8) + +> 5 February 2022 + +- Fixed exiftool to ignore unsupported file types, #615 [`1ae6270`](https://github.com/RhetTbull/osxphotos/commit/1ae627056113fc4655f1b24cfbbdf0efc04489e7) +- Updated tests [`55a601c`](https://github.com/RhetTbull/osxphotos/commit/55a601c07ea1384623c55d5c1d26b568df5d7823) +- Additional fix for #615 [`1d6bc4e`](https://github.com/RhetTbull/osxphotos/commit/1d6bc4e09e3c2359a21f842fadd781920606812e) + +#### [v0.45.6](https://github.com/RhetTbull/osxphotos/compare/v0.45.5...v0.45.6) + +> 5 February 2022 + +- Fix for unicode in query strings, #618 [`9b247ac`](https://github.com/RhetTbull/osxphotos/commit/9b247acd1cc4b2def59fdd18a6fb3c8eb9914f11) +- Fix for --name searching only original_filename on Photos 5+, #594 [`cd02144`](https://github.com/RhetTbull/osxphotos/commit/cd02144ac33cc1c13a20358133971c84d35b8a57) + +#### [v0.45.5](https://github.com/RhetTbull/osxphotos/compare/v0.45.4...v0.45.5) + +> 5 February 2022 + +- Fix for #561, no really, I mean it this time [`b3d3e14`](https://github.com/RhetTbull/osxphotos/commit/b3d3e14ffe41fbb22edb614b24f3985f379766a2) +- Updated docs [skip ci] [`2b9ea11`](https://github.com/RhetTbull/osxphotos/commit/2b9ea11701799af9a661a8e2af70fca97235f487) +- Updated tests for #561 [skip ci] [`77a49a0`](https://github.com/RhetTbull/osxphotos/commit/77a49a09a1bee74113a7114c543fbc25fa410ffc) + +#### [v0.45.4](https://github.com/RhetTbull/osxphotos/compare/v0.45.3...v0.45.4) + +> 3 February 2022 + +- docs: add oPromessa as a contributor for ideas, test [`#611`](https://github.com/RhetTbull/osxphotos/pull/611) +- Fix for filenames with special characters, #561, #618 [`f3063d3`](https://github.com/RhetTbull/osxphotos/commit/f3063d35be3c96342d83dbd87ddd614a2001bff4) +- Updated docs [skip ci] [`06c5bbf`](https://github.com/RhetTbull/osxphotos/commit/06c5bbfcfdf591a4a5d43f1456adaa27385fe01a) +- Added progress counter, #601 [`7ab5007`](https://github.com/RhetTbull/osxphotos/commit/7ab500740b28594dcd778140e10991f839220e9d) +- Updated known issues [skip ci] [`e32090b`](https://github.com/RhetTbull/osxphotos/commit/e32090bf39cb786171b49443f878ffdbab774420) + #### [v0.45.3](https://github.com/RhetTbull/osxphotos/compare/v0.45.2...v0.45.3) > 29 January 2022 diff --git a/MANIFEST.in b/MANIFEST.in index 6d966c82..6f9539f0 100644 --- a/MANIFEST.in +++ b/MANIFEST.in @@ -1,7 +1,7 @@ -include README.md -include README.rst -include osxphotos/templates/* +include osxphotos/*.json +include osxphotos/*.md include osxphotos/phototemplate.tx -include osxphotos/phototemplate.md -include osxphotos/tutorial.md -include osxphotos/queries/* \ No newline at end of file +include osxphotos/queries/* +include osxphotos/templates/* +include README.md +include README.rst \ No newline at end of file diff --git a/README.md b/README.md index 44ba15ce..401d9918 100644 --- a/README.md +++ b/README.md @@ -1728,7 +1728,7 @@ Substitution Description {lf} A line feed: '\n', alias for {newline} {cr} A carriage return: '\r' {crlf} a carriage return + line feed: '\r\n' -{osxphotos_version} The osxphotos version, e.g. '0.45.3' +{osxphotos_version} The osxphotos version, e.g. '0.45.8' {osxphotos_cmd_line} The full command line used to run osxphotos The following substitutions may result in multiple values. Thus if specified for @@ -3632,7 +3632,7 @@ The following template field substitutions are availabe for use the templating s |{lf}|A line feed: '\n', alias for {newline}| |{cr}|A carriage return: '\r'| |{crlf}|a carriage return + line feed: '\r\n'| -|{osxphotos_version}|The osxphotos version, e.g. '0.45.3'| +|{osxphotos_version}|The osxphotos version, e.g. '0.45.8'| |{osxphotos_cmd_line}|The full command line used to run osxphotos| |{album}|Album(s) photo is contained in| |{folder_album}|Folder path + album photo is contained in. e.g. 'Folder/Subfolder/Album' or just 'Album' if no enclosing folder| @@ -3977,7 +3977,6 @@ My goal is make osxphotos as reliable and comprehensive as possible. The test s - Audio-only files are not handled. It is possible to store audio-only files in Photos. osxphotos currently only handles images and videos. See [Issue #436](https://github.com/RhetTbull/osxphotos/issues/436) - Face coordinates (mouth, left eye, right eye) may not be correct for images where the head is tilted. See [Issue #196](https://github.com/RhetTbull/osxphotos/issues/196). -- Raw images imported to Photos with an associated jpeg preview are not handled correctly by osxphotos. osxphotos query and export will operate on the jpeg preview instead of the raw image as will `PhotoInfo.path`. If the user selects "Use RAW as original" in Photos, the raw image will be exported or operated on but the jpeg will be ignored. See [Issue #101](https://github.com/RhetTbull/osxphotos/issues/101). Note: Beta version of fix for this bug is implemented in the current version of osxphotos. - The `--download-missing` option for `osxphotos export` does not work correctly with burst images. It will download the primary image but not the other burst images. See [Issue #75](https://github.com/RhetTbull/osxphotos/issues/75). ## Implementation Notes diff --git a/docs/.buildinfo b/docs/.buildinfo index dadd96be..9defd753 100644 --- a/docs/.buildinfo +++ b/docs/.buildinfo @@ -1,4 +1,4 @@ # Sphinx build info version 1 # This file hashes the configuration used when building these files. When it is not found, a full rebuild will be done. -config: cf6ba426eed8ae7fdcc87f4fab23a946 +config: bf43bf49b725c31ce72a8823e4f8012b tags: 645f666f9bcd5a90fca523b33c5a78b7 diff --git a/docs/_static/documentation_options.js b/docs/_static/documentation_options.js index 9f54e845..0896a973 100644 --- a/docs/_static/documentation_options.js +++ b/docs/_static/documentation_options.js @@ -1,6 +1,6 @@ var DOCUMENTATION_OPTIONS = { URL_ROOT: document.getElementById("documentation_options").getAttribute('data-url_root'), - VERSION: '0.45.3', + VERSION: '0.45.8', LANGUAGE: 'None', COLLAPSE_INDEX: false, BUILDER: 'html', diff --git a/docs/cli.html b/docs/cli.html index 1aa73835..1dc80919 100644 --- a/docs/cli.html +++ b/docs/cli.html @@ -6,7 +6,7 @@ - osxphotos command line interface (CLI) — osxphotos 0.45.3 documentation + osxphotos command line interface (CLI) — osxphotos 0.45.8 documentation diff --git a/docs/genindex.html b/docs/genindex.html index cd17ee1e..0d697e09 100644 --- a/docs/genindex.html +++ b/docs/genindex.html @@ -5,7 +5,7 @@ - Index — osxphotos 0.45.3 documentation + Index — osxphotos 0.45.8 documentation diff --git a/docs/index.html b/docs/index.html index 0ab1025c..f1d4cf16 100644 --- a/docs/index.html +++ b/docs/index.html @@ -6,7 +6,7 @@ - Welcome to osxphotos’s documentation! — osxphotos 0.45.3 documentation + Welcome to osxphotos’s documentation! — osxphotos 0.45.8 documentation diff --git a/docs/modules.html b/docs/modules.html index e1ccdbd4..dd54b250 100644 --- a/docs/modules.html +++ b/docs/modules.html @@ -6,7 +6,7 @@ - osxphotos — osxphotos 0.45.3 documentation + osxphotos — osxphotos 0.45.8 documentation diff --git a/docs/reference.html b/docs/reference.html index d0c1edba..20928185 100644 --- a/docs/reference.html +++ b/docs/reference.html @@ -6,7 +6,7 @@ - osxphotos package — osxphotos 0.45.3 documentation + osxphotos package — osxphotos 0.45.8 documentation diff --git a/docs/search.html b/docs/search.html index f1cc8aad..7fa67509 100644 --- a/docs/search.html +++ b/docs/search.html @@ -5,7 +5,7 @@ - Search — osxphotos 0.45.3 documentation + Search — osxphotos 0.45.8 documentation diff --git a/osxphotos.spec b/osxphotos.spec index caa32801..9a3dd9dd 100644 --- a/osxphotos.spec +++ b/osxphotos.spec @@ -14,6 +14,7 @@ datas = [ ("osxphotos/phototemplate.tx", "osxphotos"), ("osxphotos/phototemplate.md", "osxphotos"), ("osxphotos/tutorial.md", "osxphotos"), + ("osxphotos/exiftool_filetypes.json", "osxphotos"), ] package_imports = [["photoscript", ["photoscript.applescript"]]] for package, files in package_imports: diff --git a/osxphotos/_constants.py b/osxphotos/_constants.py index 1c11afe2..eaad02d1 100644 --- a/osxphotos/_constants.py +++ b/osxphotos/_constants.py @@ -214,7 +214,8 @@ SEARCH_CATEGORY_PHOTO_NAME = 2056 # Max filename length on MacOS -MAX_FILENAME_LEN = 255 +# subtract 6 chars for the lock file extension in form: ".filename.lock" +MAX_FILENAME_LEN = 255 - 6 # Max directory name length on MacOS MAX_DIRNAME_LEN = 255 diff --git a/osxphotos/_version.py b/osxphotos/_version.py index 2ba5f075..267ed7ea 100644 --- a/osxphotos/_version.py +++ b/osxphotos/_version.py @@ -1,3 +1,3 @@ """ version info """ -__version__ = "0.45.3" +__version__ = "0.45.8" diff --git a/osxphotos/exiftool.py b/osxphotos/exiftool.py index 61f83c3f..41be1f33 100644 --- a/osxphotos/exiftool.py +++ b/osxphotos/exiftool.py @@ -11,6 +11,7 @@ import html import json import logging import os +import pathlib import re import shutil import subprocess @@ -19,11 +20,12 @@ from functools import lru_cache # pylint: disable=syntax-error __all__ = [ "escape_str", - "unescape_str", - "terminate_exiftool", - "get_exiftool_path", + "exiftool_can_write", "ExifTool", "ExifToolCaching", + "get_exiftool_path", + "terminate_exiftool", + "unescape_str", ] # exiftool -stay_open commands outputs this EOF marker after command is run @@ -33,6 +35,24 @@ EXIFTOOL_STAYOPEN_EOF_LEN = len(EXIFTOOL_STAYOPEN_EOF) # list of exiftool processes to cleanup when exiting or when terminate is called EXIFTOOL_PROCESSES = [] +# exiftool supported file types, created by utils/exiftool_supported_types.py +EXIFTOOL_FILETYPES_JSON = "exiftool_filetypes.json" +with (pathlib.Path(__file__).parent / EXIFTOOL_FILETYPES_JSON).open("r") as f: + EXIFTOOL_SUPPORTED_FILETYPES = json.load(f) + + +def exiftool_can_write(suffix: str) -> bool: + """Return True if exiftool supports writing to a file with the given suffix, otherwise False""" + if not suffix: + return False + suffix = suffix.lower() + if suffix[0] == ".": + suffix = suffix[1:] + return ( + suffix in EXIFTOOL_SUPPORTED_FILETYPES + and EXIFTOOL_SUPPORTED_FILETYPES[suffix]["write"] + ) + def escape_str(s): """escape string for use with exiftool -E""" diff --git a/osxphotos/exiftool_filetypes.json b/osxphotos/exiftool_filetypes.json new file mode 100644 index 00000000..e7441182 --- /dev/null +++ b/osxphotos/exiftool_filetypes.json @@ -0,0 +1,4976 @@ +{ + "360": { + "extension": "360", + "file type": [ + "360" + ], + "support": "R/W", + "description": "GoPro 360 video (QuickTime-based)", + "exif": "R/W", + "iptc": "R/W", + "xmp": "R/W/C", + "icc": null, + "other": "R/W/C QuickTime, R GoPro", + "read": true, + "write": true, + "create": false + }, + "3fr": { + "extension": "3fr", + "file type": [ + "3FR" + ], + "support": "R", + "description": "Hasselblad RAW (TIFF-based)", + "exif": "R", + "iptc": "R", + "xmp": "R", + "icc": "R", + "other": null, + "read": true, + "write": false, + "create": false + }, + "3g2": { + "extension": "3g2", + "file type": [ + "3G2", + "3GP2" + ], + "support": "R/W", + "description": "3rd Gen. Partnership Project 2 a/v (QuickTime-based)", + "exif": "R/W", + "iptc": "R/W", + "xmp": "R/W/C", + "icc": null, + "other": "R/W/C QuickTime", + "read": true, + "write": true, + "create": false + }, + "3gp2": { + "extension": "3gp2", + "file type": [ + "3G2", + "3GP2" + ], + "support": "R/W", + "description": "3rd Gen. Partnership Project 2 a/v (QuickTime-based)", + "exif": "R/W", + "iptc": "R/W", + "xmp": "R/W/C", + "icc": null, + "other": "R/W/C QuickTime", + "read": true, + "write": true, + "create": false + }, + "3gp": { + "extension": "3gp", + "file type": [ + "3GP", + "3GPP" + ], + "support": "R/W", + "description": "3rd Gen. Partnership Project a/v (QuickTime-based)", + "exif": "R/W", + "iptc": "R/W", + "xmp": "R/W/C", + "icc": null, + "other": "R/W/C QuickTime", + "read": true, + "write": true, + "create": false + }, + "3gpp": { + "extension": "3gpp", + "file type": [ + "3GP", + "3GPP" + ], + "support": "R/W", + "description": "3rd Gen. Partnership Project a/v (QuickTime-based)", + "exif": "R/W", + "iptc": "R/W", + "xmp": "R/W/C", + "icc": null, + "other": "R/W/C QuickTime", + "read": true, + "write": true, + "create": false + }, + "a": { + "extension": "a", + "file type": [ + "A" + ], + "support": "R", + "description": "Unix static library code Archive", + "exif": null, + "iptc": null, + "xmp": null, + "icc": null, + "other": "R EXE", + "read": true, + "write": false, + "create": false + }, + "aa": { + "extension": "aa", + "file type": [ + "AA" + ], + "support": "R", + "description": "Audible Audiobook", + "exif": null, + "iptc": null, + "xmp": null, + "icc": null, + "other": "R Audible", + "read": true, + "write": false, + "create": false + }, + "aae": { + "extension": "aae", + "file type": [ + "AAE" + ], + "support": "R", + "description": "Apple edit information (XML PLIST-based)", + "exif": null, + "iptc": null, + "xmp": null, + "icc": null, + "other": "R PLIST", + "read": true, + "write": false, + "create": false + }, + "aax": { + "extension": "aax", + "file type": [ + "AAX" + ], + "support": "R/W", + "description": "Audible Enhanced Audiobook (QuickTime-based)", + "exif": "R/W", + "iptc": "R/W", + "xmp": "R/W/C", + "icc": null, + "other": "R/W/C QuickTime", + "read": true, + "write": true, + "create": false + }, + "acr": { + "extension": "acr", + "file type": [ + "ACR" + ], + "support": "R", + "description": "American College of Radiology ACR-NEMA (DICOM-like)", + "exif": null, + "iptc": null, + "xmp": null, + "icc": null, + "other": "R DICOM", + "read": true, + "write": false, + "create": false + }, + "afm": { + "extension": "afm", + "file type": [ + "AFM", + "ACFM", + "AMFM" + ], + "support": "R", + "description": "Adobe [Composite/Multiple Master] Font Metrics", + "exif": null, + "iptc": null, + "xmp": null, + "icc": null, + "other": "R Font", + "read": true, + "write": false, + "create": false + }, + "acfm": { + "extension": "acfm", + "file type": [ + "AFM", + "ACFM", + "AMFM" + ], + "support": "R", + "description": "Adobe [Composite/Multiple Master] Font Metrics", + "exif": null, + "iptc": null, + "xmp": null, + "icc": null, + "other": "R Font", + "read": true, + "write": false, + "create": false + }, + "amfm": { + "extension": "amfm", + "file type": [ + "AFM", + "ACFM", + "AMFM" + ], + "support": "R", + "description": "Adobe [Composite/Multiple Master] Font Metrics", + "exif": null, + "iptc": null, + "xmp": null, + "icc": null, + "other": "R Font", + "read": true, + "write": false, + "create": false + }, + "ai": { + "extension": "ai", + "file type": [ + "AI", + "AIT" + ], + "support": "R/W", + "description": "Adobe Illustrator [Template] (PS or PDF)", + "exif": "R/W/C", + "iptc": "R/W/C", + "xmp": "R/W/C", + "icc": "R/W/C", + "other": "R/W/C PDF PostScript, R Photoshop", + "read": true, + "write": true, + "create": false + }, + "ait": { + "extension": "ait", + "file type": [ + "AI", + "AIT" + ], + "support": "R/W", + "description": "Adobe Illustrator [Template] (PS or PDF)", + "exif": "R/W/C", + "iptc": "R/W/C", + "xmp": "R/W/C", + "icc": "R/W/C", + "other": "R/W/C PDF PostScript, R Photoshop", + "read": true, + "write": true, + "create": false + }, + "aiff": { + "extension": "aiff", + "file type": [ + "AIFF", + "AIF", + "AIFC" + ], + "support": "R", + "description": "Audio Interchange File Format [Compressed]", + "exif": null, + "iptc": null, + "xmp": null, + "icc": null, + "other": "R AIFF ID3 Lyrics3", + "read": true, + "write": false, + "create": false + }, + "aif": { + "extension": "aif", + "file type": [ + "AIFF", + "AIF", + "AIFC" + ], + "support": "R", + "description": "Audio Interchange File Format [Compressed]", + "exif": null, + "iptc": null, + "xmp": null, + "icc": null, + "other": "R AIFF ID3 Lyrics3", + "read": true, + "write": false, + "create": false + }, + "aifc": { + "extension": "aifc", + "file type": [ + "AIFF", + "AIF", + "AIFC" + ], + "support": "R", + "description": "Audio Interchange File Format [Compressed]", + "exif": null, + "iptc": null, + "xmp": null, + "icc": null, + "other": "R AIFF ID3 Lyrics3", + "read": true, + "write": false, + "create": false + }, + "ape": { + "extension": "ape", + "file type": [ + "APE" + ], + "support": "R", + "description": "Monkey's Audio", + "exif": null, + "iptc": null, + "xmp": null, + "icc": null, + "other": "R APE ID3 Lyrics3", + "read": true, + "write": false, + "create": false + }, + "arq": { + "extension": "arq", + "file type": [ + "ARQ" + ], + "support": "R/W", + "description": "Sony Alpha Pixel-Shift RAW (TIFF-based)", + "exif": "R/W/C", + "iptc": "R/W/C", + "xmp": "R/W/C", + "icc": "R/W/C", + "other": "R/W Sony SonyIDC", + "read": true, + "write": true, + "create": false + }, + "arw": { + "extension": "arw", + "file type": [ + "ARW" + ], + "support": "R/W", + "description": "Sony Alpha RAW (TIFF-based)", + "exif": "R/W/C", + "iptc": "R/W/C", + "xmp": "R/W/C", + "icc": "R/W/C", + "other": "R/W Sony SonyIDC", + "read": true, + "write": true, + "create": false + }, + "asf": { + "extension": "asf", + "file type": [ + "ASF" + ], + "support": "R", + "description": "Microsoft Advanced Systems Format", + "exif": null, + "iptc": null, + "xmp": "R", + "icc": null, + "other": "R ASF", + "read": true, + "write": false, + "create": false + }, + "avi": { + "extension": "avi", + "file type": [ + "AVI" + ], + "support": "R", + "description": "Audio Video Interleaved (RIFF-based)", + "exif": "R", + "iptc": null, + "xmp": "R", + "icc": null, + "other": "R RIFF", + "read": true, + "write": false, + "create": false + }, + "avif": { + "extension": "avif", + "file type": [ + "AVIF" + ], + "support": "R/W", + "description": "AV1 Image File Format (QuickTime-based)", + "exif": "R/W/C", + "iptc": null, + "xmp": "R/W/C", + "icc": "R/W", + "other": "R/W QuickTime", + "read": true, + "write": true, + "create": false + }, + "bmp": { + "extension": "bmp", + "file type": [ + "BMP", + "DIB" + ], + "support": "R", + "description": "Windows BitMaP / Device Independent Bitmap", + "exif": null, + "iptc": null, + "xmp": null, + "icc": null, + "other": "R BMP", + "read": true, + "write": false, + "create": false + }, + "dib": { + "extension": "dib", + "file type": [ + "BMP", + "DIB" + ], + "support": "R", + "description": "Windows BitMaP / Device Independent Bitmap", + "exif": null, + "iptc": null, + "xmp": null, + "icc": null, + "other": "R BMP", + "read": true, + "write": false, + "create": false + }, + "bpg": { + "extension": "bpg", + "file type": [ + "BPG" + ], + "support": "R", + "description": "Better Portable Graphics", + "exif": "R", + "iptc": null, + "xmp": "R", + "icc": "R", + "other": "R BPG", + "read": true, + "write": false, + "create": false + }, + "btf": { + "extension": "btf", + "file type": [ + "BTF" + ], + "support": "R", + "description": "BigTIFF (64-bit Tagged Image File Format)", + "exif": "R", + "iptc": "R", + "xmp": "R", + "icc": "R", + "other": null, + "read": true, + "write": false, + "create": false + }, + "chm": { + "extension": "chm", + "file type": [ + "CHM" + ], + "support": "R", + "description": "Microsoft Compiled HTML format", + "exif": null, + "iptc": null, + "xmp": null, + "icc": null, + "other": "R EXE", + "read": true, + "write": false, + "create": false + }, + "cos": { + "extension": "cos", + "file type": [ + "COS" + ], + "support": "R", + "description": "Capture One Settings (XML-based)", + "exif": null, + "iptc": null, + "xmp": null, + "icc": null, + "other": "R XML", + "read": true, + "write": false, + "create": false + }, + "cr2": { + "extension": "cr2", + "file type": [ + "CR2" + ], + "support": "R/W", + "description": "Canon RAW 2 (TIFF-based) (CR2 spec)", + "exif": "R/W/C", + "iptc": "R/W/C", + "xmp": "R/W/C", + "icc": "R/W/C", + "other": "R/W Canon, R/W/C CanonVRD", + "read": true, + "write": true, + "create": false + }, + "cr3": { + "extension": "cr3", + "file type": [ + "CR3" + ], + "support": "R/W", + "description": "Canon RAW 3 (QuickTime-based) (CR3 spec)", + "exif": "R/W/C", + "iptc": null, + "xmp": "R/W/C", + "icc": null, + "other": "R/W Canon QuickTime, R/W/C CanonVRD", + "read": true, + "write": true, + "create": false + }, + "crm": { + "extension": "crm", + "file type": [ + "CRM" + ], + "support": "R/W", + "description": "Canon RAW Movie (QuickTime-based)", + "exif": "R/W/C", + "iptc": null, + "xmp": "R/W/C", + "icc": null, + "other": "R/W Canon QuickTime", + "read": true, + "write": true, + "create": false + }, + "crw": { + "extension": "crw", + "file type": [ + "CRW", + "CIFF" + ], + "support": "R/W", + "description": "Canon RAW Camera Image File Format (CRW spec)", + "exif": null, + "iptc": null, + "xmp": "R/W/C", + "icc": null, + "other": "R/W CanonRaw, R/W/C CanonVRD", + "read": true, + "write": true, + "create": false + }, + "ciff": { + "extension": "ciff", + "file type": [ + "CRW", + "CIFF" + ], + "support": "R/W", + "description": "Canon RAW Camera Image File Format (CRW spec)", + "exif": null, + "iptc": null, + "xmp": "R/W/C", + "icc": null, + "other": "R/W CanonRaw, R/W/C CanonVRD", + "read": true, + "write": true, + "create": false + }, + "cs1": { + "extension": "cs1", + "file type": [ + "CS1" + ], + "support": "R/W", + "description": "Sinar CaptureShop 1-shot RAW (PSD-based)", + "exif": "R/W/C", + "iptc": "R/W/C", + "xmp": "R/W/C", + "icc": "R/W/C", + "other": "R Photoshop", + "read": true, + "write": true, + "create": false + }, + "csv": { + "extension": "csv", + "file type": [ + "CSV" + ], + "support": "R", + "description": "Comma-Separated Values", + "exif": null, + "iptc": null, + "xmp": null, + "icc": null, + "other": "R Text", + "read": true, + "write": false, + "create": false + }, + "czi": { + "extension": "czi", + "file type": [ + "CZI" + ], + "support": "R", + "description": "Zeiss Integrated Software RAW (ZISRAW)", + "exif": null, + "iptc": null, + "xmp": null, + "icc": null, + "other": "R ZISRAW, R XML", + "read": true, + "write": false, + "create": false + }, + "dcm": { + "extension": "dcm", + "file type": [ + "DCM", + "DC3", + "DIC", + "DICM" + ], + "support": "R", + "description": "DICOM - Digital Imaging and Communications in Medicine", + "exif": null, + "iptc": null, + "xmp": null, + "icc": null, + "other": "R DICOM", + "read": true, + "write": false, + "create": false + }, + "dc3": { + "extension": "dc3", + "file type": [ + "DCM", + "DC3", + "DIC", + "DICM" + ], + "support": "R", + "description": "DICOM - Digital Imaging and Communications in Medicine", + "exif": null, + "iptc": null, + "xmp": null, + "icc": null, + "other": "R DICOM", + "read": true, + "write": false, + "create": false + }, + "dic": { + "extension": "dic", + "file type": [ + "DCM", + "DC3", + "DIC", + "DICM" + ], + "support": "R", + "description": "DICOM - Digital Imaging and Communications in Medicine", + "exif": null, + "iptc": null, + "xmp": null, + "icc": null, + "other": "R DICOM", + "read": true, + "write": false, + "create": false + }, + "dicm": { + "extension": "dicm", + "file type": [ + "DCM", + "DC3", + "DIC", + "DICM" + ], + "support": "R", + "description": "DICOM - Digital Imaging and Communications in Medicine", + "exif": null, + "iptc": null, + "xmp": null, + "icc": null, + "other": "R DICOM", + "read": true, + "write": false, + "create": false + }, + "dcp": { + "extension": "dcp", + "file type": [ + "DCP" + ], + "support": "R/W", + "description": "DNG Camera Profile (DNG-like)", + "exif": "R/W/C", + "iptc": "R/W/C", + "xmp": "R/W/C", + "icc": "R/W/C", + "other": null, + "read": true, + "write": true, + "create": false + }, + "dcr": { + "extension": "dcr", + "file type": [ + "DCR" + ], + "support": "R", + "description": "Kodak Digital Camera RAW (TIFF-based)", + "exif": "R", + "iptc": "R", + "xmp": "R", + "icc": "R", + "other": null, + "read": true, + "write": false, + "create": false + }, + "dfont": { + "extension": "dfont", + "file type": [ + "DFONT" + ], + "support": "R", + "description": "Macintosh Data Fork Font", + "exif": null, + "iptc": null, + "xmp": null, + "icc": null, + "other": "R Font", + "read": true, + "write": false, + "create": false + }, + "divx": { + "extension": "divx", + "file type": [ + "DIVX" + ], + "support": "R", + "description": "DivX media format (ASF-based)", + "exif": null, + "iptc": null, + "xmp": "R", + "icc": null, + "other": "R ASF", + "read": true, + "write": false, + "create": false + }, + "djvu": { + "extension": "djvu", + "file type": [ + "DJVU", + "DJV" + ], + "support": "R", + "description": "DjVu image (AIFF-like)", + "exif": null, + "iptc": null, + "xmp": "R", + "icc": null, + "other": "R DJVU", + "read": true, + "write": false, + "create": false + }, + "djv": { + "extension": "djv", + "file type": [ + "DJVU", + "DJV" + ], + "support": "R", + "description": "DjVu image (AIFF-like)", + "exif": null, + "iptc": null, + "xmp": "R", + "icc": null, + "other": "R DJVU", + "read": true, + "write": false, + "create": false + }, + "dng": { + "extension": "dng", + "file type": [ + "DNG" + ], + "support": "R/W", + "description": "Digital Negative (TIFF-based)", + "exif": "R/W/C", + "iptc": "R/W/C", + "xmp": "R/W/C", + "icc": "R/W/C", + "other": null, + "read": true, + "write": true, + "create": false + }, + "doc": { + "extension": "doc", + "file type": [ + "DOC", + "DOT" + ], + "support": "R", + "description": "Microsoft Word Document/Template (FPX-like)", + "exif": null, + "iptc": null, + "xmp": "R", + "icc": "R", + "other": "R FlashPix", + "read": true, + "write": false, + "create": false + }, + "dot": { + "extension": "dot", + "file type": [ + "DOC", + "DOT" + ], + "support": "R", + "description": "Microsoft Word Document/Template (FPX-like)", + "exif": null, + "iptc": null, + "xmp": "R", + "icc": "R", + "other": "R FlashPix", + "read": true, + "write": false, + "create": false + }, + "docx": { + "extension": "docx", + "file type": [ + "DOCX", + "DOCM" + ], + "support": "R", + "description": "Office Open XML Document [Macro-enabled]", + "exif": null, + "iptc": null, + "xmp": null, + "icc": null, + "other": "R XML ZIP", + "read": true, + "write": false, + "create": false + }, + "docm": { + "extension": "docm", + "file type": [ + "DOCX", + "DOCM" + ], + "support": "R", + "description": "Office Open XML Document [Macro-enabled]", + "exif": null, + "iptc": null, + "xmp": null, + "icc": null, + "other": "R XML ZIP", + "read": true, + "write": false, + "create": false + }, + "dotx": { + "extension": "dotx", + "file type": [ + "DOTX", + "DOTM" + ], + "support": "R", + "description": "Office Open XML Document Template [Macro-enabled]", + "exif": null, + "iptc": null, + "xmp": null, + "icc": null, + "other": "R XML ZIP", + "read": true, + "write": false, + "create": false + }, + "dotm": { + "extension": "dotm", + "file type": [ + "DOTX", + "DOTM" + ], + "support": "R", + "description": "Office Open XML Document Template [Macro-enabled]", + "exif": null, + "iptc": null, + "xmp": null, + "icc": null, + "other": "R XML ZIP", + "read": true, + "write": false, + "create": false + }, + "dpx": { + "extension": "dpx", + "file type": [ + "DPX" + ], + "support": "R", + "description": "Digital Picture Exchange", + "exif": null, + "iptc": null, + "xmp": null, + "icc": null, + "other": "R DPX", + "read": true, + "write": false, + "create": false + }, + "dr4": { + "extension": "dr4", + "file type": [ + "DR4" + ], + "support": "R/W/C", + "description": "Canon DPP version 4 Recipe", + "exif": null, + "iptc": null, + "xmp": null, + "icc": null, + "other": "R/W/C CanonVRD", + "read": true, + "write": true, + "create": true + }, + "dss": { + "extension": "dss", + "file type": [ + "DSS", + "DS2" + ], + "support": "R", + "description": "Digital Speech Standard [2]", + "exif": null, + "iptc": null, + "xmp": null, + "icc": null, + "other": "R Olympus", + "read": true, + "write": false, + "create": false + }, + "ds2": { + "extension": "ds2", + "file type": [ + "DSS", + "DS2" + ], + "support": "R", + "description": "Digital Speech Standard [2]", + "exif": null, + "iptc": null, + "xmp": null, + "icc": null, + "other": "R Olympus", + "read": true, + "write": false, + "create": false + }, + "dylib": { + "extension": "dylib", + "file type": [ + "DYLIB" + ], + "support": "R", + "description": "MacOS Mach-O executable and library files", + "exif": null, + "iptc": null, + "xmp": null, + "icc": null, + "other": "R EXE", + "read": true, + "write": false, + "create": false + }, + "dv": { + "extension": "dv", + "file type": [ + "DV" + ], + "support": "R", + "description": "Digital Video", + "exif": null, + "iptc": null, + "xmp": null, + "icc": null, + "other": "R DV", + "read": true, + "write": false, + "create": false + }, + "dvb": { + "extension": "dvb", + "file type": [ + "DVB" + ], + "support": "R/W", + "description": "Digital Video Broadcasting (QuickTime-based)", + "exif": "R/W", + "iptc": "R/W", + "xmp": "R/W/C", + "icc": null, + "other": "R/W/C QuickTime", + "read": true, + "write": true, + "create": false + }, + "dvr-ms": { + "extension": "dvr-ms", + "file type": [ + "DVR-MS" + ], + "support": "R", + "description": "Microsoft Digital Video Recording (ASF-based)", + "exif": null, + "iptc": null, + "xmp": "R", + "icc": null, + "other": "R ASF", + "read": true, + "write": false, + "create": false + }, + "eip": { + "extension": "eip", + "file type": [ + "EIP" + ], + "support": "R", + "description": "Capture One Enhanced Image Package (ZIP-based)", + "exif": "R", + "iptc": null, + "xmp": null, + "icc": null, + "other": "R XML ZIP", + "read": true, + "write": false, + "create": false + }, + "eps": { + "extension": "eps", + "file type": [ + "EPS", + "EPSF", + "PS" + ], + "support": "R/W", + "description": "[Encapsulated] PostScript Format", + "exif": "R/W/C", + "iptc": "R/W/C", + "xmp": "R/W/C", + "icc": "R/W/C", + "other": "R/W/C PostScript, R Photoshop", + "read": true, + "write": true, + "create": false + }, + "epsf": { + "extension": "epsf", + "file type": [ + "EPS", + "EPSF", + "PS" + ], + "support": "R/W", + "description": "[Encapsulated] PostScript Format", + "exif": "R/W/C", + "iptc": "R/W/C", + "xmp": "R/W/C", + "icc": "R/W/C", + "other": "R/W/C PostScript, R Photoshop", + "read": true, + "write": true, + "create": false + }, + "ps": { + "extension": "ps", + "file type": [ + "EPS", + "EPSF", + "PS" + ], + "support": "R/W", + "description": "[Encapsulated] PostScript Format", + "exif": "R/W/C", + "iptc": "R/W/C", + "xmp": "R/W/C", + "icc": "R/W/C", + "other": "R/W/C PostScript, R Photoshop", + "read": true, + "write": true, + "create": false + }, + "epub": { + "extension": "epub", + "file type": [ + "EPUB" + ], + "support": "R", + "description": "Electronic Publication (ZIP/XML-based)", + "exif": null, + "iptc": null, + "xmp": null, + "icc": null, + "other": "R XML ZIP", + "read": true, + "write": false, + "create": false + }, + "erf": { + "extension": "erf", + "file type": [ + "ERF" + ], + "support": "R/W", + "description": "Epson RAW Format (TIFF-based)", + "exif": "R/W/C", + "iptc": "R/W/C", + "xmp": "R/W/C", + "icc": "R/W/C", + "other": "R/W Olympus", + "read": true, + "write": true, + "create": false + }, + "exe": { + "extension": "exe", + "file type": [ + "EXE", + "DLL" + ], + "support": "R", + "description": "DOS/Windows executable and library files", + "exif": null, + "iptc": null, + "xmp": null, + "icc": null, + "other": "R EXE", + "read": true, + "write": false, + "create": false + }, + "dll": { + "extension": "dll", + "file type": [ + "EXE", + "DLL" + ], + "support": "R", + "description": "DOS/Windows executable and library files", + "exif": null, + "iptc": null, + "xmp": null, + "icc": null, + "other": "R EXE", + "read": true, + "write": false, + "create": false + }, + "exif": { + "extension": "exif", + "file type": [ + "EXIF" + ], + "support": "R/W/C", + "description": "Exchangeable Image File Format metadata (TIFF-based)", + "exif": "R/W/C", + "iptc": null, + "xmp": null, + "icc": null, + "other": null, + "read": true, + "write": true, + "create": true + }, + "exr": { + "extension": "exr", + "file type": [ + "EXR" + ], + "support": "R", + "description": "Open EXR (Extended Range)", + "exif": null, + "iptc": null, + "xmp": null, + "icc": null, + "other": "R OpenEXR", + "read": true, + "write": false, + "create": false + }, + "exv": { + "extension": "exv", + "file type": [ + "EXV" + ], + "support": "R/W/C", + "description": "Exiv2 metadata file (JPEG-based)", + "exif": "R/W/C", + "iptc": "R/W/C", + "xmp": "R/W/C", + "icc": "R/W/C", + "other": "Supported JPEG Meta Information", + "read": true, + "write": true, + "create": true + }, + "f4a": { + "extension": "f4a", + "file type": [ + "F4A", + "F4B", + "F4P", + "F4V" + ], + "support": "R/W", + "description": "Adobe Flash Player 9+ Audio/Video (QuickTime-based)", + "exif": "R/W", + "iptc": "R/W", + "xmp": "R/W/C", + "icc": null, + "other": "R/W/C QuickTime", + "read": true, + "write": true, + "create": false + }, + "f4b": { + "extension": "f4b", + "file type": [ + "F4A", + "F4B", + "F4P", + "F4V" + ], + "support": "R/W", + "description": "Adobe Flash Player 9+ Audio/Video (QuickTime-based)", + "exif": "R/W", + "iptc": "R/W", + "xmp": "R/W/C", + "icc": null, + "other": "R/W/C QuickTime", + "read": true, + "write": true, + "create": false + }, + "f4p": { + "extension": "f4p", + "file type": [ + "F4A", + "F4B", + "F4P", + "F4V" + ], + "support": "R/W", + "description": "Adobe Flash Player 9+ Audio/Video (QuickTime-based)", + "exif": "R/W", + "iptc": "R/W", + "xmp": "R/W/C", + "icc": null, + "other": "R/W/C QuickTime", + "read": true, + "write": true, + "create": false + }, + "f4v": { + "extension": "f4v", + "file type": [ + "F4A", + "F4B", + "F4P", + "F4V" + ], + "support": "R/W", + "description": "Adobe Flash Player 9+ Audio/Video (QuickTime-based)", + "exif": "R/W", + "iptc": "R/W", + "xmp": "R/W/C", + "icc": null, + "other": "R/W/C QuickTime", + "read": true, + "write": true, + "create": false + }, + "fff": { + "extension": "fff", + "file type": [ + "FFF" + ], + "support": "R", + "description": "FLIR Systems thermal image File Format", + "exif": null, + "iptc": null, + "xmp": null, + "icc": null, + "other": "R FLIR", + "read": true, + "write": false, + "create": false + }, + "fits": { + "extension": "fits", + "file type": [ + "FITS" + ], + "support": "R", + "description": "Flexible Image Transport System", + "exif": null, + "iptc": null, + "xmp": null, + "icc": null, + "other": "R FITS", + "read": true, + "write": false, + "create": false + }, + "fla": { + "extension": "fla", + "file type": [ + "FLA" + ], + "support": "R", + "description": "Macromedia/Adobe Flash project (FPX-like)", + "exif": null, + "iptc": null, + "xmp": "R", + "icc": "R", + "other": "R FlashPix", + "read": true, + "write": false, + "create": false + }, + "flac": { + "extension": "flac", + "file type": [ + "FLAC" + ], + "support": "R", + "description": "Free Lossless Audio Codec", + "exif": null, + "iptc": null, + "xmp": null, + "icc": null, + "other": "R FLAC ID3 Lyrics3", + "read": true, + "write": false, + "create": false + }, + "flif": { + "extension": "flif", + "file type": [ + "FLIF" + ], + "support": "R/W", + "description": "Free Lossless Image Format", + "exif": "R/W/C", + "iptc": null, + "xmp": "R/W/C", + "icc": "R/W/C", + "other": "R FLIF", + "read": true, + "write": true, + "create": false + }, + "flv": { + "extension": "flv", + "file type": [ + "FLV" + ], + "support": "R", + "description": "Flash Video", + "exif": null, + "iptc": null, + "xmp": "R", + "icc": null, + "other": "R Flash", + "read": true, + "write": false, + "create": false + }, + "fpf": { + "extension": "fpf", + "file type": [ + "FPF" + ], + "support": "R", + "description": "FLIR Public image Format", + "exif": null, + "iptc": null, + "xmp": null, + "icc": null, + "other": "R FLIR", + "read": true, + "write": false, + "create": false + }, + "fpx": { + "extension": "fpx", + "file type": [ + "FPX" + ], + "support": "R", + "description": "FlashPix image", + "exif": null, + "iptc": null, + "xmp": "R", + "icc": "R", + "other": "R FlashPix", + "read": true, + "write": false, + "create": false + }, + "gif": { + "extension": "gif", + "file type": [ + "GIF" + ], + "support": "R/W", + "description": "Compuserve Graphics Interchange Format", + "exif": null, + "iptc": null, + "xmp": "R/W/C", + "icc": "R/W/C", + "other": "R/W/C GIF", + "read": true, + "write": true, + "create": false + }, + "gpr": { + "extension": "gpr", + "file type": [ + "GPR" + ], + "support": "R/W", + "description": "GoPro RAW (DNG-based)", + "exif": "R/W/C", + "iptc": "R/W/C", + "xmp": "R/W/C", + "icc": "R/W/C", + "other": null, + "read": true, + "write": true, + "create": false + }, + "gz": { + "extension": "gz", + "file type": [ + "GZ", + "GZIP" + ], + "support": "R", + "description": "GNU ZIP compressed archive", + "exif": null, + "iptc": null, + "xmp": null, + "icc": null, + "other": "R ZIP", + "read": true, + "write": false, + "create": false + }, + "gzip": { + "extension": "gzip", + "file type": [ + "GZ", + "GZIP" + ], + "support": "R", + "description": "GNU ZIP compressed archive", + "exif": null, + "iptc": null, + "xmp": null, + "icc": null, + "other": "R ZIP", + "read": true, + "write": false, + "create": false + }, + "hdp": { + "extension": "hdp", + "file type": [ + "HDP", + "WDP", + "JXR" + ], + "support": "R/W", + "description": "Windows HD Photo / Media Photo / JPEG XR (TIFF-based)", + "exif": "R/W/C", + "iptc": "R/W/C", + "xmp": "R/W/C", + "icc": "R/W/C", + "other": null, + "read": true, + "write": true, + "create": false + }, + "wdp": { + "extension": "wdp", + "file type": [ + "HDP", + "WDP", + "JXR" + ], + "support": "R/W", + "description": "Windows HD Photo / Media Photo / JPEG XR (TIFF-based)", + "exif": "R/W/C", + "iptc": "R/W/C", + "xmp": "R/W/C", + "icc": "R/W/C", + "other": null, + "read": true, + "write": true, + "create": false + }, + "jxr": { + "extension": "jxr", + "file type": [ + "HDP", + "WDP", + "JXR" + ], + "support": "R/W", + "description": "Windows HD Photo / Media Photo / JPEG XR (TIFF-based)", + "exif": "R/W/C", + "iptc": "R/W/C", + "xmp": "R/W/C", + "icc": "R/W/C", + "other": null, + "read": true, + "write": true, + "create": false + }, + "hdr": { + "extension": "hdr", + "file type": [ + "HDR" + ], + "support": "R", + "description": "Radiance RGBE High Dynamic-Range", + "exif": null, + "iptc": null, + "xmp": null, + "icc": null, + "other": "R Radiance", + "read": true, + "write": false, + "create": false + }, + "heic": { + "extension": "heic", + "file type": [ + "HEIC", + "HEIF", + "HIF" + ], + "support": "R/W", + "description": "High Efficiency Image Format (QuickTime-based)", + "exif": "R/W/C", + "iptc": null, + "xmp": "R/W/C", + "icc": "R/W", + "other": "R/W QuickTime", + "read": true, + "write": true, + "create": false + }, + "heif": { + "extension": "heif", + "file type": [ + "HEIC", + "HEIF", + "HIF" + ], + "support": "R/W", + "description": "High Efficiency Image Format (QuickTime-based)", + "exif": "R/W/C", + "iptc": null, + "xmp": "R/W/C", + "icc": "R/W", + "other": "R/W QuickTime", + "read": true, + "write": true, + "create": false + }, + "hif": { + "extension": "hif", + "file type": [ + "HEIC", + "HEIF", + "HIF" + ], + "support": "R/W", + "description": "High Efficiency Image Format (QuickTime-based)", + "exif": "R/W/C", + "iptc": null, + "xmp": "R/W/C", + "icc": "R/W", + "other": "R/W QuickTime", + "read": true, + "write": true, + "create": false + }, + "html": { + "extension": "html", + "file type": [ + "HTML", + "HTM", + "XHTML" + ], + "support": "R", + "description": "[Extensible] HyperText Markup Language", + "exif": null, + "iptc": null, + "xmp": null, + "icc": null, + "other": "R HTML", + "read": true, + "write": false, + "create": false + }, + "htm": { + "extension": "htm", + "file type": [ + "HTML", + "HTM", + "XHTML" + ], + "support": "R", + "description": "[Extensible] HyperText Markup Language", + "exif": null, + "iptc": null, + "xmp": null, + "icc": null, + "other": "R HTML", + "read": true, + "write": false, + "create": false + }, + "xhtml": { + "extension": "xhtml", + "file type": [ + "HTML", + "HTM", + "XHTML" + ], + "support": "R", + "description": "[Extensible] HyperText Markup Language", + "exif": null, + "iptc": null, + "xmp": null, + "icc": null, + "other": "R HTML", + "read": true, + "write": false, + "create": false + }, + "icc": { + "extension": "icc", + "file type": [ + "ICC", + "ICM" + ], + "support": "R/W/C", + "description": "International Color Consortium color profile", + "exif": null, + "iptc": null, + "xmp": null, + "icc": "R/W/C", + "other": null, + "read": true, + "write": true, + "create": true + }, + "icm": { + "extension": "icm", + "file type": [ + "ICC", + "ICM" + ], + "support": "R/W/C", + "description": "International Color Consortium color profile", + "exif": null, + "iptc": null, + "xmp": null, + "icc": "R/W/C", + "other": null, + "read": true, + "write": true, + "create": true + }, + "ics": { + "extension": "ics", + "file type": [ + "ICS", + "ICAL" + ], + "support": "R", + "description": "iCalendar Schedule", + "exif": null, + "iptc": null, + "xmp": null, + "icc": null, + "other": "R VCalendar", + "read": true, + "write": false, + "create": false + }, + "ical": { + "extension": "ical", + "file type": [ + "ICS", + "ICAL" + ], + "support": "R", + "description": "iCalendar Schedule", + "exif": null, + "iptc": null, + "xmp": null, + "icc": null, + "other": "R VCalendar", + "read": true, + "write": false, + "create": false + }, + "idml": { + "extension": "idml", + "file type": [ + "IDML" + ], + "support": "R", + "description": "Adobe InDesign Markup Language (ZIP/XML-based)", + "exif": null, + "iptc": null, + "xmp": null, + "icc": null, + "other": "R XML ZIP", + "read": true, + "write": false, + "create": false + }, + "iiq": { + "extension": "iiq", + "file type": [ + "IIQ" + ], + "support": "R/W", + "description": "Phase One Intelligent Image Quality RAW (TIFF-based)", + "exif": "R/W/C", + "iptc": "R/W/C", + "xmp": "R/W/C", + "icc": "R/W/C", + "other": "R/W PhaseOne", + "read": true, + "write": true, + "create": false + }, + "ind": { + "extension": "ind", + "file type": [ + "IND", + "INDD", + "INDT" + ], + "support": "R/W", + "description": "Adobe InDesign Document/Template", + "exif": null, + "iptc": null, + "xmp": "R/W/C", + "icc": null, + "other": null, + "read": true, + "write": true, + "create": false + }, + "indd": { + "extension": "indd", + "file type": [ + "IND", + "INDD", + "INDT" + ], + "support": "R/W", + "description": "Adobe InDesign Document/Template", + "exif": null, + "iptc": null, + "xmp": "R/W/C", + "icc": null, + "other": null, + "read": true, + "write": true, + "create": false + }, + "indt": { + "extension": "indt", + "file type": [ + "IND", + "INDD", + "INDT" + ], + "support": "R/W", + "description": "Adobe InDesign Document/Template", + "exif": null, + "iptc": null, + "xmp": "R/W/C", + "icc": null, + "other": null, + "read": true, + "write": true, + "create": false + }, + "insp": { + "extension": "insp", + "file type": [ + "INSP" + ], + "support": "R/W", + "description": "Insta360 Picture (JPEG-based)", + "exif": "R/W/C", + "iptc": "R/W/C", + "xmp": "R/W/C", + "icc": "R/W/C", + "other": "Supported JPEG Meta Information", + "read": true, + "write": true, + "create": false + }, + "insv": { + "extension": "insv", + "file type": [ + "INSV" + ], + "support": "R", + "description": "Insta360 Video (QuickTime-based)", + "exif": null, + "iptc": null, + "xmp": "R", + "icc": null, + "other": "R QuickTime", + "read": true, + "write": false, + "create": false + }, + "inx": { + "extension": "inx", + "file type": [ + "INX" + ], + "support": "R", + "description": "Adobe InDesign Interchange (XML-based)", + "exif": null, + "iptc": null, + "xmp": "R", + "icc": null, + "other": null, + "read": true, + "write": false, + "create": false + }, + "iso": { + "extension": "iso", + "file type": [ + "ISO" + ], + "support": "R", + "description": "ISO 9660 disk image", + "exif": null, + "iptc": null, + "xmp": null, + "icc": null, + "other": "R ISO", + "read": true, + "write": false, + "create": false + }, + "itc": { + "extension": "itc", + "file type": [ + "ITC" + ], + "support": "R", + "description": "iTunes Cover Flow artwork", + "exif": null, + "iptc": null, + "xmp": null, + "icc": null, + "other": "R ITC", + "read": true, + "write": false, + "create": false + }, + "j2c": { + "extension": "j2c", + "file type": [ + "J2C", + "J2K", + "JPC" + ], + "support": "R", + "description": "JPEG 2000 codestream", + "exif": "R", + "iptc": "R", + "xmp": "R", + "icc": "R", + "other": "R Jpeg2000 Photoshop", + "read": true, + "write": false, + "create": false + }, + "j2k": { + "extension": "j2k", + "file type": [ + "J2C", + "J2K", + "JPC" + ], + "support": "R", + "description": "JPEG 2000 codestream", + "exif": "R", + "iptc": "R", + "xmp": "R", + "icc": "R", + "other": "R Jpeg2000 Photoshop", + "read": true, + "write": false, + "create": false + }, + "jpc": { + "extension": "jpc", + "file type": [ + "J2C", + "J2K", + "JPC" + ], + "support": "R", + "description": "JPEG 2000 codestream", + "exif": "R", + "iptc": "R", + "xmp": "R", + "icc": "R", + "other": "R Jpeg2000 Photoshop", + "read": true, + "write": false, + "create": false + }, + "jp2": { + "extension": "jp2", + "file type": [ + "JP2", + "JPF", + "JPM", + "JPX" + ], + "support": "R/W", + "description": "JPEG 2000 image [Compound/Extended]", + "exif": "R/W/C", + "iptc": "R/W/C", + "xmp": "R/W/C", + "icc": "R", + "other": "R/W/C Jpeg2000, R Photoshop", + "read": true, + "write": true, + "create": false + }, + "jpf": { + "extension": "jpf", + "file type": [ + "JP2", + "JPF", + "JPM", + "JPX" + ], + "support": "R/W", + "description": "JPEG 2000 image [Compound/Extended]", + "exif": "R/W/C", + "iptc": "R/W/C", + "xmp": "R/W/C", + "icc": "R", + "other": "R/W/C Jpeg2000, R Photoshop", + "read": true, + "write": true, + "create": false + }, + "jpm": { + "extension": "jpm", + "file type": [ + "JP2", + "JPF", + "JPM", + "JPX" + ], + "support": "R/W", + "description": "JPEG 2000 image [Compound/Extended]", + "exif": "R/W/C", + "iptc": "R/W/C", + "xmp": "R/W/C", + "icc": "R", + "other": "R/W/C Jpeg2000, R Photoshop", + "read": true, + "write": true, + "create": false + }, + "jpx": { + "extension": "jpx", + "file type": [ + "JP2", + "JPF", + "JPM", + "JPX" + ], + "support": "R/W", + "description": "JPEG 2000 image [Compound/Extended]", + "exif": "R/W/C", + "iptc": "R/W/C", + "xmp": "R/W/C", + "icc": "R", + "other": "R/W/C Jpeg2000, R Photoshop", + "read": true, + "write": true, + "create": false + }, + "jpeg": { + "extension": "jpeg", + "file type": [ + "JPEG", + "JPG", + "JPE" + ], + "support": "R/W", + "description": "Joint Photographic Experts Group image", + "exif": "R/W/C", + "iptc": "R/W/C", + "xmp": "R/W/C", + "icc": "R/W/C", + "other": "Supported JPEG Meta Information", + "read": true, + "write": true, + "create": false + }, + "jpg": { + "extension": "jpg", + "file type": [ + "JPEG", + "JPG", + "JPE" + ], + "support": "R/W", + "description": "Joint Photographic Experts Group image", + "exif": "R/W/C", + "iptc": "R/W/C", + "xmp": "R/W/C", + "icc": "R/W/C", + "other": "Supported JPEG Meta Information", + "read": true, + "write": true, + "create": false + }, + "jpe": { + "extension": "jpe", + "file type": [ + "JPEG", + "JPG", + "JPE" + ], + "support": "R/W", + "description": "Joint Photographic Experts Group image", + "exif": "R/W/C", + "iptc": "R/W/C", + "xmp": "R/W/C", + "icc": "R/W/C", + "other": "Supported JPEG Meta Information", + "read": true, + "write": true, + "create": false + }, + "json": { + "extension": "json", + "file type": [ + "JSON" + ], + "support": "R", + "description": "JavaScript Object Notation", + "exif": null, + "iptc": null, + "xmp": null, + "icc": null, + "other": "R JSON", + "read": true, + "write": false, + "create": false + }, + "jxl": { + "extension": "jxl", + "file type": [ + "JXL" + ], + "support": "R/W", + "description": "JPEG XL (codestream and ISO BMFF)", + "exif": "R/W/C", + "iptc": null, + "xmp": "R/W/C", + "icc": null, + "other": null, + "read": true, + "write": true, + "create": false + }, + "k25": { + "extension": "k25", + "file type": [ + "K25" + ], + "support": "R", + "description": "Kodak DC25 RAW (TIFF-based)", + "exif": "R", + "iptc": "R", + "xmp": "R", + "icc": "R", + "other": null, + "read": true, + "write": false, + "create": false + }, + "kdc": { + "extension": "kdc", + "file type": [ + "KDC" + ], + "support": "R", + "description": "Kodak Digital Camera RAW (TIFF-based)", + "exif": "R", + "iptc": "R", + "xmp": "R", + "icc": "R", + "other": "R Kodak", + "read": true, + "write": false, + "create": false + }, + "key": { + "extension": "key", + "file type": [ + "KEY", + "KTH" + ], + "support": "R", + "description": "Apple iWork '09 Keynote presentation/Theme", + "exif": null, + "iptc": null, + "xmp": null, + "icc": null, + "other": "R XML ZIP", + "read": true, + "write": false, + "create": false + }, + "kth": { + "extension": "kth", + "file type": [ + "KEY", + "KTH" + ], + "support": "R", + "description": "Apple iWork '09 Keynote presentation/Theme", + "exif": null, + "iptc": null, + "xmp": null, + "icc": null, + "other": "R XML ZIP", + "read": true, + "write": false, + "create": false + }, + "la": { + "extension": "la", + "file type": [ + "LA" + ], + "support": "R", + "description": "Lossless Audio (RIFF-based)", + "exif": "R", + "iptc": null, + "xmp": "R", + "icc": null, + "other": "R RIFF", + "read": true, + "write": false, + "create": false + }, + "lfp": { + "extension": "lfp", + "file type": [ + "LFP", + "LFR" + ], + "support": "R", + "description": "Lytro Light Field Picture", + "exif": null, + "iptc": null, + "xmp": null, + "icc": null, + "other": "R Lytro", + "read": true, + "write": false, + "create": false + }, + "lfr": { + "extension": "lfr", + "file type": [ + "LFP", + "LFR" + ], + "support": "R", + "description": "Lytro Light Field Picture", + "exif": null, + "iptc": null, + "xmp": null, + "icc": null, + "other": "R Lytro", + "read": true, + "write": false, + "create": false + }, + "lif": { + "extension": "lif", + "file type": [ + "LIF" + ], + "support": "R", + "description": "Leica Image File", + "exif": null, + "iptc": null, + "xmp": null, + "icc": null, + "other": "R LIF", + "read": true, + "write": false, + "create": false + }, + "lnk": { + "extension": "lnk", + "file type": [ + "LNK" + ], + "support": "R", + "description": "Microsoft Shell Link (Windows shortcut)", + "exif": null, + "iptc": null, + "xmp": null, + "icc": null, + "other": "R LNK", + "read": true, + "write": false, + "create": false + }, + "lrv": { + "extension": "lrv", + "file type": [ + "LRV" + ], + "support": "R/W", + "description": "Low-Resolution Video (QuickTime-based)", + "exif": "R/W", + "iptc": "R/W", + "xmp": "R/W/C", + "icc": null, + "other": "R/W/C QuickTime", + "read": true, + "write": true, + "create": false + }, + "m2ts": { + "extension": "m2ts", + "file type": [ + "M2TS", + "MTS", + "M2T", + "TS" + ], + "support": "R", + "description": "MPEG-2 Transport Stream (used for AVCHD video)", + "exif": null, + "iptc": null, + "xmp": null, + "icc": null, + "other": "R M2TS H264", + "read": true, + "write": false, + "create": false + }, + "mts": { + "extension": "mts", + "file type": [ + "M2TS", + "MTS", + "M2T", + "TS" + ], + "support": "R", + "description": "MPEG-2 Transport Stream (used for AVCHD video)", + "exif": null, + "iptc": null, + "xmp": null, + "icc": null, + "other": "R M2TS H264", + "read": true, + "write": false, + "create": false + }, + "m2t": { + "extension": "m2t", + "file type": [ + "M2TS", + "MTS", + "M2T", + "TS" + ], + "support": "R", + "description": "MPEG-2 Transport Stream (used for AVCHD video)", + "exif": null, + "iptc": null, + "xmp": null, + "icc": null, + "other": "R M2TS H264", + "read": true, + "write": false, + "create": false + }, + "ts": { + "extension": "ts", + "file type": [ + "M2TS", + "MTS", + "M2T", + "TS" + ], + "support": "R", + "description": "MPEG-2 Transport Stream (used for AVCHD video)", + "exif": null, + "iptc": null, + "xmp": null, + "icc": null, + "other": "R M2TS H264", + "read": true, + "write": false, + "create": false + }, + "m4a": { + "extension": "m4a", + "file type": [ + "M4A", + "M4B", + "M4P", + "M4V" + ], + "support": "R/W", + "description": "MPEG-4 Audio/Video (QuickTime-based)", + "exif": "R/W", + "iptc": "R/W", + "xmp": "R/W/C", + "icc": null, + "other": "R/W/C QuickTime", + "read": true, + "write": true, + "create": false + }, + "m4b": { + "extension": "m4b", + "file type": [ + "M4A", + "M4B", + "M4P", + "M4V" + ], + "support": "R/W", + "description": "MPEG-4 Audio/Video (QuickTime-based)", + "exif": "R/W", + "iptc": "R/W", + "xmp": "R/W/C", + "icc": null, + "other": "R/W/C QuickTime", + "read": true, + "write": true, + "create": false + }, + "m4p": { + "extension": "m4p", + "file type": [ + "M4A", + "M4B", + "M4P", + "M4V" + ], + "support": "R/W", + "description": "MPEG-4 Audio/Video (QuickTime-based)", + "exif": "R/W", + "iptc": "R/W", + "xmp": "R/W/C", + "icc": null, + "other": "R/W/C QuickTime", + "read": true, + "write": true, + "create": false + }, + "m4v": { + "extension": "m4v", + "file type": [ + "M4A", + "M4B", + "M4P", + "M4V" + ], + "support": "R/W", + "description": "MPEG-4 Audio/Video (QuickTime-based)", + "exif": "R/W", + "iptc": "R/W", + "xmp": "R/W/C", + "icc": null, + "other": "R/W/C QuickTime", + "read": true, + "write": true, + "create": false + }, + "macos": { + "extension": "macos", + "file type": [ + "MACOS" + ], + "support": "R", + "description": "MacOS \"._\" sidecar file (may have any extension)", + "exif": null, + "iptc": null, + "xmp": null, + "icc": null, + "other": "R XAttr RSRC", + "read": true, + "write": false, + "create": false + }, + "max": { + "extension": "max", + "file type": [ + "MAX" + ], + "support": "R", + "description": "3D Studio MAX (FPX-like)", + "exif": null, + "iptc": null, + "xmp": "R", + "icc": "R", + "other": "R FlashPix", + "read": true, + "write": false, + "create": false + }, + "mef": { + "extension": "mef", + "file type": [ + "MEF" + ], + "support": "R/W", + "description": "Mamiya (RAW) Electronic Format (TIFF-based)", + "exif": "R/W/C", + "iptc": "R/W/C", + "xmp": "R/W/C", + "icc": "R/W/C", + "other": null, + "read": true, + "write": true, + "create": false + }, + "mie": { + "extension": "mie", + "file type": [ + "MIE" + ], + "support": "R/W/C", + "description": "Meta Information Encapsulation (MIE specification)", + "exif": "R/W/C", + "iptc": "R/W/C", + "xmp": "R/W/C", + "icc": "R/W/C", + "other": "R/W/C MIE", + "read": true, + "write": true, + "create": true + }, + "miff": { + "extension": "miff", + "file type": [ + "MIFF", + "MIF" + ], + "support": "R", + "description": "Magick Image File Format", + "exif": "R", + "iptc": "R", + "xmp": "R", + "icc": "R", + "other": "R MIFF Photoshop", + "read": true, + "write": false, + "create": false + }, + "mif": { + "extension": "mif", + "file type": [ + "MIFF", + "MIF" + ], + "support": "R", + "description": "Magick Image File Format", + "exif": "R", + "iptc": "R", + "xmp": "R", + "icc": "R", + "other": "R MIFF Photoshop", + "read": true, + "write": false, + "create": false + }, + "mka": { + "extension": "mka", + "file type": [ + "MKA", + "MKV", + "MKS" + ], + "support": "R", + "description": "Matroska Audio/Video/Subtitle", + "exif": null, + "iptc": null, + "xmp": null, + "icc": null, + "other": "R Matroska", + "read": true, + "write": false, + "create": false + }, + "mkv": { + "extension": "mkv", + "file type": [ + "MKA", + "MKV", + "MKS" + ], + "support": "R", + "description": "Matroska Audio/Video/Subtitle", + "exif": null, + "iptc": null, + "xmp": null, + "icc": null, + "other": "R Matroska", + "read": true, + "write": false, + "create": false + }, + "mks": { + "extension": "mks", + "file type": [ + "MKA", + "MKV", + "MKS" + ], + "support": "R", + "description": "Matroska Audio/Video/Subtitle", + "exif": null, + "iptc": null, + "xmp": null, + "icc": null, + "other": "R Matroska", + "read": true, + "write": false, + "create": false + }, + "mobi": { + "extension": "mobi", + "file type": [ + "MOBI", + "AZW", + "AZW3" + ], + "support": "R", + "description": "Mobipocket electronic book (Palm-based)", + "exif": null, + "iptc": null, + "xmp": null, + "icc": null, + "other": "R Palm MOBI", + "read": true, + "write": false, + "create": false + }, + "azw": { + "extension": "azw", + "file type": [ + "MOBI", + "AZW", + "AZW3" + ], + "support": "R", + "description": "Mobipocket electronic book (Palm-based)", + "exif": null, + "iptc": null, + "xmp": null, + "icc": null, + "other": "R Palm MOBI", + "read": true, + "write": false, + "create": false + }, + "azw3": { + "extension": "azw3", + "file type": [ + "MOBI", + "AZW", + "AZW3" + ], + "support": "R", + "description": "Mobipocket electronic book (Palm-based)", + "exif": null, + "iptc": null, + "xmp": null, + "icc": null, + "other": "R Palm MOBI", + "read": true, + "write": false, + "create": false + }, + "modd": { + "extension": "modd", + "file type": [ + "MODD" + ], + "support": "R", + "description": "Sony Picture Motion metadata (XML PLIST-based)", + "exif": null, + "iptc": null, + "xmp": null, + "icc": null, + "other": "R PLIST", + "read": true, + "write": false, + "create": false + }, + "moi": { + "extension": "moi", + "file type": [ + "MOI" + ], + "support": "R", + "description": "MOD Information file", + "exif": null, + "iptc": null, + "xmp": null, + "icc": null, + "other": "R MOI", + "read": true, + "write": false, + "create": false + }, + "mos": { + "extension": "mos", + "file type": [ + "MOS" + ], + "support": "R/W", + "description": "Creo Leaf Mosaic (TIFF-based)", + "exif": "R/W/C", + "iptc": "R/W/C", + "xmp": "R/W/C", + "icc": "R/W/C", + "other": "R Leaf", + "read": true, + "write": true, + "create": false + }, + "mov": { + "extension": "mov", + "file type": [ + "MOV", + "QT" + ], + "support": "R/W", + "description": "Apple QuickTime Movie", + "exif": "R/W", + "iptc": "R/W", + "xmp": "R/W/C", + "icc": null, + "other": "R/W/C QuickTime", + "read": true, + "write": true, + "create": false + }, + "qt": { + "extension": "qt", + "file type": [ + "MOV", + "QT" + ], + "support": "R/W", + "description": "Apple QuickTime Movie", + "exif": "R/W", + "iptc": "R/W", + "xmp": "R/W/C", + "icc": null, + "other": "R/W/C QuickTime", + "read": true, + "write": true, + "create": false + }, + "mp3": { + "extension": "mp3", + "file type": [ + "MP3" + ], + "support": "R", + "description": "MPEG-1 layer 3 audio", + "exif": null, + "iptc": null, + "xmp": null, + "icc": null, + "other": "R MPEG ID3 Lyrics3 APE", + "read": true, + "write": false, + "create": false + }, + "mp4": { + "extension": "mp4", + "file type": [ + "MP4" + ], + "support": "R/W", + "description": "Motion Picture Experts Group version 4 (QuickTime-based)", + "exif": "R/W", + "iptc": "R/W", + "xmp": "R/W/C", + "icc": null, + "other": "R/W/C QuickTime", + "read": true, + "write": true, + "create": false + }, + "mpc": { + "extension": "mpc", + "file type": [ + "MPC" + ], + "support": "R", + "description": "Musepack Audio", + "exif": null, + "iptc": null, + "xmp": null, + "icc": null, + "other": "R MPC ID3 Lyrics3 APE", + "read": true, + "write": false, + "create": false + }, + "mpeg": { + "extension": "mpeg", + "file type": [ + "MPEG", + "MPG", + "M2V" + ], + "support": "R", + "description": "Motion Picture Experts Group version 1 or 2", + "exif": null, + "iptc": null, + "xmp": null, + "icc": null, + "other": "R MPEG ID3 Lyrics3", + "read": true, + "write": false, + "create": false + }, + "mpg": { + "extension": "mpg", + "file type": [ + "MPEG", + "MPG", + "M2V" + ], + "support": "R", + "description": "Motion Picture Experts Group version 1 or 2", + "exif": null, + "iptc": null, + "xmp": null, + "icc": null, + "other": "R MPEG ID3 Lyrics3", + "read": true, + "write": false, + "create": false + }, + "m2v": { + "extension": "m2v", + "file type": [ + "MPEG", + "MPG", + "M2V" + ], + "support": "R", + "description": "Motion Picture Experts Group version 1 or 2", + "exif": null, + "iptc": null, + "xmp": null, + "icc": null, + "other": "R MPEG ID3 Lyrics3", + "read": true, + "write": false, + "create": false + }, + "mpo": { + "extension": "mpo", + "file type": [ + "MPO" + ], + "support": "R/W", + "description": "Extended Multi-Picture format (JPEG with MPF extensions)", + "exif": "R/W/C", + "iptc": "R/W/C", + "xmp": "R/W/C", + "icc": "R/W/C", + "other": "Supported JPEG Meta Information", + "read": true, + "write": true, + "create": false + }, + "mqv": { + "extension": "mqv", + "file type": [ + "MQV" + ], + "support": "R/W", + "description": "Sony Mobile QuickTime Video", + "exif": "R/W", + "iptc": "R/W", + "xmp": "R/W/C", + "icc": null, + "other": "R/W/C QuickTime", + "read": true, + "write": true, + "create": false + }, + "mrw": { + "extension": "mrw", + "file type": [ + "MRW" + ], + "support": "R/W", + "description": "Minolta RAW", + "exif": "R/W/C", + "iptc": "R/W/C", + "xmp": "R/W/C", + "icc": "R/W/C", + "other": "R/W MinoltaRaw Minolta", + "read": true, + "write": true, + "create": false + }, + "mrc": { + "extension": "mrc", + "file type": [ + "MRC" + ], + "support": "R", + "description": "Medical Research Council", + "exif": null, + "iptc": null, + "xmp": null, + "icc": null, + "other": "R MRC", + "read": true, + "write": false, + "create": false + }, + "mxf": { + "extension": "mxf", + "file type": [ + "MXF" + ], + "support": "R", + "description": "Material Exchange Format", + "exif": null, + "iptc": null, + "xmp": null, + "icc": null, + "other": "R MXF", + "read": true, + "write": false, + "create": false + }, + "nef": { + "extension": "nef", + "file type": [ + "NEF" + ], + "support": "R/W", + "description": "Nikon (RAW) Electronic Format (TIFF-based)", + "exif": "R/W/C", + "iptc": "R/W/C", + "xmp": "R/W/C", + "icc": "R/W/C", + "other": "R/W Nikon NikonCapture", + "read": true, + "write": true, + "create": false + }, + "nksc": { + "extension": "nksc", + "file type": [ + "NKSC" + ], + "support": "R/W", + "description": "Nikon Sidecar (XMP-based)", + "exif": null, + "iptc": null, + "xmp": "R/W/C", + "icc": null, + "other": null, + "read": true, + "write": true, + "create": false + }, + "nmbtemplate": { + "extension": "nmbtemplate", + "file type": [ + "NMBTEMPLATE" + ], + "support": "R", + "description": "Apple iWork '09 Numbers Template", + "exif": null, + "iptc": null, + "xmp": null, + "icc": null, + "other": "R XML ZIP", + "read": true, + "write": false, + "create": false + }, + "nrw": { + "extension": "nrw", + "file type": [ + "NRW" + ], + "support": "R/W", + "description": "Nikon RAW (2) (TIFF-based)", + "exif": "R/W/C", + "iptc": "R/W/C", + "xmp": "R/W/C", + "icc": "R/W/C", + "other": "R/W Nikon NikonCapture", + "read": true, + "write": true, + "create": false + }, + "numbers": { + "extension": "numbers", + "file type": [ + "NUMBERS" + ], + "support": "R", + "description": "Apple iWork '09 Numbers spreadsheet", + "exif": null, + "iptc": null, + "xmp": null, + "icc": null, + "other": "R XML ZIP", + "read": true, + "write": false, + "create": false + }, + "o": { + "extension": "o", + "file type": [ + "O" + ], + "support": "R", + "description": "Unix compiled code Object", + "exif": null, + "iptc": null, + "xmp": null, + "icc": null, + "other": "R EXE", + "read": true, + "write": false, + "create": false + }, + "odb": { + "extension": "odb", + "file type": [ + "ODB", + "ODC", + "ODF", + "ODG", + "ODI", + "ODP", + "ODS", + "ODT" + ], + "support": "R", + "description": "Open Document Database/Chart/Formula/Graphics/Image/Presentation/Spreadsheet/Text (ZIP/XML-based)", + "exif": null, + "iptc": null, + "xmp": null, + "icc": null, + "other": "R XML ZIP", + "read": true, + "write": false, + "create": false + }, + "odc": { + "extension": "odc", + "file type": [ + "ODB", + "ODC", + "ODF", + "ODG", + "ODI", + "ODP", + "ODS", + "ODT" + ], + "support": "R", + "description": "Open Document Database/Chart/Formula/Graphics/Image/Presentation/Spreadsheet/Text (ZIP/XML-based)", + "exif": null, + "iptc": null, + "xmp": null, + "icc": null, + "other": "R XML ZIP", + "read": true, + "write": false, + "create": false + }, + "odf": { + "extension": "odf", + "file type": [ + "ODB", + "ODC", + "ODF", + "ODG", + "ODI", + "ODP", + "ODS", + "ODT" + ], + "support": "R", + "description": "Open Document Database/Chart/Formula/Graphics/Image/Presentation/Spreadsheet/Text (ZIP/XML-based)", + "exif": null, + "iptc": null, + "xmp": null, + "icc": null, + "other": "R XML ZIP", + "read": true, + "write": false, + "create": false + }, + "odg": { + "extension": "odg", + "file type": [ + "ODB", + "ODC", + "ODF", + "ODG", + "ODI", + "ODP", + "ODS", + "ODT" + ], + "support": "R", + "description": "Open Document Database/Chart/Formula/Graphics/Image/Presentation/Spreadsheet/Text (ZIP/XML-based)", + "exif": null, + "iptc": null, + "xmp": null, + "icc": null, + "other": "R XML ZIP", + "read": true, + "write": false, + "create": false + }, + "odi": { + "extension": "odi", + "file type": [ + "ODB", + "ODC", + "ODF", + "ODG", + "ODI", + "ODP", + "ODS", + "ODT" + ], + "support": "R", + "description": "Open Document Database/Chart/Formula/Graphics/Image/Presentation/Spreadsheet/Text (ZIP/XML-based)", + "exif": null, + "iptc": null, + "xmp": null, + "icc": null, + "other": "R XML ZIP", + "read": true, + "write": false, + "create": false + }, + "odp": { + "extension": "odp", + "file type": [ + "ODB", + "ODC", + "ODF", + "ODG", + "ODI", + "ODP", + "ODS", + "ODT" + ], + "support": "R", + "description": "Open Document Database/Chart/Formula/Graphics/Image/Presentation/Spreadsheet/Text (ZIP/XML-based)", + "exif": null, + "iptc": null, + "xmp": null, + "icc": null, + "other": "R XML ZIP", + "read": true, + "write": false, + "create": false + }, + "ods": { + "extension": "ods", + "file type": [ + "ODB", + "ODC", + "ODF", + "ODG", + "ODI", + "ODP", + "ODS", + "ODT" + ], + "support": "R", + "description": "Open Document Database/Chart/Formula/Graphics/Image/Presentation/Spreadsheet/Text (ZIP/XML-based)", + "exif": null, + "iptc": null, + "xmp": null, + "icc": null, + "other": "R XML ZIP", + "read": true, + "write": false, + "create": false + }, + "odt": { + "extension": "odt", + "file type": [ + "ODB", + "ODC", + "ODF", + "ODG", + "ODI", + "ODP", + "ODS", + "ODT" + ], + "support": "R", + "description": "Open Document Database/Chart/Formula/Graphics/Image/Presentation/Spreadsheet/Text (ZIP/XML-based)", + "exif": null, + "iptc": null, + "xmp": null, + "icc": null, + "other": "R XML ZIP", + "read": true, + "write": false, + "create": false + }, + "ofr": { + "extension": "ofr", + "file type": [ + "OFR" + ], + "support": "R", + "description": "OptimFROG audio (RIFF-based)", + "exif": "R", + "iptc": null, + "xmp": "R", + "icc": null, + "other": "R RIFF", + "read": true, + "write": false, + "create": false + }, + "ogg": { + "extension": "ogg", + "file type": [ + "OGG", + "OGV" + ], + "support": "R", + "description": "Ogg bitstream container", + "exif": null, + "iptc": null, + "xmp": null, + "icc": null, + "other": "R FLAC ID3 Lyrics3 Theora Vorbis", + "read": true, + "write": false, + "create": false + }, + "ogv": { + "extension": "ogv", + "file type": [ + "OGG", + "OGV" + ], + "support": "R", + "description": "Ogg bitstream container", + "exif": null, + "iptc": null, + "xmp": null, + "icc": null, + "other": "R FLAC ID3 Lyrics3 Theora Vorbis", + "read": true, + "write": false, + "create": false + }, + "onp": { + "extension": "onp", + "file type": [ + "ONP" + ], + "support": "R", + "description": "ON1 Presets", + "exif": null, + "iptc": null, + "xmp": null, + "icc": null, + "other": "R JSON PLIST", + "read": true, + "write": false, + "create": false + }, + "opus": { + "extension": "opus", + "file type": [ + "OPUS" + ], + "support": "R", + "description": "Ogg Opus audio", + "exif": null, + "iptc": null, + "xmp": null, + "icc": null, + "other": "R FLAC ID3 Lyrics3 Opus Vorbis", + "read": true, + "write": false, + "create": false + }, + "orf": { + "extension": "orf", + "file type": [ + "ORF", + "ORI" + ], + "support": "R/W", + "description": "Olympus RAW Format (TIFF-based)", + "exif": "R/W/C", + "iptc": "R/W/C", + "xmp": "R/W/C", + "icc": "R/W/C", + "other": "R/W Olympus", + "read": true, + "write": true, + "create": false + }, + "ori": { + "extension": "ori", + "file type": [ + "ORF", + "ORI" + ], + "support": "R/W", + "description": "Olympus RAW Format (TIFF-based)", + "exif": "R/W/C", + "iptc": "R/W/C", + "xmp": "R/W/C", + "icc": "R/W/C", + "other": "R/W Olympus", + "read": true, + "write": true, + "create": false + }, + "otf": { + "extension": "otf", + "file type": [ + "OTF" + ], + "support": "R", + "description": "Open Type Font", + "exif": null, + "iptc": null, + "xmp": null, + "icc": null, + "other": "R Font", + "read": true, + "write": false, + "create": false + }, + "pac": { + "extension": "pac", + "file type": [ + "PAC" + ], + "support": "R", + "description": "Lossless Predictive Audio Compression (RIFF-based)", + "exif": "R", + "iptc": null, + "xmp": "R", + "icc": null, + "other": "R RIFF", + "read": true, + "write": false, + "create": false + }, + "pages": { + "extension": "pages", + "file type": [ + "PAGES" + ], + "support": "R", + "description": "Apple iWork '09 Pages document", + "exif": null, + "iptc": null, + "xmp": null, + "icc": null, + "other": "R XML ZIP", + "read": true, + "write": false, + "create": false + }, + "pcd": { + "extension": "pcd", + "file type": [ + "PCD" + ], + "support": "R", + "description": "Kodak Photo CD Image Pac", + "exif": null, + "iptc": null, + "xmp": null, + "icc": null, + "other": "R PhotoCD", + "read": true, + "write": false, + "create": false + }, + "pcx": { + "extension": "pcx", + "file type": [ + "PCX" + ], + "support": "R", + "description": "PC Paintbrush", + "exif": null, + "iptc": null, + "xmp": null, + "icc": null, + "other": "R PCX", + "read": true, + "write": false, + "create": false + }, + "pdb": { + "extension": "pdb", + "file type": [ + "PDB", + "PRC" + ], + "support": "R", + "description": "Palm Database", + "exif": null, + "iptc": null, + "xmp": null, + "icc": null, + "other": "R Palm", + "read": true, + "write": false, + "create": false + }, + "prc": { + "extension": "prc", + "file type": [ + "PDB", + "PRC" + ], + "support": "R", + "description": "Palm Database", + "exif": null, + "iptc": null, + "xmp": null, + "icc": null, + "other": "R Palm", + "read": true, + "write": false, + "create": false + }, + "pdf": { + "extension": "pdf", + "file type": [ + "PDF" + ], + "support": "R/W", + "description": "Adobe Portable Document Format", + "exif": "R", + "iptc": "R", + "xmp": "R/W/C", + "icc": "R", + "other": "R/W/C PDF, R Photoshop", + "read": true, + "write": true, + "create": false + }, + "pef": { + "extension": "pef", + "file type": [ + "PEF" + ], + "support": "R/W", + "description": "Pentax (RAW) Electronic Format (TIFF-based)", + "exif": "R/W/C", + "iptc": "R/W/C", + "xmp": "R/W/C", + "icc": "R/W/C", + "other": "R/W Pentax", + "read": true, + "write": true, + "create": false + }, + "pfa": { + "extension": "pfa", + "file type": [ + "PFA", + "PFB" + ], + "support": "R", + "description": "PostScript Font ASCII/Binary", + "exif": null, + "iptc": null, + "xmp": null, + "icc": null, + "other": "R Font", + "read": true, + "write": false, + "create": false + }, + "pfb": { + "extension": "pfb", + "file type": [ + "PFA", + "PFB" + ], + "support": "R", + "description": "PostScript Font ASCII/Binary", + "exif": null, + "iptc": null, + "xmp": null, + "icc": null, + "other": "R Font", + "read": true, + "write": false, + "create": false + }, + "pfm": { + "extension": "pfm", + "file type": [ + "PFM" + ], + "support": "R", + "description": "Portable FloatMap", + "exif": null, + "iptc": null, + "xmp": null, + "icc": null, + "other": "R PFM", + "read": true, + "write": false, + "create": false + }, + "pgf": { + "extension": "pgf", + "file type": [ + "PGF" + ], + "support": "R", + "description": "Progressive Graphics File", + "exif": null, + "iptc": null, + "xmp": null, + "icc": null, + "other": "R PGF PNG", + "read": true, + "write": false, + "create": false + }, + "pict": { + "extension": "pict", + "file type": [ + "PICT", + "PCT" + ], + "support": "R", + "description": "Apple Picture file", + "exif": null, + "iptc": null, + "xmp": null, + "icc": "R", + "other": "R PICT Photoshop", + "read": true, + "write": false, + "create": false + }, + "pct": { + "extension": "pct", + "file type": [ + "PICT", + "PCT" + ], + "support": "R", + "description": "Apple Picture file", + "exif": null, + "iptc": null, + "xmp": null, + "icc": "R", + "other": "R PICT Photoshop", + "read": true, + "write": false, + "create": false + }, + "plist": { + "extension": "plist", + "file type": [ + "PLIST" + ], + "support": "R", + "description": "Apple Property List (binary and XML formats)", + "exif": null, + "iptc": null, + "xmp": null, + "icc": null, + "other": "R PLIST", + "read": true, + "write": false, + "create": false + }, + "pmp": { + "extension": "pmp", + "file type": [ + "PMP" + ], + "support": "R", + "description": "Sony DSC-F1 Cyber-Shot image", + "exif": null, + "iptc": null, + "xmp": null, + "icc": null, + "other": "R Sony", + "read": true, + "write": false, + "create": false + }, + "png": { + "extension": "png", + "file type": [ + "PNG", + "JNG", + "MNG" + ], + "support": "R/W", + "description": "Portable/JPEG/Multiple-image Network Graphics", + "exif": "R/W/C", + "iptc": "R/W/C", + "xmp": "R/W/C", + "icc": "R/W/C", + "other": "R/W/C PNG", + "read": true, + "write": true, + "create": false + }, + "jng": { + "extension": "jng", + "file type": [ + "PNG", + "JNG", + "MNG" + ], + "support": "R/W", + "description": "Portable/JPEG/Multiple-image Network Graphics", + "exif": "R/W/C", + "iptc": "R/W/C", + "xmp": "R/W/C", + "icc": "R/W/C", + "other": "R/W/C PNG", + "read": true, + "write": true, + "create": false + }, + "mng": { + "extension": "mng", + "file type": [ + "PNG", + "JNG", + "MNG" + ], + "support": "R/W", + "description": "Portable/JPEG/Multiple-image Network Graphics", + "exif": "R/W/C", + "iptc": "R/W/C", + "xmp": "R/W/C", + "icc": "R/W/C", + "other": "R/W/C PNG", + "read": true, + "write": true, + "create": false + }, + "ppm": { + "extension": "ppm", + "file type": [ + "PPM", + "PBM", + "PGM" + ], + "support": "R/W", + "description": "Portable Pixel/Bit/Gray Map", + "exif": null, + "iptc": null, + "xmp": null, + "icc": null, + "other": "R PPM, R/W/C Comment", + "read": true, + "write": true, + "create": false + }, + "pbm": { + "extension": "pbm", + "file type": [ + "PPM", + "PBM", + "PGM" + ], + "support": "R/W", + "description": "Portable Pixel/Bit/Gray Map", + "exif": null, + "iptc": null, + "xmp": null, + "icc": null, + "other": "R PPM, R/W/C Comment", + "read": true, + "write": true, + "create": false + }, + "pgm": { + "extension": "pgm", + "file type": [ + "PPM", + "PBM", + "PGM" + ], + "support": "R/W", + "description": "Portable Pixel/Bit/Gray Map", + "exif": null, + "iptc": null, + "xmp": null, + "icc": null, + "other": "R PPM, R/W/C Comment", + "read": true, + "write": true, + "create": false + }, + "ppt": { + "extension": "ppt", + "file type": [ + "PPT", + "PPS", + "POT" + ], + "support": "R", + "description": "PowerPoint Presentation/Slideshow/Template (FPX-like)", + "exif": null, + "iptc": null, + "xmp": "R", + "icc": "R", + "other": "R FlashPix", + "read": true, + "write": false, + "create": false + }, + "pps": { + "extension": "pps", + "file type": [ + "PPT", + "PPS", + "POT" + ], + "support": "R", + "description": "PowerPoint Presentation/Slideshow/Template (FPX-like)", + "exif": null, + "iptc": null, + "xmp": "R", + "icc": "R", + "other": "R FlashPix", + "read": true, + "write": false, + "create": false + }, + "pot": { + "extension": "pot", + "file type": [ + "PPT", + "PPS", + "POT" + ], + "support": "R", + "description": "PowerPoint Presentation/Slideshow/Template (FPX-like)", + "exif": null, + "iptc": null, + "xmp": "R", + "icc": "R", + "other": "R FlashPix", + "read": true, + "write": false, + "create": false + }, + "potx": { + "extension": "potx", + "file type": [ + "POTX", + "POTM" + ], + "support": "R", + "description": "Office Open XML Presentation Template [Macro-enabled]", + "exif": null, + "iptc": null, + "xmp": null, + "icc": null, + "other": "R XML ZIP", + "read": true, + "write": false, + "create": false + }, + "potm": { + "extension": "potm", + "file type": [ + "POTX", + "POTM" + ], + "support": "R", + "description": "Office Open XML Presentation Template [Macro-enabled]", + "exif": null, + "iptc": null, + "xmp": null, + "icc": null, + "other": "R XML ZIP", + "read": true, + "write": false, + "create": false + }, + "ppax": { + "extension": "ppax", + "file type": [ + "PPAX", + "PPAM" + ], + "support": "R", + "description": "Office Open XML Presentation Addin [Macro-enabled]", + "exif": null, + "iptc": null, + "xmp": null, + "icc": null, + "other": "R XML ZIP", + "read": true, + "write": false, + "create": false + }, + "ppam": { + "extension": "ppam", + "file type": [ + "PPAX", + "PPAM" + ], + "support": "R", + "description": "Office Open XML Presentation Addin [Macro-enabled]", + "exif": null, + "iptc": null, + "xmp": null, + "icc": null, + "other": "R XML ZIP", + "read": true, + "write": false, + "create": false + }, + "ppsx": { + "extension": "ppsx", + "file type": [ + "PPSX", + "PPSM" + ], + "support": "R", + "description": "Office Open XML Presentation Slideshow [Macro-enabled]", + "exif": null, + "iptc": null, + "xmp": null, + "icc": null, + "other": "R XML ZIP", + "read": true, + "write": false, + "create": false + }, + "ppsm": { + "extension": "ppsm", + "file type": [ + "PPSX", + "PPSM" + ], + "support": "R", + "description": "Office Open XML Presentation Slideshow [Macro-enabled]", + "exif": null, + "iptc": null, + "xmp": null, + "icc": null, + "other": "R XML ZIP", + "read": true, + "write": false, + "create": false + }, + "pptx": { + "extension": "pptx", + "file type": [ + "PPTX", + "PPTM" + ], + "support": "R", + "description": "Office Open XML Presentation [Macro-enabled]", + "exif": null, + "iptc": null, + "xmp": null, + "icc": null, + "other": "R XML ZIP", + "read": true, + "write": false, + "create": false + }, + "pptm": { + "extension": "pptm", + "file type": [ + "PPTX", + "PPTM" + ], + "support": "R", + "description": "Office Open XML Presentation [Macro-enabled]", + "exif": null, + "iptc": null, + "xmp": null, + "icc": null, + "other": "R XML ZIP", + "read": true, + "write": false, + "create": false + }, + "psd": { + "extension": "psd", + "file type": [ + "PSD", + "PSB", + "PSDT" + ], + "support": "R/W", + "description": "PhotoShop Document / Large Document / Template", + "exif": "R/W/C", + "iptc": "R/W/C", + "xmp": "R/W/C", + "icc": "R/W/C", + "other": "R Photoshop", + "read": true, + "write": true, + "create": false + }, + "psb": { + "extension": "psb", + "file type": [ + "PSD", + "PSB", + "PSDT" + ], + "support": "R/W", + "description": "PhotoShop Document / Large Document / Template", + "exif": "R/W/C", + "iptc": "R/W/C", + "xmp": "R/W/C", + "icc": "R/W/C", + "other": "R Photoshop", + "read": true, + "write": true, + "create": false + }, + "psdt": { + "extension": "psdt", + "file type": [ + "PSD", + "PSB", + "PSDT" + ], + "support": "R/W", + "description": "PhotoShop Document / Large Document / Template", + "exif": "R/W/C", + "iptc": "R/W/C", + "xmp": "R/W/C", + "icc": "R/W/C", + "other": "R Photoshop", + "read": true, + "write": true, + "create": false + }, + "psp": { + "extension": "psp", + "file type": [ + "PSP", + "PSPIMAGE" + ], + "support": "R", + "description": "Paint Shop Pro", + "exif": "R", + "iptc": null, + "xmp": null, + "icc": null, + "other": "R PSP", + "read": true, + "write": false, + "create": false + }, + "pspimage": { + "extension": "pspimage", + "file type": [ + "PSP", + "PSPIMAGE" + ], + "support": "R", + "description": "Paint Shop Pro", + "exif": "R", + "iptc": null, + "xmp": null, + "icc": null, + "other": "R PSP", + "read": true, + "write": false, + "create": false + }, + "qtif": { + "extension": "qtif", + "file type": [ + "QTIF", + "QTI", + "QIF" + ], + "support": "R/W", + "description": "QuickTime Image File", + "exif": "R/W", + "iptc": "R/W", + "xmp": "R/W/C", + "icc": null, + "other": "R/W/C QuickTime", + "read": true, + "write": true, + "create": false + }, + "qti": { + "extension": "qti", + "file type": [ + "QTIF", + "QTI", + "QIF" + ], + "support": "R/W", + "description": "QuickTime Image File", + "exif": "R/W", + "iptc": "R/W", + "xmp": "R/W/C", + "icc": null, + "other": "R/W/C QuickTime", + "read": true, + "write": true, + "create": false + }, + "qif": { + "extension": "qif", + "file type": [ + "QTIF", + "QTI", + "QIF" + ], + "support": "R/W", + "description": "QuickTime Image File", + "exif": "R/W", + "iptc": "R/W", + "xmp": "R/W/C", + "icc": null, + "other": "R/W/C QuickTime", + "read": true, + "write": true, + "create": false + }, + "r3d": { + "extension": "r3d", + "file type": [ + "R3D" + ], + "support": "R", + "description": "Redcode RAW video", + "exif": null, + "iptc": null, + "xmp": null, + "icc": null, + "other": "R Red", + "read": true, + "write": false, + "create": false + }, + "ra": { + "extension": "ra", + "file type": [ + "RA" + ], + "support": "R", + "description": "Real Audio", + "exif": null, + "iptc": null, + "xmp": null, + "icc": null, + "other": "R Real ID3 Lyrics3", + "read": true, + "write": false, + "create": false + }, + "raf": { + "extension": "raf", + "file type": [ + "RAF" + ], + "support": "R/W", + "description": "FujiFilm RAW Format", + "exif": "R/W/C", + "iptc": "R/W/C", + "xmp": "R/W/C", + "icc": "R/W/C", + "other": "R/W FujiFilm", + "read": true, + "write": true, + "create": false + }, + "ram": { + "extension": "ram", + "file type": [ + "RAM", + "RPM" + ], + "support": "R", + "description": "Real Audio/Plug-in Metafile", + "exif": null, + "iptc": null, + "xmp": null, + "icc": null, + "other": "R Real", + "read": true, + "write": false, + "create": false + }, + "rpm": { + "extension": "rpm", + "file type": [ + "RAM", + "RPM" + ], + "support": "R", + "description": "Real Audio/Plug-in Metafile", + "exif": null, + "iptc": null, + "xmp": null, + "icc": null, + "other": "R Real", + "read": true, + "write": false, + "create": false + }, + "rar": { + "extension": "rar", + "file type": [ + "RAR" + ], + "support": "R", + "description": "RAR Archive", + "exif": null, + "iptc": null, + "xmp": null, + "icc": null, + "other": "R ZIP", + "read": true, + "write": false, + "create": false + }, + "raw": { + "extension": "raw", + "file type": [ + "RAW" + ], + "support": "R/W", + "description": "Panasonic RAW (TIFF-based)", + "exif": "R/W/C", + "iptc": "R/W/C", + "xmp": "R/W/C", + "icc": "R/W/C", + "other": "R/W PanasonicRaw Panasonic", + "read": true, + "write": true, + "create": false + }, + "riff": { + "extension": "riff", + "file type": [ + "RIFF", + "RIF" + ], + "support": "R", + "description": "Resource Interchange File Format", + "exif": "R", + "iptc": null, + "xmp": "R", + "icc": null, + "other": "R RIFF", + "read": true, + "write": false, + "create": false + }, + "rif": { + "extension": "rif", + "file type": [ + "RIFF", + "RIF" + ], + "support": "R", + "description": "Resource Interchange File Format", + "exif": "R", + "iptc": null, + "xmp": "R", + "icc": null, + "other": "R RIFF", + "read": true, + "write": false, + "create": false + }, + "rm": { + "extension": "rm", + "file type": [ + "RM", + "RV", + "RMVB" + ], + "support": "R", + "description": "Real Media/Video [Variable Bitrate]", + "exif": null, + "iptc": null, + "xmp": null, + "icc": null, + "other": "R Real", + "read": true, + "write": false, + "create": false + }, + "rv": { + "extension": "rv", + "file type": [ + "RM", + "RV", + "RMVB" + ], + "support": "R", + "description": "Real Media/Video [Variable Bitrate]", + "exif": null, + "iptc": null, + "xmp": null, + "icc": null, + "other": "R Real", + "read": true, + "write": false, + "create": false + }, + "rmvb": { + "extension": "rmvb", + "file type": [ + "RM", + "RV", + "RMVB" + ], + "support": "R", + "description": "Real Media/Video [Variable Bitrate]", + "exif": null, + "iptc": null, + "xmp": null, + "icc": null, + "other": "R Real", + "read": true, + "write": false, + "create": false + }, + "rsrc": { + "extension": "rsrc", + "file type": [ + "RSRC" + ], + "support": "R", + "description": "Mac OS Resource", + "exif": null, + "iptc": null, + "xmp": null, + "icc": null, + "other": "R RSRC Photoshop PostScript Font", + "read": true, + "write": false, + "create": false + }, + "rtf": { + "extension": "rtf", + "file type": [ + "RTF" + ], + "support": "R", + "description": "Rich Text Format", + "exif": null, + "iptc": null, + "xmp": null, + "icc": null, + "other": "R RTF", + "read": true, + "write": false, + "create": false + }, + "rw2": { + "extension": "rw2", + "file type": [ + "RW2" + ], + "support": "R/W", + "description": "Panasonic RAW 2 (TIFF-based)", + "exif": "R/W/C", + "iptc": "R/W/C", + "xmp": "R/W/C", + "icc": "R/W/C", + "other": "R/W PanasonicRaw Panasonic", + "read": true, + "write": true, + "create": false + }, + "rwl": { + "extension": "rwl", + "file type": [ + "RWL" + ], + "support": "R/W", + "description": "Leica RAW (TIFF-based)", + "exif": "R/W/C", + "iptc": "R/W/C", + "xmp": "R/W/C", + "icc": "R/W/C", + "other": "R/W PanasonicRaw Panasonic", + "read": true, + "write": true, + "create": false + }, + "rwz": { + "extension": "rwz", + "file type": [ + "RWZ" + ], + "support": "R", + "description": "Rawzor compressed image", + "exif": "R", + "iptc": "R", + "xmp": "R", + "icc": "R", + "other": "R Rawzor", + "read": true, + "write": false, + "create": false + }, + "seq": { + "extension": "seq", + "file type": [ + "SEQ" + ], + "support": "R", + "description": "FLIR Systems image Sequence", + "exif": null, + "iptc": null, + "xmp": null, + "icc": null, + "other": "R FLIR", + "read": true, + "write": false, + "create": false + }, + "sketch": { + "extension": "sketch", + "file type": [ + "SKETCH" + ], + "support": "R", + "description": "Sketch design file", + "exif": null, + "iptc": null, + "xmp": null, + "icc": null, + "other": "R JSON ZIP", + "read": true, + "write": false, + "create": false + }, + "so": { + "extension": "so", + "file type": [ + "SO" + ], + "support": "R", + "description": "Unix ELF executable and Shared Object files", + "exif": null, + "iptc": null, + "xmp": null, + "icc": null, + "other": "R EXE", + "read": true, + "write": false, + "create": false + }, + "sr2": { + "extension": "sr2", + "file type": [ + "SR2" + ], + "support": "R/W", + "description": "Sony RAW 2 (TIFF-based)", + "exif": "R/W/C", + "iptc": "R/W/C", + "xmp": "R/W/C", + "icc": "R/W/C", + "other": "R/W Sony", + "read": true, + "write": true, + "create": false + }, + "srf": { + "extension": "srf", + "file type": [ + "SRF" + ], + "support": "R", + "description": "Sony RAW Format (TIFF-based)", + "exif": "R", + "iptc": "R", + "xmp": "R", + "icc": "R", + "other": "R Sony", + "read": true, + "write": false, + "create": false + }, + "srw": { + "extension": "srw", + "file type": [ + "SRW" + ], + "support": "R/W", + "description": "Samsung RAW format (TIFF-based)", + "exif": "R/W/C", + "iptc": "R/W/C", + "xmp": "R/W/C", + "icc": "R/W/C", + "other": "R/W Samsung", + "read": true, + "write": true, + "create": false + }, + "svg": { + "extension": "svg", + "file type": [ + "SVG" + ], + "support": "R", + "description": "Scalable Vector Graphics (XML-based)", + "exif": null, + "iptc": null, + "xmp": null, + "icc": null, + "other": "R SVG", + "read": true, + "write": false, + "create": false + }, + "swf": { + "extension": "swf", + "file type": [ + "SWF" + ], + "support": "R", + "description": "Shockwave Flash", + "exif": null, + "iptc": null, + "xmp": "R", + "icc": null, + "other": "R Flash", + "read": true, + "write": false, + "create": false + }, + "thm": { + "extension": "thm", + "file type": [ + "THM" + ], + "support": "R/W", + "description": "Thumbnail image (JPEG)", + "exif": "R/W/C", + "iptc": "R/W/C", + "xmp": "R/W/C", + "icc": "R/W/C", + "other": "Supported JPEG Meta Information", + "read": true, + "write": true, + "create": false + }, + "thmx": { + "extension": "thmx", + "file type": [ + "THMX" + ], + "support": "R", + "description": "Office Open XML Theme", + "exif": null, + "iptc": null, + "xmp": null, + "icc": null, + "other": "R XML ZIP", + "read": true, + "write": false, + "create": false + }, + "tiff": { + "extension": "tiff", + "file type": [ + "TIFF", + "TIF" + ], + "support": "R/W", + "description": "Tagged Image File Format", + "exif": "R/W/C", + "iptc": "R/W/C", + "xmp": "R/W/C", + "icc": "R/W/C", + "other": "R/W/C GeoTIFF, R/W Trailers", + "read": true, + "write": true, + "create": false + }, + "tif": { + "extension": "tif", + "file type": [ + "TIFF", + "TIF" + ], + "support": "R/W", + "description": "Tagged Image File Format", + "exif": "R/W/C", + "iptc": "R/W/C", + "xmp": "R/W/C", + "icc": "R/W/C", + "other": "R/W/C GeoTIFF, R/W Trailers", + "read": true, + "write": true, + "create": false + }, + "ttf": { + "extension": "ttf", + "file type": [ + "TTF", + "TTC" + ], + "support": "R", + "description": "True Type Font/Collection", + "exif": null, + "iptc": null, + "xmp": null, + "icc": null, + "other": "R Font", + "read": true, + "write": false, + "create": false + }, + "ttc": { + "extension": "ttc", + "file type": [ + "TTF", + "TTC" + ], + "support": "R", + "description": "True Type Font/Collection", + "exif": null, + "iptc": null, + "xmp": null, + "icc": null, + "other": "R Font", + "read": true, + "write": false, + "create": false + }, + "torrent": { + "extension": "torrent", + "file type": [ + "TORRENT" + ], + "support": "R", + "description": "BitTorrent description file", + "exif": null, + "iptc": null, + "xmp": null, + "icc": null, + "other": "R Torrent", + "read": true, + "write": false, + "create": false + }, + "txt": { + "extension": "txt", + "file type": [ + "TXT" + ], + "support": "R", + "description": "Text files", + "exif": null, + "iptc": null, + "xmp": null, + "icc": null, + "other": "R Text", + "read": true, + "write": false, + "create": false + }, + "vcf": { + "extension": "vcf", + "file type": [ + "VCF", + "VCARD" + ], + "support": "R", + "description": "Virtual Card", + "exif": null, + "iptc": null, + "xmp": null, + "icc": null, + "other": "R VCard", + "read": true, + "write": false, + "create": false + }, + "vcard": { + "extension": "vcard", + "file type": [ + "VCF", + "VCARD" + ], + "support": "R", + "description": "Virtual Card", + "exif": null, + "iptc": null, + "xmp": null, + "icc": null, + "other": "R VCard", + "read": true, + "write": false, + "create": false + }, + "vob": { + "extension": "vob", + "file type": [ + "VOB" + ], + "support": "R", + "description": "Video Object (MPEG-based)", + "exif": null, + "iptc": null, + "xmp": null, + "icc": null, + "other": "R MPEG", + "read": true, + "write": false, + "create": false + }, + "vrd": { + "extension": "vrd", + "file type": [ + "VRD" + ], + "support": "R/W/C", + "description": "Canon DPP Recipe Data", + "exif": null, + "iptc": null, + "xmp": "R/W/C", + "icc": null, + "other": "R/W/C CanonVRD", + "read": true, + "write": true, + "create": true + }, + "vsd": { + "extension": "vsd", + "file type": [ + "VSD" + ], + "support": "R", + "description": "Microsoft Visio Drawing (FPX-like)", + "exif": null, + "iptc": null, + "xmp": "R", + "icc": "R", + "other": "R FlashPix", + "read": true, + "write": false, + "create": false + }, + "wav": { + "extension": "wav", + "file type": [ + "WAV" + ], + "support": "R", + "description": "Windows digital audio WAVeform (RIFF-based)", + "exif": "R", + "iptc": null, + "xmp": "R", + "icc": null, + "other": "R RIFF", + "read": true, + "write": false, + "create": false + }, + "webm": { + "extension": "webm", + "file type": [ + "WEBM" + ], + "support": "R", + "description": "Google Web Movie (Matroska-based)", + "exif": null, + "iptc": null, + "xmp": null, + "icc": null, + "other": "R Matroska", + "read": true, + "write": false, + "create": false + }, + "webp": { + "extension": "webp", + "file type": [ + "WEBP" + ], + "support": "R", + "description": "Google Web Picture (RIFF-based)", + "exif": "R", + "iptc": null, + "xmp": "R", + "icc": null, + "other": "R RIFF", + "read": true, + "write": false, + "create": false + }, + "wma": { + "extension": "wma", + "file type": [ + "WMA", + "WMV" + ], + "support": "R", + "description": "Windows Media Audio/Video (ASF-based)", + "exif": null, + "iptc": null, + "xmp": "R", + "icc": null, + "other": "R ASF", + "read": true, + "write": false, + "create": false + }, + "wmv": { + "extension": "wmv", + "file type": [ + "WMA", + "WMV" + ], + "support": "R", + "description": "Windows Media Audio/Video (ASF-based)", + "exif": null, + "iptc": null, + "xmp": "R", + "icc": null, + "other": "R ASF", + "read": true, + "write": false, + "create": false + }, + "wtv": { + "extension": "wtv", + "file type": [ + "WTV" + ], + "support": "R", + "description": "Windows recorded TV show", + "exif": null, + "iptc": null, + "xmp": null, + "icc": null, + "other": "R WTV", + "read": true, + "write": false, + "create": false + }, + "wv": { + "extension": "wv", + "file type": [ + "WV" + ], + "support": "R", + "description": "WavePack lossless audio (RIFF-based)", + "exif": "R", + "iptc": null, + "xmp": "R", + "icc": null, + "other": "R RIFF", + "read": true, + "write": false, + "create": false + }, + "x3f": { + "extension": "x3f", + "file type": [ + "X3F" + ], + "support": "R/W", + "description": "Sigma/Foveon RAW", + "exif": "R/W/C", + "iptc": "R/W/C", + "xmp": "R/W/C", + "icc": "R/W/C", + "other": "R/W Sigma, R SigmaRaw", + "read": true, + "write": true, + "create": false + }, + "xcf": { + "extension": "xcf", + "file type": [ + "XCF" + ], + "support": "R", + "description": "GIMP native image format", + "exif": "R", + "iptc": "R", + "xmp": "R", + "icc": "R", + "other": "R GIMP", + "read": true, + "write": false, + "create": false + }, + "xls": { + "extension": "xls", + "file type": [ + "XLS", + "XLT" + ], + "support": "R", + "description": "Microsoft Excel Spreadsheet/Template (FPX-like)", + "exif": null, + "iptc": null, + "xmp": "R", + "icc": "R", + "other": "R FlashPix", + "read": true, + "write": false, + "create": false + }, + "xlt": { + "extension": "xlt", + "file type": [ + "XLS", + "XLT" + ], + "support": "R", + "description": "Microsoft Excel Spreadsheet/Template (FPX-like)", + "exif": null, + "iptc": null, + "xmp": "R", + "icc": "R", + "other": "R FlashPix", + "read": true, + "write": false, + "create": false + }, + "xlsx": { + "extension": "xlsx", + "file type": [ + "XLSX", + "XLSM", + "XLSB" + ], + "support": "R", + "description": "Office Open XML Spreadsheet [Macro-enabled/Binary]", + "exif": null, + "iptc": null, + "xmp": null, + "icc": null, + "other": "R XML ZIP", + "read": true, + "write": false, + "create": false + }, + "xlsm": { + "extension": "xlsm", + "file type": [ + "XLSX", + "XLSM", + "XLSB" + ], + "support": "R", + "description": "Office Open XML Spreadsheet [Macro-enabled/Binary]", + "exif": null, + "iptc": null, + "xmp": null, + "icc": null, + "other": "R XML ZIP", + "read": true, + "write": false, + "create": false + }, + "xlsb": { + "extension": "xlsb", + "file type": [ + "XLSX", + "XLSM", + "XLSB" + ], + "support": "R", + "description": "Office Open XML Spreadsheet [Macro-enabled/Binary]", + "exif": null, + "iptc": null, + "xmp": null, + "icc": null, + "other": "R XML ZIP", + "read": true, + "write": false, + "create": false + }, + "xltx": { + "extension": "xltx", + "file type": [ + "XLTX", + "XLTM" + ], + "support": "R", + "description": "Office Open XML Spreadsheet Template [Macro-enabled]", + "exif": null, + "iptc": null, + "xmp": null, + "icc": null, + "other": "R XML ZIP", + "read": true, + "write": false, + "create": false + }, + "xltm": { + "extension": "xltm", + "file type": [ + "XLTX", + "XLTM" + ], + "support": "R", + "description": "Office Open XML Spreadsheet Template [Macro-enabled]", + "exif": null, + "iptc": null, + "xmp": null, + "icc": null, + "other": "R XML ZIP", + "read": true, + "write": false, + "create": false + }, + "xmp": { + "extension": "xmp", + "file type": [ + "XMP" + ], + "support": "R/W/C", + "description": "Extensible Metadata Platform sidecar file", + "exif": null, + "iptc": null, + "xmp": "R/W/C", + "icc": null, + "other": null, + "read": true, + "write": true, + "create": true + }, + "zip": { + "extension": "zip", + "file type": [ + "ZIP" + ], + "support": "R", + "description": "ZIP archive", + "exif": null, + "iptc": null, + "xmp": null, + "icc": null, + "other": "R ZIP", + "read": true, + "write": false, + "create": false + } +} \ No newline at end of file diff --git a/osxphotos/export_db.py b/osxphotos/export_db.py index d687d596..d7bb4166 100644 --- a/osxphotos/export_db.py +++ b/osxphotos/export_db.py @@ -10,13 +10,17 @@ import sys from abc import ABC, abstractmethod from io import StringIO from sqlite3 import Error +from typing import Union from ._constants import OSXPHOTOS_EXPORT_DB from ._version import __version__ +from .utils import normalize_fs_path __all__ = ["ExportDB_ABC", "ExportDBNoOp", "ExportDB", "ExportDBInMemory"] -OSXPHOTOS_EXPORTDB_VERSION = "4.2" +OSXPHOTOS_EXPORTDB_VERSION = "4.3" +OSXPHOTOS_EXPORTDB_VERSION_MIGRATE_FILEPATH = "4.3" + OSXPHOTOS_ABOUT_STRING = f"Created by osxphotos version {__version__} (https://github.com/RhetTbull/osxphotos) on {datetime.datetime.now()}" @@ -113,10 +117,10 @@ class ExportDB_ABC(ABC): ): pass + @abstractmethod def get_connection(self): pass - class ExportDBNoOp(ExportDB_ABC): """An ExportDB with NoOp methods""" @@ -198,7 +202,6 @@ class ExportDBNoOp(ExportDB_ABC): def get_connection(self): pass - class ExportDB(ExportDB_ABC): """Interface to sqlite3 database used to store state information for osxphotos export command""" @@ -217,12 +220,13 @@ class ExportDB(ExportDB_ABC): """query database for filename and return UUID returns None if filename not found in database """ - filename = str(pathlib.Path(filename).relative_to(self._path)).lower() + filepath_normalized = self._normalize_filepath_relative(filename) conn = self.get_connection() try: c = conn.cursor() c.execute( - "SELECT uuid FROM files WHERE filepath_normalized = ?", (filename,) + "SELECT uuid FROM files WHERE filepath_normalized = ?", + (filepath_normalized,), ) results = c.fetchone() uuid = results[0] if results else None @@ -234,7 +238,7 @@ class ExportDB(ExportDB_ABC): def set_uuid_for_file(self, filename, uuid): """set UUID of filename to uuid in the database""" filename = str(pathlib.Path(filename).relative_to(self._path)) - filename_normalized = filename.lower() + filename_normalized = self._normalize_filepath(filename) conn = self.get_connection() try: c = conn.cursor() @@ -251,7 +255,7 @@ class ExportDB(ExportDB_ABC): """set stat info for filename filename: filename to set the stat info for stat: a tuple of length 3: mode, size, mtime""" - filename = str(pathlib.Path(filename).relative_to(self._path)).lower() + filename = self._normalize_filepath_relative(filename) if len(stats) != 3: raise ValueError(f"expected 3 elements for stat, got {len(stats)}") @@ -272,7 +276,7 @@ class ExportDB(ExportDB_ABC): """get stat info for filename returns: tuple of (mode, size, mtime) """ - filename = str(pathlib.Path(filename).relative_to(self._path)).lower() + filename = self._normalize_filepath_relative(filename) conn = self.get_connection() try: c = conn.cursor() @@ -308,7 +312,7 @@ class ExportDB(ExportDB_ABC): """set stat info for filename (after exiftool has updated it) filename: filename to set the stat info for stat: a tuple of length 3: mode, size, mtime""" - filename = str(pathlib.Path(filename).relative_to(self._path)).lower() + filename = self._normalize_filepath_relative(filename) if len(stats) != 3: raise ValueError(f"expected 3 elements for stat, got {len(stats)}") @@ -329,7 +333,7 @@ class ExportDB(ExportDB_ABC): """get stat info for filename (after exiftool has updated it) returns: tuple of (mode, size, mtime) """ - filename = str(pathlib.Path(filename).relative_to(self._path)).lower() + filename = self._normalize_filepath_relative(filename) conn = self.get_connection() try: c = conn.cursor() @@ -390,7 +394,7 @@ class ExportDB(ExportDB_ABC): def get_exifdata_for_file(self, filename): """returns the exifdata JSON struct for a file""" - filename = str(pathlib.Path(filename).relative_to(self._path)).lower() + filename = self._normalize_filepath_relative(filename) conn = self.get_connection() try: c = conn.cursor() @@ -408,7 +412,7 @@ class ExportDB(ExportDB_ABC): def set_exifdata_for_file(self, filename, exifdata): """sets the exifdata JSON struct for a file""" - filename = str(pathlib.Path(filename).relative_to(self._path)).lower() + filename = self._normalize_filepath_relative(filename) conn = self.get_connection() try: c = conn.cursor() @@ -422,7 +426,7 @@ class ExportDB(ExportDB_ABC): def get_sidecar_for_file(self, filename): """returns the sidecar data and signature for a file""" - filename = str(pathlib.Path(filename).relative_to(self._path)).lower() + filename = self._normalize_filepath_relative(filename) conn = self.get_connection() try: c = conn.cursor() @@ -450,7 +454,7 @@ class ExportDB(ExportDB_ABC): def set_sidecar_for_file(self, filename, sidecar_data, sidecar_sig): """sets the sidecar data and signature for a file""" - filename = str(pathlib.Path(filename).relative_to(self._path)).lower() + filename = self._normalize_filepath_relative(filename) conn = self.get_connection() try: c = conn.cursor() @@ -521,7 +525,7 @@ class ExportDB(ExportDB_ABC): ): """sets all the data for file and uuid at once; if any value is None, does not set it""" filename = str(pathlib.Path(filename).relative_to(self._path)) - filename_normalized = filename.lower() + filename_normalized = self._normalize_filepath(filename) conn = self.get_connection() try: c = conn.cursor() @@ -590,7 +594,7 @@ class ExportDB(ExportDB_ABC): return self._conn def _set_stat_for_file(self, table, filename, stats): - filename = str(pathlib.Path(filename).relative_to(self._path)).lower() + filename = self._normalize_filepath_relative(filename) if len(stats) != 3: raise ValueError(f"expected 3 elements for stat, got {len(stats)}") @@ -603,7 +607,7 @@ class ExportDB(ExportDB_ABC): conn.commit() def _get_stat_for_file(self, table, filename): - filename = str(pathlib.Path(filename).relative_to(self._path)).lower() + filename = self._normalize_filepath_relative(filename) conn = self.get_connection() c = conn.cursor() c.execute( @@ -639,6 +643,8 @@ class ExportDB(ExportDB_ABC): version_info = self._get_database_version(conn) if version_info[1] < OSXPHOTOS_EXPORTDB_VERSION: self._create_db_tables(conn) + if version_info[1] < OSXPHOTOS_EXPORTDB_VERSION_MIGRATE_FILEPATH: + self._migrate_normalized_filepath(conn) self.was_upgraded = (version_info[1], OSXPHOTOS_EXPORTDB_VERSION) else: self.was_upgraded = () @@ -795,6 +801,32 @@ class ExportDB(ExportDB_ABC): except Error as e: logging.warning(e) + def _normalize_filepath(self, filepath: Union[str, pathlib.Path]) -> str: + """normalize filepath for unicode, lower case""" + return normalize_fs_path(str(filepath)).lower() + + def _normalize_filepath_relative(self, filepath: Union[str, pathlib.Path]) -> str: + """normalize filepath for unicode, relative path (to export dir), lower case""" + filepath = str(pathlib.Path(filepath).relative_to(self._path)) + return normalize_fs_path(str(filepath)).lower() + + def _migrate_normalized_filepath(self, conn): + """Fix all filepath_normalized columns for unicode normalization""" + # Prior to database version 4.3, filepath_normalized was not normalized for unicode + c = conn.cursor() + for table in ["converted", "edited", "exifdata", "files", "sidecar"]: + old_values = c.execute( + f"SELECT filepath_normalized, id FROM {table}" + ).fetchall() + new_values = [ + (self._normalize_filepath(filepath_normalized), id_) + for filepath_normalized, id_ in old_values + ] + c.executemany( + f"UPDATE {table} SET filepath_normalized=? WHERE id=?", new_values + ) + conn.commit() + class ExportDBInMemory(ExportDB): """In memory version of ExportDB diff --git a/osxphotos/path_utils.py b/osxphotos/path_utils.py index be990773..1fcbe7a4 100644 --- a/osxphotos/path_utils.py +++ b/osxphotos/path_utils.py @@ -1,14 +1,17 @@ """ utility functions for validating/sanitizing path components """ +import re + import pathvalidate from ._constants import MAX_DIRNAME_LEN, MAX_FILENAME_LEN __all__ = [ - "sanitize_filepath", "is_valid_filepath", - "sanitize_filename", "sanitize_dirname", + "sanitize_filename", + "sanitize_filepath", + "sanitize_filestem_with_count", "sanitize_pathpart", ] @@ -53,6 +56,26 @@ def sanitize_filename(filename, replacement=":"): return filename +def sanitize_filestem_with_count(file_stem: str, file_suffix: str) -> str: + """Sanitize a filestem that may end in (1), (2), etc. to ensure it + file_suffix doesn't exceed MAX_FILENAME_LEN""" + filename_len = len(file_stem) + len(file_suffix) + if filename_len <= MAX_FILENAME_LEN: + return file_stem + + drop = filename_len - MAX_FILENAME_LEN + match = re.match(r"(.*)(\(\d+\))$", file_stem) + if not match: + # filename doesn't end in (1), (2), etc. + # truncate filename to MAX_FILENAME_LEN + return file_stem[:-drop] + + # filename ends in (1), (2), etc. + file_stem = match.group(1) + file_count = match.group(2) + file_stem = file_stem[:-drop] + return f"{file_stem}{file_count}" + + def sanitize_dirname(dirname, replacement=":"): """replace any illegal characters in a directory name and truncate directory name if needed diff --git a/osxphotos/photoexporter.py b/osxphotos/photoexporter.py index a7d56a50..e4dc7be8 100644 --- a/osxphotos/photoexporter.py +++ b/osxphotos/photoexporter.py @@ -3,7 +3,6 @@ import dataclasses -import glob import hashlib import json import logging @@ -33,7 +32,7 @@ from ._constants import ( ) from ._version import __version__ from .datetime_utils import datetime_tz_to_utc -from .exiftool import ExifTool +from .exiftool import ExifTool, exiftool_can_write from .export_db import ExportDB_ABC, ExportDBNoOp from .fileutil import FileUtil from .photokit import ( @@ -45,7 +44,7 @@ from .photokit import ( ) from .phototemplate import RenderOptions from .uti import get_preferred_uti_extension -from .utils import increment_filename, increment_filename_with_count, lineno +from .utils import increment_filename, lineno, list_directory __all__ = [ "ExportError", @@ -536,7 +535,7 @@ class PhotoExporter: preview_name = ( preview_name if options.overwrite or options.update - else pathlib.Path(increment_filename(preview_name)) + else pathlib.Path(increment_filename(preview_name, lock=True)) ) all_results += self._export_photo( preview_path, @@ -549,6 +548,13 @@ class PhotoExporter: if options.touch_file: all_results += self._touch_files(all_results, options) + # if src was missing, there will be a lock file for dest that needs cleaning up + try: + lock_file = dest.parent / f".{dest.name}.lock" + self.fileutil.unlink(lock_file) + except Exception: + pass + return all_results def _touch_files( @@ -607,7 +613,9 @@ class PhotoExporter: # if file1.png exists and exporting file1.jpeg, # dest will be file1 (1).jpeg even though file1.jpeg doesn't exist to prevent sidecar collision if options.increment and not options.update and not options.overwrite: - return pathlib.Path(increment_filename(dest)) + return pathlib.Path( + increment_filename(dest, lock=True, dry_run=options.dry_run) + ) # if update and file exists, need to check to see if it's the write file by checking export db if options.update and dest.exists() and src: @@ -626,9 +634,13 @@ class PhotoExporter: ) if dest_uuid != self.photo.uuid: # not the right file, find the right one - glob_str = str(dest.parent / f"{dest.stem} (*{dest.suffix}") - # TODO: use the normalized code in utils - dest_files = glob.glob(glob_str) + # find files that match "dest_name (*.ext" (e.g. "dest_name (1).jpg", "dest_name (2).jpg)", ...) + dest_files = list_directory( + dest.parent, + startswith=f"{dest.stem} (", + endswith=dest.suffix, + include_path=True, + ) for file_ in dest_files: dest_uuid = export_db.get_uuid_for_file(file_) if dest_uuid == self.photo.uuid: @@ -646,7 +658,9 @@ class PhotoExporter: break else: # increment the destination file - dest = pathlib.Path(increment_filename(dest)) + dest = pathlib.Path( + increment_filename(dest, lock=True, dry_run=options.dry_run) + ) # either dest was updated in the if clause above or not updated at all return dest @@ -840,7 +854,9 @@ class PhotoExporter: raise ValueError("Edited version requested but photo has no adjustments") dest = self._temp_dir_path / self.photo.original_filename - dest = pathlib.Path(increment_filename(dest)) + dest = pathlib.Path( + increment_filename(dest, lock=True, dry_run=options.dry_run) + ) # export live_photo .mov file? live_photo = bool(options.live_photo and self.photo.live_photo) @@ -940,7 +956,7 @@ class PhotoExporter: """Copies filepath to a temp file preserving access and modification times""" filepath = pathlib.Path(filepath) dest = self._temp_dir_path / filepath.name - dest = increment_filename(dest) + dest = increment_filename(dest, lock=True) self.fileutil.copy(filepath, dest) stat = os.stat(filepath) self.fileutil.utime(dest, (stat.st_atime, stat.st_mtime)) @@ -1105,7 +1121,9 @@ class PhotoExporter: # convert to a temp file before copying tmp_file = increment_filename( self._temp_dir_path - / f"{pathlib.Path(src).stem}_converted_to_jpeg.jpeg" + / f"{pathlib.Path(src).stem}_converted_to_jpeg.jpeg", + lock=True, + dry_run=options.dry_run, ) fileutil.convert_to_jpeg( src, tmp_file, compression_quality=options.jpeg_quality @@ -1136,6 +1154,20 @@ class PhotoExporter: info_json=self.photo.json(), ) + # clean up lock files + for file_ in set( + converted_to_jpeg_files + + exported_files + + update_new_files + + update_updated_files + ): + try: + file_ = pathlib.Path(file_) + lock_file = str(file_.parent / f".{file_.name}.lock") + fileutil.unlink(lock_file) + except Exception: + pass + return ExportResults( converted_to_jpeg=converted_to_jpeg_files, error=exif_results.error, @@ -1284,11 +1316,27 @@ class PhotoExporter: exiftool_results = ExportResults() + # don't try to write if unsupported file type for exiftool + if not exiftool_can_write(os.path.splitext(src)[-1]): + exiftool_results.exiftool_warning.append( + ( + dest, + f"Unsupported file type for exiftool, skipping exiftool for {dest}", + ) + ) + # set file signature so the file doesn't get re-exported with --update + export_db.set_data( + dest, + uuid=self.photo.uuid, + exif_stat=fileutil.file_sig(src), + exif_json=self._exiftool_json_sidecar(options=options), + ) + return exiftool_results + # determine if we need to write the exif metadata # if we are not updating, we always write # else, need to check the database to determine if we need to write run_exiftool = not options.update - current_data = "foo" if options.update: files_are_different = False old_data = export_db.get_exifdata_for_file(dest) @@ -1856,7 +1904,7 @@ def _export_photo_uuid_applescript( raise ValueError(f"dest {dest} must be a directory") if not original ^ edited: - raise ValueError(f"edited or original must be True but not both") + raise ValueError("edited or original must be True but not both") tmpdir = tempfile.TemporaryDirectory(prefix="osxphotos_") @@ -1879,7 +1927,6 @@ def _export_photo_uuid_applescript( if not exported_files or not filename: # nothing got exported raise ExportError(f"Could not export photo {uuid} ({lineno(__file__)})") - # need to find actual filename as sometimes Photos renames JPG to jpeg on export # may be more than one file exported (e.g. if Live Photo, Photos exports both .jpeg and .mov) # TemporaryDirectory will cleanup on return diff --git a/osxphotos/photoinfo.py b/osxphotos/photoinfo.py index 9dd69934..cc077d12 100644 --- a/osxphotos/photoinfo.py +++ b/osxphotos/photoinfo.py @@ -54,7 +54,7 @@ from .scoreinfo import ScoreInfo from .searchinfo import SearchInfo from .text_detection import detect_text from .uti import get_preferred_uti_extension, get_uti_for_extension -from .utils import _debug, _get_resource_loc, findfiles +from .utils import _debug, _get_resource_loc, list_directory __all__ = ["PhotoInfo", "PhotoInfoNone"] @@ -369,7 +369,7 @@ class PhotoInfo: # In Photos 5, raw is in same folder as original but with _4.ext # Unless "Copy Items to the Photos Library" is not checked # then RAW image is not renamed but has same name is jpeg buth with raw extension - # Current implementation uses findfiles to find images with the correct raw UTI extension + # Current implementation finds images with the correct raw UTI extension # in same folder as the original and with same stem as original in form: original_stem*.raw_ext # TODO: I don't like this -- would prefer a more deterministic approach but until I have more # data on how Photos stores and retrieves RAW images, this seems to be working @@ -405,8 +405,7 @@ class PhotoInfo: # raw files have same name as original but with _4.raw_ext appended # I believe the _4 maps to PHAssetResourceTypeAlternatePhoto = 4 # see: https://developer.apple.com/documentation/photokit/phassetresourcetype/phassetresourcetypealternatephoto?language=objc - glob_str = f"{filestem}_4*" - raw_file = findfiles(glob_str, filepath) + raw_file = list_directory(filepath, startswith=f"{filestem}_4") if not raw_file: photopath = None else: diff --git a/osxphotos/photosdb/photosdb.py b/osxphotos/photosdb/photosdb.py index 3a6629b9..05764f3f 100644 --- a/osxphotos/photosdb/photosdb.py +++ b/osxphotos/photosdb/photosdb.py @@ -39,6 +39,7 @@ from .._constants import ( _PHOTOS_5_PROJECT_ALBUM_KIND, _PHOTOS_5_ROOT_FOLDER_KIND, _PHOTOS_5_SHARED_ALBUM_KIND, + _PHOTOS_5_VERSION, _TESTED_OS_VERSIONS, _UNKNOWN_PERSON, BURST_KEY, @@ -659,14 +660,18 @@ class PhotosDB: for person in c: pk = person[0] - fullname = person[2] if person[2] is not None else _UNKNOWN_PERSON + fullname = ( + normalize_unicode(person[2]) + if person[2] is not None + else _UNKNOWN_PERSON + ) self._dbpersons_pk[pk] = { "pk": pk, "uuid": person[1], "fullname": fullname, "facecount": person[3], "keyface": person[5], - "displayname": person[4], + "displayname": normalize_unicode(person[4]), "photo_uuid": None, "keyface_uuid": None, } @@ -733,13 +738,6 @@ class PhotosDB: except KeyError: self._dbfaces_pk[pk] = [uuid] - if _debug(): - logging.debug(f"Finished walking through persons") - logging.debug(pformat(self._dbpersons_pk)) - logging.debug(pformat(self._dbpersons_fullname)) - logging.debug(pformat(self._dbfaces_pk)) - logging.debug(pformat(self._dbfaces_uuid)) - # Get info on albums verbose("Processing albums.") c.execute( @@ -876,14 +874,6 @@ class PhotosDB: else: self._dbalbum_folders[album] = {} - if _debug(): - logging.debug(f"Finished walking through albums") - logging.debug(pformat(self._dbalbums_album)) - logging.debug(pformat(self._dbalbums_uuid)) - logging.debug(pformat(self._dbalbum_details)) - logging.debug(pformat(self._dbalbum_folders)) - logging.debug(pformat(self._dbfolder_details)) - # Get info on keywords verbose("Processing keywords.") c.execute( @@ -899,13 +889,16 @@ class PhotosDB: RKMaster.uuid = RKVersion.masterUuid """ ) - for keyword in c: - if not keyword[1] in self._dbkeywords_uuid: - self._dbkeywords_uuid[keyword[1]] = [] - if not keyword[0] in self._dbkeywords_keyword: - self._dbkeywords_keyword[keyword[0]] = [] - self._dbkeywords_uuid[keyword[1]].append(keyword[0]) - self._dbkeywords_keyword[keyword[0]].append(keyword[1]) + for keyword_title, keyword_uuid, _ in c: + keyword_title = normalize_unicode(keyword_title) + try: + self._dbkeywords_uuid[keyword_uuid].append(keyword_title) + except KeyError: + self._dbkeywords_uuid[keyword_uuid] = [keyword_title] + try: + self._dbkeywords_keyword[keyword_title].append(keyword_uuid) + except KeyError: + self._dbkeywords_keyword[keyword_title] = [keyword_uuid] # Get info on disk volumes c.execute("select RKVolume.modelId, RKVolume.name from RKVolume") @@ -1027,13 +1020,11 @@ class PhotosDB: for row in c: uuid = row[0] - if _debug(): - logging.debug(f"uuid = '{uuid}, master = '{row[2]}") self._dbphotos[uuid] = {} self._dbphotos[uuid]["_uuid"] = uuid # stored here for easier debugging self._dbphotos[uuid]["modelID"] = row[1] self._dbphotos[uuid]["masterUuid"] = row[2] - self._dbphotos[uuid]["filename"] = row[3] + self._dbphotos[uuid]["filename"] = normalize_unicode(row[3]) # There are sometimes negative values for lastmodifieddate in the database # I don't know what these mean but they will raise exception in datetime if @@ -1272,13 +1263,13 @@ class PhotosDB: info["volumeId"] = row[1] info["imagePath"] = row[2] info["isMissing"] = row[3] - info["originalFilename"] = row[4] + info["originalFilename"] = normalize_unicode(row[4]) info["UTI"] = row[5] info["modelID"] = row[6] info["fileSize"] = row[7] info["isTrulyRAW"] = row[8] info["alternateMasterUuid"] = row[9] - info["filename"] = row[10] + info["filename"] = normalize_unicode(row[10]) self._dbphotos_master[uuid] = info # get details needed to find path of the edited photos @@ -1550,39 +1541,6 @@ class PhotosDB: # done processing, dump debug data if requested verbose("Done processing details from Photos library.") - if _debug(): - logging.debug("Faces (_dbfaces_uuid):") - logging.debug(pformat(self._dbfaces_uuid)) - - logging.debug("Persons (_dbpersons_pk):") - logging.debug(pformat(self._dbpersons_pk)) - - logging.debug("Keywords by uuid (_dbkeywords_uuid):") - logging.debug(pformat(self._dbkeywords_uuid)) - - logging.debug("Keywords by keyword (_dbkeywords_keywords):") - logging.debug(pformat(self._dbkeywords_keyword)) - - logging.debug("Albums by uuid (_dbalbums_uuid):") - logging.debug(pformat(self._dbalbums_uuid)) - - logging.debug("Albums by album (_dbalbums_albums):") - logging.debug(pformat(self._dbalbums_album)) - - logging.debug("Album details (_dbalbum_details):") - logging.debug(pformat(self._dbalbum_details)) - - logging.debug("Album titles (_dbalbum_titles):") - logging.debug(pformat(self._dbalbum_titles)) - - logging.debug("Volumes (_dbvolumes):") - logging.debug(pformat(self._dbvolumes)) - - logging.debug("Photos (_dbphotos):") - logging.debug(pformat(self._dbphotos)) - - logging.debug("Burst Photos (dbphotos_burst:") - logging.debug(pformat(self._dbphotos_burst)) def _build_album_folder_hierarchy_4(self, uuid, folders=None): """recursively build folder/album hierarchy @@ -1673,7 +1631,7 @@ class PhotosDB: for person in c: pk = person[0] fullname = ( - person[2] + normalize_unicode(person[2]) if (person[2] != "" and person[2] is not None) else _UNKNOWN_PERSON ) @@ -1683,7 +1641,7 @@ class PhotosDB: "fullname": fullname, "facecount": person[3], "keyface": person[4], - "displayname": person[5], + "displayname": normalize_unicode(person[5]), "photo_uuid": None, "keyface_uuid": None, } @@ -1747,13 +1705,6 @@ class PhotosDB: except KeyError: self._dbfaces_pk[pk] = [uuid] - if _debug(): - logging.debug(f"Finished walking through persons") - logging.debug(pformat(self._dbpersons_pk)) - logging.debug(pformat(self._dbpersons_fullname)) - logging.debug(pformat(self._dbfaces_pk)) - logging.debug(pformat(self._dbfaces_uuid)) - # get details about albums verbose("Processing albums.") c.execute( @@ -1870,13 +1821,6 @@ class PhotosDB: # shared albums can't be in folders self._dbalbum_folders[album] = [] - if _debug(): - logging.debug(f"Finished walking through albums") - logging.debug(pformat(self._dbalbums_album)) - logging.debug(pformat(self._dbalbums_uuid)) - logging.debug(pformat(self._dbalbum_details)) - logging.debug(pformat(self._dbalbum_folders)) - # get details on keywords verbose("Processing keywords.") c.execute( @@ -1886,29 +1830,22 @@ class PhotosDB: JOIN Z_1KEYWORDS ON Z_1KEYWORDS.Z_1ASSETATTRIBUTES = ZADDITIONALASSETATTRIBUTES.Z_PK JOIN ZKEYWORD ON ZKEYWORD.Z_PK = {keyword_join} """ ) - for keyword in c: - keyword_title = normalize_unicode(keyword[0]) - if not keyword[1] in self._dbkeywords_uuid: - self._dbkeywords_uuid[keyword[1]] = [] - if not keyword_title in self._dbkeywords_keyword: - self._dbkeywords_keyword[keyword_title] = [] - self._dbkeywords_uuid[keyword[1]].append(keyword[0]) - self._dbkeywords_keyword[keyword_title].append(keyword[1]) - - if _debug(): - logging.debug(f"Finished walking through keywords") - logging.debug(pformat(self._dbkeywords_keyword)) - logging.debug(pformat(self._dbkeywords_uuid)) + for keyword_title, keyword_uuid in c: + keyword_title = normalize_unicode(keyword_title) + try: + self._dbkeywords_uuid[keyword_uuid].append(keyword_title) + except KeyError: + self._dbkeywords_uuid[keyword_uuid] = [keyword_title] + try: + self._dbkeywords_keyword[keyword_title].append(keyword_uuid) + except KeyError: + self._dbkeywords_keyword[keyword_title] = [keyword_uuid] # get details on disk volumes c.execute("SELECT ZUUID, ZNAME from ZFILESYSTEMVOLUME") for vol in c: self._dbvolumes[vol[0]] = vol[1] - if _debug(): - logging.debug(f"Finished walking through volumes") - logging.debug(self._dbvolumes) - # get details about photos verbose("Processing photo details.") c.execute( @@ -2042,8 +1979,8 @@ class PhotosDB: info["hidden"] = row[9] info["favorite"] = row[10] - info["originalFilename"] = row[3] - info["filename"] = row[12] + info["originalFilename"] = normalize_unicode(row[3]) + info["filename"] = normalize_unicode(row[12]) info["directory"] = row[11] # set latitude and longitude @@ -2521,48 +2458,6 @@ class PhotosDB: # done processing, dump debug data if requested verbose("Done processing details from Photos library.") - if _debug(): - logging.debug("Faces (_dbfaces_uuid):") - logging.debug(pformat(self._dbfaces_uuid)) - - logging.debug("Persons (_dbpersons_pk):") - logging.debug(pformat(self._dbpersons_pk)) - - logging.debug("Keywords by uuid (_dbkeywords_uuid):") - logging.debug(pformat(self._dbkeywords_uuid)) - - logging.debug("Keywords by keyword (_dbkeywords_keywords):") - logging.debug(pformat(self._dbkeywords_keyword)) - - logging.debug("Albums by uuid (_dbalbums_uuid):") - logging.debug(pformat(self._dbalbums_uuid)) - - logging.debug("Albums by album (_dbalbums_albums):") - logging.debug(pformat(self._dbalbums_album)) - - logging.debug("Album details (_dbalbum_details):") - logging.debug(pformat(self._dbalbum_details)) - - logging.debug("Album titles (_dbalbum_titles):") - logging.debug(pformat(self._dbalbum_titles)) - - logging.debug("Album folders (_dbalbum_folders):") - logging.debug(pformat(self._dbalbum_folders)) - - logging.debug("Album parent folders (_dbalbum_parent_folders):") - logging.debug(pformat(self._dbalbum_parent_folders)) - - logging.debug("Albums pk (_dbalbums_pk):") - logging.debug(pformat(self._dbalbums_pk)) - - logging.debug("Volumes (_dbvolumes):") - logging.debug(pformat(self._dbvolumes)) - - logging.debug("Photos (_dbphotos):") - logging.debug(pformat(self._dbphotos)) - - logging.debug("Burst Photos (dbphotos_burst:") - logging.debug(pformat(self._dbphotos_burst)) def _process_moments(self): """Process data from ZMOMENT table""" @@ -2623,8 +2518,8 @@ class PhotosDB: moment_info["modificationDate"] = row[6] moment_info["representativeDate"] = row[7] moment_info["startDate"] = row[8] - moment_info["subtitle"] = row[9] - moment_info["title"] = row[10] + moment_info["subtitle"] = normalize_unicode(row[9]) + moment_info["title"] = normalize_unicode(row[10]) moment_info["uuid"] = row[11] # if both lat/lon == -180, then it means location undefined @@ -3027,6 +2922,7 @@ class PhotosDB: if keywords: keyword_set = set() for keyword in keywords: + keyword = normalize_unicode(keyword) if keyword in self._dbkeywords_keyword: keyword_set.update(self._dbkeywords_keyword[keyword]) photos_sets.append(keyword_set) @@ -3034,6 +2930,7 @@ class PhotosDB: if persons: person_set = set() for person in persons: + person = normalize_unicode(person) if person in self._dbpersons_fullname: for pk in self._dbpersons_fullname[person]: try: @@ -3076,8 +2973,6 @@ class PhotosDB: ): info = PhotoInfo(db=self, uuid=p, info=self._dbphotos[p]) photoinfo.append(info) - if _debug: - logging.debug(f"photoinfo: {pformat(photoinfo)}") return photoinfo @@ -3414,23 +3309,35 @@ class PhotosDB: # case-insensitive for n in name: n = n.lower() - photo_list.extend( - [ - p - for p in photos - if n in p.filename.lower() - or n in p.original_filename.lower() - ] - ) + if self._db_version >= _PHOTOS_5_VERSION: + # search only original_filename (#594) + photo_list.extend( + [p for p in photos if n in p.original_filename.lower()] + ) + else: + photo_list.extend( + [ + p + for p in photos + if n in p.filename.lower() + or n in p.original_filename.lower() + ] + ) else: for n in name: - photo_list.extend( - [ - p - for p in photos - if n in p.filename or n in p.original_filename - ] - ) + if self._db_version >= _PHOTOS_5_VERSION: + # search only original_filename (#594) + photo_list.extend( + [p for p in photos if n in p.original_filename] + ) + else: + photo_list.extend( + [ + p + for p in photos + if n in p.filename or n in p.original_filename + ] + ) photos = photo_list if options.min_size: diff --git a/osxphotos/utils.py b/osxphotos/utils.py index de6ce78f..178bf528 100644 --- a/osxphotos/utils.py +++ b/osxphotos/utils.py @@ -17,18 +17,18 @@ import sys import unicodedata import urllib.parse from plistlib import load as plistload -from typing import Callable, List, Union +from typing import Callable, List, Optional, Union import CoreFoundation import objc from Foundation import NSFileManager, NSPredicate, NSString from ._constants import UNICODE_FORMAT +from .path_utils import sanitize_filestem_with_count __all__ = [ "dd_to_dms_str", "expand_and_validate_filepath", - "findfiles", "get_last_library_path", "get_system_library_path", "increment_filename_with_count", @@ -266,7 +266,9 @@ def list_photo_libraries(): # On older MacOS versions, mdfind appears to ignore some libraries # glob to find libraries in ~/Pictures then mdfind to find all the others # TODO: make this more robust - lib_list = glob.glob(f"{pathlib.Path.home()}/Pictures/*.photoslibrary") + lib_list = list_directory( + f"{pathlib.Path.home()}/Pictures/", glob="*.photoslibrary" + ) # On older OS, may not get all libraries so make sure we get the last one last_lib = get_last_library_path() @@ -290,27 +292,90 @@ def normalize_fs_path(path: str) -> str: return unicodedata.normalize("NFD", path) -def findfiles(pattern, path): - """Returns list of filenames from path matched by pattern - shell pattern. Matching is case-insensitive. - If 'path_' is invalid/doesn't exist, returns [].""" - if not os.path.isdir(path): +# def findfiles(pattern, path): +# """Returns list of filenames from path matched by pattern +# shell pattern. Matching is case-insensitive. +# If 'path_' is invalid/doesn't exist, returns [].""" +# if not os.path.isdir(path): +# return [] + +# # paths need to be normalized for unicode as filesystem returns unicode in NFD form +# pattern = normalize_fs_path(pattern) +# rule = re.compile(fnmatch.translate(pattern), re.IGNORECASE) +# files = os.listdir(path) +# return [name for name in files if rule.match(name)] + + +def list_directory( + directory: Union[str, pathlib.Path], + startswith: Optional[str] = None, + endswith: Optional[str] = None, + contains: Optional[str] = None, + glob: Optional[str] = None, + include_path: bool = False, + case_sensitive: bool = False, +) -> List[Union[str, pathlib.Path]]: + """List directory contents and return list of files or directories matching search criteria. + Accounts for case-insensitive filesystems, unicode filenames. directory can be a str or a pathlib.Path object. + + Args: + directory: directory to search + startswith: string to match at start of filename + endswith: string to match at end of filename + contains: string to match anywhere in filename + glob: shell-style glob pattern to match filename + include_path: if True, return full path to file + case_sensitive: if True, match case-sensitively + + Returns: List of files or directories matching search criteria as either str or pathlib.Path objects depending on the input type; + returns empty list if directory is invalid or doesn't exist. + + """ + is_pathlib = isinstance(directory, pathlib.Path) + if is_pathlib: + directory = str(directory) + + if not os.path.isdir(directory): return [] - # paths need to be normalized for unicode as filesystem returns unicode in NFD form - pattern = normalize_fs_path(pattern) - rule = re.compile(fnmatch.translate(pattern), re.IGNORECASE) - files = os.listdir(path) - return [name for name in files if rule.match(name)] + startswith = normalize_fs_path(startswith) if startswith else None + endswith = normalize_fs_path(endswith) if endswith else None + contains = normalize_fs_path(contains) if contains else None + glob = normalize_fs_path(glob) if glob else None + files = [normalize_fs_path(f) for f in os.listdir(directory)] + if not case_sensitive: + files_normalized = {f.lower(): f for f in files} + files = [f.lower() for f in files] + startswith = startswith.lower() if startswith else None + endswith = endswith.lower() if endswith else None + contains = contains.lower() if contains else None + glob = glob.lower() if glob else None + else: + files_normalized = {f: f for f in files} -def list_directory_startswith(directory_path: str, startswith: str) -> List[str]: - """List directory contents and return list of files starting with startswith; returns [] if directory doesn't exist""" - if not os.path.isdir(directory_path): - return [] - startswith = normalize_fs_path(startswith) - files = [normalize_fs_path(f) for f in os.listdir(directory_path)] - return [f for f in files if f.startswith(startswith)] + if startswith: + files = [f for f in files if f.startswith(startswith)] + if endswith: + endswith = normalize_fs_path(endswith) + files = [f for f in files if f.endswith(endswith)] + if contains: + contains = normalize_fs_path(contains) + files = [f for f in files if contains in f] + if glob: + glob = normalize_fs_path(glob) + flags = re.IGNORECASE if not case_sensitive else 0 + rule = re.compile(fnmatch.translate(glob), flags) + files = [f for f in files if rule.match(f)] + + files = [files_normalized[f] for f in files] + + if include_path: + files = [os.path.join(directory, f) for f in files] + if is_pathlib: + files = [pathlib.Path(f) for f in files] + + return files def _open_sql_file(dbname): @@ -364,7 +429,10 @@ def normalize_unicode(value): def increment_filename_with_count( - filepath: Union[str, pathlib.Path], count: int = 0 + filepath: Union[str, pathlib.Path], + count: int = 0, + lock: bool = False, + dry_run: bool = False, ) -> str: """Return filename (1).ext, etc if filename.ext exists @@ -374,6 +442,8 @@ def increment_filename_with_count( Args: filepath: str or pathlib.Path; full path, including file name count: int; starting increment value + lock: bool; if True, create a lock file in form .filename.lock to prevent other processes from using the same filename + dry_run: bool; if True, don't actually create lock file Returns: tuple of new filepath (or same if not incremented), count @@ -381,19 +451,36 @@ def increment_filename_with_count( Note: This obviously is subject to race condition so using with caution. """ dest = filepath if isinstance(filepath, pathlib.Path) else pathlib.Path(filepath) - dest_files = list_directory_startswith(str(dest.parent), dest.stem) - dest_files = [pathlib.Path(f).stem.lower() for f in dest_files] + dest_files = list_directory(dest.parent, startswith=dest.stem) + dest_files = [f.stem.lower() for f in dest_files] dest_new = f"{dest.stem} ({count})" if count else dest.stem dest_new = normalize_fs_path(dest_new) + dest_new = sanitize_filestem_with_count(dest_new, dest.suffix) + if lock and not dry_run: + dest_lock = "." + dest_new + dest.suffix + ".lock" + dest_lock = dest.parent / dest_lock + else: + dest_lock = pathlib.Path("") - while dest_new.lower() in dest_files: + while dest_new.lower() in dest_files or ( + lock and not dry_run and dest_lock.exists() + ): count += 1 dest_new = normalize_fs_path(f"{dest.stem} ({count})") + dest_new = sanitize_filestem_with_count(dest_new, dest.suffix) + if lock: + dest_lock = "." + dest_new + dest.suffix + ".lock" + dest_lock = dest.parent / dest_lock + if lock and not dry_run: + dest_lock.touch() dest = dest.parent / f"{dest_new}{dest.suffix}" + return normalize_fs_path(str(dest)), count -def increment_filename(filepath: Union[str, pathlib.Path]) -> str: +def increment_filename( + filepath: Union[str, pathlib.Path], lock: bool = False, dry_run: bool = False +) -> str: """Return filename (1).ext, etc if filename.ext exists If file exists in filename's parent folder with same stem as filename, @@ -401,13 +488,17 @@ def increment_filename(filepath: Union[str, pathlib.Path]) -> str: Args: filepath: str or pathlib.Path; full path, including file name + lock: bool; if True, creates a lock file in form .filename.lock to prevent other processes from using the same filename + dry_run: bool; if True, don't actually create lock file Returns: new filepath (or same if not incremented) - Note: This obviously is subject to race condition so using with caution. + Note: This obviously is subject to race condition so using with caution but using lock=True reduces the risk of race condition (but lock files must be cleaned up) """ - new_filepath, _ = increment_filename_with_count(filepath) + new_filepath, _ = increment_filename_with_count( + filepath, lock=lock, dry_run=dry_run + ) return new_filepath diff --git a/tests/Test-10.15.7.photoslibrary/database/Photos.sqlite b/tests/Test-10.15.7.photoslibrary/database/Photos.sqlite index ce3f03ab..4dca1b0a 100644 Binary files a/tests/Test-10.15.7.photoslibrary/database/Photos.sqlite and b/tests/Test-10.15.7.photoslibrary/database/Photos.sqlite differ diff --git a/tests/Test-10.15.7.photoslibrary/database/Photos.sqlite-shm b/tests/Test-10.15.7.photoslibrary/database/Photos.sqlite-shm index 29616ff3..129a6164 100644 Binary files a/tests/Test-10.15.7.photoslibrary/database/Photos.sqlite-shm and b/tests/Test-10.15.7.photoslibrary/database/Photos.sqlite-shm differ diff --git a/tests/Test-10.15.7.photoslibrary/database/Photos.sqlite-wal b/tests/Test-10.15.7.photoslibrary/database/Photos.sqlite-wal index d4f00e49..dcdd7af3 100644 Binary files a/tests/Test-10.15.7.photoslibrary/database/Photos.sqlite-wal and b/tests/Test-10.15.7.photoslibrary/database/Photos.sqlite-wal differ diff --git a/tests/Test-10.15.7.photoslibrary/database/Photos.sqlite.lock b/tests/Test-10.15.7.photoslibrary/database/Photos.sqlite.lock index c00c434a..e2de3e3a 100644 --- a/tests/Test-10.15.7.photoslibrary/database/Photos.sqlite.lock +++ b/tests/Test-10.15.7.photoslibrary/database/Photos.sqlite.lock @@ -7,7 +7,7 @@ hostuuid 585B80BF-8D1F-55EF-A9E8-6CF4E5523959 pid - 1961 + 14817 processname photolibraryd uid diff --git a/tests/Test-10.15.7.photoslibrary/database/search/psi.sqlite b/tests/Test-10.15.7.photoslibrary/database/search/psi.sqlite index 78aa537a..65423812 100644 Binary files a/tests/Test-10.15.7.photoslibrary/database/search/psi.sqlite and b/tests/Test-10.15.7.photoslibrary/database/search/psi.sqlite differ diff --git a/tests/Test-10.15.7.photoslibrary/database/search/synonymsProcess.plist b/tests/Test-10.15.7.photoslibrary/database/search/synonymsProcess.plist index f0fb2125..323e1886 100644 Binary files a/tests/Test-10.15.7.photoslibrary/database/search/synonymsProcess.plist and b/tests/Test-10.15.7.photoslibrary/database/search/synonymsProcess.plist differ diff --git a/tests/Test-10.15.7.photoslibrary/database/search/zeroKeywords.data b/tests/Test-10.15.7.photoslibrary/database/search/zeroKeywords.data index 300f6b77..df4ea157 100644 Binary files a/tests/Test-10.15.7.photoslibrary/database/search/zeroKeywords.data and b/tests/Test-10.15.7.photoslibrary/database/search/zeroKeywords.data differ diff --git a/tests/Test-10.15.7.photoslibrary/originals/2/2DFD33F1-A5D8-486F-A3A9-98C07995535A.jpeg b/tests/Test-10.15.7.photoslibrary/originals/2/2DFD33F1-A5D8-486F-A3A9-98C07995535A.jpeg new file mode 100644 index 00000000..829a2345 Binary files /dev/null and b/tests/Test-10.15.7.photoslibrary/originals/2/2DFD33F1-A5D8-486F-A3A9-98C07995535A.jpeg differ diff --git a/tests/Test-10.15.7.photoslibrary/originals/5/54E76FCB-D353-4557-9997-0A457BCB4D48.jpeg b/tests/Test-10.15.7.photoslibrary/originals/5/54E76FCB-D353-4557-9997-0A457BCB4D48.jpeg new file mode 100755 index 00000000..c1872812 Binary files /dev/null and b/tests/Test-10.15.7.photoslibrary/originals/5/54E76FCB-D353-4557-9997-0A457BCB4D48.jpeg differ diff --git a/tests/Test-10.15.7.photoslibrary/originals/7/7FD37B5F-6FAA-4DB1-8A29-BF9C37E38091.jpeg b/tests/Test-10.15.7.photoslibrary/originals/7/7FD37B5F-6FAA-4DB1-8A29-BF9C37E38091.jpeg new file mode 100644 index 00000000..9e7d8551 Binary files /dev/null and b/tests/Test-10.15.7.photoslibrary/originals/7/7FD37B5F-6FAA-4DB1-8A29-BF9C37E38091.jpeg differ diff --git a/tests/Test-10.15.7.photoslibrary/originals/F/F207D5DE-EFAD-4217-8424-0764AAC971D0.jpeg b/tests/Test-10.15.7.photoslibrary/originals/F/F207D5DE-EFAD-4217-8424-0764AAC971D0.jpeg new file mode 100755 index 00000000..c1872812 Binary files /dev/null and b/tests/Test-10.15.7.photoslibrary/originals/F/F207D5DE-EFAD-4217-8424-0764AAC971D0.jpeg differ diff --git a/tests/Test-10.15.7.photoslibrary/private/com.apple.photoanalysisd/caches/graph/CLSBusinessCategoryCache.AOI.sqlite-shm b/tests/Test-10.15.7.photoslibrary/private/com.apple.photoanalysisd/caches/graph/CLSBusinessCategoryCache.AOI.sqlite-shm index c5736d8e..64c4d4fd 100644 Binary files a/tests/Test-10.15.7.photoslibrary/private/com.apple.photoanalysisd/caches/graph/CLSBusinessCategoryCache.AOI.sqlite-shm and b/tests/Test-10.15.7.photoslibrary/private/com.apple.photoanalysisd/caches/graph/CLSBusinessCategoryCache.AOI.sqlite-shm differ diff --git a/tests/Test-10.15.7.photoslibrary/private/com.apple.photoanalysisd/caches/graph/CLSBusinessCategoryCache.AOI.sqlite-wal b/tests/Test-10.15.7.photoslibrary/private/com.apple.photoanalysisd/caches/graph/CLSBusinessCategoryCache.AOI.sqlite-wal index 1124e074..97359144 100644 Binary files a/tests/Test-10.15.7.photoslibrary/private/com.apple.photoanalysisd/caches/graph/CLSBusinessCategoryCache.AOI.sqlite-wal and b/tests/Test-10.15.7.photoslibrary/private/com.apple.photoanalysisd/caches/graph/CLSBusinessCategoryCache.AOI.sqlite-wal differ diff --git a/tests/Test-10.15.7.photoslibrary/private/com.apple.photoanalysisd/caches/graph/CLSBusinessCategoryCache.Nature.sqlite-shm b/tests/Test-10.15.7.photoslibrary/private/com.apple.photoanalysisd/caches/graph/CLSBusinessCategoryCache.Nature.sqlite-shm index a9189a4e..ac6a67a0 100644 Binary files a/tests/Test-10.15.7.photoslibrary/private/com.apple.photoanalysisd/caches/graph/CLSBusinessCategoryCache.Nature.sqlite-shm and b/tests/Test-10.15.7.photoslibrary/private/com.apple.photoanalysisd/caches/graph/CLSBusinessCategoryCache.Nature.sqlite-shm differ diff --git a/tests/Test-10.15.7.photoslibrary/private/com.apple.photoanalysisd/caches/graph/CLSBusinessCategoryCache.Nature.sqlite-wal b/tests/Test-10.15.7.photoslibrary/private/com.apple.photoanalysisd/caches/graph/CLSBusinessCategoryCache.Nature.sqlite-wal index fd36b436..0650dac6 100644 Binary files a/tests/Test-10.15.7.photoslibrary/private/com.apple.photoanalysisd/caches/graph/CLSBusinessCategoryCache.Nature.sqlite-wal and b/tests/Test-10.15.7.photoslibrary/private/com.apple.photoanalysisd/caches/graph/CLSBusinessCategoryCache.Nature.sqlite-wal differ diff --git a/tests/Test-10.15.7.photoslibrary/private/com.apple.photoanalysisd/caches/graph/CLSBusinessCategoryCache.POI.sqlite-shm b/tests/Test-10.15.7.photoslibrary/private/com.apple.photoanalysisd/caches/graph/CLSBusinessCategoryCache.POI.sqlite-shm index edb0e40a..61fa6759 100644 Binary files a/tests/Test-10.15.7.photoslibrary/private/com.apple.photoanalysisd/caches/graph/CLSBusinessCategoryCache.POI.sqlite-shm and b/tests/Test-10.15.7.photoslibrary/private/com.apple.photoanalysisd/caches/graph/CLSBusinessCategoryCache.POI.sqlite-shm differ diff --git a/tests/Test-10.15.7.photoslibrary/private/com.apple.photoanalysisd/caches/graph/CLSBusinessCategoryCache.POI.sqlite-wal b/tests/Test-10.15.7.photoslibrary/private/com.apple.photoanalysisd/caches/graph/CLSBusinessCategoryCache.POI.sqlite-wal index 78a067e7..1b79d3e7 100644 Binary files a/tests/Test-10.15.7.photoslibrary/private/com.apple.photoanalysisd/caches/graph/CLSBusinessCategoryCache.POI.sqlite-wal and b/tests/Test-10.15.7.photoslibrary/private/com.apple.photoanalysisd/caches/graph/CLSBusinessCategoryCache.POI.sqlite-wal differ diff --git a/tests/Test-10.15.7.photoslibrary/private/com.apple.photoanalysisd/caches/graph/CLSBusinessCategoryCache.ROI.sqlite b/tests/Test-10.15.7.photoslibrary/private/com.apple.photoanalysisd/caches/graph/CLSBusinessCategoryCache.ROI.sqlite index fdc12c59..25cfcb95 100644 Binary files a/tests/Test-10.15.7.photoslibrary/private/com.apple.photoanalysisd/caches/graph/CLSBusinessCategoryCache.ROI.sqlite and b/tests/Test-10.15.7.photoslibrary/private/com.apple.photoanalysisd/caches/graph/CLSBusinessCategoryCache.ROI.sqlite differ diff --git a/tests/Test-10.15.7.photoslibrary/private/com.apple.photoanalysisd/caches/graph/CLSBusinessCategoryCache.ROI.sqlite-shm b/tests/Test-10.15.7.photoslibrary/private/com.apple.photoanalysisd/caches/graph/CLSBusinessCategoryCache.ROI.sqlite-shm index ee090f06..6574c772 100644 Binary files a/tests/Test-10.15.7.photoslibrary/private/com.apple.photoanalysisd/caches/graph/CLSBusinessCategoryCache.ROI.sqlite-shm and b/tests/Test-10.15.7.photoslibrary/private/com.apple.photoanalysisd/caches/graph/CLSBusinessCategoryCache.ROI.sqlite-shm differ diff --git a/tests/Test-10.15.7.photoslibrary/private/com.apple.photoanalysisd/caches/graph/CLSBusinessCategoryCache.ROI.sqlite-wal b/tests/Test-10.15.7.photoslibrary/private/com.apple.photoanalysisd/caches/graph/CLSBusinessCategoryCache.ROI.sqlite-wal index 04e3d658..6702573f 100644 Binary files a/tests/Test-10.15.7.photoslibrary/private/com.apple.photoanalysisd/caches/graph/CLSBusinessCategoryCache.ROI.sqlite-wal and b/tests/Test-10.15.7.photoslibrary/private/com.apple.photoanalysisd/caches/graph/CLSBusinessCategoryCache.ROI.sqlite-wal differ diff --git a/tests/Test-10.15.7.photoslibrary/private/com.apple.photoanalysisd/caches/graph/CLSLocationCache.sqlite-shm b/tests/Test-10.15.7.photoslibrary/private/com.apple.photoanalysisd/caches/graph/CLSLocationCache.sqlite-shm index e4ad0376..70b9eb04 100644 Binary files a/tests/Test-10.15.7.photoslibrary/private/com.apple.photoanalysisd/caches/graph/CLSLocationCache.sqlite-shm and b/tests/Test-10.15.7.photoslibrary/private/com.apple.photoanalysisd/caches/graph/CLSLocationCache.sqlite-shm differ diff --git a/tests/Test-10.15.7.photoslibrary/private/com.apple.photoanalysisd/caches/graph/CLSLocationCache.sqlite-wal b/tests/Test-10.15.7.photoslibrary/private/com.apple.photoanalysisd/caches/graph/CLSLocationCache.sqlite-wal index e0c48886..5919b918 100644 Binary files a/tests/Test-10.15.7.photoslibrary/private/com.apple.photoanalysisd/caches/graph/CLSLocationCache.sqlite-wal and b/tests/Test-10.15.7.photoslibrary/private/com.apple.photoanalysisd/caches/graph/CLSLocationCache.sqlite-wal differ diff --git a/tests/Test-10.15.7.photoslibrary/private/com.apple.photoanalysisd/caches/graph/CLSPublicEventCache.sqlite-shm b/tests/Test-10.15.7.photoslibrary/private/com.apple.photoanalysisd/caches/graph/CLSPublicEventCache.sqlite-shm index 09ebb684..17c288fd 100644 Binary files a/tests/Test-10.15.7.photoslibrary/private/com.apple.photoanalysisd/caches/graph/CLSPublicEventCache.sqlite-shm and b/tests/Test-10.15.7.photoslibrary/private/com.apple.photoanalysisd/caches/graph/CLSPublicEventCache.sqlite-shm differ diff --git a/tests/Test-10.15.7.photoslibrary/private/com.apple.photoanalysisd/caches/graph/CLSPublicEventCache.sqlite-wal b/tests/Test-10.15.7.photoslibrary/private/com.apple.photoanalysisd/caches/graph/CLSPublicEventCache.sqlite-wal index 3bda03d0..ee8c45dc 100644 Binary files a/tests/Test-10.15.7.photoslibrary/private/com.apple.photoanalysisd/caches/graph/CLSPublicEventCache.sqlite-wal and b/tests/Test-10.15.7.photoslibrary/private/com.apple.photoanalysisd/caches/graph/CLSPublicEventCache.sqlite-wal differ diff --git a/tests/Test-10.15.7.photoslibrary/private/com.apple.photoanalysisd/caches/graph/PGCurationCache.sqlite.sqlite-shm b/tests/Test-10.15.7.photoslibrary/private/com.apple.photoanalysisd/caches/graph/PGCurationCache.sqlite.sqlite-shm index dd2aafdc..5c526d73 100644 Binary files a/tests/Test-10.15.7.photoslibrary/private/com.apple.photoanalysisd/caches/graph/PGCurationCache.sqlite.sqlite-shm and b/tests/Test-10.15.7.photoslibrary/private/com.apple.photoanalysisd/caches/graph/PGCurationCache.sqlite.sqlite-shm differ diff --git a/tests/Test-10.15.7.photoslibrary/private/com.apple.photoanalysisd/caches/graph/PGCurationCache.sqlite.sqlite-wal b/tests/Test-10.15.7.photoslibrary/private/com.apple.photoanalysisd/caches/graph/PGCurationCache.sqlite.sqlite-wal index d2d5067c..f990e1d4 100644 Binary files a/tests/Test-10.15.7.photoslibrary/private/com.apple.photoanalysisd/caches/graph/PGCurationCache.sqlite.sqlite-wal and b/tests/Test-10.15.7.photoslibrary/private/com.apple.photoanalysisd/caches/graph/PGCurationCache.sqlite.sqlite-wal differ diff --git a/tests/Test-10.15.7.photoslibrary/private/com.apple.photoanalysisd/caches/graph/PGSearchComputationCache.plist b/tests/Test-10.15.7.photoslibrary/private/com.apple.photoanalysisd/caches/graph/PGSearchComputationCache.plist index c554da1d..23c59f06 100644 Binary files a/tests/Test-10.15.7.photoslibrary/private/com.apple.photoanalysisd/caches/graph/PGSearchComputationCache.plist and b/tests/Test-10.15.7.photoslibrary/private/com.apple.photoanalysisd/caches/graph/PGSearchComputationCache.plist differ diff --git a/tests/Test-10.15.7.photoslibrary/private/com.apple.photoanalysisd/caches/graph/PhotoAnalysisServicePreferences.plist b/tests/Test-10.15.7.photoslibrary/private/com.apple.photoanalysisd/caches/graph/PhotoAnalysisServicePreferences.plist index 214a2a64..f336f895 100644 --- a/tests/Test-10.15.7.photoslibrary/private/com.apple.photoanalysisd/caches/graph/PhotoAnalysisServicePreferences.plist +++ b/tests/Test-10.15.7.photoslibrary/private/com.apple.photoanalysisd/caches/graph/PhotoAnalysisServicePreferences.plist @@ -3,24 +3,24 @@ BackgroundHighlightCollection - 2021-09-14T04:40:42Z + 2022-02-04T13:51:40Z BackgroundHighlightEnrichment - 2021-09-14T04:40:42Z + 2022-02-04T13:51:39Z BackgroundJobAssetRevGeocode - 2021-09-14T04:40:42Z + 2022-02-04T13:51:40Z BackgroundJobSearch - 2021-09-14T04:40:42Z + 2022-02-04T13:51:40Z BackgroundPeopleSuggestion - 2021-09-14T04:40:41Z + 2022-02-04T13:51:39Z BackgroundUserBehaviorProcessor - 2021-09-14T04:40:42Z + 2022-02-04T13:51:40Z PhotoAnalysisGraphLastBackgroundGraphConsistencyUpdateJobDateKey 2021-07-20T05:48:08Z PhotoAnalysisGraphLastBackgroundGraphRebuildJobDate 2021-07-20T05:47:59Z PhotoAnalysisGraphLastBackgroundMemoryGenerationJobDate - 2021-09-14T04:40:43Z + 2022-02-04T13:51:40Z SiriPortraitDonation - 2021-09-14T04:40:42Z + 2022-02-04T13:51:40Z diff --git a/tests/Test-10.15.7.photoslibrary/private/com.apple.photoanalysisd/caches/graph/PhotosGraph/photosgraph.kgdb b/tests/Test-10.15.7.photoslibrary/private/com.apple.photoanalysisd/caches/graph/PhotosGraph/photosgraph.kgdb index c5d533d6..422c4941 100644 Binary files a/tests/Test-10.15.7.photoslibrary/private/com.apple.photoanalysisd/caches/graph/PhotosGraph/photosgraph.kgdb and b/tests/Test-10.15.7.photoslibrary/private/com.apple.photoanalysisd/caches/graph/PhotosGraph/photosgraph.kgdb differ diff --git a/tests/Test-10.15.7.photoslibrary/private/com.apple.photoanalysisd/caches/graph/PhotosGraph/photosgraph.kgdb-shm b/tests/Test-10.15.7.photoslibrary/private/com.apple.photoanalysisd/caches/graph/PhotosGraph/photosgraph.kgdb-shm index fe9ac284..4179edd4 100644 Binary files a/tests/Test-10.15.7.photoslibrary/private/com.apple.photoanalysisd/caches/graph/PhotosGraph/photosgraph.kgdb-shm and b/tests/Test-10.15.7.photoslibrary/private/com.apple.photoanalysisd/caches/graph/PhotosGraph/photosgraph.kgdb-shm differ diff --git a/tests/Test-10.15.7.photoslibrary/private/com.apple.photoanalysisd/caches/graph/changetoken.plist b/tests/Test-10.15.7.photoslibrary/private/com.apple.photoanalysisd/caches/graph/changetoken.plist index 0a2b8cd8..b5ced738 100644 Binary files a/tests/Test-10.15.7.photoslibrary/private/com.apple.photoanalysisd/caches/graph/changetoken.plist and b/tests/Test-10.15.7.photoslibrary/private/com.apple.photoanalysisd/caches/graph/changetoken.plist differ diff --git a/tests/Test-10.15.7.photoslibrary/resources/derivatives/2/2DFD33F1-A5D8-486F-A3A9-98C07995535A_1_105_c.jpeg b/tests/Test-10.15.7.photoslibrary/resources/derivatives/2/2DFD33F1-A5D8-486F-A3A9-98C07995535A_1_105_c.jpeg new file mode 100644 index 00000000..ccbb2fd0 Binary files /dev/null and b/tests/Test-10.15.7.photoslibrary/resources/derivatives/2/2DFD33F1-A5D8-486F-A3A9-98C07995535A_1_105_c.jpeg differ diff --git a/tests/Test-10.15.7.photoslibrary/resources/derivatives/5/54E76FCB-D353-4557-9997-0A457BCB4D48_1_105_c.jpeg b/tests/Test-10.15.7.photoslibrary/resources/derivatives/5/54E76FCB-D353-4557-9997-0A457BCB4D48_1_105_c.jpeg new file mode 100644 index 00000000..82af4102 Binary files /dev/null and b/tests/Test-10.15.7.photoslibrary/resources/derivatives/5/54E76FCB-D353-4557-9997-0A457BCB4D48_1_105_c.jpeg differ diff --git a/tests/Test-10.15.7.photoslibrary/resources/derivatives/7/7FD37B5F-6FAA-4DB1-8A29-BF9C37E38091_1_105_c.jpeg b/tests/Test-10.15.7.photoslibrary/resources/derivatives/7/7FD37B5F-6FAA-4DB1-8A29-BF9C37E38091_1_105_c.jpeg new file mode 100644 index 00000000..ab91a194 Binary files /dev/null and b/tests/Test-10.15.7.photoslibrary/resources/derivatives/7/7FD37B5F-6FAA-4DB1-8A29-BF9C37E38091_1_105_c.jpeg differ diff --git a/tests/Test-10.15.7.photoslibrary/resources/derivatives/F/F207D5DE-EFAD-4217-8424-0764AAC971D0_1_105_c.jpeg b/tests/Test-10.15.7.photoslibrary/resources/derivatives/F/F207D5DE-EFAD-4217-8424-0764AAC971D0_1_105_c.jpeg new file mode 100644 index 00000000..82af4102 Binary files /dev/null and b/tests/Test-10.15.7.photoslibrary/resources/derivatives/F/F207D5DE-EFAD-4217-8424-0764AAC971D0_1_105_c.jpeg differ diff --git a/tests/Test-10.15.7.photoslibrary/resources/derivatives/masters/2/2DFD33F1-A5D8-486F-A3A9-98C07995535A_4_5005_c.jpeg b/tests/Test-10.15.7.photoslibrary/resources/derivatives/masters/2/2DFD33F1-A5D8-486F-A3A9-98C07995535A_4_5005_c.jpeg new file mode 100644 index 00000000..c48aa6ea Binary files /dev/null and b/tests/Test-10.15.7.photoslibrary/resources/derivatives/masters/2/2DFD33F1-A5D8-486F-A3A9-98C07995535A_4_5005_c.jpeg differ diff --git a/tests/Test-10.15.7.photoslibrary/resources/derivatives/masters/5/54E76FCB-D353-4557-9997-0A457BCB4D48_4_5005_c.jpeg b/tests/Test-10.15.7.photoslibrary/resources/derivatives/masters/5/54E76FCB-D353-4557-9997-0A457BCB4D48_4_5005_c.jpeg new file mode 100644 index 00000000..c41dcce5 Binary files /dev/null and b/tests/Test-10.15.7.photoslibrary/resources/derivatives/masters/5/54E76FCB-D353-4557-9997-0A457BCB4D48_4_5005_c.jpeg differ diff --git a/tests/Test-10.15.7.photoslibrary/resources/derivatives/masters/7/7FD37B5F-6FAA-4DB1-8A29-BF9C37E38091_4_5005_c.jpeg b/tests/Test-10.15.7.photoslibrary/resources/derivatives/masters/7/7FD37B5F-6FAA-4DB1-8A29-BF9C37E38091_4_5005_c.jpeg new file mode 100644 index 00000000..a47daf4e Binary files /dev/null and b/tests/Test-10.15.7.photoslibrary/resources/derivatives/masters/7/7FD37B5F-6FAA-4DB1-8A29-BF9C37E38091_4_5005_c.jpeg differ diff --git a/tests/Test-10.15.7.photoslibrary/resources/derivatives/masters/F/F207D5DE-EFAD-4217-8424-0764AAC971D0_4_5005_c.jpeg b/tests/Test-10.15.7.photoslibrary/resources/derivatives/masters/F/F207D5DE-EFAD-4217-8424-0764AAC971D0_4_5005_c.jpeg new file mode 100644 index 00000000..c41dcce5 Binary files /dev/null and b/tests/Test-10.15.7.photoslibrary/resources/derivatives/masters/F/F207D5DE-EFAD-4217-8424-0764AAC971D0_4_5005_c.jpeg differ diff --git a/tests/Test-10.15.7.photoslibrary/resources/derivatives/thumbs/3305.ithmb b/tests/Test-10.15.7.photoslibrary/resources/derivatives/thumbs/3305.ithmb index 3e4b7f87..a69c27aa 100644 Binary files a/tests/Test-10.15.7.photoslibrary/resources/derivatives/thumbs/3305.ithmb and b/tests/Test-10.15.7.photoslibrary/resources/derivatives/thumbs/3305.ithmb differ diff --git a/tests/Test-10.15.7.photoslibrary/resources/derivatives/thumbs/4031.ithmb b/tests/Test-10.15.7.photoslibrary/resources/derivatives/thumbs/4031.ithmb index 5c75959b..aac3b1a9 100644 Binary files a/tests/Test-10.15.7.photoslibrary/resources/derivatives/thumbs/4031.ithmb and b/tests/Test-10.15.7.photoslibrary/resources/derivatives/thumbs/4031.ithmb differ diff --git a/tests/Test-10.15.7.photoslibrary/resources/derivatives/thumbs/4132.ithmb b/tests/Test-10.15.7.photoslibrary/resources/derivatives/thumbs/4132.ithmb index e9d6d0ca..9de8caed 100644 Binary files a/tests/Test-10.15.7.photoslibrary/resources/derivatives/thumbs/4132.ithmb and b/tests/Test-10.15.7.photoslibrary/resources/derivatives/thumbs/4132.ithmb differ diff --git a/tests/Test-10.15.7.photoslibrary/resources/journals/Album-change.plj b/tests/Test-10.15.7.photoslibrary/resources/journals/Album-change.plj index ae920f99..cdae3873 100644 Binary files a/tests/Test-10.15.7.photoslibrary/resources/journals/Album-change.plj and b/tests/Test-10.15.7.photoslibrary/resources/journals/Album-change.plj differ diff --git a/tests/Test-10.15.7.photoslibrary/resources/journals/Asset-change.plj b/tests/Test-10.15.7.photoslibrary/resources/journals/Asset-change.plj index 5e159dcb..320229f1 100644 Binary files a/tests/Test-10.15.7.photoslibrary/resources/journals/Asset-change.plj and b/tests/Test-10.15.7.photoslibrary/resources/journals/Asset-change.plj differ diff --git a/tests/Test-10.15.7.photoslibrary/resources/journals/Folder-change.plj b/tests/Test-10.15.7.photoslibrary/resources/journals/Folder-change.plj index e6cc5175..843d7d5a 100644 Binary files a/tests/Test-10.15.7.photoslibrary/resources/journals/Folder-change.plj and b/tests/Test-10.15.7.photoslibrary/resources/journals/Folder-change.plj differ diff --git a/tests/Test-10.15.7.photoslibrary/resources/journals/HistoryToken.plist b/tests/Test-10.15.7.photoslibrary/resources/journals/HistoryToken.plist index 38f116a9..fccc57d4 100644 Binary files a/tests/Test-10.15.7.photoslibrary/resources/journals/HistoryToken.plist and b/tests/Test-10.15.7.photoslibrary/resources/journals/HistoryToken.plist differ diff --git a/tests/Test-10.15.7.photoslibrary/resources/journals/ImportSession-change.plj b/tests/Test-10.15.7.photoslibrary/resources/journals/ImportSession-change.plj index ff61b44b..84ea6638 100644 Binary files a/tests/Test-10.15.7.photoslibrary/resources/journals/ImportSession-change.plj and b/tests/Test-10.15.7.photoslibrary/resources/journals/ImportSession-change.plj differ diff --git a/tests/search_info_test_data_10_15_7.json b/tests/search_info_test_data_10_15_7.json index c8c1e62f..a7a72781 100644 --- a/tests/search_info_test_data_10_15_7.json +++ b/tests/search_info_test_data_10_15_7.json @@ -1 +1 @@ -{"UUID_SEARCH_INFO": {"DC09F4D8-6173-452D-AC15-725C8D7C185E": {"labels": ["Cloudy", "Ocean", "Water Body", "Water", "Sky", "Sunset Sunrise", "Outdoor", "Land", "Beach"], "place_names": [], "streets": [], "neighborhoods": [], "city": "", "locality_names": [], "state": "", "state_abbreviation": "", "country": "", "bodies_of_water": [], "month": "July", "year": "2017", "holidays": ["Independence Day"], "activities": ["Travel", "Celebration", "Holiday", "Trip"], "season": "Summer", "venues": [], "venue_types": [], "media_types": []}, "AFECD4AB-937C-46AF-A79B-9C9A38AA42B1": {"labels": ["Clothing", "People", "Adult"], "place_names": [], "streets": ["Beach Blvd S"], "neighborhoods": ["Washington", "Beach Boulevard"], "city": "Orange County Area", "locality_names": ["Huntington Beach"], "state": "California", "state_abbreviation": "", "country": "United States", "bodies_of_water": [], "month": "February", "year": "2021", "holidays": [], "activities": ["Dining", "Lunch"], "season": "Winter", "venues": [], "venue_types": [], "media_types": []}, "A1C36260-92CD-47E2-927A-35DAF16D7882": {"labels": ["Car", "Adult", "Cloudy", "Retriever", "People", "Land", "Grass", "Dog", "Outdoor", "Sky", "Animal", "Mammal", "Canine", "Machine", "Vehicle", "Automobile", "Plant", "Shrub"], "place_names": [], "streets": ["Orange Grove Rd"], "neighborhoods": [], "city": "Orange", "locality_names": ["Hillsborough"], "state": "Piedmont Region", "state_abbreviation": "NC", "country": "United States", "bodies_of_water": [], "month": "June", "year": "2019", "holidays": [], "activities": ["Trip", "Travel"], "season": "Summer", "venues": [], "venue_types": [], "media_types": []}}, "UUID_SEARCH_INFO_NORMALIZED": {"DC09F4D8-6173-452D-AC15-725C8D7C185E": {"labels": ["cloudy", "ocean", "water body", "water", "sky", "sunset sunrise", "outdoor", "land", "beach"], "place_names": [], "streets": [], "neighborhoods": [], "city": "", "locality_names": [], "state": "", "state_abbreviation": "", "country": "", "bodies_of_water": [], "month": "july", "year": "2017", "holidays": ["independence day"], "activities": ["travel", "celebration", "holiday", "trip"], "season": "summer", "venues": [], "venue_types": [], "media_types": []}, "AFECD4AB-937C-46AF-A79B-9C9A38AA42B1": {"labels": ["clothing", "people", "adult"], "place_names": [], "streets": ["beach blvd s"], "neighborhoods": ["washington", "beach boulevard"], "city": "orange county area", "locality_names": ["huntington beach"], "state": "california", "state_abbreviation": "", "country": "united states", "bodies_of_water": [], "month": "february", "year": "2021", "holidays": [], "activities": ["dining", "lunch"], "season": "winter", "venues": [], "venue_types": [], "media_types": []}, "A1C36260-92CD-47E2-927A-35DAF16D7882": {"labels": ["car", "adult", "cloudy", "retriever", "people", "land", "grass", "dog", "outdoor", "sky", "animal", "mammal", "canine", "machine", "vehicle", "automobile", "plant", "shrub"], "place_names": [], "streets": ["orange grove rd"], "neighborhoods": [], "city": "orange", "locality_names": ["hillsborough"], "state": "piedmont region", "state_abbreviation": "nc", "country": "united states", "bodies_of_water": [], "month": "june", "year": "2019", "holidays": [], "activities": ["trip", "travel"], "season": "summer", "venues": [], "venue_types": [], "media_types": []}}, "UUID_SEARCH_INFO_ALL": {"DC09F4D8-6173-452D-AC15-725C8D7C185E": ["Cloudy", "Ocean", "Water Body", "Water", "Sky", "Sunset Sunrise", "Outdoor", "Land", "Beach", "Independence Day", "Travel", "Celebration", "Holiday", "Trip", "July", "2017", "Summer"], "AFECD4AB-937C-46AF-A79B-9C9A38AA42B1": ["Clothing", "People", "Adult", "Beach Blvd S", "Washington", "Beach Boulevard", "Huntington Beach", "Dining", "Lunch", "Orange County Area", "California", "United States", "February", "2021", "Winter"], "A1C36260-92CD-47E2-927A-35DAF16D7882": ["Car", "Adult", "Cloudy", "Retriever", "People", "Land", "Grass", "Dog", "Outdoor", "Sky", "Animal", "Mammal", "Canine", "Machine", "Vehicle", "Automobile", "Plant", "Shrub", "Orange Grove Rd", "Hillsborough", "Trip", "Travel", "Orange", "Piedmont Region", "NC", "United States", "June", "2019", "Summer"]}, "UUID_SEARCH_INFO_ALL_NORMALIZED": {"DC09F4D8-6173-452D-AC15-725C8D7C185E": ["cloudy", "ocean", "water body", "water", "sky", "sunset sunrise", "outdoor", "land", "beach", "independence day", "travel", "celebration", "holiday", "trip", "july", "2017", "summer"], "AFECD4AB-937C-46AF-A79B-9C9A38AA42B1": ["clothing", "people", "adult", "beach blvd s", "washington", "beach boulevard", "huntington beach", "dining", "lunch", "orange county area", "california", "united states", "february", "2021", "winter"], "A1C36260-92CD-47E2-927A-35DAF16D7882": ["car", "adult", "cloudy", "retriever", "people", "land", "grass", "dog", "outdoor", "sky", "animal", "mammal", "canine", "machine", "vehicle", "automobile", "plant", "shrub", "orange grove rd", "hillsborough", "trip", "travel", "orange", "piedmont region", "nc", "united states", "june", "2019", "summer"]}} +{"UUID_SEARCH_INFO": {"DC09F4D8-6173-452D-AC15-725C8D7C185E": {"labels": [], "place_names": [], "streets": [], "neighborhoods": [], "city": "", "locality_names": [], "state": "", "state_abbreviation": "", "country": "", "bodies_of_water": [], "month": "July", "year": "2017", "holidays": ["Independence Day"], "activities": ["Travel", "Celebration", "Holiday", "Trip"], "season": "Summer", "venues": [], "venue_types": [], "media_types": []}, "AFECD4AB-937C-46AF-A79B-9C9A38AA42B1": {"labels": [], "place_names": [], "streets": ["Beach Blvd S"], "neighborhoods": ["Washington", "Beach Boulevard"], "city": "Orange County Area", "locality_names": ["Huntington Beach"], "state": "California", "state_abbreviation": "", "country": "United States", "bodies_of_water": [], "month": "February", "year": "2021", "holidays": [], "activities": [], "season": "Winter", "venues": [], "venue_types": [], "media_types": []}, "A1C36260-92CD-47E2-927A-35DAF16D7882": {"labels": [], "place_names": [], "streets": ["Orange Grove Rd"], "neighborhoods": [], "city": "Orange County", "locality_names": ["Hillsborough"], "state": "Piedmont Region", "state_abbreviation": "NC", "country": "United States", "bodies_of_water": [], "month": "June", "year": "2019", "holidays": [], "activities": ["Trip", "Travel"], "season": "Summer", "venues": [], "venue_types": [], "media_types": []}}, "UUID_SEARCH_INFO_NORMALIZED": {"DC09F4D8-6173-452D-AC15-725C8D7C185E": {"labels": [], "place_names": [], "streets": [], "neighborhoods": [], "city": "", "locality_names": [], "state": "", "state_abbreviation": "", "country": "", "bodies_of_water": [], "month": "july", "year": "2017", "holidays": ["independence day"], "activities": ["travel", "celebration", "holiday", "trip"], "season": "summer", "venues": [], "venue_types": [], "media_types": []}, "AFECD4AB-937C-46AF-A79B-9C9A38AA42B1": {"labels": [], "place_names": [], "streets": ["beach blvd s"], "neighborhoods": ["washington", "beach boulevard"], "city": "orange county area", "locality_names": ["huntington beach"], "state": "california", "state_abbreviation": "", "country": "united states", "bodies_of_water": [], "month": "february", "year": "2021", "holidays": [], "activities": [], "season": "winter", "venues": [], "venue_types": [], "media_types": []}, "A1C36260-92CD-47E2-927A-35DAF16D7882": {"labels": [], "place_names": [], "streets": ["orange grove rd"], "neighborhoods": [], "city": "orange county", "locality_names": ["hillsborough"], "state": "piedmont region", "state_abbreviation": "nc", "country": "united states", "bodies_of_water": [], "month": "june", "year": "2019", "holidays": [], "activities": ["trip", "travel"], "season": "summer", "venues": [], "venue_types": [], "media_types": []}}, "UUID_SEARCH_INFO_ALL": {"DC09F4D8-6173-452D-AC15-725C8D7C185E": ["Independence Day", "Travel", "Celebration", "Holiday", "Trip", "July", "2017", "Summer"], "AFECD4AB-937C-46AF-A79B-9C9A38AA42B1": ["Beach Blvd S", "Washington", "Beach Boulevard", "Huntington Beach", "Orange County Area", "California", "United States", "February", "2021", "Winter"], "A1C36260-92CD-47E2-927A-35DAF16D7882": ["Orange Grove Rd", "Hillsborough", "Trip", "Travel", "Orange County", "Piedmont Region", "NC", "United States", "June", "2019", "Summer"]}, "UUID_SEARCH_INFO_ALL_NORMALIZED": {"DC09F4D8-6173-452D-AC15-725C8D7C185E": ["independence day", "travel", "celebration", "holiday", "trip", "july", "2017", "summer"], "AFECD4AB-937C-46AF-A79B-9C9A38AA42B1": ["beach blvd s", "washington", "beach boulevard", "huntington beach", "orange county area", "california", "united states", "february", "2021", "winter"], "A1C36260-92CD-47E2-927A-35DAF16D7882": ["orange grove rd", "hillsborough", "trip", "travel", "orange county", "piedmont region", "nc", "united states", "june", "2019", "summer"]}} diff --git a/tests/test_albums_folders_catalina_10_15_7.py b/tests/test_albums_folders_catalina_10_15_7.py index 1bdc2894..5f3e3cba 100644 --- a/tests/test_albums_folders_catalina_10_15_7.py +++ b/tests/test_albums_folders_catalina_10_15_7.py @@ -21,6 +21,7 @@ FOLDER_ALBUM_DICT = { ALBUM_NAMES = [ "2018-10 - Sponsion, Museum, Frühstück, Römermuseum", "2019-10/11 Paris Clermont", + "Água", "AlbumInFolder", "EmptyAlbum", "I have a deleted twin", @@ -38,6 +39,7 @@ ALBUM_NAMES = [ ALBUM_PARENT_DICT = { "2018-10 - Sponsion, Museum, Frühstück, Römermuseum": None, "2019-10/11 Paris Clermont": None, + "Água": None, "AlbumInFolder": "SubFolder2", "EmptyAlbum": None, "I have a deleted twin": None, @@ -54,6 +56,7 @@ ALBUM_PARENT_DICT = { ALBUM_FOLDER_NAMES_DICT = { "2018-10 - Sponsion, Museum, Frühstück, Römermuseum": [], "2019-10/11 Paris Clermont": [], + "Água": [], "AlbumInFolder": ["Folder1", "SubFolder2"], "EmptyAlbum": [], "I have a deleted twin": [], @@ -70,6 +73,7 @@ ALBUM_FOLDER_NAMES_DICT = { ALBUM_LEN_DICT = { "2018-10 - Sponsion, Museum, Frühstück, Römermuseum": 1, "2019-10/11 Paris Clermont": 1, + "Água": 3, "AlbumInFolder": 2, "EmptyAlbum": 0, "I have a deleted twin": 1, @@ -103,6 +107,11 @@ ALBUM_PHOTO_UUID_DICT = { "4D521201-92AC-43E5-8F7C-59BC41C37A96", "8E1D7BC9-9321-44F9-8CFB-4083F6B9232A", ], + "Água": [ + "7FD37B5F-6FAA-4DB1-8A29-BF9C37E38091", + "2DFD33F1-A5D8-486F-A3A9-98C07995535A", + "54E76FCB-D353-4557-9997-0A457BCB4D48", + ], } UUID_DICT = { diff --git a/tests/test_catalina_10_15_7.py b/tests/test_catalina_10_15_7.py index f1c0c235..f11614bb 100644 --- a/tests/test_catalina_10_15_7.py +++ b/tests/test_catalina_10_15_7.py @@ -24,10 +24,10 @@ PHOTOS_DB = "tests/Test-10.15.7.photoslibrary/database/photos.db" PHOTOS_DB_PATH = "/Test-10.15.7.photoslibrary/database/photos.db" PHOTOS_LIBRARY_PATH = "/Test-10.15.7.photoslibrary" -PHOTOS_DB_LEN = 25 -PHOTOS_NOT_IN_TRASH_LEN = 23 +PHOTOS_DB_LEN = 29 +PHOTOS_NOT_IN_TRASH_LEN = 27 PHOTOS_IN_TRASH_LEN = 2 -PHOTOS_DB_IMPORT_SESSIONS = 17 +PHOTOS_DB_IMPORT_SESSIONS = 21 KEYWORDS = [ "Kids", @@ -72,6 +72,7 @@ ALBUMS = [ "Sorted Oldest First", "Sorted Title", "Test Album", # there are 2 albums named "Test Album" for testing duplicate album names + "Água", ] KEYWORDS_DICT = { "Drink": 2, @@ -115,6 +116,7 @@ ALBUM_DICT = { "Sorted Oldest First": 3, "Sorted Title": 3, "Test Album": 2, + "Água": 3, } # Note: there are 2 albums named "Test Album" for testing duplicate album names UUID_DICT = { @@ -1091,7 +1093,7 @@ def test_from_to_date(photosdb): time.tzset() photos = photosdb.photos(from_date=datetime.datetime(2018, 10, 28)) - assert len(photos) == 16 + assert len(photos) == 20 photos = photosdb.photos(to_date=datetime.datetime(2018, 10, 28)) assert len(photos) == 7 diff --git a/tests/test_cli.py b/tests/test_cli.py index 08eadc21..1866416e 100644 --- a/tests/test_cli.py +++ b/tests/test_cli.py @@ -8,6 +8,7 @@ from click.testing import CliRunner import osxphotos from osxphotos.exiftool import get_exiftool_path +from osxphotos.utils import normalize_unicode CLI_PHOTOS_DB = "tests/Test-10.15.7.photoslibrary" LIVE_PHOTOS_DB = "tests/Test-Cloud-10.15.1.photoslibrary" @@ -79,64 +80,69 @@ CLI_OUTPUT_NO_SUBCOMMAND = [ CLI_OUTPUT_QUERY_UUID = '[{"uuid": "D79B8D77-BFFC-460B-9312-034F2877D35B", "filename": "D79B8D77-BFFC-460B-9312-034F2877D35B.jpeg", "original_filename": "Pumkins2.jpg", "date": "2018-09-28T16:07:07-04:00", "description": "Girl holding pumpkin", "title": "I found one!", "keywords": ["Kids"], "albums": ["Pumpkin Farm", "Test Album", "Multi Keyword"], "persons": ["Katie"], "path": "/tests/Test-10.15.7.photoslibrary/originals/D/D79B8D77-BFFC-460B-9312-034F2877D35B.jpeg", "ismissing": false, "hasadjustments": false, "external_edit": false, "favorite": false, "hidden": false, "latitude": 41.256566, "longitude": -95.940257, "path_edited": null, "shared": false, "isphoto": true, "ismovie": false, "uti": "public.jpeg", "burst": false, "live_photo": false, "path_live_photo": null, "iscloudasset": false, "incloud": null}]' CLI_EXPORT_FILENAMES = [ - "Pumkins1.jpg", - "Pumkins2.jpg", - "Pumpkins3.jpg", - "St James Park.jpg", - "St James Park_edited.jpeg", - "Tulips.jpg", - "wedding.jpg", - "wedding_edited.jpeg", + "[2020-08-29] AAF035 (1).jpg", + "[2020-08-29] AAF035 (2).jpg", + "[2020-08-29] AAF035 (3).jpg", + "[2020-08-29] AAF035.jpg", "DSC03584.dng", - "IMG_1693.tif", - "IMG_1994.JPG", - "IMG_1994.cr2", - "IMG_1997.JPG", - "IMG_1997.cr2", - "IMG_3092.heic", - "IMG_3092_edited.jpeg", - "IMG_4547.jpg", - "Jellyfish.MOV", - "Jellyfish1.mp4", - "Tulips_edited.jpeg", - "screenshot-really-a-png.jpeg", - "winebottle.jpeg", - "winebottle (1).jpeg", - "Frítest.jpg", "Frítest (1).jpg", "Frítest (2).jpg", "Frítest (3).jpg", - "Frítest_edited.jpeg", "Frítest_edited (1).jpeg", + "Frítest_edited.jpeg", + "Frítest.jpg", + "IMG_1693.tif", + "IMG_1994.cr2", + "IMG_1994.JPG", + "IMG_1997.cr2", + "IMG_1997.JPG", + "IMG_3092_edited.jpeg", + "IMG_3092.heic", + "IMG_4547.jpg", + "Jellyfish.MOV", + "Jellyfish1.mp4", + "Pumkins1.jpg", + "Pumkins2.jpg", + "Pumpkins3.jpg", + "screenshot-really-a-png.jpeg", + "St James Park_edited.jpeg", + "St James Park.jpg", + "Tulips_edited.jpeg", + "Tulips.jpg", + "wedding_edited.jpeg", + "wedding.jpg", + "winebottle (1).jpeg", + "winebottle.jpeg", ] CLI_EXPORT_FILENAMES_DRY_RUN = [ - "Pumkins1.jpg", - "Pumkins2.jpg", - "Pumpkins3.jpg", - "St James Park.jpg", - "St James Park_edited.jpeg", - "Tulips.jpg", - "wedding.jpg", - "wedding_edited.jpeg", + "[2020-08-29] AAF035.jpg", "DSC03584.dng", + "Frítest_edited.jpeg", + "Frítest.jpg", "IMG_1693.tif", - "IMG_1994.JPG", "IMG_1994.cr2", - "IMG_1997.JPG", + "IMG_1994.JPG", "IMG_1997.cr2", - "IMG_3092.heic", + "IMG_1997.JPG", "IMG_3092_edited.jpeg", + "IMG_3092.heic", "IMG_4547.jpg", "Jellyfish.MOV", "Jellyfish1.mp4", - "Tulips_edited.jpeg", + "Pumkins1.jpg", + "Pumkins2.jpg", + "Pumpkins3.jpg", "screenshot-really-a-png.jpeg", + "St James Park_edited.jpeg", + "St James Park.jpg", + "Tulips_edited.jpeg", + "Tulips.jpg", + "wedding_edited.jpeg", + "wedding.jpg", "winebottle.jpeg", "winebottle.jpeg", - "Frítest.jpg", - "Frítest_edited.jpeg", ] CLI_EXPORT_IGNORE_SIGNATURE_FILENAMES = ["Tulips.jpg", "wedding.jpg"] @@ -154,225 +160,253 @@ CLI_EXPORT_ORIGINAL_SUFFIX_TEMPLATE = "{edited?_original,}" CLI_EXPORT_PREVIEW_SUFFIX = "_lowres" CLI_EXPORT_FILENAMES_EDITED_SUFFIX = [ - "Pumkins1.jpg", - "Pumkins2.jpg", - "Pumpkins3.jpg", - "St James Park.jpg", - "St James Park_bearbeiten.jpeg", - "Tulips.jpg", - "wedding.jpg", - "wedding_bearbeiten.jpeg", + "[2020-08-29] AAF035 (1).jpg", + "[2020-08-29] AAF035 (2).jpg", + "[2020-08-29] AAF035 (3).jpg", + "[2020-08-29] AAF035.jpg", "DSC03584.dng", - "IMG_1693.tif", - "IMG_1994.JPG", - "IMG_1994.cr2", - "IMG_1997.JPG", - "IMG_1997.cr2", - "IMG_3092.heic", - "IMG_3092_bearbeiten.jpeg", - "IMG_4547.jpg", - "Jellyfish.MOV", - "Jellyfish1.mp4", - "Tulips_bearbeiten.jpeg", - "screenshot-really-a-png.jpeg", - "winebottle.jpeg", - "winebottle (1).jpeg", - "Frítest.jpg", "Frítest (1).jpg", "Frítest (2).jpg", "Frítest (3).jpg", - "Frítest_bearbeiten.jpeg", "Frítest_bearbeiten (1).jpeg", + "Frítest_bearbeiten.jpeg", + "Frítest.jpg", + "IMG_1693.tif", + "IMG_1994.cr2", + "IMG_1994.JPG", + "IMG_1997.cr2", + "IMG_1997.JPG", + "IMG_3092_bearbeiten.jpeg", + "IMG_3092.heic", + "IMG_4547.jpg", + "Jellyfish.MOV", + "Jellyfish1.mp4", + "Pumkins1.jpg", + "Pumkins2.jpg", + "Pumpkins3.jpg", + "screenshot-really-a-png.jpeg", + "St James Park_bearbeiten.jpeg", + "St James Park.jpg", + "Tulips_bearbeiten.jpeg", + "Tulips.jpg", + "wedding_bearbeiten.jpeg", + "wedding.jpg", + "winebottle (1).jpeg", + "winebottle.jpeg", ] CLI_EXPORT_FILENAMES_EDITED_SUFFIX_TEMPLATE = [ - "Pumkins1.jpg", - "Pumkins2.jpg", - "Pumpkins3.jpg", - "St James Park.jpg", - "St James Park_edited.jpeg", - "Tulips.jpg", - "wedding.jpg", - "wedding_edited.jpeg", + "[2020-08-29] AAF035 (1).jpg", + "[2020-08-29] AAF035 (2).jpg", + "[2020-08-29] AAF035 (3).jpg", + "[2020-08-29] AAF035.jpg", "DSC03584.dng", - "IMG_1693.tif", - "IMG_1994.JPG", - "IMG_1994.cr2", - "IMG_1997.JPG", - "IMG_1997.cr2", - "IMG_3092.heic", - "IMG_3092_edited.jpeg", - "IMG_4547.jpg", - "Jellyfish.MOV", - "Jellyfish1.mp4", - "Tulips_edited.jpeg", - "screenshot-really-a-png.jpeg", - "winebottle.jpeg", - "winebottle (1).jpeg", - "Frítest.jpg", "Frítest (1).jpg", "Frítest (2).jpg", "Frítest (3).jpg", - "Frítest_edited.jpeg", "Frítest_edited (1).jpeg", + "Frítest_edited.jpeg", + "Frítest.jpg", + "IMG_1693.tif", + "IMG_1994.cr2", + "IMG_1994.JPG", + "IMG_1997.cr2", + "IMG_1997.JPG", + "IMG_3092_edited.jpeg", + "IMG_3092.heic", + "IMG_4547.jpg", + "Jellyfish.MOV", + "Jellyfish1.mp4", + "Pumkins1.jpg", + "Pumkins2.jpg", + "Pumpkins3.jpg", + "screenshot-really-a-png.jpeg", + "St James Park_edited.jpeg", + "St James Park.jpg", + "Tulips_edited.jpeg", + "Tulips.jpg", + "wedding_edited.jpeg", + "wedding.jpg", + "winebottle (1).jpeg", + "winebottle.jpeg", ] CLI_EXPORT_FILENAMES_ORIGINAL_SUFFIX = [ - "Pumkins1_original.jpg", - "Pumkins2_original.jpg", - "Pumpkins3_original.jpg", - "St James Park_original.jpg", - "St James Park_edited.jpeg", - "Tulips_original.jpg", - "wedding_original.jpg", - "wedding_edited.jpeg", + "[2020-08-29] AAF035_original (1).jpg", + "[2020-08-29] AAF035_original (2).jpg", + "[2020-08-29] AAF035_original (3).jpg", + "[2020-08-29] AAF035_original.jpg", "DSC03584_original.dng", - "IMG_1693_original.tif", - "IMG_1994_original.JPG", - "IMG_1994_original.cr2", - "IMG_1997_original.JPG", - "IMG_1997_original.cr2", - "IMG_3092_original.heic", - "IMG_3092_edited.jpeg", - "IMG_4547_original.jpg", - "Jellyfish_original.MOV", - "Jellyfish1_original.mp4", - "Tulips_edited.jpeg", - "screenshot-really-a-png_original.jpeg", - "winebottle_original.jpeg", - "winebottle_original (1).jpeg", - "Frítest_original.jpg", + "Frítest_edited (1).jpeg", + "Frítest_edited.jpeg", "Frítest_original (1).jpg", "Frítest_original (2).jpg", "Frítest_original (3).jpg", - "Frítest_edited.jpeg", - "Frítest_edited (1).jpeg", + "Frítest_original.jpg", + "IMG_1693_original.tif", + "IMG_1994_original.cr2", + "IMG_1994_original.JPG", + "IMG_1997_original.cr2", + "IMG_1997_original.JPG", + "IMG_3092_edited.jpeg", + "IMG_3092_original.heic", + "IMG_4547_original.jpg", + "Jellyfish_original.MOV", + "Jellyfish1_original.mp4", + "Pumkins1_original.jpg", + "Pumkins2_original.jpg", + "Pumpkins3_original.jpg", + "screenshot-really-a-png_original.jpeg", + "St James Park_edited.jpeg", + "St James Park_original.jpg", + "Tulips_edited.jpeg", + "Tulips_original.jpg", + "wedding_edited.jpeg", + "wedding_original.jpg", + "winebottle_original (1).jpeg", + "winebottle_original.jpeg", ] CLI_EXPORT_FILENAMES_ORIGINAL_SUFFIX_TEMPLATE = [ - "Pumkins1.jpg", - "Pumkins2.jpg", - "Pumpkins3.jpg", - "St James Park_original.jpg", - "St James Park_edited.jpeg", - "Tulips_original.jpg", - "wedding_original.jpg", - "wedding_edited.jpeg", - "Tulips_edited.jpeg", + "[2020-08-29] AAF035 (1).jpg", + "[2020-08-29] AAF035 (2).jpg", + "[2020-08-29] AAF035 (3).jpg", + "[2020-08-29] AAF035.jpg", "DSC03584.dng", + "Frítest (1).jpg", + "Frítest_edited (1).jpeg", + "Frítest_edited.jpeg", + "Frítest_original (1).jpg", + "Frítest_original.jpg", + "Frítest.jpg", "IMG_1693.tif", - "IMG_1994.JPG", "IMG_1994.cr2", - "IMG_1997.JPG", + "IMG_1994.JPG", "IMG_1997.cr2", - "IMG_3092_original.heic", + "IMG_1997.JPG", "IMG_3092_edited.jpeg", + "IMG_3092_original.heic", "IMG_4547.jpg", "Jellyfish.MOV", "Jellyfish1.mp4", + "Pumkins1.jpg", + "Pumkins2.jpg", + "Pumpkins3.jpg", "screenshot-really-a-png.jpeg", - "winebottle.jpeg", + "St James Park_edited.jpeg", + "St James Park_original.jpg", + "Tulips_edited.jpeg", + "Tulips_original.jpg", + "wedding_edited.jpeg", + "wedding_original.jpg", "winebottle (1).jpeg", - "Frítest.jpg", - "Frítest (1).jpg", - "Frítest_original.jpg", - "Frítest_edited.jpeg", - "Frítest_original (1).jpg", - "Frítest_edited (1).jpeg", + "winebottle.jpeg", ] CLI_EXPORT_FILENAMES_CURRENT = [ + "1793FAAB-DE75-4E25-886C-2BD66C780D6A_edited.jpeg", # Frítest.jpg + "1793FAAB-DE75-4E25-886C-2BD66C780D6A.jpeg", # Frítest.jpg "1EB2B765-0765-43BA-A90C-0D0580E6172C.jpeg", + "2DFD33F1-A5D8-486F-A3A9-98C07995535A.jpeg", + "35329C57-B963-48D6-BB75-6AFF9370CBBC.mov", "3DD2C897-F19E-4CA6-8C22-B027D5A71907.jpeg", "4D521201-92AC-43E5-8F7C-59BC41C37A96.cr2", "4D521201-92AC-43E5-8F7C-59BC41C37A96.jpeg", + "52083079-73D5-4921-AC1B-FE76F279133F.jpeg", + "54E76FCB-D353-4557-9997-0A457BCB4D48.jpeg", + "6191423D-8DB8-4D4C-92BE-9BBBA308AAC4_edited.jpeg", "6191423D-8DB8-4D4C-92BE-9BBBA308AAC4.jpeg", + "7783E8E6-9CAC-40F3-BE22-81FB7051C266_edited.jpeg", + "7783E8E6-9CAC-40F3-BE22-81FB7051C266.heic", + "7F74DD34-5920-4DA3-B284-479887A34F66.jpeg", + "7FD37B5F-6FAA-4DB1-8A29-BF9C37E38091.jpeg", + "8846E3E6-8AC8-4857-8448-E3D025784410.tiff", + "A8266C97-9BAF-4AF4-99F3-0013832869B8.jpeg", # Frítest.jpg "A92D9C26-3A50-4197-9388-CB5F7DB9FA91.cr2", "A92D9C26-3A50-4197-9388-CB5F7DB9FA91.jpeg", - "D05A5FE3-15FB-49A1-A15D-AB3DA6F8B068.dng", - "D79B8D77-BFFC-460B-9312-034F2877D35B.jpeg", - "DC99FBDD-7A52-4100-A5BB-344131646C30.jpeg", - "DC99FBDD-7A52-4100-A5BB-344131646C30_edited.jpeg", - "E9BC5C36-7CD1-40A1-A72B-8B8FAC227D51.jpeg", - "E9BC5C36-7CD1-40A1-A72B-8B8FAC227D51_edited.jpeg", - "F12384F6-CD17-4151-ACBA-AE0E3688539E.jpeg", - "35329C57-B963-48D6-BB75-6AFF9370CBBC.mov", - "6191423D-8DB8-4D4C-92BE-9BBBA308AAC4_edited.jpeg", - "7783E8E6-9CAC-40F3-BE22-81FB7051C266.heic", - "7783E8E6-9CAC-40F3-BE22-81FB7051C266_edited.jpeg", - "7F74DD34-5920-4DA3-B284-479887A34F66.jpeg", - "8846E3E6-8AC8-4857-8448-E3D025784410.tiff", - "D1359D09-1373-4F3B-B0E3-1A4DE573E4A3.mp4", - "E2078879-A29C-4D6F-BACB-E3BBE6C3EB91.jpeg", - "52083079-73D5-4921-AC1B-FE76F279133F.jpeg", "B13F4485-94E0-41CD-AF71-913095D62E31.jpeg", # Frítest.jpg - "1793FAAB-DE75-4E25-886C-2BD66C780D6A.jpeg", # Frítest.jpg - "1793FAAB-DE75-4E25-886C-2BD66C780D6A_edited.jpeg", # Frítest.jpg - "A8266C97-9BAF-4AF4-99F3-0013832869B8.jpeg", # Frítest.jpg - "D1D4040D-D141-44E8-93EA-E403D9F63E07.jpeg", # Frítest.jpg + "D05A5FE3-15FB-49A1-A15D-AB3DA6F8B068.dng", + "D1359D09-1373-4F3B-B0E3-1A4DE573E4A3.mp4", "D1D4040D-D141-44E8-93EA-E403D9F63E07_edited.jpeg", # Frítest.jpg + "D1D4040D-D141-44E8-93EA-E403D9F63E07.jpeg", # Frítest.jpg + "D79B8D77-BFFC-460B-9312-034F2877D35B.jpeg", + "DC99FBDD-7A52-4100-A5BB-344131646C30_edited.jpeg", + "DC99FBDD-7A52-4100-A5BB-344131646C30.jpeg", + "E2078879-A29C-4D6F-BACB-E3BBE6C3EB91.jpeg", + "E9BC5C36-7CD1-40A1-A72B-8B8FAC227D51_edited.jpeg", + "E9BC5C36-7CD1-40A1-A72B-8B8FAC227D51.jpeg", + "F12384F6-CD17-4151-ACBA-AE0E3688539E.jpeg", + "F207D5DE-EFAD-4217-8424-0764AAC971D0.jpeg", ] CLI_EXPORT_FILENAMES_CONVERT_TO_JPEG = [ + "[2020-08-29] AAF035 (1).jpg", + "[2020-08-29] AAF035 (2).jpg", + "[2020-08-29] AAF035 (3).jpg", + "[2020-08-29] AAF035.jpg", "DSC03584.jpeg", - "IMG_1693.jpeg", - "IMG_1994.JPG", - "IMG_1994.cr2", - "IMG_1997.JPG", - "IMG_1997.cr2", - "IMG_3092.jpeg", - "IMG_3092_edited.jpeg", - "IMG_4547.jpg", - "Pumkins1.jpg", - "Pumkins2.jpg", - "Pumpkins3.jpg", - "St James Park.jpg", - "St James Park_edited.jpeg", - "Tulips.jpg", - "Tulips_edited.jpeg", - "wedding.jpg", - "wedding_edited.jpeg", - "Jellyfish.MOV", - "Jellyfish1.mp4", - "screenshot-really-a-png.jpeg", - "winebottle.jpeg", - "winebottle (1).jpeg", - "Frítest.jpg", "Frítest (1).jpg", "Frítest (2).jpg", "Frítest (3).jpg", "Frítest_edited (1).jpeg", "Frítest_edited.jpeg", + "Frítest.jpg", + "IMG_1693.jpeg", + "IMG_1994.cr2", + "IMG_1994.JPG", + "IMG_1997.cr2", + "IMG_1997.JPG", + "IMG_3092_edited.jpeg", + "IMG_3092.jpeg", + "IMG_4547.jpg", + "Jellyfish.MOV", + "Jellyfish1.mp4", + "Pumkins1.jpg", + "Pumkins2.jpg", + "Pumpkins3.jpg", + "screenshot-really-a-png.jpeg", + "St James Park_edited.jpeg", + "St James Park.jpg", + "Tulips_edited.jpeg", + "Tulips.jpg", + "wedding_edited.jpeg", + "wedding.jpg", + "winebottle (1).jpeg", + "winebottle.jpeg", ] CLI_EXPORT_FILENAMES_CONVERT_TO_JPEG_SKIP_RAW = [ + "[2020-08-29] AAF035 (1).jpg", + "[2020-08-29] AAF035 (2).jpg", + "[2020-08-29] AAF035 (3).jpg", + "[2020-08-29] AAF035.jpg", "DSC03584.jpeg", - "IMG_1693.jpeg", - "IMG_1994.JPG", - "IMG_1997.JPG", - "IMG_3092.jpeg", - "IMG_3092_edited.jpeg", - "IMG_4547.jpg", - "Pumkins1.jpg", - "Pumkins2.jpg", - "Pumpkins3.jpg", - "St James Park.jpg", - "St James Park_edited.jpeg", - "Tulips.jpg", - "Tulips_edited.jpeg", - "wedding.jpg", - "wedding_edited.jpeg", - "Jellyfish.MOV", - "Jellyfish1.mp4", - "screenshot-really-a-png.jpeg", - "winebottle.jpeg", - "winebottle (1).jpeg", - "Frítest.jpg", "Frítest (1).jpg", "Frítest (2).jpg", "Frítest (3).jpg", - "Frítest_edited.jpeg", "Frítest_edited (1).jpeg", + "Frítest_edited.jpeg", + "Frítest.jpg", + "IMG_1693.jpeg", + "IMG_1994.JPG", + "IMG_1997.JPG", + "IMG_3092_edited.jpeg", + "IMG_3092.jpeg", + "IMG_4547.jpg", + "Jellyfish.MOV", + "Jellyfish1.mp4", + "Pumkins1.jpg", + "Pumkins2.jpg", + "Pumpkins3.jpg", + "screenshot-really-a-png.jpeg", + "St James Park_edited.jpeg", + "St James Park.jpg", + "Tulips_edited.jpeg", + "Tulips.jpg", + "wedding_edited.jpeg", + "wedding.jpg", + "winebottle (1).jpeg", + "winebottle.jpeg", ] CLI_EXPORT_CONVERT_TO_JPEG_LARGE_FILE = "DSC03584.jpeg" @@ -546,7 +580,7 @@ PHOTOS_NOT_IN_TRASH_LEN_14_6 = 12 PHOTOS_IN_TRASH_LEN_14_6 = 1 PHOTOS_MISSING_14_6 = 1 -PHOTOS_NOT_IN_TRASH_LEN_15_7 = 23 +PHOTOS_NOT_IN_TRASH_LEN_15_7 = 27 PHOTOS_IN_TRASH_LEN_15_7 = 2 PHOTOS_MISSING_15_7 = 2 PHOTOS_EDITED_15_7 = 6 @@ -732,6 +766,7 @@ ALBUMS_JSON = { "Sorted Newest First": 3, "Sorted Oldest First": 3, "Sorted Title": 3, + "Água": 3, }, "shared albums": {}, } @@ -746,6 +781,7 @@ ALBUMS_STR = """albums: 2018-10 - Sponsion, Museum, Frühstück, Römermuseum: 1 2019-10/11 Paris Clermont: 1 EmptyAlbum: 0 + Água: 3 shared albums: {} """ @@ -820,37 +856,45 @@ UUID_IS_REFERENCE = [ ] UUID_IN_ALBUM = [ - "F12384F6-CD17-4151-ACBA-AE0E3688539E", - "8E1D7BC9-9321-44F9-8CFB-4083F6B9232A", "1EB2B765-0765-43BA-A90C-0D0580E6172C", - "E9BC5C36-7CD1-40A1-A72B-8B8FAC227D51", - "A92D9C26-3A50-4197-9388-CB5F7DB9FA91", - "D79B8D77-BFFC-460B-9312-034F2877D35B", - "4D521201-92AC-43E5-8F7C-59BC41C37A96", - "D05A5FE3-15FB-49A1-A15D-AB3DA6F8B068", + "2DFD33F1-A5D8-486F-A3A9-98C07995535A", "3DD2C897-F19E-4CA6-8C22-B027D5A71907", + "4D521201-92AC-43E5-8F7C-59BC41C37A96", + "54E76FCB-D353-4557-9997-0A457BCB4D48", "7783E8E6-9CAC-40F3-BE22-81FB7051C266", + "7FD37B5F-6FAA-4DB1-8A29-BF9C37E38091", + "8E1D7BC9-9321-44F9-8CFB-4083F6B9232A", + "A92D9C26-3A50-4197-9388-CB5F7DB9FA91", + "D05A5FE3-15FB-49A1-A15D-AB3DA6F8B068", + "D79B8D77-BFFC-460B-9312-034F2877D35B", + "E9BC5C36-7CD1-40A1-A72B-8B8FAC227D51", + "F12384F6-CD17-4151-ACBA-AE0E3688539E", ] UUID_NOT_IN_ALBUM = [ - "A1DD1F98-2ECD-431F-9AC9-5AFEFE2D3A5C", - "DC99FBDD-7A52-4100-A5BB-344131646C30", - "D1359D09-1373-4F3B-B0E3-1A4DE573E4A3", - "E2078879-A29C-4D6F-BACB-E3BBE6C3EB91", - "6191423D-8DB8-4D4C-92BE-9BBBA308AAC4", - "35329C57-B963-48D6-BB75-6AFF9370CBBC", - "8846E3E6-8AC8-4857-8448-E3D025784410", - "7F74DD34-5920-4DA3-B284-479887A34F66", - "52083079-73D5-4921-AC1B-FE76F279133F", - "B13F4485-94E0-41CD-AF71-913095D62E31", # Frítest.jpg "1793FAAB-DE75-4E25-886C-2BD66C780D6A", # Frítest.jpg + "35329C57-B963-48D6-BB75-6AFF9370CBBC", + "52083079-73D5-4921-AC1B-FE76F279133F", + "6191423D-8DB8-4D4C-92BE-9BBBA308AAC4", + "7F74DD34-5920-4DA3-B284-479887A34F66", + "8846E3E6-8AC8-4857-8448-E3D025784410", + "A1DD1F98-2ECD-431F-9AC9-5AFEFE2D3A5C", "A8266C97-9BAF-4AF4-99F3-0013832869B8", # Frítest.jpg + "B13F4485-94E0-41CD-AF71-913095D62E31", # Frítest.jpg + "D1359D09-1373-4F3B-B0E3-1A4DE573E4A3", "D1D4040D-D141-44E8-93EA-E403D9F63E07", # Frítest.jpg + "DC99FBDD-7A52-4100-A5BB-344131646C30", + "E2078879-A29C-4D6F-BACB-E3BBE6C3EB91", + "F207D5DE-EFAD-4217-8424-0764AAC971D0", ] UUID_DUPLICATES = [ - "7F74DD34-5920-4DA3-B284-479887A34F66", + "2DFD33F1-A5D8-486F-A3A9-98C07995535A", "52083079-73D5-4921-AC1B-FE76F279133F", + "54E76FCB-D353-4557-9997-0A457BCB4D48", + "7F74DD34-5920-4DA3-B284-479887A34F66", + "A92D9C26-3A50-4197-9388-CB5F7DB9FA91", + "F207D5DE-EFAD-4217-8424-0764AAC971D0", ] UUID_LOCATION = "D79B8D77-BFFC-460B-9312-034F2877D35B" # Pumkins2.jpg @@ -2517,7 +2561,8 @@ def test_export_duplicate(): # pylint: disable=not-context-manager with runner.isolated_filesystem(): result = runner.invoke( - export, [os.path.join(cwd, CLI_PHOTOS_DB), ".", "-V", "--duplicate"] + export, + [os.path.join(cwd, CLI_PHOTOS_DB), ".", "-V", "--duplicate", "--skip-raw"], ) assert result.exit_code == 0 files = glob.glob("*") @@ -4046,8 +4091,7 @@ def test_export_filename_template_long_description(): ], ) assert result.exit_code == 0 - for fname in CLI_EXPORTED_FILENAME_TEMPLATE_LONG_DESCRIPTION: - assert pathlib.Path(fname).is_file() + assert "exported: 1" in result.output def test_export_filename_template_3(): @@ -4700,7 +4744,14 @@ def test_export_live_edited(): # basic export result = runner.invoke( export, - [os.path.join(cwd, PHOTOS_DB_RHET), ".", "-V", "--uuid", UUID_LIVE_EDITED], + [ + os.path.join(cwd, PHOTOS_DB_RHET), + ".", + "-V", + "--uuid", + UUID_LIVE_EDITED, + "--download-missing", + ], ) assert result.exit_code == 0 files = glob.glob("*") @@ -5077,7 +5128,7 @@ def test_export_dry_run(): in result.output ) for filepath in CLI_EXPORT_FILENAMES_DRY_RUN: - assert re.search(r"Exported.*" + f"{filepath}", result.output) + assert re.search(r"Exported.*" + f"{re.escape(normalize_fs_path(filepath))}", result.output) assert not os.path.isfile(normalize_fs_path(filepath)) @@ -6022,7 +6073,7 @@ def test_export_cleanup_empty_album(): def test_export_cleanup_accented_album_name(): - """test export with --cleanup flag and photos in album with accented unicode characters (#561)""" + """test export with --cleanup flag and photos in album with accented unicode characters (#561, #618)""" import pathlib from osxphotos.cli import export @@ -6045,6 +6096,89 @@ def test_export_cleanup_accented_album_name(): ) assert "Deleted: 0 files, 0 directories" in result.output + # do it again + result = runner.invoke( + export, + [ + os.path.join(cwd, CLI_PHOTOS_DB), + tempdir, + "-V", + "--update", + "--cleanup", + "--directory", + "{folder_album}", + "--update", + ], + ) + assert "exported: 0, updated: 0" in result.output + assert "Deleted: 0 files, 0 directories" in result.output + + +@pytest.mark.skipif(exiftool is None, reason="exiftool not installed") +def test_export_cleanup_exiftool_accented_album_name_same_filenames(): + """test export with --cleanup flag and photos in album with accented unicode characters (#561, #618)""" + import pathlib + + from osxphotos.cli import export + + runner = CliRunner() + cwd = os.getcwd() + # pylint: disable=not-context-manager + with tempfile.TemporaryDirectory() as report_dir: + # keep report file out of of expor dir for --cleanup + report_file = os.path.join(report_dir, "test.csv") + with tempfile.TemporaryDirectory() as tempdir: + result = runner.invoke( + export, + [ + os.path.join(cwd, CLI_PHOTOS_DB), + tempdir, + "-V", + "--cleanup", + "--directory", + "{album[/,.|:,.]}", + "--exiftool", + "--exiftool-merge-keywords", + "--exiftool-merge-persons", + "--keyword-template", + "{keyword}", + "--report", + report_file, + "--skip-original-if-edited", + "--update", + "--touch-file", + "--not-hidden", + ], + ) + assert "Deleted: 0 files, 0 directories" in result.output + + # do it again + result = runner.invoke( + export, + [ + os.path.join(cwd, CLI_PHOTOS_DB), + tempdir, + "-V", + "--cleanup", + "--directory", + "{album[/,.|:,.]}", + "--exiftool", + "--exiftool-merge-keywords", + "--exiftool-merge-persons", + "--keyword-template", + "{keyword}", + "--report", + report_file, + "--skip-original-if-edited", + "--update", + "--touch-file", + "--not-hidden", + ], + ) + assert "exported: 0, updated: 0" in result.output + assert "updated EXIF data: 0" in result.output + assert "Deleted: 0 files, 0 directories" in result.output + def test_save_load_config(): """test --save-config, --load-config""" @@ -7001,6 +7135,30 @@ def test_query_name(): assert json_got[0]["original_filename"] == "DSC03584.dng" +def test_query_name_unicode(): + """test query --name with a unicode name""" + import json + import os + import os.path + + import osxphotos + from osxphotos.cli import query + + runner = CliRunner() + cwd = os.getcwd() + result = runner.invoke( + query, + ["--json", "--db", os.path.join(cwd, PHOTOS_DB_15_7), "--name", "Frítest"], + ) + assert result.exit_code == 0 + json_got = json.loads(result.output) + + assert len(json_got) == 4 + assert normalize_unicode(json_got[0]["original_filename"]).startswith( + normalize_unicode("Frítest.jpg") + ) + + def test_query_name_i(): """test query --name -i""" import json @@ -7030,6 +7188,46 @@ def test_query_name_i(): assert json_got[0]["original_filename"] == "DSC03584.dng" +def test_query_name_original_filename(): + """test query --name only searches original filename on Photos 5+""" + import json + import os + import os.path + + from osxphotos.cli import query + + runner = CliRunner() + cwd = os.getcwd() + result = runner.invoke( + query, + ["--json", "--db", os.path.join(cwd, PHOTOS_DB_15_7), "--name", "AA"], + ) + assert result.exit_code == 0 + json_got = json.loads(result.output) + + assert len(json_got) == 4 + + +def test_query_name_original_filename_i(): + """test query --name only searches original filename on Photos 5+ with -i""" + import json + import os + import os.path + + from osxphotos.cli import query + + runner = CliRunner() + cwd = os.getcwd() + result = runner.invoke( + query, + ["--json", "--db", os.path.join(cwd, PHOTOS_DB_15_7), "--name", "aa", "-i"], + ) + assert result.exit_code == 0 + json_got = json.loads(result.output) + + assert len(json_got) == 4 + + def test_export_name(): """test export --name""" import glob diff --git a/tests/test_export_catalina_10_15_7_use_photos_export.py b/tests/test_export_catalina_10_15_7_use_photos_export.py index 844eb30f..9b04efb7 100644 --- a/tests/test_export_catalina_10_15_7_use_photos_export.py +++ b/tests/test_export_catalina_10_15_7_use_photos_export.py @@ -140,7 +140,6 @@ def test_export_edited_exiftool(photosdb): got_dest = photos[0].export( dest, use_photos_export=True, edited=True, exiftool=True ) - logging.warning(got_dest) got_dest = got_dest[0] assert os.path.isfile(got_dest) diff --git a/tests/test_path_utils.py b/tests/test_path_utils.py index 137d7dc7..c7ba14e6 100644 --- a/tests/test_path_utils.py +++ b/tests/test_path_utils.py @@ -1,6 +1,10 @@ """ Test path_utils.py """ + def test_sanitize_filename(): + """test sanitize_filename""" + + # subtract 6 chars from max length of 255 to account for lock file extension from osxphotos.path_utils import sanitize_filename from osxphotos._constants import MAX_FILENAME_LEN @@ -30,25 +34,25 @@ def test_sanitize_filename(): filename = "foo" + "x" * 512 new_filename = sanitize_filename(filename) assert len(new_filename) == MAX_FILENAME_LEN - assert new_filename == "foo" + "x" * 252 + assert new_filename == "foo" + "x" * (252 - 6) # filename too long with extension filename = "x" * 512 + ".jpeg" new_filename = sanitize_filename(filename) assert len(new_filename) == MAX_FILENAME_LEN - assert new_filename == "x" * 250 + ".jpeg" + assert new_filename == "x" * (250 - 6) + ".jpeg" # more than one extension filename = "foo.bar" + "x" * 255 + ".foo.bar.jpeg" new_filename = sanitize_filename(filename) assert len(new_filename) == MAX_FILENAME_LEN - assert new_filename == "foo.bar" + "x" * 243 + ".jpeg" + assert new_filename == "foo.bar" + "x" * (243 - 6) + ".jpeg" # shorter than drop count filename = "foo." + "x" * 256 new_filename = sanitize_filename(filename) assert len(new_filename) == MAX_FILENAME_LEN - assert new_filename == "foo." + "x" * 251 + assert new_filename == "foo." + "x" * (251 - 6) def test_sanitize_dirname(): @@ -83,6 +87,7 @@ def test_sanitize_dirname(): assert len(new_dirname) == MAX_DIRNAME_LEN assert new_dirname == "foo" + "x" * 252 + def test_sanitize_pathpart(): from osxphotos.path_utils import sanitize_pathpart from osxphotos._constants import MAX_DIRNAME_LEN @@ -114,4 +119,3 @@ def test_sanitize_pathpart(): new_dirname = sanitize_pathpart(dirname) assert len(new_dirname) == MAX_DIRNAME_LEN assert new_dirname == "foo" + "x" * 252 - diff --git a/tests/test_utils.py b/tests/test_utils.py index 786026e6..77b27c65 100644 --- a/tests/test_utils.py +++ b/tests/test_utils.py @@ -1,27 +1,33 @@ +import logging +import os.path +import pathlib +import tempfile + import pytest +import osxphotos + DB_LOCKED_10_12 = "./tests/Test-Lock-10_12.photoslibrary/database/photos.db" DB_LOCKED_10_15 = "./tests/Test-Lock-10_15_1.photoslibrary/database/Photos.sqlite" DB_UNLOCKED_10_15 = "./tests/Test-10.15.1.photoslibrary/database/photos.db" UTI_DICT = {"public.jpeg": "jpeg", "com.canon.cr2-raw-image": "cr2"} +from osxphotos.utils import ( + _dd_to_dms, + increment_filename, + increment_filename_with_count, + list_directory, +) + def test_debug_enable(): - import logging - - import osxphotos - osxphotos._set_debug(True) logger = osxphotos._get_logger() assert logger.isEnabledFor(logging.DEBUG) def test_debug_disable(): - import logging - - import osxphotos - osxphotos._set_debug(False) logger = osxphotos._get_logger() assert not logger.isEnabledFor(logging.DEBUG) @@ -29,14 +35,12 @@ def test_debug_disable(): def test_dd_to_dms(): # expands coverage for edge case in _dd_to_dms - from osxphotos.utils import _dd_to_dms assert _dd_to_dms(-0.001) == (0, 0, -3.6) @pytest.mark.skip(reason="Fails on some machines") def test_get_system_library_path(): - import osxphotos _, major, _ = osxphotos.utils._get_os_version() if int(major) < 15: @@ -46,51 +50,73 @@ def test_get_system_library_path(): def test_db_is_locked_locked(): - import osxphotos assert osxphotos.utils._db_is_locked(DB_LOCKED_10_12) assert osxphotos.utils._db_is_locked(DB_LOCKED_10_15) def test_db_is_locked_unlocked(): - import osxphotos assert not osxphotos.utils._db_is_locked(DB_UNLOCKED_10_15) -def test_findfiles(): - import os.path - import tempfile - - from osxphotos.utils import findfiles +def test_list_directory(): + """test list_directory""" temp_dir = tempfile.TemporaryDirectory(prefix="osxphotos_") - fd = open(os.path.join(temp_dir.name, "file1.jpg"), "w+") - fd.close - fd = open(os.path.join(temp_dir.name, "file2.JPG"), "w+") - fd.close - files = findfiles("*.jpg", temp_dir.name) + temp_dir_name = pathlib.Path(temp_dir.name) + file1 = (temp_dir_name / "file1.jpg").touch() + file2 = (temp_dir_name / "File2.JPG").touch() + file3 = (temp_dir_name / "File.png").touch() + file4 = (temp_dir_name / "document.pdf").touch() + + files = list_directory(temp_dir.name, glob="*.jpg") assert len(files) == 2 assert "file1.jpg" in files - assert "file2.JPG" in files + assert "File2.JPG" in files + assert isinstance(files[0], str) + + files = list_directory(temp_dir.name, glob="*.jpg", case_sensitive=True) + assert len(files) == 1 + assert "file1.jpg" in files + + files = list_directory(temp_dir.name, startswith="file") + assert len(files) == 3 + + files = list_directory(temp_dir.name, endswith="jpg") + assert len(files) == 2 + + files = list_directory(temp_dir.name, contains="doc") + assert len(files) == 1 + assert "document.pdf" in files + + files = list_directory(temp_dir.name, startswith="File", case_sensitive=True) + assert len(files) == 2 + + files = list_directory(temp_dir.name, startswith="File", case_sensitive=False) + assert len(files) == 3 + + files = list_directory(temp_dir.name, startswith="document", include_path=True) + assert len(files) == 1 + assert files[0] == str(pathlib.Path(temp_dir.name) / "document.pdf") + + # test pathlib.Path + files = list_directory(temp_dir_name, glob="*.jpg") + assert isinstance(files[0], pathlib.Path) + + files = list_directory(temp_dir.name, glob="FooBar*.jpg") + assert not files -def test_findfiles_invalid_dir(): - import tempfile - - from osxphotos.utils import findfiles +def test_list_directory_invalid(): temp_dir = tempfile.TemporaryDirectory(prefix="osxphotos_") - files = findfiles("*.jpg", f"{temp_dir.name}/no_such_dir") + files = list_directory(f"{temp_dir.name}/no_such_dir", glob="*.jpg") assert len(files) == 0 def test_increment_filename(): # test that increment_filename works - import pathlib - import tempfile - - from osxphotos.utils import increment_filename, increment_filename_with_count with tempfile.TemporaryDirectory(prefix="osxphotos_") as temp_dir: temp_dir = pathlib.Path(temp_dir) diff --git a/utils/exiftool_supported_types.py b/utils/exiftool_supported_types.py new file mode 100644 index 00000000..fa4b1995 --- /dev/null +++ b/utils/exiftool_supported_types.py @@ -0,0 +1,57 @@ +"""Read the "Supported File Types" table from exiftool.org and build a json file from the table""" + +import json +import sys + +import requests +from bs4 import BeautifulSoup + + +if __name__ == "__main__": + url = "https://www.exiftool.org/" + json_file = "exiftool_filetypes.json" + + html_content = requests.get(url).text + + soup = BeautifulSoup(html_content, "html.parser") + + # uncomment to see all table classes + # print("Classes of each table:") + # for table in soup.find_all("table"): + # print(table.get("class")) + + # strip footnotes in tags + for span_tag in soup.findAll("span"): + span_tag.replace_with("") + + # find the table for Supported File Types + table = soup.find("table", class_="sticky tight sm bm") + + # get table headers + table_headers = [tx.text.lower() for tx in table.find_all("th")] + + # get table data + table_data = [] + for tr in table.find_all("tr"): + if row := [td.text for td in tr.find_all("td")]: + table_data.append(row) + + # make a dictionary of the table data + supported_filetypes = {} + for row in table_data: + row_dict = dict(zip(table_headers, row)) + for key, value in row_dict.items(): + if value == "-": + row_dict[key] = None + row_dict["file type"] = row_dict["file type"].split(",") + row_dict["file type"] = [ft.strip() for ft in row_dict["file type"]] + row_dict["read"] = "R" in row_dict["support"] + row_dict["write"] = "W" in row_dict["support"] + row_dict["create"] = "C" in row_dict["support"] + filetypes = [ft.lower() for ft in row_dict["file type"]] + for filetype in filetypes: + supported_filetypes[filetype] = {"extension": filetype, **row_dict} + + with open(json_file, "w") as jsonfile: + print(f"Writing {json_file}...") + json.dump(supported_filetypes, jsonfile, indent=4)