feat(binaries): Add support for MKVToolNix and mkvpropedit

* Introduced `MKVToolNix` and `mkvpropedit` binaries to the project.
* Updated the environment check to include required status for dependencies.
* Enhanced the `Tracks` class to raise an error if `MKVToolNix` is not found.
* Modified the `_apply_tags` function to utilize the `mkvpropedit` binary from the binaries module.
This commit is contained in:
Andy
2025-07-25 18:27:14 +00:00
parent 05ef841282
commit f69eb691d7
4 changed files with 36 additions and 24 deletions

View File

@@ -15,7 +15,6 @@ from unshackle.core.config import POSSIBLE_CONFIG_PATHS, config, config_path
from unshackle.core.console import console from unshackle.core.console import console
from unshackle.core.constants import context_settings from unshackle.core.constants import context_settings
from unshackle.core.services import Services from unshackle.core.services import Services
from unshackle.core.utils.osenvironment import get_os_arch
@click.group(short_help="Manage and configure the project environment.", context_settings=context_settings) @click.group(short_help="Manage and configure the project environment.", context_settings=context_settings)
@@ -28,31 +27,37 @@ def check() -> None:
"""Checks environment for the required dependencies.""" """Checks environment for the required dependencies."""
table = Table(title="Dependencies", expand=True) table = Table(title="Dependencies", expand=True)
table.add_column("Name", no_wrap=True) table.add_column("Name", no_wrap=True)
table.add_column("Required", justify="center")
table.add_column("Installed", justify="center") table.add_column("Installed", justify="center")
table.add_column("Path", no_wrap=False, overflow="fold") table.add_column("Path", no_wrap=False, overflow="fold")
# builds shaka-packager based on os, arch # Define all dependencies with their binary objects and required status
packager_dep = get_os_arch("packager")
# Helper function to find binary with multiple possible names
def find_binary(*names):
for name in names:
if binaries.find(name):
return name
return names[0] # Return first name as fallback for display
dependencies = [ dependencies = [
{"name": "CCExtractor", "binary": "ccextractor"}, {"name": "FFMpeg", "binary": binaries.FFMPEG, "required": True},
{"name": "FFMpeg", "binary": "ffmpeg"}, {"name": "FFProbe", "binary": binaries.FFProbe, "required": True},
{"name": "MKVToolNix", "binary": "mkvmerge"}, {"name": "Shaka-Packager", "binary": binaries.ShakaPackager, "required": True},
{"name": "Shaka-Packager", "binary": find_binary("shaka-packager", "packager", packager_dep)}, {"name": "MKVToolNix", "binary": binaries.MKVToolNix, "required": True},
{"name": "N_m3u8DL-RE", "binary": find_binary("N_m3u8DL-RE", "n-m3u8dl-re")}, {"name": "Mkvpropedit", "binary": binaries.Mkvpropedit, "required": True},
{"name": "Aria2(c)", "binary": "aria2c"}, {"name": "CCExtractor", "binary": binaries.CCExtractor, "required": False},
{"name": "FFPlay", "binary": binaries.FFPlay, "required": False},
{"name": "SubtitleEdit", "binary": binaries.SubtitleEdit, "required": False},
{"name": "Aria2(c)", "binary": binaries.Aria2, "required": False},
{"name": "HolaProxy", "binary": binaries.HolaProxy, "required": False},
{"name": "MPV", "binary": binaries.MPV, "required": False},
{"name": "Caddy", "binary": binaries.Caddy, "required": False},
{"name": "N_m3u8DL-RE", "binary": binaries.N_m3u8DL_RE, "required": False},
] ]
for dep in dependencies: for dep in dependencies:
path = binaries.find(dep["binary"]) path = dep["binary"]
# Required column
if dep["required"]:
required = "[red]Yes[/red]"
else:
required = "No"
# Installed column
if path: if path:
installed = "[green]:heavy_check_mark:[/green]" installed = "[green]:heavy_check_mark:[/green]"
path_output = str(path) path_output = str(path)
@@ -61,7 +66,7 @@ def check() -> None:
path_output = "Not Found" path_output = "Not Found"
# Add to the table # Add to the table
table.add_row(dep["name"], installed, path_output) table.add_row(dep["name"], required, installed, path_output)
# Display the result # Display the result
console.print(Padding(table, (1, 5))) console.print(Padding(table, (1, 5)))

View File

@@ -49,6 +49,8 @@ HolaProxy = find("hola-proxy")
MPV = find("mpv") MPV = find("mpv")
Caddy = find("caddy") Caddy = find("caddy")
N_m3u8DL_RE = find("N_m3u8DL-RE", "n-m3u8dl-re") N_m3u8DL_RE = find("N_m3u8DL-RE", "n-m3u8dl-re")
MKVToolNix = find("mkvmerge")
Mkvpropedit = find("mkvpropedit")
__all__ = ( __all__ = (
@@ -63,5 +65,7 @@ __all__ = (
"MPV", "MPV",
"Caddy", "Caddy",
"N_m3u8DL_RE", "N_m3u8DL_RE",
"MKVToolNix",
"Mkvpropedit",
"find", "find",
) )

View File

@@ -11,6 +11,7 @@ from rich.progress import BarColumn, Progress, SpinnerColumn, TextColumn, TimeRe
from rich.table import Table from rich.table import Table
from rich.tree import Tree from rich.tree import Tree
from unshackle.core import binaries
from unshackle.core.config import config from unshackle.core.config import config
from unshackle.core.console import console from unshackle.core.console import console
from unshackle.core.constants import LANGUAGE_MAX_DISTANCE, AnyTrack, TrackT from unshackle.core.constants import LANGUAGE_MAX_DISTANCE, AnyTrack, TrackT
@@ -290,8 +291,11 @@ class Tracks:
progress: Update a rich progress bar via `completed=...`. This must be the progress: Update a rich progress bar via `completed=...`. This must be the
progress object's update() func, pre-set with task id via functools.partial. progress object's update() func, pre-set with task id via functools.partial.
""" """
if not binaries.MKVToolNix:
raise RuntimeError("MKVToolNix (mkvmerge) is required for muxing but was not found")
cl = [ cl = [
"mkvmerge", str(binaries.MKVToolNix),
"--no-date", # remove dates from the output for security "--no-date", # remove dates from the output for security
] ]

View File

@@ -3,7 +3,6 @@ from __future__ import annotations
import logging import logging
import os import os
import re import re
import shutil
import subprocess import subprocess
import tempfile import tempfile
from difflib import SequenceMatcher from difflib import SequenceMatcher
@@ -12,6 +11,7 @@ from typing import Optional, Tuple
import requests import requests
from unshackle.core import binaries
from unshackle.core.config import config from unshackle.core.config import config
from unshackle.core.titles.episode import Episode from unshackle.core.titles.episode import Episode
from unshackle.core.titles.movie import Movie from unshackle.core.titles.movie import Movie
@@ -175,8 +175,7 @@ def external_ids(tmdb_id: int, kind: str) -> dict:
def _apply_tags(path: Path, tags: dict[str, str]) -> None: def _apply_tags(path: Path, tags: dict[str, str]) -> None:
if not tags: if not tags:
return return
mkvpropedit = shutil.which("mkvpropedit") if not binaries.Mkvpropedit:
if not mkvpropedit:
log.debug("mkvpropedit not found on PATH; skipping tags") log.debug("mkvpropedit not found on PATH; skipping tags")
return return
log.debug("Applying tags to %s: %s", path, tags) log.debug("Applying tags to %s: %s", path, tags)
@@ -189,7 +188,7 @@ def _apply_tags(path: Path, tags: dict[str, str]) -> None:
tmp_path = Path(f.name) tmp_path = Path(f.name)
try: try:
subprocess.run( subprocess.run(
[mkvpropedit, str(path), "--tags", f"global:{tmp_path}"], [str(binaries.Mkvpropedit), str(path), "--tags", f"global:{tmp_path}"],
check=False, check=False,
stdout=subprocess.DEVNULL, stdout=subprocess.DEVNULL,
stderr=subprocess.DEVNULL, stderr=subprocess.DEVNULL,