Compare commits
7 Commits
| Author | SHA1 | Date | |
|---|---|---|---|
|
|
6070616717 | ||
|
|
70d3247e8c | ||
|
|
25f35699d8 | ||
|
|
d07aab58d1 | ||
|
|
dc4d322dab | ||
|
|
8a3dc9b393 | ||
|
|
5e3cd66a4b |
@@ -1942,7 +1942,7 @@ cog.out(get_template_field_table())
|
||||
|{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.47.13'|
|
||||
|{osxphotos_version}|The osxphotos version, e.g. '0.48.1'|
|
||||
|{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|
|
||||
|
||||
16
CHANGELOG.md
16
CHANGELOG.md
@@ -4,6 +4,22 @@ 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.48.0](https://github.com/RhetTbull/osxphotos/compare/v0.47.13...v0.48.0)
|
||||
|
||||
> 1 May 2022
|
||||
|
||||
- Feature timewarp [`#675`](https://github.com/RhetTbull/osxphotos/pull/675)
|
||||
- Version bump [`d07aab5`](https://github.com/RhetTbull/osxphotos/commit/d07aab58d1b0ef8e8769740db089235bbc938a4e)
|
||||
- Updated dependencies for rich_theme_manager [`8a3dc9b`](https://github.com/RhetTbull/osxphotos/commit/8a3dc9b3938f2363baba6dbf1f832ee55d57eb28)
|
||||
|
||||
#### [v0.47.13](https://github.com/RhetTbull/osxphotos/compare/v0.47.12...v0.47.13)
|
||||
|
||||
> 24 April 2022
|
||||
|
||||
- Updated docs to use zipfile [`bd9a14a`](https://github.com/RhetTbull/osxphotos/commit/bd9a14a6f30042044345b91c5c3d5176ac6bc4ad)
|
||||
- Added retry to export_db for #674 [`e4b6c0f`](https://github.com/RhetTbull/osxphotos/commit/e4b6c0f1e00123064575e39183cb5a209afee49d)
|
||||
- Updated license [`ad13565`](https://github.com/RhetTbull/osxphotos/commit/ad13565dfded64fdf1dd72a001609d2522b64737)
|
||||
|
||||
#### [v0.47.12](https://github.com/RhetTbull/osxphotos/compare/v0.47.11...v0.47.12)
|
||||
|
||||
> 23 April 2022
|
||||
|
||||
12
README.md
12
README.md
@@ -124,9 +124,10 @@ This package will install a command line utility called `osxphotos` that allows
|
||||
|
||||
```
|
||||
> osxphotos
|
||||
|
||||
Usage: osxphotos [OPTIONS] COMMAND [ARGS]...
|
||||
|
||||
osxphotos: query and export your Photos library
|
||||
|
||||
Options:
|
||||
--db <Photos database path> Specify Photos database path. Path to Photos
|
||||
library/database can be specified using either
|
||||
@@ -144,8 +145,8 @@ Commands:
|
||||
about Print information about osxphotos including license.
|
||||
albums Print out albums found in the Photos library.
|
||||
diff Compare two Photos databases and print out differences
|
||||
dump Print list of all photos & associated info from the Photos...
|
||||
docs Open osxphotos documentation in your browser.
|
||||
dump Print list of all photos & associated info from the Photos...
|
||||
export Export photos from the Photos database.
|
||||
help Print help; for help on commands: help <command>.
|
||||
info Print out descriptive info of the Photos library database.
|
||||
@@ -157,7 +158,10 @@ Commands:
|
||||
places Print out places found in the Photos library.
|
||||
query Query the Photos database using 1 or more search options; if...
|
||||
repl Run interactive osxphotos REPL shell (useful for debugging,...
|
||||
run Run a python file using same environment as osxphotos
|
||||
snap Create snapshot of Photos database to use with diff command
|
||||
theme Manage osxphotos color themes.
|
||||
timewarp Adjust date/time/timezone of photos in Apple Photos.
|
||||
tutorial Display osxphotos tutorial.
|
||||
uninstall Uninstall Python packages from the osxphotos environment
|
||||
uuid Print out unique IDs (UUID) of photos selected in Photos
|
||||
@@ -1804,7 +1808,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.47.13'
|
||||
{osxphotos_version} The osxphotos version, e.g. '0.48.1'
|
||||
{osxphotos_cmd_line} The full command line used to run osxphotos
|
||||
|
||||
The following substitutions may result in multiple values. Thus if specified
|
||||
@@ -3717,7 +3721,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.47.13'|
|
||||
|{osxphotos_version}|The osxphotos version, e.g. '0.48.1'|
|
||||
|{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|
|
||||
|
||||
@@ -95,6 +95,7 @@ Alternatively, you can also run the command line utility like this: ``python3 -m
|
||||
-v, --version Show the version and exit.
|
||||
-h, --help Show this message and exit.
|
||||
|
||||
|
||||
Commands:
|
||||
about Print information about osxphotos including license.
|
||||
albums Print out albums found in the Photos library.
|
||||
@@ -115,6 +116,7 @@ Alternatively, you can also run the command line utility like this: ``python3 -m
|
||||
run Run a python file using same environment as osxphotos
|
||||
snap Create snapshot of Photos database to use with diff command
|
||||
theme Manage osxphotos color themes.
|
||||
timewarp Adjust date/time/timezone of photos in Apple Photos.
|
||||
tutorial Display osxphotos tutorial.
|
||||
uninstall Uninstall Python packages from the osxphotos environment
|
||||
uuid Print out unique IDs (UUID) of photos selected in Photos
|
||||
|
||||
@@ -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: 6e37a8386484eeb0ec9fe42fe4c8223a
|
||||
config: bea67f756056dcbf51004e51b5dc2067
|
||||
tags: 645f666f9bcd5a90fca523b33c5a78b7
|
||||
|
||||
@@ -5,7 +5,7 @@
|
||||
<meta name="color-scheme" content="light dark"><link rel="index" title="Index" href="../genindex.html" /><link rel="search" title="Search" href="../search.html" />
|
||||
|
||||
<meta name="generator" content="sphinx-4.4.0, furo 2022.04.07"/>
|
||||
<title>Overview: module code - osxphotos 0.47.13 documentation</title>
|
||||
<title>Overview: module code - osxphotos 0.48.1 documentation</title>
|
||||
<link rel="stylesheet" type="text/css" href="../_static/pygments.css" />
|
||||
<link rel="stylesheet" type="text/css" href="../_static/styles/furo.css?digest=68f4518137b9aefe99b631505a2064c3c42c9852" />
|
||||
<link rel="stylesheet" type="text/css" href="../_static/copybutton.css" />
|
||||
@@ -123,7 +123,7 @@
|
||||
</label>
|
||||
</div>
|
||||
<div class="header-center">
|
||||
<a href="../index.html"><div class="brand">osxphotos 0.47.13 documentation</div></a>
|
||||
<a href="../index.html"><div class="brand">osxphotos 0.48.1 documentation</div></a>
|
||||
</div>
|
||||
<div class="header-right">
|
||||
<div class="theme-toggle-container theme-toggle-header">
|
||||
@@ -146,7 +146,7 @@
|
||||
<div class="sidebar-sticky"><a class="sidebar-brand" href="../index.html">
|
||||
|
||||
|
||||
<span class="sidebar-brand-text">osxphotos 0.47.13 documentation</span>
|
||||
<span class="sidebar-brand-text">osxphotos 0.48.1 documentation</span>
|
||||
|
||||
</a><form class="sidebar-search-container" method="get" action="../search.html" role="search">
|
||||
<input class="sidebar-search" placeholder=Search name="q" aria-label="Search">
|
||||
|
||||
@@ -74,6 +74,7 @@ This package will install a command line utility called ``osxphotos`` that allow
|
||||
run Run a python file using same environment as osxphotos
|
||||
snap Create snapshot of Photos database to use with diff command
|
||||
theme Manage osxphotos color themes.
|
||||
timewarp Adjust date/time/timezone of photos in Apple Photos.
|
||||
tutorial Display osxphotos tutorial.
|
||||
uninstall Uninstall Python packages from the osxphotos environment
|
||||
uuid Print out unique IDs (UUID) of photos selected in Photos
|
||||
|
||||
@@ -318,7 +318,7 @@ Template Substitutions
|
||||
* - {crlf}
|
||||
- a carriage return + line feed: '\r\n'
|
||||
* - {osxphotos_version}
|
||||
- The osxphotos version, e.g. '0.47.13'
|
||||
- The osxphotos version, e.g. '0.48.1'
|
||||
* - {osxphotos_cmd_line}
|
||||
- The full command line used to run osxphotos
|
||||
* - {album}
|
||||
|
||||
2
docs/_static/documentation_options.js
vendored
2
docs/_static/documentation_options.js
vendored
@@ -1,6 +1,6 @@
|
||||
var DOCUMENTATION_OPTIONS = {
|
||||
URL_ROOT: document.getElementById("documentation_options").getAttribute('data-url_root'),
|
||||
VERSION: '0.47.13',
|
||||
VERSION: '0.48.1',
|
||||
LANGUAGE: 'None',
|
||||
COLLAPSE_INDEX: false,
|
||||
BUILDER: 'html',
|
||||
|
||||
124
docs/cli.html
124
docs/cli.html
@@ -6,7 +6,7 @@
|
||||
<link rel="index" title="Index" href="genindex.html" /><link rel="search" title="Search" href="search.html" /><link rel="next" title="OSXPhotos Template System" href="template_help.html" /><link rel="prev" title="OSXPhotos Tutorial" href="tutorial.html" />
|
||||
|
||||
<meta name="generator" content="sphinx-4.4.0, furo 2022.04.07"/>
|
||||
<title>OSXPhotos Command Line Interface (CLI) - osxphotos 0.47.13 documentation</title>
|
||||
<title>OSXPhotos Command Line Interface (CLI) - osxphotos 0.48.1 documentation</title>
|
||||
<link rel="stylesheet" type="text/css" href="_static/pygments.css" />
|
||||
<link rel="stylesheet" type="text/css" href="_static/styles/furo.css?digest=68f4518137b9aefe99b631505a2064c3c42c9852" />
|
||||
<link rel="stylesheet" type="text/css" href="_static/copybutton.css" />
|
||||
@@ -124,7 +124,7 @@
|
||||
</label>
|
||||
</div>
|
||||
<div class="header-center">
|
||||
<a href="index.html"><div class="brand">osxphotos 0.47.13 documentation</div></a>
|
||||
<a href="index.html"><div class="brand">osxphotos 0.48.1 documentation</div></a>
|
||||
</div>
|
||||
<div class="header-right">
|
||||
<div class="theme-toggle-container theme-toggle-header">
|
||||
@@ -147,7 +147,7 @@
|
||||
<div class="sidebar-sticky"><a class="sidebar-brand" href="index.html">
|
||||
|
||||
|
||||
<span class="sidebar-brand-text">osxphotos 0.47.13 documentation</span>
|
||||
<span class="sidebar-brand-text">osxphotos 0.48.1 documentation</span>
|
||||
|
||||
</a><form class="sidebar-search-container" method="get" action="search.html" role="search">
|
||||
<input class="sidebar-search" placeholder=Search name="q" aria-label="Search">
|
||||
@@ -2110,6 +2110,123 @@ uses ‘/private/tmp/osxphotos_snapshots’</p>
|
||||
<dd><p>Delete THEME.</p>
|
||||
</dd></dl>
|
||||
</section>
|
||||
<section id="osxphotos-timewarp">
|
||||
<h3>timewarp<a class="headerlink" href="#osxphotos-timewarp" title="Permalink to this headline">#</a></h3>
|
||||
<p>Adjust date/time/timezone of photos in Apple Photos.</p>
|
||||
<p>Changes will be applied to all photos currently selected in Photos.
|
||||
timewarp cannot operate on photos selected in a Smart Album;
|
||||
select photos in a regular album or in the ‘All Photos’ view.
|
||||
See Timewarp Overview below for additional information.</p>
|
||||
<div class="highlight-shell notranslate"><div class="highlight"><pre><span></span>osxphotos timewarp <span class="o">[</span>OPTIONS<span class="o">]</span>
|
||||
</pre></div>
|
||||
</div>
|
||||
<p class="rubric">Options</p>
|
||||
<dl class="std option">
|
||||
<dt class="sig sig-object std" id="cmdoption-osxphotos-timewarp-d">
|
||||
<span id="cmdoption-osxphotos-timewarp-date"></span><span class="sig-name descname"><span class="pre">-d</span></span><span class="sig-prename descclassname"></span><span class="sig-prename descclassname"><span class="pre">,</span> </span><span class="sig-name descname"><span class="pre">--date</span></span><span class="sig-prename descclassname"> <span class="pre"><DATE></span></span><a class="headerlink" href="#cmdoption-osxphotos-timewarp-d" title="Permalink to this definition">#</a></dt>
|
||||
<dd><p>Set date for selected photos. Format is ‘YYYY-MM-DD’.</p>
|
||||
</dd></dl>
|
||||
<dl class="std option">
|
||||
<dt class="sig sig-object std" id="cmdoption-osxphotos-timewarp-D">
|
||||
<span id="cmdoption-osxphotos-timewarp-date-delta"></span><span class="sig-name descname"><span class="pre">-D</span></span><span class="sig-prename descclassname"></span><span class="sig-prename descclassname"><span class="pre">,</span> </span><span class="sig-name descname"><span class="pre">--date-delta</span></span><span class="sig-prename descclassname"> <span class="pre"><DELTA></span></span><a class="headerlink" href="#cmdoption-osxphotos-timewarp-D" title="Permalink to this definition">#</a></dt>
|
||||
<dd><p>Adjust date for selected photos by DELTA. Format is one of: ‘±D days’, ‘±W weeks’, ‘±D’ where D is days</p>
|
||||
</dd></dl>
|
||||
<dl class="std option">
|
||||
<dt class="sig sig-object std" id="cmdoption-osxphotos-timewarp-t">
|
||||
<span id="cmdoption-osxphotos-timewarp-time"></span><span class="sig-name descname"><span class="pre">-t</span></span><span class="sig-prename descclassname"></span><span class="sig-prename descclassname"><span class="pre">,</span> </span><span class="sig-name descname"><span class="pre">--time</span></span><span class="sig-prename descclassname"> <span class="pre"><TIME></span></span><a class="headerlink" href="#cmdoption-osxphotos-timewarp-t" title="Permalink to this definition">#</a></dt>
|
||||
<dd><p>Set time for selected photos. Format is one of ‘HH:MM:SS’, ‘HH:MM:SS.fff’, ‘HH:MM’.</p>
|
||||
</dd></dl>
|
||||
<dl class="std option">
|
||||
<dt class="sig sig-object std" id="cmdoption-osxphotos-timewarp-T">
|
||||
<span id="cmdoption-osxphotos-timewarp-time-delta"></span><span class="sig-name descname"><span class="pre">-T</span></span><span class="sig-prename descclassname"></span><span class="sig-prename descclassname"><span class="pre">,</span> </span><span class="sig-name descname"><span class="pre">--time-delta</span></span><span class="sig-prename descclassname"> <span class="pre"><DELTA></span></span><a class="headerlink" href="#cmdoption-osxphotos-timewarp-T" title="Permalink to this definition">#</a></dt>
|
||||
<dd><p>Adjust time for selected photos by DELTA time. Format is one of ‘±HH:MM:SS’, ‘±H hours’ (or hr), ‘±M minutes’ (or min), ‘±S seconds’ (or sec), ‘±S’ (where S is seconds)</p>
|
||||
</dd></dl>
|
||||
<dl class="std option">
|
||||
<dt class="sig sig-object std" id="cmdoption-osxphotos-timewarp-z">
|
||||
<span id="cmdoption-osxphotos-timewarp-timezone"></span><span class="sig-name descname"><span class="pre">-z</span></span><span class="sig-prename descclassname"></span><span class="sig-prename descclassname"><span class="pre">,</span> </span><span class="sig-name descname"><span class="pre">--timezone</span></span><span class="sig-prename descclassname"> <span class="pre"><TIMEZONE></span></span><a class="headerlink" href="#cmdoption-osxphotos-timewarp-z" title="Permalink to this definition">#</a></dt>
|
||||
<dd><p>Set timezone for selected photos as offset from UTC. Format is one of ‘±HH:MM’, ‘±H:MM’, or ‘±HHMM’. The actual time of the photo is not adjusted which means, somewhat counterintuitively, that the time in the new timezone will be different. For example, if photo has time of 12:00 and timezone of GMT+01:00 and new timezone is specified as ‘–timezone +02:00’ (one hour ahead of current GMT+01:00 timezone), the photo’s new time will be 13:00 GMT+02:00, which is equivalent to the old time of 12:00+01:00. This is the same behavior exhibited by Photos when manually adjusting timezone in the Get Info window. See also –match-time.</p>
|
||||
</dd></dl>
|
||||
<dl class="std option">
|
||||
<dt class="sig sig-object std" id="cmdoption-osxphotos-timewarp-i">
|
||||
<span id="cmdoption-osxphotos-timewarp-inspect"></span><span class="sig-name descname"><span class="pre">-i</span></span><span class="sig-prename descclassname"></span><span class="sig-prename descclassname"><span class="pre">,</span> </span><span class="sig-name descname"><span class="pre">--inspect</span></span><span class="sig-prename descclassname"></span><a class="headerlink" href="#cmdoption-osxphotos-timewarp-i" title="Permalink to this definition">#</a></dt>
|
||||
<dd><p>Print out the date/time/timezone for each selected photo without changing any information.</p>
|
||||
</dd></dl>
|
||||
<dl class="std option">
|
||||
<dt class="sig sig-object std" id="cmdoption-osxphotos-timewarp-c">
|
||||
<span id="cmdoption-osxphotos-timewarp-compare-exif"></span><span class="sig-name descname"><span class="pre">-c</span></span><span class="sig-prename descclassname"></span><span class="sig-prename descclassname"><span class="pre">,</span> </span><span class="sig-name descname"><span class="pre">--compare-exif</span></span><span class="sig-prename descclassname"></span><a class="headerlink" href="#cmdoption-osxphotos-timewarp-c" title="Permalink to this definition">#</a></dt>
|
||||
<dd><p>Compare the EXIF date/time/timezone for each selected photo to the same data in Photos. Requires the third-party exiftool utility be installed (see <a class="reference external" href="https://exiftool.org/">https://exiftool.org/</a>). See also –add-to-album.</p>
|
||||
</dd></dl>
|
||||
<dl class="std option">
|
||||
<dt class="sig sig-object std" id="cmdoption-osxphotos-timewarp-p">
|
||||
<span id="cmdoption-osxphotos-timewarp-push-exif"></span><span class="sig-name descname"><span class="pre">-p</span></span><span class="sig-prename descclassname"></span><span class="sig-prename descclassname"><span class="pre">,</span> </span><span class="sig-name descname"><span class="pre">--push-exif</span></span><span class="sig-prename descclassname"></span><a class="headerlink" href="#cmdoption-osxphotos-timewarp-p" title="Permalink to this definition">#</a></dt>
|
||||
<dd><p>Push date/time and timezone for selected photos from Photos to the EXIF metadata in the original file in the Photos library. Requires the third-party exiftool utility be installed (see <a class="reference external" href="https://exiftool.org/">https://exiftool.org/</a>). Using this option modifies the <em>original</em> file of the image in your Photos library. –push-exif will be executed after any other updates are performed on the photo. See also –pull-exif.</p>
|
||||
</dd></dl>
|
||||
<dl class="std option">
|
||||
<dt class="sig sig-object std" id="cmdoption-osxphotos-timewarp-P">
|
||||
<span id="cmdoption-osxphotos-timewarp-pull-exif"></span><span class="sig-name descname"><span class="pre">-P</span></span><span class="sig-prename descclassname"></span><span class="sig-prename descclassname"><span class="pre">,</span> </span><span class="sig-name descname"><span class="pre">--pull-exif</span></span><span class="sig-prename descclassname"></span><a class="headerlink" href="#cmdoption-osxphotos-timewarp-P" title="Permalink to this definition">#</a></dt>
|
||||
<dd><p>Pull date/time and timezone for selected photos from EXIF metadata in the original file into Photos and update the associated data in Photos to match the EXIF data. –pull-exif will be executed before any other updates are performed on the photo. It is possible for images to have missing EXIF data, for example the date/time could be set but there might be no timezone set in the EXIF metadata. Missing data will be handled thusly: if date/time/timezone are all present in the EXIF data, the photo’s date/time/timezone will be updated. If timezone is missing but date/time is present, only the photo’s date/time will be updated. If date/time is missing but the timezone is present, only the photo’s timezone will be updated unless –use-file-time is set in which case, the photo’s file modification date/time will be used in place of EXIF date/time. If the date is present but the time is missing, the time will be set to 00:00:00. Requires the third-party exiftool utility be installed (see <a class="reference external" href="https://exiftool.org/">https://exiftool.org/</a>). See also –push-exif.</p>
|
||||
</dd></dl>
|
||||
<dl class="std option">
|
||||
<dt class="sig sig-object std" id="cmdoption-osxphotos-timewarp-F">
|
||||
<span id="cmdoption-osxphotos-timewarp-f"></span><span id="cmdoption-osxphotos-timewarp-function"></span><span class="sig-name descname"><span class="pre">-F</span></span><span class="sig-prename descclassname"></span><span class="sig-prename descclassname"><span class="pre">,</span> </span><span class="sig-name descname"><span class="pre">--function</span></span><span class="sig-prename descclassname"> <span class="pre"><filename.py::function></span></span><a class="headerlink" href="#cmdoption-osxphotos-timewarp-F" title="Permalink to this definition">#</a></dt>
|
||||
<dd><p>Run python function to determine the date/time/timezone to apply to a photo. Use this in format: –function filename.py::function where filename.py is a python file you’ve created and function is the name of the function in the python file you want to call. The function will be passed information about the photo being processed and is expected to return a naive datetime.datetime object with time in local time and UTC timezone offset in seconds. See example function at https://github.com/RhetTbull/osxphotos/blob/master/examples/timewarp_function_example.py</p>
|
||||
</dd></dl>
|
||||
<dl class="std option">
|
||||
<dt class="sig sig-object std" id="cmdoption-osxphotos-timewarp-m">
|
||||
<span id="cmdoption-osxphotos-timewarp-match-time"></span><span class="sig-name descname"><span class="pre">-m</span></span><span class="sig-prename descclassname"></span><span class="sig-prename descclassname"><span class="pre">,</span> </span><span class="sig-name descname"><span class="pre">--match-time</span></span><span class="sig-prename descclassname"></span><a class="headerlink" href="#cmdoption-osxphotos-timewarp-m" title="Permalink to this definition">#</a></dt>
|
||||
<dd><p>When used with –timezone, adjusts the photo time so that the timestamp in the new timezone matches the timestamp in the old timezone. For example, if photo has time of 12:00 and timezone of GMT+01:00 and new timezone is specified as ‘–timezone +02:00’ (one hour ahead of current GMT+01:00 timezone), the photo’s new time will be 12:00 GMT+02:00. That is, the timezone will have changed but the timestamp of the photo will match the previous timestamp. Use –match-time when the camera’s time was correct for the time the photo was taken but the timezone was missing or wrong and you want to adjust the timezone while preserving the photo’s time. See also –timezone.</p>
|
||||
</dd></dl>
|
||||
<dl class="std option">
|
||||
<dt class="sig sig-object std" id="cmdoption-osxphotos-timewarp-0">
|
||||
<span id="cmdoption-osxphotos-timewarp-use-file-time"></span><span class="sig-name descname"><span class="pre">-f</span></span><span class="sig-prename descclassname"></span><span class="sig-prename descclassname"><span class="pre">,</span> </span><span class="sig-name descname"><span class="pre">--use-file-time</span></span><span class="sig-prename descclassname"></span><a class="headerlink" href="#cmdoption-osxphotos-timewarp-0" title="Permalink to this definition">#</a></dt>
|
||||
<dd><p>When used with –pull-exif, the file modification date/time will be used if date/time is missing from the EXIF data.</p>
|
||||
</dd></dl>
|
||||
<dl class="std option">
|
||||
<dt class="sig sig-object std" id="cmdoption-osxphotos-timewarp-a">
|
||||
<span id="cmdoption-osxphotos-timewarp-add-to-album"></span><span class="sig-name descname"><span class="pre">-a</span></span><span class="sig-prename descclassname"></span><span class="sig-prename descclassname"><span class="pre">,</span> </span><span class="sig-name descname"><span class="pre">--add-to-album</span></span><span class="sig-prename descclassname"> <span class="pre"><ALBUM></span></span><a class="headerlink" href="#cmdoption-osxphotos-timewarp-a" title="Permalink to this definition">#</a></dt>
|
||||
<dd><p>When used with –compare-exif, adds any photos with date/time/timezone differences between Photos/EXIF to album ALBUM. If ALBUM does not exist, it will be created.</p>
|
||||
</dd></dl>
|
||||
<dl class="std option">
|
||||
<dt class="sig sig-object std" id="cmdoption-osxphotos-timewarp-V">
|
||||
<span id="cmdoption-osxphotos-timewarp-v"></span><span id="cmdoption-osxphotos-timewarp-verbose"></span><span class="sig-name descname"><span class="pre">-V</span></span><span class="sig-prename descclassname"></span><span class="sig-prename descclassname"><span class="pre">,</span> </span><span class="sig-name descname"><span class="pre">--verbose</span></span><span class="sig-prename descclassname"></span><a class="headerlink" href="#cmdoption-osxphotos-timewarp-V" title="Permalink to this definition">#</a></dt>
|
||||
<dd><p>Show verbose output.</p>
|
||||
</dd></dl>
|
||||
<dl class="std option">
|
||||
<dt class="sig sig-object std" id="cmdoption-osxphotos-timewarp-L">
|
||||
<span id="cmdoption-osxphotos-timewarp-l"></span><span id="cmdoption-osxphotos-timewarp-library"></span><span class="sig-name descname"><span class="pre">-L</span></span><span class="sig-prename descclassname"></span><span class="sig-prename descclassname"><span class="pre">,</span> </span><span class="sig-name descname"><span class="pre">--library</span></span><span class="sig-prename descclassname"> <span class="pre"><PHOTOS_LIBRARY_PATH></span></span><a class="headerlink" href="#cmdoption-osxphotos-timewarp-L" title="Permalink to this definition">#</a></dt>
|
||||
<dd><p>Path to Photos library (e.g. ‘~/Pictures/PhotosLibrary.photoslibrary’). This is not likely needed as osxphotos will usually be able to locate the path to the open Photos library. Use –library only if you get an error that the Photos library cannot be located.</p>
|
||||
</dd></dl>
|
||||
<dl class="std option">
|
||||
<dt class="sig sig-object std" id="cmdoption-osxphotos-timewarp-e">
|
||||
<span id="cmdoption-osxphotos-timewarp-exiftool-path"></span><span class="sig-name descname"><span class="pre">-e</span></span><span class="sig-prename descclassname"></span><span class="sig-prename descclassname"><span class="pre">,</span> </span><span class="sig-name descname"><span class="pre">--exiftool-path</span></span><span class="sig-prename descclassname"> <span class="pre"><exiftool_path></span></span><a class="headerlink" href="#cmdoption-osxphotos-timewarp-e" title="Permalink to this definition">#</a></dt>
|
||||
<dd><p>Optional path to exiftool executable (will look in $PATH if not specified) for those options which require exiftool.</p>
|
||||
</dd></dl>
|
||||
<dl class="std option">
|
||||
<dt class="sig sig-object std" id="cmdoption-osxphotos-timewarp-o">
|
||||
<span id="cmdoption-osxphotos-timewarp-output-file"></span><span class="sig-name descname"><span class="pre">-o</span></span><span class="sig-prename descclassname"></span><span class="sig-prename descclassname"><span class="pre">,</span> </span><span class="sig-name descname"><span class="pre">--output-file</span></span><span class="sig-prename descclassname"> <span class="pre"><output_file></span></span><a class="headerlink" href="#cmdoption-osxphotos-timewarp-o" title="Permalink to this definition">#</a></dt>
|
||||
<dd><p>Output file. If not specified, output is written to stdout.</p>
|
||||
</dd></dl>
|
||||
<dl class="std option">
|
||||
<dt class="sig sig-object std" id="cmdoption-osxphotos-timewarp-timestamp">
|
||||
<span class="sig-name descname"><span class="pre">--timestamp</span></span><span class="sig-prename descclassname"></span><a class="headerlink" href="#cmdoption-osxphotos-timewarp-timestamp" title="Permalink to this definition">#</a></dt>
|
||||
<dd><p>Add time stamp to verbose output</p>
|
||||
</dd></dl>
|
||||
<dl class="std option">
|
||||
<dt class="sig sig-object std" id="cmdoption-osxphotos-timewarp-theme">
|
||||
<span class="sig-name descname"><span class="pre">--theme</span></span><span class="sig-prename descclassname"> <span class="pre"><THEME></span></span><a class="headerlink" href="#cmdoption-osxphotos-timewarp-theme" title="Permalink to this definition">#</a></dt>
|
||||
<dd><p>Specify the color theme to use for –verbose output. Valid themes are ‘dark’, ‘light’, ‘mono’, and ‘plain’. Defaults to ‘dark’ or ‘light’ depending on system dark mode setting.</p>
|
||||
<dl class="field-list simple">
|
||||
<dt class="field-odd">Options</dt>
|
||||
<dd class="field-odd"><p>dark | light | mono | plain</p>
|
||||
</dd>
|
||||
</dl>
|
||||
</dd></dl>
|
||||
<dl class="std option">
|
||||
<dt class="sig sig-object std" id="cmdoption-osxphotos-timewarp-plain">
|
||||
<span class="sig-name descname"><span class="pre">--plain</span></span><span class="sig-prename descclassname"></span><a class="headerlink" href="#cmdoption-osxphotos-timewarp-plain" title="Permalink to this definition">#</a></dt>
|
||||
<dd><p>Plain text mode. Do not use rich output.</p>
|
||||
</dd></dl>
|
||||
</section>
|
||||
<section id="osxphotos-tutorial">
|
||||
<h3>tutorial<a class="headerlink" href="#osxphotos-tutorial" title="Permalink to this headline">#</a></h3>
|
||||
<p>Display osxphotos tutorial.</p>
|
||||
@@ -2289,6 +2406,7 @@ Commands:
|
||||
<li><a class="reference internal" href="#osxphotos-run">run</a></li>
|
||||
<li><a class="reference internal" href="#osxphotos-snap">snap</a></li>
|
||||
<li><a class="reference internal" href="#osxphotos-theme">theme</a></li>
|
||||
<li><a class="reference internal" href="#osxphotos-timewarp">timewarp</a></li>
|
||||
<li><a class="reference internal" href="#osxphotos-tutorial">tutorial</a></li>
|
||||
<li><a class="reference internal" href="#osxphotos-uninstall">uninstall</a></li>
|
||||
<li><a class="reference internal" href="#osxphotos-uuid">uuid</a></li>
|
||||
|
||||
@@ -4,7 +4,7 @@
|
||||
<meta name="viewport" content="width=device-width,initial-scale=1"/>
|
||||
<meta name="color-scheme" content="light dark"><link rel="index" title="Index" href="#" /><link rel="search" title="Search" href="search.html" />
|
||||
|
||||
<meta name="generator" content="sphinx-4.4.0, furo 2022.04.07"/><title>Index - osxphotos 0.47.13 documentation</title>
|
||||
<meta name="generator" content="sphinx-4.4.0, furo 2022.04.07"/><title>Index - osxphotos 0.48.1 documentation</title>
|
||||
<link rel="stylesheet" type="text/css" href="_static/pygments.css" />
|
||||
<link rel="stylesheet" type="text/css" href="_static/styles/furo.css?digest=68f4518137b9aefe99b631505a2064c3c42c9852" />
|
||||
<link rel="stylesheet" type="text/css" href="_static/copybutton.css" />
|
||||
@@ -122,7 +122,7 @@
|
||||
</label>
|
||||
</div>
|
||||
<div class="header-center">
|
||||
<a href="index.html"><div class="brand">osxphotos 0.47.13 documentation</div></a>
|
||||
<a href="index.html"><div class="brand">osxphotos 0.48.1 documentation</div></a>
|
||||
</div>
|
||||
<div class="header-right">
|
||||
<div class="theme-toggle-container theme-toggle-header">
|
||||
@@ -145,7 +145,7 @@
|
||||
<div class="sidebar-sticky"><a class="sidebar-brand" href="index.html">
|
||||
|
||||
|
||||
<span class="sidebar-brand-text">osxphotos 0.47.13 documentation</span>
|
||||
<span class="sidebar-brand-text">osxphotos 0.48.1 documentation</span>
|
||||
|
||||
</a><form class="sidebar-search-container" method="get" action="search.html" role="search">
|
||||
<input class="sidebar-search" placeholder=Search name="q" aria-label="Search">
|
||||
@@ -227,6 +227,8 @@
|
||||
|
||||
<ul>
|
||||
<li><a href="cli.html#cmdoption-osxphotos-query-add-to-album">osxphotos-query command line option</a>
|
||||
</li>
|
||||
<li><a href="cli.html#cmdoption-osxphotos-timewarp-a">osxphotos-timewarp command line option</a>
|
||||
</li>
|
||||
</ul></li>
|
||||
<li>
|
||||
@@ -279,6 +281,13 @@
|
||||
<li><a href="cli.html#cmdoption-osxphotos-query-cloudasset">osxphotos-query command line option</a>
|
||||
</li>
|
||||
<li><a href="cli.html#cmdoption-osxphotos-repl-cloudasset">osxphotos-repl command line option</a>
|
||||
</li>
|
||||
</ul></li>
|
||||
<li>
|
||||
--compare-exif
|
||||
|
||||
<ul>
|
||||
<li><a href="cli.html#cmdoption-osxphotos-timewarp-c">osxphotos-timewarp command line option</a>
|
||||
</li>
|
||||
</ul></li>
|
||||
<li>
|
||||
@@ -307,6 +316,20 @@
|
||||
|
||||
<ul>
|
||||
<li><a href="cli.html#cmdoption-osxphotos-export-current-name">osxphotos-export command line option</a>
|
||||
</li>
|
||||
</ul></li>
|
||||
<li>
|
||||
--date
|
||||
|
||||
<ul>
|
||||
<li><a href="cli.html#cmdoption-osxphotos-timewarp-d">osxphotos-timewarp command line option</a>
|
||||
</li>
|
||||
</ul></li>
|
||||
<li>
|
||||
--date-delta
|
||||
|
||||
<ul>
|
||||
<li><a href="cli.html#cmdoption-osxphotos-timewarp-D">osxphotos-timewarp command line option</a>
|
||||
</li>
|
||||
</ul></li>
|
||||
<li>
|
||||
@@ -506,6 +529,8 @@
|
||||
|
||||
<ul>
|
||||
<li><a href="cli.html#cmdoption-osxphotos-export-exiftool-path">osxphotos-export command line option</a>
|
||||
</li>
|
||||
<li><a href="cli.html#cmdoption-osxphotos-timewarp-e">osxphotos-timewarp command line option</a>
|
||||
</li>
|
||||
</ul></li>
|
||||
<li>
|
||||
@@ -612,6 +637,13 @@
|
||||
<li><a href="cli.html#cmdoption-osxphotos-query-from-time">osxphotos-query command line option</a>
|
||||
</li>
|
||||
<li><a href="cli.html#cmdoption-osxphotos-repl-from-time">osxphotos-repl command line option</a>
|
||||
</li>
|
||||
</ul></li>
|
||||
<li>
|
||||
--function
|
||||
|
||||
<ul>
|
||||
<li><a href="cli.html#cmdoption-osxphotos-timewarp-F">osxphotos-timewarp command line option</a>
|
||||
</li>
|
||||
</ul></li>
|
||||
<li>
|
||||
@@ -712,6 +744,13 @@
|
||||
<li><a href="cli.html#cmdoption-osxphotos-query-incloud">osxphotos-query command line option</a>
|
||||
</li>
|
||||
<li><a href="cli.html#cmdoption-osxphotos-repl-incloud">osxphotos-repl command line option</a>
|
||||
</li>
|
||||
</ul></li>
|
||||
<li>
|
||||
--inspect
|
||||
|
||||
<ul>
|
||||
<li><a href="cli.html#cmdoption-osxphotos-timewarp-i">osxphotos-timewarp command line option</a>
|
||||
</li>
|
||||
</ul></li>
|
||||
<li>
|
||||
@@ -791,6 +830,13 @@
|
||||
<li><a href="cli.html#cmdoption-osxphotos-query-label">osxphotos-query command line option</a>
|
||||
</li>
|
||||
<li><a href="cli.html#cmdoption-osxphotos-repl-label">osxphotos-repl command line option</a>
|
||||
</li>
|
||||
</ul></li>
|
||||
<li>
|
||||
--library
|
||||
|
||||
<ul>
|
||||
<li><a href="cli.html#cmdoption-osxphotos-timewarp-L">osxphotos-timewarp command line option</a>
|
||||
</li>
|
||||
</ul></li>
|
||||
<li>
|
||||
@@ -827,6 +873,13 @@
|
||||
<li><a href="cli.html#cmdoption-osxphotos-query-location">osxphotos-query command line option</a>
|
||||
</li>
|
||||
<li><a href="cli.html#cmdoption-osxphotos-repl-location">osxphotos-repl command line option</a>
|
||||
</li>
|
||||
</ul></li>
|
||||
<li>
|
||||
--match-time
|
||||
|
||||
<ul>
|
||||
<li><a href="cli.html#cmdoption-osxphotos-timewarp-m">osxphotos-timewarp command line option</a>
|
||||
</li>
|
||||
</ul></li>
|
||||
<li>
|
||||
@@ -946,8 +999,6 @@
|
||||
<li><a href="cli.html#cmdoption-osxphotos-repl-no-title">osxphotos-repl command line option</a>
|
||||
</li>
|
||||
</ul></li>
|
||||
</ul></td>
|
||||
<td style="width: 33%; vertical-align: top;"><ul>
|
||||
<li>
|
||||
--not-burst
|
||||
|
||||
@@ -990,6 +1041,8 @@
|
||||
<li><a href="cli.html#cmdoption-osxphotos-repl-not-hdr">osxphotos-repl command line option</a>
|
||||
</li>
|
||||
</ul></li>
|
||||
</ul></td>
|
||||
<td style="width: 33%; vertical-align: top;"><ul>
|
||||
<li>
|
||||
--not-hidden
|
||||
|
||||
@@ -1152,6 +1205,13 @@
|
||||
|
||||
<ul>
|
||||
<li><a href="cli.html#cmdoption-osxphotos-export-original-suffix">osxphotos-export command line option</a>
|
||||
</li>
|
||||
</ul></li>
|
||||
<li>
|
||||
--output-file
|
||||
|
||||
<ul>
|
||||
<li><a href="cli.html#cmdoption-osxphotos-timewarp-o">osxphotos-timewarp command line option</a>
|
||||
</li>
|
||||
</ul></li>
|
||||
<li>
|
||||
@@ -1199,6 +1259,13 @@
|
||||
<li><a href="cli.html#cmdoption-osxphotos-query-place">osxphotos-query command line option</a>
|
||||
</li>
|
||||
<li><a href="cli.html#cmdoption-osxphotos-repl-place">osxphotos-repl command line option</a>
|
||||
</li>
|
||||
</ul></li>
|
||||
<li>
|
||||
--plain
|
||||
|
||||
<ul>
|
||||
<li><a href="cli.html#cmdoption-osxphotos-timewarp-plain">osxphotos-timewarp command line option</a>
|
||||
</li>
|
||||
</ul></li>
|
||||
<li>
|
||||
@@ -1247,6 +1314,20 @@
|
||||
|
||||
<ul>
|
||||
<li><a href="cli.html#cmdoption-osxphotos-export-preview-suffix">osxphotos-export command line option</a>
|
||||
</li>
|
||||
</ul></li>
|
||||
<li>
|
||||
--pull-exif
|
||||
|
||||
<ul>
|
||||
<li><a href="cli.html#cmdoption-osxphotos-timewarp-P">osxphotos-timewarp command line option</a>
|
||||
</li>
|
||||
</ul></li>
|
||||
<li>
|
||||
--push-exif
|
||||
|
||||
<ul>
|
||||
<li><a href="cli.html#cmdoption-osxphotos-timewarp-p">osxphotos-timewarp command line option</a>
|
||||
</li>
|
||||
</ul></li>
|
||||
<li>
|
||||
@@ -1468,6 +1549,22 @@
|
||||
|
||||
<ul>
|
||||
<li><a href="cli.html#cmdoption-osxphotos-export-theme">osxphotos-export command line option</a>
|
||||
</li>
|
||||
<li><a href="cli.html#cmdoption-osxphotos-timewarp-theme">osxphotos-timewarp command line option</a>
|
||||
</li>
|
||||
</ul></li>
|
||||
<li>
|
||||
--time
|
||||
|
||||
<ul>
|
||||
<li><a href="cli.html#cmdoption-osxphotos-timewarp-t">osxphotos-timewarp command line option</a>
|
||||
</li>
|
||||
</ul></li>
|
||||
<li>
|
||||
--time-delta
|
||||
|
||||
<ul>
|
||||
<li><a href="cli.html#cmdoption-osxphotos-timewarp-T">osxphotos-timewarp command line option</a>
|
||||
</li>
|
||||
</ul></li>
|
||||
<li>
|
||||
@@ -1486,6 +1583,15 @@
|
||||
|
||||
<ul>
|
||||
<li><a href="cli.html#cmdoption-osxphotos-export-timestamp">osxphotos-export command line option</a>
|
||||
</li>
|
||||
<li><a href="cli.html#cmdoption-osxphotos-timewarp-timestamp">osxphotos-timewarp command line option</a>
|
||||
</li>
|
||||
</ul></li>
|
||||
<li>
|
||||
--timezone
|
||||
|
||||
<ul>
|
||||
<li><a href="cli.html#cmdoption-osxphotos-timewarp-z">osxphotos-timewarp command line option</a>
|
||||
</li>
|
||||
</ul></li>
|
||||
<li>
|
||||
@@ -1547,6 +1653,13 @@
|
||||
|
||||
<ul>
|
||||
<li><a href="cli.html#cmdoption-osxphotos-install-U">osxphotos-install command line option</a>
|
||||
</li>
|
||||
</ul></li>
|
||||
<li>
|
||||
--use-file-time
|
||||
|
||||
<ul>
|
||||
<li><a href="cli.html#cmdoption-osxphotos-timewarp-0">osxphotos-timewarp command line option</a>
|
||||
</li>
|
||||
</ul></li>
|
||||
<li>
|
||||
@@ -1603,6 +1716,8 @@
|
||||
<li><a href="cli.html#cmdoption-osxphotos-diff-V">osxphotos-diff command line option</a>
|
||||
</li>
|
||||
<li><a href="cli.html#cmdoption-osxphotos-export-V">osxphotos-export command line option</a>
|
||||
</li>
|
||||
<li><a href="cli.html#cmdoption-osxphotos-timewarp-V">osxphotos-timewarp command line option</a>
|
||||
</li>
|
||||
</ul></li>
|
||||
<li>
|
||||
@@ -1635,12 +1750,56 @@
|
||||
|
||||
<ul>
|
||||
<li><a href="cli.html#cmdoption-osxphotos-uninstall-y">osxphotos-uninstall command line option</a>
|
||||
</li>
|
||||
</ul></li>
|
||||
<li>
|
||||
-a
|
||||
|
||||
<ul>
|
||||
<li><a href="cli.html#cmdoption-osxphotos-timewarp-a">osxphotos-timewarp command line option</a>
|
||||
</li>
|
||||
</ul></li>
|
||||
<li>
|
||||
-c
|
||||
|
||||
<ul>
|
||||
<li><a href="cli.html#cmdoption-osxphotos-timewarp-c">osxphotos-timewarp command line option</a>
|
||||
</li>
|
||||
</ul></li>
|
||||
<li>
|
||||
-D
|
||||
|
||||
<ul>
|
||||
<li><a href="cli.html#cmdoption-osxphotos-timewarp-D">osxphotos-timewarp command line option</a>
|
||||
</li>
|
||||
</ul></li>
|
||||
<li>
|
||||
-d
|
||||
|
||||
<ul>
|
||||
<li><a href="cli.html#cmdoption-osxphotos-timewarp-d">osxphotos-timewarp command line option</a>
|
||||
</li>
|
||||
</ul></li>
|
||||
<li>
|
||||
-e
|
||||
|
||||
<ul>
|
||||
<li><a href="cli.html#cmdoption-osxphotos-timewarp-e">osxphotos-timewarp command line option</a>
|
||||
</li>
|
||||
</ul></li>
|
||||
<li>
|
||||
-F
|
||||
|
||||
<ul>
|
||||
<li><a href="cli.html#cmdoption-osxphotos-timewarp-F">osxphotos-timewarp command line option</a>
|
||||
</li>
|
||||
</ul></li>
|
||||
<li>
|
||||
-f
|
||||
|
||||
<ul>
|
||||
<li><a href="cli.html#cmdoption-osxphotos-timewarp-0">osxphotos-timewarp command line option</a>
|
||||
</li>
|
||||
<li><a href="cli.html#cmdoption-osxphotos-uuid-f">osxphotos-uuid command line option</a>
|
||||
</li>
|
||||
</ul></li>
|
||||
@@ -1653,6 +1812,43 @@
|
||||
<li><a href="cli.html#cmdoption-osxphotos-query-i">osxphotos-query command line option</a>
|
||||
</li>
|
||||
<li><a href="cli.html#cmdoption-osxphotos-repl-i">osxphotos-repl command line option</a>
|
||||
</li>
|
||||
<li><a href="cli.html#cmdoption-osxphotos-timewarp-i">osxphotos-timewarp command line option</a>
|
||||
</li>
|
||||
</ul></li>
|
||||
<li>
|
||||
-L
|
||||
|
||||
<ul>
|
||||
<li><a href="cli.html#cmdoption-osxphotos-timewarp-L">osxphotos-timewarp command line option</a>
|
||||
</li>
|
||||
</ul></li>
|
||||
<li>
|
||||
-m
|
||||
|
||||
<ul>
|
||||
<li><a href="cli.html#cmdoption-osxphotos-timewarp-m">osxphotos-timewarp command line option</a>
|
||||
</li>
|
||||
</ul></li>
|
||||
<li>
|
||||
-o
|
||||
|
||||
<ul>
|
||||
<li><a href="cli.html#cmdoption-osxphotos-timewarp-o">osxphotos-timewarp command line option</a>
|
||||
</li>
|
||||
</ul></li>
|
||||
<li>
|
||||
-P
|
||||
|
||||
<ul>
|
||||
<li><a href="cli.html#cmdoption-osxphotos-timewarp-P">osxphotos-timewarp command line option</a>
|
||||
</li>
|
||||
</ul></li>
|
||||
<li>
|
||||
-p
|
||||
|
||||
<ul>
|
||||
<li><a href="cli.html#cmdoption-osxphotos-timewarp-p">osxphotos-timewarp command line option</a>
|
||||
</li>
|
||||
</ul></li>
|
||||
<li>
|
||||
@@ -1667,6 +1863,20 @@
|
||||
|
||||
<ul>
|
||||
<li><a href="cli.html#cmdoption-osxphotos-diff-s">osxphotos-diff command line option</a>
|
||||
</li>
|
||||
</ul></li>
|
||||
<li>
|
||||
-T
|
||||
|
||||
<ul>
|
||||
<li><a href="cli.html#cmdoption-osxphotos-timewarp-T">osxphotos-timewarp command line option</a>
|
||||
</li>
|
||||
</ul></li>
|
||||
<li>
|
||||
-t
|
||||
|
||||
<ul>
|
||||
<li><a href="cli.html#cmdoption-osxphotos-timewarp-t">osxphotos-timewarp command line option</a>
|
||||
</li>
|
||||
</ul></li>
|
||||
<li>
|
||||
@@ -1683,6 +1893,8 @@
|
||||
<li><a href="cli.html#cmdoption-osxphotos-diff-V">osxphotos-diff command line option</a>
|
||||
</li>
|
||||
<li><a href="cli.html#cmdoption-osxphotos-export-V">osxphotos-export command line option</a>
|
||||
</li>
|
||||
<li><a href="cli.html#cmdoption-osxphotos-timewarp-V">osxphotos-timewarp command line option</a>
|
||||
</li>
|
||||
</ul></li>
|
||||
<li>
|
||||
@@ -1697,6 +1909,13 @@
|
||||
|
||||
<ul>
|
||||
<li><a href="cli.html#cmdoption-osxphotos-uninstall-y">osxphotos-uninstall command line option</a>
|
||||
</li>
|
||||
</ul></li>
|
||||
<li>
|
||||
-z
|
||||
|
||||
<ul>
|
||||
<li><a href="cli.html#cmdoption-osxphotos-timewarp-z">osxphotos-timewarp command line option</a>
|
||||
</li>
|
||||
</ul></li>
|
||||
</ul></td>
|
||||
@@ -2760,8 +2979,6 @@
|
||||
<li><a href="cli.html#cmdoption-osxphotos-list-json">--json</a>
|
||||
</li>
|
||||
</ul></li>
|
||||
</ul></td>
|
||||
<td style="width: 33%; vertical-align: top;"><ul>
|
||||
<li>
|
||||
osxphotos-persons command line option
|
||||
|
||||
@@ -2949,6 +3166,8 @@
|
||||
<li><a href="cli.html#cmdoption-osxphotos-query-arg-PHOTOS_LIBRARY">PHOTOS_LIBRARY</a>
|
||||
</li>
|
||||
</ul></li>
|
||||
</ul></td>
|
||||
<td style="width: 33%; vertical-align: top;"><ul>
|
||||
<li>
|
||||
osxphotos-repl command line option
|
||||
|
||||
@@ -3141,6 +3360,85 @@
|
||||
<li><a href="cli.html#cmdoption-osxphotos-theme-list">--list</a>
|
||||
</li>
|
||||
<li><a href="cli.html#cmdoption-osxphotos-theme-preview">--preview</a>
|
||||
</li>
|
||||
</ul></li>
|
||||
<li>
|
||||
osxphotos-timewarp command line option
|
||||
|
||||
<ul>
|
||||
<li><a href="cli.html#cmdoption-osxphotos-timewarp-a">--add-to-album</a>
|
||||
</li>
|
||||
<li><a href="cli.html#cmdoption-osxphotos-timewarp-c">--compare-exif</a>
|
||||
</li>
|
||||
<li><a href="cli.html#cmdoption-osxphotos-timewarp-d">--date</a>
|
||||
</li>
|
||||
<li><a href="cli.html#cmdoption-osxphotos-timewarp-D">--date-delta</a>
|
||||
</li>
|
||||
<li><a href="cli.html#cmdoption-osxphotos-timewarp-e">--exiftool-path</a>
|
||||
</li>
|
||||
<li><a href="cli.html#cmdoption-osxphotos-timewarp-F">--function</a>
|
||||
</li>
|
||||
<li><a href="cli.html#cmdoption-osxphotos-timewarp-i">--inspect</a>
|
||||
</li>
|
||||
<li><a href="cli.html#cmdoption-osxphotos-timewarp-L">--library</a>
|
||||
</li>
|
||||
<li><a href="cli.html#cmdoption-osxphotos-timewarp-m">--match-time</a>
|
||||
</li>
|
||||
<li><a href="cli.html#cmdoption-osxphotos-timewarp-o">--output-file</a>
|
||||
</li>
|
||||
<li><a href="cli.html#cmdoption-osxphotos-timewarp-plain">--plain</a>
|
||||
</li>
|
||||
<li><a href="cli.html#cmdoption-osxphotos-timewarp-P">--pull-exif</a>
|
||||
</li>
|
||||
<li><a href="cli.html#cmdoption-osxphotos-timewarp-p">--push-exif</a>
|
||||
</li>
|
||||
<li><a href="cli.html#cmdoption-osxphotos-timewarp-theme">--theme</a>
|
||||
</li>
|
||||
<li><a href="cli.html#cmdoption-osxphotos-timewarp-t">--time</a>
|
||||
</li>
|
||||
<li><a href="cli.html#cmdoption-osxphotos-timewarp-T">--time-delta</a>
|
||||
</li>
|
||||
<li><a href="cli.html#cmdoption-osxphotos-timewarp-timestamp">--timestamp</a>
|
||||
</li>
|
||||
<li><a href="cli.html#cmdoption-osxphotos-timewarp-z">--timezone</a>
|
||||
</li>
|
||||
<li><a href="cli.html#cmdoption-osxphotos-timewarp-0">--use-file-time</a>
|
||||
</li>
|
||||
<li><a href="cli.html#cmdoption-osxphotos-timewarp-V">--verbose</a>
|
||||
</li>
|
||||
<li><a href="cli.html#cmdoption-osxphotos-timewarp-a">-a</a>
|
||||
</li>
|
||||
<li><a href="cli.html#cmdoption-osxphotos-timewarp-c">-c</a>
|
||||
</li>
|
||||
<li><a href="cli.html#cmdoption-osxphotos-timewarp-d">-d</a>
|
||||
</li>
|
||||
<li><a href="cli.html#cmdoption-osxphotos-timewarp-D">-D</a>
|
||||
</li>
|
||||
<li><a href="cli.html#cmdoption-osxphotos-timewarp-e">-e</a>
|
||||
</li>
|
||||
<li><a href="cli.html#cmdoption-osxphotos-timewarp-F">-F</a>
|
||||
</li>
|
||||
<li><a href="cli.html#cmdoption-osxphotos-timewarp-0">-f</a>
|
||||
</li>
|
||||
<li><a href="cli.html#cmdoption-osxphotos-timewarp-i">-i</a>
|
||||
</li>
|
||||
<li><a href="cli.html#cmdoption-osxphotos-timewarp-L">-L</a>
|
||||
</li>
|
||||
<li><a href="cli.html#cmdoption-osxphotos-timewarp-m">-m</a>
|
||||
</li>
|
||||
<li><a href="cli.html#cmdoption-osxphotos-timewarp-o">-o</a>
|
||||
</li>
|
||||
<li><a href="cli.html#cmdoption-osxphotos-timewarp-p">-p</a>
|
||||
</li>
|
||||
<li><a href="cli.html#cmdoption-osxphotos-timewarp-P">-P</a>
|
||||
</li>
|
||||
<li><a href="cli.html#cmdoption-osxphotos-timewarp-t">-t</a>
|
||||
</li>
|
||||
<li><a href="cli.html#cmdoption-osxphotos-timewarp-T">-T</a>
|
||||
</li>
|
||||
<li><a href="cli.html#cmdoption-osxphotos-timewarp-V">-V</a>
|
||||
</li>
|
||||
<li><a href="cli.html#cmdoption-osxphotos-timewarp-z">-z</a>
|
||||
</li>
|
||||
</ul></li>
|
||||
<li>
|
||||
|
||||
@@ -6,7 +6,7 @@
|
||||
<link rel="index" title="Index" href="genindex.html" /><link rel="search" title="Search" href="search.html" /><link rel="next" title="OSXPhotos" href="overview.html" />
|
||||
|
||||
<meta name="generator" content="sphinx-4.4.0, furo 2022.04.07"/>
|
||||
<title>osxphotos 0.47.13 documentation</title>
|
||||
<title>osxphotos 0.48.1 documentation</title>
|
||||
<link rel="stylesheet" type="text/css" href="_static/pygments.css" />
|
||||
<link rel="stylesheet" type="text/css" href="_static/styles/furo.css?digest=68f4518137b9aefe99b631505a2064c3c42c9852" />
|
||||
<link rel="stylesheet" type="text/css" href="_static/copybutton.css" />
|
||||
@@ -124,7 +124,7 @@
|
||||
</label>
|
||||
</div>
|
||||
<div class="header-center">
|
||||
<a href="#"><div class="brand">osxphotos 0.47.13 documentation</div></a>
|
||||
<a href="#"><div class="brand">osxphotos 0.48.1 documentation</div></a>
|
||||
</div>
|
||||
<div class="header-right">
|
||||
<div class="theme-toggle-container theme-toggle-header">
|
||||
@@ -147,7 +147,7 @@
|
||||
<div class="sidebar-sticky"><a class="sidebar-brand" href="#">
|
||||
|
||||
|
||||
<span class="sidebar-brand-text">osxphotos 0.47.13 documentation</span>
|
||||
<span class="sidebar-brand-text">osxphotos 0.48.1 documentation</span>
|
||||
|
||||
</a><form class="sidebar-search-container" method="get" action="search.html" role="search">
|
||||
<input class="sidebar-search" placeholder=Search name="q" aria-label="Search">
|
||||
@@ -251,6 +251,7 @@
|
||||
<li class="toctree-l3"><a class="reference internal" href="cli.html#osxphotos-run">run</a></li>
|
||||
<li class="toctree-l3"><a class="reference internal" href="cli.html#osxphotos-snap">snap</a></li>
|
||||
<li class="toctree-l3"><a class="reference internal" href="cli.html#osxphotos-theme">theme</a></li>
|
||||
<li class="toctree-l3"><a class="reference internal" href="cli.html#osxphotos-timewarp">timewarp</a></li>
|
||||
<li class="toctree-l3"><a class="reference internal" href="cli.html#osxphotos-tutorial">tutorial</a></li>
|
||||
<li class="toctree-l3"><a class="reference internal" href="cli.html#osxphotos-uninstall">uninstall</a></li>
|
||||
<li class="toctree-l3"><a class="reference internal" href="cli.html#osxphotos-uuid">uuid</a></li>
|
||||
|
||||
BIN
docs/objects.inv
BIN
docs/objects.inv
Binary file not shown.
@@ -6,7 +6,7 @@
|
||||
<link rel="index" title="Index" href="genindex.html" /><link rel="search" title="Search" href="search.html" /><link rel="next" title="OSXPhotos Tutorial" href="tutorial.html" /><link rel="prev" title="Welcome to OSXPhotos’s documentation!" href="index.html" />
|
||||
|
||||
<meta name="generator" content="sphinx-4.4.0, furo 2022.04.07"/>
|
||||
<title>OSXPhotos - osxphotos 0.47.13 documentation</title>
|
||||
<title>OSXPhotos - osxphotos 0.48.1 documentation</title>
|
||||
<link rel="stylesheet" type="text/css" href="_static/pygments.css" />
|
||||
<link rel="stylesheet" type="text/css" href="_static/styles/furo.css?digest=68f4518137b9aefe99b631505a2064c3c42c9852" />
|
||||
<link rel="stylesheet" type="text/css" href="_static/copybutton.css" />
|
||||
@@ -124,7 +124,7 @@
|
||||
</label>
|
||||
</div>
|
||||
<div class="header-center">
|
||||
<a href="index.html"><div class="brand">osxphotos 0.47.13 documentation</div></a>
|
||||
<a href="index.html"><div class="brand">osxphotos 0.48.1 documentation</div></a>
|
||||
</div>
|
||||
<div class="header-right">
|
||||
<div class="theme-toggle-container theme-toggle-header">
|
||||
@@ -147,7 +147,7 @@
|
||||
<div class="sidebar-sticky"><a class="sidebar-brand" href="index.html">
|
||||
|
||||
|
||||
<span class="sidebar-brand-text">osxphotos 0.47.13 documentation</span>
|
||||
<span class="sidebar-brand-text">osxphotos 0.48.1 documentation</span>
|
||||
|
||||
</a><form class="sidebar-search-container" method="get" action="search.html" role="search">
|
||||
<input class="sidebar-search" placeholder=Search name="q" aria-label="Search">
|
||||
@@ -260,6 +260,7 @@ E.g. you can read a database created with Photos 5.0 on MacOS 10.15 on a machine
|
||||
<span class="n">run</span> <span class="n">Run</span> <span class="n">a</span> <span class="n">python</span> <span class="n">file</span> <span class="n">using</span> <span class="n">same</span> <span class="n">environment</span> <span class="k">as</span> <span class="n">osxphotos</span>
|
||||
<span class="n">snap</span> <span class="n">Create</span> <span class="n">snapshot</span> <span class="n">of</span> <span class="n">Photos</span> <span class="n">database</span> <span class="n">to</span> <span class="n">use</span> <span class="k">with</span> <span class="n">diff</span> <span class="n">command</span>
|
||||
<span class="n">theme</span> <span class="n">Manage</span> <span class="n">osxphotos</span> <span class="n">color</span> <span class="n">themes</span><span class="o">.</span>
|
||||
<span class="n">timewarp</span> <span class="n">Adjust</span> <span class="n">date</span><span class="o">/</span><span class="n">time</span><span class="o">/</span><span class="n">timezone</span> <span class="n">of</span> <span class="n">photos</span> <span class="ow">in</span> <span class="n">Apple</span> <span class="n">Photos</span><span class="o">.</span>
|
||||
<span class="n">tutorial</span> <span class="n">Display</span> <span class="n">osxphotos</span> <span class="n">tutorial</span><span class="o">.</span>
|
||||
<span class="n">uninstall</span> <span class="n">Uninstall</span> <span class="n">Python</span> <span class="n">packages</span> <span class="kn">from</span> <span class="nn">the</span> <span class="n">osxphotos</span> <span class="n">environment</span>
|
||||
<span class="n">uuid</span> <span class="n">Print</span> <span class="n">out</span> <span class="n">unique</span> <span class="n">IDs</span> <span class="p">(</span><span class="n">UUID</span><span class="p">)</span> <span class="n">of</span> <span class="n">photos</span> <span class="n">selected</span> <span class="ow">in</span> <span class="n">Photos</span>
|
||||
|
||||
@@ -6,7 +6,7 @@
|
||||
<link rel="index" title="Index" href="genindex.html" /><link rel="search" title="Search" href="search.html" /><link rel="next" title="OSXPhotos python API" href="reference.html" /><link rel="prev" title="OSXPhotos Template System" href="template_help.html" />
|
||||
|
||||
<meta name="generator" content="sphinx-4.4.0, furo 2022.04.07"/>
|
||||
<title>OSXPhotos Python Package Overview - osxphotos 0.47.13 documentation</title>
|
||||
<title>OSXPhotos Python Package Overview - osxphotos 0.48.1 documentation</title>
|
||||
<link rel="stylesheet" type="text/css" href="_static/pygments.css" />
|
||||
<link rel="stylesheet" type="text/css" href="_static/styles/furo.css?digest=68f4518137b9aefe99b631505a2064c3c42c9852" />
|
||||
<link rel="stylesheet" type="text/css" href="_static/copybutton.css" />
|
||||
@@ -124,7 +124,7 @@
|
||||
</label>
|
||||
</div>
|
||||
<div class="header-center">
|
||||
<a href="index.html"><div class="brand">osxphotos 0.47.13 documentation</div></a>
|
||||
<a href="index.html"><div class="brand">osxphotos 0.48.1 documentation</div></a>
|
||||
</div>
|
||||
<div class="header-right">
|
||||
<div class="theme-toggle-container theme-toggle-header">
|
||||
@@ -147,7 +147,7 @@
|
||||
<div class="sidebar-sticky"><a class="sidebar-brand" href="index.html">
|
||||
|
||||
|
||||
<span class="sidebar-brand-text">osxphotos 0.47.13 documentation</span>
|
||||
<span class="sidebar-brand-text">osxphotos 0.48.1 documentation</span>
|
||||
|
||||
</a><form class="sidebar-search-container" method="get" action="search.html" role="search">
|
||||
<input class="sidebar-search" placeholder=Search name="q" aria-label="Search">
|
||||
|
||||
@@ -4,7 +4,7 @@
|
||||
<meta name="viewport" content="width=device-width,initial-scale=1"/>
|
||||
<meta name="color-scheme" content="light dark"><link rel="index" title="Index" href="genindex.html" /><link rel="search" title="Search" href="search.html" />
|
||||
|
||||
<meta name="generator" content="sphinx-4.4.0, furo 2022.04.07"/><title>Python Module Index - osxphotos 0.47.13 documentation</title>
|
||||
<meta name="generator" content="sphinx-4.4.0, furo 2022.04.07"/><title>Python Module Index - osxphotos 0.48.1 documentation</title>
|
||||
<link rel="stylesheet" type="text/css" href="_static/pygments.css" />
|
||||
<link rel="stylesheet" type="text/css" href="_static/styles/furo.css?digest=68f4518137b9aefe99b631505a2064c3c42c9852" />
|
||||
<link rel="stylesheet" type="text/css" href="_static/copybutton.css" />
|
||||
@@ -122,7 +122,7 @@
|
||||
</label>
|
||||
</div>
|
||||
<div class="header-center">
|
||||
<a href="index.html"><div class="brand">osxphotos 0.47.13 documentation</div></a>
|
||||
<a href="index.html"><div class="brand">osxphotos 0.48.1 documentation</div></a>
|
||||
</div>
|
||||
<div class="header-right">
|
||||
<div class="theme-toggle-container theme-toggle-header">
|
||||
@@ -145,7 +145,7 @@
|
||||
<div class="sidebar-sticky"><a class="sidebar-brand" href="index.html">
|
||||
|
||||
|
||||
<span class="sidebar-brand-text">osxphotos 0.47.13 documentation</span>
|
||||
<span class="sidebar-brand-text">osxphotos 0.48.1 documentation</span>
|
||||
|
||||
</a><form class="sidebar-search-container" method="get" action="search.html" role="search">
|
||||
<input class="sidebar-search" placeholder=Search name="q" aria-label="Search">
|
||||
|
||||
@@ -6,7 +6,7 @@
|
||||
<link rel="index" title="Index" href="genindex.html" /><link rel="search" title="Search" href="search.html" /><link rel="prev" title="OSXPhotos Python Package Overview" href="package_overview.html" />
|
||||
|
||||
<meta name="generator" content="sphinx-4.4.0, furo 2022.04.07"/>
|
||||
<title>OSXPhotos python API - osxphotos 0.47.13 documentation</title>
|
||||
<title>OSXPhotos python API - osxphotos 0.48.1 documentation</title>
|
||||
<link rel="stylesheet" type="text/css" href="_static/pygments.css" />
|
||||
<link rel="stylesheet" type="text/css" href="_static/styles/furo.css?digest=68f4518137b9aefe99b631505a2064c3c42c9852" />
|
||||
<link rel="stylesheet" type="text/css" href="_static/copybutton.css" />
|
||||
@@ -124,7 +124,7 @@
|
||||
</label>
|
||||
</div>
|
||||
<div class="header-center">
|
||||
<a href="index.html"><div class="brand">osxphotos 0.47.13 documentation</div></a>
|
||||
<a href="index.html"><div class="brand">osxphotos 0.48.1 documentation</div></a>
|
||||
</div>
|
||||
<div class="header-right">
|
||||
<div class="theme-toggle-container theme-toggle-header">
|
||||
@@ -147,7 +147,7 @@
|
||||
<div class="sidebar-sticky"><a class="sidebar-brand" href="index.html">
|
||||
|
||||
|
||||
<span class="sidebar-brand-text">osxphotos 0.47.13 documentation</span>
|
||||
<span class="sidebar-brand-text">osxphotos 0.48.1 documentation</span>
|
||||
|
||||
</a><form class="sidebar-search-container" method="get" action="search.html" role="search">
|
||||
<input class="sidebar-search" placeholder=Search name="q" aria-label="Search">
|
||||
|
||||
@@ -4,7 +4,7 @@
|
||||
<meta name="viewport" content="width=device-width,initial-scale=1"/>
|
||||
<meta name="color-scheme" content="light dark"><link rel="index" title="Index" href="genindex.html" /><link rel="search" title="Search" href="#" />
|
||||
|
||||
<meta name="generator" content="sphinx-4.4.0, furo 2022.04.07"/><title>Search - osxphotos 0.47.13 documentation</title><link rel="stylesheet" type="text/css" href="_static/pygments.css" />
|
||||
<meta name="generator" content="sphinx-4.4.0, furo 2022.04.07"/><title>Search - osxphotos 0.48.1 documentation</title><link rel="stylesheet" type="text/css" href="_static/pygments.css" />
|
||||
<link rel="stylesheet" type="text/css" href="_static/styles/furo.css?digest=68f4518137b9aefe99b631505a2064c3c42c9852" />
|
||||
<link rel="stylesheet" type="text/css" href="_static/copybutton.css" />
|
||||
<link rel="stylesheet" type="text/css" href="_static/styles/furo-extensions.css?digest=30d1aed668e5c3a91c3e3bf6a60b675221979f0e" />
|
||||
@@ -121,7 +121,7 @@
|
||||
</label>
|
||||
</div>
|
||||
<div class="header-center">
|
||||
<a href="index.html"><div class="brand">osxphotos 0.47.13 documentation</div></a>
|
||||
<a href="index.html"><div class="brand">osxphotos 0.48.1 documentation</div></a>
|
||||
</div>
|
||||
<div class="header-right">
|
||||
<div class="theme-toggle-container theme-toggle-header">
|
||||
@@ -144,7 +144,7 @@
|
||||
<div class="sidebar-sticky"><a class="sidebar-brand" href="index.html">
|
||||
|
||||
|
||||
<span class="sidebar-brand-text">osxphotos 0.47.13 documentation</span>
|
||||
<span class="sidebar-brand-text">osxphotos 0.48.1 documentation</span>
|
||||
|
||||
</a><form class="sidebar-search-container" method="get" action="#" role="search">
|
||||
<input class="sidebar-search" placeholder=Search name="q" aria-label="Search">
|
||||
|
||||
File diff suppressed because one or more lines are too long
@@ -6,7 +6,7 @@
|
||||
<link rel="index" title="Index" href="genindex.html" /><link rel="search" title="Search" href="search.html" /><link rel="next" title="OSXPhotos Python Package Overview" href="package_overview.html" /><link rel="prev" title="OSXPhotos Command Line Interface (CLI)" href="cli.html" />
|
||||
|
||||
<meta name="generator" content="sphinx-4.4.0, furo 2022.04.07"/>
|
||||
<title>OSXPhotos Template System - osxphotos 0.47.13 documentation</title>
|
||||
<title>OSXPhotos Template System - osxphotos 0.48.1 documentation</title>
|
||||
<link rel="stylesheet" type="text/css" href="_static/pygments.css" />
|
||||
<link rel="stylesheet" type="text/css" href="_static/styles/furo.css?digest=68f4518137b9aefe99b631505a2064c3c42c9852" />
|
||||
<link rel="stylesheet" type="text/css" href="_static/copybutton.css" />
|
||||
@@ -124,7 +124,7 @@
|
||||
</label>
|
||||
</div>
|
||||
<div class="header-center">
|
||||
<a href="index.html"><div class="brand">osxphotos 0.47.13 documentation</div></a>
|
||||
<a href="index.html"><div class="brand">osxphotos 0.48.1 documentation</div></a>
|
||||
</div>
|
||||
<div class="header-right">
|
||||
<div class="theme-toggle-container theme-toggle-header">
|
||||
@@ -147,7 +147,7 @@
|
||||
<div class="sidebar-sticky"><a class="sidebar-brand" href="index.html">
|
||||
|
||||
|
||||
<span class="sidebar-brand-text">osxphotos 0.47.13 documentation</span>
|
||||
<span class="sidebar-brand-text">osxphotos 0.48.1 documentation</span>
|
||||
|
||||
</a><form class="sidebar-search-container" method="get" action="search.html" role="search">
|
||||
<input class="sidebar-search" placeholder=Search name="q" aria-label="Search">
|
||||
@@ -564,7 +564,7 @@
|
||||
<td><p>a carriage return + line feed: ‘rn’</p></td>
|
||||
</tr>
|
||||
<tr class="row-even"><td><p>{osxphotos_version}</p></td>
|
||||
<td><p>The osxphotos version, e.g. ‘0.47.13’</p></td>
|
||||
<td><p>The osxphotos version, e.g. ‘0.48.1’</p></td>
|
||||
</tr>
|
||||
<tr class="row-odd"><td><p>{osxphotos_cmd_line}</p></td>
|
||||
<td><p>The full command line used to run osxphotos</p></td>
|
||||
|
||||
@@ -6,7 +6,7 @@
|
||||
<link rel="index" title="Index" href="genindex.html" /><link rel="search" title="Search" href="search.html" /><link rel="next" title="OSXPhotos Command Line Interface (CLI)" href="cli.html" /><link rel="prev" title="OSXPhotos" href="overview.html" />
|
||||
|
||||
<meta name="generator" content="sphinx-4.4.0, furo 2022.04.07"/>
|
||||
<title>OSXPhotos Tutorial - osxphotos 0.47.13 documentation</title>
|
||||
<title>OSXPhotos Tutorial - osxphotos 0.48.1 documentation</title>
|
||||
<link rel="stylesheet" type="text/css" href="_static/pygments.css" />
|
||||
<link rel="stylesheet" type="text/css" href="_static/styles/furo.css?digest=68f4518137b9aefe99b631505a2064c3c42c9852" />
|
||||
<link rel="stylesheet" type="text/css" href="_static/copybutton.css" />
|
||||
@@ -124,7 +124,7 @@
|
||||
</label>
|
||||
</div>
|
||||
<div class="header-center">
|
||||
<a href="index.html"><div class="brand">osxphotos 0.47.13 documentation</div></a>
|
||||
<a href="index.html"><div class="brand">osxphotos 0.48.1 documentation</div></a>
|
||||
</div>
|
||||
<div class="header-right">
|
||||
<div class="theme-toggle-container theme-toggle-header">
|
||||
@@ -147,7 +147,7 @@
|
||||
<div class="sidebar-sticky"><a class="sidebar-brand" href="index.html">
|
||||
|
||||
|
||||
<span class="sidebar-brand-text">osxphotos 0.47.13 documentation</span>
|
||||
<span class="sidebar-brand-text">osxphotos 0.48.1 documentation</span>
|
||||
|
||||
</a><form class="sidebar-search-container" method="get" action="search.html" role="search">
|
||||
<input class="sidebar-search" placeholder=Search name="q" aria-label="Search">
|
||||
|
||||
@@ -74,6 +74,7 @@ This package will install a command line utility called ``osxphotos`` that allow
|
||||
run Run a python file using same environment as osxphotos
|
||||
snap Create snapshot of Photos database to use with diff command
|
||||
theme Manage osxphotos color themes.
|
||||
timewarp Adjust date/time/timezone of photos in Apple Photos.
|
||||
tutorial Display osxphotos tutorial.
|
||||
uninstall Uninstall Python packages from the osxphotos environment
|
||||
uuid Print out unique IDs (UUID) of photos selected in Photos
|
||||
|
||||
@@ -318,7 +318,7 @@ Template Substitutions
|
||||
* - {crlf}
|
||||
- a carriage return + line feed: '\r\n'
|
||||
* - {osxphotos_version}
|
||||
- The osxphotos version, e.g. '0.47.13'
|
||||
- The osxphotos version, e.g. '0.48.1'
|
||||
* - {osxphotos_cmd_line}
|
||||
- The full command line used to run osxphotos
|
||||
* - {album}
|
||||
|
||||
46
examples/timewarp_function_example.py
Normal file
46
examples/timewarp_function_example.py
Normal file
@@ -0,0 +1,46 @@
|
||||
"""Example function for use with `osxphotos timewarp --function`
|
||||
|
||||
Call this as: `osxphotos timewarp --function timewarp_function_example.py::get_date_time_timezone`
|
||||
"""
|
||||
|
||||
from datetime import datetime, timedelta
|
||||
from typing import Callable, Optional, Tuple
|
||||
|
||||
from photoscript import Photo
|
||||
|
||||
|
||||
def get_date_time_timezone(
|
||||
photo: Photo, path: Optional[str], tz_sec: int, tz_name: str, verbose: Callable
|
||||
) -> Tuple[datetime, int]:
|
||||
"""Example function for use with `osxphotos timewarp --function`
|
||||
|
||||
Args:
|
||||
photo: Photo object
|
||||
path: path to photo, which may be None if photo is not on disk
|
||||
tz_sec: timezone offset from UTC in seconds
|
||||
tz_name: timezone name
|
||||
verbose: function to print verbose messages
|
||||
|
||||
Returns:
|
||||
tuple of (new date/time as datetime.datetime, and new timezone offset from UTC in seconds as int)
|
||||
"""
|
||||
|
||||
# this example adds 3 hours to the date/time and subtracts 1 hour from the timezone
|
||||
|
||||
# the photo's date/time can be accessed as photo.date
|
||||
# photo.date is a datetime.datetime object
|
||||
# the date/time is naive (timezone unaware) as will be in local timezone
|
||||
date = photo.date
|
||||
|
||||
# add 3 hours
|
||||
date = date + timedelta(hours=3)
|
||||
|
||||
# subtract 1 hour from timezone
|
||||
timezone = tz_sec - 3600
|
||||
|
||||
# verbose(msg) prints a message to the console if user used --verbose option
|
||||
# otherwise it does nothing
|
||||
# photo's filename can be access as photo.filename
|
||||
verbose(f"Updating {photo.filename} date/time: {date} and timezone: {timezone}")
|
||||
|
||||
return date, timezone
|
||||
@@ -1,3 +1,3 @@
|
||||
""" version info """
|
||||
|
||||
__version__ = "0.47.13"
|
||||
__version__ = "0.48.1"
|
||||
|
||||
@@ -25,6 +25,7 @@ from .query import query
|
||||
from .repl import repl
|
||||
from .snap_diff import diff, snap
|
||||
from .theme import theme
|
||||
from .timewarp import timewarp
|
||||
from .tutorial import tutorial
|
||||
from .uuid import uuid
|
||||
from .version import version
|
||||
@@ -82,6 +83,7 @@ for command in [
|
||||
run,
|
||||
snap,
|
||||
theme,
|
||||
timewarp,
|
||||
tutorial,
|
||||
uninstall,
|
||||
uuid,
|
||||
|
||||
@@ -22,23 +22,26 @@ __all__ = [
|
||||
|
||||
|
||||
THEME_STYLES = [
|
||||
"bar.back",
|
||||
"bar.complete",
|
||||
"bar.finished",
|
||||
"bar.pulse",
|
||||
"change",
|
||||
"color",
|
||||
"count",
|
||||
"error",
|
||||
"filename",
|
||||
"filepath",
|
||||
"highlight",
|
||||
"no_change",
|
||||
"num",
|
||||
"time",
|
||||
"uuid",
|
||||
"warning",
|
||||
"bar.back",
|
||||
"bar.complete",
|
||||
"bar.finished",
|
||||
"bar.pulse",
|
||||
"progress.elapsed",
|
||||
"progress.percentage",
|
||||
"progress.remaining",
|
||||
"time",
|
||||
"tz",
|
||||
"uuid",
|
||||
"warning",
|
||||
]
|
||||
|
||||
COLOR_THEMES = {
|
||||
@@ -48,23 +51,26 @@ COLOR_THEMES = {
|
||||
tags=["dark"],
|
||||
styles={
|
||||
# color pallette from https://github.com/dracula/dracula-theme
|
||||
"bar.back": Style(color="rgb(68,71,90)"),
|
||||
"bar.complete": Style(color="rgb(249,38,114)"),
|
||||
"bar.finished": Style(color="rgb(80,250,123)"),
|
||||
"bar.pulse": Style(color="rgb(98,114,164)"),
|
||||
"change": Style(color="bright_red", bold=True),
|
||||
"color": Style(color="rgb(248,248,242)"),
|
||||
"count": Style(color="rgb(139,233,253)"),
|
||||
"error": Style(color="rgb(255,85,85)", bold=True),
|
||||
"filename": Style(color="rgb(189,147,249)", bold=True),
|
||||
"filepath": Style(color="rgb(80,250,123)", bold=True),
|
||||
"highlight": Style(color="#000000", bgcolor="#d73a49", bold=True),
|
||||
"no_change": Style(color="bright_green", bold=True),
|
||||
"num": Style(color="rgb(139,233,253)", bold=True),
|
||||
"time": Style(color="rgb(139,233,253)", bold=True),
|
||||
"uuid": Style(color="rgb(255,184,108)"),
|
||||
"warning": Style(color="rgb(241,250,140)", bold=True),
|
||||
"bar.back": Style(color="rgb(68,71,90)"),
|
||||
"bar.complete": Style(color="rgb(249,38,114)"),
|
||||
"bar.finished": Style(color="rgb(80,250,123)"),
|
||||
"bar.pulse": Style(color="rgb(98,114,164)"),
|
||||
"progress.elapsed": Style(color="rgb(139,233,253)"),
|
||||
"progress.percentage": Style(color="rgb(255,121,198)"),
|
||||
"progress.remaining": Style(color="rgb(139,233,253)"),
|
||||
"time": Style(color="rgb(139,233,253)", bold=True),
|
||||
"tz": Style(color="bright_cyan", bold=True),
|
||||
"uuid": Style(color="rgb(255,184,108)"),
|
||||
"warning": Style(color="rgb(241,250,140)", bold=True),
|
||||
# "headers": Style(color="rgb(165,194,97)"),
|
||||
# "options": Style(color="rgb(255,198,109)"),
|
||||
# "metavar": Style(color="rgb(12,125,157)"),
|
||||
@@ -74,23 +80,26 @@ COLOR_THEMES = {
|
||||
name="light",
|
||||
description="Light mode theme",
|
||||
styles={
|
||||
"bar.back": Style(color="grey23"),
|
||||
"bar.complete": Style(color="rgb(249,38,114)"),
|
||||
"bar.finished": Style(color="rgb(114,156,31)"),
|
||||
"bar.pulse": Style(color="rgb(249,38,114)"),
|
||||
"change": "bold dark_red",
|
||||
"color": Style(color="#000000"),
|
||||
"count": Style(color="#005cc5", bold=True),
|
||||
"error": Style(color="#b31d28", bold=True, underline=True, italic=True),
|
||||
"filename": Style(color="#6f42c1", bold=True),
|
||||
"filepath": Style(color="#22863a", bold=True),
|
||||
"highlight": Style(color="#ffffff", bgcolor="#d73a49", bold=True),
|
||||
"no_change": "bold dark_green",
|
||||
"num": Style(color="#005cc5", bold=True),
|
||||
"time": Style(color="#032f62", bold=True),
|
||||
"uuid": Style(color="#d73a49", bold=True),
|
||||
"warning": Style(color="#e36209", bold=True, underline=True, italic=True),
|
||||
"bar.back": Style(color="grey23"),
|
||||
"bar.complete": Style(color="rgb(249,38,114)"),
|
||||
"bar.finished": Style(color="rgb(114,156,31)"),
|
||||
"bar.pulse": Style(color="rgb(249,38,114)"),
|
||||
"progress.elapsed": Style(color="#032f62", bold=True),
|
||||
"progress.percentage": Style(color="#6f42c1", bold=True),
|
||||
"progress.remaining": Style(color="#032f62", bold=True),
|
||||
"time": Style(color="#032f62", bold=True),
|
||||
"tz": "bold cyan",
|
||||
"uuid": Style(color="#d73a49", bold=True),
|
||||
"warning": Style(color="#e36209", bold=True, underline=True, italic=True),
|
||||
# "headers": Style(color="rgb(254,212,66)"),
|
||||
# "options": Style(color="rgb(227,98,9)"),
|
||||
# "metavar": Style(color="rgb(111,66,193)"),
|
||||
@@ -101,22 +110,25 @@ COLOR_THEMES = {
|
||||
description="Monochromatic theme",
|
||||
tags=["mono", "colorblind"],
|
||||
styles={
|
||||
"bar.back": "",
|
||||
"bar.complete": "reverse",
|
||||
"bar.finished": "bold",
|
||||
"bar.pulse": "bold",
|
||||
"change": "reverse",
|
||||
"count": "bold",
|
||||
"error": "reverse italic",
|
||||
"filename": "bold",
|
||||
"filepath": "bold underline",
|
||||
"highlight": "reverse italic",
|
||||
"no_change": "",
|
||||
"num": "bold",
|
||||
"time": "bold",
|
||||
"uuid": "bold",
|
||||
"warning": "bold italic",
|
||||
"bar.back": "",
|
||||
"bar.complete": "reverse",
|
||||
"bar.finished": "bold",
|
||||
"bar.pulse": "bold",
|
||||
"progress.elapsed": "",
|
||||
"progress.percentage": "bold",
|
||||
"progress.remaining": "bold",
|
||||
"time": "bold",
|
||||
"tz": "",
|
||||
"uuid": "bold",
|
||||
"warning": "bold italic",
|
||||
# "headers": "bold",
|
||||
# "options": "bold",
|
||||
# "metavar": "bold",
|
||||
@@ -127,23 +139,26 @@ COLOR_THEMES = {
|
||||
description="Plain theme with no colors",
|
||||
tags=["colorblind"],
|
||||
styles={
|
||||
"bar.back": "",
|
||||
"bar.complete": "",
|
||||
"bar.finished": "",
|
||||
"bar.pulse": "",
|
||||
"change": "",
|
||||
"color": "",
|
||||
"count": "",
|
||||
"error": "",
|
||||
"filename": "",
|
||||
"filepath": "",
|
||||
"highlight": "",
|
||||
"no_change": "",
|
||||
"num": "",
|
||||
"time": "",
|
||||
"uuid": "",
|
||||
"warning": "",
|
||||
"bar.back": "",
|
||||
"bar.complete": "",
|
||||
"bar.finished": "",
|
||||
"bar.pulse": "",
|
||||
"progress.elapsed": "",
|
||||
"progress.percentage": "",
|
||||
"progress.remaining": "",
|
||||
"time": "",
|
||||
"tz": "",
|
||||
"uuid": "",
|
||||
"warning": "",
|
||||
# "headers": "",
|
||||
# "options": "",
|
||||
# "metavar": "",
|
||||
@@ -162,7 +177,9 @@ def get_theme_dir() -> pathlib.Path:
|
||||
|
||||
def get_theme_manager() -> ThemeManager:
|
||||
"""Return theme manager instance"""
|
||||
return ThemeManager(theme_dir=str(get_theme_dir()), themes=COLOR_THEMES.values())
|
||||
return ThemeManager(
|
||||
theme_dir=str(get_theme_dir()), themes=COLOR_THEMES.values(), update=True
|
||||
)
|
||||
|
||||
|
||||
def get_theme(
|
||||
|
||||
@@ -569,3 +569,4 @@ def check_version():
|
||||
"to suppress this message and prevent osxphotos from checking for latest version.",
|
||||
err=True,
|
||||
)
|
||||
|
||||
|
||||
@@ -5,19 +5,26 @@ import pathlib
|
||||
|
||||
import bitmath
|
||||
import click
|
||||
import pytimeparse
|
||||
|
||||
from osxphotos.export_db_utils import export_db_get_version
|
||||
from osxphotos.photoinfo import PhotoInfoNone
|
||||
from osxphotos.phototemplate import PhotoTemplate, RenderOptions
|
||||
from osxphotos.timeutils import time_string_to_datetime, utc_offset_string_to_seconds
|
||||
from osxphotos.timezones import Timezone
|
||||
from osxphotos.utils import expand_and_validate_filepath, load_function
|
||||
|
||||
__all__ = [
|
||||
"BitMathSize",
|
||||
"DateOffset",
|
||||
"DateTimeISO8601",
|
||||
"ExportDBType",
|
||||
"FunctionCall",
|
||||
"TimeISO8601",
|
||||
"TemplateString",
|
||||
"TimeISO8601",
|
||||
"TimeOffset",
|
||||
"TimeString",
|
||||
"UTCOffset",
|
||||
]
|
||||
|
||||
|
||||
@@ -129,3 +136,75 @@ class TemplateString(click.ParamType):
|
||||
return value
|
||||
except ValueError as e:
|
||||
self.fail(e)
|
||||
|
||||
|
||||
class TimeString(click.ParamType):
|
||||
"""A timestring in format HH:MM:SS, HH:MM:SS.fff, HH:MM"""
|
||||
|
||||
name = "TIMESTRING"
|
||||
|
||||
def convert(self, value, param, ctx):
|
||||
try:
|
||||
return time_string_to_datetime(value)
|
||||
except ValueError:
|
||||
self.fail(
|
||||
f"Invalid time format: {value}. "
|
||||
"Valid format for time: 'HH:MM:SS', 'HH:MM:SS.fff', 'HH:MM'"
|
||||
)
|
||||
|
||||
|
||||
class DateOffset(click.ParamType):
|
||||
"""A date offset string in the format ±D days, ±W weeks, ±Y years, ±D where D is days"""
|
||||
|
||||
name = "DATEOFFSET"
|
||||
|
||||
def convert(self, value, param, ctx):
|
||||
offset = pytimeparse.parse(value)
|
||||
if offset is not None:
|
||||
offset = offset / 86400
|
||||
return datetime.timedelta(days=offset)
|
||||
|
||||
# could be in format "-1" (negative offset) or "+1" (positive offset)
|
||||
try:
|
||||
return datetime.timedelta(days=int(value))
|
||||
except ValueError:
|
||||
self.fail(
|
||||
f"Invalid date offset format: {value}. "
|
||||
"Valid format for date/time offset: '±D days', '±W weeks', '±D' where D is days "
|
||||
)
|
||||
|
||||
|
||||
class TimeOffset(click.ParamType):
|
||||
"""A time offset string in the format [+-]HH:MM[:SS[.fff[fff]]] or +1 days, -2 hours, -18000, etc"""
|
||||
|
||||
name = "TIMEOFFSET"
|
||||
|
||||
def convert(self, value, param, ctx):
|
||||
offset = pytimeparse.parse(value)
|
||||
if offset is not None:
|
||||
return datetime.timedelta(seconds=offset)
|
||||
|
||||
# could be in format "-18000" (negative offset) or "+18000" (positive offset)
|
||||
try:
|
||||
return datetime.timedelta(seconds=int(value))
|
||||
except ValueError:
|
||||
self.fail(
|
||||
f"Invalid time offset format: {value}. "
|
||||
"Valid format for date/time offset: '±HH:MM:SS', '±H hours' (or hr), '±M minutes' (or min), '±S seconds' (or sec), '±S' (where S is seconds)"
|
||||
)
|
||||
|
||||
|
||||
class UTCOffset(click.ParamType):
|
||||
"""A UTC offset timezone in format ±[hh]:[mm], ±[h]:[mm], or ±[hh][mm]"""
|
||||
|
||||
name = "UTC_OFFSET"
|
||||
|
||||
def convert(self, value, param, ctx):
|
||||
try:
|
||||
offset_seconds = utc_offset_string_to_seconds(value)
|
||||
return Timezone(offset_seconds)
|
||||
except Exception:
|
||||
self.fail(
|
||||
f"Invalid timezone format: {value}. "
|
||||
"Valid format for timezone offset: '±HH:MM', '±H:MM', or '±HHMM'"
|
||||
)
|
||||
|
||||
657
osxphotos/cli/timewarp.py
Normal file
657
osxphotos/cli/timewarp.py
Normal file
@@ -0,0 +1,657 @@
|
||||
""" Fix time / date / timezone for photos in Apple Photos """
|
||||
|
||||
import datetime
|
||||
import os
|
||||
import sys
|
||||
from functools import partial
|
||||
from textwrap import dedent
|
||||
from typing import Callable, Optional
|
||||
|
||||
import click
|
||||
from photoscript import Photo, PhotosLibrary
|
||||
from rich.console import Console
|
||||
|
||||
from osxphotos._constants import APP_NAME
|
||||
from osxphotos.compare_exif import PhotoCompare
|
||||
from osxphotos.datetime_utils import datetime_naive_to_local, datetime_to_new_tz
|
||||
from osxphotos.exif_datetime_updater import ExifDateTimeUpdater
|
||||
from osxphotos.exiftool import get_exiftool_path
|
||||
from osxphotos.photosalbum import PhotosAlbumPhotoScript
|
||||
from osxphotos.phototz import PhotoTimeZone, PhotoTimeZoneUpdater
|
||||
from osxphotos.timeutils import update_datetime
|
||||
from osxphotos.timezones import Timezone
|
||||
from osxphotos.utils import noop, pluralize
|
||||
|
||||
from .click_rich_echo import (
|
||||
rich_click_echo,
|
||||
rich_echo,
|
||||
rich_echo_error,
|
||||
set_rich_console,
|
||||
set_rich_theme,
|
||||
set_rich_timestamp,
|
||||
)
|
||||
from .color_themes import get_theme
|
||||
from .common import THEME_OPTION
|
||||
from .darkmode import is_dark_mode
|
||||
from .help import HELP_WIDTH, rich_text
|
||||
from .param_types import (
|
||||
DateOffset,
|
||||
DateTimeISO8601,
|
||||
FunctionCall,
|
||||
TimeOffset,
|
||||
TimeString,
|
||||
UTCOffset,
|
||||
)
|
||||
from .rich_progress import rich_progress
|
||||
from .verbose import get_verbose_console, verbose_print
|
||||
|
||||
# format for pretty printing date/times
|
||||
DATETIME_FORMAT = "%Y-%m-%d %H:%M:%S%z"
|
||||
|
||||
|
||||
def update_photo_date_time(
|
||||
photo: Photo,
|
||||
date,
|
||||
time,
|
||||
date_delta,
|
||||
time_delta,
|
||||
verbose_print: Callable,
|
||||
):
|
||||
"""Update date, time in photo"""
|
||||
photo_date = photo.date
|
||||
new_photo_date = update_datetime(
|
||||
photo_date, date=date, time=time, date_delta=date_delta, time_delta=time_delta
|
||||
)
|
||||
filename = photo.filename
|
||||
uuid = photo.uuid
|
||||
if new_photo_date != photo_date:
|
||||
photo.date = new_photo_date
|
||||
verbose_print(
|
||||
f"Updated date/time for photo [filename]{filename}[/filename] "
|
||||
f"([uuid]{uuid}[/uuid]) from: [time]{photo_date}[/time] to [time]{new_photo_date}[/time]"
|
||||
)
|
||||
else:
|
||||
verbose_print(
|
||||
f"Skipped date/time update for photo [filename]{filename}[/filename] "
|
||||
f"([uuid]{uuid}[/uuid]): nothing to do"
|
||||
)
|
||||
|
||||
|
||||
def update_photo_time_for_new_timezone(
|
||||
library_path: str,
|
||||
photo: Photo,
|
||||
new_timezone: Timezone,
|
||||
verbose_print: Callable,
|
||||
):
|
||||
"""Update time in photo to keep it the same time but in a new timezone
|
||||
|
||||
For example, photo time is 12:00+0100 and new timezone is +0200,
|
||||
so adjust photo time by 1 hour so it will now be 12:00+0200 instead of
|
||||
13:00+0200 as it would be with no adjustment to the time"""
|
||||
old_timezone = PhotoTimeZone(library_path=library_path).get_timezone(photo)[0]
|
||||
# need to move time in opposite direction of timezone offset so that
|
||||
# photo time is the same time but in the new timezone
|
||||
delta = old_timezone - new_timezone.offset
|
||||
photo_date = photo.date
|
||||
new_photo_date = update_datetime(
|
||||
dt=photo_date, time_delta=datetime.timedelta(seconds=delta)
|
||||
)
|
||||
filename = photo.filename
|
||||
uuid = photo.uuid
|
||||
if photo_date != new_photo_date:
|
||||
photo.date = new_photo_date
|
||||
verbose_print(
|
||||
f"Adjusted date/time for photo [filename]{filename}[/filename] ([uuid]{uuid}[/uuid]) to match "
|
||||
f"previous time [time]{photo_date}[time] but in new timezone [tz]{new_timezone}[/tz]."
|
||||
)
|
||||
else:
|
||||
verbose_print(
|
||||
f"Skipping date/time update for photo [filename]{filename}[/filename] ([uuid]{photo.uuid}[/uuid]), "
|
||||
f"already matches new timezone [tz]{new_timezone}[/tz]"
|
||||
)
|
||||
|
||||
|
||||
def update_photo_from_function(
|
||||
library_path: str,
|
||||
function: Callable,
|
||||
verbose_print: Callable,
|
||||
photo: Photo,
|
||||
path: Optional[str],
|
||||
):
|
||||
"""Update photo from function call"""
|
||||
photo_tz_sec, _, photo_tz_name = PhotoTimeZone(
|
||||
library_path=library_path
|
||||
).get_timezone(photo)
|
||||
dt_new, tz_new = function(
|
||||
photo=photo,
|
||||
path=path,
|
||||
tz_sec=photo_tz_sec,
|
||||
tz_name=photo_tz_name,
|
||||
verbose=verbose_print,
|
||||
)
|
||||
if dt_new != photo.date:
|
||||
old_date = photo.date
|
||||
photo.date = dt_new
|
||||
verbose_print(
|
||||
f"Updated date/time for photo [filename]{photo.filename}[/filename] "
|
||||
f"([uuid]{photo.uuid}[/uuid]) from: [time]{old_date}[/time] to [time]{dt_new}[/time]"
|
||||
)
|
||||
else:
|
||||
verbose_print(
|
||||
f"Skipped date/time update for photo [filename]{photo.filename}[/filename] "
|
||||
f"([uuid]{photo.uuid}[/uuid]): nothing to do"
|
||||
)
|
||||
if tz_new != photo_tz_sec:
|
||||
tz_updater = PhotoTimeZoneUpdater(
|
||||
timezone=Timezone(tz_new), verbose=verbose_print, library_path=library_path
|
||||
)
|
||||
tz_updater.update_photo(photo)
|
||||
else:
|
||||
verbose_print(
|
||||
f"Skipped timezone update for photo [filename]{photo.filename}[/filename] "
|
||||
f"([uuid]{photo.uuid}[/uuid]): nothing to do"
|
||||
)
|
||||
|
||||
|
||||
class TimeWarpCommand(click.Command):
|
||||
"""Custom click.Command that overrides get_help() to show additional help info for export"""
|
||||
|
||||
def get_help(self, ctx):
|
||||
help_text = super().get_help(ctx)
|
||||
formatter = click.HelpFormatter(width=HELP_WIDTH)
|
||||
formatter.write("\n\n")
|
||||
formatter.write(
|
||||
rich_text(
|
||||
dedent(
|
||||
"""
|
||||
# Timewarp Overview
|
||||
|
||||
Timewarp operates on photos selected in Apple Photos. To use it, open Photos, select the photos for which you'd like to adjust the date/time/timezone, then run osxphotos timewarp from the command line:
|
||||
|
||||
`osxphotos timewarp --date 2021-09-10 --time-delta "-1 hour" --timezone -0700 --verbose`
|
||||
|
||||
This example sets the date for all selected photos to `2021-09-10`, subtracts 1 hour from the time of each photo, and sets the timezone of each photo to `GMT -07:00` (Pacific Daylight Time).
|
||||
|
||||
osxphotos timewarp has been well tested on macOS Catalina (10.15). It should work on macOS Big Sur (11.0) and macOS Monterey (12.0) but I have not been able to test this. It will not work on macOS Mojave (10.14) or earlier as the Photos database format is different.
|
||||
|
||||
**Caution**: This app directly modifies your Photos library database using undocumented features. It may corrupt, damage, or destroy your Photos library. Use at your own caution. I strongly recommend you make a backup of your Photos library before using this script (e.g. use Time Machine).
|
||||
|
||||
## Examples
|
||||
|
||||
**Add 1 day to the date of each photo**
|
||||
|
||||
`osxphotos timewarp --date-delta 1`
|
||||
|
||||
or
|
||||
|
||||
`osxphotos timewarp --date-delta "+1 day"`
|
||||
|
||||
**Set the date of each photo to 23 April 2020 and add 3 hours to the time**
|
||||
|
||||
`osxphotos timewarp --date 2020-04-23 --time-delta "+3 hours"`
|
||||
|
||||
or
|
||||
|
||||
`osxphotos timewarp --date 2020-04-23 --time-delta "+03:00:00"`
|
||||
|
||||
**Set the time of each photo to 14:30 and set the timezone to UTC +1:00 (Central European Time)**
|
||||
|
||||
`osxphotos timewarp --time 14:30 --timezone +01:00`
|
||||
|
||||
or
|
||||
|
||||
`osxphotos timewarp --time 14:30 --timezone +0100`
|
||||
|
||||
**Subtract 1 week from the date for each photo, add 3 hours to the time, set the timezone to UTC -07:00 (Pacific Daylight Time) and also use exiftool to update the EXIF metadata accordingly in the original file; use --verbose to print additional details**
|
||||
|
||||
`osxphotos timewarp --date-delta "-1 week" --time-delta "+3 hours" --timezone -0700 --push-exif --verbose`
|
||||
|
||||
For this to work, you'll need to install the third-party exiftool (https://exiftool.org/) utility. If you use homebrew (https://brew.sh/) you can do this with `brew install exiftool`.
|
||||
|
||||
**Set the timezone to UTC +03:00 for each photo but keep the time the same (that is, don't adjust time for the new timezone)**
|
||||
|
||||
`osxphotos timewarp --timezone 0300 --match-time`
|
||||
|
||||
*Note on timezones and times*: In Photos, when you change the timezone, Photos assumes the time itself was correct for the previous timezone and adjusts the time accordingly to the new timezone. E.g. if the photo's time is `13:00` and the timezone is `GMT -07:00` and you adjust the timezone one hour east to `GMT -06:00`, Photos will change the time of the photo to `14:00`. osxphotos timewarp follows this behavior. Using `--match-time` allows you to adjust the timezone but keep the same time without adjustment. For example, if your camera clock was correct but lacked timezone information and you took photos in one timezone but imported them to photos in another, Photos will add the timezone of the computer at time of import. You can use osxphotos timewarp to adjust the timezone but keep the time using `--match-time`.
|
||||
|
||||
**Compare the date/time/timezone of selected photos with the date/time/timezone in the photos' original EXIF metadata**
|
||||
|
||||
`osxphotos timewarp --compare-exif`
|
||||
|
||||
"""
|
||||
),
|
||||
width=formatter.width,
|
||||
markdown=True,
|
||||
)
|
||||
)
|
||||
help_text += formatter.getvalue()
|
||||
return help_text
|
||||
|
||||
|
||||
@click.command(cls=TimeWarpCommand, name="timewarp")
|
||||
@click.option(
|
||||
"--date",
|
||||
"-d",
|
||||
metavar="DATE",
|
||||
type=DateTimeISO8601(),
|
||||
help="Set date for selected photos. Format is 'YYYY-MM-DD'.",
|
||||
)
|
||||
@click.option(
|
||||
"--date-delta",
|
||||
"-D",
|
||||
metavar="DELTA",
|
||||
type=DateOffset(),
|
||||
help="Adjust date for selected photos by DELTA. "
|
||||
"Format is one of: '±D days', '±W weeks', '±D' where D is days",
|
||||
)
|
||||
@click.option(
|
||||
"--time",
|
||||
"-t",
|
||||
metavar="TIME",
|
||||
type=TimeString(),
|
||||
help="Set time for selected photos. Format is one of 'HH:MM:SS', 'HH:MM:SS.fff', 'HH:MM'.",
|
||||
)
|
||||
@click.option(
|
||||
"--time-delta",
|
||||
"-T",
|
||||
metavar="DELTA",
|
||||
type=TimeOffset(),
|
||||
help="Adjust time for selected photos by DELTA time. "
|
||||
"Format is one of '±HH:MM:SS', '±H hours' (or hr), '±M minutes' (or min), '±S seconds' (or sec), '±S' (where S is seconds)",
|
||||
)
|
||||
@click.option(
|
||||
"--timezone",
|
||||
"-z",
|
||||
metavar="TIMEZONE",
|
||||
type=UTCOffset(),
|
||||
help="Set timezone for selected photos as offset from UTC. "
|
||||
"Format is one of '±HH:MM', '±H:MM', or '±HHMM'. "
|
||||
"The actual time of the photo is not adjusted which means, somewhat counterintuitively, "
|
||||
"that the time in the new timezone will be different. "
|
||||
"For example, if photo has time of 12:00 and timezone of GMT+01:00 and new timezone is specified as "
|
||||
"'--timezone +02:00' (one hour ahead of current GMT+01:00 timezone), the photo's new time will be 13:00 GMT+02:00, "
|
||||
"which is equivalent to the old time of 12:00+01:00. "
|
||||
"This is the same behavior exhibited by Photos when manually adjusting timezone in the Get Info window. "
|
||||
"See also --match-time. ",
|
||||
)
|
||||
@click.option(
|
||||
"--inspect",
|
||||
"-i",
|
||||
is_flag=True,
|
||||
help="Print out the date/time/timezone for each selected photo without changing any information.",
|
||||
)
|
||||
@click.option(
|
||||
"--compare-exif",
|
||||
"-c",
|
||||
is_flag=True,
|
||||
help="Compare the EXIF date/time/timezone for each selected photo to the same data in Photos. "
|
||||
"Requires the third-party exiftool utility be installed (see https://exiftool.org/). "
|
||||
"See also --add-to-album.",
|
||||
)
|
||||
@click.option(
|
||||
"--push-exif",
|
||||
"-p",
|
||||
is_flag=True,
|
||||
help="Push date/time and timezone for selected photos from Photos to the "
|
||||
"EXIF metadata in the original file in the Photos library. "
|
||||
"Requires the third-party exiftool utility be installed (see https://exiftool.org/). "
|
||||
"Using this option modifies the *original* file of the image in your Photos library. "
|
||||
"--push-exif will be executed after any other updates are performed on the photo. "
|
||||
"See also --pull-exif.",
|
||||
)
|
||||
@click.option(
|
||||
"--pull-exif",
|
||||
"-P",
|
||||
is_flag=True,
|
||||
help="Pull date/time and timezone for selected photos from EXIF metadata in the original file "
|
||||
"into Photos and update the associated data in Photos to match the EXIF data. "
|
||||
"--pull-exif will be executed before any other updates are performed on the photo. "
|
||||
"It is possible for images to have missing EXIF data, for example the date/time could be set but there might be "
|
||||
"no timezone set in the EXIF metadata. "
|
||||
"Missing data will be handled thusly: if date/time/timezone are all present in the EXIF data, "
|
||||
"the photo's date/time/timezone will be updated. If timezone is missing but date/time is present, "
|
||||
"only the photo's date/time will be updated. If date/time is missing but the timezone is present, only the "
|
||||
"photo's timezone will be updated unless --use-file-time is set in which case, "
|
||||
"the photo's file modification date/time will be used in place of EXIF date/time. "
|
||||
"If the date is present but the time is missing, the time will be set to 00:00:00. "
|
||||
"Requires the third-party exiftool utility be installed (see https://exiftool.org/). "
|
||||
"See also --push-exif.",
|
||||
)
|
||||
@click.option(
|
||||
"--function",
|
||||
"-F",
|
||||
metavar="filename.py::function",
|
||||
nargs=1,
|
||||
type=FunctionCall(),
|
||||
multiple=False,
|
||||
help="Run python function to determine the date/time/timezone to apply to a photo. "
|
||||
"Use this in format: --function filename.py::function where filename.py is a python "
|
||||
"file you've created and function is the name of the function in the python file you want to call. The function will be "
|
||||
"passed information about the photo being processed and is expected to return "
|
||||
"a naive datetime.datetime object with time in local time and UTC timezone offset in seconds. "
|
||||
"See example function at https://github.com/RhetTbull/osxphotos/blob/master/examples/timewarp_function_example.py",
|
||||
)
|
||||
@click.option(
|
||||
"--match-time",
|
||||
"-m",
|
||||
is_flag=True,
|
||||
help="When used with --timezone, adjusts the photo time so that the timestamp in the new timezone matches "
|
||||
"the timestamp in the old timezone. "
|
||||
"For example, if photo has time of 12:00 and timezone of GMT+01:00 and new timezone is specified as "
|
||||
"'--timezone +02:00' (one hour ahead of current GMT+01:00 timezone), the photo's new time will be 12:00 GMT+02:00. "
|
||||
"That is, the timezone will have changed but the timestamp of the photo will match the previous timestamp. "
|
||||
"Use --match-time when the camera's time was correct for the time the photo was taken but the "
|
||||
"timezone was missing or wrong and you want to adjust the timezone while preserving the photo's time. "
|
||||
"See also --timezone.",
|
||||
)
|
||||
@click.option(
|
||||
"--use-file-time",
|
||||
"-f",
|
||||
is_flag=True,
|
||||
help="When used with --pull-exif, the file modification date/time will be used if date/time "
|
||||
"is missing from the EXIF data. ",
|
||||
)
|
||||
@click.option(
|
||||
"--add-to-album",
|
||||
"-a",
|
||||
metavar="ALBUM",
|
||||
help="When used with --compare-exif, adds any photos with date/time/timezone differences "
|
||||
"between Photos/EXIF to album ALBUM. If ALBUM does not exist, it will be created.",
|
||||
)
|
||||
@click.option("--verbose", "-V", "verbose", is_flag=True, help="Show verbose output.")
|
||||
@click.option(
|
||||
"--library",
|
||||
"-L",
|
||||
metavar="PHOTOS_LIBRARY_PATH",
|
||||
type=click.Path(),
|
||||
help=r"Path to Photos library (e.g. '~/Pictures/Photos\ Library.photoslibrary'). "
|
||||
f"This is not likely needed as {APP_NAME} will usually be able to locate the path to the open Photos library. "
|
||||
"Use --library only if you get an error that the Photos library cannot be located.",
|
||||
)
|
||||
@click.option(
|
||||
"--exiftool-path",
|
||||
"-e",
|
||||
type=click.Path(exists=True),
|
||||
help="Optional path to exiftool executable (will look in $PATH if not specified) for those options which require exiftool.",
|
||||
)
|
||||
@click.option(
|
||||
"--output-file",
|
||||
"-o",
|
||||
type=click.File(mode="w", lazy=False),
|
||||
help="Output file. If not specified, output is written to stdout.",
|
||||
)
|
||||
@click.option(
|
||||
"--terminal-width",
|
||||
"-w",
|
||||
type=int,
|
||||
help="Terminal width in characters.",
|
||||
hidden=True,
|
||||
)
|
||||
@click.option("--timestamp", is_flag=True, help="Add time stamp to verbose output")
|
||||
@THEME_OPTION
|
||||
@click.option(
|
||||
"--plain",
|
||||
is_flag=True,
|
||||
help="Plain text mode. Do not use rich output.",
|
||||
)
|
||||
def timewarp(
|
||||
date,
|
||||
date_delta,
|
||||
time,
|
||||
time_delta,
|
||||
timezone,
|
||||
inspect,
|
||||
compare_exif,
|
||||
push_exif,
|
||||
pull_exif,
|
||||
function,
|
||||
match_time,
|
||||
use_file_time,
|
||||
add_to_album,
|
||||
exiftool_path,
|
||||
verbose,
|
||||
library,
|
||||
theme,
|
||||
plain,
|
||||
output_file,
|
||||
terminal_width,
|
||||
timestamp,
|
||||
):
|
||||
"""Adjust date/time/timezone of photos in Apple Photos.
|
||||
|
||||
Changes will be applied to all photos currently selected in Photos.
|
||||
timewarp cannot operate on photos selected in a Smart Album;
|
||||
select photos in a regular album or in the 'All Photos' view.
|
||||
See Timewarp Overview below for additional information.
|
||||
"""
|
||||
|
||||
# check constraints
|
||||
if not any(
|
||||
[
|
||||
date,
|
||||
date_delta,
|
||||
time,
|
||||
time_delta,
|
||||
timezone,
|
||||
inspect,
|
||||
compare_exif,
|
||||
push_exif,
|
||||
pull_exif,
|
||||
function,
|
||||
]
|
||||
):
|
||||
raise click.UsageError(
|
||||
"At least one of --date, --date-delta, --time, --time-delta, "
|
||||
"--timezone, --inspect, --compare-exif, --push-exif, --pull-exif, --function "
|
||||
"must be specified."
|
||||
)
|
||||
|
||||
if date and date_delta:
|
||||
raise click.UsageError("--date and --date-delta are mutually exclusive.")
|
||||
|
||||
if time and time_delta:
|
||||
raise click.UsageError("--time and --time-delta are mutually exclusive.")
|
||||
|
||||
if match_time and not timezone:
|
||||
raise click.UsageError("--match-time must be used with --timezone.")
|
||||
|
||||
if add_to_album and not compare_exif:
|
||||
raise click.UsageError("--add-to-album must be used with --compare-exif.")
|
||||
|
||||
color_theme = get_theme(theme)
|
||||
verbose_ = verbose_print(
|
||||
verbose,
|
||||
timestamp,
|
||||
rich=True,
|
||||
theme=color_theme,
|
||||
highlight=False,
|
||||
file=output_file,
|
||||
)
|
||||
# set console for rich_echo to be same as for verbose_
|
||||
# TODO: this is a hack, find a better way to do this
|
||||
terminal_width = terminal_width or (1000 if output_file else None)
|
||||
if output_file:
|
||||
set_rich_console(Console(file=output_file, width=terminal_width))
|
||||
elif terminal_width:
|
||||
set_rich_console(
|
||||
Console(
|
||||
file=sys.stdout,
|
||||
theme=color_theme,
|
||||
force_terminal=True,
|
||||
width=terminal_width,
|
||||
)
|
||||
)
|
||||
else:
|
||||
set_rich_console(get_verbose_console(theme=color_theme))
|
||||
set_rich_theme(color_theme)
|
||||
|
||||
if any([compare_exif, push_exif, pull_exif]):
|
||||
exiftool_path = exiftool_path or get_exiftool_path()
|
||||
verbose_(f"exiftool path: [filename]{exiftool_path}[/filename]")
|
||||
|
||||
try:
|
||||
photos = PhotosLibrary().selection
|
||||
if not photos:
|
||||
rich_echo_error("[warning]No photos selected[/]")
|
||||
sys.exit(0)
|
||||
except Exception as e:
|
||||
# AppleScript error -1728 occurs if user attempts to get selected photos in a Smart Album
|
||||
if "(-1728)" in str(e):
|
||||
rich_echo_error(
|
||||
"[error]Could not get selected photos. Ensure photos is open and photos are selected. "
|
||||
"If you have selected photos and you see this message, it may be because the selected photos are in a Photos Smart Album. "
|
||||
f"{APP_NAME} cannot access photos in a Smart Album. Select the photos in a regular album or in 'All Photos' view. "
|
||||
"Another option is to create a new album using 'File | New Album With Selection' then select the photos in the new album.[/]",
|
||||
)
|
||||
else:
|
||||
rich_echo_error(
|
||||
f"[error]Could not get selected photos. Ensure Photos is open and photos to process are selected. {e}[/]",
|
||||
)
|
||||
sys.exit(1)
|
||||
|
||||
update_photo_date_time_ = partial(
|
||||
update_photo_date_time,
|
||||
date=date,
|
||||
time=time,
|
||||
date_delta=date_delta,
|
||||
time_delta=time_delta,
|
||||
verbose_print=verbose_,
|
||||
)
|
||||
|
||||
update_photo_time_for_new_timezone_ = partial(
|
||||
update_photo_time_for_new_timezone,
|
||||
library_path=library,
|
||||
verbose_print=verbose_,
|
||||
)
|
||||
|
||||
if function:
|
||||
update_photo_from_function_ = partial(
|
||||
update_photo_from_function,
|
||||
library_path=library,
|
||||
function=function[0],
|
||||
verbose_print=verbose_,
|
||||
)
|
||||
else:
|
||||
update_photo_from_function_ = noop
|
||||
|
||||
if inspect:
|
||||
tzinfo = PhotoTimeZone(library_path=library)
|
||||
if photos:
|
||||
rich_echo(
|
||||
"[filename]filename[/filename], [uuid]uuid[/uuid], [time]photo time (local)[/time], [time]photo time[/time], [tz]timezone offset[/tz], [tz]timezone name[/tz]"
|
||||
)
|
||||
for photo in photos:
|
||||
tz_seconds, tz_str, tz_name = tzinfo.get_timezone(photo)
|
||||
photo_date_local = datetime_naive_to_local(photo.date)
|
||||
photo_date_tz = datetime_to_new_tz(photo_date_local, tz_seconds)
|
||||
rich_echo(
|
||||
f"[filename]{photo.filename}[/filename], [uuid]{photo.uuid}[/uuid], [time]{photo_date_local.strftime(DATETIME_FORMAT)}[/time], [time]{photo_date_tz.strftime(DATETIME_FORMAT)}[/time], [tz]{tz_str}[/tz], [tz]{tz_name}[/tz]"
|
||||
)
|
||||
sys.exit(0)
|
||||
|
||||
if compare_exif:
|
||||
album = PhotosAlbumPhotoScript(add_to_album) if add_to_album else None
|
||||
different_photos = 0
|
||||
if photos:
|
||||
photocomp = PhotoCompare(
|
||||
library_path=library,
|
||||
verbose=verbose_,
|
||||
exiftool_path=exiftool_path,
|
||||
)
|
||||
if not album:
|
||||
rich_echo(
|
||||
"filename, uuid, photo time (Photos), photo time (EXIF), timezone offset (Photos), timezone offset (EXIF)"
|
||||
)
|
||||
for photo in photos:
|
||||
diff_results = (
|
||||
photocomp.compare_exif_no_markup(photo)
|
||||
if plain
|
||||
else photocomp.compare_exif_with_markup(photo)
|
||||
)
|
||||
|
||||
if not plain:
|
||||
filename = (
|
||||
f"[change]{photo.filename}[/change]"
|
||||
if diff_results.diff
|
||||
else f"[no_change]{photo.filename}[/no_change]"
|
||||
)
|
||||
else:
|
||||
filename = photo.filename
|
||||
uuid = f"[uuid]{photo.uuid}[/uuid]"
|
||||
if album:
|
||||
if diff_results.diff:
|
||||
different_photos += 1
|
||||
verbose_(
|
||||
f"Photo {filename} ({uuid}) has different date/time/timezone, adding to album '{album.name}'"
|
||||
)
|
||||
album.add(photo)
|
||||
else:
|
||||
verbose_(f"Photo {filename} ({uuid}) has same date/time/timezone")
|
||||
else:
|
||||
rich_echo(
|
||||
f"{filename}, {uuid}, "
|
||||
f"{diff_results.photos_date} {diff_results.photos_time}, {diff_results.exif_date} {diff_results.exif_time}, "
|
||||
f"{diff_results.photos_tz}, {diff_results.exif_tz}"
|
||||
)
|
||||
if album:
|
||||
rich_echo(
|
||||
f"Compared {len(photos)} photos, found {different_photos} "
|
||||
f"that {pluralize(different_photos, 'is', 'are')} different and "
|
||||
f"added {pluralize(different_photos, 'it', 'them')} to album '{album.name}'."
|
||||
)
|
||||
sys.exit(0)
|
||||
|
||||
if timezone:
|
||||
tz_updater = PhotoTimeZoneUpdater(
|
||||
timezone, verbose=verbose_, library_path=library
|
||||
)
|
||||
|
||||
if any([push_exif, pull_exif, function]):
|
||||
# ExifDateTimeUpdater used to get photo path for --function
|
||||
exif_updater = ExifDateTimeUpdater(
|
||||
library_path=library,
|
||||
verbose=verbose_,
|
||||
exiftool_path=exiftool_path,
|
||||
plain=plain,
|
||||
)
|
||||
|
||||
num_photos = len(photos)
|
||||
with rich_progress(console=get_verbose_console(), mock=verbose) as progress:
|
||||
task = progress.add_task(
|
||||
f"Processing [num]{num_photos}[/] {pluralize(len(photos), 'photo', 'photos')}",
|
||||
total=num_photos,
|
||||
)
|
||||
for p in photos:
|
||||
if pull_exif:
|
||||
exif_updater.update_photos_from_exif(
|
||||
p, use_file_modify_date=use_file_time
|
||||
)
|
||||
if any([date, time, date_delta, time_delta]):
|
||||
update_photo_date_time_(p)
|
||||
if match_time:
|
||||
# need to adjust time before the timezone is updated
|
||||
# or the old timezone will be overwritten in the database
|
||||
update_photo_time_for_new_timezone_(photo=p, new_timezone=timezone)
|
||||
if timezone:
|
||||
tz_updater.update_photo(p)
|
||||
if function:
|
||||
verbose_(f"Calling function [bold]{function[1]}")
|
||||
photo_path = exif_updater.get_photo_path(p)
|
||||
update_photo_from_function_(photo=p, path=photo_path)
|
||||
if push_exif:
|
||||
# this should be the last step in the if chain to ensure all Photos data is updated
|
||||
# before exiftool is run
|
||||
exif_warn, exif_error = exif_updater.update_exif_from_photos(p)
|
||||
if exif_warn:
|
||||
rich_echo_error(
|
||||
f"[warning]Warning running exiftool: {exif_warn}[/]"
|
||||
)
|
||||
if exif_error:
|
||||
rich_echo_error(f"[error]Error running exiftool: {exif_error}[/]")
|
||||
|
||||
progress.advance(task)
|
||||
|
||||
rich_echo("Done.")
|
||||
|
||||
# if output_file:
|
||||
# output_file.close()
|
||||
@@ -43,15 +43,18 @@ def noop(*args, **kwargs):
|
||||
pass
|
||||
|
||||
|
||||
def get_verbose_console() -> Console:
|
||||
"""Get console object
|
||||
def get_verbose_console(theme: t.Optional[Theme] = None) -> Console:
|
||||
"""Get console object or create one if not already created
|
||||
|
||||
Args:
|
||||
theme: optional rich.theme.Theme object to use for formatting
|
||||
|
||||
Returns:
|
||||
Console object
|
||||
"""
|
||||
global _console
|
||||
if _console.console is None:
|
||||
_console.console = Console(force_terminal=True)
|
||||
_console.console = Console(force_terminal=True, theme=theme)
|
||||
return _console.console
|
||||
|
||||
|
||||
@@ -61,6 +64,7 @@ def verbose_print(
|
||||
rich: bool = False,
|
||||
highlight: bool = False,
|
||||
theme: t.Optional[Theme] = None,
|
||||
file: t.Optional[t.IO] = None,
|
||||
**kwargs: t.Any,
|
||||
) -> t.Callable:
|
||||
"""Create verbose function to print output
|
||||
@@ -71,6 +75,7 @@ def verbose_print(
|
||||
rich: use rich.print instead of click.echo
|
||||
highlight: if True, use automatic rich.print highlighting
|
||||
theme: optional rich.theme.Theme object to use for formatting
|
||||
file: optional file handle to write to instead of stdout
|
||||
kwargs: any extra arguments to pass to click.echo or rich.print depending on whether rich==True
|
||||
|
||||
Returns:
|
||||
@@ -80,7 +85,10 @@ def verbose_print(
|
||||
return noop
|
||||
|
||||
global _console
|
||||
_console.console = Console(theme=theme, width=10_000)
|
||||
if file:
|
||||
_console.console = Console(theme=theme, file=file)
|
||||
else:
|
||||
_console.console = Console(theme=theme, width=10_000)
|
||||
|
||||
# closure to capture timestamp
|
||||
def verbose_(*args):
|
||||
|
||||
167
osxphotos/compare_exif.py
Normal file
167
osxphotos/compare_exif.py
Normal file
@@ -0,0 +1,167 @@
|
||||
""" PhotoCompare class to compare date/time/timezone in Photos to the exif data """
|
||||
|
||||
from collections import namedtuple
|
||||
from typing import Callable, List, Optional, Tuple
|
||||
|
||||
from osxphotos import PhotosDB
|
||||
from osxphotos.exiftool import ExifTool
|
||||
from photoscript import Photo
|
||||
|
||||
from .datetime_utils import datetime_naive_to_local, datetime_to_new_tz
|
||||
from .exif_datetime_updater import get_exif_date_time_offset
|
||||
from .phototz import PhotoTimeZone
|
||||
from .utils import noop
|
||||
|
||||
ExifDiff = namedtuple(
|
||||
"ExifDiff",
|
||||
[
|
||||
"diff",
|
||||
"photos_date",
|
||||
"photos_time",
|
||||
"photos_tz",
|
||||
"exif_date",
|
||||
"exif_time",
|
||||
"exif_tz",
|
||||
],
|
||||
)
|
||||
|
||||
|
||||
def change(msg: str) -> str:
|
||||
"""Add change tag to string"""
|
||||
return f"[change]{msg}[/change]"
|
||||
|
||||
|
||||
def no_change(msg: str) -> str:
|
||||
"""Add no change tag to string"""
|
||||
return f"[no_change]{msg}[/no_change]"
|
||||
|
||||
|
||||
class PhotoCompare:
|
||||
"""Class to compare date/time/timezone in Photos to the exif data"""
|
||||
|
||||
def __init__(
|
||||
self,
|
||||
library_path: Optional[str] = None,
|
||||
verbose: Optional[Callable] = None,
|
||||
exiftool_path: Optional[str] = None,
|
||||
):
|
||||
self.library_path = library_path
|
||||
self.db = PhotosDB(self.library_path)
|
||||
self.verbose = verbose or noop
|
||||
self.exiftool_path = exiftool_path
|
||||
self.phototz = PhotoTimeZone(self.library_path)
|
||||
|
||||
def compare_exif(self, photo: Photo) -> List[str]:
|
||||
"""Compare date/time/timezone in Photos to the exif data
|
||||
|
||||
Args:
|
||||
photo (Photo): Photo object to compare
|
||||
|
||||
Returns:
|
||||
List of strings:
|
||||
"""
|
||||
photos_offset_seconds, photos_tz_str, _ = self.phototz.get_timezone(photo)
|
||||
photos_date = datetime_naive_to_local(photo.date)
|
||||
photos_date = datetime_to_new_tz(photos_date, photos_offset_seconds)
|
||||
photos_date_str = photos_date.strftime("%Y-%m-%d %H:%M:%S")
|
||||
|
||||
photo_ = self.db.get_photo(photo.uuid)
|
||||
if photo_path := photo_.path:
|
||||
exif = ExifTool(filepath=photo_path, exiftool=self.exiftool_path)
|
||||
exif_dict = exif.asdict()
|
||||
exif_dt_offset = get_exif_date_time_offset(exif_dict)
|
||||
exif_offset = exif_dt_offset.offset_str
|
||||
exif_date = (
|
||||
exif_dt_offset.datetime.strftime("%Y-%m-%d %H:%M:%S")
|
||||
if exif_dt_offset.datetime
|
||||
else ""
|
||||
)
|
||||
else:
|
||||
exif_date = ""
|
||||
exif_offset = ""
|
||||
|
||||
return [photos_date_str, photos_tz_str, exif_date, exif_offset]
|
||||
|
||||
def compare_exif_with_markup(self, photo: Photo) -> ExifDiff:
|
||||
"""Compare date/time/timezone in Photos to the exif data and return an ExifDiff named tuple;
|
||||
adds rich markup to strings to show differences
|
||||
|
||||
Args:
|
||||
photo (Photo): Photo object to compare
|
||||
"""
|
||||
photos_date, photos_tz, exif_date, exif_tz = self.compare_exif(photo)
|
||||
diff = False
|
||||
photos_date, photos_time = photos_date.split(" ", 1)
|
||||
try:
|
||||
exif_date, exif_time = exif_date.split(" ", 1)
|
||||
except ValueError:
|
||||
exif_date = exif_date
|
||||
exif_time = ""
|
||||
|
||||
if photos_date != exif_date:
|
||||
photos_date = change(photos_date)
|
||||
exif_date = change(exif_date)
|
||||
diff = True
|
||||
else:
|
||||
photos_date = no_change(photos_date)
|
||||
exif_date = no_change(exif_date)
|
||||
|
||||
if photos_time != exif_time:
|
||||
photos_time = change(photos_time)
|
||||
exif_time = change(exif_time)
|
||||
diff = True
|
||||
else:
|
||||
photos_time = no_change(photos_time)
|
||||
exif_time = no_change(exif_time)
|
||||
|
||||
if photos_tz != exif_tz:
|
||||
photos_tz = change(photos_tz)
|
||||
exif_tz = change(exif_tz)
|
||||
diff = True
|
||||
else:
|
||||
photos_tz = no_change(photos_tz)
|
||||
exif_tz = no_change(exif_tz)
|
||||
|
||||
return ExifDiff(
|
||||
diff,
|
||||
photos_date,
|
||||
photos_time,
|
||||
photos_tz,
|
||||
exif_date,
|
||||
exif_time,
|
||||
exif_tz,
|
||||
)
|
||||
|
||||
def compare_exif_no_markup(self, photo: Photo) -> ExifDiff:
|
||||
"""Compare date/time/timezone in Photos to the exif data and return an ExifDiff named tuple;
|
||||
|
||||
Args:
|
||||
photo (Photo): Photo object to compare
|
||||
"""
|
||||
photos_date, photos_tz, exif_date, exif_tz = self.compare_exif(photo)
|
||||
diff = False
|
||||
photos_date, photos_time = photos_date.split(" ", 1)
|
||||
try:
|
||||
exif_date, exif_time = exif_date.split(" ", 1)
|
||||
except ValueError:
|
||||
exif_date = exif_date
|
||||
exif_time = ""
|
||||
|
||||
if photos_date != exif_date:
|
||||
diff = True
|
||||
|
||||
if photos_time != exif_time:
|
||||
diff = True
|
||||
|
||||
if photos_tz != exif_tz:
|
||||
diff = True
|
||||
|
||||
return ExifDiff(
|
||||
diff,
|
||||
photos_date,
|
||||
photos_time,
|
||||
photos_tz,
|
||||
exif_date,
|
||||
exif_time,
|
||||
exif_tz,
|
||||
)
|
||||
@@ -1,19 +1,28 @@
|
||||
""" datetime.datetime helper functions for converting to/from UTC """
|
||||
""" datetime.datetime helper functions for converting to/from UTC and other datetime manipulations"""
|
||||
|
||||
# source: https://github.com/RhetTbull/datetime-utils
|
||||
|
||||
__version__ = "2022.04.30"
|
||||
|
||||
import datetime
|
||||
|
||||
# TODO: probably shouldn't use replace here, see this:
|
||||
# https://stackoverflow.com/questions/13994594/how-to-add-timezone-into-a-naive-datetime-instance-in-python/13994611#13994611
|
||||
|
||||
__all__ = [
|
||||
"get_local_tz",
|
||||
"datetime_has_tz",
|
||||
"datetime_tz_to_utc",
|
||||
"datetime_remove_tz",
|
||||
"datetime_naive_to_utc",
|
||||
"datetime_naive_to_local",
|
||||
"datetime_naive_to_utc",
|
||||
"datetime_remove_tz",
|
||||
"datetime_to_new_tz",
|
||||
"datetime_tz_to_utc",
|
||||
"datetime_utc_to_local",
|
||||
"get_local_tz",
|
||||
"utc_offset_seconds",
|
||||
]
|
||||
|
||||
|
||||
def get_local_tz(dt):
|
||||
# TODO: look at https://github.com/regebro/tzlocal for more robust implementation
|
||||
def get_local_tz(dt: datetime.datetime) -> datetime.tzinfo:
|
||||
"""Return local timezone as datetime.timezone tzinfo for dt
|
||||
|
||||
Args:
|
||||
@@ -31,7 +40,7 @@ def get_local_tz(dt):
|
||||
raise ValueError("dt must be naive datetime.datetime object")
|
||||
|
||||
|
||||
def datetime_has_tz(dt):
|
||||
def datetime_has_tz(dt: datetime.datetime) -> bool:
|
||||
"""Return True if datetime dt has tzinfo else False
|
||||
|
||||
Args:
|
||||
@@ -50,7 +59,7 @@ def datetime_has_tz(dt):
|
||||
return dt.tzinfo is not None and dt.tzinfo.utcoffset(dt) is not None
|
||||
|
||||
|
||||
def datetime_tz_to_utc(dt):
|
||||
def datetime_tz_to_utc(dt: datetime.datetime) -> datetime.datetime:
|
||||
"""Convert datetime.datetime object with timezone to UTC timezone
|
||||
|
||||
Args:
|
||||
@@ -70,10 +79,10 @@ def datetime_tz_to_utc(dt):
|
||||
if dt.tzinfo is not None and dt.tzinfo.utcoffset(dt) is not None:
|
||||
return dt.replace(tzinfo=dt.tzinfo).astimezone(tz=datetime.timezone.utc)
|
||||
else:
|
||||
raise ValueError(f"dt does not have timezone info")
|
||||
raise ValueError("dt does not have timezone info")
|
||||
|
||||
|
||||
def datetime_remove_tz(dt):
|
||||
def datetime_remove_tz(dt: datetime.datetime) -> datetime.datetime:
|
||||
"""Remove timezone from a datetime.datetime object
|
||||
|
||||
Args:
|
||||
@@ -92,7 +101,7 @@ def datetime_remove_tz(dt):
|
||||
return dt.replace(tzinfo=None)
|
||||
|
||||
|
||||
def datetime_naive_to_utc(dt):
|
||||
def datetime_naive_to_utc(dt: datetime.datetime) -> datetime.datetime:
|
||||
"""Convert naive (timezone unaware) datetime.datetime
|
||||
to aware timezone in UTC timezone
|
||||
|
||||
@@ -120,7 +129,7 @@ def datetime_naive_to_utc(dt):
|
||||
return dt.replace(tzinfo=datetime.timezone.utc)
|
||||
|
||||
|
||||
def datetime_naive_to_local(dt):
|
||||
def datetime_naive_to_local(dt: datetime.datetime) -> datetime.datetime:
|
||||
"""Convert naive (timezone unaware) datetime.datetime
|
||||
to aware timezone in local timezone
|
||||
|
||||
@@ -142,13 +151,13 @@ def datetime_naive_to_local(dt):
|
||||
# has timezone info
|
||||
raise ValueError(
|
||||
"dt must be naive/timezone unaware: "
|
||||
f"{dt} has tzinfo {dt.tzinfo} and offset {dt.tizinfo.utcoffset(dt)}"
|
||||
f"{dt} has tzinfo {dt.tzinfo} and offset {dt.tzinfo.utcoffset(dt)}"
|
||||
)
|
||||
|
||||
return dt.replace(tzinfo=get_local_tz(dt))
|
||||
|
||||
|
||||
def datetime_utc_to_local(dt):
|
||||
def datetime_utc_to_local(dt: datetime.datetime) -> datetime.datetime:
|
||||
"""Convert datetime.datetime object in UTC timezone to local timezone
|
||||
|
||||
Args:
|
||||
@@ -169,3 +178,33 @@ def datetime_utc_to_local(dt):
|
||||
raise ValueError(f"{dt} must be in UTC timezone: timezone = {dt.tzinfo}")
|
||||
|
||||
return dt.replace(tzinfo=datetime.timezone.utc).astimezone(tz=None)
|
||||
|
||||
|
||||
def datetime_to_new_tz(dt: datetime.datetime, offset) -> datetime.datetime:
|
||||
"""Convert datetime.datetime object from current timezone to new timezone with offset of seconds from UTC"""
|
||||
if not datetime_has_tz(dt):
|
||||
raise ValueError("dt must be timezone aware")
|
||||
|
||||
time_delta = datetime.timedelta(seconds=offset)
|
||||
tz = datetime.timezone(time_delta)
|
||||
return dt.astimezone(tz=tz)
|
||||
|
||||
|
||||
def utc_offset_seconds(dt: datetime.datetime) -> int:
|
||||
"""Return offset in seconds from UTC for timezone aware datetime.datetime object
|
||||
|
||||
Args:
|
||||
dt: datetime.datetime object
|
||||
|
||||
Returns:
|
||||
offset in seconds from UTC
|
||||
|
||||
Raises:
|
||||
ValueError if dt does not have timezone information
|
||||
"""
|
||||
|
||||
if dt.tzinfo is not None and dt.tzinfo.utcoffset(dt) is not None:
|
||||
return dt.tzinfo.utcoffset(dt).total_seconds()
|
||||
else:
|
||||
raise ValueError("dt does not have timezone info")
|
||||
|
||||
|
||||
Binary file not shown.
348
osxphotos/exif_datetime_updater.py
Normal file
348
osxphotos/exif_datetime_updater.py
Normal file
@@ -0,0 +1,348 @@
|
||||
"""Use exiftool to update exif data in photos """
|
||||
|
||||
import datetime
|
||||
import re
|
||||
from collections import namedtuple
|
||||
from typing import Callable, Dict, Optional, Tuple
|
||||
|
||||
from photoscript import Photo
|
||||
|
||||
from .datetime_utils import (
|
||||
datetime_has_tz,
|
||||
datetime_naive_to_local,
|
||||
datetime_remove_tz,
|
||||
datetime_to_new_tz,
|
||||
datetime_tz_to_utc,
|
||||
datetime_utc_to_local,
|
||||
)
|
||||
from .exiftool import ExifTool
|
||||
from .photosdb import PhotosDB
|
||||
from .phototz import PhotoTimeZone, PhotoTimeZoneUpdater
|
||||
from .timezones import Timezone, format_offset_time
|
||||
from .utils import noop
|
||||
|
||||
__all__ = ["ExifDateTime", "ExifDateTimeUpdater"]
|
||||
|
||||
# date/time/timezone extracted from regex as a timezone aware datetime.datetime object
|
||||
# default_time is True if the time is not specified in the exif otherwise False (and if True, set to 00:00:00)
|
||||
# default_offset is True if timezone offset is not specified in the exif otherwise False (and if True, set to +00:00)
|
||||
# used_file_modify_date is True if the date/time is not specified in the exif and the FileModifyDate is used instead
|
||||
ExifDateTime = namedtuple(
|
||||
"ExifDateTime",
|
||||
[
|
||||
"datetime",
|
||||
"offset_seconds",
|
||||
"offset_str",
|
||||
"default_time",
|
||||
"used_file_modify_date",
|
||||
],
|
||||
)
|
||||
|
||||
|
||||
def exif_offset_to_seconds(offset: str) -> int:
|
||||
"""Convert timezone offset from UTC in exiftool format (+/-hh:mm) to seconds"""
|
||||
sign = 1 if offset[0] == "+" else -1
|
||||
hours, minutes = offset[1:].split(":")
|
||||
return sign * (int(hours) * 3600 + int(minutes) * 60)
|
||||
|
||||
|
||||
class ExifDateTimeUpdater:
|
||||
"""Update exif data in photos"""
|
||||
|
||||
def __init__(
|
||||
self,
|
||||
library_path: Optional[str] = None,
|
||||
verbose: Optional[Callable] = None,
|
||||
exiftool_path: Optional[str] = None,
|
||||
plain=False,
|
||||
):
|
||||
self.library_path = library_path
|
||||
self.db = PhotosDB(self.library_path)
|
||||
self.verbose = verbose or noop
|
||||
self.exiftool_path = exiftool_path
|
||||
self.tzinfo = PhotoTimeZone(library_path=self.library_path)
|
||||
self.plain = plain
|
||||
|
||||
def filename_color(self, filename: str) -> str:
|
||||
"""Colorize filename for display in verbose output"""
|
||||
return filename if self.plain else f"[filename]{filename}[/filename]"
|
||||
|
||||
def uuid_color(self, uuid: str) -> str:
|
||||
"""Colorize uuid for display in verbose output"""
|
||||
return uuid if self.plain else f"[uuid]{uuid}[/uuid]"
|
||||
|
||||
def update_exif_from_photos(self, photo: Photo) -> Tuple[str, str]:
|
||||
"""Update EXIF data in photo to match the date/time/timezone in Photos library
|
||||
|
||||
Args:
|
||||
photo: photoscript.Photo object to act on
|
||||
"""
|
||||
|
||||
# photo is the photoscript.Photo object passed in
|
||||
# _photo is the osxphotos.PhotoInfo object for the same photo
|
||||
# Need _photo to get the photo's path
|
||||
_photo = self.db.get_photo(photo.uuid)
|
||||
if not _photo:
|
||||
raise ValueError(f"Photo {photo.uuid} not found")
|
||||
|
||||
if not _photo.path:
|
||||
self.verbose(
|
||||
"Skipping EXIF update for missing photo "
|
||||
f"[filename]{_photo.original_filename}[/filename] ([uuid]{_photo.uuid}[/uuid])"
|
||||
)
|
||||
return "", ""
|
||||
|
||||
self.verbose(
|
||||
"Updating EXIF data for "
|
||||
f"[filename]{photo.filename}[/filename] ([uuid]{photo.uuid}[/uuid])"
|
||||
)
|
||||
|
||||
photo_date = datetime_naive_to_local(photo.date)
|
||||
timezone_offset = self.tzinfo.get_timezone(photo)[0]
|
||||
photo_date = datetime_to_new_tz(photo_date, timezone_offset)
|
||||
|
||||
# exiftool expects format to "2015:01:18 12:00:00"
|
||||
datetimeoriginal = photo_date.strftime("%Y:%m:%d %H:%M:%S")
|
||||
|
||||
# exiftool expects format of "-04:00"
|
||||
offset = format_offset_time(timezone_offset)
|
||||
|
||||
# process date/time and timezone offset
|
||||
# Photos exports the following fields and sets modify date to creation date
|
||||
# [EXIF] Date/Time Original : 2020:10:30 00:00:00
|
||||
# [EXIF] Create Date : 2020:10:30 00:00:00
|
||||
# [IPTC] Digital Creation Date : 2020:10:30
|
||||
# [IPTC] Date Created : 2020:10:30
|
||||
#
|
||||
# for videos:
|
||||
# [QuickTime] CreateDate : 2020:12:11 06:10:10
|
||||
# [Keys] CreationDate : 2020:12:10 22:10:10-08:00
|
||||
exif = {}
|
||||
if _photo.isphoto:
|
||||
exif["EXIF:DateTimeOriginal"] = datetimeoriginal
|
||||
exif["EXIF:CreateDate"] = datetimeoriginal
|
||||
dateoriginal = photo_date.strftime("%Y:%m:%d")
|
||||
exif["IPTC:DateCreated"] = dateoriginal
|
||||
timeoriginal = photo_date.strftime(f"%H:%M:%S{offset}")
|
||||
exif["IPTC:TimeCreated"] = timeoriginal
|
||||
|
||||
exif["EXIF:OffsetTimeOriginal"] = offset
|
||||
|
||||
elif _photo.ismovie:
|
||||
# QuickTime spec specifies times in UTC
|
||||
# QuickTime:CreateDate and ModifyDate are in UTC w/ no timezone
|
||||
# QuickTime:CreationDate must include time offset or Photos shows invalid values
|
||||
# reference: https://exiftool.org/TagNames/QuickTime.html#Keys
|
||||
# https://exiftool.org/forum/index.php?topic=11927.msg64369#msg64369
|
||||
creationdate = f"{datetimeoriginal}{offset}"
|
||||
exif["QuickTime:CreationDate"] = creationdate
|
||||
|
||||
# need to convert to UTC then back to formatted string
|
||||
tzdate = datetime.datetime.strptime(creationdate, "%Y:%m:%d %H:%M:%S%z")
|
||||
utcdate = datetime_tz_to_utc(tzdate)
|
||||
createdate = utcdate.strftime("%Y:%m:%d %H:%M:%S")
|
||||
exif["QuickTime:CreateDate"] = createdate
|
||||
|
||||
self.verbose(
|
||||
f"Writing EXIF data with exiftool to {self.filename_color(_photo.path)}"
|
||||
)
|
||||
with ExifTool(filepath=_photo.path, exiftool=self.exiftool_path) as exiftool:
|
||||
for tag, val in exif.items():
|
||||
if type(val) == list:
|
||||
for v in val:
|
||||
exiftool.setvalue(tag, v)
|
||||
else:
|
||||
exiftool.setvalue(tag, val)
|
||||
return exiftool.warning, exiftool.error
|
||||
|
||||
def update_photos_from_exif(
|
||||
self, photo: Photo, use_file_modify_date: bool = False
|
||||
) -> None:
|
||||
"""Update date/time/timezone in Photos library to match the data in EXIF
|
||||
|
||||
Args:
|
||||
photo: photoscript.Photo object to act on
|
||||
use_file_modify_date: if True, use the file modify date if there's no date/time in the exif data
|
||||
"""
|
||||
|
||||
# photo is the photoscript.Photo object passed in
|
||||
# _photo is the osxphotos.PhotoInfo object for the same photo
|
||||
# Need _photo to get the photo's path
|
||||
_photo = self.db.get_photo(photo.uuid)
|
||||
if not _photo:
|
||||
raise ValueError(f"Photo {photo.uuid} not found")
|
||||
|
||||
if not _photo.path:
|
||||
self.verbose(
|
||||
"Skipping EXIF update for missing photo "
|
||||
f"[filename]{_photo.original_filename}[/filename] ([uuid]{_photo.uuid}[/uuid])"
|
||||
)
|
||||
return None
|
||||
|
||||
self.verbose(
|
||||
"Updating Photos from EXIF data for "
|
||||
f"[filename]{photo.filename}[/filename] ([uuid]{photo.uuid}[/uuid])"
|
||||
)
|
||||
|
||||
dtinfo = self.get_date_time_offset_from_exif(
|
||||
_photo.path, use_file_modify_date=use_file_modify_date
|
||||
)
|
||||
if dtinfo.used_file_modify_date:
|
||||
self.verbose(
|
||||
"EXIF date/time missing, using file modify date/time for "
|
||||
f"[filename]{photo.filename}[/filename] ([uuid]{photo.uuid}[/uuid])"
|
||||
)
|
||||
if not dtinfo.datetime and not dtinfo.offset_seconds:
|
||||
self.verbose(
|
||||
"Skipping update for missing EXIF data in photo "
|
||||
f"[filename]{photo.filename}[/filename] ([uuid]{photo.uuid}[/uuid])"
|
||||
)
|
||||
return None
|
||||
|
||||
if dtinfo.offset_seconds:
|
||||
# update timezone then update date/time
|
||||
timezone = Timezone(dtinfo.offset_seconds)
|
||||
tzupdater = PhotoTimeZoneUpdater(
|
||||
library_path=self.library_path, timezone=timezone
|
||||
)
|
||||
tzupdater.update_photo(photo)
|
||||
self.verbose(
|
||||
"Updated timezone offset for photo "
|
||||
f"[filename]{photo.filename}[/filename] ([uuid]{photo.uuid}[/uuid]): [tz]{timezone}[/tz]"
|
||||
)
|
||||
|
||||
if dtinfo.datetime:
|
||||
if datetime_has_tz(dtinfo.datetime):
|
||||
# convert datetime to naive local time for setting in photos
|
||||
local_datetime = datetime_remove_tz(
|
||||
datetime_utc_to_local(datetime_tz_to_utc(dtinfo.datetime))
|
||||
)
|
||||
else:
|
||||
local_datetime = dtinfo.datetime
|
||||
# update date/time
|
||||
photo.date = local_datetime
|
||||
self.verbose(
|
||||
"Updated date/time for photo "
|
||||
f"[filename]{photo.filename}[/filename] ([uuid]{photo.uuid}[/uuid]): [time]{local_datetime}[/time]"
|
||||
)
|
||||
|
||||
return None
|
||||
|
||||
def get_date_time_offset_from_exif(
|
||||
self, photo_path: str, use_file_modify_date: bool = False
|
||||
) -> ExifDateTime:
|
||||
"""Get date/time/timezone from EXIF data for a photo
|
||||
|
||||
Args:
|
||||
photo_path: path to photo to get EXIF data from
|
||||
use_file_modify_date: if True, use the file modify date if there's no date/time in the exif data
|
||||
|
||||
Returns:
|
||||
ExifDateTime named tuple
|
||||
|
||||
"""
|
||||
exiftool = ExifTool(filepath=photo_path, exiftool=self.exiftool_path)
|
||||
exif = exiftool.asdict()
|
||||
return get_exif_date_time_offset(
|
||||
exif, use_file_modify_date=use_file_modify_date
|
||||
)
|
||||
|
||||
def get_photo_path(self, photo: Photo) -> Optional[str]:
|
||||
"""Get the path to a photo
|
||||
|
||||
Args:
|
||||
photo: photoscript.Photo object to act on
|
||||
|
||||
Returns:
|
||||
str: path to photo or None if not found
|
||||
"""
|
||||
_photo = self.db.get_photo(photo.uuid)
|
||||
return _photo.path if _photo else None
|
||||
|
||||
|
||||
def get_exif_date_time_offset(
|
||||
exif: Dict, use_file_modify_date: bool = False
|
||||
) -> ExifDateTime:
|
||||
"""Get datetime/offset from an exif dict as returned by osxphotos.exiftool.ExifTool.asdict()
|
||||
|
||||
Args:
|
||||
exif: dict of exif data
|
||||
use_file_modify_date: if True, use the file modify date if there's no date/time in the exif data
|
||||
"""
|
||||
|
||||
# set to True if no time is found
|
||||
default_time = False
|
||||
|
||||
# set to True if no date/time in EXIF and the FileModifyDate is used
|
||||
used_file_modify_date = False
|
||||
|
||||
# search these fields in this order for date/time/timezone
|
||||
time_fields = [
|
||||
"EXIF:DateTimeOriginal",
|
||||
"EXIF:CreateDate",
|
||||
"QuickTime:CreationDate",
|
||||
"QuickTime:CreateDate",
|
||||
"IPTC:DateCreated",
|
||||
"XMP-exif:DateTimeOriginal",
|
||||
"XMP-xmp:CreateDate",
|
||||
]
|
||||
if use_file_modify_date:
|
||||
time_fields.append("File:FileModifyDate")
|
||||
|
||||
for dt_str in time_fields:
|
||||
dt = exif.get(dt_str)
|
||||
if dt and dt_str == "IPTC:DateCreated":
|
||||
# also need time
|
||||
time_ = exif.get("IPTC:TimeCreated")
|
||||
if not time_:
|
||||
time_ = "00:00:00"
|
||||
default_time = True
|
||||
dt = f"{dt} {time_}"
|
||||
|
||||
if dt:
|
||||
used_file_modify_date = dt_str == "File:FileModifyDate"
|
||||
break
|
||||
else:
|
||||
# no date/time found
|
||||
dt = None
|
||||
|
||||
# try to get offset from EXIF:OffsetTimeOriginal
|
||||
offset = exif.get("EXIF:OffsetTimeOriginal")
|
||||
if dt and not offset:
|
||||
# see if offset set in the dt string
|
||||
matched = re.match(r"\d{4}:\d{2}:\d{2}\s\d{2}:\d{2}:\d{2}([+-]\d{2}:\d{2})", dt)
|
||||
offset = matched.group(1) if matched else None
|
||||
|
||||
if dt:
|
||||
# make sure we have time
|
||||
matched = re.match(r"\d{4}:\d{2}:\d{2}\s(\d{2}:\d{2}:\d{2})", dt)
|
||||
if not matched:
|
||||
if matched := re.match(r"^(\d{4}:\d{2}:\d{2})", dt):
|
||||
# set time to 00:00:00
|
||||
dt = f"{matched.group(1)} 00:00:00"
|
||||
default_time = True
|
||||
|
||||
offset_seconds = exif_offset_to_seconds(offset) if offset else None
|
||||
|
||||
if dt:
|
||||
if offset:
|
||||
# drop offset from dt string and add it back on in datetime %z format
|
||||
dt = re.sub(r"[+-]\d{2}:\d{2}$", "", dt)
|
||||
offset = offset.replace(":", "")
|
||||
dt = f"{dt}{offset}"
|
||||
dt_format = "%Y:%m:%d %H:%M:%S%z"
|
||||
else:
|
||||
dt_format = "%Y:%m:%d %H:%M:%S"
|
||||
|
||||
# convert to datetime
|
||||
# some files can have bad date/time data, (e.g. #24, Date/Time Original = 0000:00:00 00:00:00)
|
||||
try:
|
||||
dt = datetime.datetime.strptime(dt, dt_format)
|
||||
except ValueError:
|
||||
dt = None
|
||||
|
||||
# format offset in form +/-hhmm
|
||||
offset_str = offset.replace(":", "") if offset else ""
|
||||
return ExifDateTime(
|
||||
dt, offset_seconds, offset_str, default_time, used_file_modify_date
|
||||
)
|
||||
@@ -4,14 +4,17 @@ from typing import List, Optional
|
||||
|
||||
import photoscript
|
||||
from more_itertools import chunked
|
||||
from photoscript import Photo, PhotosLibrary
|
||||
|
||||
from .photoinfo import PhotoInfo
|
||||
from .utils import noop
|
||||
from .utils import noop, pluralize
|
||||
|
||||
__all__ = ["PhotosAlbum"]
|
||||
__all__ = ["PhotosAlbum", "PhotosAlbumPhotoScript"]
|
||||
|
||||
|
||||
class PhotosAlbum:
|
||||
"""Add osxphotos.photoinfo.PhotoInfo objects to album"""
|
||||
|
||||
def __init__(self, name: str, verbose: Optional[callable] = None):
|
||||
self.name = name
|
||||
self.verbose = verbose or noop
|
||||
@@ -39,9 +42,40 @@ class PhotosAlbum:
|
||||
self.verbose(f"Error creating Photo object for photo {p.uuid}: {e}")
|
||||
for photolist in chunked(photos, 10):
|
||||
self.album.add(photolist)
|
||||
photo_len = len(photos)
|
||||
photo_word = "photos" if photo_len > 1 else "photo"
|
||||
self.verbose(f"Added {photo_len} {photo_word} to album {self.name}")
|
||||
photo_len = len(photo_list)
|
||||
self.verbose(
|
||||
f"Added {photo_len} {pluralize(photo_len, 'photo', 'photos')} to album {self.name}"
|
||||
)
|
||||
|
||||
def photos(self):
|
||||
return self.album.photos()
|
||||
|
||||
|
||||
class PhotosAlbumPhotoScript:
|
||||
"""Add photoscript.Photo objects to album"""
|
||||
|
||||
def __init__(self, name: str, verbose: Optional[callable] = None):
|
||||
self.name = name
|
||||
self.verbose = verbose or noop
|
||||
self.library = PhotosLibrary()
|
||||
|
||||
album = self.library.album(name)
|
||||
if album is None:
|
||||
self.verbose(f"Creating Photos album '{self.name}'")
|
||||
album = self.library.create_album(name)
|
||||
self.album = album
|
||||
|
||||
def add(self, photo: Photo):
|
||||
self.album.add([photo])
|
||||
self.verbose(f"Added {photo.filename} ({photo.uuid}) to album {self.name}")
|
||||
|
||||
def add_list(self, photo_list: List[Photo]):
|
||||
for photolist in chunked(photo_list, 10):
|
||||
self.album.add(photolist)
|
||||
photo_len = len(photo_list)
|
||||
self.verbose(
|
||||
f"Added {photo_len} {pluralize(photo_len, 'photo', 'photos')} to album {self.name}"
|
||||
)
|
||||
|
||||
def photos(self):
|
||||
return self.album.photos()
|
||||
|
||||
156
osxphotos/phototz.py
Normal file
156
osxphotos/phototz.py
Normal file
@@ -0,0 +1,156 @@
|
||||
""" Update the timezone of a photo in Apple Photos' library """
|
||||
# WARNING: This is a hack. It might destroy your Photos library.
|
||||
# Ensure you have a backup before using!
|
||||
# You have been warned.
|
||||
|
||||
import pathlib
|
||||
import sqlite3
|
||||
from typing import Callable, Optional, Tuple
|
||||
|
||||
from photoscript import Photo
|
||||
from tenacity import retry, stop_after_attempt, wait_exponential
|
||||
|
||||
from ._constants import _DB_TABLE_NAMES
|
||||
from .photosdb.photosdb_utils import get_photos_library_version
|
||||
from .timezones import Timezone
|
||||
from .utils import get_last_library_path, get_system_library_path, noop
|
||||
|
||||
|
||||
def tz_to_str(tz_seconds: int) -> str:
|
||||
"""convert timezone offset in seconds to string in form +00:00 (as offset from GMT)"""
|
||||
sign = "+" if tz_seconds >= 0 else "-"
|
||||
tz_seconds = abs(tz_seconds)
|
||||
# get min and seconds first
|
||||
mm, _ = divmod(tz_seconds, 60)
|
||||
# Get hours
|
||||
hh, mm = divmod(mm, 60)
|
||||
return f"{sign}{hh:02}{mm:02}"
|
||||
|
||||
|
||||
class PhotoTimeZone:
|
||||
"""Get timezone info for photos"""
|
||||
|
||||
def __init__(
|
||||
self,
|
||||
library_path: Optional[str] = None,
|
||||
):
|
||||
# get_last_library_path() returns the path to the last Photos library
|
||||
# opened but sometimes (rarely) fails on some systems
|
||||
try:
|
||||
db_path = (
|
||||
library_path or get_last_library_path() or get_system_library_path()
|
||||
)
|
||||
except Exception:
|
||||
db_path = None
|
||||
if not db_path:
|
||||
raise FileNotFoundError("Could not find Photos database path")
|
||||
|
||||
photos_version = get_photos_library_version(db_path)
|
||||
db_path = str(pathlib.Path(db_path) / "database/Photos.sqlite")
|
||||
self.db_path = db_path
|
||||
self.ASSET_TABLE = _DB_TABLE_NAMES[photos_version]["ASSET"]
|
||||
|
||||
def get_timezone(self, photo: Photo) -> Tuple[int, str, str]:
|
||||
"""Return (timezone_seconds, timezone_str, timezone_name) of photo"""
|
||||
uuid = photo.uuid
|
||||
sql = f""" SELECT
|
||||
ZADDITIONALASSETATTRIBUTES.ZTIMEZONEOFFSET,
|
||||
ZADDITIONALASSETATTRIBUTES.ZTIMEZONENAME
|
||||
FROM ZADDITIONALASSETATTRIBUTES
|
||||
JOIN {self.ASSET_TABLE}
|
||||
ON ZADDITIONALASSETATTRIBUTES.ZASSET = {self.ASSET_TABLE}.Z_PK
|
||||
WHERE {self.ASSET_TABLE}.ZUUID = '{uuid}'
|
||||
"""
|
||||
with sqlite3.connect(self.db_path) as conn:
|
||||
c = conn.cursor()
|
||||
c.execute(sql)
|
||||
results = c.fetchone()
|
||||
tz, tzname = (results[0], results[1])
|
||||
tz_str = tz_to_str(tz)
|
||||
return tz, tz_str, tzname
|
||||
|
||||
|
||||
class PhotoTimeZoneUpdater:
|
||||
"""Update timezones for Photos objects"""
|
||||
|
||||
def __init__(
|
||||
self,
|
||||
timezone: Timezone,
|
||||
verbose: Optional[Callable] = None,
|
||||
library_path: Optional[str] = None,
|
||||
):
|
||||
self.timezone = timezone
|
||||
self.tz_offset = timezone.offset
|
||||
self.tz_name = timezone.name
|
||||
|
||||
self.verbose = verbose or noop
|
||||
|
||||
# get_last_library_path() returns the path to the last Photos library
|
||||
# opened but sometimes (rarely) fails on some systems
|
||||
try:
|
||||
db_path = (
|
||||
library_path or get_last_library_path() or get_system_library_path()
|
||||
)
|
||||
except Exception:
|
||||
db_path = None
|
||||
if not db_path:
|
||||
raise FileNotFoundError("Could not find Photos database path")
|
||||
|
||||
photos_version = get_photos_library_version(db_path)
|
||||
db_path = str(pathlib.Path(db_path) / "database/Photos.sqlite")
|
||||
self.db_path = db_path
|
||||
self.ASSET_TABLE = _DB_TABLE_NAMES[photos_version]["ASSET"]
|
||||
|
||||
def update_photo(self, photo: Photo):
|
||||
"""Update the timezone of a photo in the database
|
||||
|
||||
Args:
|
||||
photo: Photo object to update
|
||||
"""
|
||||
try:
|
||||
self._update_photo(photo)
|
||||
except Exception as e:
|
||||
self.verbose(f"Error updating {photo.uuid}: {e}")
|
||||
|
||||
@retry(
|
||||
wait=wait_exponential(multiplier=1, min=0.100, max=5),
|
||||
stop=stop_after_attempt(10),
|
||||
)
|
||||
def _update_photo(self, photo: Photo):
|
||||
try:
|
||||
uuid = photo.uuid
|
||||
sql = f""" SELECT
|
||||
ZADDITIONALASSETATTRIBUTES.Z_PK,
|
||||
ZADDITIONALASSETATTRIBUTES.Z_OPT,
|
||||
ZADDITIONALASSETATTRIBUTES.ZTIMEZONEOFFSET,
|
||||
ZADDITIONALASSETATTRIBUTES.ZTIMEZONENAME
|
||||
FROM ZADDITIONALASSETATTRIBUTES
|
||||
JOIN {self.ASSET_TABLE}
|
||||
ON ZADDITIONALASSETATTRIBUTES.ZASSET = {self.ASSET_TABLE}.Z_PK
|
||||
WHERE {self.ASSET_TABLE}.ZUUID = '{uuid}'
|
||||
"""
|
||||
with sqlite3.connect(self.db_path) as conn:
|
||||
c = conn.cursor()
|
||||
c.execute(sql)
|
||||
results = c.fetchone()
|
||||
z_opt = results[1] + 1
|
||||
z_pk = results[0]
|
||||
tz_offset = results[2]
|
||||
tz_name = results[3]
|
||||
sql_update = f""" UPDATE ZADDITIONALASSETATTRIBUTES
|
||||
SET Z_OPT={z_opt},
|
||||
ZTIMEZONEOFFSET={self.tz_offset},
|
||||
ZTIMEZONENAME='{self.tz_name}'
|
||||
WHERE Z_PK={z_pk};
|
||||
"""
|
||||
with sqlite3.connect(self.db_path) as conn:
|
||||
c = conn.cursor()
|
||||
c.execute(sql_update)
|
||||
conn.commit()
|
||||
self.verbose(
|
||||
f"Updated timezone for photo [filename]{photo.filename}[/filename] ([uuid]{photo.uuid}[/uuid]) "
|
||||
+ f"from [tz]{tz_name}[/tz], offset=[tz]{tz_offset}[/tz] "
|
||||
+ f"to [tz]{self.tz_name}[/tz], offset=[tz]{self.tz_offset}[/tz]"
|
||||
)
|
||||
except Exception as e:
|
||||
raise e
|
||||
84
osxphotos/timeutils.py
Normal file
84
osxphotos/timeutils.py
Normal file
@@ -0,0 +1,84 @@
|
||||
"""Utilities for working with datetimes"""
|
||||
|
||||
import datetime
|
||||
from typing import Optional
|
||||
|
||||
import re
|
||||
|
||||
|
||||
def utc_offset_string_to_seconds(utc_offset: str) -> int:
|
||||
"""match a UTC offset in format ±[hh]:[mm], ±[h]:[mm], or ±[hh][mm] and return number of seconds offset"""
|
||||
patterns = ["^([+-]?)(\d{1,2}):(\d{2})$", "^([+-]?)(\d{2})(\d{2})$"]
|
||||
for pattern in patterns:
|
||||
match = re.match(pattern, utc_offset)
|
||||
if not match:
|
||||
continue
|
||||
sign = match[1]
|
||||
hours = int(match[2])
|
||||
minutes = int(match[3])
|
||||
if sign == "-":
|
||||
hours = -hours
|
||||
minutes = -minutes
|
||||
return (hours * 60 + minutes) * 60
|
||||
raise ValueError(f"Invalid UTC offset format: {utc_offset}.")
|
||||
|
||||
|
||||
def update_datetime(
|
||||
dt: datetime.datetime,
|
||||
date: Optional[datetime.date] = None,
|
||||
time: Optional[datetime.time] = None,
|
||||
date_delta: Optional[datetime.timedelta] = None,
|
||||
time_delta: Optional[datetime.timedelta] = None,
|
||||
) -> datetime.datetime:
|
||||
"""
|
||||
Update the date and time of a datetime object.
|
||||
|
||||
Args:
|
||||
dt: datetime object
|
||||
date: new date
|
||||
time: new time
|
||||
date_delta: a timedelta to apply
|
||||
time_delta: a timedelta to apply
|
||||
"""
|
||||
if date is not None:
|
||||
dt = dt.replace(year=date.year, month=date.month, day=date.day)
|
||||
if time is not None:
|
||||
dt = dt.replace(
|
||||
hour=time.hour,
|
||||
minute=time.minute,
|
||||
second=time.second,
|
||||
microsecond=time.microsecond,
|
||||
)
|
||||
if date_delta is not None:
|
||||
dt = dt + date_delta
|
||||
if time_delta is not None:
|
||||
dt = dt + time_delta
|
||||
return dt
|
||||
|
||||
|
||||
def time_string_to_datetime(time: str) -> datetime.datetime:
|
||||
"""Convert time string to datetime.datetime"""
|
||||
|
||||
""" valid time formats:
|
||||
- HH:MM:SS,
|
||||
- HH:MM:SS.fff,
|
||||
- HH:MM,
|
||||
|
||||
"""
|
||||
|
||||
time_formats = [
|
||||
"%H:%M:%S",
|
||||
"%H:%M:%S.%f",
|
||||
"%H:%M",
|
||||
]
|
||||
|
||||
for dt_format in time_formats:
|
||||
try:
|
||||
parsed_dt = datetime.datetime.strptime(time, dt_format)
|
||||
except ValueError as e:
|
||||
pass
|
||||
else:
|
||||
return parsed_dt
|
||||
raise ValueError(
|
||||
f"Could not parse time format: {time} does not match {time_formats}"
|
||||
)
|
||||
56
osxphotos/timezones.py
Normal file
56
osxphotos/timezones.py
Normal file
@@ -0,0 +1,56 @@
|
||||
"""Get list of valid timezones on macOS"""
|
||||
|
||||
from typing import Union
|
||||
|
||||
import Foundation
|
||||
import objc
|
||||
|
||||
|
||||
def known_timezone_names():
|
||||
"""Get list of valid timezones on macOS"""
|
||||
return Foundation.NSTimeZone.knownTimeZoneNames()
|
||||
|
||||
|
||||
def format_offset_time(offset: int) -> str:
|
||||
"""Format offset time to exiftool format: -04:00"""
|
||||
sign = "-" if offset < 0 else "+"
|
||||
hours, remainder = divmod(abs(offset), 3600)
|
||||
minutes, _ = divmod(remainder, 60)
|
||||
return f"{sign}{hours:02d}:{minutes:02d}"
|
||||
|
||||
|
||||
class Timezone:
|
||||
"""Create Timezone object from either name (str) or offset from GMT (int)"""
|
||||
|
||||
def __init__(self, tz: Union[str, int]):
|
||||
with objc.autorelease_pool():
|
||||
if isinstance(tz, str):
|
||||
self.timezone = Foundation.NSTimeZone.timeZoneWithName_(tz)
|
||||
self._name = tz
|
||||
elif isinstance(tz, int):
|
||||
self.timezone = Foundation.NSTimeZone.timeZoneForSecondsFromGMT_(tz)
|
||||
self._name = self.timezone.name()
|
||||
else:
|
||||
raise TypeError("Timezone must be a string or an int")
|
||||
|
||||
@property
|
||||
def name(self) -> str:
|
||||
return self._name
|
||||
|
||||
@property
|
||||
def offset(self) -> int:
|
||||
return self.timezone.secondsFromGMT()
|
||||
|
||||
@property
|
||||
def offset_str(self) -> str:
|
||||
return format_offset_time(self.offset)
|
||||
|
||||
@property
|
||||
def abbreviation(self) -> str:
|
||||
return self.timezone.abbreviation()
|
||||
|
||||
def __str__(self):
|
||||
return self.name
|
||||
|
||||
def __repr__(self):
|
||||
return self.name
|
||||
@@ -38,6 +38,7 @@ __all__ = [
|
||||
"noop",
|
||||
"normalize_fs_path",
|
||||
"normalize_unicode",
|
||||
"pluralize",
|
||||
]
|
||||
|
||||
|
||||
@@ -511,3 +512,8 @@ def get_latest_version() -> Tuple[Optional[str], str]:
|
||||
return data["info"]["version"], ""
|
||||
except Exception as e:
|
||||
return None, e
|
||||
|
||||
|
||||
def pluralize(count, singular, plural):
|
||||
"""Return singular or plural based on count"""
|
||||
return singular if count == 1 else plural
|
||||
|
||||
@@ -19,10 +19,11 @@ pyobjc-framework-Metal>=7.3,<9.0
|
||||
pyobjc-framework-Photos>=7.3,<9.0
|
||||
pyobjc-framework-Quartz>=7.3,<9.0
|
||||
pyobjc-framework-Vision>=7.3,<9.0
|
||||
pytimeparse==1.1.8
|
||||
PyYAML>=5.4.1,<6.0.0
|
||||
requests>=2.27.1,<3.0.0
|
||||
rich>=11.2.0,<13.0.0
|
||||
rich_theme_manager>=0.7.0
|
||||
rich_theme_manager>=0.11.0
|
||||
tenacity>=8.0.1,<9.0.0
|
||||
textx>=2.3.0,<2.4.0
|
||||
toml>=0.10.2,<0.11.0
|
||||
|
||||
3
setup.py
3
setup.py
@@ -95,9 +95,10 @@ setup(
|
||||
"pyobjc-framework-Photos>=7.3,<9.0",
|
||||
"pyobjc-framework-Quartz>=7.3,<9.0",
|
||||
"pyobjc-framework-Vision>=7.3,<9.0",
|
||||
"pytimeparse==1.1.8",
|
||||
"requests>=2.27.1,<3.0.0",
|
||||
"rich>=11.2.0,<13.0.0",
|
||||
"rich_theme_manager>=0.7.0",
|
||||
"rich_theme_manager>=0.11.0",
|
||||
"tenacity>=8.0.1,<9.0.0",
|
||||
"textx>=2.3.0,<3.0.0",
|
||||
"toml>=0.10.2,<0.11.0",
|
||||
|
||||
@@ -0,0 +1,10 @@
|
||||
<?xml version="1.0" encoding="UTF-8"?>
|
||||
<!DOCTYPE plist PUBLIC "-//Apple//DTD PLIST 1.0//EN" "http://www.apple.com/DTDs/PropertyList-1.0.dtd">
|
||||
<plist version="1.0">
|
||||
<dict>
|
||||
<key>LibrarySchemaVersion</key>
|
||||
<integer>5001</integer>
|
||||
<key>MetaSchemaVersion</key>
|
||||
<integer>3</integer>
|
||||
</dict>
|
||||
</plist>
|
||||
BIN
tests/TestTimeWarp-10.15.7.photoslibrary/database/Photos.sqlite
Normal file
BIN
tests/TestTimeWarp-10.15.7.photoslibrary/database/Photos.sqlite
Normal file
Binary file not shown.
Binary file not shown.
Binary file not shown.
@@ -0,0 +1,16 @@
|
||||
<?xml version="1.0" encoding="UTF-8"?>
|
||||
<!DOCTYPE plist PUBLIC "-//Apple//DTD PLIST 1.0//EN" "http://www.apple.com/DTDs/PropertyList-1.0.dtd">
|
||||
<plist version="1.0">
|
||||
<dict>
|
||||
<key>hostname</key>
|
||||
<string>Rhets-MacBook-Pro.local</string>
|
||||
<key>hostuuid</key>
|
||||
<string>585B80BF-8D1F-55EF-A9E8-6CF4E5523959</string>
|
||||
<key>pid</key>
|
||||
<integer>1961</integer>
|
||||
<key>processname</key>
|
||||
<string>photolibraryd</string>
|
||||
<key>uid</key>
|
||||
<integer>501</integer>
|
||||
</dict>
|
||||
</plist>
|
||||
BIN
tests/TestTimeWarp-10.15.7.photoslibrary/database/metaSchema.db
Normal file
BIN
tests/TestTimeWarp-10.15.7.photoslibrary/database/metaSchema.db
Normal file
Binary file not shown.
BIN
tests/TestTimeWarp-10.15.7.photoslibrary/database/photos.db
Normal file
BIN
tests/TestTimeWarp-10.15.7.photoslibrary/database/photos.db
Normal file
Binary file not shown.
Binary file not shown.
Binary file not shown.
Binary file not shown.
@@ -0,0 +1,188 @@
|
||||
<?xml version="1.0" encoding="UTF-8"?>
|
||||
<!DOCTYPE plist PUBLIC "-//Apple//DTD PLIST 1.0//EN" "http://www.apple.com/DTDs/PropertyList-1.0.dtd">
|
||||
<plist version="1.0">
|
||||
<dict>
|
||||
<key>BlacklistedMeaningsByMeaning</key>
|
||||
<dict/>
|
||||
<key>MePersonUUID</key>
|
||||
<string>39488755-78C0-40B2-B378-EDA280E1823C</string>
|
||||
<key>SceneWhitelist</key>
|
||||
<array>
|
||||
<string>Graduation</string>
|
||||
<string>Aquarium</string>
|
||||
<string>Food</string>
|
||||
<string>Ice Skating</string>
|
||||
<string>Mountain</string>
|
||||
<string>Cliff</string>
|
||||
<string>Basketball</string>
|
||||
<string>Tennis</string>
|
||||
<string>Jewelry</string>
|
||||
<string>Cheese</string>
|
||||
<string>Softball</string>
|
||||
<string>Football</string>
|
||||
<string>Circus</string>
|
||||
<string>Jet Ski</string>
|
||||
<string>Playground</string>
|
||||
<string>Carousel</string>
|
||||
<string>Paint Ball</string>
|
||||
<string>Windsurfing</string>
|
||||
<string>Sailboat</string>
|
||||
<string>Sunbathing</string>
|
||||
<string>Dam</string>
|
||||
<string>Fireplace</string>
|
||||
<string>Flower</string>
|
||||
<string>Scuba</string>
|
||||
<string>Hiking</string>
|
||||
<string>Cetacean</string>
|
||||
<string>Pier</string>
|
||||
<string>Bowling</string>
|
||||
<string>Snowboarding</string>
|
||||
<string>Zoo</string>
|
||||
<string>Snowmobile</string>
|
||||
<string>Theater</string>
|
||||
<string>Boat</string>
|
||||
<string>Casino</string>
|
||||
<string>Car</string>
|
||||
<string>Diving</string>
|
||||
<string>Cycling</string>
|
||||
<string>Musical Instrument</string>
|
||||
<string>Board Game</string>
|
||||
<string>Castle</string>
|
||||
<string>Sunset Sunrise</string>
|
||||
<string>Martial Arts</string>
|
||||
<string>Motocross</string>
|
||||
<string>Submarine</string>
|
||||
<string>Cat</string>
|
||||
<string>Snow</string>
|
||||
<string>Kiteboarding</string>
|
||||
<string>Squash</string>
|
||||
<string>Geyser</string>
|
||||
<string>Music</string>
|
||||
<string>Archery</string>
|
||||
<string>Desert</string>
|
||||
<string>Blackjack</string>
|
||||
<string>Fireworks</string>
|
||||
<string>Sportscar</string>
|
||||
<string>Feline</string>
|
||||
<string>Soccer</string>
|
||||
<string>Museum</string>
|
||||
<string>Baby</string>
|
||||
<string>Fencing</string>
|
||||
<string>Railroad</string>
|
||||
<string>Nascar</string>
|
||||
<string>Sky Surfing</string>
|
||||
<string>Bird</string>
|
||||
<string>Games</string>
|
||||
<string>Baseball</string>
|
||||
<string>Dressage</string>
|
||||
<string>Snorkeling</string>
|
||||
<string>Pyramid</string>
|
||||
<string>Kite</string>
|
||||
<string>Rowboat</string>
|
||||
<string>Golf</string>
|
||||
<string>Watersports</string>
|
||||
<string>Lightning</string>
|
||||
<string>Canyon</string>
|
||||
<string>Auditorium</string>
|
||||
<string>Night Sky</string>
|
||||
<string>Karaoke</string>
|
||||
<string>Skiing</string>
|
||||
<string>Parade</string>
|
||||
<string>Forest</string>
|
||||
<string>Hot Air Balloon</string>
|
||||
<string>Dragon Parade</string>
|
||||
<string>Easter Egg</string>
|
||||
<string>Monument</string>
|
||||
<string>Jungle</string>
|
||||
<string>Thanksgiving</string>
|
||||
<string>Jockey Horse</string>
|
||||
<string>Stadium</string>
|
||||
<string>Airplane</string>
|
||||
<string>Ballet</string>
|
||||
<string>Yoga</string>
|
||||
<string>Coral Reef</string>
|
||||
<string>Skating</string>
|
||||
<string>Wrestling</string>
|
||||
<string>Bicycle</string>
|
||||
<string>Tattoo</string>
|
||||
<string>Amusement Park</string>
|
||||
<string>Canoe</string>
|
||||
<string>Cheerleading</string>
|
||||
<string>Ping Pong</string>
|
||||
<string>Fishing</string>
|
||||
<string>Magic</string>
|
||||
<string>Reptile</string>
|
||||
<string>Winter Sport</string>
|
||||
<string>Waterfall</string>
|
||||
<string>Train</string>
|
||||
<string>Bonsai</string>
|
||||
<string>Surfing</string>
|
||||
<string>Dog</string>
|
||||
<string>Cake</string>
|
||||
<string>Sledding</string>
|
||||
<string>Sandcastle</string>
|
||||
<string>Glacier</string>
|
||||
<string>Lighthouse</string>
|
||||
<string>Equestrian</string>
|
||||
<string>Rafting</string>
|
||||
<string>Shore</string>
|
||||
<string>Hockey</string>
|
||||
<string>Santa Claus</string>
|
||||
<string>Formula One Car</string>
|
||||
<string>Sport</string>
|
||||
<string>Vehicle</string>
|
||||
<string>Boxing</string>
|
||||
<string>Rollerskating</string>
|
||||
<string>Underwater</string>
|
||||
<string>Orchestra</string>
|
||||
<string>Carnival</string>
|
||||
<string>Rocket</string>
|
||||
<string>Skateboarding</string>
|
||||
<string>Helicopter</string>
|
||||
<string>Performance</string>
|
||||
<string>Oktoberfest</string>
|
||||
<string>Water Polo</string>
|
||||
<string>Skate Park</string>
|
||||
<string>Animal</string>
|
||||
<string>Nightclub</string>
|
||||
<string>String Instrument</string>
|
||||
<string>Dinosaur</string>
|
||||
<string>Gymnastics</string>
|
||||
<string>Cricket</string>
|
||||
<string>Volcano</string>
|
||||
<string>Lake</string>
|
||||
<string>Aurora</string>
|
||||
<string>Dancing</string>
|
||||
<string>Concert</string>
|
||||
<string>Rock Climbing</string>
|
||||
<string>Hang Glider</string>
|
||||
<string>Rodeo</string>
|
||||
<string>Fish</string>
|
||||
<string>Art</string>
|
||||
<string>Motorcycle</string>
|
||||
<string>Volleyball</string>
|
||||
<string>Wake Boarding</string>
|
||||
<string>Badminton</string>
|
||||
<string>Motor Sport</string>
|
||||
<string>Sumo</string>
|
||||
<string>Parasailing</string>
|
||||
<string>Skydiving</string>
|
||||
<string>Kickboxing</string>
|
||||
<string>Pinata</string>
|
||||
<string>Foosball</string>
|
||||
<string>Go Kart</string>
|
||||
<string>Poker</string>
|
||||
<string>Kayak</string>
|
||||
<string>Swimming</string>
|
||||
<string>Atv</string>
|
||||
<string>Beach</string>
|
||||
<string>Dartboard</string>
|
||||
<string>Athletics</string>
|
||||
<string>Camping</string>
|
||||
<string>Tornado</string>
|
||||
<string>Billiards</string>
|
||||
<string>Rugby</string>
|
||||
<string>Airshow</string>
|
||||
</array>
|
||||
</dict>
|
||||
</plist>
|
||||
@@ -0,0 +1,26 @@
|
||||
<?xml version="1.0" encoding="UTF-8"?>
|
||||
<!DOCTYPE plist PUBLIC "-//Apple//DTD PLIST 1.0//EN" "http://www.apple.com/DTDs/PropertyList-1.0.dtd">
|
||||
<plist version="1.0">
|
||||
<dict>
|
||||
<key>insertAlbum</key>
|
||||
<array/>
|
||||
<key>insertAsset</key>
|
||||
<array/>
|
||||
<key>insertHighlight</key>
|
||||
<array/>
|
||||
<key>insertMemory</key>
|
||||
<array/>
|
||||
<key>insertMoment</key>
|
||||
<array/>
|
||||
<key>removeAlbum</key>
|
||||
<array/>
|
||||
<key>removeAsset</key>
|
||||
<array/>
|
||||
<key>removeHighlight</key>
|
||||
<array/>
|
||||
<key>removeMemory</key>
|
||||
<array/>
|
||||
<key>removeMoment</key>
|
||||
<array/>
|
||||
</dict>
|
||||
</plist>
|
||||
@@ -0,0 +1,14 @@
|
||||
<?xml version="1.0" encoding="UTF-8"?>
|
||||
<!DOCTYPE plist PUBLIC "-//Apple//DTD PLIST 1.0//EN" "http://www.apple.com/DTDs/PropertyList-1.0.dtd">
|
||||
<plist version="1.0">
|
||||
<dict>
|
||||
<key>embeddingVersion</key>
|
||||
<string>1</string>
|
||||
<key>localeIdentifier</key>
|
||||
<string>en_US</string>
|
||||
<key>sceneTaxonomySHA</key>
|
||||
<string>87914a047c69fbe8013fad2c70fa70c6c03b08b56190fe4054c880e6b9f57cc3</string>
|
||||
<key>searchIndexVersion</key>
|
||||
<string>10</string>
|
||||
</dict>
|
||||
</plist>
|
||||
Binary file not shown.
Binary file not shown.
Binary file not shown.
Binary file not shown.
|
After Width: | Height: | Size: 2.1 MiB |
Binary file not shown.
|
After Width: | Height: | Size: 2.6 MiB |
Binary file not shown.
|
After Width: | Height: | Size: 3.5 MiB |
Binary file not shown.
|
After Width: | Height: | Size: 3.6 MiB |
Binary file not shown.
|
After Width: | Height: | Size: 3.6 MiB |
@@ -0,0 +1,8 @@
|
||||
<?xml version="1.0" encoding="UTF-8"?>
|
||||
<!DOCTYPE plist PUBLIC "-//Apple//DTD PLIST 1.0//EN" "http://www.apple.com/DTDs/PropertyList-1.0.dtd">
|
||||
<plist version="1.0">
|
||||
<dict>
|
||||
<key>CollapsedSidebarSectionIdentifiers</key>
|
||||
<array/>
|
||||
</dict>
|
||||
</plist>
|
||||
Binary file not shown.
Binary file not shown.
Binary file not shown.
Binary file not shown.
Binary file not shown.
Binary file not shown.
Binary file not shown.
Binary file not shown.
Binary file not shown.
Binary file not shown.
Binary file not shown.
Binary file not shown.
Binary file not shown.
Binary file not shown.
Binary file not shown.
Binary file not shown.
Binary file not shown.
Binary file not shown.
Binary file not shown.
Binary file not shown.
Binary file not shown.
Binary file not shown.
Binary file not shown.
@@ -0,0 +1,26 @@
|
||||
<?xml version="1.0" encoding="UTF-8"?>
|
||||
<!DOCTYPE plist PUBLIC "-//Apple//DTD PLIST 1.0//EN" "http://www.apple.com/DTDs/PropertyList-1.0.dtd">
|
||||
<plist version="1.0">
|
||||
<dict>
|
||||
<key>BackgroundHighlightCollection</key>
|
||||
<date>2021-10-09T17:36:25Z</date>
|
||||
<key>BackgroundHighlightEnrichment</key>
|
||||
<date>2021-10-09T17:36:25Z</date>
|
||||
<key>BackgroundJobAssetRevGeocode</key>
|
||||
<date>2021-10-09T17:36:25Z</date>
|
||||
<key>BackgroundJobSearch</key>
|
||||
<date>2021-10-09T17:36:25Z</date>
|
||||
<key>BackgroundPeopleSuggestion</key>
|
||||
<date>2021-10-09T17:36:24Z</date>
|
||||
<key>BackgroundUserBehaviorProcessor</key>
|
||||
<date>2021-10-07T03:30:30Z</date>
|
||||
<key>PhotoAnalysisGraphLastBackgroundGraphConsistencyUpdateJobDateKey</key>
|
||||
<date>2021-10-07T03:30:29Z</date>
|
||||
<key>PhotoAnalysisGraphLastBackgroundGraphRebuildJobDate</key>
|
||||
<date>2021-10-07T03:30:29Z</date>
|
||||
<key>PhotoAnalysisGraphLastBackgroundMemoryGenerationJobDate</key>
|
||||
<date>2021-10-09T17:36:25Z</date>
|
||||
<key>SiriPortraitDonation</key>
|
||||
<date>2021-10-08T20:51:05Z</date>
|
||||
</dict>
|
||||
</plist>
|
||||
Binary file not shown.
Binary file not shown.
Binary file not shown.
Binary file not shown.
Binary file not shown.
Some files were not shown because too many files have changed in this diff Show More
Reference in New Issue
Block a user