feat(downloads): guarantee every download is browser-playable (mp4/H.264/AAC)

The Download module's purpose is download -> share -> play in-browser, so the
output must play in EVERY browser (iOS/Safari/WebKit included). The only
universally-safe combo is mp4 + H.264 + AAC/MP3.

- formats.normalize() now CLAMPS every spec to that profile (container->mp4,
  vcodec->None, audio_format opus->m4a) at the single point all paths flow
  through (picker, stored profile, raw API) — not just by hiding UI options. A
  source exposing no H.264 is still caught by the worker's browser-safe
  re-encode, so playability is guaranteed end-to-end.
- build_ydl_opts drops the now-unreachable non-compat branches (+ _VCODEC_SORT).
- _SIG_VERSION 3->4 so assets cached under an old non-compat spec — or a stale
  non-H.264 mp4 that slipped the v3 net (the HEVC Facebook dls) — re-derive.
- ProfileEditor drops the container/codec pickers and Opus; audio is M4A/MP3.
- i18n: remove the now-unused container/vcodec/any keys (HU+EN parity).
This commit is contained in:
2026-08-29 06:08:21 +02:00
parent fb2c843fa9
commit 94fcc40bde
5 changed files with 83 additions and 93 deletions
+5 -30
View File
@@ -195,6 +195,8 @@ export default function ProfileEditor({ onClose }: { onClose: () => void }) {
<label className="block text-sm font-medium mb-1">
{t("downloads.profiles.audioFormat")}
</label>
{/* Only browser-playable audio: AAC (m4a) + MP3. Opus is dropped — Safari plays it
unreliably as a bare file, and the module's purpose is in-browser playback. */}
<select
value={spec.audio_format ?? "m4a"}
onChange={set("audio_format")}
@@ -202,39 +204,12 @@ export default function ProfileEditor({ onClose }: { onClose: () => void }) {
>
<option value="m4a">M4A</option>
<option value="mp3">MP3</option>
<option value="opus">Opus</option>
</select>
</div>
)}
{spec.mode !== "a" && (
<>
<div>
<label className="block text-sm font-medium mb-1">
{t("downloads.profiles.container")}
</label>
<select
value={spec.container ?? "mp4"}
onChange={set("container")}
className={inputCls}
>
<option value="mp4">MP4</option>
<option value="mkv">MKV</option>
<option value="webm">WebM</option>
</select>
</div>
<div>
<label className="block text-sm font-medium mb-1">
{t("downloads.profiles.vcodec")}
</label>
<select value={spec.vcodec ?? ""} onChange={set("vcodec")} className={inputCls}>
<option value="">{t("downloads.profiles.any")}</option>
<option value="h264">H.264</option>
<option value="vp9">VP9</option>
<option value="av1">AV1</option>
</select>
</div>
</>
)}
{/* Container/codec are no longer user-selectable: every download is clamped to the
mp4/H.264/AAC compat profile (the only universally browser-playable combo) so a
shared link plays everywhere, iOS/Safari included. Enforced in backend `normalize`. */}
</div>
<div className="space-y-1.5 pt-1">
{spec.mode !== "a" && (