From 56f3cfaac2dcc1fa5be82cc215f8bb37404bbdd3 Mon Sep 17 00:00:00 2001 From: exostring Date: Wed, 9 Sep 2026 19:56:24 +0500 Subject: [PATCH] Fix mobile black-screen: declared video dimensions didn't match the file ProcessedMedia.width/height for downloaded videos were always VK's original source dimensions (e.g. 3840x2160 for a 4K upload), passed straight through to Telegram's sendVideo width/height params - even though the actual file sent is capped to video_max_height (e.g. 720p) by _select_format. A client declaring 4K but receiving a 720p stream is a known cause of black-screen playback on strict mobile decoders, which can allocate the render surface from the declared size before the real stream is probed - matches the reported symptom exactly (fine on desktop and in PiP, black in Telegram mobile's fullscreen decoder path). Neither the faststart remux nor the negative-DTS fix addressed this, since both were about the file's own internal structure, not the metadata describing it externally. Adds _probe_video_dims (ffprobe on the actual downloaded/remuxed file) and uses its real width/height/duration instead of VK's pre-download values. Verified on the actual failing video: now reports the real 1280x720 instead of VK's original 3840x2160. Co-Authored-By: Claude Sonnet 5 --- src/media_processor.py | 39 ++++++++++++++++++++++++++++++++++++--- 1 file changed, 36 insertions(+), 3 deletions(-) diff --git a/src/media_processor.py b/src/media_processor.py index 5349f6c..649ff57 100644 --- a/src/media_processor.py +++ b/src/media_processor.py @@ -207,6 +207,38 @@ class MediaProcessor: await unregister_active_path(path) return fixed_path + async def _probe_video_dims(self, path: Path) -> tuple[Optional[int], Optional[int], Optional[float]]: + """Reads the ACTUAL encoded width/height/duration of a downloaded file via + ffprobe. Telegram was declared VK's original video dimensions (e.g. a 4K + source) while the file we actually send is capped to video_max_height (e.g. + 720p) - that mismatch between declared and real dimensions is a known cause of + black-screen playback on strict mobile decoders, since some clients allocate + the render surface from the declared size before the real stream is probed.""" + try: + proc = await asyncio.create_subprocess_exec( + "ffprobe", "-v", "error", "-select_streams", "v:0", + "-show_entries", "stream=width,height:format=duration", + "-of", "csv=p=0", + str(path), + stdout=asyncio.subprocess.PIPE, stderr=asyncio.subprocess.DEVNULL, + ) + out, _ = await proc.communicate() + except OSError: + return None, None, None + lines = [l for l in out.decode().strip().splitlines() if l] + width = height = None + duration = None + for line in lines: + parts = line.split(",") + if len(parts) == 2 and parts[0].isdigit() and parts[1].isdigit(): + width, height = int(parts[0]), int(parts[1]) + elif len(parts) == 1: + try: + duration = float(parts[0]) + except ValueError: + pass + return width, height, duration + async def _download_video_ytdlp_attempt( self, url: str, prefix: str = "video_" ) -> tuple[Optional[Path], Optional[str], bool]: @@ -373,6 +405,7 @@ class MediaProcessor: p, err, perm = await self.download_video_ytdlp(url, prefix=f"v_{att_id}_") if p: + real_width, real_height, real_duration = await self._probe_video_dims(p) results.append( ProcessedMedia( media_type="video", @@ -380,9 +413,9 @@ class MediaProcessor: original_url=url, attachment_id=att_id, size_bytes=p.stat().st_size, - duration_sec=item.duration_sec, - width=item.width, - height=item.height, + duration_sec=int(real_duration) if real_duration else item.duration_sec, + width=real_width or item.width, + height=real_height or item.height, ) ) else: