This commit is contained in:
@@ -1,9 +1,12 @@
|
||||
"""Video streaming: preferred-audio remux and live channel concat."""
|
||||
|
||||
from __future__ import annotations
|
||||
|
||||
import json
|
||||
import logging
|
||||
import subprocess
|
||||
import tempfile
|
||||
from collections.abc import Iterator
|
||||
from pathlib import Path
|
||||
from urllib.parse import unquote
|
||||
|
||||
@@ -107,45 +110,64 @@ def get_preferred_audio_stream_index(path: Path) -> int | None:
|
||||
return None
|
||||
|
||||
|
||||
def stream_file_with_preferred_audio(path: Path, wfile) -> bool:
|
||||
def try_remux_preferred_audio(path: Path) -> tuple[bytes, Iterator[bytes]] | None:
|
||||
"""
|
||||
Stream the file via FFmpeg with only video and the preferred audio track.
|
||||
Returns True if streaming was started, False if caller should fall back to raw file.
|
||||
Start FFmpeg remux with preferred audio. Returns (first_chunk, rest_iterator) if
|
||||
FFmpeg produces output, else None. Caller must send headers only after getting
|
||||
a non-None result, then write first_chunk and iterate rest.
|
||||
"""
|
||||
audio_idx = get_preferred_audio_stream_index(path)
|
||||
if audio_idx is None:
|
||||
return False
|
||||
return None
|
||||
try:
|
||||
proc = subprocess.Popen(
|
||||
[
|
||||
"ffmpeg",
|
||||
"-i", str(path),
|
||||
"-map", "0:v",
|
||||
"-map", "0:v:0",
|
||||
"-map", f"0:{audio_idx}",
|
||||
"-c", "copy",
|
||||
"-f", "mp4",
|
||||
"-movflags", "frag_keyframe+empty_moov+default_base_moof",
|
||||
"-max_muxing_queue_size", "1024",
|
||||
"-",
|
||||
],
|
||||
stdout=subprocess.PIPE,
|
||||
stderr=subprocess.DEVNULL,
|
||||
stderr=subprocess.PIPE,
|
||||
start_new_session=True,
|
||||
)
|
||||
while True:
|
||||
chunk = proc.stdout.read(65536)
|
||||
if not chunk:
|
||||
break
|
||||
wfile.write(chunk)
|
||||
wfile.flush()
|
||||
proc.wait(timeout=1)
|
||||
except (OSError, subprocess.TimeoutExpired):
|
||||
first = proc.stdout.read(65536)
|
||||
if not first:
|
||||
proc.wait(timeout=5)
|
||||
if proc.returncode != 0:
|
||||
err = proc.stderr.read().decode("utf-8", errors="replace")[-500:]
|
||||
logger.warning("FFmpeg remux failed (exit %s): %s", proc.returncode, err)
|
||||
return None
|
||||
|
||||
def rest():
|
||||
try:
|
||||
while True:
|
||||
chunk = proc.stdout.read(65536)
|
||||
if not chunk:
|
||||
break
|
||||
yield chunk
|
||||
finally:
|
||||
proc.wait(timeout=5)
|
||||
try:
|
||||
proc.terminate()
|
||||
proc.wait(timeout=2)
|
||||
except Exception:
|
||||
pass
|
||||
|
||||
return (first, rest())
|
||||
except (OSError, subprocess.TimeoutExpired) as e:
|
||||
logger.warning("FFmpeg remux error: %s", e)
|
||||
try:
|
||||
proc.terminate()
|
||||
proc.wait(timeout=2)
|
||||
except Exception:
|
||||
pass
|
||||
return False
|
||||
return True
|
||||
return None
|
||||
|
||||
|
||||
def _escape_path(p: Path) -> str:
|
||||
|
||||
Reference in New Issue
Block a user