Add --add-exported-to-album, # 428

This commit is contained in:
Rhet Turnbull
2021-05-01 21:15:31 -07:00
parent 64379f313e
commit cd8dd552a4
26 changed files with 556 additions and 26 deletions

View File

@@ -1050,6 +1050,36 @@ Options:
run with --cleanup first if you're not
certain.
--add-exported-to-album ALBUM Add all exported photos to album ALBUM in
Photos. Album ALBUM will be created if it
doesn't exist. All exported photos will be
added to this album. This only works if the
Photos library being exported is the last-
opened (default) library in Photos. This
feature is currently experimental. I don't
know how well it will work on large export
sets.
--add-skipped-to-album ALBUM Add all skipped photos to album ALBUM in
Photos. Album ALBUM will be created if it
doesn't exist. All skipped photos will be
added to this album. This only works if the
Photos library being exported is the last-
opened (default) library in Photos. This
feature is currently experimental. I don't
know how well it will work on large export
sets.
--add-missing-to-album ALBUM Add all missing photos to album ALBUM in
Photos. Album ALBUM will be created if it
doesn't exist. All missing photos will be
added to this album. This only works if the
Photos library being exported is the last-
opened (default) library in Photos. This
feature is currently experimental. I don't
know how well it will work on large export
sets.
--exportdb EXPORTDB_FILE Specify alternate name for database file which
stores state information for export and
--update. If --exportdb is not specified,

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: 09f774bbf0a11a7f854d5b240879b3b4
config: 5342827b9d06cfc608d1a286ed0f5c3f
tags: 645f666f9bcd5a90fca523b33c5a78b7

View File

@@ -5,7 +5,7 @@
<head>
<meta charset="utf-8" />
<meta name="viewport" content="width=device-width, initial-scale=1.0" />
<title>Overview: module code &#8212; osxphotos 0.42.13 documentation</title>
<title>Overview: module code &#8212; osxphotos 0.42.14 documentation</title>
<link rel="stylesheet" href="../_static/pygments.css" type="text/css" />
<link rel="stylesheet" href="../_static/alabaster.css" type="text/css" />
<script id="documentation_options" data-url_root="../" src="../_static/documentation_options.js"></script>

View File

@@ -5,7 +5,7 @@
<head>
<meta charset="utf-8" />
<meta name="viewport" content="width=device-width, initial-scale=1.0" />
<title>osxphotos.photosdb.photosdb &#8212; osxphotos 0.42.11 documentation</title>
<title>osxphotos.photosdb.photosdb &#8212; osxphotos 0.42.14 documentation</title>
<link rel="stylesheet" href="../../../_static/pygments.css" type="text/css" />
<link rel="stylesheet" href="../../../_static/alabaster.css" type="text/css" />
<script id="documentation_options" data-url_root="../../../" src="../../../_static/documentation_options.js"></script>

View File

@@ -55,7 +55,7 @@ By default, osxphotos will use the original filename of the photo when exporting
`osxphotos export /path/to/export --filename "{title}"`
The above command will export photos using the title. Note that you don't need to specify the extension as part of the `--filename` template as osxphotos will automatically add the correct fie extension. Some photos might not have a title so in this case, you could use the default value feature to specify a different name for these photos. For example, to use the title as the filename, but if no title is specified, use the original filename instead:
The above command will export photos using the title. Note that you don't need to specify the extension as part of the `--filename` template as osxphotos will automatically add the correct file extension. Some photos might not have a title so in this case, you could use the default value feature to specify a different name for these photos. For example, to use the title as the filename, but if no title is specified, use the original filename instead:
```txt
osxphotos export /path/to/export --filename "{title,{original_name}}"
@@ -315,6 +315,40 @@ Then the next to you run osxphotos, you can simply do this:
The configuration file is a plain text file in [TOML](https://toml.io/en/) format so the `.toml` extension is standard but you can name the file anything you like.
### An example from an actual osxphotos user
Here's a comprehensive use case from an actual osxphotos user that integrates many of the concepts discussed in this tutorial (thank-you Philippe for contributing this!):
I usually import my iPhones photo roll on a more or less regular basis, and it
includes photos and videos. As a result, the size ot my Photos library may rise
very quickly. Nevertheless, I will tag and geolocate everything as Photos has a
quite good keyword management system.
After a while, I want to take most of the videos out of the library and move them
to a separate "videos" folder on a different folder / volume. As I might want to
use them in Final Cut Pro, and since Final Cut is able to import Finder tags into
its internal library tagging system, I will use osxphotos to do just this.
Picking the videos can be left to Photos, using a smart folder for instance. Then
just add a keyword to all videos to be processed. Here I chose "Quik" as I wanted
to spot all videos created on my iPhone using the Quik application (now part of
GoPro).
I want to retrieve my keywords only and make sure they populate the Finder tags, as
well as export all the persons identified in the videos by Photos. I also want to
merge any keywords or persons already in the video metadata with the exported
metadata.
Keeping Photos edited titles and descriptions and putting both in the Finder
comments field in a readable manner is also enabled.
And I want to keep the files creation date (using `--touch-file`).
Finally, use `--strip` to remove any leading or trailing whitespace from processed
template fields.
`osxphotos export ~/Desktop/folder for exported videos/ --keyword Quik --only-movies --db /path to my.photoslibrary --touch-file --finder-tag-keywords --person-keyword --xattr-template findercomment "{title}{title?{descr?{newline},},}{descr}" --exiftool-merge-keywords --exiftool-merge-persons --exiftool --strip`
### Conclusion
osxphotos is very flexible. If you merely want to backup your Photos library, then spending a few minutes to understand the `--directory` option is likely all you need and you can be up and running in minutes. However, if you have a more complex workflow, osxphotos likely provides options to implement your workflow. This tutorial does not attempt to cover every option offered by osxphotos but hopefully it provides a good understanding of what kinds of things are possible and where to explore if you want to learn more.

View File

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

View File

@@ -5,7 +5,7 @@
<head>
<meta charset="utf-8" />
<meta name="viewport" content="width=device-width, initial-scale=1.0" />
<title>osxphotos command line interface (CLI) &#8212; osxphotos 0.42.13 documentation</title>
<title>osxphotos command line interface (CLI) &#8212; osxphotos 0.42.14 documentation</title>
<link rel="stylesheet" href="_static/pygments.css" type="text/css" />
<link rel="stylesheet" href="_static/alabaster.css" type="text/css" />
<script id="documentation_options" data-url_root="./" src="_static/documentation_options.js"></script>
@@ -813,6 +813,24 @@ to modify this behavior.</p>
<dd><p>Cleanup export directory by deleting any files which were not included in this export set. For example, photos which had previously been exported and were subsequently deleted in Photos. WARNING: cleanup will delete <em>any</em> files in the export directory that were not exported by osxphotos, for example, your own scripts or other files. Be sure this is what you intend before using cleanup. Use dry-run with cleanup first if youre not certain.</p>
</dd></dl>
<dl class="std option">
<dt id="cmdoption-osxphotos-export-add-exported-to-album">
<code class="sig-name descname"><span class="pre">--add-exported-to-album</span></code><code class="sig-prename descclassname"> <span class="pre">&lt;ALBUM&gt;</span></code><a class="headerlink" href="#cmdoption-osxphotos-export-add-exported-to-album" title="Permalink to this definition"></a></dt>
<dd><p>Add all exported photos to album ALBUM in Photos. Album ALBUM will be created if it doesnt exist. All exported photos will be added to this album. This only works if the Photos library being exported is the last-opened (default) library in Photos. This feature is currently experimental. I dont know how well it will work on large export sets.</p>
</dd></dl>
<dl class="std option">
<dt id="cmdoption-osxphotos-export-add-skipped-to-album">
<code class="sig-name descname"><span class="pre">--add-skipped-to-album</span></code><code class="sig-prename descclassname"> <span class="pre">&lt;ALBUM&gt;</span></code><a class="headerlink" href="#cmdoption-osxphotos-export-add-skipped-to-album" title="Permalink to this definition"></a></dt>
<dd><p>Add all skipped photos to album ALBUM in Photos. Album ALBUM will be created if it doesnt exist. All skipped photos will be added to this album. This only works if the Photos library being exported is the last-opened (default) library in Photos. This feature is currently experimental. I dont know how well it will work on large export sets.</p>
</dd></dl>
<dl class="std option">
<dt id="cmdoption-osxphotos-export-add-missing-to-album">
<code class="sig-name descname"><span class="pre">--add-missing-to-album</span></code><code class="sig-prename descclassname"> <span class="pre">&lt;ALBUM&gt;</span></code><a class="headerlink" href="#cmdoption-osxphotos-export-add-missing-to-album" title="Permalink to this definition"></a></dt>
<dd><p>Add all missing photos to album ALBUM in Photos. Album ALBUM will be created if it doesnt exist. All missing photos will be added to this album. This only works if the Photos library being exported is the last-opened (default) library in Photos. This feature is currently experimental. I dont know how well it will work on large export sets.</p>
</dd></dl>
<dl class="std option">
<dt id="cmdoption-osxphotos-export-exportdb">
<code class="sig-name descname"><span class="pre">--exportdb</span></code><code class="sig-prename descclassname"> <span class="pre">&lt;EXPORTDB_FILE&gt;</span></code><a class="headerlink" href="#cmdoption-osxphotos-export-exportdb" title="Permalink to this definition"></a></dt>

View File

@@ -5,7 +5,7 @@
<head>
<meta charset="utf-8" />
<meta name="viewport" content="width=device-width, initial-scale=1.0" />
<title>Index &#8212; osxphotos 0.42.13 documentation</title>
<title>Index &#8212; osxphotos 0.42.14 documentation</title>
<link rel="stylesheet" href="_static/pygments.css" type="text/css" />
<link rel="stylesheet" href="_static/alabaster.css" type="text/css" />
<script id="documentation_options" data-url_root="./" src="_static/documentation_options.js"></script>
@@ -65,6 +65,27 @@
<table style="width: 100%" class="indextable genindextable"><tr>
<td style="width: 33%; vertical-align: top;"><ul>
<li>
--add-exported-to-album &lt;ALBUM&gt;
<ul>
<li><a href="cli.html#cmdoption-osxphotos-export-add-exported-to-album">osxphotos-export command line option</a>
</li>
</ul></li>
<li>
--add-missing-to-album &lt;ALBUM&gt;
<ul>
<li><a href="cli.html#cmdoption-osxphotos-export-add-missing-to-album">osxphotos-export command line option</a>
</li>
</ul></li>
<li>
--add-skipped-to-album &lt;ALBUM&gt;
<ul>
<li><a href="cli.html#cmdoption-osxphotos-export-add-skipped-to-album">osxphotos-export command line option</a>
</li>
</ul></li>
<li>
--album &lt;ALBUM&gt;
<ul>
@@ -557,6 +578,8 @@
<li><a href="cli.html#cmdoption-osxphotos-query-no-comment">osxphotos-query command line option</a>
</li>
</ul></li>
</ul></td>
<td style="width: 33%; vertical-align: top;"><ul>
<li>
--no-description
@@ -566,8 +589,6 @@
<li><a href="cli.html#cmdoption-osxphotos-query-no-description">osxphotos-query command line option</a>
</li>
</ul></li>
</ul></td>
<td style="width: 33%; vertical-align: top;"><ul>
<li>
--no-likes
@@ -1490,6 +1511,12 @@
osxphotos-export command line option
<ul>
<li><a href="cli.html#cmdoption-osxphotos-export-add-exported-to-album">--add-exported-to-album &lt;ALBUM&gt;</a>
</li>
<li><a href="cli.html#cmdoption-osxphotos-export-add-missing-to-album">--add-missing-to-album &lt;ALBUM&gt;</a>
</li>
<li><a href="cli.html#cmdoption-osxphotos-export-add-skipped-to-album">--add-skipped-to-album &lt;ALBUM&gt;</a>
</li>
<li><a href="cli.html#cmdoption-osxphotos-export-album">--album &lt;ALBUM&gt;</a>
</li>
<li><a href="cli.html#cmdoption-osxphotos-export-album-keyword">--album-keyword</a>

View File

@@ -5,7 +5,7 @@
<head>
<meta charset="utf-8" />
<meta name="viewport" content="width=device-width, initial-scale=1.0" />
<title>Welcome to osxphotoss documentation! &#8212; osxphotos 0.42.13 documentation</title>
<title>Welcome to osxphotoss documentation! &#8212; osxphotos 0.42.14 documentation</title>
<link rel="stylesheet" href="_static/pygments.css" type="text/css" />
<link rel="stylesheet" href="_static/alabaster.css" type="text/css" />
<script id="documentation_options" data-url_root="./" src="_static/documentation_options.js"></script>

View File

@@ -5,7 +5,7 @@
<head>
<meta charset="utf-8" />
<meta name="viewport" content="width=device-width, initial-scale=1.0" />
<title>osxphotos &#8212; osxphotos 0.42.13 documentation</title>
<title>osxphotos &#8212; osxphotos 0.42.14 documentation</title>
<link rel="stylesheet" href="_static/pygments.css" type="text/css" />
<link rel="stylesheet" href="_static/alabaster.css" type="text/css" />
<script id="documentation_options" data-url_root="./" src="_static/documentation_options.js"></script>

Binary file not shown.

Binary file not shown.

File diff suppressed because one or more lines are too long

View File

@@ -5,7 +5,7 @@
<head>
<meta charset="utf-8" />
<meta name="viewport" content="width=device-width, initial-scale=1.0" />
<title>Search &#8212; osxphotos 0.42.13 documentation</title>
<title>Search &#8212; osxphotos 0.42.14 documentation</title>
<link rel="stylesheet" href="_static/pygments.css" type="text/css" />
<link rel="stylesheet" href="_static/alabaster.css" type="text/css" />

File diff suppressed because one or more lines are too long

View File

@@ -5,7 +5,7 @@
<head>
<meta charset="utf-8" />
<meta name="viewport" content="width=device-width, initial-scale=1.0" />
<title>Export your photos &#8212; osxphotos 0.42.13 documentation</title>
<title>Export your photos &#8212; osxphotos 0.42.14 documentation</title>
<link rel="stylesheet" href="_static/pygments.css" type="text/css" />
<link rel="stylesheet" href="_static/alabaster.css" type="text/css" />
<script id="documentation_options" data-url_root="./" src="_static/documentation_options.js"></script>
@@ -69,7 +69,7 @@
<h1>Specify exported filename<a class="headerlink" href="#specify-exported-filename" title="Permalink to this headline"></a></h1>
<p>By default, osxphotos will use the original filename of the photo when exporting. That is, the filename the photo had when it was taken or imported into Photos. This is often something like <code class="docutils literal notranslate"><span class="pre">IMG_1234.JPG</span></code> or <code class="docutils literal notranslate"><span class="pre">DSC05678.dng</span></code>. osxphotos allows you to specify a custom filename template using the <code class="docutils literal notranslate"><span class="pre">--filename</span></code> option in the same way as <code class="docutils literal notranslate"><span class="pre">--directory</span></code> allows you to specify a custom directory name. For example, Photos allows you specify a title or caption for a photo and you can use this in place of the original filename:</p>
<p><code class="docutils literal notranslate"><span class="pre">osxphotos</span> <span class="pre">export</span> <span class="pre">/path/to/export</span> <span class="pre">--filename</span> <span class="pre">&quot;{title}&quot;</span></code></p>
<p>The above command will export photos using the title. Note that you dont need to specify the extension as part of the <code class="docutils literal notranslate"><span class="pre">--filename</span></code> template as osxphotos will automatically add the correct fie extension. Some photos might not have a title so in this case, you could use the default value feature to specify a different name for these photos. For example, to use the title as the filename, but if no title is specified, use the original filename instead:</p>
<p>The above command will export photos using the title. Note that you dont need to specify the extension as part of the <code class="docutils literal notranslate"><span class="pre">--filename</span></code> template as osxphotos will automatically add the correct file extension. Some photos might not have a title so in this case, you could use the default value feature to specify a different name for these photos. For example, to use the title as the filename, but if no title is specified, use the original filename instead:</p>
<div class="highlight-txt notranslate"><div class="highlight"><pre><span></span>osxphotos export /path/to/export --filename &quot;{title,{original_name}}&quot;
│ ││ │
│ ││ │
@@ -258,6 +258,40 @@
<p><code class="docutils literal notranslate"><span class="pre">osxphotos</span> <span class="pre">export</span> <span class="pre">/path/to/export</span> <span class="pre">--load-config</span> <span class="pre">osxphotos.toml</span></code></p>
<p>The configuration file is a plain text file in <a class="reference external" href="https://toml.io/en/">TOML</a> format so the <code class="docutils literal notranslate"><span class="pre">.toml</span></code> extension is standard but you can name the file anything you like.</p>
</div>
<div class="section" id="an-example-from-an-actual-osxphotos-user">
<h1>An example from an actual osxphotos user<a class="headerlink" href="#an-example-from-an-actual-osxphotos-user" title="Permalink to this headline"></a></h1>
<p>Heres a comprehensive use case from an actual osxphotos user that integrates many of the concepts discussed in this tutorial (thank-you Philippe for contributing this!):</p>
<div class="highlight-default notranslate"><div class="highlight"><pre><span></span>I usually import my iPhones photo roll on a more or less regular basis, and it
includes photos and videos. As a result, the size ot my Photos library may rise
very quickly. Nevertheless, I will tag and geolocate everything as Photos has a
quite good keyword management system.
After a while, I want to take most of the videos out of the library and move them
to a separate &quot;videos&quot; folder on a different folder / volume. As I might want to
use them in Final Cut Pro, and since Final Cut is able to import Finder tags into
its internal library tagging system, I will use osxphotos to do just this.
Picking the videos can be left to Photos, using a smart folder for instance. Then
just add a keyword to all videos to be processed. Here I chose &quot;Quik&quot; as I wanted
to spot all videos created on my iPhone using the Quik application (now part of
GoPro).
I want to retrieve my keywords only and make sure they populate the Finder tags, as
well as export all the persons identified in the videos by Photos. I also want to
merge any keywords or persons already in the video metadata with the exported
metadata.
Keeping Photos edited titles and descriptions and putting both in the Finder
comments field in a readable manner is also enabled.
And I want to keep the files creation date (using `--touch-file`).
Finally, use `--strip` to remove any leading or trailing whitespace from processed
template fields.
</pre></div>
</div>
<p><code class="docutils literal notranslate"><span class="pre">osxphotos</span> <span class="pre">export</span> <span class="pre">~/Desktop/folder</span> <span class="pre">for</span> <span class="pre">exported</span> <span class="pre">videos/</span> <span class="pre">--keyword</span> <span class="pre">Quik</span> <span class="pre">--only-movies</span> <span class="pre">--db</span> <span class="pre">/path</span> <span class="pre">to</span> <span class="pre">my.photoslibrary</span> <span class="pre">--touch-file</span> <span class="pre">--finder-tag-keywords</span> <span class="pre">--person-keyword</span> <span class="pre">--xattr-template</span> <span class="pre">findercomment</span> <span class="pre">&quot;{title}{title?{descr?{newline},},}{descr}&quot;</span> <span class="pre">--exiftool-merge-keywords</span> <span class="pre">--exiftool-merge-persons</span> <span class="pre">--exiftool</span> <span class="pre">--strip</span></code></p>
</div>
<div class="section" id="conclusion">
<h1>Conclusion<a class="headerlink" href="#conclusion" title="Permalink to this headline"></a></h1>
<p>osxphotos is very flexible. If you merely want to backup your Photos library, then spending a few minutes to understand the <code class="docutils literal notranslate"><span class="pre">--directory</span></code> option is likely all you need and you can be up and running in minutes. However, if you have a more complex workflow, osxphotos likely provides options to implement your workflow. This tutorial does not attempt to cover every option offered by osxphotos but hopefully it provides a good understanding of what kinds of things are possible and where to explore if you want to learn more.</p>

View File

@@ -1,3 +1,3 @@
""" version info """
__version__ = "0.42.13"
__version__ = "0.42.14"

View File

@@ -14,6 +14,7 @@ import unicodedata
import bitmath
import click
import osxmetadata
import photoscript
import yaml
import osxphotos
@@ -53,6 +54,7 @@ from .photoinfo import ExportResults
from .photokit import check_photokit_authorization, request_photokit_authorization
from .queryoptions import QueryOptions
from .utils import get_preferred_uti_extension
from .photosalbum import PhotosAlbum
# global variable to control verbose output
# set via --verbose/-V
@@ -878,6 +880,30 @@ def cli(ctx, db, json_, debug):
"for example, your own scripts or other files. Be sure this is what you intend before using "
"--cleanup. Use --dry-run with --cleanup first if you're not certain.",
)
@click.option(
"--add-exported-to-album",
metavar="ALBUM",
help="Add all exported photos to album ALBUM in Photos. Album ALBUM will be created "
"if it doesn't exist. All exported photos will be added to this album. "
"This only works if the Photos library being exported is the last-opened (default) library in Photos. "
"This feature is currently experimental. I don't know how well it will work on large export sets.",
)
@click.option(
"--add-skipped-to-album",
metavar="ALBUM",
help="Add all skipped photos to album ALBUM in Photos. Album ALBUM will be created "
"if it doesn't exist. All skipped photos will be added to this album. "
"This only works if the Photos library being exported is the last-opened (default) library in Photos. "
"This feature is currently experimental. I don't know how well it will work on large export sets.",
)
@click.option(
"--add-missing-to-album",
metavar="ALBUM",
help="Add all missing photos to album ALBUM in Photos. Album ALBUM will be created "
"if it doesn't exist. All missing photos will be added to this album. "
"This only works if the Photos library being exported is the last-opened (default) library in Photos. "
"This feature is currently experimental. I don't know how well it will work on large export sets.",
)
@click.option(
"--exportdb",
metavar="EXPORTDB_FILE",
@@ -1027,6 +1053,9 @@ def export(
use_photokit,
report,
cleanup,
add_exported_to_album,
add_skipped_to_album,
add_missing_to_album,
exportdb,
load_config,
save_config,
@@ -1180,6 +1209,9 @@ def export(
use_photokit = cfg.use_photokit
report = cfg.report
cleanup = cfg.cleanup
add_exported_to_album = cfg.add_exported_to_album
add_skipped_to_album = cfg.add_skipped_to_album
add_missing_to_album = cfg.add_missing_to_album
exportdb = cfg.exportdb
beta = cfg.beta
only_new = cfg.only_new
@@ -1524,6 +1556,24 @@ def export(
original_name = not current_name
results = ExportResults()
# set up for --add-export-to-album if needed
album_export = (
PhotosAlbum(add_exported_to_album, verbose=verbose_)
if add_exported_to_album
else None
)
album_skipped = (
PhotosAlbum(add_skipped_to_album, verbose=verbose_)
if add_skipped_to_album
else None
)
album_missing = (
PhotosAlbum(add_missing_to_album, verbose=verbose_)
if add_missing_to_album
else None
)
# send progress bar output to /dev/null if verbose to hide the progress bar
fp = open(os.devnull, "w") if verbose else None
with click.progressbar(photos, file=fp) as bar:
@@ -1571,6 +1621,46 @@ def export(
replace_keywords=replace_keywords,
retry=retry,
)
if album_export and export_results.exported:
try:
album_export.add(p)
export_results.exported_album = [
(filename, album_export.name)
for filename in export_results.exported
]
except Exception as e:
click.echo(
f"Error adding photo {p.original_filename} ({p.uuid}) to album {album_export.name}: {e}",
err=True,
)
if album_skipped and export_results.skipped:
try:
album_skipped.add(p)
export_results.skipped_album = [
(filename, album_skipped.name)
for filename in export_results.skipped
]
except Exception as e:
click.echo(
f"Error adding photo {p.original_filename} ({p.uuid}) to album {album_skipped.name}: {e}",
err=True,
)
if album_missing and export_results.missing:
try:
album_missing.add(p)
export_results.missing_album = [
(filename, album_missing.name)
for filename in export_results.missing
]
except Exception as e:
click.echo(
f"Error adding photo {p.original_filename} ({p.uuid}) to album {album_missing.name}: {e}",
err=True,
)
results += export_results
# all photo files (not including sidecars) that are part of this export set
@@ -2784,7 +2874,6 @@ def write_export_report(report_file, results):
"""
# Collect results for reporting
# TODO: pull this in a separate write_report function
all_results = {
result: {
"filename": result,
@@ -2806,6 +2895,7 @@ def write_export_report(report_file, results):
"extended_attributes_skipped": 0,
"cleanup_deleted_file": 0,
"cleanup_deleted_directory": 0,
"exported_album": "",
}
for result in results.all_files()
+ results.deleted_files
@@ -2881,6 +2971,9 @@ def write_export_report(report_file, results):
for result in results.deleted_directories:
all_results[result]["cleanup_deleted_directory"] = 1
for result, album in results.exported_album:
all_results[result]["exported_album"] = album
report_columns = [
"filename",
"exported",
@@ -2901,6 +2994,7 @@ def write_export_report(report_file, results):
"extended_attributes_skipped",
"cleanup_deleted_file",
"cleanup_deleted_directory",
"exported_album",
]
try:

View File

@@ -89,6 +89,9 @@ class ExportResults:
xattr_skipped=None,
deleted_files=None,
deleted_directories=None,
exported_album=None,
skipped_album=None,
missing_album=None,
):
self.exported = exported or []
self.new = new or []
@@ -111,6 +114,9 @@ class ExportResults:
self.xattr_skipped = xattr_skipped or []
self.deleted_files = deleted_files or []
self.deleted_directories = deleted_directories or []
self.exported_album = exported_album or []
self.skipped_album = skipped_album or []
self.missing_album = missing_album or []
def all_files(self):
""" return all filenames contained in results """
@@ -157,6 +163,10 @@ class ExportResults:
self.exiftool_error += other.exiftool_error
self.deleted_files += other.deleted_files
self.deleted_directories += other.deleted_directories
self.exported_album += other.exported_album
self.skipped_album += other.skipped_album
self.missing_album += other.missing_album
return self
def __str__(self):
@@ -181,6 +191,9 @@ class ExportResults:
+ f",exiftool_error={self.exiftool_error}"
+ f",deleted_files={self.deleted_files}"
+ f",deleted_directories={self.deleted_directories}"
+ f",exported_album={self.exported_album}"
+ f",skipped_album={self.skipped_album}"
+ f",missing_album={self.missing_album}"
+ ")"
)
@@ -621,7 +634,11 @@ def export2(
)
edited_name = pathlib.Path(self.path_edited).name
edited_suffix = pathlib.Path(edited_name).suffix
fname = pathlib.Path(self.original_filename).stem + edited_identifier + edited_suffix
fname = (
pathlib.Path(self.original_filename).stem
+ edited_identifier
+ edited_suffix
)
else:
fname = self.original_filename
@@ -1654,7 +1671,7 @@ def _exiftool_dict(
exif["QuickTime:ModifyDate"] = datetime_tz_to_utc(
self.date_modified
).strftime("%Y:%m:%d %H:%M:%S")
# remove any new lines in any fields
for field, val in exif.items():
if type(val) == str:

74
osxphotos/photosalbum.py Normal file
View File

@@ -0,0 +1,74 @@
""" PhotosAlbum class to create an album in default Photos library and add photos to it """
from typing import Optional
import photoscript
from .photoinfo import PhotoInfo
from .utils import noop
class PhotosAlbum:
def __init__(self, name: str, verbose: Optional[callable] = None):
self.name = name
self.verbose = verbose or noop
self.library = photoscript.PhotosLibrary()
album = self.library.album(name)
if album is None:
self.verbose(f"Creating Photos album '{self.name}'")
album = self.library.create_album(name)
self.album = album
def add(self, photo: PhotoInfo):
photo_ = photoscript.Photo(photo.uuid)
self.album.add([photo_])
self.verbose(
f"Added {photo.original_filename} ({photo.uuid}) to album {self.name}"
)
def photos(self):
return self.album.photos()
# def add_photo_to_album(photo, album_pairs, results):
# # todo: class PhotoAlbum
# # keeps a name, maintains state
# """ add photo to album(s) as defined in album_pairs
# Args:
# photo: PhotoInfo object
# album_pairs: list of tuples with [(album name, results_list)]
# results: ExportResults object
# Returns:
# updated ExportResults object
# """
# for album, result_list in album_pairs:
# try:
# if album_export is None:
# # first time fetching the album, see if it exists already
# album_export = photos_library.album(
# add_exported_to_album
# )
# if album_export is None:
# # album doesn't exist, so create it
# verbose_(
# f"Creating Photos album '{add_exported_to_album}'"
# )
# album_export = photos_library.create_album(
# add_exported_to_album
# )
# exported_photo = photoscript.Photo(p.uuid)
# album_export.add([exported_photo])
# verbose_(
# f"Added {p.original_filename} ({p.uuid}) to album {add_exported_to_album}"
# )
# exported_album = [
# (filename, add_exported_to_album)
# for filename in export_results.exported
# ]
# export_results.exported_album = exported_album
# if
# except Exception as e:
# click.echo(
# f"Error adding photo {p.original_filename} ({p.uuid}) to album {add_exported_to_album}"
# )

View File

@@ -48,7 +48,7 @@ parso==0.6.2
pathspec==0.7.0
pathvalidate==2.2.1
pexpect==4.8.0
photoscript==0.1.0
photoscript==0.1.2
pickleshare==0.7.5
Pillow==8.1.1
pkginfo==1.5.0.1

View File

@@ -81,7 +81,7 @@ setup(
"pathvalidate==2.2.1",
"dataclasses==0.7;python_version<'3.7'",
"wurlitzer>=2.0.1",
"photoscript>=0.1.0",
"photoscript>=0.1.2",
"toml>=0.10.0",
"osxmetadata>=0.99.13",
"textx==2.3.0",

View File

@@ -1,9 +1,116 @@
""" pytest test configuration """
import os
import pathlib
import pytest
from applescript import AppleScript
from photoscript.utils import ditto
from osxphotos.exiftool import _ExifToolProc
def get_os_version():
import platform
# returns tuple containing OS version
# e.g. 10.13.6 = (10, 13, 6)
version = platform.mac_ver()[0].split(".")
if len(version) == 2:
(ver, major) = version
minor = "0"
elif len(version) == 3:
(ver, major, minor) = version
else:
raise (
ValueError(
f"Could not parse version string: {platform.mac_ver()} {version}"
)
)
return (ver, major, minor)
OS_VER = get_os_version()[1]
if OS_VER == "15":
TEST_LIBRARY = "tests/Test-10.15.7.photoslibrary"
else:
TEST_LIBRARY = None
pytest.exit("This test suite currently only runs on MacOS Catalina ")
@pytest.fixture(autouse=True)
def reset_singletons():
""" Need to clean up any ExifTool singletons between tests """
_ExifToolProc.instance = None
_ExifToolProc.instance = None
def pytest_addoption(parser):
parser.addoption(
"--addalbum",
action="store_true",
default=False,
help="run --add-exported-to-album tests",
)
def pytest_configure(config):
config.addinivalue_line(
"markers", "addalbum: mark test as requiring --addalbum to run"
)
def pytest_collection_modifyitems(config, items):
if config.getoption("--addalbum"):
# --addalbum given in cli: do not skip addalbum tests (these require interactive test)
return
skip_addalbum = pytest.mark.skip(reason="need --addalbum option to run")
for item in items:
if "addalbum" in item.keywords:
item.add_marker(skip_addalbum)
def copy_photos_library(photos_library=TEST_LIBRARY, delay=0):
""" copy the test library and open Photos, returns path to copied library """
script = AppleScript(
"""
tell application "Photos"
quit
end tell
"""
)
script.run()
src = pathlib.Path(os.getcwd()) / photos_library
picture_folder = (
pathlib.Path(os.environ["PHOTOSCRIPT_PICTURES_FOLDER"])
if "PHOTOSCRIPT_PICTURES_FOLDER" in os.environ
else pathlib.Path("~/Pictures")
)
picture_folder = picture_folder.expanduser()
if not picture_folder.is_dir():
pytest.exit(f"Invalid picture folder: '{picture_folder}'")
dest = picture_folder / photos_library
ditto(src, dest)
script = AppleScript(
f"""
set tries to 0
repeat while tries < 5
try
tell application "Photos"
activate
delay 3
open POSIX file "{dest}"
delay {delay}
end tell
set tries to 5
on error
set tries to tries + 1
end try
end repeat
"""
)
script.run()
return dest
@pytest.fixture
def addalbum_library():
copy_photos_library(delay=10)

File diff suppressed because one or more lines are too long

View File

@@ -0,0 +1,92 @@
""" Test --add-exported-to-album """
import pytest
import os
from click.testing import CliRunner
import photoscript
UUID_EXPORT = {"3DD2C897-F19E-4CA6-8C22-B027D5A71907": {"filename": "IMG_4547.jpg"}}
UUID_MISSING = {
"8E1D7BC9-9321-44F9-8CFB-4083F6B9232A": {"filename": "IMG_2000.JPGssss"}
}
@pytest.mark.addalbum
def test_export_add_to_album(addalbum_library):
from osxphotos.cli import export
runner = CliRunner()
cwd = os.getcwd()
with runner.isolated_filesystem():
EXPORT_ALBUM = "OSXPhotos Export"
SKIP_ALBUM = "OSXPhotos Skipped"
MISSING_ALBUM = "OSXPhotos Missing"
uuid_opt = [f"--uuid={uuid}" for uuid in UUID_EXPORT]
uuid_opt += [f"--uuid={uuid}" for uuid in UUID_MISSING]
result = runner.invoke(
export,
[
".",
"-V",
"--add-exported-to-album",
EXPORT_ALBUM,
"--add-skipped-to-album",
SKIP_ALBUM,
*uuid_opt,
],
)
assert result.exit_code == 0
assert f"Creating Photos album '{EXPORT_ALBUM}'" in result.output
assert f"Creating Photos album '{SKIP_ALBUM}'" in result.output
photoslib = photoscript.PhotosLibrary()
album = photoslib.album(EXPORT_ALBUM)
assert album is not None
assert len(album) == len(UUID_EXPORT)
got_uuids = [p.uuid for p in album.photos()]
assert sorted(got_uuids) == sorted(list(UUID_EXPORT.keys()))
skip_album = photoslib.album(SKIP_ALBUM)
assert skip_album is not None
assert len(skip_album) == 0
result = runner.invoke(
export,
[
".",
"-V",
"--add-exported-to-album",
EXPORT_ALBUM,
"--add-skipped-to-album",
SKIP_ALBUM,
"--add-missing-to-album",
MISSING_ALBUM,
"--update",
*uuid_opt,
],
)
assert result.exit_code == 0
assert f"Creating Photos album '{EXPORT_ALBUM}'" not in result.output
assert f"Creating Photos album '{SKIP_ALBUM}'" not in result.output
assert f"Creating Photos album '{MISSING_ALBUM}'" in result.output
photoslib = photoscript.PhotosLibrary()
export_album = photoslib.album(EXPORT_ALBUM)
assert export_album is not None
assert len(export_album) == len(UUID_EXPORT)
skip_album = photoslib.album(SKIP_ALBUM)
assert skip_album is not None
assert len(skip_album) == len(UUID_EXPORT)
got_uuids = [p.uuid for p in skip_album.photos()]
assert sorted(got_uuids) == sorted(list(UUID_EXPORT.keys()))
missing_album = photoslib.album(MISSING_ALBUM)
assert missing_album is not None
assert len(missing_album) == len(UUID_MISSING)
got_uuids = [p.uuid for p in missing_album.photos()]
assert sorted(got_uuids) == sorted(list(UUID_MISSING.keys()))

View File

@@ -47,6 +47,9 @@ def test_exportresults_init():
assert results.exiftool_error == []
assert results.deleted_files == []
assert results.deleted_directories == []
assert results.exported_album == []
assert results.skipped_album == []
assert results.missing_album == []
def test_exportresults_iadd():
@@ -110,6 +113,6 @@ def test_str():
results = ExportResults()
assert (
str(results)
== "ExportResults(exported=[],new=[],updated=[],skipped=[],exif_updated=[],touched=[],converted_to_jpeg=[],sidecar_json_written=[],sidecar_json_skipped=[],sidecar_exiftool_written=[],sidecar_exiftool_skipped=[],sidecar_xmp_written=[],sidecar_xmp_skipped=[],missing=[],error=[],exiftool_warning=[],exiftool_error=[],deleted_files=[],deleted_directories=[])"
== "ExportResults(exported=[],new=[],updated=[],skipped=[],exif_updated=[],touched=[],converted_to_jpeg=[],sidecar_json_written=[],sidecar_json_skipped=[],sidecar_exiftool_written=[],sidecar_exiftool_skipped=[],sidecar_xmp_written=[],sidecar_xmp_skipped=[],missing=[],error=[],exiftool_warning=[],exiftool_error=[],deleted_files=[],deleted_directories=[],exported_album=[],skipped_album=[],missing_album=[])"
)