Added --preview, #470

This commit is contained in:
Rhet Turnbull
2021-07-04 08:39:06 -07:00
parent 28c681aa96
commit 7e2d09bf12
5 changed files with 142 additions and 61 deletions

View File

@@ -210,6 +210,9 @@ DEFAULT_EDITED_SUFFIX = "_edited"
# Default suffix to add to original images # Default suffix to add to original images
DEFAULT_ORIGINAL_SUFFIX = "" DEFAULT_ORIGINAL_SUFFIX = ""
# Default suffix to add to preview images
DEFAULT_PREVIEW_SUFFIX = "_preview"
# Colors for print CLI messages # Colors for print CLI messages
CLI_COLOR_ERROR = "red" CLI_COLOR_ERROR = "red"
CLI_COLOR_WARNING = "yellow" CLI_COLOR_WARNING = "yellow"

View File

@@ -1,3 +1,3 @@
""" version info """ """ version info """
__version__ = "0.42.56" __version__ = "0.42.57"

View File

@@ -17,9 +17,9 @@ import bitmath
import click import click
import osxmetadata import osxmetadata
import photoscript import photoscript
import rich.traceback
import yaml import yaml
from rich import pretty from rich import pretty
import rich.traceback
import osxphotos import osxphotos
@@ -33,6 +33,7 @@ from ._constants import (
DEFAULT_EDITED_SUFFIX, DEFAULT_EDITED_SUFFIX,
DEFAULT_JPEG_QUALITY, DEFAULT_JPEG_QUALITY,
DEFAULT_ORIGINAL_SUFFIX, DEFAULT_ORIGINAL_SUFFIX,
DEFAULT_PREVIEW_SUFFIX,
EXTENDED_ATTRIBUTE_NAMES, EXTENDED_ATTRIBUTE_NAMES,
EXTENDED_ATTRIBUTE_NAMES_QUOTED, EXTENDED_ATTRIBUTE_NAMES_QUOTED,
OSXPHOTOS_EXPORT_DB, OSXPHOTOS_EXPORT_DB,
@@ -704,12 +705,20 @@ def cli(ctx, db, json_, debug):
"a value of 0.0 specifies maximum compression. " "a value of 0.0 specifies maximum compression. "
f"Defaults to {DEFAULT_JPEG_QUALITY}", f"Defaults to {DEFAULT_JPEG_QUALITY}",
) )
# @click.option( @click.option(
# "--preview", "--preview",
# is_flag=True, is_flag=True,
# help="Export preview image generated by Photos. " help="Export preview image generated by Photos. "
# "This is a lower-resolution image used by Photos to quickly preview the image.", "This is a lower-resolution image used by Photos to quickly preview the image.",
# ) )
@click.option(
"--preview-suffix",
metavar="SUFFIX",
help="Optional suffix template for naming preview photos. Default name for preview photos is in form "
f"'photoname{DEFAULT_PREVIEW_SUFFIX}.ext'. For example, with '--preview-suffix _low_res', the preview photo "
f"would be named 'photoname_low_res.ext'. The default suffix is '{DEFAULT_PREVIEW_SUFFIX}'. "
"Multi-value templates (see Templating System) are not permitted with --preview-suffix.",
)
@click.option( @click.option(
"--download-missing", "--download-missing",
is_flag=True, is_flag=True,
@@ -1169,7 +1178,8 @@ def export(
duplicate, duplicate,
post_command, post_command,
post_function, post_function,
preview=False, preview,
preview_suffix,
): ):
"""Export photos from the Photos database. """Export photos from the Photos database.
Export path DEST is required. Export path DEST is required.
@@ -1330,6 +1340,8 @@ def export(
duplicate = cfg.duplicate duplicate = cfg.duplicate
post_command = cfg.post_command post_command = cfg.post_command
post_function = cfg.post_function post_function = cfg.post_function
preview = cfg.preview
preview_suffix = cfg.preview_suffix
# config file might have changed verbose # config file might have changed verbose
VERBOSE = bool(verbose) VERBOSE = bool(verbose)
@@ -1419,6 +1431,9 @@ def export(
original_suffix = ( original_suffix = (
DEFAULT_ORIGINAL_SUFFIX if original_suffix is None else original_suffix DEFAULT_ORIGINAL_SUFFIX if original_suffix is None else original_suffix
) )
preview_suffix = (
DEFAULT_PREVIEW_SUFFIX if preview_suffix is None else preview_suffix
)
retry = 0 if not retry else retry retry = 0 if not retry else retry
if not os.path.isdir(dest): if not os.path.isdir(dest):
@@ -1737,6 +1752,7 @@ def export(
retry=retry, retry=retry,
export_dir=dest, export_dir=dest,
export_preview=preview, export_preview=preview,
preview_suffix=preview_suffix,
) )
if post_function: if post_function:
@@ -2392,6 +2408,7 @@ def export_photo(
retry=0, retry=0,
export_dir=None, export_dir=None,
export_preview=False, export_preview=False,
preview_suffix=None,
): ):
"""Helper function for export that does the actual export """Helper function for export that does the actual export
@@ -2434,6 +2451,7 @@ def export_photo(
retry: retry up to retry # of times if there's an error retry: retry up to retry # of times if there's an error
export_dir: top-level export directory for {export_dir} template export_dir: top-level export directory for {export_dir} template
export_preview: export the preview image generated by Photos export_preview: export the preview image generated by Photos
preview_suffix: str, template to use as suffix for preview images
Returns: Returns:
list of path(s) of exported photo or None if photo was missing list of path(s) of exported photo or None if photo was missing
@@ -2494,27 +2512,12 @@ def export_photo(
if "exiftool" in sidecar: if "exiftool" in sidecar:
sidecar_flags |= SIDECAR_EXIFTOOL sidecar_flags |= SIDECAR_EXIFTOOL
rendered_suffix = "" rendered_suffix = _render_suffix_template(
if original_suffix: original_suffix, "original_suffix", "--original-suffix", strip, dest, photo
try: )
options = RenderOptions(filename=True, strip=strip, export_dir=dest) rendered_preview_suffix = _render_suffix_template(
rendered_suffix, unmatched = photo.render_template(original_suffix, options) preview_suffix, "preview_suffix", "--preview-suffix", strip, dest, photo
except ValueError as e: )
raise click.BadOptionUsage(
"original_suffix",
f"Invalid template for --original-suffix '{original_suffix}': {e}",
)
if not rendered_suffix or unmatched:
raise click.BadOptionUsage(
"original_suffix",
f"Invalid template for --original-suffix '{original_suffix}': results={rendered_suffix} unknown field={unmatched}",
)
if len(rendered_suffix) > 1:
raise click.BadOptionUsage(
"original_suffix",
f"Invalid template for --original-suffix: may not use multi-valued templates: '{original_suffix}': results={rendered_suffix}",
)
rendered_suffix = rendered_suffix[0]
# if download_missing and the photo is missing or path doesn't exist, # if download_missing and the photo is missing or path doesn't exist,
# try to download with Photos # try to download with Photos
@@ -2594,6 +2597,7 @@ def export_photo(
retry=retry, retry=retry,
export_dir=export_dir, export_dir=export_dir,
export_preview=export_preview, export_preview=export_preview,
preview_suffix=rendered_preview_suffix,
) )
if export_edited and photo.hasadjustments: if export_edited and photo.hasadjustments:
@@ -2625,35 +2629,12 @@ def export_photo(
): ):
edited_ext = "." + jpeg_ext if jpeg_ext else ".jpeg" edited_ext = "." + jpeg_ext if jpeg_ext else ".jpeg"
if edited_suffix: rendered_edited_suffix = _render_suffix_template(
try: edited_suffix, "edited_suffix", "--edited-suffix", strip, dest, photo
options = RenderOptions( )
filename=True, edited_filename = (
strip=strip, f"{edited_filename.stem}{rendered_edited_suffix}{edited_ext}"
export_dir=dest, )
)
rendered_suffix, unmatched = photo.render_template(
edited_suffix, options
)
except ValueError as e:
raise click.BadOptionUsage(
"edited_suffix",
f"Invalid template for --edited-suffix '{edited_suffix}': {e}",
)
if not rendered_suffix or unmatched:
raise click.BadOptionUsage(
"edited_suffix",
f"Invalid template for --edited-suffix '{edited_suffix}': unknown field={unmatched}",
)
if len(rendered_suffix) > 1:
raise click.BadOptionUsage(
"edited_suffix",
f"Invalid template for --edited-suffix: may not use multi-valued templates: '{edited_suffix}': results={rendered_suffix}",
)
rendered_suffix = rendered_suffix[0]
edited_filename = f"{edited_filename.stem}{rendered_suffix}{edited_ext}"
else:
edited_filename = f"{edited_filename.stem}{edited_ext}"
verbose_( verbose_(
f"Exporting edited version of {photo.original_filename} ({photo.filename}) as {edited_filename}" f"Exporting edited version of {photo.original_filename} ({photo.filename}) as {edited_filename}"
@@ -2700,11 +2681,42 @@ def export_photo(
retry=retry, retry=retry,
export_dir=export_dir, export_dir=export_dir,
export_preview=not export_original and export_preview, export_preview=not export_original and export_preview,
preview_suffix=rendered_preview_suffix,
) )
return results return results
def _render_suffix_template(suffix_template, var_name, option_name, strip, dest, photo):
"""render suffix template
Returns:
rendered template
"""
if not suffix_template:
return ""
try:
options = RenderOptions(filename=True, strip=strip, export_dir=dest)
rendered_suffix, unmatched = photo.render_template(suffix_template, options)
except ValueError as e:
raise click.BadOptionUsage(
var_name,
f"Invalid template for {option_name} '{suffix_template}': {e}",
)
if not rendered_suffix or unmatched:
raise click.BadOptionUsage(
var_name,
f"Invalid template for {option_name} '{suffix_template}': results={rendered_suffix} unknown field={unmatched}",
)
if len(rendered_suffix) > 1:
raise click.BadOptionUsage(
var_name,
f"Invalid template for {option_name}: may not use multi-valued templates: '{suffix_template}': results={rendered_suffix}",
)
return rendered_suffix[0]
def export_photo_with_template( def export_photo_with_template(
photo, photo,
filename, filename,
@@ -2746,6 +2758,7 @@ def export_photo_with_template(
retry, retry,
export_dir, export_dir,
export_preview, export_preview,
preview_suffix,
): ):
"""Evaluate directory template then export photo to each directory""" """Evaluate directory template then export photo to each directory"""
@@ -2839,6 +2852,7 @@ def export_photo_with_template(
replace_keywords=replace_keywords, replace_keywords=replace_keywords,
render_options=render_options, render_options=render_options,
preview=export_preview, preview=export_preview,
preview_suffix=preview_suffix,
) )
for warning_ in export_results.exiftool_warning: for warning_ in export_results.exiftool_warning:
verbose_(f"exiftool warning for file {warning_[0]}: {warning_[1]}") verbose_(f"exiftool warning for file {warning_[0]}: {warning_[1]}")

View File

@@ -37,6 +37,7 @@ from .._constants import (
_UNKNOWN_PERSON, _UNKNOWN_PERSON,
_XMP_TEMPLATE_NAME, _XMP_TEMPLATE_NAME,
_XMP_TEMPLATE_NAME_BETA, _XMP_TEMPLATE_NAME_BETA,
DEFAULT_PREVIEW_SUFFIX,
LIVE_VIDEO_EXTENSIONS, LIVE_VIDEO_EXTENSIONS,
SIDECAR_EXIFTOOL, SIDECAR_EXIFTOOL,
SIDECAR_JSON, SIDECAR_JSON,
@@ -527,7 +528,7 @@ def export2(
location=True, location=True,
replace_keywords=False, replace_keywords=False,
preview=False, preview=False,
preview_suffix="_preview", preview_suffix=DEFAULT_PREVIEW_SUFFIX,
render_options: Optional[RenderOptions] = None, render_options: Optional[RenderOptions] = None,
): ):
"""export photo, like export but with update and dry_run options """export photo, like export but with update and dry_run options
@@ -585,7 +586,7 @@ def export2(
location: if True, include location in exported metadata location: if True, include location in exported metadata
replace_keywords: if True, keyword_template replaces any keywords, otherwise it's additive replace_keywords: if True, keyword_template replaces any keywords, otherwise it's additive
preview: if True, also exports preview image preview: if True, also exports preview image
preview_suffix: optional string to append to end of filename for preview images, if not provided, uses "_preview" preview_suffix: optional string to append to end of filename for preview images
render_options: optional osxphotos.phototemplate.RenderOptions instance to specify options for rendering templates render_options: optional osxphotos.phototemplate.RenderOptions instance to specify options for rendering templates
Returns: ExportResults class Returns: ExportResults class

View File

@@ -134,6 +134,7 @@ CLI_EXPORT_EDITED_SUFFIX = "_bearbeiten"
CLI_EXPORT_EDITED_SUFFIX_TEMPLATE = "{edited?_edited,}" CLI_EXPORT_EDITED_SUFFIX_TEMPLATE = "{edited?_edited,}"
CLI_EXPORT_ORIGINAL_SUFFIX = "_original" CLI_EXPORT_ORIGINAL_SUFFIX = "_original"
CLI_EXPORT_ORIGINAL_SUFFIX_TEMPLATE = "{edited?_original,}" CLI_EXPORT_ORIGINAL_SUFFIX_TEMPLATE = "{edited?_original,}"
CLI_EXPORT_PREVIEW_SUFFIX = "_lowres"
CLI_EXPORT_FILENAMES_EDITED_SUFFIX = [ CLI_EXPORT_FILENAMES_EDITED_SUFFIX = [
"Pumkins1.jpg", "Pumkins1.jpg",
@@ -430,6 +431,8 @@ CLI_EXPORT_UUID_KEYWORD_PATHSEP = "7783E8E6-9CAC-40F3-BE22-81FB7051C266"
CLI_EXPORT_UUID_LONG_DESCRIPTION = "8846E3E6-8AC8-4857-8448-E3D025784410" CLI_EXPORT_UUID_LONG_DESCRIPTION = "8846E3E6-8AC8-4857-8448-E3D025784410"
CLI_EXPORT_UUID_FILENAME = "Pumkins2.jpg" CLI_EXPORT_UUID_FILENAME = "Pumkins2.jpg"
CLI_EXPORT_UUID_FILENAME_PREVIEW = "Pumkins2_preview.jpeg"
CLI_EXPORT_UUID_FILENAME_PREVIEW_TEMPLATE = "Pumkins2_lowres.jpeg"
CLI_EXPORT_BY_DATE_TOUCH_UUID = [ CLI_EXPORT_BY_DATE_TOUCH_UUID = [
"1EB2B765-0765-43BA-A90C-0D0580E6172C", # Pumpkins3.jpg "1EB2B765-0765-43BA-A90C-0D0580E6172C", # Pumpkins3.jpg
@@ -1242,6 +1245,66 @@ def test_export_uuid_from_file():
assert sorted(files) == sorted(CLI_EXPORT_UUID_FROM_FILE_FILENAMES) assert sorted(files) == sorted(CLI_EXPORT_UUID_FROM_FILE_FILENAMES)
def test_export_preview():
"""test export with --preview"""
import glob
import os
import os.path
import osxphotos
from osxphotos.cli import export
runner = CliRunner()
cwd = os.getcwd()
# pylint: disable=not-context-manager
with runner.isolated_filesystem():
result = runner.invoke(
export,
[
os.path.join(cwd, CLI_PHOTOS_DB),
".",
"-V",
"--preview",
"--uuid",
CLI_EXPORT_UUID,
],
)
assert result.exit_code == 0
files = glob.glob("*")
assert CLI_EXPORT_UUID_FILENAME_PREVIEW in files
def test_export_preview_suffix():
"""test export with --preview and --preview-suffix"""
import glob
import os
import os.path
import osxphotos
from osxphotos.cli import export
runner = CliRunner()
cwd = os.getcwd()
# pylint: disable=not-context-manager
with runner.isolated_filesystem():
result = runner.invoke(
export,
[
os.path.join(cwd, CLI_PHOTOS_DB),
".",
"-V",
"--preview",
"--preview-suffix",
CLI_EXPORT_PREVIEW_SUFFIX,
"--uuid",
CLI_EXPORT_UUID,
],
)
assert result.exit_code == 0
files = glob.glob("*")
assert CLI_EXPORT_UUID_FILENAME_PREVIEW_TEMPLATE in files
def test_export_as_hardlink(): def test_export_as_hardlink():
import glob import glob
import os import os