fix(downloads): make shared files iOS-playable + default to 720p

Shared /watch links showed a "broken play button" on iPad (any iOS
browser — all forced onto WebKit) while playing fine on desktop Chrome.
Root cause: the stored mp4's video codec was AV1 (confirmed via ffprobe),
which WebKit can't decode. The existing H.264 guard was only a
format_sort *preference*, so a non-YouTube source (Facebook) that ships
AV1 as its best video-only stream slipped through.

- B1: for the mp4 compat profile, prefer H.264 at the SELECTION level
  with a progressive fallback (bestvideo[vcodec^=avc1]+bestaudio /
  best[vcodec^=avc1] / anything) so an avc1 progressive is chosen over
  an AV1-only video-only stream.
- B2: re-encode the rare AV1/VP9-only result to H.264+AAC (+faststart)
  in the worker before storing, gated on the compat profile; corrects
  the asset codec metadata to match.
- Bump _SIG_VERSION 2->3 so cached AV1 assets re-derive.
- Default download preset is now 720p (migration 0058 re-seeds builtins
  720p-first; ProfileEditor BLANK 1080->720).
- Tests for the selector, compat opt-outs, and the transcode predicate.
This commit is contained in:
2026-07-22 01:19:31 +02:00
parent 51781819db
commit 8fd0049dce
6 changed files with 236 additions and 16 deletions
+59
View File
@@ -0,0 +1,59 @@
"""build_ydl_opts: the mp4 compat profile must SELECT H.264 (not merely prefer it), so shared
downloads play on iOS/Safari, while custom vcodec / non-mp4 profiles opt out. See the iPad AV1
broken-player fix."""
from app.downloads import edit as editmod
from app.downloads import formats
def _opts(spec):
return formats.build_ydl_opts(spec, "/tmp/%(id)s.%(ext)s", lambda d: None)
def test_mp4_default_selects_avc1_with_progressive_fallback():
fmt = _opts({"mode": "av", "container": "mp4"})["format"]
# avc1 video-only first, then the avc1 progressive, then an unrestricted fallback.
assert fmt.startswith("bestvideo[vcodec^=avc1]+bestaudio[acodec^=mp4a]")
assert "/best[vcodec^=avc1]" in fmt
assert fmt.endswith("bestvideo+bestaudio/best")
def test_mp4_default_keeps_h264_sort_tiebreaker():
sort = _opts({"mode": "av", "container": "mp4"})["format_sort"]
assert "vcodec:h264" in sort and "acodec:aac" in sort
def test_video_only_mp4_selects_avc1():
fmt = _opts({"mode": "v", "container": "mp4"})["format"]
assert fmt.startswith("bestvideo[vcodec^=avc1]")
assert "+bestaudio" not in fmt # video-only never merges audio
def test_max_height_caps_every_branch():
fmt = _opts({"mode": "av", "container": "mp4", "max_height": 720})["format"]
assert fmt.count("[height<=?720]") >= 3 # applied to each avc1/fallback branch
def test_explicit_vcodec_opts_out_of_avc1_selector():
opts = _opts({"mode": "av", "container": "mp4", "vcodec": "vp9"})
assert "vcodec^=avc1" not in opts["format"]
assert opts["format"] == "bestvideo+bestaudio/best"
assert "vcodec:vp9" in opts["format_sort"]
def test_non_mp4_container_opts_out():
fmt = _opts({"mode": "av", "container": "webm"})["format"]
assert "vcodec^=avc1" not in fmt
def test_audio_only_unaffected():
assert _opts({"mode": "a", "audio_format": "m4a"})["format"] == "bestaudio/best"
def test_needs_ios_transcode_flags_only_undecodable_codecs():
assert editmod.needs_ios_transcode("av1") is True
assert editmod.needs_ios_transcode("vp9") is True
assert editmod.needs_ios_transcode("h264") is False
assert editmod.needs_ios_transcode("hevc") is False
# An unknown/failed probe must NOT trigger a needless re-encode.
assert editmod.needs_ios_transcode(None) is False
assert editmod.needs_ios_transcode("none") is False