Added --preview, #470
This commit is contained in:
@@ -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"
|
||||
|
||||
@@ -1,3 +1,3 @@
|
||||
""" version info """
|
||||
|
||||
__version__ = "0.42.56"
|
||||
__version__ = "0.42.57"
|
||||
|
||||
130
osxphotos/cli.py
130
osxphotos/cli.py
@@ -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]}")
|
||||
|
||||
@@ -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
|
||||
|
||||
@@ -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
|
||||
|
||||
Reference in New Issue
Block a user