mirror of
https://github.com/blakeblackshear/frigate.git
synced 2026-05-03 06:50:58 +00:00
Compare commits
No commits in common. "3892f8c73265ff88ac172e4b7d57099a78e110a2" and "5dd30b273a2ef4123e0b52a51e68a582196bc7fc" have entirely different histories.
3892f8c732
...
5dd30b273a
@ -2,7 +2,7 @@
|
|||||||
|
|
||||||
# https://askubuntu.com/questions/972516/debian-frontend-environment-variable
|
# https://askubuntu.com/questions/972516/debian-frontend-environment-variable
|
||||||
ARG DEBIAN_FRONTEND=noninteractive
|
ARG DEBIAN_FRONTEND=noninteractive
|
||||||
ARG ROCM=1
|
ARG ROCM=6.4.0
|
||||||
ARG AMDGPU=gfx900
|
ARG AMDGPU=gfx900
|
||||||
ARG HSA_OVERRIDE_GFX_VERSION
|
ARG HSA_OVERRIDE_GFX_VERSION
|
||||||
ARG HSA_OVERRIDE
|
ARG HSA_OVERRIDE
|
||||||
@ -13,12 +13,12 @@ FROM wget AS rocm
|
|||||||
ARG ROCM
|
ARG ROCM
|
||||||
ARG AMDGPU
|
ARG AMDGPU
|
||||||
|
|
||||||
RUN apt update -qq && \
|
RUN apt update && \
|
||||||
apt install -y wget gpg && \
|
apt install -y wget gpg && \
|
||||||
wget -O rocm.deb https://repo.radeon.com/amdgpu-install/6.4.1/ubuntu/jammy/amdgpu-install_6.4.60401-1_all.deb && \
|
wget -O rocm.deb https://repo.radeon.com/amdgpu-install/6.4/ubuntu/jammy/amdgpu-install_6.4.60400-1_all.deb && \
|
||||||
apt install -y ./rocm.deb && \
|
apt install -y ./rocm.deb && \
|
||||||
apt update && \
|
apt update && \
|
||||||
apt install -qq -y rocm
|
apt install -y rocm
|
||||||
|
|
||||||
RUN mkdir -p /opt/rocm-dist/opt/rocm-$ROCM/lib
|
RUN mkdir -p /opt/rocm-dist/opt/rocm-$ROCM/lib
|
||||||
RUN cd /opt/rocm-$ROCM/lib && \
|
RUN cd /opt/rocm-$ROCM/lib && \
|
||||||
|
|||||||
@ -1 +1 @@
|
|||||||
onnxruntime-rocm @ https://github.com/NickM-27/frigate-onnxruntime-rocm/releases/download/v6.4.1/onnxruntime_rocm-1.21.1-cp311-cp311-linux_x86_64.whl
|
onnxruntime-rocm @ https://github.com/NickM-27/frigate-onnxruntime-rocm/releases/download/v6.4.0/onnxruntime_rocm-1.21.1-cp311-cp311-linux_x86_64.whl
|
||||||
@ -2,7 +2,7 @@ variable "AMDGPU" {
|
|||||||
default = "gfx900"
|
default = "gfx900"
|
||||||
}
|
}
|
||||||
variable "ROCM" {
|
variable "ROCM" {
|
||||||
default = "6.4.1"
|
default = "6.4.0"
|
||||||
}
|
}
|
||||||
variable "HSA_OVERRIDE_GFX_VERSION" {
|
variable "HSA_OVERRIDE_GFX_VERSION" {
|
||||||
default = ""
|
default = ""
|
||||||
|
|||||||
@ -1,8 +1,7 @@
|
|||||||
from enum import Enum
|
from enum import Enum
|
||||||
from typing import Optional, Union
|
from typing import Optional
|
||||||
|
|
||||||
from pydantic import BaseModel
|
from pydantic import BaseModel
|
||||||
from pydantic.json_schema import SkipJsonSchema
|
|
||||||
|
|
||||||
|
|
||||||
class Extension(str, Enum):
|
class Extension(str, Enum):
|
||||||
@ -47,10 +46,3 @@ class MediaMjpegFeedQueryParams(BaseModel):
|
|||||||
class MediaRecordingsSummaryQueryParams(BaseModel):
|
class MediaRecordingsSummaryQueryParams(BaseModel):
|
||||||
timezone: str = "utc"
|
timezone: str = "utc"
|
||||||
cameras: Optional[str] = "all"
|
cameras: Optional[str] = "all"
|
||||||
|
|
||||||
|
|
||||||
class MediaRecordingsAvailabilityQueryParams(BaseModel):
|
|
||||||
cameras: str = "all"
|
|
||||||
before: Union[float, SkipJsonSchema[None]] = None
|
|
||||||
after: Union[float, SkipJsonSchema[None]] = None
|
|
||||||
scale: int = 30
|
|
||||||
|
|||||||
@ -8,7 +8,6 @@ import os
|
|||||||
import subprocess as sp
|
import subprocess as sp
|
||||||
import time
|
import time
|
||||||
from datetime import datetime, timedelta, timezone
|
from datetime import datetime, timedelta, timezone
|
||||||
from functools import reduce
|
|
||||||
from pathlib import Path as FilePath
|
from pathlib import Path as FilePath
|
||||||
from typing import Any
|
from typing import Any
|
||||||
from urllib.parse import unquote
|
from urllib.parse import unquote
|
||||||
@ -20,7 +19,7 @@ from fastapi import APIRouter, Path, Query, Request, Response
|
|||||||
from fastapi.params import Depends
|
from fastapi.params import Depends
|
||||||
from fastapi.responses import FileResponse, JSONResponse, StreamingResponse
|
from fastapi.responses import FileResponse, JSONResponse, StreamingResponse
|
||||||
from pathvalidate import sanitize_filename
|
from pathvalidate import sanitize_filename
|
||||||
from peewee import DoesNotExist, fn, operator
|
from peewee import DoesNotExist, fn
|
||||||
from tzlocal import get_localzone_name
|
from tzlocal import get_localzone_name
|
||||||
|
|
||||||
from frigate.api.defs.query.media_query_parameters import (
|
from frigate.api.defs.query.media_query_parameters import (
|
||||||
@ -28,7 +27,6 @@ from frigate.api.defs.query.media_query_parameters import (
|
|||||||
MediaEventsSnapshotQueryParams,
|
MediaEventsSnapshotQueryParams,
|
||||||
MediaLatestFrameQueryParams,
|
MediaLatestFrameQueryParams,
|
||||||
MediaMjpegFeedQueryParams,
|
MediaMjpegFeedQueryParams,
|
||||||
MediaRecordingsAvailabilityQueryParams,
|
|
||||||
MediaRecordingsSummaryQueryParams,
|
MediaRecordingsSummaryQueryParams,
|
||||||
)
|
)
|
||||||
from frigate.api.defs.tags import Tags
|
from frigate.api.defs.tags import Tags
|
||||||
@ -544,66 +542,6 @@ def recordings(
|
|||||||
return JSONResponse(content=list(recordings))
|
return JSONResponse(content=list(recordings))
|
||||||
|
|
||||||
|
|
||||||
@router.get("/recordings/unavailable", response_model=list[dict])
|
|
||||||
def no_recordings(params: MediaRecordingsAvailabilityQueryParams = Depends()):
|
|
||||||
"""Get time ranges with no recordings."""
|
|
||||||
cameras = params.cameras
|
|
||||||
before = params.before or datetime.datetime.now().timestamp()
|
|
||||||
after = (
|
|
||||||
params.after
|
|
||||||
or (datetime.datetime.now() - datetime.timedelta(hours=1)).timestamp()
|
|
||||||
)
|
|
||||||
scale = params.scale
|
|
||||||
|
|
||||||
clauses = [(Recordings.start_time > after) & (Recordings.end_time < before)]
|
|
||||||
if cameras != "all":
|
|
||||||
camera_list = cameras.split(",")
|
|
||||||
clauses.append((Recordings.camera << camera_list))
|
|
||||||
|
|
||||||
# Get recording start times
|
|
||||||
data: list[Recordings] = (
|
|
||||||
Recordings.select(Recordings.start_time, Recordings.end_time)
|
|
||||||
.where(reduce(operator.and_, clauses))
|
|
||||||
.order_by(Recordings.start_time.asc())
|
|
||||||
.dicts()
|
|
||||||
.iterator()
|
|
||||||
)
|
|
||||||
|
|
||||||
# Convert recordings to list of (start, end) tuples
|
|
||||||
recordings = [(r["start_time"], r["end_time"]) for r in data]
|
|
||||||
|
|
||||||
# Generate all time segments
|
|
||||||
current = after
|
|
||||||
no_recording_segments = []
|
|
||||||
current_start = None
|
|
||||||
|
|
||||||
while current < before:
|
|
||||||
segment_end = current + scale
|
|
||||||
# Check if segment overlaps with any recording
|
|
||||||
has_recording = any(
|
|
||||||
start <= segment_end and end >= current for start, end in recordings
|
|
||||||
)
|
|
||||||
if not has_recording:
|
|
||||||
if current_start is None:
|
|
||||||
current_start = current # Start a new gap
|
|
||||||
else:
|
|
||||||
if current_start is not None:
|
|
||||||
# End the current gap and append it
|
|
||||||
no_recording_segments.append(
|
|
||||||
{"start_time": int(current_start), "end_time": int(current)}
|
|
||||||
)
|
|
||||||
current_start = None
|
|
||||||
current = segment_end
|
|
||||||
|
|
||||||
# Append the last gap if it exists
|
|
||||||
if current_start is not None:
|
|
||||||
no_recording_segments.append(
|
|
||||||
{"start_time": int(current_start), "end_time": int(before)}
|
|
||||||
)
|
|
||||||
|
|
||||||
return JSONResponse(content=no_recording_segments)
|
|
||||||
|
|
||||||
|
|
||||||
@router.get(
|
@router.get(
|
||||||
"/{camera_name}/start/{start_ts}/end/{end_ts}/clip.mp4",
|
"/{camera_name}/start/{start_ts}/end/{end_ts}/clip.mp4",
|
||||||
description="For iOS devices, use the master.m3u8 HLS link instead of clip.mp4. Safari does not reliably process progressive mp4 files.",
|
description="For iOS devices, use the master.m3u8 HLS link instead of clip.mp4. Safari does not reliably process progressive mp4 files.",
|
||||||
|
|||||||
@ -289,10 +289,7 @@ class Dispatcher:
|
|||||||
logger.info(f"Turning off detection for {camera_name}")
|
logger.info(f"Turning off detection for {camera_name}")
|
||||||
detect_settings.enabled = False
|
detect_settings.enabled = False
|
||||||
|
|
||||||
self.config_updater.publish_update(
|
self.config_updater.publish(f"config/detect/{camera_name}", detect_settings)
|
||||||
CameraConfigUpdateTopic(CameraConfigUpdateEnum.detect, camera_name),
|
|
||||||
detect_settings,
|
|
||||||
)
|
|
||||||
self.publish(f"{camera_name}/detect/state", payload, retain=True)
|
self.publish(f"{camera_name}/detect/state", payload, retain=True)
|
||||||
|
|
||||||
def _on_enabled_command(self, camera_name: str, payload: str) -> None:
|
def _on_enabled_command(self, camera_name: str, payload: str) -> None:
|
||||||
|
|||||||
@ -34,37 +34,10 @@ class BirdClassificationConfig(FrigateBaseModel):
|
|||||||
)
|
)
|
||||||
|
|
||||||
|
|
||||||
class CustomClassificationStateCameraConfig(FrigateBaseModel):
|
|
||||||
crop: list[int, int, int, int] = Field(
|
|
||||||
title="Crop of image frame on this camera to run classification on."
|
|
||||||
)
|
|
||||||
|
|
||||||
|
|
||||||
class CustomClassificationStateConfig(FrigateBaseModel):
|
|
||||||
cameras: Dict[str, CustomClassificationStateCameraConfig] = Field(
|
|
||||||
title="Cameras to run classification on."
|
|
||||||
)
|
|
||||||
|
|
||||||
|
|
||||||
class CustomClassificationObjectConfig(FrigateBaseModel):
|
|
||||||
objects: list[str] = Field(title="Object types to classify.")
|
|
||||||
|
|
||||||
|
|
||||||
class CustomClassificationConfig(FrigateBaseModel):
|
|
||||||
enabled: bool = Field(default=True, title="Enable running the model.")
|
|
||||||
model_path: str = Field(title="Path to custom classification tflite model.")
|
|
||||||
labelmap_path: str = Field(title="Path to custom classification model labelmap.")
|
|
||||||
object_config: CustomClassificationObjectConfig | None = Field(default=None)
|
|
||||||
state_config: CustomClassificationStateConfig | None = Field(default=None)
|
|
||||||
|
|
||||||
|
|
||||||
class ClassificationConfig(FrigateBaseModel):
|
class ClassificationConfig(FrigateBaseModel):
|
||||||
bird: BirdClassificationConfig = Field(
|
bird: BirdClassificationConfig = Field(
|
||||||
default_factory=BirdClassificationConfig, title="Bird classification config."
|
default_factory=BirdClassificationConfig, title="Bird classification config."
|
||||||
)
|
)
|
||||||
custom: Dict[str, CustomClassificationConfig] = Field(
|
|
||||||
default={}, title="Custom Classification Model Configs."
|
|
||||||
)
|
|
||||||
|
|
||||||
|
|
||||||
class SemanticSearchConfig(FrigateBaseModel):
|
class SemanticSearchConfig(FrigateBaseModel):
|
||||||
|
|||||||
@ -1,178 +0,0 @@
|
|||||||
"""Real time processor that works with classification tflite models."""
|
|
||||||
|
|
||||||
import logging
|
|
||||||
from typing import Any
|
|
||||||
|
|
||||||
import cv2
|
|
||||||
import numpy as np
|
|
||||||
|
|
||||||
from frigate.comms.event_metadata_updater import (
|
|
||||||
EventMetadataPublisher,
|
|
||||||
EventMetadataTypeEnum,
|
|
||||||
)
|
|
||||||
from frigate.config import FrigateConfig
|
|
||||||
from frigate.config.classification import CustomClassificationConfig
|
|
||||||
from frigate.util.builtin import load_labels
|
|
||||||
from frigate.util.object import calculate_region
|
|
||||||
|
|
||||||
from ..types import DataProcessorMetrics
|
|
||||||
from .api import RealTimeProcessorApi
|
|
||||||
|
|
||||||
try:
|
|
||||||
from tflite_runtime.interpreter import Interpreter
|
|
||||||
except ModuleNotFoundError:
|
|
||||||
from tensorflow.lite.python.interpreter import Interpreter
|
|
||||||
|
|
||||||
logger = logging.getLogger(__name__)
|
|
||||||
|
|
||||||
|
|
||||||
class CustomStateClassificationProcessor(RealTimeProcessorApi):
|
|
||||||
def __init__(
|
|
||||||
self,
|
|
||||||
config: FrigateConfig,
|
|
||||||
model_config: CustomClassificationConfig,
|
|
||||||
metrics: DataProcessorMetrics,
|
|
||||||
):
|
|
||||||
super().__init__(config, metrics)
|
|
||||||
self.model_config = model_config
|
|
||||||
self.interpreter: Interpreter = None
|
|
||||||
self.tensor_input_details: dict[str, Any] = None
|
|
||||||
self.tensor_output_details: dict[str, Any] = None
|
|
||||||
self.labelmap: dict[int, str] = {}
|
|
||||||
self.__build_detector()
|
|
||||||
|
|
||||||
def __build_detector(self) -> None:
|
|
||||||
self.interpreter = Interpreter(
|
|
||||||
model_path=self.model_config.model_path,
|
|
||||||
num_threads=2,
|
|
||||||
)
|
|
||||||
self.interpreter.allocate_tensors()
|
|
||||||
self.tensor_input_details = self.interpreter.get_input_details()
|
|
||||||
self.tensor_output_details = self.interpreter.get_output_details()
|
|
||||||
self.labelmap = load_labels(self.model_config.labelmap_path, prefill=0)
|
|
||||||
|
|
||||||
def process_frame(self, frame_data: dict[str, Any], frame: np.ndarray):
|
|
||||||
camera = frame_data.get("camera")
|
|
||||||
if camera not in self.model_config.state_config.cameras:
|
|
||||||
return
|
|
||||||
|
|
||||||
camera_config = self.model_config.state_config.cameras[camera]
|
|
||||||
x, y, x2, y2 = calculate_region(
|
|
||||||
frame.shape,
|
|
||||||
camera_config.crop[0],
|
|
||||||
camera_config.crop[1],
|
|
||||||
camera_config.crop[2],
|
|
||||||
camera_config.crop[3],
|
|
||||||
224,
|
|
||||||
1.0,
|
|
||||||
)
|
|
||||||
|
|
||||||
rgb = cv2.cvtColor(frame, cv2.COLOR_YUV2RGB_I420)
|
|
||||||
input = rgb[
|
|
||||||
y:y2,
|
|
||||||
x:x2,
|
|
||||||
]
|
|
||||||
|
|
||||||
if input.shape != (224, 224):
|
|
||||||
input = cv2.resize(input, (224, 224))
|
|
||||||
|
|
||||||
input = np.expand_dims(input, axis=0)
|
|
||||||
self.interpreter.set_tensor(self.tensor_input_details[0]["index"], input)
|
|
||||||
self.interpreter.invoke()
|
|
||||||
res: np.ndarray = self.interpreter.get_tensor(
|
|
||||||
self.tensor_output_details[0]["index"]
|
|
||||||
)[0]
|
|
||||||
print(f"the gate res is {res}")
|
|
||||||
probs = res / res.sum(axis=0)
|
|
||||||
best_id = np.argmax(probs)
|
|
||||||
score = round(probs[best_id], 2)
|
|
||||||
|
|
||||||
print(f"got {self.labelmap[best_id]} with score {score}")
|
|
||||||
|
|
||||||
def handle_request(self, topic, request_data):
|
|
||||||
return None
|
|
||||||
|
|
||||||
def expire_object(self, object_id, camera):
|
|
||||||
pass
|
|
||||||
|
|
||||||
|
|
||||||
class CustomObjectClassificationProcessor(RealTimeProcessorApi):
|
|
||||||
def __init__(
|
|
||||||
self,
|
|
||||||
config: FrigateConfig,
|
|
||||||
model_config: CustomClassificationConfig,
|
|
||||||
sub_label_publisher: EventMetadataPublisher,
|
|
||||||
metrics: DataProcessorMetrics,
|
|
||||||
):
|
|
||||||
super().__init__(config, metrics)
|
|
||||||
self.model_config = model_config
|
|
||||||
self.interpreter: Interpreter = None
|
|
||||||
self.sub_label_publisher = sub_label_publisher
|
|
||||||
self.tensor_input_details: dict[str, Any] = None
|
|
||||||
self.tensor_output_details: dict[str, Any] = None
|
|
||||||
self.detected_objects: dict[str, float] = {}
|
|
||||||
self.labelmap: dict[int, str] = {}
|
|
||||||
self.__build_detector()
|
|
||||||
|
|
||||||
def __build_detector(self) -> None:
|
|
||||||
self.interpreter = Interpreter(
|
|
||||||
model_path=self.model_config.model_path,
|
|
||||||
num_threads=2,
|
|
||||||
)
|
|
||||||
self.interpreter.allocate_tensors()
|
|
||||||
self.tensor_input_details = self.interpreter.get_input_details()
|
|
||||||
self.tensor_output_details = self.interpreter.get_output_details()
|
|
||||||
self.labelmap = load_labels(self.model_config.labelmap_path, prefill=0)
|
|
||||||
|
|
||||||
def process_frame(self, obj_data, frame):
|
|
||||||
if obj_data["label"] not in self.model_config.object_config.objects:
|
|
||||||
return
|
|
||||||
|
|
||||||
x, y, x2, y2 = calculate_region(
|
|
||||||
frame.shape,
|
|
||||||
obj_data["box"][0],
|
|
||||||
obj_data["box"][1],
|
|
||||||
obj_data["box"][2],
|
|
||||||
obj_data["box"][3],
|
|
||||||
224,
|
|
||||||
1.0,
|
|
||||||
)
|
|
||||||
|
|
||||||
rgb = cv2.cvtColor(frame, cv2.COLOR_YUV2RGB_I420)
|
|
||||||
input = rgb[
|
|
||||||
y:y2,
|
|
||||||
x:x2,
|
|
||||||
]
|
|
||||||
|
|
||||||
if input.shape != (224, 224):
|
|
||||||
input = cv2.resize(input, (224, 224))
|
|
||||||
|
|
||||||
input = np.expand_dims(input, axis=0)
|
|
||||||
self.interpreter.set_tensor(self.tensor_input_details[0]["index"], input)
|
|
||||||
self.interpreter.invoke()
|
|
||||||
res: np.ndarray = self.interpreter.get_tensor(
|
|
||||||
self.tensor_output_details[0]["index"]
|
|
||||||
)[0]
|
|
||||||
probs = res / res.sum(axis=0)
|
|
||||||
best_id = np.argmax(probs)
|
|
||||||
|
|
||||||
score = round(probs[best_id], 2)
|
|
||||||
|
|
||||||
previous_score = self.detected_objects.get(obj_data["id"], 0.0)
|
|
||||||
|
|
||||||
if score <= previous_score:
|
|
||||||
logger.debug(f"Score {score} is worse than previous score {previous_score}")
|
|
||||||
return
|
|
||||||
|
|
||||||
self.sub_label_publisher.publish(
|
|
||||||
EventMetadataTypeEnum.sub_label,
|
|
||||||
(obj_data["id"], self.labelmap[best_id], score),
|
|
||||||
)
|
|
||||||
self.detected_objects[obj_data["id"]] = score
|
|
||||||
|
|
||||||
def handle_request(self, topic, request_data):
|
|
||||||
return None
|
|
||||||
|
|
||||||
def expire_object(self, object_id, camera):
|
|
||||||
if object_id in self.detected_objects:
|
|
||||||
self.detected_objects.pop(object_id)
|
|
||||||
@ -42,10 +42,6 @@ from frigate.data_processing.post.license_plate import (
|
|||||||
)
|
)
|
||||||
from frigate.data_processing.real_time.api import RealTimeProcessorApi
|
from frigate.data_processing.real_time.api import RealTimeProcessorApi
|
||||||
from frigate.data_processing.real_time.bird import BirdRealTimeProcessor
|
from frigate.data_processing.real_time.bird import BirdRealTimeProcessor
|
||||||
from frigate.data_processing.real_time.custom_classification import (
|
|
||||||
CustomObjectClassificationProcessor,
|
|
||||||
CustomStateClassificationProcessor,
|
|
||||||
)
|
|
||||||
from frigate.data_processing.real_time.face import FaceRealTimeProcessor
|
from frigate.data_processing.real_time.face import FaceRealTimeProcessor
|
||||||
from frigate.data_processing.real_time.license_plate import (
|
from frigate.data_processing.real_time.license_plate import (
|
||||||
LicensePlateRealTimeProcessor,
|
LicensePlateRealTimeProcessor,
|
||||||
@ -147,18 +143,6 @@ class EmbeddingMaintainer(threading.Thread):
|
|||||||
)
|
)
|
||||||
)
|
)
|
||||||
|
|
||||||
for model in self.config.classification.custom.values():
|
|
||||||
self.realtime_processors.append(
|
|
||||||
CustomStateClassificationProcessor(self.config, model, self.metrics)
|
|
||||||
if model.state_config != None
|
|
||||||
else CustomObjectClassificationProcessor(
|
|
||||||
self.config,
|
|
||||||
model,
|
|
||||||
self.event_metadata_publisher,
|
|
||||||
self.metrics,
|
|
||||||
)
|
|
||||||
)
|
|
||||||
|
|
||||||
# post processors
|
# post processors
|
||||||
self.post_processors: list[PostProcessorApi] = []
|
self.post_processors: list[PostProcessorApi] = []
|
||||||
|
|
||||||
@ -188,7 +172,7 @@ class EmbeddingMaintainer(threading.Thread):
|
|||||||
self._process_requests()
|
self._process_requests()
|
||||||
self._process_updates()
|
self._process_updates()
|
||||||
self._process_recordings_updates()
|
self._process_recordings_updates()
|
||||||
self._process_frame_updates()
|
self._process_dedicated_lpr()
|
||||||
self._expire_dedicated_lpr()
|
self._expire_dedicated_lpr()
|
||||||
self._process_finalized()
|
self._process_finalized()
|
||||||
self._process_event_metadata()
|
self._process_event_metadata()
|
||||||
@ -465,7 +449,7 @@ class EmbeddingMaintainer(threading.Thread):
|
|||||||
event_id, RegenerateDescriptionEnum(source)
|
event_id, RegenerateDescriptionEnum(source)
|
||||||
)
|
)
|
||||||
|
|
||||||
def _process_frame_updates(self) -> None:
|
def _process_dedicated_lpr(self) -> None:
|
||||||
"""Process event updates"""
|
"""Process event updates"""
|
||||||
(topic, data) = self.detection_subscriber.check_for_update()
|
(topic, data) = self.detection_subscriber.check_for_update()
|
||||||
|
|
||||||
@ -474,7 +458,7 @@ class EmbeddingMaintainer(threading.Thread):
|
|||||||
|
|
||||||
camera, frame_name, _, _, motion_boxes, _ = data
|
camera, frame_name, _, _, motion_boxes, _ = data
|
||||||
|
|
||||||
if not camera or len(motion_boxes) == 0:
|
if not camera or not self.config.lpr.enabled or len(motion_boxes) == 0:
|
||||||
return
|
return
|
||||||
|
|
||||||
camera_config = self.config.cameras[camera]
|
camera_config = self.config.cameras[camera]
|
||||||
@ -482,8 +466,8 @@ class EmbeddingMaintainer(threading.Thread):
|
|||||||
if (
|
if (
|
||||||
camera_config.type != CameraTypeEnum.lpr
|
camera_config.type != CameraTypeEnum.lpr
|
||||||
or "license_plate" in camera_config.objects.track
|
or "license_plate" in camera_config.objects.track
|
||||||
) and len(self.config.classification.custom) == 0:
|
):
|
||||||
# no active features that use this data
|
# we're not a dedicated lpr camera or we are one but we're using frigate+
|
||||||
return
|
return
|
||||||
|
|
||||||
try:
|
try:
|
||||||
@ -503,9 +487,6 @@ class EmbeddingMaintainer(threading.Thread):
|
|||||||
if isinstance(processor, LicensePlateRealTimeProcessor):
|
if isinstance(processor, LicensePlateRealTimeProcessor):
|
||||||
processor.process_frame(camera, yuv_frame, True)
|
processor.process_frame(camera, yuv_frame, True)
|
||||||
|
|
||||||
if isinstance(processor, CustomStateClassificationProcessor):
|
|
||||||
processor.process_frame({"camera": camera}, yuv_frame)
|
|
||||||
|
|
||||||
self.frame_manager.close(frame_name)
|
self.frame_manager.close(frame_name)
|
||||||
|
|
||||||
def _create_thumbnail(self, yuv_frame, box, height=500) -> Optional[bytes]:
|
def _create_thumbnail(self, yuv_frame, box, height=500) -> Optional[bytes]:
|
||||||
|
|||||||
@ -17,7 +17,6 @@ import {
|
|||||||
VirtualizedMotionSegments,
|
VirtualizedMotionSegments,
|
||||||
VirtualizedMotionSegmentsRef,
|
VirtualizedMotionSegmentsRef,
|
||||||
} from "./VirtualizedMotionSegments";
|
} from "./VirtualizedMotionSegments";
|
||||||
import { RecordingSegment } from "@/types/record";
|
|
||||||
|
|
||||||
export type MotionReviewTimelineProps = {
|
export type MotionReviewTimelineProps = {
|
||||||
segmentDuration: number;
|
segmentDuration: number;
|
||||||
@ -39,7 +38,6 @@ export type MotionReviewTimelineProps = {
|
|||||||
setExportEndTime?: React.Dispatch<React.SetStateAction<number>>;
|
setExportEndTime?: React.Dispatch<React.SetStateAction<number>>;
|
||||||
events: ReviewSegment[];
|
events: ReviewSegment[];
|
||||||
motion_events: MotionData[];
|
motion_events: MotionData[];
|
||||||
noRecordingRanges?: RecordingSegment[];
|
|
||||||
contentRef: RefObject<HTMLDivElement>;
|
contentRef: RefObject<HTMLDivElement>;
|
||||||
timelineRef?: RefObject<HTMLDivElement>;
|
timelineRef?: RefObject<HTMLDivElement>;
|
||||||
onHandlebarDraggingChange?: (isDragging: boolean) => void;
|
onHandlebarDraggingChange?: (isDragging: boolean) => void;
|
||||||
@ -68,7 +66,6 @@ export function MotionReviewTimeline({
|
|||||||
setExportEndTime,
|
setExportEndTime,
|
||||||
events,
|
events,
|
||||||
motion_events,
|
motion_events,
|
||||||
noRecordingRanges,
|
|
||||||
contentRef,
|
contentRef,
|
||||||
timelineRef,
|
timelineRef,
|
||||||
onHandlebarDraggingChange,
|
onHandlebarDraggingChange,
|
||||||
@ -100,17 +97,6 @@ export function MotionReviewTimeline({
|
|||||||
motion_events,
|
motion_events,
|
||||||
);
|
);
|
||||||
|
|
||||||
const getRecordingAvailability = useCallback(
|
|
||||||
(time: number): boolean | undefined => {
|
|
||||||
if (!noRecordingRanges?.length) return undefined;
|
|
||||||
|
|
||||||
return !noRecordingRanges.some(
|
|
||||||
(range) => time >= range.start_time && time < range.end_time,
|
|
||||||
);
|
|
||||||
},
|
|
||||||
[noRecordingRanges],
|
|
||||||
);
|
|
||||||
|
|
||||||
const segmentTimes = useMemo(() => {
|
const segmentTimes = useMemo(() => {
|
||||||
const segments = [];
|
const segments = [];
|
||||||
let segmentTime = timelineStartAligned;
|
let segmentTime = timelineStartAligned;
|
||||||
@ -220,7 +206,6 @@ export function MotionReviewTimeline({
|
|||||||
dense={dense}
|
dense={dense}
|
||||||
motionOnly={motionOnly}
|
motionOnly={motionOnly}
|
||||||
getMotionSegmentValue={getMotionSegmentValue}
|
getMotionSegmentValue={getMotionSegmentValue}
|
||||||
getRecordingAvailability={getRecordingAvailability}
|
|
||||||
/>
|
/>
|
||||||
</ReviewTimeline>
|
</ReviewTimeline>
|
||||||
);
|
);
|
||||||
|
|||||||
@ -15,7 +15,6 @@ type MotionSegmentProps = {
|
|||||||
timestampSpread: number;
|
timestampSpread: number;
|
||||||
firstHalfMotionValue: number;
|
firstHalfMotionValue: number;
|
||||||
secondHalfMotionValue: number;
|
secondHalfMotionValue: number;
|
||||||
hasRecording?: boolean;
|
|
||||||
motionOnly: boolean;
|
motionOnly: boolean;
|
||||||
showMinimap: boolean;
|
showMinimap: boolean;
|
||||||
minimapStartTime?: number;
|
minimapStartTime?: number;
|
||||||
@ -32,7 +31,6 @@ export function MotionSegment({
|
|||||||
timestampSpread,
|
timestampSpread,
|
||||||
firstHalfMotionValue,
|
firstHalfMotionValue,
|
||||||
secondHalfMotionValue,
|
secondHalfMotionValue,
|
||||||
hasRecording,
|
|
||||||
motionOnly,
|
motionOnly,
|
||||||
showMinimap,
|
showMinimap,
|
||||||
minimapStartTime,
|
minimapStartTime,
|
||||||
@ -178,12 +176,6 @@ export function MotionSegment({
|
|||||||
segmentClasses,
|
segmentClasses,
|
||||||
severity[0] && "bg-gradient-to-r",
|
severity[0] && "bg-gradient-to-r",
|
||||||
severity[0] && severityColorsBg[severity[0]],
|
severity[0] && severityColorsBg[severity[0]],
|
||||||
// TODO: will update this for 0.17
|
|
||||||
false &&
|
|
||||||
hasRecording == false &&
|
|
||||||
firstHalfMotionValue == 0 &&
|
|
||||||
secondHalfMotionValue == 0 &&
|
|
||||||
"bg-slashes",
|
|
||||||
)}
|
)}
|
||||||
onClick={segmentClick}
|
onClick={segmentClick}
|
||||||
onTouchEnd={(event) => handleTouchStart(event, segmentClick)}
|
onTouchEnd={(event) => handleTouchStart(event, segmentClick)}
|
||||||
|
|||||||
@ -24,7 +24,6 @@ type VirtualizedMotionSegmentsProps = {
|
|||||||
dense: boolean;
|
dense: boolean;
|
||||||
motionOnly: boolean;
|
motionOnly: boolean;
|
||||||
getMotionSegmentValue: (timestamp: number) => number;
|
getMotionSegmentValue: (timestamp: number) => number;
|
||||||
getRecordingAvailability: (timestamp: number) => boolean | undefined;
|
|
||||||
};
|
};
|
||||||
|
|
||||||
export interface VirtualizedMotionSegmentsRef {
|
export interface VirtualizedMotionSegmentsRef {
|
||||||
@ -56,7 +55,6 @@ export const VirtualizedMotionSegments = forwardRef<
|
|||||||
dense,
|
dense,
|
||||||
motionOnly,
|
motionOnly,
|
||||||
getMotionSegmentValue,
|
getMotionSegmentValue,
|
||||||
getRecordingAvailability,
|
|
||||||
},
|
},
|
||||||
ref,
|
ref,
|
||||||
) => {
|
) => {
|
||||||
@ -156,8 +154,6 @@ export const VirtualizedMotionSegments = forwardRef<
|
|||||||
(item.end_time ?? segmentTime) >= motionEnd),
|
(item.end_time ?? segmentTime) >= motionEnd),
|
||||||
);
|
);
|
||||||
|
|
||||||
const hasRecording = getRecordingAvailability(segmentTime);
|
|
||||||
|
|
||||||
if ((!segmentMotion || overlappingReviewItems) && motionOnly) {
|
if ((!segmentMotion || overlappingReviewItems) && motionOnly) {
|
||||||
return null; // Skip rendering this segment in motion only mode
|
return null; // Skip rendering this segment in motion only mode
|
||||||
}
|
}
|
||||||
@ -176,7 +172,6 @@ export const VirtualizedMotionSegments = forwardRef<
|
|||||||
events={events}
|
events={events}
|
||||||
firstHalfMotionValue={firstHalfMotionValue}
|
firstHalfMotionValue={firstHalfMotionValue}
|
||||||
secondHalfMotionValue={secondHalfMotionValue}
|
secondHalfMotionValue={secondHalfMotionValue}
|
||||||
hasRecording={hasRecording}
|
|
||||||
segmentDuration={segmentDuration}
|
segmentDuration={segmentDuration}
|
||||||
segmentTime={segmentTime}
|
segmentTime={segmentTime}
|
||||||
timestampSpread={timestampSpread}
|
timestampSpread={timestampSpread}
|
||||||
@ -194,7 +189,6 @@ export const VirtualizedMotionSegments = forwardRef<
|
|||||||
[
|
[
|
||||||
events,
|
events,
|
||||||
getMotionSegmentValue,
|
getMotionSegmentValue,
|
||||||
getRecordingAvailability,
|
|
||||||
motionOnly,
|
motionOnly,
|
||||||
segmentDuration,
|
segmentDuration,
|
||||||
showMinimap,
|
showMinimap,
|
||||||
|
|||||||
@ -43,11 +43,7 @@ import Logo from "@/components/Logo";
|
|||||||
import { Skeleton } from "@/components/ui/skeleton";
|
import { Skeleton } from "@/components/ui/skeleton";
|
||||||
import { FaVideo } from "react-icons/fa";
|
import { FaVideo } from "react-icons/fa";
|
||||||
import { VideoResolutionType } from "@/types/live";
|
import { VideoResolutionType } from "@/types/live";
|
||||||
import {
|
import { ASPECT_VERTICAL_LAYOUT, ASPECT_WIDE_LAYOUT } from "@/types/record";
|
||||||
ASPECT_VERTICAL_LAYOUT,
|
|
||||||
ASPECT_WIDE_LAYOUT,
|
|
||||||
RecordingSegment,
|
|
||||||
} from "@/types/record";
|
|
||||||
import { useResizeObserver } from "@/hooks/resize-observer";
|
import { useResizeObserver } from "@/hooks/resize-observer";
|
||||||
import { cn } from "@/lib/utils";
|
import { cn } from "@/lib/utils";
|
||||||
import { useFullscreen } from "@/hooks/use-fullscreen";
|
import { useFullscreen } from "@/hooks/use-fullscreen";
|
||||||
@ -812,16 +808,6 @@ function Timeline({
|
|||||||
},
|
},
|
||||||
]);
|
]);
|
||||||
|
|
||||||
const { data: noRecordings } = useSWR<RecordingSegment[]>([
|
|
||||||
"recordings/unavailable",
|
|
||||||
{
|
|
||||||
before: timeRange.before,
|
|
||||||
after: timeRange.after,
|
|
||||||
scale: Math.round(zoomSettings.segmentDuration / 2),
|
|
||||||
cameras: mainCamera,
|
|
||||||
},
|
|
||||||
]);
|
|
||||||
|
|
||||||
const [exportStart, setExportStartTime] = useState<number>(0);
|
const [exportStart, setExportStartTime] = useState<number>(0);
|
||||||
const [exportEnd, setExportEndTime] = useState<number>(0);
|
const [exportEnd, setExportEndTime] = useState<number>(0);
|
||||||
|
|
||||||
@ -867,7 +853,6 @@ function Timeline({
|
|||||||
setHandlebarTime={setCurrentTime}
|
setHandlebarTime={setCurrentTime}
|
||||||
events={mainCameraReviewItems}
|
events={mainCameraReviewItems}
|
||||||
motion_events={motionData ?? []}
|
motion_events={motionData ?? []}
|
||||||
noRecordingRanges={noRecordings ?? []}
|
|
||||||
contentRef={contentRef}
|
contentRef={contentRef}
|
||||||
onHandlebarDraggingChange={(scrubbing) => setScrubbing(scrubbing)}
|
onHandlebarDraggingChange={(scrubbing) => setScrubbing(scrubbing)}
|
||||||
isZooming={isZooming}
|
isZooming={isZooming}
|
||||||
|
|||||||
@ -42,10 +42,6 @@ module.exports = {
|
|||||||
wide: "32 / 9",
|
wide: "32 / 9",
|
||||||
tall: "8 / 9",
|
tall: "8 / 9",
|
||||||
},
|
},
|
||||||
backgroundImage: {
|
|
||||||
slashes:
|
|
||||||
"repeating-linear-gradient(45deg, hsl(var(--primary-variant) / 0.2), hsl(var(--primary-variant) / 0.2) 2px, transparent 2px, transparent 8px)",
|
|
||||||
},
|
|
||||||
colors: {
|
colors: {
|
||||||
border: "hsl(var(--border))",
|
border: "hsl(var(--border))",
|
||||||
input: "hsl(var(--input))",
|
input: "hsl(var(--input))",
|
||||||
|
|||||||
Loading…
x
Reference in New Issue
Block a user