from __future__ import annotations import shutil import subprocess from pathlib import Path from tools.exiftool import run_exiftool_command EXCLUDED_COPY_TAGS = [ "--File:all", "--ExifTool:all", "--Composite:all", "--SourceFile", "--Directory", "--FileName", "--FileSize", "--FileModifyDate", "--FileAccessDate", "--FileInodeChangeDate", "--FilePermissions", "--FileType", "--FileTypeExtension", "--MIMEType", "--ImageWidth", "--ImageHeight", "--ExifImageWidth", "--ExifImageHeight", "--PixelXDimension", "--PixelYDimension", "--RelatedImageWidth", "--RelatedImageHeight", "--ImageSize", "--Megapixels", "--XResolution", "--YResolution", "--ResolutionUnit", "--Duration", "--DurationScale", "--DurationValue", "--MediaDuration", "--MediaTimeScale", "--TrackDuration", "--TrackCreateDate", "--TrackModifyDate", "--MediaCreateDate", "--MediaModifyDate", "--AvgBitrate", "--Bitrate", "--MaxBitrate", "--VideoFrameRate", "--CaptureFrameRate", "--AudioBitrate", "--AudioSampleRate", "--AudioSamplingRate", "--AudioBitsPerSample", "--AudioChannels", "--CompressorID", "--CompressorName", "--MediaDataOffset", "--MediaDataSize", ] def copy_meaningful_metadata( exiftool: str, source: Path, destination: Path, ) -> None: if destination.suffix.lower() in {".mp4", ".mov"}: copy_meaningful_metadata_with_exiftool(exiftool, source, destination) return copy_container_metadata_with_ffmpeg(source, destination) def copy_meaningful_metadata_with_exiftool( exiftool: str, source: Path, destination: Path, ) -> None: args = [ "-overwrite_original", "-TagsFromFile", str(source), "-all:all", *EXCLUDED_COPY_TAGS, str(destination), ] run_exiftool_command( exiftool, args, check=True, capture_output=True, text=True, ) def copy_container_metadata_with_ffmpeg(source: Path, destination: Path) -> None: ffmpeg = shutil.which("ffmpeg") if not ffmpeg: print(" metadata: ffmpeg not found; skipped best-effort metadata remux") return temp_output = destination.with_name(f"{destination.stem}.metadata-copy{destination.suffix}") temp_output = _unique_path(temp_output) command = [ ffmpeg, "-y", "-hide_banner", "-loglevel", "error", "-i", str(destination), "-i", str(source), "-map", "0", "-map_metadata", "1", "-c", "copy", str(temp_output), ] try: subprocess.run(command, check=True, capture_output=True, text=True) temp_output.replace(destination) finally: if temp_output.exists(): temp_output.unlink() def _unique_path(path: Path) -> Path: if not path.exists(): return path for index in range(1, 10000): candidate = path.with_name(f"{path.stem}-{index}{path.suffix}") if not candidate.exists(): return candidate raise RuntimeError(f"Could not find a unique temporary path for {path}")