Compare commits

...

8 Commits

Author SHA1 Message Date
Blake Blackshear
46fe06e779 tweak vod settings for varying iframe intervals 2021-08-28 21:26:23 -05:00
Blake Blackshear
fbea51372f sync global snapshot options (fixes #1621) 2021-08-28 09:14:00 -05:00
Blake Blackshear
fa5ec8d019 cleanup global and camera detect config (fixes #1615) 2021-08-28 08:51:51 -05:00
Blake Blackshear
11c425a7eb error on invalid role 2021-08-28 08:16:25 -05:00
Blake Blackshear
0d352f3d8a use model from frogfish release 2021-08-28 08:04:29 -05:00
Blake Blackshear
6ccff71408 handle missing camera names 2021-08-28 07:43:51 -05:00
Blake Blackshear
41fea2a531 fix match for websocket url (fixes #1633) 2021-08-28 07:42:30 -05:00
Blake Blackshear
3d6dad7e7e reverse sort within a day for recordings 2021-08-27 07:26:11 -05:00
9 changed files with 237 additions and 52 deletions

View File

@@ -40,8 +40,8 @@ COPY --from=nginx /usr/local/nginx/ /usr/local/nginx/
# get model and labels
COPY labelmap.txt /labelmap.txt
RUN wget -q https://github.com/google-coral/test_data/raw/master/ssdlite_mobiledet_coco_qat_postprocess_edgetpu.tflite -O /edgetpu_model.tflite
RUN wget -q https://github.com/google-coral/test_data/raw/master/ssdlite_mobiledet_coco_qat_postprocess.tflite -O /cpu_model.tflite
RUN wget -q https://github.com/google-coral/test_data/raw/release-frogfish/ssdlite_mobiledet_coco_qat_postprocess_edgetpu.tflite -O /edgetpu_model.tflite
RUN wget -q https://github.com/google-coral/test_data/raw/release-frogfish/ssdlite_mobiledet_coco_qat_postprocess.tflite -O /cpu_model.tflite
WORKDIR /opt/frigate/
ADD frigate frigate/

View File

@@ -52,6 +52,8 @@ http {
vod_mode mapped;
vod_max_mapping_response_size 1m;
vod_upstream_location /api;
vod_align_segments_to_key_frames on;
vod_manifest_segment_durations_mode accurate;
# vod caches
vod_metadata_cache metadata_cache 512m;

View File

@@ -284,11 +284,11 @@ cameras:
# Required: Camera level detect settings
detect:
# Required: width of the frame for the input with the detect role
# Optional: width of the frame for the input with the detect role (default: shown below)
width: 1280
# Required: height of the frame for the input with the detect role
# Optional: height of the frame for the input with the detect role (default: shown below)
height: 720
# Required: desired fps for your camera for the input with the detect role
# Optional: desired fps for your camera for the input with the detect role (default: shown below)
# NOTE: Recommended value of 5. Ideally, try and reduce your FPS on the camera.
fps: 5
# Optional: enables detection for the camera (default: True)

View File

@@ -167,21 +167,6 @@ record:
person: 15
```
## `snapshots`
Can be overridden at the camera level. Global snapshot retention settings.
```yaml
# Optional: Configuration for the jpg snapshots written to the clips directory for each event
snapshots:
retain:
# Required: Default retention days (default: shown below)
default: 10
# Optional: Per object retention days
objects:
person: 15
```
### `ffmpeg`
Can be overridden at the camera level.

View File

@@ -149,9 +149,11 @@ class RuntimeMotionConfig(MotionConfig):
class DetectConfig(BaseModel):
height: int = Field(title="Height of the stream for the detect role.")
width: int = Field(title="Width of the stream for the detect role.")
fps: int = Field(title="Number of frames per second to process through detection.")
height: int = Field(default=720, title="Height of the stream for the detect role.")
width: int = Field(default=1280, title="Width of the stream for the detect role.")
fps: int = Field(
default=5, title="Number of frames per second to process through detection."
)
enabled: bool = Field(default=True, title="Detection Enabled.")
max_disappeared: Optional[int] = Field(
title="Maximum number of frames the object can dissapear before detection ends."
@@ -332,9 +334,15 @@ class FfmpegConfig(BaseModel):
)
class CameraRoleEnum(str, Enum):
record = "record"
rtmp = "rtmp"
detect = "detect"
class CameraInput(BaseModel):
path: str = Field(title="Camera input path.")
roles: List[str] = Field(title="Roles assigned to this input.")
roles: List[CameraRoleEnum] = Field(title="Roles assigned to this input.")
global_args: Union[str, List[str]] = Field(
default_factory=list, title="FFmpeg global arguments."
)
@@ -363,7 +371,7 @@ class CameraFfmpegConfig(FfmpegConfig):
return v
class CameraSnapshotsConfig(BaseModel):
class SnapshotsConfig(BaseModel):
enabled: bool = Field(default=False, title="Snapshots enabled.")
clean_copy: bool = Field(
default=True, title="Create a clean copy of the snapshot image."
@@ -449,9 +457,11 @@ class CameraConfig(BaseModel):
rtmp: CameraRtmpConfig = Field(
default_factory=CameraRtmpConfig, title="RTMP restreaming configuration."
)
live: Optional[CameraLiveConfig] = Field(title="Live playback settings.")
snapshots: CameraSnapshotsConfig = Field(
default_factory=CameraSnapshotsConfig, title="Snapshot configuration."
live: CameraLiveConfig = Field(
default_factory=CameraLiveConfig, title="Live playback settings."
)
snapshots: SnapshotsConfig = Field(
default_factory=SnapshotsConfig, title="Snapshot configuration."
)
mqtt: CameraMqttConfig = Field(
default_factory=CameraMqttConfig, title="MQTT configuration."
@@ -460,7 +470,9 @@ class CameraConfig(BaseModel):
default_factory=ObjectConfig, title="Object configuration."
)
motion: Optional[MotionConfig] = Field(title="Motion detection configuration.")
detect: DetectConfig = Field(title="Object detection configuration.")
detect: DetectConfig = Field(
default_factory=DetectConfig, title="Object detection configuration."
)
timestamp_style: TimestampStyleConfig = Field(
default_factory=TimestampStyleConfig, title="Timestamp style configuration."
)
@@ -620,12 +632,6 @@ class LoggerConfig(BaseModel):
)
class SnapshotsConfig(BaseModel):
retain: RetainConfig = Field(
default_factory=RetainConfig, title="Global snapshot retention configuration."
)
class FrigateConfig(BaseModel):
mqtt: MqttConfig = Field(title="MQTT Configuration.")
database: DatabaseConfig = Field(
@@ -662,8 +668,8 @@ class FrigateConfig(BaseModel):
motion: Optional[MotionConfig] = Field(
title="Global motion detection configuration."
)
detect: Optional[DetectConfig] = Field(
title="Global object tracking configuration."
detect: DetectConfig = Field(
default_factory=DetectConfig, title="Global object tracking configuration."
)
cameras: Dict[str, CameraConfig] = Field(title="Camera configuration.")
@@ -695,6 +701,11 @@ class FrigateConfig(BaseModel):
{"name": name, **merged_config}
)
# Default max_disappeared configuration
max_disappeared = camera_config.detect.fps * 5
if camera_config.detect.max_disappeared is None:
camera_config.detect.max_disappeared = max_disappeared
# FFMPEG input substitution
for input in camera_config.ffmpeg.inputs:
input.path = input.path.format(**FRIGATE_ENV_VARS)
@@ -742,15 +753,6 @@ class FrigateConfig(BaseModel):
**camera_config.motion.dict(exclude_unset=True),
)
# Default detect configuration
max_disappeared = camera_config.detect.fps * 5
if camera_config.detect.max_disappeared is None:
camera_config.detect.max_disappeared = max_disappeared
# Default live configuration
if camera_config.live is None:
camera_config.live = CameraLiveConfig()
config.cameras[name] = camera_config
return config

View File

@@ -75,8 +75,9 @@ class BroadcastThread(threading.Thread):
ws_iter = iter(websockets.values())
for ws in ws_iter:
if not ws.terminated and ws.environ["PATH_INFO"].endswith(
self.camera
if (
not ws.terminated
and ws.environ["PATH_INFO"] == f"/{self.camera}"
):
try:
ws.send(buf, binary=True)

View File

@@ -78,7 +78,10 @@ class RecordingMaintainer(threading.Thread):
start_time = datetime.datetime.strptime(date, "%Y%m%d%H%M%S")
# Just delete files if recordings are turned off
if not self.config.cameras[camera].record.enabled:
if (
not camera in self.config.cameras
or not self.config.cameras[camera].record.enabled
):
Path(cache_path).unlink(missing_ok=True)
continue

View File

@@ -32,8 +32,8 @@ class TestConfig(unittest.TestCase):
assert self.minimal == frigate_config.dict(exclude_unset=True)
runtime_config = frigate_config.runtime_config
assert "coral" in runtime_config.detectors.keys()
assert runtime_config.detectors["coral"].type == DetectorTypeEnum.edgetpu
assert "cpu" in runtime_config.detectors.keys()
assert runtime_config.detectors["cpu"].type == DetectorTypeEnum.cpu
def test_invalid_mqtt_config(self):
config = {
@@ -692,6 +692,198 @@ class TestConfig(unittest.TestCase):
runtime_config = frigate_config.runtime_config
assert runtime_config.model.merged_labelmap[0] == "person"
def test_fails_on_invalid_role(self):
config = {
"mqtt": {"host": "mqtt"},
"cameras": {
"back": {
"ffmpeg": {
"inputs": [
{
"path": "rtsp://10.0.0.1:554/video",
"roles": ["detect", "clips"],
},
]
},
"detect": {
"height": 1080,
"width": 1920,
"fps": 5,
},
}
},
}
self.assertRaises(ValidationError, lambda: FrigateConfig(**config))
def test_global_detect(self):
config = {
"mqtt": {"host": "mqtt"},
"detect": {"max_disappeared": 1},
"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 config == frigate_config.dict(exclude_unset=True)
runtime_config = frigate_config.runtime_config
assert runtime_config.cameras["back"].detect.max_disappeared == 1
assert runtime_config.cameras["back"].detect.height == 1080
def test_default_detect(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"].detect.max_disappeared == 25
assert runtime_config.cameras["back"].detect.height == 720
def test_global_detect_merge(self):
config = {
"mqtt": {"host": "mqtt"},
"detect": {"max_disappeared": 1, "height": 720},
"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 config == frigate_config.dict(exclude_unset=True)
runtime_config = frigate_config.runtime_config
assert runtime_config.cameras["back"].detect.max_disappeared == 1
assert runtime_config.cameras["back"].detect.height == 1080
assert runtime_config.cameras["back"].detect.width == 1920
def test_global_snapshots(self):
config = {
"mqtt": {"host": "mqtt"},
"snapshots": {"enabled": True},
"cameras": {
"back": {
"ffmpeg": {
"inputs": [
{
"path": "rtsp://10.0.0.1:554/video",
"roles": ["detect"],
},
]
},
"snapshots": {
"height": 100,
},
}
},
}
frigate_config = FrigateConfig(**config)
assert config == frigate_config.dict(exclude_unset=True)
runtime_config = frigate_config.runtime_config
assert runtime_config.cameras["back"].snapshots.enabled
assert runtime_config.cameras["back"].snapshots.height == 100
def test_default_snapshots(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"].snapshots.bounding_box
assert runtime_config.cameras["back"].snapshots.quality == 70
def test_global_snapshots_merge(self):
config = {
"mqtt": {"host": "mqtt"},
"snapshots": {"bounding_box": False, "height": 300},
"cameras": {
"back": {
"ffmpeg": {
"inputs": [
{
"path": "rtsp://10.0.0.1:554/video",
"roles": ["detect"],
},
]
},
"snapshots": {
"height": 150,
"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"].snapshots.bounding_box == False
assert runtime_config.cameras["back"].snapshots.height == 150
assert runtime_config.cameras["back"].snapshots.enabled
if __name__ == "__main__":
unittest.main(verbosity=2)

View File

@@ -21,7 +21,7 @@ export default function RecordingPlaylist({ camera, recordings, selectedDate, se
events={recording.events}
selected={recording.date === selectedDate}
>
{recording.recordings.map((item, i) => (
{recording.recordings.slice().reverse().map((item, i) => (
<div className="mb-2 w-full">
<div
className={`flex w-full text-md text-white px-8 py-2 mb-2 ${