continuous stream
Build and Push Docker Images / build-and-push (push) Successful in 22s

This commit is contained in:
2026-02-11 22:14:14 +01:00
parent bc4a834e46
commit 6348ecb732
4 changed files with 71 additions and 12 deletions
+7
View File
@@ -60,3 +60,10 @@ TMDB_IMAGE_STILL_SIZE = "w500"
# Preferred audio languages for streaming (in order): Dutch → Flemish → default
PREFERRED_AUDIO_LANGUAGES = ("nld", "dut", "nl", "vls")
# Live stream normalization: re-encode to uniform format to avoid crashes when
# switching between episodes with different resolutions/codecs. Set
# NORMALIZE_LIVE_STREAM=1 to enable. Uses more CPU but produces a stable stream.
NORMALIZE_LIVE_STREAM = os.environ.get("NORMALIZE_LIVE_STREAM", "").lower() in ("1", "true", "yes")
# Target resolution when normalizing (e.g. "1920x1080" or "1280x720")
NORMALIZE_TARGET_RES = os.environ.get("NORMALIZE_TARGET_RES", "1920x1080").strip() or "1920x1080"
+47 -11
View File
@@ -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,