feat: Track yt-dlp releases in cinema-tools updates
This commit is contained in:
@@ -3,6 +3,7 @@ from __future__ import annotations
|
||||
import argparse
|
||||
import hashlib
|
||||
import os
|
||||
import re
|
||||
import shutil
|
||||
import sys
|
||||
import tempfile
|
||||
@@ -15,8 +16,10 @@ from zipfile import ZIP_STORED, ZipFile, ZipInfo
|
||||
PLUGIN_ID = "cinema-tools"
|
||||
DEFAULT_INSTANCE = "1.44.1"
|
||||
|
||||
YT_DLP_REPO = "yt-dlp/yt-dlp"
|
||||
YT_DLP_ASSET = "yt-dlp.exe"
|
||||
YT_DLP_VERSION = "2026.07.04"
|
||||
YT_DLP_URL = f"https://github.com/yt-dlp/yt-dlp/releases/download/{YT_DLP_VERSION}/yt-dlp.exe"
|
||||
YT_DLP_URL = f"https://github.com/{YT_DLP_REPO}/releases/download/{YT_DLP_VERSION}/{YT_DLP_ASSET}"
|
||||
YT_DLP_SHA256 = "52fe3c26dcf71fbdc85b528589020bb0b8e383155cfa81b64dd447bbe35e24b8"
|
||||
|
||||
FFMPEG_VERSION = "8.1.2"
|
||||
@@ -31,6 +34,36 @@ ASSET_NAME = f"CinemaTools-yt-dlp-{YT_DLP_VERSION}-ffmpeg-{FFMPEG_VERSION}.zip"
|
||||
BUNDLE_SHA256 = "4d299e40f747abb9777838542ca68ec450e0b86e6eb11af341e6bd568f47edf2"
|
||||
ZIP_TIMESTAMP = (1980, 1, 1, 0, 0, 0)
|
||||
|
||||
_TAG_RE = re.compile(r"^yt-dlp-(?P<yt_dlp>.+)-ffmpeg-(?P<ffmpeg>[^-]+)$")
|
||||
|
||||
|
||||
def bundle_tag(yt_dlp_version: str) -> str:
|
||||
return f"yt-dlp-{yt_dlp_version}-ffmpeg-{FFMPEG_VERSION}"
|
||||
|
||||
|
||||
def asset_name(yt_dlp_version: str) -> str:
|
||||
return f"CinemaTools-{bundle_tag(yt_dlp_version)}.zip"
|
||||
|
||||
|
||||
def yt_dlp_url(yt_dlp_version: str) -> str:
|
||||
return f"https://github.com/{YT_DLP_REPO}/releases/download/{yt_dlp_version}/{YT_DLP_ASSET}"
|
||||
|
||||
|
||||
def yt_dlp_version_from_tag(tag: str | None) -> str | None:
|
||||
match = _TAG_RE.match(tag or "")
|
||||
return match.group("yt_dlp") if match else None
|
||||
|
||||
|
||||
def lock_reason(yt_dlp_version: str, yt_dlp_sha256: str, instance: str) -> str:
|
||||
return (
|
||||
"Helper-managed Windows executable bundle for Cinema's hardcoded Proton paths "
|
||||
"Libs/yt-dlp.exe and Libs/ffmpeg.exe. Rebuild with PYTHONPATH=src python -m "
|
||||
f"plugin_helper.recipes.cinema_tools --instance {instance} --yt-dlp-version {yt_dlp_version} "
|
||||
f"--yt-dlp-sha256 {yt_dlp_sha256}. Sources: {yt_dlp_url(yt_dlp_version)} with sha256 "
|
||||
f"{yt_dlp_sha256}, and {FFMPEG_URL} with sha256 {FFMPEG_ZIP_SHA256}; the recipe extracts "
|
||||
f"*/bin/ffmpeg.exe with sha256 {FFMPEG_EXE_SHA256} and writes a deterministic root zip."
|
||||
)
|
||||
|
||||
|
||||
@dataclass(frozen=True)
|
||||
class BuiltAsset:
|
||||
@@ -94,18 +127,27 @@ def _write_member(archive: ZipFile, name: str, data: bytes) -> None:
|
||||
archive.writestr(info, data)
|
||||
|
||||
|
||||
def build_asset(output: Path, *, work_dir: Path | None = None) -> BuiltAsset:
|
||||
def build_asset(
|
||||
output: Path,
|
||||
*,
|
||||
yt_dlp_version: str = YT_DLP_VERSION,
|
||||
yt_dlp_sha256: str = YT_DLP_SHA256,
|
||||
work_dir: Path | None = None,
|
||||
) -> BuiltAsset:
|
||||
output = output.expanduser()
|
||||
output.parent.mkdir(parents=True, exist_ok=True)
|
||||
# The bundle hash is only known ahead of time for the pinned yt-dlp release.
|
||||
expected_bundle_sha256 = BUNDLE_SHA256 if yt_dlp_version == YT_DLP_VERSION else None
|
||||
name = asset_name(yt_dlp_version)
|
||||
|
||||
with tempfile.TemporaryDirectory(dir=work_dir) as tmp:
|
||||
tmp_path = Path(tmp)
|
||||
yt_dlp_path = tmp_path / "yt-dlp.exe"
|
||||
yt_dlp_path = tmp_path / YT_DLP_ASSET
|
||||
ffmpeg_zip_path = tmp_path / f"ffmpeg-{FFMPEG_VERSION}-essentials_build.zip"
|
||||
staged_output = tmp_path / ASSET_NAME
|
||||
staged_output = tmp_path / name
|
||||
|
||||
_download(YT_DLP_URL, yt_dlp_path)
|
||||
_verify(yt_dlp_path, YT_DLP_SHA256, "yt-dlp.exe")
|
||||
_download(yt_dlp_url(yt_dlp_version), yt_dlp_path)
|
||||
_verify(yt_dlp_path, yt_dlp_sha256, YT_DLP_ASSET)
|
||||
|
||||
_download(FFMPEG_URL, ffmpeg_zip_path)
|
||||
_verify(ffmpeg_zip_path, FFMPEG_ZIP_SHA256, "ffmpeg essentials zip")
|
||||
@@ -118,9 +160,9 @@ def build_asset(output: Path, *, work_dir: Path | None = None) -> BuiltAsset:
|
||||
_write_member(archive, "Libs/ffmpeg.exe", ffmpeg_data)
|
||||
|
||||
actual_bundle_sha256 = _sha256_file(staged_output)
|
||||
if actual_bundle_sha256 != BUNDLE_SHA256:
|
||||
if expected_bundle_sha256 and actual_bundle_sha256 != expected_bundle_sha256:
|
||||
raise ValueError(
|
||||
f"{ASSET_NAME} sha256 mismatch: expected {BUNDLE_SHA256}, got {actual_bundle_sha256}"
|
||||
f"{name} sha256 mismatch: expected {expected_bundle_sha256}, got {actual_bundle_sha256}"
|
||||
)
|
||||
shutil.move(str(staged_output), output)
|
||||
|
||||
@@ -142,15 +184,30 @@ def build_parser() -> argparse.ArgumentParser:
|
||||
help="Exact output path; defaults to <state-dir>/cache/downloads/<instance>/cinema-tools/<asset>",
|
||||
)
|
||||
parser.add_argument("--work-dir", type=Path, help="Parent directory for temporary downloads")
|
||||
parser.add_argument("--yt-dlp-version", default=YT_DLP_VERSION, help="yt-dlp release tag to bundle")
|
||||
parser.add_argument(
|
||||
"--yt-dlp-sha256",
|
||||
help="Expected yt-dlp.exe sha256; required when --yt-dlp-version differs from the pinned release",
|
||||
)
|
||||
return parser
|
||||
|
||||
|
||||
def run(argv: list[str] | None = None) -> int:
|
||||
parser = build_parser()
|
||||
args = parser.parse_args(argv)
|
||||
output = args.output or args.state_dir / "cache" / "downloads" / args.instance / PLUGIN_ID / ASSET_NAME
|
||||
yt_dlp_sha256 = args.yt_dlp_sha256 or (YT_DLP_SHA256 if args.yt_dlp_version == YT_DLP_VERSION else None)
|
||||
if not yt_dlp_sha256:
|
||||
parser.error("--yt-dlp-sha256 is required with a non-default --yt-dlp-version")
|
||||
output = args.output or (
|
||||
args.state_dir / "cache" / "downloads" / args.instance / PLUGIN_ID / asset_name(args.yt_dlp_version)
|
||||
)
|
||||
try:
|
||||
built = build_asset(output, work_dir=args.work_dir)
|
||||
built = build_asset(
|
||||
output,
|
||||
yt_dlp_version=args.yt_dlp_version,
|
||||
yt_dlp_sha256=yt_dlp_sha256,
|
||||
work_dir=args.work_dir,
|
||||
)
|
||||
except Exception as exc:
|
||||
print(f"error: {exc}", file=sys.stderr)
|
||||
return 2
|
||||
@@ -158,7 +215,7 @@ def run(argv: list[str] | None = None) -> int:
|
||||
print(f"Size: {built.size}")
|
||||
print(f"SHA-256: {built.sha256}")
|
||||
print("Members:")
|
||||
print(f" Libs/yt-dlp.exe {YT_DLP_SHA256}")
|
||||
print(f" Libs/yt-dlp.exe {yt_dlp_sha256}")
|
||||
print(f" Libs/ffmpeg.exe {FFMPEG_EXE_SHA256}")
|
||||
return 0
|
||||
|
||||
|
||||
Reference in New Issue
Block a user