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:
@@ -268,6 +268,46 @@ def run_ffmpeg(cmd, total_s, on_progress, should_cancel) -> None:
|
||||
raise RuntimeError(f"ffmpeg failed ({ret}): {err[:300]}")
|
||||
|
||||
|
||||
# --- ffmpeg: iOS/Safari codec compatibility -------------------------------------------------
|
||||
# Apple WebKit (every iOS browser, Brave included) decodes only H.264/H.265 in mp4 — an AV1/VP9
|
||||
# file plays on desktop Chrome but shows a "broken player" on iPad. The mp4 compat profile now
|
||||
# *selects* H.264, but a source may expose no H.264 stream at all, so this is the last-ditch net.
|
||||
_IOS_SAFE_VCODECS = ("h264", "hevc")
|
||||
|
||||
|
||||
def probe_vcodec(path: Path) -> str | None:
|
||||
"""Return the file's primary video codec name (lowercased) via ffprobe, or None if unknown."""
|
||||
try:
|
||||
out = subprocess.run(
|
||||
["ffprobe", "-v", "error", "-select_streams", "v:0", "-show_entries",
|
||||
"stream=codec_name", "-of", "default=nw=1:nk=1", str(path)],
|
||||
capture_output=True, text=True, timeout=30, check=True,
|
||||
).stdout.strip().lower()
|
||||
except (subprocess.TimeoutExpired, subprocess.CalledProcessError, OSError):
|
||||
return None
|
||||
return out or None
|
||||
|
||||
|
||||
def needs_ios_transcode(vcodec: str | None) -> bool:
|
||||
"""True when an mp4's video codec won't play in Apple WebKit and should be re-encoded to H.264.
|
||||
None (unknown/ffprobe failed) is treated as safe so we never re-encode on a bad probe."""
|
||||
return bool(vcodec) and vcodec not in _IOS_SAFE_VCODECS and vcodec != "none"
|
||||
|
||||
|
||||
def build_h264_transcode_cmd(src: Path, dest: Path) -> list[str]:
|
||||
"""Re-encode `src` to an iOS-safe H.264 + AAC mp4 (faststart), preserving resolution.
|
||||
`-progress pipe:1` streams machine-readable progress for run_ffmpeg."""
|
||||
return [
|
||||
"ffmpeg", "-y", "-hide_banner", "-loglevel", "error", "-nostdin",
|
||||
"-progress", "pipe:1", "-nostats",
|
||||
"-i", str(src),
|
||||
"-c:v", "libx264", "-preset", "veryfast", "-crf", "20",
|
||||
"-c:a", "aac", "-b:a", "192k",
|
||||
"-movflags", "+faststart",
|
||||
str(dest),
|
||||
]
|
||||
|
||||
|
||||
# --- filmstrip (editor scrub track) ---------------------------------------------------------
|
||||
|
||||
_SB_COLS = 12
|
||||
|
||||
Reference in New Issue
Block a user