From ea1df66dc3686881e2fd63430fd3a97d8a9e3ef5 Mon Sep 17 00:00:00 2001 From: npeter83 Date: Sat, 4 Jul 2026 17:29:05 +0200 Subject: [PATCH 1/2] fix(downloads): prefer H.264+AAC for mp4 so shared files play on iOS/Safari MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit mp4 downloads used yt-dlp's default best video+audio, which on YouTube is VP9+Opus at 1080p. Chromium plays that, but iOS/Safari WebKit (which every iOS browser is forced to use) decodes only H.264/H.265+AAC inside mp4 — so a shared /watch link showed a broken player on iPhone/iPad while the download still worked. Rank vcodec:h264 + acodec:aac above resolution for mp4 output (unless a custom profile pins a vcodec), keeping VP9/AV1 as a graceful fallback. Bump the format signature (v2) so an identical spec gets a fresh cache identity instead of hitting a stale VP9 asset. --- backend/app/downloads/formats.py | 19 ++++++++++++++++++- 1 file changed, 18 insertions(+), 1 deletion(-) diff --git a/backend/app/downloads/formats.py b/backend/app/downloads/formats.py index 0fb927f..ef38ef9 100644 --- a/backend/app/downloads/formats.py +++ b/backend/app/downloads/formats.py @@ -44,6 +44,11 @@ _DEFAULTS = { "sponsorblock": False, } +# Bump when the spec→output-bytes mapping changes so an identical spec gets a fresh cache +# identity instead of hitting a stale asset produced by the old rule. v2: mp4 downloads now +# prefer H.264+AAC (browser/iOS-compatible) rather than yt-dlp's default VP9/Opus best. +_SIG_VERSION = 2 + _SPONSORBLOCK_CATEGORIES = ["sponsor", "selfpromo", "interaction"] _VCODEC_SORT = {"h264": "vcodec:h264", "vp9": "vcodec:vp9", "av1": "vcodec:av01"} @@ -69,7 +74,9 @@ def normalize(spec: dict) -> dict: def format_sig(spec: dict) -> str: """Stable short hash of the output-affecting spec — the MediaAsset cache key component.""" s = normalize(spec) - payload = json.dumps({k: s[k] for k in _SIG_KEYS}, sort_keys=True, separators=(",", ":")) + data = {k: s[k] for k in _SIG_KEYS} + data["_v"] = _SIG_VERSION + payload = json.dumps(data, sort_keys=True, separators=(",", ":")) return hashlib.sha256(payload.encode()).hexdigest()[:24] @@ -145,6 +152,16 @@ def build_ydl_opts( if s["container"]: opts["merge_output_format"] = s["container"] sort = [] + # Compatibility-first for mp4 (the universal container): prefer H.264 video + AAC + # audio so shared/downloaded files play in EVERY browser, including iOS/Safari. + # WebKit (which every iOS browser, Brave included, is forced to use) decodes only + # H.264/H.265 + AAC inside mp4 — a VP9/Opus-in-mp4 file shows a broken player there, + # even though Chromium plays it fine. These sort keys rank codec ABOVE resolution, so + # a 720p H.264 stream is chosen over a 1080p-VP9-only one; VP9/AV1 remain a graceful + # fallback only when no H.264 stream exists. A custom profile that explicitly sets + # `vcodec` opts out (its codec choice wins over the compatibility default). + if s["container"] == "mp4" and s["vcodec"] is None: + sort += ["vcodec:h264", "acodec:aac"] if h: sort.append(f"res:{h}") if s["vcodec"] in _VCODEC_SORT: From 95984a7ce403785aab36f30236192d113ebaf2d9 Mon Sep 17 00:00:00 2001 From: npeter83 Date: Sat, 4 Jul 2026 17:30:02 +0200 Subject: [PATCH 2/2] =?UTF-8?q?chore(release):=20v0.22.1=20=E2=80=94=20iOS?= =?UTF-8?q?/Safari-compatible=20downloads?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- VERSION | 2 +- frontend/src/lib/releaseNotes.ts | 8 ++++++++ 2 files changed, 9 insertions(+), 1 deletion(-) diff --git a/VERSION b/VERSION index 2157409..a04c2de 100644 --- a/VERSION +++ b/VERSION @@ -1 +1 @@ -0.22.0 +0.22.1 \ No newline at end of file diff --git a/frontend/src/lib/releaseNotes.ts b/frontend/src/lib/releaseNotes.ts index abf8efa..6a8625a 100644 --- a/frontend/src/lib/releaseNotes.ts +++ b/frontend/src/lib/releaseNotes.ts @@ -14,6 +14,14 @@ export interface ReleaseEntry { } export const RELEASE_NOTES: ReleaseEntry[] = [ + { + version: "0.22.1", + date: "2026-07-04", + summary: "Downloaded and shared videos now play in every browser, including on iPhone and iPad.", + fixes: [ + "Downloads now use widely-compatible video (H.264) and audio (AAC) so a saved file — and a public watch link — plays everywhere, including iOS/Safari, where the previous format left a broken player. Existing downloads keep their old format; re-download to get the compatible version.", + ], + }, { version: "0.22.0", date: "2026-07-04",