import shutil from pathlib import Path from unittest.mock import patch import pytest from photools3.metadata_writer import ( MetadataWriter, _is_prefixed, _prefixed, ) from photools3.settings import settings from test.test_photo import PATHS_DNG_EXAMPLES PREFIX = settings.metadata_prefix @pytest.fixture def make_dng_file(tmp_path): def _make(src: Path) -> Path: dst = tmp_path / f"copy_{src.name}" shutil.copy(src, dst) return dst return _make @pytest.fixture def metadata_writer_on_existing(make_dng_file): metadata_writer = MetadataWriter(make_dng_file(PATHS_DNG_EXAMPLES[0])) metadata_writer.write_caption( "Let's pretend this is the caption of the original image", include_prefix=False, ) return metadata_writer @pytest.mark.parametrize("pic", PATHS_DNG_EXAMPLES) def test_read_metadata(make_dng_file, pic): """primitive test for `MetadataWriter`""" file_path = make_dng_file(pic) result = MetadataWriter(file_path) assert result assert result.metadata assert isinstance(result.metadata, dict) @pytest.mark.parametrize( "pic, expected_keywords", [ (PATHS_DNG_EXAMPLES[0], ["architektur", "zeit|abend"]), (PATHS_DNG_EXAMPLES[1], ["architektur"]), (PATHS_DNG_EXAMPLES[2], ["morgen", "tiere|vögel"]), ( PATHS_DNG_EXAMPLES[3], [ "abend", "architektur", "bewegungsunschärfe", "lichter", "mainz", "spiegelung", "wasser", ], ), ( PATHS_DNG_EXAMPLES[4], ["grau", "landschaft", "landschaft|weinberge", "personen", "tiefe", "weg"], ), ], ) def test_get_keywords(make_dng_file, pic, expected_keywords): path = make_dng_file(pic) result = MetadataWriter(path).get_keywords() assert sorted(result) == sorted(expected_keywords) def test_prefixed(): txt = "bla_bla" txt_prefixed = _prefixed(txt) assert txt_prefixed == f"{PREFIX}bla_bla" @pytest.mark.parametrize( "txt, expected_result", [ (f"{PREFIX}some text", True), ("prefix is missing here", False), ("some_perfix|but not the right one", False), ], ) def test_is_prefixed(txt, expected_result): assert _is_prefixed(txt) == expected_result @pytest.mark.parametrize("pic", PATHS_DNG_EXAMPLES) def test_read_metadata_sidecar_path(make_dng_file, pic): file_path = make_dng_file(pic) metadata_writer = MetadataWriter(file_path) assert isinstance(metadata_writer.sidecar_path, Path) assert str(metadata_writer.sidecar_path).lower().endswith(".dng.xmp") # file does not exits yet, only the name assert not metadata_writer.sidecar_path.exists() @pytest.mark.parametrize("pic", PATHS_DNG_EXAMPLES) def test_set_caption(make_dng_file, pic): metadata_writer = MetadataWriter(make_dng_file(pic)) assert not metadata_writer.sidecar_path.exists() # sidecar does not exist yet ... metadata_writer.write_caption("This is a test") assert metadata_writer.sidecar_path.exists() # ... but now! result = metadata_writer.get_caption() assert result assert result == f"{PREFIX}This is a test" def test_set_caption_on_existing(metadata_writer_on_existing): assert ( metadata_writer_on_existing.get_caption() == "Let's pretend this is the caption of the original image" ) metadata_writer_on_existing.write_caption("Another caption") assert ( metadata_writer_on_existing.get_caption() == f"Let's pretend this is the caption of the original image. {PREFIX}Another caption" ) metadata_writer_on_existing.write_caption("Updated caption") assert ( metadata_writer_on_existing.get_caption() == f"Let's pretend this is the caption of the original image. {PREFIX}Updated caption" ) metadata_writer_on_existing.write_caption("Don't do this!", override=False) assert ( metadata_writer_on_existing.get_caption() == f"Let's pretend this is the caption of the original image. {PREFIX}Updated caption" ) @pytest.mark.parametrize( "pic, expected_keywords", [ ( PATHS_DNG_EXAMPLES[0], [ "architektur", "zeit|abend", f"{PREFIX}hund", f"{PREFIX}katze", f"{PREFIX}maus", ], ), ( PATHS_DNG_EXAMPLES[1], ["architektur", f"{PREFIX}hund", f"{PREFIX}katze", f"{PREFIX}maus"], ), ( PATHS_DNG_EXAMPLES[2], [ "morgen", "tiere|vögel", f"{PREFIX}hund", f"{PREFIX}katze", f"{PREFIX}maus", ], ), ( PATHS_DNG_EXAMPLES[3], [ "abend", "architektur", "bewegungsunschärfe", "lichter", "mainz", "spiegelung", "wasser", f"{PREFIX}hund", f"{PREFIX}katze", f"{PREFIX}maus", ], ), ( PATHS_DNG_EXAMPLES[4], [ "grau", "landschaft", "landschaft|weinberge", "personen", "tiefe", "weg", f"{PREFIX}hund", f"{PREFIX}katze", f"{PREFIX}maus", ], ), ], ) def test_set_keywords(make_dng_file, pic, expected_keywords): metadata_writer = MetadataWriter(make_dng_file(pic)) metadata_writer.write_keywords(["hund", "katze", "maus"]) result = metadata_writer.get_keywords() assert result assert isinstance(result, list) assert f"{PREFIX}hund" in result assert f"{PREFIX}katze" in result assert f"{PREFIX}maus" in result def test_set_keywords_on_existing(): # TODO ... def test_write_caption_and_keywords_in_one_metadata_update(tmp_path): writer = MetadataWriter(tmp_path / "photo.jpg") with ( patch.object(writer, "get_caption", return_value="Original caption"), patch.object(writer, "get_keywords", return_value=["existing"]), patch.object(writer, "set_metadata") as set_metadata, ): writer.write(caption="Generated caption", keywords=["architektur"]) set_metadata.assert_called_once() metadata_tags = set_metadata.call_args.args[0] assert metadata_tags["XMP:Description"] == ( f"Original caption. {PREFIX}Generated caption" ) assert set(metadata_tags["XMP:Subject"]) == { "existing", f"{PREFIX}architektur", } def test_set_metadata_surfaces_write_failures(tmp_path): writer = MetadataWriter(tmp_path / "photo.jpg") with patch("photools3.metadata_writer.exiftool.ExifToolHelper") as exif: exif_instance = exif.return_value.__enter__.return_value exif_instance.execute.side_effect = OSError("ExifTool failed") with pytest.raises(RuntimeError, match="Could not write metadata"): writer.set_metadata({"XMP:Description": "caption"})