Compare commits

...

18 Commits

Author SHA1 Message Date
Rhet Turnbull
e9cc6ce137 Bug fix, #695 2022-05-23 22:53:39 -07:00
Rhet Turnbull
bc32b1827f Removed screencast, [skip ci] 2022-05-22 15:55:52 -07:00
Rhet Turnbull
206ad8c33c Added screencast [skip ci] 2022-05-22 15:50:54 -07:00
Rhet Turnbull
65e8be7ed7 Added screencast [skip ci] 2022-05-22 15:43:40 -07:00
Rhet Turnbull
6477292a13 Updated CHANGELOG.md [skip ci] 2022-05-22 15:43:21 -07:00
Rhet Turnbull
7a52d413a3 Bug fix 2022-05-22 15:19:48 -07:00
Rhet Turnbull
ab8b7b4b19 Fixed detected_text for videos 2022-05-22 15:06:31 -07:00
Rhet Turnbull
25d4459efd Updated CHANGELOG.md [skip ci] 2022-05-22 13:33:59 -07:00
Rhet Turnbull
128e84c7a4 Added inspect command 2022-05-22 13:32:20 -07:00
Rhet Turnbull
4771207595 Feature inspect command (#701)
* Initial implementation of inspect command

* Updated help for inspect
2022-05-22 12:23:40 -07:00
Rhet Turnbull
0eca3b9c13 Initial implementation of inspect command (#700) 2022-05-22 12:18:40 -07:00
Rhet Turnbull
afec845e1b Updated CHANGELOG.md [skip ci] 2022-05-21 11:13:55 -07:00
Rhet Turnbull
64002044d2 Added timestamp to export_data in exportdb, #697 2022-05-21 11:00:52 -07:00
Rhet Turnbull
4e40d4b74e Added warning on hardlinks to exiftool command 2022-05-21 08:48:59 -07:00
Rhet Turnbull
63d515646b Updated CHANGELOG.md [skip ci] 2022-05-21 08:20:21 -07:00
Rhet Turnbull
0a7575b889 Updated docs 2022-05-21 08:12:32 -07:00
Rhet Turnbull
c776f3070d Added --uuid-info, --uuid-files to exportdb 2022-05-21 08:08:25 -07:00
Rhet Turnbull
3b789242aa Updated CHANGELOG.md [skip ci] 2022-05-21 07:29:12 -07:00
36 changed files with 1039 additions and 137 deletions

View File

@@ -1943,7 +1943,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.49.2'|
|{osxphotos_version}|The osxphotos version, e.g. '0.49.7'|
|{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|

View File

@@ -4,6 +4,45 @@ 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.49.6](https://github.com/RhetTbull/osxphotos/compare/v0.49.5...v0.49.6)
> 22 May 2022
- Bug fix [`7a52d41`](https://github.com/RhetTbull/osxphotos/commit/7a52d413a3cb130a6d11413775fad88933249f36)
- Fixed detected_text for videos [`ab8b7b4`](https://github.com/RhetTbull/osxphotos/commit/ab8b7b4b198c00569b3852b9a21b0b032fad1940)
#### [v0.49.5](https://github.com/RhetTbull/osxphotos/compare/v0.49.4...v0.49.5)
> 22 May 2022
- Feature inspect command [`#701`](https://github.com/RhetTbull/osxphotos/pull/701)
- Initial implementation of inspect command [`#700`](https://github.com/RhetTbull/osxphotos/pull/700)
- Added inspect command [`128e84c`](https://github.com/RhetTbull/osxphotos/commit/128e84c7a4b9745d70e71e3bb1c48c9952431dec)
#### [v0.49.4](https://github.com/RhetTbull/osxphotos/compare/v0.49.3...v0.49.4)
> 21 May 2022
- Added timestamp to export_data in exportdb, #697 [`6400204`](https://github.com/RhetTbull/osxphotos/commit/64002044d2cbbd6fb3c2f0ab69ff6cd5ee2e8e25)
- Added warning on hardlinks to exiftool command [`4e40d4b`](https://github.com/RhetTbull/osxphotos/commit/4e40d4b74e9b244b8eee602f839e595af4f99dfb)
#### [v0.49.3](https://github.com/RhetTbull/osxphotos/compare/v0.49.2...v0.49.3)
> 21 May 2022
- Updated docs [`0a7575b`](https://github.com/RhetTbull/osxphotos/commit/0a7575b889949f9e74ad716bc316e87f2599d4ad)
- Added --uuid-info, --uuid-files to exportdb [`c776f30`](https://github.com/RhetTbull/osxphotos/commit/c776f3070d40ed960eabe3c21c57c354108ff5e4)
#### [v0.49.2](https://github.com/RhetTbull/osxphotos/compare/v0.49.1...v0.49.2)
> 21 May 2022
- Initial implementation of exiftool command, #691 [`#696`](https://github.com/RhetTbull/osxphotos/pull/696)
- Added exiftool command [`8e9f279`](https://github.com/RhetTbull/osxphotos/commit/8e9f27995b56489da8968e77018a8a3d1bbe76bd)
- Added example [skip ci] [`6d5af5c`](https://github.com/RhetTbull/osxphotos/commit/6d5af5c5e87aa0699da7291376cbf05c4237f6ec)
- Updated docs [skip ci] [`3473c2e`](https://github.com/RhetTbull/osxphotos/commit/3473c2ece2b6eea9885893c85aa9eceb16921b94)
- Updated test [`dfcb99f`](https://github.com/RhetTbull/osxphotos/commit/dfcb99f3774cb519c30a9fcf403ca9fdc3fed993)
#### [v0.49.1](https://github.com/RhetTbull/osxphotos/compare/v0.49.0...v0.49.1)
> 17 May 2022

View File

@@ -154,6 +154,7 @@ Commands:
exportdb Utilities for working with the osxphotos export database
help Print help; for help on commands: help <command>.
info Print out descriptive info of the Photos library database.
inspect Interactively inspect photos selected in Photos.
install Install Python packages into the same environment as osxphotos
keywords Print out keywords found in the Photos library.
labels Print out image classification labels found in the Photos...
@@ -1849,7 +1850,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.49.2'
{osxphotos_version} The osxphotos version, e.g. '0.49.7'
{osxphotos_cmd_line} The full command line used to run osxphotos
The following substitutions may result in multiple values. Thus if specified
@@ -4025,7 +4026,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.49.2'|
|{osxphotos_version}|The osxphotos version, e.g. '0.49.7'|
|{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|

View File

@@ -107,6 +107,7 @@ Alternatively, you can also run the command line utility like this: ``python3 -m
exportdb Utilities for working with the osxphotos export database
help Print help; for help on commands: help <command>.
info Print out descriptive info of the Photos library database.
inspect Interactively inspect photos selected in Photos.
install Install Python packages into the same environment as osxphotos
keywords Print out keywords found in the Photos library.
labels Print out image classification labels found in the Photos...

View File

@@ -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: 3a7efae54b6e07cdaf59f7d2e9729b6e
config: 0da8263b5cc3d544b725c487a729f7a1
tags: 645f666f9bcd5a90fca523b33c5a78b7

View File

@@ -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.49.2 documentation</title>
<title>Overview: module code - osxphotos 0.49.7 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.49.2 documentation</div></a>
<a href="../index.html"><div class="brand">osxphotos 0.49.7 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.49.2 documentation</span>
<span class="sidebar-brand-text">osxphotos 0.49.7 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">

View File

@@ -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>osxphotos.export_db - osxphotos 0.49.2 documentation</title>
<title>osxphotos.export_db - osxphotos 0.49.7 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.49.2 documentation</div></a>
<a href="../../index.html"><div class="brand">osxphotos 0.49.7 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.49.2 documentation</span>
<span class="sidebar-brand-text">osxphotos 0.49.7 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">
@@ -202,8 +202,10 @@
<span class="kn">import</span> <span class="nn">json</span>
<span class="kn">import</span> <span class="nn">logging</span>
<span class="kn">import</span> <span class="nn">os</span>
<span class="kn">import</span> <span class="nn">os.path</span>
<span class="kn">import</span> <span class="nn">pathlib</span>
<span class="kn">import</span> <span class="nn">pickle</span>
<span class="kn">import</span> <span class="nn">re</span>
<span class="kn">import</span> <span class="nn">sqlite3</span>
<span class="kn">import</span> <span class="nn">sys</span>
<span class="kn">import</span> <span class="nn">time</span>
@@ -211,7 +213,7 @@
<span class="kn">from</span> <span class="nn">io</span> <span class="kn">import</span> <span class="n">StringIO</span>
<span class="kn">from</span> <span class="nn">sqlite3</span> <span class="kn">import</span> <span class="n">Error</span>
<span class="kn">from</span> <span class="nn">tempfile</span> <span class="kn">import</span> <span class="n">TemporaryDirectory</span>
<span class="kn">from</span> <span class="nn">typing</span> <span class="kn">import</span> <span class="n">Any</span><span class="p">,</span> <span class="n">Optional</span><span class="p">,</span> <span class="n">Tuple</span><span class="p">,</span> <span class="n">Union</span>
<span class="kn">from</span> <span class="nn">typing</span> <span class="kn">import</span> <span class="n">Any</span><span class="p">,</span> <span class="n">List</span><span class="p">,</span> <span class="n">Optional</span><span class="p">,</span> <span class="n">Tuple</span><span class="p">,</span> <span class="n">Union</span>
<span class="kn">from</span> <span class="nn">tenacity</span> <span class="kn">import</span> <span class="n">retry</span><span class="p">,</span> <span class="n">stop_after_attempt</span>
@@ -226,7 +228,7 @@
<span class="s2">"ExportDBTemp"</span><span class="p">,</span>
<span class="p">]</span>
<span class="n">OSXPHOTOS_EXPORTDB_VERSION</span> <span class="o">=</span> <span class="s2">"7.0"</span>
<span class="n">OSXPHOTOS_EXPORTDB_VERSION</span> <span class="o">=</span> <span class="s2">"7.1"</span>
<span class="n">OSXPHOTOS_ABOUT_STRING</span> <span class="o">=</span> <span class="sa">f</span><span class="s2">"Created by osxphotos version </span><span class="si">{</span><span class="n">__version__</span><span class="si">}</span><span class="s2"> (https://github.com/RhetTbull/osxphotos) on </span><span class="si">{</span><span class="n">datetime</span><span class="o">.</span><span class="n">datetime</span><span class="o">.</span><span class="n">now</span><span class="p">()</span><span class="si">}</span><span class="s2">"</span>
<span class="c1"># max retry attempts for methods which use tenacity.retry</span>
@@ -367,6 +369,17 @@
<span class="n">uuid</span> <span class="o">=</span> <span class="kc">None</span>
<span class="k">return</span> <span class="n">uuid</span></div>
<div class="viewcode-block" id="ExportDB.get_files_for_uuid"><a class="viewcode-back" href="../../reference.html#osxphotos.ExportDB.get_files_for_uuid">[docs]</a> <span class="k">def</span> <span class="nf">get_files_for_uuid</span><span class="p">(</span><span class="bp">self</span><span class="p">,</span> <span class="n">uuid</span><span class="p">:</span> <span class="nb">str</span><span class="p">)</span> <span class="o">-&gt;</span> <span class="n">List</span><span class="p">:</span>
<span class="sd">"""query database for UUID and return list of files associated with UUID or empty list"""</span>
<span class="n">conn</span> <span class="o">=</span> <span class="bp">self</span><span class="o">.</span><span class="n">_conn</span>
<span class="n">c</span> <span class="o">=</span> <span class="n">conn</span><span class="o">.</span><span class="n">cursor</span><span class="p">()</span>
<span class="n">c</span><span class="o">.</span><span class="n">execute</span><span class="p">(</span>
<span class="s2">"SELECT filepath FROM export_data WHERE uuid = ?"</span><span class="p">,</span>
<span class="p">(</span><span class="n">uuid</span><span class="p">,),</span>
<span class="p">)</span>
<span class="n">results</span> <span class="o">=</span> <span class="n">c</span><span class="o">.</span><span class="n">fetchall</span><span class="p">()</span>
<span class="k">return</span> <span class="p">[</span><span class="n">os</span><span class="o">.</span><span class="n">path</span><span class="o">.</span><span class="n">join</span><span class="p">(</span><span class="bp">self</span><span class="o">.</span><span class="n">export_dir</span><span class="p">,</span> <span class="n">r</span><span class="p">[</span><span class="mi">0</span><span class="p">])</span> <span class="k">for</span> <span class="n">r</span> <span class="ow">in</span> <span class="n">results</span><span class="p">]</span></div>
<div class="viewcode-block" id="ExportDB.get_photoinfo_for_uuid"><a class="viewcode-back" href="../../reference.html#osxphotos.ExportDB.get_photoinfo_for_uuid">[docs]</a> <span class="k">def</span> <span class="nf">get_photoinfo_for_uuid</span><span class="p">(</span><span class="bp">self</span><span class="p">,</span> <span class="n">uuid</span><span class="p">):</span>
<span class="sd">"""returns the photoinfo JSON struct for a UUID"""</span>
<span class="n">conn</span> <span class="o">=</span> <span class="bp">self</span><span class="o">.</span><span class="n">_conn</span>
@@ -394,6 +407,35 @@
<span class="k">except</span> <span class="n">Error</span> <span class="k">as</span> <span class="n">e</span><span class="p">:</span>
<span class="n">logging</span><span class="o">.</span><span class="n">warning</span><span class="p">(</span><span class="n">e</span><span class="p">)</span></div>
<div class="viewcode-block" id="ExportDB.get_target_for_file"><a class="viewcode-back" href="../../reference.html#osxphotos.ExportDB.get_target_for_file">[docs]</a> <span class="k">def</span> <span class="nf">get_target_for_file</span><span class="p">(</span>
<span class="bp">self</span><span class="p">,</span> <span class="n">uuid</span><span class="p">:</span> <span class="nb">str</span><span class="p">,</span> <span class="n">filename</span><span class="p">:</span> <span class="n">Union</span><span class="p">[</span><span class="nb">str</span><span class="p">,</span> <span class="n">pathlib</span><span class="o">.</span><span class="n">Path</span><span class="p">]</span>
<span class="p">)</span> <span class="o">-&gt;</span> <span class="n">Optional</span><span class="p">[</span><span class="nb">str</span><span class="p">]:</span>
<span class="sd">"""query database for file matching file name and return the matching filename if there is one;</span>
<span class="sd"> otherwise return None; looks for file.ext, file (1).ext, file (2).ext and so on to find the</span>
<span class="sd"> actual target name that was used to export filename</span>
<span class="sd"> Returns: the matching filename or None if no match found</span>
<span class="sd"> """</span>
<span class="n">conn</span> <span class="o">=</span> <span class="bp">self</span><span class="o">.</span><span class="n">_conn</span>
<span class="n">c</span> <span class="o">=</span> <span class="n">conn</span><span class="o">.</span><span class="n">cursor</span><span class="p">()</span>
<span class="n">filepath_normalized</span> <span class="o">=</span> <span class="bp">self</span><span class="o">.</span><span class="n">_normalize_filepath_relative</span><span class="p">(</span><span class="n">filename</span><span class="p">)</span>
<span class="n">filepath_stem</span> <span class="o">=</span> <span class="n">os</span><span class="o">.</span><span class="n">path</span><span class="o">.</span><span class="n">splitext</span><span class="p">(</span><span class="n">filepath_normalized</span><span class="p">)[</span><span class="mi">0</span><span class="p">]</span>
<span class="n">c</span><span class="o">.</span><span class="n">execute</span><span class="p">(</span>
<span class="s2">"SELECT uuid, filepath, filepath_normalized FROM export_data WHERE uuid = ? AND filepath_normalized LIKE ?"</span><span class="p">,</span>
<span class="p">(</span>
<span class="n">uuid</span><span class="p">,</span>
<span class="sa">f</span><span class="s2">"</span><span class="si">{</span><span class="n">filepath_stem</span><span class="si">}</span><span class="s2">%"</span><span class="p">,</span>
<span class="p">),</span>
<span class="p">)</span>
<span class="n">results</span> <span class="o">=</span> <span class="n">c</span><span class="o">.</span><span class="n">fetchall</span><span class="p">()</span>
<span class="k">for</span> <span class="n">result</span> <span class="ow">in</span> <span class="n">results</span><span class="p">:</span>
<span class="n">filepath_normalized</span> <span class="o">=</span> <span class="n">os</span><span class="o">.</span><span class="n">path</span><span class="o">.</span><span class="n">splitext</span><span class="p">(</span><span class="n">result</span><span class="p">[</span><span class="mi">2</span><span class="p">])[</span><span class="mi">0</span><span class="p">]</span>
<span class="k">if</span> <span class="n">re</span><span class="o">.</span><span class="n">match</span><span class="p">(</span><span class="n">re</span><span class="o">.</span><span class="n">escape</span><span class="p">(</span><span class="n">filepath_stem</span><span class="p">)</span> <span class="o">+</span> <span class="sa">r</span><span class="s2">"(\s\(\d+\))?$"</span><span class="p">,</span> <span class="n">filepath_normalized</span><span class="p">):</span>
<span class="k">return</span> <span class="n">os</span><span class="o">.</span><span class="n">path</span><span class="o">.</span><span class="n">join</span><span class="p">(</span><span class="bp">self</span><span class="o">.</span><span class="n">export_dir</span><span class="p">,</span> <span class="n">result</span><span class="p">[</span><span class="mi">1</span><span class="p">])</span>
<span class="k">return</span> <span class="kc">None</span></div>
<div class="viewcode-block" id="ExportDB.get_previous_uuids"><a class="viewcode-back" href="../../reference.html#osxphotos.ExportDB.get_previous_uuids">[docs]</a> <span class="k">def</span> <span class="nf">get_previous_uuids</span><span class="p">(</span><span class="bp">self</span><span class="p">):</span>
<span class="sd">"""returns list of UUIDs of previously exported photos found in export database"""</span>
<span class="n">conn</span> <span class="o">=</span> <span class="bp">self</span><span class="o">.</span><span class="n">_conn</span>
@@ -666,6 +708,10 @@
<span class="c1"># create report_data table</span>
<span class="bp">self</span><span class="o">.</span><span class="n">_migrate_6_0_to_7_0</span><span class="p">(</span><span class="n">conn</span><span class="p">)</span>
<span class="k">if</span> <span class="n">version</span><span class="p">[</span><span class="mi">1</span><span class="p">]</span> <span class="o">&lt;</span> <span class="s2">"7.1"</span><span class="p">:</span>
<span class="c1"># add timestamp to export_data</span>
<span class="bp">self</span><span class="o">.</span><span class="n">_migrate_7_0_to_7_1</span><span class="p">(</span><span class="n">conn</span><span class="p">)</span>
<span class="n">conn</span><span class="o">.</span><span class="n">execute</span><span class="p">(</span><span class="s2">"VACUUM;"</span><span class="p">)</span>
<span class="n">conn</span><span class="o">.</span><span class="n">commit</span><span class="p">()</span>
@@ -883,6 +929,32 @@
<span class="k">except</span> <span class="n">Error</span> <span class="k">as</span> <span class="n">e</span><span class="p">:</span>
<span class="n">logging</span><span class="o">.</span><span class="n">warning</span><span class="p">(</span><span class="n">e</span><span class="p">)</span>
<span class="k">def</span> <span class="nf">_migrate_7_0_to_7_1</span><span class="p">(</span><span class="bp">self</span><span class="p">,</span> <span class="n">conn</span><span class="p">):</span>
<span class="k">try</span><span class="p">:</span>
<span class="n">c</span> <span class="o">=</span> <span class="n">conn</span><span class="o">.</span><span class="n">cursor</span><span class="p">()</span>
<span class="n">c</span><span class="o">.</span><span class="n">execute</span><span class="p">(</span><span class="s2">"""ALTER TABLE export_data ADD COLUMN timestamp DATETIME;"""</span><span class="p">)</span>
<span class="n">c</span><span class="o">.</span><span class="n">execute</span><span class="p">(</span>
<span class="sd">"""</span>
<span class="sd"> CREATE TRIGGER insert_timestamp_trigger</span>
<span class="sd"> AFTER INSERT ON export_data</span>
<span class="sd"> BEGIN</span>
<span class="sd"> UPDATE export_data SET timestamp = STRFTIME('%Y-%m-%d %H:%M:%f', 'NOW') WHERE id = NEW.id;</span>
<span class="sd"> END;</span>
<span class="sd"> """</span>
<span class="p">)</span>
<span class="n">c</span><span class="o">.</span><span class="n">execute</span><span class="p">(</span>
<span class="sd">"""</span>
<span class="sd"> CREATE TRIGGER update_timestamp_trigger</span>
<span class="sd"> AFTER UPDATE On export_data</span>
<span class="sd"> BEGIN</span>
<span class="sd"> UPDATE export_data SET timestamp = STRFTIME('%Y-%m-%d %H:%M:%f', 'NOW') WHERE id = NEW.id;</span>
<span class="sd"> END;</span>
<span class="sd"> """</span>
<span class="p">)</span>
<span class="n">conn</span><span class="o">.</span><span class="n">commit</span><span class="p">()</span>
<span class="k">except</span> <span class="n">Error</span> <span class="k">as</span> <span class="n">e</span><span class="p">:</span>
<span class="n">logging</span><span class="o">.</span><span class="n">warning</span><span class="p">(</span><span class="n">e</span><span class="p">)</span>
<span class="k">def</span> <span class="nf">_perform_db_maintenace</span><span class="p">(</span><span class="bp">self</span><span class="p">,</span> <span class="n">conn</span><span class="p">):</span>
<span class="sd">"""Perform database maintenance"""</span>
<span class="k">try</span><span class="p">:</span>
@@ -1244,6 +1316,21 @@
<span class="k">if</span> <span class="ow">not</span> <span class="bp">self</span><span class="o">.</span><span class="n">_context_manager</span><span class="p">:</span>
<span class="n">conn</span><span class="o">.</span><span class="n">commit</span><span class="p">()</span>
<span class="nd">@property</span>
<span class="k">def</span> <span class="nf">timestamp</span><span class="p">(</span><span class="bp">self</span><span class="p">):</span>
<span class="sd">"""returns the timestamp value"""</span>
<span class="n">conn</span> <span class="o">=</span> <span class="bp">self</span><span class="o">.</span><span class="n">_conn</span>
<span class="n">c</span> <span class="o">=</span> <span class="n">conn</span><span class="o">.</span><span class="n">cursor</span><span class="p">()</span>
<span class="k">if</span> <span class="n">row</span> <span class="o">:=</span> <span class="n">c</span><span class="o">.</span><span class="n">execute</span><span class="p">(</span>
<span class="s2">"SELECT timestamp FROM export_data WHERE filepath_normalized = ?;"</span><span class="p">,</span>
<span class="p">(</span><span class="bp">self</span><span class="o">.</span><span class="n">_filepath_normalized</span><span class="p">,),</span>
<span class="p">)</span><span class="o">.</span><span class="n">fetchone</span><span class="p">():</span>
<span class="k">return</span> <span class="n">row</span><span class="p">[</span><span class="mi">0</span><span class="p">]</span>
<span class="k">raise</span> <span class="ne">ValueError</span><span class="p">(</span>
<span class="sa">f</span><span class="s2">"No timestamp found in database for </span><span class="si">{</span><span class="bp">self</span><span class="o">.</span><span class="n">_filepath_normalized</span><span class="si">}</span><span class="s2">"</span>
<span class="p">)</span>
<span class="k">def</span> <span class="nf">asdict</span><span class="p">(</span><span class="bp">self</span><span class="p">):</span>
<span class="sd">"""Return dict of self"""</span>
<span class="n">exifdata</span> <span class="o">=</span> <span class="n">json</span><span class="o">.</span><span class="n">loads</span><span class="p">(</span><span class="bp">self</span><span class="o">.</span><span class="n">exifdata</span><span class="p">)</span> <span class="k">if</span> <span class="bp">self</span><span class="o">.</span><span class="n">exifdata</span> <span class="k">else</span> <span class="kc">None</span>
@@ -1252,6 +1339,7 @@
<span class="s2">"filepath"</span><span class="p">:</span> <span class="bp">self</span><span class="o">.</span><span class="n">filepath</span><span class="p">,</span>
<span class="s2">"filepath_normalized"</span><span class="p">:</span> <span class="bp">self</span><span class="o">.</span><span class="n">filepath_normalized</span><span class="p">,</span>
<span class="s2">"uuid"</span><span class="p">:</span> <span class="bp">self</span><span class="o">.</span><span class="n">uuid</span><span class="p">,</span>
<span class="s2">"timestamp"</span><span class="p">:</span> <span class="bp">self</span><span class="o">.</span><span class="n">timestamp</span><span class="p">,</span>
<span class="s2">"digest"</span><span class="p">:</span> <span class="bp">self</span><span class="o">.</span><span class="n">digest</span><span class="p">,</span>
<span class="s2">"src_sig"</span><span class="p">:</span> <span class="bp">self</span><span class="o">.</span><span class="n">src_sig</span><span class="p">,</span>
<span class="s2">"dest_sig"</span><span class="p">:</span> <span class="bp">self</span><span class="o">.</span><span class="n">dest_sig</span><span class="p">,</span>

View File

@@ -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>osxphotos.photoexporter - osxphotos 0.49.2 documentation</title>
<title>osxphotos.photoexporter - osxphotos 0.49.7 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.49.2 documentation</div></a>
<a href="../../index.html"><div class="brand">osxphotos 0.49.7 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.49.2 documentation</span>
<span class="sidebar-brand-text">osxphotos 0.49.7 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">
@@ -240,7 +240,13 @@
<span class="kn">from</span> <span class="nn">.phototemplate</span> <span class="kn">import</span> <span class="n">RenderOptions</span>
<span class="kn">from</span> <span class="nn">.rich_utils</span> <span class="kn">import</span> <span class="n">add_rich_markup_tag</span>
<span class="kn">from</span> <span class="nn">.uti</span> <span class="kn">import</span> <span class="n">get_preferred_uti_extension</span>
<span class="kn">from</span> <span class="nn">.utils</span> <span class="kn">import</span> <span class="n">hexdigest</span><span class="p">,</span> <span class="n">increment_filename</span><span class="p">,</span> <span class="n">lineno</span><span class="p">,</span> <span class="n">list_directory</span>
<span class="kn">from</span> <span class="nn">.utils</span> <span class="kn">import</span> <span class="p">(</span>
<span class="n">hexdigest</span><span class="p">,</span>
<span class="n">increment_filename</span><span class="p">,</span>
<span class="n">increment_filename_with_count</span><span class="p">,</span>
<span class="n">lineno</span><span class="p">,</span>
<span class="n">list_directory</span><span class="p">,</span>
<span class="p">)</span>
<span class="n">__all__</span> <span class="o">=</span> <span class="p">[</span>
<span class="s2">"ExportError"</span><span class="p">,</span>
@@ -684,7 +690,7 @@
<span class="n">src</span> <span class="o">=</span> <span class="n">staged_files</span><span class="o">.</span><span class="n">edited</span> <span class="k">if</span> <span class="n">options</span><span class="o">.</span><span class="n">edited</span> <span class="k">else</span> <span class="n">staged_files</span><span class="o">.</span><span class="n">original</span>
<span class="c1"># get the right destination path depending on options.update, etc.</span>
<span class="n">dest</span> <span class="o">=</span> <span class="bp">self</span><span class="o">.</span><span class="n">_get_dest_path</span><span class="p">(</span><span class="n">src</span><span class="p">,</span> <span class="n">dest</span><span class="p">,</span> <span class="n">options</span><span class="p">)</span>
<span class="n">dest</span> <span class="o">=</span> <span class="bp">self</span><span class="o">.</span><span class="n">_get_dest_path</span><span class="p">(</span><span class="n">dest</span><span class="p">,</span> <span class="n">options</span><span class="p">)</span>
<span class="bp">self</span><span class="o">.</span><span class="n">_render_options</span><span class="o">.</span><span class="n">filepath</span> <span class="o">=</span> <span class="nb">str</span><span class="p">(</span><span class="n">dest</span><span class="p">)</span>
<span class="n">all_results</span> <span class="o">=</span> <span class="n">ExportResults</span><span class="p">()</span>
@@ -838,12 +844,11 @@
<span class="k">return</span> <span class="n">edited_filename</span>
<span class="k">def</span> <span class="nf">_get_dest_path</span><span class="p">(</span>
<span class="bp">self</span><span class="p">,</span> <span class="n">src</span><span class="p">:</span> <span class="nb">str</span><span class="p">,</span> <span class="n">dest</span><span class="p">:</span> <span class="n">pathlib</span><span class="o">.</span><span class="n">Path</span><span class="p">,</span> <span class="n">options</span><span class="p">:</span> <span class="n">ExportOptions</span>
<span class="bp">self</span><span class="p">,</span> <span class="n">dest</span><span class="p">:</span> <span class="n">pathlib</span><span class="o">.</span><span class="n">Path</span><span class="p">,</span> <span class="n">options</span><span class="p">:</span> <span class="n">ExportOptions</span>
<span class="p">)</span> <span class="o">-&gt;</span> <span class="n">pathlib</span><span class="o">.</span><span class="n">Path</span><span class="p">:</span>
<span class="sd">"""If destination exists find match in ExportDB, on disk, or add (1), (2), and so on to filename to get a valid destination</span>
<span class="sd"> Args:</span>
<span class="sd"> src (str): source file path</span>
<span class="sd"> dest (str): destination path</span>
<span class="sd"> options (ExportOptions): Export options</span>
@@ -859,6 +864,10 @@
<span class="sa">f</span><span class="s2">"destination exists (</span><span class="si">{</span><span class="n">dest</span><span class="si">}</span><span class="s2">); overwrite=</span><span class="si">{</span><span class="n">options</span><span class="o">.</span><span class="n">overwrite</span><span class="si">}</span><span class="s2">, increment=</span><span class="si">{</span><span class="n">options</span><span class="o">.</span><span class="n">increment</span><span class="si">}</span><span class="s2">"</span>
<span class="p">)</span>
<span class="c1"># if overwrite, we don't care if the file exists or not</span>
<span class="k">if</span> <span class="n">options</span><span class="o">.</span><span class="n">overwrite</span><span class="p">:</span>
<span class="k">return</span> <span class="n">dest</span>
<span class="c1"># if not update or overwrite, check to see if file exists and if so, add (1), (2), etc</span>
<span class="c1"># until we find one that works</span>
<span class="c1"># Photos checks the stem and adds (1), (2), etc which avoids collision with sidecars</span>
@@ -871,29 +880,36 @@
<span class="k">return</span> <span class="n">pathlib</span><span class="o">.</span><span class="n">Path</span><span class="p">(</span><span class="n">increment_filename</span><span class="p">(</span><span class="n">dest</span><span class="p">))</span>
<span class="c1"># if update and file exists, need to check to see if it's the right file by checking export db</span>
<span class="k">if</span> <span class="p">(</span><span class="n">options</span><span class="o">.</span><span class="n">update</span> <span class="ow">or</span> <span class="n">options</span><span class="o">.</span><span class="n">force_update</span><span class="p">)</span> <span class="ow">and</span> <span class="n">dest</span><span class="o">.</span><span class="n">exists</span><span class="p">()</span> <span class="ow">and</span> <span class="n">src</span><span class="p">:</span>
<span class="k">if</span> <span class="n">options</span><span class="o">.</span><span class="n">update</span> <span class="ow">or</span> <span class="n">options</span><span class="o">.</span><span class="n">force_update</span><span class="p">:</span>
<span class="n">export_db</span> <span class="o">=</span> <span class="n">options</span><span class="o">.</span><span class="n">export_db</span>
<span class="c1"># destination exists, check to see if destination is the right UUID</span>
<span class="n">dest_uuid</span> <span class="o">=</span> <span class="n">export_db</span><span class="o">.</span><span class="n">get_uuid_for_file</span><span class="p">(</span><span class="n">dest</span><span class="p">)</span>
<span class="k">if</span> <span class="n">dest_uuid</span> <span class="o">!=</span> <span class="bp">self</span><span class="o">.</span><span class="n">photo</span><span class="o">.</span><span class="n">uuid</span><span class="p">:</span>
<span class="c1"># not the right file, find the right one</span>
<span class="c1"># find files that match "dest_name (*.ext" (e.g. "dest_name (1).jpg", "dest_name (2).jpg)", ...)</span>
<span class="n">dest_files</span> <span class="o">=</span> <span class="n">list_directory</span><span class="p">(</span>
<span class="n">dest</span><span class="o">.</span><span class="n">parent</span><span class="p">,</span>
<span class="n">startswith</span><span class="o">=</span><span class="sa">f</span><span class="s2">"</span><span class="si">{</span><span class="n">dest</span><span class="o">.</span><span class="n">stem</span><span class="si">}</span><span class="s2"> ("</span><span class="p">,</span>
<span class="n">endswith</span><span class="o">=</span><span class="n">dest</span><span class="o">.</span><span class="n">suffix</span><span class="p">,</span>
<span class="n">include_path</span><span class="o">=</span><span class="kc">True</span><span class="p">,</span>
<span class="p">)</span>
<span class="k">for</span> <span class="n">file_</span> <span class="ow">in</span> <span class="n">dest_files</span><span class="p">:</span>
<span class="n">dest_uuid</span> <span class="o">=</span> <span class="n">export_db</span><span class="o">.</span><span class="n">get_uuid_for_file</span><span class="p">(</span><span class="n">file_</span><span class="p">)</span>
<span class="k">if</span> <span class="n">dest_uuid</span> <span class="o">==</span> <span class="bp">self</span><span class="o">.</span><span class="n">photo</span><span class="o">.</span><span class="n">uuid</span><span class="p">:</span>
<span class="n">dest</span> <span class="o">=</span> <span class="n">pathlib</span><span class="o">.</span><span class="n">Path</span><span class="p">(</span><span class="n">file_</span><span class="p">)</span>
<span class="k">break</span>
<span class="k">else</span><span class="p">:</span>
<span class="c1"># increment the destination file</span>
<span class="n">dest</span> <span class="o">=</span> <span class="n">pathlib</span><span class="o">.</span><span class="n">Path</span><span class="p">(</span><span class="n">increment_filename</span><span class="p">(</span><span class="n">dest</span><span class="p">))</span>
<span class="k">if</span> <span class="n">dest_uuid</span> <span class="ow">is</span> <span class="kc">None</span> <span class="ow">and</span> <span class="ow">not</span> <span class="n">dest</span><span class="o">.</span><span class="n">exists</span><span class="p">():</span>
<span class="c1"># destination doesn't exist in export db and doesn't exist on disk</span>
<span class="c1"># so we can just use it</span>
<span class="k">return</span> <span class="n">dest</span>
<span class="c1"># either dest was updated in the if clause above or not updated at all</span>
<span class="k">if</span> <span class="n">dest_uuid</span> <span class="o">==</span> <span class="bp">self</span><span class="o">.</span><span class="n">photo</span><span class="o">.</span><span class="n">uuid</span><span class="p">:</span>
<span class="c1"># destination is the right file</span>
<span class="k">return</span> <span class="n">dest</span>
<span class="c1"># either dest_uuid is wrong or file exists and there's no associated UUID, so find a name that matches</span>
<span class="c1"># or create a new name if no match</span>
<span class="c1"># find files that match "dest_name (*.ext" (e.g. "dest_name (1).jpg", "dest_name (2).jpg)", ...)</span>
<span class="c1"># first, find all matching files in export db and see if there's a match</span>
<span class="k">if</span> <span class="n">dest_target</span> <span class="o">:=</span> <span class="n">export_db</span><span class="o">.</span><span class="n">get_target_for_file</span><span class="p">(</span><span class="bp">self</span><span class="o">.</span><span class="n">photo</span><span class="o">.</span><span class="n">uuid</span><span class="p">,</span> <span class="n">dest</span><span class="p">):</span>
<span class="c1"># there's a match so use that</span>
<span class="k">return</span> <span class="n">pathlib</span><span class="o">.</span><span class="n">Path</span><span class="p">(</span><span class="n">dest_target</span><span class="p">)</span>
<span class="c1"># no match so need to create a new name</span>
<span class="c1"># increment the destination file until we find one that doesn't exist and doesn't match another uuid in the database</span>
<span class="n">count</span> <span class="o">=</span> <span class="mi">0</span>
<span class="n">dest</span><span class="p">,</span> <span class="n">count</span> <span class="o">=</span> <span class="n">increment_filename_with_count</span><span class="p">(</span><span class="n">dest</span><span class="p">,</span> <span class="n">count</span><span class="p">)</span>
<span class="n">count</span> <span class="o">+=</span> <span class="mi">1</span>
<span class="k">while</span> <span class="n">export_db</span><span class="o">.</span><span class="n">get_uuid_for_file</span><span class="p">(</span><span class="n">dest</span><span class="p">)</span> <span class="ow">is</span> <span class="ow">not</span> <span class="kc">None</span><span class="p">:</span>
<span class="n">dest</span><span class="p">,</span> <span class="n">count</span> <span class="o">=</span> <span class="n">increment_filename_with_count</span><span class="p">(</span><span class="n">dest</span><span class="p">,</span> <span class="n">count</span><span class="p">)</span>
<span class="k">return</span> <span class="n">pathlib</span><span class="o">.</span><span class="n">Path</span><span class="p">(</span><span class="n">dest</span><span class="p">)</span>
<span class="c1"># fail safe...I can't think of a case that gets here</span>
<span class="k">return</span> <span class="n">dest</span>
<span class="k">def</span> <span class="nf">_should_update_photo</span><span class="p">(</span>
@@ -1232,9 +1248,9 @@
<span class="k">def</span> <span class="nf">_export_photo</span><span class="p">(</span>
<span class="bp">self</span><span class="p">,</span>
<span class="n">src</span><span class="p">,</span>
<span class="n">dest</span><span class="p">,</span>
<span class="n">options</span><span class="p">,</span>
<span class="n">src</span><span class="p">:</span> <span class="nb">str</span><span class="p">,</span>
<span class="n">dest</span><span class="p">:</span> <span class="n">pathlib</span><span class="o">.</span><span class="n">Path</span><span class="p">,</span>
<span class="n">options</span><span class="p">:</span> <span class="n">ExportOptions</span><span class="p">,</span>
<span class="p">):</span>
<span class="sd">"""Helper function for export()</span>
<span class="sd"> Does the actual copy or hardlink taking the appropriate</span>
@@ -1640,7 +1656,7 @@
<span class="p">)</span> <span class="o">-&gt;</span> <span class="n">ExportResults</span><span class="p">:</span>
<span class="sd">"""Write exif metadata to src file using exiftool</span>
<span class="sd"> Caution: This method modifies *src*, not *dest*, </span>
<span class="sd"> Caution: This method modifies *src*, not *dest*,</span>
<span class="sd"> so src must be a copy of the original file if you don't want the source modified;</span>
<span class="sd"> it also does not write to dest (dest is the intended destination for purposes of</span>
<span class="sd"> referencing the export database. This allows the exiftool update to be done on the</span>

View File

@@ -320,7 +320,7 @@ Template Substitutions
* - {crlf}
- a carriage return + line feed: '\r\n'
* - {osxphotos_version}
- The osxphotos version, e.g. '0.49.2'
- The osxphotos version, e.g. '0.49.7'
* - {osxphotos_cmd_line}
- The full command line used to run osxphotos
* - {album}

View File

@@ -1,6 +1,6 @@
var DOCUMENTATION_OPTIONS = {
URL_ROOT: document.getElementById("documentation_options").getAttribute('data-url_root'),
VERSION: '0.49.2',
VERSION: '0.49.7',
LANGUAGE: 'None',
COLLAPSE_INDEX: false,
BUILDER: 'html',

View File

@@ -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.49.2 documentation</title>
<title>OSXPhotos Command Line Interface (CLI) - osxphotos 0.49.7 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.49.2 documentation</div></a>
<a href="index.html"><div class="brand">osxphotos 0.49.7 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.49.2 documentation</span>
<span class="sidebar-brand-text">osxphotos 0.49.7 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">
@@ -1278,6 +1278,16 @@ to modify this behavior.</p>
<dd><p>Print information about FILE_PATH contained in the database.</p>
</dd></dl>
<dl class="std option">
<dt class="sig sig-object std" id="cmdoption-osxphotos-exportdb-uuid-files">
<span class="sig-name descname"><span class="pre">--uuid-files</span></span><span class="sig-prename descclassname"> <span class="pre">&lt;UUID&gt;</span></span><a class="headerlink" href="#cmdoption-osxphotos-exportdb-uuid-files" title="Permalink to this definition">#</a></dt>
<dd><p>List exported files associated with UUID.</p>
</dd></dl>
<dl class="std option">
<dt class="sig sig-object std" id="cmdoption-osxphotos-exportdb-uuid-info">
<span class="sig-name descname"><span class="pre">--uuid-info</span></span><span class="sig-prename descclassname"> <span class="pre">&lt;UUID&gt;</span></span><a class="headerlink" href="#cmdoption-osxphotos-exportdb-uuid-info" title="Permalink to this definition">#</a></dt>
<dd><p>Print information about UUID contained in the database.</p>
</dd></dl>
<dl class="std option">
<dt class="sig sig-object std" id="cmdoption-osxphotos-exportdb-report">
<span class="sig-name descname"><span class="pre">--report</span></span><span class="sig-prename descclassname"> <span class="pre">&lt;REPORT_FILE</span> <span class="pre">RUN_ID&gt;</span></span><a class="headerlink" href="#cmdoption-osxphotos-exportdb-report" title="Permalink to this definition">#</a></dt>
<dd><p>Generate an export report as <cite>osxphotos export … report REPORT_FILE</cite> would have done. This allows you to re-create an export report if you didnt use the report option when running <cite>osxphotos export</cite>. The extension of the report file is used to determine the format. Valid extensions are: .csv (CSV file), .json (JSON), .db and .sqlite (SQLite database). RUN_ID may be any integer from -10 to 0 specifying which run to use. For example, <cite>report report.csv 0</cite> will generate a CSV report for the last run and <cite>report report.json -1</cite> will generate a JSON report for the second-to-last run (one run prior to last run). REPORT_FILE may be a template string (see Templating System), for example, report export_{today.date}.csv will write a CSV report file named with todays date. See also append.</p>
@@ -1361,6 +1371,38 @@ to modify this behavior.</p>
<dd><p>Optional argument(s)</p>
</dd></dl>
</section>
<section id="osxphotos-inspect">
<h3>inspect<a class="headerlink" href="#osxphotos-inspect" title="Permalink to this headline">#</a></h3>
<p>Interactively inspect photos selected in Photos.</p>
<p>Open Photos then run <cite>osxphotos inspect</cite> in the terminal.
As you select a photo in Photos, inspect will display metadata about the photo.
Press Ctrl+C to exit when done.
Works best with a modern terminal like iTerm2 or Kitty.</p>
<div class="highlight-shell notranslate"><div class="highlight"><pre><span></span>osxphotos inspect <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-inspect-t">
<span id="cmdoption-osxphotos-inspect-detect-text"></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">--detect-text</span></span><span class="sig-prename descclassname"></span><a class="headerlink" href="#cmdoption-osxphotos-inspect-t" title="Permalink to this definition">#</a></dt>
<dd><p>Detect text in photos</p>
</dd></dl>
<dl class="std option">
<dt class="sig sig-object std" id="cmdoption-osxphotos-inspect-theme">
<span class="sig-name descname"><span class="pre">--theme</span></span><span class="sig-prename descclassname"> <span class="pre">&lt;THEME&gt;</span></span><a class="headerlink" href="#cmdoption-osxphotos-inspect-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-inspect-db">
<span class="sig-name descname"><span class="pre">--db</span></span><span class="sig-prename descclassname"> <span class="pre">&lt;PHOTOS_LIBRARY_PATH&gt;</span></span><a class="headerlink" href="#cmdoption-osxphotos-inspect-db" title="Permalink to this definition">#</a></dt>
<dd><p>Specify Photos database path. Path to Photos library/database can be specified using either db or directly as PHOTOS_LIBRARY positional argument. If neither db or PHOTOS_LIBRARY provided, will attempt to find the library to use in the following order: 1. last opened library, 2. system library, 3. ~/Pictures/Photos Library.photoslibrary</p>
</dd></dl>
</section>
<section id="osxphotos-install">
<h3>install<a class="headerlink" href="#osxphotos-install" title="Permalink to this headline">#</a></h3>
<p>Install Python packages into the same environment as osxphotos</p>
@@ -2705,6 +2747,7 @@ Commands:
<li><a class="reference internal" href="#osxphotos-exportdb">exportdb</a></li>
<li><a class="reference internal" href="#osxphotos-help">help</a></li>
<li><a class="reference internal" href="#osxphotos-info">info</a></li>
<li><a class="reference internal" href="#osxphotos-inspect">inspect</a></li>
<li><a class="reference internal" href="#osxphotos-install">install</a></li>
<li><a class="reference internal" href="#osxphotos-keywords">keywords</a></li>
<li><a class="reference internal" href="#osxphotos-labels">labels</a></li>

View File

@@ -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.49.2 documentation</title>
<meta name="generator" content="sphinx-4.4.0, furo 2022.04.07"/><title>Index - osxphotos 0.49.7 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.49.2 documentation</div></a>
<a href="index.html"><div class="brand">osxphotos 0.49.7 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.49.2 documentation</span>
<span class="sidebar-brand-text">osxphotos 0.49.7 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">
@@ -402,6 +402,8 @@
<li><a href="cli.html#cmdoption-osxphotos-export-db">osxphotos-export command line option</a>
</li>
<li><a href="cli.html#cmdoption-osxphotos-info-db">osxphotos-info command line option</a>
</li>
<li><a href="cli.html#cmdoption-osxphotos-inspect-db">osxphotos-inspect command line option</a>
</li>
<li><a href="cli.html#cmdoption-osxphotos-keywords-db">osxphotos-keywords command line option</a>
</li>
@@ -483,6 +485,13 @@
<li><a href="cli.html#cmdoption-osxphotos-exiftool-description-template">osxphotos-exiftool command line option</a>
</li>
<li><a href="cli.html#cmdoption-osxphotos-export-description-template">osxphotos-export command line option</a>
</li>
</ul></li>
<li>
--detect-text
<ul>
<li><a href="cli.html#cmdoption-osxphotos-inspect-t">osxphotos-inspect command line option</a>
</li>
</ul></li>
<li>
@@ -1143,8 +1152,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
@@ -1156,6 +1163,8 @@
<li><a href="cli.html#cmdoption-osxphotos-repl-not-burst">osxphotos-repl command line option</a>
</li>
</ul></li>
</ul></td>
<td style="width: 33%; vertical-align: top;"><ul>
<li>
--not-cloudasset
@@ -1714,6 +1723,8 @@
<li><a href="cli.html#cmdoption-osxphotos-exiftool-theme">osxphotos-exiftool command line option</a>
</li>
<li><a href="cli.html#cmdoption-osxphotos-export-theme">osxphotos-export command line option</a>
</li>
<li><a href="cli.html#cmdoption-osxphotos-inspect-theme">osxphotos-inspect command line option</a>
</li>
<li><a href="cli.html#cmdoption-osxphotos-timewarp-theme">osxphotos-timewarp command line option</a>
</li>
@@ -1872,6 +1883,13 @@
<li><a href="cli.html#cmdoption-osxphotos-query-uuid">osxphotos-query command line option</a>
</li>
<li><a href="cli.html#cmdoption-osxphotos-repl-uuid">osxphotos-repl command line option</a>
</li>
</ul></li>
<li>
--uuid-files
<ul>
<li><a href="cli.html#cmdoption-osxphotos-exportdb-uuid-files">osxphotos-exportdb command line option</a>
</li>
</ul></li>
<li>
@@ -1883,6 +1901,13 @@
<li><a href="cli.html#cmdoption-osxphotos-query-uuid-from-file">osxphotos-query command line option</a>
</li>
<li><a href="cli.html#cmdoption-osxphotos-repl-uuid-from-file">osxphotos-repl command line option</a>
</li>
</ul></li>
<li>
--uuid-info
<ul>
<li><a href="cli.html#cmdoption-osxphotos-exportdb-uuid-info">osxphotos-exportdb command line option</a>
</li>
</ul></li>
<li>
@@ -2072,6 +2097,8 @@
-t
<ul>
<li><a href="cli.html#cmdoption-osxphotos-inspect-t">osxphotos-inspect command line option</a>
</li>
<li><a href="cli.html#cmdoption-osxphotos-timewarp-t">osxphotos-timewarp command line option</a>
</li>
</ul></li>
@@ -2464,6 +2491,8 @@
<li><a href="reference.html#osxphotos.ExportDB.get_exported_files">get_exported_files() (osxphotos.ExportDB method)</a>
</li>
<li><a href="reference.html#osxphotos.ExportDB.get_file_record">get_file_record() (osxphotos.ExportDB method)</a>
</li>
<li><a href="reference.html#osxphotos.ExportDB.get_files_for_uuid">get_files_for_uuid() (osxphotos.ExportDB method)</a>
</li>
<li><a href="reference.html#osxphotos.PhotoTemplate.get_media_type">get_media_type() (osxphotos.PhotoTemplate method)</a>
</li>
@@ -2476,6 +2505,8 @@
</ul></td>
<td style="width: 33%; vertical-align: top;"><ul>
<li><a href="reference.html#osxphotos.ExportDB.get_previous_uuids">get_previous_uuids() (osxphotos.ExportDB method)</a>
</li>
<li><a href="reference.html#osxphotos.ExportDB.get_target_for_file">get_target_for_file() (osxphotos.ExportDB method)</a>
</li>
<li><a href="reference.html#osxphotos.PhotoTemplate.get_template_value">get_template_value() (osxphotos.PhotoTemplate method)</a>
</li>
@@ -3248,6 +3279,10 @@
<li><a href="cli.html#cmdoption-osxphotos-exportdb-touch-file">--touch-file</a>
</li>
<li><a href="cli.html#cmdoption-osxphotos-exportdb-update-signatures">--update-signatures</a>
</li>
<li><a href="cli.html#cmdoption-osxphotos-exportdb-uuid-files">--uuid-files</a>
</li>
<li><a href="cli.html#cmdoption-osxphotos-exportdb-uuid-info">--uuid-info</a>
</li>
<li><a href="cli.html#cmdoption-osxphotos-exportdb-vacuum">--vacuum</a>
</li>
@@ -3278,6 +3313,19 @@
<li><a href="cli.html#cmdoption-osxphotos-info-json">--json</a>
</li>
<li><a href="cli.html#cmdoption-osxphotos-info-arg-PHOTOS_LIBRARY">PHOTOS_LIBRARY</a>
</li>
</ul></li>
<li>
osxphotos-inspect command line option
<ul>
<li><a href="cli.html#cmdoption-osxphotos-inspect-db">--db</a>
</li>
<li><a href="cli.html#cmdoption-osxphotos-inspect-t">--detect-text</a>
</li>
<li><a href="cli.html#cmdoption-osxphotos-inspect-theme">--theme</a>
</li>
<li><a href="cli.html#cmdoption-osxphotos-inspect-t">-t</a>
</li>
</ul></li>
<li>
@@ -3313,6 +3361,8 @@
<li><a href="cli.html#cmdoption-osxphotos-labels-arg-PHOTOS_LIBRARY">PHOTOS_LIBRARY</a>
</li>
</ul></li>
</ul></td>
<td style="width: 33%; vertical-align: top;"><ul>
<li>
osxphotos-list command line option
@@ -3320,8 +3370,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

View File

@@ -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.49.2 documentation</title>
<title>osxphotos 0.49.7 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.49.2 documentation</div></a>
<a href="#"><div class="brand">osxphotos 0.49.7 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.49.2 documentation</span>
<span class="sidebar-brand-text">osxphotos 0.49.7 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">
@@ -242,6 +242,7 @@
<li class="toctree-l3"><a class="reference internal" href="cli.html#osxphotos-exportdb">exportdb</a></li>
<li class="toctree-l3"><a class="reference internal" href="cli.html#osxphotos-help">help</a></li>
<li class="toctree-l3"><a class="reference internal" href="cli.html#osxphotos-info">info</a></li>
<li class="toctree-l3"><a class="reference internal" href="cli.html#osxphotos-inspect">inspect</a></li>
<li class="toctree-l3"><a class="reference internal" href="cli.html#osxphotos-install">install</a></li>
<li class="toctree-l3"><a class="reference internal" href="cli.html#osxphotos-keywords">keywords</a></li>
<li class="toctree-l3"><a class="reference internal" href="cli.html#osxphotos-labels">labels</a></li>

Binary file not shown.

View File

@@ -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 OSXPhotoss documentation!" href="index.html" />
<meta name="generator" content="sphinx-4.4.0, furo 2022.04.07"/>
<title>OSXPhotos - osxphotos 0.49.2 documentation</title>
<title>OSXPhotos - osxphotos 0.49.7 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.49.2 documentation</div></a>
<a href="index.html"><div class="brand">osxphotos 0.49.7 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.49.2 documentation</span>
<span class="sidebar-brand-text">osxphotos 0.49.7 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">

View File

@@ -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.49.2 documentation</title>
<title>OSXPhotos Python Package Overview - osxphotos 0.49.7 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.49.2 documentation</div></a>
<a href="index.html"><div class="brand">osxphotos 0.49.7 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.49.2 documentation</span>
<span class="sidebar-brand-text">osxphotos 0.49.7 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">

View File

@@ -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.49.2 documentation</title>
<meta name="generator" content="sphinx-4.4.0, furo 2022.04.07"/><title>Python Module Index - osxphotos 0.49.7 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.49.2 documentation</div></a>
<a href="index.html"><div class="brand">osxphotos 0.49.7 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.49.2 documentation</span>
<span class="sidebar-brand-text">osxphotos 0.49.7 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">

View File

@@ -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.49.2 documentation</title>
<title>OSXPhotos python API - osxphotos 0.49.7 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.49.2 documentation</div></a>
<a href="index.html"><div class="brand">osxphotos 0.49.7 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.49.2 documentation</span>
<span class="sidebar-brand-text">osxphotos 0.49.7 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">
@@ -423,6 +423,11 @@ If called in context manager, returns True (execution is delayed until exiting c
<p>Returns: an ExportRecord object or None if filename not found</p>
</dd></dl>
<dl class="py method">
<dt class="sig sig-object py" id="osxphotos.ExportDB.get_files_for_uuid">
<span class="sig-name descname"><span class="pre">get_files_for_uuid</span></span><span class="sig-paren">(</span><em class="sig-param"><span class="n"><span class="pre">uuid</span></span><span class="p"><span class="pre">:</span></span><span class="w"> </span><span class="n"><span class="pre">str</span></span></em><span class="sig-paren">)</span> <span class="sig-return"><span class="sig-return-icon"></span> <span class="sig-return-typehint"><span class="pre">List</span></span></span><a class="reference internal" href="_modules/osxphotos/export_db.html#ExportDB.get_files_for_uuid"><span class="viewcode-link"><span class="pre">[source]</span></span></a><a class="headerlink" href="#osxphotos.ExportDB.get_files_for_uuid" title="Permalink to this definition">#</a></dt>
<dd><p>query database for UUID and return list of files associated with UUID or empty list</p>
</dd></dl>
<dl class="py method">
<dt class="sig sig-object py" id="osxphotos.ExportDB.get_photoinfo_for_uuid">
<span class="sig-name descname"><span class="pre">get_photoinfo_for_uuid</span></span><span class="sig-paren">(</span><em class="sig-param"><span class="n"><span class="pre">uuid</span></span></em><span class="sig-paren">)</span><a class="reference internal" href="_modules/osxphotos/export_db.html#ExportDB.get_photoinfo_for_uuid"><span class="viewcode-link"><span class="pre">[source]</span></span></a><a class="headerlink" href="#osxphotos.ExportDB.get_photoinfo_for_uuid" title="Permalink to this definition">#</a></dt>
<dd><p>returns the photoinfo JSON struct for a UUID</p>
@@ -433,6 +438,16 @@ If called in context manager, returns True (execution is delayed until exiting c
<dd><p>returns list of UUIDs of previously exported photos found in export database</p>
</dd></dl>
<dl class="py method">
<dt class="sig sig-object py" id="osxphotos.ExportDB.get_target_for_file">
<span class="sig-name descname"><span class="pre">get_target_for_file</span></span><span class="sig-paren">(</span><em class="sig-param"><span class="n"><span class="pre">uuid</span></span><span class="p"><span class="pre">:</span></span><span class="w"> </span><span class="n"><span class="pre">str</span></span></em>, <em class="sig-param"><span class="n"><span class="pre">filename</span></span><span class="p"><span class="pre">:</span></span><span class="w"> </span><span class="n"><span class="pre">Union</span><span class="p"><span class="pre">[</span></span><span class="pre">str</span><span class="p"><span class="pre">,</span></span><span class="w"> </span><span class="pre">pathlib.Path</span><span class="p"><span class="pre">]</span></span></span></em><span class="sig-paren">)</span> <span class="sig-return"><span class="sig-return-icon"></span> <span class="sig-return-typehint"><span class="pre">Optional</span><span class="p"><span class="pre">[</span></span><span class="pre">str</span><span class="p"><span class="pre">]</span></span></span></span><a class="reference internal" href="_modules/osxphotos/export_db.html#ExportDB.get_target_for_file"><span class="viewcode-link"><span class="pre">[source]</span></span></a><a class="headerlink" href="#osxphotos.ExportDB.get_target_for_file" title="Permalink to this definition">#</a></dt>
<dd><dl class="simple">
<dt>query database for file matching file name and return the matching filename if there is one;</dt><dd><p>otherwise return None; looks for file.ext, file (1).ext, file (2).ext and so on to find the
actual target name that was used to export filename</p>
</dd>
</dl>
<p>Returns: the matching filename or None if no match found</p>
</dd></dl>
<dl class="py method">
<dt class="sig sig-object py" id="osxphotos.ExportDB.get_uuid_for_file">
<span class="sig-name descname"><span class="pre">get_uuid_for_file</span></span><span class="sig-paren">(</span><em class="sig-param"><span class="n"><span class="pre">filename</span></span></em><span class="sig-paren">)</span><a class="reference internal" href="_modules/osxphotos/export_db.html#ExportDB.get_uuid_for_file"><span class="viewcode-link"><span class="pre">[source]</span></span></a><a class="headerlink" href="#osxphotos.ExportDB.get_uuid_for_file" title="Permalink to this definition">#</a></dt>
<dd><p>query database for filename and return UUID

Binary file not shown.

After

Width:  |  Height:  |  Size: 28 MiB

View File

@@ -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.49.2 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.49.7 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.49.2 documentation</div></a>
<a href="index.html"><div class="brand">osxphotos 0.49.7 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.49.2 documentation</span>
<span class="sidebar-brand-text">osxphotos 0.49.7 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

View File

@@ -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.49.2 documentation</title>
<title>OSXPhotos Template System - osxphotos 0.49.7 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.49.2 documentation</div></a>
<a href="index.html"><div class="brand">osxphotos 0.49.7 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.49.2 documentation</span>
<span class="sidebar-brand-text">osxphotos 0.49.7 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">
@@ -567,7 +567,7 @@
<td><p>a carriage return + line feed: rn</p></td>
</tr>
<tr class="row-odd"><td><p>{osxphotos_version}</p></td>
<td><p>The osxphotos version, e.g. 0.49.2</p></td>
<td><p>The osxphotos version, e.g. 0.49.7</p></td>
</tr>
<tr class="row-even"><td><p>{osxphotos_cmd_line}</p></td>
<td><p>The full command line used to run osxphotos</p></td>

View File

@@ -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.49.2 documentation</title>
<title>OSXPhotos Tutorial - osxphotos 0.49.7 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.49.2 documentation</div></a>
<a href="index.html"><div class="brand">osxphotos 0.49.7 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.49.2 documentation</span>
<span class="sidebar-brand-text">osxphotos 0.49.7 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">

View File

@@ -320,7 +320,7 @@ Template Substitutions
* - {crlf}
- a carriage return + line feed: '\r\n'
* - {osxphotos_version}
- The osxphotos version, e.g. '0.49.2'
- The osxphotos version, e.g. '0.49.7'
* - {osxphotos_cmd_line}
- The full command line used to run osxphotos
* - {album}

View File

@@ -1,3 +1,3 @@
""" version info """
__version__ = "0.49.2"
__version__ = "0.49.7"

View File

@@ -59,6 +59,7 @@ from .keywords import keywords
from .labels import labels
from .list import _list_libraries, list_libraries
from .persons import persons
from .photo_inspect import photo_inspect
from .places import places
from .query import query
from .repl import repl
@@ -88,6 +89,7 @@ __all__ = [
"list_libraries",
"load_uuid_from_file",
"persons",
"photo_inspect",
"places",
"query",
"repl",

View File

@@ -21,6 +21,7 @@ from .keywords import keywords
from .labels import labels
from .list import list_libraries
from .persons import persons
from .photo_inspect import photo_inspect
from .places import places
from .query import query
from .repl import repl
@@ -79,6 +80,7 @@ for command in [
labels,
list_libraries,
persons,
photo_inspect,
places,
query,
repl,

View File

@@ -1,5 +1,6 @@
"""exiftool command for osxphotos CLI to update an previous export with exiftool metadata"""
import os
import pathlib
import sys
from typing import Callable
@@ -18,7 +19,6 @@ from osxphotos.utils import pluralize
from .click_rich_echo import (
rich_click_echo,
rich_echo,
rich_echo_error,
set_rich_console,
set_rich_theme,
@@ -343,6 +343,8 @@ def process_files(
total = len(files)
count = 1
all_results = ExportResults()
# process files that are hardlinked? Requires user confirmation
hardlink_ok = False
with rich_progress(console=get_verbose_console(), mock=options.verbose) as progress:
task = progress.add_task("Processing files", total=total)
for uuid, file in files:
@@ -350,7 +352,14 @@ def process_files(
verbose(f"Skipping missing file [filepath]{file}[/]")
report_writer.write(ExportResults(missing=[file]))
continue
# zzz put in check for hardlink
if not hardlink_ok and os.stat(file).st_nlink > 1:
rich_click_echo(
f"[warning]:warning-emoji: Warning: file [filepath]{file}[/] is hardlinked.\n"
"You may be modifying linked original files in your Photos library. "
"This is inadvisable.[/]",
)
click.confirm("Continue processing hardlinks?", abort=True)
hardlink_ok = True
verbose(f"Processing file [filepath]{file}[/] ([num]{count}/{total}[/num])")
photo = photosdb.get_photo(uuid)
export_options = ExportOptions(

View File

@@ -1,5 +1,6 @@
"""exportdb command for osxphotos CLI"""
import json
import pathlib
import sys
@@ -63,6 +64,18 @@ from .verbose import verbose_print
nargs=1,
help="Print information about FILE_PATH contained in the database.",
)
@click.option(
"--uuid-files",
metavar="UUID",
nargs=1,
help="List exported files associated with UUID.",
)
@click.option(
"--uuid-info",
metavar="UUID",
nargs=1,
help="Print information about UUID contained in the database.",
)
@click.option(
"--report",
metavar="REPORT_FILE RUN_ID",
@@ -110,22 +123,24 @@ from .verbose import verbose_print
)
@click.argument("export_db", metavar="EXPORT_DATABASE", type=click.Path(exists=True))
def exportdb(
version,
vacuum,
check_signatures,
update_signatures,
touch_file,
last_run,
save_config,
info,
report,
migrate,
sql,
export_dir,
append,
verbose,
check_signatures,
dry_run,
export_db,
export_dir,
info,
last_run,
migrate,
report,
save_config,
sql,
touch_file,
update_signatures,
uuid_files,
uuid_info,
vacuum,
verbose,
version,
):
"""Utilities for working with the osxphotos export database"""
@@ -163,6 +178,8 @@ def exportdb(
sql,
touch_file,
update_signatures,
uuid_files,
uuid_info,
vacuum,
version,
]
@@ -273,6 +290,37 @@ def exportdb(
print(f"[red]File '{info}' not found in export database[/red]")
sys.exit(0)
if uuid_info:
# get photoinfo record for a uuid
exportdb = ExportDB(export_db, export_dir)
try:
info_rec = exportdb.get_photoinfo_for_uuid(uuid_info)
except Exception as e:
print(f"[red]Error: {e}[/red]")
sys.exit(1)
else:
if info_rec:
print(json.dumps(json.loads(info_rec), sort_keys=True, indent=2))
else:
print(f"[red]UUID '{uuid_info}' not found in export database[/red]")
sys.exit(0)
if uuid_files:
# list files associated with a uuid
exportdb = ExportDB(export_db, export_dir)
try:
file_list = exportdb.get_files_for_uuid(uuid_files)
except Exception as e:
print(f"[red]Error: {e}[/red]")
sys.exit(1)
else:
if file_list:
for f in file_list:
print(f)
else:
print(f"[red]UUID '{uuid_files}' not found in export database[/red]")
sys.exit(0)
if report:
exportdb = ExportDB(export_db, export_dir)
report_template, run_id = report

View File

@@ -0,0 +1,475 @@
"""Inspect photos selected in Photos """
import functools
import re
from fractions import Fraction
from multiprocessing import Process, Queue
from queue import Empty
from time import gmtime, sleep, strftime
from typing import List, Optional, Tuple
import pathlib
import bitmath
import click
from applescript import ScriptError
from photoscript import PhotosLibrary
from rich.console import Console
from rich.layout import Layout
from rich.live import Live
from rich.panel import Panel
from osxphotos import PhotoInfo, PhotosDB
from osxphotos._constants import _UNKNOWN_PERSON
from osxphotos.rich_utils import add_rich_markup_tag
from osxphotos.text_detection import detect_text as detect_text_in_photo
from osxphotos.utils import dd_to_dms_str
from .color_themes import get_theme
from .common import DB_OPTION, THEME_OPTION, get_photos_db
# global that tracks UUID being inspected
CURRENT_UUID = None
# helpers for markup
bold = add_rich_markup_tag("bold")
dim = add_rich_markup_tag("dim")
def extract_uuid(text: str) -> str:
"""Extract a UUID from a string"""
if match := re.search(
r"([0-9a-fA-F]{8}-[0-9a-fA-F]{4}-[0-9a-fA-F]{4}-[0-9a-fA-F]{4}-[0-9a-fA-F]{12})",
text,
):
return match[1]
return None
def trim(text: str, pad: str = "") -> str:
"""Truncate a string to a fit in console, - len(pad) - 4; also removes new lines"""
width = Console().width - len(pad) - 4
text = text.replace("\n", " ")
return text if len(text) <= width else f"{text[: width- 3]}..."
def inspect_photo(photo: PhotoInfo, detected_text: Optional[str] = None) -> str:
"""Get info about an osxphotos PhotoInfo object formatted for printing"""
properties = [
bold("Filename: ") + f"[filename]{photo.original_filename}[/]",
bold("Type: ") + get_photo_type(photo),
bold("UUID: ") + f"[uuid]{photo.uuid}[/]",
bold("Date: ") + f"[time]{photo.date.isoformat()}[/]",
bold("Date added: ") + f"[time]{photo.date_added.isoformat()}[/]",
]
if photo.date_modified:
properties.append(
bold("Date modified: ") + f"[time]{photo.date_modified.isoformat()}[/]"
)
if photo.intrash and photo.date_trashed:
properties.append(
bold("Date deleted: ") + f"[time]{photo.date_trashed.isoformat()}[/]"
)
if photo.import_info and photo.import_info.creation_date:
properties.append(
bold("Date imported: ")
+ f"[time]{photo.import_info.creation_date.isoformat()}[/]"
)
properties.extend(
[
bold("Dimensions: ")
+ f"[num]{photo.width}[/] x [num]{photo.height}[/] "
+ bold("Orientation: ")
+ f"[num]{photo.orientation}[/]",
bold("File size: ")
+ f"[num]{float(bitmath.Byte(photo.original_filesize).to_MB()):.2f} MB[/]",
bold("Title: ") + f"{photo.title or '-'}",
bold("Description: ")
+ f"{trim(photo.description or '-', 'Description: ')}",
bold("Edited: ")
+ f"{'' if photo.hasadjustments else '-'} "
+ bold("External edits: ")
+ f"{'' if photo.external_edit else '-'}",
bold("Keywords: ") + f"{', '.join(photo.keywords) or '-'}",
bold("Persons: ")
+ f"{', '.join(p for p in photo.persons if p != _UNKNOWN_PERSON) or '-'}",
bold("Location: ")
+ f"{', '.join(dd_to_dms_str(*photo.location)) if photo.location[0] else '-'}",
bold("Place: ") + f"{photo.place.name if photo.place else '-'}",
bold("Categories: ") + f"{', '.join(photo.labels) or '-'}",
]
)
properties.append(format_flags(photo))
properties.append(format_albums(photo))
if photo.project_info:
properties.append(
bold("Projects: ")
+ f"{', '.join(p.title for p in photo.project_info) or '-'}"
)
if photo.moment_info:
properties.append(bold("Moment: ") + f"{photo.moment_info.title or '-'}")
if photo.comments:
comments = [f"{c.user}: {c.text}" for c in photo.comments]
properties.append(
bold("Comments: ") + trim(f"{', '.join(comments)}", "Comments: ")
)
if photo.likes:
properties.append(
bold("Likes: ")
+ trim(f"{', '.join(l.user for l in photo.likes)}", "Likes: ")
)
properties.append(format_exif_info(photo))
properties.append(format_score_info(photo))
if detected_text:
# have detected text for this photo
properties.append(
bold("Detected text: ") + trim(detected_text, "Detected text: ")
)
properties.append(format_paths(photo))
return "\n".join(properties)
def format_score_info(photo: PhotoInfo) -> str:
"""Format score_info"""
score_str = bold("Score: ")
if photo.score:
score_str += f"[num]{photo.score.overall}[/]" if photo.score else "-"
return score_str
def format_flags(photo: PhotoInfo) -> str:
"""Format special properties"""
flag_str = bold("Flags: ")
flags = []
if photo.favorite:
flags.append("favorite")
if photo.visible:
flags.append("visible")
if photo.hidden:
flags.append("hidden")
if photo.ismissing:
flags.append("missing")
if photo.intrash:
flags.append("in trash")
if photo.iscloudasset:
flags.append("cloud asset")
if photo.incloud:
flags.append("in cloud")
if photo.shared:
flags.append("shared")
flag_str += f"{', '.join(flags) or '-'}"
return flag_str
def format_albums(photo: PhotoInfo) -> str:
"""Format albums for inspect_photo"""
album_str = bold("Albums: ")
album_names = []
for album in photo.album_info:
if album.folder_names:
folder_str = "/".join(album.folder_names)
album_names.append(f"{folder_str}/{album.title}")
else:
album_names.append(album.title)
album_str += f"{', '.join(album_names) or '-'}"
return album_str
def format_path_link(path: str) -> str:
"""Format a path as URI for display in terminal"""
return f"[link={pathlib.Path(path).as_uri()}]{path}[/link]"
def format_paths(photo: PhotoInfo) -> str:
"""format photo paths for inspect_photo"""
path_str = bold("Path original: ")
path_str += f"[filepath]{format_path_link(photo.path)}[/]" if photo.path else "-"
if photo.path_edited:
path_str += "\n"
path_str += bold("Path edited: ")
path_str += f"[filepath]{format_path_link(photo.path_edited)}[/]"
if photo.path_raw:
path_str += "\n"
path_str += bold("Path raw: ")
path_str += f"[filepath]{format_path_link(photo.path_raw)}[/]"
if photo.path_derivatives:
path_str += "\n"
path_str += bold("Path preview: ")
path_str += f"[filepath]{format_path_link(photo.path_derivatives[0])}[/]"
return path_str
def format_exif_info(photo: PhotoInfo) -> str:
"""Format exif_info for inspect_photo"""
exif_str = bold("EXIF: ")
exif = photo.exif_info
if not exif:
return f"{exif_str}-"
if exif.camera_make:
exif_str += f"{exif.camera_make} "
if exif.camera_model:
exif_str += f"{exif.camera_model} "
if exif.focal_length:
exif_str += f"{exif.focal_length:.2f}mm "
if exif.iso:
exif_str += f"ISO {exif.iso} "
if exif.flash_fired:
exif_str += "Flash "
if exif.exposure_bias is not None:
exif_str += (
f"{int(exif.exposure_bias)} ev "
if exif.exposure_bias == int(exif.exposure_bias)
else f"{exif.exposure_bias} ev "
)
if exif.aperture:
exif_str += (
f"ƒ{int(exif.aperture)} "
if exif.aperture == int(exif.aperture)
else f"ƒ{exif.aperture:.1f} "
)
if exif.shutter_speed:
exif_str += f"{Fraction(exif.shutter_speed).limit_denominator(100_000)}s "
if exif.bit_rate:
exif_str += f"{exif.bit_rate} bit rate"
if exif.sample_rate:
exif_str += f"{exif.sample_rate} sample rate"
if exif.fps:
exif_str += f"{exif.fps or 0:.1f}FPS "
if exif.duration:
exif_str += f"{strftime('%H:%M:%S', gmtime(exif.duration or 0))} "
if exif.codec:
exif_str += f"{exif.codec}"
if exif.track_format:
exif_str += f"{exif.track_format}"
return exif_str
def get_photo_type(photo: PhotoInfo) -> str:
"""Return a string describing the type of photo"""
photo_type = "video" if photo.ismovie else "photo"
if photo.has_raw:
photo_type += " RAW+JPEG"
if photo.israw:
photo_type += " RAW"
if photo.burst:
photo_type += " burst"
if photo.live_photo:
photo_type += " live"
if photo.selfie:
photo_type += " selfie"
if photo.panorama:
photo_type += " panorama"
if photo.hdr:
photo_type += " HDR"
if photo.screenshot:
photo_type += " screenshot"
if photo.slow_mo:
photo_type += " slow-mo"
if photo.time_lapse:
photo_type += " time-lapse"
if photo.portrait:
photo_type += " portrait"
return photo_type
def start_text_detection(photo: PhotoInfo) -> Tuple[Process, Queue]:
"""Start text detection process for a photo"""
path_preview = photo.path_derivatives[0] if photo.path_derivatives else None
path = photo.path_edited or photo.path or path_preview
if not path:
raise ValueError("No path to photo")
queue = Queue()
p = Process(
target=_get_detected_text,
args=(photo.uuid, path, photo.orientation, queue),
)
p.start()
return (p, queue)
def _get_detected_text(uuid: str, path: str, orientation: int, queue: Queue) -> None:
"""Called by start_text_detection to run text detection in separate process"""
try:
if text := detect_text_in_photo(path, orientation):
queue.put([uuid, " ".join(t[0] for t in text if t[1] > 0.5)])
except Exception as e:
queue.put([None, str(e)])
def get_uuid_for_photos_selection() -> List[str]:
"""Get the uuid for the first photo selected in Photos
Returns: tuple of (uuid, total_selected_photos)"""
photoslib = PhotosLibrary()
try:
if photos := photoslib.selection:
return photos[0].uuid, len(photos)
except (ValueError, ScriptError) as e:
if uuid := extract_uuid(str(e)):
return uuid, 1
else:
raise e
return None, 0
def make_layout() -> Layout:
"""Define the layout."""
layout = Layout(name="root")
layout.split(
Layout(name="main", ratio=1),
Layout(name="status", size=1),
Layout(name="footer", size=1),
)
return layout
@click.command(name="inspect")
@click.option("--detect-text", "-t", is_flag=True, help="Detect text in photos")
@THEME_OPTION
@DB_OPTION
def photo_inspect(db, theme, detect_text):
"""Interactively inspect photos selected in Photos.
Open Photos then run `osxphotos inspect` in the terminal.
As you select a photo in Photos, inspect will display metadata about the photo.
Press Ctrl+C to exit when done.
Works best with a modern terminal like iTerm2 or Kitty.
"""
db = get_photos_db(db)
if not db:
raise click.UsageError(
"Did not locate Photos database. Try running with --db option."
)
theme = get_theme(theme)
console = Console(theme=theme)
layout = make_layout()
layout["footer"].update("Press Ctrl+C to quit")
layout["main"].update(
Panel("Loading Photos database, hold on...", title="Loading...")
)
def loading_status(status: str):
layout["status"].update(f"Loading database: {status}")
def update_status(status: str):
layout["status"].update(status)
def update_detected_text(photo: PhotoInfo, uuid: str, text: str):
global CURRENT_UUID
if uuid == CURRENT_UUID:
layout["main"].update(
Panel(
inspect_photo(photo, text),
title=photo.title or photo.original_filename,
)
)
with Live(layout, console=console, refresh_per_second=10, screen=True):
photosdb = PhotosDB(dbfile=db, verbose=loading_status)
layout["main"].update(
Panel("Select a photo in Photos to inspect", title="Select a photo")
)
processes = []
global CURRENT_UUID
detected_text_cache = {}
last_uuid = None
uuid = None
total = 0
while True:
try:
uuid, total = get_uuid_for_photos_selection()
except Exception as e:
layout["main"].update(Panel(f"Error: {e}", title="Error"))
except KeyboardInterrupt:
# allow Ctrl+C to quit
break
finally:
if uuid and uuid != last_uuid:
if total > 1:
update_status(
f"{total} photos selected; inspecting uuid={uuid}"
)
else:
update_status(f"Inspecting uuid={uuid}")
CURRENT_UUID = uuid
if photo := photosdb.get_photo(uuid):
layout["main"].update(
Panel(
inspect_photo(
photo,
detected_text=detected_text_cache.get(uuid, None),
),
title=photo.title or photo.original_filename,
)
)
# start text detection if requested
if (
detect_text
and photo.isphoto
and (
photo.path
or photo.path_edited
or photo.path_derivatives
)
and uuid not in detected_text_cache
):
# can only detect text on photos if on disk
# start text detection in separate process as it can take a few seconds
update_detected_text_callback = functools.partial(
update_detected_text, photo
)
process, queue = start_text_detection(photo)
processes.append(
[True, process, queue, update_detected_text_callback]
)
else:
layout["main"].update(
Panel(
f"No photo found in database for uuid={uuid}",
title="Error",
)
)
last_uuid = uuid
if not uuid:
last_uuid = None
layout["main"].update(
Panel("Select a photo in Photos to inspect", title="Select a photo")
)
update_status("Select a photo in Photos to inspect")
if detect_text:
# check on text detection processes
for _, values in enumerate(processes):
alive, process, queue, update_detected_text_callback = values
if alive:
# process hasn't been marked as dead yet
try:
uuid, text = queue.get(False)
update_detected_text_callback(uuid, text)
detected_text_cache[uuid] = text
process.join()
# set alive = False
values[0] = False
except Empty:
if not process.is_alive():
# process has finished, nothing in the queue
process.join()
# set alive = False
values[0] = False
sleep(0.100)

Binary file not shown.

View File

@@ -6,8 +6,10 @@ import gzip
import json
import logging
import os
import os.path
import pathlib
import pickle
import re
import sqlite3
import sys
import time
@@ -15,7 +17,7 @@ from contextlib import suppress
from io import StringIO
from sqlite3 import Error
from tempfile import TemporaryDirectory
from typing import Any, Optional, Tuple, Union
from typing import Any, List, Optional, Tuple, Union
from tenacity import retry, stop_after_attempt
@@ -30,7 +32,7 @@ __all__ = [
"ExportDBTemp",
]
OSXPHOTOS_EXPORTDB_VERSION = "7.0"
OSXPHOTOS_EXPORTDB_VERSION = "7.1"
OSXPHOTOS_ABOUT_STRING = f"Created by osxphotos version {__version__} (https://github.com/RhetTbull/osxphotos) on {datetime.datetime.now()}"
# max retry attempts for methods which use tenacity.retry
@@ -171,6 +173,17 @@ class ExportDB:
uuid = None
return uuid
def get_files_for_uuid(self, uuid: str) -> List:
"""query database for UUID and return list of files associated with UUID or empty list"""
conn = self._conn
c = conn.cursor()
c.execute(
"SELECT filepath FROM export_data WHERE uuid = ?",
(uuid,),
)
results = c.fetchall()
return [os.path.join(self.export_dir, r[0]) for r in results]
def get_photoinfo_for_uuid(self, uuid):
"""returns the photoinfo JSON struct for a UUID"""
conn = self._conn
@@ -198,6 +211,35 @@ class ExportDB:
except Error as e:
logging.warning(e)
def get_target_for_file(
self, uuid: str, filename: Union[str, pathlib.Path]
) -> Optional[str]:
"""query database for file matching file name and return the matching filename if there is one;
otherwise return None; looks for file.ext, file (1).ext, file (2).ext and so on to find the
actual target name that was used to export filename
Returns: the matching filename or None if no match found
"""
conn = self._conn
c = conn.cursor()
filepath_normalized = self._normalize_filepath_relative(filename)
filepath_stem = os.path.splitext(filepath_normalized)[0]
c.execute(
"SELECT uuid, filepath, filepath_normalized FROM export_data WHERE uuid = ? AND filepath_normalized LIKE ?",
(
uuid,
f"{filepath_stem}%",
),
)
results = c.fetchall()
for result in results:
filepath_normalized = os.path.splitext(result[2])[0]
if re.match(re.escape(filepath_stem) + r"(\s\(\d+\))?$", filepath_normalized):
return os.path.join(self.export_dir, result[1])
return None
def get_previous_uuids(self):
"""returns list of UUIDs of previously exported photos found in export database"""
conn = self._conn
@@ -470,6 +512,10 @@ class ExportDB:
# create report_data table
self._migrate_6_0_to_7_0(conn)
if version[1] < "7.1":
# add timestamp to export_data
self._migrate_7_0_to_7_1(conn)
conn.execute("VACUUM;")
conn.commit()
@@ -687,6 +733,32 @@ class ExportDB:
except Error as e:
logging.warning(e)
def _migrate_7_0_to_7_1(self, conn):
try:
c = conn.cursor()
c.execute("""ALTER TABLE export_data ADD COLUMN timestamp DATETIME;""")
c.execute(
"""
CREATE TRIGGER insert_timestamp_trigger
AFTER INSERT ON export_data
BEGIN
UPDATE export_data SET timestamp = STRFTIME('%Y-%m-%d %H:%M:%f', 'NOW') WHERE id = NEW.id;
END;
"""
)
c.execute(
"""
CREATE TRIGGER update_timestamp_trigger
AFTER UPDATE On export_data
BEGIN
UPDATE export_data SET timestamp = STRFTIME('%Y-%m-%d %H:%M:%f', 'NOW') WHERE id = NEW.id;
END;
"""
)
conn.commit()
except Error as e:
logging.warning(e)
def _perform_db_maintenace(self, conn):
"""Perform database maintenance"""
try:
@@ -1048,6 +1120,21 @@ class ExportRecord:
if not self._context_manager:
conn.commit()
@property
def timestamp(self):
"""returns the timestamp value"""
conn = self._conn
c = conn.cursor()
if row := c.execute(
"SELECT timestamp FROM export_data WHERE filepath_normalized = ?;",
(self._filepath_normalized,),
).fetchone():
return row[0]
raise ValueError(
f"No timestamp found in database for {self._filepath_normalized}"
)
def asdict(self):
"""Return dict of self"""
exifdata = json.loads(self.exifdata) if self.exifdata else None
@@ -1056,6 +1143,7 @@ class ExportRecord:
"filepath": self.filepath,
"filepath_normalized": self.filepath_normalized,
"uuid": self.uuid,
"timestamp": self.timestamp,
"digest": self.digest,
"src_sig": self.src_sig,
"dest_sig": self.dest_sig,

View File

@@ -44,7 +44,13 @@ from .photokit import (
from .phototemplate import RenderOptions
from .rich_utils import add_rich_markup_tag
from .uti import get_preferred_uti_extension
from .utils import hexdigest, increment_filename, lineno, list_directory
from .utils import (
hexdigest,
increment_filename,
increment_filename_with_count,
lineno,
list_directory,
)
__all__ = [
"ExportError",
@@ -488,7 +494,7 @@ class PhotoExporter:
src = staged_files.edited if options.edited else staged_files.original
# get the right destination path depending on options.update, etc.
dest = self._get_dest_path(src, dest, options)
dest = self._get_dest_path(dest, options)
self._render_options.filepath = str(dest)
all_results = ExportResults()
@@ -642,12 +648,11 @@ class PhotoExporter:
return edited_filename
def _get_dest_path(
self, src: str, dest: pathlib.Path, options: ExportOptions
self, dest: pathlib.Path, options: ExportOptions
) -> pathlib.Path:
"""If destination exists find match in ExportDB, on disk, or add (1), (2), and so on to filename to get a valid destination
Args:
src (str): source file path
dest (str): destination path
options (ExportOptions): Export options
@@ -663,6 +668,10 @@ class PhotoExporter:
f"destination exists ({dest}); overwrite={options.overwrite}, increment={options.increment}"
)
# if overwrite, we don't care if the file exists or not
if options.overwrite:
return dest
# if not update or overwrite, check to see if file exists and if so, add (1), (2), etc
# until we find one that works
# Photos checks the stem and adds (1), (2), etc which avoids collision with sidecars
@@ -675,29 +684,36 @@ class PhotoExporter:
return pathlib.Path(increment_filename(dest))
# if update and file exists, need to check to see if it's the right file by checking export db
if (options.update or options.force_update) and dest.exists() and src:
if options.update or options.force_update:
export_db = options.export_db
# destination exists, check to see if destination is the right UUID
dest_uuid = export_db.get_uuid_for_file(dest)
if dest_uuid != self.photo.uuid:
# not the right file, find the right one
# find files that match "dest_name (*.ext" (e.g. "dest_name (1).jpg", "dest_name (2).jpg)", ...)
dest_files = list_directory(
dest.parent,
startswith=f"{dest.stem} (",
endswith=dest.suffix,
include_path=True,
)
for file_ in dest_files:
dest_uuid = export_db.get_uuid_for_file(file_)
if dest_uuid == self.photo.uuid:
dest = pathlib.Path(file_)
break
else:
# increment the destination file
dest = pathlib.Path(increment_filename(dest))
if dest_uuid is None and not dest.exists():
# destination doesn't exist in export db and doesn't exist on disk
# so we can just use it
return dest
# either dest was updated in the if clause above or not updated at all
if dest_uuid == self.photo.uuid:
# destination is the right file
return dest
# either dest_uuid is wrong or file exists and there's no associated UUID, so find a name that matches
# or create a new name if no match
# find files that match "dest_name (*.ext" (e.g. "dest_name (1).jpg", "dest_name (2).jpg)", ...)
# first, find all matching files in export db and see if there's a match
if dest_target := export_db.get_target_for_file(self.photo.uuid, dest):
# there's a match so use that
return pathlib.Path(dest_target)
# no match so need to create a new name
# increment the destination file until we find one that doesn't exist and doesn't match another uuid in the database
count = 0
dest, count = increment_filename_with_count(dest, count)
count += 1
while export_db.get_uuid_for_file(dest) is not None:
dest, count = increment_filename_with_count(dest, count)
return pathlib.Path(dest)
# fail safe...I can't think of a case that gets here
return dest
def _should_update_photo(
@@ -1036,9 +1052,9 @@ class PhotoExporter:
def _export_photo(
self,
src,
dest,
options,
src: str,
dest: pathlib.Path,
options: ExportOptions,
):
"""Helper function for export()
Does the actual copy or hardlink taking the appropriate
@@ -1444,7 +1460,7 @@ class PhotoExporter:
) -> ExportResults:
"""Write exif metadata to src file using exiftool
Caution: This method modifies *src*, not *dest*,
Caution: This method modifies *src*, not *dest*,
so src must be a copy of the original file if you don't want the source modified;
it also does not write to dest (dest is the intended destination for purposes of
referencing the export database. This allows the exiftool update to be done on the

View File

@@ -32,7 +32,7 @@ def detect_text(img_path: str, orientation: Optional[int] = None) -> List:
orientation: optional EXIF orientation (if known, passing orientation may improve quality of results)
"""
if not vision:
logging.warning(f"detect_text not implemented for this version of macOS")
logging.warning("detect_text not implemented for this version of macOS")
return []
with objc.autorelease_pool():
@@ -47,18 +47,18 @@ def detect_text(img_path: str, orientation: Optional[int] = None) -> List:
input_image = Quartz.CIImage.imageWithContentsOfURL_(input_url)
vision_options = NSDictionary.dictionaryWithDictionary_({})
if orientation is not None:
if not 1 <= orientation <= 8:
raise ValueError("orientation must be between 1 and 8")
vision_handler = Vision.VNImageRequestHandler.alloc().initWithCIImage_orientation_options_(
input_image, orientation, vision_options
)
else:
if orientation is None:
vision_handler = (
Vision.VNImageRequestHandler.alloc().initWithCIImage_options_(
input_image, vision_options
)
)
elif 1 <= orientation <= 8:
vision_handler = Vision.VNImageRequestHandler.alloc().initWithCIImage_orientation_options_(
input_image, orientation, vision_options
)
else:
raise ValueError("orientation must be between 1 and 8")
results = []
handler = make_request_handler(results)
vision_request = (

View File

@@ -411,7 +411,7 @@ def normalize_unicode(value):
def increment_filename_with_count(
filepath: Union[str, pathlib.Path], count: int = 0
) -> str:
) -> Tuple[str, int]:
"""Return filename (1).ext, etc if filename.ext exists
If file exists in filename's parent folder with same stem as filename,
@@ -447,6 +447,7 @@ def increment_filename(filepath: Union[str, pathlib.Path]) -> str:
Args:
filepath: str or pathlib.Path; full path, including file name
force: force the file count to increment by at least 1 even if filepath doesn't exist
Returns:
new filepath (or same if not incremented)
@@ -457,6 +458,13 @@ def increment_filename(filepath: Union[str, pathlib.Path]) -> str:
return new_filepath
def extract_increment_count_from_filename(filepath: Union[str, pathlib.Path]) -> int:
"""Extract a count from end of file name if it exists or 0 if not; count takes forms file (1).ext, file (2).ext, etc."""
filepath = str(filepath)
match = re.search(r"(?s:.*)\((\d+)\)", filepath)
return int(match[1]) if match else 0
def expand_and_validate_filepath(path: str) -> str:
"""validate and expand ~ in filepath, also un-escapes spaces

View File

@@ -50,6 +50,7 @@ def test_export_db():
assert db.get_uuid_for_file(filepath) is None
db.create_file_record(filepath, uuid)
assert db.get_uuid_for_file(filepath) == uuid
assert db.get_files_for_uuid(uuid) == [filepath]
record = db.get_file_record(filepath)
assert record.uuid == uuid
@@ -142,6 +143,7 @@ def test_export_db_in_memory():
assert record2.digest == DIGEST_DATA
assert record2.src_sig == (7, 8, 9)
assert record2.dest_sig == (10, 11, 12)
assert dbram.get_files_for_uuid(uuid) == [filepath]
# change some values
record2.photoinfo = INFO_DATA2