Skip to content

Commit

Permalink
Added --exportdir
Browse files Browse the repository at this point in the history
  • Loading branch information
RhetTbull committed Jun 2, 2024
1 parent 7263015 commit d04914a
Show file tree
Hide file tree
Showing 5 changed files with 125 additions and 16 deletions.
55 changes: 49 additions & 6 deletions osxphotos/cli/import_cli.py
Original file line number Diff line number Diff line change
Expand Up @@ -56,7 +56,10 @@
datetime_utc_to_local,
)
from osxphotos.exiftool import get_exiftool_path
from osxphotos.export_db_utils import export_db_get_photoinfo_for_filepath
from osxphotos.export_db_utils import (
export_db_get_photoinfo_for_filepath,
export_db_migrate_photos_library,
)
from osxphotos.fingerprintquery import FingerprintQuery
from osxphotos.image_file_utils import (
EDITED_RE,
Expand Down Expand Up @@ -735,9 +738,29 @@ def get_help(self, ctx):
"--exportdb",
"-B",
metavar="EXPORTDB_PATH",
type=click.Path(exists=True),
help="Use an osxphotos export database (created by 'osxphotos export') "
"to set metadata (title, description, keywords, location, album). "
"See also --sidecar, --sidecar-filename, --exiftool.",
"See also --exportdir, --sidecar, --sidecar-filename, --exiftool.",
)
@click.option(
"--exportdir",
"-I",
metavar="EXPORT_DIR_PATH",
type=click.Path(exists=True),
help="Specify the path to the export directory when using --exportdb "
"to import metadata from an osxphotos export database created by 'osxphotos export'. "
"This is only needed if you have moved the exported files to a new location since the export. "
"If you have moved the exported files, osxphotos will need to know the path to the top-level of "
"the export directory in order to use --exportdb to read the metadata for the files. "
"For example, if you used 'osxphotos export' to export photos to '/Volumes/Exported' "
"but you subsequently moved the files to '/Volumes/PhotosBackup' you would use: "
"'--exportdb /Volumes/PhotosBackup --exportdir /Volumes/PhotosBackup' to import the metadata "
"from the export database. This is needed because osxphotos needs to know both the path to the "
"export database and the root folder of the exported files in order to match the files to the "
"correct entry in the export database and the database may be in a different location than the "
"exported files. "
"See also --exportdb.",
)
@click.option(
"--relative-to",
Expand Down Expand Up @@ -929,6 +952,7 @@ def import_main(
exiftool: bool,
exiftool_path: str | None,
exportdb: str | None,
exportdir: str | None,
favorite_rating: int | None,
files_or_dirs: tuple[str, ...],
glob: tuple[str, ...],
Expand Down Expand Up @@ -988,7 +1012,6 @@ def import_main(
If edited files do not contain an associated .AAE or if they do not match one of these two conventions,
they will be imported as separate assets.
"""

kwargs = locals()
kwargs.pop("ctx")
kwargs.pop("cli_obj")
Expand All @@ -1012,6 +1035,7 @@ def import_cli(
exiftool: bool = False,
exiftool_path: str | None = None,
exportdb: str | None = None,
exportdir: str | None = None,
favorite_rating: int | None = None,
files_or_dirs: tuple[str, ...] = (),
glob: tuple[str, ...] = (),
Expand Down Expand Up @@ -1176,6 +1200,7 @@ def import_cli(
exiftool=exiftool,
exiftool_path=exiftool_path,
exportdb=exportdb,
exportdir=exportdir,
favorite_rating=favorite_rating,
sidecar=sidecar,
sidecar_ignore_date=sidecar_ignore_date,
Expand Down Expand Up @@ -1364,14 +1389,18 @@ def add_photo_to_albums_from_exportdb(
photo: Photo | None,
filepath: pathlib.Path,
exportdb_path: str,
exportdir_path: str | None,
exiftool_path: str,
verbose: Callable[..., None],
dry_run: bool,
) -> list[str]:
"""Add photo to one or more albums from data found in export database"""
with suppress(ValueError):
if photoinfo := export_db_get_photoinfo_for_filepath(
exportdb_path=exportdb_path, filepath=filepath, exiftool=exiftool_path
exportdb_path=exportdb_path,
filepath=filepath,
exiftool=exiftool_path,
exportdir_path=exportdir_path,
):
if photoinfo.album_info:
verbose(
Expand Down Expand Up @@ -1444,6 +1473,7 @@ def add_duplicate_to_albums_from_exportdb(
duplicates: list[tuple[str, datetime.datetime, str]],
filepath: pathlib.Path,
exportdb_path: str,
exportdir_path: str | None,
exiftool_path: str,
verbose: Callable[..., None],
dry_run: bool,
Expand All @@ -1453,6 +1483,7 @@ def add_duplicate_to_albums_from_exportdb(
duplicates: list of tuples of (uuid, date, filename) for duplicates as returned by FingerprintQuery.possible_duplicates
filepath: path to file to import
exportdb_path: path to the export db
exportdir_path: path to the export directory if it cannot be determined from exportdb_path (e.g. the export was moved)
exiftool_path: path to exiftool
verbose: verbose function
dry_run: dry run
Expand All @@ -1479,6 +1510,7 @@ def add_duplicate_to_albums_from_exportdb(
dup_photo,
filepath,
exportdb_path,
exportdir_path,
exiftool_path,
verbose,
dry_run,
Expand Down Expand Up @@ -1551,6 +1583,7 @@ def set_photo_metadata_from_exportdb(
photo: Photo | None,
filepath: pathlib.Path,
exportdb_path: pathlib.Path,
exportdir_path: pathlib.Path | None,
exiftool_path: str,
merge_keywords: bool,
verbose: Callable[..., None],
Expand All @@ -1560,7 +1593,10 @@ def set_photo_metadata_from_exportdb(
photoinfo = None
with suppress(ValueError):
photoinfo = export_db_get_photoinfo_for_filepath(
exportdb_path=exportdb_path, filepath=filepath, exiftool=exiftool_path
exportdb_path=exportdb_path,
filepath=filepath,
exiftool=exiftool_path,
exportdir_path=exportdir_path,
)
if photoinfo:
metadata = metadata_from_photoinfo(photoinfo)
Expand Down Expand Up @@ -1893,6 +1929,7 @@ def apply_photo_metadata(
exiftool: bool,
exiftool_path: str,
exportdb: pathlib.Path | None,
exportdir: pathlib.Path | None,
favorite_rating: bool,
filepath: pathlib.Path,
keyword: str | None,
Expand Down Expand Up @@ -1920,6 +1957,7 @@ def apply_photo_metadata(
photo,
filepath,
exportdb,
exportdir,
exiftool_path,
merge_keywords,
verbose,
Expand Down Expand Up @@ -2020,6 +2058,7 @@ def apply_photo_albums(
exiftool: bool,
exiftool_path: str | None,
exportdb: str | None,
exportdir: str | None,
filepath: pathlib.Path,
photo: Photo,
relative_filepath: pathlib.Path,
Expand All @@ -2045,7 +2084,7 @@ def apply_photo_albums(
if exportdb:
# add photo to any albums defined in the exportdb data
report_record.albums += add_photo_to_albums_from_exportdb(
photo, filepath, exportdb, exiftool_path, verbose, dry_run
photo, filepath, exportdb, exportdir, exiftool_path, verbose, dry_run
)


Expand Down Expand Up @@ -2954,6 +2993,7 @@ def import_files(
exiftool: bool,
exiftool_path: str,
exportdb: str | None,
exportdir: str | None,
favorite_rating: int | None,
sidecar: bool,
sidecar_ignore_date: bool,
Expand Down Expand Up @@ -3117,6 +3157,7 @@ def import_files(
duplicates,
filepath,
exportdb,
exportdir,
exiftool_path,
verbose,
dry_run,
Expand Down Expand Up @@ -3192,6 +3233,7 @@ def import_files(
exiftool=exiftool,
exiftool_path=exiftool_path,
exportdb=exportdb,
exportdir=exportdir,
favorite_rating=favorite_rating,
filepath=filepath,
keyword=keyword,
Expand All @@ -3213,6 +3255,7 @@ def import_files(
exiftool=exiftool,
exiftool_path=exiftool_path,
exportdb=exportdb,
exportdir=exportdir,
filepath=filepath,
photo=photo,
relative_filepath=relative_filepath,
Expand Down
4 changes: 3 additions & 1 deletion osxphotos/export_db_utils.py
Original file line number Diff line number Diff line change
Expand Up @@ -605,17 +605,19 @@ def export_db_get_photoinfo_for_filepath(
exportdb_path: str | os.PathLike,
filepath: str | os.PathLike,
exiftool: str | os.PathLike | None = None,
exportdir_path: str | os.PathLike | None = None,
) -> PhotoInfoFromDict | None:
"""Return photoinfo object for a given filepath
Args:
exportdb: path to the export database
exportdir: path to the export directory or None
filepath: absolute path to the file to retrieve info for from the database
exiftool: optional path to exiftool to be passed to the PhotoInfoFromDict object
Returns: PhotoInfoFromDict | None
"""
last_export_dir = export_db_get_last_export_dir(exportdb_path)
last_export_dir = exportdir_path or export_db_get_last_export_dir(exportdb_path)
if not pathlib.Path(exportdb_path).is_file():
exportdb_path = find_export_db_for_filepath(exportdb_path)
if not exportdb_path:
Expand Down
12 changes: 7 additions & 5 deletions tests/conftest.py
Original file line number Diff line number Diff line change
Expand Up @@ -41,6 +41,8 @@
# don't clean up crash logs (configured with --no-cleanup)
NO_CLEANUP = False

LIBRARY_COPY_DELAY = 5


def get_os_version():
if not is_macos:
Expand Down Expand Up @@ -96,35 +98,35 @@ def get_os_version():
def setup_photos_timewarp():
if not TEST_TIMEWARP:
return
copy_photos_library(TEST_LIBRARY_TIMEWARP, delay=5)
copy_photos_library(TEST_LIBRARY_TIMEWARP, delay=LIBRARY_COPY_DELAY)


@pytest.fixture(scope="session", autouse=is_macos)
def setup_photos_import():
if not TEST_IMPORT:
return
copy_photos_library(TEST_LIBRARY_IMPORT, delay=10)
copy_photos_library(TEST_LIBRARY_IMPORT, delay=LIBRARY_COPY_DELAY)


@pytest.fixture(scope="session", autouse=is_macos)
def setup_photos_import_takeout():
if not TEST_IMPORT_TAKEOUT:
return
copy_photos_library(TEST_LIBRARY_TAKEOUT, delay=10)
copy_photos_library(TEST_LIBRARY_TAKEOUT, delay=LIBRARY_COPY_DELAY)


@pytest.fixture(scope="session", autouse=is_macos)
def setup_photos_sync():
if not TEST_SYNC:
return
copy_photos_library(TEST_LIBRARY_SYNC, delay=10)
copy_photos_library(TEST_LIBRARY_SYNC, delay=LIBRARY_COPY_DELAY)


@pytest.fixture(scope="session", autouse=is_macos)
def setup_photos_add_locations():
if not TEST_ADD_LOCATIONS:
return
copy_photos_library(TEST_LIBRARY_ADD_LOCATIONS, delay=10)
copy_photos_library(TEST_LIBRARY_ADD_LOCATIONS, delay=LIBRARY_COPY_DELAY)


@pytest.fixture(autouse=True)
Expand Down
63 changes: 59 additions & 4 deletions tests/test_cli_import.py
Original file line number Diff line number Diff line change
@@ -1,8 +1,11 @@
""" Tests which require user interaction to run for osxphotos import command; run with pytest --test-import """
""" Tests which require user interaction to run for osxphotos import command.
run with pytest tests/test_cli_import.py --test-import
"""

import csv
import datetime
import filecmp
import hashlib
import json
import os
import os.path
Expand All @@ -13,17 +16,17 @@
import sys
import time
import unicodedata
from string import Template
from tempfile import TemporaryDirectory
from typing import Dict
from zoneinfo import ZoneInfo
import hashlib

import pytest
from click.testing import CliRunner
from pytest import MonkeyPatch, approx

from osxphotos import PhotosDB, QueryOptions
from osxphotos._constants import UUID_PATTERN
from osxphotos._constants import OSXPHOTOS_EXPORT_DB, UUID_PATTERN
from osxphotos.datetime_utils import datetime_remove_tz, get_local_tz
from osxphotos.exiftool import get_exiftool_path
from osxphotos.platform import is_macos
Expand Down Expand Up @@ -63,7 +66,6 @@
TEST_LIVE_PHOTO_ORIGINAL_VIDEO = "IMG_1853.MOV"
TEST_LIVE_PHOTO_EDITED_VIDEO = "IMG_E1853.mov"
TEST_LIVE_PHOTO_AAE = "IMG_1853.AAE"
TEST_LIVE_PHOTO_GLOB = "*1853*"

TEST_DATA = {
TEST_IMAGE_1: {
Expand Down Expand Up @@ -1588,6 +1590,7 @@ def test_import_exportdb(tmp_path):
terminal_width=TERMINAL_WIDTH,
)
assert result.exit_code == 0
assert "Setting metadata and location from export database" in result.output
results = parse_import_output(result.output)
photosdb = PhotosDB()
photo = photosdb.query(QueryOptions(uuid=[results["wedding.jpg"]]))[0]
Expand All @@ -1598,6 +1601,58 @@ def test_import_exportdb(tmp_path):
assert "AlbumInFolder" in photo.albums


@pytest.mark.test_import
def test_import_exportdb_exportdir(tmp_path):
"""Test osxphotos import with --exportdb option and --exportdir"""

# first, export an image
runner = CliRunner()
result = runner.invoke(
export,
[
"--verbose",
str(tmp_path),
"--name",
"wedding.jpg",
"--library",
TEST_EXPORT_LIBRARY,
],
)
assert result.exit_code == 0

# move the export dir to a different directory
with TemporaryDirectory() as new_export_dir:
# need to get fully resolved path to the new temp dir as it may be /var but really -> /private/var
# which causes the exportdir lookup to fail
# this is a quirk of the test configuration
new_export_dir = str(pathlib.Path(new_export_dir).resolve().absolute())
shutil.copy(str(tmp_path / "wedding.jpg"), new_export_dir)

# now import that exported photo with --exportdb
result = runner.invoke(
import_main,
[
new_export_dir,
"--verbose",
"--exportdb",
str(tmp_path),
"--exportdir",
new_export_dir,
],
terminal_width=TERMINAL_WIDTH,
)
assert result.exit_code == 0
assert "Setting metadata and location from export database" in result.output
results = parse_import_output(result.output)
photosdb = PhotosDB()
photo = photosdb.query(QueryOptions(uuid=[results["wedding.jpg"]]))[0]
assert not photo.title
assert photo.description == "Bride Wedding day"
assert photo.keywords == ["wedding"]
assert "I have a deleted twin" in photo.albums
assert "AlbumInFolder" in photo.albums


@pytest.mark.test_import
def test_import_aae(tmp_path):
"""Test import with aae files; test that invalid AAE are ignored during import"""
Expand Down
Loading

0 comments on commit d04914a

Please sign in to comment.