This commit is contained in:
@@ -7,7 +7,13 @@ import tempfile
|
||||
from pathlib import Path
|
||||
from urllib.parse import unquote
|
||||
|
||||
from .config import VIDEO_EXTENSIONS, MOVIES_ROOT, SERIES_ROOT
|
||||
from .config import (
|
||||
VIDEO_EXTENSIONS,
|
||||
MOVIES_ROOT,
|
||||
SERIES_ROOT,
|
||||
NORMALIZE_LIVE_STREAM,
|
||||
NORMALIZE_TARGET_RES,
|
||||
)
|
||||
|
||||
logger = logging.getLogger(__name__)
|
||||
|
||||
@@ -196,11 +202,24 @@ def write_concat_list_from_current(paths: list[Path], durations: list[float], fd
|
||||
fd.flush()
|
||||
|
||||
|
||||
def _parse_resolution(res: str) -> tuple[int, int]:
|
||||
"""Parse 'WxH' into (width, height). Default 1920x1080 on parse error."""
|
||||
try:
|
||||
w, h = res.strip().lower().split("x")
|
||||
return int(w), int(h)
|
||||
except (ValueError, AttributeError):
|
||||
return 1920, 1080
|
||||
|
||||
|
||||
def stream_live_channel(paths: list[Path], wfile, durations: list[float]) -> None:
|
||||
"""
|
||||
Run FFmpeg to output a continuous MPEG-TS stream. Concat list starts at
|
||||
current position in schedule (midnight UTC cycle) so each new client joins
|
||||
where the 'channel' is now; then the list loops. EPG and stream stay in sync.
|
||||
|
||||
When NORMALIZE_LIVE_STREAM is enabled, videos are re-encoded to a uniform
|
||||
format (resolution, codec). This prevents crashes when switching between
|
||||
episodes with different resolutions or codec parameters.
|
||||
"""
|
||||
if not paths:
|
||||
return
|
||||
@@ -209,20 +228,37 @@ def stream_live_channel(paths: list[Path], wfile, durations: list[float]) -> Non
|
||||
write_concat_list_from_current(paths, durations, f)
|
||||
list_path = f.name
|
||||
try:
|
||||
proc = subprocess.Popen(
|
||||
[
|
||||
"ffmpeg",
|
||||
"-fflags", "+nobuffer+flush_packets",
|
||||
"-stream_loop", "-1",
|
||||
"-f", "concat", "-safe", "0", "-i", list_path,
|
||||
"-c", "copy",
|
||||
base_args = [
|
||||
"ffmpeg",
|
||||
"-fflags", "+nobuffer+flush_packets",
|
||||
"-stream_loop", "-1",
|
||||
"-f", "concat", "-safe", "0", "-i", list_path,
|
||||
]
|
||||
if NORMALIZE_LIVE_STREAM:
|
||||
w, h = _parse_resolution(NORMALIZE_TARGET_RES)
|
||||
# Scale to fit target res, pad to exact size. Re-encode for uniform output.
|
||||
vf = f"scale={w}:{h}:force_original_aspect_ratio=decrease,pad={w}:{h}:(ow-iw)/2:(oh-ih)/2"
|
||||
output_args = [
|
||||
"-vf", vf,
|
||||
"-c:v", "libx264", "-preset", "ultrafast", "-tune", "zerolatency",
|
||||
"-c:a", "aac", "-ac", "2", "-b:a", "128k",
|
||||
"-avoid_negative_ts", "make_zero",
|
||||
"-muxdelay", "0",
|
||||
"-muxpreload", "0",
|
||||
"-muxdelay", "0", "-muxpreload", "0",
|
||||
"-max_muxing_queue_size", "1024",
|
||||
"-f", "mpegts",
|
||||
"-",
|
||||
],
|
||||
]
|
||||
else:
|
||||
output_args = [
|
||||
"-c", "copy",
|
||||
"-avoid_negative_ts", "make_zero",
|
||||
"-muxdelay", "0", "-muxpreload", "0",
|
||||
"-max_muxing_queue_size", "1024",
|
||||
"-f", "mpegts",
|
||||
"-",
|
||||
]
|
||||
proc = subprocess.Popen(
|
||||
base_args + output_args,
|
||||
stdout=subprocess.PIPE,
|
||||
stderr=subprocess.DEVNULL,
|
||||
start_new_session=True,
|
||||
|
||||
Reference in New Issue
Block a user