Added --missing to export, see issue #277
This commit is contained in:
@@ -227,6 +227,9 @@ Options:
|
|||||||
--no-comment Search for photos with no comments.
|
--no-comment Search for photos with no comments.
|
||||||
--has-likes Search for photos that have likes.
|
--has-likes Search for photos that have likes.
|
||||||
--no-likes Search for photos with no likes.
|
--no-likes Search for photos with no likes.
|
||||||
|
--missing Export only photos missing from the Photos
|
||||||
|
library; must be used with --download-
|
||||||
|
missing.
|
||||||
--deleted Include photos from the 'Recently Deleted'
|
--deleted Include photos from the 'Recently Deleted'
|
||||||
folder.
|
folder.
|
||||||
--deleted-only Include only photos from the 'Recently
|
--deleted-only Include only photos from the 'Recently
|
||||||
@@ -234,7 +237,8 @@ Options:
|
|||||||
--update Only export new or updated files. See notes
|
--update Only export new or updated files. See notes
|
||||||
below on export and --update.
|
below on export and --update.
|
||||||
--dry-run Dry run (test) the export but don't actually
|
--dry-run Dry run (test) the export but don't actually
|
||||||
export any files; most useful with --verbose
|
export any files; most useful with
|
||||||
|
--verbose.
|
||||||
--export-as-hardlink Hardlink files instead of copying them.
|
--export-as-hardlink Hardlink files instead of copying them.
|
||||||
Cannot be used with --exiftool which creates
|
Cannot be used with --exiftool which creates
|
||||||
copies of the files with embedded EXIF data.
|
copies of the files with embedded EXIF data.
|
||||||
|
|||||||
@@ -98,7 +98,7 @@ class DateTimeISO8601(click.ParamType):
|
|||||||
def convert(self, value, param, ctx):
|
def convert(self, value, param, ctx):
|
||||||
try:
|
try:
|
||||||
return datetime.datetime.fromisoformat(value)
|
return datetime.datetime.fromisoformat(value)
|
||||||
except:
|
except Exception:
|
||||||
self.fail(
|
self.fail(
|
||||||
f"Invalid value for --{param.name}: invalid datetime format {value}. "
|
f"Invalid value for --{param.name}: invalid datetime format {value}. "
|
||||||
"Valid format: YYYY-MM-DD[*HH[:MM[:SS[.fff[fff]]]][+HH:MM[:SS[.ffffff]]]]"
|
"Valid format: YYYY-MM-DD[*HH[:MM[:SS[.fff[fff]]]][+HH:MM[:SS[.ffffff]]]]"
|
||||||
@@ -651,7 +651,7 @@ def debug_dump(ctx, cli_obj, db, photos_library, dump, uuid, verbose_):
|
|||||||
val = getattr(photosdb, attr)
|
val = getattr(photosdb, attr)
|
||||||
print(f"{attr}:")
|
print(f"{attr}:")
|
||||||
pprint.pprint(val)
|
pprint.pprint(val)
|
||||||
except:
|
except Exception:
|
||||||
print(f"Did not find attribute {attr} in PhotosDB")
|
print(f"Did not find attribute {attr} in PhotosDB")
|
||||||
|
|
||||||
|
|
||||||
@@ -846,12 +846,12 @@ def places(ctx, cli_obj, db, json_, photos_library):
|
|||||||
if photo.place:
|
if photo.place:
|
||||||
try:
|
try:
|
||||||
place_names[photo.place.name] += 1
|
place_names[photo.place.name] += 1
|
||||||
except:
|
except Exception:
|
||||||
place_names[photo.place.name] = 1
|
place_names[photo.place.name] = 1
|
||||||
else:
|
else:
|
||||||
try:
|
try:
|
||||||
place_names[_UNKNOWN_PLACE] += 1
|
place_names[_UNKNOWN_PLACE] += 1
|
||||||
except:
|
except Exception:
|
||||||
place_names[_UNKNOWN_PLACE] = 1
|
place_names[_UNKNOWN_PLACE] = 1
|
||||||
|
|
||||||
# sort by place count
|
# sort by place count
|
||||||
@@ -1199,6 +1199,11 @@ def query(
|
|||||||
@DB_OPTION
|
@DB_OPTION
|
||||||
@click.option("--verbose", "-V", "verbose_", is_flag=True, help="Print verbose output.")
|
@click.option("--verbose", "-V", "verbose_", is_flag=True, help="Print verbose output.")
|
||||||
@query_options
|
@query_options
|
||||||
|
@click.option(
|
||||||
|
"--missing",
|
||||||
|
is_flag=True,
|
||||||
|
help="Export only photos missing from the Photos library; must be used with --download-missing.",
|
||||||
|
)
|
||||||
@deleted_options
|
@deleted_options
|
||||||
@click.option(
|
@click.option(
|
||||||
"--update",
|
"--update",
|
||||||
@@ -1208,7 +1213,7 @@ def query(
|
|||||||
@click.option(
|
@click.option(
|
||||||
"--dry-run",
|
"--dry-run",
|
||||||
is_flag=True,
|
is_flag=True,
|
||||||
help="Dry run (test) the export but don't actually export any files; most useful with --verbose",
|
help="Dry run (test) the export but don't actually export any files; most useful with --verbose.",
|
||||||
)
|
)
|
||||||
@click.option(
|
@click.option(
|
||||||
"--export-as-hardlink",
|
"--export-as-hardlink",
|
||||||
@@ -1451,6 +1456,7 @@ def export(
|
|||||||
from_date,
|
from_date,
|
||||||
to_date,
|
to_date,
|
||||||
verbose_,
|
verbose_,
|
||||||
|
missing,
|
||||||
update,
|
update,
|
||||||
dry_run,
|
dry_run,
|
||||||
export_as_hardlink,
|
export_as_hardlink,
|
||||||
@@ -1567,6 +1573,20 @@ def export(
|
|||||||
click.echo(cli.commands["export"].get_help(ctx), err=True)
|
click.echo(cli.commands["export"].get_help(ctx), err=True)
|
||||||
return
|
return
|
||||||
|
|
||||||
|
if export_as_hardlink and download_missing:
|
||||||
|
click.echo(
|
||||||
|
"Incompatible export options: --export-as-hardlink is not compatible with --download-missing",
|
||||||
|
err=True,
|
||||||
|
)
|
||||||
|
raise click.Abort()
|
||||||
|
|
||||||
|
if missing and not download_missing:
|
||||||
|
click.echo(
|
||||||
|
"Incompatible export options: --missing must be used with --download-missing",
|
||||||
|
err=True,
|
||||||
|
)
|
||||||
|
raise click.Abort()
|
||||||
|
|
||||||
if use_photokit and not check_photokit_authorization():
|
if use_photokit and not check_photokit_authorization():
|
||||||
click.echo(
|
click.echo(
|
||||||
"Requesting access to use your Photos library. Click 'OK' on the dialog box to grant access."
|
"Requesting access to use your Photos library. Click 'OK' on the dialog box to grant access."
|
||||||
@@ -1674,7 +1694,7 @@ def export(
|
|||||||
not_favorite=not_favorite,
|
not_favorite=not_favorite,
|
||||||
hidden=hidden,
|
hidden=hidden,
|
||||||
not_hidden=not_hidden,
|
not_hidden=not_hidden,
|
||||||
missing=None, # missing -- won't export these but will warn user
|
missing=missing,
|
||||||
not_missing=None,
|
not_missing=None,
|
||||||
shared=shared,
|
shared=shared,
|
||||||
not_shared=not_shared,
|
not_shared=not_shared,
|
||||||
@@ -2575,7 +2595,7 @@ def export_photo(
|
|||||||
verbose(f"Skipped up to date file {skipped}")
|
verbose(f"Skipped up to date file {skipped}")
|
||||||
for touched in export_results.touched:
|
for touched in export_results.touched:
|
||||||
verbose(f"Touched date on file {touched}")
|
verbose(f"Touched date on file {touched}")
|
||||||
except:
|
except Exception:
|
||||||
click.echo(
|
click.echo(
|
||||||
f"Error exporting photo {photo.original_filename} ({photo.filename}) as {original_filename}",
|
f"Error exporting photo {photo.original_filename} ({photo.filename}) as {original_filename}",
|
||||||
err=True,
|
err=True,
|
||||||
@@ -2603,7 +2623,7 @@ def export_photo(
|
|||||||
f"{edited_filename.stem}{edited_suffix}{edited_ext}"
|
f"{edited_filename.stem}{edited_suffix}{edited_ext}"
|
||||||
)
|
)
|
||||||
verbose(
|
verbose(
|
||||||
f"Exporting edited version of {filename} as {edited_filename}"
|
f"Exporting edited version of {photo.original_filename} ({photo.filename}) as {edited_filename}"
|
||||||
)
|
)
|
||||||
try:
|
try:
|
||||||
export_results_edited = photo.export2(
|
export_results_edited = photo.export2(
|
||||||
@@ -2666,7 +2686,7 @@ def export_photo(
|
|||||||
verbose(f"Skipped up to date file {skipped}")
|
verbose(f"Skipped up to date file {skipped}")
|
||||||
for touched in export_results_edited.touched:
|
for touched in export_results_edited.touched:
|
||||||
verbose(f"Touched date on file {touched}")
|
verbose(f"Touched date on file {touched}")
|
||||||
except:
|
except Exception:
|
||||||
click.echo(
|
click.echo(
|
||||||
f"Error exporting photo {filename} as {edited_filename}",
|
f"Error exporting photo {filename} as {edited_filename}",
|
||||||
err=True,
|
err=True,
|
||||||
|
|||||||
@@ -1,4 +1,4 @@
|
|||||||
""" version info """
|
""" version info """
|
||||||
|
|
||||||
__version__ = "0.37.1"
|
__version__ = "0.37.2"
|
||||||
|
|
||||||
|
|||||||
@@ -17,6 +17,7 @@ PLACES_PHOTOS_DB_13 = "tests/Test-Places-High-Sierra-10.13.6.photoslibrary"
|
|||||||
PHOTOS_DB_15_4 = "tests/Test-10.15.4.photoslibrary"
|
PHOTOS_DB_15_4 = "tests/Test-10.15.4.photoslibrary"
|
||||||
PHOTOS_DB_15_5 = "tests/Test-10.15.5.photoslibrary"
|
PHOTOS_DB_15_5 = "tests/Test-10.15.5.photoslibrary"
|
||||||
PHOTOS_DB_15_6 = "tests/Test-10.15.6.photoslibrary"
|
PHOTOS_DB_15_6 = "tests/Test-10.15.6.photoslibrary"
|
||||||
|
PHOTOS_DB_15_7 = "tests/Test-10.15.7.photoslibrary"
|
||||||
PHOTOS_DB_TOUCH = PHOTOS_DB_15_6
|
PHOTOS_DB_TOUCH = PHOTOS_DB_15_6
|
||||||
PHOTOS_DB_14_6 = "tests/Test-10.14.6.photoslibrary"
|
PHOTOS_DB_14_6 = "tests/Test-10.14.6.photoslibrary"
|
||||||
|
|
||||||
@@ -3804,3 +3805,75 @@ def test_export_report_not_a_file():
|
|||||||
assert result.exit_code != 0
|
assert result.exit_code != 0
|
||||||
assert "Aborted!" in result.output
|
assert "Aborted!" in result.output
|
||||||
|
|
||||||
|
|
||||||
|
def test_export_as_hardlink_download_missing():
|
||||||
|
""" test export with incompatible export options """
|
||||||
|
import glob
|
||||||
|
import os
|
||||||
|
import os.path
|
||||||
|
import osxphotos
|
||||||
|
from osxphotos.__main__ 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",
|
||||||
|
"--download-missing",
|
||||||
|
"--export-as-hardlink",
|
||||||
|
".",
|
||||||
|
],
|
||||||
|
)
|
||||||
|
assert result.exit_code != 0
|
||||||
|
assert "Aborted!" in result.output
|
||||||
|
|
||||||
|
|
||||||
|
def test_export_missing():
|
||||||
|
""" test export with --missing """
|
||||||
|
import glob
|
||||||
|
import os
|
||||||
|
import os.path
|
||||||
|
import osxphotos
|
||||||
|
from osxphotos.__main__ import export
|
||||||
|
|
||||||
|
runner = CliRunner()
|
||||||
|
cwd = os.getcwd()
|
||||||
|
# pylint: disable=not-context-manager
|
||||||
|
with runner.isolated_filesystem():
|
||||||
|
result = runner.invoke(
|
||||||
|
export,
|
||||||
|
[
|
||||||
|
os.path.join(cwd, PHOTOS_DB_15_7),
|
||||||
|
".",
|
||||||
|
"-V",
|
||||||
|
"--missing",
|
||||||
|
"--download-missing",
|
||||||
|
".",
|
||||||
|
],
|
||||||
|
)
|
||||||
|
assert result.exit_code == 0
|
||||||
|
assert "Exporting 2 photos" in result.output
|
||||||
|
|
||||||
|
|
||||||
|
def test_export_missing_not_download_missing():
|
||||||
|
""" test export with incompatible export options """
|
||||||
|
import glob
|
||||||
|
import os
|
||||||
|
import os.path
|
||||||
|
import osxphotos
|
||||||
|
from osxphotos.__main__ 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", "--missing", "."]
|
||||||
|
)
|
||||||
|
assert result.exit_code != 0
|
||||||
|
assert "Aborted!" in result.output
|
||||||
|
|||||||
Reference in New Issue
Block a user