140 lines
3.2 KiB
Python
140 lines
3.2 KiB
Python
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}")
|