mirror of
https://github.com/blakeblackshear/frigate.git
synced 2026-01-05 20:04:51 +01:00
Miscellaneous fixes (0.17 beta) (#21431)
* Add shortSummary field to review summary to be used for notifications * pull in current config version into default config * fix crash when dynamically adding cameras depending on where we are in the update loop, camera configs might not be updated yet and we are receiving detections already * add no tracked objects and icon to explore summary view * reset add camera wizard when closing and saving * don't flash no exports icon while loading * Improve handling of homekit config * Increase prompt tokens reservation * Adjust * Catch event not found object detection * Use thread lock for JinaV2 in onnxruntime * remove incorrect embeddings process from memray docs * only show transcribe button if audio event has video * apply aspect ratio and margin constraints to path overlay in detail stream on mobile improves a specific case where the overlay was not aligned with 4:3 cameras on mobile phones * show metadata title as tooltip on icon hover in detail stream --------- Co-authored-by: Josh Hawkins <32435876+hawkeye217@users.noreply.github.com>
This commit is contained in:
parent
e20b324e0a
commit
3c5eb1aee5
@ -55,7 +55,7 @@ function setup_homekit_config() {
|
||||
|
||||
if [[ ! -f "${config_path}" ]]; then
|
||||
echo "[INFO] Creating empty HomeKit config file..."
|
||||
echo '{}' > "${config_path}"
|
||||
echo 'homekit: {}' > "${config_path}"
|
||||
fi
|
||||
|
||||
# Convert YAML to JSON for jq processing
|
||||
@ -70,12 +70,14 @@ function setup_homekit_config() {
|
||||
jq '
|
||||
# Keep only the homekit section if it exists, otherwise empty object
|
||||
if has("homekit") then {homekit: .homekit} else {homekit: {}} end
|
||||
' "${temp_json}" > "${cleaned_json}" 2>/dev/null || echo '{"homekit": {}}' > "${cleaned_json}"
|
||||
' "${temp_json}" > "${cleaned_json}" 2>/dev/null || {
|
||||
echo '{"homekit": {}}' > "${cleaned_json}"
|
||||
}
|
||||
|
||||
# Convert back to YAML and write to the config file
|
||||
yq eval -P "${cleaned_json}" > "${config_path}" 2>/dev/null || {
|
||||
echo "[WARNING] Failed to convert cleaned config to YAML, creating minimal config"
|
||||
echo '{"homekit": {}}' > "${config_path}"
|
||||
echo 'homekit: {}' > "${config_path}"
|
||||
}
|
||||
|
||||
# Clean up temp files
|
||||
|
||||
@ -16,12 +16,13 @@ Review summaries provide structured JSON responses that are saved for each revie
|
||||
```
|
||||
- `title` (string): A concise, direct title that describes the purpose or overall action (e.g., "Person taking out trash", "Joe walking dog").
|
||||
- `scene` (string): A narrative description of what happens across the sequence from start to finish, including setting, detected objects, and their observable actions.
|
||||
- `shortSummary` (string): A brief 2-sentence summary of the scene, suitable for notifications. This is a condensed version of the scene description.
|
||||
- `confidence` (float): 0-1 confidence in the analysis. Higher confidence when objects/actions are clearly visible and context is unambiguous.
|
||||
- `other_concerns` (list): List of user-defined concerns that may need additional investigation.
|
||||
- `potential_threat_level` (integer): 0, 1, or 2 as defined below.
|
||||
```
|
||||
|
||||
This will show in multiple places in the UI to give additional context about each activity, and allow viewing more details when extra attention is required. Frigate's built in notifications will also automatically show the title and description when the data is available.
|
||||
This will show in multiple places in the UI to give additional context about each activity, and allow viewing more details when extra attention is required. Frigate's built in notifications will automatically show the title and `shortSummary` when the data is available, while the full `scene` description is available in the UI for detailed review.
|
||||
|
||||
### Defining Typical Activity
|
||||
|
||||
|
||||
@ -36,7 +36,6 @@ Frigate processes are named using a module-based naming scheme. Common module na
|
||||
- `frigate.output` - Output processing
|
||||
- `frigate.audio_manager` - Audio processing
|
||||
- `frigate.embeddings` - Embeddings processing
|
||||
- `frigate.embeddings_manager` - Embeddings manager
|
||||
|
||||
You can also specify the full process name (including camera-specific identifiers) if you want to profile a specific camera:
|
||||
|
||||
|
||||
@ -388,7 +388,7 @@ class WebPushClient(Communicator):
|
||||
else:
|
||||
title = base_title
|
||||
|
||||
message = payload["after"]["data"]["metadata"]["scene"]
|
||||
message = payload["after"]["data"]["metadata"]["shortSummary"]
|
||||
else:
|
||||
zone_names = payload["after"]["data"]["zones"]
|
||||
formatted_zone_names = []
|
||||
|
||||
@ -28,6 +28,7 @@ from frigate.util.builtin import (
|
||||
get_ffmpeg_arg_list,
|
||||
)
|
||||
from frigate.util.config import (
|
||||
CURRENT_CONFIG_VERSION,
|
||||
StreamInfoRetriever,
|
||||
convert_area_to_pixels,
|
||||
find_config_file,
|
||||
@ -76,11 +77,12 @@ logger = logging.getLogger(__name__)
|
||||
|
||||
yaml = YAML()
|
||||
|
||||
DEFAULT_CONFIG = """
|
||||
DEFAULT_CONFIG = f"""
|
||||
mqtt:
|
||||
enabled: False
|
||||
|
||||
cameras: {} # No cameras defined, UI wizard should be used
|
||||
cameras: {{}} # No cameras defined, UI wizard should be used
|
||||
version: {CURRENT_CONFIG_VERSION}
|
||||
"""
|
||||
|
||||
DEFAULT_DETECTORS = {"cpu": {"type": "cpu"}}
|
||||
@ -753,8 +755,7 @@ class FrigateConfig(FrigateBaseModel):
|
||||
if new_config and f.tell() == 0:
|
||||
f.write(DEFAULT_CONFIG)
|
||||
logger.info(
|
||||
"Created default config file, see the getting started docs \
|
||||
for configuration https://docs.frigate.video/guides/getting_started"
|
||||
"Created default config file, see the getting started docs for configuration: https://docs.frigate.video/guides/getting_started"
|
||||
)
|
||||
|
||||
f.seek(0)
|
||||
|
||||
@ -86,7 +86,11 @@ class ObjectDescriptionProcessor(PostProcessorApi):
|
||||
and data["id"] not in self.early_request_sent
|
||||
):
|
||||
if data["has_clip"] and data["has_snapshot"]:
|
||||
event: Event = Event.get(Event.id == data["id"])
|
||||
try:
|
||||
event: Event = Event.get(Event.id == data["id"])
|
||||
except DoesNotExist:
|
||||
logger.error(f"Event {data['id']} not found")
|
||||
return
|
||||
|
||||
if (
|
||||
not camera_config.objects.genai.objects
|
||||
|
||||
@ -92,7 +92,7 @@ class ReviewDescriptionProcessor(PostProcessorApi):
|
||||
|
||||
pixels_per_image = width * height
|
||||
tokens_per_image = pixels_per_image / 1250
|
||||
prompt_tokens = 3500
|
||||
prompt_tokens = 3800
|
||||
response_tokens = 300
|
||||
available_tokens = context_size - prompt_tokens - response_tokens
|
||||
max_frames = int(available_tokens / tokens_per_image)
|
||||
|
||||
@ -8,6 +8,9 @@ class ReviewMetadata(BaseModel):
|
||||
scene: str = Field(
|
||||
description="A comprehensive description of the setting and entities, including relevant context and plausible inferences if supported by visual evidence."
|
||||
)
|
||||
shortSummary: str = Field(
|
||||
description="A brief 2-sentence summary of the scene, suitable for notifications. Should capture the key activity and context without full detail."
|
||||
)
|
||||
confidence: float = Field(
|
||||
description="A float between 0 and 1 representing your overall confidence in this analysis."
|
||||
)
|
||||
|
||||
@ -139,8 +139,31 @@ class ONNXModelRunner(BaseModelRunner):
|
||||
ModelTypeEnum.dfine.value,
|
||||
]
|
||||
|
||||
def __init__(self, ort: ort.InferenceSession):
|
||||
@staticmethod
|
||||
def is_concurrent_model(model_type: str | None) -> bool:
|
||||
"""Check if model requires thread locking for concurrent inference.
|
||||
|
||||
Some models (like JinaV2) share one runner between text and vision embeddings
|
||||
called from different threads, requiring thread synchronization.
|
||||
"""
|
||||
if not model_type:
|
||||
return False
|
||||
|
||||
# Import here to avoid circular imports
|
||||
from frigate.embeddings.types import EnrichmentModelTypeEnum
|
||||
|
||||
return model_type == EnrichmentModelTypeEnum.jina_v2.value
|
||||
|
||||
def __init__(self, ort: ort.InferenceSession, model_type: str | None = None):
|
||||
self.ort = ort
|
||||
self.model_type = model_type
|
||||
|
||||
# Thread lock to prevent concurrent inference (needed for JinaV2 which shares
|
||||
# one runner between text and vision embeddings called from different threads)
|
||||
if self.is_concurrent_model(model_type):
|
||||
self._inference_lock = threading.Lock()
|
||||
else:
|
||||
self._inference_lock = None
|
||||
|
||||
def get_input_names(self) -> list[str]:
|
||||
return [input.name for input in self.ort.get_inputs()]
|
||||
@ -150,6 +173,10 @@ class ONNXModelRunner(BaseModelRunner):
|
||||
return self.ort.get_inputs()[0].shape[3]
|
||||
|
||||
def run(self, input: dict[str, Any]) -> Any | None:
|
||||
if self._inference_lock:
|
||||
with self._inference_lock:
|
||||
return self.ort.run(None, input)
|
||||
|
||||
return self.ort.run(None, input)
|
||||
|
||||
|
||||
@ -576,5 +603,6 @@ def get_optimized_runner(
|
||||
),
|
||||
providers=providers,
|
||||
provider_options=options,
|
||||
)
|
||||
),
|
||||
model_type=model_type,
|
||||
)
|
||||
|
||||
@ -633,7 +633,7 @@ class EmbeddingMaintainer(threading.Thread):
|
||||
|
||||
camera, frame_name, _, _, motion_boxes, _ = data
|
||||
|
||||
if not camera or len(motion_boxes) == 0:
|
||||
if not camera or len(motion_boxes) == 0 or camera not in self.config.cameras:
|
||||
return
|
||||
|
||||
camera_config = self.config.cameras[camera]
|
||||
|
||||
@ -101,6 +101,7 @@ When forming your description:
|
||||
Your response MUST be a flat JSON object with:
|
||||
- `title` (string): A concise, direct title that describes the primary action or event in the sequence, not just what you literally see. Use spatial context when available to make titles more meaningful. When multiple objects/actions are present, prioritize whichever is most prominent or occurs first. Use names from "Objects in Scene" based on what you visually observe. If you see both a name and an unidentified object of the same type but visually observe only one person/object, use ONLY the name. Examples: "Joe walking dog", "Person taking out trash", "Vehicle arriving in driveway", "Joe accessing vehicle", "Person leaving porch for driveway".
|
||||
- `scene` (string): A narrative description of what happens across the sequence from start to finish, in chronological order. Start by describing how the sequence begins, then describe the progression of events. **Describe all significant movements and actions in the order they occur.** For example, if a vehicle arrives and then a person exits, describe both actions sequentially. **Only describe actions you can actually observe happening in the frames provided.** Do not infer or assume actions that aren't visible (e.g., if you see someone walking but never see them sit, don't say they sat down). Include setting, detected objects, and their observable actions. Avoid speculation or filling in assumed behaviors. Your description should align with and support the threat level you assign.
|
||||
- `shortSummary` (string): A brief 2-sentence summary of the scene, suitable for notifications. Should capture the key activity and context without full detail. This should be a condensed version of the scene description above.
|
||||
- `confidence` (float): 0-1 confidence in your analysis. Higher confidence when objects/actions are clearly visible and context is unambiguous. Lower confidence when the sequence is unclear, objects are partially obscured, or context is ambiguous.
|
||||
- `potential_threat_level` (integer): 0, 1, or 2 as defined in "Normal Activity Patterns for This Property" above. Your threat level must be consistent with your scene description and the guidance above.
|
||||
{get_concern_prompt()}
|
||||
@ -192,6 +193,8 @@ Input format: Each event is a JSON object with:
|
||||
- "title", "scene", "confidence", "potential_threat_level" (0-2), "other_concerns", "camera", "time", "start_time", "end_time"
|
||||
- "context": array of related events from other cameras that occurred during overlapping time periods
|
||||
|
||||
**Note: Use the "scene" field for event descriptions in the report. Ignore any "shortSummary" field if present.**
|
||||
|
||||
Report Structure - Use this EXACT format:
|
||||
|
||||
# Security Summary - {time_range}
|
||||
|
||||
@ -139,9 +139,11 @@ class OutputProcess(FrigateProcess):
|
||||
if CameraConfigUpdateEnum.add in updates:
|
||||
for camera in updates["add"]:
|
||||
jsmpeg_cameras[camera] = JsmpegCamera(
|
||||
cam_config, self.stop_event, websocket_server
|
||||
self.config.cameras[camera], self.stop_event, websocket_server
|
||||
)
|
||||
preview_recorders[camera] = PreviewRecorder(
|
||||
self.config.cameras[camera]
|
||||
)
|
||||
preview_recorders[camera] = PreviewRecorder(cam_config)
|
||||
preview_write_times[camera] = 0
|
||||
|
||||
if (
|
||||
|
||||
@ -1604,7 +1604,8 @@ function ObjectDetailsTab({
|
||||
|
||||
{config?.cameras[search?.camera].audio_transcription.enabled &&
|
||||
search?.label == "speech" &&
|
||||
search?.end_time && (
|
||||
search?.end_time &&
|
||||
search?.has_clip && (
|
||||
<Tooltip>
|
||||
<TooltipTrigger asChild>
|
||||
<button
|
||||
|
||||
@ -356,7 +356,17 @@ export default function HlsVideoPlayer({
|
||||
loadedMetadata &&
|
||||
videoDimensions.width > 0 &&
|
||||
videoDimensions.height > 0 && (
|
||||
<div className="absolute z-50 size-full">
|
||||
<div
|
||||
className={cn(
|
||||
"absolute inset-0 z-50",
|
||||
isDesktop
|
||||
? "size-full"
|
||||
: "mx-auto flex items-center justify-center portrait:max-h-[50dvh]",
|
||||
)}
|
||||
style={{
|
||||
aspectRatio: `${videoDimensions.width} / ${videoDimensions.height}`,
|
||||
}}
|
||||
>
|
||||
<ObjectTrackOverlay
|
||||
key={`overlay-${currentTime}`}
|
||||
camera={camera}
|
||||
|
||||
@ -31,7 +31,8 @@ type WizardState = {
|
||||
type WizardAction =
|
||||
| { type: "UPDATE_DATA"; payload: Partial<WizardFormData> }
|
||||
| { type: "UPDATE_AND_NEXT"; payload: Partial<WizardFormData> }
|
||||
| { type: "RESET_NAVIGATE" };
|
||||
| { type: "RESET_NAVIGATE" }
|
||||
| { type: "RESET_ALL" };
|
||||
|
||||
const wizardReducer = (
|
||||
state: WizardState,
|
||||
@ -50,6 +51,11 @@ const wizardReducer = (
|
||||
};
|
||||
case "RESET_NAVIGATE":
|
||||
return { ...state, shouldNavigateNext: false };
|
||||
case "RESET_ALL":
|
||||
return {
|
||||
wizardData: { streams: [] },
|
||||
shouldNavigateNext: false,
|
||||
};
|
||||
default:
|
||||
return state;
|
||||
}
|
||||
@ -84,13 +90,13 @@ export default function CameraWizardDialog({
|
||||
useEffect(() => {
|
||||
if (open) {
|
||||
setCurrentStep(0);
|
||||
dispatch({ type: "UPDATE_DATA", payload: { streams: [] } });
|
||||
dispatch({ type: "RESET_ALL" });
|
||||
}
|
||||
}, [open]);
|
||||
|
||||
const handleClose = useCallback(() => {
|
||||
setCurrentStep(0);
|
||||
dispatch({ type: "UPDATE_DATA", payload: { streams: [] } });
|
||||
dispatch({ type: "RESET_ALL" });
|
||||
onClose();
|
||||
}, [onClose]);
|
||||
|
||||
|
||||
@ -409,7 +409,14 @@ function ReviewGroup({
|
||||
<div className="flex flex-col gap-0.5">
|
||||
{review.data.metadata?.title && (
|
||||
<div className="mb-1 flex min-w-0 items-center gap-1 text-sm text-primary-variant">
|
||||
<MdAutoAwesome className="size-3 shrink-0" />
|
||||
<Tooltip>
|
||||
<TooltipTrigger>
|
||||
<MdAutoAwesome className="size-3 shrink-0" />
|
||||
</TooltipTrigger>
|
||||
<TooltipContent>
|
||||
{review.data.metadata.title}
|
||||
</TooltipContent>
|
||||
</Tooltip>
|
||||
<span className="truncate">{review.data.metadata.title}</span>
|
||||
</div>
|
||||
)}
|
||||
|
||||
@ -219,12 +219,12 @@ function Exports() {
|
||||
/>
|
||||
))}
|
||||
</div>
|
||||
) : (
|
||||
) : exports !== undefined ? (
|
||||
<div className="absolute left-1/2 top-1/2 flex -translate-x-1/2 -translate-y-1/2 flex-col items-center justify-center text-center">
|
||||
<LuFolderX className="size-16" />
|
||||
{t("noExports")}
|
||||
</div>
|
||||
)}
|
||||
) : null}
|
||||
</div>
|
||||
</div>
|
||||
);
|
||||
|
||||
@ -22,6 +22,7 @@ import { SearchTab } from "@/components/overlay/detail/SearchDetailDialog";
|
||||
import { FrigateConfig } from "@/types/frigateConfig";
|
||||
import { useTranslation } from "react-i18next";
|
||||
import { getTranslatedLabel } from "@/utils/i18n";
|
||||
import { LuSearchX } from "react-icons/lu";
|
||||
|
||||
type ExploreViewProps = {
|
||||
setSearchDetail: (search: SearchResult | undefined) => void;
|
||||
@ -86,6 +87,15 @@ export default function ExploreView({
|
||||
);
|
||||
}
|
||||
|
||||
if (eventsByLabel && Object.keys(eventsByLabel).length == 0 && !isLoading) {
|
||||
return (
|
||||
<div className="absolute left-1/2 top-1/2 flex -translate-x-1/2 -translate-y-1/2 flex-col items-center justify-center text-center">
|
||||
<LuSearchX className="size-16" />
|
||||
{t("noTrackedObjects")}
|
||||
</div>
|
||||
);
|
||||
}
|
||||
|
||||
return (
|
||||
<div className="mx-2 space-y-4">
|
||||
{Object.entries(eventsByLabel).map(([label, filteredEvents]) => (
|
||||
|
||||
Loading…
Reference in New Issue
Block a user