first try new approach
This commit is contained in:
@@ -39,7 +39,7 @@ def receive_and_record(stream_url, output_path):
|
||||
)
|
||||
|
||||
|
||||
def receive_record_and_relay(stream_url, output_path, relay_url, scene_relay_url=None):
|
||||
def receive_record_and_relay(stream_url, output_path, relay_url):
|
||||
"""Receive TCP stream, write to fragmented MP4, and relay to UDP loopback.
|
||||
|
||||
Fragmented MP4 (frag_keyframe+empty_moov) avoids MKV tail corruption:
|
||||
@@ -47,7 +47,6 @@ def receive_record_and_relay(stream_url, output_path, relay_url, scene_relay_url
|
||||
always valid up to the last complete fragment (~1 keyframe interval ≈ 2s).
|
||||
|
||||
Uses ffmpeg tee via merge_outputs: one process, identical timestamps.
|
||||
Optionally sends a second relay for the scene detector.
|
||||
"""
|
||||
stream = ffmpeg.input(stream_url, fflags="nobuffer", flags="low_delay")
|
||||
file_out = ffmpeg.output(
|
||||
@@ -61,50 +60,49 @@ def receive_record_and_relay(stream_url, output_path, relay_url, scene_relay_url
|
||||
stream, relay_url,
|
||||
c="copy", f="mpegts",
|
||||
)
|
||||
outputs = [file_out, relay_out]
|
||||
if scene_relay_url:
|
||||
scene_out = ffmpeg.output(
|
||||
stream, scene_relay_url,
|
||||
c="copy", f="mpegts",
|
||||
)
|
||||
outputs.append(scene_out)
|
||||
return ffmpeg.merge_outputs(*outputs).global_args(*QUIET_ARGS)
|
||||
return ffmpeg.merge_outputs(file_out, relay_out).global_args(*QUIET_ARGS)
|
||||
|
||||
|
||||
def start_live_scene_detector(stream_url, output_dir, scene_threshold=0.10,
|
||||
start_number=1):
|
||||
"""Start a persistent ffmpeg process that detects scenes from a live stream.
|
||||
def receive_record_relay_and_detect(stream_url, output_path, relay_url,
|
||||
frames_dir, scene_threshold=0.10,
|
||||
start_number=1):
|
||||
"""Single process: receive TCP → record fMP4 + relay UDP + scene detect.
|
||||
|
||||
Reads from the UDP relay in real-time — no file seeking, no restart overhead.
|
||||
Writes frame JPEGs and emits showinfo on stderr as scenes are detected.
|
||||
Returns the async process (stderr must be read continuously).
|
||||
One ffmpeg process, three output branches from the same TCP input:
|
||||
1. File output — c=copy to fMP4
|
||||
2. UDP relay — c=copy to mpegts for live display
|
||||
3. Scene frames — CUDA decode → select(scene) → showinfo → JPEG files
|
||||
|
||||
The scene filter runs on decoded frames in-process, so detection latency
|
||||
is near-zero (no polling, no file re-reading, no separate process).
|
||||
Stderr must be read continuously to parse showinfo lines.
|
||||
"""
|
||||
stream = ffmpeg.input(stream_url, fflags="nobuffer", flags="low_delay",
|
||||
hwaccel="cuda")
|
||||
|
||||
# Copy outputs (raw packet remux, no decode)
|
||||
file_out = ffmpeg.output(
|
||||
stream, str(output_path),
|
||||
c="copy", f="mp4",
|
||||
movflags="frag_keyframe+empty_moov+default_base_moof",
|
||||
flush_packets=1,
|
||||
**{"bsf:a": "aac_adtstoasc"},
|
||||
)
|
||||
relay_out = ffmpeg.output(
|
||||
stream, relay_url,
|
||||
c="copy", f="mpegts",
|
||||
)
|
||||
|
||||
# Scene detection output (decode + filter → JPEG)
|
||||
select_expr = f"gt(scene,{scene_threshold})"
|
||||
|
||||
stream = ffmpeg.input(
|
||||
stream_url,
|
||||
fflags="nobuffer+flush_packets",
|
||||
flags="low_delay",
|
||||
probesize="32000",
|
||||
analyzeduration="0",
|
||||
hwaccel="cuda",
|
||||
)
|
||||
stream = stream.filter("select", select_expr).filter("showinfo")
|
||||
|
||||
output = (
|
||||
ffmpeg.output(
|
||||
stream,
|
||||
str(output_dir / "F%04d.jpg"),
|
||||
vsync="vfr",
|
||||
flush_packets=1,
|
||||
**{"q:v": "2"},
|
||||
start_number=start_number,
|
||||
)
|
||||
.global_args(*GLOBAL_ARGS)
|
||||
scene_stream = stream.filter("select", select_expr).filter("showinfo")
|
||||
scene_out = ffmpeg.output(
|
||||
scene_stream, str(frames_dir / "F%04d.jpg"),
|
||||
vsync="vfr", flush_packets=1, **{"q:v": "2"},
|
||||
start_number=start_number,
|
||||
)
|
||||
|
||||
log.info("start_live_scene_detector: %s", " ".join(output.compile()))
|
||||
return run_async(output, pipe_stderr=True)
|
||||
return ffmpeg.merge_outputs(file_out, relay_out, scene_out).global_args(*GLOBAL_ARGS)
|
||||
|
||||
|
||||
def extract_scene_frames(input_path, output_dir, scene_threshold=0.10,
|
||||
|
||||
Reference in New Issue
Block a user