From 4b6576ec53593d66715c061f418efd85cd8ed9dc Mon Sep 17 00:00:00 2001 From: LordBaryhobal Date: Mon, 12 May 2025 10:43:23 +0200 Subject: [PATCH] refactor: add mkvpropedit as alternative for in-place modification --- Dockerfile | 8 ++-- src/metadata_extractor.py | 6 +++ src/metadata_writer.py | 93 ++++++++++++++++++++++++++++++--------- 3 files changed, 82 insertions(+), 25 deletions(-) diff --git a/Dockerfile b/Dockerfile index 04a42ca..9bff060 100644 --- a/Dockerfile +++ b/Dockerfile @@ -1,4 +1,4 @@ -FROM debian:bullseye-slim AS builder +FROM debian:bookworm-slim AS builder # Install ffmpeg and mkvtoolnix # but only keep the binaries and libs for ffprobe and mkvmerge @@ -7,11 +7,13 @@ RUN apt-get update && apt-get install -y --no-install-recommends \ && mkdir -p /artifacts/bin /artifacts/lib \ && cp $(which ffprobe) /artifacts/bin/ \ && cp $(which mkvmerge) /artifacts/bin/ \ + && cp $(which mkvpropedit) /artifacts/bin/ \ && ldd $(which ffprobe) | awk '{print $3}' | xargs -I '{}' cp -v '{}' /artifacts/lib/ || true \ - && ldd $(which mkvmerge) | awk '{print $3}' | xargs -I '{}' cp -v '{}' /artifacts/lib/ || true + && ldd $(which mkvmerge) | awk '{print $3}' | xargs -I '{}' cp -v '{}' /artifacts/lib/ || true \ + && ldd $(which mkvpropedit) | awk '{print $3}' | xargs -I '{}' cp -v '{}' /artifacts/lib/ || true # Must be the same base as builder image for shared libraries compatibility -FROM python:3.13.3-slim-bullseye +FROM python:3.13.3-slim-bookworm COPY --from=builder /artifacts/bin/* /usr/local/bin/ COPY --from=builder /artifacts/lib/* /usr/local/lib/ diff --git a/src/metadata_extractor.py b/src/metadata_extractor.py index cf45d22..15c05cd 100644 --- a/src/metadata_extractor.py +++ b/src/metadata_extractor.py @@ -81,6 +81,12 @@ class MetadataExtractor: } metadata["subtitle_tracks"].append(track) + elif codec_type == "video": + pass + + elif codec_type == "button": + pass + else: self.logger.warning(f"Unknown track codec type '{codec_type}'") diff --git a/src/metadata_writer.py b/src/metadata_writer.py index b23ff66..69e1c91 100644 --- a/src/metadata_writer.py +++ b/src/metadata_writer.py @@ -11,26 +11,7 @@ class MetadataWriter: def __init__(self): self.logger: logging.Logger = logging.getLogger("MetadataWriter") - def apply_metadata(self, metadata: dict, in_path: str, out_path: Optional[str] = None) -> bool: - """ - Writes metadata to a video file using mkvmerge - - :param metadata: Metadata information - :param in_path: Path of the input video file - :param out_path: Path of the output video file. If None, ``"_modified"`` is appended to ``in_path`` instead - :return: True if successful, False otherwise - """ - - if not os.path.isfile(in_path): - self.logger.error(f"Input file not found: {in_path}") - return False - - if out_path is None: - # Create a temporary output file - base_name, ext = os.path.splitext(in_path) - out_path: str = f"{base_name}_modified{ext}" - - # Start building the mkvmerge command + def get_mkvmerge_cmd(self, metadata: dict, in_path: str, out_path: str) -> list[str]: cmd: list[str] = [ "mkvmerge", "-o", out_path @@ -66,8 +47,76 @@ class MetadataWriter: # Add input file cmd.append(in_path) + return cmd - # Execute the mkvmerge command + def get_mkvpropedit_cmd(self, metadata: dict, path: str) -> list[str]: + cmd: list[str] = [ + "mkvpropedit", + path + ] + + # Add global metadata (title) + if "title" in metadata: + cmd.extend(["--edit", "info", "--set", f"title={metadata["title"]}"]) + + # Process audio + subtitle tracks + tracks: list[dict] = metadata.get("audio_tracks", []) + metadata.get("subtitle_tracks", []) + for track in tracks: + # Use the actual track index from the metadata + track_id = track.get("index", 0) + + cmd.extend(["--edit", f"track:{track_id}"]) + + # Set language + if "language" in track: + cmd.extend(["--set", f"language={track["language"]}"]) + + # Set title/name + if "name" in track and track["name"]: + cmd.extend(["--set", f"name={track["name"]}"]) + + # Set disposition flags + flags = track.get("flags", {}) + + def yes_no(flag: str): + return f"{track_id}:{"yes" if flags.get(flag, False) else "no"}" + + cmd.extend(["--set", f"flag-default={int(flags.get("default", False))}"]) + cmd.extend(["--set", f"flag-forced={int(flags.get("forced", False))}"]) + cmd.extend(["--set", f"flag-original={int(flags.get("original", False))}"]) + + return cmd + + def apply_metadata(self, metadata: dict, in_path: str, out_path: Optional[str] = None) -> bool: + """ + Writes metadata to a video file using mkvmerge or mkvpropedit + + :param metadata: Metadata information + :param in_path: Path of the input video file + :param out_path: Path of the output video file. If None, ``"_modified"`` is appended to ``in_path`` instead + :return: True if successful, False otherwise + """ + + if not os.path.isfile(in_path): + self.logger.error(f"Input file not found: {in_path}") + return False + + if out_path is None: + # Create a temporary output file + base_name, ext = os.path.splitext(in_path) + out_path: str = f"{base_name}_modified{ext}" + + # Build the command + overwriting: bool = os.path.abspath(in_path) == os.path.abspath(out_path) + cmd: list[str] = ( + self.get_mkvpropedit_cmd(metadata, in_path) + if overwriting else + self.get_mkvmerge_cmd(metadata, in_path, out_path) + ) + + print(cmd) + + # Execute the command self.logger.debug(f"Writing metadata to {os.path.basename(out_path)}") try: @@ -80,7 +129,7 @@ class MetadataWriter: return True except Exception as e: - self.logger.error(f"Error executing mkvmerge: {str(e)}") + self.logger.error(f"Error executing {cmd[0]}: {str(e)}") return False @staticmethod