some changes

This commit is contained in:
2026-04-01 16:26:25 -03:00
parent bdc5705022
commit 68802db15c
10 changed files with 500 additions and 567 deletions

View File

@@ -1,17 +1,13 @@
"""
Thin wrapper around ffmpeg-python for building and running ffmpeg pipelines.
All ffmpeg command construction goes through this module so manager.py
and other consumers never build raw CLI arg lists.
All ffmpeg command construction goes through this module.
Uses ffmpeg-python's own run/run_async for subprocess management.
"""
import logging
import os
import signal
import subprocess
from pathlib import Path
import ffmpeg
@@ -20,84 +16,37 @@ log = logging.getLogger(__name__)
GLOBAL_ARGS = ("-hide_banner", "-loglevel", "warning")
def receive_to_pipe(stream_url, segment_dir=None, segment_duration=60):
"""Receive mpegts stream and pipe to stdout for mpv.
def receive_and_record(stream_url, output_path):
"""Receive mpegts stream and write to a single growing file.
If segment_dir is provided, also saves segments to disk.
Uses pipe (not fifo) so OS kernel buffers prevent blocking.
mpv reads this file for DVR-style playback.
ffmpeg scene detection runs on this file for frame extraction.
Audio is preserved in the recording (muxed mpegts).
"""
stream = ffmpeg.input(stream_url, fflags="nobuffer", flags="low_delay")
out_pipe = ffmpeg.output(stream, "pipe:", c="copy", f="mpegts")
if segment_dir:
out_segments = ffmpeg.output(
stream,
str(segment_dir / "segment_%04d.ts"),
c="copy",
f="segment",
segment_time=segment_duration,
reset_timestamps=1,
)
return ffmpeg.merge_outputs(out_pipe, out_segments).global_args(*GLOBAL_ARGS)
return out_pipe.global_args(*GLOBAL_ARGS)
def receive_and_segment(stream_url, segment_dir, segment_duration=60):
"""Receive mpegts stream and save as segmented .ts files."""
stream = ffmpeg.input(stream_url, fflags="nobuffer", flags="low_delay")
return (
ffmpeg.output(
stream,
str(segment_dir / "segment_%04d.ts"),
c="copy",
f="segment",
segment_time=segment_duration,
reset_timestamps=1,
)
ffmpeg.output(stream, str(output_path), c="copy", f="mpegts")
.global_args(*GLOBAL_ARGS)
)
def receive_and_segment_with_monitor(stream_url, segment_dir, fifo_path, segment_duration=60):
"""Receive stream, save segments AND tee to a named pipe for monitoring."""
if not fifo_path.exists():
os.mkfifo(str(fifo_path))
stream = ffmpeg.input(stream_url, fflags="nobuffer", flags="low_delay")
out_segments = ffmpeg.output(
stream,
str(segment_dir / "segment_%04d.ts"),
c="copy",
f="segment",
segment_time=segment_duration,
reset_timestamps=1,
)
out_monitor = ffmpeg.output(
stream,
str(fifo_path),
c="copy",
f="mpegts",
)
return ffmpeg.merge_outputs(out_segments, out_monitor).global_args(*GLOBAL_ARGS)
def extract_scene_frames(input_path, output_dir, scene_threshold=0.3,
max_interval=30, start_number=1):
max_interval=30, start_number=1, start_time=0.0):
"""Extract frames from a file on scene change.
Uses ffmpeg select filter with scene detection and a max-interval fallback.
Returns (stdout bytes, stderr bytes) for timestamp parsing.
start_time: skip to this position before processing (avoids re-scanning).
Returns (stdout, stderr) as decoded strings for timestamp parsing.
"""
select_expr = (
f"gt(scene,{scene_threshold})"
f"+gte(t-prev_selected_t,{max_interval})"
)
stream = ffmpeg.input(str(input_path))
input_opts = {}
if start_time > 0:
input_opts["ss"] = str(start_time)
stream = ffmpeg.input(str(input_path), **input_opts)
stream = stream.filter("select", select_expr).filter("showinfo")
output = (
@@ -116,23 +65,6 @@ def extract_scene_frames(input_path, output_dir, scene_threshold=0.3,
return stdout.decode("utf-8", errors="replace"), stderr.decode("utf-8", errors="replace")
def extract_audio_pcm(input_path):
"""Extract audio as 16kHz mono PCM wav, returning an output node for piping."""
stream = ffmpeg.input(str(input_path))
return (
ffmpeg.output(
stream.audio,
"pipe:",
vn=None,
acodec="pcm_s16le",
ar=16000,
ac=1,
f="wav",
)
.global_args(*GLOBAL_ARGS)
)
def run_async(output_node, pipe_stdout=False, pipe_stderr=False):
"""Start an ffmpeg pipeline asynchronously via ffmpeg-python's run_async."""
log.info("run_async: %s", " ".join(output_node.compile()))