Feature add import 754 (#762)
* Initial alpha version of import command * Refactored * Improved help, added --clear-metadata * Added --clear-metadata, --exiftool to import * Added --keyword, --title, --description * Added --location * Added test for --location * Changed --auto-folder to --split-folder, added docs * Added --walk, updated docs * Added --check-templates * Updated help text for import
This commit is contained in:
@@ -21,6 +21,12 @@ Some of the export tests rely on photos in my local library and will look for `O
|
||||
|
||||
One test for locale does not run on GitHub's automated workflow and will look for `OSXPHOTOS_TEST_LOCALE=1` to determine if it should be run. If you want to run this test, set the environment variable.
|
||||
|
||||
A couple of tests require interaction with Photos and configuring a specific test library. Currently these run only on Catalina. The tests must be specified by using a pytest flag. Only one of these interactive tests can be run at a time. The current flags are:
|
||||
|
||||
--addalbum: test --add-to-album options
|
||||
--timewarp: test `osxphotos timewarp`
|
||||
--test-import: test `osxphotos import`
|
||||
|
||||
## Test Photo Libraries
|
||||
**Important**: The test code uses several test photo libraries created on various version of MacOS. If you need to inspect one of these or modify one for a test, make a copy of the library (for example, copy it to your ~/Pictures folder) then open the copy in Photos. Once done, copy the revised library back to the tests/ folder. If you do not do this, the Photos background process photoanalysisd will forever try to process the library resulting in updates to the database which will cause git to see changes to the file you didn't intend. I'm not aware of any way to disassociate photoanalysisd from the library once you've opened it in Photos.
|
||||
|
||||
|
||||
@@ -15,6 +15,9 @@ from .test_catalina_10_15_7 import UUID_DICT_LOCAL
|
||||
# run timewarp tests (configured with --timewarp)
|
||||
TEST_TIMEWARP = False
|
||||
|
||||
# run import tests (configured with --import)
|
||||
TEST_IMPORT = False
|
||||
|
||||
# don't clean up crash logs (configured with --no-cleanup)
|
||||
NO_CLEANUP = False
|
||||
|
||||
@@ -42,6 +45,7 @@ def get_os_version():
|
||||
OS_VER = get_os_version()[1]
|
||||
if OS_VER == "15":
|
||||
TEST_LIBRARY = "tests/Test-10.15.7.photoslibrary"
|
||||
TEST_LIBRARY_IMPORT = TEST_LIBRARY
|
||||
from tests.config_timewarp_catalina import TEST_LIBRARY_TIMEWARP
|
||||
else:
|
||||
TEST_LIBRARY = None
|
||||
@@ -56,6 +60,13 @@ def setup_photos_timewarp():
|
||||
copy_photos_library(TEST_LIBRARY_TIMEWARP, delay=10)
|
||||
|
||||
|
||||
@pytest.fixture(scope="session", autouse=True)
|
||||
def setup_photos_import():
|
||||
if not TEST_IMPORT:
|
||||
return
|
||||
copy_photos_library(TEST_LIBRARY_IMPORT, delay=10)
|
||||
|
||||
|
||||
@pytest.fixture(autouse=True)
|
||||
def reset_singletons():
|
||||
"""Need to clean up any ExifTool singletons between tests"""
|
||||
@@ -72,6 +83,12 @@ def pytest_addoption(parser):
|
||||
parser.addoption(
|
||||
"--timewarp", action="store_true", default=False, help="run --timewarp tests"
|
||||
)
|
||||
parser.addoption(
|
||||
"--test-import",
|
||||
action="store_true",
|
||||
default=False,
|
||||
help="run `osxphotos import` tests",
|
||||
)
|
||||
parser.addoption(
|
||||
"--no-cleanup",
|
||||
action="store_true",
|
||||
@@ -81,8 +98,18 @@ def pytest_addoption(parser):
|
||||
|
||||
|
||||
def pytest_configure(config):
|
||||
if config.getoption("--addalbum") and config.getoption("--timewarp"):
|
||||
pytest.exit("--addalbum and --timewarp are mutually exclusive")
|
||||
if (
|
||||
sum(
|
||||
bool(x)
|
||||
for x in [
|
||||
config.getoption("--addalbum"),
|
||||
config.getoption("--timewarp"),
|
||||
config.getoption("--test-import"),
|
||||
]
|
||||
)
|
||||
> 1
|
||||
):
|
||||
pytest.exit("--addalbum, --timewarp, --test-import are mutually exclusive")
|
||||
|
||||
config.addinivalue_line(
|
||||
"markers", "addalbum: mark test as requiring --addalbum to run"
|
||||
@@ -90,12 +117,19 @@ def pytest_configure(config):
|
||||
config.addinivalue_line(
|
||||
"markers", "timewarp: mark test as requiring --timewarp to run"
|
||||
)
|
||||
config.addinivalue_line(
|
||||
"markers", "test_import: mark test as requiring --test-import to run"
|
||||
)
|
||||
|
||||
# this is hacky but I can't figure out how to check config options in other fixtures
|
||||
if config.getoption("--timewarp"):
|
||||
global TEST_TIMEWARP
|
||||
TEST_TIMEWARP = True
|
||||
|
||||
if config.getoption("--test-import"):
|
||||
global TEST_IMPORT
|
||||
TEST_IMPORT = True
|
||||
|
||||
if config.getoption("--no-cleanup"):
|
||||
global NO_CLEANUP
|
||||
NO_CLEANUP = True
|
||||
@@ -118,6 +152,14 @@ def pytest_collection_modifyitems(config, items):
|
||||
if "timewarp" in item.keywords:
|
||||
item.add_marker(skip_timewarp)
|
||||
|
||||
if not (config.getoption("--test-import") and TEST_LIBRARY_IMPORT is not None):
|
||||
skip_test_import = pytest.mark.skip(
|
||||
reason="need --test-import option and MacOS Catalina to run"
|
||||
)
|
||||
for item in items:
|
||||
if "test_import" in item.keywords:
|
||||
item.add_marker(skip_test_import)
|
||||
|
||||
|
||||
def copy_photos_library(photos_library, delay=0):
|
||||
"""copy the test library and open Photos, returns path to copied library"""
|
||||
|
||||
BIN
tests/test-images/IMG_4179.jpeg
Normal file
BIN
tests/test-images/IMG_4179.jpeg
Normal file
Binary file not shown.
|
After Width: | Height: | Size: 3.2 MiB |
BIN
tests/test-images/Jellyfish.mov
Normal file
BIN
tests/test-images/Jellyfish.mov
Normal file
Binary file not shown.
646
tests/test_cli_import.py
Normal file
646
tests/test_cli_import.py
Normal file
@@ -0,0 +1,646 @@
|
||||
""" Tests which require user interaction to run for osxphotos import command; run with pytest --test-import """
|
||||
|
||||
import os
|
||||
import os.path
|
||||
import pathlib
|
||||
import re
|
||||
import time
|
||||
from typing import Dict
|
||||
|
||||
import pytest
|
||||
from click.testing import CliRunner
|
||||
from photoscript import Photo
|
||||
from pytest import approx
|
||||
|
||||
from osxphotos.cli.import_cli import import_cli
|
||||
from osxphotos.exiftool import ExifTool, get_exiftool_path
|
||||
from tests.conftest import get_os_version
|
||||
|
||||
TERMINAL_WIDTH = 250
|
||||
|
||||
TEST_IMAGES_DIR = "tests/test-images"
|
||||
TEST_IMAGE_1 = "tests/test-images/IMG_4179.jpeg"
|
||||
TEST_IMAGE_2 = "tests/test-images/faceinfo/exif1.jpg"
|
||||
TEST_VIDEO_1 = "tests/test-images/Jellyfish.mov"
|
||||
TEST_VIDEO_2 = "tests/test-images/IMG_0670B_NOGPS.MOV"
|
||||
|
||||
TEST_DATA = {
|
||||
TEST_IMAGE_1: {
|
||||
"title": "Waves crashing on rocks",
|
||||
"description": "Used for testing osxphotos",
|
||||
"keywords": ["osxphotos"],
|
||||
"lat": 33.7150638888889,
|
||||
"lon": -118.319672222222,
|
||||
"check_templates": [
|
||||
"exiftool title: Waves crashing on rocks",
|
||||
"exiftool description: Used for testing osxphotos",
|
||||
"exiftool keywords: ['osxphotos']",
|
||||
"exiftool location: (33.7150638888889, -118.319672222222)",
|
||||
"title: {exiftool:XMP:Title}: Waves crashing on rocks",
|
||||
"description: {exiftool:IPTC:Caption-Abstract}: Used for testing osxphotos",
|
||||
"keyword: {exiftool:IPTC:Keywords}: ['osxphotos']",
|
||||
"album: {filepath.parent}: test-images",
|
||||
],
|
||||
},
|
||||
TEST_VIDEO_1: {
|
||||
"title": "Jellyfish",
|
||||
"description": "Jellyfish Video",
|
||||
# "keywords": ["Travel"], # exiftool doesn't seem to support the binary QuickTime:Keywords
|
||||
"keywords": [],
|
||||
"lat": 34.0533,
|
||||
"lon": -118.2423,
|
||||
},
|
||||
TEST_VIDEO_2: {
|
||||
"title": "",
|
||||
"description": "",
|
||||
"lat": None,
|
||||
"lon": None,
|
||||
},
|
||||
TEST_IMAGE_2: {
|
||||
"albums": ["faceinfo"],
|
||||
},
|
||||
}
|
||||
|
||||
# set timezone to avoid issues with comparing dates
|
||||
os.environ["TZ"] = "US/Pacific"
|
||||
time.tzset()
|
||||
|
||||
|
||||
# determine if exiftool installed so exiftool tests can be skipped
|
||||
try:
|
||||
exiftool_path = get_exiftool_path()
|
||||
except FileNotFoundError:
|
||||
exiftool_path = None
|
||||
|
||||
OS_VER = get_os_version()[1]
|
||||
if OS_VER != "15":
|
||||
pytest.skip(allow_module_level=True)
|
||||
|
||||
|
||||
def prompt(message):
|
||||
"""Helper function for tests that require user input"""
|
||||
message = f"\n{message}\nPlease answer y/n: "
|
||||
answer = input(message)
|
||||
return answer.lower() == "y"
|
||||
|
||||
|
||||
def say(msg: str) -> None:
|
||||
"""Say message with text to speech"""
|
||||
os.system(f"say {msg}")
|
||||
|
||||
|
||||
def parse_import_output(output: str) -> Dict[str, str]:
|
||||
"""Parse output of osxphotos import command and return dict of {image name: uuid} for imported photos"""
|
||||
# look for lines that look like this:
|
||||
# Imported IMG_4179.jpeg with UUID A62792F0-4524-4529-9931-56E52C95E873
|
||||
|
||||
results = {}
|
||||
for line in output.split("\n"):
|
||||
pattern = re.compile(
|
||||
r"Imported ([\w\.]+)\s.*UUID\s([0-9a-fA-F]{8}\b-[0-9a-fA-F]{4}\b-[0-9a-fA-F]{4}\b-[0-9a-fA-F]{4}\b-[0-9a-fA-F]{12})"
|
||||
)
|
||||
if match := re.match(pattern, line):
|
||||
file = match[1]
|
||||
uuid = match[2]
|
||||
results[file] = uuid
|
||||
return results
|
||||
|
||||
|
||||
########## Interactive tests run first ##########
|
||||
|
||||
|
||||
@pytest.mark.test_import
|
||||
def test_import():
|
||||
"""Test basic import"""
|
||||
cwd = os.getcwd()
|
||||
test_image_1 = os.path.join(cwd, TEST_IMAGE_1)
|
||||
runner = CliRunner()
|
||||
result = runner.invoke(
|
||||
import_cli,
|
||||
["--verbose", test_image_1],
|
||||
terminal_width=TERMINAL_WIDTH,
|
||||
)
|
||||
|
||||
assert result.exit_code == 0
|
||||
|
||||
import_data = parse_import_output(result.output)
|
||||
file_1 = pathlib.Path(test_image_1).name
|
||||
uuid_1 = import_data[file_1]
|
||||
photo_1 = Photo(uuid_1)
|
||||
|
||||
assert photo_1.filename == file_1
|
||||
|
||||
|
||||
@pytest.mark.test_import
|
||||
def test_import_dup_check():
|
||||
"""Test basic import with --dup-check"""
|
||||
say("Please click Import when prompted by Photos to import duplicate photo.")
|
||||
|
||||
cwd = os.getcwd()
|
||||
test_image_1 = os.path.join(cwd, TEST_IMAGE_1)
|
||||
runner = CliRunner()
|
||||
result = runner.invoke(
|
||||
import_cli,
|
||||
["--verbose", "--dup-check", test_image_1],
|
||||
terminal_width=TERMINAL_WIDTH,
|
||||
)
|
||||
|
||||
assert result.exit_code == 0
|
||||
|
||||
import_data = parse_import_output(result.output)
|
||||
file_1 = pathlib.Path(test_image_1).name
|
||||
uuid_1 = import_data[file_1]
|
||||
photo_1 = Photo(uuid_1)
|
||||
|
||||
assert photo_1.filename == file_1
|
||||
|
||||
|
||||
@pytest.mark.test_import
|
||||
def test_import_album():
|
||||
"""Test basic import to an album"""
|
||||
cwd = os.getcwd()
|
||||
test_image_1 = os.path.join(cwd, TEST_IMAGE_1)
|
||||
runner = CliRunner()
|
||||
result = runner.invoke(
|
||||
import_cli,
|
||||
["--verbose", "--album", "My New Album", test_image_1],
|
||||
terminal_width=TERMINAL_WIDTH,
|
||||
)
|
||||
|
||||
assert result.exit_code == 0
|
||||
|
||||
import_data = parse_import_output(result.output)
|
||||
file_1 = pathlib.Path(test_image_1).name
|
||||
uuid_1 = import_data[file_1]
|
||||
photo_1 = Photo(uuid_1)
|
||||
|
||||
assert photo_1.filename == file_1
|
||||
albums = photo_1.albums
|
||||
assert len(albums) == 1
|
||||
assert albums[0].title == "My New Album"
|
||||
|
||||
|
||||
@pytest.mark.test_import
|
||||
def test_import_album_2():
|
||||
"""Test basic import to an album with a "/" in it"""
|
||||
cwd = os.getcwd()
|
||||
test_image_1 = os.path.join(cwd, TEST_IMAGE_1)
|
||||
runner = CliRunner()
|
||||
result = runner.invoke(
|
||||
import_cli,
|
||||
["--verbose", "--album", "Folder/My New Album", test_image_1],
|
||||
terminal_width=TERMINAL_WIDTH,
|
||||
)
|
||||
|
||||
assert result.exit_code == 0
|
||||
|
||||
import_data = parse_import_output(result.output)
|
||||
file_1 = pathlib.Path(test_image_1).name
|
||||
uuid_1 = import_data[file_1]
|
||||
photo_1 = Photo(uuid_1)
|
||||
|
||||
assert photo_1.filename == file_1
|
||||
albums = photo_1.albums
|
||||
assert len(albums) == 1
|
||||
assert albums[0].title == "Folder/My New Album"
|
||||
assert albums[0].path_str() == "Folder/My New Album"
|
||||
|
||||
|
||||
@pytest.mark.test_import
|
||||
def test_import_album_slit_folder():
|
||||
"""Test basic import to an album with a "/" in it and --split-folder"""
|
||||
cwd = os.getcwd()
|
||||
test_image_1 = os.path.join(cwd, TEST_IMAGE_1)
|
||||
runner = CliRunner()
|
||||
result = runner.invoke(
|
||||
import_cli,
|
||||
[
|
||||
"--verbose",
|
||||
"--album",
|
||||
"Folder/My New Album",
|
||||
"--split-folder",
|
||||
"/",
|
||||
test_image_1,
|
||||
],
|
||||
terminal_width=TERMINAL_WIDTH,
|
||||
)
|
||||
|
||||
assert result.exit_code == 0
|
||||
|
||||
import_data = parse_import_output(result.output)
|
||||
file_1 = pathlib.Path(test_image_1).name
|
||||
uuid_1 = import_data[file_1]
|
||||
photo_1 = Photo(uuid_1)
|
||||
|
||||
assert photo_1.filename == file_1
|
||||
albums = photo_1.albums
|
||||
assert len(albums) == 1
|
||||
assert albums[0].title == "My New Album"
|
||||
assert albums[0].path_str() == "Folder/My New Album"
|
||||
|
||||
|
||||
@pytest.mark.test_import
|
||||
def test_import_album_relative_to():
|
||||
"""Test import with --relative-to"""
|
||||
cwd = os.getcwd()
|
||||
test_image_1 = os.path.join(cwd, TEST_IMAGE_1)
|
||||
runner = CliRunner()
|
||||
result = runner.invoke(
|
||||
import_cli,
|
||||
[
|
||||
"--verbose",
|
||||
"--album",
|
||||
"{filepath.parent}",
|
||||
"--split-folder",
|
||||
"/",
|
||||
"--relative-to",
|
||||
cwd,
|
||||
test_image_1,
|
||||
],
|
||||
terminal_width=TERMINAL_WIDTH,
|
||||
)
|
||||
|
||||
assert result.exit_code == 0
|
||||
|
||||
import_data = parse_import_output(result.output)
|
||||
file_1 = pathlib.Path(test_image_1).name
|
||||
uuid_1 = import_data[file_1]
|
||||
photo_1 = Photo(uuid_1)
|
||||
|
||||
assert photo_1.filename == file_1
|
||||
albums = photo_1.albums
|
||||
assert len(albums) == 1
|
||||
assert albums[0].title == "test-images"
|
||||
assert albums[0].path_str() == "tests/test-images"
|
||||
|
||||
|
||||
@pytest.mark.test_import
|
||||
def test_import_clear_metadata():
|
||||
"""Test import with --clear-metadata"""
|
||||
cwd = os.getcwd()
|
||||
test_image_1 = os.path.join(cwd, TEST_IMAGE_1)
|
||||
runner = CliRunner()
|
||||
result = runner.invoke(
|
||||
import_cli,
|
||||
[
|
||||
"--verbose",
|
||||
"--clear-metadata",
|
||||
test_image_1,
|
||||
],
|
||||
terminal_width=TERMINAL_WIDTH,
|
||||
)
|
||||
|
||||
assert result.exit_code == 0
|
||||
|
||||
import_data = parse_import_output(result.output)
|
||||
file_1 = pathlib.Path(test_image_1).name
|
||||
uuid_1 = import_data[file_1]
|
||||
photo_1 = Photo(uuid_1)
|
||||
|
||||
assert photo_1.filename == file_1
|
||||
assert not photo_1.title
|
||||
assert not photo_1.description
|
||||
assert not photo_1.keywords
|
||||
|
||||
|
||||
@pytest.mark.skipif(exiftool_path is None, reason="exiftool not installed")
|
||||
@pytest.mark.test_import
|
||||
def test_import_exiftool():
|
||||
"""Test import file with --exiftool"""
|
||||
cwd = os.getcwd()
|
||||
test_image_1 = os.path.join(cwd, TEST_IMAGE_1)
|
||||
runner = CliRunner()
|
||||
result = runner.invoke(
|
||||
import_cli,
|
||||
[
|
||||
"--verbose",
|
||||
"--clear-metadata",
|
||||
"--exiftool",
|
||||
test_image_1,
|
||||
],
|
||||
terminal_width=TERMINAL_WIDTH,
|
||||
)
|
||||
|
||||
assert result.exit_code == 0
|
||||
|
||||
import_data = parse_import_output(result.output)
|
||||
file_1 = pathlib.Path(test_image_1).name
|
||||
uuid_1 = import_data[file_1]
|
||||
photo_1 = Photo(uuid_1)
|
||||
|
||||
assert photo_1.filename == file_1
|
||||
assert photo_1.title == TEST_DATA[TEST_IMAGE_1]["title"]
|
||||
assert photo_1.description == TEST_DATA[TEST_IMAGE_1]["description"]
|
||||
assert photo_1.keywords == TEST_DATA[TEST_IMAGE_1]["keywords"]
|
||||
lat, lon = photo_1.location
|
||||
assert lat == approx(TEST_DATA[TEST_IMAGE_1]["lat"])
|
||||
assert lon == approx(TEST_DATA[TEST_IMAGE_1]["lon"])
|
||||
|
||||
|
||||
@pytest.mark.skipif(exiftool_path is None, reason="exiftool not installed")
|
||||
@pytest.mark.test_import
|
||||
def test_import_exiftool_video():
|
||||
"""Test import video file with --exiftool"""
|
||||
cwd = os.getcwd()
|
||||
test_image_1 = os.path.join(cwd, TEST_VIDEO_1)
|
||||
runner = CliRunner()
|
||||
result = runner.invoke(
|
||||
import_cli,
|
||||
[
|
||||
"--verbose",
|
||||
"--clear-metadata",
|
||||
"--exiftool",
|
||||
test_image_1,
|
||||
],
|
||||
terminal_width=TERMINAL_WIDTH,
|
||||
)
|
||||
|
||||
assert result.exit_code == 0
|
||||
|
||||
import_data = parse_import_output(result.output)
|
||||
file_1 = pathlib.Path(test_image_1).name
|
||||
uuid_1 = import_data[file_1]
|
||||
photo_1 = Photo(uuid_1)
|
||||
|
||||
assert photo_1.filename == file_1
|
||||
assert photo_1.title == TEST_DATA[TEST_VIDEO_1]["title"]
|
||||
assert photo_1.description == TEST_DATA[TEST_VIDEO_1]["description"]
|
||||
assert photo_1.keywords == TEST_DATA[TEST_VIDEO_1]["keywords"]
|
||||
lat, lon = photo_1.location
|
||||
assert lat == approx(TEST_DATA[TEST_VIDEO_1]["lat"])
|
||||
assert lon == approx(TEST_DATA[TEST_VIDEO_1]["lon"])
|
||||
|
||||
|
||||
@pytest.mark.skipif(exiftool_path is None, reason="exiftool not installed")
|
||||
@pytest.mark.test_import
|
||||
def test_import_exiftool_video_no_metadata():
|
||||
"""Test import video file with --exiftool that has no metadata"""
|
||||
cwd = os.getcwd()
|
||||
test_image_1 = os.path.join(cwd, TEST_VIDEO_2)
|
||||
runner = CliRunner()
|
||||
result = runner.invoke(
|
||||
import_cli,
|
||||
[
|
||||
"--verbose",
|
||||
"--clear-metadata",
|
||||
"--exiftool",
|
||||
test_image_1,
|
||||
],
|
||||
terminal_width=TERMINAL_WIDTH,
|
||||
)
|
||||
|
||||
assert result.exit_code == 0
|
||||
|
||||
import_data = parse_import_output(result.output)
|
||||
file_1 = pathlib.Path(test_image_1).name
|
||||
uuid_1 = import_data[file_1]
|
||||
photo_1 = Photo(uuid_1)
|
||||
|
||||
assert photo_1.filename == file_1
|
||||
assert photo_1.title == ""
|
||||
assert photo_1.description == ""
|
||||
assert photo_1.keywords == []
|
||||
lat, lon = photo_1.location
|
||||
assert lat is None
|
||||
assert lon is None
|
||||
|
||||
|
||||
@pytest.mark.skipif(exiftool_path is None, reason="exiftool not installed")
|
||||
@pytest.mark.test_import
|
||||
def test_import_title():
|
||||
"""Test import with --title"""
|
||||
cwd = os.getcwd()
|
||||
test_image_1 = os.path.join(cwd, TEST_IMAGE_1)
|
||||
runner = CliRunner()
|
||||
result = runner.invoke(
|
||||
import_cli,
|
||||
[
|
||||
"--verbose",
|
||||
"--clear-metadata",
|
||||
"--title",
|
||||
"{exiftool:XMP:Title|upper}",
|
||||
test_image_1,
|
||||
],
|
||||
terminal_width=TERMINAL_WIDTH,
|
||||
)
|
||||
|
||||
assert result.exit_code == 0
|
||||
|
||||
import_data = parse_import_output(result.output)
|
||||
file_1 = pathlib.Path(test_image_1).name
|
||||
uuid_1 = import_data[file_1]
|
||||
photo_1 = Photo(uuid_1)
|
||||
|
||||
assert photo_1.filename == file_1
|
||||
assert photo_1.title == TEST_DATA[TEST_IMAGE_1]["title"].upper()
|
||||
|
||||
|
||||
@pytest.mark.skipif(exiftool_path is None, reason="exiftool not installed")
|
||||
@pytest.mark.test_import
|
||||
def test_import_description():
|
||||
"""Test import with --description"""
|
||||
cwd = os.getcwd()
|
||||
test_image_1 = os.path.join(cwd, TEST_IMAGE_1)
|
||||
runner = CliRunner()
|
||||
result = runner.invoke(
|
||||
import_cli,
|
||||
[
|
||||
"--verbose",
|
||||
"--clear-metadata",
|
||||
"--description",
|
||||
"{exiftool:XMP:Description|upper}",
|
||||
test_image_1,
|
||||
],
|
||||
terminal_width=TERMINAL_WIDTH,
|
||||
)
|
||||
|
||||
assert result.exit_code == 0
|
||||
|
||||
import_data = parse_import_output(result.output)
|
||||
file_1 = pathlib.Path(test_image_1).name
|
||||
uuid_1 = import_data[file_1]
|
||||
photo_1 = Photo(uuid_1)
|
||||
|
||||
assert photo_1.filename == file_1
|
||||
assert photo_1.description == TEST_DATA[TEST_IMAGE_1]["description"].upper()
|
||||
|
||||
|
||||
@pytest.mark.test_import
|
||||
def test_import_keyword():
|
||||
"""Test import with --keyword"""
|
||||
cwd = os.getcwd()
|
||||
test_image_1 = os.path.join(cwd, TEST_IMAGE_1)
|
||||
runner = CliRunner()
|
||||
result = runner.invoke(
|
||||
import_cli,
|
||||
[
|
||||
"--verbose",
|
||||
"--keyword",
|
||||
"Bar",
|
||||
"--keyword",
|
||||
"Foo",
|
||||
test_image_1,
|
||||
],
|
||||
terminal_width=TERMINAL_WIDTH,
|
||||
)
|
||||
|
||||
assert result.exit_code == 0
|
||||
|
||||
import_data = parse_import_output(result.output)
|
||||
file_1 = pathlib.Path(test_image_1).name
|
||||
uuid_1 = import_data[file_1]
|
||||
photo_1 = Photo(uuid_1)
|
||||
|
||||
assert photo_1.filename == file_1
|
||||
assert sorted(photo_1.keywords) == ["Bar", "Foo"]
|
||||
|
||||
|
||||
@pytest.mark.skipif(exiftool_path is None, reason="exiftool not installed")
|
||||
@pytest.mark.test_import
|
||||
def test_import_keyword_merge():
|
||||
"""Test import with --keyword and --merge-keywords"""
|
||||
cwd = os.getcwd()
|
||||
test_image_1 = os.path.join(cwd, TEST_IMAGE_1)
|
||||
runner = CliRunner()
|
||||
result = runner.invoke(
|
||||
import_cli,
|
||||
[
|
||||
"--verbose",
|
||||
"--clear-metadata",
|
||||
"--exiftool",
|
||||
"--keyword",
|
||||
"Bar",
|
||||
"--keyword",
|
||||
"Foo",
|
||||
"--merge-keywords",
|
||||
test_image_1,
|
||||
],
|
||||
terminal_width=TERMINAL_WIDTH,
|
||||
)
|
||||
|
||||
assert result.exit_code == 0
|
||||
|
||||
import_data = parse_import_output(result.output)
|
||||
file_1 = pathlib.Path(test_image_1).name
|
||||
uuid_1 = import_data[file_1]
|
||||
photo_1 = Photo(uuid_1)
|
||||
|
||||
assert photo_1.filename == file_1
|
||||
assert sorted(photo_1.keywords) == ["Bar", "Foo", "osxphotos"]
|
||||
|
||||
|
||||
@pytest.mark.test_import
|
||||
def test_import_location():
|
||||
"""Test import file with --location"""
|
||||
cwd = os.getcwd()
|
||||
test_image_1 = os.path.join(cwd, TEST_IMAGE_1)
|
||||
runner = CliRunner()
|
||||
result = runner.invoke(
|
||||
import_cli,
|
||||
[
|
||||
"--verbose",
|
||||
"--clear-metadata",
|
||||
"--location",
|
||||
"-45.0",
|
||||
"-45.0",
|
||||
test_image_1,
|
||||
],
|
||||
terminal_width=TERMINAL_WIDTH,
|
||||
)
|
||||
|
||||
assert result.exit_code == 0
|
||||
|
||||
import_data = parse_import_output(result.output)
|
||||
file_1 = pathlib.Path(test_image_1).name
|
||||
uuid_1 = import_data[file_1]
|
||||
photo_1 = Photo(uuid_1)
|
||||
|
||||
assert photo_1.filename == file_1
|
||||
lat, lon = photo_1.location
|
||||
assert lat == approx(-45.0)
|
||||
assert lon == approx(-45.0)
|
||||
|
||||
|
||||
@pytest.mark.test_import
|
||||
def test_import_glob():
|
||||
"""Test import with --glob"""
|
||||
cwd = os.getcwd()
|
||||
test_image_1 = os.path.join(cwd, TEST_IMAGE_1)
|
||||
runner = CliRunner()
|
||||
result = runner.invoke(
|
||||
import_cli,
|
||||
["--verbose", f"{cwd}/{TEST_IMAGES_DIR}/", "--walk", "--glob", "Pumpk*.jpg"],
|
||||
terminal_width=TERMINAL_WIDTH,
|
||||
)
|
||||
|
||||
assert result.exit_code == 0
|
||||
assert "imported 2 files" in result.output
|
||||
|
||||
|
||||
@pytest.mark.test_import
|
||||
def test_import_glob_walk():
|
||||
"""Test import with --walk --glob"""
|
||||
cwd = os.getcwd()
|
||||
test_image_1 = os.path.join(cwd, TEST_IMAGE_1)
|
||||
runner = CliRunner()
|
||||
result = runner.invoke(
|
||||
import_cli,
|
||||
[
|
||||
"--verbose",
|
||||
f"{cwd}/{TEST_IMAGES_DIR}/",
|
||||
"--walk",
|
||||
"--glob",
|
||||
"exif*.jpg",
|
||||
"--album",
|
||||
"{filepath.parent.name}",
|
||||
"--relative-to",
|
||||
f"{cwd}/{TEST_IMAGES_DIR}",
|
||||
],
|
||||
terminal_width=TERMINAL_WIDTH,
|
||||
)
|
||||
assert result.exit_code == 0
|
||||
assert "imported 4 files" in result.output
|
||||
|
||||
import_data = parse_import_output(result.output)
|
||||
file_1 = pathlib.Path(TEST_IMAGE_2).name
|
||||
uuid_1 = import_data[file_1]
|
||||
photo_1 = Photo(uuid_1)
|
||||
|
||||
assert photo_1.filename == file_1
|
||||
assert [a.title for a in photo_1.albums] == TEST_DATA[TEST_IMAGE_2]["albums"]
|
||||
|
||||
|
||||
@pytest.mark.skipif(exiftool_path is None, reason="exiftool not installed")
|
||||
@pytest.mark.test_import
|
||||
def test_import_check_templates():
|
||||
"""Test import file with --check-templates"""
|
||||
cwd = os.getcwd()
|
||||
test_image_1 = os.path.join(cwd, TEST_IMAGE_1)
|
||||
runner = CliRunner()
|
||||
result = runner.invoke(
|
||||
import_cli,
|
||||
[
|
||||
"--verbose",
|
||||
"--exiftool",
|
||||
"--title",
|
||||
"{exiftool:XMP:Title}",
|
||||
"--description",
|
||||
"{exiftool:IPTC:Caption-Abstract}",
|
||||
"--keyword",
|
||||
"{exiftool:IPTC:Keywords}",
|
||||
"--album",
|
||||
"{filepath.parent}",
|
||||
"--relative-to",
|
||||
f"{cwd}/tests",
|
||||
"--check-templates",
|
||||
test_image_1,
|
||||
],
|
||||
terminal_width=TERMINAL_WIDTH,
|
||||
)
|
||||
# assert result.output == "foo"
|
||||
assert result.exit_code == 0
|
||||
output = result.output.splitlines()
|
||||
output.pop(0)
|
||||
|
||||
for idx, line in enumerate(output):
|
||||
assert line == TEST_DATA[TEST_IMAGE_1]["check_templates"][idx]
|
||||
@@ -1228,6 +1228,9 @@ def test_filepath():
|
||||
rendered, _ = template.render("{filepath.parent}", options)
|
||||
assert rendered[0] == "/foo"
|
||||
|
||||
rendered, _ = template.render("{filepath.parent.name}", options)
|
||||
assert rendered[0] == "foo"
|
||||
|
||||
rendered, _ = template.render("{filepath.stem}", options)
|
||||
assert rendered[0] == "bar"
|
||||
|
||||
|
||||
Reference in New Issue
Block a user