Fix black-screen-with-audio videos: faststart remux after download
Root cause of a real report (post #343474, video-36860851_456244054): _select_format's video-only+bestaudio path merges VK's separate DASH video and audio streams via ffmpeg, which leaves the moov atom at the end of the file by default (confirmed: mdat at offset 44, moov near EOF). Streaming players - Telegram's mobile clients in particular - can play the audio track immediately but can't render video without seeking to the index first, showing black video with working audio until the whole file has downloaded. Adds a fast, lossless -c copy -movflags +faststart remux after every video download (cheap - just repositions the atom, no re-encode) so the moov atom is always at the front regardless of which yt-dlp code path produced the file. Falls back to the original file if the remux fails for any reason, rather than dropping the video. Verified on the actual failing video: moov now at offset 36 instead of past 2MB in. Co-Authored-By: Claude Sonnet 5 <noreply@anthropic.com>
This commit is contained in:
@@ -170,6 +170,37 @@ class MediaProcessor:
|
|||||||
return f"{best_video_only[1]}+bestaudio", None
|
return f"{best_video_only[1]}+bestaudio", None
|
||||||
return None, "no format under the size limit with a known size"
|
return None, "no format under the size limit with a known size"
|
||||||
|
|
||||||
|
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."""
|
||||||
|
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",
|
||||||
|
str(fixed_path),
|
||||||
|
stdout=asyncio.subprocess.DEVNULL, stderr=asyncio.subprocess.DEVNULL,
|
||||||
|
)
|
||||||
|
await proc.wait()
|
||||||
|
except OSError as exc:
|
||||||
|
logger.warning("ffmpeg faststart remux failed to start: {}", exc)
|
||||||
|
|
||||||
|
if not proc or proc.returncode != 0 or not fixed_path.exists() or fixed_path.stat().st_size == 0:
|
||||||
|
logger.warning("ffmpeg faststart remux failed for {}, sending as-is", path)
|
||||||
|
fixed_path.unlink(missing_ok=True)
|
||||||
|
await unregister_active_path(fixed_path)
|
||||||
|
return path
|
||||||
|
|
||||||
|
path.unlink(missing_ok=True)
|
||||||
|
await unregister_active_path(path)
|
||||||
|
return fixed_path
|
||||||
|
|
||||||
async def _download_video_ytdlp_attempt(
|
async def _download_video_ytdlp_attempt(
|
||||||
self, url: str, prefix: str = "video_"
|
self, url: str, prefix: str = "video_"
|
||||||
) -> tuple[Optional[Path], Optional[str], bool]:
|
) -> tuple[Optional[Path], Optional[str], bool]:
|
||||||
@@ -263,6 +294,8 @@ class MediaProcessor:
|
|||||||
await unregister_active_path(output_path)
|
await unregister_active_path(output_path)
|
||||||
return None, f"yt-dlp failed: {err_msg[:200]}", is_permanent
|
return None, f"yt-dlp failed: {err_msg[:200]}", is_permanent
|
||||||
|
|
||||||
|
output_path = await self._apply_faststart(output_path)
|
||||||
|
|
||||||
size = output_path.stat().st_size
|
size = output_path.stat().st_size
|
||||||
if size > max_size:
|
if size > max_size:
|
||||||
# Shouldn't happen - _select_format only hands back formats whose known size
|
# Shouldn't happen - _select_format only hands back formats whose known size
|
||||||
|
|||||||
Reference in New Issue
Block a user