Add --add-exported-to-album, # 428
This commit is contained in:
30
README.md
30
README.md
@@ -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,
|
||||
|
||||
@@ -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
|
||||
|
||||
@@ -5,7 +5,7 @@
|
||||
<head>
|
||||
<meta charset="utf-8" />
|
||||
<meta name="viewport" content="width=device-width, initial-scale=1.0" />
|
||||
<title>Overview: module code — osxphotos 0.42.13 documentation</title>
|
||||
<title>Overview: module code — 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>
|
||||
|
||||
@@ -5,7 +5,7 @@
|
||||
<head>
|
||||
<meta charset="utf-8" />
|
||||
<meta name="viewport" content="width=device-width, initial-scale=1.0" />
|
||||
<title>osxphotos.photosdb.photosdb — osxphotos 0.42.11 documentation</title>
|
||||
<title>osxphotos.photosdb.photosdb — 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>
|
||||
|
||||
@@ -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 iPhone’s 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 Photo’s 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 file’s 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.
|
||||
2
docs/_static/documentation_options.js
vendored
2
docs/_static/documentation_options.js
vendored
@@ -1,6 +1,6 @@
|
||||
var DOCUMENTATION_OPTIONS = {
|
||||
URL_ROOT: document.getElementById("documentation_options").getAttribute('data-url_root'),
|
||||
VERSION: '0.42.13',
|
||||
VERSION: '0.42.14',
|
||||
LANGUAGE: 'None',
|
||||
COLLAPSE_INDEX: false,
|
||||
BUILDER: 'html',
|
||||
|
||||
@@ -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) — osxphotos 0.42.13 documentation</title>
|
||||
<title>osxphotos command line interface (CLI) — 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 you’re 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"><ALBUM></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 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.</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"><ALBUM></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 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.</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"><ALBUM></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 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.</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"><EXPORTDB_FILE></span></code><a class="headerlink" href="#cmdoption-osxphotos-export-exportdb" title="Permalink to this definition">¶</a></dt>
|
||||
|
||||
@@ -5,7 +5,7 @@
|
||||
<head>
|
||||
<meta charset="utf-8" />
|
||||
<meta name="viewport" content="width=device-width, initial-scale=1.0" />
|
||||
<title>Index — osxphotos 0.42.13 documentation</title>
|
||||
<title>Index — 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 <ALBUM>
|
||||
|
||||
<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 <ALBUM>
|
||||
|
||||
<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 <ALBUM>
|
||||
|
||||
<ul>
|
||||
<li><a href="cli.html#cmdoption-osxphotos-export-add-skipped-to-album">osxphotos-export command line option</a>
|
||||
</li>
|
||||
</ul></li>
|
||||
<li>
|
||||
--album <ALBUM>
|
||||
|
||||
<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 <ALBUM></a>
|
||||
</li>
|
||||
<li><a href="cli.html#cmdoption-osxphotos-export-add-missing-to-album">--add-missing-to-album <ALBUM></a>
|
||||
</li>
|
||||
<li><a href="cli.html#cmdoption-osxphotos-export-add-skipped-to-album">--add-skipped-to-album <ALBUM></a>
|
||||
</li>
|
||||
<li><a href="cli.html#cmdoption-osxphotos-export-album">--album <ALBUM></a>
|
||||
</li>
|
||||
<li><a href="cli.html#cmdoption-osxphotos-export-album-keyword">--album-keyword</a>
|
||||
|
||||
@@ -5,7 +5,7 @@
|
||||
<head>
|
||||
<meta charset="utf-8" />
|
||||
<meta name="viewport" content="width=device-width, initial-scale=1.0" />
|
||||
<title>Welcome to osxphotos’s documentation! — osxphotos 0.42.13 documentation</title>
|
||||
<title>Welcome to osxphotos’s documentation! — 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>
|
||||
|
||||
@@ -5,7 +5,7 @@
|
||||
<head>
|
||||
<meta charset="utf-8" />
|
||||
<meta name="viewport" content="width=device-width, initial-scale=1.0" />
|
||||
<title>osxphotos — osxphotos 0.42.13 documentation</title>
|
||||
<title>osxphotos — 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>
|
||||
|
||||
BIN
docs/objects.inv
BIN
docs/objects.inv
Binary file not shown.
Binary file not shown.
File diff suppressed because one or more lines are too long
@@ -5,7 +5,7 @@
|
||||
<head>
|
||||
<meta charset="utf-8" />
|
||||
<meta name="viewport" content="width=device-width, initial-scale=1.0" />
|
||||
<title>Search — osxphotos 0.42.13 documentation</title>
|
||||
<title>Search — 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
@@ -5,7 +5,7 @@
|
||||
<head>
|
||||
<meta charset="utf-8" />
|
||||
<meta name="viewport" content="width=device-width, initial-scale=1.0" />
|
||||
<title>Export your photos — osxphotos 0.42.13 documentation</title>
|
||||
<title>Export your photos — 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">"{title}"</span></code></p>
|
||||
<p>The above command will export photos using the title. Note that you don’t 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 don’t 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 "{title,{original_name}}"
|
||||
│ ││ │
|
||||
│ ││ │
|
||||
@@ -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>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!):</p>
|
||||
<div class="highlight-default notranslate"><div class="highlight"><pre><span></span>I usually import my iPhone’s 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 Photo’s 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 file’s 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">"{title}{title?{descr?{newline},},}{descr}"</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>
|
||||
|
||||
@@ -1,3 +1,3 @@
|
||||
""" version info """
|
||||
|
||||
__version__ = "0.42.13"
|
||||
__version__ = "0.42.14"
|
||||
|
||||
@@ -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:
|
||||
|
||||
@@ -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
74
osxphotos/photosalbum.py
Normal 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}"
|
||||
# )
|
||||
@@ -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
|
||||
|
||||
2
setup.py
2
setup.py
@@ -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",
|
||||
|
||||
@@ -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
92
tests/test_cli_add_to_album.py
Normal file
92
tests/test_cli_add_to_album.py
Normal 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()))
|
||||
|
||||
@@ -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=[])"
|
||||
)
|
||||
|
||||
|
||||
Reference in New Issue
Block a user