From e875475f882015c9d7202345f6562543224c6759 Mon Sep 17 00:00:00 2001 From: exostring Date: Wed, 9 Sep 2026 19:49:34 +0500 Subject: [PATCH] Fix mobile black-screen-in-fullscreen: normalize negative DTS on remux The faststart fix (f8842f8) handled the moov-atom position but missed a second issue in the same merged video+audio stream: the video track's first packet has a slightly negative DTS (from B-frame reordering across the merge), confirmed via ffprobe on the actual failing video (-0.083322s). Reported symptom after the faststart fix was already live: preview thumbnail fine, black screen on opening fullscreen on Telegram's mobile app, but picture visible in PiP mode - consistent with the hardware-accelerated fullscreen decoder path choking on it while PiP's path (apparently software) tolerates it. Desktop played it fine regardless. Adds -avoid_negative_ts make_zero to the same lossless -c copy remux pass (no extra cost - it's the same command). Verified on the actual video: first packet DTS goes from -0.083322 to -0.000322 (down to B-frame rounding noise, not a real negative start anymore). Co-Authored-By: Claude Sonnet 5 --- src/media_processor.py | 20 +++++++++++++------- 1 file changed, 13 insertions(+), 7 deletions(-) diff --git a/src/media_processor.py b/src/media_processor.py index 09330b8..5349f6c 100644 --- a/src/media_processor.py +++ b/src/media_processor.py @@ -172,18 +172,24 @@ class MediaProcessor: async def _apply_faststart(self, path: Path) -> Path: """Moves the moov atom to the front with a fast, lossless remux (-c copy - no - re-encode, just repositions the index). Needed because muxing a separate - video+audio stream (see _select_format's "+bestaudio" case) leaves the moov atom - at the end by default: confirmed on a real post where the result played audio - with a black video, because streaming players (notably Telegram's mobile clients) - can't render video without seeking to the index first. Falls back to the original - file - rather than dropping the video - if ffmpeg fails for any reason.""" + re-encode, just repositions the index and timestamps). Needed because muxing a + separate video+audio stream (see _select_format's "+bestaudio" case) leaves the + moov atom at the end by default, AND leaves the video track starting at a + slightly negative DTS (its first frame is a B-frame reordered before the + keyframe's PTS) - confirmed on a real post where the result played audio with a + black video on Telegram's mobile app in fullscreen (but rendered fine in PiP, + which apparently tolerates it - a strong hint the hardware decoder path is what + chokes on it) while desktop played it fine either way. -avoid_negative_ts + make_zero fixes the second half; without it, moov-only faststart wasn't enough. + Falls back to the original file - rather than dropping the video - if ffmpeg + fails for any reason.""" fixed_path = path.with_name(f"{path.stem}_fs.mp4") await register_active_path(fixed_path) proc = None try: proc = await asyncio.create_subprocess_exec( - "ffmpeg", "-y", "-i", str(path), "-c", "copy", "-movflags", "+faststart", + "ffmpeg", "-y", "-i", str(path), "-c", "copy", + "-avoid_negative_ts", "make_zero", "-movflags", "+faststart", str(fixed_path), stdout=asyncio.subprocess.DEVNULL, stderr=asyncio.subprocess.DEVNULL, )