forked from Github/frigate
Rewrite restream (#5106)
* Tear out restream config * Rework birdseye restream * Create go2rtc config handler * Fix bug * Write start script * Rework style * Fix python run syntax * Output as json instead of yaml * Put old live config back and fix birdseye references * Fix camera webUI * Add frigate env var subsitutions * Fix webui checks * Check keys * Remove unused prest * Fix tests * Update restream docs * Update restream docs * Update live docs * Update camera specific recommendation * Update more docs * add links for the docs Co-authored-by: Felipe Santos <felipecassiors@gmail.com> * Update note about supported audio codecs * Move restream to go2rtc * Docs fixes * Add verification of stream name * Ensure that webUI uses camera name * Update docs to reflect new live stream name * Fix check * Formatting * Remove audio from detect Co-authored-by: Felipe Santos <felipecassiors@gmail.com> * Fix docs * Don't handle env variable substitution * Add go2rtc version * Clarify docs Co-authored-by: Felipe Santos <felipecassiors@gmail.com>
This commit is contained in:
@@ -344,6 +344,7 @@ class BirdseyeModeEnum(str, Enum):
|
||||
|
||||
class BirdseyeConfig(FrigateBaseModel):
|
||||
enabled: bool = Field(default=True, title="Enable birdseye view.")
|
||||
restream: bool = Field(default=False, title="Restream birdseye via RTSP.")
|
||||
width: int = Field(default=1280, title="Birdseye width.")
|
||||
height: int = Field(default=720, title="Birdseye height.")
|
||||
quality: int = Field(
|
||||
@@ -405,7 +406,6 @@ class FfmpegConfig(FrigateBaseModel):
|
||||
|
||||
class CameraRoleEnum(str, Enum):
|
||||
record = "record"
|
||||
restream = "restream"
|
||||
rtmp = "rtmp"
|
||||
detect = "detect"
|
||||
|
||||
@@ -519,39 +519,15 @@ class RtmpConfig(FrigateBaseModel):
|
||||
enabled: bool = Field(default=False, title="RTMP restreaming enabled.")
|
||||
|
||||
|
||||
class JsmpegStreamConfig(FrigateBaseModel):
|
||||
height: int = Field(default=720, title="Live camera view height.")
|
||||
quality: int = Field(default=8, ge=1, le=31, title="Live camera view quality.")
|
||||
class CameraLiveConfig(FrigateBaseModel):
|
||||
stream_name: str = Field(default="", title="Name of restream to use as live view.")
|
||||
height: int = Field(default=720, title="Live camera view height")
|
||||
quality: int = Field(default=8, ge=1, le=31, title="Live camera view quality")
|
||||
|
||||
|
||||
class RestreamVideoCodecEnum(str, Enum):
|
||||
copy = "copy"
|
||||
h264 = "h264"
|
||||
h265 = "h265"
|
||||
|
||||
|
||||
class RestreamAudioCodecEnum(str, Enum):
|
||||
aac = "aac"
|
||||
copy = "copy"
|
||||
opus = "opus"
|
||||
|
||||
|
||||
class RestreamConfig(FrigateBaseModel):
|
||||
enabled: bool = Field(default=True, title="Restreaming enabled.")
|
||||
audio_encoding: list[RestreamAudioCodecEnum] = Field(
|
||||
default=[RestreamAudioCodecEnum.aac, RestreamAudioCodecEnum.opus],
|
||||
title="Codecs to supply for audio.",
|
||||
)
|
||||
video_encoding: RestreamVideoCodecEnum = Field(
|
||||
default=RestreamVideoCodecEnum.copy, title="Method for encoding the restream."
|
||||
)
|
||||
force_audio: bool = Field(
|
||||
default=True, title="Force audio compatibility with the browser."
|
||||
)
|
||||
birdseye: bool = Field(default=False, title="Restream the birdseye feed via RTSP.")
|
||||
jsmpeg: JsmpegStreamConfig = Field(
|
||||
default_factory=JsmpegStreamConfig, title="Jsmpeg Stream Configuration."
|
||||
)
|
||||
class RestreamConfig(BaseModel):
|
||||
class Config:
|
||||
extra = Extra.allow
|
||||
|
||||
|
||||
class CameraUiConfig(FrigateBaseModel):
|
||||
@@ -578,8 +554,8 @@ class CameraConfig(FrigateBaseModel):
|
||||
rtmp: RtmpConfig = Field(
|
||||
default_factory=RtmpConfig, title="RTMP restreaming configuration."
|
||||
)
|
||||
restream: RestreamConfig = Field(
|
||||
default_factory=RestreamConfig, title="Restreaming configuration."
|
||||
live: CameraLiveConfig = Field(
|
||||
default_factory=CameraLiveConfig, title="Live playback settings."
|
||||
)
|
||||
snapshots: SnapshotsConfig = Field(
|
||||
default_factory=SnapshotsConfig, title="Snapshot configuration."
|
||||
@@ -621,7 +597,6 @@ class CameraConfig(FrigateBaseModel):
|
||||
config["ffmpeg"]["inputs"][0]["roles"] = [
|
||||
"record",
|
||||
"detect",
|
||||
"restream",
|
||||
]
|
||||
|
||||
if has_rtmp:
|
||||
@@ -758,9 +733,17 @@ def verify_config_roles(camera_config: CameraConfig) -> None:
|
||||
f"Camera {camera_config.name} has rtmp enabled, but rtmp is not assigned to an input."
|
||||
)
|
||||
|
||||
if camera_config.restream.enabled and not "restream" in assigned_roles:
|
||||
raise ValueError(
|
||||
f"Camera {camera_config.name} has restream enabled, but restream is not assigned to an input."
|
||||
|
||||
def verify_valid_live_stream_name(
|
||||
frigate_config: FrigateConfig, camera_config: CameraConfig
|
||||
) -> None:
|
||||
"""Verify that a restream exists to use for live view."""
|
||||
if (
|
||||
camera_config.live.stream_name
|
||||
not in frigate_config.go2rtc.dict().get("streams", {}).keys()
|
||||
):
|
||||
return ValueError(
|
||||
f"No restream with name {camera_config.live.stream_name} exists for camera {camera_config.name}."
|
||||
)
|
||||
|
||||
|
||||
@@ -854,7 +837,10 @@ class FrigateConfig(FrigateBaseModel):
|
||||
rtmp: RtmpConfig = Field(
|
||||
default_factory=RtmpConfig, title="Global RTMP restreaming configuration."
|
||||
)
|
||||
restream: RestreamConfig = Field(
|
||||
live: CameraLiveConfig = Field(
|
||||
default_factory=CameraLiveConfig, title="Live playback settings."
|
||||
)
|
||||
go2rtc: RestreamConfig = Field(
|
||||
default_factory=RestreamConfig, title="Global restream configuration."
|
||||
)
|
||||
birdseye: BirdseyeConfig = Field(
|
||||
@@ -895,7 +881,7 @@ class FrigateConfig(FrigateBaseModel):
|
||||
"record": ...,
|
||||
"snapshots": ...,
|
||||
"rtmp": ...,
|
||||
"restream": ...,
|
||||
"live": ...,
|
||||
"objects": ...,
|
||||
"motion": ...,
|
||||
"detect": ...,
|
||||
@@ -968,7 +954,12 @@ class FrigateConfig(FrigateBaseModel):
|
||||
**camera_config.motion.dict(exclude_unset=True),
|
||||
)
|
||||
|
||||
# Set live view stream if none is set
|
||||
if not camera_config.live.stream_name:
|
||||
camera_config.live.stream_name = name
|
||||
|
||||
verify_config_roles(camera_config)
|
||||
verify_valid_live_stream_name(config, camera_config)
|
||||
verify_old_retain_config(camera_config)
|
||||
verify_recording_retention(camera_config)
|
||||
verify_recording_segments_setup_with_reasonable_time(camera_config)
|
||||
|
||||
@@ -102,17 +102,6 @@ PRESETS_HW_ACCEL_ENCODE = {
|
||||
"default": "ffmpeg -hide_banner {0} -c:v libx264 -g 50 -profile:v high -level:v 4.1 -preset:v superfast -tune:v zerolatency {1}",
|
||||
}
|
||||
|
||||
PRESETS_HW_ACCEL_GO2RTC_ENGINE = {
|
||||
"preset-rpi-32-h264": "v4l2m2m",
|
||||
"preset-rpi-64-h264": "v4l2m2m",
|
||||
"preset-intel-vaapi": "vaapi",
|
||||
"preset-intel-qsv-h264": "vaapi", # go2rtc doesn't support qsv
|
||||
"preset-intel-qsv-h265": "vaapi",
|
||||
"preset-amd-vaapi": "vaapi",
|
||||
"preset-nvidia-h264": "cuda",
|
||||
"preset-nvidia-h265": "cuda",
|
||||
}
|
||||
|
||||
|
||||
def parse_preset_hardware_acceleration_decode(arg: Any) -> list[str]:
|
||||
"""Return the correct preset if in preset format otherwise return None."""
|
||||
@@ -156,14 +145,6 @@ def parse_preset_hardware_acceleration_encode(arg: Any, input: str, output: str)
|
||||
)
|
||||
|
||||
|
||||
def parse_preset_hardware_acceleration_go2rtc_engine(arg: Any) -> list[str]:
|
||||
"""Return the correct engine for the preset otherwise returns None."""
|
||||
if not isinstance(arg, str):
|
||||
return None
|
||||
|
||||
return PRESETS_HW_ACCEL_GO2RTC_ENGINE.get(arg)
|
||||
|
||||
|
||||
PRESETS_INPUT = {
|
||||
"preset-http-jpeg-generic": _user_agent_args
|
||||
+ [
|
||||
|
||||
@@ -415,15 +415,15 @@ def output_frames(config: FrigateConfig, video_output_queue):
|
||||
|
||||
for camera, cam_config in config.cameras.items():
|
||||
width = int(
|
||||
cam_config.restream.jsmpeg.height
|
||||
cam_config.live.height
|
||||
* (cam_config.frame_shape[1] / cam_config.frame_shape[0])
|
||||
)
|
||||
converters[camera] = FFMpegConverter(
|
||||
cam_config.frame_shape[1],
|
||||
cam_config.frame_shape[0],
|
||||
width,
|
||||
cam_config.restream.jsmpeg.height,
|
||||
cam_config.restream.jsmpeg.quality,
|
||||
cam_config.live.height,
|
||||
cam_config.live.quality,
|
||||
)
|
||||
broadcasters[camera] = BroadcastThread(
|
||||
camera, converters[camera], websocket_server
|
||||
@@ -436,7 +436,7 @@ def output_frames(config: FrigateConfig, video_output_queue):
|
||||
config.birdseye.width,
|
||||
config.birdseye.height,
|
||||
config.birdseye.quality,
|
||||
config.restream.birdseye,
|
||||
config.birdseye.restream,
|
||||
)
|
||||
broadcasters["birdseye"] = BroadcastThread(
|
||||
"birdseye", converters["birdseye"], websocket_server
|
||||
@@ -449,7 +449,7 @@ def output_frames(config: FrigateConfig, video_output_queue):
|
||||
|
||||
birdseye_manager = BirdsEyeFrameManager(config, frame_manager)
|
||||
|
||||
if config.restream.birdseye:
|
||||
if config.birdseye.restream:
|
||||
birdseye_buffer = frame_manager.create(
|
||||
"birdseye",
|
||||
birdseye_manager.yuv_shape[0] * birdseye_manager.yuv_shape[1],
|
||||
@@ -479,7 +479,7 @@ def output_frames(config: FrigateConfig, video_output_queue):
|
||||
converters[camera].write(frame.tobytes())
|
||||
|
||||
if config.birdseye.enabled and (
|
||||
config.restream.birdseye
|
||||
config.birdseye.restream
|
||||
or any(
|
||||
ws.environ["PATH_INFO"].endswith("birdseye")
|
||||
for ws in websocket_server.manager
|
||||
@@ -494,7 +494,7 @@ def output_frames(config: FrigateConfig, video_output_queue):
|
||||
):
|
||||
frame_bytes = birdseye_manager.frame.tobytes()
|
||||
|
||||
if config.restream.birdseye:
|
||||
if config.birdseye.restream:
|
||||
birdseye_buffer[:] = frame_bytes
|
||||
|
||||
converters["birdseye"].write(frame_bytes)
|
||||
|
||||
@@ -4,42 +4,15 @@
|
||||
import logging
|
||||
import requests
|
||||
|
||||
from typing import Optional
|
||||
|
||||
from frigate.config import FrigateConfig, RestreamAudioCodecEnum, RestreamVideoCodecEnum
|
||||
from frigate.config import FrigateConfig
|
||||
from frigate.const import BIRDSEYE_PIPE
|
||||
from frigate.ffmpeg_presets import (
|
||||
parse_preset_hardware_acceleration_encode,
|
||||
parse_preset_hardware_acceleration_go2rtc_engine,
|
||||
)
|
||||
from frigate.util import escape_special_characters
|
||||
|
||||
logger = logging.getLogger(__name__)
|
||||
|
||||
|
||||
def get_manual_go2rtc_stream(
|
||||
camera_url: str,
|
||||
aCodecs: list[RestreamAudioCodecEnum],
|
||||
vCodec: RestreamVideoCodecEnum,
|
||||
engine: Optional[str],
|
||||
) -> str:
|
||||
"""Get a manual stream for go2rtc."""
|
||||
stream = f"ffmpeg:{camera_url}"
|
||||
|
||||
for aCodec in aCodecs:
|
||||
stream += f"#audio={aCodec}"
|
||||
|
||||
if vCodec == RestreamVideoCodecEnum.copy:
|
||||
stream += "#video=copy"
|
||||
else:
|
||||
stream += f"#video={vCodec}"
|
||||
|
||||
if engine:
|
||||
stream += f"#hardware={engine}"
|
||||
|
||||
return stream
|
||||
|
||||
|
||||
class RestreamApi:
|
||||
"""Control go2rtc relay API."""
|
||||
|
||||
@@ -50,34 +23,7 @@ class RestreamApi:
|
||||
"""Add cameras to go2rtc."""
|
||||
self.relays: dict[str, str] = {}
|
||||
|
||||
for cam_name, camera in self.config.cameras.items():
|
||||
if not camera.restream.enabled:
|
||||
continue
|
||||
|
||||
for input in camera.ffmpeg.inputs:
|
||||
if "restream" in input.roles:
|
||||
if (
|
||||
input.path.startswith("rtsp")
|
||||
and camera.restream.video_encoding
|
||||
== RestreamVideoCodecEnum.copy
|
||||
and camera.restream.audio_encoding
|
||||
== [RestreamAudioCodecEnum.copy]
|
||||
):
|
||||
self.relays[
|
||||
cam_name
|
||||
] = f"{escape_special_characters(input.path)}#backchannel=0"
|
||||
else:
|
||||
# go2rtc only supports rtsp for direct relay, otherwise ffmpeg is used
|
||||
self.relays[cam_name] = get_manual_go2rtc_stream(
|
||||
escape_special_characters(input.path),
|
||||
camera.restream.audio_encoding,
|
||||
camera.restream.video_encoding,
|
||||
parse_preset_hardware_acceleration_go2rtc_engine(
|
||||
self.config.ffmpeg.hwaccel_args
|
||||
),
|
||||
)
|
||||
|
||||
if self.config.restream.birdseye:
|
||||
if self.config.birdseye.restream:
|
||||
self.relays[
|
||||
"birdseye"
|
||||
] = f"exec:{parse_preset_hardware_acceleration_encode(self.config.ffmpeg.hwaccel_args, f'-f rawvideo -pix_fmt yuv420p -video_size {self.config.birdseye.width}x{self.config.birdseye.height} -r 10 -i {BIRDSEYE_PIPE}', '-rtsp_transport tcp -f rtsp {output}')}"
|
||||
|
||||
@@ -621,7 +621,7 @@ class TestConfig(unittest.TestCase):
|
||||
"inputs": [
|
||||
{
|
||||
"path": "rtsp://10.0.0.1:554/video",
|
||||
"roles": ["detect", "rtmp", "restream"],
|
||||
"roles": ["detect", "rtmp"],
|
||||
},
|
||||
{"path": "rtsp://10.0.0.1:554/record", "roles": ["record"]},
|
||||
]
|
||||
@@ -883,7 +883,6 @@ class TestConfig(unittest.TestCase):
|
||||
|
||||
config = {
|
||||
"mqtt": {"host": "mqtt"},
|
||||
"restream": {"enabled": False},
|
||||
"cameras": {
|
||||
"back": {
|
||||
"ffmpeg": {
|
||||
@@ -1096,30 +1095,6 @@ class TestConfig(unittest.TestCase):
|
||||
assert runtime_config.cameras["back"].snapshots.height == 150
|
||||
assert runtime_config.cameras["back"].snapshots.enabled
|
||||
|
||||
def test_global_restream(self):
|
||||
|
||||
config = {
|
||||
"mqtt": {"host": "mqtt"},
|
||||
"restream": {"enabled": True},
|
||||
"cameras": {
|
||||
"back": {
|
||||
"ffmpeg": {
|
||||
"inputs": [
|
||||
{
|
||||
"path": "rtsp://10.0.0.1:554/video",
|
||||
"roles": ["detect"],
|
||||
},
|
||||
]
|
||||
},
|
||||
}
|
||||
},
|
||||
}
|
||||
frigate_config = FrigateConfig(**config)
|
||||
assert config == frigate_config.dict(exclude_unset=True)
|
||||
|
||||
runtime_config = frigate_config.runtime_config
|
||||
assert runtime_config.cameras["back"].restream.enabled
|
||||
|
||||
def test_global_rtmp_disabled(self):
|
||||
|
||||
config = {
|
||||
@@ -1166,56 +1141,6 @@ class TestConfig(unittest.TestCase):
|
||||
runtime_config = frigate_config.runtime_config
|
||||
assert not runtime_config.cameras["back"].rtmp.enabled
|
||||
|
||||
def test_default_restream(self):
|
||||
|
||||
config = {
|
||||
"mqtt": {"host": "mqtt"},
|
||||
"cameras": {
|
||||
"back": {
|
||||
"ffmpeg": {
|
||||
"inputs": [
|
||||
{
|
||||
"path": "rtsp://10.0.0.1:554/video",
|
||||
"roles": ["detect"],
|
||||
},
|
||||
]
|
||||
}
|
||||
}
|
||||
},
|
||||
}
|
||||
frigate_config = FrigateConfig(**config)
|
||||
assert config == frigate_config.dict(exclude_unset=True)
|
||||
|
||||
runtime_config = frigate_config.runtime_config
|
||||
assert runtime_config.cameras["back"].restream.enabled
|
||||
|
||||
def test_global_restream_merge(self):
|
||||
|
||||
config = {
|
||||
"mqtt": {"host": "mqtt"},
|
||||
"restream": {"enabled": False},
|
||||
"cameras": {
|
||||
"back": {
|
||||
"ffmpeg": {
|
||||
"inputs": [
|
||||
{
|
||||
"path": "rtsp://10.0.0.1:554/video",
|
||||
"roles": ["detect"],
|
||||
},
|
||||
]
|
||||
},
|
||||
"restream": {
|
||||
"enabled": True,
|
||||
},
|
||||
}
|
||||
},
|
||||
}
|
||||
frigate_config = FrigateConfig(**config)
|
||||
assert config == frigate_config.dict(exclude_unset=True)
|
||||
|
||||
runtime_config = frigate_config.runtime_config
|
||||
assert runtime_config.cameras["back"].restream.enabled
|
||||
|
||||
def test_global_rtmp_merge(self):
|
||||
|
||||
config = {
|
||||
@@ -1247,7 +1172,6 @@ class TestConfig(unittest.TestCase):
|
||||
|
||||
config = {
|
||||
"mqtt": {"host": "mqtt"},
|
||||
"restream": {"enabled": False},
|
||||
"cameras": {
|
||||
"back": {
|
||||
"ffmpeg": {
|
||||
@@ -1275,7 +1199,7 @@ class TestConfig(unittest.TestCase):
|
||||
|
||||
config = {
|
||||
"mqtt": {"host": "mqtt"},
|
||||
"restream": {"jsmpeg": {"quality": 4}},
|
||||
"live": {"quality": 4},
|
||||
"cameras": {
|
||||
"back": {
|
||||
"ffmpeg": {
|
||||
@@ -1293,7 +1217,7 @@ class TestConfig(unittest.TestCase):
|
||||
assert config == frigate_config.dict(exclude_unset=True)
|
||||
|
||||
runtime_config = frigate_config.runtime_config
|
||||
assert runtime_config.cameras["back"].restream.jsmpeg.quality == 4
|
||||
assert runtime_config.cameras["back"].live.quality == 4
|
||||
|
||||
def test_default_live(self):
|
||||
|
||||
@@ -1316,13 +1240,13 @@ class TestConfig(unittest.TestCase):
|
||||
assert config == frigate_config.dict(exclude_unset=True)
|
||||
|
||||
runtime_config = frigate_config.runtime_config
|
||||
assert runtime_config.cameras["back"].restream.jsmpeg.quality == 8
|
||||
assert runtime_config.cameras["back"].live.quality == 8
|
||||
|
||||
def test_global_live_merge(self):
|
||||
|
||||
config = {
|
||||
"mqtt": {"host": "mqtt"},
|
||||
"restream": {"jsmpeg": {"quality": 4, "height": 480}},
|
||||
"live": {"quality": 4, "height": 480},
|
||||
"cameras": {
|
||||
"back": {
|
||||
"ffmpeg": {
|
||||
@@ -1333,10 +1257,8 @@ class TestConfig(unittest.TestCase):
|
||||
},
|
||||
]
|
||||
},
|
||||
"restream": {
|
||||
"jsmpeg": {
|
||||
"quality": 7,
|
||||
}
|
||||
"live": {
|
||||
"quality": 7,
|
||||
},
|
||||
}
|
||||
},
|
||||
@@ -1345,8 +1267,8 @@ class TestConfig(unittest.TestCase):
|
||||
assert config == frigate_config.dict(exclude_unset=True)
|
||||
|
||||
runtime_config = frigate_config.runtime_config
|
||||
assert runtime_config.cameras["back"].restream.jsmpeg.quality == 7
|
||||
assert runtime_config.cameras["back"].restream.jsmpeg.height == 480
|
||||
assert runtime_config.cameras["back"].live.quality == 7
|
||||
assert runtime_config.cameras["back"].live.height == 480
|
||||
|
||||
def test_global_timestamp_style(self):
|
||||
|
||||
|
||||
@@ -1,82 +0,0 @@
|
||||
"""Test restream.py."""
|
||||
|
||||
from unittest import TestCase, main
|
||||
from unittest.mock import patch
|
||||
|
||||
from frigate.config import FrigateConfig
|
||||
from frigate.restream import RestreamApi
|
||||
|
||||
|
||||
class TestRestream(TestCase):
|
||||
def setUp(self) -> None:
|
||||
"""Setup the tests."""
|
||||
self.config = {
|
||||
"mqtt": {"host": "mqtt"},
|
||||
"restream": {"enabled": False},
|
||||
"cameras": {
|
||||
"back": {
|
||||
"ffmpeg": {
|
||||
"inputs": [
|
||||
{
|
||||
"path": "rtsp://10.0.0.1:554/video",
|
||||
"roles": ["detect", "restream"],
|
||||
},
|
||||
]
|
||||
},
|
||||
"restream": {
|
||||
"enabled": True,
|
||||
"audio_encoding": ["copy"],
|
||||
},
|
||||
},
|
||||
"front": {
|
||||
"ffmpeg": {
|
||||
"inputs": [
|
||||
{
|
||||
"path": "http://10.0.0.1:554/video/stream",
|
||||
"roles": ["detect", "restream"],
|
||||
},
|
||||
]
|
||||
},
|
||||
"restream": {
|
||||
"enabled": True,
|
||||
},
|
||||
},
|
||||
},
|
||||
}
|
||||
|
||||
@patch("frigate.restream.requests")
|
||||
def test_rtsp_stream(
|
||||
self, mock_request
|
||||
) -> None: # need to ensure restream doesn't try to call API
|
||||
"""Test that the normal rtsp stream is sent plainly."""
|
||||
frigate_config = FrigateConfig(**self.config)
|
||||
restream = RestreamApi(frigate_config)
|
||||
restream.add_cameras()
|
||||
assert restream.relays["back"].startswith("rtsp")
|
||||
|
||||
@patch("frigate.restream.requests")
|
||||
def test_http_stream(
|
||||
self, mock_request
|
||||
) -> None: # need to ensure restream doesn't try to call API
|
||||
"""Test that the http stream is sent via ffmpeg."""
|
||||
frigate_config = FrigateConfig(**self.config)
|
||||
restream = RestreamApi(frigate_config)
|
||||
restream.add_cameras()
|
||||
assert not restream.relays["front"].startswith("rtsp")
|
||||
|
||||
@patch("frigate.restream.requests")
|
||||
def test_restream_codec_change(
|
||||
self, mock_request
|
||||
) -> None: # need to ensure restream doesn't try to call API
|
||||
"""Test that the http stream is sent via ffmpeg."""
|
||||
self.config["cameras"]["front"]["restream"]["video_encoding"] = "h265"
|
||||
self.config["ffmpeg"] = {"hwaccel_args": "preset-nvidia-h264"}
|
||||
frigate_config = FrigateConfig(**self.config)
|
||||
restream = RestreamApi(frigate_config)
|
||||
restream.add_cameras()
|
||||
assert "#hardware=cuda" in restream.relays["front"]
|
||||
assert "#video=h265" in restream.relays["front"]
|
||||
|
||||
|
||||
if __name__ == "__main__":
|
||||
main(verbosity=2)
|
||||
Reference in New Issue
Block a user