forked from Github/frigate
Compare commits
8 Commits
v0.9.0-rc1
...
v0.9.0-rc2
| Author | SHA1 | Date | |
|---|---|---|---|
|
|
46fe06e779 | ||
|
|
fbea51372f | ||
|
|
fa5ec8d019 | ||
|
|
11c425a7eb | ||
|
|
0d352f3d8a | ||
|
|
6ccff71408 | ||
|
|
41fea2a531 | ||
|
|
3d6dad7e7e |
@@ -40,8 +40,8 @@ COPY --from=nginx /usr/local/nginx/ /usr/local/nginx/
|
|||||||
|
|
||||||
# get model and labels
|
# get model and labels
|
||||||
COPY labelmap.txt /labelmap.txt
|
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/release-frogfish/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.tflite -O /cpu_model.tflite
|
||||||
|
|
||||||
WORKDIR /opt/frigate/
|
WORKDIR /opt/frigate/
|
||||||
ADD frigate frigate/
|
ADD frigate frigate/
|
||||||
|
|||||||
@@ -52,6 +52,8 @@ http {
|
|||||||
vod_mode mapped;
|
vod_mode mapped;
|
||||||
vod_max_mapping_response_size 1m;
|
vod_max_mapping_response_size 1m;
|
||||||
vod_upstream_location /api;
|
vod_upstream_location /api;
|
||||||
|
vod_align_segments_to_key_frames on;
|
||||||
|
vod_manifest_segment_durations_mode accurate;
|
||||||
|
|
||||||
# vod caches
|
# vod caches
|
||||||
vod_metadata_cache metadata_cache 512m;
|
vod_metadata_cache metadata_cache 512m;
|
||||||
|
|||||||
@@ -284,11 +284,11 @@ cameras:
|
|||||||
|
|
||||||
# Required: Camera level detect settings
|
# Required: Camera level detect settings
|
||||||
detect:
|
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
|
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
|
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.
|
# NOTE: Recommended value of 5. Ideally, try and reduce your FPS on the camera.
|
||||||
fps: 5
|
fps: 5
|
||||||
# Optional: enables detection for the camera (default: True)
|
# Optional: enables detection for the camera (default: True)
|
||||||
|
|||||||
@@ -167,21 +167,6 @@ record:
|
|||||||
person: 15
|
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`
|
### `ffmpeg`
|
||||||
|
|
||||||
Can be overridden at the camera level.
|
Can be overridden at the camera level.
|
||||||
|
|||||||
@@ -149,9 +149,11 @@ class RuntimeMotionConfig(MotionConfig):
|
|||||||
|
|
||||||
|
|
||||||
class DetectConfig(BaseModel):
|
class DetectConfig(BaseModel):
|
||||||
height: int = Field(title="Height of the stream for the detect role.")
|
height: int = Field(default=720, title="Height of the stream for the detect role.")
|
||||||
width: int = Field(title="Width of the stream for the detect role.")
|
width: int = Field(default=1280, title="Width of the stream for the detect role.")
|
||||||
fps: int = Field(title="Number of frames per second to process through detection.")
|
fps: int = Field(
|
||||||
|
default=5, title="Number of frames per second to process through detection."
|
||||||
|
)
|
||||||
enabled: bool = Field(default=True, title="Detection Enabled.")
|
enabled: bool = Field(default=True, title="Detection Enabled.")
|
||||||
max_disappeared: Optional[int] = Field(
|
max_disappeared: Optional[int] = Field(
|
||||||
title="Maximum number of frames the object can dissapear before detection ends."
|
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):
|
class CameraInput(BaseModel):
|
||||||
path: str = Field(title="Camera input path.")
|
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(
|
global_args: Union[str, List[str]] = Field(
|
||||||
default_factory=list, title="FFmpeg global arguments."
|
default_factory=list, title="FFmpeg global arguments."
|
||||||
)
|
)
|
||||||
@@ -363,7 +371,7 @@ class CameraFfmpegConfig(FfmpegConfig):
|
|||||||
return v
|
return v
|
||||||
|
|
||||||
|
|
||||||
class CameraSnapshotsConfig(BaseModel):
|
class SnapshotsConfig(BaseModel):
|
||||||
enabled: bool = Field(default=False, title="Snapshots enabled.")
|
enabled: bool = Field(default=False, title="Snapshots enabled.")
|
||||||
clean_copy: bool = Field(
|
clean_copy: bool = Field(
|
||||||
default=True, title="Create a clean copy of the snapshot image."
|
default=True, title="Create a clean copy of the snapshot image."
|
||||||
@@ -449,9 +457,11 @@ class CameraConfig(BaseModel):
|
|||||||
rtmp: CameraRtmpConfig = Field(
|
rtmp: CameraRtmpConfig = Field(
|
||||||
default_factory=CameraRtmpConfig, title="RTMP restreaming configuration."
|
default_factory=CameraRtmpConfig, title="RTMP restreaming configuration."
|
||||||
)
|
)
|
||||||
live: Optional[CameraLiveConfig] = Field(title="Live playback settings.")
|
live: CameraLiveConfig = Field(
|
||||||
snapshots: CameraSnapshotsConfig = Field(
|
default_factory=CameraLiveConfig, title="Live playback settings."
|
||||||
default_factory=CameraSnapshotsConfig, title="Snapshot configuration."
|
)
|
||||||
|
snapshots: SnapshotsConfig = Field(
|
||||||
|
default_factory=SnapshotsConfig, title="Snapshot configuration."
|
||||||
)
|
)
|
||||||
mqtt: CameraMqttConfig = Field(
|
mqtt: CameraMqttConfig = Field(
|
||||||
default_factory=CameraMqttConfig, title="MQTT configuration."
|
default_factory=CameraMqttConfig, title="MQTT configuration."
|
||||||
@@ -460,7 +470,9 @@ class CameraConfig(BaseModel):
|
|||||||
default_factory=ObjectConfig, title="Object configuration."
|
default_factory=ObjectConfig, title="Object configuration."
|
||||||
)
|
)
|
||||||
motion: Optional[MotionConfig] = Field(title="Motion detection 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(
|
timestamp_style: TimestampStyleConfig = Field(
|
||||||
default_factory=TimestampStyleConfig, title="Timestamp style configuration."
|
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):
|
class FrigateConfig(BaseModel):
|
||||||
mqtt: MqttConfig = Field(title="MQTT Configuration.")
|
mqtt: MqttConfig = Field(title="MQTT Configuration.")
|
||||||
database: DatabaseConfig = Field(
|
database: DatabaseConfig = Field(
|
||||||
@@ -662,8 +668,8 @@ class FrigateConfig(BaseModel):
|
|||||||
motion: Optional[MotionConfig] = Field(
|
motion: Optional[MotionConfig] = Field(
|
||||||
title="Global motion detection configuration."
|
title="Global motion detection configuration."
|
||||||
)
|
)
|
||||||
detect: Optional[DetectConfig] = Field(
|
detect: DetectConfig = Field(
|
||||||
title="Global object tracking configuration."
|
default_factory=DetectConfig, title="Global object tracking configuration."
|
||||||
)
|
)
|
||||||
cameras: Dict[str, CameraConfig] = Field(title="Camera configuration.")
|
cameras: Dict[str, CameraConfig] = Field(title="Camera configuration.")
|
||||||
|
|
||||||
@@ -695,6 +701,11 @@ class FrigateConfig(BaseModel):
|
|||||||
{"name": name, **merged_config}
|
{"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
|
# FFMPEG input substitution
|
||||||
for input in camera_config.ffmpeg.inputs:
|
for input in camera_config.ffmpeg.inputs:
|
||||||
input.path = input.path.format(**FRIGATE_ENV_VARS)
|
input.path = input.path.format(**FRIGATE_ENV_VARS)
|
||||||
@@ -742,15 +753,6 @@ class FrigateConfig(BaseModel):
|
|||||||
**camera_config.motion.dict(exclude_unset=True),
|
**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
|
config.cameras[name] = camera_config
|
||||||
|
|
||||||
return config
|
return config
|
||||||
|
|||||||
@@ -75,8 +75,9 @@ class BroadcastThread(threading.Thread):
|
|||||||
ws_iter = iter(websockets.values())
|
ws_iter = iter(websockets.values())
|
||||||
|
|
||||||
for ws in ws_iter:
|
for ws in ws_iter:
|
||||||
if not ws.terminated and ws.environ["PATH_INFO"].endswith(
|
if (
|
||||||
self.camera
|
not ws.terminated
|
||||||
|
and ws.environ["PATH_INFO"] == f"/{self.camera}"
|
||||||
):
|
):
|
||||||
try:
|
try:
|
||||||
ws.send(buf, binary=True)
|
ws.send(buf, binary=True)
|
||||||
|
|||||||
@@ -78,7 +78,10 @@ class RecordingMaintainer(threading.Thread):
|
|||||||
start_time = datetime.datetime.strptime(date, "%Y%m%d%H%M%S")
|
start_time = datetime.datetime.strptime(date, "%Y%m%d%H%M%S")
|
||||||
|
|
||||||
# Just delete files if recordings are turned off
|
# 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)
|
Path(cache_path).unlink(missing_ok=True)
|
||||||
continue
|
continue
|
||||||
|
|
||||||
|
|||||||
@@ -32,8 +32,8 @@ class TestConfig(unittest.TestCase):
|
|||||||
assert self.minimal == frigate_config.dict(exclude_unset=True)
|
assert self.minimal == frigate_config.dict(exclude_unset=True)
|
||||||
|
|
||||||
runtime_config = frigate_config.runtime_config
|
runtime_config = frigate_config.runtime_config
|
||||||
assert "coral" in runtime_config.detectors.keys()
|
assert "cpu" in runtime_config.detectors.keys()
|
||||||
assert runtime_config.detectors["coral"].type == DetectorTypeEnum.edgetpu
|
assert runtime_config.detectors["cpu"].type == DetectorTypeEnum.cpu
|
||||||
|
|
||||||
def test_invalid_mqtt_config(self):
|
def test_invalid_mqtt_config(self):
|
||||||
config = {
|
config = {
|
||||||
@@ -692,6 +692,198 @@ class TestConfig(unittest.TestCase):
|
|||||||
runtime_config = frigate_config.runtime_config
|
runtime_config = frigate_config.runtime_config
|
||||||
assert runtime_config.model.merged_labelmap[0] == "person"
|
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__":
|
if __name__ == "__main__":
|
||||||
unittest.main(verbosity=2)
|
unittest.main(verbosity=2)
|
||||||
|
|||||||
@@ -21,7 +21,7 @@ export default function RecordingPlaylist({ camera, recordings, selectedDate, se
|
|||||||
events={recording.events}
|
events={recording.events}
|
||||||
selected={recording.date === selectedDate}
|
selected={recording.date === selectedDate}
|
||||||
>
|
>
|
||||||
{recording.recordings.map((item, i) => (
|
{recording.recordings.slice().reverse().map((item, i) => (
|
||||||
<div className="mb-2 w-full">
|
<div className="mb-2 w-full">
|
||||||
<div
|
<div
|
||||||
className={`flex w-full text-md text-white px-8 py-2 mb-2 ${
|
className={`flex w-full text-md text-white px-8 py-2 mb-2 ${
|
||||||
|
|||||||
Reference in New Issue
Block a user