diff --git a/docs/docs/configuration/reference.md b/docs/docs/configuration/reference.md index cccaf3eaa..996244d91 100644 --- a/docs/docs/configuration/reference.md +++ b/docs/docs/configuration/reference.md @@ -263,6 +263,8 @@ ffmpeg: detect: -threads 2 -f rawvideo -pix_fmt yuv420p # Optional: output args for record streams (default: shown below) record: preset-record-generic + # Optional: Set segment length for recording stream. (default: shown below) + segment_time : 10 # Optional: Time in seconds to wait before ffmpeg retries connecting to the camera. (default: shown below) # If set too low, frigate will retry a connection to the camera's stream too frequently, using up the limited streams some cameras can allow at once # If set too high, then if a ffmpeg crash or camera stream timeout occurs, you could potentially lose up to a maximum of retry_interval second(s) of footage diff --git a/frigate/config/camera/camera.py b/frigate/config/camera/camera.py index 0f2b1c8be..d49f82ee8 100644 --- a/frigate/config/camera/camera.py +++ b/frigate/config/camera/camera.py @@ -214,6 +214,7 @@ class CameraConfig(FrigateBaseModel): parse_preset_output_record( self.ffmpeg.output_args.record, self.ffmpeg.apple_compatibility, + self.ffmpeg.output_args.segment_time, ) or self.ffmpeg.output_args.record ) diff --git a/frigate/config/camera/ffmpeg.py b/frigate/config/camera/ffmpeg.py index 2c1e4cdca..ee6da522a 100644 --- a/frigate/config/camera/ffmpeg.py +++ b/frigate/config/camera/ffmpeg.py @@ -41,6 +41,7 @@ class FfmpegOutputArgsConfig(FrigateBaseModel): default=RECORD_FFMPEG_OUTPUT_ARGS_DEFAULT, title="Record role FFmpeg output arguments.", ) + segment_time: int = Field(default=10, title="Segment length for recording stream.") class FfmpegConfig(FrigateBaseModel): diff --git a/frigate/const.py b/frigate/const.py index 41c24f087..cb49e21cf 100644 --- a/frigate/const.py +++ b/frigate/const.py @@ -76,6 +76,8 @@ FFMPEG_HWACCEL_VULKAN = "preset-vulkan" FFMPEG_HWACCEL_RKMPP = "preset-rkmpp" FFMPEG_HWACCEL_AMF = "preset-amd-amf" FFMPEG_HVC1_ARGS = ["-tag:v", "hvc1"] +FFMPEG_SEGMENT_TIME_PARAM = "-segment_time" +FFMPEG_SEGMENT_TIME_VALUE = "10" # RKNN constants SUPPORTED_RK_SOCS = ["rk3562", "rk3566", "rk3568", "rk3576", "rk3588"] diff --git a/frigate/ffmpeg_presets.py b/frigate/ffmpeg_presets.py index 43272a6d1..bc940e18e 100644 --- a/frigate/ffmpeg_presets.py +++ b/frigate/ffmpeg_presets.py @@ -12,6 +12,8 @@ from frigate.const import ( FFMPEG_HWACCEL_RKMPP, FFMPEG_HWACCEL_VAAPI, FFMPEG_HWACCEL_VULKAN, + FFMPEG_SEGMENT_TIME_PARAM, + FFMPEG_SEGMENT_TIME_VALUE, LIBAVFORMAT_VERSION_MAJOR, ) from frigate.util.services import vainfo_hwaccel @@ -446,8 +448,8 @@ PRESETS_RECORD_OUTPUT = { "preset-record-generic": [ "-f", "segment", - "-segment_time", - "10", + FFMPEG_SEGMENT_TIME_PARAM, + FFMPEG_SEGMENT_TIME_VALUE, "-segment_format", "mp4", "-reset_timestamps", @@ -461,8 +463,8 @@ PRESETS_RECORD_OUTPUT = { "preset-record-generic-audio-aac": [ "-f", "segment", - "-segment_time", - "10", + FFMPEG_SEGMENT_TIME_PARAM, + FFMPEG_SEGMENT_TIME_VALUE, "-segment_format", "mp4", "-reset_timestamps", @@ -477,8 +479,8 @@ PRESETS_RECORD_OUTPUT = { "preset-record-generic-audio-copy": [ "-f", "segment", - "-segment_time", - "10", + FFMPEG_SEGMENT_TIME_PARAM, + FFMPEG_SEGMENT_TIME_VALUE, "-segment_format", "mp4", "-reset_timestamps", @@ -491,8 +493,8 @@ PRESETS_RECORD_OUTPUT = { "preset-record-mjpeg": [ "-f", "segment", - "-segment_time", - "10", + FFMPEG_SEGMENT_TIME_PARAM, + FFMPEG_SEGMENT_TIME_VALUE, "-segment_format", "mp4", "-reset_timestamps", @@ -506,8 +508,8 @@ PRESETS_RECORD_OUTPUT = { "preset-record-jpeg": [ "-f", "segment", - "-segment_time", - "10", + FFMPEG_SEGMENT_TIME_PARAM, + FFMPEG_SEGMENT_TIME_VALUE, "-segment_format", "mp4", "-reset_timestamps", @@ -521,8 +523,8 @@ PRESETS_RECORD_OUTPUT = { "preset-record-ubiquiti": [ "-f", "segment", - "-segment_time", - "10", + FFMPEG_SEGMENT_TIME_PARAM, + FFMPEG_SEGMENT_TIME_VALUE, "-segment_format", "mp4", "-reset_timestamps", @@ -539,7 +541,7 @@ PRESETS_RECORD_OUTPUT = { } -def parse_preset_output_record(arg: Any, force_record_hvc1: bool) -> list[str]: +def parse_preset_output_record(arg: Any, force_record_hvc1: bool, segment_time: int = -1) -> list[str] | None: """Return the correct preset if in preset format otherwise return None.""" if not isinstance(arg, str): return None @@ -549,6 +551,11 @@ def parse_preset_output_record(arg: Any, force_record_hvc1: bool) -> list[str]: if not preset: return None + if 0 < segment_time <= 60 and FFMPEG_SEGMENT_TIME_PARAM in preset: + idx = preset.index(FFMPEG_SEGMENT_TIME_PARAM) + if idx + 1 < len(preset): + preset[idx + 1] = str(segment_time) + if force_record_hvc1: # Apple only supports HEVC if it is hvc1 (vs. hev1) return preset + FFMPEG_HVC1_ARGS diff --git a/frigate/test/test_config.py b/frigate/test/test_config.py index 4bafe7369..5b96caaf3 100644 --- a/frigate/test/test_config.py +++ b/frigate/test/test_config.py @@ -128,6 +128,75 @@ class TestConfig(unittest.TestCase): } self.assertRaises(ValidationError, lambda: FrigateConfig(**config)) + def test_default_segment_length(self): + config = { + "mqtt": {"host": "mqtt"}, + "cameras": { + "back": { + "ffmpeg": { + "inputs": [ + {"path": "rtsp://10.0.0.1:554/video", "roles": ["detect"]} + ] + }, + "detect": { + "height": 1080, + "width": 1920, + "fps": 5, + }, + } + }, + } + + frigate_config = FrigateConfig(**config) + assert frigate_config.cameras["back"].ffmpeg.output_args.segment_time == 10 + + def test_inherit_segment_length(self): + config = { + "mqtt": {"host": "mqtt"}, + "ffmpeg": {"output_args": {"segment_time": 15}}, + "cameras": { + "back": { + "ffmpeg": { + "inputs": [ + {"path": "rtsp://10.0.0.1:554/video", "roles": ["detect"]} + ] + }, + "detect": { + "height": 1080, + "width": 1920, + "fps": 5, + }, + } + }, + } + + frigate_config = FrigateConfig(**config) + assert frigate_config.cameras["back"].ffmpeg.output_args.segment_time == 15 + + def test_override_segment_length(self): + config = { + "mqtt": {"host": "mqtt"}, + "ffmpeg": {"output_args": {"segment_time": 15}}, + "cameras": { + "back": { + "ffmpeg": { + "inputs": [ + {"path": "rtsp://10.0.0.1:554/video", "roles": ["detect"]} + ], + "output_args": {"segment_time": 25} + }, + "detect": { + "height": 1080, + "width": 1920, + "fps": 5, + }, + } + }, + } + + frigate_config = FrigateConfig(**config) + assert frigate_config.cameras["back"].ffmpeg.output_args.segment_time == 25 + def test_inherit_tracked_objects(self): config = { "mqtt": {"host": "mqtt"}, diff --git a/web/public/locales/en/config/cameras.json b/web/public/locales/en/config/cameras.json index 67015bde5..7ce83c551 100644 --- a/web/public/locales/en/config/cameras.json +++ b/web/public/locales/en/config/cameras.json @@ -158,6 +158,9 @@ }, "record": { "label": "Record role FFmpeg output arguments." + }, + "segment_time": { + "label": "Segment length for recording stream." } } }, diff --git a/web/public/locales/en/config/ffmpeg.json b/web/public/locales/en/config/ffmpeg.json index 570da5a35..2aa06143e 100644 --- a/web/public/locales/en/config/ffmpeg.json +++ b/web/public/locales/en/config/ffmpeg.json @@ -21,6 +21,9 @@ }, "record": { "label": "Record role FFmpeg output arguments." + }, + "segment_time": { + "label": "Segment length for recording stream." } } }, diff --git a/web/src/types/frigateConfig.ts b/web/src/types/frigateConfig.ts index 94c9ba6e9..dd54fc9c9 100644 --- a/web/src/types/frigateConfig.ts +++ b/web/src/types/frigateConfig.ts @@ -87,6 +87,7 @@ export interface CameraConfig { detect: string[]; record: string; rtmp: string; + segment_time: number; }; retry_interval: number; }; @@ -418,6 +419,7 @@ export interface FrigateConfig { detect: string[]; record: string; rtmp: string; + segment_time: number; }; retry_interval: number; };