mirror of
https://github.com/blakeblackshear/frigate.git
synced 2026-06-29 08:31:27 +03:00
Compare commits
19 Commits
b1de5e2290
...
03f4f76b72
| Author | SHA1 | Date | |
|---|---|---|---|
|
|
03f4f76b72 | ||
|
|
6ffb9f2c9e | ||
|
|
b470258d95 | ||
|
|
fac11286f5 | ||
|
|
50f7f11f0b | ||
|
|
f96127c264 | ||
|
|
2ae415be6b | ||
|
|
59faa4e088 | ||
|
|
3df7c22f4d | ||
|
|
f4cbbe806d | ||
|
|
cfb1420660 | ||
|
|
161f56b5d4 | ||
|
|
5ddf8bc1b0 | ||
|
|
dc2c48f6d7 | ||
|
|
6e5d55ff64 | ||
|
|
d439b09f90 | ||
|
|
3f7768a48f | ||
|
|
7881bea60f | ||
|
|
b0b00fe1d0 |
40
.github/copilot-instructions.md
vendored
40
.github/copilot-instructions.md
vendored
@ -162,7 +162,6 @@ When reviewing code, do NOT comment on:
|
||||
- **Linting**: ESLint (see `web/.eslintrc.cjs`)
|
||||
- **Formatting**: Prettier with Tailwind CSS plugin
|
||||
- **Type Safety**: TypeScript strict mode enabled
|
||||
- **Testing**: Vitest for unit tests
|
||||
|
||||
### Component Patterns
|
||||
|
||||
@ -233,6 +232,9 @@ ruff format frigate/
|
||||
|
||||
# Run linter
|
||||
ruff check frigate/
|
||||
|
||||
# Type check
|
||||
python3 -u -m mypy --config-file frigate/mypy.ini frigate
|
||||
```
|
||||
|
||||
### Frontend (from web/ directory)
|
||||
@ -252,6 +254,38 @@ npm run lint:fix
|
||||
|
||||
# Format code
|
||||
npm run prettier:write
|
||||
|
||||
# E2E: first-time setup
|
||||
npm install
|
||||
npx playwright install chromium
|
||||
|
||||
# E2E: build the app and run all tests
|
||||
npm run e2e:build && npm run e2e
|
||||
|
||||
# E2E: interactive UI for debugging
|
||||
npm run e2e:ui
|
||||
|
||||
# E2E: run a specific spec
|
||||
npx playwright test --config e2e/playwright.config.ts e2e/specs/live.spec.ts
|
||||
|
||||
# E2E: filter by name, or run only desktop/mobile
|
||||
npx playwright test --config e2e/playwright.config.ts --grep="severity tab"
|
||||
npx playwright test --config e2e/playwright.config.ts --project=desktop
|
||||
|
||||
# E2E: regenerate mock data after backend model changes (from repo root)
|
||||
PYTHONPATH=. python3 web/e2e/fixtures/mock-data/generate-mock-data.py
|
||||
|
||||
# Regenerate config translations from Pydantic models — outputs to
|
||||
# web/public/locales/en/config/{global,cameras}.json. NEVER edit those
|
||||
# JSON files by hand; change the Pydantic field title/description and
|
||||
# re-run this script. (from repo root)
|
||||
python3 generate_config_translations.py
|
||||
|
||||
# Extract i18n keys from source into the locale files after adding
|
||||
# new t() calls. Use the :ci variant to verify the locale files are
|
||||
# in sync with source (fails if extraction would change anything).
|
||||
npm run i18n:extract
|
||||
npm run i18n:extract:ci
|
||||
```
|
||||
|
||||
### Docker Development
|
||||
@ -371,6 +405,10 @@ except ValueError:
|
||||
)
|
||||
```
|
||||
|
||||
## WebSocket Broadcasts
|
||||
|
||||
Outbound WebSocket broadcasts go through a per-recipient classifier in `frigate/comms/ws.py` that enforces camera-level access. **The classifier is fail-closed: any topic it doesn't recognize is dropped for every client.** New outbound topics must be classified there or they'll silently disappear.
|
||||
|
||||
## Project-Specific Conventions
|
||||
|
||||
### Configuration Files
|
||||
|
||||
@ -774,6 +774,8 @@ def config_set(request: Request, body: AppConfigSetBody):
|
||||
|
||||
if request.app.dispatcher is not None:
|
||||
request.app.dispatcher.config = config
|
||||
for comm in request.app.dispatcher.comms:
|
||||
comm.config = config
|
||||
|
||||
if body.update_topic:
|
||||
if body.update_topic.startswith("config/cameras/"):
|
||||
|
||||
@ -35,9 +35,13 @@ from frigate.api.defs.response.chat_response import (
|
||||
ToolCall,
|
||||
)
|
||||
from frigate.api.defs.tags import Tags
|
||||
from frigate.api.event import events
|
||||
from frigate.api.event import _build_attribute_filter_clause, events
|
||||
from frigate.config import FrigateConfig
|
||||
from frigate.config.ui import UnitSystemEnum
|
||||
from frigate.genai.prompts import (
|
||||
build_chat_system_prompt,
|
||||
get_attribute_classifications,
|
||||
get_tool_definitions,
|
||||
)
|
||||
from frigate.genai.utils import build_assistant_message_for_conversation
|
||||
from frigate.jobs.vlm_watch import (
|
||||
get_vlm_watch_job,
|
||||
@ -68,390 +72,6 @@ class VLMMonitorRequest(BaseModel):
|
||||
zones: List[str] = []
|
||||
|
||||
|
||||
def get_tool_definitions(
|
||||
semantic_search_enabled: bool = False,
|
||||
) -> List[Dict[str, Any]]:
|
||||
"""
|
||||
Get OpenAI-compatible tool definitions for Frigate.
|
||||
|
||||
Returns a list of tool definitions that can be used with OpenAI-compatible
|
||||
function calling APIs. When semantic search is enabled, the search_objects
|
||||
tool exposes an additional `semantic_query` parameter for descriptive
|
||||
queries (e.g. "person riding a lawn mower") and find_similar_objects is
|
||||
included.
|
||||
"""
|
||||
search_objects_properties: Dict[str, Any] = {
|
||||
"camera": {
|
||||
"type": "string",
|
||||
"description": "Camera name to filter by (optional).",
|
||||
},
|
||||
"label": {
|
||||
"type": "string",
|
||||
"description": (
|
||||
"Generic object class to filter by — one of the tracked detector "
|
||||
"labels such as 'person', 'package', 'car', 'dog', 'bird'. Use "
|
||||
"this for broad queries like 'show me all cars today'. Combine "
|
||||
"with semantic_query when the user also describes appearance or "
|
||||
"behavior (e.g. label='person', semantic_query='riding a lawn "
|
||||
"mower')."
|
||||
),
|
||||
},
|
||||
"sub_label": {
|
||||
"type": "string",
|
||||
"description": (
|
||||
"Filter by a DISCRETE NAMED entity recognized in the detection. "
|
||||
"Use this for: a known person's name ('John'), a delivery "
|
||||
"company ('Amazon', 'UPS'), a recognized animal species or "
|
||||
"breed ('blue jay', 'cardinal', 'golden retriever'), or a "
|
||||
"license plate string. When filtering by a specific name, set "
|
||||
"only sub_label and leave label unset. Do NOT use sub_label "
|
||||
"for descriptions of appearance, clothing, or actions — those "
|
||||
"belong in semantic_query."
|
||||
),
|
||||
},
|
||||
"after": {
|
||||
"type": "string",
|
||||
"description": "Start time in ISO 8601 format (e.g., '2024-01-01T00:00:00Z').",
|
||||
},
|
||||
"before": {
|
||||
"type": "string",
|
||||
"description": "End time in ISO 8601 format (e.g., '2024-01-01T23:59:59Z').",
|
||||
},
|
||||
"zones": {
|
||||
"type": "array",
|
||||
"items": {"type": "string"},
|
||||
"description": "List of zone names to filter by.",
|
||||
},
|
||||
"limit": {
|
||||
"type": "integer",
|
||||
"description": "Maximum number of objects to return (default: 25).",
|
||||
"default": 25,
|
||||
},
|
||||
}
|
||||
|
||||
if semantic_search_enabled:
|
||||
search_objects_properties["semantic_query"] = {
|
||||
"type": "string",
|
||||
"description": (
|
||||
"Optional natural-language description of a PHYSICAL "
|
||||
"CHARACTERISTIC, APPEARANCE, or ACTIVITY the user mentioned, "
|
||||
"used to semantically narrow results. Only set this when the "
|
||||
"user describes something beyond what label and sub_label can "
|
||||
"express on their own.\n"
|
||||
"USE for descriptive phrases like: 'riding a lawn mower', "
|
||||
"'wearing a red jacket', 'carrying a package', 'walking a "
|
||||
"dog', 'on a bicycle', 'holding an umbrella'.\n"
|
||||
"DO NOT USE for:\n"
|
||||
"- specific named people, pets, or delivery companies → use sub_label\n"
|
||||
"- animal species or breed names like 'blue jay', 'cardinal', "
|
||||
"'golden retriever' → use sub_label\n"
|
||||
"- license plate strings → use sub_label\n"
|
||||
"- generic object queries like 'all cars today' or 'every "
|
||||
"person' → use label alone with no semantic_query\n"
|
||||
"When set, combine with label/time/camera/zone filters as "
|
||||
"usual (e.g. label='person', semantic_query='riding a lawn "
|
||||
"mower', after='2024-05-01T00:00:00Z')."
|
||||
),
|
||||
}
|
||||
|
||||
search_objects_description = (
|
||||
"Search the historical record of detected objects in Frigate. "
|
||||
"Use this ONLY for questions about the PAST — e.g. 'did anyone come by today?', "
|
||||
"'when was the last car?', 'show me detections from yesterday'. "
|
||||
"Do NOT use this for monitoring or alerting requests about future events — "
|
||||
"use start_camera_watch instead for those. "
|
||||
"An 'object' in Frigate represents a tracked detection (e.g., a person, package, car).\n\n"
|
||||
"Choose filters based on what the user is asking for:\n"
|
||||
"- Generic class query ('show me all cars today'): set `label` only.\n"
|
||||
"- Specific NAMED entity (known person, delivery company, animal "
|
||||
"species/breed like 'blue jay' or 'golden retriever', license "
|
||||
"plate): set `sub_label` only and leave `label` unset.\n"
|
||||
)
|
||||
if semantic_search_enabled:
|
||||
search_objects_description += (
|
||||
"- Physical CHARACTERISTIC, APPEARANCE, or ACTIVITY that is not a "
|
||||
"discrete name ('person riding a lawn mower', 'someone in a red "
|
||||
"jacket', 'person carrying a package'): set `semantic_query` with "
|
||||
"the descriptive phrase, optionally alongside `label` for the "
|
||||
"object class. Do NOT put descriptive phrases in sub_label."
|
||||
)
|
||||
|
||||
return [
|
||||
{
|
||||
"type": "function",
|
||||
"function": {
|
||||
"name": "search_objects",
|
||||
"description": search_objects_description,
|
||||
"parameters": {
|
||||
"type": "object",
|
||||
"properties": search_objects_properties,
|
||||
},
|
||||
"required": [],
|
||||
},
|
||||
},
|
||||
{
|
||||
"type": "function",
|
||||
"function": {
|
||||
"name": "find_similar_objects",
|
||||
"description": (
|
||||
"Find tracked objects that are visually and semantically similar "
|
||||
"to a specific past event. Use this when the user references a "
|
||||
"particular object they have seen and wants to find other "
|
||||
"sightings of the same or similar one ('that green car', 'the "
|
||||
"person in the red jacket', 'the package that was delivered'). "
|
||||
"Prefer this over search_objects whenever the user's intent is "
|
||||
"'find more like this specific one.' Use search_objects first "
|
||||
"only if you need to locate the anchor event. Requires semantic "
|
||||
"search to be enabled."
|
||||
),
|
||||
"parameters": {
|
||||
"type": "object",
|
||||
"properties": {
|
||||
"event_id": {
|
||||
"type": "string",
|
||||
"description": "The id of the anchor event to find similar objects to.",
|
||||
},
|
||||
"after": {
|
||||
"type": "string",
|
||||
"description": "Start time in ISO 8601 format (e.g., '2024-01-01T00:00:00Z').",
|
||||
},
|
||||
"before": {
|
||||
"type": "string",
|
||||
"description": "End time in ISO 8601 format (e.g., '2024-01-01T23:59:59Z').",
|
||||
},
|
||||
"cameras": {
|
||||
"type": "array",
|
||||
"items": {"type": "string"},
|
||||
"description": "Optional list of cameras to restrict to. Defaults to all.",
|
||||
},
|
||||
"labels": {
|
||||
"type": "array",
|
||||
"items": {"type": "string"},
|
||||
"description": "Optional list of labels to restrict to. Defaults to the anchor event's label.",
|
||||
},
|
||||
"sub_labels": {
|
||||
"type": "array",
|
||||
"items": {"type": "string"},
|
||||
"description": "Optional list of sub_labels (names) to restrict to.",
|
||||
},
|
||||
"zones": {
|
||||
"type": "array",
|
||||
"items": {"type": "string"},
|
||||
"description": "Optional list of zones. An event matches if any of its zones overlap.",
|
||||
},
|
||||
"similarity_mode": {
|
||||
"type": "string",
|
||||
"enum": ["visual", "semantic", "fused"],
|
||||
"description": "Which similarity signal(s) to use. 'fused' (default) combines visual and semantic.",
|
||||
"default": "fused",
|
||||
},
|
||||
"min_score": {
|
||||
"type": "number",
|
||||
"description": "Drop matches with a similarity score below this threshold (0.0-1.0).",
|
||||
},
|
||||
"limit": {
|
||||
"type": "integer",
|
||||
"description": "Maximum number of matches to return (default: 10).",
|
||||
"default": 10,
|
||||
},
|
||||
},
|
||||
"required": ["event_id"],
|
||||
},
|
||||
},
|
||||
},
|
||||
{
|
||||
"type": "function",
|
||||
"function": {
|
||||
"name": "set_camera_state",
|
||||
"description": (
|
||||
"Change a camera's feature state (e.g., turn detection on/off, enable/disable recordings). "
|
||||
"Use camera='*' to apply to all cameras at once. "
|
||||
"Only call this tool when the user explicitly asks to change a camera setting. "
|
||||
"Requires admin privileges."
|
||||
),
|
||||
"parameters": {
|
||||
"type": "object",
|
||||
"properties": {
|
||||
"camera": {
|
||||
"type": "string",
|
||||
"description": "Camera name to target, or '*' to target all cameras.",
|
||||
},
|
||||
"feature": {
|
||||
"type": "string",
|
||||
"enum": [
|
||||
"detect",
|
||||
"record",
|
||||
"snapshots",
|
||||
"audio",
|
||||
"motion",
|
||||
"enabled",
|
||||
"birdseye",
|
||||
"birdseye_mode",
|
||||
"improve_contrast",
|
||||
"ptz_autotracker",
|
||||
"motion_contour_area",
|
||||
"motion_threshold",
|
||||
"notifications",
|
||||
"audio_transcription",
|
||||
"review_alerts",
|
||||
"review_detections",
|
||||
"object_descriptions",
|
||||
"review_descriptions",
|
||||
"profile",
|
||||
],
|
||||
"description": (
|
||||
"The feature to change. Most features accept ON or OFF. "
|
||||
"birdseye_mode accepts CONTINUOUS, MOTION, or OBJECTS. "
|
||||
"motion_contour_area and motion_threshold accept a number. "
|
||||
"profile accepts a profile name or 'none' to deactivate (requires camera='*')."
|
||||
),
|
||||
},
|
||||
"value": {
|
||||
"type": "string",
|
||||
"description": "The value to set. ON or OFF for toggles, a number for thresholds, a profile name or 'none' for profile.",
|
||||
},
|
||||
},
|
||||
"required": ["camera", "feature", "value"],
|
||||
},
|
||||
},
|
||||
},
|
||||
{
|
||||
"type": "function",
|
||||
"function": {
|
||||
"name": "get_live_context",
|
||||
"description": (
|
||||
"Get the current live image and detection information for a camera: objects being tracked, "
|
||||
"zones, timestamps. Use this to understand what is visible in the live view. "
|
||||
"Call this when answering questions about what is happening right now on a specific camera."
|
||||
),
|
||||
"parameters": {
|
||||
"type": "object",
|
||||
"properties": {
|
||||
"camera": {
|
||||
"type": "string",
|
||||
"description": "Camera name to get live context for.",
|
||||
},
|
||||
},
|
||||
"required": ["camera"],
|
||||
},
|
||||
},
|
||||
},
|
||||
{
|
||||
"type": "function",
|
||||
"function": {
|
||||
"name": "start_camera_watch",
|
||||
"description": (
|
||||
"Start a continuous VLM watch job that monitors a camera and sends a notification "
|
||||
"when a specified condition is met. Use this when the user wants to be alerted about "
|
||||
"a future event, e.g. 'tell me when guests arrive' or 'notify me when the package is picked up'. "
|
||||
"Only one watch job can run at a time. Returns a job ID."
|
||||
),
|
||||
"parameters": {
|
||||
"type": "object",
|
||||
"properties": {
|
||||
"camera": {
|
||||
"type": "string",
|
||||
"description": "Camera ID to monitor.",
|
||||
},
|
||||
"condition": {
|
||||
"type": "string",
|
||||
"description": (
|
||||
"Natural-language description of the condition to watch for, "
|
||||
"e.g. 'a person arrives at the front door'."
|
||||
),
|
||||
},
|
||||
"max_duration_minutes": {
|
||||
"type": "integer",
|
||||
"description": "Maximum time to watch before giving up (minutes, default 60).",
|
||||
"default": 60,
|
||||
},
|
||||
"labels": {
|
||||
"type": "array",
|
||||
"items": {"type": "string"},
|
||||
"description": "Object labels that should trigger a VLM check (e.g. ['person', 'car']). If omitted, any detection on the camera triggers a check.",
|
||||
},
|
||||
"zones": {
|
||||
"type": "array",
|
||||
"items": {"type": "string"},
|
||||
"description": "Zone names to filter by. If specified, only detections in these zones trigger a VLM check.",
|
||||
},
|
||||
},
|
||||
"required": ["camera", "condition"],
|
||||
},
|
||||
},
|
||||
},
|
||||
{
|
||||
"type": "function",
|
||||
"function": {
|
||||
"name": "stop_camera_watch",
|
||||
"description": (
|
||||
"Cancel the currently running VLM watch job. Use this when the user wants to "
|
||||
"stop a previously started watch, e.g. 'stop watching the front door'."
|
||||
),
|
||||
"parameters": {
|
||||
"type": "object",
|
||||
"properties": {},
|
||||
"required": [],
|
||||
},
|
||||
},
|
||||
},
|
||||
{
|
||||
"type": "function",
|
||||
"function": {
|
||||
"name": "get_profile_status",
|
||||
"description": (
|
||||
"Get the current profile status including the active profile and "
|
||||
"timestamps of when each profile was last activated. Use this to "
|
||||
"determine time periods for recap requests — e.g. when the user asks "
|
||||
"'what happened while I was away?', call this first to find the relevant "
|
||||
"time window based on profile activation history."
|
||||
),
|
||||
"parameters": {
|
||||
"type": "object",
|
||||
"properties": {},
|
||||
"required": [],
|
||||
},
|
||||
},
|
||||
},
|
||||
{
|
||||
"type": "function",
|
||||
"function": {
|
||||
"name": "get_recap",
|
||||
"description": (
|
||||
"Get a recap of all activity (alerts and detections) for a given time period. "
|
||||
"Use this after calling get_profile_status to retrieve what happened during "
|
||||
"a specific window — e.g. 'what happened while I was away?'. Returns a "
|
||||
"chronological list of activity with camera, objects, zones, and GenAI-generated "
|
||||
"descriptions when available. Summarize the results for the user."
|
||||
),
|
||||
"parameters": {
|
||||
"type": "object",
|
||||
"properties": {
|
||||
"after": {
|
||||
"type": "string",
|
||||
"description": "Start of the time period in ISO 8601 format (e.g. '2025-03-15T08:00:00').",
|
||||
},
|
||||
"before": {
|
||||
"type": "string",
|
||||
"description": "End of the time period in ISO 8601 format (e.g. '2025-03-15T17:00:00').",
|
||||
},
|
||||
"cameras": {
|
||||
"type": "string",
|
||||
"description": "Comma-separated camera IDs to include, or 'all' for all cameras. Default is 'all'.",
|
||||
},
|
||||
"severity": {
|
||||
"type": "string",
|
||||
"enum": ["alert", "detection"],
|
||||
"description": "Filter by severity level. Omit to include both alerts and detections.",
|
||||
},
|
||||
},
|
||||
"required": ["after", "before"],
|
||||
},
|
||||
},
|
||||
},
|
||||
]
|
||||
|
||||
|
||||
@router.get(
|
||||
"/chat/tools",
|
||||
dependencies=[Depends(allow_any_authenticated())],
|
||||
@ -460,10 +80,13 @@ def get_tool_definitions(
|
||||
)
|
||||
def get_tools(request: Request) -> JSONResponse:
|
||||
"""Get list of available tools for LLM function calling."""
|
||||
semantic_search_enabled = bool(
|
||||
getattr(request.app.frigate_config.semantic_search, "enabled", False)
|
||||
config = request.app.frigate_config
|
||||
semantic_search_enabled = bool(getattr(config.semantic_search, "enabled", False))
|
||||
attribute_classifications = get_attribute_classifications(config)
|
||||
tools = get_tool_definitions(
|
||||
semantic_search_enabled=semantic_search_enabled,
|
||||
attribute_classifications=attribute_classifications,
|
||||
)
|
||||
tools = get_tool_definitions(semantic_search_enabled=semantic_search_enabled)
|
||||
return JSONResponse(content={"tools": tools})
|
||||
|
||||
|
||||
@ -554,11 +177,14 @@ async def _execute_search_objects(
|
||||
elif zones is None:
|
||||
zones = "all"
|
||||
|
||||
attribute = arguments.get("attribute")
|
||||
|
||||
# Build query parameters compatible with EventsQueryParams
|
||||
query_params = EventsQueryParams(
|
||||
cameras=arguments.get("camera", "all"),
|
||||
labels=arguments.get("label", "all"),
|
||||
sub_labels=arguments.get("sub_label", "all"), # case-insensitive on the backend
|
||||
attributes=attribute if attribute else "all",
|
||||
zones=zones,
|
||||
zone=zones,
|
||||
after=after,
|
||||
@ -626,6 +252,7 @@ async def _execute_search_objects_semantic(
|
||||
|
||||
label = arguments.get("label")
|
||||
sub_label = arguments.get("sub_label")
|
||||
attribute = arguments.get("attribute")
|
||||
|
||||
zones = arguments.get("zones")
|
||||
if isinstance(zones, list) and zones:
|
||||
@ -668,6 +295,10 @@ async def _execute_search_objects_semantic(
|
||||
if sub_label:
|
||||
# case-insensitive match to mirror events() behavior
|
||||
clauses.append(fn.LOWER(Event.sub_label.cast("text")) == sub_label.lower())
|
||||
if attribute:
|
||||
attribute_clause = _build_attribute_filter_clause(attribute)
|
||||
if attribute_clause is not None:
|
||||
clauses.append(attribute_clause)
|
||||
if zones:
|
||||
zone_clauses = [Event.zones.cast("text") % f'*"{zone}"*' for zone in zones]
|
||||
clauses.append(reduce(operator.or_, zone_clauses))
|
||||
@ -1481,72 +1112,19 @@ async def chat_completion(
|
||||
|
||||
config = request.app.frigate_config
|
||||
semantic_search_enabled = bool(getattr(config.semantic_search, "enabled", False))
|
||||
tools = get_tool_definitions(semantic_search_enabled=semantic_search_enabled)
|
||||
attribute_classifications = get_attribute_classifications(config)
|
||||
tools = get_tool_definitions(
|
||||
semantic_search_enabled=semantic_search_enabled,
|
||||
attribute_classifications=attribute_classifications,
|
||||
)
|
||||
conversation = []
|
||||
|
||||
current_datetime = datetime.now()
|
||||
current_date_str = current_datetime.strftime("%Y-%m-%d")
|
||||
current_time_str = current_datetime.strftime("%I:%M:%S %p")
|
||||
|
||||
cameras_info = []
|
||||
has_speed_zone = False
|
||||
for camera_id in allowed_cameras:
|
||||
if camera_id not in config.cameras:
|
||||
continue
|
||||
camera_config = config.cameras[camera_id]
|
||||
friendly_name = (
|
||||
camera_config.friendly_name
|
||||
if camera_config.friendly_name
|
||||
else camera_id.replace("_", " ").title()
|
||||
)
|
||||
zone_names = list(camera_config.zones.keys())
|
||||
if not has_speed_zone:
|
||||
has_speed_zone = any(
|
||||
zone.distances for zone in camera_config.zones.values()
|
||||
)
|
||||
if zone_names:
|
||||
cameras_info.append(
|
||||
f" - {friendly_name} (ID: {camera_id}, zones: {', '.join(zone_names)})"
|
||||
)
|
||||
else:
|
||||
cameras_info.append(f" - {friendly_name} (ID: {camera_id})")
|
||||
|
||||
cameras_section = ""
|
||||
if cameras_info:
|
||||
cameras_section = (
|
||||
"\n\nAvailable cameras:\n"
|
||||
+ "\n".join(cameras_info)
|
||||
+ "\n\nWhen users refer to cameras by their friendly name (e.g., 'Back Deck Camera'), use the corresponding camera ID (e.g., 'back_deck_cam') in tool calls."
|
||||
)
|
||||
|
||||
speed_units_section = ""
|
||||
if has_speed_zone:
|
||||
speed_unit = (
|
||||
"mph" if config.ui.unit_system == UnitSystemEnum.imperial else "km/h"
|
||||
)
|
||||
speed_units_section = f"\n\nReport object speeds to the user in {speed_unit}."
|
||||
|
||||
semantic_search_section = ""
|
||||
if semantic_search_enabled:
|
||||
semantic_search_section = (
|
||||
"\n\nWhen routing a search_objects call, pick filters by the shape of the user's request:\n"
|
||||
"- Generic class ('show me all cars today'): set `label` only.\n"
|
||||
"- Specific named entity — a known person ('John'), delivery company ('Amazon'), animal species/breed ('blue jay', 'cardinal', 'golden retriever'), or license plate: set `sub_label` only and leave `label` unset.\n"
|
||||
"- Physical characteristic, appearance, or activity that is NOT a discrete name ('find me people riding a lawn mower', 'someone in a red jacket', 'a person carrying a package'): set `semantic_query` with the descriptive phrase, optionally combined with `label` for the object class. Never put descriptive phrases in `sub_label`."
|
||||
)
|
||||
|
||||
system_prompt = f"""You are a helpful assistant for Frigate, a security camera NVR system. You help users answer questions about their cameras, detected objects, and events.
|
||||
|
||||
Current server local date and time: {current_date_str} at {current_time_str}
|
||||
|
||||
Do not start your response with phrases like "I will check...", "Let me see...", or "Let me look...". Answer directly.
|
||||
|
||||
Always present times to the user in the server's local timezone. When tool results include start_time_local and end_time_local, use those exact strings when listing or describing detection times—do not convert or invent timestamps. Do not use UTC or ISO format with Z for the user-facing answer unless the tool result only provides Unix timestamps without local time fields.
|
||||
When users ask about "today", "yesterday", "this week", etc., use the current date above as reference.
|
||||
When searching for objects or events, use ISO 8601 format for dates (e.g., {current_date_str}T00:00:00Z for the start of today).
|
||||
Always be accurate with time calculations based on the current date provided.
|
||||
|
||||
When a user refers to a specific object they have seen or describe with identifying details ("that green car", "the person in the red jacket", "a package left today"), prefer the find_similar_objects tool over search_objects. Use search_objects first only to locate the anchor event, then pass its id to find_similar_objects. For generic queries like "show me all cars today", keep using search_objects. If a user message begins with [attached_event:<id>], treat that event id as the anchor for any similarity or "tell me more" request in the same message and call find_similar_objects with that id.{semantic_search_section}{cameras_section}{speed_units_section}"""
|
||||
system_prompt = build_chat_system_prompt(
|
||||
config=config,
|
||||
allowed_cameras=allowed_cameras,
|
||||
semantic_search_enabled=semantic_search_enabled,
|
||||
attribute_classifications=attribute_classifications,
|
||||
)
|
||||
|
||||
conversation.append(
|
||||
{
|
||||
@ -1607,6 +1185,13 @@ When a user refers to a specific object they have seen or describe with identify
|
||||
)
|
||||
+ b"\n"
|
||||
)
|
||||
elif kind == "reasoning_delta":
|
||||
yield (
|
||||
json.dumps({"type": "reasoning", "delta": value}).encode(
|
||||
"utf-8"
|
||||
)
|
||||
+ b"\n"
|
||||
)
|
||||
elif kind == "stats":
|
||||
yield (
|
||||
json.dumps({"type": "stats", **value}).encode("utf-8")
|
||||
@ -1707,6 +1292,7 @@ When a user refers to a specific object they have seen or describe with identify
|
||||
final_content = response.get("content") or ""
|
||||
|
||||
if body.stream:
|
||||
final_reasoning = response.get("reasoning")
|
||||
|
||||
async def stream_body() -> Any:
|
||||
if tool_calls:
|
||||
@ -1721,6 +1307,15 @@ When a user refers to a specific object they have seen or describe with identify
|
||||
).encode("utf-8")
|
||||
+ b"\n"
|
||||
)
|
||||
# Emit the full reasoning trace up front when the
|
||||
# underlying client did not stream it
|
||||
if final_reasoning:
|
||||
yield (
|
||||
json.dumps(
|
||||
{"type": "reasoning", "delta": final_reasoning}
|
||||
).encode("utf-8")
|
||||
+ b"\n"
|
||||
)
|
||||
# Stream content in word-sized chunks for smooth UX
|
||||
for part in chunk_content(final_content):
|
||||
yield (
|
||||
@ -1741,6 +1336,7 @@ When a user refers to a specific object they have seen or describe with identify
|
||||
message=ChatMessageResponse(
|
||||
role="assistant",
|
||||
content=final_content,
|
||||
reasoning=response.get("reasoning"),
|
||||
tool_calls=None,
|
||||
),
|
||||
finish_reason=response.get("finish_reason", "stop"),
|
||||
|
||||
@ -20,6 +20,10 @@ class ChatMessageResponse(BaseModel):
|
||||
content: Optional[str] = Field(
|
||||
default=None, description="Message content (None if tool calls present)"
|
||||
)
|
||||
reasoning: Optional[str] = Field(
|
||||
default=None,
|
||||
description="Separated reasoning/thinking trace if the model emitted one",
|
||||
)
|
||||
tool_calls: Optional[list[ToolCallInvocation]] = Field(
|
||||
default=None, description="Tool calls if LLM wants to call tools"
|
||||
)
|
||||
|
||||
@ -34,6 +34,8 @@ from frigate.const import (
|
||||
UPDATE_REVIEW_DESCRIPTION,
|
||||
UPSERT_REVIEW_SEGMENT,
|
||||
)
|
||||
from frigate.models import User
|
||||
from frigate.output.ws_auth import ws_has_camera_access
|
||||
|
||||
logger = logging.getLogger(__name__)
|
||||
|
||||
@ -66,6 +68,7 @@ _WS_VIEWER_TOPICS = frozenset(
|
||||
"audioTranscriptionState",
|
||||
"birdseyeLayout",
|
||||
"embeddingsReindexProgress",
|
||||
"jobState",
|
||||
}
|
||||
)
|
||||
|
||||
@ -102,6 +105,321 @@ def _check_ws_authorization(
|
||||
return topic in _WS_VIEWER_TOPICS
|
||||
|
||||
|
||||
# ---- Outbound filtering ---------------------------------------------------
|
||||
#
|
||||
# Every WebSocket broadcast is classified into one of a small set of scopes,
|
||||
# then materialized per recipient. Connections with restricted roles only see
|
||||
# data for cameras they are authorized to access; admin and full-access roles
|
||||
# behave as today.
|
||||
|
||||
# Topics that are safe to broadcast to every authenticated client.
|
||||
_WS_GLOBAL_OUTBOUND_TOPICS = frozenset(
|
||||
{
|
||||
"model_state",
|
||||
"embeddings_reindex_progress",
|
||||
"audio_transcription_state",
|
||||
"profile/state",
|
||||
"notifications/state",
|
||||
"notification_test",
|
||||
}
|
||||
)
|
||||
|
||||
# Topics that restricted roles must never receive. Birdseye composites span
|
||||
# all cameras, so the existing JSMPEG policy already restricts birdseye access
|
||||
# to unrestricted roles; the layout broadcast follows the same rule.
|
||||
_WS_UNRESTRICTED_ONLY_TOPICS = frozenset(
|
||||
{
|
||||
"birdseye_layout",
|
||||
}
|
||||
)
|
||||
|
||||
# Topics whose payload (parsed as JSON) names a single owning camera at the
|
||||
# given key path. Used to scope events, reviews, triggers, etc.
|
||||
_WS_PAYLOAD_CAMERA_TOPICS: dict[str, tuple[str, ...]] = {
|
||||
"events": ("after", "camera"),
|
||||
"reviews": ("after", "camera"),
|
||||
"tracked_object_update": ("camera",),
|
||||
"triggers": ("camera",),
|
||||
"camera_monitoring": ("camera",),
|
||||
}
|
||||
|
||||
# Topics whose payload is a dict keyed by camera name; filter keys per
|
||||
# recipient.
|
||||
_WS_RESHAPE_BY_CAMERA_KEY_TOPICS = frozenset(
|
||||
{
|
||||
"camera_activity",
|
||||
"audio_detections",
|
||||
}
|
||||
)
|
||||
|
||||
# Topics whose payload is a dict keyed by job_type, where each entry may
|
||||
# contain a "camera" or "source_camera" field, or a nested ``results.jobs``
|
||||
# list of per-camera sub-jobs (export broadcasts).
|
||||
_WS_RESHAPE_JOB_STATE_TOPICS = frozenset(
|
||||
{
|
||||
"job_state",
|
||||
}
|
||||
)
|
||||
|
||||
# Topics whose payload mixes global aggregates with a ``cameras`` sub-dict
|
||||
# keyed by camera name. Aggregates and detector data stay; per-camera entries
|
||||
# are filtered.
|
||||
_WS_RESHAPE_STATS_TOPICS = frozenset(
|
||||
{
|
||||
"stats",
|
||||
}
|
||||
)
|
||||
|
||||
|
||||
def _collect_zone_names(config: FrigateConfig) -> set[str]:
|
||||
"""Return the set of all zone names defined across cameras."""
|
||||
names: set[str] = set()
|
||||
for camera in config.cameras.values():
|
||||
zones = getattr(camera, "zones", None) or {}
|
||||
names.update(zones.keys())
|
||||
return names
|
||||
|
||||
|
||||
def _parse_json_payload(payload: Any) -> Any:
|
||||
"""Return payload parsed as JSON if it is a string, else as-is."""
|
||||
if isinstance(payload, str):
|
||||
try:
|
||||
return json.loads(payload)
|
||||
except (ValueError, TypeError):
|
||||
return None
|
||||
return payload
|
||||
|
||||
|
||||
def _scope_job_entry_to_allowed(entry: Any, allowed: set[str]) -> dict[str, Any] | None:
|
||||
"""Filter a single job_state entry to the recipient's allowed cameras.
|
||||
|
||||
Returns the (possibly reshaped) entry, or None to drop it. Four shapes
|
||||
are handled:
|
||||
|
||||
* Top-level ``camera`` or ``source_camera`` (motion_search, vlm_watch,
|
||||
export sub-job dicts): drop the entry if not allowed.
|
||||
* Nested ``results.jobs`` list of per-camera sub-jobs (the aggregated
|
||||
export broadcast): filter the list; drop the entry if nothing remains.
|
||||
* Nested ``results.camera`` or ``results.source_camera`` (debug_replay,
|
||||
which puts replay-specific fields inside ``results``): drop the entry
|
||||
if not allowed.
|
||||
* No camera anywhere (e.g. ``media_sync``): treat as global and keep.
|
||||
"""
|
||||
if not isinstance(entry, dict):
|
||||
return None
|
||||
|
||||
cam = entry.get("camera") or entry.get("source_camera")
|
||||
|
||||
if cam is None:
|
||||
results = entry.get("results")
|
||||
if isinstance(results, dict):
|
||||
sub_jobs = results.get("jobs")
|
||||
if isinstance(sub_jobs, list):
|
||||
filtered_jobs = [
|
||||
j
|
||||
for j in sub_jobs
|
||||
if isinstance(j, dict)
|
||||
and (j.get("camera") or j.get("source_camera")) in allowed
|
||||
]
|
||||
if not filtered_jobs:
|
||||
return None
|
||||
reshaped = dict(entry)
|
||||
reshaped["results"] = dict(results)
|
||||
reshaped["results"]["jobs"] = filtered_jobs
|
||||
return reshaped
|
||||
|
||||
cam = results.get("camera") or results.get("source_camera")
|
||||
|
||||
if cam is not None:
|
||||
return entry if cam in allowed else None
|
||||
|
||||
return entry
|
||||
|
||||
|
||||
def _extract_payload_camera(payload: Any, path: tuple[str, ...]) -> str | None:
|
||||
"""Walk the dotted path through a (possibly JSON-encoded) payload."""
|
||||
cur = _parse_json_payload(payload)
|
||||
for key in path:
|
||||
if not isinstance(cur, dict):
|
||||
return None
|
||||
cur = cur.get(key)
|
||||
return cur if isinstance(cur, str) else None
|
||||
|
||||
|
||||
def _classify_outbound(
|
||||
topic: str, all_cameras: set[str], all_zones: set[str]
|
||||
) -> tuple[str, Any]:
|
||||
"""Classify an outbound topic into (kind, extra).
|
||||
|
||||
kind values:
|
||||
- "global" : send to every authenticated client
|
||||
- "drop" : send to nobody (fail-closed for unknowns)
|
||||
- "unrestricted_only" : send only to admin/full-access roles
|
||||
- "camera" : extra is the owning camera name
|
||||
- "payload_camera" : extra is the JSON key path to the camera name
|
||||
- "reshape_by_camera_key"
|
||||
- "reshape_job_state"
|
||||
- "reshape_stats"
|
||||
"""
|
||||
if topic in _WS_GLOBAL_OUTBOUND_TOPICS:
|
||||
return ("global", None)
|
||||
if topic in _WS_UNRESTRICTED_ONLY_TOPICS:
|
||||
return ("unrestricted_only", None)
|
||||
if topic in _WS_RESHAPE_BY_CAMERA_KEY_TOPICS:
|
||||
return ("reshape_by_camera_key", None)
|
||||
if topic in _WS_RESHAPE_JOB_STATE_TOPICS:
|
||||
return ("reshape_job_state", None)
|
||||
if topic in _WS_RESHAPE_STATS_TOPICS:
|
||||
return ("reshape_stats", None)
|
||||
if topic in _WS_PAYLOAD_CAMERA_TOPICS:
|
||||
return ("payload_camera", _WS_PAYLOAD_CAMERA_TOPICS[topic])
|
||||
|
||||
# Topic-prefix based: first segment names the owning camera or zone.
|
||||
first = topic.split("/", 1)[0]
|
||||
if first in all_cameras:
|
||||
return ("camera", first)
|
||||
if first in all_zones:
|
||||
# Zone aggregates span cameras; restricted users see nothing here.
|
||||
return ("unrestricted_only", None)
|
||||
|
||||
return ("drop", None)
|
||||
|
||||
|
||||
def _ws_role_header(ws: Any) -> str | None:
|
||||
"""Return the HTTP_REMOTE_ROLE header value, if any."""
|
||||
environ = getattr(ws, "environ", None)
|
||||
if not environ:
|
||||
return None
|
||||
value = environ.get("HTTP_REMOTE_ROLE")
|
||||
return value if isinstance(value, str) else None
|
||||
|
||||
|
||||
def _ws_valid_roles(ws: Any, config: FrigateConfig) -> list[str]:
|
||||
"""Return the list of recognized roles for this connection."""
|
||||
header = _ws_role_header(ws)
|
||||
if not header:
|
||||
return []
|
||||
roles = [r.strip() for r in header.split(config.proxy.separator) if r.strip()]
|
||||
return [r for r in roles if r in config.auth.roles]
|
||||
|
||||
|
||||
def _ws_is_unrestricted(ws: Any, config: FrigateConfig) -> bool:
|
||||
"""True when the connection has unrestricted camera access.
|
||||
|
||||
Mirrors the policy in ``frigate.output.ws_auth``: admin or any role with
|
||||
an empty allow-list grants full access.
|
||||
"""
|
||||
roles = _ws_valid_roles(ws, config)
|
||||
if not roles:
|
||||
return False
|
||||
roles_dict = config.auth.roles
|
||||
return any(r == "admin" or not roles_dict.get(r) for r in roles)
|
||||
|
||||
|
||||
def _ws_allowed_cameras(ws: Any, config: FrigateConfig) -> set[str]:
|
||||
"""Return the union of cameras this connection may access across its roles."""
|
||||
roles = _ws_valid_roles(ws, config)
|
||||
if not roles:
|
||||
return set()
|
||||
all_cameras = set(config.cameras.keys())
|
||||
allowed: set[str] = set()
|
||||
for role in roles:
|
||||
if role == "admin" or not config.auth.roles.get(role):
|
||||
return all_cameras
|
||||
allowed.update(User.get_allowed_cameras(role, config.auth.roles, all_cameras))
|
||||
return allowed
|
||||
|
||||
|
||||
def _wrap_envelope(topic: str, inner_payload: Any) -> str:
|
||||
"""Re-serialize a (topic, payload) message after payload reshaping.
|
||||
|
||||
Frigate's wire format keeps payloads as JSON-encoded strings inside the
|
||||
outer envelope, mirroring what producers send today.
|
||||
"""
|
||||
return json.dumps({"topic": topic, "payload": json.dumps(inner_payload)})
|
||||
|
||||
|
||||
def _materialize_for_ws(
|
||||
ws: Any,
|
||||
topic: str,
|
||||
full_message: str,
|
||||
scope: tuple[str, Any],
|
||||
parsed_payload: Any,
|
||||
config: FrigateConfig,
|
||||
) -> str | None:
|
||||
"""Return the JSON string to deliver to ``ws``, or None to skip it."""
|
||||
kind, extra = scope
|
||||
has_role = _ws_role_header(ws) is not None
|
||||
|
||||
if kind == "drop":
|
||||
return None
|
||||
|
||||
if kind == "global":
|
||||
# Globals still require an authenticated connection. Missing role
|
||||
# falls back to viewer semantics (matching the inbound rule).
|
||||
return full_message
|
||||
|
||||
# Beyond globals, an authenticated role header is required (fail-closed).
|
||||
if not has_role:
|
||||
return None
|
||||
|
||||
if kind == "unrestricted_only":
|
||||
return full_message if _ws_is_unrestricted(ws, config) else None
|
||||
|
||||
if kind == "camera":
|
||||
return full_message if ws_has_camera_access(ws, extra, config) else None
|
||||
|
||||
if kind == "payload_camera":
|
||||
camera = _extract_payload_camera(parsed_payload, extra)
|
||||
if camera is None:
|
||||
return None
|
||||
return full_message if ws_has_camera_access(ws, camera, config) else None
|
||||
|
||||
if kind == "reshape_by_camera_key":
|
||||
if _ws_is_unrestricted(ws, config):
|
||||
return full_message
|
||||
if not isinstance(parsed_payload, dict):
|
||||
return None
|
||||
allowed = _ws_allowed_cameras(ws, config)
|
||||
filtered = {cam: data for cam, data in parsed_payload.items() if cam in allowed}
|
||||
if not filtered:
|
||||
return None
|
||||
return _wrap_envelope(topic, filtered)
|
||||
|
||||
if kind == "reshape_job_state":
|
||||
if _ws_is_unrestricted(ws, config):
|
||||
return full_message
|
||||
if not isinstance(parsed_payload, dict):
|
||||
return None
|
||||
allowed = _ws_allowed_cameras(ws, config)
|
||||
filtered_jobs: dict[str, Any] = {}
|
||||
for job_type, job_payload in parsed_payload.items():
|
||||
scoped = _scope_job_entry_to_allowed(job_payload, allowed)
|
||||
if scoped is not None:
|
||||
filtered_jobs[job_type] = scoped
|
||||
if not filtered_jobs:
|
||||
return None
|
||||
return _wrap_envelope(topic, filtered_jobs)
|
||||
|
||||
if kind == "reshape_stats":
|
||||
if _ws_is_unrestricted(ws, config):
|
||||
return full_message
|
||||
if not isinstance(parsed_payload, dict):
|
||||
return None
|
||||
allowed = _ws_allowed_cameras(ws, config)
|
||||
cameras_block = parsed_payload.get("cameras")
|
||||
if isinstance(cameras_block, dict):
|
||||
filtered_cameras = {
|
||||
name: data for name, data in cameras_block.items() if name in allowed
|
||||
}
|
||||
reshaped = dict(parsed_payload)
|
||||
reshaped["cameras"] = filtered_cameras
|
||||
return _wrap_envelope(topic, reshaped)
|
||||
return full_message
|
||||
|
||||
return None
|
||||
|
||||
|
||||
class WebSocket(WebSocket_): # type: ignore[misc]
|
||||
def unhandled_error(self, error: Any) -> None:
|
||||
"""
|
||||
@ -183,6 +501,10 @@ class WebSocketClient(Communicator):
|
||||
self.websocket_thread.start()
|
||||
|
||||
def publish(self, topic: str, payload: Any, _: bool = False) -> None:
|
||||
if self.websocket_server is None:
|
||||
logger.debug("Skipping message, websocket not connected yet")
|
||||
return
|
||||
|
||||
try:
|
||||
ws_message = json.dumps(
|
||||
{
|
||||
@ -195,14 +517,42 @@ class WebSocketClient(Communicator):
|
||||
logger.debug(f"payload for {topic} wasn't text. Skipping...")
|
||||
return
|
||||
|
||||
if self.websocket_server is None:
|
||||
logger.debug("Skipping message, websocket not connected yet")
|
||||
all_cameras = set(self.config.cameras.keys())
|
||||
all_zones = _collect_zone_names(self.config)
|
||||
scope = _classify_outbound(topic, all_cameras, all_zones)
|
||||
|
||||
if scope[0] == "drop":
|
||||
return
|
||||
|
||||
try:
|
||||
self.websocket_server.manager.broadcast(ws_message)
|
||||
except ConnectionResetError:
|
||||
pass
|
||||
# Pre-parse payload once for topics that need to read its contents.
|
||||
parsed_payload: Any = None
|
||||
if scope[0] in (
|
||||
"payload_camera",
|
||||
"reshape_by_camera_key",
|
||||
"reshape_job_state",
|
||||
"reshape_stats",
|
||||
):
|
||||
parsed_payload = _parse_json_payload(payload)
|
||||
if parsed_payload is None:
|
||||
# malformed payload — fail closed
|
||||
return
|
||||
|
||||
manager = self.websocket_server.manager
|
||||
with manager.lock:
|
||||
websockets = list(manager.websockets.values())
|
||||
|
||||
for ws in websockets:
|
||||
if getattr(ws, "terminated", False):
|
||||
continue
|
||||
message = _materialize_for_ws(
|
||||
ws, topic, ws_message, scope, parsed_payload, self.config
|
||||
)
|
||||
if message is None:
|
||||
continue
|
||||
try:
|
||||
ws.send(message)
|
||||
except (ConnectionResetError, BrokenPipeError, ValueError):
|
||||
pass
|
||||
|
||||
def stop(self) -> None:
|
||||
if self.websocket_server is not None:
|
||||
|
||||
@ -1,6 +1,5 @@
|
||||
"""Generative AI module for Frigate."""
|
||||
|
||||
import datetime
|
||||
import importlib
|
||||
import json
|
||||
import logging
|
||||
@ -9,13 +8,18 @@ import re
|
||||
from typing import Any, Callable, Optional
|
||||
|
||||
import numpy as np
|
||||
from playhouse.shortcuts import model_to_dict
|
||||
from pydantic import ValidationError
|
||||
|
||||
from frigate.config import CameraConfig, GenAIConfig, GenAIProviderEnum
|
||||
from frigate.const import CLIPS_DIR
|
||||
from frigate.data_processing.post.types import ReviewMetadata
|
||||
from frigate.genai.manager import GenAIClientManager
|
||||
from frigate.genai.prompts import (
|
||||
build_object_description_prompt,
|
||||
build_review_description_prompt,
|
||||
build_review_description_response_format,
|
||||
build_review_summary_prompt,
|
||||
)
|
||||
from frigate.models import Event
|
||||
|
||||
logger = logging.getLogger(__name__)
|
||||
@ -61,75 +65,14 @@ class GenAIClient:
|
||||
activity_context_prompt: str,
|
||||
) -> ReviewMetadata | None:
|
||||
"""Generate a description for the review item activity."""
|
||||
context_prompt = build_review_description_prompt(
|
||||
review_data,
|
||||
thumbnails,
|
||||
concerns,
|
||||
preferred_language,
|
||||
activity_context_prompt,
|
||||
)
|
||||
|
||||
def get_concern_prompt() -> str:
|
||||
if concerns:
|
||||
concern_list = "\n - ".join(concerns)
|
||||
return f"""- `other_concerns` (list of strings): Include a list of any of the following concerns that are occurring:
|
||||
- {concern_list}"""
|
||||
else:
|
||||
return ""
|
||||
|
||||
def get_language_prompt() -> str:
|
||||
if preferred_language:
|
||||
return f"Provide your answer in {preferred_language}"
|
||||
else:
|
||||
return ""
|
||||
|
||||
def get_objects_list() -> str:
|
||||
if review_data["unified_objects"]:
|
||||
return "\n- " + "\n- ".join(review_data["unified_objects"])
|
||||
else:
|
||||
return "\n- (No objects detected)"
|
||||
|
||||
context_prompt = f"""
|
||||
Your task is to analyze a sequence of images taken in chronological order from a security camera.
|
||||
|
||||
## Normal Activity Patterns for This Property
|
||||
|
||||
{activity_context_prompt}
|
||||
|
||||
## Task Instructions
|
||||
|
||||
Describe the scene based on observable actions and movements, evaluate the activity against the Activity Indicators above, and assign a potential_threat_level (0, 1, or 2) by applying the threat level indicators consistently.
|
||||
|
||||
## Analysis Guidelines
|
||||
|
||||
When forming your description:
|
||||
- **CRITICAL: Only describe objects explicitly listed in "Objects in Scene" below.** Do not infer or mention additional people, vehicles, or objects not present in this list, even if visual patterns suggest them. If only a car is listed, do not describe a person interacting with it unless "person" is also in the objects list.
|
||||
- **Only describe actions actually visible in the frames.** Do not assume or infer actions that you don't observe happening. If someone walks toward furniture but you never see them sit, do not say they sat. Stick to what you can see across the sequence.
|
||||
- Describe what you observe: actions, movements, interactions with objects and the environment. Include any observable environmental changes (e.g., lighting changes triggered by activity).
|
||||
- Note visible details such as clothing, items being carried or placed, tools or equipment present, and how they interact with the property or objects.
|
||||
- Consider the full sequence chronologically: what happens from start to finish, how duration and actions relate to the location and objects involved.
|
||||
- **Use the actual timestamp provided in "Activity started at"** below for time of day context—do not infer time from image brightness or darkness. Unusual hours (late night/early morning) should increase suspicion when the observable behavior itself appears questionable. However, recognize that some legitimate activities can occur at any hour.
|
||||
- **Consider duration as a primary factor**: Apply the duration thresholds defined in the activity patterns above. Brief sequences during normal hours with apparent purpose typically indicate normal activity unless explicit suspicious actions are visible.
|
||||
- **Weigh all evidence holistically**: Match the activity against the normal and suspicious patterns defined above, then evaluate based on the complete context (zone, objects, time, actions, duration). Apply the threat level indicators consistently. Use your judgment for edge cases.
|
||||
|
||||
## Response Field Guidelines
|
||||
|
||||
Respond with a JSON object matching the provided schema. Field-specific guidance:
|
||||
- `observations`: Include the very start of the activity — for example, a vehicle entering the frame or pulling into the driveway — even if it lasts only a few frames and the rest of the clip is dominated by a longer activity. Include each arrival, departure, object handled, and notable change in position or state. Each item is a single concrete fact written as a complete sentence.
|
||||
- `scene`: Describe how the sequence begins, then the progression of events — all significant movements and actions in order. For example, if a vehicle arrives and then a person exits, describe both sequentially. For named subjects (those with a `←` separator in "Objects in Scene"), always use their name — do not replace them with generic terms. For unnamed objects (e.g., "person", "car"), refer to them naturally with articles (e.g., "a person", "the car"). Your description should align with and support the threat level you assign.
|
||||
- `title`: Name the primary activity across the observations, together with the location. An activity is what is being done with objects, tools, or surfaces; locomotion through the scene qualifies as the activity only when no other interaction is observed. For named subjects, always use their name. For unnamed objects, refer to them naturally with articles.
|
||||
- `shortSummary`: Briefly summarize the primary activity across the observations.
|
||||
- `potential_threat_level`: Must be consistent with your scene description and the activity patterns above.
|
||||
|
||||
## Sequence Details
|
||||
|
||||
- Camera: {review_data["camera"]}
|
||||
- Total frames: {len(thumbnails)} (Frame 1 = earliest, Frame {len(thumbnails)} = latest)
|
||||
- Activity started at {review_data["start"]} and lasted {review_data["duration"]} seconds
|
||||
- Zones involved: {", ".join(review_data["zones"]) if review_data["zones"] else "None"}
|
||||
|
||||
## Objects in Scene
|
||||
|
||||
Each line represents a detection state, not necessarily unique individuals. The `←` symbol separates a recognized subject's name from their object type — use only the name (before the `←`) in your response, not the type after it. The same subject may appear across multiple lines if detected multiple times.
|
||||
|
||||
**Note: Unidentified objects (without names) are NOT indicators of suspicious activity—they simply mean the system hasn't identified that object.**
|
||||
{get_objects_list()}
|
||||
|
||||
{get_language_prompt()}
|
||||
"""
|
||||
logger.debug(
|
||||
f"Sending {len(thumbnails)} images to create review description on {review_data['camera']}"
|
||||
)
|
||||
@ -143,25 +86,7 @@ Each line represents a detection state, not necessarily unique individuals. The
|
||||
) as f:
|
||||
f.write(context_prompt)
|
||||
|
||||
# Build JSON schema for structured output from ReviewMetadata model
|
||||
schema = ReviewMetadata.model_json_schema()
|
||||
schema.get("properties", {}).pop("time", None)
|
||||
|
||||
if "time" in schema.get("required", []):
|
||||
schema["required"].remove("time")
|
||||
if not concerns:
|
||||
schema.get("properties", {}).pop("other_concerns", None)
|
||||
if "other_concerns" in schema.get("required", []):
|
||||
schema["required"].remove("other_concerns")
|
||||
|
||||
response_format = {
|
||||
"type": "json_schema",
|
||||
"json_schema": {
|
||||
"name": "review_metadata",
|
||||
"strict": True,
|
||||
"schema": schema,
|
||||
},
|
||||
}
|
||||
response_format = build_review_description_response_format(concerns)
|
||||
|
||||
response = self._send(context_prompt, thumbnails, response_format)
|
||||
|
||||
@ -240,61 +165,9 @@ Each line represents a detection state, not necessarily unique individuals. The
|
||||
debug_save: bool,
|
||||
) -> str | None:
|
||||
"""Generate a summary of review item descriptions over a period of time."""
|
||||
time_range = f"{datetime.datetime.fromtimestamp(start_ts).strftime('%B %d, %Y at %I:%M %p')} to {datetime.datetime.fromtimestamp(end_ts).strftime('%B %d, %Y at %I:%M %p')}"
|
||||
timeline_summary_prompt = f"""
|
||||
You are a security officer writing a concise security report.
|
||||
|
||||
Time range: {time_range}
|
||||
|
||||
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}
|
||||
|
||||
## Overview
|
||||
[Write 1-2 sentences summarizing the overall activity pattern during this period.]
|
||||
|
||||
---
|
||||
|
||||
## Timeline
|
||||
|
||||
[Group events by time periods (e.g., "Morning (6:00 AM - 12:00 PM)", "Afternoon (12:00 PM - 5:00 PM)", "Evening (5:00 PM - 9:00 PM)", "Night (9:00 PM - 6:00 AM)"). Use appropriate time blocks based on when events occurred.]
|
||||
|
||||
### [Time Block Name]
|
||||
|
||||
**HH:MM AM/PM** | [Camera Name] | [Threat Level Indicator]
|
||||
- [Event title]: [Clear description incorporating contextual information from the "context" array]
|
||||
- Context: [If context array has items, mention them here, e.g., "Delivery truck present on Front Driveway Cam (HH:MM AM/PM)"]
|
||||
- Assessment: [Brief assessment incorporating context - if context explains the event, note it here]
|
||||
|
||||
[Repeat for each event in chronological order within the time block]
|
||||
|
||||
---
|
||||
|
||||
## Summary
|
||||
[One sentence summarizing the period. If all events are normal/explained: "Routine activity observed." If review needed: "Some activity requires review but no security concerns." If security concerns: "Security concerns requiring immediate attention."]
|
||||
|
||||
Guidelines:
|
||||
- List ALL events in chronological order, grouped by time blocks
|
||||
- Threat level indicators: ✓ Normal, ⚠️ Needs review, 🔴 Security concern
|
||||
- Integrate contextual information naturally - use the "context" array to enrich each event's description
|
||||
- If context explains the event (e.g., delivery truck explains person at door), describe it accordingly (e.g., "delivery person" not "unidentified person")
|
||||
- Be concise but informative - focus on what happened and what it means
|
||||
- If contextual information makes an event clearly normal, reflect that in your assessment
|
||||
- Only create time blocks that have events - don't create empty sections
|
||||
"""
|
||||
|
||||
timeline_summary_prompt += "\n\nEvents:\n"
|
||||
for event in events:
|
||||
timeline_summary_prompt += f"\n{event}\n"
|
||||
|
||||
if preferred_language:
|
||||
timeline_summary_prompt += f"\nProvide your answer in {preferred_language}"
|
||||
timeline_summary_prompt = build_review_summary_prompt(
|
||||
start_ts, end_ts, events, preferred_language
|
||||
)
|
||||
|
||||
if debug_save:
|
||||
with open(
|
||||
@ -326,10 +199,7 @@ Guidelines:
|
||||
) -> Optional[str]:
|
||||
"""Generate a description for the frame."""
|
||||
try:
|
||||
prompt = camera_config.objects.genai.object_prompts.get(
|
||||
str(event.label),
|
||||
camera_config.objects.genai.prompt,
|
||||
).format(**model_to_dict(event))
|
||||
prompt = build_object_description_prompt(camera_config, event)
|
||||
except KeyError as e:
|
||||
logger.error(f"Invalid key in GenAI prompt: {e}")
|
||||
return None
|
||||
@ -430,6 +300,10 @@ Guidelines:
|
||||
Returns:
|
||||
Dictionary with:
|
||||
- 'content': Optional[str] - The text response from the LLM, None if tool calls
|
||||
- 'reasoning': Optional[str] - The separated reasoning/thinking trace
|
||||
if the model emitted one (e.g. via OpenAI-compatible
|
||||
`reasoning_content`). None when the model does not surface a
|
||||
trace or the provider does not parse it.
|
||||
- 'tool_calls': Optional[List[Dict]] - List of tool calls if LLM wants to call tools.
|
||||
Each tool call dict has:
|
||||
- 'id': str - Unique identifier for this tool call
|
||||
@ -441,6 +315,14 @@ Guidelines:
|
||||
- 'length': Hit token limit
|
||||
- 'error': An error occurred
|
||||
|
||||
Streaming counterpart `chat_with_tools_stream` yields
|
||||
``(kind, value)`` tuples where ``kind`` is one of:
|
||||
- 'content_delta': value is a string fragment of the answer
|
||||
- 'reasoning_delta': value is a string fragment of the reasoning
|
||||
trace (emitted before content for thinking models)
|
||||
- 'stats': value is a usage stats dict
|
||||
- 'message': value is the final dict shape described above
|
||||
|
||||
Raises:
|
||||
NotImplementedError: If the provider doesn't implement this method.
|
||||
"""
|
||||
@ -451,14 +333,15 @@ Guidelines:
|
||||
)
|
||||
return {
|
||||
"content": None,
|
||||
"reasoning": None,
|
||||
"tool_calls": None,
|
||||
"finish_reason": "error",
|
||||
}
|
||||
|
||||
|
||||
def load_providers() -> None:
|
||||
package_dir = os.path.dirname(__file__)
|
||||
for filename in os.listdir(package_dir):
|
||||
plugins_dir = os.path.join(os.path.dirname(__file__), "plugins")
|
||||
for filename in os.listdir(plugins_dir):
|
||||
if filename.endswith(".py") and filename != "__init__.py":
|
||||
module_name = f"frigate.genai.{filename[:-3]}"
|
||||
module_name = f"frigate.genai.plugins.{filename[:-3]}"
|
||||
importlib.import_module(module_name)
|
||||
|
||||
@ -1,315 +0,0 @@
|
||||
"""Azure OpenAI Provider for Frigate AI."""
|
||||
|
||||
import base64
|
||||
import json
|
||||
import logging
|
||||
from typing import Any, AsyncGenerator, Optional
|
||||
from urllib.parse import parse_qs, urlparse
|
||||
|
||||
from openai import AzureOpenAI
|
||||
|
||||
from frigate.config import GenAIProviderEnum
|
||||
from frigate.genai import GenAIClient, register_genai_provider
|
||||
from frigate.genai.openai import _stats_from_openai_usage
|
||||
|
||||
logger = logging.getLogger(__name__)
|
||||
|
||||
|
||||
@register_genai_provider(GenAIProviderEnum.azure_openai)
|
||||
class OpenAIClient(GenAIClient):
|
||||
"""Generative AI client for Frigate using Azure OpenAI."""
|
||||
|
||||
provider: AzureOpenAI
|
||||
|
||||
def _init_provider(self) -> AzureOpenAI | None:
|
||||
"""Initialize the client."""
|
||||
try:
|
||||
parsed_url = urlparse(self.genai_config.base_url or "")
|
||||
query_params = parse_qs(parsed_url.query)
|
||||
api_version = query_params.get("api-version", [None])[0]
|
||||
azure_endpoint = f"{parsed_url.scheme}://{parsed_url.netloc}/"
|
||||
|
||||
if not api_version:
|
||||
logger.warning("Azure OpenAI url is missing API version.")
|
||||
return None
|
||||
|
||||
except Exception as e:
|
||||
logger.warning("Error parsing Azure OpenAI url: %s", str(e))
|
||||
return None
|
||||
|
||||
return AzureOpenAI(
|
||||
api_key=self.genai_config.api_key,
|
||||
api_version=api_version,
|
||||
azure_endpoint=azure_endpoint,
|
||||
)
|
||||
|
||||
def _send(
|
||||
self,
|
||||
prompt: str,
|
||||
images: list[bytes],
|
||||
response_format: Optional[dict] = None,
|
||||
) -> Optional[str]:
|
||||
"""Submit a request to Azure OpenAI."""
|
||||
encoded_images = [base64.b64encode(image).decode("utf-8") for image in images]
|
||||
try:
|
||||
request_params = {
|
||||
"model": self.genai_config.model,
|
||||
"messages": [
|
||||
{
|
||||
"role": "user",
|
||||
"content": [{"type": "text", "text": prompt}]
|
||||
+ [
|
||||
{
|
||||
"type": "image_url",
|
||||
"image_url": {
|
||||
"url": f"data:image/jpeg;base64,{image}",
|
||||
"detail": "low",
|
||||
},
|
||||
}
|
||||
for image in encoded_images
|
||||
],
|
||||
},
|
||||
],
|
||||
"timeout": self.timeout,
|
||||
**self.genai_config.runtime_options,
|
||||
}
|
||||
if response_format:
|
||||
request_params["response_format"] = response_format
|
||||
result = self.provider.chat.completions.create(**request_params)
|
||||
except Exception as e:
|
||||
logger.warning("Azure OpenAI returned an error: %s", str(e))
|
||||
return None
|
||||
if len(result.choices) > 0:
|
||||
return str(result.choices[0].message.content.strip())
|
||||
return None
|
||||
|
||||
def list_models(self) -> list[str]:
|
||||
"""Return available model IDs from Azure OpenAI."""
|
||||
try:
|
||||
return sorted(m.id for m in self.provider.models.list().data)
|
||||
except Exception as e:
|
||||
logger.warning("Failed to list Azure OpenAI models: %s", e)
|
||||
return []
|
||||
|
||||
def get_context_size(self) -> int:
|
||||
"""Get the context window size for Azure OpenAI."""
|
||||
return 128000
|
||||
|
||||
def chat_with_tools(
|
||||
self,
|
||||
messages: list[dict[str, Any]],
|
||||
tools: Optional[list[dict[str, Any]]] = None,
|
||||
tool_choice: Optional[str] = "auto",
|
||||
) -> dict[str, Any]:
|
||||
try:
|
||||
openai_tool_choice = None
|
||||
if tool_choice:
|
||||
if tool_choice == "none":
|
||||
openai_tool_choice = "none"
|
||||
elif tool_choice == "auto":
|
||||
openai_tool_choice = "auto"
|
||||
elif tool_choice == "required":
|
||||
openai_tool_choice = "required"
|
||||
|
||||
request_params = {
|
||||
"model": self.genai_config.model,
|
||||
"messages": messages,
|
||||
"timeout": self.timeout,
|
||||
}
|
||||
|
||||
if tools:
|
||||
request_params["tools"] = tools
|
||||
if openai_tool_choice is not None:
|
||||
request_params["tool_choice"] = openai_tool_choice
|
||||
|
||||
result = self.provider.chat.completions.create(**request_params) # type: ignore[call-overload]
|
||||
|
||||
if (
|
||||
result is None
|
||||
or not hasattr(result, "choices")
|
||||
or len(result.choices) == 0
|
||||
):
|
||||
return {
|
||||
"content": None,
|
||||
"tool_calls": None,
|
||||
"finish_reason": "error",
|
||||
}
|
||||
|
||||
choice = result.choices[0]
|
||||
message = choice.message
|
||||
|
||||
content = message.content.strip() if message.content else None
|
||||
|
||||
tool_calls = None
|
||||
if message.tool_calls:
|
||||
tool_calls = []
|
||||
for tool_call in message.tool_calls:
|
||||
try:
|
||||
arguments = json.loads(tool_call.function.arguments)
|
||||
except (json.JSONDecodeError, AttributeError) as e:
|
||||
logger.warning(
|
||||
f"Failed to parse tool call arguments: {e}, "
|
||||
f"tool: {tool_call.function.name if hasattr(tool_call.function, 'name') else 'unknown'}"
|
||||
)
|
||||
arguments = {}
|
||||
|
||||
tool_calls.append(
|
||||
{
|
||||
"id": tool_call.id if hasattr(tool_call, "id") else "",
|
||||
"name": tool_call.function.name
|
||||
if hasattr(tool_call.function, "name")
|
||||
else "",
|
||||
"arguments": arguments,
|
||||
}
|
||||
)
|
||||
|
||||
finish_reason = "error"
|
||||
if hasattr(choice, "finish_reason") and choice.finish_reason:
|
||||
finish_reason = choice.finish_reason
|
||||
elif tool_calls:
|
||||
finish_reason = "tool_calls"
|
||||
elif content:
|
||||
finish_reason = "stop"
|
||||
|
||||
return {
|
||||
"content": content,
|
||||
"tool_calls": tool_calls,
|
||||
"finish_reason": finish_reason,
|
||||
}
|
||||
|
||||
except Exception as e:
|
||||
logger.warning("Azure OpenAI returned an error: %s", str(e))
|
||||
return {
|
||||
"content": None,
|
||||
"tool_calls": None,
|
||||
"finish_reason": "error",
|
||||
}
|
||||
|
||||
async def chat_with_tools_stream(
|
||||
self,
|
||||
messages: list[dict[str, Any]],
|
||||
tools: Optional[list[dict[str, Any]]] = None,
|
||||
tool_choice: Optional[str] = "auto",
|
||||
) -> AsyncGenerator[tuple[str, Any], None]:
|
||||
"""
|
||||
Stream chat with tools; yields content deltas then final message.
|
||||
|
||||
Implements streaming function calling/tool usage for Azure OpenAI models.
|
||||
"""
|
||||
try:
|
||||
openai_tool_choice = None
|
||||
if tool_choice:
|
||||
if tool_choice == "none":
|
||||
openai_tool_choice = "none"
|
||||
elif tool_choice == "auto":
|
||||
openai_tool_choice = "auto"
|
||||
elif tool_choice == "required":
|
||||
openai_tool_choice = "required"
|
||||
|
||||
request_params = {
|
||||
"model": self.genai_config.model,
|
||||
"messages": messages,
|
||||
"timeout": self.timeout,
|
||||
"stream": True,
|
||||
"stream_options": {"include_usage": True},
|
||||
}
|
||||
|
||||
if tools:
|
||||
request_params["tools"] = tools
|
||||
if openai_tool_choice is not None:
|
||||
request_params["tool_choice"] = openai_tool_choice
|
||||
|
||||
# Use streaming API
|
||||
content_parts: list[str] = []
|
||||
tool_calls_by_index: dict[int, dict[str, Any]] = {}
|
||||
finish_reason = "stop"
|
||||
usage_stats: Optional[dict[str, Any]] = None
|
||||
|
||||
stream = self.provider.chat.completions.create(**request_params) # type: ignore[call-overload]
|
||||
|
||||
for chunk in stream:
|
||||
chunk_usage = getattr(chunk, "usage", None)
|
||||
if chunk_usage is not None:
|
||||
usage_stats = _stats_from_openai_usage(chunk_usage)
|
||||
|
||||
if not chunk or not chunk.choices:
|
||||
continue
|
||||
|
||||
choice = chunk.choices[0]
|
||||
delta = choice.delta
|
||||
|
||||
# Check for finish reason
|
||||
if choice.finish_reason:
|
||||
finish_reason = choice.finish_reason
|
||||
|
||||
# Extract content deltas
|
||||
if delta.content:
|
||||
content_parts.append(delta.content)
|
||||
yield ("content_delta", delta.content)
|
||||
|
||||
# Extract tool calls
|
||||
if delta.tool_calls:
|
||||
for tc in delta.tool_calls:
|
||||
idx = tc.index
|
||||
fn = tc.function
|
||||
|
||||
if idx not in tool_calls_by_index:
|
||||
tool_calls_by_index[idx] = {
|
||||
"id": tc.id or "",
|
||||
"name": fn.name if fn and fn.name else "",
|
||||
"arguments": "",
|
||||
}
|
||||
|
||||
t = tool_calls_by_index[idx]
|
||||
if tc.id:
|
||||
t["id"] = tc.id
|
||||
if fn and fn.name:
|
||||
t["name"] = fn.name
|
||||
if fn and fn.arguments:
|
||||
t["arguments"] += fn.arguments
|
||||
|
||||
# Build final message
|
||||
full_content = "".join(content_parts).strip() or None
|
||||
|
||||
# Convert tool calls to list format
|
||||
tool_calls_list = None
|
||||
if tool_calls_by_index:
|
||||
tool_calls_list = []
|
||||
for tc in tool_calls_by_index.values():
|
||||
try:
|
||||
# Parse accumulated arguments as JSON
|
||||
parsed_args = json.loads(tc["arguments"])
|
||||
except (json.JSONDecodeError, Exception):
|
||||
parsed_args = tc["arguments"]
|
||||
|
||||
tool_calls_list.append(
|
||||
{
|
||||
"id": tc["id"],
|
||||
"name": tc["name"],
|
||||
"arguments": parsed_args,
|
||||
}
|
||||
)
|
||||
finish_reason = "tool_calls"
|
||||
|
||||
if usage_stats is not None:
|
||||
yield ("stats", usage_stats)
|
||||
|
||||
yield (
|
||||
"message",
|
||||
{
|
||||
"content": full_content,
|
||||
"tool_calls": tool_calls_list,
|
||||
"finish_reason": finish_reason,
|
||||
},
|
||||
)
|
||||
|
||||
except Exception as e:
|
||||
logger.warning("Azure OpenAI streaming returned an error: %s", str(e))
|
||||
yield (
|
||||
"message",
|
||||
{
|
||||
"content": None,
|
||||
"tool_calls": None,
|
||||
"finish_reason": "error",
|
||||
},
|
||||
)
|
||||
1
frigate/genai/plugins/__init__.py
Normal file
1
frigate/genai/plugins/__init__.py
Normal file
@ -0,0 +1 @@
|
||||
"""GenAI provider plugins."""
|
||||
53
frigate/genai/plugins/azure-openai.py
Normal file
53
frigate/genai/plugins/azure-openai.py
Normal file
@ -0,0 +1,53 @@
|
||||
"""Azure OpenAI Provider for Frigate AI.
|
||||
|
||||
Azure OpenAI exposes the same chat completions API as OpenAI once the
|
||||
client is constructed, so this provider inherits all transport, streaming,
|
||||
reasoning, and tool-calling logic from :class:`OpenAIClient` and only
|
||||
overrides what is genuinely Azure-specific:
|
||||
|
||||
- Client construction: parses ``api-version`` out of the configured
|
||||
``base_url`` query string and instantiates :class:`openai.AzureOpenAI`
|
||||
with ``azure_endpoint`` instead of ``base_url``. Raises if the URL is
|
||||
malformed; :class:`GenAIClientManager` catches the exception and
|
||||
disables the provider.
|
||||
- Context size: Azure does not expose a per-model ``max_model_len`` field
|
||||
reliably, so we keep the historical 128K default rather than the
|
||||
model-name heuristic used by OpenAI.
|
||||
"""
|
||||
|
||||
import logging
|
||||
from urllib.parse import parse_qs, urlparse
|
||||
|
||||
from openai import AzureOpenAI
|
||||
|
||||
from frigate.config import GenAIProviderEnum
|
||||
from frigate.genai import register_genai_provider
|
||||
from frigate.genai.plugins.openai import OpenAIClient
|
||||
|
||||
logger = logging.getLogger(__name__)
|
||||
|
||||
|
||||
@register_genai_provider(GenAIProviderEnum.azure_openai)
|
||||
class AzureOpenAIClient(OpenAIClient):
|
||||
"""Generative AI client for Frigate using Azure OpenAI."""
|
||||
|
||||
def _init_provider(self) -> AzureOpenAI:
|
||||
"""Initialize the AzureOpenAI client from the configured base_url."""
|
||||
parsed_url = urlparse(self.genai_config.base_url or "")
|
||||
query_params = parse_qs(parsed_url.query)
|
||||
api_version = query_params.get("api-version", [None])[0]
|
||||
|
||||
if not api_version:
|
||||
raise ValueError("Azure OpenAI base_url is missing api-version.")
|
||||
|
||||
azure_endpoint = f"{parsed_url.scheme}://{parsed_url.netloc}/"
|
||||
|
||||
return AzureOpenAI(
|
||||
api_key=self.genai_config.api_key,
|
||||
api_version=api_version,
|
||||
azure_endpoint=azure_endpoint,
|
||||
)
|
||||
|
||||
def get_context_size(self) -> int:
|
||||
"""Azure does not reliably surface per-model context size; use 128K."""
|
||||
return 128000
|
||||
@ -248,6 +248,13 @@ class GeminiClient(GenAIClient):
|
||||
if tool_config:
|
||||
config_params["tool_config"] = tool_config
|
||||
|
||||
# Ask thinking-capable models (Gemini 2.5+) to include their
|
||||
# reasoning trace as separate `thought` parts so we can surface
|
||||
# it on the reasoning channel. Older models ignore this field.
|
||||
config_params["thinking_config"] = types.ThinkingConfig(
|
||||
include_thoughts=True
|
||||
)
|
||||
|
||||
# Merge runtime_options
|
||||
if isinstance(self.genai_config.runtime_options, dict):
|
||||
config_params.update(self.genai_config.runtime_options)
|
||||
@ -262,19 +269,24 @@ class GeminiClient(GenAIClient):
|
||||
if not response or not response.candidates:
|
||||
return {
|
||||
"content": None,
|
||||
"reasoning": None,
|
||||
"tool_calls": None,
|
||||
"finish_reason": "error",
|
||||
}
|
||||
|
||||
candidate = response.candidates[0]
|
||||
content = None
|
||||
reasoning_parts: list[str] = []
|
||||
tool_calls = None
|
||||
|
||||
# Extract content and tool calls from response
|
||||
# Extract content, reasoning, and tool calls from response
|
||||
if candidate.content and candidate.content.parts:
|
||||
for part in candidate.content.parts:
|
||||
if part.text:
|
||||
content = part.text.strip()
|
||||
if getattr(part, "thought", False):
|
||||
reasoning_parts.append(part.text)
|
||||
else:
|
||||
content = part.text.strip()
|
||||
elif part.function_call:
|
||||
# Handle function call
|
||||
if tool_calls is None:
|
||||
@ -297,6 +309,8 @@ class GeminiClient(GenAIClient):
|
||||
}
|
||||
)
|
||||
|
||||
reasoning = "".join(reasoning_parts).strip() or None
|
||||
|
||||
# Determine finish reason
|
||||
finish_reason = "error"
|
||||
if hasattr(candidate, "finish_reason") and candidate.finish_reason:
|
||||
@ -322,6 +336,7 @@ class GeminiClient(GenAIClient):
|
||||
|
||||
return {
|
||||
"content": content,
|
||||
"reasoning": reasoning,
|
||||
"tool_calls": tool_calls,
|
||||
"finish_reason": finish_reason,
|
||||
}
|
||||
@ -330,6 +345,7 @@ class GeminiClient(GenAIClient):
|
||||
logger.warning("Gemini API error during chat_with_tools: %s", str(e))
|
||||
return {
|
||||
"content": None,
|
||||
"reasoning": None,
|
||||
"tool_calls": None,
|
||||
"finish_reason": "error",
|
||||
}
|
||||
@ -339,6 +355,7 @@ class GeminiClient(GenAIClient):
|
||||
)
|
||||
return {
|
||||
"content": None,
|
||||
"reasoning": None,
|
||||
"tool_calls": None,
|
||||
"finish_reason": "error",
|
||||
}
|
||||
@ -477,12 +494,19 @@ class GeminiClient(GenAIClient):
|
||||
if tool_config:
|
||||
config_params["tool_config"] = tool_config
|
||||
|
||||
# Ask thinking-capable models to include their reasoning trace
|
||||
# as separate `thought` parts (Gemini 2.5+; ignored elsewhere).
|
||||
config_params["thinking_config"] = types.ThinkingConfig(
|
||||
include_thoughts=True
|
||||
)
|
||||
|
||||
# Merge runtime_options
|
||||
if isinstance(self.genai_config.runtime_options, dict):
|
||||
config_params.update(self.genai_config.runtime_options)
|
||||
|
||||
# Use streaming API
|
||||
content_parts: list[str] = []
|
||||
reasoning_parts: list[str] = []
|
||||
tool_calls_by_index: dict[int, dict[str, Any]] = {}
|
||||
finish_reason = "stop"
|
||||
usage_stats: Optional[dict[str, Any]] = None
|
||||
@ -519,12 +543,16 @@ class GeminiClient(GenAIClient):
|
||||
]:
|
||||
finish_reason = "error"
|
||||
|
||||
# Extract content and tool calls from chunk
|
||||
# Extract content, reasoning, and tool calls from chunk
|
||||
if candidate.content and candidate.content.parts:
|
||||
for part in candidate.content.parts:
|
||||
if part.text:
|
||||
content_parts.append(part.text)
|
||||
yield ("content_delta", part.text)
|
||||
if getattr(part, "thought", False):
|
||||
reasoning_parts.append(part.text)
|
||||
yield ("reasoning_delta", part.text)
|
||||
else:
|
||||
content_parts.append(part.text)
|
||||
yield ("content_delta", part.text)
|
||||
elif part.function_call:
|
||||
# Handle function call
|
||||
try:
|
||||
@ -565,6 +593,7 @@ class GeminiClient(GenAIClient):
|
||||
|
||||
# Build final message
|
||||
full_content = "".join(content_parts).strip() or None
|
||||
full_reasoning = "".join(reasoning_parts).strip() or None
|
||||
|
||||
# Convert tool calls to list format
|
||||
tool_calls_list = None
|
||||
@ -593,6 +622,7 @@ class GeminiClient(GenAIClient):
|
||||
"message",
|
||||
{
|
||||
"content": full_content,
|
||||
"reasoning": full_reasoning,
|
||||
"tool_calls": tool_calls_list,
|
||||
"finish_reason": finish_reason,
|
||||
},
|
||||
@ -604,6 +634,7 @@ class GeminiClient(GenAIClient):
|
||||
"message",
|
||||
{
|
||||
"content": None,
|
||||
"reasoning": None,
|
||||
"tool_calls": None,
|
||||
"finish_reason": "error",
|
||||
},
|
||||
@ -616,6 +647,7 @@ class GeminiClient(GenAIClient):
|
||||
"message",
|
||||
{
|
||||
"content": None,
|
||||
"reasoning": None,
|
||||
"tool_calls": None,
|
||||
"finish_reason": "error",
|
||||
},
|
||||
@ -527,19 +527,28 @@ class LlamaCppClient(GenAIClient):
|
||||
k: v for k, v in self.provider_options.items() if k != "context_size"
|
||||
}
|
||||
payload.update(provider_opts)
|
||||
payload.update(self.genai_config.runtime_options)
|
||||
return payload
|
||||
|
||||
def _message_from_choice(self, choice: dict[str, Any]) -> dict[str, Any]:
|
||||
"""Parse OpenAI-style choice into {content, tool_calls, finish_reason}."""
|
||||
"""Parse OpenAI-style choice into {content, reasoning, tool_calls, finish_reason}.
|
||||
|
||||
llama.cpp's `--reasoning-format` puts the trace in
|
||||
`message.reasoning_content` (preferred) or `message.thinking`; both
|
||||
keys are accepted so different builds work without configuration.
|
||||
"""
|
||||
message = choice.get("message", {})
|
||||
content = message.get("content")
|
||||
content = content.strip() if content else None
|
||||
reasoning = message.get("reasoning_content") or message.get("thinking")
|
||||
reasoning = reasoning.strip() if reasoning else None
|
||||
tool_calls = parse_tool_calls_from_message(message)
|
||||
finish_reason = choice.get("finish_reason") or (
|
||||
"tool_calls" if tool_calls else "stop" if content else "error"
|
||||
)
|
||||
return {
|
||||
"content": content,
|
||||
"reasoning": reasoning,
|
||||
"tool_calls": tool_calls,
|
||||
"finish_reason": finish_reason,
|
||||
}
|
||||
@ -802,6 +811,7 @@ class LlamaCppClient(GenAIClient):
|
||||
try:
|
||||
payload = self._build_payload(messages, tools, tool_choice, stream=True)
|
||||
content_parts: list[str] = []
|
||||
reasoning_parts: list[str] = []
|
||||
tool_calls_by_index: dict[int, dict[str, Any]] = {}
|
||||
finish_reason = "stop"
|
||||
|
||||
@ -831,6 +841,15 @@ class LlamaCppClient(GenAIClient):
|
||||
delta = choices[0].get("delta", {})
|
||||
if choices[0].get("finish_reason"):
|
||||
finish_reason = choices[0]["finish_reason"]
|
||||
# llama.cpp emits separated thinking under
|
||||
# reasoning_content (preferred) or thinking before any
|
||||
# content tokens arrive
|
||||
reasoning_delta = delta.get("reasoning_content") or delta.get(
|
||||
"thinking"
|
||||
)
|
||||
if reasoning_delta:
|
||||
reasoning_parts.append(reasoning_delta)
|
||||
yield ("reasoning_delta", reasoning_delta)
|
||||
if delta.get("content"):
|
||||
content_parts.append(delta["content"])
|
||||
yield ("content_delta", delta["content"])
|
||||
@ -856,6 +875,7 @@ class LlamaCppClient(GenAIClient):
|
||||
)
|
||||
|
||||
full_content = "".join(content_parts).strip() or None
|
||||
full_reasoning = "".join(reasoning_parts).strip() or None
|
||||
tool_calls_list = self._streamed_tool_calls_to_list(tool_calls_by_index)
|
||||
if tool_calls_list:
|
||||
finish_reason = "tool_calls"
|
||||
@ -863,6 +883,7 @@ class LlamaCppClient(GenAIClient):
|
||||
"message",
|
||||
{
|
||||
"content": full_content,
|
||||
"reasoning": full_reasoning,
|
||||
"tool_calls": tool_calls_list,
|
||||
"finish_reason": finish_reason,
|
||||
},
|
||||
@ -309,6 +309,7 @@ class OllamaClient(GenAIClient):
|
||||
"model": self.genai_config.model,
|
||||
"messages": request_messages,
|
||||
**self.provider_options,
|
||||
**self.genai_config.runtime_options,
|
||||
}
|
||||
if stream:
|
||||
request_params["stream"] = True
|
||||
@ -336,6 +337,9 @@ class OllamaClient(GenAIClient):
|
||||
response.get("done"),
|
||||
)
|
||||
content = message.get("content", "").strip() if message.get("content") else None
|
||||
reasoning = (
|
||||
message.get("thinking", "").strip() if message.get("thinking") else None
|
||||
)
|
||||
tool_calls = parse_tool_calls_from_message(message)
|
||||
finish_reason = "error"
|
||||
if response.get("done"):
|
||||
@ -348,6 +352,7 @@ class OllamaClient(GenAIClient):
|
||||
finish_reason = "stop"
|
||||
return {
|
||||
"content": content,
|
||||
"reasoning": reasoning,
|
||||
"tool_calls": tool_calls,
|
||||
"finish_reason": finish_reason,
|
||||
}
|
||||
@ -431,6 +436,9 @@ class OllamaClient(GenAIClient):
|
||||
)
|
||||
response = await async_client.chat(**request_params)
|
||||
result = self._message_from_response(response)
|
||||
reasoning = result.get("reasoning")
|
||||
if reasoning:
|
||||
yield ("reasoning_delta", reasoning)
|
||||
content = result.get("content")
|
||||
if content:
|
||||
yield ("content_delta", content)
|
||||
@ -449,6 +457,7 @@ class OllamaClient(GenAIClient):
|
||||
headers=self._auth_headers(),
|
||||
)
|
||||
content_parts: list[str] = []
|
||||
reasoning_parts: list[str] = []
|
||||
final_message: dict[str, Any] | None = None
|
||||
final_chunk: Any = None
|
||||
stream = await async_client.chat(**request_params)
|
||||
@ -456,6 +465,10 @@ class OllamaClient(GenAIClient):
|
||||
if not chunk or "message" not in chunk:
|
||||
continue
|
||||
msg = chunk.get("message", {})
|
||||
reasoning_delta = msg.get("thinking") or ""
|
||||
if reasoning_delta:
|
||||
reasoning_parts.append(reasoning_delta)
|
||||
yield ("reasoning_delta", reasoning_delta)
|
||||
delta = msg.get("content") or ""
|
||||
if delta:
|
||||
content_parts.append(delta)
|
||||
@ -463,8 +476,10 @@ class OllamaClient(GenAIClient):
|
||||
if chunk.get("done"):
|
||||
final_chunk = chunk
|
||||
full_content = "".join(content_parts).strip() or None
|
||||
full_reasoning = "".join(reasoning_parts).strip() or None
|
||||
final_message = {
|
||||
"content": full_content,
|
||||
"reasoning": full_reasoning,
|
||||
"tool_calls": None,
|
||||
"finish_reason": "stop",
|
||||
}
|
||||
@ -481,6 +496,7 @@ class OllamaClient(GenAIClient):
|
||||
"message",
|
||||
{
|
||||
"content": "".join(content_parts).strip() or None,
|
||||
"reasoning": "".join(reasoning_parts).strip() or None,
|
||||
"tool_calls": None,
|
||||
"finish_reason": "stop",
|
||||
},
|
||||
@ -38,7 +38,11 @@ class OpenAIClient(GenAIClient):
|
||||
context_size: Optional[int] = None
|
||||
|
||||
def _init_provider(self) -> OpenAI:
|
||||
"""Initialize the client."""
|
||||
"""Initialize the client.
|
||||
|
||||
Subclasses (e.g. Azure) should raise on configuration errors; the
|
||||
manager catches construction failures and disables the provider.
|
||||
"""
|
||||
# Extract context_size from provider_options as it's not a valid OpenAI client parameter
|
||||
# It will be used in get_context_size() instead
|
||||
provider_opts = {
|
||||
@ -236,6 +240,10 @@ class OpenAIClient(GenAIClient):
|
||||
choice = result.choices[0]
|
||||
message = choice.message
|
||||
content = message.content.strip() if message.content else None
|
||||
raw_reasoning = getattr(message, "reasoning_content", None) or getattr(
|
||||
message, "reasoning", None
|
||||
)
|
||||
reasoning = raw_reasoning.strip() if raw_reasoning else None
|
||||
|
||||
tool_calls = None
|
||||
if message.tool_calls:
|
||||
@ -270,6 +278,7 @@ class OpenAIClient(GenAIClient):
|
||||
|
||||
return {
|
||||
"content": content,
|
||||
"reasoning": reasoning,
|
||||
"tool_calls": tool_calls,
|
||||
"finish_reason": finish_reason,
|
||||
}
|
||||
@ -278,6 +287,7 @@ class OpenAIClient(GenAIClient):
|
||||
logger.warning("OpenAI request timed out: %s", str(e))
|
||||
return {
|
||||
"content": None,
|
||||
"reasoning": None,
|
||||
"tool_calls": None,
|
||||
"finish_reason": "error",
|
||||
}
|
||||
@ -285,6 +295,7 @@ class OpenAIClient(GenAIClient):
|
||||
logger.warning("OpenAI returned an error: %s", str(e))
|
||||
return {
|
||||
"content": None,
|
||||
"reasoning": None,
|
||||
"tool_calls": None,
|
||||
"finish_reason": "error",
|
||||
}
|
||||
@ -335,6 +346,7 @@ class OpenAIClient(GenAIClient):
|
||||
|
||||
# Use streaming API
|
||||
content_parts: list[str] = []
|
||||
reasoning_parts: list[str] = []
|
||||
tool_calls_by_index: dict[int, dict[str, Any]] = {}
|
||||
finish_reason = "stop"
|
||||
usage_stats: Optional[dict[str, Any]] = None
|
||||
@ -356,6 +368,15 @@ class OpenAIClient(GenAIClient):
|
||||
if choice.finish_reason:
|
||||
finish_reason = choice.finish_reason
|
||||
|
||||
# Extract reasoning deltas (reasoning_content or reasoning,
|
||||
# depending on the server)
|
||||
reasoning_delta = getattr(delta, "reasoning_content", None) or getattr(
|
||||
delta, "reasoning", None
|
||||
)
|
||||
if reasoning_delta:
|
||||
reasoning_parts.append(reasoning_delta)
|
||||
yield ("reasoning_delta", reasoning_delta)
|
||||
|
||||
# Extract content deltas
|
||||
if delta.content:
|
||||
content_parts.append(delta.content)
|
||||
@ -384,6 +405,7 @@ class OpenAIClient(GenAIClient):
|
||||
|
||||
# Build final message
|
||||
full_content = "".join(content_parts).strip() or None
|
||||
full_reasoning = "".join(reasoning_parts).strip() or None
|
||||
|
||||
# Convert tool calls to list format
|
||||
tool_calls_list = None
|
||||
@ -412,6 +434,7 @@ class OpenAIClient(GenAIClient):
|
||||
"message",
|
||||
{
|
||||
"content": full_content,
|
||||
"reasoning": full_reasoning,
|
||||
"tool_calls": tool_calls_list,
|
||||
"finish_reason": finish_reason,
|
||||
},
|
||||
@ -423,6 +446,7 @@ class OpenAIClient(GenAIClient):
|
||||
"message",
|
||||
{
|
||||
"content": None,
|
||||
"reasoning": None,
|
||||
"tool_calls": None,
|
||||
"finish_reason": "error",
|
||||
},
|
||||
@ -433,6 +457,7 @@ class OpenAIClient(GenAIClient):
|
||||
"message",
|
||||
{
|
||||
"content": None,
|
||||
"reasoning": None,
|
||||
"tool_calls": None,
|
||||
"finish_reason": "error",
|
||||
},
|
||||
739
frigate/genai/prompts.py
Normal file
739
frigate/genai/prompts.py
Normal file
@ -0,0 +1,739 @@
|
||||
"""Prompt and response-format builders for GenAI features.
|
||||
|
||||
Centralizes the per-feature prompt framing and structured-output schema
|
||||
shaping so provider clients in :mod:`frigate.genai.plugins` only handle
|
||||
transport.
|
||||
"""
|
||||
|
||||
import datetime
|
||||
from typing import Any, Dict, List, Optional
|
||||
|
||||
from playhouse.shortcuts import model_to_dict
|
||||
|
||||
from frigate.config import CameraConfig, FrigateConfig
|
||||
from frigate.config.classification import ObjectClassificationType
|
||||
from frigate.config.ui import UnitSystemEnum
|
||||
from frigate.data_processing.post.types import ReviewMetadata
|
||||
from frigate.models import Event
|
||||
|
||||
|
||||
def build_review_description_prompt(
|
||||
review_data: dict[str, Any],
|
||||
thumbnails: list[bytes],
|
||||
concerns: list[str],
|
||||
preferred_language: str | None,
|
||||
activity_context_prompt: str,
|
||||
) -> str:
|
||||
"""Build the prompt for review activity description generation."""
|
||||
|
||||
def get_concern_prompt() -> str:
|
||||
if concerns:
|
||||
concern_list = "\n - ".join(concerns)
|
||||
return (
|
||||
"\n- `other_concerns` (list of strings): Include a list of any of "
|
||||
"the following concerns that are occurring:\n"
|
||||
f" - {concern_list}"
|
||||
)
|
||||
else:
|
||||
return ""
|
||||
|
||||
def get_language_prompt() -> str:
|
||||
if preferred_language:
|
||||
return f"Provide your answer in {preferred_language}"
|
||||
else:
|
||||
return ""
|
||||
|
||||
def get_objects_list() -> str:
|
||||
if review_data["unified_objects"]:
|
||||
return "\n- " + "\n- ".join(review_data["unified_objects"])
|
||||
else:
|
||||
return "\n- (No objects detected)"
|
||||
|
||||
return f"""
|
||||
Your task is to analyze a sequence of images taken in chronological order from a security camera.
|
||||
|
||||
## Normal Activity Patterns for This Property
|
||||
|
||||
{activity_context_prompt}
|
||||
|
||||
## Task Instructions
|
||||
|
||||
Describe the scene based on observable actions and movements, evaluate the activity against the Activity Indicators above, and assign a potential_threat_level (0, 1, or 2) by applying the threat level indicators consistently.
|
||||
|
||||
## Analysis Guidelines
|
||||
|
||||
When forming your description:
|
||||
- **CRITICAL: Only describe objects explicitly listed in "Objects in Scene" below.** Do not infer or mention additional people, vehicles, or objects not present in this list, even if visual patterns suggest them. If only a car is listed, do not describe a person interacting with it unless "person" is also in the objects list.
|
||||
- **Only describe actions actually visible in the frames.** Do not assume or infer actions that you don't observe happening. If someone walks toward furniture but you never see them sit, do not say they sat. Stick to what you can see across the sequence.
|
||||
- Describe what you observe: actions, movements, interactions with objects and the environment. Include any observable environmental changes (e.g., lighting changes triggered by activity).
|
||||
- Note visible details such as clothing, items being carried or placed, tools or equipment present, and how they interact with the property or objects.
|
||||
- Consider the full sequence chronologically: what happens from start to finish, how duration and actions relate to the location and objects involved.
|
||||
- **Use the actual timestamp provided in "Activity started at"** below for time of day context—do not infer time from image brightness or darkness. Unusual hours (late night/early morning) should increase suspicion when the observable behavior itself appears questionable. However, recognize that some legitimate activities can occur at any hour.
|
||||
- **Consider duration as a primary factor**: Apply the duration thresholds defined in the activity patterns above. Brief sequences during normal hours with apparent purpose typically indicate normal activity unless explicit suspicious actions are visible.
|
||||
- **Weigh all evidence holistically**: Match the activity against the normal and suspicious patterns defined above, then evaluate based on the complete context (zone, objects, time, actions, duration). Apply the threat level indicators consistently. Use your judgment for edge cases.
|
||||
|
||||
## Response Field Guidelines
|
||||
|
||||
Respond with a JSON object matching the provided schema. Field-specific guidance:
|
||||
- `observations`: Include the very start of the activity — for example, a vehicle entering the frame or pulling into the driveway — even if it lasts only a few frames and the rest of the clip is dominated by a longer activity. Include each arrival, departure, object handled, and notable change in position or state. Each item is a single concrete fact written as a complete sentence.
|
||||
- `scene`: Describe how the sequence begins, then the progression of events — all significant movements and actions in order. For example, if a vehicle arrives and then a person exits, describe both sequentially. For named subjects (those with a `←` separator in "Objects in Scene"), always use their name — do not replace them with generic terms. For unnamed objects (e.g., "person", "car"), refer to them naturally with articles (e.g., "a person", "the car"). Your description should align with and support the threat level you assign.
|
||||
- `title`: Name the primary activity across the observations, together with the location. An activity is what is being done with objects, tools, or surfaces; locomotion through the scene qualifies as the activity only when no other interaction is observed. For named subjects, always use their name. For unnamed objects, refer to them naturally with articles.
|
||||
- `shortSummary`: Briefly summarize the primary activity across the observations.
|
||||
- `potential_threat_level`: Must be consistent with your scene description and the activity patterns above.
|
||||
{get_concern_prompt()}
|
||||
|
||||
## Sequence Details
|
||||
|
||||
- Camera: {review_data["camera"]}
|
||||
- Total frames: {len(thumbnails)} (Frame 1 = earliest, Frame {len(thumbnails)} = latest)
|
||||
- Activity started at {review_data["start"]} and lasted {review_data["duration"]} seconds
|
||||
- Zones involved: {", ".join(review_data["zones"]) if review_data["zones"] else "None"}
|
||||
|
||||
## Objects in Scene
|
||||
|
||||
Each line represents a detection state, not necessarily unique individuals. The `←` symbol separates a recognized subject's name from their object type — use only the name (before the `←`) in your response, not the type after it. The same subject may appear across multiple lines if detected multiple times.
|
||||
|
||||
**Note: Unidentified objects (without names) are NOT indicators of suspicious activity—they simply mean the system hasn't identified that object.**
|
||||
{get_objects_list()}
|
||||
|
||||
{get_language_prompt()}
|
||||
"""
|
||||
|
||||
|
||||
def build_review_description_response_format(concerns: list[str]) -> dict[str, Any]:
|
||||
"""Build the structured-output JSON schema for review descriptions.
|
||||
|
||||
Strips the `time` field (populated server-side) and drops
|
||||
`other_concerns` when no concerns are configured.
|
||||
"""
|
||||
schema = ReviewMetadata.model_json_schema()
|
||||
schema.get("properties", {}).pop("time", None)
|
||||
|
||||
if "time" in schema.get("required", []):
|
||||
schema["required"].remove("time")
|
||||
if not concerns:
|
||||
schema.get("properties", {}).pop("other_concerns", None)
|
||||
if "other_concerns" in schema.get("required", []):
|
||||
schema["required"].remove("other_concerns")
|
||||
|
||||
return {
|
||||
"type": "json_schema",
|
||||
"json_schema": {
|
||||
"name": "review_metadata",
|
||||
"strict": True,
|
||||
"schema": schema,
|
||||
},
|
||||
}
|
||||
|
||||
|
||||
def build_review_summary_prompt(
|
||||
start_ts: float,
|
||||
end_ts: float,
|
||||
events: list[dict[str, Any]],
|
||||
preferred_language: str | None,
|
||||
) -> str:
|
||||
"""Build the prompt for a multi-event review summary."""
|
||||
time_range = (
|
||||
f"{datetime.datetime.fromtimestamp(start_ts).strftime('%B %d, %Y at %I:%M %p')}"
|
||||
f" to "
|
||||
f"{datetime.datetime.fromtimestamp(end_ts).strftime('%B %d, %Y at %I:%M %p')}"
|
||||
)
|
||||
prompt = f"""
|
||||
You are a security officer writing a concise security report.
|
||||
|
||||
Time range: {time_range}
|
||||
|
||||
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}
|
||||
|
||||
## Overview
|
||||
[Write 1-2 sentences summarizing the overall activity pattern during this period.]
|
||||
|
||||
---
|
||||
|
||||
## Timeline
|
||||
|
||||
[Group events by time periods (e.g., "Morning (6:00 AM - 12:00 PM)", "Afternoon (12:00 PM - 5:00 PM)", "Evening (5:00 PM - 9:00 PM)", "Night (9:00 PM - 6:00 AM)"). Use appropriate time blocks based on when events occurred.]
|
||||
|
||||
### [Time Block Name]
|
||||
|
||||
**HH:MM AM/PM** | [Camera Name] | [Threat Level Indicator]
|
||||
- [Event title]: [Clear description incorporating contextual information from the "context" array]
|
||||
- Context: [If context array has items, mention them here, e.g., "Delivery truck present on Front Driveway Cam (HH:MM AM/PM)"]
|
||||
- Assessment: [Brief assessment incorporating context - if context explains the event, note it here]
|
||||
|
||||
[Repeat for each event in chronological order within the time block]
|
||||
|
||||
---
|
||||
|
||||
## Summary
|
||||
[One sentence summarizing the period. If all events are normal/explained: "Routine activity observed." If review needed: "Some activity requires review but no security concerns." If security concerns: "Security concerns requiring immediate attention."]
|
||||
|
||||
Guidelines:
|
||||
- List ALL events in chronological order, grouped by time blocks
|
||||
- Threat level indicators: ✓ Normal, ⚠️ Needs review, 🔴 Security concern
|
||||
- Integrate contextual information naturally - use the "context" array to enrich each event's description
|
||||
- If context explains the event (e.g., delivery truck explains person at door), describe it accordingly (e.g., "delivery person" not "unidentified person")
|
||||
- Be concise but informative - focus on what happened and what it means
|
||||
- If contextual information makes an event clearly normal, reflect that in your assessment
|
||||
- Only create time blocks that have events - don't create empty sections
|
||||
"""
|
||||
|
||||
prompt += "\n\nEvents:\n"
|
||||
for event in events:
|
||||
prompt += f"\n{event}\n"
|
||||
|
||||
if preferred_language:
|
||||
prompt += f"\nProvide your answer in {preferred_language}"
|
||||
|
||||
return prompt
|
||||
|
||||
|
||||
def build_object_description_prompt(
|
||||
camera_config: CameraConfig,
|
||||
event: Event,
|
||||
) -> str:
|
||||
"""Build the prompt for a per-object description.
|
||||
|
||||
Pulls the per-label override from `objects.genai.object_prompts`, falling
|
||||
back to the camera default, and interpolates event fields.
|
||||
|
||||
Raises:
|
||||
KeyError: if the user-defined prompt template references an unknown
|
||||
event field.
|
||||
"""
|
||||
template = camera_config.objects.genai.object_prompts.get(
|
||||
str(event.label),
|
||||
camera_config.objects.genai.prompt,
|
||||
)
|
||||
return template.format(**model_to_dict(event))
|
||||
|
||||
|
||||
def get_attribute_classifications(config: FrigateConfig) -> List[Dict[str, Any]]:
|
||||
"""Return enabled custom classification models of `attribute` type.
|
||||
|
||||
Each entry: {"name": <model name>, "objects": [<object label>, ...]}.
|
||||
These models attach attribute metadata to events on the listed object
|
||||
types, which can later be filtered via the search_objects `attribute`
|
||||
field.
|
||||
"""
|
||||
result: List[Dict[str, Any]] = []
|
||||
|
||||
for model_key, model_config in config.classification.custom.items():
|
||||
if not model_config.enabled or model_config.object_config is None:
|
||||
continue
|
||||
|
||||
if (
|
||||
model_config.object_config.classification_type
|
||||
!= ObjectClassificationType.attribute
|
||||
):
|
||||
continue
|
||||
|
||||
result.append(
|
||||
{
|
||||
"name": model_config.name or model_key,
|
||||
"objects": list(model_config.object_config.objects or []),
|
||||
}
|
||||
)
|
||||
|
||||
return result
|
||||
|
||||
|
||||
def get_tool_definitions(
|
||||
semantic_search_enabled: bool = False,
|
||||
attribute_classifications: Optional[List[Dict[str, Any]]] = None,
|
||||
) -> List[Dict[str, Any]]:
|
||||
"""
|
||||
Get OpenAI-compatible tool definitions for Frigate.
|
||||
|
||||
Returns a list of tool definitions that can be used with OpenAI-compatible
|
||||
function calling APIs. When semantic search is enabled, the search_objects
|
||||
tool exposes an additional `semantic_query` parameter for descriptive
|
||||
queries (e.g. "person riding a lawn mower") and find_similar_objects is
|
||||
included. When attribute classification models are configured, an
|
||||
`attribute` parameter is exposed for filtering by their labels.
|
||||
"""
|
||||
search_objects_properties: Dict[str, Any] = {
|
||||
"camera": {
|
||||
"type": "string",
|
||||
"description": "Camera name to filter by (optional).",
|
||||
},
|
||||
"label": {
|
||||
"type": "string",
|
||||
"description": (
|
||||
"Generic object class to filter by — one of the tracked detector "
|
||||
"labels such as 'person', 'package', 'car', 'dog', 'bird'. Use "
|
||||
"this for broad queries like 'show me all cars today'. Combine "
|
||||
"with semantic_query when the user also describes appearance or "
|
||||
"behavior (e.g. label='person', semantic_query='riding a lawn "
|
||||
"mower')."
|
||||
),
|
||||
},
|
||||
"sub_label": {
|
||||
"type": "string",
|
||||
"description": (
|
||||
"Filter by a DISCRETE NAMED entity recognized in the detection. "
|
||||
"Use this for: a known person's name ('John'), a delivery "
|
||||
"company ('Amazon', 'UPS'), a recognized animal species or "
|
||||
"breed ('blue jay', 'cardinal', 'golden retriever'), or a "
|
||||
"license plate string. When filtering by a specific name, set "
|
||||
"only sub_label and leave label unset. Do NOT use sub_label "
|
||||
"for descriptions of appearance, clothing, or actions — those "
|
||||
"belong in semantic_query."
|
||||
),
|
||||
},
|
||||
"after": {
|
||||
"type": "string",
|
||||
"description": "Start time in ISO 8601 format (e.g., '2024-01-01T00:00:00Z').",
|
||||
},
|
||||
"before": {
|
||||
"type": "string",
|
||||
"description": "End time in ISO 8601 format (e.g., '2024-01-01T23:59:59Z').",
|
||||
},
|
||||
"zones": {
|
||||
"type": "array",
|
||||
"items": {"type": "string"},
|
||||
"description": "List of zone names to filter by.",
|
||||
},
|
||||
"limit": {
|
||||
"type": "integer",
|
||||
"description": "Maximum number of objects to return (default: 25).",
|
||||
"default": 25,
|
||||
},
|
||||
}
|
||||
|
||||
if attribute_classifications:
|
||||
model_outline = "; ".join(
|
||||
f"{m['name']} (applies to {', '.join(m['objects']) or 'any object'})"
|
||||
for m in attribute_classifications
|
||||
)
|
||||
search_objects_properties["attribute"] = {
|
||||
"type": "string",
|
||||
"description": (
|
||||
"Filter by a classification attribute label produced by a "
|
||||
"configured attribute classification model. Use this INSTEAD "
|
||||
"of semantic_query when the user's request matches one of "
|
||||
"these classifications. Configured models: "
|
||||
f"{model_outline}. "
|
||||
"Set the value to the attribute label that matches the user's "
|
||||
"phrasing (case-sensitive)."
|
||||
),
|
||||
}
|
||||
|
||||
if semantic_search_enabled:
|
||||
search_objects_properties["semantic_query"] = {
|
||||
"type": "string",
|
||||
"description": (
|
||||
"Optional natural-language description of a PHYSICAL "
|
||||
"CHARACTERISTIC, APPEARANCE, or ACTIVITY the user mentioned, "
|
||||
"used to semantically narrow results. Only set this when the "
|
||||
"user describes something beyond what label and sub_label can "
|
||||
"express on their own.\n"
|
||||
"USE for descriptive phrases like: 'riding a lawn mower', "
|
||||
"'wearing a red jacket', 'carrying a package', 'walking a "
|
||||
"dog', 'on a bicycle', 'holding an umbrella'.\n"
|
||||
"DO NOT USE for:\n"
|
||||
"- specific named people, pets, or delivery companies → use sub_label\n"
|
||||
"- animal species or breed names like 'blue jay', 'cardinal', "
|
||||
"'golden retriever' → use sub_label\n"
|
||||
"- license plate strings → use sub_label\n"
|
||||
"- generic object queries like 'all cars today' or 'every "
|
||||
"person' → use label alone with no semantic_query\n"
|
||||
"When set, combine with label/time/camera/zone filters as "
|
||||
"usual (e.g. label='person', semantic_query='riding a lawn "
|
||||
"mower', after='2024-05-01T00:00:00Z')."
|
||||
),
|
||||
}
|
||||
|
||||
search_objects_description = (
|
||||
"Search the historical record of detected objects in Frigate. "
|
||||
"Use this ONLY for questions about the PAST — e.g. 'did anyone come by today?', "
|
||||
"'when was the last car?', 'show me detections from yesterday'. "
|
||||
"Do NOT use this for monitoring or alerting requests about future events — "
|
||||
"use start_camera_watch instead for those. "
|
||||
"An 'object' in Frigate represents a tracked detection (e.g., a person, package, car).\n\n"
|
||||
"Choose filters based on what the user is asking for:\n"
|
||||
"- Generic class query ('show me all cars today'): set `label` only.\n"
|
||||
"- Specific NAMED entity (known person, delivery company, animal "
|
||||
"species/breed like 'blue jay' or 'golden retriever', license "
|
||||
"plate): set `sub_label` only and leave `label` unset.\n"
|
||||
)
|
||||
if semantic_search_enabled:
|
||||
search_objects_description += (
|
||||
"- Physical CHARACTERISTIC, APPEARANCE, or ACTIVITY that is not a "
|
||||
"discrete name ('person riding a lawn mower', 'someone in a red "
|
||||
"jacket', 'person carrying a package'): set `semantic_query` with "
|
||||
"the descriptive phrase, optionally alongside `label` for the "
|
||||
"object class. Do NOT put descriptive phrases in sub_label."
|
||||
)
|
||||
|
||||
return [
|
||||
{
|
||||
"type": "function",
|
||||
"function": {
|
||||
"name": "search_objects",
|
||||
"description": search_objects_description,
|
||||
"parameters": {
|
||||
"type": "object",
|
||||
"properties": search_objects_properties,
|
||||
},
|
||||
"required": [],
|
||||
},
|
||||
},
|
||||
{
|
||||
"type": "function",
|
||||
"function": {
|
||||
"name": "find_similar_objects",
|
||||
"description": (
|
||||
"Find tracked objects that are visually and semantically similar "
|
||||
"to a specific past event. Use this when the user references a "
|
||||
"particular object they have seen and wants to find other "
|
||||
"sightings of the same or similar one ('that green car', 'the "
|
||||
"person in the red jacket', 'the package that was delivered'). "
|
||||
"Prefer this over search_objects whenever the user's intent is "
|
||||
"'find more like this specific one.' Use search_objects first "
|
||||
"only if you need to locate the anchor event. Requires semantic "
|
||||
"search to be enabled."
|
||||
),
|
||||
"parameters": {
|
||||
"type": "object",
|
||||
"properties": {
|
||||
"event_id": {
|
||||
"type": "string",
|
||||
"description": "The id of the anchor event to find similar objects to.",
|
||||
},
|
||||
"after": {
|
||||
"type": "string",
|
||||
"description": "Start time in ISO 8601 format (e.g., '2024-01-01T00:00:00Z').",
|
||||
},
|
||||
"before": {
|
||||
"type": "string",
|
||||
"description": "End time in ISO 8601 format (e.g., '2024-01-01T23:59:59Z').",
|
||||
},
|
||||
"cameras": {
|
||||
"type": "array",
|
||||
"items": {"type": "string"},
|
||||
"description": "Optional list of cameras to restrict to. Defaults to all.",
|
||||
},
|
||||
"labels": {
|
||||
"type": "array",
|
||||
"items": {"type": "string"},
|
||||
"description": "Optional list of labels to restrict to. Defaults to the anchor event's label.",
|
||||
},
|
||||
"sub_labels": {
|
||||
"type": "array",
|
||||
"items": {"type": "string"},
|
||||
"description": "Optional list of sub_labels (names) to restrict to.",
|
||||
},
|
||||
"zones": {
|
||||
"type": "array",
|
||||
"items": {"type": "string"},
|
||||
"description": "Optional list of zones. An event matches if any of its zones overlap.",
|
||||
},
|
||||
"similarity_mode": {
|
||||
"type": "string",
|
||||
"enum": ["visual", "semantic", "fused"],
|
||||
"description": "Which similarity signal(s) to use. 'fused' (default) combines visual and semantic.",
|
||||
"default": "fused",
|
||||
},
|
||||
"min_score": {
|
||||
"type": "number",
|
||||
"description": "Drop matches with a similarity score below this threshold (0.0-1.0).",
|
||||
},
|
||||
"limit": {
|
||||
"type": "integer",
|
||||
"description": "Maximum number of matches to return (default: 10).",
|
||||
"default": 10,
|
||||
},
|
||||
},
|
||||
"required": ["event_id"],
|
||||
},
|
||||
},
|
||||
},
|
||||
{
|
||||
"type": "function",
|
||||
"function": {
|
||||
"name": "set_camera_state",
|
||||
"description": (
|
||||
"Change a camera's feature state (e.g., turn detection on/off, enable/disable recordings). "
|
||||
"Use camera='*' to apply to all cameras at once. "
|
||||
"Only call this tool when the user explicitly asks to change a camera setting. "
|
||||
"Requires admin privileges."
|
||||
),
|
||||
"parameters": {
|
||||
"type": "object",
|
||||
"properties": {
|
||||
"camera": {
|
||||
"type": "string",
|
||||
"description": "Camera name to target, or '*' to target all cameras.",
|
||||
},
|
||||
"feature": {
|
||||
"type": "string",
|
||||
"enum": [
|
||||
"detect",
|
||||
"record",
|
||||
"snapshots",
|
||||
"audio",
|
||||
"motion",
|
||||
"enabled",
|
||||
"birdseye",
|
||||
"birdseye_mode",
|
||||
"improve_contrast",
|
||||
"ptz_autotracker",
|
||||
"motion_contour_area",
|
||||
"motion_threshold",
|
||||
"notifications",
|
||||
"audio_transcription",
|
||||
"review_alerts",
|
||||
"review_detections",
|
||||
"object_descriptions",
|
||||
"review_descriptions",
|
||||
"profile",
|
||||
],
|
||||
"description": (
|
||||
"The feature to change. Most features accept ON or OFF. "
|
||||
"birdseye_mode accepts CONTINUOUS, MOTION, or OBJECTS. "
|
||||
"motion_contour_area and motion_threshold accept a number. "
|
||||
"profile accepts a profile name or 'none' to deactivate (requires camera='*')."
|
||||
),
|
||||
},
|
||||
"value": {
|
||||
"type": "string",
|
||||
"description": "The value to set. ON or OFF for toggles, a number for thresholds, a profile name or 'none' for profile.",
|
||||
},
|
||||
},
|
||||
"required": ["camera", "feature", "value"],
|
||||
},
|
||||
},
|
||||
},
|
||||
{
|
||||
"type": "function",
|
||||
"function": {
|
||||
"name": "get_live_context",
|
||||
"description": (
|
||||
"Get the current live image and detection information for a camera: objects being tracked, "
|
||||
"zones, timestamps. Use this to understand what is visible in the live view. "
|
||||
"Call this when answering questions about what is happening right now on a specific camera."
|
||||
),
|
||||
"parameters": {
|
||||
"type": "object",
|
||||
"properties": {
|
||||
"camera": {
|
||||
"type": "string",
|
||||
"description": "Camera name to get live context for.",
|
||||
},
|
||||
},
|
||||
"required": ["camera"],
|
||||
},
|
||||
},
|
||||
},
|
||||
{
|
||||
"type": "function",
|
||||
"function": {
|
||||
"name": "start_camera_watch",
|
||||
"description": (
|
||||
"Start a continuous VLM watch job that monitors a camera and sends a notification "
|
||||
"when a specified condition is met. Use this when the user wants to be alerted about "
|
||||
"a future event, e.g. 'tell me when guests arrive' or 'notify me when the package is picked up'. "
|
||||
"Only one watch job can run at a time. Returns a job ID."
|
||||
),
|
||||
"parameters": {
|
||||
"type": "object",
|
||||
"properties": {
|
||||
"camera": {
|
||||
"type": "string",
|
||||
"description": "Camera ID to monitor.",
|
||||
},
|
||||
"condition": {
|
||||
"type": "string",
|
||||
"description": (
|
||||
"Natural-language description of the condition to watch for, "
|
||||
"e.g. 'a person arrives at the front door'."
|
||||
),
|
||||
},
|
||||
"max_duration_minutes": {
|
||||
"type": "integer",
|
||||
"description": "Maximum time to watch before giving up (minutes, default 60).",
|
||||
"default": 60,
|
||||
},
|
||||
"labels": {
|
||||
"type": "array",
|
||||
"items": {"type": "string"},
|
||||
"description": "Object labels that should trigger a VLM check (e.g. ['person', 'car']). If omitted, any detection on the camera triggers a check.",
|
||||
},
|
||||
"zones": {
|
||||
"type": "array",
|
||||
"items": {"type": "string"},
|
||||
"description": "Zone names to filter by. If specified, only detections in these zones trigger a VLM check.",
|
||||
},
|
||||
},
|
||||
"required": ["camera", "condition"],
|
||||
},
|
||||
},
|
||||
},
|
||||
{
|
||||
"type": "function",
|
||||
"function": {
|
||||
"name": "stop_camera_watch",
|
||||
"description": (
|
||||
"Cancel the currently running VLM watch job. Use this when the user wants to "
|
||||
"stop a previously started watch, e.g. 'stop watching the front door'."
|
||||
),
|
||||
"parameters": {
|
||||
"type": "object",
|
||||
"properties": {},
|
||||
"required": [],
|
||||
},
|
||||
},
|
||||
},
|
||||
{
|
||||
"type": "function",
|
||||
"function": {
|
||||
"name": "get_profile_status",
|
||||
"description": (
|
||||
"Get the current profile status including the active profile and "
|
||||
"timestamps of when each profile was last activated. Use this to "
|
||||
"determine time periods for recap requests — e.g. when the user asks "
|
||||
"'what happened while I was away?', call this first to find the relevant "
|
||||
"time window based on profile activation history."
|
||||
),
|
||||
"parameters": {
|
||||
"type": "object",
|
||||
"properties": {},
|
||||
"required": [],
|
||||
},
|
||||
},
|
||||
},
|
||||
{
|
||||
"type": "function",
|
||||
"function": {
|
||||
"name": "get_recap",
|
||||
"description": (
|
||||
"Get a recap of all activity (alerts and detections) for a given time period. "
|
||||
"Use this after calling get_profile_status to retrieve what happened during "
|
||||
"a specific window — e.g. 'what happened while I was away?'. Returns a "
|
||||
"chronological list of activity with camera, objects, zones, and GenAI-generated "
|
||||
"descriptions when available. Summarize the results for the user."
|
||||
),
|
||||
"parameters": {
|
||||
"type": "object",
|
||||
"properties": {
|
||||
"after": {
|
||||
"type": "string",
|
||||
"description": "Start of the time period in ISO 8601 format (e.g. '2025-03-15T08:00:00').",
|
||||
},
|
||||
"before": {
|
||||
"type": "string",
|
||||
"description": "End of the time period in ISO 8601 format (e.g. '2025-03-15T17:00:00').",
|
||||
},
|
||||
"cameras": {
|
||||
"type": "string",
|
||||
"description": "Comma-separated camera IDs to include, or 'all' for all cameras. Default is 'all'.",
|
||||
},
|
||||
"severity": {
|
||||
"type": "string",
|
||||
"enum": ["alert", "detection"],
|
||||
"description": "Filter by severity level. Omit to include both alerts and detections.",
|
||||
},
|
||||
},
|
||||
"required": ["after", "before"],
|
||||
},
|
||||
},
|
||||
},
|
||||
]
|
||||
|
||||
|
||||
def build_chat_system_prompt(
|
||||
config: FrigateConfig,
|
||||
allowed_cameras: List[str],
|
||||
semantic_search_enabled: bool,
|
||||
attribute_classifications: List[Dict[str, Any]],
|
||||
) -> str:
|
||||
"""Build the system prompt for the chat completion endpoint.
|
||||
|
||||
Composes the static framing with conditional sections describing the
|
||||
available cameras, speed units, semantic-search routing guidance, and
|
||||
configured attribute classifications.
|
||||
"""
|
||||
current_datetime = datetime.datetime.now()
|
||||
current_date_str = current_datetime.strftime("%Y-%m-%d")
|
||||
current_time_str = current_datetime.strftime("%I:%M:%S %p")
|
||||
|
||||
cameras_info: List[str] = []
|
||||
has_speed_zone = False
|
||||
for camera_id in allowed_cameras:
|
||||
if camera_id not in config.cameras:
|
||||
continue
|
||||
camera_config = config.cameras[camera_id]
|
||||
friendly_name = (
|
||||
camera_config.friendly_name
|
||||
if camera_config.friendly_name
|
||||
else camera_id.replace("_", " ").title()
|
||||
)
|
||||
zone_names = list(camera_config.zones.keys())
|
||||
if not has_speed_zone:
|
||||
has_speed_zone = any(
|
||||
zone.distances for zone in camera_config.zones.values()
|
||||
)
|
||||
if zone_names:
|
||||
cameras_info.append(
|
||||
f" - {friendly_name} (ID: {camera_id}, zones: {', '.join(zone_names)})"
|
||||
)
|
||||
else:
|
||||
cameras_info.append(f" - {friendly_name} (ID: {camera_id})")
|
||||
|
||||
cameras_section = ""
|
||||
if cameras_info:
|
||||
cameras_section = (
|
||||
"\n\nAvailable cameras:\n"
|
||||
+ "\n".join(cameras_info)
|
||||
+ "\n\nWhen users refer to cameras by their friendly name (e.g., 'Back Deck Camera'), use the corresponding camera ID (e.g., 'back_deck_cam') in tool calls."
|
||||
)
|
||||
|
||||
speed_units_section = ""
|
||||
if has_speed_zone:
|
||||
speed_unit = (
|
||||
"mph" if config.ui.unit_system == UnitSystemEnum.imperial else "km/h"
|
||||
)
|
||||
speed_units_section = f"\n\nReport object speeds to the user in {speed_unit}."
|
||||
|
||||
semantic_search_section = ""
|
||||
if semantic_search_enabled:
|
||||
semantic_search_section = (
|
||||
"\n\nWhen routing a search_objects call, pick filters by the shape of the user's request:\n"
|
||||
"- Generic class ('show me all cars today'): set `label` only.\n"
|
||||
"- Specific named entity — a known person ('John'), delivery company ('Amazon'), animal species/breed ('blue jay', 'cardinal', 'golden retriever'), or license plate: set `sub_label` only and leave `label` unset.\n"
|
||||
"- Physical characteristic, appearance, or activity that is NOT a discrete name ('find me people riding a lawn mower', 'someone in a red jacket', 'a person carrying a package'): set `semantic_query` with the descriptive phrase, optionally combined with `label` for the object class. Never put descriptive phrases in `sub_label`."
|
||||
)
|
||||
|
||||
attribute_classification_section = ""
|
||||
if attribute_classifications:
|
||||
model_lines = "\n".join(
|
||||
f"- {m['name']}: applies to {', '.join(m['objects']) or 'any object'}"
|
||||
for m in attribute_classifications
|
||||
)
|
||||
attribute_classification_section = (
|
||||
"\n\nAttribute classification models are configured for the following object types:\n"
|
||||
f"{model_lines}\n"
|
||||
"When the user's request matches one of these classifications, set the search_objects `attribute` field to the matching label rather than using `semantic_query`. Reserve `semantic_query` for descriptive phrases that fall outside the configured attribute labels."
|
||||
)
|
||||
|
||||
return f"""You are a helpful assistant for Frigate, a security camera NVR system. You help users answer questions about their cameras, detected objects, and events.
|
||||
|
||||
Current server local date and time: {current_date_str} at {current_time_str}
|
||||
|
||||
Do not start your response with phrases like "I will check...", "Let me see...", or "Let me look...". Answer directly.
|
||||
|
||||
Always present times to the user in the server's local timezone. When tool results include start_time_local and end_time_local, use those exact strings when listing or describing detection times—do not convert or invent timestamps. Do not use UTC or ISO format with Z for the user-facing answer unless the tool result only provides Unix timestamps without local time fields.
|
||||
When users ask about "today", "yesterday", "this week", etc., use the current date above as reference.
|
||||
When searching for objects or events, use ISO 8601 format for dates (e.g., {current_date_str}T00:00:00Z for the start of today).
|
||||
Always be accurate with time calculations based on the current date provided.
|
||||
|
||||
When a user refers to a specific object they have seen or describe with identifying details ("that green car", "the person in the red jacket", "a package left today"), prefer the find_similar_objects tool over search_objects. Use search_objects first only to locate the anchor event, then pass its id to find_similar_objects. For generic queries like "show me all cars today", keep using search_objects. If a user message begins with [attached_event:<id>], treat that event id as the anchor for any similarity or "tell me more" request in the same message and call find_similar_objects with that id.{semantic_search_section}{attribute_classification_section}{cameras_section}{speed_units_section}"""
|
||||
806
frigate/test/test_ws_outbound_filter.py
Normal file
806
frigate/test/test_ws_outbound_filter.py
Normal file
@ -0,0 +1,806 @@
|
||||
"""Tests for outbound WebSocket broadcast filtering."""
|
||||
|
||||
import json
|
||||
import threading
|
||||
import unittest
|
||||
from types import SimpleNamespace
|
||||
from typing import Any
|
||||
|
||||
from frigate.comms.ws import (
|
||||
WebSocketClient,
|
||||
_classify_outbound,
|
||||
_collect_zone_names,
|
||||
_extract_payload_camera,
|
||||
_materialize_for_ws,
|
||||
_ws_allowed_cameras,
|
||||
_ws_is_unrestricted,
|
||||
)
|
||||
from frigate.config import FrigateConfig
|
||||
|
||||
|
||||
def _build_config(
|
||||
*,
|
||||
extra_roles: dict[str, list[str]] | None = None,
|
||||
extra_cameras: dict[str, dict[str, Any]] | None = None,
|
||||
extra_zones: dict[str, dict[str, dict[str, Any]]] | None = None,
|
||||
) -> FrigateConfig:
|
||||
"""Construct a FrigateConfig used by the outbound filter tests.
|
||||
|
||||
The default fixture has three cameras: front_door, back_door, garage.
|
||||
Restricted role "house_only" sees front_door + back_door but not garage.
|
||||
"""
|
||||
cameras: dict[str, dict[str, Any]] = {
|
||||
"front_door": {
|
||||
"ffmpeg": {
|
||||
"inputs": [{"path": "rtsp://10.0.0.1:554/v", "roles": ["detect"]}],
|
||||
},
|
||||
"detect": {"height": 1080, "width": 1920, "fps": 5},
|
||||
},
|
||||
"back_door": {
|
||||
"ffmpeg": {
|
||||
"inputs": [{"path": "rtsp://10.0.0.2:554/v", "roles": ["detect"]}],
|
||||
},
|
||||
"detect": {"height": 1080, "width": 1920, "fps": 5},
|
||||
},
|
||||
"garage": {
|
||||
"ffmpeg": {
|
||||
"inputs": [{"path": "rtsp://10.0.0.3:554/v", "roles": ["detect"]}],
|
||||
},
|
||||
"detect": {"height": 1080, "width": 1920, "fps": 5},
|
||||
},
|
||||
}
|
||||
if extra_cameras:
|
||||
cameras.update(extra_cameras)
|
||||
if extra_zones:
|
||||
for cam_name, zones in extra_zones.items():
|
||||
cameras[cam_name]["zones"] = zones
|
||||
|
||||
roles = {"house_only": ["front_door", "back_door"]}
|
||||
if extra_roles:
|
||||
roles.update(extra_roles)
|
||||
|
||||
return FrigateConfig(
|
||||
mqtt={"host": "mqtt"},
|
||||
auth={"roles": roles},
|
||||
cameras=cameras,
|
||||
)
|
||||
|
||||
|
||||
def _ws(role: str | None) -> Any:
|
||||
"""Build a fake ws4py-style websocket exposing ``environ``."""
|
||||
environ = {} if role is None else {"HTTP_REMOTE_ROLE": role}
|
||||
return SimpleNamespace(environ=environ, terminated=False, sent=[])
|
||||
|
||||
|
||||
class TestClassifyOutbound(unittest.TestCase):
|
||||
"""The pure classifier — bucket every topic into a scope."""
|
||||
|
||||
def setUp(self):
|
||||
self.config = _build_config(
|
||||
extra_zones={"front_door": {"driveway": {"coordinates": "0,0,1,0,1,1,0,1"}}}
|
||||
)
|
||||
self.all_cameras = set(self.config.cameras.keys())
|
||||
self.all_zones = _collect_zone_names(self.config)
|
||||
|
||||
def _classify(self, topic: str) -> tuple[str, Any]:
|
||||
return _classify_outbound(topic, self.all_cameras, self.all_zones)
|
||||
|
||||
# --- Global allowlist ---
|
||||
|
||||
def test_model_state_is_global(self):
|
||||
self.assertEqual(self._classify("model_state"), ("global", None))
|
||||
|
||||
def test_profile_state_is_global(self):
|
||||
self.assertEqual(self._classify("profile/state"), ("global", None))
|
||||
|
||||
def test_bare_notifications_state_is_global(self):
|
||||
"""The 2-segment ``notifications/state`` is global; the 3-segment
|
||||
``<camera>/notifications/state`` is camera-scoped (see below)."""
|
||||
self.assertEqual(self._classify("notifications/state"), ("global", None))
|
||||
|
||||
def test_notification_test_is_global(self):
|
||||
self.assertEqual(self._classify("notification_test"), ("global", None))
|
||||
|
||||
# --- Unrestricted-only ---
|
||||
|
||||
def test_birdseye_layout_is_unrestricted_only(self):
|
||||
self.assertEqual(self._classify("birdseye_layout"), ("unrestricted_only", None))
|
||||
|
||||
# --- Camera-prefixed ---
|
||||
|
||||
def test_camera_state_topic_resolves_to_camera(self):
|
||||
self.assertEqual(
|
||||
self._classify("front_door/detect/state"), ("camera", "front_door")
|
||||
)
|
||||
|
||||
def test_camera_motion_topic_resolves_to_camera(self):
|
||||
self.assertEqual(self._classify("back_door/motion"), ("camera", "back_door"))
|
||||
|
||||
def test_camera_per_notification_topic_resolves_to_camera(self):
|
||||
self.assertEqual(
|
||||
self._classify("front_door/notifications/state"),
|
||||
("camera", "front_door"),
|
||||
)
|
||||
|
||||
def test_camera_label_counter_resolves_to_camera(self):
|
||||
self.assertEqual(self._classify("front_door/person"), ("camera", "front_door"))
|
||||
|
||||
def test_camera_object_mask_state_resolves_to_camera(self):
|
||||
self.assertEqual(
|
||||
self._classify("front_door/object_mask/zone_1/state"),
|
||||
("camera", "front_door"),
|
||||
)
|
||||
|
||||
# --- Zone-prefixed ---
|
||||
|
||||
def test_zone_aggregate_topic_is_unrestricted_only(self):
|
||||
self.assertEqual(self._classify("driveway/person"), ("unrestricted_only", None))
|
||||
|
||||
def test_zone_all_topic_is_unrestricted_only(self):
|
||||
self.assertEqual(self._classify("driveway/all"), ("unrestricted_only", None))
|
||||
|
||||
# --- Payload-camera ---
|
||||
|
||||
def test_events_topic_marks_payload_camera_path(self):
|
||||
self.assertEqual(
|
||||
self._classify("events"), ("payload_camera", ("after", "camera"))
|
||||
)
|
||||
|
||||
def test_reviews_topic_marks_payload_camera_path(self):
|
||||
self.assertEqual(
|
||||
self._classify("reviews"), ("payload_camera", ("after", "camera"))
|
||||
)
|
||||
|
||||
def test_triggers_topic_marks_payload_camera_path(self):
|
||||
self.assertEqual(self._classify("triggers"), ("payload_camera", ("camera",)))
|
||||
|
||||
def test_tracked_object_update_marks_payload_camera_path(self):
|
||||
self.assertEqual(
|
||||
self._classify("tracked_object_update"), ("payload_camera", ("camera",))
|
||||
)
|
||||
|
||||
# --- Reshape ---
|
||||
|
||||
def test_camera_activity_is_reshape_by_camera_key(self):
|
||||
self.assertEqual(
|
||||
self._classify("camera_activity"), ("reshape_by_camera_key", None)
|
||||
)
|
||||
|
||||
def test_audio_detections_is_reshape_by_camera_key(self):
|
||||
self.assertEqual(
|
||||
self._classify("audio_detections"), ("reshape_by_camera_key", None)
|
||||
)
|
||||
|
||||
def test_job_state_is_reshape_job_state(self):
|
||||
self.assertEqual(self._classify("job_state"), ("reshape_job_state", None))
|
||||
|
||||
def test_stats_is_reshape_stats(self):
|
||||
self.assertEqual(self._classify("stats"), ("reshape_stats", None))
|
||||
|
||||
# --- Fail-closed ---
|
||||
|
||||
def test_unknown_topic_is_dropped(self):
|
||||
self.assertEqual(self._classify("some_random_topic"), ("drop", None))
|
||||
|
||||
def test_unknown_camera_prefix_is_dropped(self):
|
||||
self.assertEqual(self._classify("ghost_camera/detect/state"), ("drop", None))
|
||||
|
||||
|
||||
class TestCollectZoneNames(unittest.TestCase):
|
||||
def test_zones_from_all_cameras(self):
|
||||
config = _build_config(
|
||||
extra_zones={
|
||||
"front_door": {"driveway": {"coordinates": "0,0,1,0,1,1,0,1"}},
|
||||
"back_door": {"yard": {"coordinates": "0,0,1,0,1,1,0,1"}},
|
||||
}
|
||||
)
|
||||
self.assertEqual(_collect_zone_names(config), {"driveway", "yard"})
|
||||
|
||||
def test_no_zones_returns_empty(self):
|
||||
self.assertEqual(_collect_zone_names(_build_config()), set())
|
||||
|
||||
|
||||
class TestExtractPayloadCamera(unittest.TestCase):
|
||||
def test_extract_from_dict_path(self):
|
||||
payload = {"after": {"camera": "front_door"}}
|
||||
self.assertEqual(
|
||||
_extract_payload_camera(payload, ("after", "camera")), "front_door"
|
||||
)
|
||||
|
||||
def test_extract_from_json_string(self):
|
||||
payload = json.dumps({"after": {"camera": "front_door"}})
|
||||
self.assertEqual(
|
||||
_extract_payload_camera(payload, ("after", "camera")), "front_door"
|
||||
)
|
||||
|
||||
def test_extract_single_segment_path(self):
|
||||
self.assertEqual(
|
||||
_extract_payload_camera({"camera": "garage"}, ("camera",)), "garage"
|
||||
)
|
||||
|
||||
def test_missing_key_returns_none(self):
|
||||
self.assertIsNone(_extract_payload_camera({}, ("after", "camera")))
|
||||
|
||||
def test_malformed_json_returns_none(self):
|
||||
self.assertIsNone(_extract_payload_camera("not-json", ("camera",)))
|
||||
|
||||
def test_non_string_camera_returns_none(self):
|
||||
self.assertIsNone(_extract_payload_camera({"camera": 42}, ("camera",)))
|
||||
|
||||
|
||||
class TestWsRoleHelpers(unittest.TestCase):
|
||||
def setUp(self):
|
||||
self.config = _build_config()
|
||||
|
||||
def test_admin_is_unrestricted(self):
|
||||
self.assertTrue(_ws_is_unrestricted(_ws("admin"), self.config))
|
||||
|
||||
def test_viewer_is_unrestricted(self):
|
||||
self.assertTrue(_ws_is_unrestricted(_ws("viewer"), self.config))
|
||||
|
||||
def test_restricted_role_is_not_unrestricted(self):
|
||||
self.assertFalse(_ws_is_unrestricted(_ws("house_only"), self.config))
|
||||
|
||||
def test_missing_role_is_not_unrestricted(self):
|
||||
self.assertFalse(_ws_is_unrestricted(_ws(None), self.config))
|
||||
|
||||
def test_unknown_role_is_not_unrestricted(self):
|
||||
self.assertFalse(_ws_is_unrestricted(_ws("ghost"), self.config))
|
||||
|
||||
def test_admin_allowed_cameras_is_all(self):
|
||||
self.assertEqual(
|
||||
_ws_allowed_cameras(_ws("admin"), self.config),
|
||||
{"front_door", "back_door", "garage"},
|
||||
)
|
||||
|
||||
def test_restricted_role_allowed_cameras_is_subset(self):
|
||||
self.assertEqual(
|
||||
_ws_allowed_cameras(_ws("house_only"), self.config),
|
||||
{"front_door", "back_door"},
|
||||
)
|
||||
|
||||
def test_missing_role_allowed_cameras_is_empty(self):
|
||||
self.assertEqual(_ws_allowed_cameras(_ws(None), self.config), set())
|
||||
|
||||
def test_multi_role_union_grants_widest(self):
|
||||
self.assertEqual(
|
||||
_ws_allowed_cameras(_ws("house_only,admin"), self.config),
|
||||
{"front_door", "back_door", "garage"},
|
||||
)
|
||||
|
||||
|
||||
class TestMaterializeForWs(unittest.TestCase):
|
||||
def setUp(self):
|
||||
self.config = _build_config(
|
||||
extra_zones={"front_door": {"driveway": {"coordinates": "0,0,1,0,1,1,0,1"}}}
|
||||
)
|
||||
self.all_cameras = set(self.config.cameras.keys())
|
||||
self.all_zones = _collect_zone_names(self.config)
|
||||
|
||||
def _materialize(self, ws: Any, topic: str, payload: Any) -> str | None:
|
||||
scope = _classify_outbound(topic, self.all_cameras, self.all_zones)
|
||||
from frigate.comms.ws import _parse_json_payload
|
||||
|
||||
parsed = (
|
||||
_parse_json_payload(payload)
|
||||
if scope[0]
|
||||
in (
|
||||
"payload_camera",
|
||||
"reshape_by_camera_key",
|
||||
"reshape_job_state",
|
||||
"reshape_stats",
|
||||
)
|
||||
else None
|
||||
)
|
||||
full = json.dumps({"topic": topic, "payload": payload})
|
||||
return _materialize_for_ws(ws, topic, full, scope, parsed, self.config)
|
||||
|
||||
# --- Globals: every authenticated client sees them ---
|
||||
|
||||
def test_globals_reach_admin(self):
|
||||
self.assertIsNotNone(self._materialize(_ws("admin"), "model_state", "{}"))
|
||||
|
||||
def test_globals_reach_restricted(self):
|
||||
self.assertIsNotNone(self._materialize(_ws("house_only"), "model_state", "{}"))
|
||||
|
||||
def test_globals_reach_no_role(self):
|
||||
"""A missing role header still gets globals (matches viewer-default
|
||||
for inbound)."""
|
||||
self.assertIsNotNone(self._materialize(_ws(None), "model_state", "{}"))
|
||||
|
||||
# --- Unknown topic dropped for everyone ---
|
||||
|
||||
def test_unknown_topic_dropped_for_admin(self):
|
||||
self.assertIsNone(self._materialize(_ws("admin"), "rogue_topic", "{}"))
|
||||
|
||||
# --- Non-global topics require a role (fail-closed) ---
|
||||
|
||||
def test_no_role_blocked_from_camera_topic(self):
|
||||
self.assertIsNone(self._materialize(_ws(None), "front_door/detect/state", "ON"))
|
||||
|
||||
def test_no_role_blocked_from_events(self):
|
||||
payload = json.dumps({"after": {"camera": "front_door"}})
|
||||
self.assertIsNone(self._materialize(_ws(None), "events", payload))
|
||||
|
||||
# --- Camera-prefixed ---
|
||||
|
||||
def test_restricted_role_sees_allowed_camera(self):
|
||||
self.assertIsNotNone(
|
||||
self._materialize(_ws("house_only"), "front_door/detect/state", "ON")
|
||||
)
|
||||
|
||||
def test_restricted_role_blocked_from_unallowed_camera(self):
|
||||
self.assertIsNone(
|
||||
self._materialize(_ws("house_only"), "garage/detect/state", "ON")
|
||||
)
|
||||
|
||||
def test_admin_sees_all_camera_topics(self):
|
||||
self.assertIsNotNone(
|
||||
self._materialize(_ws("admin"), "garage/detect/state", "ON")
|
||||
)
|
||||
|
||||
# --- Unrestricted-only (zones, birdseye_layout) ---
|
||||
|
||||
def test_zone_aggregate_blocked_for_restricted(self):
|
||||
self.assertIsNone(self._materialize(_ws("house_only"), "driveway/person", 3))
|
||||
|
||||
def test_zone_aggregate_visible_to_admin(self):
|
||||
self.assertIsNotNone(self._materialize(_ws("admin"), "driveway/person", 3))
|
||||
|
||||
def test_birdseye_layout_blocked_for_restricted(self):
|
||||
payload = json.dumps(
|
||||
{"front_door": {"x": 0, "y": 0, "width": 100, "height": 100}}
|
||||
)
|
||||
self.assertIsNone(
|
||||
self._materialize(_ws("house_only"), "birdseye_layout", payload)
|
||||
)
|
||||
|
||||
def test_birdseye_layout_visible_to_admin(self):
|
||||
payload = json.dumps(
|
||||
{"front_door": {"x": 0, "y": 0, "width": 100, "height": 100}}
|
||||
)
|
||||
self.assertIsNotNone(
|
||||
self._materialize(_ws("admin"), "birdseye_layout", payload)
|
||||
)
|
||||
|
||||
# --- Payload-camera ---
|
||||
|
||||
def test_events_filtered_by_payload_camera(self):
|
||||
payload = json.dumps({"after": {"camera": "garage"}})
|
||||
self.assertIsNone(self._materialize(_ws("house_only"), "events", payload))
|
||||
|
||||
payload = json.dumps({"after": {"camera": "front_door"}})
|
||||
self.assertIsNotNone(self._materialize(_ws("house_only"), "events", payload))
|
||||
|
||||
def test_events_with_missing_camera_dropped(self):
|
||||
payload = json.dumps({"after": {}})
|
||||
self.assertIsNone(self._materialize(_ws("house_only"), "events", payload))
|
||||
|
||||
def test_triggers_filtered_by_payload_camera(self):
|
||||
payload = json.dumps({"name": "t1", "camera": "garage"})
|
||||
self.assertIsNone(self._materialize(_ws("house_only"), "triggers", payload))
|
||||
|
||||
# --- Reshape: dict keyed by camera ---
|
||||
|
||||
def test_camera_activity_filtered_to_allowed_keys(self):
|
||||
payload = json.dumps(
|
||||
{
|
||||
"front_door": {"objects": 1},
|
||||
"back_door": {"objects": 0},
|
||||
"garage": {"objects": 2},
|
||||
}
|
||||
)
|
||||
message = self._materialize(_ws("house_only"), "camera_activity", payload)
|
||||
self.assertIsNotNone(message)
|
||||
envelope = json.loads(message) # type: ignore[arg-type]
|
||||
inner = json.loads(envelope["payload"])
|
||||
self.assertEqual(set(inner.keys()), {"front_door", "back_door"})
|
||||
self.assertNotIn("garage", inner)
|
||||
|
||||
def test_camera_activity_unchanged_for_admin(self):
|
||||
payload = json.dumps({"front_door": {}, "back_door": {}, "garage": {}})
|
||||
message = self._materialize(_ws("admin"), "camera_activity", payload)
|
||||
envelope = json.loads(message) # type: ignore[arg-type]
|
||||
self.assertEqual(envelope["payload"], payload)
|
||||
|
||||
def test_camera_activity_with_no_allowed_returns_none(self):
|
||||
payload = json.dumps({"garage": {"objects": 2}})
|
||||
self.assertIsNone(
|
||||
self._materialize(_ws("house_only"), "camera_activity", payload)
|
||||
)
|
||||
|
||||
def test_audio_detections_filtered_to_allowed_keys(self):
|
||||
payload = json.dumps({"front_door": {"bark": {}}, "garage": {"speech": {}}})
|
||||
message = self._materialize(_ws("house_only"), "audio_detections", payload)
|
||||
envelope = json.loads(message) # type: ignore[arg-type]
|
||||
inner = json.loads(envelope["payload"])
|
||||
self.assertEqual(set(inner.keys()), {"front_door"})
|
||||
|
||||
# --- Reshape: job_state ---
|
||||
|
||||
def test_job_state_admin_sees_full_payload(self):
|
||||
payload = json.dumps(
|
||||
{
|
||||
"motion_search": {"job_type": "motion_search", "camera": "garage"},
|
||||
"media_sync": {"job_type": "media_sync"},
|
||||
}
|
||||
)
|
||||
message = self._materialize(_ws("admin"), "job_state", payload)
|
||||
envelope = json.loads(message) # type: ignore[arg-type]
|
||||
self.assertEqual(envelope["payload"], payload)
|
||||
|
||||
def test_job_state_restricted_keeps_allowed_camera_jobs(self):
|
||||
"""Top-level camera field on a job entry: drop if not allowed."""
|
||||
payload = json.dumps(
|
||||
{
|
||||
"motion_search": {"job_type": "motion_search", "camera": "front_door"},
|
||||
"vlm_watch": {"job_type": "vlm_watch", "camera": "garage"},
|
||||
}
|
||||
)
|
||||
message = self._materialize(_ws("house_only"), "job_state", payload)
|
||||
envelope = json.loads(message) # type: ignore[arg-type]
|
||||
inner = json.loads(envelope["payload"])
|
||||
self.assertIn("motion_search", inner)
|
||||
self.assertNotIn("vlm_watch", inner)
|
||||
|
||||
def test_job_state_export_results_jobs_filtered_per_recipient(self):
|
||||
"""The aggregated export broadcast nests per-camera sub-jobs under
|
||||
``results.jobs``. Restricted users must only see allowed entries."""
|
||||
payload = json.dumps(
|
||||
{
|
||||
"export": {
|
||||
"job_type": "export",
|
||||
"status": "running",
|
||||
"results": {
|
||||
"jobs": [
|
||||
{"job_type": "export", "camera": "front_door", "id": "a"},
|
||||
{"job_type": "export", "camera": "garage", "id": "b"},
|
||||
{"job_type": "export", "camera": "back_door", "id": "c"},
|
||||
]
|
||||
},
|
||||
}
|
||||
}
|
||||
)
|
||||
message = self._materialize(_ws("house_only"), "job_state", payload)
|
||||
envelope = json.loads(message) # type: ignore[arg-type]
|
||||
inner = json.loads(envelope["payload"])
|
||||
self.assertIn("export", inner)
|
||||
kept_cameras = [j["camera"] for j in inner["export"]["results"]["jobs"]]
|
||||
self.assertEqual(kept_cameras, ["front_door", "back_door"])
|
||||
# Sibling fields like ``status`` must survive reshaping.
|
||||
self.assertEqual(inner["export"]["status"], "running")
|
||||
|
||||
def test_job_state_export_entry_dropped_when_no_jobs_allowed(self):
|
||||
payload = json.dumps(
|
||||
{
|
||||
"export": {
|
||||
"job_type": "export",
|
||||
"status": "running",
|
||||
"results": {
|
||||
"jobs": [
|
||||
{"job_type": "export", "camera": "garage", "id": "b"},
|
||||
]
|
||||
},
|
||||
}
|
||||
}
|
||||
)
|
||||
self.assertIsNone(self._materialize(_ws("house_only"), "job_state", payload))
|
||||
|
||||
# --- Reshape: stats ---
|
||||
|
||||
def _stats_payload(self) -> str:
|
||||
return json.dumps(
|
||||
{
|
||||
"cameras": {
|
||||
"front_door": {"camera_fps": 5.0, "pid": 1234},
|
||||
"back_door": {"camera_fps": 5.0, "pid": 1235},
|
||||
"garage": {"camera_fps": 5.0, "pid": 1236},
|
||||
},
|
||||
"detectors": {"cpu": {"detection_start": 0.0, "inference_speed": 10}},
|
||||
"service": {"uptime": 12345, "version": "0.16.0"},
|
||||
"camera_fps": 15.0,
|
||||
"detection_fps": 6.0,
|
||||
}
|
||||
)
|
||||
|
||||
def test_stats_admin_sees_full_payload(self):
|
||||
message = self._materialize(_ws("admin"), "stats", self._stats_payload())
|
||||
envelope = json.loads(message) # type: ignore[arg-type]
|
||||
self.assertEqual(envelope["payload"], self._stats_payload())
|
||||
|
||||
def test_stats_restricted_filters_camera_keys_but_keeps_aggregates(self):
|
||||
message = self._materialize(_ws("house_only"), "stats", self._stats_payload())
|
||||
envelope = json.loads(message) # type: ignore[arg-type]
|
||||
inner = json.loads(envelope["payload"])
|
||||
self.assertEqual(set(inner["cameras"].keys()), {"front_door", "back_door"})
|
||||
self.assertNotIn("garage", inner["cameras"])
|
||||
# Aggregates, detectors, and service block must survive.
|
||||
self.assertEqual(inner["camera_fps"], 15.0)
|
||||
self.assertEqual(inner["detection_fps"], 6.0)
|
||||
self.assertIn("detectors", inner)
|
||||
self.assertIn("service", inner)
|
||||
|
||||
def test_stats_restricted_with_no_allowed_cameras_still_sends_aggregates(self):
|
||||
"""A restricted role whose allow-list contains only nonexistent cameras
|
||||
still gets the global aggregates and service block."""
|
||||
config = _build_config(extra_roles={"empty_role": ["nonexistent"]})
|
||||
from frigate.comms.ws import _parse_json_payload
|
||||
|
||||
payload = self._stats_payload()
|
||||
all_cameras = set(config.cameras.keys())
|
||||
scope = _classify_outbound("stats", all_cameras, _collect_zone_names(config))
|
||||
full = json.dumps({"topic": "stats", "payload": payload})
|
||||
message = _materialize_for_ws(
|
||||
_ws("empty_role"),
|
||||
"stats",
|
||||
full,
|
||||
scope,
|
||||
_parse_json_payload(payload),
|
||||
config,
|
||||
)
|
||||
envelope = json.loads(message) # type: ignore[arg-type]
|
||||
inner = json.loads(envelope["payload"])
|
||||
self.assertEqual(inner["cameras"], {})
|
||||
self.assertEqual(inner["camera_fps"], 15.0)
|
||||
self.assertIn("service", inner)
|
||||
|
||||
def test_stats_without_cameras_key_passes_through(self):
|
||||
"""A malformed stats payload missing the cameras sub-dict shouldn't
|
||||
break delivery for restricted users — fall back to the full message."""
|
||||
payload = json.dumps({"detectors": {}, "service": {}, "detection_fps": 0.0})
|
||||
message = self._materialize(_ws("house_only"), "stats", payload)
|
||||
envelope = json.loads(message) # type: ignore[arg-type]
|
||||
self.assertEqual(envelope["payload"], payload)
|
||||
|
||||
def test_job_state_export_entry_unchanged_for_admin(self):
|
||||
payload = json.dumps(
|
||||
{
|
||||
"export": {
|
||||
"job_type": "export",
|
||||
"status": "running",
|
||||
"results": {
|
||||
"jobs": [
|
||||
{"job_type": "export", "camera": "garage", "id": "b"},
|
||||
]
|
||||
},
|
||||
}
|
||||
}
|
||||
)
|
||||
message = self._materialize(_ws("admin"), "job_state", payload)
|
||||
envelope = json.loads(message) # type: ignore[arg-type]
|
||||
self.assertEqual(envelope["payload"], payload)
|
||||
|
||||
def test_job_state_restricted_keeps_global_jobs(self):
|
||||
"""media_sync has no camera field; restricted users still see it."""
|
||||
payload = json.dumps(
|
||||
{"media_sync": {"job_type": "media_sync", "status": "running"}}
|
||||
)
|
||||
message = self._materialize(_ws("house_only"), "job_state", payload)
|
||||
envelope = json.loads(message) # type: ignore[arg-type]
|
||||
inner = json.loads(envelope["payload"])
|
||||
self.assertIn("media_sync", inner)
|
||||
|
||||
def test_job_state_debug_replay_nested_source_camera_filtered(self):
|
||||
"""debug_replay puts ``source_camera`` inside ``results`` (see
|
||||
jobs/debug_replay.py:to_dict). Restricted users must not receive
|
||||
entries whose nested source camera is unauthorized."""
|
||||
payload = json.dumps(
|
||||
{
|
||||
"debug_replay": {
|
||||
"id": "bd6dc99d-a7d",
|
||||
"job_type": "debug_replay",
|
||||
"status": "running",
|
||||
"start_time": 1.0,
|
||||
"end_time": None,
|
||||
"error_message": None,
|
||||
"results": {
|
||||
"current_step": "preparing_clip",
|
||||
"progress_percent": 0.0,
|
||||
"source_camera": "garage",
|
||||
"replay_camera_name": "_replay_garage",
|
||||
"start_ts": 0.0,
|
||||
"end_ts": 1.0,
|
||||
},
|
||||
}
|
||||
}
|
||||
)
|
||||
self.assertIsNone(self._materialize(_ws("house_only"), "job_state", payload))
|
||||
|
||||
def test_job_state_debug_replay_nested_source_camera_allowed(self):
|
||||
payload = json.dumps(
|
||||
{
|
||||
"debug_replay": {
|
||||
"id": "bd6dc99d-a7d",
|
||||
"job_type": "debug_replay",
|
||||
"status": "running",
|
||||
"results": {
|
||||
"source_camera": "front_door",
|
||||
"replay_camera_name": "_replay_front_door",
|
||||
},
|
||||
}
|
||||
}
|
||||
)
|
||||
message = self._materialize(_ws("house_only"), "job_state", payload)
|
||||
envelope = json.loads(message) # type: ignore[arg-type]
|
||||
inner = json.loads(envelope["payload"])
|
||||
self.assertIn("debug_replay", inner)
|
||||
self.assertEqual(
|
||||
inner["debug_replay"]["results"]["source_camera"], "front_door"
|
||||
)
|
||||
|
||||
|
||||
class _FakeManager:
|
||||
"""Minimal ws4py manager: holds clients and exposes a lock."""
|
||||
|
||||
def __init__(self, clients: list[Any]) -> None:
|
||||
self.lock = threading.Lock()
|
||||
self.websockets = {id(c): c for c in clients}
|
||||
|
||||
|
||||
class _FakeServer:
|
||||
def __init__(self, manager: _FakeManager) -> None:
|
||||
self.manager = manager
|
||||
|
||||
|
||||
class _CapturingWs(SimpleNamespace):
|
||||
"""Fake ws4py client that records what was sent."""
|
||||
|
||||
def __init__(self, role: str | None) -> None:
|
||||
environ = {} if role is None else {"HTTP_REMOTE_ROLE": role}
|
||||
super().__init__(environ=environ, terminated=False)
|
||||
self.sent: list[str] = []
|
||||
|
||||
def send(self, message: str) -> None: # noqa: D401 - matches ws4py API
|
||||
self.sent.append(message)
|
||||
|
||||
|
||||
class TestPublishEndToEnd(unittest.TestCase):
|
||||
"""Drive WebSocketClient.publish() against fake clients with different roles."""
|
||||
|
||||
def setUp(self):
|
||||
self.config = _build_config(
|
||||
extra_zones={"front_door": {"driveway": {"coordinates": "0,0,1,0,1,1,0,1"}}}
|
||||
)
|
||||
self.admin = _CapturingWs("admin")
|
||||
self.restricted = _CapturingWs("house_only")
|
||||
self.anon = _CapturingWs(None)
|
||||
self.client = WebSocketClient(self.config)
|
||||
self.client.websocket_server = _FakeServer(
|
||||
_FakeManager([self.admin, self.restricted, self.anon])
|
||||
)
|
||||
|
||||
def _payloads(self, ws: _CapturingWs) -> list[Any]:
|
||||
return [json.loads(m)["payload"] for m in ws.sent]
|
||||
|
||||
def test_global_topic_reaches_everyone(self):
|
||||
self.client.publish("model_state", "{}")
|
||||
self.assertEqual(len(self.admin.sent), 1)
|
||||
self.assertEqual(len(self.restricted.sent), 1)
|
||||
self.assertEqual(len(self.anon.sent), 1)
|
||||
|
||||
def test_camera_topic_filters_restricted_recipient(self):
|
||||
self.client.publish("garage/detect/state", "ON")
|
||||
self.assertEqual(len(self.admin.sent), 1)
|
||||
self.assertEqual(len(self.restricted.sent), 0)
|
||||
self.assertEqual(len(self.anon.sent), 0)
|
||||
|
||||
def test_camera_topic_allows_restricted_recipient_for_allowed_camera(self):
|
||||
self.client.publish("front_door/detect/state", "ON")
|
||||
self.assertEqual(len(self.admin.sent), 1)
|
||||
self.assertEqual(len(self.restricted.sent), 1)
|
||||
self.assertEqual(len(self.anon.sent), 0)
|
||||
|
||||
def test_events_payload_filtered(self):
|
||||
self.client.publish("events", json.dumps({"after": {"camera": "garage"}}))
|
||||
self.assertEqual(len(self.admin.sent), 1)
|
||||
self.assertEqual(len(self.restricted.sent), 0)
|
||||
|
||||
def test_camera_activity_reshaped_per_recipient(self):
|
||||
self.client.publish(
|
||||
"camera_activity",
|
||||
json.dumps(
|
||||
{
|
||||
"front_door": {"objects": 1},
|
||||
"back_door": {"objects": 0},
|
||||
"garage": {"objects": 2},
|
||||
}
|
||||
),
|
||||
)
|
||||
self.assertEqual(len(self.admin.sent), 1)
|
||||
admin_inner = json.loads(self._payloads(self.admin)[0])
|
||||
self.assertEqual(set(admin_inner.keys()), {"front_door", "back_door", "garage"})
|
||||
|
||||
self.assertEqual(len(self.restricted.sent), 1)
|
||||
restricted_inner = json.loads(self._payloads(self.restricted)[0])
|
||||
self.assertEqual(set(restricted_inner.keys()), {"front_door", "back_door"})
|
||||
|
||||
self.assertEqual(len(self.anon.sent), 0)
|
||||
|
||||
def test_birdseye_layout_blocked_for_restricted_and_anon(self):
|
||||
self.client.publish(
|
||||
"birdseye_layout",
|
||||
json.dumps({"front_door": {"x": 0, "y": 0, "width": 1, "height": 1}}),
|
||||
)
|
||||
self.assertEqual(len(self.admin.sent), 1)
|
||||
self.assertEqual(len(self.restricted.sent), 0)
|
||||
self.assertEqual(len(self.anon.sent), 0)
|
||||
|
||||
def test_zone_aggregate_blocked_for_restricted(self):
|
||||
self.client.publish("driveway/person", 2)
|
||||
self.assertEqual(len(self.admin.sent), 1)
|
||||
self.assertEqual(len(self.restricted.sent), 0)
|
||||
|
||||
def test_stats_reshaped_per_recipient(self):
|
||||
self.client.publish(
|
||||
"stats",
|
||||
json.dumps(
|
||||
{
|
||||
"cameras": {
|
||||
"front_door": {"camera_fps": 5.0},
|
||||
"garage": {"camera_fps": 5.0},
|
||||
},
|
||||
"service": {"uptime": 1},
|
||||
"camera_fps": 10.0,
|
||||
}
|
||||
),
|
||||
)
|
||||
self.assertEqual(len(self.admin.sent), 1)
|
||||
admin_inner = json.loads(self._payloads(self.admin)[0])
|
||||
self.assertEqual(set(admin_inner["cameras"].keys()), {"front_door", "garage"})
|
||||
|
||||
self.assertEqual(len(self.restricted.sent), 1)
|
||||
restricted_inner = json.loads(self._payloads(self.restricted)[0])
|
||||
self.assertEqual(set(restricted_inner["cameras"].keys()), {"front_door"})
|
||||
self.assertEqual(restricted_inner["camera_fps"], 10.0)
|
||||
self.assertIn("service", restricted_inner)
|
||||
|
||||
# Stats requires a role; anonymous gets nothing.
|
||||
self.assertEqual(len(self.anon.sent), 0)
|
||||
|
||||
def test_export_job_state_filters_results_jobs_per_recipient(self):
|
||||
self.client.publish(
|
||||
"job_state",
|
||||
json.dumps(
|
||||
{
|
||||
"export": {
|
||||
"job_type": "export",
|
||||
"status": "running",
|
||||
"results": {
|
||||
"jobs": [
|
||||
{"camera": "front_door", "id": "a"},
|
||||
{"camera": "garage", "id": "b"},
|
||||
]
|
||||
},
|
||||
}
|
||||
}
|
||||
),
|
||||
)
|
||||
self.assertEqual(len(self.admin.sent), 1)
|
||||
admin_inner = json.loads(self._payloads(self.admin)[0])
|
||||
self.assertEqual(
|
||||
[j["camera"] for j in admin_inner["export"]["results"]["jobs"]],
|
||||
["front_door", "garage"],
|
||||
)
|
||||
|
||||
self.assertEqual(len(self.restricted.sent), 1)
|
||||
restricted_inner = json.loads(self._payloads(self.restricted)[0])
|
||||
self.assertEqual(
|
||||
[j["camera"] for j in restricted_inner["export"]["results"]["jobs"]],
|
||||
["front_door"],
|
||||
)
|
||||
|
||||
def test_unknown_topic_dropped_for_everyone(self):
|
||||
self.client.publish("some_rogue_topic", "data")
|
||||
self.assertEqual(self.admin.sent, [])
|
||||
self.assertEqual(self.restricted.sent, [])
|
||||
self.assertEqual(self.anon.sent, [])
|
||||
|
||||
def test_terminated_client_is_skipped(self):
|
||||
self.restricted.terminated = True
|
||||
self.client.publish("front_door/detect/state", "ON")
|
||||
self.assertEqual(len(self.admin.sent), 1)
|
||||
self.assertEqual(len(self.restricted.sent), 0)
|
||||
|
||||
|
||||
if __name__ == "__main__":
|
||||
unittest.main()
|
||||
@ -138,7 +138,7 @@
|
||||
"plucked_string_instrument": "Instrument de corda pinçada",
|
||||
"guitar": "Guitarra",
|
||||
"electric_guitar": "Guitarra elèctrica",
|
||||
"bass_guitar": "Baix",
|
||||
"bass_guitar": "Guitarra baixa",
|
||||
"acoustic_guitar": "Guitarra acústica",
|
||||
"steel_guitar": "Guitarra steel",
|
||||
"tapping": "Tapping",
|
||||
|
||||
@ -49,7 +49,8 @@
|
||||
"gl": "Galego (Gallec)",
|
||||
"id": "Bahasa Indonesia (Indonesi)",
|
||||
"ur": "اردو (Urdú)",
|
||||
"hr": "Hrvatski (croat)"
|
||||
"hr": "Hrvatski (croat)",
|
||||
"bs": "Bosanski (Bosni)"
|
||||
},
|
||||
"system": "Sistema",
|
||||
"systemMetrics": "Mètriques del sistema",
|
||||
|
||||
@ -33,7 +33,11 @@
|
||||
},
|
||||
"filters": {
|
||||
"label": "Filtres d'àudio",
|
||||
"description": "Paràmetres de filtre per-àudio-tipus, com ara llindars de confiança utilitzats per reduir falsos positius."
|
||||
"description": "Paràmetres de filtre per-àudio-tipus, com ara llindars de confiança utilitzats per reduir falsos positius.",
|
||||
"threshold": {
|
||||
"label": "Confiança mínima de l'àudio",
|
||||
"description": "Llindar mínim de confiança per a l'esdeveniment d'àudio a comptar."
|
||||
}
|
||||
},
|
||||
"enabled_in_config": {
|
||||
"label": "Estat d'àudio original",
|
||||
|
||||
@ -258,6 +258,41 @@
|
||||
},
|
||||
"raw_mask": {
|
||||
"label": "Màscara en brut"
|
||||
},
|
||||
"filters_attribute": {
|
||||
"label": "Filtres d'atribut",
|
||||
"description": "Filtres aplicats als atributs detectats per reduir falsos positius (àrea, relació, confiança).",
|
||||
"min_area": {
|
||||
"label": "Àrea mínima de l'atribut",
|
||||
"description": "Es requereix una àrea de caixa contenidora mínima (píxels o percentatge) per a aquest atribut. Pot ser píxels (int) o percentatge (float entre 0,000001 i 0.99)."
|
||||
},
|
||||
"max_area": {
|
||||
"label": "Àrea màxima de l'atribut",
|
||||
"description": "Es permet l'àrea màxima del contenidor (píxels o percentatge) per a aquest atribut. Pot ser píxels (int) o percentatge (float entre 0,000001 i 0.99)."
|
||||
},
|
||||
"min_ratio": {
|
||||
"label": "Relació mínima d'aspecte",
|
||||
"description": "Relació mínima d'amplada/alçada requerida per a la casella contenidora a qualificar."
|
||||
},
|
||||
"max_ratio": {
|
||||
"label": "Relació màxima d'aspecte",
|
||||
"description": "Es permet la relació màxima d'amplada/alçada per a la casella contenidora a qualificar."
|
||||
},
|
||||
"threshold": {
|
||||
"label": "Llindar de confiança",
|
||||
"description": "Es requereix un llindar de confiança mitjà per a la detecció perquè l'atribut es consideri un veritable positiu."
|
||||
},
|
||||
"min_score": {
|
||||
"label": "Confiança mínima",
|
||||
"description": "Es requereix una confiança mínima de detecció d'un sol fotograma per a associar aquest atribut amb el seu objecte pare."
|
||||
},
|
||||
"mask": {
|
||||
"label": "Màscara de filtre",
|
||||
"description": "Coordenades de polígon que defineixen on s'aplica aquest filtre dins del marc."
|
||||
},
|
||||
"raw_mask": {
|
||||
"label": "Màscara en brut"
|
||||
}
|
||||
}
|
||||
},
|
||||
"record": {
|
||||
@ -1987,7 +2022,11 @@
|
||||
},
|
||||
"filters": {
|
||||
"label": "Filtres d'àudio",
|
||||
"description": "Paràmetres de filtre per-àudio-tipus, com ara llindars de confiança utilitzats per reduir falsos positius."
|
||||
"description": "Paràmetres de filtre per-àudio-tipus, com ara llindars de confiança utilitzats per reduir falsos positius.",
|
||||
"threshold": {
|
||||
"label": "Confiança mínima de l'àudio",
|
||||
"description": "Llindar mínim de confiança per a l'esdeveniment d'àudio a comptar."
|
||||
}
|
||||
},
|
||||
"enabled_in_config": {
|
||||
"label": "Estat d'àudio original",
|
||||
@ -2207,7 +2246,7 @@
|
||||
},
|
||||
"match_distance": {
|
||||
"label": "Distància de la coincidència",
|
||||
"description": "Nombre de desajustos de caràcters permesos quan es comparen les plaques detectades amb les plaques conegudes."
|
||||
"description": "Nombre de discrepàncies de caràcters permesos en comparar les plaques detectades amb les plaques conegudes."
|
||||
},
|
||||
"known_plates": {
|
||||
"label": "Matricules conegudes",
|
||||
|
||||
@ -121,5 +121,10 @@
|
||||
"royal_mail": "Royal Mail",
|
||||
"school_bus": "Bus escolar",
|
||||
"skunk": "Mofeta",
|
||||
"kangaroo": "Cangur"
|
||||
"kangaroo": "Cangur",
|
||||
"baby": "Nadó",
|
||||
"baby_stroller": "Cotxet",
|
||||
"rickshaw": "Ricksaw",
|
||||
"Rodent": "Rosegador",
|
||||
"rodent": "Rosegador"
|
||||
}
|
||||
|
||||
@ -42,5 +42,28 @@
|
||||
"show_camera_status": "Quin és l'estat actual de les meves càmeres?",
|
||||
"recap": "Què va passar mentre jo era fora?",
|
||||
"watch_camera": "Vigila la porta d'entrada i fes-me saber si algú apareix"
|
||||
},
|
||||
"new_chat": "Xat nou",
|
||||
"settings": {
|
||||
"title": "Configuració del xat",
|
||||
"show_stats": {
|
||||
"title": "Mostra les estadístiques",
|
||||
"desc": "Mostra la velocitat de generació i la mida del context per a les respostes del xat.",
|
||||
"while_generating": "En generar",
|
||||
"always": "Sempre"
|
||||
},
|
||||
"auto_scroll": {
|
||||
"title": "Desplaçament automàtic",
|
||||
"desc": "Segueix els missatges nous a mesura que arriben."
|
||||
}
|
||||
},
|
||||
"stats": {
|
||||
"context": "{{tokens}} tokens",
|
||||
"tokens_per_second": "{{rate}} t/s"
|
||||
},
|
||||
"reasoning": {
|
||||
"active": "Raonant…",
|
||||
"show": "Mostra el raonament",
|
||||
"hide": "Amaga el raonament"
|
||||
}
|
||||
}
|
||||
|
||||
@ -14,7 +14,11 @@
|
||||
"empty": "No hi ha intents recents de reconeixement de rostres",
|
||||
"title": "Reconeixements recents",
|
||||
"aria": "Selecciona els reconeixements recents",
|
||||
"titleShort": "Recent"
|
||||
"titleShort": "Recent",
|
||||
"emptyNoLibrary": {
|
||||
"title": "Puja una cara",
|
||||
"description": "Heu d'afegir com a mínim una cara a la biblioteca perquè el reconeixement de la cara funcioni."
|
||||
}
|
||||
},
|
||||
"description": {
|
||||
"addFace": "Afegiu una col·lecció nova a la biblioteca de cares pujant la vostra primera imatge.",
|
||||
|
||||
@ -15,7 +15,8 @@
|
||||
"globalConfig": "Configuració global - Frigate",
|
||||
"cameraConfig": "Configuració de la càmera - Frigate",
|
||||
"maintenance": "Manteniment - Frigate",
|
||||
"profiles": "Perfils - Frigate"
|
||||
"profiles": "Perfils - Frigate",
|
||||
"detectorsAndModel": "Detectors i model - Frigate"
|
||||
},
|
||||
"menu": {
|
||||
"ui": "Interfície d'usuari",
|
||||
@ -90,7 +91,8 @@
|
||||
"regionGrid": "Quadrícula de la regió",
|
||||
"uiSettings": "Paràmetres de la IU",
|
||||
"profiles": "Perfils",
|
||||
"systemGo2rtcStreams": "go2rtc streams"
|
||||
"systemGo2rtcStreams": "go2rtc streams",
|
||||
"systemDetectorsAndModel": "Detectors i model"
|
||||
},
|
||||
"dialog": {
|
||||
"unsavedChanges": {
|
||||
@ -526,7 +528,7 @@
|
||||
},
|
||||
"title": "Afinador de detecció de moviment",
|
||||
"toast": {
|
||||
"success": "Els ajustos de la detecció de moviment s'han desat."
|
||||
"success": "S'han desat els paràmetres del moviment."
|
||||
},
|
||||
"unsavedChanges": "Canvis no desats en l'ajust de moviment {{camera}}"
|
||||
},
|
||||
@ -724,7 +726,7 @@
|
||||
"trainDate": "Data d'entrenament",
|
||||
"title": "Informació del model",
|
||||
"supportedDetectors": "Detectors compatibles",
|
||||
"availableModels": "Models disponibles",
|
||||
"availableModels": "Models Frigate+ disponibles",
|
||||
"cameras": "Càmeres",
|
||||
"plusModelType": {
|
||||
"userModel": "Afinat",
|
||||
@ -733,7 +735,15 @@
|
||||
"loadingAvailableModels": "Carregant models disponibles…",
|
||||
"loading": "Carregant informació del model…",
|
||||
"error": "No s'ha pogut carregar la informació del model",
|
||||
"modelSelect": "Els models disponibles a Frigate+ es poden seleccionar aquí. Tingues en compte que només es poden triar els models compatibles amb la configuració actual del detector."
|
||||
"modelSelect": "Els models disponibles a Frigate+ es poden seleccionar aquí. Tingues en compte que només es poden triar els models compatibles amb la configuració actual del detector.",
|
||||
"noModelLoaded": "Actualment no s'ha carregat cap model Frigate+.",
|
||||
"selectModel": "Selecciona un model",
|
||||
"noModelsAvailable": "No hi ha models disponibles",
|
||||
"filter": {
|
||||
"ariaLabel": "Filtra els models per tipus",
|
||||
"baseModels": "Models de base",
|
||||
"fineTunedModels": "Models ajustats"
|
||||
}
|
||||
},
|
||||
"apiKey": {
|
||||
"plusLink": "Llegeix més sobre Frigate+",
|
||||
@ -755,7 +765,8 @@
|
||||
"currentModel": "Model actual",
|
||||
"otherModels": "Altres models",
|
||||
"configuration": "Configuració"
|
||||
}
|
||||
},
|
||||
"changeInDetectorsAndModel": "Canviar model"
|
||||
},
|
||||
"enrichments": {
|
||||
"semanticSearch": {
|
||||
@ -1295,7 +1306,7 @@
|
||||
"title": "Habilita / Inhabilita les càmeres",
|
||||
"desc": "Inhabilita temporalment una càmera fins que es reiniciï la fragata. La inhabilitació d'una càmera atura completament el processament de Frigate dels fluxos d'aquesta càmera. La detecció, l'enregistrament i la depuració no estaran disponibles.<br /> <em>Nota: això no desactiva les retransmissions de go2rtc.</em>",
|
||||
"enableLabel": "Càmeres habilitades",
|
||||
"enableDesc": "Inhabilita temporalment una càmera habilitada fins que es reiniciï Frigate. La inhabilitació d'una càmera atura completament el processament de Frigate dels fluxos d'aquesta càmera. La detecció, l'enregistrament i la depuració no estaran disponibles.<br /> <em>Nota: això no desactiva les retransmissions de go2rtc.</em>",
|
||||
"enableDesc": "Inhabilita temporalment una càmera habilitada fins que es reiniciï Frigate. La inhabilitació d'una càmera atura completament el processament de Frigate dels fluxos d'aquesta càmera. La detecció, l'enregistrament i la depuració no estaran disponibles.<br /> <em>Nota: això no inhabilita els restreams go2rtc.</em><br /><br />Drag el handle per reordenar les càmeres tal com apareixen a la interfície d'usuari. L'ordre de les càmeres habilitades es reflectirà en tota la interfície d'usuari, incloent el tauler en viu i els desplegables de selecció de càmeres.",
|
||||
"disableLabel": "Càmeres inhabilitades",
|
||||
"disableDesc": "Habilita una càmera que actualment no és visible a la interfície d'usuari i està desactivada a la configuració. Es requereix un reinici de Frigate després d'activar-la.",
|
||||
"enableSuccess": "{{cameraName}} activat a la configuració. Reinicia Frigate per aplicar els canvis.",
|
||||
@ -1304,7 +1315,10 @@
|
||||
"title": "Edita el nom de la pantalla",
|
||||
"description": "Estableix el nom amigable que es mostra per a aquesta càmera a tota la interfície d'usuari de la Fragata. Deixeu-ho en blanc per utilitzar l'ID de la càmera.",
|
||||
"rename": "Canvia el nom"
|
||||
}
|
||||
},
|
||||
"reorderHandle": "Arrossega per reordenar",
|
||||
"saving": "S'està desant…",
|
||||
"saved": "Desat"
|
||||
},
|
||||
"cameraConfig": {
|
||||
"add": "Afegeix una càmera",
|
||||
@ -1362,7 +1376,8 @@
|
||||
"dedicatedLpr": "LPR dedicat",
|
||||
"saveSuccess": "Tipus de càmera actualitzat per {{cameraName}}. Reinicia la fragata per aplicar els canvis.",
|
||||
"normal": "Normal"
|
||||
}
|
||||
},
|
||||
"description": "Afegiu, editeu i suprimiu les càmeres, controleu quines càmeres estan habilitades, i configureu les superposicions per perfil i tipus de càmera. Per a configurar fluxos, detecció, moviment i altres paràmetres específics de la càmera, trieu la secció específica a Configuració de la càmera."
|
||||
},
|
||||
"cameraReview": {
|
||||
"object_descriptions": {
|
||||
@ -1661,7 +1676,9 @@
|
||||
"options": {
|
||||
"embeddings": "Incrustació",
|
||||
"vision": "Visió",
|
||||
"tools": "Eines"
|
||||
"tools": "Eines",
|
||||
"descriptions": "Descripcions",
|
||||
"chat": "Xat"
|
||||
}
|
||||
},
|
||||
"semanticSearchModel": {
|
||||
@ -1718,7 +1735,10 @@
|
||||
"saveAllPartial_many": "{{successCount}} de {{totalCount}} seccions desades. {{failCount}} ha fallat.",
|
||||
"saveAllPartial_other": "{{successCount}} de {{totalCount}} seccions desades. {{failCount}} ha fallat.",
|
||||
"saveAllFailure": "Ha fallat en desar totes les seccions.",
|
||||
"applied": "La configuració s'ha aplicat correctament"
|
||||
"applied": "La configuració s'ha aplicat correctament",
|
||||
"saveAllSuccessRestartRequired_one": "S'ha desat la secció {{count}} correctament. Reinicia la fragata per aplicar els canvis.",
|
||||
"saveAllSuccessRestartRequired_many": "Totes les {{count}} seccions s'han desat correctament. Reinicia la fragata per aplicar els canvis.",
|
||||
"saveAllSuccessRestartRequired_other": "Totes les {{count}} seccions s'han desat correctament. Reinicia la fragata per aplicar els canvis."
|
||||
},
|
||||
"unsavedChanges": "Teniu canvis sense desar",
|
||||
"confirmReset": "Confirma el restabliment",
|
||||
@ -1743,7 +1763,15 @@
|
||||
"othersField_many": "{{count}} altres",
|
||||
"othersField_other": "{{count}} altres",
|
||||
"profilePrefix": "Perfil {{profile}}: {{fields}}"
|
||||
}
|
||||
},
|
||||
"overriddenGlobalHeading_one": "Aquesta càmera substitueix el camp {{count}} de la configuració global:",
|
||||
"overriddenGlobalHeading_many": "Aquesta càmera anul·la {{count}} camps de la configuració global:",
|
||||
"overriddenGlobalHeading_other": "Aquesta càmera anul·la {{count}} camps de la configuració global:",
|
||||
"overriddenGlobalNoDeltas": "Aquesta càmera anul·la la configuració global, però no hi ha valors de camp diferents.",
|
||||
"overriddenBaseConfigHeading_one": "El perfil {{profile}} substitueix el camp {{count}} de la configuració base:",
|
||||
"overriddenBaseConfigHeading_many": "El perfil {{profile}} substitueix {{count}} camps de la configuració base:",
|
||||
"overriddenBaseConfigHeading_other": "El perfil {{profile}} substitueix {{count}} camps de la configuració base:",
|
||||
"overriddenBaseConfigNoDeltas": "El perfil {{profile}} substitueix aquesta secció, però no hi ha valors de camp diferents de la configuració base."
|
||||
},
|
||||
"profiles": {
|
||||
"title": "Perfils",
|
||||
@ -1827,8 +1855,17 @@
|
||||
"audioMp3": "Transcodifica a MP3",
|
||||
"audioExclude": "Exclou",
|
||||
"hardwareNone": "Sense acceleració de hardware",
|
||||
"hardwareAuto": "Acceleració de hardware automàtica"
|
||||
}
|
||||
"hardwareAuto": "Automàtic (recomanat)",
|
||||
"addVideoCodec": "Afegeix un còdec de vídeo",
|
||||
"addAudioCodec": "Afegeix un còdec d'àudio",
|
||||
"removeCodec": "Elimina el còdec",
|
||||
"hardwareVaapi": "VAAPI",
|
||||
"hardwareCuda": "CUDA",
|
||||
"hardwareV4l2m2m": "V4L2 M2M",
|
||||
"hardwareDxva2": "DXVA2",
|
||||
"hardwareVideotoolbox": "VideoToolbox"
|
||||
},
|
||||
"streamNumber": "Flux {{index}}"
|
||||
},
|
||||
"timestampPosition": {
|
||||
"tl": "A dalt a l'esquerra",
|
||||
@ -1838,7 +1875,14 @@
|
||||
},
|
||||
"onvif": {
|
||||
"profileAuto": "Automàtic",
|
||||
"profileLoading": "S'estan carregant perfils..."
|
||||
"profileLoading": "S'estan carregant perfils...",
|
||||
"autotracking": {
|
||||
"zooming": {
|
||||
"disabled": "Desactivat",
|
||||
"absolute": "Absolut",
|
||||
"relative": "Relatiu"
|
||||
}
|
||||
}
|
||||
},
|
||||
"configMessages": {
|
||||
"review": {
|
||||
@ -1886,5 +1930,104 @@
|
||||
"semanticSearch": {
|
||||
"jinav2SmallModelSize": "La mida 'petita' amb el model Jina V2 té un alt cost de RAM i d'inferència. Es recomana el model 'gran' amb una GPU discreta."
|
||||
}
|
||||
},
|
||||
"modelSize": {
|
||||
"large": "Gran",
|
||||
"small": "Petit"
|
||||
},
|
||||
"birdseye": {
|
||||
"trackingMode": {
|
||||
"objects": "Objectes",
|
||||
"motion": "Moviment",
|
||||
"continuous": "Continu"
|
||||
},
|
||||
"cameraOrder": {
|
||||
"label": "Ordre de la càmera",
|
||||
"description": "Arrossega les càmeres per establir el seu ordre en la disposició Birdseye.",
|
||||
"reorderHandle": "Arrossega per reordenar",
|
||||
"saving": "S'està desant…",
|
||||
"saved": "Desat"
|
||||
}
|
||||
},
|
||||
"snapshot": {
|
||||
"retainMode": {
|
||||
"all": "Tots",
|
||||
"motion": "Moviment",
|
||||
"active_objects": "Objectes Actius"
|
||||
}
|
||||
},
|
||||
"ui": {
|
||||
"timeFormat": {
|
||||
"browser": "Visor",
|
||||
"12hour": "12 hores",
|
||||
"24hour": "24 hores"
|
||||
},
|
||||
"TimeOrDateStyle": {
|
||||
"full": "Complet",
|
||||
"long": "Llarg",
|
||||
"medium": "Mitjà",
|
||||
"short": "Curt"
|
||||
},
|
||||
"unitSystem": {
|
||||
"metric": "Métric",
|
||||
"imperial": "Imperial"
|
||||
}
|
||||
},
|
||||
"review": {
|
||||
"imageSource": {
|
||||
"recordings": "Gravacions",
|
||||
"previews": "Previsualitzacions"
|
||||
}
|
||||
},
|
||||
"logger": {
|
||||
"logLevel": {
|
||||
"debug": "Depurar",
|
||||
"info": "Informació",
|
||||
"warning": "Avís",
|
||||
"error": "Error",
|
||||
"critical": "Crític"
|
||||
}
|
||||
},
|
||||
"retainMode": {
|
||||
"all": "Tots",
|
||||
"motion": "Moviment",
|
||||
"active_objects": "Objectes actius"
|
||||
},
|
||||
"previewQuality": {
|
||||
"very_high": "Molt alta",
|
||||
"high": "Alta",
|
||||
"medium": "Mitja",
|
||||
"low": "Baix",
|
||||
"very_low": "Molt baix"
|
||||
},
|
||||
"detectorsAndModel": {
|
||||
"restartRequired": "Reinici requerit (canvi en detector o model)",
|
||||
"title": "Detectors i model",
|
||||
"description": "Configuri el detector final que corre la detecció d'objectes i el model que usa. Els canvis es gravaràn junts i així el detector i el model estan sincronitzats.",
|
||||
"cardTitles": {
|
||||
"detector": "Detector Hardware",
|
||||
"model": "Model de detecció"
|
||||
},
|
||||
"tabs": {
|
||||
"plus": "Frigate+",
|
||||
"custom": "Model personalitzat"
|
||||
},
|
||||
"mismatch": {
|
||||
"warning": "El model actual de Frigate+ \"{{model}}\" requereix el detector {{required}}. Selecciona un model compatible a baix o canvía e model personalitzat abans de gravar."
|
||||
},
|
||||
"plusModel": {
|
||||
"requiresDetector": "Requereix: {{detector}}",
|
||||
"noModelSelected": "Selecciona un model Frigate+"
|
||||
},
|
||||
"toast": {
|
||||
"saveSuccess": "Configuració de detectors i model guardats. Reinicia Frigate per aplicar els canvis.",
|
||||
"saveError": "Fallo en gravar la configuració de detector i model"
|
||||
},
|
||||
"unsavedChanges": "Canvis de detector i model no gravats"
|
||||
},
|
||||
"menuDot": {
|
||||
"overrideGlobal": "Aquesta secció substitueix la configuració global",
|
||||
"overrideProfile": "Aquesta secció està substituïda pel perfil {{profile}}",
|
||||
"unsaved": "Aquesta secció té canvis sense desar"
|
||||
}
|
||||
}
|
||||
|
||||
@ -192,7 +192,8 @@
|
||||
"bg": "Български (bulgarisch)",
|
||||
"gl": "Galego (Galicisch)",
|
||||
"id": "Bahasa Indonesia (Indonesisch)",
|
||||
"hr": "Hrvatski (Kroatisch)"
|
||||
"hr": "Hrvatski (Kroatisch)",
|
||||
"bs": "Bosnisch"
|
||||
},
|
||||
"appearance": "Erscheinung",
|
||||
"theme": {
|
||||
|
||||
@ -25,7 +25,11 @@
|
||||
},
|
||||
"filters": {
|
||||
"label": "Audiofilter",
|
||||
"description": "Filtereinstellungen pro Audiotyp, wie z. B. Konfidenzschwellenwerte, die zur Reduzierung von Fehlalarmen verwendet werden."
|
||||
"description": "Filtereinstellungen pro Audiotyp, wie z. B. Konfidenzschwellenwerte, die zur Reduzierung von Fehlalarmen verwendet werden.",
|
||||
"threshold": {
|
||||
"label": "Mindestvertrauensgrad für Audio",
|
||||
"description": "Mindestschwellenwert für die Zuverlässigkeit, damit das Audioereignis gezählt wird."
|
||||
}
|
||||
},
|
||||
"max_not_heard": {
|
||||
"label": "Ende Timeout",
|
||||
|
||||
@ -23,7 +23,11 @@
|
||||
},
|
||||
"filters": {
|
||||
"label": "Audiofilter",
|
||||
"description": "Filtereinstellungen pro Audiotyp, wie z. B. Konfidenzschwellenwerte, die zur Reduzierung von Fehlalarmen verwendet werden."
|
||||
"description": "Filtereinstellungen pro Audiotyp, wie z. B. Konfidenzschwellenwerte, die zur Reduzierung von Fehlalarmen verwendet werden.",
|
||||
"threshold": {
|
||||
"label": "Mindestvertrauensgrad für Audio",
|
||||
"description": "Mindestschwellenwert für die Zuverlässigkeit, damit das Audioereignis gezählt wird."
|
||||
}
|
||||
},
|
||||
"max_not_heard": {
|
||||
"label": "Ende Timeout",
|
||||
|
||||
@ -121,5 +121,9 @@
|
||||
"royal_mail": "Royal-Mail",
|
||||
"school_bus": "Schulbus",
|
||||
"skunk": "Stinktier",
|
||||
"kangaroo": "Känguruh"
|
||||
"kangaroo": "Känguruh",
|
||||
"baby": "Baby",
|
||||
"baby_stroller": "Kinderwagen",
|
||||
"rickshaw": "Rikscha",
|
||||
"rodent": "Nagetier"
|
||||
}
|
||||
|
||||
@ -42,5 +42,23 @@
|
||||
"show_camera_status": "Wie ist der aktuelle Status meiner Kameras?",
|
||||
"recap": "Was ist passiert, während ich weg war?",
|
||||
"watch_camera": "Pass auf die Haustür auf und sag mir Bescheid, wenn jemand kommt"
|
||||
},
|
||||
"new_chat": "Neuer Chat",
|
||||
"settings": {
|
||||
"title": "Chat Einstellung",
|
||||
"show_stats": {
|
||||
"title": "Statistiken anzeigen",
|
||||
"desc": "Generierungsrate und Kontextgröße für Chat-Antworten anzeigen.",
|
||||
"while_generating": "Während der Erstellung",
|
||||
"always": "Immer"
|
||||
},
|
||||
"auto_scroll": {
|
||||
"title": "Auto scrollen",
|
||||
"desc": "Verfolgen Sie neue Nachrichten, sobald sie eintreffen."
|
||||
}
|
||||
},
|
||||
"stats": {
|
||||
"context": "{{tokens}} tokens",
|
||||
"tokens_per_second": "{{rate}} t/s"
|
||||
}
|
||||
}
|
||||
|
||||
@ -48,7 +48,11 @@
|
||||
"title": "Neueste Erkennungen",
|
||||
"aria": "Wähle aktuelle Erkennungen",
|
||||
"empty": "Es gibt keine aktuellen Versuche zur Gesichtserkennung",
|
||||
"titleShort": "frisch"
|
||||
"titleShort": "frisch",
|
||||
"emptyNoLibrary": {
|
||||
"title": "Gesicht hinzufügen",
|
||||
"description": "Sie müssen mindestens ein Gesicht zur Bibliothek hinzufügen, damit die Gesichtserkennung funktioniert."
|
||||
}
|
||||
},
|
||||
"deleteFaceLibrary": {
|
||||
"title": "Lösche Name",
|
||||
|
||||
@ -803,7 +803,15 @@
|
||||
"availableModels": "Verfügbare Modelle",
|
||||
"loadingAvailableModels": "Lade verfügbare Modelle…",
|
||||
"baseModel": "Basis Model",
|
||||
"title": "Model Informationen"
|
||||
"title": "Model Informationen",
|
||||
"noModelLoaded": "Derzeit ist kein „Frigate+“-Modell geladen.",
|
||||
"selectModel": "Wählen Sie ein Modell aus",
|
||||
"noModelsAvailable": "Keine Modelle verfügbar",
|
||||
"filter": {
|
||||
"ariaLabel": "Modelle nach Typ filtern",
|
||||
"baseModels": "Basismodelle",
|
||||
"fineTunedModels": "Optimierte Modelle"
|
||||
}
|
||||
},
|
||||
"toast": {
|
||||
"error": "Speichern der Konfigurationsänderungen fehlgeschlagen: {{errorMessage}}",
|
||||
@ -1415,7 +1423,8 @@
|
||||
"normal": "Normal",
|
||||
"dedicatedLpr": "Spezielles LPR-System",
|
||||
"saveSuccess": "Der Kameratyp für {{cameraName}} wurde aktualisiert. Starte Frigate neu, um die Änderungen zu übernehmen."
|
||||
}
|
||||
},
|
||||
"description": "Fügen Sie Kameras hinzu, bearbeiten und löschen Sie sie, legen Sie fest, welche Kameras aktiviert sind, und konfigurieren Sie profil- und kameratypabhängige Übersteuerungen. Um Streams, Erkennung, Bewegung und andere kameraspezifische Einstellungen zu konfigurieren, wählen Sie den entsprechenden Abschnitt unter „Kamerakonfiguration“ aus."
|
||||
},
|
||||
"cameraReview": {
|
||||
"title": "Kamera-Einstellungen überprüfen",
|
||||
@ -1489,7 +1498,13 @@
|
||||
"othersField_one": "{{count}} andere",
|
||||
"othersField_other": "{{count}} weitere",
|
||||
"profilePrefix": "{{profile}} Profile: {{fields}}"
|
||||
}
|
||||
},
|
||||
"overriddenGlobalHeading_one": "Diese Kamera überschreibt das Feld {{count}} aus der globalen Konfiguration:",
|
||||
"overriddenGlobalHeading_other": "Diese Kamera überschreibt alle Felder {{count}} aus der globalen Konfiguration:",
|
||||
"overriddenGlobalNoDeltas": "Diese Kamera überschreibt die globale Konfiguration, es gibt jedoch keine Abweichungen bei den Feldwerten.",
|
||||
"overriddenBaseConfigHeading_one": "Das Profil {{profile}} überschreibt das Feld {{count}} aus der Basiskonfiguration:",
|
||||
"overriddenBaseConfigHeading_other": "Das Profil {{profile}} überschreibt di Felder {{count}} aus der Basiskonfiguration:",
|
||||
"overriddenBaseConfigNoDeltas": "Das Profil {{profile}} überschreibt diesen Abschnitt, jedoch weichen keine Feldwerte von der Basiskonfiguration ab."
|
||||
},
|
||||
"timestampPosition": {
|
||||
"tl": "Oben links",
|
||||
@ -1726,7 +1741,9 @@
|
||||
"options": {
|
||||
"embeddings": "Einbetten",
|
||||
"vision": "Vision",
|
||||
"tools": "Werkzeuge"
|
||||
"tools": "Werkzeuge",
|
||||
"descriptions": "Beschreibung",
|
||||
"chat": "Chat"
|
||||
}
|
||||
},
|
||||
"semanticSearchModel": {
|
||||
@ -1884,7 +1901,14 @@
|
||||
},
|
||||
"onvif": {
|
||||
"profileAuto": "Auto",
|
||||
"profileLoading": "Profile werden geladen..."
|
||||
"profileLoading": "Profile werden geladen...",
|
||||
"autotracking": {
|
||||
"zooming": {
|
||||
"disabled": "deaktiviert",
|
||||
"absolute": "Absolut",
|
||||
"relative": "Verwandter"
|
||||
}
|
||||
}
|
||||
},
|
||||
"configMessages": {
|
||||
"review": {
|
||||
@ -1932,5 +1956,60 @@
|
||||
"semanticSearch": {
|
||||
"jinav2SmallModelSize": "Die „kleine“ Variante des Jina V2-Modells verursacht hohe RAM- und Inferenzkosten. Es wird das „große“ Modell mit einer dedizierten GPU empfohlen."
|
||||
}
|
||||
},
|
||||
"birdseye": {
|
||||
"trackingMode": {
|
||||
"objects": "Objekte",
|
||||
"motion": "Bewegung",
|
||||
"continuous": "Fortlaufend"
|
||||
}
|
||||
},
|
||||
"retainMode": {
|
||||
"all": "Alle",
|
||||
"motion": "Bewegung",
|
||||
"active_objects": "Aktive Objekte"
|
||||
},
|
||||
"previewQuality": {
|
||||
"very_high": "sehr hoch",
|
||||
"high": "hoch",
|
||||
"medium": "Mittel",
|
||||
"low": "niedrig",
|
||||
"very_low": "sehr niedrig"
|
||||
},
|
||||
"ui": {
|
||||
"timeFormat": {
|
||||
"browser": "Browser",
|
||||
"12hour": "12 Stunden",
|
||||
"24hour": "24 Stunden"
|
||||
},
|
||||
"TimeOrDateStyle": {
|
||||
"full": "vollständig",
|
||||
"long": "lang",
|
||||
"medium": "mittel",
|
||||
"short": "kurz"
|
||||
},
|
||||
"unitSystem": {
|
||||
"metric": "Metrik",
|
||||
"imperial": "Imperial"
|
||||
}
|
||||
},
|
||||
"review": {
|
||||
"imageSource": {
|
||||
"recordings": "Aufnahmen",
|
||||
"previews": "Vorschau"
|
||||
}
|
||||
},
|
||||
"logger": {
|
||||
"logLevel": {
|
||||
"debug": "Debug",
|
||||
"info": "Info",
|
||||
"warning": "Warnung",
|
||||
"error": "Fehler",
|
||||
"critical": "Kritisch"
|
||||
}
|
||||
},
|
||||
"modelSize": {
|
||||
"small": "klein",
|
||||
"large": "groß"
|
||||
}
|
||||
}
|
||||
|
||||
@ -60,5 +60,10 @@
|
||||
"stats": {
|
||||
"context": "{{tokens}} tokens",
|
||||
"tokens_per_second": "{{rate}} t/s"
|
||||
},
|
||||
"reasoning": {
|
||||
"active": "Reasoning…",
|
||||
"show": "Show reasoning",
|
||||
"hide": "Hide reasoning"
|
||||
}
|
||||
}
|
||||
|
||||
@ -154,7 +154,8 @@
|
||||
"gl": "Galego (Gallego)",
|
||||
"id": "Bahasa Indonesia (Indonesio)",
|
||||
"ur": "اردو (Urdu)",
|
||||
"hr": "Hrvatski (Croata)"
|
||||
"hr": "Hrvatski (Croata)",
|
||||
"bs": "Bosanski (Bosnio)"
|
||||
},
|
||||
"appearance": "Apariencia",
|
||||
"darkMode": {
|
||||
@ -196,7 +197,10 @@
|
||||
"uiPlayground": "Zona de pruebas de la interfaz de usuario",
|
||||
"faceLibrary": "Biblioteca de rostros",
|
||||
"classification": "Clasificación",
|
||||
"profiles": "Perfiles"
|
||||
"profiles": "Perfiles",
|
||||
"actions": "Acciones",
|
||||
"features": "Funciones",
|
||||
"chat": "Chat"
|
||||
},
|
||||
"unit": {
|
||||
"speed": {
|
||||
@ -252,7 +256,19 @@
|
||||
"saving": "Guardando…",
|
||||
"exitFullscreen": "Salir de pantalla completa",
|
||||
"on": "ENCENDIDO",
|
||||
"continue": "Continuar"
|
||||
"continue": "Continuar",
|
||||
"add": "Añadir",
|
||||
"applying": "Aplicando…",
|
||||
"undo": "Deshacer",
|
||||
"copiedToClipboard": "Copiado al portapapeles",
|
||||
"modified": "Modificado",
|
||||
"overridden": "Sobrescrito",
|
||||
"resetToGlobal": "Restablecer a global",
|
||||
"resetToDefault": "Restablecer valores predeterminados",
|
||||
"saveAll": "Guardar todo",
|
||||
"savingAll": "Guardando todo…",
|
||||
"undoAll": "Deshacer todo",
|
||||
"retry": "Reintentar"
|
||||
},
|
||||
"toast": {
|
||||
"save": {
|
||||
@ -260,7 +276,8 @@
|
||||
"noMessage": "No se pudieron guardar los cambios de configuración",
|
||||
"title": "No se pudieron guardar los cambios de configuración: {{errorMessage}}"
|
||||
},
|
||||
"title": "Guardar"
|
||||
"title": "Guardar",
|
||||
"success": "Cambios de configuración guardados correctamente."
|
||||
},
|
||||
"copyUrlToClipboard": "URL copiada al portapapeles."
|
||||
},
|
||||
@ -314,5 +331,7 @@
|
||||
"field": {
|
||||
"optional": "Opcional",
|
||||
"internalID": "La ID interna que usa Frigate en la configuración y en la base de datos"
|
||||
}
|
||||
},
|
||||
"no_items": "No hay elementos",
|
||||
"validation_errors": "Errores de validación"
|
||||
}
|
||||
|
||||
@ -71,16 +71,77 @@
|
||||
"endTimeMustAfterStartTime": "La hora de finalización debe ser posterior a la hora de inicio"
|
||||
},
|
||||
"success": "Exportación iniciada con éxito. Ver el archivo en la página exportaciones.",
|
||||
"view": "Ver"
|
||||
"view": "Ver",
|
||||
"queued": "Exportación en cola. Consulta el progreso en la página de exportaciones.",
|
||||
"batchSuccess_one": "Se inició 1 exportación. Abriendo el caso ahora.",
|
||||
"batchSuccess_many": "Se iniciaron {{count}} exportaciones. Abriendo el caso ahora.",
|
||||
"batchSuccess_other": "Se iniciaron {{count}} exportaciones. Abriendo el caso ahora.",
|
||||
"batchPartial": "Se iniciaron {{successful}} de {{total}} exportaciones. Cámaras fallidas: {{failedCameras}}",
|
||||
"batchFailed": "No se pudieron iniciar {{total}} exportaciones. Cámaras fallidas: {{failedCameras}}",
|
||||
"batchQueuedSuccess_one": "1 exportación en cola. Abriendo el caso ahora.",
|
||||
"batchQueuedSuccess_many": "{{count}} exportaciones en cola. Abriendo el caso ahora.",
|
||||
"batchQueuedSuccess_other": "{{count}} exportaciones en cola. Abriendo el caso ahora.",
|
||||
"batchQueuedPartial": "{{successful}} de {{total}} exportaciones en cola. Cámaras fallidas: {{failedCameras}}",
|
||||
"batchQueueFailed": "No se pudieron poner en cola {{total}} exportaciones. Cámaras fallidas: {{failedCameras}}"
|
||||
},
|
||||
"fromTimeline": {
|
||||
"saveExport": "Guardar exportación",
|
||||
"previewExport": "Vista previa de la exportación"
|
||||
"previewExport": "Vista previa de la exportación",
|
||||
"queueingExport": "Poniendo exportación en cola...",
|
||||
"useThisRange": "Usar este intervalo"
|
||||
},
|
||||
"selectOrExport": "Seleccionar o exportar",
|
||||
"case": {
|
||||
"label": "Caso",
|
||||
"newCaseDescriptionPlaceholder": "Descripción de caso"
|
||||
"newCaseDescriptionPlaceholder": "Descripción de caso",
|
||||
"newCaseOption": "Crear nuevo caso",
|
||||
"newCaseNamePlaceholder": "Nombre del nuevo caso",
|
||||
"nonAdminHelp": "Se creará un nuevo caso para estas exportaciones.",
|
||||
"placeholder": "Selecciona un caso"
|
||||
},
|
||||
"queueing": "Poniendo la exportación en cola…",
|
||||
"tabs": {
|
||||
"export": "Cámara única",
|
||||
"multiCamera": "Multicámara"
|
||||
},
|
||||
"multiCamera": {
|
||||
"timeRange": "Intervalo de tiempo",
|
||||
"selectFromTimeline": "Seleccionar desde la línea de tiempo",
|
||||
"cameraSelection": "Cámaras",
|
||||
"cameraSelectionHelp": "Las cámaras con objetos detectados en este intervalo de tiempo están preseleccionadas",
|
||||
"checkingActivity": "Comprobando actividad de las cámaras...",
|
||||
"noCameras": "No hay cámaras disponibles",
|
||||
"detectionCount_one": "1 objeto detectado",
|
||||
"detectionCount_many": "{{count}} objetos detectados",
|
||||
"detectionCount_other": "{{count}} objetos detectados",
|
||||
"nameLabel": "Nombre de la exportación",
|
||||
"namePlaceholder": "Nombre base opcional para estas exportaciones",
|
||||
"queueingButton": "Poniendo exportaciones en cola...",
|
||||
"exportButton_one": "Exportar 1 cámara",
|
||||
"exportButton_many": "Exportar {{count}} cámaras",
|
||||
"exportButton_other": "Exportar {{count}} cámaras"
|
||||
},
|
||||
"multi": {
|
||||
"title_one": "Exportar 1 revisión",
|
||||
"title_many": "Exportar {{count}} revisiones",
|
||||
"title_other": "Exportar {{count}} revisiones",
|
||||
"description": "Exportar cada revisión seleccionada. Todas las exportaciones se agruparán en un único caso.",
|
||||
"descriptionNoCase": "Exportar cada revisión seleccionada.",
|
||||
"caseNamePlaceholder": "Exportación de revisión - {{date}}",
|
||||
"exportButton_one": "Exportar 1 revisión",
|
||||
"exportButton_many": "Exportar {{count}} revisiones",
|
||||
"exportButton_other": "Exportar {{count}} revisiones",
|
||||
"exportingButton": "Exportando...",
|
||||
"toast": {
|
||||
"started_one": "Se inició 1 exportación. Abriendo el caso ahora.",
|
||||
"started_many": "Se iniciaron {{count}} exportaciones. Abriendo el caso ahora.",
|
||||
"started_other": "Se iniciaron {{count}} exportaciones. Abriendo el caso ahora.",
|
||||
"startedNoCase_one": "Se inició 1 exportación.",
|
||||
"startedNoCase_many": "Se iniciaron {{count}} exportaciones.",
|
||||
"startedNoCase_other": "Se iniciaron {{count}} exportaciones.",
|
||||
"partial": "Se iniciaron {{successful}} de {{total}} exportaciones. Fallidas: {{failedItems}}",
|
||||
"failed": "No se pudieron iniciar {{total}} exportaciones. Fallidas: {{failedItems}}"
|
||||
}
|
||||
}
|
||||
},
|
||||
"streaming": {
|
||||
@ -130,7 +191,12 @@
|
||||
"markAsUnreviewed": "Marcar como no revisado"
|
||||
},
|
||||
"shareTimestamp": {
|
||||
"description": "Comparta una URL con marca de tiempo de la posición actual del reproductor o elija una marca de tiempo personalizada. Tenga en cuenta que esta no es una URL pública para compartir y solo es accesible para los usuarios que tienen acceso a Frigate y a esta cámara."
|
||||
"description": "Comparta una URL con marca de tiempo de la posición actual del reproductor o elija una marca de tiempo personalizada. Tenga en cuenta que esta no es una URL pública para compartir y solo es accesible para los usuarios que tienen acceso a Frigate y a esta cámara.",
|
||||
"label": "Compartir marca de tiempo",
|
||||
"title": "Compartir marca de tiempo",
|
||||
"custom": "Marca de tiempo personalizada",
|
||||
"button": "Compartir URL de la marca de tiempo",
|
||||
"shareTitle": "Marca de tiempo de revisión de Frigate: {{camera}}"
|
||||
}
|
||||
},
|
||||
"imagePicker": {
|
||||
|
||||
@ -8,7 +8,7 @@
|
||||
"description": "Habilitado"
|
||||
},
|
||||
"audio": {
|
||||
"label": "Eventos de audio",
|
||||
"label": "Detección de audio",
|
||||
"description": "Configuración para la detección de eventos basada en audio para esta cámara.",
|
||||
"enabled": {
|
||||
"label": "Habilitar la detección de audio",
|
||||
@ -28,14 +28,19 @@
|
||||
},
|
||||
"filters": {
|
||||
"label": "Filtros de audio",
|
||||
"description": "Ajustes de filtrado por tipo de audio, como umbrales de confianza utilizados para reducir los falsos positivos."
|
||||
"description": "Ajustes de filtrado por tipo de audio, como umbrales de confianza utilizados para reducir los falsos positivos.",
|
||||
"threshold": {
|
||||
"label": "Confianza mínima de audio",
|
||||
"description": "Umbral mínimo de confianza para que se cuente el evento de audio."
|
||||
}
|
||||
},
|
||||
"enabled_in_config": {
|
||||
"description": "Indica si la detección de audio estaba habilitada originalmente en el archivo de configuración estática.",
|
||||
"label": "Estado original del audio"
|
||||
},
|
||||
"num_threads": {
|
||||
"label": "Hilos de detección"
|
||||
"label": "Hilos de detección",
|
||||
"description": "Número de hilos que se utilizarán para el procesamiento de la detección de audio."
|
||||
}
|
||||
},
|
||||
"friendly_name": {
|
||||
@ -50,29 +55,79 @@
|
||||
},
|
||||
"autotracking": {
|
||||
"zoom_factor": {
|
||||
"description": "Controla el nivel de zoom en los objetos rastreados. Los valores más bajos mantienen una mayor parte de la escena a la vista; los valores más altos acercan la imagen, pero pueden provocar la pérdida del rastreo. Valores entre 0.1 y 0.75."
|
||||
"description": "Controla el nivel de zoom en los objetos rastreados. Los valores más bajos mantienen una mayor parte de la escena a la vista; los valores más altos acercan la imagen, pero pueden provocar la pérdida del rastreo. Valores entre 0.1 y 0.75.",
|
||||
"label": "Factor de zoom"
|
||||
},
|
||||
"calibrate_on_startup": {
|
||||
"description": "Mida la velocidad de los motores PTZ al encenderlos para mejorar la precisión del seguimiento. Frigate actualizará la configuración con los `movement_weights` tras la calibración."
|
||||
"description": "Mida la velocidad de los motores PTZ al encenderlos para mejorar la precisión del seguimiento. Frigate actualizará la configuración con los `movement_weights` tras la calibración.",
|
||||
"label": "Calibrar al iniciar"
|
||||
},
|
||||
"description": "Realice un seguimiento automático de objetos en movimiento y manténgalos centrados en el encuadre mediante movimientos de cámara PTZ.",
|
||||
"zooming": {
|
||||
"description": "Control del comportamiento del zoom: deshabilitado (solo panorámica/inclinación), absoluto (mayor compatibilidad) o relativo (panorámica/inclinación/zoom simultáneos)."
|
||||
"description": "Control del comportamiento del zoom: deshabilitado (solo panorámica/inclinación), absoluto (mayor compatibilidad) o relativo (panorámica/inclinación/zoom simultáneos).",
|
||||
"label": "Modo de zoom"
|
||||
},
|
||||
"return_preset": {
|
||||
"description": "Nombre del preajuste ONVIF configurado en el firmware de la cámara al que regresar una vez finalizado el seguimiento."
|
||||
"description": "Nombre del preajuste ONVIF configurado en el firmware de la cámara al que regresar una vez finalizado el seguimiento.",
|
||||
"label": "Preajuste de retorno"
|
||||
},
|
||||
"timeout": {
|
||||
"description": "Espere esta cantidad de segundos después de perder el seguimiento antes de devolver la cámara a la posición preestablecida."
|
||||
"description": "Espere esta cantidad de segundos después de perder el seguimiento antes de devolver la cámara a la posición preestablecida.",
|
||||
"label": "Tiempo de espera de retorno"
|
||||
},
|
||||
"label": "Seguimiento automático",
|
||||
"enabled": {
|
||||
"label": "Habilitar seguimiento automático",
|
||||
"description": "Habilita o deshabilita el seguimiento automático con cámara PTZ de objetos detectados."
|
||||
},
|
||||
"track": {
|
||||
"label": "Objetos rastreados",
|
||||
"description": "Lista de tipos de objetos que deben activar el seguimiento automático."
|
||||
},
|
||||
"required_zones": {
|
||||
"label": "Zonas requeridas",
|
||||
"description": "Los objetos deben entrar en una de estas zonas antes de que comience el seguimiento automático."
|
||||
},
|
||||
"movement_weights": {
|
||||
"label": "Pesos de movimiento",
|
||||
"description": "Valores de calibración generados automáticamente por la calibración de la cámara. No los modifiques manualmente."
|
||||
},
|
||||
"enabled_in_config": {
|
||||
"label": "Estado original de autoseguimiento",
|
||||
"description": "Campo interno para rastrear si el seguimiento automático estaba habilitado en la configuración."
|
||||
}
|
||||
},
|
||||
"tls_insecure": {
|
||||
"description": "Omitir la verificación TLS y deshabilitar la autenticación digest para ONVIF (no seguro; usar solo en redes seguras)."
|
||||
"description": "Omitir la verificación TLS y deshabilitar la autenticación digest para ONVIF (no seguro; usar solo en redes seguras).",
|
||||
"label": "Deshabilitar verificación TLS"
|
||||
},
|
||||
"label": "ONVIF",
|
||||
"description": "Ajustes de conexión ONVIF y seguimiento automático PTZ para esta cámara.",
|
||||
"host": {
|
||||
"label": "Host ONVIF",
|
||||
"description": "Host (y esquema opcional) para el servicio ONVIF de esta cámara."
|
||||
},
|
||||
"port": {
|
||||
"label": "Puerto ONVIF",
|
||||
"description": "Número de puerto del servicio ONVIF."
|
||||
},
|
||||
"user": {
|
||||
"label": "Nombre de usuario ONVIF",
|
||||
"description": "Nombre de usuario para la autenticación ONVIF; algunos dispositivos requieren un usuario administrador para ONVIF."
|
||||
},
|
||||
"password": {
|
||||
"label": "Contraseña ONVIF",
|
||||
"description": "Contraseña para la autenticación ONVIF."
|
||||
},
|
||||
"ignore_time_mismatch": {
|
||||
"label": "Ignorar discrepancia horaria",
|
||||
"description": "Ignora las diferencias de sincronización horaria entre la cámara y el servidor Frigate para la comunicación ONVIF."
|
||||
}
|
||||
},
|
||||
"zones": {
|
||||
"distances": {
|
||||
"label": "Distancias reales"
|
||||
"label": "Distancias reales",
|
||||
"description": "Distancias reales opcionales para cada lado del cuadrilátero de la zona, usadas para cálculos de velocidad o distancia. Debe tener exactamente 4 valores si se establece."
|
||||
},
|
||||
"coordinates": {
|
||||
"description": "Coordenadas del polígono que definen el área de la zona. Puede ser una cadena separada por comas o una lista de cadenas de coordenadas. Las coordenadas deben ser relativas (0-1) o absolutas (heredadas).",
|
||||
@ -106,23 +161,41 @@
|
||||
"description": "Área máxima del cuadro delimitador (píxeles o porcentaje) permitida para este tipo de objeto. Puede expresarse en píxeles (entero) o como porcentaje (decimal entre 0,000001 y 0,99).",
|
||||
"label": "Área máxima del objeto"
|
||||
},
|
||||
"description": "Filtros para aplicar a los objetos dentro de esta zona. Se utilizan para reducir los falsos positivos o restringir qué objetos se consideran presentes en la zona."
|
||||
"description": "Filtros para aplicar a los objetos dentro de esta zona. Se utilizan para reducir los falsos positivos o restringir qué objetos se consideran presentes en la zona.",
|
||||
"label": "Filtros de zona",
|
||||
"min_area": {
|
||||
"label": "Área mínima de objeto",
|
||||
"description": "Área mínima del cuadro delimitador (píxeles o porcentaje) necesaria para este tipo de objeto. Puede ser píxeles (int) o porcentaje (float entre 0.000001 y 0.99)."
|
||||
}
|
||||
},
|
||||
"objects": {
|
||||
"description": "Lista de tipos de objetos (del mapa de etiquetas) que pueden activar esta zona. Puede ser una cadena de texto o una lista de cadenas. Si está vacío, se consideran todos los objetos."
|
||||
"description": "Lista de tipos de objetos (del mapa de etiquetas) que pueden activar esta zona. Puede ser una cadena de texto o una lista de cadenas. Si está vacío, se consideran todos los objetos.",
|
||||
"label": "Objetos activadores"
|
||||
},
|
||||
"description": "Las zonas le permiten definir un área específica del fotograma, de modo que pueda determinar si un objeto se encuentra o no dentro de un área determinada.",
|
||||
"speed_threshold": {
|
||||
"description": "Velocidad mínima (en unidades del mundo real, si se han configurado distancias) requerida para que un objeto se considere presente en la zona. Se utiliza para los disparadores de zona basados en la velocidad."
|
||||
"description": "Velocidad mínima (en unidades del mundo real, si se han configurado distancias) requerida para que un objeto se considere presente en la zona. Se utiliza para los disparadores de zona basados en la velocidad.",
|
||||
"label": "Velocidad mínima"
|
||||
},
|
||||
"friendly_name": {
|
||||
"description": "Un nombre fácil de usar para la zona, que se muestra en la interfaz de usuario de Frigate. Si no se especifica, se utilizará una versión formateada del nombre de la zona."
|
||||
"description": "Un nombre fácil de usar para la zona, que se muestra en la interfaz de usuario de Frigate. Si no se especifica, se utilizará una versión formateada del nombre de la zona.",
|
||||
"label": "Nombre de zona"
|
||||
},
|
||||
"inertia": {
|
||||
"description": "Número de fotogramas consecutivos en los que se debe detectar un objeto dentro de la zona antes de considerarlo presente. Ayuda a filtrar las detecciones transitorias."
|
||||
"description": "Número de fotogramas consecutivos en los que se debe detectar un objeto dentro de la zona antes de considerarlo presente. Ayuda a filtrar las detecciones transitorias.",
|
||||
"label": "Fotogramas de inercia"
|
||||
},
|
||||
"loitering_time": {
|
||||
"description": "Número de segundos que un objeto debe permanecer en la zona para ser considerado como merodeo. Establezca en 0 para desactivar la detección de merodeo."
|
||||
"description": "Número de segundos que un objeto debe permanecer en la zona para ser considerado como merodeo. Establezca en 0 para desactivar la detección de merodeo.",
|
||||
"label": "Segundos de permanencia"
|
||||
},
|
||||
"label": "Zonas",
|
||||
"enabled": {
|
||||
"label": "Habilitado",
|
||||
"description": "Habilita o deshabilita esta zona. Las zonas deshabilitadas se ignoran en tiempo de ejecución."
|
||||
},
|
||||
"enabled_in_config": {
|
||||
"label": "Mantiene el registro del estado original de la zona."
|
||||
}
|
||||
},
|
||||
"objects": {
|
||||
@ -142,148 +215,739 @@
|
||||
},
|
||||
"send_triggers": {
|
||||
"after_significant_updates": {
|
||||
"description": "Envía una solicitud a GenAI tras un número especificado de actualizaciones significativas del objeto rastreado."
|
||||
"description": "Envía una solicitud a GenAI tras un número especificado de actualizaciones significativas del objeto rastreado.",
|
||||
"label": "Activador temprano de GenAI"
|
||||
},
|
||||
"description": "Define cuándo se deben enviar los fotogramas a GenAI (al finalizar, después de las actualizaciones, etc.)."
|
||||
"description": "Define cuándo se deben enviar los fotogramas a GenAI (al finalizar, después de las actualizaciones, etc.).",
|
||||
"label": "Activadores de GenAI",
|
||||
"tracked_object_end": {
|
||||
"label": "Enviar al finalizar",
|
||||
"description": "Envía una solicitud a GenAI cuando finaliza el objeto rastreado."
|
||||
}
|
||||
},
|
||||
"required_zones": {
|
||||
"description": "Zonas en las que deben ubicarse los objetos para ser elegibles para la generación de descripciones con GenAI."
|
||||
"description": "Zonas en las que deben ubicarse los objetos para ser elegibles para la generación de descripciones con GenAI.",
|
||||
"label": "Zonas requeridas"
|
||||
},
|
||||
"prompt": {
|
||||
"label": "Prompt de descripción",
|
||||
"description": "Plantilla de prompt predeterminada usada al generar descripciones con GenAI."
|
||||
},
|
||||
"object_prompts": {
|
||||
"label": "Prompts de objetos",
|
||||
"description": "Prompts por objeto para personalizar las salidas de GenAI para etiquetas concretas."
|
||||
},
|
||||
"objects": {
|
||||
"label": "Objetos de GenAI",
|
||||
"description": "Lista de etiquetas de objetos que se enviarán a GenAI de forma predeterminada."
|
||||
},
|
||||
"debug_save_thumbnails": {
|
||||
"label": "Guardar miniaturas",
|
||||
"description": "Guarda las miniaturas enviadas a GenAI para depuración y revisión."
|
||||
},
|
||||
"enabled_in_config": {
|
||||
"label": "Estado original de GenAI",
|
||||
"description": "Indica si GenAI estaba habilitado en la configuración estática original."
|
||||
}
|
||||
},
|
||||
"label": "Objetos",
|
||||
"description": "Valores predeterminados de seguimiento de objetos, incluidas las etiquetas que se rastrean y los filtros por objeto.",
|
||||
"track": {
|
||||
"label": "Objetos a rastrear",
|
||||
"description": "Lista de etiquetas de objetos a rastrear para esta cámara."
|
||||
},
|
||||
"filters": {
|
||||
"label": "Filtros de objetos",
|
||||
"description": "Filtros aplicados a los objetos detectados para reducir falsos positivos (área, relación, confianza).",
|
||||
"min_area": {
|
||||
"label": "Área mínima de objeto",
|
||||
"description": "Área mínima del cuadro delimitador (píxeles o porcentaje) necesaria para este tipo de objeto. Puede ser píxeles (int) o porcentaje (float entre 0.000001 y 0.99)."
|
||||
},
|
||||
"max_area": {
|
||||
"label": "Área máxima de objeto",
|
||||
"description": "Área máxima del cuadro delimitador (píxeles o porcentaje) permitida para este tipo de objeto. Puede ser píxeles (int) o porcentaje (float entre 0.000001 y 0.99)."
|
||||
},
|
||||
"min_ratio": {
|
||||
"label": "Relación de aspecto mínima",
|
||||
"description": "Relación mínima anchura/altura necesaria para que el cuadro delimitador sea válido."
|
||||
},
|
||||
"max_ratio": {
|
||||
"label": "Relación de aspecto máxima",
|
||||
"description": "Relación máxima anchura/altura permitida para que el cuadro delimitador sea válido."
|
||||
},
|
||||
"threshold": {
|
||||
"label": "Umbral de confianza",
|
||||
"description": "Umbral medio de confianza de detección necesario para que el objeto se considere un positivo verdadero."
|
||||
},
|
||||
"min_score": {
|
||||
"label": "Confianza mínima",
|
||||
"description": "Confianza mínima de detección en un único fotograma necesaria para que el objeto se contabilice."
|
||||
},
|
||||
"mask": {
|
||||
"label": "Máscara de filtro",
|
||||
"description": "Coordenadas del polígono que definen dónde se aplica este filtro dentro del fotograma."
|
||||
},
|
||||
"raw_mask": {
|
||||
"label": "Máscara sin procesar"
|
||||
}
|
||||
},
|
||||
"mask": {
|
||||
"label": "Máscara de objeto",
|
||||
"description": "Polígono de máscara usado para evitar la detección de objetos en áreas especificadas."
|
||||
}
|
||||
},
|
||||
"mqtt": {
|
||||
"label": "MQTT",
|
||||
"required_zones": {
|
||||
"description": "Zonas en las que debe entrar un objeto para que se publique una imagen MQTT."
|
||||
"description": "Zonas en las que debe entrar un objeto para que se publique una imagen MQTT.",
|
||||
"label": "Zonas requeridas"
|
||||
},
|
||||
"description": "Ajustes de publicación de imágenes MQTT.",
|
||||
"enabled": {
|
||||
"label": "Enviar imagen",
|
||||
"description": "Habilita la publicación de instantáneas de objetos en temas MQTT para esta cámara."
|
||||
},
|
||||
"timestamp": {
|
||||
"label": "Añadir marca de tiempo",
|
||||
"description": "Superpone una marca de tiempo en las imágenes publicadas en MQTT."
|
||||
},
|
||||
"bounding_box": {
|
||||
"label": "Añadir cuadro delimitador",
|
||||
"description": "Dibuja cuadros delimitadores en las imágenes publicadas mediante MQTT."
|
||||
},
|
||||
"crop": {
|
||||
"label": "Recortar imagen",
|
||||
"description": "Recorta las imágenes publicadas en MQTT al cuadro delimitador del objeto detectado."
|
||||
},
|
||||
"height": {
|
||||
"label": "Altura de imagen",
|
||||
"description": "Altura (píxeles) a la que redimensionar las imágenes publicadas mediante MQTT."
|
||||
},
|
||||
"quality": {
|
||||
"label": "Calidad JPEG",
|
||||
"description": "Calidad JPEG de las imágenes publicadas en MQTT (0-100)."
|
||||
}
|
||||
},
|
||||
"notifications": {
|
||||
"email": {
|
||||
"label": "Email de notificacion"
|
||||
"label": "Email de notificacion",
|
||||
"description": "Dirección de correo electrónico usada para notificaciones push o requerida por ciertos proveedores de notificaciones."
|
||||
},
|
||||
"label": "Notificaciones",
|
||||
"description": "Ajustes para habilitar y controlar las notificaciones de esta cámara.",
|
||||
"enabled": {
|
||||
"label": "Habilitar notificaciones",
|
||||
"description": "Habilita o deshabilita las notificaciones para esta cámara."
|
||||
},
|
||||
"cooldown": {
|
||||
"label": "Periodo de enfriamiento",
|
||||
"description": "Periodo de enfriamiento (segundos) entre notificaciones para evitar saturar a los destinatarios."
|
||||
},
|
||||
"enabled_in_config": {
|
||||
"label": "Estado original de notificaciones",
|
||||
"description": "Indica si las notificaciones estaban habilitadas en la configuración estática original."
|
||||
}
|
||||
},
|
||||
"audio_transcription": {
|
||||
"description": "Configuración para la transcripción de audio en vivo y de voz, utilizada para eventos y subtítulos en tiempo real.",
|
||||
"enabled": {
|
||||
"label": "Habilitar transcripción"
|
||||
"label": "Habilitar transcripción",
|
||||
"description": "Activar o desactivar la transcripción de eventos de audio activados manualmente."
|
||||
},
|
||||
"label": "Transcripción de audio",
|
||||
"enabled_in_config": {
|
||||
"label": "Estado original de la transcripción"
|
||||
},
|
||||
"live_enabled": {
|
||||
"label": "Transcripción en directo",
|
||||
"description": "Activar la transcripción en directo del audio a medida que se recibe."
|
||||
}
|
||||
},
|
||||
"motion": {
|
||||
"skip_motion_threshold": {
|
||||
"description": "Si se establece en un valor entre 0,0 y 1,0, y más de esta fracción de la imagen cambia en un solo fotograma, el detector no devolverá cuadros de movimiento y se recalibrará inmediatamente. Esto puede ahorrar recursos de CPU y reducir los falsos positivos durante tormentas eléctricas, tempestades, etc., aunque podría pasar por alto eventos reales, como el seguimiento automático de un objeto por parte de una cámara PTZ. La disyuntiva está entre descartar unos cuantos megabytes de grabaciones o revisar un par de clips cortos. Deje este parámetro sin establecer (None) para desactivar esta función."
|
||||
"description": "Si se establece en un valor entre 0,0 y 1,0, y más de esta fracción de la imagen cambia en un solo fotograma, el detector no devolverá cuadros de movimiento y se recalibrará inmediatamente. Esto puede ahorrar recursos de CPU y reducir los falsos positivos durante tormentas eléctricas, tempestades, etc., aunque podría pasar por alto eventos reales, como el seguimiento automático de un objeto por parte de una cámara PTZ. La disyuntiva está entre descartar unos cuantos megabytes de grabaciones o revisar un par de clips cortos. Deje este parámetro sin establecer (None) para desactivar esta función.",
|
||||
"label": "Omitir umbral de movimiento"
|
||||
},
|
||||
"lightning_threshold": {
|
||||
"description": "Umbral para detectar e ignorar breves picos de luz (un valor menor indica mayor sensibilidad; valores entre 0,3 y 1,0). Esto no impide por completo la detección de movimiento; Simplemente provoca que el detector deje de analizar fotogramas adicionales una vez que se supera el umbral. Durante estos eventos aún se realizan grabaciones basadas en el movimiento."
|
||||
"description": "Umbral para detectar e ignorar breves picos de luz (un valor menor indica mayor sensibilidad; valores entre 0,3 y 1,0). Esto no impide por completo la detección de movimiento; Simplemente provoca que el detector deje de analizar fotogramas adicionales una vez que se supera el umbral. Durante estos eventos aún se realizan grabaciones basadas en el movimiento.",
|
||||
"label": "Umbral de iluminación"
|
||||
},
|
||||
"threshold": {
|
||||
"description": "Umbral de diferencia de píxeles utilizado por el detector de movimiento; los valores más altos reducen la sensibilidad (rango 1-255)."
|
||||
"description": "Umbral de diferencia de píxeles utilizado por el detector de movimiento; los valores más altos reducen la sensibilidad (rango 1-255).",
|
||||
"label": "Umbral de movimiento"
|
||||
},
|
||||
"label": "Detección de movimiento",
|
||||
"description": "Ajustes predeterminados de detección de movimiento para esta cámara.",
|
||||
"enabled": {
|
||||
"label": "Habilitar detección de movimiento",
|
||||
"description": "Habilita o deshabilita la detección de movimiento para esta cámara."
|
||||
},
|
||||
"improve_contrast": {
|
||||
"label": "Mejorar contraste",
|
||||
"description": "Aplica una mejora de contraste a los fotogramas antes del análisis de movimiento para ayudar a la detección."
|
||||
},
|
||||
"contour_area": {
|
||||
"label": "Área de contorno",
|
||||
"description": "Área mínima de contorno en píxeles necesaria para que se cuente un contorno de movimiento."
|
||||
},
|
||||
"delta_alpha": {
|
||||
"label": "Delta alfa",
|
||||
"description": "Factor de mezcla alfa usado en la diferencia entre fotogramas para calcular el movimiento."
|
||||
},
|
||||
"frame_alpha": {
|
||||
"label": "Alfa del fotograma",
|
||||
"description": "Valor alfa usado al mezclar fotogramas para el preprocesamiento de movimiento."
|
||||
},
|
||||
"frame_height": {
|
||||
"label": "Altura del fotograma",
|
||||
"description": "Altura en píxeles a la que escalar los fotogramas al calcular el movimiento."
|
||||
},
|
||||
"mask": {
|
||||
"label": "Coordenadas de máscara",
|
||||
"description": "Coordenadas x,y ordenadas que definen el polígono de máscara de movimiento usado para incluir/excluir áreas."
|
||||
},
|
||||
"mqtt_off_delay": {
|
||||
"label": "Retraso de apagado MQTT",
|
||||
"description": "Segundos a esperar tras el último movimiento antes de publicar un estado MQTT 'off'."
|
||||
},
|
||||
"enabled_in_config": {
|
||||
"label": "Estado de movimiento original",
|
||||
"description": "Indica si la detección de movimiento estaba habilitada en la configuración estática original."
|
||||
},
|
||||
"raw_mask": {
|
||||
"label": "Máscara sin procesar"
|
||||
}
|
||||
},
|
||||
"lpr": {
|
||||
"enhancement": {
|
||||
"description": "Nivel de mejora (0-10) que se aplicará a los recortes de matrículas antes del OCR; los valores más altos no siempre mejoran los resultados, y los niveles superiores a 5 podrían funcionar únicamente con matrículas capturadas de noche, por lo que deben utilizarse con precaución."
|
||||
"description": "Nivel de mejora (0-10) que se aplicará a los recortes de matrículas antes del OCR; los valores más altos no siempre mejoran los resultados, y los niveles superiores a 5 podrían funcionar únicamente con matrículas capturadas de noche, por lo que deben utilizarse con precaución.",
|
||||
"label": "Nivel de mejora"
|
||||
},
|
||||
"expire_time": {
|
||||
"description": "Tiempo en segundos tras el cual una matrícula no detectada caduca en el sistema de seguimiento (solo para cámaras LPR dedicadas)."
|
||||
"description": "Tiempo en segundos tras el cual una matrícula no detectada caduca en el sistema de seguimiento (solo para cámaras LPR dedicadas).",
|
||||
"label": "Segundos hasta caducar"
|
||||
},
|
||||
"label": "Reconocimiento de matrículas",
|
||||
"description": "Ajustes de reconocimiento de matrículas, incluidos umbrales de detección, formato y matrículas conocidas.",
|
||||
"enabled": {
|
||||
"label": "Habilitar LPR",
|
||||
"description": "Habilita o deshabilita LPR en esta cámara."
|
||||
},
|
||||
"min_area": {
|
||||
"label": "Área mínima de matrícula",
|
||||
"description": "Área mínima de matrícula (píxeles) necesaria para intentar el reconocimiento."
|
||||
}
|
||||
},
|
||||
"detect": {
|
||||
"fps": {
|
||||
"description": "Fotogramas por segundo deseados para ejecutar la detección; los valores más bajos reducen el uso de la CPU (el valor recomendado es 5; establezca un valor superior —como máximo de 10— únicamente si realiza el seguimiento de objetos que se mueven con extrema rapidez)."
|
||||
"description": "Fotogramas por segundo deseados para ejecutar la detección; los valores más bajos reducen el uso de la CPU (el valor recomendado es 5; establezca un valor superior —como máximo de 10— únicamente si realiza el seguimiento de objetos que se mueven con extrema rapidez).",
|
||||
"label": "FPS de detección"
|
||||
},
|
||||
"min_initialized": {
|
||||
"description": "Número de detecciones consecutivas requeridas antes de crear un objeto rastreado. Auméntelo para reducir las inicializaciones falsas. El valor predeterminado es los FPS divididos por 2."
|
||||
"description": "Número de detecciones consecutivas requeridas antes de crear un objeto rastreado. Auméntelo para reducir las inicializaciones falsas. El valor predeterminado es los FPS divididos por 2.",
|
||||
"label": "Fotogramas mínimos de inicialización"
|
||||
},
|
||||
"height": {
|
||||
"description": "Altura (en píxeles) de los fotogramas utilizados para la transmisión de detección; déjelo vacío para utilizar la resolución nativa de la transmisión."
|
||||
"description": "Altura (en píxeles) de los fotogramas utilizados para la transmisión de detección; déjelo vacío para utilizar la resolución nativa de la transmisión.",
|
||||
"label": "Altura de detección"
|
||||
},
|
||||
"width": {
|
||||
"description": "Ancho (en píxeles) de los fotogramas utilizados para la transmisión de detección; déjelo vacío para utilizar la resolución nativa de la transmisión."
|
||||
"description": "Ancho (en píxeles) de los fotogramas utilizados para la transmisión de detección; déjelo vacío para utilizar la resolución nativa de la transmisión.",
|
||||
"label": "Anchura de detección"
|
||||
},
|
||||
"stationary": {
|
||||
"description": "Configuración para detectar y gestionar objetos que permanecen inmóviles durante un periodo de tiempo."
|
||||
"description": "Configuración para detectar y gestionar objetos que permanecen inmóviles durante un periodo de tiempo.",
|
||||
"label": "Configuración de objetos estacionarios",
|
||||
"interval": {
|
||||
"label": "Intervalo estacionario",
|
||||
"description": "Frecuencia (en fotogramas) con la que se ejecuta una comprobación de detección para confirmar un objeto estacionario."
|
||||
},
|
||||
"threshold": {
|
||||
"label": "Umbral estacionario",
|
||||
"description": "Número de fotogramas sin cambio de posición necesarios para marcar un objeto como estacionario."
|
||||
},
|
||||
"max_frames": {
|
||||
"label": "Fotogramas máximos",
|
||||
"description": "Limita durante cuánto tiempo se rastrean los objetos estacionarios antes de descartarlos.",
|
||||
"default": {
|
||||
"label": "Fotogramas máximos predeterminados",
|
||||
"description": "Número máximo predeterminado de fotogramas para rastrear un objeto estacionario antes de detenerse."
|
||||
},
|
||||
"objects": {
|
||||
"label": "Fotogramas máximos por objeto",
|
||||
"description": "Sobrescrituras por objeto para el número máximo de fotogramas en los que rastrear objetos estacionarios."
|
||||
}
|
||||
},
|
||||
"classifier": {
|
||||
"label": "Habilitar clasificador visual",
|
||||
"description": "Usa un clasificador visual para detectar objetos realmente estacionarios incluso cuando los cuadros delimitadores oscilan."
|
||||
}
|
||||
},
|
||||
"label": "Detección de objetos",
|
||||
"description": "Ajustes del rol de detección/detect usado para ejecutar la detección de objetos e inicializar los rastreadores.",
|
||||
"enabled": {
|
||||
"label": "Habilitar detección de objetos",
|
||||
"description": "Habilita o deshabilita la detección de objetos para esta cámara."
|
||||
},
|
||||
"max_disappeared": {
|
||||
"label": "Fotogramas máximos desaparecido",
|
||||
"description": "Número de fotogramas sin detección antes de que un objeto rastreado se considere desaparecido."
|
||||
},
|
||||
"annotation_offset": {
|
||||
"label": "Desplazamiento de anotaciones",
|
||||
"description": "Milisegundos para desplazar las anotaciones de detección y alinear mejor los cuadros delimitadores de la línea de tiempo con las grabaciones; puede ser positivo o negativo."
|
||||
}
|
||||
},
|
||||
"record": {
|
||||
"motion": {
|
||||
"description": "Número de días para conservar las grabaciones activadas por movimiento, independientemente de los objetos rastreados. Establézcalo en 0 si solo desea conservar las grabaciones de alertas y detecciones."
|
||||
"description": "Número de días para conservar las grabaciones activadas por movimiento, independientemente de los objetos rastreados. Establézcalo en 0 si solo desea conservar las grabaciones de alertas y detecciones.",
|
||||
"label": "Retención de movimiento",
|
||||
"days": {
|
||||
"label": "Días de retención",
|
||||
"description": "Días durante los que conservar las grabaciones."
|
||||
}
|
||||
},
|
||||
"continuous": {
|
||||
"description": "Número de días para conservar las grabaciones, independientemente de los objetos rastreados o del movimiento. Establézcalo en 0 si solo desea conservar las grabaciones de alertas y detecciones."
|
||||
"description": "Número de días para conservar las grabaciones, independientemente de los objetos rastreados o del movimiento. Establézcalo en 0 si solo desea conservar las grabaciones de alertas y detecciones.",
|
||||
"label": "Retención continua",
|
||||
"days": {
|
||||
"label": "Días de retención",
|
||||
"description": "Días durante los que conservar las grabaciones."
|
||||
}
|
||||
},
|
||||
"detections": {
|
||||
"pre_capture": {
|
||||
"description": "Número de segundos antes del evento de detección que se incluirán en la grabación."
|
||||
"description": "Número de segundos antes del evento de detección que se incluirán en la grabación.",
|
||||
"label": "Segundos de captura previa"
|
||||
},
|
||||
"post_capture": {
|
||||
"description": "Número de segundos después del evento de detección que se incluirán en la grabación."
|
||||
"description": "Número de segundos después del evento de detección que se incluirán en la grabación.",
|
||||
"label": "Segundos de captura posterior"
|
||||
},
|
||||
"label": "Retención de detección",
|
||||
"description": "Ajustes de retención de grabaciones para eventos de detección, incluidas las duraciones de captura previa/posterior.",
|
||||
"retain": {
|
||||
"label": "Retención de eventos",
|
||||
"description": "Ajustes de retención para grabaciones de eventos de detección.",
|
||||
"days": {
|
||||
"label": "Días de retención",
|
||||
"description": "Número de días durante los que conservar grabaciones de eventos de detección."
|
||||
},
|
||||
"mode": {
|
||||
"label": "Modo de retención",
|
||||
"description": "Modo de retención: all (guarda todos los segmentos), motion (guarda segmentos con movimiento) o active_objects (guarda segmentos con objetos activos)."
|
||||
}
|
||||
}
|
||||
},
|
||||
"alerts": {
|
||||
"pre_capture": {
|
||||
"description": "Número de segundos antes del evento de detección que se incluirán en la grabación."
|
||||
"description": "Número de segundos antes del evento de detección que se incluirán en la grabación.",
|
||||
"label": "Segundos de captura previa"
|
||||
},
|
||||
"post_capture": {
|
||||
"description": "Número de segundos después del evento de detección que se incluirán en la grabación."
|
||||
"description": "Número de segundos después del evento de detección que se incluirán en la grabación.",
|
||||
"label": "Segundos de captura posterior"
|
||||
},
|
||||
"label": "Retención de alertas",
|
||||
"description": "Ajustes de retención de grabaciones para eventos de alerta, incluidas las duraciones de captura previa/posterior.",
|
||||
"retain": {
|
||||
"label": "Retención de eventos",
|
||||
"description": "Ajustes de retención para grabaciones de eventos de detección.",
|
||||
"days": {
|
||||
"label": "Días de retención",
|
||||
"description": "Número de días durante los que conservar grabaciones de eventos de detección."
|
||||
},
|
||||
"mode": {
|
||||
"label": "Modo de retención",
|
||||
"description": "Modo de retención: all (guarda todos los segmentos), motion (guarda segmentos con movimiento) o active_objects (guarda segmentos con objetos activos)."
|
||||
}
|
||||
}
|
||||
},
|
||||
"label": "Grabación",
|
||||
"description": "Ajustes de grabación y retención para esta cámara.",
|
||||
"enabled": {
|
||||
"label": "Habilitar grabación",
|
||||
"description": "Habilita o deshabilita la grabación para esta cámara."
|
||||
},
|
||||
"expire_interval": {
|
||||
"label": "Intervalo de limpieza de grabaciones",
|
||||
"description": "Minutos entre pasadas de limpieza que eliminan segmentos de grabación caducados."
|
||||
},
|
||||
"export": {
|
||||
"label": "Configuración de exportación",
|
||||
"description": "Ajustes usados al exportar grabaciones, como timelapse y aceleración por hardware.",
|
||||
"hwaccel_args": {
|
||||
"label": "Argumentos hwaccel de exportación",
|
||||
"description": "Argumentos de aceleración por hardware que se usarán en operaciones de exportación/transcodificación."
|
||||
},
|
||||
"max_concurrent": {
|
||||
"label": "Exportaciones simultáneas máximas",
|
||||
"description": "Número máximo de trabajos de exportación que se procesarán al mismo tiempo."
|
||||
}
|
||||
},
|
||||
"preview": {
|
||||
"label": "Configuración de vista previa",
|
||||
"description": "Ajustes que controlan la calidad de las vistas previas de grabaciones mostradas en la interfaz.",
|
||||
"quality": {
|
||||
"label": "Calidad de vista previa",
|
||||
"description": "Nivel de calidad de vista previa (very_low, low, medium, high, very_high)."
|
||||
}
|
||||
},
|
||||
"enabled_in_config": {
|
||||
"label": "Estado de grabación original",
|
||||
"description": "Indica si la grabación estaba habilitada en la configuración estática original."
|
||||
}
|
||||
},
|
||||
"ui": {
|
||||
"dashboard": {
|
||||
"description": "Alterna si esta cámara es visible en toda la interfaz de usuario de Frigate. Desactivar esta opción requerirá editar manualmente la configuración para volver a visualizar esta cámara en la interfaz."
|
||||
"description": "Alterna si esta cámara es visible en toda la interfaz de usuario de Frigate. Desactivar esta opción requerirá editar manualmente la configuración para volver a visualizar esta cámara en la interfaz.",
|
||||
"label": "Mostrar en la interfaz"
|
||||
},
|
||||
"label": "Interfaz de cámara",
|
||||
"description": "Orden de visualización y visibilidad de esta cámara en la interfaz. El orden afecta al panel predeterminado. Para un control más granular, usa grupos de cámaras.",
|
||||
"order": {
|
||||
"label": "Orden en la interfaz",
|
||||
"description": "Orden numérico usado para ordenar la cámara en la interfaz (panel predeterminado y listas); los números más altos aparecen más tarde."
|
||||
}
|
||||
},
|
||||
"live": {
|
||||
"height": {
|
||||
"description": "Altura (en píxeles) para renderizar la transmisión en vivo de jsmpeg en la interfaz web; debe ser <= a la altura de la transmisión de detección."
|
||||
"description": "Altura (en píxeles) para renderizar la transmisión en vivo de jsmpeg en la interfaz web; debe ser <= a la altura de la transmisión de detección.",
|
||||
"label": "Altura en directo"
|
||||
},
|
||||
"description": "Configuraciones utilizadas por la interfaz web para controlar la selección, la resolución y la calidad de transmisiónes en vivo."
|
||||
"description": "Configuraciones utilizadas por la interfaz web para controlar la selección, la resolución y la calidad de transmisiónes en vivo.",
|
||||
"label": "Reproducción en directo",
|
||||
"streams": {
|
||||
"label": "Nombres de flujos en directo",
|
||||
"description": "Asignación de nombres de flujos configurados a nombres de restream/go2rtc usados para la reproducción en directo."
|
||||
},
|
||||
"quality": {
|
||||
"label": "Calidad en directo",
|
||||
"description": "Calidad de codificación para el flujo jsmpeg (1 la más alta, 31 la más baja)."
|
||||
}
|
||||
},
|
||||
"review": {
|
||||
"description": "Configuraciones que controlan las alertas, las detecciones y los resúmenes de revisión de GenAI utilizados por la interfaz de usuario y el almacenamiento de esta cámara.",
|
||||
"alerts": {
|
||||
"required_zones": {
|
||||
"description": "Zonas en las que debe entrar un objeto para ser considerado una alerta; dejar vacío para permitir cualquier zona."
|
||||
"description": "Zonas en las que debe entrar un objeto para ser considerado una alerta; dejar vacío para permitir cualquier zona.",
|
||||
"label": "Zonas requeridas"
|
||||
},
|
||||
"labels": {
|
||||
"description": "Lista de etiquetas de objetos que califican como alertas (por ejemplo: car, person)."
|
||||
"description": "Lista de etiquetas de objetos que califican como alertas (por ejemplo: car, person).",
|
||||
"label": "Etiquetas de alerta"
|
||||
},
|
||||
"label": "Configuración de alertas",
|
||||
"description": "Ajustes sobre qué objetos rastreados generan alertas y cómo se conservan las alertas.",
|
||||
"enabled": {
|
||||
"label": "Habilitar alertas",
|
||||
"description": "Habilita o deshabilita la generación de alertas para esta cámara."
|
||||
},
|
||||
"enabled_in_config": {
|
||||
"label": "Estado original de alertas",
|
||||
"description": "Rastrea si las alertas estaban habilitadas originalmente en la configuración estática."
|
||||
},
|
||||
"cutoff_time": {
|
||||
"label": "Tiempo de corte de alertas",
|
||||
"description": "Segundos que se esperarán tras dejar de haber actividad causante de alerta antes de cortar una alerta."
|
||||
}
|
||||
},
|
||||
"detections": {
|
||||
"required_zones": {
|
||||
"description": "Zonas en las que debe entrar un objeto para ser considerado detectado; dejar vacío para permitir cualquier zona."
|
||||
"description": "Zonas en las que debe entrar un objeto para ser considerado detectado; dejar vacío para permitir cualquier zona.",
|
||||
"label": "Zonas requeridas"
|
||||
},
|
||||
"description": "Configuración para determinar qué objetos rastreados generan detecciones (no alertas) y cómo se retienen dichas detecciones."
|
||||
"description": "Configuración para determinar qué objetos rastreados generan detecciones (no alertas) y cómo se retienen dichas detecciones.",
|
||||
"label": "Configuración de detecciones",
|
||||
"enabled": {
|
||||
"label": "Habilitar detecciones",
|
||||
"description": "Habilita o deshabilita los eventos de detección para esta cámara."
|
||||
},
|
||||
"labels": {
|
||||
"label": "Etiquetas de detección",
|
||||
"description": "Lista de etiquetas de objetos que cuentan como eventos de detección."
|
||||
},
|
||||
"cutoff_time": {
|
||||
"label": "Tiempo de corte de detecciones",
|
||||
"description": "Segundos que se esperarán tras dejar de haber actividad causante de detección antes de cortar una detección."
|
||||
},
|
||||
"enabled_in_config": {
|
||||
"label": "Estado original de detecciones",
|
||||
"description": "Rastrea si las detecciones estaban habilitadas originalmente en la configuración estática."
|
||||
}
|
||||
},
|
||||
"genai": {
|
||||
"image_source": {
|
||||
"description": "Fuente de las imágenes enviadas a GenAI ('preview' o 'recordings'); La opción 'recordings' utiliza fotogramas de mayor calidad, pero requiere más tokens."
|
||||
"description": "Fuente de las imágenes enviadas a GenAI ('preview' o 'recordings'); La opción 'recordings' utiliza fotogramas de mayor calidad, pero requiere más tokens.",
|
||||
"label": "Origen de imagen de revisión"
|
||||
},
|
||||
"additional_concerns": {
|
||||
"description": "Una lista de preocupaciones o notas adicionales que GenAI debería tener en cuenta al evaluar la actividad en esta cámara."
|
||||
"description": "Una lista de preocupaciones o notas adicionales que GenAI debería tener en cuenta al evaluar la actividad en esta cámara.",
|
||||
"label": "Consideraciones adicionales"
|
||||
},
|
||||
"activity_context_prompt": {
|
||||
"description": "Instrucción personalizada que describe qué constituye y qué no una actividad sospechosa, con el fin de proporcionar contexto para los resúmenes generados por GenAI."
|
||||
"description": "Instrucción personalizada que describe qué constituye y qué no una actividad sospechosa, con el fin de proporcionar contexto para los resúmenes generados por GenAI.",
|
||||
"label": "Prompt de contexto de actividad"
|
||||
},
|
||||
"description": "Controla el uso de IA generativa (GenAI) para la elaboración de descripciones y resúmenes de elementos de revisión.",
|
||||
"debug_save_thumbnails": {
|
||||
"description": "Guarde las miniaturas que se envían al proveedor de GenAI para su depuración y revisión."
|
||||
"description": "Guarde las miniaturas que se envían al proveedor de GenAI para su depuración y revisión.",
|
||||
"label": "Guardar miniaturas"
|
||||
},
|
||||
"label": "Configuración de GenAI",
|
||||
"enabled": {
|
||||
"label": "Habilitar descripciones de GenAI",
|
||||
"description": "Habilita o deshabilita las descripciones y resúmenes generados por GenAI para los elementos de revisión."
|
||||
},
|
||||
"alerts": {
|
||||
"label": "Habilitar GenAI para alertas",
|
||||
"description": "Usa GenAI para generar descripciones de elementos de alerta."
|
||||
},
|
||||
"detections": {
|
||||
"label": "Habilitar GenAI para detecciones",
|
||||
"description": "Usa GenAI para generar descripciones de elementos de detección."
|
||||
},
|
||||
"enabled_in_config": {
|
||||
"label": "Estado original de GenAI",
|
||||
"description": "Rastrea si la revisión de GenAI estaba habilitada originalmente en la configuración estática."
|
||||
},
|
||||
"preferred_language": {
|
||||
"label": "Idioma preferido",
|
||||
"description": "Idioma preferido que se solicitará al proveedor de GenAI para las respuestas generadas."
|
||||
}
|
||||
}
|
||||
},
|
||||
"label": "Revisión"
|
||||
},
|
||||
"birdseye": {
|
||||
"description": "Configuración para la vista compuesta Birdseye, que combina las transmisiones de múltiples cámaras en una sola vista."
|
||||
"description": "Configuración para la vista compuesta Birdseye, que combina las transmisiones de múltiples cámaras en una sola vista.",
|
||||
"label": "Vista general",
|
||||
"enabled": {
|
||||
"label": "Habilitar Birdseye",
|
||||
"description": "Habilita o deshabilita la función de vista Birdseye."
|
||||
},
|
||||
"mode": {
|
||||
"label": "Modo de seguimiento",
|
||||
"description": "Modo para incluir cámaras en Birdseye: 'objects', 'motion' o 'continuous'."
|
||||
},
|
||||
"order": {
|
||||
"label": "Posición",
|
||||
"description": "Posición numérica que controla el orden de la cámara en el diseño de Birdseye."
|
||||
}
|
||||
},
|
||||
"ffmpeg": {
|
||||
"retry_interval": {
|
||||
"description": "Segundos de espera antes de intentar reconectar la transmisión de una cámara tras un fallo. El valor predeterminado es 10."
|
||||
"description": "Segundos de espera antes de intentar reconectar la transmisión de una cámara tras un fallo. El valor predeterminado es 10.",
|
||||
"label": "Tiempo de reintento de FFmpeg"
|
||||
},
|
||||
"path": {
|
||||
"description": "Ruta al binario de FFmpeg que se va a utilizar o un alias de versión (\"5.0\" o \"7.0\")."
|
||||
"description": "Ruta al binario de FFmpeg que se va a utilizar o un alias de versión (\"5.0\" o \"7.0\").",
|
||||
"label": "Ruta de FFmpeg"
|
||||
},
|
||||
"output_args": {
|
||||
"description": "Argumentos de salida predeterminados utilizados para diferentes roles de FFmpeg, tales como detección y grabación."
|
||||
"description": "Argumentos de salida predeterminados utilizados para diferentes roles de FFmpeg, tales como detección y grabación.",
|
||||
"label": "Argumentos de salida",
|
||||
"detect": {
|
||||
"label": "Argumentos de salida de detección",
|
||||
"description": "Argumentos de salida predeterminados para los flujos con rol de detección."
|
||||
},
|
||||
"record": {
|
||||
"label": "Argumentos de salida de grabación",
|
||||
"description": "Argumentos de salida predeterminados para los flujos con rol de grabación."
|
||||
}
|
||||
},
|
||||
"description": "Configuración de FFmpeg, incluyendo la ruta del binario, argumentos, opciones de aceleración por hardware y argumentos de salida por rol."
|
||||
"description": "Configuración de FFmpeg, incluyendo la ruta del binario, argumentos, opciones de aceleración por hardware y argumentos de salida por rol.",
|
||||
"label": "FFmpeg",
|
||||
"global_args": {
|
||||
"label": "Argumentos globales de FFmpeg",
|
||||
"description": "Argumentos globales pasados a los procesos de FFmpeg."
|
||||
},
|
||||
"hwaccel_args": {
|
||||
"label": "Argumentos de aceleración por hardware",
|
||||
"description": "Argumentos de aceleración por hardware para FFmpeg. Se recomiendan preajustes específicos del proveedor."
|
||||
},
|
||||
"input_args": {
|
||||
"label": "Argumentos de entrada",
|
||||
"description": "Argumentos de entrada aplicados a los flujos de entrada de FFmpeg."
|
||||
},
|
||||
"apple_compatibility": {
|
||||
"label": "Compatibilidad con Apple",
|
||||
"description": "Habilita el etiquetado HEVC para mejorar la compatibilidad con reproductores de Apple al grabar H.265."
|
||||
},
|
||||
"gpu": {
|
||||
"label": "Índice de GPU",
|
||||
"description": "Índice de GPU predeterminado usado para la aceleración por hardware si está disponible."
|
||||
},
|
||||
"inputs": {
|
||||
"label": "Entradas de cámara",
|
||||
"description": "Lista de definiciones de flujos de entrada (rutas y roles) para esta cámara.",
|
||||
"path": {
|
||||
"label": "Ruta de entrada",
|
||||
"description": "URL o ruta del flujo de entrada de la cámara."
|
||||
},
|
||||
"roles": {
|
||||
"label": "Roles de entrada",
|
||||
"description": "Roles para este flujo de entrada."
|
||||
},
|
||||
"global_args": {
|
||||
"label": "Argumentos globales de FFmpeg",
|
||||
"description": "Argumentos globales de FFmpeg para este flujo de entrada."
|
||||
},
|
||||
"hwaccel_args": {
|
||||
"label": "Argumentos de aceleración por hardware",
|
||||
"description": "Argumentos de aceleración por hardware para este flujo de entrada."
|
||||
},
|
||||
"input_args": {
|
||||
"label": "Argumentos de entrada",
|
||||
"description": "Argumentos de entrada específicos para este flujo."
|
||||
}
|
||||
}
|
||||
},
|
||||
"face_recognition": {
|
||||
"label": "Reconocimiento facial",
|
||||
"description": "Ajustes de detección y reconocimiento facial para esta cámara.",
|
||||
"enabled": {
|
||||
"label": "Habilitar reconocimiento facial",
|
||||
"description": "Habilita o deshabilita el reconocimiento facial."
|
||||
},
|
||||
"min_area": {
|
||||
"label": "Área mínima de rostro",
|
||||
"description": "Área mínima (píxeles) del cuadro de un rostro detectado necesaria para intentar el reconocimiento."
|
||||
}
|
||||
},
|
||||
"semantic_search": {
|
||||
"label": "Búsqueda semántica",
|
||||
"description": "Ajustes de búsqueda semántica, que crea y consulta embeddings de objetos para encontrar elementos similares.",
|
||||
"triggers": {
|
||||
"label": "Activadores",
|
||||
"description": "Acciones y criterios de coincidencia para activadores de búsqueda semántica específicos de la cámara.",
|
||||
"friendly_name": {
|
||||
"label": "Nombre descriptivo",
|
||||
"description": "Nombre descriptivo opcional mostrado en la interfaz para este activador."
|
||||
},
|
||||
"enabled": {
|
||||
"label": "Habilitar este activador",
|
||||
"description": "Habilita o deshabilita este activador de búsqueda semántica."
|
||||
},
|
||||
"type": {
|
||||
"label": "Tipo de activador",
|
||||
"description": "Tipo de activador: 'thumbnail' (coincidir con imagen) o 'description' (coincidir con texto)."
|
||||
},
|
||||
"data": {
|
||||
"label": "Contenido del activador",
|
||||
"description": "Frase de texto o ID de miniatura que se comparará con objetos rastreados."
|
||||
},
|
||||
"threshold": {
|
||||
"label": "Umbral del activador",
|
||||
"description": "Puntuación mínima de similitud (0-1) necesaria para activar este activador."
|
||||
},
|
||||
"actions": {
|
||||
"label": "Acciones del activador",
|
||||
"description": "Lista de acciones que se ejecutarán cuando el activador coincida (notification, sub_label, attribute)."
|
||||
}
|
||||
}
|
||||
},
|
||||
"snapshots": {
|
||||
"label": "Instantáneas",
|
||||
"description": "Ajustes de instantáneas generadas por la API de objetos rastreados para esta cámara.",
|
||||
"enabled": {
|
||||
"label": "Habilitar instantáneas",
|
||||
"description": "Habilita o deshabilita el guardado de instantáneas para esta cámara."
|
||||
},
|
||||
"timestamp": {
|
||||
"label": "Superposición de marca de tiempo",
|
||||
"description": "Superpone una marca de tiempo en las instantáneas de la API."
|
||||
},
|
||||
"bounding_box": {
|
||||
"label": "Superposición de cuadro delimitador",
|
||||
"description": "Dibuja cuadros delimitadores para los objetos rastreados en las instantáneas de la API."
|
||||
},
|
||||
"crop": {
|
||||
"label": "Recortar instantánea",
|
||||
"description": "Recorta las instantáneas de la API al cuadro delimitador del objeto detectado."
|
||||
},
|
||||
"required_zones": {
|
||||
"label": "Zonas requeridas",
|
||||
"description": "Zonas en las que debe entrar un objeto para que se guarde una instantánea."
|
||||
},
|
||||
"height": {
|
||||
"label": "Altura de instantánea",
|
||||
"description": "Altura (píxeles) a la que redimensionar las instantáneas de la API; déjalo vacío para conservar el tamaño original."
|
||||
},
|
||||
"retain": {
|
||||
"label": "Retención de instantáneas",
|
||||
"description": "Ajustes de retención de instantáneas, incluidos días predeterminados y sobrescrituras por objeto.",
|
||||
"default": {
|
||||
"label": "Retención predeterminada",
|
||||
"description": "Número predeterminado de días durante los que conservar instantáneas."
|
||||
},
|
||||
"mode": {
|
||||
"label": "Modo de retención",
|
||||
"description": "Modo de retención: all (guarda todos los segmentos), motion (guarda segmentos con movimiento) o active_objects (guarda segmentos con objetos activos)."
|
||||
},
|
||||
"objects": {
|
||||
"label": "Retención por objeto",
|
||||
"description": "Sobrescrituras por objeto para los días de retención de instantáneas."
|
||||
}
|
||||
},
|
||||
"quality": {
|
||||
"label": "Calidad de instantánea",
|
||||
"description": "Calidad de codificación de las instantáneas guardadas (0-100)."
|
||||
}
|
||||
},
|
||||
"timestamp_style": {
|
||||
"label": "Estilo de marca de tiempo",
|
||||
"description": "Opciones de estilo para marcas de tiempo integradas aplicadas a grabaciones e instantáneas.",
|
||||
"position": {
|
||||
"label": "Posición de marca de tiempo",
|
||||
"description": "Posición de la marca de tiempo en la imagen (tl/tr/bl/br)."
|
||||
},
|
||||
"format": {
|
||||
"label": "Formato de marca de tiempo",
|
||||
"description": "Cadena de formato de fecha y hora usada para las marcas de tiempo (códigos de formato datetime de Python)."
|
||||
},
|
||||
"color": {
|
||||
"label": "Color de marca de tiempo",
|
||||
"description": "Valores de color RGB para el texto de la marca de tiempo (todos los valores 0-255).",
|
||||
"red": {
|
||||
"label": "Rojo",
|
||||
"description": "Componente rojo (0-255) para el color de la marca de tiempo."
|
||||
},
|
||||
"green": {
|
||||
"label": "Verde",
|
||||
"description": "Componente verde (0-255) para el color de la marca de tiempo."
|
||||
},
|
||||
"blue": {
|
||||
"label": "Azul",
|
||||
"description": "Componente azul (0-255) para el color de la marca de tiempo."
|
||||
}
|
||||
},
|
||||
"thickness": {
|
||||
"label": "Grosor de marca de tiempo",
|
||||
"description": "Grosor de línea del texto de la marca de tiempo."
|
||||
},
|
||||
"effect": {
|
||||
"label": "Efecto de marca de tiempo",
|
||||
"description": "Efecto visual para el texto de la marca de tiempo (none, solid, shadow)."
|
||||
}
|
||||
},
|
||||
"best_image_timeout": {
|
||||
"label": "Tiempo de espera de mejor imagen",
|
||||
"description": "Tiempo que se esperará la imagen con la puntuación de confianza más alta."
|
||||
},
|
||||
"type": {
|
||||
"label": "Tipo de cámara",
|
||||
"description": "Tipo de cámara"
|
||||
},
|
||||
"webui_url": {
|
||||
"label": "URL de la cámara",
|
||||
"description": "URL para visitar la cámara directamente desde la página del sistema"
|
||||
},
|
||||
"profiles": {
|
||||
"label": "Perfiles",
|
||||
"description": "Perfiles de configuración con nombre y sobrescrituras parciales que pueden activarse en tiempo de ejecución."
|
||||
},
|
||||
"enabled_in_config": {
|
||||
"label": "Estado original de cámara",
|
||||
"description": "Mantiene el registro del estado original de la cámara."
|
||||
}
|
||||
}
|
||||
|
||||
File diff suppressed because it is too large
Load Diff
@ -116,5 +116,15 @@
|
||||
"animal": "Animal",
|
||||
"postnord": "PostNord",
|
||||
"usps": "USPS",
|
||||
"gls": "GLS"
|
||||
"gls": "GLS",
|
||||
"canada_post": "Canada Post",
|
||||
"royal_mail": "Royal Mail",
|
||||
"school_bus": "Autobús escolar",
|
||||
"skunk": "Mofeta",
|
||||
"kangaroo": "Canguro",
|
||||
"baby": "Bebé",
|
||||
"baby_stroller": "Cochecito de bebé",
|
||||
"rickshaw": "Rickshaw",
|
||||
"Rodent": "Roedor",
|
||||
"rodent": "Roedor"
|
||||
}
|
||||
|
||||
@ -1 +1,69 @@
|
||||
{}
|
||||
{
|
||||
"documentTitle": "Chat - Frigate",
|
||||
"title": "Frigate Chat",
|
||||
"subtitle": "Tu asistente de IA para la gestión de cámaras y análisis",
|
||||
"placeholder": "Pregunta cualquier cosa...",
|
||||
"error": "Algo salió mal. Por favor, inténtalo de nuevo.",
|
||||
"processing": "Procesando...",
|
||||
"toolsUsed": "Usado: {{tools}}",
|
||||
"showTools": "Mostrar herramientas ({{count}})",
|
||||
"hideTools": "Ocultar herramientas",
|
||||
"call": "Llamar",
|
||||
"result": "Resultado",
|
||||
"arguments": "Argumentos:",
|
||||
"response": "Respuesta:",
|
||||
"attachment_chip_label": "{{label}} en {{camera}}",
|
||||
"attachment_chip_remove": "Eliminar adjunto",
|
||||
"open_in_explore": "Abrir en Explorar",
|
||||
"attach_event_aria": "Adjuntar evento {{eventId}}",
|
||||
"attachment_picker_paste_label": "O pega el ID del evento",
|
||||
"attachment_picker_attach": "Adjuntar",
|
||||
"attachment_picker_placeholder": "Adjuntar un evento",
|
||||
"quick_reply_find_similar": "Buscar avistamientos similares",
|
||||
"quick_reply_tell_me_more": "Cuéntame más sobre esto",
|
||||
"quick_reply_when_else": "¿Cuándo más se vio?",
|
||||
"quick_reply_find_similar_text": "Buscar avistamientos similares a este.",
|
||||
"quick_reply_tell_me_more_text": "Cuéntame más sobre este.",
|
||||
"quick_reply_when_else_text": "¿Cuándo más se vio esto?",
|
||||
"anchor": "Referencia",
|
||||
"similarity_score": "Similitud",
|
||||
"no_similar_objects_found": "No se encontraron objetos similares.",
|
||||
"semantic_search_required": "La búsqueda semántica debe estar activada para encontrar objetos similares.",
|
||||
"send": "Enviar",
|
||||
"suggested_requests": "Prueba preguntando:",
|
||||
"starting_requests": {
|
||||
"show_recent_events": "Mostrar eventos recientes",
|
||||
"show_camera_status": "Mostrar estado de la cámara",
|
||||
"recap": "¿Qué ha pasado mientras estaba fuera?",
|
||||
"watch_camera": "Vigilar una cámara en busca de actividad"
|
||||
},
|
||||
"starting_requests_prompts": {
|
||||
"show_recent_events": "Muéstrame los eventos recientes de la última hora",
|
||||
"show_camera_status": "¿Cuál es el estado actual de mis cámaras?",
|
||||
"recap": "¿Qué ha pasado mientras estaba fuera?",
|
||||
"watch_camera": "Vigila la puerta principal y avísame si aparece alguien"
|
||||
},
|
||||
"new_chat": "Nuevo chat",
|
||||
"settings": {
|
||||
"title": "Ajustes del chat",
|
||||
"show_stats": {
|
||||
"title": "Mostrar estadísticas",
|
||||
"desc": "Mostrar la velocidad de generación y el tamaño del contexto en las respuestas del chat.",
|
||||
"while_generating": "Durante la generación",
|
||||
"always": "Siempre"
|
||||
},
|
||||
"auto_scroll": {
|
||||
"title": "Desplazamiento automático",
|
||||
"desc": "Seguir los mensajes nuevos a medida que llegan."
|
||||
}
|
||||
},
|
||||
"stats": {
|
||||
"context": "{{tokens}} tokens",
|
||||
"tokens_per_second": "{{rate}} t/s"
|
||||
},
|
||||
"reasoning": {
|
||||
"active": "Razonando…",
|
||||
"show": "Mostrar razonamiento",
|
||||
"hide": "Ocultar razonamiento"
|
||||
}
|
||||
}
|
||||
|
||||
@ -146,7 +146,7 @@
|
||||
"generateSuccess": "Imágenes de ejemplo generadas correctamente",
|
||||
"missingStatesWarning": {
|
||||
"title": "Faltan Ejemplos de Estado",
|
||||
"description": "Se recomienda seleccionar ejemplos para todos los estados para obtener mejores resultados. Puede continuar sin seleccionar todos los estados, pero el modelo no se entrenará hasta que todos los estados tengan imágenes. Después de continuar, use la vista \"Clasificaciones recientes\" para clasificar las imágenes de los estados faltantes y luego entrene el modelo."
|
||||
"description": "No todas las clases tienen ejemplos. Prueba a generar nuevos ejemplos para encontrar la clase que falta, o continúa y usa la vista de Clasificaciones recientes para añadir imágenes más tarde."
|
||||
},
|
||||
"allImagesRequired_one": "Por favor clasifique todas las imágenes. Queda {{count}} imagen.",
|
||||
"allImagesRequired_many": "Por favor clasifique todas las imágenes. Quedan {{count}} imágenes.",
|
||||
|
||||
@ -32,7 +32,9 @@
|
||||
},
|
||||
"camera": "Cámara",
|
||||
"recordings": {
|
||||
"documentTitle": "Grabaciones - Frigate"
|
||||
"documentTitle": "Grabaciones - Frigate",
|
||||
"invalidSharedLink": "No se puede abrir el enlace de la grabación con marca de tiempo debido a un error de análisis.",
|
||||
"invalidSharedCamera": "No se puede abrir el enlace de la grabación con marca de tiempo debido a una cámara desconocida o no autorizada."
|
||||
},
|
||||
"calendarFilter": {
|
||||
"last24Hours": "Últimas 24 horas"
|
||||
@ -66,5 +68,28 @@
|
||||
"select_all": "Todas",
|
||||
"normalActivity": "Normal",
|
||||
"needsReview": "Necesita revisión",
|
||||
"securityConcern": "Aviso de seguridad"
|
||||
"securityConcern": "Aviso de seguridad",
|
||||
"motionSearch": {
|
||||
"menuItem": "Búsqueda de movimiento",
|
||||
"openMenu": "Opciones de cámara"
|
||||
},
|
||||
"motionPreviews": {
|
||||
"menuItem": "Ver vistas previas de movimiento",
|
||||
"title": "Vistas previas de movimiento: {{camera}}",
|
||||
"mobileSettingsTitle": "Ajustes de vistas previas de movimiento",
|
||||
"mobileSettingsDesc": "Ajusta la velocidad de reproducción y el atenuado, y elige una fecha para revisar clips solo de movimiento.",
|
||||
"dim": "Atenuar",
|
||||
"dimAria": "Ajustar intensidad de atenuado",
|
||||
"dimDesc": "Aumenta el atenuado para mejorar la visibilidad de las áreas con movimiento.",
|
||||
"speed": "Velocidad",
|
||||
"speedAria": "Seleccionar velocidad de reproducción de las vistas previas",
|
||||
"speedDesc": "Elige la velocidad a la que se reproducen los clips de vista previa.",
|
||||
"back": "Atrás",
|
||||
"empty": "No hay vistas previas disponibles",
|
||||
"noPreview": "Vista previa no disponible",
|
||||
"seekAria": "Mover el reproductor de {{camera}} a {{time}}",
|
||||
"filter": "Filtrar",
|
||||
"filterDesc": "Selecciona áreas para mostrar solo clips con movimiento en esas regiones.",
|
||||
"filterClear": "Limpiar"
|
||||
}
|
||||
}
|
||||
|
||||
@ -226,6 +226,10 @@
|
||||
},
|
||||
"more": {
|
||||
"aria": "Más"
|
||||
},
|
||||
"debugReplay": {
|
||||
"label": "Reproducción de depuración",
|
||||
"aria": "Ver este objeto rastreado en la reproducción de depuración"
|
||||
}
|
||||
},
|
||||
"dialog": {
|
||||
@ -282,7 +286,10 @@
|
||||
"zones": "Zonas",
|
||||
"area": "Área",
|
||||
"score": "Puntuación",
|
||||
"ratio": "Ratio(proporción)"
|
||||
"ratio": "Ratio(proporción)",
|
||||
"computedScore": "Puntuación calculada",
|
||||
"topScore": "Puntuación más alta",
|
||||
"toggleAdvancedScores": "Alternar puntuaciones avanzadas"
|
||||
},
|
||||
"entered_zone": "{{label}} ha entrado en {{zones}}"
|
||||
},
|
||||
|
||||
@ -13,7 +13,9 @@
|
||||
"toast": {
|
||||
"error": {
|
||||
"renameExportFailed": "No se pudo renombrar la exportación: {{errorMessage}}",
|
||||
"assignCaseFailed": "Fallo en la actualización de la asignación de caso: {{errorMessage}}"
|
||||
"assignCaseFailed": "Fallo en la actualización de la asignación de caso: {{errorMessage}}",
|
||||
"caseSaveFailed": "No se pudo guardar el caso: {{errorMessage}}",
|
||||
"caseDeleteFailed": "No se pudo eliminar el caso: {{errorMessage}}"
|
||||
}
|
||||
},
|
||||
"deleteExport.desc": "¿Estás seguro de que quieres eliminar {{exportName}}?",
|
||||
@ -38,10 +40,89 @@
|
||||
"descriptionLabel": "Descripción"
|
||||
},
|
||||
"toolbar": {
|
||||
"addExport": "Añadir Exportación"
|
||||
"addExport": "Añadir Exportación",
|
||||
"newCase": "Nuevo caso",
|
||||
"editCase": "Editar caso",
|
||||
"deleteCase": "Eliminar caso"
|
||||
},
|
||||
"deleteCase": {
|
||||
"label": "Eliminar caso",
|
||||
"desc": "¿Estás seguro de que quieres eliminar {{caseName}}?"
|
||||
"desc": "¿Estás seguro de que quieres eliminar {{caseName}}?",
|
||||
"descKeepExports": "Las exportaciones seguirán disponibles como exportaciones sin categoría.",
|
||||
"descDeleteExports": "Todas las exportaciones de este caso se eliminarán de forma permanente.",
|
||||
"deleteExports": "Eliminar también las exportaciones"
|
||||
},
|
||||
"caseCard": {
|
||||
"emptyCase": "Aún no hay exportaciones"
|
||||
},
|
||||
"jobCard": {
|
||||
"defaultName": "Exportación de {{camera}}",
|
||||
"queued": "En cola",
|
||||
"running": "En ejecución",
|
||||
"preparing": "Preparando",
|
||||
"copying": "Copiando",
|
||||
"encoding": "Codificando",
|
||||
"encodingRetry": "Codificando (reintento)",
|
||||
"finalizing": "Finalizando"
|
||||
},
|
||||
"caseView": {
|
||||
"noDescription": "Sin descripción",
|
||||
"createdAt": "Creado {{value}}",
|
||||
"exportCount_one": "1 exportación",
|
||||
"exportCount_other": "{{count}} exportaciones",
|
||||
"cameraCount_one": "1 cámara",
|
||||
"cameraCount_other": "{{count}} cámaras",
|
||||
"showMore": "Mostrar más",
|
||||
"showLess": "Mostrar menos",
|
||||
"emptyTitle": "Este caso está vacío",
|
||||
"emptyDescription": "Añade exportaciones existentes sin categorizar para mantener el caso organizado.",
|
||||
"emptyDescriptionNoExports": "Todavía no hay exportaciones sin categorizar disponibles para añadir."
|
||||
},
|
||||
"caseEditor": {
|
||||
"createTitle": "Crear caso",
|
||||
"editTitle": "Editar caso",
|
||||
"namePlaceholder": "Nombre del caso",
|
||||
"descriptionPlaceholder": "Añade notas o contexto para este caso"
|
||||
},
|
||||
"addExportDialog": {
|
||||
"title": "Añadir exportación a {{caseName}}",
|
||||
"searchPlaceholder": "Buscar exportaciones sin categorizar",
|
||||
"empty": "Ninguna exportación sin categorizar coincide con esta búsqueda.",
|
||||
"addButton_one": "Añadir 1 exportación",
|
||||
"addButton_other": "Añadir {{count}} exportaciones",
|
||||
"adding": "Añadiendo..."
|
||||
},
|
||||
"selected_one": "{{count}} seleccionados",
|
||||
"selected_other": "{{count}} seleccionados",
|
||||
"bulkActions": {
|
||||
"addToCase": "Añadir al caso",
|
||||
"moveToCase": "Mover al caso",
|
||||
"removeFromCase": "Eliminar del caso",
|
||||
"delete": "Eliminar",
|
||||
"deleteNow": "Eliminar ahora"
|
||||
},
|
||||
"bulkDelete": {
|
||||
"title": "Eliminar exportaciones",
|
||||
"desc_one": "¿Seguro que quieres eliminar {{count}} exportación?",
|
||||
"desc_other": "¿Seguro que quieres eliminar {{count}} exportaciones?"
|
||||
},
|
||||
"bulkRemoveFromCase": {
|
||||
"title": "Eliminar del caso",
|
||||
"desc_one": "¿Eliminar {{count}} exportación de este caso?",
|
||||
"desc_other": "¿Eliminar {{count}} exportaciones de este caso?",
|
||||
"descKeepExports": "Las exportaciones se moverán a sin categorizar.",
|
||||
"descDeleteExports": "Las exportaciones se eliminarán permanentemente.",
|
||||
"deleteExports": "Eliminar exportaciones en su lugar"
|
||||
},
|
||||
"bulkToast": {
|
||||
"success": {
|
||||
"delete": "Exportaciones eliminadas correctamente",
|
||||
"reassign": "Asignación de caso actualizada correctamente",
|
||||
"remove": "Exportaciones eliminadas del caso correctamente"
|
||||
},
|
||||
"error": {
|
||||
"deleteFailed": "No se pudieron eliminar las exportaciones: {{errorMessage}}",
|
||||
"reassignFailed": "No se pudo actualizar la asignación del caso: {{errorMessage}}"
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
@ -30,7 +30,11 @@
|
||||
"title": "Reconocimientos Recientes",
|
||||
"aria": "Seleccionar reconocimientos recientes",
|
||||
"empty": "No hay intentos recientes de reconocimiento facial",
|
||||
"titleShort": "Reciente"
|
||||
"titleShort": "Reciente",
|
||||
"emptyNoLibrary": {
|
||||
"title": "Subir una cara",
|
||||
"description": "Debes añadir al menos una cara a la biblioteca para que el reconocimiento facial funcione."
|
||||
}
|
||||
},
|
||||
"selectItem": "Seleccionar {{item}}",
|
||||
"selectFace": "Seleccionar rostro",
|
||||
|
||||
@ -69,7 +69,8 @@
|
||||
},
|
||||
"recording": {
|
||||
"enable": "Habilitar grabación",
|
||||
"disable": "Deshabilitar grabación"
|
||||
"disable": "Deshabilitar grabación",
|
||||
"disabledInConfig": "La grabación debe activarse primero en Ajustes para esta cámara."
|
||||
},
|
||||
"snapshots": {
|
||||
"enable": "Habilitar capturas de pantalla",
|
||||
|
||||
@ -1 +1,77 @@
|
||||
{}
|
||||
{
|
||||
"documentTitle": "Búsqueda por movimiento - Frigate",
|
||||
"title": "Búsqueda por movimiento",
|
||||
"description": "Dibuja un polígono para definir la región de interés y especifica un intervalo de tiempo para buscar cambios de movimiento dentro de esa región.",
|
||||
"selectCamera": "Búsqueda por movimiento se está cargando",
|
||||
"startSearch": "Iniciar búsqueda",
|
||||
"searchStarted": "Búsqueda iniciada",
|
||||
"searchCancelled": "Búsqueda cancelada",
|
||||
"cancelSearch": "Cancelar",
|
||||
"searching": "Búsqueda en progreso.",
|
||||
"searchComplete": "Búsqueda completada",
|
||||
"noResultsYet": "Ejecuta una búsqueda para encontrar cambios de movimiento en la región seleccionada",
|
||||
"noChangesFound": "No se detectaron cambios de píxeles en la región seleccionada",
|
||||
"changesFound_one": "Encontrado {{count}} cambio de movimiento",
|
||||
"changesFound_many": "Encontrados {{count}} cambios de movimiento",
|
||||
"changesFound_other": "Encontrados {{count}} cambios de movimiento",
|
||||
"framesProcessed": "{{count}} fotogramas procesados",
|
||||
"jumpToTime": "Saltar a este tiempo",
|
||||
"results": "Resultados",
|
||||
"showSegmentHeatmap": "Mapa de calor",
|
||||
"newSearch": "Nueva búsqueda",
|
||||
"clearResults": "Borrar resultados",
|
||||
"clearROI": "Borrar polígono",
|
||||
"polygonControls": {
|
||||
"points_one": "{{count}} punto",
|
||||
"points_many": "{{count}} puntos",
|
||||
"points_other": "{{count}} puntos",
|
||||
"undo": "Deshacer el último punto",
|
||||
"reset": "Restablecer polígono"
|
||||
},
|
||||
"motionHeatmapLabel": "Mapa de calor de movimiento",
|
||||
"dialog": {
|
||||
"title": "Búsqueda de movimiento",
|
||||
"cameraLabel": "Cámara",
|
||||
"previewAlt": "Vista previa de la cámara {{camera}}"
|
||||
},
|
||||
"timeRange": {
|
||||
"title": "Rango de búsqueda",
|
||||
"start": "Hora de inicio",
|
||||
"end": "Hora de finalización"
|
||||
},
|
||||
"settings": {
|
||||
"title": "Ajustes de búsqueda",
|
||||
"parallelMode": "Modo paralelo",
|
||||
"parallelModeDesc": "Analiza varios segmentos de grabación al mismo tiempo (más rápido, pero consume significativamente más CPU)",
|
||||
"threshold": "Umbral de sensibilidad",
|
||||
"thresholdDesc": "Los valores más bajos detectan cambios más pequeños (1-255)",
|
||||
"minArea": "Área mínima de cambio",
|
||||
"minAreaDesc": "Porcentaje mínimo de la región de interés que debe cambiar para considerarse significativo",
|
||||
"frameSkip": "Salto de fotogramas",
|
||||
"frameSkipDesc": "Procesa cada N fotogramas. Establécelo según la tasa de FPS de tu cámara para procesar un fotograma por segundo (p. ej., 5 para una cámara de 5 FPS, 30 para una cámara de 30 FPS). Los valores más altos serán más rápidos, pero pueden omitir eventos de movimiento breves.",
|
||||
"maxResults": "Resultados máximos",
|
||||
"maxResultsDesc": "Detener después de esta cantidad de marcas de tiempo coincidentes"
|
||||
},
|
||||
"errors": {
|
||||
"noCamera": "Selecciona una cámara",
|
||||
"noROI": "Dibuja una región de interés",
|
||||
"noTimeRange": "Selecciona un rango de tiempo",
|
||||
"invalidTimeRange": "La hora de fin debe ser posterior a la hora de inicio",
|
||||
"searchFailed": "La búsqueda falló: {{message}}",
|
||||
"polygonTooSmall": "El polígono debe tener al menos 3 puntos",
|
||||
"unknown": "Error desconocido"
|
||||
},
|
||||
"changePercentage": "{{percentage}}% cambiado",
|
||||
"metrics": {
|
||||
"title": "Métricas de búsqueda",
|
||||
"segmentsScanned": "Segmentos analizados",
|
||||
"segmentsProcessed": "Procesado",
|
||||
"segmentsSkippedInactive": "Omitido (sin actividad)",
|
||||
"segmentsSkippedHeatmap": "Omitido (sin superposición de ROI)",
|
||||
"fallbackFullRange": "Análisis completo de respaldo",
|
||||
"framesDecoded": "Fotogramas decodificados",
|
||||
"wallTime": "Tiempo de búsqueda",
|
||||
"segmentErrors": "Errores de segmento",
|
||||
"seconds": "{{seconds}} s"
|
||||
}
|
||||
}
|
||||
|
||||
@ -1 +1,59 @@
|
||||
{}
|
||||
{
|
||||
"title": "Depuración de reproducción",
|
||||
"description": "Reproducir grabaciones de cámara para depuración. La lista de objetos muestra un resumen con retraso temporal de los objetos detectados y la pestaña Mensajes muestra un flujo de los mensajes internos de Frigate de la grabación reproducida.",
|
||||
"websocket_messages": "Mensajes",
|
||||
"dialog": {
|
||||
"title": "Iniciar depuración de reproducción",
|
||||
"description": "Crea una cámara de reproducción temporal que reproduzca en bucle imágenes históricas para depurar problemas de detección y seguimiento de objetos. La cámara de reproducción tendrá la misma configuración de detección que la cámara de origen. Elige un intervalo de tiempo para comenzar.",
|
||||
"camera": "Cámara de origen",
|
||||
"timeRange": "Intervalo de tiempo",
|
||||
"preset": {
|
||||
"1m": "Último 1 minuto",
|
||||
"5m": "Últimos 5 minutos",
|
||||
"timeline": "Desde la línea de tiempo",
|
||||
"custom": "Personalizado"
|
||||
},
|
||||
"startButton": "Iniciar reproducción",
|
||||
"selectFromTimeline": "Seleccionar",
|
||||
"starting": "Iniciando reproducción...",
|
||||
"startLabel": "Iniciar",
|
||||
"endLabel": "Fin",
|
||||
"toast": {
|
||||
"error": "No se pudo iniciar la reproducción de depuración: {{error}}",
|
||||
"alreadyActive": "Ya hay una sesión de reproducción activa",
|
||||
"stopError": "No se pudo detener la reproducción de depuración: {{error}}",
|
||||
"goToReplay": "Ir a la reproducción"
|
||||
}
|
||||
},
|
||||
"page": {
|
||||
"noSession": "No hay ninguna sesión activa de reproducción de depuración",
|
||||
"noSessionDesc": "Inicia una reproducción de depuración desde la vista Historial haciendo clic en el botón Acciones de la barra de herramientas y seleccionando Reproducción de depuración.",
|
||||
"goToRecordings": "Ir al historial",
|
||||
"preparingClip": "Preparando clip…",
|
||||
"preparingClipDesc": "Frigate está uniendo las grabaciones del intervalo de tiempo seleccionado. Esto puede tardar un minuto en intervalos más largos.",
|
||||
"startingCamera": "Iniciando reproducción de depuración…",
|
||||
"startError": {
|
||||
"title": "No se pudo iniciar la reproducción de depuración",
|
||||
"back": "Volver al historial"
|
||||
},
|
||||
"sourceCamera": "Cámara de origen",
|
||||
"replayCamera": "Cámara de reproducción",
|
||||
"initializingReplay": "Inicializando reproducción de depuración…",
|
||||
"stoppingReplay": "Deteniendo repetición de depuración...",
|
||||
"stopReplay": "Detener repetición",
|
||||
"confirmStop": {
|
||||
"title": "¿Detener repetición de depuración?",
|
||||
"description": "Esto detendrá la sesión y eliminará todos los datos temporales. ¿Estás seguro?",
|
||||
"confirm": "Detener repetición",
|
||||
"cancel": "Cancelar"
|
||||
},
|
||||
"activity": "Actividad",
|
||||
"objects": "Lista de objetos",
|
||||
"audioDetections": "Detecciones de audio",
|
||||
"noActivity": "No se detectó actividad",
|
||||
"activeTracking": "Seguimiento activo",
|
||||
"noActiveTracking": "No hay seguimiento activo",
|
||||
"configuration": "Configuración",
|
||||
"configurationDesc": "Ajusta con precisión la detección de movimiento y los ajustes de seguimiento de objetos para la cámara de repetición de depuración. No se guardará ningún cambio en el archivo de configuración de Frigate."
|
||||
}
|
||||
}
|
||||
|
||||
@ -16,7 +16,8 @@
|
||||
"globalConfig": "Configuración Global - Frigate",
|
||||
"cameraConfig": "Configuración de Cámara - Frigate",
|
||||
"maintenance": "Mantenimiento - Frigate",
|
||||
"profiles": "Perfiles - Frigate"
|
||||
"profiles": "Perfiles - Frigate",
|
||||
"detectorsAndModel": "Detectores y modelo - Frigate"
|
||||
},
|
||||
"menu": {
|
||||
"cameras": "Configuración de Cámara",
|
||||
@ -42,7 +43,7 @@
|
||||
"globalDetect": "Detección de Objetos",
|
||||
"globalRecording": "Grabación",
|
||||
"globalSnapshots": "Instantáneas",
|
||||
"globalFfmpeg": "FFmpeg",
|
||||
"globalFfmpeg": "arguments,Introduce",
|
||||
"globalMotion": "Detección de Movimiento",
|
||||
"globalObjects": "Objetos",
|
||||
"globalReview": "Revisión",
|
||||
@ -50,7 +51,49 @@
|
||||
"globalLivePlayback": "Reproducción en Vivo",
|
||||
"globalTimestampStyle": "Estilo de Marca de Tiempo",
|
||||
"systemDatabase": "Base de Datos",
|
||||
"systemAuthentication": "Autenticación"
|
||||
"systemAuthentication": "Autenticación",
|
||||
"systemTls": "TLS",
|
||||
"systemNetworking": "Red",
|
||||
"systemProxy": "Proxy",
|
||||
"systemUi": "Interfaz",
|
||||
"systemLogging": "Registro",
|
||||
"systemEnvironmentVariables": "Variables de entorno",
|
||||
"systemTelemetry": "Telemetría",
|
||||
"systemBirdseye": "Birdseye",
|
||||
"systemFfmpeg": "FFmpeg",
|
||||
"systemDetectorHardware": "Hardware del detector",
|
||||
"systemDetectionModel": "Modelo de detección",
|
||||
"systemMqtt": "MQTT",
|
||||
"systemGo2rtcStreams": "Flujos go2rtc",
|
||||
"integrationSemanticSearch": "Búsqueda semántica",
|
||||
"integrationGenerativeAi": "IA generativa",
|
||||
"integrationFaceRecognition": "Reconocimiento facial",
|
||||
"integrationLpr": "Reconocimiento de matrículas",
|
||||
"integrationObjectClassification": "Clasificación de objetos",
|
||||
"integrationAudioTranscription": "Transcripción de audio",
|
||||
"cameraDetect": "Detección de objetos",
|
||||
"cameraFfmpeg": "FFmpeg",
|
||||
"cameraRecording": "Grabación",
|
||||
"cameraSnapshots": "Instantáneas",
|
||||
"cameraMotion": "Detección de movimiento",
|
||||
"cameraObjects": "Objetos",
|
||||
"cameraConfigReview": "Revisión",
|
||||
"cameraAudioEvents": "Detección de audio",
|
||||
"cameraAudioTranscription": "Transcripción de audio",
|
||||
"cameraNotifications": "Notificaciones",
|
||||
"cameraLivePlayback": "Reproducción en directo",
|
||||
"cameraBirdseye": "Birdseye",
|
||||
"cameraFaceRecognition": "Reconocimiento facial",
|
||||
"cameraLpr": "Reconocimiento de matrículas",
|
||||
"cameraMqttConfig": "MQTT",
|
||||
"cameraOnvif": "ONVIF",
|
||||
"cameraUi": "Interfaz de cámara",
|
||||
"cameraTimestampStyle": "Estilo de marca de tiempo",
|
||||
"cameraMqtt": "MQTT de cámara",
|
||||
"maintenance": "Mantenimiento",
|
||||
"mediaSync": "Sincronización de medios",
|
||||
"regionGrid": "Cuadrícula de regiones",
|
||||
"systemDetectorsAndModel": "Detectores y modelo"
|
||||
},
|
||||
"dialog": {
|
||||
"unsavedChanges": {
|
||||
@ -59,7 +102,7 @@
|
||||
}
|
||||
},
|
||||
"cameraSetting": {
|
||||
"camera": "Cámara",
|
||||
"camera": "Overrides,Sobrescrituras",
|
||||
"noCamera": "Sin cámara"
|
||||
},
|
||||
"general": {
|
||||
@ -303,6 +346,10 @@
|
||||
"zone": "zona",
|
||||
"motion_mask": "máscara de movimiento",
|
||||
"object_mask": "máscara de objeto"
|
||||
},
|
||||
"revertOverride": {
|
||||
"title": "Revertir a la configuración base",
|
||||
"desc": "Esto eliminará la sobrescritura del perfil para {{type}} <em>{{name}}</em> y revertirá a la configuración base."
|
||||
}
|
||||
},
|
||||
"speed": {
|
||||
@ -314,6 +361,12 @@
|
||||
"error": {
|
||||
"mustNotBeEmpty": "El nombre no puede estar vacío."
|
||||
}
|
||||
},
|
||||
"id": {
|
||||
"error": {
|
||||
"mustNotBeEmpty": "El ID no puede estar vacío.",
|
||||
"alreadyExists": "Ya existe una máscara con este ID para esta cámara."
|
||||
}
|
||||
}
|
||||
},
|
||||
"zones": {
|
||||
@ -370,7 +423,8 @@
|
||||
"success": "La zona ({{zoneName}}) ha sido guardada."
|
||||
},
|
||||
"enabled": {
|
||||
"description": "Indica si esta zona está activa y habilitada en la configuración. Si está deshabilitado, no puede ser habilitado por MQTT. Las zonas deshabilitadas se ignoran durante la ejecución."
|
||||
"description": "Indica si esta zona está activa y habilitada en la configuración. Si está deshabilitado, no puede ser habilitado por MQTT. Las zonas deshabilitadas se ignoran durante la ejecución.",
|
||||
"title": "Habilitado"
|
||||
}
|
||||
},
|
||||
"toast": {
|
||||
@ -411,7 +465,13 @@
|
||||
"documentTitle": "Editar Máscara de Movimiento - Frigate",
|
||||
"point_one": "{{count}} punto",
|
||||
"point_many": "{{count}} puntos",
|
||||
"point_other": "{{count}} puntos"
|
||||
"point_other": "{{count}} puntos",
|
||||
"defaultName": "Máscara de movimiento {{number}}",
|
||||
"name": {
|
||||
"title": "Nombre",
|
||||
"description": "Un nombre descriptivo opcional para esta máscara de movimiento.",
|
||||
"placeholder": "Introduce un nombre..."
|
||||
}
|
||||
},
|
||||
"objectMasks": {
|
||||
"label": "Máscaras de Objetos",
|
||||
@ -437,11 +497,26 @@
|
||||
"point_one": "{{count}} punto",
|
||||
"point_many": "{{count}} puntos",
|
||||
"point_other": "{{count}} puntos",
|
||||
"clickDrawPolygon": "Haz clic para dibujar un polígono en la imagen."
|
||||
"clickDrawPolygon": "Haz clic para dibujar un polígono en la imagen.",
|
||||
"name": {
|
||||
"title": "Nombre",
|
||||
"description": "Un nombre descriptivo opcional para esta máscara de objeto.",
|
||||
"placeholder": "Introduce un nombre..."
|
||||
}
|
||||
},
|
||||
"restart_required": "Es necesario reiniciar (se han cambiado las máscaras/zonas)",
|
||||
"motionMaskLabel": "Máscara de movimiento {{number}}",
|
||||
"objectMaskLabel": "Máscara de objeto {{number}}"
|
||||
"objectMaskLabel": "Máscara de objeto {{number}}",
|
||||
"disabledInConfig": "El elemento está deshabilitado en el archivo de configuración",
|
||||
"addDisabledProfile": "Añádelo primero a la configuración base y luego sobrescríbelo en el perfil",
|
||||
"profileBase": "(base)",
|
||||
"profileOverride": "(sobrescritura)",
|
||||
"masks": {
|
||||
"enabled": {
|
||||
"title": "Habilitado",
|
||||
"description": "Indica si esta máscara está habilitada en el archivo de configuración. Si está deshabilitada, no se puede habilitar mediante MQTT. Las máscaras deshabilitadas se ignoran en tiempo de ejecución."
|
||||
}
|
||||
}
|
||||
},
|
||||
"motionDetectionTuner": {
|
||||
"title": "Sintonizador de Detección de Movimiento",
|
||||
@ -714,7 +789,7 @@
|
||||
"snapshots": "Instantáneas",
|
||||
"cleanCopySnapshots": "<code>clean_copy</code> Instantáneas"
|
||||
},
|
||||
"desc": "Enviar a Frigate+ requiere que tanto las capturas instantáneas como las capturas <code>clean_copy</code> estén habilitadas en tu configuración.",
|
||||
"desc": "Enviar a Frigate+ requiere que las instantáneas estén habilitadas en tu configuración.",
|
||||
"cleanCopyWarning": "Algunas cámaras tienen las instantáneas deshabilitadas"
|
||||
},
|
||||
"modelInfo": {
|
||||
@ -726,13 +801,21 @@
|
||||
"cameras": "Cámaras",
|
||||
"loading": "Cargando información del modelo…",
|
||||
"error": "No se pudo cargar la información del modelo",
|
||||
"availableModels": "Modelos disponibles",
|
||||
"availableModels": "Modelos de Frigate+ disponibles",
|
||||
"loadingAvailableModels": "Cargando modelos disponibles…",
|
||||
"modelSelect": "Tus modelos disponibles en Frigate+ se pueden seleccionar aquí. Ten en cuenta que solo se pueden seleccionar modelos compatibles con tu configuración actual de detectores.",
|
||||
"trainDate": "Fecha de entrenamiento",
|
||||
"plusModelType": {
|
||||
"baseModel": "Modelo Base",
|
||||
"userModel": "Ajustado Finamente"
|
||||
},
|
||||
"noModelLoaded": "Actualmente no hay ningún modelo de Frigate+ cargado.",
|
||||
"selectModel": "Selecciona un modelo",
|
||||
"noModelsAvailable": "No hay modelos disponibles",
|
||||
"filter": {
|
||||
"ariaLabel": "Filtrar modelos por tipo",
|
||||
"baseModels": "Modelos base",
|
||||
"fineTunedModels": "Modelos ajustados"
|
||||
}
|
||||
},
|
||||
"toast": {
|
||||
@ -741,7 +824,14 @@
|
||||
},
|
||||
"restart_required": "Es necesario reiniciar (se ha cambiado el modelo Frigate+)",
|
||||
"unsavedChanges": "Cambios en la configuración de Frigate+ no guardados",
|
||||
"description": "Frigate+ es un servicio de suscripción que proporciona acceso a funciones y capacidades adicionales para su instancia de Frigate, incluida la posibilidad de utilizar modelos de detección de objetos personalizados entrenados con sus propios datos. Puede gestionar la configuración de sus modelos de Frigate+ aquí."
|
||||
"description": "Frigate+ es un servicio de suscripción que proporciona acceso a funciones y capacidades adicionales para su instancia de Frigate, incluida la posibilidad de utilizar modelos de detección de objetos personalizados entrenados con sus propios datos. Puede gestionar la configuración de sus modelos de Frigate+ aquí.",
|
||||
"cardTitles": {
|
||||
"api": "API",
|
||||
"currentModel": "Modelo actual",
|
||||
"otherModels": "Otros modelos",
|
||||
"configuration": "Configuración"
|
||||
},
|
||||
"changeInDetectorsAndModel": "Cambiar modelo"
|
||||
},
|
||||
"enrichments": {
|
||||
"title": "Configuración de Enriquecimientos",
|
||||
@ -767,11 +857,11 @@
|
||||
"modelSize": {
|
||||
"label": "Tamaño del Modelo",
|
||||
"small": {
|
||||
"title": "pequeño",
|
||||
"title": "size",
|
||||
"desc": "Usar la opción <em>small</em> emplea una versión cuantizada del modelo que consume menos memoria RAM y se ejecuta más rápido en la CPU, con una diferencia muy pequeña o casi imperceptible en la calidad de las representaciones (embeddings)."
|
||||
},
|
||||
"large": {
|
||||
"title": "grande",
|
||||
"title": "model",
|
||||
"desc": "Usar la opción <em>large</em> emplea el modelo completo de Jina y se ejecutará automáticamente en la GPU, si está disponible."
|
||||
},
|
||||
"desc": "Tamaño del modelo usado para la búsqueda semántica."
|
||||
@ -1157,7 +1247,8 @@
|
||||
},
|
||||
"hikvision": {
|
||||
"substreamWarning": "La subtransmisión 1 está limitada a una resolución baja. Muchas cámaras Hikvision admiten subtransmisiones adicionales que deben habilitarse en la configuración de la cámara. Se recomienda comprobar y utilizar dichas transmisiones si están disponibles."
|
||||
}
|
||||
},
|
||||
"resolutionUnknown": "No se pudo detectar la resolución de este flujo. Debes establecer manualmente la resolución de detección en Ajustes o en tu configuración."
|
||||
}
|
||||
},
|
||||
"title": "Añadir cámara",
|
||||
@ -1192,7 +1283,20 @@
|
||||
"streams": {
|
||||
"title": "Habilitar/deshabilitar cámaras",
|
||||
"desc": "Desactiva temporalmente una cámara hasta que Frigate se reinicie. Desactivar una cámara detiene por completo el procesamiento de las transmisiones de Frigate. La detección, la grabación y la depuración no estarán disponibles.<br /> <em>Nota: Esto no desactiva las retransmisiones de go2rtc.</em>",
|
||||
"enableDesc": "Deshabilita temporalmente una cámara habilitada hasta que Frigate se reinicie. Deshabilitar una cámara detiene por completo el procesamiento de las transmisiones de esa cámara por parte de Frigate. La detección, la grabación y la depuración no estarán disponibles.<br /> <em>Nota: Esto no deshabilita las retransmisiones de go2rtc.</em>"
|
||||
"enableDesc": "Deshabilita temporalmente una cámara habilitada hasta que Frigate se reinicie. Deshabilitar una cámara detiene completamente el procesamiento de los flujos de esa cámara por parte de Frigate. La detección, la grabación y la depuración no estarán disponibles. Nota: Esto no deshabilita las retransmisiones de go2rtc.Arrastra el controlador para reordenar las cámaras tal y como aparecen en la interfaz. El orden de las cámaras habilitadas se reflejará en toda la interfaz, incluido el panel en directo y los menús desplegables de selección de cámaras.",
|
||||
"enableLabel": "Cámaras habilitadas",
|
||||
"disableLabel": "Cámaras deshabilitadas",
|
||||
"disableDesc": "Habilita una cámara que actualmente no está visible en la interfaz y está deshabilitada en la configuración. Es necesario reiniciar Frigate después de habilitarla.",
|
||||
"enableSuccess": "{{cameraName}} se ha habilitado en la configuración. Reinicia Frigate para aplicar los cambios.",
|
||||
"friendlyName": {
|
||||
"edit": "Editar nombre visible de la cámara",
|
||||
"title": "Editar nombre visible",
|
||||
"description": "Establece el nombre descriptivo que se mostrará para esta cámara en toda la interfaz de Frigate. Déjalo en blanco para usar el ID de la cámara.",
|
||||
"rename": "Renombrar"
|
||||
},
|
||||
"reorderHandle": "Arrastrar para reordenar",
|
||||
"saving": "Guardando…",
|
||||
"saved": "Guardado"
|
||||
},
|
||||
"cameraConfig": {
|
||||
"add": "Añadir cámara",
|
||||
@ -1224,8 +1328,34 @@
|
||||
}
|
||||
},
|
||||
"deleteCameraDialog": {
|
||||
"description": "Eliminar una cámara borrará permanentemente todas las grabaciones, los objetos rastreados y la configuración de esa cámara. Es posible que sea necesario eliminar manualmente cualquier transmisión go2rtc asociada a esta cámara."
|
||||
}
|
||||
"description": "Eliminar una cámara borrará permanentemente todas las grabaciones, los objetos rastreados y la configuración de esa cámara. Es posible que sea necesario eliminar manualmente cualquier transmisión go2rtc asociada a esta cámara.",
|
||||
"title": "Eliminar cámara",
|
||||
"selectPlaceholder": "Elegir cámara...",
|
||||
"confirmTitle": "¿Estás seguro?",
|
||||
"confirmWarning": "Eliminar <strong>{{cameraName}}</strong> no se puede deshacer.",
|
||||
"deleteExports": "Eliminar también las exportaciones de esta cámara",
|
||||
"confirmButton": "Eliminar permanentemente",
|
||||
"success": "La cámara {{cameraName}} se ha eliminado correctamente",
|
||||
"error": "No se pudo eliminar la cámara {{cameraName}}"
|
||||
},
|
||||
"deleteCamera": "Eliminar cámara",
|
||||
"profiles": {
|
||||
"title": "Sobrescrituras de cámaras del perfil",
|
||||
"selectLabel": "Seleccionar perfil",
|
||||
"description": "Configura qué cámaras se habilitan o deshabilitan cuando se activa un perfil. Las cámaras configuradas como \"Heredar\" conservan su estado base habilitado.",
|
||||
"inherit": "Heredar",
|
||||
"enabled": "Habilitado",
|
||||
"disabled": "Deshabilitado"
|
||||
},
|
||||
"cameraType": {
|
||||
"title": "Tipo de cámara",
|
||||
"label": "Tipo de cámara",
|
||||
"description": "Establece el tipo de cada cámara. Las cámaras LPR dedicadas son cámaras de un solo propósito con un zoom óptico potente para capturar matrículas de vehículos lejanos. La mayoría de cámaras deberían usar el tipo de cámara normal salvo que la cámara esté específicamente destinada a LPR y tenga una vista muy enfocada a matrículas.",
|
||||
"normal": "Normal",
|
||||
"dedicatedLpr": "LPR dedicada",
|
||||
"saveSuccess": "Se ha actualizado el tipo de cámara de {{cameraName}}. Reinicia Frigate para aplicar los cambios."
|
||||
},
|
||||
"description": "Añade, edita y elimina cámaras, controla qué cámaras están habilitadas y configura sobrescrituras por perfil y tipo de cámara. Para configurar flujos, detección, movimiento y otros ajustes específicos de cámara, selecciona la sección correspondiente dentro de Configuración de cámara."
|
||||
},
|
||||
"cameraReview": {
|
||||
"title": "Configuración de revisión de la cámara",
|
||||
@ -1268,34 +1398,295 @@
|
||||
"overriddenGlobal": "Sobrescrito (Global)",
|
||||
"overriddenBaseConfigTooltip": "El perfil {{profile}} sobrescribe los ajustes de configuración de esta sección",
|
||||
"overriddenGlobalTooltip": "Esta cámara sobrescribe los ajustes de configuración global en esta sección",
|
||||
"overriddenBaseConfig": "Sobrescrito (Configuración Base)"
|
||||
"overriddenBaseConfig": "Sobrescrito (Configuración Base)",
|
||||
"overriddenInCameras": {
|
||||
"label_one": "Sobrescrito en {{count}} cámara",
|
||||
"label_many": "Sobrescrito en {{count}} cámaras",
|
||||
"label_other": "Sobrescrito en {{count}} cámaras",
|
||||
"tooltip_one": "{{count}} cámaras sobrescriben los valores de esta sección. Haz clic para ver los detalles.",
|
||||
"tooltip_many": "{{count}} cámaras sobrescriben los valores de esta sección. Haz clic para ver los detalles.",
|
||||
"tooltip_other": "{{count}} cámaras sobrescriben los valores de esta sección. Haz clic para ver los detalles.",
|
||||
"heading_one": "This global section has fields that are overridden in {{count}} camera.",
|
||||
"heading_many": "Esta sección global tiene campos que están sobrescritos en {{count}} cámaras.",
|
||||
"heading_other": "Esta sección global tiene campos que están sobrescritos en {{count}} cámaras.",
|
||||
"othersField_one": "{{count}} más",
|
||||
"othersField_many": "{{count}} más",
|
||||
"othersField_other": "{{count}} más",
|
||||
"profilePrefix": "Perfil {{profile}}: {{fields}}"
|
||||
},
|
||||
"overriddenGlobalHeading_one": "Esta cámara sobrescribe {{count}} campo de la configuración global:",
|
||||
"overriddenGlobalHeading_many": "Esta cámara sobrescribe {{count}} campos de la configuración global:",
|
||||
"overriddenGlobalHeading_other": "Esta cámara sobrescribe {{count}} campos de la configuración global:",
|
||||
"overriddenGlobalNoDeltas": "Esta cámara sobrescribe la configuración global, pero no hay diferencias en los valores de los campos.",
|
||||
"overriddenBaseConfigHeading_one": "El perfil {{profile}} sobrescribe {{count}} campo de la configuración base:",
|
||||
"overriddenBaseConfigHeading_many": "El perfil {{profile}} sobrescribe {{count}} campos de la configuración base:",
|
||||
"overriddenBaseConfigHeading_other": "El perfil {{profile}} sobrescribe {{count}} campos de la configuración base:",
|
||||
"overriddenBaseConfigNoDeltas": "El perfil {{profile}} sobrescribe esta sección, pero no hay diferencias en los valores de los campos respecto a la configuración base."
|
||||
},
|
||||
"onvif": {
|
||||
"profileLoading": "Cargando perfiles..."
|
||||
"profileLoading": "Cargando perfiles...",
|
||||
"profileAuto": "Auto",
|
||||
"autotracking": {
|
||||
"zooming": {
|
||||
"disabled": "Deshabilitado",
|
||||
"absolute": "Absoluto",
|
||||
"relative": "Relativo"
|
||||
}
|
||||
}
|
||||
},
|
||||
"maintenance": {
|
||||
"sync": {
|
||||
"verboseDesc": "Escribe una lista completa de archivos huérfanos en el disco para su revisión.",
|
||||
"verbose": "Detallado",
|
||||
"desc": "Frigate limpiará periódicamente los archivos multimedia según un cronograma regular, de acuerdo con su configuración de retención. Es normal ver algunos archivos huérfanos mientras Frigate se ejecuta. Utilice esta función para eliminar del disco los archivos multimedia huérfanos que ya no se referencian en la base de datos.",
|
||||
"forceDesc": "Omitir el umbral de seguridad y completar la sincronización incluso si se eliminara más del 50% de los archivos."
|
||||
"forceDesc": "Omitir el umbral de seguridad y completar la sincronización incluso si se eliminara más del 50% de los archivos.",
|
||||
"title": "Sincronización de medios",
|
||||
"started": "Sincronización de medios iniciada.",
|
||||
"alreadyRunning": "Ya hay una tarea de sincronización en ejecución",
|
||||
"error": "No se pudo iniciar la sincronización",
|
||||
"currentStatus": "Estado",
|
||||
"jobId": "ID de tarea",
|
||||
"startTime": "Hora de inicio",
|
||||
"endTime": "Hora de finalización",
|
||||
"statusLabel": "Estado",
|
||||
"results": "Resultados",
|
||||
"errorLabel": "Error",
|
||||
"mediaTypes": "Tipos de medios",
|
||||
"allMedia": "Todos los medios",
|
||||
"dryRun": "Simulación",
|
||||
"dryRunEnabled": "No se eliminará ningún archivo",
|
||||
"dryRunDisabled": "Se eliminarán archivos",
|
||||
"force": "Forzar",
|
||||
"running": "Sincronización en curso...",
|
||||
"start": "Iniciar sincronización",
|
||||
"inProgress": "La sincronización está en curso. Esta página está deshabilitada.",
|
||||
"status": {
|
||||
"queued": "En cola",
|
||||
"running": "En ejecución",
|
||||
"completed": "Completado",
|
||||
"failed": "Fallido",
|
||||
"notRunning": "No está en ejecución"
|
||||
},
|
||||
"resultsFields": {
|
||||
"filesChecked": "Archivos comprobados",
|
||||
"orphansFound": "Huérfanos encontrados",
|
||||
"orphansDeleted": "Huérfanos eliminados",
|
||||
"aborted": "Abortado. La eliminación superaría el umbral de seguridad.",
|
||||
"error": "Error",
|
||||
"totals": "Totales"
|
||||
},
|
||||
"event_snapshots": "Instantáneas de objetos rastreados",
|
||||
"event_thumbnails": "Miniaturas de objetos rastreados",
|
||||
"review_thumbnails": "Miniaturas de revisión",
|
||||
"previews": "Vistas previas",
|
||||
"exports": "Exportaciones",
|
||||
"recordings": "Grabaciones"
|
||||
},
|
||||
"regionGrid": {
|
||||
"clearConfirmDesc": "No se recomienda borrar la cuadrícula de la región a menos que haya cambiado recientemente el tamaño del modelo de su detector o la posición física de su cámara y esté experimentando problemas de seguimiento de objetos. La cuadrícula se reconstruirá automáticamente con el tiempo a medida que se realice el seguimiento de los objetos. Es necesario reiniciar Frigate para que los cambios surtan efecto.",
|
||||
"desc": "La cuadrícula de regiones es una optimización que aprende dónde suelen aparecer los objetos de diferentes tamaños en el campo de visión de cada cámara. Frigate utiliza estos datos para dimensionar de forma eficiente las regiones de detección. La cuadrícula se construye automáticamente a lo largo del tiempo a partir de los datos de los objetos rastreados."
|
||||
}
|
||||
"desc": "La cuadrícula de regiones es una optimización que aprende dónde suelen aparecer los objetos de diferentes tamaños en el campo de visión de cada cámara. Frigate utiliza estos datos para dimensionar de forma eficiente las regiones de detección. La cuadrícula se construye automáticamente a lo largo del tiempo a partir de los datos de los objetos rastreados.",
|
||||
"title": "Cuadrícula de regiones",
|
||||
"clear": "Borrar cuadrícula de regiones",
|
||||
"clearConfirmTitle": "Borrar cuadrícula de regiones",
|
||||
"clearSuccess": "Cuadrícula de regiones borrada correctamente",
|
||||
"clearError": "No se pudo borrar la cuadrícula de regiones",
|
||||
"restartRequired": "Es necesario reiniciar para que los cambios de la cuadrícula de regiones surtan efecto"
|
||||
},
|
||||
"title": "Mantenimiento"
|
||||
},
|
||||
"configForm": {
|
||||
"camera": {
|
||||
"noCameras": "No hay cámaras disponibles",
|
||||
"description": "Estos ajustes se aplican únicamente a esta cámara y anulan los ajustes globales."
|
||||
"description": "Estos ajustes se aplican únicamente a esta cámara y anulan los ajustes globales.",
|
||||
"title": "Ajustes de cámara"
|
||||
},
|
||||
"genaiModel": {
|
||||
"noModels": "No hay modelos disponibles"
|
||||
"noModels": "No hay modelos disponibles",
|
||||
"placeholder": "Seleccionar modelo…",
|
||||
"search": "Buscar modelos…"
|
||||
},
|
||||
"global": {
|
||||
"description": "Estos ajustes se aplican a todas las cámaras, a menos que se anulen en los ajustes específicos de cada cámara."
|
||||
}
|
||||
"description": "Estos ajustes se aplican a todas las cámaras, a menos que se anulen en los ajustes específicos de cada cámara.",
|
||||
"title": "Ajustes globales"
|
||||
},
|
||||
"sections": {
|
||||
"go2rtc": "streams",
|
||||
"detect": "Detección",
|
||||
"record": "Grabación",
|
||||
"snapshots": "Instantáneas",
|
||||
"motion": "Movimiento",
|
||||
"objects": "Objetos",
|
||||
"review": "Revisión",
|
||||
"audio": "Audio",
|
||||
"notifications": "Notificaciones",
|
||||
"live": "Vista en directo",
|
||||
"timestamp_style": "Marcas de tiempo",
|
||||
"mqtt": "MQTT",
|
||||
"database": "Base de datos",
|
||||
"telemetry": "Telemetría",
|
||||
"auth": "Autenticación",
|
||||
"tls": "TLS",
|
||||
"proxy": "Proxy",
|
||||
"ffmpeg": "FFmpeg",
|
||||
"detectors": "Detectores",
|
||||
"model": "Modelo",
|
||||
"semantic_search": "Búsqueda semántica",
|
||||
"genai": "GenAI",
|
||||
"face_recognition": "Reconocimiento facial",
|
||||
"lpr": "Reconocimiento de matrículas",
|
||||
"birdseye": "Birdseye",
|
||||
"masksAndZones": "Máscaras / zonas"
|
||||
},
|
||||
"advancedSettingsCount": "Ajustes avanzados ({{count}})",
|
||||
"advancedCount": "Avanzado ({{count}})",
|
||||
"showAdvanced": "Mostrar ajustes avanzados",
|
||||
"tabs": {
|
||||
"sharedDefaults": "Valores predeterminados compartidos",
|
||||
"system": "Sistema",
|
||||
"integrations": "Integraciones"
|
||||
},
|
||||
"additionalProperties": {
|
||||
"keyLabel": "Clave",
|
||||
"valueLabel": "Valor",
|
||||
"keyPlaceholder": "Nueva clave",
|
||||
"remove": "Eliminar"
|
||||
},
|
||||
"knownPlates": {
|
||||
"namePlaceholder": "p. ej., Coche de mi mujer",
|
||||
"platePlaceholder": "Número de matrícula o regex"
|
||||
},
|
||||
"timezone": {
|
||||
"defaultOption": "Usar zona horaria del navegador"
|
||||
},
|
||||
"roleMap": {
|
||||
"empty": "No hay asignaciones de roles",
|
||||
"roleLabel": "Rol",
|
||||
"groupsLabel": "Grupos",
|
||||
"addMapping": "Añadir asignación de rol",
|
||||
"remove": "Eliminar"
|
||||
},
|
||||
"ffmpegArgs": {
|
||||
"preset": "Preajuste",
|
||||
"manual": "Argumentos manuales",
|
||||
"inherit": "Heredar del ajuste de cámara",
|
||||
"none": "Ninguno",
|
||||
"useGlobalSetting": "Heredar del ajuste global",
|
||||
"selectPreset": "Seleccionar preajuste",
|
||||
"manualPlaceholder": "Introduce argumentos de FFmpeg",
|
||||
"presetLabels": {
|
||||
"preset-rpi-64-h264": "Raspberry Pi (H.264)",
|
||||
"preset-rpi-64-h265": "Raspberry Pi (H.265)",
|
||||
"preset-vaapi": "VAAPI (GPU Intel/AMD)",
|
||||
"preset-intel-qsv-h264": "Intel QuickSync (H.264)",
|
||||
"preset-intel-qsv-h265": "Intel QuickSync (H.265)",
|
||||
"preset-nvidia": "GPU NVIDIA",
|
||||
"preset-jetson-h264": "NVIDIA Jetson (H.264)",
|
||||
"preset-jetson-h265": "NVIDIA Jetson (H.265)",
|
||||
"preset-rkmpp": "Rockchip RKMPP",
|
||||
"preset-http-jpeg-generic": "HTTP JPEG (genérico)",
|
||||
"preset-http-mjpeg-generic": "HTTP MJPEG (genérico)",
|
||||
"preset-http-reolink": "HTTP - Cámaras Reolink",
|
||||
"preset-rtmp-generic": "RTMP (genérico)",
|
||||
"preset-rtsp-generic": "RTSP (genérico)",
|
||||
"preset-rtsp-restream": "RTSP - Retransmisión desde go2rtc",
|
||||
"preset-rtsp-restream-low-latency": "RTSP - Retransmisión desde go2rtc (baja latencia)",
|
||||
"preset-rtsp-udp": "RTSP - UDP",
|
||||
"preset-rtsp-blue-iris": "RTSP - Blue Iris",
|
||||
"preset-record-generic": "Grabación (genérica, sin audio)",
|
||||
"preset-record-generic-audio-copy": "Grabación (genérica + copiar audio)",
|
||||
"preset-record-generic-audio-aac": "Grabación (genérica + audio a AAC)",
|
||||
"preset-record-mjpeg": "Grabación - Cámaras MJPEG",
|
||||
"preset-record-jpeg": "Grabación - Cámaras JPEG",
|
||||
"preset-record-ubiquiti": "Grabación - Cámaras Ubiquiti"
|
||||
}
|
||||
},
|
||||
"cameraInputs": {
|
||||
"itemTitle": "Flujo {{index}}"
|
||||
},
|
||||
"restartRequiredField": "Reinicio necesario",
|
||||
"restartRequiredFooter": "Configuración modificada - reinicio necesario",
|
||||
"detect": {
|
||||
"title": "Ajustes de detección"
|
||||
},
|
||||
"detectors": {
|
||||
"title": "Ajustes de detector",
|
||||
"singleType": "Solo se permite un detector {{type}}.",
|
||||
"keyRequired": "El nombre del detector es obligatorio.",
|
||||
"keyDuplicate": "El nombre del detector ya existe.",
|
||||
"noSchema": "No hay esquemas de detector disponibles.",
|
||||
"none": "No hay instancias de detector configuradas.",
|
||||
"add": "Añadir detector",
|
||||
"addCustomKey": "Añadir clave personalizada"
|
||||
},
|
||||
"record": {
|
||||
"title": "Ajustes de grabación"
|
||||
},
|
||||
"snapshots": {
|
||||
"title": "Ajustes de instantáneas"
|
||||
},
|
||||
"motion": {
|
||||
"title": "Ajustes de movimiento"
|
||||
},
|
||||
"objects": {
|
||||
"title": "Ajustes de objetos"
|
||||
},
|
||||
"audioLabels": {
|
||||
"summary": "{{count}} etiquetas de audio seleccionadas",
|
||||
"empty": "No hay etiquetas de audio disponibles"
|
||||
},
|
||||
"objectLabels": {
|
||||
"summary": "{{count}} tipos de objeto seleccionados",
|
||||
"empty": "No hay etiquetas de objeto disponibles"
|
||||
},
|
||||
"reviewLabels": {
|
||||
"summary": "{{count}} etiquetas seleccionadas",
|
||||
"empty": "No hay etiquetas disponibles"
|
||||
},
|
||||
"filters": {
|
||||
"objectFieldLabel": "{{field}} para {{label}}"
|
||||
},
|
||||
"zoneNames": {
|
||||
"summary": "{{count}} seleccionados",
|
||||
"empty": "No hay zonas disponibles"
|
||||
},
|
||||
"inputRoles": {
|
||||
"summary": "{{count}} roles seleccionados",
|
||||
"empty": "No hay roles disponibles",
|
||||
"options": {
|
||||
"detect": "Detectar",
|
||||
"record": "Grabar",
|
||||
"audio": "Audio"
|
||||
}
|
||||
},
|
||||
"genaiRoles": {
|
||||
"options": {
|
||||
"embeddings": "Embedding",
|
||||
"descriptions": "Descripciones",
|
||||
"chat": "Chat"
|
||||
}
|
||||
},
|
||||
"semanticSearchModel": {
|
||||
"placeholder": "Seleccionar modelo…",
|
||||
"builtIn": "Modelos integrados",
|
||||
"genaiProviders": "Proveedores de GenAI"
|
||||
},
|
||||
"review": {
|
||||
"title": "Ajustes de revisión"
|
||||
},
|
||||
"audio": {
|
||||
"title": "Ajustes de audio"
|
||||
},
|
||||
"notifications": {
|
||||
"title": "Ajustes de notificaciones"
|
||||
},
|
||||
"live": {
|
||||
"title": "Ajustes de vista en directo"
|
||||
},
|
||||
"timestamp_style": {
|
||||
"title": "Ajustes de marcas de tiempo"
|
||||
},
|
||||
"searchPlaceholder": "Buscar...",
|
||||
"addCustomLabel": "Añadir etiqueta personalizada..."
|
||||
},
|
||||
"globalConfig": {
|
||||
"title": "Configuración global",
|
||||
@ -1330,7 +1721,10 @@
|
||||
"saveAllPartial_one": "Se ha guardado {{successCount}} de {{totalCount}} sección. {{failCount}} ha fallado.",
|
||||
"saveAllPartial_many": "Se han guardado {{successCount}} de {{totalCount}} secciones. {{failCount}} han fallado.",
|
||||
"saveAllPartial_other": "Se han guardado {{successCount}} de {{totalCount}} secciones. {{failCount}} han fallado.",
|
||||
"saveAllFailure": "Error al guardar todas las secciones."
|
||||
"saveAllFailure": "Error al guardar todas las secciones.",
|
||||
"saveAllSuccessRestartRequired_one": "La sección {{count}} se ha guardado correctamente. Reinicia Frigate para aplicar los cambios.",
|
||||
"saveAllSuccessRestartRequired_many": "Las {{count}} secciones se han guardado correctamente. Reinicia Frigate para aplicar los cambios.",
|
||||
"saveAllSuccessRestartRequired_other": "Las {{count}} secciones se han guardado correctamente. Reinicia Frigate para aplicar los cambios."
|
||||
},
|
||||
"profiles": {
|
||||
"title": "Perfiles",
|
||||
@ -1364,26 +1758,84 @@
|
||||
"renameProfile": "Renombrar perfil",
|
||||
"renameSuccess": "Perfil renombrado a '{{profile}}'",
|
||||
"enabledDescription": "Los perfiles están habilitados. Cree un nuevo perfil a continuación, navegue a una sección de configuración de cámara para realizar sus cambios y guarde para que estos surtan efecto.",
|
||||
"disabledDescription": "Los perfiles le permiten definir conjuntos con nombre de anulaciones de configuración de la cámara (por ejemplo: armado, fuera, noche) que pueden activarse bajo demanda."
|
||||
"disabledDescription": "Los perfiles le permiten definir conjuntos con nombre de anulaciones de configuración de la cámara (por ejemplo: armado, fuera, noche) que pueden activarse bajo demanda.",
|
||||
"deleteProfile": "Eliminar perfil",
|
||||
"deleteProfileConfirm": "¿Eliminar el perfil \"{{profile}}\" de todas las cámaras? Esta acción no se puede deshacer.",
|
||||
"deleteSuccess": "Perfil '{{profile}}' eliminado",
|
||||
"createSuccess": "Perfil '{{profile}}' creado",
|
||||
"removeOverride": "Eliminar sobrescritura de perfil",
|
||||
"deleteSection": "Eliminar sobrescrituras de sección",
|
||||
"deleteSectionConfirm": "¿Eliminar las sobrescrituras de {{section}} del perfil {{profile}} en {{camera}}?",
|
||||
"deleteSectionSuccess": "Sobrescrituras de {{section}} eliminadas para {{profile}}",
|
||||
"enableSwitch": "Habilitar perfiles"
|
||||
},
|
||||
"go2rtcStreams": {
|
||||
"renameStreamDesc": "Introduce un nuevo nombre para esta transmisión. Cambiar el nombre de una transmisión puede provocar fallos en las cámaras u otras transmisiones que hagan referencia a ella por su nombre.",
|
||||
"addStreamDesc": "Introduce un nombre para la nueva transmisión. Este nombre se utilizará para hacer referencia a la transmisión en la configuración de su cámara.",
|
||||
"description": "Gestione las configuraciones de transmisión de go2rtc para la retransmisión de cámaras. Cada transmisión tiene un nombre y una o más URL de origen.",
|
||||
"deleteStreamConfirm": "¿Está seguro de que desea eliminar la transmisión \"{{streamName}}\"? Las cámaras que hagan referencia a esta transmisión podrían dejar de funcionar."
|
||||
"deleteStreamConfirm": "¿Está seguro de que desea eliminar la transmisión \"{{streamName}}\"? Las cámaras que hagan referencia a esta transmisión podrían dejar de funcionar.",
|
||||
"title": "Flujos go2rtc",
|
||||
"addStream": "Añadir flujo",
|
||||
"addUrl": "Añadir URL",
|
||||
"streamName": "Nombre del flujo",
|
||||
"streamNamePlaceholder": "p. ej., puerta_principal",
|
||||
"streamUrlPlaceholder": "p. ej., rtsp://usuario:contraseña@192.168.1.100/stream",
|
||||
"deleteStream": "Eliminar flujo",
|
||||
"noStreams": "No hay flujos go2rtc configurados. Añade un flujo para empezar.",
|
||||
"validation": {
|
||||
"nameRequired": "El nombre del flujo es obligatorio",
|
||||
"nameDuplicate": "Ya existe un flujo con este nombre",
|
||||
"nameInvalid": "El nombre del flujo solo puede contener letras, números, guiones bajos y guiones",
|
||||
"urlRequired": "Se requiere al menos una URL"
|
||||
},
|
||||
"renameStream": "Renombrar flujo",
|
||||
"newStreamName": "Nuevo nombre del flujo",
|
||||
"ffmpeg": {
|
||||
"useFfmpegModule": "Usar modo de compatibilidad (ffmpeg)",
|
||||
"video": "Vídeo",
|
||||
"audio": "Audio",
|
||||
"hardware": "Aceleración por hardware",
|
||||
"videoCopy": "Copiar",
|
||||
"videoH264": "Transcodificar a H.264",
|
||||
"videoH265": "Transcodificar a H.265",
|
||||
"videoExclude": "Excluir",
|
||||
"audioCopy": "Copiar",
|
||||
"audioAac": "Transcodificar a AAC",
|
||||
"audioOpus": "Transcodificar a Opus",
|
||||
"audioPcmu": "Transcodificar a PCM μ-law",
|
||||
"audioPcma": "Transcodificar a PCM A-law",
|
||||
"audioPcm": "Transcodificar a PCM",
|
||||
"audioMp3": "Transcodificar a MP3",
|
||||
"audioExclude": "Excluir",
|
||||
"hardwareNone": "Sin aceleración por hardware",
|
||||
"hardwareAuto": "Automático (recomendado)",
|
||||
"hardwareVaapi": "VAAPI",
|
||||
"hardwareCuda": "CUDA",
|
||||
"hardwareV4l2m2m": "V4L2 M2M",
|
||||
"hardwareDxva2": "DXVA2",
|
||||
"hardwareVideotoolbox": "VideoToolbox",
|
||||
"addVideoCodec": "Añadir códec de vídeo",
|
||||
"addAudioCodec": "Añadir códec de audio",
|
||||
"removeCodec": "Eliminar códec"
|
||||
},
|
||||
"streamNumber": "Flujo {{index}}"
|
||||
},
|
||||
"configMessages": {
|
||||
"birdseye": {
|
||||
"objectsModeDetectDisabled": "Birdseye está configurado en modo 'objects', pero la detección de objetos está desactivada para esta cámara. La cámara no aparecerá en Birdseye."
|
||||
},
|
||||
"lpr": {
|
||||
"globalDisabled": "El reconocimiento de matrículas no está habilitado a nivel global. Habilítelo en la configuración global para que funcione el reconocimiento de matrículas a nivel de cámara."
|
||||
"globalDisabled": "El reconocimiento de matrículas no está habilitado a nivel global. Habilítelo en la configuración global para que funcione el reconocimiento de matrículas a nivel de cámara.",
|
||||
"vehicleNotTracked": "El reconocimiento de matrículas requiere rastrear 'car' o 'motorcycle'. Habilita 'car' o 'motorcycle' en Objetos para esta cámara.",
|
||||
"modelSizeLarge": "El modelo 'large' está optimizado para matrículas de varias líneas. El modelo 'small' ofrece mejor rendimiento que 'large' y debería usarse salvo que tu región use formatos de matrícula de varias líneas."
|
||||
},
|
||||
"audio": {
|
||||
"noAudioRole": "Ninguna transmisión tiene definido el rol de audio. Debe habilitar el rol de audio para que funcione la detección de audio."
|
||||
},
|
||||
"faceRecognition": {
|
||||
"personNotTracked": "El reconocimiento facial requiere que se realice el seguimiento del objeto 'person'. Asegúrese de que 'person' se encuentre en la lista de seguimiento de objetos."
|
||||
"personNotTracked": "El reconocimiento facial requiere que se realice el seguimiento del objeto 'person'. Asegúrese de que 'person' se encuentre en la lista de seguimiento de objetos.",
|
||||
"globalDisabled": "El enriquecimiento de reconocimiento facial debe estar habilitado para que las funciones de reconocimiento facial funcionen en esta cámara.",
|
||||
"modelSizeLarge": "El modelo 'large' requiere una GPU o NPU para ofrecer un rendimiento razonable. Usa 'small' en sistemas solo con CPU."
|
||||
},
|
||||
"audioTranscription": {
|
||||
"audioDetectionDisabled": "La detección de audio no está habilitada para esta cámara. La transcripción de audio requiere que la detección de audio esté activa."
|
||||
@ -1392,17 +1844,165 @@
|
||||
"detectDisabled": "La detección de objetos está desactivada. Las instantáneas se generan a partir de los objetos rastreados y no se crearán."
|
||||
},
|
||||
"detectors": {
|
||||
"mixedTypes": "Todos los detectores deben ser del mismo tipo. Retire los detectores existentes para utilizar un tipo diferente."
|
||||
"mixedTypes": "Todos los detectores deben ser del mismo tipo. Retire los detectores existentes para utilizar un tipo diferente.",
|
||||
"mixedTypesSuggestion": "Todos los detectores deben usar el mismo tipo. Elimina los detectores existentes o selecciona {{type}}."
|
||||
},
|
||||
"review": {
|
||||
"detectDisabled": "La detección de objetos está desactivada. Los elementos de revisión requieren objetos detectados para categorizar las alertas y detecciones."
|
||||
"detectDisabled": "La detección de objetos está desactivada. Los elementos de revisión requieren objetos detectados para categorizar las alertas y detecciones.",
|
||||
"recordDisabled": "La grabación está deshabilitada; no se generarán elementos de revisión.",
|
||||
"allNonAlertDetections": "Toda la actividad que no sea de alerta se incluirá como detecciones.",
|
||||
"genaiImageSourceRecordingsRecordDisabled": "El origen de imagen está establecido en 'recordings', pero la grabación está deshabilitada. Frigate usará imágenes de vista previa como alternativa."
|
||||
},
|
||||
"detect": {
|
||||
"fpsGreaterThanFive": "No se recomienda establecer los FPS de detección por encima de 5. Valores más altos pueden causar problemas de rendimiento y no aportarán ningún beneficio.",
|
||||
"disabled": "La detección de objetos está deshabilitada. Las instantáneas, los elementos de revisión y enriquecimientos como el reconocimiento facial, el reconocimiento de matrículas y la IA generativa no funcionarán."
|
||||
},
|
||||
"objects": {
|
||||
"genaiNoDescriptionsProvider": "Debes configurar un proveedor GenAI con el rol 'descriptions' para que se generen descripciones."
|
||||
},
|
||||
"record": {
|
||||
"noRecordRole": "Ningún flujo tiene definido el rol de grabación. La grabación no funcionará."
|
||||
},
|
||||
"semanticSearch": {
|
||||
"jinav2SmallModelSize": "El tamaño 'small' con el modelo Jina V2 tiene un alto consumo de RAM y coste de inferencia. Se recomienda el modelo 'large' con una GPU dedicada."
|
||||
}
|
||||
},
|
||||
"resetToDefaultDescription": "Esto restablecerá todos los ajustes de esta sección a sus valores predeterminados. Esta acción no se puede deshacer.",
|
||||
"resetToGlobalDescription": "Esto restablecerá la configuración de esta sección a los valores predeterminados globales. Esta acción no se puede deshacer.",
|
||||
"detectionModel": {
|
||||
"plusActive": {
|
||||
"description": "Esta instancia está ejecutando un modelo de Frigate+. Seleccione o cambie su modelo en la configuración de Frigate+."
|
||||
"description": "Esta instancia está ejecutando un modelo de Frigate+. Seleccione o cambie su modelo en la configuración de Frigate+.",
|
||||
"title": "Gestión de modelos de Frigate+",
|
||||
"label": "Origen del modelo actual",
|
||||
"goToFrigatePlus": "Ir a los ajustes de Frigate+",
|
||||
"showModelForm": "Configurar un modelo manualmente"
|
||||
}
|
||||
},
|
||||
"saveAllPreview": {
|
||||
"profile": {
|
||||
"label": "Override,Eliminar"
|
||||
},
|
||||
"title": "Cambios pendientes de guardar",
|
||||
"triggerLabel": "Revisar cambios pendientes",
|
||||
"empty": "No hay cambios pendientes.",
|
||||
"scope": {
|
||||
"label": "Ámbito",
|
||||
"global": "Global",
|
||||
"camera": "Cámara: {{cameraName}}"
|
||||
},
|
||||
"field": {
|
||||
"label": "Campo"
|
||||
},
|
||||
"value": {
|
||||
"label": "Nuevo valor",
|
||||
"reset": "Restablecer"
|
||||
}
|
||||
},
|
||||
"timestampPosition": {
|
||||
"tl": "Arriba a la izquierda",
|
||||
"tr": "Arriba a la derecha",
|
||||
"bl": "Abajo a la izquierda",
|
||||
"br": "Abajo a la derecha"
|
||||
},
|
||||
"unsavedChanges": "Tienes cambios sin guardar",
|
||||
"confirmReset": "Confirmar restablecimiento",
|
||||
"birdseye": {
|
||||
"trackingMode": {
|
||||
"objects": "Objetos",
|
||||
"motion": "Movimiento",
|
||||
"continuous": "Continuo"
|
||||
},
|
||||
"cameraOrder": {
|
||||
"label": "Orden de cámaras",
|
||||
"description": "Arrastra las cámaras para establecer su orden en el diseño de Birdseye.",
|
||||
"reorderHandle": "Arrastrar para reordenar",
|
||||
"saving": "Guardando…",
|
||||
"saved": "Guardado"
|
||||
}
|
||||
},
|
||||
"snapshot": {
|
||||
"retainMode": {
|
||||
"all": "Todo",
|
||||
"motion": "Movimiento",
|
||||
"active_objects": "Objetos activos"
|
||||
}
|
||||
},
|
||||
"ui": {
|
||||
"timeFormat": {
|
||||
"browser": "Navegador",
|
||||
"12hour": "12 horas",
|
||||
"24hour": "24 horas"
|
||||
},
|
||||
"TimeOrDateStyle": {
|
||||
"full": "Completo",
|
||||
"long": "Largo",
|
||||
"medium": "Medio",
|
||||
"short": "Corto"
|
||||
},
|
||||
"unitSystem": {
|
||||
"metric": "Métrico",
|
||||
"imperial": "Imperial"
|
||||
}
|
||||
},
|
||||
"review": {
|
||||
"imageSource": {
|
||||
"recordings": "Grabaciones",
|
||||
"previews": "Vistas previas"
|
||||
}
|
||||
},
|
||||
"logger": {
|
||||
"logLevel": {
|
||||
"debug": "Depuración",
|
||||
"info": "Información",
|
||||
"warning": "Advertencia",
|
||||
"error": "Error",
|
||||
"critical": "Crítico"
|
||||
}
|
||||
},
|
||||
"modelSize": {
|
||||
"small": "Pequeño",
|
||||
"large": "Grande"
|
||||
},
|
||||
"retainMode": {
|
||||
"all": "Todo",
|
||||
"motion": "Movimiento",
|
||||
"active_objects": "Objetos activos"
|
||||
},
|
||||
"previewQuality": {
|
||||
"very_high": "Muy alto",
|
||||
"high": "Alto",
|
||||
"medium": "Medio",
|
||||
"low": "Bajo",
|
||||
"very_low": "Muy bajo"
|
||||
},
|
||||
"detectorsAndModel": {
|
||||
"title": "Detectores y modelo",
|
||||
"description": "Configura el backend del detector que ejecuta la detección de objetos y el modelo que utiliza. Los cambios se guardan juntos para que el detector y el modelo permanezcan sincronizados.",
|
||||
"cardTitles": {
|
||||
"detector": "Hardware del detector",
|
||||
"model": "Modelo de detección"
|
||||
},
|
||||
"tabs": {
|
||||
"plus": "Frigate+",
|
||||
"custom": "Modelo personalizado"
|
||||
},
|
||||
"mismatch": {
|
||||
"warning": "El modelo actual de Frigate+ “{{model}}” requiere el detector {{required}}. Selecciona un modelo compatible a continuación o cambia a Modelo personalizado antes de guardar."
|
||||
},
|
||||
"plusModel": {
|
||||
"requiresDetector": "Requiere: {{detector}}",
|
||||
"noModelSelected": "Selecciona un modelo de Frigate+"
|
||||
},
|
||||
"toast": {
|
||||
"saveSuccess": "Los ajustes de detectores y modelo se han guardado. Reinicia Frigate para aplicar los cambios.",
|
||||
"saveError": "No se pudieron guardar los ajustes del detector y del modelo"
|
||||
},
|
||||
"unsavedChanges": "Cambios sin guardar en el detector y el modelo",
|
||||
"restartRequired": "Reinicio necesario (se ha cambiado el detector o el modelo)"
|
||||
},
|
||||
"menuDot": {
|
||||
"overrideGlobal": "Esta sección sobrescribe la configuración global",
|
||||
"overrideProfile": "Esta sección está sobrescrita por el perfil {{profile}}",
|
||||
"unsaved": "Esta sección tiene cambios sin guardar"
|
||||
}
|
||||
}
|
||||
|
||||
@ -55,7 +55,10 @@
|
||||
},
|
||||
"count_other": "{{count}} mensajes",
|
||||
"count_one": "{{count}} mensaje",
|
||||
"empty": "No se han capturado mensaje aún"
|
||||
"empty": "No se han capturado mensaje aún",
|
||||
"expanded": {
|
||||
"payload": "Carga útil"
|
||||
}
|
||||
}
|
||||
},
|
||||
"title": "Sistema",
|
||||
@ -209,6 +212,9 @@
|
||||
"unusable": "No usable",
|
||||
"fair": "Normal",
|
||||
"stallsLastHour": "Bloqueos (última hora)"
|
||||
},
|
||||
"noCameras": {
|
||||
"title": "No se han encontrado cámaras"
|
||||
}
|
||||
},
|
||||
"lastRefreshed": "Última actualización: ",
|
||||
|
||||
@ -140,7 +140,8 @@
|
||||
"gl": "Galego (galeegi keel)",
|
||||
"id": "Bahasa Indonesia (indoneesia keel)",
|
||||
"ur": "اردو (urdu keel)",
|
||||
"hr": "Hrvatski (horvaadi keel)"
|
||||
"hr": "Hrvatski (horvaadi keel)",
|
||||
"bs": "Bosanski (bosnia keel)"
|
||||
},
|
||||
"system": "Süsteem",
|
||||
"systemMetrics": "Süsteemi meetrika",
|
||||
|
||||
@ -121,5 +121,10 @@
|
||||
"royal_mail": "Royal Mail",
|
||||
"school_bus": "Koolibuss",
|
||||
"skunk": "Vinukloom (skunk)",
|
||||
"kangaroo": "Känguru"
|
||||
"kangaroo": "Känguru",
|
||||
"baby": "Väikelaps",
|
||||
"baby_stroller": "Lapsevanker",
|
||||
"rickshaw": "Rikša",
|
||||
"Rodent": "Näriline",
|
||||
"rodent": "Näriline"
|
||||
}
|
||||
|
||||
@ -121,5 +121,9 @@
|
||||
"royal_mail": "Poste du Royaume Uni",
|
||||
"school_bus": "Bus scolaire",
|
||||
"skunk": "Mouffette",
|
||||
"kangaroo": "Kangourou"
|
||||
"kangaroo": "Kangourou",
|
||||
"baby": "Bébé",
|
||||
"baby_stroller": "Poussette",
|
||||
"rickshaw": "Pousse-pousse",
|
||||
"Rodent": "Rongeur"
|
||||
}
|
||||
|
||||
@ -16,5 +16,7 @@
|
||||
"attach_event_aria": "Attacher l'événement {{eventId}}",
|
||||
"attachment_picker_paste_label": "Ou coller l'event ID",
|
||||
"attachment_picker_placeholder": "Attacher un événement",
|
||||
"quick_reply_find_similar": "Trouver des observations similaires"
|
||||
"quick_reply_find_similar": "Trouver des observations similaires",
|
||||
"no_similar_objects_found": "Aucun objet similaire trouvé.",
|
||||
"semantic_search_required": "La recherche sémantique doit être activée afin de trouver un objet similaire."
|
||||
}
|
||||
|
||||
@ -3,6 +3,31 @@
|
||||
"description": "Rejouer les enregistrement de la camera, à but de débogage. La liste d'objets montre un résumé avec retard des objets détectés; et l'onglet Messages montre le flux des messages internes à Frigate liés à la vidéo rejouée.",
|
||||
"websocket_messages": "Messages",
|
||||
"dialog": {
|
||||
"title": "Démarrer le Rejeu-Debogage"
|
||||
"title": "Démarrer le Rejeu-Debogage",
|
||||
"timeRange": "Intervalle",
|
||||
"preset": {
|
||||
"1m": "Dernière minute",
|
||||
"5m": "5 dernières minutes",
|
||||
"timeline": "Depuis la chronologie",
|
||||
"custom": "Personnalisé"
|
||||
},
|
||||
"startButton": "Démarrer le revisionnage",
|
||||
"selectFromTimeline": "Sélectionner",
|
||||
"starting": "Démarrage du revisionnage...",
|
||||
"startLabel": "Démarrer",
|
||||
"endLabel": "Fin",
|
||||
"toast": {
|
||||
"error": "Echec du démarrage du revisionnage de déboggage : {{error}}",
|
||||
"alreadyActive": "Une session de revisionnage est déjà active",
|
||||
"stopError": "Echec de l'arrêt du revisionnage de déboggage : {{error}}",
|
||||
"goToReplay": "Vers le revisionnage"
|
||||
}
|
||||
},
|
||||
"page": {
|
||||
"noSession": "Aucune session de revisionnage de déboggage active",
|
||||
"noSessionDesc": "Démarrer un revisionnage de déboggage depuis l'Historique en cliquant sur le boutons Actions dans la barre d'outils et choisir Revisionnage de déboggage.",
|
||||
"goToRecordings": "Vers l'historique",
|
||||
"preparingClip": "Préparation du clip…",
|
||||
"preparingClipDesc": "Frigate est encore en train de recoller les enregistrements pour l'intervalle de temps sélectionnée. Cela peut prendre une minute pour les plus longues intervalles."
|
||||
}
|
||||
}
|
||||
|
||||
@ -221,7 +221,8 @@
|
||||
"gl": "Galego (Galiziano)",
|
||||
"id": "Bahasa Indonesia (Indonesiano)",
|
||||
"ur": "اردو (Urdu)",
|
||||
"hr": "Hrvatski (Croato)"
|
||||
"hr": "Hrvatski (Croato)",
|
||||
"bs": "Bosanski (Bosniaco)"
|
||||
},
|
||||
"darkMode": {
|
||||
"label": "Modalità scura",
|
||||
|
||||
@ -33,7 +33,11 @@
|
||||
},
|
||||
"filters": {
|
||||
"label": "Filtri audio",
|
||||
"description": "Impostazioni di filtro per ciascun tipo di audio, come le soglie di confidenza utilizzate per ridurre i falsi positivi."
|
||||
"description": "Impostazioni di filtro per ciascun tipo di audio, come le soglie di confidenza utilizzate per ridurre i falsi positivi.",
|
||||
"threshold": {
|
||||
"label": "Affidabilità audio minima",
|
||||
"description": "Soglia minima di fiducia affinché l'evento audio venga conteggiato."
|
||||
}
|
||||
},
|
||||
"enabled_in_config": {
|
||||
"label": "Stato audio originale",
|
||||
@ -129,7 +133,44 @@
|
||||
}
|
||||
},
|
||||
"detect": {
|
||||
"label": "Rilevamento oggetti"
|
||||
"label": "Rilevamento oggetti",
|
||||
"description": "Impostazioni per il ruolo di rilevamento/rilevamento utilizzato per eseguire il rilevamento degli oggetti e inizializzare i localizzatori.",
|
||||
"enabled": {
|
||||
"label": "Abilita il rilevamento degli oggetti",
|
||||
"description": "Abilita o disabilita il rilevamento degli oggetti per questa telecamera."
|
||||
},
|
||||
"height": {
|
||||
"label": "Rileva altezza",
|
||||
"description": "Altezza (in pixel) dei fotogrammi utilizzati per il flusso di rilevamento; lascia vuoto per utilizzare la risoluzione nativa del flusso."
|
||||
},
|
||||
"width": {
|
||||
"label": "Rileva larghezza",
|
||||
"description": "Larghezza (in pixel) dei fotogrammi utilizzati per il flusso di rilevamento; lascia vuoto per utilizzare la risoluzione nativa del flusso."
|
||||
},
|
||||
"fps": {
|
||||
"label": "Rileva FPS",
|
||||
"description": "Numero di fotogrammi al secondo desiderati per eseguire il rilevamento; valori inferiori riducono l'utilizzo della CPU (il valore consigliato è 5, impostarne uno superiore - al massimo 10 - solo se si devono tracciare oggetti in movimento estremamente rapidi)."
|
||||
},
|
||||
"min_initialized": {
|
||||
"label": "Frame di inizializzazione minimi",
|
||||
"description": "Numero di rilevamenti consecutivi necessari prima di creare un oggetto tracciato. Aumenta questo valore per ridurre le inizializzazioni errate. Il valore predefinito è FPS diviso per 2."
|
||||
},
|
||||
"max_disappeared": {
|
||||
"label": "Numero di fotogrammi scomparsi",
|
||||
"description": "Numero di fotogrammi senza rilevamento prima che un oggetto tracciato venga considerato scomparso."
|
||||
},
|
||||
"stationary": {
|
||||
"label": "Configurazione degli oggetti stazionari",
|
||||
"description": "Impostazioni per rilevare e gestire gli oggetti che rimangono fermi per un certo periodo di tempo.",
|
||||
"interval": {
|
||||
"label": "Intervallo stazionario",
|
||||
"description": "Con quale frequenza (in fotogrammi) eseguire un controllo di rilevamento per confermare che l'oggetto sia stazionario."
|
||||
},
|
||||
"threshold": {
|
||||
"label": "Soglia stazionaria",
|
||||
"description": "Numero di fotogrammi senza cambio di posizione necessari per contrassegnare un oggetto come stazionario."
|
||||
}
|
||||
}
|
||||
},
|
||||
"face_recognition": {
|
||||
"label": "Riconoscimento facciale"
|
||||
@ -189,7 +230,20 @@
|
||||
}
|
||||
},
|
||||
"birdseye": {
|
||||
"label": "Birdseye"
|
||||
"label": "Birdseye",
|
||||
"description": "Impostazioni per la vista composita Birdseye che unisce più flussi video di telecamere in un unico formato.",
|
||||
"enabled": {
|
||||
"label": "Abilita Birdseye",
|
||||
"description": "Abilita o disabilita la funzione di visualizzazione Birdseye."
|
||||
},
|
||||
"mode": {
|
||||
"label": "Modalità di tracciamento",
|
||||
"description": "Modalità per includere le telecamere in Birdseye: 'oggetti', 'movimento' o 'continuo'."
|
||||
},
|
||||
"order": {
|
||||
"label": "Posizione",
|
||||
"description": "Posizione numerica che controlla l'ordine delle telecamere nella disposizione Birdseye."
|
||||
}
|
||||
},
|
||||
"semantic_search": {
|
||||
"label": "Ricerca semantica",
|
||||
@ -216,5 +270,9 @@
|
||||
"label": "Abilitata"
|
||||
},
|
||||
"label": "Zone"
|
||||
},
|
||||
"type": {
|
||||
"description": "Tipo di telecamera",
|
||||
"label": "Tipo di telecamera"
|
||||
}
|
||||
}
|
||||
|
||||
@ -30,7 +30,11 @@
|
||||
},
|
||||
"filters": {
|
||||
"label": "Filtri audio",
|
||||
"description": "Impostazioni di filtro per ciascun tipo di audio, come le soglie di confidenza utilizzate per ridurre i falsi positivi."
|
||||
"description": "Impostazioni di filtro per ciascun tipo di audio, come le soglie di confidenza utilizzate per ridurre i falsi positivi.",
|
||||
"threshold": {
|
||||
"label": "Affidabilità audio minima",
|
||||
"description": "Soglia minima di fiducia affinché l'evento audio venga conteggiato."
|
||||
}
|
||||
},
|
||||
"enabled_in_config": {
|
||||
"label": "Stato audio originale",
|
||||
@ -254,7 +258,44 @@
|
||||
}
|
||||
},
|
||||
"detect": {
|
||||
"label": "Rilevamento oggetti"
|
||||
"label": "Rilevamento oggetti",
|
||||
"description": "Impostazioni per il ruolo di rilevamento/rilevamento utilizzato per eseguire il rilevamento degli oggetti e inizializzare i localizzatori.",
|
||||
"enabled": {
|
||||
"label": "Abilita il rilevamento degli oggetti",
|
||||
"description": "Abilita o disabilita il rilevamento degli oggetti per tutte le telecamere; l'impostazione può essere modificata per ogni singola telecamera."
|
||||
},
|
||||
"height": {
|
||||
"label": "Rileva altezza",
|
||||
"description": "Altezza (in pixel) dei fotogrammi utilizzati per il flusso di rilevamento; lascia vuoto per utilizzare la risoluzione nativa del flusso."
|
||||
},
|
||||
"width": {
|
||||
"label": "Rileva larghezza",
|
||||
"description": "Larghezza (in pixel) dei fotogrammi utilizzati per il flusso di rilevamento; lascia vuoto per utilizzare la risoluzione nativa del flusso."
|
||||
},
|
||||
"fps": {
|
||||
"label": "Rileva FPS",
|
||||
"description": "Numero di fotogrammi al secondo desiderati per eseguire il rilevamento; valori inferiori riducono l'utilizzo della CPU (il valore consigliato è 5, impostarne uno superiore - al massimo 10 - solo se si devono tracciare oggetti in movimento estremamente rapidi)."
|
||||
},
|
||||
"min_initialized": {
|
||||
"label": "Frame di inizializzazione minimi",
|
||||
"description": "Numero di rilevamenti consecutivi necessari prima di creare un oggetto tracciato. Aumenta questo valore per ridurre le inizializzazioni errate. Il valore predefinito è FPS diviso per 2."
|
||||
},
|
||||
"max_disappeared": {
|
||||
"label": "Numero di fotogrammi scomparsi",
|
||||
"description": "Numero di fotogrammi senza rilevamento prima che un oggetto tracciato venga considerato scomparso."
|
||||
},
|
||||
"stationary": {
|
||||
"label": "Configurazione degli oggetti stazionari",
|
||||
"description": "Impostazioni per rilevare e gestire gli oggetti che rimangono fermi per un certo periodo di tempo.",
|
||||
"interval": {
|
||||
"label": "Intervallo stazionario",
|
||||
"description": "Con quale frequenza (in fotogrammi) eseguire un controllo di rilevamento per confermare che l'oggetto sia stazionario."
|
||||
},
|
||||
"threshold": {
|
||||
"label": "Soglia stazionaria",
|
||||
"description": "Numero di fotogrammi senza cambio di posizione necessari per contrassegnare un oggetto come stazionario."
|
||||
}
|
||||
}
|
||||
},
|
||||
"face_recognition": {
|
||||
"label": "Riconoscimento facciale",
|
||||
@ -351,7 +392,7 @@
|
||||
},
|
||||
"notifications": {
|
||||
"label": "Notifiche",
|
||||
"description": "Impostazioni per abilitare e controllare le notifiche per tutte le telecamere; possono essere modificate per ogni singola telecamera.",
|
||||
"description": "Impostazioni per abilitare e controllare le notifiche per tutte le telecamere; possono essere sovrascritte per ogni singola telecamera.",
|
||||
"enabled": {
|
||||
"label": "Abilita le notifiche",
|
||||
"description": "Abilita o disabilita le notifiche per tutte le telecamere; l'impostazione può essere modificata per ogni singola telecamera."
|
||||
@ -400,7 +441,20 @@
|
||||
"label": "Telemetria"
|
||||
},
|
||||
"birdseye": {
|
||||
"label": "Birdseye"
|
||||
"label": "Birdseye",
|
||||
"description": "Impostazioni per la vista composita Birdseye che unisce più flussi video di telecamere in un unico formato.",
|
||||
"enabled": {
|
||||
"label": "Abilita Birdseye",
|
||||
"description": "Abilita o disabilita la funzione di visualizzazione Birdseye."
|
||||
},
|
||||
"mode": {
|
||||
"label": "Modalità di tracciamento",
|
||||
"description": "Modalità per includere le telecamere in Birdseye: 'oggetti', 'movimento' o 'continuo'."
|
||||
},
|
||||
"order": {
|
||||
"label": "Posizione",
|
||||
"description": "Posizione numerica che controlla l'ordine delle telecamere nella disposizione Birdseye."
|
||||
}
|
||||
},
|
||||
"model": {
|
||||
"label": "Modello di rilevamento"
|
||||
@ -445,5 +499,8 @@
|
||||
"label": "Mostra nell'interfaccia utente",
|
||||
"description": "Abilita o disabilita la visualizzazione di questa telecamera in ogni punto dell'interfaccia utente di Frigate. Disabilitando questa opzione, sarà necessario modificare manualmente la configurazione per visualizzare nuovamente la telecamera nell'interfaccia utente."
|
||||
}
|
||||
},
|
||||
"active_profile": {
|
||||
"label": "Profilo attivo"
|
||||
}
|
||||
}
|
||||
|
||||
@ -121,5 +121,9 @@
|
||||
"royal_mail": "Royal Mail",
|
||||
"school_bus": "Autobus scolastico",
|
||||
"skunk": "Puzzola",
|
||||
"kangaroo": "Canguro"
|
||||
"kangaroo": "Canguro",
|
||||
"baby": "Bambino",
|
||||
"baby_stroller": "Passeggino per bambini",
|
||||
"rickshaw": "Risciò",
|
||||
"rodent": "Roditore"
|
||||
}
|
||||
|
||||
@ -42,5 +42,23 @@
|
||||
"show_camera_status": "Qual è lo stato attuale delle mie telecamere?",
|
||||
"recap": "Cosa è successo mentre ero via?",
|
||||
"watch_camera": "Controlla la porta d'ingresso e fammi sapere se arriva qualcuno"
|
||||
},
|
||||
"new_chat": "Nuova chat",
|
||||
"settings": {
|
||||
"title": "Impostazioni chat",
|
||||
"show_stats": {
|
||||
"title": "Mostra statistiche",
|
||||
"desc": "Mostra la frequenza di generazione e la dimensione del contesto per le risposte in chat.",
|
||||
"while_generating": "Durante la generazione",
|
||||
"always": "Sempre"
|
||||
},
|
||||
"auto_scroll": {
|
||||
"title": "Scorrimento automatico",
|
||||
"desc": "Segui i nuovi messaggi non appena arrivano."
|
||||
}
|
||||
},
|
||||
"stats": {
|
||||
"context": "{{tokens}} token",
|
||||
"tokens_per_second": "{{rate}} t/s"
|
||||
}
|
||||
}
|
||||
|
||||
@ -2,7 +2,7 @@
|
||||
"alerts": "Avvisi",
|
||||
"detections": "Rilevamenti",
|
||||
"motion": {
|
||||
"label": "Movimenti",
|
||||
"label": "Movimento",
|
||||
"only": "Solo movimenti"
|
||||
},
|
||||
"empty": {
|
||||
|
||||
@ -20,7 +20,11 @@
|
||||
"title": "Riconoscimenti recenti",
|
||||
"aria": "Seleziona i riconoscimenti recenti",
|
||||
"empty": "Non ci sono recenti tentativi di riconoscimento facciale",
|
||||
"titleShort": "Recente"
|
||||
"titleShort": "Recente",
|
||||
"emptyNoLibrary": {
|
||||
"title": "Carica un volto",
|
||||
"description": "Affinché il riconoscimento facciale funzioni è necessario aggiungere almeno un volto alla libreria."
|
||||
}
|
||||
},
|
||||
"button": {
|
||||
"addFace": "Aggiungi volto",
|
||||
|
||||
@ -158,7 +158,7 @@
|
||||
},
|
||||
"effectiveRetainMode": {
|
||||
"modes": {
|
||||
"all": "Tutto",
|
||||
"all": "Tutti",
|
||||
"motion": "Movimento",
|
||||
"active_objects": "Oggetti attivi"
|
||||
},
|
||||
|
||||
@ -52,7 +52,15 @@
|
||||
"modelType": "Tipo di modello",
|
||||
"modelSelect": "Qui puoi selezionare i modelli disponibili su Frigate+. Nota: puoi selezionare solo i modelli compatibili con la configurazione attuale del tuo rilevatore.",
|
||||
"title": "Informazioni sul modello",
|
||||
"loading": "Caricamento informazioni sul modello…"
|
||||
"loading": "Caricamento informazioni sul modello…",
|
||||
"noModelsAvailable": "Nessun modello disponibile",
|
||||
"noModelLoaded": "Al momento non è caricato alcun modello di Frigate+.",
|
||||
"selectModel": "Seleziona un modello",
|
||||
"filter": {
|
||||
"ariaLabel": "Filtra i modelli per tipo",
|
||||
"baseModels": "Modelli base",
|
||||
"fineTunedModels": "Modelli ottimizzati"
|
||||
}
|
||||
},
|
||||
"toast": {
|
||||
"error": "Impossibile salvare le modifiche alla configurazione: {{errorMessage}}",
|
||||
@ -203,6 +211,10 @@
|
||||
"zone": "zona",
|
||||
"motion_mask": "maschera di movimento",
|
||||
"object_mask": "maschera di oggetto"
|
||||
},
|
||||
"revertOverride": {
|
||||
"title": "Ripristina la configurazione di base",
|
||||
"desc": "Questo rimuoverà la sovrascrittura del profilo per {{type}} <em>{{name}}</em> e ripristinerà la configurazione di base."
|
||||
}
|
||||
},
|
||||
"inertia": {
|
||||
@ -219,6 +231,17 @@
|
||||
"error": {
|
||||
"mustBeGreaterOrEqualTo": "La soglia di velocità deve essere maggiore o uguale a 0,1."
|
||||
}
|
||||
},
|
||||
"id": {
|
||||
"error": {
|
||||
"mustNotBeEmpty": "L'ID non deve essere vuoto.",
|
||||
"alreadyExists": "Esiste già una maschera con questo ID per questa telecamera."
|
||||
}
|
||||
},
|
||||
"name": {
|
||||
"error": {
|
||||
"mustNotBeEmpty": "Il nome non deve essere vuoto."
|
||||
}
|
||||
}
|
||||
},
|
||||
"filter": {
|
||||
@ -329,7 +352,11 @@
|
||||
"title": "Abilitata",
|
||||
"description": "Indica se questa maschera è abilitata nel file di configurazione. Se disabilitata, non può essere abilitata tramite MQTT. Le maschere disabilitate vengono ignorate in fase di esecuzione."
|
||||
}
|
||||
}
|
||||
},
|
||||
"disabledInConfig": "L'elemento è disabilitato nel file di configurazione",
|
||||
"addDisabledProfile": "Aggiungi prima alla configurazione di base, poi sovrascrivi nel profilo",
|
||||
"profileBase": "(base)",
|
||||
"profileOverride": "(sovrascrivi)"
|
||||
},
|
||||
"cameraSetting": {
|
||||
"camera": "Telecamera",
|
||||
@ -477,7 +504,10 @@
|
||||
"cameraOnvif": "ONVIF",
|
||||
"cameraTimestampStyle": "Stile orario",
|
||||
"cameraUi": "Interfaccia utente telecamera",
|
||||
"mediaSync": "Sincronizzazione multimediale"
|
||||
"mediaSync": "Sincronizzazione multimediale",
|
||||
"cameraMqtt": "MQTT telecamera",
|
||||
"maintenance": "Manutenzione",
|
||||
"regionGrid": "Griglia di regioni"
|
||||
},
|
||||
"users": {
|
||||
"dialog": {
|
||||
@ -1359,7 +1389,8 @@
|
||||
},
|
||||
"hikvision": {
|
||||
"substreamWarning": "Il sottoflusso 1 è bloccato a bassa risoluzione. Molte telecamere Hikvision supportano sottoflussi aggiuntivi che devono essere abilitati nelle impostazioni della telecamera. Si consiglia di controllare e utilizzare tali flussi, se disponibili."
|
||||
}
|
||||
},
|
||||
"resolutionUnknown": "Non è stato possibile rilevare la risoluzione di questo flusso. È necessario impostare manualmente la risoluzione di rilevamento nelle Impostazioni o nella configurazione."
|
||||
}
|
||||
}
|
||||
},
|
||||
@ -1414,7 +1445,33 @@
|
||||
"addGo2rtcStream": "Aggiungi flusso go2rtc"
|
||||
},
|
||||
"profiles": {
|
||||
"enabled": "Abilitato"
|
||||
"enabled": "Abilitato",
|
||||
"title": "Sovrascritture della telecamera del profilo",
|
||||
"selectLabel": "Seleziona il profilo",
|
||||
"description": "Configura quali telecamere vengono abilitate o disabilitate all'attivazione di un profilo. Le telecamere impostate su \"Eredita\" mantengono il loro stato di abilitazione predefinito.",
|
||||
"inherit": "Eredita",
|
||||
"disabled": "Disabilitato"
|
||||
},
|
||||
"description": "Aggiungi, modifica ed elimina le telecamere, controlla quali telecamere sono abilitate e configura le impostazioni personalizzate per profilo e tipo di telecamera. Per configurare flussi video, rilevamento, movimento e altre impostazioni specifiche per ciascuna telecamera, seleziona la sezione corrispondente in Configurazione telecamera.",
|
||||
"deleteCamera": "Elimina telecamera",
|
||||
"deleteCameraDialog": {
|
||||
"title": "Elimina telecamera",
|
||||
"description": "L'eliminazione di una telecamera rimuoverà in modo permanente tutte le registrazioni, gli oggetti tracciati e la configurazione relativi a tale telecamera. Potrebbe essere comunque necessario rimuovere manualmente gli eventuali flussi go2rtc associati a questa telecamera.",
|
||||
"selectPlaceholder": "Scegli telecamera...",
|
||||
"confirmTitle": "Sei sicuro?",
|
||||
"confirmWarning": "L'eliminazione di <strong>{{cameraName}}</strong> è irreversibile.",
|
||||
"deleteExports": "Elimina anche le esportazioni per questa telecamera",
|
||||
"confirmButton": "Elimina definitivamente",
|
||||
"success": "Telecamera {{cameraName}} eliminata con successo",
|
||||
"error": "Impossibile eliminare la telecamera {{cameraName}}"
|
||||
},
|
||||
"cameraType": {
|
||||
"title": "Tipo di telecamera",
|
||||
"label": "Tipo di telecamera",
|
||||
"description": "Imposta il tipo per ogni telecamera. Le telecamere LPR dedicate sono telecamere monouso con un potente zoom ottico per acquisire le targhe dei veicoli distanti. La maggior parte delle telecamere dovrebbe utilizzare il tipo di telecamera normale, a meno che non siano specificamente progettate per il riconoscimento delle targhe e abbiano una visuale molto ravvicinata sulle targhe.",
|
||||
"normal": "Normale",
|
||||
"dedicatedLpr": "LPR dedicata",
|
||||
"saveSuccess": "Tipo di telecamera aggiornato per {{cameraName}}. Riavviare Frigate per applicare le modifiche."
|
||||
}
|
||||
},
|
||||
"button": {
|
||||
@ -1436,7 +1493,15 @@
|
||||
"othersField_many": "{{count}} altri",
|
||||
"othersField_other": "{{count}} altri",
|
||||
"profilePrefix": "Profilo {{profile}}: {{fields}}"
|
||||
}
|
||||
},
|
||||
"overriddenGlobalHeading_one": "Questa telecamera sovrascrive il campo {{count}} dalla configurazione globale:",
|
||||
"overriddenGlobalHeading_many": "Questa telecamera sovrascrive i campi {{count}} della configurazione globale:",
|
||||
"overriddenGlobalHeading_other": "Questa telecamera sovrascrive i campi {{count}} della configurazione globale:",
|
||||
"overriddenGlobalNoDeltas": "Questa telecamera sovrascrive la configurazione globale, ma nessun valore dei campi risulta diverso.",
|
||||
"overriddenBaseConfigHeading_one": "Il profilo {{profile}} sovrascrive il campo {{count}} della configurazione di base:",
|
||||
"overriddenBaseConfigHeading_many": "Il profilo {{profile}} sovrascrive i campi {{count}} della configurazione di base:",
|
||||
"overriddenBaseConfigHeading_other": "Il profilo {{profile}} sovrascrive i campi {{count}} della configurazione di base:",
|
||||
"overriddenBaseConfigNoDeltas": "Il profilo {{profile}} sovrascrive questa sezione, ma nessun valore di campo differisce dalla configurazione di base."
|
||||
},
|
||||
"go2rtcStreams": {
|
||||
"title": "Flussi go2rtc",
|
||||
@ -1447,8 +1512,38 @@
|
||||
"videoCopy": "Copia",
|
||||
"hardware": "Accelerazione hardware",
|
||||
"hardwareNone": "Nessuna accelerazione hardware",
|
||||
"hardwareAuto": "Accelerazione hardware automatica"
|
||||
}
|
||||
"hardwareAuto": "Accelerazione hardware automatica",
|
||||
"useFfmpegModule": "Utilizza la modalità di compatibilità (ffmpeg)",
|
||||
"videoH264": "Transcodifica in H.264",
|
||||
"videoH265": "Transcodifica in H.265",
|
||||
"videoExclude": "Escludi",
|
||||
"audioAac": "Transcodifica in AAC",
|
||||
"audioOpus": "Transcodifica in Opus",
|
||||
"audioPcmu": "Transcodifica in PCM μ-law",
|
||||
"audioPcma": "Transcodifica in PCM A-law",
|
||||
"audioPcm": "Transcodifica in PCM",
|
||||
"audioMp3": "Transcodifica in MP3",
|
||||
"audioExclude": "Escludi"
|
||||
},
|
||||
"description": "Gestisci le configurazioni del flusso go2rtc per la ritrasmissione delle immagini della telecamera. Ogni flusso ha un nome e uno o più URL sorgente.",
|
||||
"addStream": "Aggiungi flusso",
|
||||
"addStreamDesc": "Inserisci un nome per il nuovo flusso. Questo nome verrà utilizzato per identificare il flusso nella configurazione della telecamera.",
|
||||
"addUrl": "Aggiungi URL",
|
||||
"streamName": "Nome flusso",
|
||||
"streamNamePlaceholder": "p. es., porta_ingresso",
|
||||
"streamUrlPlaceholder": "p. es., rtsp://utente:password@192.168.1.100/flusso",
|
||||
"deleteStream": "Elimina flusso",
|
||||
"deleteStreamConfirm": "Sei sicuro di voler eliminare il flusso \"{{streamName}}\"? Le telecamere che fanno riferimento a questo flusso potrebbero smettere di funzionare.",
|
||||
"noStreams": "Nessun flusso go2rtc configurato. Aggiungi un flusso per iniziare.",
|
||||
"validation": {
|
||||
"nameRequired": "Il nome del flusso è obbligatorio",
|
||||
"nameDuplicate": "Esiste già un flusso con questo nome",
|
||||
"nameInvalid": "Il nome del flusso può contenere solo lettere, numeri, trattini bassi e trattini",
|
||||
"urlRequired": "È richiesto almeno un URL"
|
||||
},
|
||||
"renameStream": "Rinomina flusso",
|
||||
"renameStreamDesc": "Inserisci un nuovo nome per questo flusso. Rinominare un flusso potrebbe causare problemi alle telecamere o ad altri flussi che lo referenziano tramite il suo nome.",
|
||||
"newStreamName": "Nuovo nome del flusso"
|
||||
},
|
||||
"configForm": {
|
||||
"sections": {
|
||||
@ -1470,7 +1565,14 @@
|
||||
"face_recognition": "Riconoscimento facciale",
|
||||
"masksAndZones": "Maschere / Zone",
|
||||
"audio": "Audio",
|
||||
"model": "Modello"
|
||||
"model": "Modello",
|
||||
"detect": "Rilevamento",
|
||||
"motion": "Movimento",
|
||||
"live": "Vista dal vivo",
|
||||
"timestamp_style": "Orari",
|
||||
"go2rtc": "go2rtc",
|
||||
"detectors": "Rivelatori",
|
||||
"genai": "GenAI"
|
||||
},
|
||||
"tabs": {
|
||||
"system": "Sistema",
|
||||
@ -1479,12 +1581,19 @@
|
||||
},
|
||||
"inputRoles": {
|
||||
"options": {
|
||||
"audio": "Audio"
|
||||
}
|
||||
"audio": "Audio",
|
||||
"detect": "Rileva",
|
||||
"record": "Registra"
|
||||
},
|
||||
"summary": "{{count}} ruoli selezionati",
|
||||
"empty": "Nessun ruolo disponibile"
|
||||
},
|
||||
"roleMap": {
|
||||
"roleLabel": "Ruolo",
|
||||
"remove": "Rimuovi"
|
||||
"remove": "Rimuovi",
|
||||
"empty": "Nessuna mappatura dei ruoli",
|
||||
"groupsLabel": "Gruppi",
|
||||
"addMapping": "Aggiungi la mappatura dei ruoli"
|
||||
},
|
||||
"notifications": {
|
||||
"title": "Impostazioni di notifica"
|
||||
@ -1518,19 +1627,179 @@
|
||||
"presetLabels": {
|
||||
"preset-rpi-64-h264": "Raspberry Pi (H.264)",
|
||||
"preset-rpi-64-h265": "Raspberry Pi (H.265)",
|
||||
"preset-vaapi": "VAAPI (GPU Intel/AMD)"
|
||||
"preset-vaapi": "VAAPI (GPU Intel/AMD)",
|
||||
"preset-intel-qsv-h264": "Intel QuickSync (H.264)",
|
||||
"preset-intel-qsv-h265": "Intel QuickSync (H.265)",
|
||||
"preset-nvidia": "GPU NVIDIA",
|
||||
"preset-jetson-h264": "NVIDIA Jetson (H.264)",
|
||||
"preset-jetson-h265": "NVIDIA Jetson (H.265)",
|
||||
"preset-rkmpp": "Rockchip RKMPP",
|
||||
"preset-http-jpeg-generic": "HTTP JPEG (Generico)",
|
||||
"preset-http-mjpeg-generic": "HTTP JPEG (Generico)",
|
||||
"preset-http-reolink": "HTTP - Telecamere Reolink",
|
||||
"preset-rtmp-generic": "RTMP (Generico)",
|
||||
"preset-rtsp-generic": "RTSP (Generico)",
|
||||
"preset-rtsp-restream": "RTSP - Ritrasmissione da go2rtc",
|
||||
"preset-rtsp-restream-low-latency": "RTSP - Ritrasmissione da go2rtc (bassa latenza)",
|
||||
"preset-rtsp-udp": "RTSP - UDP",
|
||||
"preset-rtsp-blue-iris": "RTSP - Blue Iris",
|
||||
"preset-record-generic": "Registrazione (Generica, senza audio)",
|
||||
"preset-record-generic-audio-copy": "Registrazione (generica + copia audio)",
|
||||
"preset-record-generic-audio-aac": "Registrazione (generica + audio in AAC)",
|
||||
"preset-record-mjpeg": "Registrazione - Telecamere MJPEG",
|
||||
"preset-record-jpeg": "Registrazione - Telecamere JPEG",
|
||||
"preset-record-ubiquiti": "Registrazione - Telecamere Ubiquiti"
|
||||
}
|
||||
},
|
||||
"cameraInputs": {
|
||||
"itemTitle": "Stream {{index}}"
|
||||
},
|
||||
"restartRequiredField": "Riavvio richiesto",
|
||||
"restartRequiredFooter": "Configurazione modificata - Riavvio necessario",
|
||||
"detect": {
|
||||
"title": "Impostazioni di rilevamento"
|
||||
},
|
||||
"detectors": {
|
||||
"title": "Impostazioni del rilevatore",
|
||||
"singleType": "È consentito un solo rilevatore di tipo {{type}}.",
|
||||
"keyRequired": "Il nome del rilevatore è obbligatorio.",
|
||||
"keyDuplicate": "Il nome del rilevatore esiste già.",
|
||||
"noSchema": "Non sono disponibili schemi di rilevamento.",
|
||||
"none": "Nessuna istanza del rilevatore configurata.",
|
||||
"add": "Aggiungi rilevatore",
|
||||
"addCustomKey": "Aggiungi chiave personalizzata"
|
||||
},
|
||||
"record": {
|
||||
"title": "Impostazioni di registrazione"
|
||||
},
|
||||
"snapshots": {
|
||||
"title": "Impostazioni istantanea"
|
||||
},
|
||||
"motion": {
|
||||
"title": "Impostazioni di movimento"
|
||||
},
|
||||
"objects": {
|
||||
"title": "Impostazioni oggetto"
|
||||
},
|
||||
"audioLabels": {
|
||||
"summary": "{{count}} etichette audio selezionate",
|
||||
"empty": "Nessuna etichetta audio disponibile"
|
||||
},
|
||||
"objectLabels": {
|
||||
"summary": "{{count}} tipi di oggetto selezionati",
|
||||
"empty": "Non sono disponibili etichette per gli oggetti"
|
||||
},
|
||||
"reviewLabels": {
|
||||
"summary": "{{count}} etichette selezionate",
|
||||
"empty": "Nessuna etichetta disponibile"
|
||||
},
|
||||
"filters": {
|
||||
"objectFieldLabel": "{{field}} per {{label}}"
|
||||
},
|
||||
"zoneNames": {
|
||||
"summary": "{{count}} selezionati",
|
||||
"empty": "Nessuna zona disponibile"
|
||||
},
|
||||
"genaiRoles": {
|
||||
"options": {
|
||||
"embeddings": "Incorporamento",
|
||||
"descriptions": "Descrizioni",
|
||||
"chat": "Chat"
|
||||
}
|
||||
},
|
||||
"semanticSearchModel": {
|
||||
"placeholder": "Seleziona il modello…",
|
||||
"builtIn": "Modelli integrati",
|
||||
"genaiProviders": "Fornitori di GenAI"
|
||||
},
|
||||
"genaiModel": {
|
||||
"placeholder": "Seleziona il modello…",
|
||||
"search": "Ricerca modelli…",
|
||||
"noModels": "Nessun modello disponibile"
|
||||
},
|
||||
"review": {
|
||||
"title": "Impostazioni di revisione"
|
||||
},
|
||||
"audio": {
|
||||
"title": "Impostazioni audio"
|
||||
},
|
||||
"live": {
|
||||
"title": "Impostazioni della visualizzazione dal vivo"
|
||||
},
|
||||
"timestamp_style": {
|
||||
"title": "Impostazioni orario"
|
||||
},
|
||||
"searchPlaceholder": "Ricerca...",
|
||||
"addCustomLabel": "Aggiungi etichetta personalizzata...",
|
||||
"knownPlates": {
|
||||
"namePlaceholder": "p. es., auto della moglie",
|
||||
"platePlaceholder": "Numero di targa o espressione regolare"
|
||||
},
|
||||
"timezone": {
|
||||
"defaultOption": "Utilizza il fuso orario del browser"
|
||||
}
|
||||
},
|
||||
"globalConfig": {
|
||||
"title": "Configurazione globale"
|
||||
"title": "Configurazione globale",
|
||||
"description": "Configura le impostazioni globali che si applicano a tutte le telecamere, a meno che non vengano sovrascritte.",
|
||||
"toast": {
|
||||
"success": "Impostazioni globali salvate correttamente",
|
||||
"error": "Impossibile salvare le impostazioni globali",
|
||||
"validationError": "Validazione fallita"
|
||||
}
|
||||
},
|
||||
"cameraConfig": {
|
||||
"title": "Configurazione telecamera"
|
||||
"title": "Configurazione telecamera",
|
||||
"description": "Configura le impostazioni per le singole telecamere. Le impostazioni personalizzate sovrascrivono le impostazioni predefinite globali.",
|
||||
"overriddenBadge": "Sovrascritto",
|
||||
"resetToGlobal": "Ripristina impostazioni globali",
|
||||
"toast": {
|
||||
"success": "Impostazioni della telecamera salvate correttamente",
|
||||
"error": "Impossibile salvare le impostazioni della telecamera"
|
||||
}
|
||||
},
|
||||
"profiles": {
|
||||
"title": "Profili",
|
||||
"columnCamera": "Telecamera"
|
||||
"columnCamera": "Telecamera",
|
||||
"activeProfile": "Profilo attivo",
|
||||
"noActiveProfile": "Nessun profilo attivo",
|
||||
"active": "Attivo",
|
||||
"activated": "Profilo '{{profile}}' attivato",
|
||||
"activateFailed": "Impossibile impostare il profilo",
|
||||
"deactivated": "Profilo disattivato",
|
||||
"noProfiles": "Nessun profilo definito.",
|
||||
"noOverrides": "Nessuna sovrascrittura",
|
||||
"cameraCount_one": "{{count}} telecamera",
|
||||
"cameraCount_many": "{{count}} telecamere",
|
||||
"cameraCount_other": "{{count}} telecamere",
|
||||
"columnOverrides": "Sovrascritture del profilo",
|
||||
"baseConfig": "Configurazione di base",
|
||||
"addProfile": "Aggiungi profilo",
|
||||
"newProfile": "Nuovo profilo",
|
||||
"profileNamePlaceholder": "p. es., Inserita, Assente, Modalità notturna",
|
||||
"friendlyNameLabel": "Nome profilo",
|
||||
"profileIdLabel": "ID profilo",
|
||||
"profileIdDescription": "Identificativo interno utilizzato nella configurazione e nelle automazioni",
|
||||
"nameInvalid": "Sono consentite solo lettere minuscole, numeri e trattini bassi",
|
||||
"nameDuplicate": "Esiste già un profilo con questo nome",
|
||||
"error": {
|
||||
"mustBeAtLeastTwoCharacters": "Deve contenere almeno 2 caratteri",
|
||||
"mustNotContainPeriod": "Non deve contenere punti",
|
||||
"alreadyExists": "Esiste già un profilo con questo ID"
|
||||
},
|
||||
"renameProfile": "Rinomina profilo",
|
||||
"renameSuccess": "Profilo rinominato in '{{profile}}'",
|
||||
"deleteProfile": "Elimina profilo",
|
||||
"deleteProfileConfirm": "Eliminare il profilo \"{{profile}}\" da tutte le telecamere? Questa operazione non può essere annullata.",
|
||||
"deleteSuccess": "Profilo '{{profile}}' eliminato",
|
||||
"createSuccess": "Profilo '{{profile}}' creato",
|
||||
"removeOverride": "Rimuovi la sovrascrittura del profilo",
|
||||
"deleteSection": "Elimina le sostituzioni della sezione",
|
||||
"deleteSectionConfirm": "Rimuovere le sovrascritture {{section}} per il profilo {{profile}} su {{camera}}?",
|
||||
"deleteSectionSuccess": "Rimosse le sovrascritture di {{section}} per {{profile}}",
|
||||
"enableSwitch": "Abilita profili",
|
||||
"enabledDescription": "I profili sono abilitati. Crea un nuovo profilo qui sotto, vai alla sezione di configurazione della telecamera per apportare le modifiche e salva affinché le modifiche abbiano effetto.",
|
||||
"disabledDescription": "I profili consentono di definire insiemi denominati di impostazioni di configurazione della telecamera (p.es., inserita, assente, notturna) che possono essere attivate su richiesta."
|
||||
},
|
||||
"timestampPosition": {
|
||||
"tl": "In alto a sinistra",
|
||||
@ -1564,14 +1833,37 @@
|
||||
"errorLabel": "Errore",
|
||||
"resultsFields": {
|
||||
"error": "Errore",
|
||||
"totals": "Totali"
|
||||
"totals": "Totali",
|
||||
"filesChecked": "File controllati",
|
||||
"orphansFound": "Orfani trovati",
|
||||
"orphansDeleted": "Orfani eliminati",
|
||||
"aborted": "Interrotto. La cancellazione supererebbe la soglia di sicurezza."
|
||||
},
|
||||
"event_snapshots": "Istantanee degli oggetti tracciati",
|
||||
"event_thumbnails": "Miniature degli oggetti tracciati",
|
||||
"review_thumbnails": "Anteprima delle miniature",
|
||||
"previews": "Anteprime",
|
||||
"exports": "Esportazioni",
|
||||
"recordings": "Registrazioni"
|
||||
"recordings": "Registrazioni",
|
||||
"mediaTypes": "Tipi di supporto",
|
||||
"allMedia": "Tutti i supporti",
|
||||
"dryRun": "Prova a secco",
|
||||
"dryRunEnabled": "Nessun file verrà eliminato",
|
||||
"dryRunDisabled": "I file verranno eliminati",
|
||||
"force": "Forza",
|
||||
"forceDesc": "Ignora la soglia di sicurezza e completa la sincronizzazione anche se più del 50% dei file verrebbe eliminato.",
|
||||
"verbose": "Dettagliato",
|
||||
"verboseDesc": "Scrivi un elenco completo dei file orfani su disco per la revisione.",
|
||||
"running": "Sincronizzazione in corso...",
|
||||
"start": "Avvia sincronizzazione",
|
||||
"inProgress": "Sincronizzazione in corso. Questa pagina è disabilitata.",
|
||||
"status": {
|
||||
"queued": "In coda",
|
||||
"running": "In corso",
|
||||
"completed": "Completata",
|
||||
"failed": "Fallita",
|
||||
"notRunning": "Non in esecuzione"
|
||||
}
|
||||
},
|
||||
"regionGrid": {
|
||||
"title": "Griglia di regioni",
|
||||
@ -1583,5 +1875,158 @@
|
||||
"clearError": "Impossibile pulire la griglia di regioni",
|
||||
"restartRequired": "È necessario riavviare il sistema affinché le modifiche alla griglia di regioni abbiano effetto"
|
||||
}
|
||||
},
|
||||
"retainMode": {
|
||||
"motion": "Movimento",
|
||||
"all": "Tutti",
|
||||
"active_objects": "Oggetti attivi"
|
||||
},
|
||||
"birdseye": {
|
||||
"trackingMode": {
|
||||
"motion": "Movimento",
|
||||
"objects": "Oggetti",
|
||||
"continuous": "Continuo"
|
||||
}
|
||||
},
|
||||
"toast": {
|
||||
"success": "Impostazioni salvate correttamente",
|
||||
"applied": "Impostazioni applicate correttamente",
|
||||
"successRestartRequired": "Impostazioni salvate correttamente. Riavvia Frigate per applicare le modifiche.",
|
||||
"error": "Impossibile salvare le impostazioni",
|
||||
"validationError": "Validazione non riuscita: {{message}}",
|
||||
"resetSuccess": "Ripristina le impostazioni predefinite globali",
|
||||
"resetError": "Impossibile ripristinare le impostazioni",
|
||||
"saveAllSuccess_one": "Salvata {{count}} sezione correttamente.",
|
||||
"saveAllSuccess_many": "Tutte le {{count}} sezioni sono state salvate correttamente.",
|
||||
"saveAllSuccess_other": "Tutte le {{count}} sezioni sono state salvate correttamente.",
|
||||
"saveAllPartial_one": "{{successCount}} sezione su {{totalCount}} salvata. {{failCount}} errore.",
|
||||
"saveAllPartial_many": "{{successCount}} sezioni su {{totalCount}} salvate. {{failCount}} errori.",
|
||||
"saveAllPartial_other": "{{successCount}} sezioni su {{totalCount}} salvate. {{failCount}} errori.",
|
||||
"saveAllFailure": "Impossibile salvare tutte le sezioni."
|
||||
},
|
||||
"unsavedChanges": "Hai delle modifiche non salvate",
|
||||
"confirmReset": "Conferma il ripristino",
|
||||
"resetToDefaultDescription": "Questa operazione ripristinerà tutte le impostazioni di questa sezione ai valori predefiniti. Tale azione è irreversibile.",
|
||||
"resetToGlobalDescription": "Questa operazione ripristinerà le impostazioni di questa sezione ai valori predefiniti globali. Tale azione è irreversibile.",
|
||||
"previewQuality": {
|
||||
"very_high": "Molto alta",
|
||||
"high": "Alta",
|
||||
"medium": "Media",
|
||||
"low": "Bassa",
|
||||
"very_low": "Molto bassa"
|
||||
},
|
||||
"ui": {
|
||||
"TimeOrDateStyle": {
|
||||
"medium": "Medio",
|
||||
"full": "Completo",
|
||||
"long": "Lungo",
|
||||
"short": "Corto"
|
||||
},
|
||||
"timeFormat": {
|
||||
"browser": "Navigatore",
|
||||
"12hour": "12 ore",
|
||||
"24hour": "24 ore"
|
||||
},
|
||||
"unitSystem": {
|
||||
"metric": "Metrico",
|
||||
"imperial": "Imperiale"
|
||||
}
|
||||
},
|
||||
"review": {
|
||||
"imageSource": {
|
||||
"recordings": "Registrazioni",
|
||||
"previews": "Anteprime"
|
||||
}
|
||||
},
|
||||
"logger": {
|
||||
"logLevel": {
|
||||
"debug": "Correzioni",
|
||||
"info": "Informazioni",
|
||||
"warning": "Avviso",
|
||||
"error": "Errore",
|
||||
"critical": "Critico"
|
||||
}
|
||||
},
|
||||
"onvif": {
|
||||
"profileAuto": "Automatico",
|
||||
"profileLoading": "Caricamento profili...",
|
||||
"autotracking": {
|
||||
"zooming": {
|
||||
"disabled": "Disabilitato",
|
||||
"absolute": "Assoluto",
|
||||
"relative": "Relativo"
|
||||
}
|
||||
}
|
||||
},
|
||||
"modelSize": {
|
||||
"small": "Piccolo",
|
||||
"large": "Grande"
|
||||
},
|
||||
"configMessages": {
|
||||
"review": {
|
||||
"recordDisabled": "La registrazione è disabilitata, pertanto non verranno generati elementi di revisione.",
|
||||
"detectDisabled": "Il rilevamento degli oggetti è disabilitato. Gli elementi di revisione richiedono la presenza di oggetti rilevati per poter classificare avvisi e rilevamenti.",
|
||||
"allNonAlertDetections": "Tutte le attività non di avviso saranno incluse tra i rilevamenti.",
|
||||
"genaiImageSourceRecordingsRecordDisabled": "La sorgente dell'immagine è impostata su 'registrazioni', ma la registrazione è disabilitata. Frigate utilizzerà le immagini di anteprima."
|
||||
},
|
||||
"audio": {
|
||||
"noAudioRole": "Nessun flusso ha il ruolo audio definito. È necessario abilitare il ruolo audio affinché il rilevamento audio funzioni."
|
||||
},
|
||||
"audioTranscription": {
|
||||
"audioDetectionDisabled": "Il rilevamento audio non è abilitato per questa telecamera. La trascrizione audio richiede che il rilevamento audio sia attivo."
|
||||
},
|
||||
"detect": {
|
||||
"fpsGreaterThanFive": "Impostare il valore di FPS rilevato su un valore superiore a 5 non è consigliabile. Valori più elevati potrebbero causare problemi di prestazioni e non apporteranno alcun vantaggio.",
|
||||
"disabled": "Il rilevamento degli oggetti è disabilitato. Le istantanee, gli elementi di revisione e le funzionalità aggiuntive come il riconoscimento facciale, il riconoscimento delle targhe e l'intelligenza artificiale generativa non funzioneranno."
|
||||
},
|
||||
"objects": {
|
||||
"genaiNoDescriptionsProvider": "Per generare le descrizioni è necessario configurare un provider GenAI con il ruolo 'descrizioni'."
|
||||
},
|
||||
"faceRecognition": {
|
||||
"globalDisabled": "Perché le funzionalità di riconoscimento facciale funzionino correttamente su questa telecamera, è necessario abilitare l'arricchimento del riconoscimento facciale.",
|
||||
"personNotTracked": "Il riconoscimento facciale richiede che l'oggetto 'persona' venga tracciato. Abilita 'persona' nella sezione ogggetti di questa telecamera.",
|
||||
"modelSizeLarge": "Il modello 'grande' richiede una GPU o una NPU per prestazioni accettabili. Utilizzare il modello 'piccolo' su sistemi dotati solo di CPU."
|
||||
},
|
||||
"lpr": {
|
||||
"globalDisabled": "Per il corretto funzionamento delle funzioni LPR (riconoscimento targhe) su questa telecamera, è necessario abilitare la funzione di arricchimento del riconoscimento delle targhe.",
|
||||
"vehicleNotTracked": "Il riconoscimento della targa richiede che venga tracciato 'automobile' o 'moto'. Abilita 'automobile' o 'moto' nella sezione oggetti per questa telecamera.",
|
||||
"modelSizeLarge": "Il modello 'grande' è ottimizzato per le targhe multilinea. Il modello 'piccolo' offre prestazioni migliori rispetto al modello 'grande' e dovrebbe essere utilizzato a meno che nella vostra regione non siano in vigore formati di targa multilinea."
|
||||
},
|
||||
"record": {
|
||||
"noRecordRole": "Nessun flusso ha il ruolo di registrazione definito. La registrazione non funzionerà."
|
||||
},
|
||||
"birdseye": {
|
||||
"objectsModeDetectDisabled": "Birdseye è impostato sulla modalità 'oggetti', ma il rilevamento degli oggetti è disabilitato per questa telecamera. La telecamera non verrà visualizzata in Birdseye."
|
||||
},
|
||||
"snapshots": {
|
||||
"detectDisabled": "Il rilevamento degli oggetti è disabilitato. Le istantanee vengono generate dagli oggetti tracciati e non verranno create."
|
||||
},
|
||||
"detectors": {
|
||||
"mixedTypes": "Tutti i rilevatori devono essere dello stesso tipo. Rimuovi i rilevatori esistenti per poter utilizzare un tipo diverso.",
|
||||
"mixedTypesSuggestion": "Tutti i rilevatori devono essere dello stesso tipo. Rimuovi i rilevatori esistenti oppure seleziona {{type}}."
|
||||
},
|
||||
"semanticSearch": {
|
||||
"jinav2SmallModelSize": "Il modello 'piccolo' Jina V2 presenta elevati consumi di RAM e di inferenza. Si consiglia il modello 'grande' con GPU dedicata."
|
||||
}
|
||||
},
|
||||
"saveAllPreview": {
|
||||
"title": "Modifiche da salvare",
|
||||
"triggerLabel": "Revisione delle modifiche in sospeso",
|
||||
"empty": "Nessuna modifica in sospeso.",
|
||||
"scope": {
|
||||
"label": "Ambito",
|
||||
"global": "Globale",
|
||||
"camera": "Telecamera: {{cameraName}}"
|
||||
},
|
||||
"profile": {
|
||||
"label": "Profilo"
|
||||
},
|
||||
"field": {
|
||||
"label": "Campo"
|
||||
},
|
||||
"value": {
|
||||
"label": "Nuovo valore",
|
||||
"reset": "Reimposta"
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
@ -175,7 +175,7 @@
|
||||
"framesAndDetections": "Fotogrammi / Rilevamenti",
|
||||
"label": {
|
||||
"camera": "telecamera",
|
||||
"detect": "rilevamento",
|
||||
"detect": "rileva",
|
||||
"skipped": "saltati",
|
||||
"ffmpeg": "FFmpeg",
|
||||
"capture": "cattura",
|
||||
|
||||
@ -216,7 +216,8 @@
|
||||
"gl": "Galego (Galisisk)",
|
||||
"id": "Bahasa Indonesia (Indonesisk)",
|
||||
"ur": "اردو (Urdu)",
|
||||
"hr": "Hrvatski (Kroatisk)"
|
||||
"hr": "Hrvatski (Kroatisk)",
|
||||
"bs": "Bosanski (Bosnisk)"
|
||||
},
|
||||
"appearance": "Utseende",
|
||||
"darkMode": {
|
||||
@ -241,7 +242,8 @@
|
||||
"classification": "Klassifisering",
|
||||
"profiles": "Profiler",
|
||||
"chat": "Chat",
|
||||
"actions": "Handlinger"
|
||||
"actions": "Handlinger",
|
||||
"features": "Funksjoner"
|
||||
},
|
||||
"pagination": {
|
||||
"next": {
|
||||
|
||||
@ -52,7 +52,7 @@
|
||||
"description": "Innstillinger for å aktivere og kontrollere varslinger for dette kameraet."
|
||||
},
|
||||
"audio": {
|
||||
"label": "Lydhendelser",
|
||||
"label": "Lyddeteksjon",
|
||||
"enabled": {
|
||||
"label": "Aktiver lyddeteksjon",
|
||||
"description": "Aktiver eller deaktiver deteksjon av lydhendelser for dette kameraet."
|
||||
@ -71,7 +71,11 @@
|
||||
},
|
||||
"filters": {
|
||||
"label": "Lydfiltre",
|
||||
"description": "Filterinnstillinger per lydtype, som konfidensterskler for å redusere falske positive."
|
||||
"description": "Filterinnstillinger per lydtype, som konfidensterskler for å redusere falske positive.",
|
||||
"threshold": {
|
||||
"description": "Minimum konfidens-terskel for at lydhendelsen skal bli registrert.",
|
||||
"label": "Minimum konfidens for lyd"
|
||||
}
|
||||
},
|
||||
"enabled_in_config": {
|
||||
"label": "Opprinnelig lydstatus",
|
||||
@ -476,6 +480,10 @@
|
||||
"hwaccel_args": {
|
||||
"label": "Argumenter for maskinvareakselerasjon ved eksport",
|
||||
"description": "Argumenter for maskinvareakselerasjon som skal brukes ved eksport og transkoding."
|
||||
},
|
||||
"max_concurrent": {
|
||||
"description": "Maksimalt antall eksportjobber som kan behandles samtidig.",
|
||||
"label": "Maksimalt antall samtidige eksporter"
|
||||
}
|
||||
},
|
||||
"preview": {
|
||||
|
||||
@ -242,7 +242,7 @@
|
||||
"description": "Aktiver overvåking av nettverksbåndbredde per prosess for kamera-ffmpeg-prosesser og detektorer."
|
||||
},
|
||||
"intel_gpu_device": {
|
||||
"label": "SR-IOV-enhet",
|
||||
"label": "Intel GPU-enhet",
|
||||
"description": "Enhetsidentifikator som brukes når Intel-GPU-er behandles som SR-IOV for å korrigere GPU-statistikk."
|
||||
}
|
||||
},
|
||||
@ -539,7 +539,7 @@
|
||||
}
|
||||
},
|
||||
"audio": {
|
||||
"label": "Lydhendelser",
|
||||
"label": "Lyddeteksjon",
|
||||
"description": "Innstillinger for lydbasert hendelsesdeteksjon for alle kameraer; kan overstyres per kamera.",
|
||||
"enabled": {
|
||||
"label": "Aktiver lyddeteksjon",
|
||||
@ -559,7 +559,11 @@
|
||||
},
|
||||
"filters": {
|
||||
"label": "Lydfiltre",
|
||||
"description": "Filterinnstillinger per lydtype, som konfidensterskler for å redusere falske positive."
|
||||
"description": "Filterinnstillinger per lydtype, som konfidensterskler for å redusere falske positive.",
|
||||
"threshold": {
|
||||
"description": "Minimum konfidens-terskel for at lydhendelsen skal bli registrert.",
|
||||
"label": "Minimum konfidens for lyd"
|
||||
}
|
||||
},
|
||||
"enabled_in_config": {
|
||||
"label": "Opprinnelig lydstatus",
|
||||
@ -1000,6 +1004,10 @@
|
||||
"hwaccel_args": {
|
||||
"label": "Argumenter for maskinvareakselerasjon ved eksport",
|
||||
"description": "Argumenter for maskinvareakselerasjon som skal brukes ved eksport og transkoding."
|
||||
},
|
||||
"max_concurrent": {
|
||||
"description": "Maksimalt antall eksportjobber som kan behandles samtidig.",
|
||||
"label": "Maksimalt antall samtidige eksporter"
|
||||
}
|
||||
},
|
||||
"preview": {
|
||||
|
||||
@ -121,5 +121,10 @@
|
||||
"skunk": "Skunk",
|
||||
"school_bus": "Skolebuss",
|
||||
"royal_mail": "Royal Mail",
|
||||
"canada_post": "Canada Post"
|
||||
"canada_post": "Canada Post",
|
||||
"baby_stroller": "Barnevogn",
|
||||
"Rodent": "Gnager",
|
||||
"baby": "Baby",
|
||||
"rickshaw": "Rickshaw",
|
||||
"rodent": "Gnager"
|
||||
}
|
||||
|
||||
@ -1 +1,64 @@
|
||||
{}
|
||||
{
|
||||
"documentTitle": "Chat - Frigate",
|
||||
"title": "Frigate Chat",
|
||||
"subtitle": "Din AI-assistent for kamerahåndtering og innsikt",
|
||||
"placeholder": "Spør om hva som helst...",
|
||||
"error": "Noe gikk galt. Vennligst prøv igjen.",
|
||||
"processing": "Behandler...",
|
||||
"toolsUsed": "Brukt: {{tools}}",
|
||||
"showTools": "Vis verktøy ({{count}})",
|
||||
"hideTools": "Skjul verktøy",
|
||||
"call": "Kall",
|
||||
"result": "Resultat",
|
||||
"arguments": "Argumenter:",
|
||||
"response": "Svar:",
|
||||
"attachment_chip_label": "{{label}} på {{camera}}",
|
||||
"attachment_chip_remove": "Fjern vedlegg",
|
||||
"open_in_explore": "Åpne i Utforsk",
|
||||
"attach_event_aria": "Legg ved hendelse {{eventId}}",
|
||||
"attachment_picker_paste_label": "Eller lim inn hendelses-ID",
|
||||
"attachment_picker_attach": "Legg ved",
|
||||
"attachment_picker_placeholder": "Legg ved en hendelse",
|
||||
"quick_reply_find_similar": "Finn lignende observasjoner",
|
||||
"quick_reply_tell_me_more": "Fortell meg mer om dette",
|
||||
"quick_reply_when_else": "Når ellers ble det sett?",
|
||||
"quick_reply_find_similar_text": "Finn lignende observasjoner som denne.",
|
||||
"quick_reply_tell_me_more_text": "Fortell meg mer om denne.",
|
||||
"quick_reply_when_else_text": "Når ellers ble denne sett?",
|
||||
"anchor": "Referanse",
|
||||
"similarity_score": "Likhet",
|
||||
"no_similar_objects_found": "Ingen lignende objekter funnet.",
|
||||
"semantic_search_required": "Semantisk søk må være aktivert for å finne lignende objekter.",
|
||||
"send": "Send",
|
||||
"suggested_requests": "Prøv å spørre:",
|
||||
"starting_requests": {
|
||||
"show_recent_events": "Vis nylige hendelser",
|
||||
"show_camera_status": "Vis kamerastatus",
|
||||
"recap": "Hva skjedde mens jeg var borte?",
|
||||
"watch_camera": "Overvåk et kamera for aktivitet"
|
||||
},
|
||||
"starting_requests_prompts": {
|
||||
"show_recent_events": "Vis meg nylige hendelser fra den siste timen",
|
||||
"show_camera_status": "Hva er status for kameraene mine akkurat nå?",
|
||||
"recap": "Hva skjedde mens jeg var borte?",
|
||||
"watch_camera": "Overvåk inngangsdøren og gi meg beskjed hvis noen dukker opp"
|
||||
},
|
||||
"new_chat": "Ny chat",
|
||||
"settings": {
|
||||
"title": "Chat-innstillinger",
|
||||
"show_stats": {
|
||||
"title": "Vis statistikk",
|
||||
"desc": "Vis genereringshastighet og kontekststørrelse for chat-svar.",
|
||||
"while_generating": "Under generering",
|
||||
"always": "Alltid"
|
||||
},
|
||||
"auto_scroll": {
|
||||
"title": "Auto-rulling",
|
||||
"desc": "Følg nye meldinger etter hvert som de ankommer."
|
||||
}
|
||||
},
|
||||
"stats": {
|
||||
"context": "{{tokens}} tokens",
|
||||
"tokens_per_second": "{{rate}} t/s"
|
||||
}
|
||||
}
|
||||
|
||||
@ -27,7 +27,11 @@
|
||||
"aria": "Velg nylige gjenkjennelser",
|
||||
"title": "Nylige gjenkjennelser",
|
||||
"empty": "Det er ingen nylige forsøk på ansiktsgjenkjenning",
|
||||
"titleShort": "Nylige"
|
||||
"titleShort": "Nylige",
|
||||
"emptyNoLibrary": {
|
||||
"description": "Du må legge til minst ett ansikt i biblioteket for at ansiktsgjenkjenning skal fungere.",
|
||||
"title": "Last opp et ansikt"
|
||||
}
|
||||
},
|
||||
"selectFace": "Velg ansikt",
|
||||
"deleteFaceLibrary": {
|
||||
|
||||
@ -152,7 +152,8 @@
|
||||
},
|
||||
"recording": {
|
||||
"enable": "Aktiver opptak",
|
||||
"disable": "Deaktiver opptak"
|
||||
"disable": "Deaktiver opptak",
|
||||
"disabledInConfig": "Opptak må først aktiveres i Innstillinger for dette kameraet."
|
||||
},
|
||||
"streamStats": {
|
||||
"enable": "Vis Strømmestatistikk",
|
||||
|
||||
@ -1 +1,75 @@
|
||||
{}
|
||||
{
|
||||
"documentTitle": "Bevegelsessøk - Frigate",
|
||||
"title": "Bevegelsessøk",
|
||||
"description": "Tegn et polygon for å definere et interesseområde, og angi et tidsrom for å søke etter bevegelsesendringer i dette området.",
|
||||
"selectCamera": "Bevegelsessøk laster",
|
||||
"startSearch": "Start søk",
|
||||
"searchStarted": "Søk startet",
|
||||
"searchCancelled": "Søk avbrutt",
|
||||
"cancelSearch": "Avbryt",
|
||||
"searching": "Søk pågår...",
|
||||
"searchComplete": "Søk fullført",
|
||||
"noResultsYet": "Kjør et søk for å finne bevegelsesendringer i det valgte området",
|
||||
"noChangesFound": "Ingen pikselendringer ble funnet i det valgte området",
|
||||
"changesFound_one": "Fant {{count}} bevegelsesendring",
|
||||
"changesFound_other": "Fant {{count}} bevegelsesendringer",
|
||||
"framesProcessed": "{{count}} bilder behandlet",
|
||||
"jumpToTime": "Gå til dette tidspunktet",
|
||||
"results": "Resultater",
|
||||
"showSegmentHeatmap": "Varmekart",
|
||||
"newSearch": "Nytt søk",
|
||||
"clearResults": "Tøm resultater",
|
||||
"clearROI": "Slett polygon",
|
||||
"polygonControls": {
|
||||
"points_one": "{{count}} punkt",
|
||||
"points_other": "{{count}} punkter",
|
||||
"undo": "Angre siste punkt",
|
||||
"reset": "Tilbakestill polygon"
|
||||
},
|
||||
"motionHeatmapLabel": "Varmekart for bevegelse",
|
||||
"dialog": {
|
||||
"title": "Bevegelsessøk",
|
||||
"cameraLabel": "Kamera",
|
||||
"previewAlt": "Forhåndsvisning av kamera for {{camera}}"
|
||||
},
|
||||
"timeRange": {
|
||||
"title": "Søkeperiode",
|
||||
"start": "Starttid",
|
||||
"end": "Sluttid"
|
||||
},
|
||||
"settings": {
|
||||
"title": "Søkeinnstillinger",
|
||||
"parallelMode": "Parallellmodus",
|
||||
"parallelModeDesc": "Skann flere opptakssegmenter samtidig (raskere, men betydelig mer CPU-intensivt)",
|
||||
"threshold": "Følsomhetsterskel",
|
||||
"thresholdDesc": "Lavere verdier detekterer mindre endringer (1–255)",
|
||||
"minArea": "Minimum endringsområde",
|
||||
"minAreaDesc": "Minimum prosentandel av interesseområdet som må endres for å anses som betydelig",
|
||||
"frameSkip": "Bilde-sprang",
|
||||
"frameSkipDesc": "Behandle hvert N-te bilde. Sett denne til kameraets bildefrekvens for å behandle ett bilde i sekundet (f.eks. 5 for et 5 FPS-kamera, 30 for et 30 FPS-kamera). Høyere verdier vil være raskere, men kan gå glipp av korte bevegelseshendelser.",
|
||||
"maxResults": "Maksimalt antall resultater",
|
||||
"maxResultsDesc": "Stopp etter dette antallet samsvarende tidsstempler"
|
||||
},
|
||||
"errors": {
|
||||
"noCamera": "Vennligst velg et kamera",
|
||||
"noROI": "Vennligst tegn et interesseområde",
|
||||
"noTimeRange": "Vennligst velg en tidsperiode",
|
||||
"invalidTimeRange": "Sluttid må være etter starttid",
|
||||
"searchFailed": "Søk mislyktes: {{message}}",
|
||||
"polygonTooSmall": "Polygonet må ha minst 3 punkter",
|
||||
"unknown": "Ukjent feil"
|
||||
},
|
||||
"changePercentage": "{{percentage}} % endret",
|
||||
"metrics": {
|
||||
"title": "Statistikk for søk",
|
||||
"segmentsScanned": "Segmenter skannet",
|
||||
"segmentsProcessed": "Behandlet",
|
||||
"segmentsSkippedInactive": "Hoppet over (ingen aktivitet)",
|
||||
"segmentsSkippedHeatmap": "Hoppet over (manglende ROI-overlapp)",
|
||||
"fallbackFullRange": "Fullskanning som reserve",
|
||||
"framesDecoded": "Bilder dekodet",
|
||||
"wallTime": "Søketid",
|
||||
"segmentErrors": "Segmentfeil",
|
||||
"seconds": "{{seconds}}s"
|
||||
}
|
||||
}
|
||||
|
||||
@ -1 +1,59 @@
|
||||
{}
|
||||
{
|
||||
"title": "Feilsøkingsavspilling",
|
||||
"description": "Spill av kameraopptak for feilsøking. Objektlisten viser et tidsforsinket sammendrag av detekterte objekter, og fanen Meldinger viser en strøm av Frigates interne meldinger fra avspillingen.",
|
||||
"websocket_messages": "Meldinger",
|
||||
"dialog": {
|
||||
"title": "Start feilsøkingsavspilling",
|
||||
"description": "Opprett et midlertidig avspillingskamera som repeterer historisk materiale for å feilsøke problemer med objektdeteksjon og sporing. Avspillingskameraet vil ha samme deteksjonskonfigurasjon som kildekameraet. Velg et tidsrom for å begynne.",
|
||||
"camera": "Kildekamera",
|
||||
"timeRange": "Tidsrom",
|
||||
"preset": {
|
||||
"1m": "Siste minutt",
|
||||
"5m": "Siste 5 minutter",
|
||||
"timeline": "Fra tidslinje",
|
||||
"custom": "Egendefinert"
|
||||
},
|
||||
"startButton": "Start avspilling",
|
||||
"selectFromTimeline": "Velg",
|
||||
"starting": "Starter avspilling...",
|
||||
"startLabel": "Start",
|
||||
"endLabel": "Slutt",
|
||||
"toast": {
|
||||
"error": "Kunne ikke starte feilsøkingsavspilling: {{error}}",
|
||||
"alreadyActive": "En avspillingsøkt er allerede aktiv",
|
||||
"stopError": "Kunne ikke stoppe feilsøkingsavspilling: {{error}}",
|
||||
"goToReplay": "Gå til avspilling"
|
||||
}
|
||||
},
|
||||
"page": {
|
||||
"noSession": "Ingen aktiv feilsøkingsøkt",
|
||||
"noSessionDesc": "Start en feilsøkingsavspilling fra Historikk-visningen ved å klikke på Handlinger-knappen i verktøylinjen og velge Feilsøkingsavspilling.",
|
||||
"goToRecordings": "Gå til historikk",
|
||||
"preparingClip": "Forbereder klipp…",
|
||||
"preparingClipDesc": "Frigate syr sammen opptak for det valgte tidsrommet. Dette kan ta et minutt for lengre perioder.",
|
||||
"startingCamera": "Starter feilsøkingsavspilling…",
|
||||
"startError": {
|
||||
"title": "Kunne ikke starte feilsøkingsavspilling",
|
||||
"back": "Tilbake til historikk"
|
||||
},
|
||||
"sourceCamera": "Kildekamera",
|
||||
"replayCamera": "Avspillingskamera",
|
||||
"initializingReplay": "Initialiserer feilsøkingsavspilling...",
|
||||
"stoppingReplay": "Stopper feilsøkingsavspilling...",
|
||||
"stopReplay": "Stopp avspilling",
|
||||
"confirmStop": {
|
||||
"title": "Stoppe feilsøkingsavspilling?",
|
||||
"description": "Dette vil stoppe økten og slette alle midlertidige data. Er du sikker?",
|
||||
"confirm": "Stopp avspilling",
|
||||
"cancel": "Avbryt"
|
||||
},
|
||||
"activity": "Aktivitet",
|
||||
"objects": "Objektliste",
|
||||
"audioDetections": "Lyd-deteksjoner",
|
||||
"noActivity": "Ingen aktivitet detektert",
|
||||
"activeTracking": "Aktiv sporing",
|
||||
"noActiveTracking": "Ingen aktiv sporing",
|
||||
"configuration": "Konfigurasjon",
|
||||
"configurationDesc": "Finjuster innstillinger for bevegelsesdeteksjon og objektsporing for feilsøkingskameraet. Ingen endringer lagres i din Frigate-konfigurasjonsfil."
|
||||
}
|
||||
}
|
||||
|
||||
@ -61,8 +61,8 @@
|
||||
"cameraLpr": "Kjennemerke-gjenkjenning",
|
||||
"integrationLpr": "Kjennemerke-gjenkjenning",
|
||||
"systemLogging": "Logging",
|
||||
"cameraAudioEvents": "Lydhendelser",
|
||||
"globalAudioEvents": "Lydhendelser",
|
||||
"cameraAudioEvents": "Lyd-deteksjon",
|
||||
"globalAudioEvents": "Lyd-deteksjon",
|
||||
"cameraAudioTranscription": "Lydtranskripsjon",
|
||||
"integrationAudioTranscription": "Lydtranskripsjon",
|
||||
"systemDetectorHardware": "Maskinvare for detektor",
|
||||
@ -791,6 +791,14 @@
|
||||
"plusModelType": {
|
||||
"userModel": "Finjustert",
|
||||
"baseModel": "Basismodell"
|
||||
},
|
||||
"noModelLoaded": "Ingen Frigate+-modell er lastet inn for øyeblikket.",
|
||||
"selectModel": "Velg en modell",
|
||||
"noModelsAvailable": "Ingen modeller tilgjengelig",
|
||||
"filter": {
|
||||
"ariaLabel": "Filtrer modeller etter type",
|
||||
"baseModels": "Basismodeller",
|
||||
"fineTunedModels": "Finjusterte modeller"
|
||||
}
|
||||
},
|
||||
"title": "Frigate+ Innstillinger",
|
||||
@ -1341,7 +1349,8 @@
|
||||
},
|
||||
"hikvision": {
|
||||
"substreamWarning": "Substrøm 1 er låst til lav oppløsning. Mange Hikvision-kameraer støtter flere substrømmer som må aktiveres i kameraets innstillinger. Det anbefales å sjekke og benytte disse strømmene hvis de er tilgjengelige."
|
||||
}
|
||||
},
|
||||
"resolutionUnknown": "Oppløsningen for denne strømmen kunne ikke fastslås. Du bør angi deteksjonsoppløsningen manuelt i innstillingene eller i konfigurasjonen din."
|
||||
}
|
||||
}
|
||||
},
|
||||
@ -1358,7 +1367,13 @@
|
||||
"enableSuccess": "Aktiverte {{cameraName}} i konfigurasjonen. Start Frigate på nytt for å ta i bruk endringene.",
|
||||
"enableLabel": "Aktiverte kameraer",
|
||||
"enableDesc": "Deaktiver et aktivert kamera midlertidig frem til Frigate starter på nytt. Deaktivering av et kamera stopper all prosessering av kameraets strømmer. Deteksjon, opptak og feilsøking vil være utilgjengelig.<br /> <em>Merk: Dette deaktiverer ikke videreformidling (restream) i go2rtc.</em>",
|
||||
"disableLabel": "Deaktiverte kameraer"
|
||||
"disableLabel": "Deaktiverte kameraer",
|
||||
"friendlyName": {
|
||||
"edit": "Rediger visningsnavn for kamera",
|
||||
"title": "Rediger visningsnavn",
|
||||
"description": "Angi visningsnavnet som skal brukes for dette kameraet i Frigate-grensesnittet. La feltet stå tomt for å bruke kamera-ID.",
|
||||
"rename": "Omdøp"
|
||||
}
|
||||
},
|
||||
"cameraConfig": {
|
||||
"add": "Legg til kamera",
|
||||
@ -1408,7 +1423,16 @@
|
||||
"description": "Sletting av et kamera vil fjerne alle opptak, sporede objekter og konfigurasjon for dette kameraet permanent. Eventuelle go2rtc-strømmer tilknyttet kameraet må eventuelt fjernes manuelt.",
|
||||
"selectPlaceholder": "Velg kamera..."
|
||||
},
|
||||
"deleteCamera": "Slett kamera"
|
||||
"deleteCamera": "Slett kamera",
|
||||
"cameraType": {
|
||||
"title": "Kameratype",
|
||||
"label": "Kameratype",
|
||||
"description": "Angi type for hvert kamera. Dedikerte LPR-kameraer er spesialkameraer med kraftig optisk zoom for å fange opp kjennemerker på kjøretøy langt unna. De fleste kameraer bør bruke typen \"Normal\", med mindre kameraet er spesifikt for gjenkjenning av kjennemerker og har et snevert fokus på kjennemerker.",
|
||||
"normal": "Normal",
|
||||
"dedicatedLpr": "Dedikert LPR (lesing av kjennemerker)",
|
||||
"saveSuccess": "Kameratype oppdatert for {{cameraName}}. Start Frigate på nytt for å bruke endringene."
|
||||
},
|
||||
"description": "Legg til, rediger og slett kameraer, kontroller hvilke kameraer som er aktivert, og konfigurer overstyringer for hver profil og kameratype. For å konfigurere strømmer, deteksjon, bevegelse og andre kameraspesifikke innstillinger, velg den aktuelle seksjonen under Kamerakonfigurasjon."
|
||||
},
|
||||
"cameraReview": {
|
||||
"title": "Innstillinger for kamerainspeksjon",
|
||||
@ -1572,7 +1596,9 @@
|
||||
"options": {
|
||||
"embeddings": "Vektorrepresentasjoner",
|
||||
"tools": "Verktøy",
|
||||
"vision": "Bildegjenkjenning"
|
||||
"vision": "Bildegjenkjenning",
|
||||
"descriptions": "Beskrivelser",
|
||||
"chat": "Chat"
|
||||
}
|
||||
},
|
||||
"additionalProperties": {
|
||||
@ -1645,7 +1671,24 @@
|
||||
"overriddenBaseConfigTooltip": "{{profile}}-profilen overstyrer konfigurasjonsinnstillinger i denne seksjonen",
|
||||
"overriddenGlobalTooltip": "Dette kameraet overstyrer globale konfigurasjonsinnstillinger i denne seksjonen",
|
||||
"overriddenBaseConfig": "Overstyrt (Basiskonfigurasjon)",
|
||||
"overriddenGlobal": "Overstyrt (Global)"
|
||||
"overriddenGlobal": "Overstyrt (Global)",
|
||||
"overriddenInCameras": {
|
||||
"label_one": "Overstyrt i {{count}} kamera",
|
||||
"label_other": "Overstyrt i {{count}} kameraer",
|
||||
"tooltip_one": "{{count}} kamera overstyrer verdier i denne seksjonen. Klikk for å se detaljer.",
|
||||
"tooltip_other": "{{count}} kameraer overstyrer verdier i denne seksjonen. Klikk for å se detaljer.",
|
||||
"heading_one": "Denne globale seksjonen har felt som er overstyrt i {{count}} kamera.",
|
||||
"heading_other": "Denne globale seksjonen har felt som er overstyrt i {{count}} kameraer.",
|
||||
"othersField_one": "{{count}} annen",
|
||||
"othersField_other": "{{count}} andre",
|
||||
"profilePrefix": "{{profile}}-profil: {{fields}}"
|
||||
},
|
||||
"overriddenGlobalHeading_one": "Dette kameraet overstyrer {{count}} felt fra den globale konfigurasjonen:",
|
||||
"overriddenGlobalHeading_other": "Dette kameraet overstyrer {{count}} felt fra den globale konfigurasjonen:",
|
||||
"overriddenGlobalNoDeltas": "Dette kameraet overstyrer den globale konfigurasjonen, men ingen feltverdier er forskjellige.",
|
||||
"overriddenBaseConfigHeading_one": "{{profile}}-profilen overstyrer {{count}} felt fra basiskonfigurasjonen:",
|
||||
"overriddenBaseConfigHeading_other": "{{profile}}-profilen overstyrer {{count}} felt fra basiskonfigurasjonen:",
|
||||
"overriddenBaseConfigNoDeltas": "{{profile}}-profilen overstyrer denne seksjonen, men ingen feltverdier er forskjellige fra basiskonfigurasjonen."
|
||||
},
|
||||
"detectionModel": {
|
||||
"plusActive": {
|
||||
@ -1744,18 +1787,21 @@
|
||||
"review": {
|
||||
"allNonAlertDetections": "All aktivitet som ikke er et varsel, vil bli inkludert som deteksjoner.",
|
||||
"detectDisabled": "Objektdeteksjon er deaktivert. Inspeksjonselementer krever detekterte objekter for å kategorisere varsler og deteksjoner.",
|
||||
"recordDisabled": "Opptak er deaktivert, inspeksjonselementer vil ikke bli generert."
|
||||
"recordDisabled": "Opptak er deaktivert, inspeksjonselementer vil ikke bli generert.",
|
||||
"genaiImageSourceRecordingsRecordDisabled": "Bildekilde er satt til \"opptak\", men opptak er deaktivert. Frigate vil falle tilbake til forhåndsvisningsbilder."
|
||||
},
|
||||
"detectors": {
|
||||
"mixedTypesSuggestion": "Alle detektorer må bruke samme type. Fjern eksisterende detektorer eller velg {{type}}.",
|
||||
"mixedTypes": "Alle detektorer må bruke samme type. Fjern eksisterende detektorer for å bruke en annen type."
|
||||
},
|
||||
"faceRecognition": {
|
||||
"globalDisabled": "Ansiktsgjenkjenning er ikke aktivert på globalt nivå. Aktiver det i globale innstillinger for at ansiktsgjenkjenning på kameranivå skal fungere.",
|
||||
"personNotTracked": "Ansiktsgjenkjenning krever at objektet 'person' spores. Sørg for at 'person' er i listen over objektsporing."
|
||||
"globalDisabled": "Utvidelse for ansiktsgjenkjenning må være aktivert for at funksjoner for ansiktsgjenkjenning skal fungere på dette kameraet.",
|
||||
"personNotTracked": "Ansiktsgjenkjenning krever at objektet 'person' spores. Aktiver 'person' under Objekter for dette kameraet.",
|
||||
"modelSizeLarge": "Den store (large) modellen krever GPU eller NPU for akseptabel ytelse. Bruk liten (small) på systemer med kun CPU."
|
||||
},
|
||||
"detect": {
|
||||
"fpsGreaterThanFive": "Det anbefales ikke å sette FPS for deteksjon høyere enn 5."
|
||||
"fpsGreaterThanFive": "Det anbefales ikke å sette FPS for deteksjon høyere enn 5. Høyere verdier kan føre til ytelsesproblemer uten å gi noen fordeler.",
|
||||
"disabled": "Objektdeteksjon er deaktivert. Stillbilder, inspeksjonselementer og utvidelser som ansiktsgjenkjenning, lesing av kjennemerker og generativ AI vil ikke fungere."
|
||||
},
|
||||
"birdseye": {
|
||||
"objectsModeDetectDisabled": "Fugleperspektiv er satt til 'objekter'-modus, men objektdeteksjon er deaktivert for dette kameraet. Kameraet vil ikke vises i Fugleperspektiv."
|
||||
@ -1773,8 +1819,15 @@
|
||||
"detectDisabled": "Objektdeteksjon er deaktivert. Stillbilder genereres fra sporede objekter og vil ikke bli opprettet."
|
||||
},
|
||||
"lpr": {
|
||||
"globalDisabled": "Identifisering av kjennemerker er ikke aktivert på globalt nivå. Aktiver det i globale innstillinger for at identifisering på kameranivå skal fungere.",
|
||||
"vehicleNotTracked": "Identifisering av kjnnemerker krever at 'bil' eller 'motorsykkel' spores."
|
||||
"globalDisabled": "Utvidelse for identifisering av kjennemerker må være aktivert for at LPR-funksjoner skal fungere på dette kameraet.",
|
||||
"vehicleNotTracked": "Identifisering av kjennemerker krever at 'bil' eller 'motorsykkel' spores. Aktiver 'bil' eller 'motorsykkel' under Objekter for dette kameraet.",
|
||||
"modelSizeLarge": "Den store (large) modellen er optimalisert for kjennemerker over flere linjer. Den lille (small) modellen gir bedre ytelse og bør brukes med mindre din region bruker skiltformater med flere linjer."
|
||||
},
|
||||
"objects": {
|
||||
"genaiNoDescriptionsProvider": "Du må konfigurere en GenAI-leverandør med rollen \"beskrivelser\" for at beskrivelser skal kunne genereres."
|
||||
},
|
||||
"semanticSearch": {
|
||||
"jinav2SmallModelSize": "Størrelsen \"liten\" med Jina V2-modellen har høyt minnebruk og beregningskostnad. Den \"store\" modellen med en dedikert GPU anbefales."
|
||||
}
|
||||
},
|
||||
"maintenance": {
|
||||
@ -1839,7 +1892,14 @@
|
||||
},
|
||||
"onvif": {
|
||||
"profileAuto": "Auto",
|
||||
"profileLoading": "Laster profiler..."
|
||||
"profileLoading": "Laster profiler...",
|
||||
"autotracking": {
|
||||
"zooming": {
|
||||
"disabled": "Deaktivert",
|
||||
"absolute": "Absolutt",
|
||||
"relative": "Relativ"
|
||||
}
|
||||
}
|
||||
},
|
||||
"confirmReset": "Bekreft nullstilling",
|
||||
"resetToDefaultDescription": "Dette vil nullstille alle innstillinger i denne seksjonen til standardverdiene. Denne handlingen kan ikke angres.",
|
||||
@ -1903,5 +1963,67 @@
|
||||
"bl": "Nederst til venstre",
|
||||
"tr": "Øverst til høyre",
|
||||
"tl": "Øverst til venstre"
|
||||
},
|
||||
"birdseye": {
|
||||
"trackingMode": {
|
||||
"objects": "Objekter",
|
||||
"motion": "Bevegelse",
|
||||
"continuous": "Kontinuerlig"
|
||||
}
|
||||
},
|
||||
"snapshot": {
|
||||
"retainMode": {
|
||||
"all": "Alle",
|
||||
"motion": "Bevegelse",
|
||||
"active_objects": "Aktive objekter"
|
||||
}
|
||||
},
|
||||
"ui": {
|
||||
"timeFormat": {
|
||||
"browser": "Nettleser",
|
||||
"12hour": "12 timer",
|
||||
"24hour": "24 timer"
|
||||
},
|
||||
"TimeOrDateStyle": {
|
||||
"full": "Full",
|
||||
"long": "Lang",
|
||||
"medium": "Middels",
|
||||
"short": "Kort"
|
||||
},
|
||||
"unitSystem": {
|
||||
"metric": "Metrisk",
|
||||
"imperial": "Imperial"
|
||||
}
|
||||
},
|
||||
"review": {
|
||||
"imageSource": {
|
||||
"recordings": "Opptak",
|
||||
"previews": "Forhåndsvisninger"
|
||||
}
|
||||
},
|
||||
"logger": {
|
||||
"logLevel": {
|
||||
"debug": "Debug",
|
||||
"info": "Info",
|
||||
"warning": "Advarsel",
|
||||
"error": "Feil",
|
||||
"critical": "Kritisk"
|
||||
}
|
||||
},
|
||||
"modelSize": {
|
||||
"small": "Liten",
|
||||
"large": "Stor"
|
||||
},
|
||||
"retainMode": {
|
||||
"all": "Alle",
|
||||
"motion": "Bevegelse",
|
||||
"active_objects": "Aktive objekter"
|
||||
},
|
||||
"previewQuality": {
|
||||
"very_high": "Svært høy",
|
||||
"high": "Høy",
|
||||
"medium": "Middels",
|
||||
"low": "Lav",
|
||||
"very_low": "Svært lav"
|
||||
}
|
||||
}
|
||||
|
||||
@ -210,6 +210,9 @@
|
||||
"expectedFps": "Forventet BPS",
|
||||
"reconnectsLastHour": "Gjentatte tilkoblinger (siste time)",
|
||||
"stallsLastHour": "Avbrudd (siste time)"
|
||||
},
|
||||
"noCameras": {
|
||||
"title": "Ingen kameraer funnet"
|
||||
}
|
||||
},
|
||||
"enrichments": {
|
||||
|
||||
@ -6,5 +6,16 @@
|
||||
"bellow": "तलतिर",
|
||||
"motorcycle": "मोटरसाइकल",
|
||||
"whoop": "हुप (Whoop)",
|
||||
"whispering": "सानो बोल्दै"
|
||||
"whispering": "सानो बोल्दै",
|
||||
"babbling": "बडबडाउँदै",
|
||||
"bus": "बस",
|
||||
"laughter": "हाँसो",
|
||||
"train": "रेल",
|
||||
"snicker": "स्निकर",
|
||||
"boat": "डुङ्गा",
|
||||
"crying": "रुँदै",
|
||||
"singing": "गाउँदै",
|
||||
"choir": "गायन यन्त्र",
|
||||
"yodeling": "योडेलिङ",
|
||||
"chant": "मन्त्र"
|
||||
}
|
||||
|
||||
@ -3,6 +3,16 @@
|
||||
"untilForRestart": "फ्रिगेट पुनः सुरु नभएसम्म।",
|
||||
"untilRestart": "पुन: सुरु नभएसम्म",
|
||||
"never": "कहिल्यै होइन",
|
||||
"ago": "{{timeAgo}} अघि"
|
||||
"ago": "{{timeAgo}} अघि",
|
||||
"untilForTime": "{{time}} सम्म",
|
||||
"justNow": "भर्खरै",
|
||||
"today": "आज",
|
||||
"yesterday": "हिजो",
|
||||
"last7": "पछिल्लो ७ दिन",
|
||||
"last14": "पछिल्लो १४ दिन",
|
||||
"last30": "पछिल्लो ३० दिन",
|
||||
"thisWeek": "यो हप्ता",
|
||||
"lastWeek": "गत हप्ता",
|
||||
"thisMonth": "यो महिना"
|
||||
}
|
||||
}
|
||||
|
||||
@ -5,7 +5,12 @@
|
||||
"login": "लगइन",
|
||||
"firstTimeLogin": "पहिलो पटक लग इन गर्ने प्रयास गर्दै हुनुहुन्छ? प्रमाणपत्रहरू फ्रिगेट लगहरूमा छापिएका हुन्छन्।",
|
||||
"errors": {
|
||||
"usernameRequired": "प्रयोगकर्ता नाम आवश्यक छ"
|
||||
"usernameRequired": "प्रयोगकर्ता नाम आवश्यक छ",
|
||||
"passwordRequired": "पासवर्ड आवश्यक छ",
|
||||
"rateLimit": "दर सीमा नाघ्यो। पछि फेरि प्रयास गर्नुहोस्।",
|
||||
"loginFailed": "लगइन असफल भयो",
|
||||
"unknownError": "अज्ञात त्रुटि। लगहरू जाँच गर्नुहोस्",
|
||||
"webUnknownError": "अज्ञात त्रुटि। कन्सोल लगहरू जाँच गर्नुहोस्।"
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
@ -6,8 +6,23 @@
|
||||
"delete": {
|
||||
"label": "क्यामेरा समूह मेटाउनुहोस्",
|
||||
"confirm": {
|
||||
"title": "मेटाउने पुष्टि गर्नुहोस्"
|
||||
"title": "मेटाउने पुष्टि गर्नुहोस्",
|
||||
"desc": "के तपाईं क्यामेरा समूह <em>{{name}}</em> मेटाउन निश्चित हुनुहुन्छ?"
|
||||
}
|
||||
},
|
||||
"name": {
|
||||
"label": "नाम",
|
||||
"placeholder": "नाम प्रविष्ट गर्नुहोस्…",
|
||||
"errorMessage": {
|
||||
"mustLeastCharacters": "क्यामेरा समूहको नाम कम्तिमा २ वर्णको हुनुपर्छ।",
|
||||
"exists": "क्यामेरा समूहको नाम पहिले नै अवस्थित छ।",
|
||||
"nameMustNotPeriod": "क्यामेरा समूहको नाममा पूर्णविराम हुनुहुँदैन।",
|
||||
"invalid": "क्यामेरा समूहको नाम अमान्य छ।"
|
||||
}
|
||||
},
|
||||
"cameras": {
|
||||
"label": "क्यामेराहरू",
|
||||
"desc": "यस समूहको लागि क्यामेराहरू चयन गर्नुहोस्।"
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
@ -5,7 +5,30 @@
|
||||
"button": "पुनः सुरु",
|
||||
"restarting": {
|
||||
"title": "फ्रिगेट पुन: सुरु हुँदैछ",
|
||||
"content": "यो पृष्ठ {{countdown}} सेकेन्डमा पुन: लोड हुनेछ।"
|
||||
"content": "यो पृष्ठ {{countdown}} सेकेन्डमा पुन: लोड हुनेछ।",
|
||||
"button": "अहिले नै जबरजस्ती पुन: लोड गर्नुहोस्"
|
||||
}
|
||||
},
|
||||
"explore": {
|
||||
"plus": {
|
||||
"submitToPlus": {
|
||||
"label": "फ्रिगेट+ मा पेश गर्नुहोस्",
|
||||
"desc": "तपाईंले बेवास्ता गर्न चाहनुभएको स्थानहरूमा रहेका वस्तुहरू गलत सकारात्मक होइनन्। तिनीहरूलाई गलत सकारात्मकको रूपमा पेश गर्नाले मोडेल भ्रमित हुनेछ।"
|
||||
},
|
||||
"review": {
|
||||
"question": {
|
||||
"label": "फ्रिगेट प्लसको लागि यो लेबल पुष्टि गर्नुहोस्",
|
||||
"ask_a": "के यो वस्तु <code>{{label}}</code> हो?",
|
||||
"ask_an": "के यो वस्तु <code>{{label}}</code> हो?",
|
||||
"ask_full": "के यो वस्तु <code>{{untranslatedLabel}}</code> ({{translatedLabel}}) हो?"
|
||||
},
|
||||
"state": {
|
||||
"submitted": "पेश गरियो"
|
||||
}
|
||||
}
|
||||
},
|
||||
"video": {
|
||||
"viewInHistory": "इतिहासमा हेर्नुहोस्"
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
@ -7,5 +7,24 @@
|
||||
},
|
||||
"count_one": "{{count}} कक्षा",
|
||||
"count_other": "{{count}} कक्षाहरू"
|
||||
},
|
||||
"labels": {
|
||||
"label": "लेबलहरू",
|
||||
"all": {
|
||||
"title": "सबै लेबलहरू",
|
||||
"short": "लेबलहरू"
|
||||
},
|
||||
"count_one": "{{count}} लेबल",
|
||||
"count_other": "{{count}} लेबलहरू"
|
||||
},
|
||||
"zones": {
|
||||
"label": "क्षेत्रहरू",
|
||||
"all": {
|
||||
"title": "सबै क्षेत्रहरू",
|
||||
"short": "क्षेत्रहरू"
|
||||
}
|
||||
},
|
||||
"dates": {
|
||||
"selectPreset": "प्रिसेट चयन गर्नुहोस्…"
|
||||
}
|
||||
}
|
||||
|
||||
@ -5,5 +5,22 @@
|
||||
"title": "यो फ्रेम Frigate+ मा बुझाउने हो?",
|
||||
"submit": "पेश गर्नुहोस्",
|
||||
"previewError": "स्न्यापसट पूर्वावलोकन लोड गर्न सकिएन। रेकर्डिङ यस समयमा उपलब्ध नहुन सक्छ।"
|
||||
},
|
||||
"noRecordingsFoundForThisTime": "यस समयको लागि कुनै रेकर्डिङ फेला परेन",
|
||||
"livePlayerRequiredIOSVersion": "यस लाइभ स्ट्रिम प्रकारको लागि iOS १७.१ वा सोभन्दा माथिको संस्करण आवश्यक छ।",
|
||||
"streamOffline": {
|
||||
"title": "अफलाइन स्ट्रिम गर्नुहोस्",
|
||||
"desc": "{{cameraName}} <code>detect</code> स्ट्रिममा कुनै पनि फ्रेमहरू प्राप्त भएका छैनन्, त्रुटि लगहरू जाँच गर्नुहोस्"
|
||||
},
|
||||
"cameraDisabled": "क्यामेरा असक्षम पारिएको छ",
|
||||
"stats": {
|
||||
"streamType": {
|
||||
"title": "स्ट्रिम प्रकार:",
|
||||
"short": "प्रकार"
|
||||
},
|
||||
"bandwidth": {
|
||||
"title": "ब्यान्डविथ:",
|
||||
"short": "ब्यान्डविथ"
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
@ -7,5 +7,27 @@
|
||||
"friendly_name": {
|
||||
"label": "मैत्रीपूर्ण नाम",
|
||||
"description": "फ्रिगेट UI मा प्रयोग गरिएको क्यामेरा मैत्री नाम"
|
||||
},
|
||||
"enabled": {
|
||||
"label": "सक्षम पारिएको",
|
||||
"description": "सक्षम पारिएको"
|
||||
},
|
||||
"audio": {
|
||||
"label": "अडियो पत्ता लगाउने सुविधा",
|
||||
"description": "यस क्यामेराको लागि अडियो-आधारित घटना पत्ता लगाउने सेटिङहरू।",
|
||||
"enabled": {
|
||||
"label": "अडियो पत्ता लगाउने सुविधा सक्षम पार्नुहोस्",
|
||||
"description": "यस क्यामेराको लागि अडियो घटना पत्ता लगाउने सुविधा सक्षम वा असक्षम पार्नुहोस्।"
|
||||
},
|
||||
"max_not_heard": {
|
||||
"label": "समयसीमा समाप्त गर्नुहोस्",
|
||||
"description": "अडियो घटना समाप्त हुनुभन्दा पहिले कन्फिगर गरिएको अडियो प्रकार बिना सेकेन्डको मात्रा।"
|
||||
},
|
||||
"min_volume": {
|
||||
"label": "न्यूनतम भोल्युम"
|
||||
}
|
||||
},
|
||||
"zones": {
|
||||
"label": "क्षेत्रहरू"
|
||||
}
|
||||
}
|
||||
|
||||
@ -1 +1,42 @@
|
||||
{}
|
||||
{
|
||||
"version": {
|
||||
"label": "हालको कन्फिगरेसन संस्करण",
|
||||
"description": "माइग्रेसन वा ढाँचा परिवर्तनहरू पत्ता लगाउन मद्दत गर्न सक्रिय कन्फिगरेसनको संख्यात्मक वा स्ट्रिङ संस्करण।"
|
||||
},
|
||||
"safe_mode": {
|
||||
"label": "सुरक्षित मोड",
|
||||
"description": "सक्षम हुँदा, समस्या निवारणको लागि कम सुविधाहरूको साथ सुरक्षित मोडमा फ्रिगेट सुरु गर्नुहोस्।"
|
||||
},
|
||||
"environment_vars": {
|
||||
"label": "वातावरणीय चरहरू",
|
||||
"description": "होम असिस्टेन्ट ओएसमा फ्रिगेट प्रक्रियाको लागि सेट गर्नुपर्ने वातावरण चरहरूको कुञ्जी/मान जोडीहरू। गैर-HAOS प्रयोगकर्ताहरूले यसको सट्टा डकर वातावरण चर कन्फिगरेसन प्रयोग गर्नुपर्छ।"
|
||||
},
|
||||
"logger": {
|
||||
"label": "लगिङ",
|
||||
"description": "पूर्वनिर्धारित लग शब्दावली र प्रति-घटक लग स्तर ओभरराइडहरू नियन्त्रण गर्दछ।",
|
||||
"default": {
|
||||
"label": "लगिङ स्तर",
|
||||
"description": "पूर्वनिर्धारित विश्वव्यापी लग शब्दावली (डिबग, जानकारी, चेतावनी, त्रुटि)।"
|
||||
},
|
||||
"logs": {
|
||||
"label": "प्रति-प्रक्रिया लग स्तर",
|
||||
"description": "विशिष्ट मोड्युलहरूको लागि शब्दावली बढाउन वा घटाउन प्रति-घटक लग स्तर ओभरराइड हुन्छ।"
|
||||
}
|
||||
},
|
||||
"audio": {
|
||||
"label": "अडियो पत्ता लगाउने सुविधा",
|
||||
"enabled": {
|
||||
"label": "अडियो पत्ता लगाउने सुविधा सक्षम पार्नुहोस्"
|
||||
},
|
||||
"max_not_heard": {
|
||||
"label": "समयसीमा समाप्त गर्नुहोस्",
|
||||
"description": "अडियो घटना समाप्त हुनुभन्दा पहिले कन्फिगर गरिएको अडियो प्रकार बिना सेकेन्डको मात्रा।"
|
||||
},
|
||||
"min_volume": {
|
||||
"label": "न्यूनतम भोल्युम"
|
||||
}
|
||||
},
|
||||
"auth": {
|
||||
"label": "प्रमाणीकरण"
|
||||
}
|
||||
}
|
||||
|
||||
@ -12,6 +12,33 @@
|
||||
"timestamp_style": {
|
||||
"global": {
|
||||
"appearance": "विश्वव्यापी उपस्थिति"
|
||||
},
|
||||
"cameras": {
|
||||
"appearance": "उपस्थिति"
|
||||
}
|
||||
},
|
||||
"motion": {
|
||||
"global": {
|
||||
"sensitivity": "विश्वव्यापी संवेदनशीलता",
|
||||
"algorithm": "विश्वव्यापी एल्गोरिथम"
|
||||
},
|
||||
"cameras": {
|
||||
"sensitivity": "संवेदनशीलता",
|
||||
"algorithm": "एल्गोरिथ्म"
|
||||
}
|
||||
},
|
||||
"snapshots": {
|
||||
"global": {
|
||||
"display": "विश्वव्यापी प्रदर्शन"
|
||||
},
|
||||
"cameras": {
|
||||
"display": "प्रदर्शन"
|
||||
}
|
||||
},
|
||||
"detect": {
|
||||
"global": {
|
||||
"resolution": "विश्वव्यापी रिजोल्युसन",
|
||||
"tracking": "विश्वव्यापी ट्र्याकिङ"
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
@ -3,5 +3,14 @@
|
||||
"maximum": "बढीमा हुनुपर्छ {{limit}}",
|
||||
"exclusiveMinimum": "{{limit}} भन्दा बढी हुनुपर्छ",
|
||||
"exclusiveMaximum": ".{{limit}} भन्दा कम हुनुपर्छ",
|
||||
"minLength": "कम्तिमा {{limit}} वर्ण(हरू) हुनुपर्छ।"
|
||||
"minLength": "कम्तिमा {{limit}} वर्ण(हरू) हुनुपर्छ।",
|
||||
"maxLength": "बढीमा {{limit}} वर्ण(हरू) हुनु पर्छ",
|
||||
"minItems": "कम्तिमा {{limit}} वस्तुहरू हुनुपर्छ",
|
||||
"maxItems": "बढीमा {{limit}} वस्तुहरू हुनुपर्छ",
|
||||
"pattern": "अमान्य ढाँचा",
|
||||
"required": "यो क्षेत्र आवश्यक छ",
|
||||
"type": "अमान्य मान प्रकार",
|
||||
"enum": "अनुमति दिइएको मानहरू मध्ये एक हुनुपर्छ",
|
||||
"const": "मान अपेक्षित स्थिरांकसँग मेल खाँदैन",
|
||||
"uniqueItems": "सबै वस्तुहरू अद्वितीय हुनुपर्छ"
|
||||
}
|
||||
|
||||
@ -3,5 +3,14 @@
|
||||
"bicycle": "साइकल",
|
||||
"car": "कार",
|
||||
"motorcycle": "मोटरसाइकल",
|
||||
"airplane": "हवाइजहाज"
|
||||
"airplane": "हवाइजहाज",
|
||||
"bus": "बस",
|
||||
"train": "रेल",
|
||||
"boat": "डुङ्गा",
|
||||
"traffic_light": "ट्राफिक लाइट",
|
||||
"fire_hydrant": "आगो निभाउने यन्त्र",
|
||||
"street_sign": "सडक चिन्ह",
|
||||
"stop_sign": "रोक चिन्ह",
|
||||
"parking_meter": "पार्किङ मिटर",
|
||||
"bench": "बेन्च"
|
||||
}
|
||||
|
||||
@ -3,5 +3,13 @@
|
||||
"title": "फ्रिगेट च्याट",
|
||||
"subtitle": "क्यामेरा व्यवस्थापन र अन्तर्दृष्टिको लागि तपाईंको एआई सहायक",
|
||||
"placeholder": "सोध्नुहोस्...",
|
||||
"error": "केही गडबड भयो। कृपया फेरि प्रयास गर्नुहोस्।"
|
||||
"error": "केही गडबड भयो। कृपया फेरि प्रयास गर्नुहोस्।",
|
||||
"processing": "प्रशोधन गर्दै...",
|
||||
"toolsUsed": "प्रयोग गरिएको: {{tools}}",
|
||||
"showTools": "उपकरणहरू देखाउनुहोस् ({{count}})",
|
||||
"hideTools": "उपकरणहरू लुकाउनुहोस्",
|
||||
"call": "कल गर्नुहोस्",
|
||||
"result": "नतिजा",
|
||||
"arguments": "तर्कहरू:",
|
||||
"response": "प्रतिक्रिया:"
|
||||
}
|
||||
|
||||
@ -10,6 +10,16 @@
|
||||
},
|
||||
"button": {
|
||||
"deleteClassificationAttempts": "वर्गीकरण छविहरू मेटाउनुहोस्",
|
||||
"renameCategory": "वर्गको नाम बदल्नुहोस्"
|
||||
"renameCategory": "वर्गको नाम बदल्नुहोस्",
|
||||
"deleteCategory": "कक्षा मेटाउनुहोस्",
|
||||
"deleteImages": "छविहरू मेटाउनुहोस्",
|
||||
"trainModel": "रेल मोडेल",
|
||||
"addClassification": "वर्गीकरण थप्नुहोस्",
|
||||
"deleteModels": "मोडेलहरू मेटाउनुहोस्",
|
||||
"editModel": "मोडेल सम्पादन गर्नुहोस्"
|
||||
},
|
||||
"tooltip": {
|
||||
"trainingInProgress": "मोडेल हाल प्रशिक्षणमा छिन्",
|
||||
"noNewImages": "तालिम दिनको लागि कुनै नयाँ तस्बिरहरू छैनन्। पहिले डेटासेटमा थप तस्बिरहरू वर्गीकृत गर्नुहोस्।"
|
||||
}
|
||||
}
|
||||
|
||||
@ -3,5 +3,16 @@
|
||||
"configEditor": "कन्फिग सम्पादक",
|
||||
"safeConfigEditor": "कन्फिग सम्पादक (सुरक्षित मोड)",
|
||||
"safeModeDescription": "कन्फिग प्रमाणीकरण त्रुटिको कारणले फ्रिगेट सुरक्षित मोडमा छ।",
|
||||
"copyConfig": "कन्फिग प्रतिलिपि गर्नुहोस्"
|
||||
"copyConfig": "कन्फिग प्रतिलिपि गर्नुहोस्",
|
||||
"saveAndRestart": "बचत गर्नुहोस् र पुन: सुरु गर्नुहोस्",
|
||||
"saveOnly": "बचत मात्र",
|
||||
"confirm": "बचत नगरी बाहिर निस्कने हो?",
|
||||
"toast": {
|
||||
"success": {
|
||||
"copyToClipboard": "कन्फिगरेसन क्लिपबोर्डमा प्रतिलिपि गरियो।"
|
||||
},
|
||||
"error": {
|
||||
"savingError": "कन्फिगरेसन बचत गर्दा त्रुटि भयो"
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
@ -5,5 +5,19 @@
|
||||
"label": "गति",
|
||||
"only": "गति मात्र"
|
||||
},
|
||||
"allCameras": "सबै क्यामेराहरू"
|
||||
"allCameras": "सबै क्यामेराहरू",
|
||||
"empty": {
|
||||
"alert": "समीक्षा गर्न कुनै अलर्टहरू छैनन्",
|
||||
"detection": "समीक्षा गर्न कुनै पनि पत्ता लगाइएको छैन",
|
||||
"motion": "गतिसम्बन्धी कुनै डेटा फेला परेन",
|
||||
"recordingsDisabled": {
|
||||
"title": "रेकर्डिङहरू सक्षम पारिएको हुनुपर्छ",
|
||||
"description": "क्यामेराको लागि रेकर्डिङ सक्षम पारिएको बेला मात्र समीक्षा वस्तुहरू सिर्जना गर्न सकिन्छ।"
|
||||
}
|
||||
},
|
||||
"timeline": {
|
||||
"label": "समयरेखा",
|
||||
"aria": "टाइमलाइन चयन गर्नुहोस्"
|
||||
},
|
||||
"zoomIn": "जुम इन गर्नुहोस्"
|
||||
}
|
||||
|
||||
@ -1,5 +1,28 @@
|
||||
{
|
||||
"details": {
|
||||
"timestamp": "टाइमस्ट्याम्प"
|
||||
},
|
||||
"documentTitle": "अन्वेषण गर्नुहोस् - फ्रिगेट",
|
||||
"generativeAI": "जेनेरेटिभ एआई",
|
||||
"exploreMore": "थप {{label}} वस्तुहरू अन्वेषण गर्नुहोस्",
|
||||
"exploreIsUnavailable": {
|
||||
"title": "अन्वेषण उपलब्ध छैन",
|
||||
"embeddingsReindexing": {
|
||||
"context": "ट्र्याक गरिएका वस्तु इम्बेडिङहरूले पुन: अनुक्रमणिका समाप्त गरेपछि अन्वेषण प्रयोग गर्न सकिन्छ।",
|
||||
"startingUp": "सुरु गर्दै…",
|
||||
"estimatedTime": "अनुमानित बाँकी समय:",
|
||||
"finishingShortly": "चाँडै नै समाप्त हुँदैछ",
|
||||
"step": {
|
||||
"thumbnailsEmbedded": "इम्बेड गरिएका थम्बनेलहरू: ",
|
||||
"descriptionsEmbedded": "इम्बेड गरिएका विवरणहरू: ",
|
||||
"trackedObjectsProcessed": "ट्र्याक गरिएका वस्तुहरू प्रशोधन गरियो: "
|
||||
}
|
||||
},
|
||||
"downloadingModels": {
|
||||
"context": "फ्रिगेटले सिमान्टिक खोज सुविधालाई समर्थन गर्न आवश्यक इम्बेडिङ मोडेलहरू डाउनलोड गर्दैछ। तपाईंको नेटवर्क जडानको गतिमा निर्भर गर्दै यसले धेरै मिनेट लिन सक्छ।",
|
||||
"setup": {
|
||||
"visionModel": "भिजन मोडेल"
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
@ -4,5 +4,21 @@
|
||||
"headings": {
|
||||
"cases": "केसहरू",
|
||||
"uncategorizedExports": "वर्गीकृत नगरिएका निर्यातहरू"
|
||||
},
|
||||
"documentTitle": "निर्यात - फ्रिगेट",
|
||||
"deleteExport": {
|
||||
"label": "निर्यात मेटाउनुहोस्",
|
||||
"desc": "के तपाईं {{exportName}} मेटाउन चाहनुहुन्छ?"
|
||||
},
|
||||
"editExport": {
|
||||
"title": "निर्यातको नाम बदल्नुहोस्",
|
||||
"desc": "यो निर्यातको लागि नयाँ नाम प्रविष्ट गर्नुहोस्।",
|
||||
"saveExport": "निर्यात बचत गर्नुहोस्"
|
||||
},
|
||||
"tooltip": {
|
||||
"shareExport": "निर्यात सेयर गर्नुहोस्",
|
||||
"downloadVideo": "भिडियो डाउनलोड गर्नुहोस्",
|
||||
"editName": "नाम सम्पादन गर्नुहोस्",
|
||||
"deleteExport": "निर्यात मेटाउनुहोस्"
|
||||
}
|
||||
}
|
||||
|
||||
@ -7,6 +7,20 @@
|
||||
},
|
||||
"details": {
|
||||
"unknown": "अज्ञात",
|
||||
"timestamp": "टाइमस्ट्याम्प"
|
||||
"timestamp": "टाइमस्ट्याम्प",
|
||||
"scoreInfo": "स्कोर भनेको सबै अनुहारको स्कोरको भारित औसत हो, जुन प्रत्येक छविमा अनुहारको आकारद्वारा भारित हुन्छ।"
|
||||
},
|
||||
"documentTitle": "फेस लाइब्रेरी - फ्रिगेट",
|
||||
"uploadFaceImage": {
|
||||
"title": "अनुहारको छवि अपलोड गर्नुहोस्",
|
||||
"desc": "अनुहारहरू स्क्यान गर्न र {{pageToggle}} को लागि समावेश गर्न एउटा छवि अपलोड गर्नुहोस्"
|
||||
},
|
||||
"collections": "सङ्ग्रहहरू",
|
||||
"createFaceLibrary": {
|
||||
"new": "नयाँ अनुहार सिर्जना गर्नुहोस्",
|
||||
"nextSteps": "बलियो जग निर्माण गर्न:<li>प्रत्येक पत्ता लागेको व्यक्तिको लागि छविहरू चयन गर्न र तालिम दिन हालसालैको पहिचान ट्याब प्रयोग गर्नुहोस्।</li><li>उत्तम परिणामहरूको लागि सिधा-अन छविहरूमा ध्यान केन्द्रित गर्नुहोस्; कोणमा अनुहारहरू खिच्ने तालिम छविहरूबाट बच्नुहोस्।</li></ul>"
|
||||
},
|
||||
"steps": {
|
||||
"faceName": "अनुहारको नाम प्रविष्ट गर्नुहोस्"
|
||||
}
|
||||
}
|
||||
|
||||
@ -7,5 +7,28 @@
|
||||
"twoWayTalk": {
|
||||
"enable": "दुईतर्फी कुराकानी सक्षम पार्नुहोस्",
|
||||
"disable": "दुईतर्फी कुराकानी असक्षम पार्नुहोस्"
|
||||
},
|
||||
"cameraAudio": {
|
||||
"enable": "क्यामेरा अडियो सक्षम पार्नुहोस्",
|
||||
"disable": "क्यामेरा अडियो असक्षम पार्नुहोस्"
|
||||
},
|
||||
"ptz": {
|
||||
"move": {
|
||||
"clickMove": {
|
||||
"label": "क्यामेरालाई केन्द्रमा राख्न फ्रेममा क्लिक गर्नुहोस्",
|
||||
"enable": "सार्न क्लिक गर्नुहोस् सक्षम पार्नुहोस्",
|
||||
"enableWithZoom": "सार्न क्लिक गर्नुहोस् / जुम गर्न तान्नुहोस् सक्षम गर्नुहोस्",
|
||||
"disable": "सार्न क्लिक गर्ने सुविधा असक्षम पार्नुहोस्"
|
||||
},
|
||||
"left": {
|
||||
"label": "PTZ क्यामेरालाई बायाँतिर सार्नुहोस्"
|
||||
},
|
||||
"up": {
|
||||
"label": "PTZ क्यामेरा माथि सार्नुहोस्"
|
||||
},
|
||||
"down": {
|
||||
"label": "PTZ क्यामेरा तल सार्नुहोस्"
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
@ -3,5 +3,15 @@
|
||||
"title": "गति खोज",
|
||||
"description": "रुचिको क्षेत्र परिभाषित गर्न बहुभुज कोर्नुहोस्, र त्यो क्षेत्र भित्र गति परिवर्तनहरू खोज्नको लागि समय दायरा निर्दिष्ट गर्नुहोस्।",
|
||||
"selectCamera": "गति खोज लोड हुँदैछ",
|
||||
"startSearch": "खोज सुरु गर्नुहोस्"
|
||||
"startSearch": "खोज सुरु गर्नुहोस्",
|
||||
"searchStarted": "खोजी सुरु भयो",
|
||||
"searchCancelled": "खोज रद्द गरियो",
|
||||
"cancelSearch": "रद्द गर्नुहोस्",
|
||||
"searching": "खोजी भइरहेको छ।",
|
||||
"searchComplete": "खोज पूरा भयो",
|
||||
"noResultsYet": "चयन गरिएको क्षेत्रमा चाल परिवर्तनहरू फेला पार्न खोज चलाउनुहोस्",
|
||||
"noChangesFound": "चयन गरिएको क्षेत्रमा कुनै पिक्सेल परिवर्तनहरू फेला परेनन्",
|
||||
"changesFound_one": "{{count}} गति परिवर्तन फेला पर्यो",
|
||||
"changesFound_other": "{{count}} गति परिवर्तनहरू फेला परे",
|
||||
"framesProcessed": "{{count}} फ्रेमहरू प्रशोधन गरियो"
|
||||
}
|
||||
|
||||
@ -5,7 +5,8 @@
|
||||
"filters": "फिल्टरहरू",
|
||||
"toast": {
|
||||
"error": {
|
||||
"noValidTimeSelected": "कुनै मान्य समय दायरा चयन गरिएको छैन"
|
||||
"noValidTimeSelected": "कुनै मान्य समय दायरा चयन गरिएको छैन",
|
||||
"endTimeMustAfterStartTime": "अन्त्य समय सुरु समय पछि हुनुपर्छ"
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
@ -5,6 +5,16 @@
|
||||
"dialog": {
|
||||
"title": "डिबग रिप्ले सुरु गर्नुहोस्",
|
||||
"description": "वस्तु पत्ता लगाउने र ट्र्याकिङ समस्याहरू डिबग गर्न ऐतिहासिक फुटेज लुप गर्ने अस्थायी रिप्ले क्यामेरा सिर्जना गर्नुहोस्। रिप्ले क्यामेरामा स्रोत क्यामेरा जस्तै पत्ता लगाउने कन्फिगरेसन हुनेछ। सुरु गर्न समय दायरा छनौट गर्नुहोस्।",
|
||||
"camera": "स्रोत क्यामेरा"
|
||||
"camera": "स्रोत क्यामेरा",
|
||||
"timeRange": "समय दायरा",
|
||||
"preset": {
|
||||
"1m": "अन्तिम १ मिनेट",
|
||||
"5m": "अन्तिम ५ मिनेट",
|
||||
"timeline": "टाइमलाइनबाट",
|
||||
"custom": "अनुकूलन"
|
||||
},
|
||||
"startButton": "रिप्ले सुरु गर्नुहोस्",
|
||||
"selectFromTimeline": "चयन गर्नुहोस्",
|
||||
"starting": "रिप्ले सुरु गर्दै..."
|
||||
}
|
||||
}
|
||||
|
||||
@ -4,6 +4,19 @@
|
||||
"searchFor": "खोज्नुहोस् {{inputValue}}",
|
||||
"button": {
|
||||
"clear": "खोज खाली गर्नुहोस्",
|
||||
"save": "खोज बचत गर्नुहोस्"
|
||||
"save": "खोज बचत गर्नुहोस्",
|
||||
"delete": "सुरक्षित गरिएको खोज मेटाउनुहोस्",
|
||||
"filterInformation": "फिल्टर जानकारी",
|
||||
"filterActive": "फिल्टरहरू सक्रिय छन्"
|
||||
},
|
||||
"trackedObjectId": "ट्र्याक गरिएको वस्तु ID",
|
||||
"filter": {
|
||||
"label": {
|
||||
"cameras": "क्यामेराहरू",
|
||||
"labels": "लेबलहरू",
|
||||
"zones": "क्षेत्रहरू",
|
||||
"sub_labels": "उप लेबलहरू",
|
||||
"attributes": "विशेषताहरू"
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
@ -4,6 +4,14 @@
|
||||
"authentication": "प्रमाणीकरण सेटिङहरू - फ्रिगेट",
|
||||
"cameraManagement": "क्यामेराहरू व्यवस्थापन गर्नुहोस् - फ्रिगेट",
|
||||
"cameraReview": "क्यामेरा समीक्षा सेटिङहरू - फ्रिगेट",
|
||||
"enrichments": "संवर्धन सेटिङहरू - फ्रिगेट"
|
||||
"enrichments": "संवर्धन सेटिङहरू - फ्रिगेट",
|
||||
"masksAndZones": "मास्क र जोन सम्पादक - फ्रिगेट",
|
||||
"motionTuner": "मोशन ट्युनर - फ्रिगेट",
|
||||
"object": "डिबग - फ्रिगेट",
|
||||
"general": "UI सेटिङहरू - फ्रिगेट",
|
||||
"globalConfig": "विश्वव्यापी कन्फिगरेसन - फ्रिगेट",
|
||||
"cameraConfig": "क्यामेरा कन्फिगरेसन - फ्रिगेट",
|
||||
"frigatePlus": "फ्रिगेट+ सेटिङहरू - फ्रिगेट",
|
||||
"notifications": "सूचना सेटिङहरू - फ्रिगेट"
|
||||
}
|
||||
}
|
||||
|
||||
@ -6,7 +6,19 @@
|
||||
"enrichments": "संवर्धन तथ्याङ्क - फ्रिगेट",
|
||||
"logs": {
|
||||
"frigate": "फ्रिगेट लगहरू - फ्रिगेट",
|
||||
"go2rtc": "Go2RTC लगहरू - फ्रिगेट"
|
||||
"go2rtc": "Go2RTC लगहरू - फ्रिगेट",
|
||||
"nginx": "Nginx लगहरू - फ्रिगेट",
|
||||
"websocket": "सन्देश लगहरू - फ्रिगेट"
|
||||
}
|
||||
},
|
||||
"title": "प्रणाली",
|
||||
"metrics": "प्रणाली मेट्रिक्स",
|
||||
"logs": {
|
||||
"websocket": {
|
||||
"label": "सन्देशहरू",
|
||||
"pause": "पज गर्नुहोस्",
|
||||
"resume": "पुनःसुरु गर्नुहोस्",
|
||||
"clear": "खाली गर्नुहोस्"
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
@ -30,7 +30,8 @@
|
||||
},
|
||||
"submitFrigatePlus": {
|
||||
"title": "Dit frame indienen bij Frigate+?",
|
||||
"submit": "Indienen"
|
||||
"submit": "Indienen",
|
||||
"previewError": "Het was niet mogelijk om de snapshot preview te laden. De opname is mogelijk niet beschikbaar op dit moment."
|
||||
},
|
||||
"streamOffline": {
|
||||
"title": "Stream is Offline",
|
||||
|
||||
Some files were not shown because too many files have changed in this diff Show More
Loading…
Reference in New Issue
Block a user