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:
@@ -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
|
||||
Reference in New Issue
Block a user