Stream Mp4 downloads

This commit is contained in:
dirwin31
2026-08-27 18:38:47 -07:00
parent 6c1ec6798f
commit 4d281c22d7
3 changed files with 91 additions and 3 deletions
@@ -487,6 +487,46 @@ def test_video_cache_never_evicts_the_entry_being_written(monkeypatch, tmp_path)
assert keep.exists()
def test_combined_video_streams_fragmented_mp4_without_a_full_cache_file(monkeypatch, tmp_path):
cache = tmp_path / "video_cache"
first = tmp_path / "first.hevc"
second = tmp_path / "second.hevc"
first.write_bytes(b"first")
second.write_bytes(b"second")
monkeypatch.setattr(utilities, "VIDEO_CACHE_PATH", cache)
captured = {}
class FakeProcess:
def __init__(self):
self.stdout = io.BytesIO(b"streamed-video")
self.returncode = None
def wait(self, timeout=None):
self.returncode = 0
return 0
def poll(self):
return self.returncode
def popen(command, **kwargs):
captured["command"] = command
list_path = Path(command[command.index("-i") + 1])
captured["list_path"] = list_path
captured["list_contents"] = list_path.read_text()
return FakeProcess()
monkeypatch.setattr(utilities.subprocess, "Popen", popen)
payload = b"".join(utilities.ffmpeg_stream_concatenated_mp4([first, second], chunk_size=4))
assert payload == b"streamed-video"
assert "frag_keyframe+empty_moov+default_base_moof" in captured["command"]
assert captured["command"][-1] == "pipe:1"
assert captured["list_contents"] == f"file '{first}'\nfile '{second}'\n"
assert not captured["list_path"].exists()
assert not list(cache.glob("*.mp4"))
def _stub_remux(monkeypatch, tmp_path, payload=b"wrapped-video"):
"""Stand in for the ffmpeg remux, returning a real file so send_file can stream it."""
wrapped = tmp_path / "wrapped.mp4"
@@ -501,7 +541,7 @@ def test_sparse_route_metadata_and_video_downloads(monkeypatch, tmp_path):
(segment / "fcamera.hevc").write_bytes(b"hevc")
monkeypatch.setattr(utilities, "get_route_start_time", lambda path: datetime(2026, 8, 26, tzinfo=timezone.utc))
_stub_remux(monkeypatch, tmp_path)
monkeypatch.setattr(utilities, "ffmpeg_concat_segments_to_mp4", lambda paths, cache_key=None: io.BytesIO(b"combined-video"))
monkeypatch.setattr(utilities, "ffmpeg_stream_concatenated_mp4", lambda paths: iter((b"combined-", b"video")))
client = _make_client(monkeypatch, tmp_path)
metadata = client.get(f"/api/routes/{ROUTE_NAME}")
@@ -519,6 +559,7 @@ def test_sparse_route_metadata_and_video_downloads(monkeypatch, tmp_path):
assert combined_video.status_code == 200
assert combined_video.mimetype == "video/mp4"
assert combined_video.data == b"combined-video"
assert combined_video.headers["X-Accel-Buffering"] == "no"
def test_route_metadata_never_probes_segments_with_ffprobe(monkeypatch, tmp_path):
+4 -2
View File
@@ -6482,8 +6482,10 @@ def setup(app):
if not input_files:
return {"error": "No video files found"}, 404
mp4_file = utilities.ffmpeg_concat_segments_to_mp4(input_files, cache_key=f"{name}-{camera}")
return send_file(mp4_file, mimetype="video/mp4")
response = Response(utilities.ffmpeg_stream_concatenated_mp4(input_files), mimetype="video/mp4")
response.headers["Cache-Control"] = "no-store"
response.headers["X-Accel-Buffering"] = "no"
return response
return {"error": "Route not found"}, 404
+45
View File
@@ -12,6 +12,7 @@ import signal
import socket
import subprocess
import sys
import tempfile
import threading
import time
@@ -749,6 +750,50 @@ def ffmpeg_concat_segments_to_mp4(input_files, cache_key=None):
return open(cache_path, "rb")
def ffmpeg_stream_concatenated_mp4(input_files, chunk_size=256 * 1024):
"""Stream-copy camera segments as fragmented MP4 without building a full cache file."""
if not input_files:
raise ValueError("No input files provided for concatenation")
VIDEO_CACHE_PATH.mkdir(exist_ok=True)
with tempfile.NamedTemporaryFile("w", suffix=".txt", prefix="route-download-", dir=VIDEO_CACHE_PATH, delete=False) as list_file:
list_path = Path(list_file.name)
for segment in input_files:
list_file.write(f"file '{Path(segment)}'\n")
process = None
try:
process = subprocess.Popen(
[FFMPEG_BIN, "-hide_banner", "-loglevel", "error", "-f", "concat", "-safe", "0",
"-i", str(list_path), "-c", "copy", "-movflags", "frag_keyframe+empty_moov+default_base_moof",
"-f", "mp4", "pipe:1"],
stdout=subprocess.PIPE,
stderr=subprocess.DEVNULL,
)
while True:
chunk = process.stdout.read(chunk_size)
if not chunk:
break
yield chunk
if process.wait() != 0:
raise ValueError("Could not stream the combined route video")
finally:
if process is not None:
if process.stdout is not None:
process.stdout.close()
if process.poll() is None:
process.terminate()
try:
process.wait(timeout=2)
except subprocess.TimeoutExpired:
process.kill()
process.wait()
try:
list_path.unlink()
except OSError:
pass
def ffmpeg_mp4_wrap_to_path(filename):
"""Remux one raw .hevc segment to mp4 and return the cache path.