from __future__ import annotations import json import os import subprocess import tempfile from pathlib import Path from typing import Any from tools.executables import require_executable def require_exiftool() -> str: return require_executable("exiftool") def _use_windows_argument_file() -> bool: return os.name == "nt" def run_exiftool_command( executable: str, args: list[str], **run_kwargs: Any, ) -> subprocess.CompletedProcess: """Run ExifTool with Unicode-safe filename arguments on Windows.""" command = [executable, "-charset", "filename=UTF8", *args] if not _use_windows_argument_file(): return subprocess.run(command, **run_kwargs) # ExifTool can't reliably receive arbitrary Unicode paths on the Windows # command line. The outer path is ASCII and the UTF-8 argument file holds # the actual paths and options. with tempfile.TemporaryDirectory(prefix="mbt-exiftool-") as temp_dir: argument_file = Path(temp_dir) / "arguments.txt" argument_file.write_text("\n".join(args) + "\n", encoding="utf-8") return subprocess.run( [executable, "-charset", "filename=UTF8", "-@", argument_file.name], cwd=temp_dir, **run_kwargs, ) def run_exiftool_json( executable: str, paths: list[Path], *, quicktime_utc: bool = True, ) -> list[dict]: results: list[dict] = [] for start in range(0, len(paths), 80): chunk = paths[start : start + 80] args = [ "-j", "-G", ] if quicktime_utc: args.extend(["-api", "QuickTimeUTC=1"]) args.extend(str(path) for path in chunk) completed = run_exiftool_command( executable, args, check=True, capture_output=True, text=True, ) results.extend(json.loads(completed.stdout or "[]")) return results def run_exiftool_write(executable: str, path: Path, args: list[str]) -> None: if not args: return command = [ "-overwrite_original", *args, str(path), ] run_exiftool_command(executable, command, check=True)