mirror of
https://github.com/blakeblackshear/frigate.git
synced 2026-02-07 11:45:24 +03:00
Add ability to configure preview quality
This commit is contained in:
parent
32e30853fb
commit
e5e42aa659
@ -369,6 +369,11 @@ record:
|
|||||||
# The -r (framerate) dictates how smooth the output video is.
|
# The -r (framerate) dictates how smooth the output video is.
|
||||||
# So the args would be -vf setpts=0.02*PTS -r 30 in that case.
|
# So the args would be -vf setpts=0.02*PTS -r 30 in that case.
|
||||||
timelapse_args: "-vf setpts=0.04*PTS -r 30"
|
timelapse_args: "-vf setpts=0.04*PTS -r 30"
|
||||||
|
# Optional: Recording Preview Settings
|
||||||
|
preview:
|
||||||
|
# Optional: Quality of recording preview (default: shown below).
|
||||||
|
# Options are: very_low, low, medium, high, very_high
|
||||||
|
quality: medium
|
||||||
# Optional: Event recording settings
|
# Optional: Event recording settings
|
||||||
events:
|
events:
|
||||||
# Optional: Number of seconds before the event to include (default: shown below)
|
# Optional: Number of seconds before the event to include (default: shown below)
|
||||||
|
|||||||
@ -260,6 +260,20 @@ class RecordExportConfig(FrigateBaseModel):
|
|||||||
)
|
)
|
||||||
|
|
||||||
|
|
||||||
|
class RecordQualityEnum(str, Enum):
|
||||||
|
very_low = "very_low"
|
||||||
|
low = "low"
|
||||||
|
medium = "medium"
|
||||||
|
high = "high"
|
||||||
|
very_high = "very_high"
|
||||||
|
|
||||||
|
|
||||||
|
class RecordPreviewConfig(FrigateBaseModel):
|
||||||
|
quality: RecordQualityEnum = Field(
|
||||||
|
default=RecordQualityEnum.medium, title="Quality of recording preview."
|
||||||
|
)
|
||||||
|
|
||||||
|
|
||||||
class RecordConfig(FrigateBaseModel):
|
class RecordConfig(FrigateBaseModel):
|
||||||
enabled: bool = Field(default=False, title="Enable record on all cameras.")
|
enabled: bool = Field(default=False, title="Enable record on all cameras.")
|
||||||
sync_recordings: bool = Field(
|
sync_recordings: bool = Field(
|
||||||
@ -278,6 +292,9 @@ class RecordConfig(FrigateBaseModel):
|
|||||||
export: RecordExportConfig = Field(
|
export: RecordExportConfig = Field(
|
||||||
default_factory=RecordExportConfig, title="Recording Export Config"
|
default_factory=RecordExportConfig, title="Recording Export Config"
|
||||||
)
|
)
|
||||||
|
preview: RecordPreviewConfig = Field(
|
||||||
|
default_factory=RecordPreviewConfig, title="Recording Preview Config"
|
||||||
|
)
|
||||||
enabled_in_config: Optional[bool] = Field(
|
enabled_in_config: Optional[bool] = Field(
|
||||||
title="Keep track of original state of recording."
|
title="Keep track of original state of recording."
|
||||||
)
|
)
|
||||||
|
|||||||
@ -12,7 +12,7 @@ from pathlib import Path
|
|||||||
import cv2
|
import cv2
|
||||||
import numpy as np
|
import numpy as np
|
||||||
|
|
||||||
from frigate.config import CameraConfig
|
from frigate.config import CameraConfig, RecordQualityEnum
|
||||||
from frigate.const import CACHE_DIR, CLIPS_DIR, INSERT_PREVIEW
|
from frigate.const import CACHE_DIR, CLIPS_DIR, INSERT_PREVIEW
|
||||||
from frigate.ffmpeg_presets import (
|
from frigate.ffmpeg_presets import (
|
||||||
FPS_VFR_PARAM,
|
FPS_VFR_PARAM,
|
||||||
@ -29,6 +29,13 @@ PREVIEW_OUTPUT_FPS = 1
|
|||||||
PREVIEW_SEGMENT_DURATION = 3600 # one hour
|
PREVIEW_SEGMENT_DURATION = 3600 # one hour
|
||||||
# important to have lower keyframe to maintain scrubbing performance
|
# important to have lower keyframe to maintain scrubbing performance
|
||||||
PREVIEW_KEYFRAME_INTERVAL = 60
|
PREVIEW_KEYFRAME_INTERVAL = 60
|
||||||
|
PREVIEW_BIT_RATES = {
|
||||||
|
RecordQualityEnum.very_low: 4096,
|
||||||
|
RecordQualityEnum.low: 6144,
|
||||||
|
RecordQualityEnum.medium: 8192,
|
||||||
|
RecordQualityEnum.high: 12288,
|
||||||
|
RecordQualityEnum.very_high: 16384,
|
||||||
|
}
|
||||||
|
|
||||||
|
|
||||||
def get_cache_image_name(camera: str, frame_time: float) -> str:
|
def get_cache_image_name(camera: str, frame_time: float) -> str:
|
||||||
@ -50,19 +57,19 @@ class FFMpegConverter(threading.Thread):
|
|||||||
):
|
):
|
||||||
threading.Thread.__init__(self)
|
threading.Thread.__init__(self)
|
||||||
self.name = f"{config.name}_preview_converter"
|
self.name = f"{config.name}_preview_converter"
|
||||||
self.camera = config.name
|
self.config = config
|
||||||
self.frame_times = frame_times
|
self.frame_times = frame_times
|
||||||
self.inter_process_queue = inter_process_queue
|
self.inter_process_queue = inter_process_queue
|
||||||
self.path = os.path.join(
|
self.path = os.path.join(
|
||||||
CLIPS_DIR,
|
CLIPS_DIR,
|
||||||
f"previews/{self.camera}/{self.frame_times[0]}-{self.frame_times[-1]}.mp4",
|
f"previews/{self.config.name}/{self.frame_times[0]}-{self.frame_times[-1]}.mp4",
|
||||||
)
|
)
|
||||||
|
|
||||||
# write a PREVIEW at fps and 1 key frame per clip
|
# write a PREVIEW at fps and 1 key frame per clip
|
||||||
self.ffmpeg_cmd = parse_preset_hardware_acceleration_encode(
|
self.ffmpeg_cmd = parse_preset_hardware_acceleration_encode(
|
||||||
config.ffmpeg.hwaccel_args,
|
config.ffmpeg.hwaccel_args,
|
||||||
input="-f concat -y -protocol_whitelist pipe,file -safe 0 -i /dev/stdin",
|
input="-f concat -y -protocol_whitelist pipe,file -safe 0 -i /dev/stdin",
|
||||||
output=f"-g {PREVIEW_KEYFRAME_INTERVAL} -fpsmax {PREVIEW_OUTPUT_FPS} -bf 0 -b:v 9820 {FPS_VFR_PARAM} -movflags +faststart -pix_fmt yuv420p {self.path}",
|
output=f"-g {PREVIEW_KEYFRAME_INTERVAL} -fpsmax {PREVIEW_OUTPUT_FPS} -bf 0 -b:v {PREVIEW_BIT_RATES[self.config.record.preview]} {FPS_VFR_PARAM} -movflags +faststart -pix_fmt yuv420p {self.path}",
|
||||||
type=EncodeTypeEnum.preview,
|
type=EncodeTypeEnum.preview,
|
||||||
)
|
)
|
||||||
|
|
||||||
@ -75,12 +82,12 @@ class FFMpegConverter(threading.Thread):
|
|||||||
if t_idx == item_count - 1:
|
if t_idx == item_count - 1:
|
||||||
# last frame does not get a duration
|
# last frame does not get a duration
|
||||||
playlist.append(
|
playlist.append(
|
||||||
f"file '{get_cache_image_name(self.camera, self.frame_times[t_idx])}'"
|
f"file '{get_cache_image_name(self.config.name, self.frame_times[t_idx])}'"
|
||||||
)
|
)
|
||||||
continue
|
continue
|
||||||
|
|
||||||
playlist.append(
|
playlist.append(
|
||||||
f"file '{get_cache_image_name(self.camera, self.frame_times[t_idx])}'"
|
f"file '{get_cache_image_name(self.config.name, self.frame_times[t_idx])}'"
|
||||||
)
|
)
|
||||||
playlist.append(
|
playlist.append(
|
||||||
f"duration {self.frame_times[t_idx + 1] - self.frame_times[t_idx]}"
|
f"duration {self.frame_times[t_idx + 1] - self.frame_times[t_idx]}"
|
||||||
@ -102,8 +109,8 @@ class FFMpegConverter(threading.Thread):
|
|||||||
(
|
(
|
||||||
INSERT_PREVIEW,
|
INSERT_PREVIEW,
|
||||||
{
|
{
|
||||||
Previews.id: f"{self.camera}_{end}",
|
Previews.id: f"{self.config.name}_{end}",
|
||||||
Previews.camera: self.camera,
|
Previews.camera: self.config.name,
|
||||||
Previews.path: self.path,
|
Previews.path: self.path,
|
||||||
Previews.start_time: start,
|
Previews.start_time: start,
|
||||||
Previews.end_time: end,
|
Previews.end_time: end,
|
||||||
@ -112,12 +119,12 @@ class FFMpegConverter(threading.Thread):
|
|||||||
)
|
)
|
||||||
)
|
)
|
||||||
else:
|
else:
|
||||||
logger.error(f"Error saving preview for {self.camera} :: {p.stderr}")
|
logger.error(f"Error saving preview for {self.config.name} :: {p.stderr}")
|
||||||
|
|
||||||
# unlink files from cache
|
# unlink files from cache
|
||||||
# don't delete last frame as it will be used as first frame in next segment
|
# don't delete last frame as it will be used as first frame in next segment
|
||||||
for t in self.frame_times[0:-1]:
|
for t in self.frame_times[0:-1]:
|
||||||
Path(get_cache_image_name(self.camera, t)).unlink(missing_ok=True)
|
Path(get_cache_image_name(self.config.name, t)).unlink(missing_ok=True)
|
||||||
|
|
||||||
|
|
||||||
class PreviewRecorder:
|
class PreviewRecorder:
|
||||||
|
|||||||
Loading…
Reference in New Issue
Block a user