From 1366e3b3e76a5c9856a7a6b14a27836de9aa1903 Mon Sep 17 00:00:00 2001 From: Mathias Scheider Date: Sun, 4 Oct 2026 07:54:33 +0200 Subject: [PATCH] metadata-writer: first skeleton --- pyproject.toml | 1 + src/photools3/metadata_writer.py | 46 ++++++++++++++++++++++++++++++++ uv.lock | 13 ++++++++- 3 files changed, 59 insertions(+), 1 deletion(-) create mode 100644 src/photools3/metadata_writer.py diff --git a/pyproject.toml b/pyproject.toml index e004ca3..b758f24 100644 --- a/pyproject.toml +++ b/pyproject.toml @@ -13,6 +13,7 @@ dependencies = [ "pillow>=12.3.0", "pydantic>=2.13.5", "pydantic-settings>=2.15.0", + "pyexiftool>=0.5.6", "pytest>=9.1.1", "ruff>=0.16.9", "typer>=0.12.0", diff --git a/src/photools3/metadata_writer.py b/src/photools3/metadata_writer.py new file mode 100644 index 0000000..6f92e91 --- /dev/null +++ b/src/photools3/metadata_writer.py @@ -0,0 +1,46 @@ +import logging +from pathlib import Path + +log = logging.getLogger(__name__) + +# File extensions that should use XMP sidecars +SIDECAR_EXTENSIONS = {".dng", ".nef", ".cr2", ".cr3", ".arw", ".raf", ".orf", ".rw2"} + + +type Caption = str | None +type Keywords = list[str] | None +type Color = str | None + + +class MetadataWriter: + """ + Write metadata (captions and keywords) to image files. + For DNG files, creates/updates XMP sidecar files. + """ + + def __init__( + self, force_sidecar: bool = False, append: bool = True, prefix: str = "photools" + ): + self.force_sidecar = force_sidecar + self.append = append + self.prefix = prefix + + def write_caption(self, path: Path, caption: Caption): ... + + def write_keywords(self, path: Path, keywords: Keywords): ... + + def write_color(self, path: Path, color: Color): ... + + def write( + self, + path: Path, + caption: Caption = None, + keywords: Keywords = None, + color: Color = None, + ): + if caption is not None: + self.write_caption(path, caption) + if keywords is not None: + self.write_keywords(path, keywords) + if color is not None: + self.write_color(path, color) diff --git a/uv.lock b/uv.lock index 4f2abe4..dca5d71 100644 --- a/uv.lock +++ b/uv.lock @@ -1,5 +1,5 @@ version = 1 -revision = 3 +revision = 5 requires-python = ">=3.14" [[package]] @@ -580,6 +580,7 @@ dependencies = [ { name = "pillow" }, { name = "pydantic" }, { name = "pydantic-settings" }, + { name = "pyexiftool" }, { name = "pytest" }, { name = "ruff" }, { name = "typer" }, @@ -593,6 +594,7 @@ requires-dist = [ { name = "pillow", specifier = ">=12.3.0" }, { name = "pydantic", specifier = ">=2.13.5" }, { name = "pydantic-settings", specifier = ">=2.15.0" }, + { name = "pyexiftool", specifier = ">=0.5.6" }, { name = "pytest", specifier = ">=9.1.1" }, { name = "ruff", specifier = ">=0.16.9" }, { name = "typer", specifier = ">=0.12.0" }, @@ -746,6 +748,15 @@ wheels = [ { url = "https://files.pythonhosted.org/packages/30/a4/2bffa9f8e804325a09867f0e9d30795c80ea9f8d62560bd1b6ad6220eb2f/pydantic_settings-2.15.0-py3-none-any.whl", hash = "sha256:0ba092c291c94baceb5eff768aa0d56400a457585bc0175925a5a5510303da42", size = 69413, upload-time = "2026-08-07T09:24:55.839Z" }, ] +[[package]] +name = "pyexiftool" +version = "0.5.6" +source = { registry = "https://pypi.org/simple" } +sdist = { url = "https://files.pythonhosted.org/packages/5e/48/406da6691d15abf3c8d399bce8bc588709a5b54e857fd7c22dad2f90c33c/PyExifTool-0.5.6.tar.gz", hash = "sha256:22a972c1c212d1ad5f61916fded5057333dcc48fb8e42eed12d2ff9665b367ae", size = 56365, upload-time = "2023-10-22T23:18:18.819Z" } +wheels = [ + { url = "https://files.pythonhosted.org/packages/fc/b9/175e9a1f8f3f94b22f622f0fcac853ae2c43cb4ac6034f849269c6086dac/PyExifTool-0.5.6-py3-none-any.whl", hash = "sha256:ac7d7836d2bf373f20aa558528f6b2222c4c0d896ed28c951a3ff8e6cec05a87", size = 51243, upload-time = "2023-10-22T23:18:16.614Z" }, +] + [[package]] name = "pygments" version = "2.21.0"