diff --git a/frigate/api/media.py b/frigate/api/media.py index 489c008b4..69f0b8372 100644 --- a/frigate/api/media.py +++ b/frigate/api/media.py @@ -1368,12 +1368,17 @@ def preview_gif( file_start = f"preview_{camera_name}-" start_file = f"{file_start}{start_ts}.{PREVIEW_FRAME_TYPE}" end_file = f"{file_start}{end_ts}.{PREVIEW_FRAME_TYPE}" + + camera_files = [ + entry.name + for entry in os.scandir(preview_dir) + if entry.name.startswith(file_start) + ] + camera_files.sort() + selected_previews = [] - for file in sorted(os.listdir(preview_dir)): - if not file.startswith(file_start): - continue - + for file in camera_files: if file < start_file: continue @@ -1550,12 +1555,17 @@ def preview_mp4( file_start = f"preview_{camera_name}-" start_file = f"{file_start}{start_ts}.{PREVIEW_FRAME_TYPE}" end_file = f"{file_start}{end_ts}.{PREVIEW_FRAME_TYPE}" + + camera_files = [ + entry.name + for entry in os.scandir(preview_dir) + if entry.name.startswith(file_start) + ] + camera_files.sort() + selected_previews = [] - for file in sorted(os.listdir(preview_dir)): - if not file.startswith(file_start): - continue - + for file in camera_files: if file < start_file: continue diff --git a/frigate/api/preview.py b/frigate/api/preview.py index a5e30764d..a307b5abc 100644 --- a/frigate/api/preview.py +++ b/frigate/api/preview.py @@ -148,12 +148,17 @@ def get_preview_frames_from_cache(camera_name: str, start_ts: float, end_ts: flo file_start = f"preview_{camera_name}-" start_file = f"{file_start}{start_ts}.{PREVIEW_FRAME_TYPE}" end_file = f"{file_start}{end_ts}.{PREVIEW_FRAME_TYPE}" + + camera_files = [ + entry.name + for entry in os.scandir(preview_dir) + if entry.name.startswith(file_start) + ] + camera_files.sort() + selected_previews = [] - for file in sorted(os.listdir(preview_dir)): - if not file.startswith(file_start): - continue - + for file in camera_files: if file < start_file: continue diff --git a/frigate/const.py b/frigate/const.py index 51e06e4ad..07537ea5f 100644 --- a/frigate/const.py +++ b/frigate/const.py @@ -15,7 +15,7 @@ TRIGGER_DIR = f"{CLIPS_DIR}/triggers" BIRDSEYE_PIPE = "/tmp/cache/birdseye" CACHE_DIR = "/tmp/cache" REPLAY_CAMERA_PREFIX = "_replay_" -REPLAY_DIR = os.path.join(CACHE_DIR, "replay") +REPLAY_DIR = os.path.join(CLIPS_DIR, "replay") PLUS_ENV_VAR = "PLUS_API_KEY" PLUS_API_HOST = "https://api.frigate.video" diff --git a/frigate/data_processing/post/review_descriptions.py b/frigate/data_processing/post/review_descriptions.py index c5e51d612..3740ac25f 100644 --- a/frigate/data_processing/post/review_descriptions.py +++ b/frigate/data_processing/post/review_descriptions.py @@ -366,12 +366,17 @@ class ReviewDescriptionProcessor(PostProcessorApi): file_start = f"preview_{camera}-" start_file = f"{file_start}{start_time}.webp" end_file = f"{file_start}{end_time}.webp" + + camera_files = [ + entry.name + for entry in os.scandir(preview_dir) + if entry.name.startswith(file_start) + ] + camera_files.sort() + all_frames: list[str] = [] - for file in sorted(os.listdir(preview_dir)): - if not file.startswith(file_start): - continue - + for file in camera_files: if file < start_file: if len(all_frames): all_frames[0] = os.path.join(preview_dir, file) diff --git a/frigate/genai/__init__.py b/frigate/genai/__init__.py index e26b50757..3bc98100c 100644 --- a/frigate/genai/__init__.py +++ b/frigate/genai/__init__.py @@ -153,9 +153,6 @@ Each line represents a detection state, not necessarily unique individuals. The if "other_concerns" in schema.get("required", []): schema["required"].remove("other_concerns") - # OpenAI strict mode requires additionalProperties: false on all objects - schema["additionalProperties"] = False - response_format = { "type": "json_schema", "json_schema": { diff --git a/frigate/genai/gemini.py b/frigate/genai/gemini.py index c4befbe90..eec22a991 100644 --- a/frigate/genai/gemini.py +++ b/frigate/genai/gemini.py @@ -136,11 +136,29 @@ class GeminiClient(GenAIClient): ) ) elif role == "assistant": - gemini_messages.append( - types.Content( - role="model", parts=[types.Part.from_text(text=content)] - ) - ) + parts: list[types.Part] = [] + if content: + parts.append(types.Part.from_text(text=content)) + for tc in msg.get("tool_calls") or []: + func = tc.get("function") or {} + tc_name = func.get("name") or "" + tc_args: Any = func.get("arguments") + if isinstance(tc_args, str): + try: + tc_args = json.loads(tc_args) + except (json.JSONDecodeError, TypeError): + tc_args = {} + if not isinstance(tc_args, dict): + tc_args = {} + if tc_name: + parts.append( + types.Part.from_function_call( + name=tc_name, args=tc_args + ) + ) + if not parts: + parts.append(types.Part.from_text(text=" ")) + gemini_messages.append(types.Content(role="model", parts=parts)) elif role == "tool": # Handle tool response response_payload = ( @@ -151,7 +169,9 @@ class GeminiClient(GenAIClient): role="function", parts=[ types.Part.from_function_response( - name=msg.get("name", ""), + name=msg.get("name") + or msg.get("tool_call_id") + or "", response=response_payload, ) ], @@ -345,11 +365,29 @@ class GeminiClient(GenAIClient): ) ) elif role == "assistant": - gemini_messages.append( - types.Content( - role="model", parts=[types.Part.from_text(text=content)] - ) - ) + parts: list[types.Part] = [] + if content: + parts.append(types.Part.from_text(text=content)) + for tc in msg.get("tool_calls") or []: + func = tc.get("function") or {} + tc_name = func.get("name") or "" + tc_args: Any = func.get("arguments") + if isinstance(tc_args, str): + try: + tc_args = json.loads(tc_args) + except (json.JSONDecodeError, TypeError): + tc_args = {} + if not isinstance(tc_args, dict): + tc_args = {} + if tc_name: + parts.append( + types.Part.from_function_call( + name=tc_name, args=tc_args + ) + ) + if not parts: + parts.append(types.Part.from_text(text=" ")) + gemini_messages.append(types.Content(role="model", parts=parts)) elif role == "tool": # Handle tool response response_payload = ( @@ -360,7 +398,9 @@ class GeminiClient(GenAIClient): role="function", parts=[ types.Part.from_function_response( - name=msg.get("name", ""), + name=msg.get("name") + or msg.get("tool_call_id") + or "", response=response_payload, ) ], diff --git a/frigate/genai/openai.py b/frigate/genai/openai.py index af94859de..432641332 100644 --- a/frigate/genai/openai.py +++ b/frigate/genai/openai.py @@ -73,8 +73,17 @@ class OpenAIClient(GenAIClient): **self.genai_config.runtime_options, } if response_format: + # OpenAI strict mode requires additionalProperties: false on the schema + if response_format.get("type") == "json_schema" and response_format.get( + "json_schema", {} + ).get("strict"): + schema = response_format.get("json_schema", {}).get("schema") + if isinstance(schema, dict): + schema["additionalProperties"] = False request_params["response_format"] = response_format + result = self.provider.chat.completions.create(**request_params) + if ( result is not None and hasattr(result, "choices") diff --git a/web/src/components/overlay/MobileReviewSettingsDrawer.tsx b/web/src/components/overlay/MobileReviewSettingsDrawer.tsx index 63d391162..c409e5cfa 100644 --- a/web/src/components/overlay/MobileReviewSettingsDrawer.tsx +++ b/web/src/components/overlay/MobileReviewSettingsDrawer.tsx @@ -391,10 +391,8 @@ export default function MobileReviewSettingsDrawer({ className="flex w-full items-center justify-center gap-2" aria-label={t("title", { ns: "views/replay" })} onClick={() => { - const now = new Date(latestTime * 1000); - now.setHours(now.getHours() - 1); setDebugReplayRange({ - after: now.getTime() / 1000, + after: latestTime - 60, before: latestTime, }); setSelectedReplayOption("1"); @@ -541,11 +539,9 @@ export default function MobileReviewSettingsDrawer({ return; } - const hours = parseInt(option); + const minutes = parseInt(option, 10); const end = latestTime; - const now = new Date(end * 1000); - now.setHours(now.getHours() - hours); - setDebugReplayRange({ after: now.getTime() / 1000, before: end }); + setDebugReplayRange({ after: end - minutes * 60, before: end }); }; content = ( diff --git a/web/src/components/player/HlsVideoPlayer.tsx b/web/src/components/player/HlsVideoPlayer.tsx index 1c8b099e2..9433e3975 100644 --- a/web/src/components/player/HlsVideoPlayer.tsx +++ b/web/src/components/player/HlsVideoPlayer.tsx @@ -396,7 +396,6 @@ export default function HlsVideoPlayer({ }} > { - const now = new Date(timeRange.before * 1000); - now.setHours(now.getHours() - 1); setDebugReplayRange({ - after: now.getTime() / 1000, + after: timeRange.before - 60, before: timeRange.before, }); setDebugReplayMode("select");