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_ORIGINAL_SUFFIX = ""
# Default suffix to add to preview images
DEFAULT_PREVIEW_SUFFIX = "_preview"
# Colors for print CLI messages
CLI_COLOR_ERROR = "red"
CLI_COLOR_WARNING = "yellow"

View File

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

View File

@@ -17,9 +17,9 @@ import bitmath
import click
import osxmetadata
import photoscript
import rich.traceback
import yaml
from rich import pretty
import rich.traceback
import osxphotos
@@ -33,6 +33,7 @@ from ._constants import (
DEFAULT_EDITED_SUFFIX,
DEFAULT_JPEG_QUALITY,
DEFAULT_ORIGINAL_SUFFIX,
DEFAULT_PREVIEW_SUFFIX,
EXTENDED_ATTRIBUTE_NAMES,
EXTENDED_ATTRIBUTE_NAMES_QUOTED,
OSXPHOTOS_EXPORT_DB,
@@ -704,12 +705,20 @@ def cli(ctx, db, json_, debug):
"a value of 0.0 specifies maximum compression. "
f"Defaults to {DEFAULT_JPEG_QUALITY}",
)
# @click.option(
# "--preview",
# is_flag=True,
# help="Export preview image generated by Photos. "
# "This is a lower-resolution image used by Photos to quickly preview the image.",
# )
@click.option(
"--preview",
is_flag=True,
help="Export preview image generated by Photos. "
"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(
"--download-missing",
is_flag=True,
@@ -1169,7 +1178,8 @@ def export(
duplicate,
post_command,
post_function,
preview=False,
preview,
preview_suffix,
):
"""Export photos from the Photos database.
Export path DEST is required.
@@ -1330,6 +1340,8 @@ def export(
duplicate = cfg.duplicate
post_command = cfg.post_command
post_function = cfg.post_function
preview = cfg.preview
preview_suffix = cfg.preview_suffix
# config file might have changed verbose
VERBOSE = bool(verbose)
@@ -1419,6 +1431,9 @@ def export(
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
if not os.path.isdir(dest):
@@ -1737,6 +1752,7 @@ def export(
retry=retry,
export_dir=dest,
export_preview=preview,
preview_suffix=preview_suffix,
)
if post_function:
@@ -2392,6 +2408,7 @@ def export_photo(
retry=0,
export_dir=None,
export_preview=False,
preview_suffix=None,
):
"""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
export_dir: top-level export directory for {export_dir} template
export_preview: export the preview image generated by Photos
preview_suffix: str, template to use as suffix for preview images
Returns:
list of path(s) of exported photo or None if photo was missing
@@ -2494,27 +2512,12 @@ def export_photo(
if "exiftool" in sidecar:
sidecar_flags |= SIDECAR_EXIFTOOL
rendered_suffix = ""
if original_suffix:
try:
options = RenderOptions(filename=True, strip=strip, export_dir=dest)
rendered_suffix, unmatched = photo.render_template(original_suffix, options)
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]
rendered_suffix = _render_suffix_template(
original_suffix, "original_suffix", "--original-suffix", strip, dest, photo
)
rendered_preview_suffix = _render_suffix_template(
preview_suffix, "preview_suffix", "--preview-suffix", strip, dest, photo
)
# if download_missing and the photo is missing or path doesn't exist,
# try to download with Photos
@@ -2594,6 +2597,7 @@ def export_photo(
retry=retry,
export_dir=export_dir,
export_preview=export_preview,
preview_suffix=rendered_preview_suffix,
)
if export_edited and photo.hasadjustments:
@@ -2625,35 +2629,12 @@ def export_photo(
):
edited_ext = "." + jpeg_ext if jpeg_ext else ".jpeg"
if edited_suffix:
try:
options = RenderOptions(
filename=True,
strip=strip,
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}"
rendered_edited_suffix = _render_suffix_template(
edited_suffix, "edited_suffix", "--edited-suffix", strip, dest, photo
)
edited_filename = (
f"{edited_filename.stem}{rendered_edited_suffix}{edited_ext}"
)
verbose_(
f"Exporting edited version of {photo.original_filename} ({photo.filename}) as {edited_filename}"
@@ -2700,11 +2681,42 @@ def export_photo(
retry=retry,
export_dir=export_dir,
export_preview=not export_original and export_preview,
preview_suffix=rendered_preview_suffix,
)
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(
photo,
filename,
@@ -2746,6 +2758,7 @@ def export_photo_with_template(
retry,
export_dir,
export_preview,
preview_suffix,
):
"""Evaluate directory template then export photo to each directory"""
@@ -2839,6 +2852,7 @@ def export_photo_with_template(
replace_keywords=replace_keywords,
render_options=render_options,
preview=export_preview,
preview_suffix=preview_suffix,
)
for warning_ in export_results.exiftool_warning:
verbose_(f"exiftool warning for file {warning_[0]}: {warning_[1]}")

View File

@@ -37,6 +37,7 @@ from .._constants import (
_UNKNOWN_PERSON,
_XMP_TEMPLATE_NAME,
_XMP_TEMPLATE_NAME_BETA,
DEFAULT_PREVIEW_SUFFIX,
LIVE_VIDEO_EXTENSIONS,
SIDECAR_EXIFTOOL,
SIDECAR_JSON,
@@ -527,7 +528,7 @@ def export2(
location=True,
replace_keywords=False,
preview=False,
preview_suffix="_preview",
preview_suffix=DEFAULT_PREVIEW_SUFFIX,
render_options: Optional[RenderOptions] = None,
):
"""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
replace_keywords: if True, keyword_template replaces any keywords, otherwise it's additive
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
Returns: ExportResults class

View File

@@ -134,6 +134,7 @@ CLI_EXPORT_EDITED_SUFFIX = "_bearbeiten"
CLI_EXPORT_EDITED_SUFFIX_TEMPLATE = "{edited?_edited,}"
CLI_EXPORT_ORIGINAL_SUFFIX = "_original"
CLI_EXPORT_ORIGINAL_SUFFIX_TEMPLATE = "{edited?_original,}"
CLI_EXPORT_PREVIEW_SUFFIX = "_lowres"
CLI_EXPORT_FILENAMES_EDITED_SUFFIX = [
"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_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 = [
"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)
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():
import glob
import os