mirror of
https://github.com/blakeblackshear/frigate.git
synced 2026-10-08 15:52:48 +03:00
Compare commits
4
Commits
dev
...
update-annke
| Author | SHA1 | Date | |
|---|---|---|---|
|
|
3e8ad400cd | ||
|
|
f929134a1b | ||
|
|
cb745d0a50 | ||
|
|
e912d0852b |
@@ -234,7 +234,7 @@ The FeatureList on the [ONVIF Conformant Products Database](https://www.onvif.or
|
||||
| Amcrest ASH21 | ✅ | ❌ | ONVIF service port: 80 |
|
||||
| Amcrest IP4M-S2112EW-AI | ✅ | ❌ | FOV relative movement not supported. |
|
||||
| Amcrest IP5M-1190EW | ✅ | ❌ | ONVIF Port: 80. FOV relative movement not supported. |
|
||||
| Annke CZ504 | ✅ | ✅ | Annke support provide specific firmware ([V5.7.1 build 250227](https://github.com/pierrepinon/annke_cz504/raw/refs/heads/main/digicap_V5-7-1_build_250227.dav)) to fix issue with ONVIF "TranslationSpaceFov" |
|
||||
| Annke CZ504 | ✅ | ❌ | |
|
||||
| Axis Q-6155E | ✅ | ❌ | ONVIF service port: 80; Camera does not support MoveStatus. |
|
||||
| Ctronics PTZ | ✅ | ❌ | |
|
||||
| Dahua | ✅ | ✅ | Some low-end Dahuas (lite series, picoo series (commonly), among others) have been reported to not support autotracking. These models usually don't have a four digit model number with chassis prefix and options postfix (e.g. DH-P5AE-PV vs DH-SD49825GB-HNR). |
|
||||
|
||||
@@ -73,9 +73,10 @@ You must use a vision-capable model with Frigate. The following models are recom
|
||||
|
||||
The `embeddings` role needs a different kind of model. Text queries are matched against the stored image embeddings, so the model must be trained to place images and text into the same vector space. A chat or description model will still return vectors when asked, but those vectors are not trained for retrieval and text searches will return poor matches with no error to indicate why.
|
||||
|
||||
| Model | Notes |
|
||||
| -------------------- | ------------------------------------------------------------------------------------------------------------------------------------------------------------------- |
|
||||
| `qwen3-vl-embedding` | Multimodal embeddings for [Semantic Search](/configuration/semantic_search#genai-provider). Must be served by llama.cpp started with `--embeddings` and `--mmproj`. |
|
||||
| Model | Notes |
|
||||
| -------------------- | ---------------------------------------------------------------------------------------------------------------------------------------------------------------------- |
|
||||
| `embeddinggemma-2` | Multimodal embeddings for [Semantic Search](/configuration/semantic_search#genai-provider). Strong semantic search accuracy with efficient inference on a small model. |
|
||||
| `qwen3-vl-embedding` | Multimodal embeddings for [Semantic Search](/configuration/semantic_search#genai-provider). Good performance, large model that requires strong hardware for inference. |
|
||||
|
||||
:::info
|
||||
|
||||
|
||||
@@ -35,7 +35,11 @@ from frigate.config.camera.updater import (
|
||||
)
|
||||
from frigate.config.env import substitute_frigate_vars
|
||||
from frigate.models import User
|
||||
from frigate.util.builtin import clean_camera_user_pass, get_record_segment_time
|
||||
from frigate.util.builtin import (
|
||||
clean_camera_user_pass,
|
||||
clear_orphaned_comments,
|
||||
get_record_segment_time,
|
||||
)
|
||||
from frigate.util.camera_cleanup import cleanup_camera_db, cleanup_camera_files
|
||||
from frigate.util.config import find_config_file
|
||||
from frigate.util.image import run_ffmpeg_snapshot
|
||||
@@ -1201,6 +1205,7 @@ async def delete_camera(
|
||||
# Remove camera from config
|
||||
if "cameras" in data and camera_name in data["cameras"]:
|
||||
del data["cameras"][camera_name]
|
||||
clear_orphaned_comments(data["cameras"], data, "cameras")
|
||||
|
||||
# Remove camera from auth roles
|
||||
auth = data.get("auth", {})
|
||||
|
||||
+13
-4
@@ -351,8 +351,13 @@ async def get_snapshot_from_recording(
|
||||
mime_type = "png" if format == "png" else "jpeg"
|
||||
config: FrigateConfig = request.app.frigate_config
|
||||
|
||||
image_data = get_image_from_recording(
|
||||
config.ffmpeg, recording.path, time_in_segment, codec, height
|
||||
image_data = await asyncio.to_thread(
|
||||
get_image_from_recording,
|
||||
config.ffmpeg,
|
||||
recording.path,
|
||||
time_in_segment,
|
||||
codec,
|
||||
height,
|
||||
)
|
||||
|
||||
if not image_data:
|
||||
@@ -406,8 +411,12 @@ async def submit_recording_snapshot_to_plus(
|
||||
config: FrigateConfig = request.app.frigate_config
|
||||
recording: Recordings = recording_query.get()
|
||||
time_in_segment = frame_time - recording.start_time
|
||||
image_data = get_image_from_recording(
|
||||
config.ffmpeg, recording.path, time_in_segment, "png"
|
||||
image_data = await asyncio.to_thread(
|
||||
get_image_from_recording,
|
||||
config.ffmpeg,
|
||||
recording.path,
|
||||
time_in_segment,
|
||||
"png",
|
||||
)
|
||||
|
||||
if not image_data:
|
||||
|
||||
@@ -1,6 +1,9 @@
|
||||
"""Unit tests for recordings/media API endpoints."""
|
||||
|
||||
import threading
|
||||
from concurrent.futures import ThreadPoolExecutor
|
||||
from datetime import UTC, datetime
|
||||
from unittest.mock import patch
|
||||
|
||||
import pytz
|
||||
from fastapi import Request
|
||||
@@ -527,3 +530,90 @@ class TestHttpMedia(BaseTestHttp):
|
||||
|
||||
assert response.status_code == 200
|
||||
assert response.json() == [{"start_time": 1010, "end_time": 1030}]
|
||||
|
||||
def _insert_recording(self, id: str, start_time: float, end_time: float) -> None:
|
||||
Recordings.insert(
|
||||
id=id,
|
||||
path=f"/media/recordings/{id}.mp4",
|
||||
camera="front_door",
|
||||
start_time=start_time,
|
||||
end_time=end_time,
|
||||
duration=end_time - start_time,
|
||||
motion=0,
|
||||
).execute()
|
||||
|
||||
def test_recording_snapshot_does_not_block_event_loop(self):
|
||||
self._insert_recording("snapshot", 1000, 1010)
|
||||
started = threading.Event()
|
||||
release = threading.Event()
|
||||
finished = threading.Event()
|
||||
|
||||
def slow_image(*args):
|
||||
started.set()
|
||||
release.wait(timeout=5)
|
||||
finished.set()
|
||||
return b"jpeg"
|
||||
|
||||
with (
|
||||
AuthTestClient(self.app) as client,
|
||||
patch("frigate.api.media.get_image_from_recording", slow_image),
|
||||
ThreadPoolExecutor(max_workers=1) as executor,
|
||||
):
|
||||
snapshot = executor.submit(
|
||||
client.get, "/front_door/recordings/1005/snapshot.jpg"
|
||||
)
|
||||
self.assertTrue(started.wait(timeout=2))
|
||||
version = client.get("/version")
|
||||
blocked = finished.is_set()
|
||||
release.set()
|
||||
|
||||
self.assertEqual(version.status_code, 200)
|
||||
self.assertFalse(blocked)
|
||||
self.assertEqual(snapshot.result().status_code, 200)
|
||||
|
||||
def test_recording_snapshot_hit_returns_image(self):
|
||||
self._insert_recording("snapshot", 1000, 1010)
|
||||
|
||||
with (
|
||||
AuthTestClient(self.app) as client,
|
||||
patch("frigate.api.media.get_image_from_recording", return_value=b"jpeg"),
|
||||
):
|
||||
response = client.get("/front_door/recordings/1005/snapshot.jpg")
|
||||
|
||||
self.assertEqual(response.status_code, 200)
|
||||
self.assertEqual(response.headers["Content-Type"], "image/jpeg")
|
||||
self.assertEqual(response.content, b"jpeg")
|
||||
|
||||
def test_recording_snapshot_misses_return_404(self):
|
||||
self._insert_recording("snapshot", 1000, 1010)
|
||||
|
||||
with AuthTestClient(self.app) as client:
|
||||
absent = client.get("/front_door/recordings/2000/snapshot.jpg")
|
||||
rounded_miss = client.get("/front_door/recordings/1010.5/snapshot.jpg")
|
||||
|
||||
self.assertEqual(absent.status_code, 404)
|
||||
self.assertEqual(rounded_miss.status_code, 404)
|
||||
|
||||
def test_plus_snapshot_miss_returns_404_without_image_lookup(self):
|
||||
with (
|
||||
AuthTestClient(self.app) as client,
|
||||
patch("frigate.api.media.get_image_from_recording") as image,
|
||||
):
|
||||
response = client.post("/front_door/plus/1005")
|
||||
|
||||
self.assertEqual(response.status_code, 404)
|
||||
image.assert_not_called()
|
||||
|
||||
def test_plus_snapshot_hit_uploads_image(self):
|
||||
self._insert_recording("snapshot", 1000, 1010)
|
||||
|
||||
with (
|
||||
AuthTestClient(self.app) as client,
|
||||
patch("frigate.api.media.get_image_from_recording", return_value=b"png"),
|
||||
patch("frigate.api.media.cv2.imdecode", return_value="frame"),
|
||||
patch.object(self.app.frigate_config.plus_api, "upload_image") as upload,
|
||||
):
|
||||
response = client.post("/front_door/plus/1005")
|
||||
|
||||
self.assertEqual(response.status_code, 200)
|
||||
upload.assert_called_once_with("frame", "front_door")
|
||||
|
||||
@@ -6,7 +6,7 @@ import unittest
|
||||
|
||||
from ruamel.yaml import YAML
|
||||
|
||||
from frigate.util.builtin import update_yaml_file_bulk
|
||||
from frigate.util.builtin import clear_orphaned_comments, update_yaml_file_bulk
|
||||
|
||||
|
||||
class TestUpdateYaml(unittest.TestCase):
|
||||
@@ -178,6 +178,27 @@ class TestUpdateYaml(unittest.TestCase):
|
||||
assert data["cameras"]["cam1"]["detect"]["fps"] == 5
|
||||
assert "# tuned for the pi" in self._read()
|
||||
|
||||
def test_delete_only_camera_with_comment_on_cameras_key(self):
|
||||
"""Deleting the only camera stays parseable when the cameras key
|
||||
carries the default config's trailing comment."""
|
||||
self._write(
|
||||
"cameras: # No cameras defined, UI wizard should be used\n"
|
||||
" cam1:\n"
|
||||
" enabled: true\n"
|
||||
"version: 0.18-0\n"
|
||||
)
|
||||
yaml = YAML()
|
||||
with open(self.config_path) as f:
|
||||
data = yaml.load(f)
|
||||
del data["cameras"]["cam1"]
|
||||
clear_orphaned_comments(data["cameras"], data, "cameras")
|
||||
with open(self.config_path, "w") as f:
|
||||
yaml.dump(data, f)
|
||||
|
||||
data = self._load()
|
||||
assert data["cameras"] == {}
|
||||
assert data["version"] == "0.18-0"
|
||||
|
||||
|
||||
if __name__ == "__main__":
|
||||
unittest.main(verbosity=2)
|
||||
|
||||
@@ -91,7 +91,7 @@ export default function CameraInfoDialog({
|
||||
open={showCameraInfoDialog}
|
||||
onOpenChange={setShowCameraInfoDialog}
|
||||
>
|
||||
<DialogContent>
|
||||
<DialogContent className="scrollbar-container max-h-[90dvh] overflow-y-auto">
|
||||
<DialogHeader>
|
||||
<DialogTitle className="smart-capitalize">
|
||||
{t("cameras.info.cameraProbeInfo", {
|
||||
|
||||
Reference in New Issue
Block a user