mirror of
https://github.com/blakeblackshear/frigate.git
synced 2026-03-17 13:48:21 +03:00
Compare commits
52 Commits
856ee99f90
...
eb762f70ce
| Author | SHA1 | Date | |
|---|---|---|---|
|
|
eb762f70ce | ||
|
|
3f12a082dd | ||
|
|
6193399c60 | ||
|
|
6273f8c689 | ||
|
|
6df3a7cedb | ||
|
|
e902a32696 | ||
|
|
623471178f | ||
|
|
d15f35f94f | ||
|
|
fd6d175fea | ||
|
|
7d47369b2f | ||
|
|
88581c5b35 | ||
|
|
8978540d8d | ||
|
|
b963340322 | ||
|
|
03aa405b88 | ||
|
|
0bd543204c | ||
|
|
919e14041b | ||
|
|
b9787e65d6 | ||
|
|
e4adf3faa9 | ||
|
|
52cfac97aa | ||
|
|
904fd9a0cc | ||
|
|
3d238a9c83 | ||
|
|
c10cfc1861 | ||
|
|
a9058f7d1d | ||
|
|
2d73e2ef06 | ||
|
|
0bf45f0208 | ||
|
|
cacacb8be6 | ||
|
|
bc58277243 | ||
|
|
0202b219c2 | ||
|
|
79de297622 | ||
|
|
39c9472899 | ||
|
|
6f1338d528 | ||
|
|
474af69a7a | ||
|
|
3ecdd9a391 | ||
|
|
dc69d3c036 | ||
|
|
a9794767c6 | ||
|
|
759bc2f487 | ||
|
|
aaf8d62486 | ||
|
|
49ace018ab | ||
|
|
46ba340123 | ||
|
|
9e6a240fde | ||
|
|
31a1777bd2 | ||
|
|
446f1ce07d | ||
|
|
a0cbf3b370 | ||
|
|
d66822be84 | ||
|
|
cfdc6dc95c | ||
|
|
75130ef634 | ||
|
|
01d4dc8bd0 | ||
|
|
34cc1208a6 | ||
|
|
2babfd2ec9 | ||
|
|
229436c94a | ||
|
|
02678f4a09 | ||
|
|
65db9b0aec |
@ -38,7 +38,6 @@ Remember that motion detection is just used to determine when object detection s
|
||||
The threshold value dictates how much of a change in a pixels luminance is required to be considered motion.
|
||||
|
||||
```yaml
|
||||
# default threshold value
|
||||
motion:
|
||||
# Optional: The threshold passed to cv2.threshold to determine if a pixel is different enough to be counted as motion. (default: shown below)
|
||||
# Increasing this value will make motion detection less sensitive and decreasing it will make motion detection more sensitive.
|
||||
@ -53,7 +52,6 @@ Watching the motion boxes in the debug view, increase the threshold until you on
|
||||
### Contour Area
|
||||
|
||||
```yaml
|
||||
# default contour_area value
|
||||
motion:
|
||||
# Optional: Minimum size in pixels in the resized motion image that counts as motion (default: shown below)
|
||||
# Increasing this value will prevent smaller areas of motion from being detected. Decreasing will
|
||||
@ -81,27 +79,49 @@ However, if the preferred day settings do not work well at night it is recommend
|
||||
|
||||
## Tuning For Large Changes In Motion
|
||||
|
||||
### Lightning Threshold
|
||||
|
||||
```yaml
|
||||
# default lightning_threshold:
|
||||
motion:
|
||||
# Optional: The percentage of the image used to detect lightning or other substantial changes where motion detection
|
||||
# needs to recalibrate. (default: shown below)
|
||||
# Increasing this value will make motion detection more likely to consider lightning or ir mode changes as valid motion.
|
||||
# Decreasing this value will make motion detection more likely to ignore large amounts of motion such as a person approaching
|
||||
# a doorbell camera.
|
||||
# Optional: The percentage of the image used to detect lightning or
|
||||
# other substantial changes where motion detection needs to
|
||||
# recalibrate. (default: shown below)
|
||||
# Increasing this value will make motion detection more likely
|
||||
# to consider lightning or IR mode changes as valid motion.
|
||||
# Decreasing this value will make motion detection more likely
|
||||
# to ignore large amounts of motion such as a person
|
||||
# approaching a doorbell camera.
|
||||
lightning_threshold: 0.8
|
||||
```
|
||||
|
||||
Large changes in motion like PTZ moves and camera switches between Color and IR mode should result in a pause in object detection. `lightning_threshold` defines the percentage of the image used to detect these substantial changes. Increasing this value makes motion detection more likely to treat large changes (like IR mode switches) as valid motion. Decreasing it makes motion detection more likely to ignore large amounts of motion, such as a person approaching a doorbell camera.
|
||||
|
||||
Note that `lightning_threshold` does **not** stop motion-based recordings from being saved — it only prevents additional motion analysis after the threshold is exceeded, reducing false positive object detections during high-motion periods (e.g. storms or PTZ sweeps) without interfering with recordings.
|
||||
|
||||
:::warning
|
||||
|
||||
Some cameras like doorbell cameras may have missed detections when someone walks directly in front of the camera and the lightning_threshold causes motion detection to be re-calibrated. In this case, it may be desirable to increase the `lightning_threshold` to ensure these objects are not missed.
|
||||
Some cameras, like doorbell cameras, may have missed detections when someone walks directly in front of the camera and the `lightning_threshold` causes motion detection to recalibrate. In this case, it may be desirable to increase the `lightning_threshold` to ensure these objects are not missed.
|
||||
|
||||
:::
|
||||
|
||||
:::note
|
||||
### Skip Motion On Large Scene Changes
|
||||
|
||||
Lightning threshold does not stop motion based recordings from being saved.
|
||||
```yaml
|
||||
motion:
|
||||
# Optional: Fraction of the frame that must change in a single update
|
||||
# before Frigate will completely ignore any motion in that frame.
|
||||
# Values range between 0.0 and 1.0, leave unset (null) to disable.
|
||||
# Setting this to 0.7 would cause Frigate to **skip** reporting
|
||||
# motion boxes when more than 70% of the image appears to change
|
||||
# (e.g. during lightning storms, IR/color mode switches, or other
|
||||
# sudden lighting events).
|
||||
skip_motion_threshold: 0.7
|
||||
```
|
||||
|
||||
This option is handy when you want to prevent large transient changes from triggering recordings or object detection. It differs from `lightning_threshold` because it completely suppresses motion instead of just forcing a recalibration.
|
||||
|
||||
:::warning
|
||||
|
||||
When the skip threshold is exceeded, **no motion is reported** for that frame, meaning **nothing is recorded** for that frame. That means you can miss something important, like a PTZ camera auto-tracking an object or activity while the camera is moving. If you prefer to guarantee that every frame is saved, leave this unset and accept occasional recordings containing scene noise — they typically only take up a few megabytes and are quick to scan in the timeline UI.
|
||||
|
||||
:::
|
||||
|
||||
Large changes in motion like PTZ moves and camera switches between Color and IR mode should result in a pause in object detection. This is done via the `lightning_threshold` configuration. It is defined as the percentage of the image used to detect lightning or other substantial changes where motion detection needs to recalibrate. Increasing this value will make motion detection more likely to consider lightning or IR mode changes as valid motion. Decreasing this value will make motion detection more likely to ignore large amounts of motion such as a person approaching a doorbell camera.
|
||||
|
||||
@ -480,12 +480,16 @@ motion:
|
||||
# Increasing this value will make motion detection less sensitive and decreasing it will make motion detection more sensitive.
|
||||
# The value should be between 1 and 255.
|
||||
threshold: 30
|
||||
# Optional: The percentage of the image used to detect lightning or other substantial changes where motion detection
|
||||
# needs to recalibrate. (default: shown below)
|
||||
# Optional: The percentage of the image used to detect lightning or other substantial changes where motion detection needs
|
||||
# to recalibrate and motion checks stop for that frame. Recordings are unaffected. (default: shown below)
|
||||
# Increasing this value will make motion detection more likely to consider lightning or ir mode changes as valid motion.
|
||||
# Decreasing this value will make motion detection more likely to ignore large amounts of motion such as a person approaching
|
||||
# a doorbell camera.
|
||||
# Decreasing this value will make motion detection more likely to ignore large amounts of motion such as a person approaching a doorbell camera.
|
||||
lightning_threshold: 0.8
|
||||
# Optional: Fraction of the frame that must change in a single update before motion boxes are completely
|
||||
# ignored. Values range between 0.0 and 1.0. When exceeded, no motion boxes are reported and **no motion
|
||||
# recording** is created for that frame. Leave unset (null) to disable this feature. Use with care on PTZ
|
||||
# cameras or other situations where you require guaranteed frame capture.
|
||||
skip_motion_threshold: None
|
||||
# Optional: Minimum size in pixels in the resized motion image that counts as motion (default: shown below)
|
||||
# Increasing this value will prevent smaller areas of motion from being detected. Decreasing will
|
||||
# make motion detection more sensitive to smaller moving objects.
|
||||
|
||||
@ -32,6 +32,12 @@ from frigate.models import User
|
||||
|
||||
logger = logging.getLogger(__name__)
|
||||
|
||||
# In-memory cache to track which clients we've logged for an anonymous access event.
|
||||
# Keyed by a hashed value combining remote address + user-agent. The value is
|
||||
# an expiration timestamp (float).
|
||||
FIRST_LOAD_TTL_SECONDS = 60 * 60 * 24 * 7 # 7 days
|
||||
_first_load_seen: dict[str, float] = {}
|
||||
|
||||
|
||||
def require_admin_by_default():
|
||||
"""
|
||||
@ -284,6 +290,15 @@ def get_remote_addr(request: Request):
|
||||
return remote_addr or "127.0.0.1"
|
||||
|
||||
|
||||
def _cleanup_first_load_seen() -> None:
|
||||
"""Cleanup expired entries in the in-memory first-load cache."""
|
||||
now = time.time()
|
||||
# Build list for removal to avoid mutating dict during iteration
|
||||
expired = [k for k, exp in _first_load_seen.items() if exp <= now]
|
||||
for k in expired:
|
||||
del _first_load_seen[k]
|
||||
|
||||
|
||||
def get_jwt_secret() -> str:
|
||||
jwt_secret = None
|
||||
# check env var
|
||||
@ -744,10 +759,30 @@ def profile(request: Request):
|
||||
roles_dict = request.app.frigate_config.auth.roles
|
||||
allowed_cameras = User.get_allowed_cameras(role, roles_dict, all_camera_names)
|
||||
|
||||
return JSONResponse(
|
||||
response = JSONResponse(
|
||||
content={"username": username, "role": role, "allowed_cameras": allowed_cameras}
|
||||
)
|
||||
|
||||
if username == "anonymous":
|
||||
try:
|
||||
remote_addr = get_remote_addr(request)
|
||||
except Exception:
|
||||
remote_addr = (
|
||||
request.client.host if hasattr(request, "client") else "unknown"
|
||||
)
|
||||
|
||||
ua = request.headers.get("user-agent", "")
|
||||
key_material = f"{remote_addr}|{ua}"
|
||||
cache_key = hashlib.sha256(key_material.encode()).hexdigest()
|
||||
|
||||
_cleanup_first_load_seen()
|
||||
now = time.time()
|
||||
if cache_key not in _first_load_seen:
|
||||
_first_load_seen[cache_key] = now + FIRST_LOAD_TTL_SECONDS
|
||||
logger.info(f"Anonymous user access from {remote_addr} ua={ua[:200]}")
|
||||
|
||||
return response
|
||||
|
||||
|
||||
@router.get(
|
||||
"/logout",
|
||||
|
||||
@ -11,6 +11,7 @@ class Tags(Enum):
|
||||
classification = "Classification"
|
||||
logs = "Logs"
|
||||
media = "Media"
|
||||
motion_search = "Motion Search"
|
||||
notifications = "Notifications"
|
||||
preview = "Preview"
|
||||
recordings = "Recordings"
|
||||
|
||||
@ -22,6 +22,7 @@ from frigate.api import (
|
||||
event,
|
||||
export,
|
||||
media,
|
||||
motion_search,
|
||||
notification,
|
||||
preview,
|
||||
record,
|
||||
@ -135,6 +136,7 @@ def create_fastapi_app(
|
||||
app.include_router(export.router)
|
||||
app.include_router(event.router)
|
||||
app.include_router(media.router)
|
||||
app.include_router(motion_search.router)
|
||||
app.include_router(record.router)
|
||||
app.include_router(debug_replay.router)
|
||||
# App Properties
|
||||
|
||||
@ -24,6 +24,7 @@ from tzlocal import get_localzone_name
|
||||
from frigate.api.auth import (
|
||||
allow_any_authenticated,
|
||||
require_camera_access,
|
||||
require_role,
|
||||
)
|
||||
from frigate.api.defs.query.media_query_parameters import (
|
||||
Extension,
|
||||
@ -1005,6 +1006,23 @@ def grid_snapshot(
|
||||
)
|
||||
|
||||
|
||||
@router.delete(
|
||||
"/{camera_name}/region_grid", dependencies=[Depends(require_role("admin"))]
|
||||
)
|
||||
def clear_region_grid(request: Request, camera_name: str):
|
||||
"""Clear the region grid for a camera."""
|
||||
if camera_name not in request.app.frigate_config.cameras:
|
||||
return JSONResponse(
|
||||
content={"success": False, "message": "Camera not found"},
|
||||
status_code=404,
|
||||
)
|
||||
|
||||
Regions.delete().where(Regions.camera == camera_name).execute()
|
||||
return JSONResponse(
|
||||
content={"success": True, "message": "Region grid cleared"},
|
||||
)
|
||||
|
||||
|
||||
@router.get(
|
||||
"/events/{event_id}/snapshot-clean.webp",
|
||||
dependencies=[Depends(require_camera_access)],
|
||||
|
||||
292
frigate/api/motion_search.py
Normal file
292
frigate/api/motion_search.py
Normal file
@ -0,0 +1,292 @@
|
||||
"""Motion search API for detecting changes within a region of interest."""
|
||||
|
||||
import logging
|
||||
from typing import Any, List, Optional
|
||||
|
||||
from fastapi import APIRouter, Depends, Request
|
||||
from fastapi.responses import JSONResponse
|
||||
from pydantic import BaseModel, Field
|
||||
|
||||
from frigate.api.auth import require_camera_access
|
||||
from frigate.api.defs.tags import Tags
|
||||
from frigate.jobs.motion_search import (
|
||||
cancel_motion_search_job,
|
||||
get_motion_search_job,
|
||||
start_motion_search_job,
|
||||
)
|
||||
from frigate.types import JobStatusTypesEnum
|
||||
|
||||
logger = logging.getLogger(__name__)
|
||||
|
||||
router = APIRouter(tags=[Tags.motion_search])
|
||||
|
||||
|
||||
class MotionSearchRequest(BaseModel):
|
||||
"""Request body for motion search."""
|
||||
|
||||
start_time: float = Field(description="Start timestamp for the search range")
|
||||
end_time: float = Field(description="End timestamp for the search range")
|
||||
polygon_points: List[List[float]] = Field(
|
||||
description="List of [x, y] normalized coordinates (0-1) defining the ROI polygon"
|
||||
)
|
||||
threshold: int = Field(
|
||||
default=30,
|
||||
ge=1,
|
||||
le=255,
|
||||
description="Pixel difference threshold (1-255)",
|
||||
)
|
||||
min_area: float = Field(
|
||||
default=5.0,
|
||||
ge=0.1,
|
||||
le=100.0,
|
||||
description="Minimum change area as a percentage of the ROI",
|
||||
)
|
||||
frame_skip: int = Field(
|
||||
default=5,
|
||||
ge=1,
|
||||
le=30,
|
||||
description="Process every Nth frame (1=all frames, 5=every 5th frame)",
|
||||
)
|
||||
parallel: bool = Field(
|
||||
default=False,
|
||||
description="Enable parallel scanning across segments",
|
||||
)
|
||||
max_results: int = Field(
|
||||
default=25,
|
||||
ge=1,
|
||||
le=200,
|
||||
description="Maximum number of search results to return",
|
||||
)
|
||||
|
||||
|
||||
class MotionSearchResult(BaseModel):
|
||||
"""A single search result with timestamp and change info."""
|
||||
|
||||
timestamp: float = Field(description="Timestamp where change was detected")
|
||||
change_percentage: float = Field(description="Percentage of ROI area that changed")
|
||||
|
||||
|
||||
class MotionSearchMetricsResponse(BaseModel):
|
||||
"""Metrics collected during motion search execution."""
|
||||
|
||||
segments_scanned: int = 0
|
||||
segments_processed: int = 0
|
||||
metadata_inactive_segments: int = 0
|
||||
heatmap_roi_skip_segments: int = 0
|
||||
fallback_full_range_segments: int = 0
|
||||
frames_decoded: int = 0
|
||||
wall_time_seconds: float = 0.0
|
||||
segments_with_errors: int = 0
|
||||
|
||||
|
||||
class MotionSearchStartResponse(BaseModel):
|
||||
"""Response when motion search job starts."""
|
||||
|
||||
success: bool
|
||||
message: str
|
||||
job_id: str
|
||||
|
||||
|
||||
class MotionSearchStatusResponse(BaseModel):
|
||||
"""Response containing job status and results."""
|
||||
|
||||
success: bool
|
||||
message: str
|
||||
status: str # "queued", "running", "success", "failed", or "cancelled"
|
||||
results: Optional[List[MotionSearchResult]] = None
|
||||
total_frames_processed: Optional[int] = None
|
||||
error_message: Optional[str] = None
|
||||
metrics: Optional[MotionSearchMetricsResponse] = None
|
||||
|
||||
|
||||
@router.post(
|
||||
"/{camera_name}/search/motion",
|
||||
response_model=MotionSearchStartResponse,
|
||||
dependencies=[Depends(require_camera_access)],
|
||||
summary="Start motion search job",
|
||||
description="""Starts an asynchronous search for significant motion changes within
|
||||
a user-defined Region of Interest (ROI) over a specified time range. Returns a job_id
|
||||
that can be used to poll for results.""",
|
||||
)
|
||||
async def start_motion_search(
|
||||
request: Request,
|
||||
camera_name: str,
|
||||
body: MotionSearchRequest,
|
||||
):
|
||||
"""Start an async motion search job."""
|
||||
config = request.app.frigate_config
|
||||
|
||||
if camera_name not in config.cameras:
|
||||
return JSONResponse(
|
||||
content={"success": False, "message": f"Camera {camera_name} not found"},
|
||||
status_code=404,
|
||||
)
|
||||
|
||||
# Validate polygon has at least 3 points
|
||||
if len(body.polygon_points) < 3:
|
||||
return JSONResponse(
|
||||
content={
|
||||
"success": False,
|
||||
"message": "Polygon must have at least 3 points",
|
||||
},
|
||||
status_code=400,
|
||||
)
|
||||
|
||||
# Validate time range
|
||||
if body.start_time >= body.end_time:
|
||||
return JSONResponse(
|
||||
content={
|
||||
"success": False,
|
||||
"message": "Start time must be before end time",
|
||||
},
|
||||
status_code=400,
|
||||
)
|
||||
|
||||
# Start the job using the jobs module
|
||||
job_id = start_motion_search_job(
|
||||
config=config,
|
||||
camera_name=camera_name,
|
||||
start_time=body.start_time,
|
||||
end_time=body.end_time,
|
||||
polygon_points=body.polygon_points,
|
||||
threshold=body.threshold,
|
||||
min_area=body.min_area,
|
||||
frame_skip=body.frame_skip,
|
||||
parallel=body.parallel,
|
||||
max_results=body.max_results,
|
||||
)
|
||||
|
||||
return JSONResponse(
|
||||
content={
|
||||
"success": True,
|
||||
"message": "Search job started",
|
||||
"job_id": job_id,
|
||||
}
|
||||
)
|
||||
|
||||
|
||||
@router.get(
|
||||
"/{camera_name}/search/motion/{job_id}",
|
||||
response_model=MotionSearchStatusResponse,
|
||||
dependencies=[Depends(require_camera_access)],
|
||||
summary="Get motion search job status",
|
||||
description="Returns the status and results (if complete) of a motion search job.",
|
||||
)
|
||||
async def get_motion_search_status_endpoint(
|
||||
request: Request,
|
||||
camera_name: str,
|
||||
job_id: str,
|
||||
):
|
||||
"""Get the status of a motion search job."""
|
||||
config = request.app.frigate_config
|
||||
|
||||
if camera_name not in config.cameras:
|
||||
return JSONResponse(
|
||||
content={"success": False, "message": f"Camera {camera_name} not found"},
|
||||
status_code=404,
|
||||
)
|
||||
|
||||
job = get_motion_search_job(job_id)
|
||||
if not job:
|
||||
return JSONResponse(
|
||||
content={"success": False, "message": "Job not found"},
|
||||
status_code=404,
|
||||
)
|
||||
|
||||
api_status = job.status
|
||||
|
||||
# Build response content
|
||||
response_content: dict[str, Any] = {
|
||||
"success": api_status != JobStatusTypesEnum.failed,
|
||||
"status": api_status,
|
||||
}
|
||||
|
||||
if api_status == JobStatusTypesEnum.failed:
|
||||
response_content["message"] = job.error_message or "Search failed"
|
||||
response_content["error_message"] = job.error_message
|
||||
elif api_status == JobStatusTypesEnum.cancelled:
|
||||
response_content["message"] = "Search cancelled"
|
||||
response_content["total_frames_processed"] = job.total_frames_processed
|
||||
elif api_status == JobStatusTypesEnum.success:
|
||||
response_content["message"] = "Search complete"
|
||||
if job.results:
|
||||
response_content["results"] = job.results.get("results", [])
|
||||
response_content["total_frames_processed"] = job.results.get(
|
||||
"total_frames_processed", job.total_frames_processed
|
||||
)
|
||||
else:
|
||||
response_content["results"] = []
|
||||
response_content["total_frames_processed"] = job.total_frames_processed
|
||||
else:
|
||||
response_content["message"] = "Job processing"
|
||||
response_content["total_frames_processed"] = job.total_frames_processed
|
||||
# Include partial results if available (streaming)
|
||||
if job.results:
|
||||
response_content["results"] = job.results.get("results", [])
|
||||
response_content["total_frames_processed"] = job.results.get(
|
||||
"total_frames_processed", job.total_frames_processed
|
||||
)
|
||||
|
||||
# Include metrics if available
|
||||
if job.metrics:
|
||||
response_content["metrics"] = job.metrics.to_dict()
|
||||
|
||||
return JSONResponse(content=response_content)
|
||||
|
||||
|
||||
@router.post(
|
||||
"/{camera_name}/search/motion/{job_id}/cancel",
|
||||
dependencies=[Depends(require_camera_access)],
|
||||
summary="Cancel motion search job",
|
||||
description="Cancels an active motion search job if it is still processing.",
|
||||
)
|
||||
async def cancel_motion_search_endpoint(
|
||||
request: Request,
|
||||
camera_name: str,
|
||||
job_id: str,
|
||||
):
|
||||
"""Cancel an active motion search job."""
|
||||
config = request.app.frigate_config
|
||||
|
||||
if camera_name not in config.cameras:
|
||||
return JSONResponse(
|
||||
content={"success": False, "message": f"Camera {camera_name} not found"},
|
||||
status_code=404,
|
||||
)
|
||||
|
||||
job = get_motion_search_job(job_id)
|
||||
if not job:
|
||||
return JSONResponse(
|
||||
content={"success": False, "message": "Job not found"},
|
||||
status_code=404,
|
||||
)
|
||||
|
||||
# Check if already finished
|
||||
api_status = job.status
|
||||
if api_status not in (JobStatusTypesEnum.queued, JobStatusTypesEnum.running):
|
||||
return JSONResponse(
|
||||
content={
|
||||
"success": True,
|
||||
"message": "Job already finished",
|
||||
"status": api_status,
|
||||
}
|
||||
)
|
||||
|
||||
# Request cancellation
|
||||
cancelled = cancel_motion_search_job(job_id)
|
||||
if cancelled:
|
||||
return JSONResponse(
|
||||
content={
|
||||
"success": True,
|
||||
"message": "Search cancelled",
|
||||
"status": "cancelled",
|
||||
}
|
||||
)
|
||||
|
||||
return JSONResponse(
|
||||
content={
|
||||
"success": False,
|
||||
"message": "Failed to cancel job",
|
||||
},
|
||||
status_code=500,
|
||||
)
|
||||
@ -261,6 +261,7 @@ async def recordings(
|
||||
Recordings.segment_size,
|
||||
Recordings.motion,
|
||||
Recordings.objects,
|
||||
Recordings.motion_heatmap,
|
||||
Recordings.duration,
|
||||
)
|
||||
.where(
|
||||
|
||||
@ -51,6 +51,7 @@ from frigate.embeddings import EmbeddingProcess, EmbeddingsContext
|
||||
from frigate.events.audio import AudioProcessor
|
||||
from frigate.events.cleanup import EventCleanup
|
||||
from frigate.events.maintainer import EventProcessor
|
||||
from frigate.jobs.motion_search import stop_all_motion_search_jobs
|
||||
from frigate.log import _stop_logging
|
||||
from frigate.models import (
|
||||
Event,
|
||||
@ -599,6 +600,9 @@ class FrigateApp:
|
||||
# used by the docker healthcheck
|
||||
Path("/dev/shm/.frigate-is-stopping").touch()
|
||||
|
||||
# Cancel any running motion search jobs before setting stop_event
|
||||
stop_all_motion_search_jobs()
|
||||
|
||||
self.stop_event.set()
|
||||
|
||||
# set an end_time on entries without an end_time before exiting
|
||||
|
||||
@ -24,10 +24,17 @@ class MotionConfig(FrigateBaseModel):
|
||||
lightning_threshold: float = Field(
|
||||
default=0.8,
|
||||
title="Lightning threshold",
|
||||
description="Threshold to detect and ignore brief lighting spikes (lower is more sensitive, values between 0.3 and 1.0).",
|
||||
description="Threshold to detect and ignore brief lighting spikes (lower is more sensitive, values between 0.3 and 1.0). This does not prevent motion detection entirely; it merely causes the detector to stop analyzing additional frames once the threshold is exceeded. Motion-based recordings are still created during these events.",
|
||||
ge=0.3,
|
||||
le=1.0,
|
||||
)
|
||||
skip_motion_threshold: Optional[float] = Field(
|
||||
default=None,
|
||||
title="Skip motion threshold",
|
||||
description="If set to a value between 0.0 and 1.0, and more than this fraction of the image changes in a single frame, the detector will return no motion boxes and immediately recalibrate. This can save CPU and reduce false positives during lightning, storms, etc., but may miss real events such as a PTZ camera auto‑tracking an object. The trade‑off is between dropping a few megabytes of recordings versus reviewing a couple short clips. Leave unset (None) to disable this feature.",
|
||||
ge=0.0,
|
||||
le=1.0,
|
||||
)
|
||||
improve_contrast: bool = Field(
|
||||
default=True,
|
||||
title="Improve contrast",
|
||||
|
||||
@ -1,5 +1,6 @@
|
||||
"""Ollama Provider for Frigate AI."""
|
||||
|
||||
import json
|
||||
import logging
|
||||
from typing import Any, Optional
|
||||
|
||||
@ -108,7 +109,22 @@ class OllamaClient(GenAIClient):
|
||||
if msg.get("name"):
|
||||
msg_dict["name"] = msg["name"]
|
||||
if msg.get("tool_calls"):
|
||||
msg_dict["tool_calls"] = msg["tool_calls"]
|
||||
# Ollama requires tool call arguments as dicts, but the
|
||||
# conversation format (OpenAI-style) stores them as JSON
|
||||
# strings. Convert back to dicts for Ollama.
|
||||
ollama_tool_calls = []
|
||||
for tc in msg["tool_calls"]:
|
||||
func = tc.get("function") or {}
|
||||
args = func.get("arguments") or {}
|
||||
if isinstance(args, str):
|
||||
try:
|
||||
args = json.loads(args)
|
||||
except (json.JSONDecodeError, TypeError):
|
||||
args = {}
|
||||
ollama_tool_calls.append(
|
||||
{"function": {"name": func.get("name", ""), "arguments": args}}
|
||||
)
|
||||
msg_dict["tool_calls"] = ollama_tool_calls
|
||||
request_messages.append(msg_dict)
|
||||
|
||||
request_params: dict[str, Any] = {
|
||||
@ -120,25 +136,27 @@ class OllamaClient(GenAIClient):
|
||||
request_params["stream"] = True
|
||||
if tools:
|
||||
request_params["tools"] = tools
|
||||
if tool_choice:
|
||||
request_params["tool_choice"] = (
|
||||
"none"
|
||||
if tool_choice == "none"
|
||||
else "required"
|
||||
if tool_choice == "required"
|
||||
else "auto"
|
||||
)
|
||||
return request_params
|
||||
|
||||
def _message_from_response(self, response: dict[str, Any]) -> dict[str, Any]:
|
||||
"""Parse Ollama chat response into {content, tool_calls, finish_reason}."""
|
||||
if not response or "message" not in response:
|
||||
logger.debug("Ollama response empty or missing 'message' key")
|
||||
return {
|
||||
"content": None,
|
||||
"tool_calls": None,
|
||||
"finish_reason": "error",
|
||||
}
|
||||
message = response["message"]
|
||||
logger.debug(
|
||||
"Ollama response message keys: %s, content_len=%s, thinking_len=%s, "
|
||||
"tool_calls=%s, done=%s",
|
||||
list(message.keys()) if hasattr(message, "keys") else "N/A",
|
||||
len(message.get("content", "") or "") if message.get("content") else 0,
|
||||
len(message.get("thinking", "") or "") if message.get("thinking") else 0,
|
||||
bool(message.get("tool_calls")),
|
||||
response.get("done"),
|
||||
)
|
||||
content = message.get("content", "").strip() if message.get("content") else None
|
||||
tool_calls = parse_tool_calls_from_message(message)
|
||||
finish_reason = "error"
|
||||
@ -198,7 +216,13 @@ class OllamaClient(GenAIClient):
|
||||
tools: Optional[list[dict[str, Any]]] = None,
|
||||
tool_choice: Optional[str] = "auto",
|
||||
):
|
||||
"""Stream chat with tools; yields content deltas then final message."""
|
||||
"""Stream chat with tools; yields content deltas then final message.
|
||||
|
||||
When tools are provided, Ollama streaming does not include tool_calls
|
||||
in the response chunks. To work around this, we use a non-streaming
|
||||
call when tools are present to ensure tool calls are captured, then
|
||||
emit the content as a single delta followed by the final message.
|
||||
"""
|
||||
if self.provider is None:
|
||||
logger.warning(
|
||||
"Ollama provider has not been initialized. Check your Ollama configuration."
|
||||
@ -213,6 +237,27 @@ class OllamaClient(GenAIClient):
|
||||
)
|
||||
return
|
||||
try:
|
||||
# Ollama does not return tool_calls in streaming mode, so fall
|
||||
# back to a non-streaming call when tools are provided.
|
||||
if tools:
|
||||
logger.debug(
|
||||
"Ollama: tools provided, using non-streaming call for tool support"
|
||||
)
|
||||
request_params = self._build_request_params(
|
||||
messages, tools, tool_choice, stream=False
|
||||
)
|
||||
async_client = OllamaAsyncClient(
|
||||
host=self.genai_config.base_url,
|
||||
timeout=self.timeout,
|
||||
)
|
||||
response = await async_client.chat(**request_params)
|
||||
result = self._message_from_response(response)
|
||||
content = result.get("content")
|
||||
if content:
|
||||
yield ("content_delta", content)
|
||||
yield ("message", result)
|
||||
return
|
||||
|
||||
request_params = self._build_request_params(
|
||||
messages, tools, tool_choice, stream=True
|
||||
)
|
||||
@ -233,11 +278,10 @@ class OllamaClient(GenAIClient):
|
||||
yield ("content_delta", delta)
|
||||
if chunk.get("done"):
|
||||
full_content = "".join(content_parts).strip() or None
|
||||
tool_calls = parse_tool_calls_from_message(msg)
|
||||
final_message = {
|
||||
"content": full_content,
|
||||
"tool_calls": tool_calls,
|
||||
"finish_reason": "tool_calls" if tool_calls else "stop",
|
||||
"tool_calls": None,
|
||||
"finish_reason": "stop",
|
||||
}
|
||||
break
|
||||
|
||||
|
||||
@ -23,21 +23,26 @@ def parse_tool_calls_from_message(
|
||||
if not raw or not isinstance(raw, list):
|
||||
return None
|
||||
result = []
|
||||
for tool_call in raw:
|
||||
for idx, tool_call in enumerate(raw):
|
||||
function_data = tool_call.get("function") or {}
|
||||
try:
|
||||
arguments_str = function_data.get("arguments") or "{}"
|
||||
arguments = json.loads(arguments_str)
|
||||
except (json.JSONDecodeError, KeyError, TypeError) as e:
|
||||
logger.warning(
|
||||
"Failed to parse tool call arguments: %s, tool: %s",
|
||||
e,
|
||||
function_data.get("name", "unknown"),
|
||||
)
|
||||
raw_arguments = function_data.get("arguments") or {}
|
||||
if isinstance(raw_arguments, dict):
|
||||
arguments = raw_arguments
|
||||
elif isinstance(raw_arguments, str):
|
||||
try:
|
||||
arguments = json.loads(raw_arguments)
|
||||
except (json.JSONDecodeError, KeyError, TypeError) as e:
|
||||
logger.warning(
|
||||
"Failed to parse tool call arguments: %s, tool: %s",
|
||||
e,
|
||||
function_data.get("name", "unknown"),
|
||||
)
|
||||
arguments = {}
|
||||
else:
|
||||
arguments = {}
|
||||
result.append(
|
||||
{
|
||||
"id": tool_call.get("id", ""),
|
||||
"id": tool_call.get("id", "") or f"call_{idx}",
|
||||
"name": function_data.get("name", ""),
|
||||
"arguments": arguments,
|
||||
}
|
||||
|
||||
864
frigate/jobs/motion_search.py
Normal file
864
frigate/jobs/motion_search.py
Normal file
@ -0,0 +1,864 @@
|
||||
"""Motion search job management with background execution and parallel verification."""
|
||||
|
||||
import logging
|
||||
import os
|
||||
import threading
|
||||
from concurrent.futures import Future, ThreadPoolExecutor, as_completed
|
||||
from dataclasses import asdict, dataclass, field
|
||||
from datetime import datetime
|
||||
from typing import Any, Optional
|
||||
|
||||
import cv2
|
||||
import numpy as np
|
||||
|
||||
from frigate.comms.inter_process import InterProcessRequestor
|
||||
from frigate.config import FrigateConfig
|
||||
from frigate.const import UPDATE_JOB_STATE
|
||||
from frigate.jobs.job import Job
|
||||
from frigate.jobs.manager import (
|
||||
get_job_by_id,
|
||||
set_current_job,
|
||||
)
|
||||
from frigate.models import Recordings
|
||||
from frigate.types import JobStatusTypesEnum
|
||||
|
||||
logger = logging.getLogger(__name__)
|
||||
|
||||
# Constants
|
||||
HEATMAP_GRID_SIZE = 16
|
||||
|
||||
|
||||
@dataclass
|
||||
class MotionSearchMetrics:
|
||||
"""Metrics collected during motion search execution."""
|
||||
|
||||
segments_scanned: int = 0
|
||||
segments_processed: int = 0
|
||||
metadata_inactive_segments: int = 0
|
||||
heatmap_roi_skip_segments: int = 0
|
||||
fallback_full_range_segments: int = 0
|
||||
frames_decoded: int = 0
|
||||
wall_time_seconds: float = 0.0
|
||||
segments_with_errors: int = 0
|
||||
|
||||
def to_dict(self) -> dict[str, Any]:
|
||||
"""Convert to dictionary."""
|
||||
return asdict(self)
|
||||
|
||||
|
||||
@dataclass
|
||||
class MotionSearchResult:
|
||||
"""A single search result with timestamp and change info."""
|
||||
|
||||
timestamp: float
|
||||
change_percentage: float
|
||||
|
||||
def to_dict(self) -> dict[str, Any]:
|
||||
"""Convert to dictionary."""
|
||||
return asdict(self)
|
||||
|
||||
|
||||
@dataclass
|
||||
class MotionSearchJob(Job):
|
||||
"""Job state for motion search operations."""
|
||||
|
||||
job_type: str = "motion_search"
|
||||
camera: str = ""
|
||||
start_time_range: float = 0.0
|
||||
end_time_range: float = 0.0
|
||||
polygon_points: list[list[float]] = field(default_factory=list)
|
||||
threshold: int = 30
|
||||
min_area: float = 5.0
|
||||
frame_skip: int = 5
|
||||
parallel: bool = False
|
||||
max_results: int = 25
|
||||
|
||||
# Track progress
|
||||
total_frames_processed: int = 0
|
||||
|
||||
# Metrics for observability
|
||||
metrics: Optional[MotionSearchMetrics] = None
|
||||
|
||||
def to_dict(self) -> dict[str, Any]:
|
||||
"""Convert to dictionary for WebSocket transmission."""
|
||||
d = asdict(self)
|
||||
if self.metrics:
|
||||
d["metrics"] = self.metrics.to_dict()
|
||||
return d
|
||||
|
||||
|
||||
def create_polygon_mask(
|
||||
polygon_points: list[list[float]], frame_width: int, frame_height: int
|
||||
) -> np.ndarray:
|
||||
"""Create a binary mask from normalized polygon coordinates."""
|
||||
motion_points = np.array(
|
||||
[[int(p[0] * frame_width), int(p[1] * frame_height)] for p in polygon_points],
|
||||
dtype=np.int32,
|
||||
)
|
||||
mask = np.zeros((frame_height, frame_width), dtype=np.uint8)
|
||||
cv2.fillPoly(mask, [motion_points], 255)
|
||||
return mask
|
||||
|
||||
|
||||
def compute_roi_bbox_normalized(
|
||||
polygon_points: list[list[float]],
|
||||
) -> tuple[float, float, float, float]:
|
||||
"""Compute the bounding box of the ROI in normalized coordinates (0-1).
|
||||
|
||||
Returns (x_min, y_min, x_max, y_max) in normalized coordinates.
|
||||
"""
|
||||
if not polygon_points:
|
||||
return (0.0, 0.0, 1.0, 1.0)
|
||||
|
||||
x_coords = [p[0] for p in polygon_points]
|
||||
y_coords = [p[1] for p in polygon_points]
|
||||
return (min(x_coords), min(y_coords), max(x_coords), max(y_coords))
|
||||
|
||||
|
||||
def heatmap_overlaps_roi(
|
||||
heatmap: dict[str, int], roi_bbox: tuple[float, float, float, float]
|
||||
) -> bool:
|
||||
"""Check if a sparse motion heatmap has any overlap with the ROI bounding box.
|
||||
|
||||
Args:
|
||||
heatmap: Sparse dict mapping cell index (str) to intensity (1-255).
|
||||
roi_bbox: (x_min, y_min, x_max, y_max) in normalized coordinates (0-1).
|
||||
|
||||
Returns:
|
||||
True if there is overlap (any active cell in the ROI region).
|
||||
"""
|
||||
if not isinstance(heatmap, dict):
|
||||
# Invalid heatmap, assume overlap to be safe
|
||||
return True
|
||||
|
||||
x_min, y_min, x_max, y_max = roi_bbox
|
||||
|
||||
# Convert normalized coordinates to grid cells (0-15)
|
||||
grid_x_min = max(0, int(x_min * HEATMAP_GRID_SIZE))
|
||||
grid_y_min = max(0, int(y_min * HEATMAP_GRID_SIZE))
|
||||
grid_x_max = min(HEATMAP_GRID_SIZE - 1, int(x_max * HEATMAP_GRID_SIZE))
|
||||
grid_y_max = min(HEATMAP_GRID_SIZE - 1, int(y_max * HEATMAP_GRID_SIZE))
|
||||
|
||||
# Check each cell in the ROI bbox
|
||||
for y in range(grid_y_min, grid_y_max + 1):
|
||||
for x in range(grid_x_min, grid_x_max + 1):
|
||||
idx = str(y * HEATMAP_GRID_SIZE + x)
|
||||
if idx in heatmap:
|
||||
return True
|
||||
|
||||
return False
|
||||
|
||||
|
||||
def segment_passes_activity_gate(recording: Recordings) -> bool:
|
||||
"""Check if a segment passes the activity gate.
|
||||
|
||||
Returns True if any of motion, objects, or regions is non-zero/non-null.
|
||||
Returns True if all are null (old segments without data).
|
||||
"""
|
||||
motion = recording.motion
|
||||
objects = recording.objects
|
||||
regions = recording.regions
|
||||
|
||||
# Old segments without metadata - pass through (conservative)
|
||||
if motion is None and objects is None and regions is None:
|
||||
return True
|
||||
|
||||
# Pass if any activity indicator is positive
|
||||
return bool(motion) or bool(objects) or bool(regions)
|
||||
|
||||
|
||||
def segment_passes_heatmap_gate(
|
||||
recording: Recordings, roi_bbox: tuple[float, float, float, float]
|
||||
) -> bool:
|
||||
"""Check if a segment passes the heatmap overlap gate.
|
||||
|
||||
Returns True if:
|
||||
- No heatmap is stored (old segments).
|
||||
- The heatmap overlaps with the ROI bbox.
|
||||
"""
|
||||
heatmap = getattr(recording, "motion_heatmap", None)
|
||||
if heatmap is None:
|
||||
# No heatmap stored, fall back to activity gate
|
||||
return True
|
||||
|
||||
return heatmap_overlaps_roi(heatmap, roi_bbox)
|
||||
|
||||
|
||||
class MotionSearchRunner(threading.Thread):
|
||||
"""Thread-based runner for motion search jobs with parallel verification."""
|
||||
|
||||
def __init__(
|
||||
self,
|
||||
job: MotionSearchJob,
|
||||
config: FrigateConfig,
|
||||
cancel_event: threading.Event,
|
||||
) -> None:
|
||||
super().__init__(daemon=True, name=f"motion_search_{job.id}")
|
||||
self.job = job
|
||||
self.config = config
|
||||
self.cancel_event = cancel_event
|
||||
self.internal_stop_event = threading.Event()
|
||||
self.requestor = InterProcessRequestor()
|
||||
self.metrics = MotionSearchMetrics()
|
||||
self.job.metrics = self.metrics
|
||||
|
||||
# Worker cap: min(4, cpu_count)
|
||||
cpu_count = os.cpu_count() or 1
|
||||
self.max_workers = min(4, cpu_count)
|
||||
|
||||
def run(self) -> None:
|
||||
"""Execute the motion search job."""
|
||||
try:
|
||||
self.job.status = JobStatusTypesEnum.running
|
||||
self.job.start_time = datetime.now().timestamp()
|
||||
self._broadcast_status()
|
||||
|
||||
results = self._execute_search()
|
||||
|
||||
if self.cancel_event.is_set():
|
||||
self.job.status = JobStatusTypesEnum.cancelled
|
||||
else:
|
||||
self.job.status = JobStatusTypesEnum.success
|
||||
self.job.results = {
|
||||
"results": [r.to_dict() for r in results],
|
||||
"total_frames_processed": self.job.total_frames_processed,
|
||||
}
|
||||
|
||||
self.job.end_time = datetime.now().timestamp()
|
||||
self.metrics.wall_time_seconds = self.job.end_time - self.job.start_time
|
||||
self.job.metrics = self.metrics
|
||||
|
||||
logger.debug(
|
||||
"Motion search job %s completed: status=%s, results=%d, frames=%d",
|
||||
self.job.id,
|
||||
self.job.status,
|
||||
len(results),
|
||||
self.job.total_frames_processed,
|
||||
)
|
||||
self._broadcast_status()
|
||||
|
||||
except Exception as e:
|
||||
logger.exception("Motion search job %s failed: %s", self.job.id, e)
|
||||
self.job.status = JobStatusTypesEnum.failed
|
||||
self.job.error_message = str(e)
|
||||
self.job.end_time = datetime.now().timestamp()
|
||||
self.metrics.wall_time_seconds = self.job.end_time - (
|
||||
self.job.start_time or 0
|
||||
)
|
||||
self.job.metrics = self.metrics
|
||||
self._broadcast_status()
|
||||
|
||||
finally:
|
||||
if self.requestor:
|
||||
self.requestor.stop()
|
||||
|
||||
def _broadcast_status(self) -> None:
|
||||
"""Broadcast job status update via IPC to WebSocket subscribers."""
|
||||
if self.job.status == JobStatusTypesEnum.running and self.job.start_time:
|
||||
self.metrics.wall_time_seconds = (
|
||||
datetime.now().timestamp() - self.job.start_time
|
||||
)
|
||||
|
||||
try:
|
||||
self.requestor.send_data(UPDATE_JOB_STATE, self.job.to_dict())
|
||||
except Exception as e:
|
||||
logger.warning("Failed to broadcast motion search status: %s", e)
|
||||
|
||||
def _should_stop(self) -> bool:
|
||||
"""Check if processing should stop due to cancellation or internal limits."""
|
||||
return self.cancel_event.is_set() or self.internal_stop_event.is_set()
|
||||
|
||||
def _execute_search(self) -> list[MotionSearchResult]:
|
||||
"""Main search execution logic."""
|
||||
camera_name = self.job.camera
|
||||
camera_config = self.config.cameras.get(camera_name)
|
||||
if not camera_config:
|
||||
raise ValueError(f"Camera {camera_name} not found")
|
||||
|
||||
frame_width = camera_config.detect.width
|
||||
frame_height = camera_config.detect.height
|
||||
|
||||
# Create polygon mask
|
||||
polygon_mask = create_polygon_mask(
|
||||
self.job.polygon_points, frame_width, frame_height
|
||||
)
|
||||
|
||||
if np.count_nonzero(polygon_mask) == 0:
|
||||
logger.warning("Polygon mask is empty for job %s", self.job.id)
|
||||
return []
|
||||
|
||||
# Compute ROI bbox in normalized coordinates for heatmap gate
|
||||
roi_bbox = compute_roi_bbox_normalized(self.job.polygon_points)
|
||||
|
||||
# Query recordings
|
||||
recordings = list(
|
||||
Recordings.select()
|
||||
.where(
|
||||
(
|
||||
Recordings.start_time.between(
|
||||
self.job.start_time_range, self.job.end_time_range
|
||||
)
|
||||
)
|
||||
| (
|
||||
Recordings.end_time.between(
|
||||
self.job.start_time_range, self.job.end_time_range
|
||||
)
|
||||
)
|
||||
| (
|
||||
(self.job.start_time_range > Recordings.start_time)
|
||||
& (self.job.end_time_range < Recordings.end_time)
|
||||
)
|
||||
)
|
||||
.where(Recordings.camera == camera_name)
|
||||
.order_by(Recordings.start_time.asc())
|
||||
)
|
||||
|
||||
if not recordings:
|
||||
logger.debug("No recordings found for motion search job %s", self.job.id)
|
||||
return []
|
||||
|
||||
logger.debug(
|
||||
"Motion search job %s: queried %d recording segments for camera %s "
|
||||
"(range %.1f - %.1f)",
|
||||
self.job.id,
|
||||
len(recordings),
|
||||
camera_name,
|
||||
self.job.start_time_range,
|
||||
self.job.end_time_range,
|
||||
)
|
||||
|
||||
self.metrics.segments_scanned = len(recordings)
|
||||
|
||||
# Apply activity and heatmap gates
|
||||
filtered_recordings = []
|
||||
for recording in recordings:
|
||||
if not segment_passes_activity_gate(recording):
|
||||
self.metrics.metadata_inactive_segments += 1
|
||||
self.metrics.segments_processed += 1
|
||||
logger.debug(
|
||||
"Motion search job %s: segment %s skipped by activity gate "
|
||||
"(motion=%s, objects=%s, regions=%s)",
|
||||
self.job.id,
|
||||
recording.id,
|
||||
recording.motion,
|
||||
recording.objects,
|
||||
recording.regions,
|
||||
)
|
||||
continue
|
||||
if not segment_passes_heatmap_gate(recording, roi_bbox):
|
||||
self.metrics.heatmap_roi_skip_segments += 1
|
||||
self.metrics.segments_processed += 1
|
||||
logger.debug(
|
||||
"Motion search job %s: segment %s skipped by heatmap gate "
|
||||
"(heatmap present=%s, roi_bbox=%s)",
|
||||
self.job.id,
|
||||
recording.id,
|
||||
recording.motion_heatmap is not None,
|
||||
roi_bbox,
|
||||
)
|
||||
continue
|
||||
filtered_recordings.append(recording)
|
||||
|
||||
self._broadcast_status()
|
||||
|
||||
# Fallback: if all segments were filtered out, scan all segments
|
||||
# This allows motion search to find things the detector missed
|
||||
if not filtered_recordings and recordings:
|
||||
logger.info(
|
||||
"All %d segments filtered by gates, falling back to full scan",
|
||||
len(recordings),
|
||||
)
|
||||
self.metrics.fallback_full_range_segments = len(recordings)
|
||||
filtered_recordings = recordings
|
||||
|
||||
logger.debug(
|
||||
"Motion search job %s: %d/%d segments passed gates "
|
||||
"(activity_skipped=%d, heatmap_skipped=%d)",
|
||||
self.job.id,
|
||||
len(filtered_recordings),
|
||||
len(recordings),
|
||||
self.metrics.metadata_inactive_segments,
|
||||
self.metrics.heatmap_roi_skip_segments,
|
||||
)
|
||||
|
||||
if self.job.parallel:
|
||||
return self._search_motion_parallel(filtered_recordings, polygon_mask)
|
||||
|
||||
return self._search_motion_sequential(filtered_recordings, polygon_mask)
|
||||
|
||||
def _search_motion_parallel(
|
||||
self,
|
||||
recordings: list[Recordings],
|
||||
polygon_mask: np.ndarray,
|
||||
) -> list[MotionSearchResult]:
|
||||
"""Search for motion in parallel across segments, streaming results."""
|
||||
all_results: list[MotionSearchResult] = []
|
||||
total_frames = 0
|
||||
next_recording_idx_to_merge = 0
|
||||
|
||||
logger.debug(
|
||||
"Motion search job %s: starting motion search with %d workers "
|
||||
"across %d segments",
|
||||
self.job.id,
|
||||
self.max_workers,
|
||||
len(recordings),
|
||||
)
|
||||
|
||||
# Initialize partial results on the job so they stream to the frontend
|
||||
self.job.results = {"results": [], "total_frames_processed": 0}
|
||||
|
||||
with ThreadPoolExecutor(max_workers=self.max_workers) as executor:
|
||||
futures: dict[Future, int] = {}
|
||||
completed_segments: dict[int, tuple[list[MotionSearchResult], int]] = {}
|
||||
|
||||
for idx, recording in enumerate(recordings):
|
||||
if self._should_stop():
|
||||
break
|
||||
|
||||
future = executor.submit(
|
||||
self._process_recording_for_motion,
|
||||
recording.path,
|
||||
recording.start_time,
|
||||
recording.end_time,
|
||||
self.job.start_time_range,
|
||||
self.job.end_time_range,
|
||||
polygon_mask,
|
||||
self.job.threshold,
|
||||
self.job.min_area,
|
||||
self.job.frame_skip,
|
||||
)
|
||||
futures[future] = idx
|
||||
|
||||
for future in as_completed(futures):
|
||||
if self._should_stop():
|
||||
# Cancel remaining futures
|
||||
for f in futures:
|
||||
f.cancel()
|
||||
break
|
||||
|
||||
recording_idx = futures[future]
|
||||
recording = recordings[recording_idx]
|
||||
|
||||
try:
|
||||
results, frames = future.result()
|
||||
self.metrics.segments_processed += 1
|
||||
completed_segments[recording_idx] = (results, frames)
|
||||
|
||||
while next_recording_idx_to_merge in completed_segments:
|
||||
segment_results, segment_frames = completed_segments.pop(
|
||||
next_recording_idx_to_merge
|
||||
)
|
||||
|
||||
all_results.extend(segment_results)
|
||||
total_frames += segment_frames
|
||||
self.job.total_frames_processed = total_frames
|
||||
self.metrics.frames_decoded = total_frames
|
||||
|
||||
if segment_results:
|
||||
deduped = self._deduplicate_results(all_results)
|
||||
self.job.results = {
|
||||
"results": [
|
||||
r.to_dict() for r in deduped[: self.job.max_results]
|
||||
],
|
||||
"total_frames_processed": total_frames,
|
||||
}
|
||||
|
||||
self._broadcast_status()
|
||||
|
||||
if segment_results and len(deduped) >= self.job.max_results:
|
||||
self.internal_stop_event.set()
|
||||
for pending_future in futures:
|
||||
pending_future.cancel()
|
||||
break
|
||||
|
||||
next_recording_idx_to_merge += 1
|
||||
|
||||
if self.internal_stop_event.is_set():
|
||||
break
|
||||
|
||||
except Exception as e:
|
||||
self.metrics.segments_processed += 1
|
||||
self.metrics.segments_with_errors += 1
|
||||
self._broadcast_status()
|
||||
logger.warning(
|
||||
"Error processing segment %s: %s",
|
||||
recording.path,
|
||||
e,
|
||||
)
|
||||
|
||||
self.job.total_frames_processed = total_frames
|
||||
self.metrics.frames_decoded = total_frames
|
||||
|
||||
logger.debug(
|
||||
"Motion search job %s: motion search complete, "
|
||||
"found %d raw results, decoded %d frames, %d segment errors",
|
||||
self.job.id,
|
||||
len(all_results),
|
||||
total_frames,
|
||||
self.metrics.segments_with_errors,
|
||||
)
|
||||
|
||||
# Sort and deduplicate results
|
||||
all_results.sort(key=lambda x: x.timestamp)
|
||||
return self._deduplicate_results(all_results)[: self.job.max_results]
|
||||
|
||||
def _search_motion_sequential(
|
||||
self,
|
||||
recordings: list[Recordings],
|
||||
polygon_mask: np.ndarray,
|
||||
) -> list[MotionSearchResult]:
|
||||
"""Search for motion sequentially across segments, streaming results."""
|
||||
all_results: list[MotionSearchResult] = []
|
||||
total_frames = 0
|
||||
|
||||
logger.debug(
|
||||
"Motion search job %s: starting sequential motion search across %d segments",
|
||||
self.job.id,
|
||||
len(recordings),
|
||||
)
|
||||
|
||||
self.job.results = {"results": [], "total_frames_processed": 0}
|
||||
|
||||
for recording in recordings:
|
||||
if self.cancel_event.is_set():
|
||||
break
|
||||
|
||||
try:
|
||||
results, frames = self._process_recording_for_motion(
|
||||
recording.path,
|
||||
recording.start_time,
|
||||
recording.end_time,
|
||||
self.job.start_time_range,
|
||||
self.job.end_time_range,
|
||||
polygon_mask,
|
||||
self.job.threshold,
|
||||
self.job.min_area,
|
||||
self.job.frame_skip,
|
||||
)
|
||||
all_results.extend(results)
|
||||
total_frames += frames
|
||||
|
||||
self.job.total_frames_processed = total_frames
|
||||
self.metrics.frames_decoded = total_frames
|
||||
self.metrics.segments_processed += 1
|
||||
|
||||
if results:
|
||||
all_results.sort(key=lambda x: x.timestamp)
|
||||
deduped = self._deduplicate_results(all_results)[
|
||||
: self.job.max_results
|
||||
]
|
||||
self.job.results = {
|
||||
"results": [r.to_dict() for r in deduped],
|
||||
"total_frames_processed": total_frames,
|
||||
}
|
||||
|
||||
self._broadcast_status()
|
||||
|
||||
if results and len(deduped) >= self.job.max_results:
|
||||
break
|
||||
|
||||
except Exception as e:
|
||||
self.metrics.segments_processed += 1
|
||||
self.metrics.segments_with_errors += 1
|
||||
self._broadcast_status()
|
||||
logger.warning("Error processing segment %s: %s", recording.path, e)
|
||||
|
||||
self.job.total_frames_processed = total_frames
|
||||
self.metrics.frames_decoded = total_frames
|
||||
|
||||
logger.debug(
|
||||
"Motion search job %s: sequential motion search complete, "
|
||||
"found %d raw results, decoded %d frames, %d segment errors",
|
||||
self.job.id,
|
||||
len(all_results),
|
||||
total_frames,
|
||||
self.metrics.segments_with_errors,
|
||||
)
|
||||
|
||||
all_results.sort(key=lambda x: x.timestamp)
|
||||
return self._deduplicate_results(all_results)[: self.job.max_results]
|
||||
|
||||
def _deduplicate_results(
|
||||
self, results: list[MotionSearchResult], min_gap: float = 1.0
|
||||
) -> list[MotionSearchResult]:
|
||||
"""Deduplicate results that are too close together."""
|
||||
if not results:
|
||||
return results
|
||||
|
||||
deduplicated: list[MotionSearchResult] = []
|
||||
last_timestamp = 0.0
|
||||
|
||||
for result in results:
|
||||
if result.timestamp - last_timestamp >= min_gap:
|
||||
deduplicated.append(result)
|
||||
last_timestamp = result.timestamp
|
||||
|
||||
return deduplicated
|
||||
|
||||
def _process_recording_for_motion(
|
||||
self,
|
||||
recording_path: str,
|
||||
recording_start: float,
|
||||
recording_end: float,
|
||||
search_start: float,
|
||||
search_end: float,
|
||||
polygon_mask: np.ndarray,
|
||||
threshold: int,
|
||||
min_area: float,
|
||||
frame_skip: int,
|
||||
) -> tuple[list[MotionSearchResult], int]:
|
||||
"""Process a single recording file for motion detection.
|
||||
|
||||
This method is designed to be called from a thread pool.
|
||||
|
||||
Args:
|
||||
min_area: Minimum change area as a percentage of the ROI (0-100).
|
||||
"""
|
||||
results: list[MotionSearchResult] = []
|
||||
frames_processed = 0
|
||||
|
||||
if not os.path.exists(recording_path):
|
||||
logger.warning("Recording file not found: %s", recording_path)
|
||||
return results, frames_processed
|
||||
|
||||
cap = cv2.VideoCapture(recording_path)
|
||||
if not cap.isOpened():
|
||||
logger.error("Could not open recording: %s", recording_path)
|
||||
return results, frames_processed
|
||||
|
||||
try:
|
||||
fps = cap.get(cv2.CAP_PROP_FPS) or 30.0
|
||||
total_frames = int(cap.get(cv2.CAP_PROP_FRAME_COUNT))
|
||||
recording_duration = recording_end - recording_start
|
||||
|
||||
# Calculate frame range
|
||||
start_offset = max(0, search_start - recording_start)
|
||||
end_offset = min(recording_duration, search_end - recording_start)
|
||||
start_frame = int(start_offset * fps)
|
||||
end_frame = int(end_offset * fps)
|
||||
start_frame = max(0, min(start_frame, total_frames - 1))
|
||||
end_frame = max(0, min(end_frame, total_frames))
|
||||
|
||||
if start_frame >= end_frame:
|
||||
return results, frames_processed
|
||||
|
||||
cap.set(cv2.CAP_PROP_POS_FRAMES, start_frame)
|
||||
|
||||
# Get ROI bounding box
|
||||
roi_bbox = cv2.boundingRect(polygon_mask)
|
||||
roi_x, roi_y, roi_w, roi_h = roi_bbox
|
||||
|
||||
prev_frame_gray = None
|
||||
frame_step = max(frame_skip, 1)
|
||||
frame_idx = start_frame
|
||||
|
||||
while frame_idx < end_frame:
|
||||
if self._should_stop():
|
||||
break
|
||||
|
||||
ret, frame = cap.read()
|
||||
if not ret:
|
||||
frame_idx += 1
|
||||
continue
|
||||
|
||||
if (frame_idx - start_frame) % frame_step != 0:
|
||||
frame_idx += 1
|
||||
continue
|
||||
|
||||
frames_processed += 1
|
||||
|
||||
gray = cv2.cvtColor(frame, cv2.COLOR_BGR2GRAY)
|
||||
|
||||
# Handle frame dimension changes
|
||||
if gray.shape != polygon_mask.shape:
|
||||
resized_mask = cv2.resize(
|
||||
polygon_mask, (gray.shape[1], gray.shape[0]), cv2.INTER_NEAREST
|
||||
)
|
||||
current_bbox = cv2.boundingRect(resized_mask)
|
||||
else:
|
||||
resized_mask = polygon_mask
|
||||
current_bbox = roi_bbox
|
||||
|
||||
roi_x, roi_y, roi_w, roi_h = current_bbox
|
||||
cropped_gray = gray[roi_y : roi_y + roi_h, roi_x : roi_x + roi_w]
|
||||
cropped_mask = resized_mask[
|
||||
roi_y : roi_y + roi_h, roi_x : roi_x + roi_w
|
||||
]
|
||||
|
||||
cropped_mask_area = np.count_nonzero(cropped_mask)
|
||||
if cropped_mask_area == 0:
|
||||
frame_idx += 1
|
||||
continue
|
||||
|
||||
# Convert percentage to pixel count for this ROI
|
||||
min_area_pixels = int((min_area / 100.0) * cropped_mask_area)
|
||||
|
||||
masked_gray = cv2.bitwise_and(
|
||||
cropped_gray, cropped_gray, mask=cropped_mask
|
||||
)
|
||||
|
||||
if prev_frame_gray is not None:
|
||||
diff = cv2.absdiff(prev_frame_gray, masked_gray)
|
||||
diff_blurred = cv2.GaussianBlur(diff, (3, 3), 0)
|
||||
_, thresh = cv2.threshold(
|
||||
diff_blurred, threshold, 255, cv2.THRESH_BINARY
|
||||
)
|
||||
thresh_dilated = cv2.dilate(thresh, None, iterations=1)
|
||||
thresh_masked = cv2.bitwise_and(
|
||||
thresh_dilated, thresh_dilated, mask=cropped_mask
|
||||
)
|
||||
|
||||
change_pixels = cv2.countNonZero(thresh_masked)
|
||||
if change_pixels > min_area_pixels:
|
||||
contours, _ = cv2.findContours(
|
||||
thresh_masked, cv2.RETR_EXTERNAL, cv2.CHAIN_APPROX_SIMPLE
|
||||
)
|
||||
total_change_area = sum(
|
||||
cv2.contourArea(c)
|
||||
for c in contours
|
||||
if cv2.contourArea(c) >= min_area_pixels
|
||||
)
|
||||
if total_change_area > 0:
|
||||
frame_time_offset = (frame_idx - start_frame) / fps
|
||||
timestamp = (
|
||||
recording_start + start_offset + frame_time_offset
|
||||
)
|
||||
change_percentage = (
|
||||
total_change_area / cropped_mask_area
|
||||
) * 100
|
||||
results.append(
|
||||
MotionSearchResult(
|
||||
timestamp=timestamp,
|
||||
change_percentage=round(change_percentage, 2),
|
||||
)
|
||||
)
|
||||
|
||||
prev_frame_gray = masked_gray
|
||||
frame_idx += 1
|
||||
|
||||
finally:
|
||||
cap.release()
|
||||
|
||||
logger.debug(
|
||||
"Motion search segment complete: %s, %d frames processed, %d results found",
|
||||
recording_path,
|
||||
frames_processed,
|
||||
len(results),
|
||||
)
|
||||
return results, frames_processed
|
||||
|
||||
|
||||
# Module-level state for managing per-camera jobs
|
||||
_motion_search_jobs: dict[str, tuple[MotionSearchJob, threading.Event]] = {}
|
||||
_jobs_lock = threading.Lock()
|
||||
|
||||
|
||||
def stop_all_motion_search_jobs() -> None:
|
||||
"""Cancel all running motion search jobs for clean shutdown."""
|
||||
with _jobs_lock:
|
||||
for job_id, (job, cancel_event) in _motion_search_jobs.items():
|
||||
if job.status in (JobStatusTypesEnum.queued, JobStatusTypesEnum.running):
|
||||
cancel_event.set()
|
||||
logger.debug("Signalling motion search job %s to stop", job_id)
|
||||
|
||||
|
||||
def start_motion_search_job(
|
||||
config: FrigateConfig,
|
||||
camera_name: str,
|
||||
start_time: float,
|
||||
end_time: float,
|
||||
polygon_points: list[list[float]],
|
||||
threshold: int = 30,
|
||||
min_area: float = 5.0,
|
||||
frame_skip: int = 5,
|
||||
parallel: bool = False,
|
||||
max_results: int = 25,
|
||||
) -> str:
|
||||
"""Start a new motion search job.
|
||||
|
||||
Returns the job ID.
|
||||
"""
|
||||
job = MotionSearchJob(
|
||||
camera=camera_name,
|
||||
start_time_range=start_time,
|
||||
end_time_range=end_time,
|
||||
polygon_points=polygon_points,
|
||||
threshold=threshold,
|
||||
min_area=min_area,
|
||||
frame_skip=frame_skip,
|
||||
parallel=parallel,
|
||||
max_results=max_results,
|
||||
)
|
||||
|
||||
cancel_event = threading.Event()
|
||||
|
||||
with _jobs_lock:
|
||||
_motion_search_jobs[job.id] = (job, cancel_event)
|
||||
|
||||
set_current_job(job)
|
||||
|
||||
runner = MotionSearchRunner(job, config, cancel_event)
|
||||
runner.start()
|
||||
|
||||
logger.debug(
|
||||
"Started motion search job %s for camera %s: "
|
||||
"time_range=%.1f-%.1f, threshold=%d, min_area=%.1f%%, "
|
||||
"frame_skip=%d, parallel=%s, max_results=%d, polygon_points=%d vertices",
|
||||
job.id,
|
||||
camera_name,
|
||||
start_time,
|
||||
end_time,
|
||||
threshold,
|
||||
min_area,
|
||||
frame_skip,
|
||||
parallel,
|
||||
max_results,
|
||||
len(polygon_points),
|
||||
)
|
||||
return job.id
|
||||
|
||||
|
||||
def get_motion_search_job(job_id: str) -> Optional[MotionSearchJob]:
|
||||
"""Get a motion search job by ID."""
|
||||
with _jobs_lock:
|
||||
job_entry = _motion_search_jobs.get(job_id)
|
||||
if job_entry:
|
||||
return job_entry[0]
|
||||
# Check completed jobs via manager
|
||||
return get_job_by_id("motion_search", job_id)
|
||||
|
||||
|
||||
def cancel_motion_search_job(job_id: str) -> bool:
|
||||
"""Cancel a motion search job.
|
||||
|
||||
Returns True if cancellation was initiated, False if job not found.
|
||||
"""
|
||||
with _jobs_lock:
|
||||
job_entry = _motion_search_jobs.get(job_id)
|
||||
if not job_entry:
|
||||
return False
|
||||
|
||||
job, cancel_event = job_entry
|
||||
|
||||
if job.status not in (JobStatusTypesEnum.queued, JobStatusTypesEnum.running):
|
||||
# Already finished
|
||||
return True
|
||||
|
||||
cancel_event.set()
|
||||
job.status = JobStatusTypesEnum.cancelled
|
||||
job_payload = job.to_dict()
|
||||
logger.info("Cancelled motion search job %s", job_id)
|
||||
|
||||
requestor: Optional[InterProcessRequestor] = None
|
||||
try:
|
||||
requestor = InterProcessRequestor()
|
||||
requestor.send_data(UPDATE_JOB_STATE, job_payload)
|
||||
except Exception as e:
|
||||
logger.warning(
|
||||
"Failed to broadcast cancelled motion search job %s: %s", job_id, e
|
||||
)
|
||||
finally:
|
||||
if requestor:
|
||||
requestor.stop()
|
||||
|
||||
return True
|
||||
@ -78,6 +78,7 @@ class Recordings(Model):
|
||||
dBFS = IntegerField(null=True)
|
||||
segment_size = FloatField(default=0) # this should be stored as MB
|
||||
regions = IntegerField(null=True)
|
||||
motion_heatmap = JSONField(null=True) # 16x16 grid, 256 values (0-255)
|
||||
|
||||
|
||||
class ExportCase(Model):
|
||||
|
||||
@ -176,11 +176,32 @@ class ImprovedMotionDetector(MotionDetector):
|
||||
motion_boxes = []
|
||||
pct_motion = 0
|
||||
|
||||
# skip motion entirely if the scene change percentage exceeds configured
|
||||
# threshold. this is useful to ignore lighting storms, IR mode switches,
|
||||
# etc. rather than registering them as brief motion and then recalibrating.
|
||||
# note: skipping means the frame is dropped and **no recording will be
|
||||
# created**, which could hide a legitimate object if the camera is actively
|
||||
# auto‑tracking. the alternative is to allow motion and accept a small
|
||||
# recording that can be reviewed in the timeline. disabled by default (None).
|
||||
if (
|
||||
self.config.skip_motion_threshold is not None
|
||||
and pct_motion > self.config.skip_motion_threshold
|
||||
):
|
||||
# force a recalibration so we transition to the new background
|
||||
self.calibrating = True
|
||||
return []
|
||||
|
||||
# once the motion is less than 5% and the number of contours is < 4, assume its calibrated
|
||||
if pct_motion < 0.05 and len(motion_boxes) <= 4:
|
||||
self.calibrating = False
|
||||
|
||||
# if calibrating or the motion contours are > 80% of the image area (lightning, ir, ptz) recalibrate
|
||||
# if calibrating or the motion contours are > 80% of the image area
|
||||
# (lightning, ir, ptz) recalibrate. the lightning threshold does **not**
|
||||
# stop motion detection entirely; it simply halts additional processing for
|
||||
# the current frame once the percentage crosses the threshold. this helps
|
||||
# reduce false positive object detections and CPU usage during high‑motion
|
||||
# events. recordings continue to be generated because users expect data
|
||||
# while a PTZ camera is moving.
|
||||
if self.calibrating or pct_motion > self.config.lightning_threshold:
|
||||
self.calibrating = True
|
||||
|
||||
|
||||
@ -50,11 +50,13 @@ class SegmentInfo:
|
||||
active_object_count: int,
|
||||
region_count: int,
|
||||
average_dBFS: int,
|
||||
motion_heatmap: dict[str, int] | None = None,
|
||||
) -> None:
|
||||
self.motion_count = motion_count
|
||||
self.active_object_count = active_object_count
|
||||
self.region_count = region_count
|
||||
self.average_dBFS = average_dBFS
|
||||
self.motion_heatmap = motion_heatmap
|
||||
|
||||
def should_discard_segment(self, retain_mode: RetainModeEnum) -> bool:
|
||||
keep = False
|
||||
@ -454,6 +456,59 @@ class RecordingMaintainer(threading.Thread):
|
||||
if end_time < retain_cutoff:
|
||||
self.drop_segment(cache_path)
|
||||
|
||||
def _compute_motion_heatmap(
|
||||
self, camera: str, motion_boxes: list[tuple[int, int, int, int]]
|
||||
) -> dict[str, int] | None:
|
||||
"""Compute a 16x16 motion intensity heatmap from motion boxes.
|
||||
|
||||
Returns a sparse dict mapping cell index (as string) to intensity (1-255).
|
||||
Only cells with motion are included.
|
||||
|
||||
Args:
|
||||
camera: Camera name to get detect dimensions from.
|
||||
motion_boxes: List of (x1, y1, x2, y2) pixel coordinates.
|
||||
|
||||
Returns:
|
||||
Sparse dict like {"45": 3, "46": 5}, or None if no boxes.
|
||||
"""
|
||||
if not motion_boxes:
|
||||
return None
|
||||
|
||||
camera_config = self.config.cameras.get(camera)
|
||||
if not camera_config:
|
||||
return None
|
||||
|
||||
frame_width = camera_config.detect.width
|
||||
frame_height = camera_config.detect.height
|
||||
|
||||
if frame_width <= 0 or frame_height <= 0:
|
||||
return None
|
||||
|
||||
GRID_SIZE = 16
|
||||
counts: dict[int, int] = {}
|
||||
|
||||
for box in motion_boxes:
|
||||
if len(box) < 4:
|
||||
continue
|
||||
x1, y1, x2, y2 = box
|
||||
|
||||
# Convert pixel coordinates to grid cells
|
||||
grid_x1 = max(0, int((x1 / frame_width) * GRID_SIZE))
|
||||
grid_y1 = max(0, int((y1 / frame_height) * GRID_SIZE))
|
||||
grid_x2 = min(GRID_SIZE - 1, int((x2 / frame_width) * GRID_SIZE))
|
||||
grid_y2 = min(GRID_SIZE - 1, int((y2 / frame_height) * GRID_SIZE))
|
||||
|
||||
for y in range(grid_y1, grid_y2 + 1):
|
||||
for x in range(grid_x1, grid_x2 + 1):
|
||||
idx = y * GRID_SIZE + x
|
||||
counts[idx] = min(255, counts.get(idx, 0) + 1)
|
||||
|
||||
if not counts:
|
||||
return None
|
||||
|
||||
# Convert to string keys for JSON storage
|
||||
return {str(k): v for k, v in counts.items()}
|
||||
|
||||
def segment_stats(
|
||||
self, camera: str, start_time: datetime.datetime, end_time: datetime.datetime
|
||||
) -> SegmentInfo:
|
||||
@ -461,6 +516,8 @@ class RecordingMaintainer(threading.Thread):
|
||||
active_count = 0
|
||||
region_count = 0
|
||||
motion_count = 0
|
||||
all_motion_boxes: list[tuple[int, int, int, int]] = []
|
||||
|
||||
for frame in self.object_recordings_info[camera]:
|
||||
# frame is after end time of segment
|
||||
if frame[0] > end_time.timestamp():
|
||||
@ -479,6 +536,8 @@ class RecordingMaintainer(threading.Thread):
|
||||
)
|
||||
motion_count += len(frame[2])
|
||||
region_count += len(frame[3])
|
||||
# Collect motion boxes for heatmap computation
|
||||
all_motion_boxes.extend(frame[2])
|
||||
|
||||
audio_values = []
|
||||
for frame in self.audio_recordings_info[camera]:
|
||||
@ -498,8 +557,14 @@ class RecordingMaintainer(threading.Thread):
|
||||
|
||||
average_dBFS = 0 if not audio_values else np.average(audio_values)
|
||||
|
||||
motion_heatmap = self._compute_motion_heatmap(camera, all_motion_boxes)
|
||||
|
||||
return SegmentInfo(
|
||||
motion_count, active_count, region_count, round(average_dBFS)
|
||||
motion_count,
|
||||
active_count,
|
||||
region_count,
|
||||
round(average_dBFS),
|
||||
motion_heatmap,
|
||||
)
|
||||
|
||||
async def move_segment(
|
||||
@ -590,6 +655,7 @@ class RecordingMaintainer(threading.Thread):
|
||||
Recordings.regions.name: segment_info.region_count,
|
||||
Recordings.dBFS.name: segment_info.average_dBFS,
|
||||
Recordings.segment_size.name: segment_size,
|
||||
Recordings.motion_heatmap.name: segment_info.motion_heatmap,
|
||||
}
|
||||
except Exception as e:
|
||||
logger.error(f"Unable to store recording segment {cache_path}")
|
||||
|
||||
91
frigate/test/test_motion_detector.py
Normal file
91
frigate/test/test_motion_detector.py
Normal file
@ -0,0 +1,91 @@
|
||||
import unittest
|
||||
|
||||
import numpy as np
|
||||
|
||||
from frigate.config.camera.motion import MotionConfig
|
||||
from frigate.motion.improved_motion import ImprovedMotionDetector
|
||||
|
||||
|
||||
class TestImprovedMotionDetector(unittest.TestCase):
|
||||
def setUp(self):
|
||||
# small frame for testing; actual frames are grayscale
|
||||
self.frame_shape = (100, 100) # height, width
|
||||
self.config = MotionConfig()
|
||||
# motion detector assumes a rasterized_mask attribute exists on config
|
||||
# when update_mask() is called; add one manually by bypassing pydantic.
|
||||
object.__setattr__(
|
||||
self.config,
|
||||
"rasterized_mask",
|
||||
np.ones((self.frame_shape[0], self.frame_shape[1]), dtype=np.uint8),
|
||||
)
|
||||
|
||||
# create minimal PTZ metrics stub to satisfy detector checks
|
||||
class _Stub:
|
||||
def __init__(self, value=False):
|
||||
self.value = value
|
||||
|
||||
def is_set(self):
|
||||
return bool(self.value)
|
||||
|
||||
class DummyPTZ:
|
||||
def __init__(self):
|
||||
self.autotracker_enabled = _Stub(False)
|
||||
self.motor_stopped = _Stub(False)
|
||||
self.stop_time = _Stub(0)
|
||||
|
||||
self.detector = ImprovedMotionDetector(
|
||||
self.frame_shape, self.config, fps=30, ptz_metrics=DummyPTZ()
|
||||
)
|
||||
|
||||
# establish a baseline frame (all zeros)
|
||||
base_frame = np.zeros(
|
||||
(self.frame_shape[0], self.frame_shape[1]), dtype=np.uint8
|
||||
)
|
||||
self.detector.detect(base_frame)
|
||||
|
||||
def _half_change_frame(self) -> np.ndarray:
|
||||
"""Produce a frame where roughly half of the pixels are different."""
|
||||
frame = np.zeros((self.frame_shape[0], self.frame_shape[1]), dtype=np.uint8)
|
||||
# flip the top half to white
|
||||
frame[: self.frame_shape[0] // 2, :] = 255
|
||||
return frame
|
||||
|
||||
def test_skip_motion_threshold_default(self):
|
||||
"""With the default (None) setting, motion should always be reported."""
|
||||
frame = self._half_change_frame()
|
||||
boxes = self.detector.detect(frame)
|
||||
self.assertTrue(
|
||||
boxes, "Expected motion boxes when skip threshold is unset (disabled)"
|
||||
)
|
||||
|
||||
def test_skip_motion_threshold_applied(self):
|
||||
"""Setting a low skip threshold should prevent any boxes from being returned."""
|
||||
# change the config and update the detector reference
|
||||
self.config.skip_motion_threshold = 0.4
|
||||
self.detector.config = self.config
|
||||
self.detector.update_mask()
|
||||
|
||||
frame = self._half_change_frame()
|
||||
boxes = self.detector.detect(frame)
|
||||
self.assertEqual(
|
||||
boxes,
|
||||
[],
|
||||
"Motion boxes should be empty when scene change exceeds skip threshold",
|
||||
)
|
||||
|
||||
def test_skip_motion_threshold_does_not_affect_calibration(self):
|
||||
"""Even when skipping, the detector should go into calibrating state."""
|
||||
self.config.skip_motion_threshold = 0.4
|
||||
self.detector.config = self.config
|
||||
self.detector.update_mask()
|
||||
|
||||
frame = self._half_change_frame()
|
||||
_ = self.detector.detect(frame)
|
||||
self.assertTrue(
|
||||
self.detector.calibrating,
|
||||
"Detector should be in calibrating state after skip event",
|
||||
)
|
||||
|
||||
|
||||
if __name__ == "__main__":
|
||||
unittest.main()
|
||||
@ -110,6 +110,7 @@ def ensure_torch_dependencies() -> bool:
|
||||
"pip",
|
||||
"install",
|
||||
"--break-system-packages",
|
||||
"setuptools<81",
|
||||
"torch",
|
||||
"torchvision",
|
||||
],
|
||||
|
||||
34
migrations/035_add_motion_heatmap.py
Normal file
34
migrations/035_add_motion_heatmap.py
Normal file
@ -0,0 +1,34 @@
|
||||
"""Peewee migrations -- 035_add_motion_heatmap.py.
|
||||
|
||||
Some examples (model - class or model name)::
|
||||
|
||||
> Model = migrator.orm['model_name'] # Return model in current state by name
|
||||
|
||||
> migrator.sql(sql) # Run custom SQL
|
||||
> migrator.python(func, *args, **kwargs) # Run python code
|
||||
> migrator.create_model(Model) # Create a model (could be used as decorator)
|
||||
> migrator.remove_model(model, cascade=True) # Remove a model
|
||||
> migrator.add_fields(model, **fields) # Add fields to a model
|
||||
> migrator.change_fields(model, **fields) # Change fields
|
||||
> migrator.remove_fields(model, *field_names, cascade=True)
|
||||
> migrator.rename_field(model, old_field_name, new_field_name)
|
||||
> migrator.rename_table(model, new_table_name)
|
||||
> migrator.add_index(model, *col_names, unique=False)
|
||||
> migrator.drop_index(model, *col_names)
|
||||
> migrator.add_not_null(model, *field_names)
|
||||
> migrator.drop_not_null(model, *field_names)
|
||||
> migrator.add_default(model, field_name, default)
|
||||
|
||||
"""
|
||||
|
||||
import peewee as pw
|
||||
|
||||
SQL = pw.SQL
|
||||
|
||||
|
||||
def migrate(migrator, database, fake=False, **kwargs):
|
||||
migrator.sql('ALTER TABLE "recordings" ADD COLUMN "motion_heatmap" TEXT NULL')
|
||||
|
||||
|
||||
def rollback(migrator, database, fake=False, **kwargs):
|
||||
pass
|
||||
63
web/package-lock.json
generated
63
web/package-lock.json
generated
@ -22,6 +22,7 @@
|
||||
"@radix-ui/react-hover-card": "^1.1.6",
|
||||
"@radix-ui/react-label": "^2.1.2",
|
||||
"@radix-ui/react-popover": "^1.1.6",
|
||||
"@radix-ui/react-progress": "^1.1.8",
|
||||
"@radix-ui/react-radio-group": "^1.2.3",
|
||||
"@radix-ui/react-scroll-area": "^1.2.3",
|
||||
"@radix-ui/react-select": "^2.1.6",
|
||||
@ -2922,6 +2923,68 @@
|
||||
}
|
||||
}
|
||||
},
|
||||
"node_modules/@radix-ui/react-progress": {
|
||||
"version": "1.1.8",
|
||||
"resolved": "https://registry.npmjs.org/@radix-ui/react-progress/-/react-progress-1.1.8.tgz",
|
||||
"integrity": "sha512-+gISHcSPUJ7ktBy9RnTqbdKW78bcGke3t6taawyZ71pio1JewwGSJizycs7rLhGTvMJYCQB1DBK4KQsxs7U8dA==",
|
||||
"license": "MIT",
|
||||
"dependencies": {
|
||||
"@radix-ui/react-context": "1.1.3",
|
||||
"@radix-ui/react-primitive": "2.1.4"
|
||||
},
|
||||
"peerDependencies": {
|
||||
"@types/react": "*",
|
||||
"@types/react-dom": "*",
|
||||
"react": "^16.8 || ^17.0 || ^18.0 || ^19.0 || ^19.0.0-rc",
|
||||
"react-dom": "^16.8 || ^17.0 || ^18.0 || ^19.0 || ^19.0.0-rc"
|
||||
},
|
||||
"peerDependenciesMeta": {
|
||||
"@types/react": {
|
||||
"optional": true
|
||||
},
|
||||
"@types/react-dom": {
|
||||
"optional": true
|
||||
}
|
||||
}
|
||||
},
|
||||
"node_modules/@radix-ui/react-progress/node_modules/@radix-ui/react-context": {
|
||||
"version": "1.1.3",
|
||||
"resolved": "https://registry.npmjs.org/@radix-ui/react-context/-/react-context-1.1.3.tgz",
|
||||
"integrity": "sha512-ieIFACdMpYfMEjF0rEf5KLvfVyIkOz6PDGyNnP+u+4xQ6jny3VCgA4OgXOwNx2aUkxn8zx9fiVcM8CfFYv9Lxw==",
|
||||
"license": "MIT",
|
||||
"peerDependencies": {
|
||||
"@types/react": "*",
|
||||
"react": "^16.8 || ^17.0 || ^18.0 || ^19.0 || ^19.0.0-rc"
|
||||
},
|
||||
"peerDependenciesMeta": {
|
||||
"@types/react": {
|
||||
"optional": true
|
||||
}
|
||||
}
|
||||
},
|
||||
"node_modules/@radix-ui/react-progress/node_modules/@radix-ui/react-primitive": {
|
||||
"version": "2.1.4",
|
||||
"resolved": "https://registry.npmjs.org/@radix-ui/react-primitive/-/react-primitive-2.1.4.tgz",
|
||||
"integrity": "sha512-9hQc4+GNVtJAIEPEqlYqW5RiYdrr8ea5XQ0ZOnD6fgru+83kqT15mq2OCcbe8KnjRZl5vF3ks69AKz3kh1jrhg==",
|
||||
"license": "MIT",
|
||||
"dependencies": {
|
||||
"@radix-ui/react-slot": "1.2.4"
|
||||
},
|
||||
"peerDependencies": {
|
||||
"@types/react": "*",
|
||||
"@types/react-dom": "*",
|
||||
"react": "^16.8 || ^17.0 || ^18.0 || ^19.0 || ^19.0.0-rc",
|
||||
"react-dom": "^16.8 || ^17.0 || ^18.0 || ^19.0 || ^19.0.0-rc"
|
||||
},
|
||||
"peerDependenciesMeta": {
|
||||
"@types/react": {
|
||||
"optional": true
|
||||
},
|
||||
"@types/react-dom": {
|
||||
"optional": true
|
||||
}
|
||||
}
|
||||
},
|
||||
"node_modules/@radix-ui/react-radio-group": {
|
||||
"version": "1.3.8",
|
||||
"resolved": "https://registry.npmjs.org/@radix-ui/react-radio-group/-/react-radio-group-1.3.8.tgz",
|
||||
|
||||
@ -28,6 +28,7 @@
|
||||
"@radix-ui/react-hover-card": "^1.1.6",
|
||||
"@radix-ui/react-label": "^2.1.2",
|
||||
"@radix-ui/react-popover": "^1.1.6",
|
||||
"@radix-ui/react-progress": "^1.1.8",
|
||||
"@radix-ui/react-radio-group": "^1.2.3",
|
||||
"@radix-ui/react-scroll-area": "^1.2.3",
|
||||
"@radix-ui/react-select": "^2.1.6",
|
||||
|
||||
@ -106,7 +106,9 @@
|
||||
"logout": "Tanca la sessió",
|
||||
"current": "Usuari actual: {{user}}"
|
||||
},
|
||||
"classification": "Classificació"
|
||||
"classification": "Classificació",
|
||||
"chat": "Xat",
|
||||
"actions": "Accions"
|
||||
},
|
||||
"pagination": {
|
||||
"previous": {
|
||||
@ -268,7 +270,18 @@
|
||||
"unselect": "Desseleccionar",
|
||||
"enable": "Habilitar",
|
||||
"enabled": "Habilitat",
|
||||
"continue": "Continua"
|
||||
"continue": "Continua",
|
||||
"add": "Afegeix",
|
||||
"undo": "Desfés",
|
||||
"copiedToClipboard": "S'ha copiat al porta-retalls",
|
||||
"modified": "Modificat",
|
||||
"overridden": "Sobreescrit",
|
||||
"resetToGlobal": "Restableix a global",
|
||||
"resetToDefault": "Restableix al valor predeterminat",
|
||||
"saveAll": "Desa-ho tot",
|
||||
"savingAll": "S'està desant tot…",
|
||||
"undoAll": "Desfés-ho tot",
|
||||
"applying": "S'està aplicant…"
|
||||
},
|
||||
"toast": {
|
||||
"copyUrlToClipboard": "URL copiada al porta-retalls.",
|
||||
|
||||
@ -65,6 +65,10 @@
|
||||
"fromTimeline": {
|
||||
"saveExport": "Guardar exportació",
|
||||
"previewExport": "Previsualitzar exportació"
|
||||
},
|
||||
"case": {
|
||||
"label": "Cas",
|
||||
"placeholder": "Selecciona un cas"
|
||||
}
|
||||
},
|
||||
"streaming": {
|
||||
|
||||
936
web/public/locales/ca/config/cameras.json
Normal file
936
web/public/locales/ca/config/cameras.json
Normal file
@ -0,0 +1,936 @@
|
||||
{
|
||||
"label": "ConfiguracióDeLaCcàmera",
|
||||
"name": {
|
||||
"label": "Nom de la càmera",
|
||||
"description": "Es requereix el nom de la càmera"
|
||||
},
|
||||
"friendly_name": {
|
||||
"label": "Nom amistós",
|
||||
"description": "Nom amigable de la càmera utilitzat a la interfície d'usuari de la Frigate"
|
||||
},
|
||||
"enabled": {
|
||||
"label": "Habilitat",
|
||||
"description": "Habilitat"
|
||||
},
|
||||
"audio": {
|
||||
"label": "Esdeveniments d'àudio",
|
||||
"description": "Configuració per a la detecció d'esdeveniments basats en àudio per a aquesta càmera.",
|
||||
"enabled": {
|
||||
"label": "Habilita la detecció d'àudio",
|
||||
"description": "Activa o desactiva la detecció d'esdeveniments d'àudio per a aquesta càmera."
|
||||
},
|
||||
"max_not_heard": {
|
||||
"label": "Temps d'espera final",
|
||||
"description": "Quantitat de segons sense el tipus d'àudio configurat abans que acabi l'esdeveniment d'àudio."
|
||||
},
|
||||
"min_volume": {
|
||||
"label": "Volum mínim",
|
||||
"description": "Llindar mínim de volum RMS necessari per executar la detecció d'àudio; els valors més baixos augmenten la sensibilitat (p. ex., 200 alta, 500 mitjana, 1000 baixa)."
|
||||
},
|
||||
"listen": {
|
||||
"label": "Tipus d'escoltes",
|
||||
"description": "Llista de tipus d'esdeveniment d'àudio a detectar (per exemple: escorça, focarmalarma, crit, parla, crida)."
|
||||
},
|
||||
"filters": {
|
||||
"label": "Filtres d'àudio",
|
||||
"description": "Paràmetres de filtre per-àudio-tipus, com ara llindars de confiança utilitzats per reduir falsos positius."
|
||||
},
|
||||
"enabled_in_config": {
|
||||
"label": "Estat d'àudio original",
|
||||
"description": "Indica si la detecció d'àudio s'ha activat originalment al fitxer de configuració estàtic."
|
||||
},
|
||||
"num_threads": {
|
||||
"label": "Fils de detecció",
|
||||
"description": "Nombre de fils a utilitzar per al processament de detecció d'àudio."
|
||||
}
|
||||
},
|
||||
"audio_transcription": {
|
||||
"label": "Transcripció d'àudio",
|
||||
"description": "Configuració per a la transcripció d'àudio en viu i de veu utilitzada per a esdeveniments i llegendes en directe.",
|
||||
"enabled": {
|
||||
"label": "Habilita la transcripció",
|
||||
"description": "Activa o desactiva la transcripció d'esdeveniments d'àudio activada manualment."
|
||||
},
|
||||
"enabled_in_config": {
|
||||
"label": "Estat de transcripció original"
|
||||
},
|
||||
"live_enabled": {
|
||||
"label": "Transcripció en viu",
|
||||
"description": "Habilita la transcripció en directe per a l'àudio a mesura que es rep."
|
||||
}
|
||||
},
|
||||
"birdseye": {
|
||||
"label": "Birdseye",
|
||||
"description": "Arranjament per a la vista composta Birdseye que compon múltiples canals de càmera en una única disposició.",
|
||||
"enabled": {
|
||||
"label": "Habilita Birdseye",
|
||||
"description": "Activa o desactiva la funció de vista Birdseye."
|
||||
},
|
||||
"mode": {
|
||||
"label": "Mode de seguiment",
|
||||
"description": "Mode per a incloure càmeres en Birdseye: 'objectes', 'motion' o 'continuous'."
|
||||
},
|
||||
"order": {
|
||||
"label": "Posició",
|
||||
"description": "Posició numèrica que controla l'ordenació de la càmera en la disposició Birdseye."
|
||||
}
|
||||
},
|
||||
"detect": {
|
||||
"label": "Detecció d'objectes",
|
||||
"description": "Configuració del rol de detecció utilitzat per executar la detecció d'objectes i inicialitzar els rastrejadors.",
|
||||
"enabled": {
|
||||
"label": "Detecció activada",
|
||||
"description": "Activa o desactiva la detecció d'objectes per a aquesta càmera. La detecció s'ha d'activar perquè s'executi el seguiment d'objectes."
|
||||
},
|
||||
"height": {
|
||||
"label": "Detecta l'alçada",
|
||||
"description": "Alçada (píxels) dels fotogrames utilitzats per al flux de detecció; deixeu-ho buit per a utilitzar la resolució nativa del flux."
|
||||
},
|
||||
"width": {
|
||||
"label": "Detecta l'amplada",
|
||||
"description": "Amplada (píxels) dels fotogrames utilitzats per al flux de detecció; deixeu-ho buit per a utilitzar la resolució nativa del flux."
|
||||
},
|
||||
"fps": {
|
||||
"label": "Detecta FPS",
|
||||
"description": "Fotogrames desitjats per segon per executar la detecció; els valors més baixos redueixen l'ús de la CPU (el valor recomanat és 5, només estableix més alt - com a màxim 10 - si el seguiment d'objectes en moviment extremadament ràpid)."
|
||||
},
|
||||
"min_initialized": {
|
||||
"label": "Fotogrames d'inicialització mínims",
|
||||
"description": "Nombre d'incidències de detecció consecutives necessàries abans de crear un objecte rastrejat. Incrementa per a reduir les falses inicialitzacions. El valor per defecte és fps dividit per 2."
|
||||
},
|
||||
"max_disappeared": {
|
||||
"label": "Màxim de fotogrames desapareguts",
|
||||
"description": "Nombre de fotogrames sense detecció abans que es consideri que un objecte rastrejat ha desaparegut."
|
||||
},
|
||||
"stationary": {
|
||||
"label": "Configuració d'objectes estacionaris",
|
||||
"description": "Configuració per detectar i gestionar objectes que romanen estacionaris durant un període de temps.",
|
||||
"interval": {
|
||||
"label": "Interval estacionari",
|
||||
"description": "Amb quina freqüència (en fotogrames) s'executa una comprovació de detecció per confirmar un objecte estacionari."
|
||||
},
|
||||
"threshold": {
|
||||
"label": "Llindar estacionari",
|
||||
"description": "Nombre de fotogrames sense cap canvi de posició necessari per a marcar un objecte com a estacionari."
|
||||
},
|
||||
"max_frames": {
|
||||
"label": "Fotogrames màxims",
|
||||
"description": "Limita quant de temps es segueixen els objectes estacionaris abans de descartar-los.",
|
||||
"default": {
|
||||
"label": "Fotogrames màxims predeterminats",
|
||||
"description": "Fotogrames màxims predeterminats per a fer el seguiment d'un objecte estacionari abans d'aturar-se."
|
||||
},
|
||||
"objects": {
|
||||
"label": "Fotogrames màxims de l'objecte",
|
||||
"description": "Sobreescriu l'objecte per als fotogrames màxims per fer un seguiment dels objectes estacionaris."
|
||||
}
|
||||
},
|
||||
"classifier": {
|
||||
"label": "Habilita el classificador visual",
|
||||
"description": "Utilitzeu un classificador visual per detectar objectes realment estacionaris, fins i tot quan les caixes contenidores tremolen."
|
||||
}
|
||||
},
|
||||
"annotation_offset": {
|
||||
"label": "Desplaçament de l'anotació",
|
||||
"description": "Mil·lisegons per a desplaçar detecta anotacions per a alinear millor els límits de la línia de temps amb els enregistraments; pot ser positiu o negatiu."
|
||||
}
|
||||
},
|
||||
"face_recognition": {
|
||||
"label": "Reconeixement de cares",
|
||||
"description": "Configuració per a la detecció de la cara i el reconeixement d'aquesta càmera.",
|
||||
"enabled": {
|
||||
"label": "Habilita el reconeixement facial",
|
||||
"description": "Activa o desactiva el reconeixement facial."
|
||||
},
|
||||
"min_area": {
|
||||
"label": "Àrea mínima de la cara",
|
||||
"description": "Àrea mínima (píxels) d'un quadre facial detectat requerit per intentar el reconeixement."
|
||||
}
|
||||
},
|
||||
"ffmpeg": {
|
||||
"label": "FFmpeg",
|
||||
"description": "Paràmetres del FFmpeg que inclouen camins binaris, args, opcions de hwaccel i args de sortida per rol.",
|
||||
"path": {
|
||||
"label": "Ruta FFmpeg",
|
||||
"description": "Ruta al binari FFmpeg a usar o un àlies de versió («5.0» o «7.0»)."
|
||||
},
|
||||
"global_args": {
|
||||
"label": "Arguments globals del FFmpeg",
|
||||
"description": "Arguments globals passats als processos FFmpeg."
|
||||
},
|
||||
"hwaccel_args": {
|
||||
"label": "Arguments d'acceleració del maquinari",
|
||||
"description": "Arguments d'acceleració de maquinari per a FFmpeg. Es recomanen predefinits específics del proveïdor."
|
||||
},
|
||||
"input_args": {
|
||||
"label": "Arguments d'entrada",
|
||||
"description": "Arguments d'entrada aplicats als fluxos d'entrada del FFmpeg."
|
||||
},
|
||||
"output_args": {
|
||||
"label": "Arguments de sortida",
|
||||
"description": "Arguments de sortida predeterminats utilitzats per a diferents rols FFmpeg com detecta i registra.",
|
||||
"detect": {
|
||||
"label": "Detecta els arguments de sortida",
|
||||
"description": "Arguments de sortida predeterminats per a detectar fluxos de rol."
|
||||
},
|
||||
"record": {
|
||||
"label": "Registra els arguments de sortida",
|
||||
"description": "Arguments de sortida predeterminats per a enregistrar fluxos de rols."
|
||||
}
|
||||
},
|
||||
"retry_interval": {
|
||||
"label": "Temps de reintent del FFmpeg",
|
||||
"description": "Segons a esperar abans d'intentar tornar a connectar un flux de càmera després d'un error. Per defecte és 10."
|
||||
},
|
||||
"apple_compatibility": {
|
||||
"label": "Compatibilitat d'Apple",
|
||||
"description": "Activa l'etiquetatge HEVC per a una millor compatibilitat amb el reproductor d'Apple en gravar H.265."
|
||||
},
|
||||
"gpu": {
|
||||
"label": "Índex de GPU",
|
||||
"description": "Índex de GPU predeterminat utilitzat per a l'acceleració de maquinari si està disponible."
|
||||
},
|
||||
"inputs": {
|
||||
"label": "Entrada de la càmera",
|
||||
"description": "Llista de definicions de flux d'entrada (camins i rols) per a aquesta càmera.",
|
||||
"path": {
|
||||
"label": "Ruta d'entrada",
|
||||
"description": "URL o camí del flux d'entrada de la càmera."
|
||||
},
|
||||
"roles": {
|
||||
"label": "Rols d'entrada",
|
||||
"description": "Rols per a aquest flux d'entrada."
|
||||
},
|
||||
"global_args": {
|
||||
"label": "Arguments globals del FFmpeg",
|
||||
"description": "Arguments globals del FFmpeg per a aquest flux d'entrada."
|
||||
},
|
||||
"hwaccel_args": {
|
||||
"label": "Arguments d'acceleració del maquinari",
|
||||
"description": "Arguments d'acceleració del maquinari per a aquest flux d'entrada."
|
||||
},
|
||||
"input_args": {
|
||||
"label": "Arguments d'entrada",
|
||||
"description": "Arguments d'entrada específics d'aquest flux."
|
||||
}
|
||||
}
|
||||
},
|
||||
"live": {
|
||||
"label": "Reproducció en directe",
|
||||
"description": "Configuració utilitzada per la interfície d'usuari web per controlar la selecció, resolució i qualitat del flux en viu.",
|
||||
"streams": {
|
||||
"label": "Noms de flux en viu",
|
||||
"description": "Assignació de noms de flux configurats per a restream/go2rtc noms utilitzats per a la reproducció en viu."
|
||||
},
|
||||
"height": {
|
||||
"label": "Alçada del directe",
|
||||
"description": "Alçada (píxels) per a renderitzar el flux en viu jsmpeg a la interfície d'usuari web; ha de ser . detecta l'alçada del flux."
|
||||
},
|
||||
"quality": {
|
||||
"label": "Qualitat del directe",
|
||||
"description": "Qualitat de codificació per al flux jsmpeg (1 més alt, 31 més baix)."
|
||||
}
|
||||
},
|
||||
"lpr": {
|
||||
"label": "Reconeixement de la placa de llicència",
|
||||
"description": "Paràmetres de reconeixement de la matrícula de la llicència, inclosos els llindars de detecció, el format i les plaques conegudes.",
|
||||
"enabled": {
|
||||
"label": "Habilita el LPR",
|
||||
"description": "Activa o desactiva LPR en aquesta càmera."
|
||||
},
|
||||
"expire_time": {
|
||||
"label": "Caduca els segons",
|
||||
"description": "Temps en segons després del qual una placa no vista expira del rastrejador (només per a càmeres LPR dedicades)."
|
||||
},
|
||||
"min_area": {
|
||||
"label": "Àrea mínima de la placa",
|
||||
"description": "Àrea mínima de placa (píxels) necessària per intentar el reconeixement."
|
||||
},
|
||||
"enhancement": {
|
||||
"label": "Nivell de millora",
|
||||
"description": "Nivell de millora (0-10) per aplicar als cultius de plaques abans de l'OCR; els valors més alts no sempre poden millorar els resultats, els nivells superiors a 5 només poden funcionar amb plaques nocturnes i s'han d'utilitzar amb precaució."
|
||||
}
|
||||
},
|
||||
"motion": {
|
||||
"label": "Detecció de moviment",
|
||||
"enabled": {
|
||||
"label": "Habilita la detecció de moviment",
|
||||
"description": "Activa o desactiva la detecció de moviment d'aquesta càmera."
|
||||
},
|
||||
"description": "Configuració predeterminada de detecció de moviment per a aquesta càmera.",
|
||||
"threshold": {
|
||||
"label": "Llindar del moviment",
|
||||
"description": "Llindar de diferència de píxels utilitzat pel detector de moviment; els valors més alts redueixen la sensibilitat (interval 1-255)."
|
||||
},
|
||||
"lightning_threshold": {
|
||||
"label": "Llindar del llamp",
|
||||
"description": "Llindar per detectar i ignorar les puntes d'il·luminació breu (més baixes són més sensibles, valors entre 0,3 i 1,0)."
|
||||
},
|
||||
"improve_contrast": {
|
||||
"label": "Millora el contrast",
|
||||
"description": "Aplicar la millora del contrast als fotogrames abans de l'anàlisi del moviment per ajudar a la detecció."
|
||||
},
|
||||
"contour_area": {
|
||||
"label": "Àrea de la vora",
|
||||
"description": "Àrea mínima de contorn en píxels necessària per a comptar un contorn de moviment."
|
||||
},
|
||||
"delta_alpha": {
|
||||
"label": "Delta alfa",
|
||||
"description": "Factor de barreja alfa utilitzat en la diferència de fotogrames per al càlcul del moviment."
|
||||
},
|
||||
"frame_alpha": {
|
||||
"label": "Alfa del fotograma",
|
||||
"description": "Valor alfa utilitzat en la barreja de fotogrames per al preprocessament del moviment."
|
||||
},
|
||||
"frame_height": {
|
||||
"label": "Alçada del marc",
|
||||
"description": "Alçada en píxels per a escalar els fotogrames quan es computa el moviment."
|
||||
},
|
||||
"mask": {
|
||||
"label": "Coordenades de la màscara",
|
||||
"description": "Coordenades x,y que defineixen el polígon de màscara de moviment utilitzat per incloure/excloure àrees."
|
||||
},
|
||||
"mqtt_off_delay": {
|
||||
"label": "Retard MQTT desactivat",
|
||||
"description": "Segons a esperar després de l'última moció abans de publicar un estat MQTT 'off'."
|
||||
},
|
||||
"enabled_in_config": {
|
||||
"label": "Estat del moviment original",
|
||||
"description": "Indica si la detecció de moviment s'ha activat en la configuració estàtica original."
|
||||
},
|
||||
"raw_mask": {
|
||||
"label": "Màscara en brut"
|
||||
}
|
||||
},
|
||||
"objects": {
|
||||
"label": "Objectes",
|
||||
"description": "Object tracking defaults incloent quines etiquetes rastrejar i per objecte filtres.",
|
||||
"track": {
|
||||
"label": "Objectes a seguir",
|
||||
"description": "Llista d'etiquetes d'objectes a seguir per a aquesta càmera."
|
||||
},
|
||||
"filters": {
|
||||
"label": "Filtres d'objectes",
|
||||
"description": "Filtres aplicats als objectes detectats per reduir falsos positius (àrea, relació, confiança).",
|
||||
"min_area": {
|
||||
"label": "Àrea mínima de l'objecte",
|
||||
"description": "Es requereix una àrea de caixa contenidora mínima (píxels o percentatge) per a aquest tipus d'objecte. Pot ser píxels (int) o percentatge (float entre 0,000001 i 0.99)."
|
||||
},
|
||||
"max_area": {
|
||||
"label": "Àrea màxima de l'objecte",
|
||||
"description": "Es permet l'àrea màxima de la caixa contenidora (píxels o percentatge) per a aquest tipus d'objecte. 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": {
|
||||
"description": "Es permet la relació màxima d'amplada/alçada per a la casella contenidora a qualificar.",
|
||||
"label": "Relació màxima d'aspecte"
|
||||
},
|
||||
"threshold": {
|
||||
"label": "Llindar de confiança",
|
||||
"description": "Es requereix un llindar de confiança mitjà per a la detecció perquè l'objecte 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 comptar l'objecte."
|
||||
},
|
||||
"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"
|
||||
}
|
||||
},
|
||||
"mask": {
|
||||
"label": "Màscara d'objecte",
|
||||
"description": "Polígon de màscara utilitzat per evitar la detecció d'objectes en àrees especificades."
|
||||
},
|
||||
"raw_mask": {
|
||||
"label": "Màscara en brut"
|
||||
},
|
||||
"genai": {
|
||||
"label": "Configuració de l'objecte GenAI",
|
||||
"description": "Opcions de GenAI per descriure objectes rastrejats i enviar fotogrames per a la generació.",
|
||||
"enabled": {
|
||||
"label": "Habilita el GenAI",
|
||||
"description": "Habilita la generació de descripcions de GenAI per als objectes rastrejats de manera predeterminada."
|
||||
},
|
||||
"use_snapshot": {
|
||||
"label": "Utilitza instantànies",
|
||||
"description": "Usa instantànies d'objecte en lloc de miniatures per a la generació de descripcions de GenAI."
|
||||
},
|
||||
"prompt": {
|
||||
"label": "Indicació de la llegenda",
|
||||
"description": "Plantilla de pregunta predeterminada utilitzada en generar descripcions amb GenAI."
|
||||
},
|
||||
"object_prompts": {
|
||||
"label": "Peticions d'objecte",
|
||||
"description": "Per objecte demana personalitzar les sortides de GenAI per a etiquetes específiques."
|
||||
},
|
||||
"objects": {
|
||||
"label": "Objectes GenAI",
|
||||
"description": "Llista d'etiquetes d'objectes a enviar a GenAI per defecte."
|
||||
},
|
||||
"required_zones": {
|
||||
"label": "Zones requerides",
|
||||
"description": "Zones que s'han d'introduir perquè els objectes es puguin classificar per a la generació de descripcions de GenAI."
|
||||
},
|
||||
"debug_save_thumbnails": {
|
||||
"label": "Desa les miniatures",
|
||||
"description": "Desa les miniatures enviades a GenAI per a la depuració i la revisió."
|
||||
},
|
||||
"send_triggers": {
|
||||
"label": "Activadors de GenAI",
|
||||
"description": "Defineix quan s'han d'enviar fotogrames a GenAI (al final, després de les actualitzacions, etc.).",
|
||||
"tracked_object_end": {
|
||||
"label": "Envia al final",
|
||||
"description": "Envia una sol·licitud a GenAI quan acabi l'objecte rastrejat."
|
||||
},
|
||||
"after_significant_updates": {
|
||||
"label": "Activador de GenAI primerenc",
|
||||
"description": "Envia una sol·licitud a GenAI després d'un nombre especificat d'actualitzacions significatives per a l'objecte rastrejat."
|
||||
}
|
||||
},
|
||||
"enabled_in_config": {
|
||||
"label": "Estat original de GenAI",
|
||||
"description": "Indica si el GenAI s'ha activat a la configuració estàtica original."
|
||||
}
|
||||
}
|
||||
},
|
||||
"record": {
|
||||
"label": "Enregistrament",
|
||||
"description": "Configuració d'enregistrament i retenció d'aquesta càmera.",
|
||||
"enabled": {
|
||||
"label": "Habilita l'enregistrament",
|
||||
"description": "Activa o desactiva l'enregistrament d'aquesta càmera."
|
||||
},
|
||||
"expire_interval": {
|
||||
"label": "Interval de neteja de l'enregistrament",
|
||||
"description": "Minuts entre passades de neteja que eliminen segments d'enregistrament caducats."
|
||||
},
|
||||
"continuous": {
|
||||
"label": "Retenció contínua",
|
||||
"description": "Nombre de dies per a retenir els enregistraments independentment dels objectes rastrejats o del moviment. Establiu-ho a 0 si només voleu retenir enregistraments d'alertes i deteccions.",
|
||||
"days": {
|
||||
"label": "Dies de retenció",
|
||||
"description": "Dies per retenir enregistraments."
|
||||
}
|
||||
},
|
||||
"motion": {
|
||||
"label": "Retenció del moviment",
|
||||
"description": "Nombre de dies per a retenir els enregistraments activats pel moviment independentment dels objectes rastrejats. Establiu-ho a 0 si només voleu retenir enregistraments d'alertes i deteccions.",
|
||||
"days": {
|
||||
"label": "Dies de retenció",
|
||||
"description": "Dies per retenir enregistraments."
|
||||
}
|
||||
},
|
||||
"detections": {
|
||||
"label": "Retenció de detecció",
|
||||
"description": "Configuració de retenció de l'enregistrament per a esdeveniments de detecció, incloent-hi la durada de la captura anterior a la publicació.",
|
||||
"pre_capture": {
|
||||
"label": "Segons de precaptura",
|
||||
"description": "Nombre de segons abans de l'esdeveniment de detecció a incloure en l'enregistrament."
|
||||
},
|
||||
"post_capture": {
|
||||
"label": "Segons de postcaptura",
|
||||
"description": "Nombre de segons després de l'esdeveniment de detecció que s'inclourà a l'enregistrament."
|
||||
},
|
||||
"retain": {
|
||||
"label": "Retenció d'esdeveniments",
|
||||
"description": "Configuració de retenció per a enregistraments d'esdeveniments de detecció.",
|
||||
"days": {
|
||||
"label": "Dies de retenció",
|
||||
"description": "Nombre de dies per a retenir enregistraments d'esdeveniments de detecció."
|
||||
},
|
||||
"mode": {
|
||||
"label": "Mode de retenció",
|
||||
"description": "Mode de retenció: tot (desa tots els segments), moviment (desa els segments amb moviment), o actiuobobjectes (desa els segments amb objectes actius)."
|
||||
}
|
||||
}
|
||||
},
|
||||
"alerts": {
|
||||
"label": "Retenció d'alerta",
|
||||
"description": "Configuració de retenció de l'enregistrament per a esdeveniments d'alerta, incloses les durades de captura anteriors a la publicació.",
|
||||
"pre_capture": {
|
||||
"label": "Segons de precaptura",
|
||||
"description": "Nombre de segons abans de l'esdeveniment de detecció a incloure en l'enregistrament."
|
||||
},
|
||||
"post_capture": {
|
||||
"label": "Segons de postcaptura",
|
||||
"description": "Nombre de segons després de l'esdeveniment de detecció que s'inclourà a l'enregistrament."
|
||||
},
|
||||
"retain": {
|
||||
"label": "Retenció d'esdeveniments",
|
||||
"description": "Configuració de retenció per a enregistraments d'esdeveniments de detecció.",
|
||||
"days": {
|
||||
"label": "Dies de retenció",
|
||||
"description": "Nombre de dies per a retenir enregistraments d'esdeveniments de detecció."
|
||||
},
|
||||
"mode": {
|
||||
"label": "Mode de retenció",
|
||||
"description": "Mode de retenció: tot (desa tots els segments), moviment (desa els segments amb moviment), o actiuobobjectes (desa els segments amb objectes actius)."
|
||||
}
|
||||
}
|
||||
},
|
||||
"export": {
|
||||
"label": "Exporta la configuració",
|
||||
"description": "Paràmetres utilitzats en exportar enregistraments com el timelapse i l'acceleració del maquinari.",
|
||||
"hwaccel_args": {
|
||||
"label": "Exporta els arguments de l'hwaccel",
|
||||
"description": "Args d'acceleració de maquinari a utilitzar per a operacions d'exportació/transcodificació."
|
||||
}
|
||||
},
|
||||
"preview": {
|
||||
"label": "Configuració de la vista prèvia",
|
||||
"description": "Paràmetres que controlen la qualitat de les vistes prèvies de l'enregistrament que es mostren a la interfície d'usuari.",
|
||||
"quality": {
|
||||
"label": "Qualitat de la vista prèvia",
|
||||
"description": "Nivell de qualitat de la vista prèvia (moltlowbaix, baix, mitjà, alt, molt).alt)."
|
||||
}
|
||||
},
|
||||
"enabled_in_config": {
|
||||
"label": "Estat de l'enregistrament original",
|
||||
"description": "Indica si l'enregistrament s'ha activat en la configuració estàtica original."
|
||||
}
|
||||
},
|
||||
"review": {
|
||||
"label": "Revisió",
|
||||
"description": "Configuració que controla les alertes, les deteccions i els resums de revisió de GenAI utilitzats per la interfície d'usuari i l'emmagatzematge d'aquesta càmera.",
|
||||
"alerts": {
|
||||
"label": "Configuració d'alertes",
|
||||
"description": "Paràmetres per als quals els objectes rastrejats generen alertes i com es mantenen les alertes",
|
||||
"enabled": {
|
||||
"label": "Habilita les alertes",
|
||||
"description": "Activa o desactiva la generació d'alertes per a aquesta càmera."
|
||||
},
|
||||
"labels": {
|
||||
"label": "Etiquetes d'alerta",
|
||||
"description": "Llista d'etiquetes d'objectes que qualifiquen d'alertes (per exemple: cotxe, persona)."
|
||||
},
|
||||
"required_zones": {
|
||||
"label": "Zones requerides",
|
||||
"description": "Zones que un objecte ha d'introduir per a ser considerat una alerta; deixeu-ho buit per a permetre qualsevol zona."
|
||||
},
|
||||
"enabled_in_config": {
|
||||
"label": "Estat de les alertes originals",
|
||||
"description": "Fa un seguiment de si les alertes es van habilitar originalment a la configuració estàtica."
|
||||
},
|
||||
"cutoff_time": {
|
||||
"label": "Temps de tall d'alertes",
|
||||
"description": "Segons a esperar després de no provocar activitat d'alerta abans de tallar una alerta."
|
||||
}
|
||||
},
|
||||
"detections": {
|
||||
"label": "Configuració de les deteccions",
|
||||
"description": "Paràmetres per a crear esdeveniments de detecció (no-alerta) i quant de temps conservar-los.",
|
||||
"enabled": {
|
||||
"label": "Habilita les deteccions",
|
||||
"description": "Activa o desactiva els esdeveniments de detecció d'aquesta càmera."
|
||||
},
|
||||
"labels": {
|
||||
"label": "Etiquetes de detecció",
|
||||
"description": "Llista d'etiquetes d'objectes que es qualifiquen com a esdeveniments de detecció."
|
||||
},
|
||||
"required_zones": {
|
||||
"label": "Zones requerides",
|
||||
"description": "Zones que un objecte ha d'introduir per a ser considerat una detecció; deixeu-ho buit per a permetre qualsevol zona."
|
||||
},
|
||||
"cutoff_time": {
|
||||
"label": "Temps de tall de detecció",
|
||||
"description": "Segons a esperar després de no haver-hi activitat de detecció abans de tallar una detecció"
|
||||
},
|
||||
"enabled_in_config": {
|
||||
"label": "Estat de les deteccions originals",
|
||||
"description": "Fa un seguiment de si les deteccions es van habilitar originalment a la configuració estàtica."
|
||||
}
|
||||
},
|
||||
"genai": {
|
||||
"label": "Configuració del GenAI",
|
||||
"description": "Controla l'ús de la IA generativa per a la producció de descripcions i resums d'articles de revisió.",
|
||||
"enabled": {
|
||||
"label": "Habilita les descripcions del GenAI",
|
||||
"description": "Activa o desactiva les descripcions i resums generats per GenAI per als elements de revisió."
|
||||
},
|
||||
"alerts": {
|
||||
"label": "Habilita el GenAI per a alertes",
|
||||
"description": "Utilitzeu GenAI per a generar descripcions per als elements d'alerta."
|
||||
},
|
||||
"detections": {
|
||||
"label": "Habilita el GenAI per a les deteccions",
|
||||
"description": "Utilitzeu GenAI per generar descripcions per als elements de detecció."
|
||||
},
|
||||
"image_source": {
|
||||
"label": "Revisa l'origen de la imatge",
|
||||
"description": "Font d'imatges enviades a GenAI ('previsualització' o 'enregistraments'); 'enregistraments' utilitza Fotogrames de més qualitat però més tokens."
|
||||
},
|
||||
"additional_concerns": {
|
||||
"label": "Altres preocupacions",
|
||||
"description": "Una llista de preocupacions o notes addicionals que el GenAI ha de tenir en compte a l'hora d'avaluar l'activitat en aquesta càmera."
|
||||
},
|
||||
"debug_save_thumbnails": {
|
||||
"label": "Desa les miniatures",
|
||||
"description": "Desa les miniatures que s'envien al proveïdor GenAI per a la depuració i la revisió."
|
||||
},
|
||||
"enabled_in_config": {
|
||||
"label": "Estat original de GenAI",
|
||||
"description": "Fa un seguiment de si la revisió de GenAI es va habilitar originalment a la configuració estàtica."
|
||||
},
|
||||
"preferred_language": {
|
||||
"label": "Idioma preferit",
|
||||
"description": "Idioma preferit per sol·licitar al proveïdor GenAI respostes generades."
|
||||
},
|
||||
"activity_context_prompt": {
|
||||
"label": "Indicador de context de l'activitat",
|
||||
"description": "Pregunta personalitzada que descriu el que és i no és una activitat sospitosa per proporcionar context per als resums de GenAI."
|
||||
}
|
||||
}
|
||||
},
|
||||
"semantic_search": {
|
||||
"label": "Cerca semàntica",
|
||||
"description": "Paràmetres per a la cerca semàntica que construeix i consulta incrustacions d'objectes per a trobar elements similars.",
|
||||
"triggers": {
|
||||
"label": "Activadors",
|
||||
"description": "Accions i criteris coincidents per als desencadenants de cerca semàntica específics de la càmera.",
|
||||
"friendly_name": {
|
||||
"label": "Nom amistós",
|
||||
"description": "Nom opcional amistós que es mostra a la interfície d'usuari per a aquest activador."
|
||||
},
|
||||
"enabled": {
|
||||
"label": "Habilita aquest activador",
|
||||
"description": "Activa o desactiva aquest activador de cerca semàntica."
|
||||
},
|
||||
"type": {
|
||||
"label": "Tipus d'activador",
|
||||
"description": "Tipus d'activador: «miniatures» (match contra imatge) o «descripció» (match contra text)."
|
||||
},
|
||||
"data": {
|
||||
"label": "Contingut del disparador",
|
||||
"description": "Frase de text o ID de miniatures per a coincidir amb els objectes rastrejats."
|
||||
},
|
||||
"threshold": {
|
||||
"label": "Llindar d'activació",
|
||||
"description": "Puntuació mínima de similitud (0-1) necessària per activar aquest activador."
|
||||
},
|
||||
"actions": {
|
||||
"label": "Accions d'activació",
|
||||
"description": "Llista d'accions a executar quan coincideixi l'activador (notificació, sublabeletiqueta, atribut)."
|
||||
}
|
||||
}
|
||||
},
|
||||
"snapshots": {
|
||||
"label": "Instantànies",
|
||||
"description": "Configuració per a les instantànies JPEG desades dels objectes seguits per a aquesta càmera.",
|
||||
"enabled": {
|
||||
"label": "Instantànies habilitades",
|
||||
"description": "Activa o desactiva el desament de les instantànies d'aquesta càmera."
|
||||
},
|
||||
"clean_copy": {
|
||||
"label": "Desa la còpia neta",
|
||||
"description": "Desa una còpia neta no anotada de les instantànies a més de les anotades."
|
||||
},
|
||||
"timestamp": {
|
||||
"label": "Superposició de marca horària",
|
||||
"description": "Superposa una marca horària a les instantànies desades."
|
||||
},
|
||||
"bounding_box": {
|
||||
"label": "Superposició de la caixa contenidora",
|
||||
"description": "Dibuixa caixes contenidores per als objectes seguits en les instantànies desades."
|
||||
},
|
||||
"crop": {
|
||||
"label": "Retalla la instantània",
|
||||
"description": "Retalla les instantànies desades a la caixa contenidora de l'objecte detectat."
|
||||
},
|
||||
"required_zones": {
|
||||
"label": "Zones requerides",
|
||||
"description": "Zones que ha d'introduir un objecte perquè es desi una instantània."
|
||||
},
|
||||
"height": {
|
||||
"label": "Alçada de la instantània",
|
||||
"description": "Alçada (píxels) per a canviar la mida de les instantànies desades; deixeu-ho buit per a preservar la mida original."
|
||||
},
|
||||
"retain": {
|
||||
"label": "Retenció de la instantània",
|
||||
"description": "Paràmetres de retenció per a les instantànies desades, inclosos els dies predeterminats i les anul·lacions per objecte.",
|
||||
"default": {
|
||||
"label": "Retenció predeterminada",
|
||||
"description": "Nombre predeterminat de dies per a retenir les instantànies."
|
||||
},
|
||||
"mode": {
|
||||
"label": "Mode de retenció",
|
||||
"description": "Mode de retenció: tot (desa tots els segments), moviment (desa els segments amb moviment), o actiuobobjectes (desa els segments amb objectes actius)."
|
||||
},
|
||||
"objects": {
|
||||
"label": "Retenció d'objectes",
|
||||
"description": "Anul·lació per objecte per dies de retenció d'instantànies."
|
||||
}
|
||||
},
|
||||
"quality": {
|
||||
"label": "Qualitat JPEG",
|
||||
"description": "Qualitat del codi JPEG per a les instantànies desades (0-100)."
|
||||
}
|
||||
},
|
||||
"timestamp_style": {
|
||||
"label": "Estil de la marca horària",
|
||||
"description": "Opcions d'estilització per a marques de temps d'alimentació aplicades a enregistraments i instantànies.",
|
||||
"position": {
|
||||
"label": "Posició de la marca horària",
|
||||
"description": "Posició de la marca horària a la imatge (tl/tr/bl/br)."
|
||||
},
|
||||
"format": {
|
||||
"label": "Format de la marca horària",
|
||||
"description": "Cadena de format de data i hora utilitzada per a marques horàries (codis de format de data i hora de Python)."
|
||||
},
|
||||
"color": {
|
||||
"label": "Color de la marca horària",
|
||||
"description": "Valors de color RGB per al text de la marca de temps (tots els valors 0-255).",
|
||||
"red": {
|
||||
"label": "Vermell",
|
||||
"description": "Component vermell (0-255) per al color de la marca horària."
|
||||
},
|
||||
"green": {
|
||||
"label": "Verd",
|
||||
"description": "Component verd (0-255) per al color de la marca horària."
|
||||
},
|
||||
"blue": {
|
||||
"label": "Blau",
|
||||
"description": "Component blau (0-255) per al color de la marca horària."
|
||||
}
|
||||
},
|
||||
"thickness": {
|
||||
"label": "Gruix de la marca de temps",
|
||||
"description": "Gruix de la línia del text de la marca de temps."
|
||||
},
|
||||
"effect": {
|
||||
"label": "Efecte de marca horària",
|
||||
"description": "Efecte visual per al text de la marca de temps (cap, sòlid, ombra)."
|
||||
}
|
||||
},
|
||||
"best_image_timeout": {
|
||||
"label": "Temps d'espera de la millor imatge",
|
||||
"description": "Quant de temps s'espera per a la imatge amb la puntuació de confiança més alta."
|
||||
},
|
||||
"mqtt": {
|
||||
"label": "MQTT",
|
||||
"description": "Configuració de la publicació d'imatges MQTT.",
|
||||
"enabled": {
|
||||
"label": "Envia la imatge",
|
||||
"description": "Habilita la publicació d'instantànies d'imatges per a objectes als temes MQTT d'aquesta càmera."
|
||||
},
|
||||
"timestamp": {
|
||||
"label": "Afegeix una marca horària",
|
||||
"description": "Superposa una marca horària a les imatges publicades a MQTT."
|
||||
},
|
||||
"bounding_box": {
|
||||
"label": "Afegeix el quadre de delimitació",
|
||||
"description": "Dibuixa caixes delimitadores en imatges publicades sobre MQTT."
|
||||
},
|
||||
"crop": {
|
||||
"label": "Retalla la imatge",
|
||||
"description": "Retalla les imatges publicades a MQTT a la caixa contenidora de l'objecte detectat."
|
||||
},
|
||||
"height": {
|
||||
"label": "Alçada de la imatge",
|
||||
"description": "Alçada (píxels) per a canviar la mida de les imatges publicades sobre MQTT."
|
||||
},
|
||||
"required_zones": {
|
||||
"label": "Zones requerides",
|
||||
"description": "Zones que ha d'introduir un objecte perquè es publiqui una imatge MQTT."
|
||||
},
|
||||
"quality": {
|
||||
"label": "Qualitat JPEG",
|
||||
"description": "Qualitat JPEG per a les imatges publicades a MQTT (0-100)."
|
||||
}
|
||||
},
|
||||
"notifications": {
|
||||
"label": "Notificacions",
|
||||
"description": "Configuració per a habilitar i controlar les notificacions d'aquesta càmera.",
|
||||
"enabled": {
|
||||
"label": "Habilita les notificacions",
|
||||
"description": "Activa o desactiva les notificacions d'aquesta càmera."
|
||||
},
|
||||
"email": {
|
||||
"label": "Correu electrònic de notificació",
|
||||
"description": "Adreça de correu electrònic utilitzada per a notificacions push o requerides per determinats proveïdors de notificacions."
|
||||
},
|
||||
"cooldown": {
|
||||
"label": "Període de reducció",
|
||||
"description": "Retirada (segons) entre notificacions per evitar els destinataris de correu brossa."
|
||||
},
|
||||
"enabled_in_config": {
|
||||
"label": "Estat de les notificacions originals",
|
||||
"description": "Indica si les notificacions s'han activat en la configuració estàtica original."
|
||||
}
|
||||
},
|
||||
"onvif": {
|
||||
"label": "ONVIF",
|
||||
"description": "Connexió ONVIF i configuració de seguiment automàtic PTZ per a aquesta càmera.",
|
||||
"host": {
|
||||
"label": "Servidor ONVIF",
|
||||
"description": "Host (i esquema opcional) per al servei ONVIF per a aquesta càmera."
|
||||
},
|
||||
"port": {
|
||||
"label": "Port ONVIF",
|
||||
"description": "Número de port del servei ONVIF."
|
||||
},
|
||||
"user": {
|
||||
"label": "Nom d'usuari ONVIF",
|
||||
"description": "Nom d'usuari per a l'autenticació ONVIF; alguns dispositius requereixen l'usuari administrador per a ONVIF."
|
||||
},
|
||||
"password": {
|
||||
"label": "Contrasenya ONVIF",
|
||||
"description": "Contrasenya per a l'autenticació ONVIF."
|
||||
},
|
||||
"tls_insecure": {
|
||||
"label": "Inhabilita la verificació TLS",
|
||||
"description": "Omet la verificació TLS i desactiva l'autenticació de resum per a ONVIF (no segur; només s'utilitza en xarxes segures)."
|
||||
},
|
||||
"autotracking": {
|
||||
"label": "Aeguiment automàtic",
|
||||
"description": "Segueix automàticament els objectes en moviment i els manté centrats en el marc utilitzant els moviments de la càmera PTZ.",
|
||||
"enabled": {
|
||||
"label": "Habilita el seguiment automàtic",
|
||||
"description": "Activa o desactiva el seguiment automàtic de la càmera PTZ dels objectes detectats."
|
||||
},
|
||||
"calibrate_on_startup": {
|
||||
"label": "Calibra a l'inici",
|
||||
"description": "Mesura les velocitats del motor PTZ a l'inici per millorar la precisió del seguiment. Frigate actualitzarà la configuració amb «movtion.weights» després del calibratge."
|
||||
},
|
||||
"zooming": {
|
||||
"label": "Mode de zoom",
|
||||
"description": "Comportament de zoom de control: desactivat (només pan/tilt), absolut (més compatible) o relatiu (pa/tilt/zoom concurrent)."
|
||||
},
|
||||
"zoom_factor": {
|
||||
"label": "Factor de zoom",
|
||||
"description": "Controla el nivell d'ampliació dels objectes rastrejats. Els valors més baixos mantenen més escena a la vista; els valors més alts s'apropen, però poden perdre el seguiment. Valors entre 0,1 i 0,75."
|
||||
},
|
||||
"track": {
|
||||
"label": "Objectes rastrejats",
|
||||
"description": "Llista de tipus d'objectes que haurien d'activar el seguiment automàtic."
|
||||
},
|
||||
"required_zones": {
|
||||
"label": "Zones requerides",
|
||||
"description": "Els objectes han d'entrar en una d'aquestes zones abans que comenci el seguiment automàtic."
|
||||
},
|
||||
"return_preset": {
|
||||
"label": "Retorna la predefinició",
|
||||
"description": "Nom predefinit ONVIF configurat al microprogramari de la càmera per tornar després de finalitzar el seguiment."
|
||||
},
|
||||
"timeout": {
|
||||
"label": "Temps d'espera de retorn",
|
||||
"description": "Espereu tants segons després de perdre el seguiment abans de tornar la càmera a la posició preestablerta."
|
||||
},
|
||||
"movement_weights": {
|
||||
"label": "Pes del moviment",
|
||||
"description": "Valors de calibratge generats automàticament pel calibratge de la càmera. No modifiquis manualment."
|
||||
},
|
||||
"enabled_in_config": {
|
||||
"label": "Estat de la pista automàtica original",
|
||||
"description": "Camp intern per a fer el seguiment de si s'ha habilitat el seguiment automàtic a la configuració."
|
||||
}
|
||||
},
|
||||
"ignore_time_mismatch": {
|
||||
"label": "Ignora el desajust de temps",
|
||||
"description": "Ignora les diferències de sincronització de temps entre càmera i servidor Frigate per a la comunicació ONVIF."
|
||||
}
|
||||
},
|
||||
"type": {
|
||||
"label": "Tipus de càmera",
|
||||
"description": "Tipus de càmera"
|
||||
},
|
||||
"ui": {
|
||||
"label": "Interfície d'usuari de la càmera",
|
||||
"description": "Mostra l'ordre i la visibilitat d'aquesta càmera a la interfície d'usuari. La comanda afecta el tauler predeterminat. Per a un control més granular, utilitzeu grups de càmera.",
|
||||
"order": {
|
||||
"label": "Ordre de la interfície",
|
||||
"description": "Ordre numèric utilitzat per ordenar la càmera a la interfície d'usuari (taulell de control i llistes per defecte); els nombres més grans apareixen més tard."
|
||||
},
|
||||
"dashboard": {
|
||||
"label": "Mostra a l'interfície d'usuari",
|
||||
"description": "Estableix si aquesta càmera és visible a tot arreu a la interfície d'usuari de la Frigate. Desactivar això requerirà editar manualment la configuració per tornar a veure aquesta càmera a la interfície d'usuari."
|
||||
}
|
||||
},
|
||||
"webui_url": {
|
||||
"label": "URL de la càmera",
|
||||
"description": "URL per visitar la càmera directament des de la pàgina del sistema"
|
||||
},
|
||||
"zones": {
|
||||
"label": "Zones",
|
||||
"description": "Les zones permeten definir una àrea específica del marc perquè pugueu determinar si un objecte es troba dins d'una àrea determinada.",
|
||||
"friendly_name": {
|
||||
"label": "Nom de la zona",
|
||||
"description": "Un nom fàcil d'utilitzar per a la zona, que es mostra a la interfície d'usuari de la fragata. Si no s'estableix, s'utilitzarà una versió amb format del nom de la zona."
|
||||
},
|
||||
"enabled": {
|
||||
"label": "Si aquesta zona està activa. Les zones inhabilitades s'ignoren en temps d'execució."
|
||||
},
|
||||
"enabled_in_config": {
|
||||
"label": "Feu un seguiment de l'estat original de la zona."
|
||||
},
|
||||
"filters": {
|
||||
"label": "Filtres de zona",
|
||||
"description": "Filtres que s'aplicaran als objectes d'aquesta zona. S'utilitza per reduir falsos positius o restringir quins objectes es consideren presents a la zona.",
|
||||
"min_area": {
|
||||
"label": "Àrea mínima de l'objecte",
|
||||
"description": "Es requereix una àrea de caixa contenidora mínima (píxels o percentatge) per a aquest tipus d'objecte. Pot ser píxels (int) o percentatge (float entre 0,000001 i 0.99)."
|
||||
},
|
||||
"max_area": {
|
||||
"label": "Àrea màxima de l'objecte",
|
||||
"description": "Es permet l'àrea màxima de la caixa contenidora (píxels o percentatge) per a aquest tipus d'objecte. 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'objecte 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 comptar l'objecte."
|
||||
},
|
||||
"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"
|
||||
}
|
||||
},
|
||||
"objects": {
|
||||
"description": "Llista de tipus d'objectes (des del mapa d'etiquetes) que poden activar aquesta zona. Pot ser una cadena o una llista de cadenes. Si està buit, es consideraran tots els objectes.",
|
||||
"label": "Objectes d'activació"
|
||||
},
|
||||
"coordinates": {
|
||||
"label": "Coordenades",
|
||||
"description": "Coordenades de polígon que defineixen l'àrea de zona. Pot ser una cadena separada per comes o una llista de cadenes de coordenades. Les coordenades han de ser relatives (0-1) o absolutes (antic)."
|
||||
},
|
||||
"distances": {
|
||||
"label": "Distàncies del món real",
|
||||
"description": "Distàncies opcionals del món real per a cada costat del quadrilàter de la zona, utilitzades per a càlculs de velocitat o distància. Si s'estableix, ha de tenir exactament 4 valors."
|
||||
},
|
||||
"inertia": {
|
||||
"label": "Fotogrames d'inèrcia",
|
||||
"description": "Nombre de fotogrames consecutius que s'ha de detectar un objecte a la zona abans de considerar-lo present. Ajuda a filtrar les deteccions transitòries."
|
||||
},
|
||||
"loitering_time": {
|
||||
"label": "Segons flotants",
|
||||
"description": "Nombre de segons que un objecte ha de romandre a la zona a considerar com a errant. Establiu-ho a 0 per a desactivar la detecció de la itinerància."
|
||||
},
|
||||
"speed_threshold": {
|
||||
"label": "Velocitat mínima",
|
||||
"description": "Velocitat mínima (en unitats del món real si s'estableixen distàncies) necessària perquè un objecte es consideri present a la zona. S'utilitza per a activadors de zona basats en velocitat."
|
||||
}
|
||||
},
|
||||
"enabled_in_config": {
|
||||
"label": "Estat original de la càmera",
|
||||
"description": "Feu un seguiment de l'estat original de la càmera."
|
||||
}
|
||||
}
|
||||
2188
web/public/locales/ca/config/global.json
Normal file
2188
web/public/locales/ca/config/global.json
Normal file
File diff suppressed because it is too large
Load Diff
73
web/public/locales/ca/config/groups.json
Normal file
73
web/public/locales/ca/config/groups.json
Normal file
@ -0,0 +1,73 @@
|
||||
{
|
||||
"audio": {
|
||||
"global": {
|
||||
"detection": "Detecció global",
|
||||
"sensitivity": "Sensibilitat global"
|
||||
},
|
||||
"cameras": {
|
||||
"detection": "Detecció",
|
||||
"sensitivity": "Sensibilitat"
|
||||
}
|
||||
},
|
||||
"timestamp_style": {
|
||||
"global": {
|
||||
"appearance": "Aparença global"
|
||||
},
|
||||
"cameras": {
|
||||
"appearance": "Aparença"
|
||||
}
|
||||
},
|
||||
"motion": {
|
||||
"global": {
|
||||
"sensitivity": "Sensibilitat global",
|
||||
"algorithm": "Algorisme global"
|
||||
},
|
||||
"cameras": {
|
||||
"sensitivity": "Sensibilitat",
|
||||
"algorithm": "Algorisme"
|
||||
}
|
||||
},
|
||||
"snapshots": {
|
||||
"global": {
|
||||
"display": "Visualització global"
|
||||
},
|
||||
"cameras": {
|
||||
"display": "Mostra"
|
||||
}
|
||||
},
|
||||
"detect": {
|
||||
"global": {
|
||||
"resolution": "Resolució global",
|
||||
"tracking": "Seguiment global"
|
||||
},
|
||||
"cameras": {
|
||||
"resolution": "Resolució",
|
||||
"tracking": "Seguiment"
|
||||
}
|
||||
},
|
||||
"objects": {
|
||||
"global": {
|
||||
"tracking": "Seguiment global",
|
||||
"filtering": "Filtratge global"
|
||||
},
|
||||
"cameras": {
|
||||
"tracking": "Seguiment",
|
||||
"filtering": "Filtra"
|
||||
}
|
||||
},
|
||||
"record": {
|
||||
"global": {
|
||||
"retention": "Retenció global",
|
||||
"events": "Esdeveniments globals"
|
||||
},
|
||||
"cameras": {
|
||||
"retention": "Retenció",
|
||||
"events": "Esdeveniment"
|
||||
}
|
||||
},
|
||||
"ffmpeg": {
|
||||
"cameras": {
|
||||
"cameraFfmpeg": "Arguments específics del FFmpeg"
|
||||
}
|
||||
}
|
||||
}
|
||||
32
web/public/locales/ca/config/validation.json
Normal file
32
web/public/locales/ca/config/validation.json
Normal file
@ -0,0 +1,32 @@
|
||||
{
|
||||
"minimum": "Ha de ser com a mínim {{limit}}",
|
||||
"maximum": "Ha de ser com a màxim {{limit}}",
|
||||
"exclusiveMinimum": "Ha de ser més gran que {{limit}}",
|
||||
"exclusiveMaximum": "Ha de ser inferior a {{limit}}",
|
||||
"minLength": "Ha de tenir com a mínim {{limit}} caràcters",
|
||||
"maxLength": "Ha de tenir com a màxim {{limit}} caràcters",
|
||||
"minItems": "Ha de tenir com a mínim {{limit}} elements",
|
||||
"maxItems": "Ha de tenir com a màxim {{limit}} elements",
|
||||
"pattern": "Format no vàlid",
|
||||
"required": "Aquest camp és obligatori",
|
||||
"type": "Tipus de valor no vàlid",
|
||||
"enum": "Ha de ser un dels valors permesos",
|
||||
"const": "El valor no coincideix amb la constant esperada",
|
||||
"uniqueItems": "Tots els elements han de ser únics",
|
||||
"format": "Format no vàlid",
|
||||
"additionalProperties": "No es permet la propietat desconeguda",
|
||||
"oneOf": "Ha de coincidir exactament amb un dels esquemes permesos",
|
||||
"anyOf": "Ha de coincidir almenys amb un dels esquemes permesos",
|
||||
"proxy": {
|
||||
"header_map": {
|
||||
"roleHeaderRequired": "Es requereix la capçalera del rol quan es configuren els mapes de rols."
|
||||
}
|
||||
},
|
||||
"ffmpeg": {
|
||||
"inputs": {
|
||||
"rolesUnique": "Cada rol només es pot assignar a un flux d'entrada.",
|
||||
"detectRequired": "Almenys un flux d'entrada ha de tenir assignat el rol «detecta».",
|
||||
"hwaccelDetectOnly": "Només el flux d'entrada amb el rol detect pot definir arguments d'acceleració del maquinari."
|
||||
}
|
||||
}
|
||||
}
|
||||
@ -39,7 +39,7 @@
|
||||
"surfboard": "Taula de surf",
|
||||
"tennis_racket": "Raqueta de tenis",
|
||||
"bottle": "Ampolla",
|
||||
"plate": "Placa",
|
||||
"plate": "Matrícula",
|
||||
"wine_glass": "Got de vi",
|
||||
"cup": "Copa",
|
||||
"fork": "Forquilla",
|
||||
|
||||
@ -27,7 +27,7 @@
|
||||
"deleteImageFailed": "No s'ha pogut suprimir: {{errorMessage}}",
|
||||
"deleteCategoryFailed": "No s'ha pogut suprimir la classe: {{errorMessage}}",
|
||||
"categorizeFailed": "No s'ha pogut categoritzar la imatge: {{errorMessage}}",
|
||||
"trainingFailed": "Ha fallat l'entrenament del model. Comproveu els registres de fragata per a més detalls.",
|
||||
"trainingFailed": "Ha fallat l'entrenament del model. Comproveu els registres de Frigate per a més detalls.",
|
||||
"deleteModelFailed": "No s'ha pogut suprimir el model: {{errorMessage}}",
|
||||
"updateModelFailed": "No s'ha pogut actualitzar el model: {{errorMessage}}",
|
||||
"renameCategoryFailed": "No s'ha pogut canviar el nom de la classe: {{errorMessage}}",
|
||||
|
||||
@ -234,6 +234,10 @@
|
||||
"downloadCleanSnapshot": {
|
||||
"label": "Descarrega la instantània neta",
|
||||
"aria": "Descarrega la instantània neta"
|
||||
},
|
||||
"debugReplay": {
|
||||
"label": "Depura la repetició",
|
||||
"aria": "Mostra aquest objecte rastrejat a la vista de reproducció de depuració"
|
||||
}
|
||||
},
|
||||
"noTrackedObjects": "No s'han trobat objectes rastrejats",
|
||||
@ -285,7 +289,7 @@
|
||||
"title": "Configuració d'anotacions",
|
||||
"showAllZones": {
|
||||
"title": "Mostra totes les Zones",
|
||||
"desc": "Mostra sempre les zones amb marcs on els objectes hagin entrat a la zona."
|
||||
"desc": "Mostra sempre les zones amb fotogrames on els objectes hagin entrat a la zona."
|
||||
},
|
||||
"offset": {
|
||||
"label": "Òfset d'Anotació",
|
||||
|
||||
@ -11,13 +11,27 @@
|
||||
},
|
||||
"toast": {
|
||||
"error": {
|
||||
"renameExportFailed": "Error al canviar el nom de l’exportació: {{errorMessage}}"
|
||||
"renameExportFailed": "Error al canviar el nom de l’exportació: {{errorMessage}}",
|
||||
"assignCaseFailed": "No s'ha pogut actualitzar l'assignació de cas:{{errorMessage}}"
|
||||
}
|
||||
},
|
||||
"tooltip": {
|
||||
"shareExport": "Comparteix l'exportació",
|
||||
"downloadVideo": "Baixa el vídeo",
|
||||
"editName": "Edita el nom",
|
||||
"deleteExport": "Suprimeix l'exportació"
|
||||
"deleteExport": "Suprimeix l'exportació",
|
||||
"assignToCase": "Afegeix al cas"
|
||||
},
|
||||
"headings": {
|
||||
"cases": "Casos",
|
||||
"uncategorizedExports": "Exportacions sense categoria"
|
||||
},
|
||||
"caseDialog": {
|
||||
"title": "Afegeix al cas",
|
||||
"description": "Trieu un cas existent o creeu-ne un de nou.",
|
||||
"selectLabel": "Cas",
|
||||
"newCaseOption": "Crea un cas nou",
|
||||
"nameLabel": "Nom del cas",
|
||||
"descriptionLabel": "Descripció"
|
||||
}
|
||||
}
|
||||
|
||||
@ -7,17 +7,20 @@
|
||||
"authentication": "Configuració d'autenticació - Frigate",
|
||||
"camera": "Paràmetres de càmera - Frigate",
|
||||
"masksAndZones": "Editor de màscares i zones - Frigate",
|
||||
"general": "Configuració de la interfície d'usuari - Fragata",
|
||||
"general": "Configuració del perfil - Frigate",
|
||||
"frigatePlus": "Paràmetres de Frigate+ - Frigate",
|
||||
"notifications": "Paràmetres de notificació - Frigate",
|
||||
"cameraManagement": "Gestionar càmeres - Frigate",
|
||||
"cameraReview": "Configuració Revisió de Càmeres - Frigate"
|
||||
"cameraReview": "Configuració Revisió de Càmeres - Frigate",
|
||||
"globalConfig": "Configuració global - Frigate",
|
||||
"cameraConfig": "Configuració de la càmera - Frigate",
|
||||
"maintenance": "Manteniment - Frigate"
|
||||
},
|
||||
"menu": {
|
||||
"ui": "Interfície d'usuari",
|
||||
"cameras": "Paràmetres de la càmera",
|
||||
"masksAndZones": "Màscares / Zones",
|
||||
"motionTuner": "Ajust de detecció de moviment",
|
||||
"motionTuner": "Afinador de moviment",
|
||||
"users": "Usuaris",
|
||||
"notifications": "Notificacions",
|
||||
"debug": "Depuració",
|
||||
@ -26,7 +29,62 @@
|
||||
"triggers": "Disparadors",
|
||||
"cameraManagement": "Gestió",
|
||||
"cameraReview": "Revisió",
|
||||
"roles": "Rols"
|
||||
"roles": "Rols",
|
||||
"general": "General",
|
||||
"globalConfig": "Configuració global",
|
||||
"system": "Sistema",
|
||||
"integrations": "Integracions",
|
||||
"profileSettings": "Configuració del perfil",
|
||||
"globalDetect": "Detecció d'objectes",
|
||||
"globalRecording": "Enregistrament",
|
||||
"globalSnapshots": "Instantànies",
|
||||
"globalFfmpeg": "FFmpeg",
|
||||
"globalMotion": "Detecció de moviment",
|
||||
"globalObjects": "Objectes",
|
||||
"globalReview": "Revisió",
|
||||
"globalAudioEvents": "Esdeveniments d'àudio",
|
||||
"globalLivePlayback": "Reproducció en directe",
|
||||
"globalTimestampStyle": "Estil de la marca horària",
|
||||
"systemDatabase": "Base de dades",
|
||||
"systemTls": "TLS",
|
||||
"systemAuthentication": "Autenticació",
|
||||
"systemNetworking": "Xarxa",
|
||||
"systemProxy": "Proxy",
|
||||
"systemUi": "UI",
|
||||
"systemLogging": "Registre",
|
||||
"systemEnvironmentVariables": "Variables d'entorn",
|
||||
"systemTelemetry": "Telemetria",
|
||||
"systemBirdseye": "Birdseye",
|
||||
"systemFfmpeg": "FFmpeg",
|
||||
"systemDetectorHardware": "Hardware del detector",
|
||||
"systemDetectionModel": "Model de detecció",
|
||||
"systemMqtt": "MQTT",
|
||||
"integrationSemanticSearch": "Cerca semàntica",
|
||||
"integrationGenerativeAi": "IA generativa",
|
||||
"integrationFaceRecognition": "Reconeixement de cares",
|
||||
"integrationLpr": "Reconeixement de la matrícula",
|
||||
"integrationObjectClassification": "Classificació de l'objecte",
|
||||
"integrationAudioTranscription": "Transcripció d'àudio",
|
||||
"cameraDetect": "Detecció d'objectes",
|
||||
"cameraFfmpeg": "FFmpeg",
|
||||
"cameraRecording": "Enregistrament",
|
||||
"cameraSnapshots": "Instantànies",
|
||||
"cameraMotion": "Detecció de moviment",
|
||||
"cameraObjects": "Objectes",
|
||||
"cameraConfigReview": "Revisió",
|
||||
"cameraAudioEvents": "Esdeveniments d'àudio",
|
||||
"cameraAudioTranscription": "Transcripció d'àudio",
|
||||
"cameraNotifications": "Notificacions",
|
||||
"cameraLivePlayback": "Reproducció en directe",
|
||||
"cameraBirdseye": "Birdseye",
|
||||
"cameraFaceRecognition": "Reconeixement de cares",
|
||||
"cameraLpr": "Reconeixement de la matrícula",
|
||||
"cameraMqttConfig": "MQTT",
|
||||
"cameraOnvif": "ONVIF",
|
||||
"cameraUi": "UI de la càmera",
|
||||
"cameraTimestampStyle": "Estil de la marca horària",
|
||||
"cameraMqtt": "Càmera MQTT",
|
||||
"maintenance": "Manteniment"
|
||||
},
|
||||
"dialog": {
|
||||
"unsavedChanges": {
|
||||
@ -39,7 +97,7 @@
|
||||
"noCamera": "Cap càmera"
|
||||
},
|
||||
"general": {
|
||||
"title": "Paràmetres de la interfície d'usuari",
|
||||
"title": "Configuració del perfil",
|
||||
"liveDashboard": {
|
||||
"title": "Panell en directe",
|
||||
"automaticLiveView": {
|
||||
@ -205,6 +263,10 @@
|
||||
"clickDrawPolygon": "Fes click per a dibuixar un polígon a la imatge.",
|
||||
"toast": {
|
||||
"success": "S'ha desat la zona ({{zoneName}})."
|
||||
},
|
||||
"enabled": {
|
||||
"title": "Habilitat",
|
||||
"description": "Si aquesta zona està activa i activada al fitxer de configuració. Si està desactivat, no pot ser habilitat per MQTT. Les zones inhabilitades s'ignoren en temps d'execució."
|
||||
}
|
||||
},
|
||||
"filter": {
|
||||
@ -237,6 +299,12 @@
|
||||
"title": "{{polygonName}} s'ha desat.",
|
||||
"noName": "La màscara de moviment ha estat desada."
|
||||
}
|
||||
},
|
||||
"defaultName": "Màscara de moviment {{number}}",
|
||||
"name": {
|
||||
"title": "Nom",
|
||||
"description": "Un nom opcional per a aquesta màscara de moviment.",
|
||||
"placeholder": "Introduïu un nom..."
|
||||
}
|
||||
},
|
||||
"objectMasks": {
|
||||
@ -263,11 +331,16 @@
|
||||
"noName": "La màscara d'objectes ha estat desada."
|
||||
}
|
||||
},
|
||||
"context": "Les màscares de filtratge d’objectes s’utilitzen per descartar falsos positius d’un tipus d’objecte concret segons la seva ubicació."
|
||||
"context": "Les màscares de filtratge d’objectes s’utilitzen per descartar falsos positius d’un tipus d’objecte concret segons la seva ubicació.",
|
||||
"name": {
|
||||
"title": "Nom",
|
||||
"description": "Un nom opcional per a aquesta màscara d'objecte.",
|
||||
"placeholder": "Introduïu un nom..."
|
||||
}
|
||||
},
|
||||
"restart_required": "Reinici necessari (canvi de màscares o zones)",
|
||||
"motionMaskLabel": "Màscara de moviment {{number}}",
|
||||
"objectMaskLabel": "Màscara d'objecte {{number}} ({{label}})",
|
||||
"objectMaskLabel": "Màscara d'objecte {{number}}",
|
||||
"toast": {
|
||||
"success": {
|
||||
"copyCoordinates": "S'han copiat les coordenades per a {{polyName}} al porta-retalls."
|
||||
@ -275,6 +348,13 @@
|
||||
"error": {
|
||||
"copyCoordinatesFailed": "No s'han pogut copiar les coordenades al porta-retalls."
|
||||
}
|
||||
},
|
||||
"disabledInConfig": "L'element està desactivat al fitxer de configuració",
|
||||
"masks": {
|
||||
"enabled": {
|
||||
"title": "Habilitat",
|
||||
"description": "Si aquesta màscara està activada al fitxer de configuració. Si està desactivat, no pot ser habilitat per MQTT. Les màscares desactivades s'ignoren en temps d'execució."
|
||||
}
|
||||
}
|
||||
},
|
||||
"notification": {
|
||||
@ -644,7 +724,14 @@
|
||||
"error": "No s'han pogut guardar els canvis de configuració: {{errorMessage}}",
|
||||
"success": "Els paràmetres de Frigate+ han estat desats. Reincia Frigate per aplicar els canvis."
|
||||
},
|
||||
"restart_required": "Es necessari un reinici (El model de Frigate+ ha cambiat)"
|
||||
"restart_required": "Es necessari un reinici (El model de Frigate+ ha cambiat)",
|
||||
"description": "Frigate+ és un servei de subscripció que proporciona accés a funcions i capacitats addicionals per a la vostra instància de Frigate, inclosa la capacitat d'utilitzar models de detecció d'objectes personalitzats entrenats en les vostres pròpies dades. Podeu gestionar la configuració del model Frigate+ aquí.",
|
||||
"cardTitles": {
|
||||
"api": "API",
|
||||
"currentModel": "Model actual",
|
||||
"otherModels": "Altres models",
|
||||
"configuration": "Configuració"
|
||||
}
|
||||
},
|
||||
"enrichments": {
|
||||
"semanticSearch": {
|
||||
@ -665,7 +752,7 @@
|
||||
"success": "La reindexació ha començat amb èxit.",
|
||||
"label": "Reindexar ara",
|
||||
"confirmTitle": "Confirmar la reindexació",
|
||||
"desc": "La reindexació regenerarà les incrustacions (embeddings) de tots els objectes seguits. Aquest procés s’executa en segon pla i pot arribar a saturar la CPU, així com trigar una bona estona depenent del nombre d’objectes seguits que tinguis.",
|
||||
"desc": "La reindexació regenerarà les incrustacions per a tots els objectes rastrejats. Aquest procés s'executa en segon pla i pot treure el màxim de la CPU i prendre una quantitat de temps raonable depenent del nombre d'objectes rastrejats que tingueu.",
|
||||
"confirmDesc": "Estàs segur que vols reindexar totes les incrustacions (embeddings) dels objectes seguits? Aquest procés s’executarà en segon pla, però pot arribar a saturar la CPU i trigar bastant temps. Pots seguir-ne el progrés a la pàgina d’Explora.",
|
||||
"alreadyInProgress": "La reindexació ja està en curs.",
|
||||
"error": "Error en iniciar la reindexació: {{errorMessage}}"
|
||||
@ -1181,7 +1268,12 @@
|
||||
"backToSettings": "Torna a la configuració de la càmera",
|
||||
"streams": {
|
||||
"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>"
|
||||
"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>",
|
||||
"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."
|
||||
},
|
||||
"cameraConfig": {
|
||||
"add": "Afegeix una càmera",
|
||||
@ -1236,7 +1328,7 @@
|
||||
"selectDetectionsZones": "Selecció de zones per a les deteccions",
|
||||
"limitDetections": "Limita les deteccions a zones específiques",
|
||||
"toast": {
|
||||
"success": "S'ha desat la configuració de la classificació de la revisió. Reinicia la fragata per aplicar canvis."
|
||||
"success": "S'ha desat la configuració de la classificació de la revisió. Reinicia Frigate per aplicar canvis."
|
||||
},
|
||||
"unsavedChanges": "Paràmetres de classificació de revisions sense desar per {{camera}}",
|
||||
"objectAlertsTips": "Totes els objectes {{alertsLabels}} de {{cameraName}} es mostraran com avisos.",
|
||||
@ -1249,5 +1341,255 @@
|
||||
}
|
||||
},
|
||||
"title": "Paràmetres de Revisió de la Càmera"
|
||||
}
|
||||
},
|
||||
"saveAllPreview": {
|
||||
"title": "Canvis a desar",
|
||||
"triggerLabel": "Revisa els canvis pendents",
|
||||
"empty": "No hi ha canvis pendents.",
|
||||
"scope": {
|
||||
"label": "Àmbit",
|
||||
"global": "Global",
|
||||
"camera": "Càmara:{{cameraName}}"
|
||||
},
|
||||
"field": {
|
||||
"label": "Camp"
|
||||
},
|
||||
"value": {
|
||||
"label": "Valor nou",
|
||||
"reset": "Restableix"
|
||||
}
|
||||
},
|
||||
"detectionModel": {
|
||||
"plusActive": {
|
||||
"title": "Gestió del model Frigate+",
|
||||
"label": "Font del model actual",
|
||||
"description": "Aquesta instància està executant un model Frigate+. Seleccioneu o canvieu el vostre model a la configuració de Frigate+.",
|
||||
"goToFrigatePlus": "Ves a la configuració de Frigate+",
|
||||
"showModelForm": "Configuració manual d'un model"
|
||||
}
|
||||
},
|
||||
"maintenance": {
|
||||
"title": "Manteniment",
|
||||
"sync": {
|
||||
"title": "Sincronització multimèdia",
|
||||
"desc": "Frigate netejarà periòdicament els mitjans en un horari regular segons la configuració de la seva retenció. És normal veure alguns arxius orfes mentre corre Frigate. Utilitzeu aquesta característica per eliminar fitxers multimèdia orfes del disc que ja no estan referenciats a la base de dades.",
|
||||
"started": "S'ha iniciat la sincronització del mitjà.",
|
||||
"alreadyRunning": "Ja s'està executant una tasca de sincronització",
|
||||
"error": "No s'ha pogut iniciar la sincronització",
|
||||
"currentStatus": "Estat",
|
||||
"jobId": "ID de la tasca",
|
||||
"startTime": "Hora d'inici",
|
||||
"endTime": "Hora final",
|
||||
"statusLabel": "Estat",
|
||||
"results": "Resultats",
|
||||
"errorLabel": "Error",
|
||||
"mediaTypes": "Tipus de suport",
|
||||
"allMedia": "Tots els suports",
|
||||
"dryRun": "Executa en sec",
|
||||
"dryRunEnabled": "No s'eliminarà cap fitxer",
|
||||
"dryRunDisabled": "S'eliminaran els fitxers",
|
||||
"force": "Força",
|
||||
"forceDesc": "Evita el llindar de seguretat i completa la sincronització fins i tot si més del 50% dels fitxers s'eliminarien.",
|
||||
"running": "Sincronització en execució...",
|
||||
"start": "Inicia la sincronització",
|
||||
"inProgress": "La sincronització està en curs. Aquesta pàgina està desactivada.",
|
||||
"status": {
|
||||
"queued": "En cua",
|
||||
"running": "En execució",
|
||||
"completed": "Completat",
|
||||
"failed": "Ha fallat",
|
||||
"notRunning": "No s'està executant"
|
||||
},
|
||||
"resultsFields": {
|
||||
"filesChecked": "Fitxers comprovats",
|
||||
"orphansFound": "Orfes trobades",
|
||||
"orphansDeleted": "Orfes eliminats",
|
||||
"aborted": "Avortat. La supressió superaria el llindar de seguretat.",
|
||||
"error": "Error",
|
||||
"totals": "Totals"
|
||||
},
|
||||
"event_snapshots": "Instantànies de l'objecte rastrejat",
|
||||
"event_thumbnails": "Miniatures d'objecte rastrejat",
|
||||
"review_thumbnails": "Revisa les miniatures",
|
||||
"previews": "Previsualitzacions",
|
||||
"exports": "Exporta",
|
||||
"recordings": "Enregistraments"
|
||||
}
|
||||
},
|
||||
"configForm": {
|
||||
"global": {
|
||||
"title": "Configuració global",
|
||||
"description": "Aquestes opcions de configuració s'apliquen a totes les càmeres, llevat que se substitueixin en la configuració específica de la càmera."
|
||||
},
|
||||
"camera": {
|
||||
"title": "Configuració de la càmera",
|
||||
"description": "Aquests paràmetres només s'apliquen a aquesta càmera i substitueixen els paràmetres globals."
|
||||
},
|
||||
"advancedSettingsCount": "Configuració avançada ({{count}})",
|
||||
"advancedCount": "Avançat ({{count}})",
|
||||
"showAdvanced": "Mostra la configuració avançada",
|
||||
"tabs": {
|
||||
"sharedDefaults": "Per defecte compartit",
|
||||
"system": "Sistema",
|
||||
"integrations": "Integracions"
|
||||
},
|
||||
"additionalProperties": {
|
||||
"keyLabel": "Clau",
|
||||
"valueLabel": "Valor",
|
||||
"keyPlaceholder": "Nou valor",
|
||||
"remove": "Elimina"
|
||||
},
|
||||
"timezone": {
|
||||
"defaultOption": "Utilitza la zona horària del navegador"
|
||||
},
|
||||
"roleMap": {
|
||||
"empty": "No hi ha assignacions de rols",
|
||||
"roleLabel": "Rol",
|
||||
"groupsLabel": "Grups",
|
||||
"addMapping": "Afegeix un mapatge de rol",
|
||||
"remove": "Elimina"
|
||||
},
|
||||
"ffmpegArgs": {
|
||||
"preset": "Predefinit",
|
||||
"manual": "Arguments manuals",
|
||||
"inherit": "Hereta de la configuració de la càmera",
|
||||
"selectPreset": "Selecció de valors predefinits",
|
||||
"manualPlaceholder": "ntroduïu els arguments FFmpeg"
|
||||
},
|
||||
"cameraInputs": {
|
||||
"itemTitle": "Flux {{index}}"
|
||||
},
|
||||
"restartRequiredField": "Reinicia requerit",
|
||||
"restartRequiredFooter": "S'ha canviat la configuració - es requereix reiniciar",
|
||||
"sections": {
|
||||
"detect": "Detecció",
|
||||
"record": "Enregistrament",
|
||||
"snapshots": "Instantànies",
|
||||
"motion": "Moviment",
|
||||
"objects": "Objectes",
|
||||
"review": "Revisió",
|
||||
"audio": "Àudio",
|
||||
"notifications": "Notificacions",
|
||||
"live": "Vista en viu",
|
||||
"timestamp_style": "Marques temporals",
|
||||
"mqtt": "MQTT",
|
||||
"database": "Base de dades",
|
||||
"telemetry": "Telemetria",
|
||||
"auth": "Autenticació",
|
||||
"tls": "TLS",
|
||||
"proxy": "Proxy",
|
||||
"go2rtc": "go2rtc",
|
||||
"ffmpeg": "FFmpeg",
|
||||
"detectors": "Detectors",
|
||||
"model": "Model",
|
||||
"semantic_search": "Cerca semàntica",
|
||||
"genai": "GenAI",
|
||||
"face_recognition": "Reconeixement de cares",
|
||||
"lpr": "Reconeixement de matrícules",
|
||||
"birdseye": "Birdseye"
|
||||
},
|
||||
"detect": {
|
||||
"title": "Configuració de detecció"
|
||||
},
|
||||
"detectors": {
|
||||
"title": "Configuració del detector",
|
||||
"singleType": "Només es permet un detector {{type}}.",
|
||||
"keyRequired": "Es requereix el nom del detector.",
|
||||
"keyDuplicate": "El nom del detector ja existeix.",
|
||||
"noSchema": "No hi ha esquemes de detector disponibles.",
|
||||
"none": "No s'ha configurat cap instància de detector.",
|
||||
"add": "Afegeix un detector"
|
||||
},
|
||||
"record": {
|
||||
"title": "Configuració de l'enregistrament"
|
||||
},
|
||||
"snapshots": {
|
||||
"title": "Configuració de la instantània"
|
||||
},
|
||||
"motion": {
|
||||
"title": "Configuració del moviment"
|
||||
},
|
||||
"objects": {
|
||||
"title": "Configuració de l'objecte"
|
||||
},
|
||||
"audioLabels": {
|
||||
"summary": "{{count}} etiquetes d'àudio seleccionades",
|
||||
"empty": "No hi ha etiquetes d'àudio disponibles"
|
||||
},
|
||||
"objectLabels": {
|
||||
"summary": "{{count}} tipus d'objectes seleccionats",
|
||||
"empty": "No hi ha cap etiqueta d'objecte disponible"
|
||||
},
|
||||
"filters": {
|
||||
"objectFieldLabel": "{{field}} per {{label}}"
|
||||
},
|
||||
"zoneNames": {
|
||||
"summary": "{{count}} seleccionats",
|
||||
"empty": "No hi ha zones disponibles"
|
||||
},
|
||||
"inputRoles": {
|
||||
"summary": "{{count}} rols seleccionats",
|
||||
"empty": "No hi ha cap rol disponible",
|
||||
"options": {
|
||||
"detect": "Detecta",
|
||||
"record": "Enregistrament",
|
||||
"audio": "Àudio"
|
||||
}
|
||||
},
|
||||
"review": {
|
||||
"title": "Configuració de la revisió"
|
||||
},
|
||||
"audio": {
|
||||
"title": "Configuració de l'àudio"
|
||||
},
|
||||
"notifications": {
|
||||
"title": "Configuració de notificacions"
|
||||
},
|
||||
"live": {
|
||||
"title": "Configuració de la vista en viu"
|
||||
},
|
||||
"timestamp_style": {
|
||||
"title": "Configuració de la marca horària"
|
||||
},
|
||||
"searchPlaceholder": "Cerca..."
|
||||
},
|
||||
"globalConfig": {
|
||||
"title": "Configuració global",
|
||||
"description": "Configura la configuració global que s'aplica a totes les càmeres llevat que se sobreescriti.",
|
||||
"toast": {
|
||||
"success": "La configuració global s'ha desat correctament",
|
||||
"error": "No s'ha pogut desar la configuració global",
|
||||
"validationError": "Ha fallat la validació"
|
||||
}
|
||||
},
|
||||
"cameraConfig": {
|
||||
"title": "Configuració de la càmera",
|
||||
"description": "Configura la configuració per a les càmeres individuals. La configuració substitueix els valors predeterminats globals.",
|
||||
"overriddenBadge": "Sobreescrit",
|
||||
"resetToGlobal": "Restableix a global",
|
||||
"toast": {
|
||||
"success": "La configuració de la càmera s'ha desat correctament",
|
||||
"error": "Ha fallat en desar la configuració de la càmera"
|
||||
}
|
||||
},
|
||||
"toast": {
|
||||
"success": "La configuració s'ha desat correctament",
|
||||
"successRestartRequired": "La configuració s'ha desat correctament. Reinicia Frigate per aplicar els canvis.",
|
||||
"error": "No s'ha pogut desar la configuració",
|
||||
"validationError": "Ha fallat la validació: {{message}}",
|
||||
"resetSuccess": "Restableix als valors predeterminats globals",
|
||||
"resetError": "No s'ha pogut restablir la configuració",
|
||||
"saveAllSuccess_one": "S'ha desat la secció {{count}} correctament.",
|
||||
"saveAllSuccess_many": "Totes les {{count}} seccions s'han desat correctament.",
|
||||
"saveAllSuccess_other": "Totes les {{count}} seccions s'han desat correctament.",
|
||||
"saveAllPartial_one": "{{successCount}} de la secció {{totalCount}} desada. {{failCount}} ha fallat.",
|
||||
"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"
|
||||
},
|
||||
"unsavedChanges": "Teniu canvis sense desar",
|
||||
"confirmReset": "Confirma el restabliment",
|
||||
"resetToDefaultDescription": "Això restablirà tots els paràmetres d'aquesta secció als seus valors predeterminats. Aquesta acció no es pot desfer.",
|
||||
"resetToGlobalDescription": "Això restablirà la configuració d'aquesta secció als valors predeterminats globals. Aquesta acció no es pot desfer."
|
||||
}
|
||||
|
||||
@ -6,7 +6,8 @@
|
||||
"logs": {
|
||||
"frigate": "Registres de Frigate - Frigate",
|
||||
"go2rtc": "Registres de Go2RTC - Frigate",
|
||||
"nginx": "Registres de Nginix - Frigate"
|
||||
"nginx": "Registres de Nginix - Frigate",
|
||||
"websocket": "Registres de missatges - Frigate"
|
||||
},
|
||||
"enrichments": "Estadístiques complementàries - Frigate"
|
||||
},
|
||||
@ -33,6 +34,32 @@
|
||||
"fetchingLogsFailed": "Error al obtenir els registres: {{errorMessage}}",
|
||||
"whileStreamingLogs": "Error en la transmissió dels registres: {{errorMessage}}"
|
||||
}
|
||||
},
|
||||
"websocket": {
|
||||
"label": "Missatges",
|
||||
"pause": "Pausa",
|
||||
"resume": "Reprèn",
|
||||
"clear": "Neteja",
|
||||
"filter": {
|
||||
"all": "Tots els temes",
|
||||
"topics": "Temes",
|
||||
"events": "Esdeveniment",
|
||||
"reviews": "Revisions",
|
||||
"classification": "Classificació",
|
||||
"face_recognition": "Reconeixement facial",
|
||||
"lpr": "LPR",
|
||||
"camera_activity": "Activitat de la càmera",
|
||||
"system": "Sistema",
|
||||
"camera": "Càmara",
|
||||
"all_cameras": "Totes les càmeres",
|
||||
"cameras_count_one": "{{count}} càmera",
|
||||
"cameras_count_other": "{{count}} Càmeres"
|
||||
},
|
||||
"empty": "Encara no s'ha capturat cap missatge",
|
||||
"count": "{{count}} missatges",
|
||||
"expanded": {
|
||||
"payload": "Payload"
|
||||
}
|
||||
}
|
||||
},
|
||||
"general": {
|
||||
@ -80,8 +107,10 @@
|
||||
"intelGpuWarning": {
|
||||
"title": "Avís d'estadístiques de la GPU d'Intel",
|
||||
"message": "Estadístiques de GPU no disponibles",
|
||||
"description": "Aquest és un error conegut en les eines d'informació de les estadístiques de GPU d'Intel (intel.gpu.top) on es trencarà i retornarà repetidament un ús de GPU del 0% fins i tot en els casos en què l'acceleració del maquinari i la detecció d'objectes s'executen correctament a la (i)GPU. Això no és un error de fragata. Podeu reiniciar l'amfitrió per a corregir temporalment el problema i confirmar que la GPU funciona correctament. Això no afecta el rendiment."
|
||||
}
|
||||
"description": "Aquest és un error conegut en les eines d'informació de les estadístiques de GPU d'Intel (intel.gpu.top) on es trencarà i retornarà repetidament un ús de GPU del 0% fins i tot en els casos en què l'acceleració del maquinari i la detecció d'objectes s'executen correctament a la (i)GPU. Això no és un error de Frigate. Podeu reiniciar l'amfitrió per a corregir temporalment el problema i confirmar que la GPU funciona correctament. Això no afecta el rendiment."
|
||||
},
|
||||
"gpuTemperature": "Temperatura de la GPU",
|
||||
"npuTemperature": "Temperatura NPU"
|
||||
},
|
||||
"otherProcesses": {
|
||||
"title": "Altres processos",
|
||||
@ -165,6 +194,17 @@
|
||||
"error": {
|
||||
"unableToProbeCamera": "No s'ha pogut sondejar la càmera: {{errorMessage}}"
|
||||
}
|
||||
},
|
||||
"connectionQuality": {
|
||||
"title": "Qualitat de la connexió",
|
||||
"excellent": "Excel·lent",
|
||||
"fair": "Fira",
|
||||
"poor": "Pobre",
|
||||
"unusable": "No utilitzable",
|
||||
"fps": "FPS",
|
||||
"expectedFps": "FPS esperat",
|
||||
"reconnectsLastHour": "Reconnecta (última hora)",
|
||||
"stallsLastHour": "Parades (última hora)"
|
||||
}
|
||||
},
|
||||
"lastRefreshed": "Darrera actualització: ",
|
||||
@ -176,7 +216,8 @@
|
||||
"detectHighCpuUsage": "{{camera}} te un ús elevat de CPU per la detecció ({{detectAvg}}%)",
|
||||
"detectIsVerySlow": "{{detect}} és molt lent ({{speed}} ms)",
|
||||
"detectIsSlow": "{{detect}} és lent ({{speed}} ms)",
|
||||
"shmTooLow": "/dev/shm directori ({{total}} MB) hauria de ser incrementat com a mínim {{min}} MB."
|
||||
"shmTooLow": "/dev/shm directori ({{total}} MB) hauria de ser incrementat com a mínim {{min}} MB.",
|
||||
"debugReplayActive": "La sessió de repetició de depuració està activa"
|
||||
},
|
||||
"enrichments": {
|
||||
"title": "Enriquiments",
|
||||
|
||||
@ -133,7 +133,7 @@
|
||||
},
|
||||
"unit": {
|
||||
"speed": {
|
||||
"kph": "Km/h",
|
||||
"kph": "km/h",
|
||||
"mph": "míle/h"
|
||||
},
|
||||
"length": {
|
||||
@ -177,7 +177,7 @@
|
||||
"fi": "Suomi (Finština)",
|
||||
"sk": "Slovenčina (Slovenština)",
|
||||
"withSystem": {
|
||||
"label": "Použít systémové nastavení pro jazyk"
|
||||
"label": "Použít systémové nastavení jazyka"
|
||||
},
|
||||
"zhCN": "简体中文 (Zjednodušená čínština)",
|
||||
"es": "Español (Španělština)",
|
||||
@ -205,14 +205,15 @@
|
||||
"pl": "Polski (Polština)",
|
||||
"th": "ไทย (Thaiština)",
|
||||
"ca": "Català (Katalánština)",
|
||||
"sl": "Slovinština (Slovinsko)",
|
||||
"ptBR": "Português brasileiro (Brazilian Portuguese)",
|
||||
"sr": "Српски (Serbian)",
|
||||
"lt": "Lietuvių (Lithuanian)",
|
||||
"bg": "Български (Bulgarian)",
|
||||
"gl": "Galego (Galician)",
|
||||
"id": "Bahasa Indonesia (Indonesian)",
|
||||
"ur": "اردو (Urdu)"
|
||||
"sl": "Slovinština (Slovinština)",
|
||||
"ptBR": "Português brasileiro (Brazilská Portugalština)",
|
||||
"sr": "Српски (Srbština)",
|
||||
"lt": "Lietuvių (Litevština)",
|
||||
"bg": "Български (Bulharština)",
|
||||
"gl": "Galego (Galicijština)",
|
||||
"id": "Bahasa Indonesia (Indonéština)",
|
||||
"ur": "اردو (Urdština)",
|
||||
"hr": "Hrvatski (Chorvatština)"
|
||||
},
|
||||
"theme": {
|
||||
"highcontrast": "Vysoký kontrast",
|
||||
|
||||
@ -123,7 +123,18 @@
|
||||
"on": "AN",
|
||||
"suspended": "Pausierte",
|
||||
"unsuspended": "fortsetzen",
|
||||
"continue": "Weiter"
|
||||
"continue": "Weiter",
|
||||
"add": "Hinzufügen",
|
||||
"applying": "Wird angewendet…",
|
||||
"undo": "Rückgängig",
|
||||
"copiedToClipboard": "In die Zwischenablage kopiert",
|
||||
"modified": "Verändert",
|
||||
"overridden": "Überschrieben",
|
||||
"resetToGlobal": "Auf Global zurückgesetzen",
|
||||
"resetToDefault": "Auf Werkseinstellungen zurücksetzten",
|
||||
"saveAll": "Alle speichern",
|
||||
"savingAll": "Alle werden gespeichert…",
|
||||
"undoAll": "Alle rückgängig"
|
||||
},
|
||||
"label": {
|
||||
"back": "Zurück",
|
||||
|
||||
32
web/public/locales/de/config/cameras.json
Normal file
32
web/public/locales/de/config/cameras.json
Normal file
@ -0,0 +1,32 @@
|
||||
{
|
||||
"label": "KameraEinstellungen",
|
||||
"name": {
|
||||
"label": "Name der Kamera",
|
||||
"description": "Kameraname ist erforderlich"
|
||||
},
|
||||
"enabled": {
|
||||
"label": "Aktiviert",
|
||||
"description": "Aktiviert"
|
||||
},
|
||||
"audio": {
|
||||
"label": "Audioereignisse",
|
||||
"description": "Einstellungen für audiobasierte Ereigniserkennung für diese Kamera.",
|
||||
"enabled": {
|
||||
"label": "Aktivieren der Audioerkennung",
|
||||
"description": "Aktivieren / Deaktivieren der audiobasierten Ereigniserkennung für diese Kamera."
|
||||
},
|
||||
"min_volume": {
|
||||
"label": "Mindestlautstärke"
|
||||
},
|
||||
"listen": {
|
||||
"description": "Liste der zu erkennenden Audioereignisse (z.B: bellen, Feueralarm, schreien, sprechen, rufen)."
|
||||
},
|
||||
"filters": {
|
||||
"label": "Audiofilter"
|
||||
}
|
||||
},
|
||||
"friendly_name": {
|
||||
"label": "Anzeigename",
|
||||
"description": "Kamera-Anzeigename in der Frigate-Benutzeroberfläche"
|
||||
}
|
||||
}
|
||||
32
web/public/locales/de/config/global.json
Normal file
32
web/public/locales/de/config/global.json
Normal file
@ -0,0 +1,32 @@
|
||||
{
|
||||
"version": {
|
||||
"label": "Aktuelle Version der Konfiguration",
|
||||
"description": "Die Version Numerisch oder als Zeichenketten der aktiven Konfiguration, um Migrationen oder Formatänderungen zu erkennen."
|
||||
},
|
||||
"safe_mode": {
|
||||
"label": "abgesicherter Modus",
|
||||
"description": "Wenn aktiviert, starte Frigate im abgesicherten Modus mit reduzierten Features für die Fehlersuche."
|
||||
},
|
||||
"audio": {
|
||||
"label": "Audioereignisse",
|
||||
"enabled": {
|
||||
"label": "Aktivieren der Audioerkennung"
|
||||
},
|
||||
"min_volume": {
|
||||
"label": "Mindestlautstärke"
|
||||
},
|
||||
"listen": {
|
||||
"description": "Liste der zu erkennenden Audioereignisse (z.B: bellen, Feueralarm, schreien, sprechen, rufen)."
|
||||
},
|
||||
"filters": {
|
||||
"label": "Audiofilter"
|
||||
}
|
||||
},
|
||||
"environment_vars": {
|
||||
"label": "Umgebungsvariablen",
|
||||
"description": "Schlüssel-/Wertpaare für Umgebungsvariablen des Frigate-Prozesses in Home Assistant OS. Nicht-HAOS Benutzer müssen anstatt dessen Docker Umgebungsvariablen nutzen."
|
||||
},
|
||||
"logger": {
|
||||
"label": "Protokollierung"
|
||||
}
|
||||
}
|
||||
26
web/public/locales/de/config/groups.json
Normal file
26
web/public/locales/de/config/groups.json
Normal file
@ -0,0 +1,26 @@
|
||||
{
|
||||
"audio": {
|
||||
"global": {
|
||||
"detection": "Globale Erkennung",
|
||||
"sensitivity": "Globale Empfindlichkeit"
|
||||
},
|
||||
"cameras": {
|
||||
"detection": "Erkennung",
|
||||
"sensitivity": "Empfindlichkeit"
|
||||
}
|
||||
},
|
||||
"timestamp_style": {
|
||||
"global": {
|
||||
"appearance": "Globale Darstellung"
|
||||
},
|
||||
"cameras": {
|
||||
"appearance": "Erscheinungsbild"
|
||||
}
|
||||
},
|
||||
"motion": {
|
||||
"global": {
|
||||
"sensitivity": "Globale Empfindlichkeit",
|
||||
"algorithm": "Globaler Algorithmus"
|
||||
}
|
||||
}
|
||||
}
|
||||
32
web/public/locales/de/config/validation.json
Normal file
32
web/public/locales/de/config/validation.json
Normal file
@ -0,0 +1,32 @@
|
||||
{
|
||||
"maximum": "Darf nicht größer sein als {{limit}}",
|
||||
"minimum": "Darf nicht kleiner sein als {{limit}}",
|
||||
"exclusiveMinimum": "Muss größer sein als {{limit}}",
|
||||
"minLength": "Muss mindestens {{limit}} Zeichen lang sein",
|
||||
"maxLength": "Muss maximal {{limit}} Zeichen lang sein",
|
||||
"minItems": "Muss mindestens {{limit}} mal vorkommen",
|
||||
"exclusiveMaximum": "Muss kleiner sein als {{limit}}",
|
||||
"maxItems": "Muss maximal {{limit}} mal vorkommen",
|
||||
"pattern": "Ungültiges Format",
|
||||
"required": "Pflichtfeld",
|
||||
"type": "Ungültiger Wertetyp",
|
||||
"enum": "Muss einer der erlaubten Werte sein",
|
||||
"const": "Wert stimmt nicht mit erwarteter Konstante überein",
|
||||
"uniqueItems": "Alle Einträge müssen eindeutig sein",
|
||||
"format": "Ungültiges Format",
|
||||
"additionalProperties": "Unbekannte Eigenschaft ist nicht erlaubt",
|
||||
"oneOf": "Muss exakt mit einem der erlaubten Schemas übereinstimmen",
|
||||
"anyOf": "Muss mindestens mit einem der erlaubten Schemas übereinstimmen",
|
||||
"proxy": {
|
||||
"header_map": {
|
||||
"roleHeaderRequired": "Rollen-Header muss angegeben werden, wenn Rollen-Zuordnungen konfiguriert sind."
|
||||
}
|
||||
},
|
||||
"ffmpeg": {
|
||||
"inputs": {
|
||||
"rolesUnique": "Jede Rolle kann nur einem input stream zugeteilt werden.",
|
||||
"detectRequired": "Es muss mindestens ein input stream die Rolle 'erkennen' tragen.",
|
||||
"hwaccelDetectOnly": "Nur der input-stream mit der Rolle 'erkennen' kann Hardwarebeschleunigungs Argumente definieren."
|
||||
}
|
||||
}
|
||||
}
|
||||
@ -19,5 +19,9 @@
|
||||
"downloadVideo": "Video herunterladen",
|
||||
"editName": "Name ändern",
|
||||
"deleteExport": "Export löschen"
|
||||
},
|
||||
"headings": {
|
||||
"cases": "Fälle",
|
||||
"uncategorizedExports": "Unkategorisierte Exporte"
|
||||
}
|
||||
}
|
||||
|
||||
@ -5,7 +5,7 @@
|
||||
"camera": "Kameraeinstellungen - Frigate",
|
||||
"masksAndZones": "Masken- und Zoneneditor – Frigate",
|
||||
"object": "Debug - Frigate",
|
||||
"general": "UI-Einstellungen - Frigate",
|
||||
"general": "Profileinstellungen - Frigate",
|
||||
"frigatePlus": "Frigate+ Einstellungen – Frigate",
|
||||
"classification": "Klassifizierungseinstellungen – Frigate",
|
||||
"motionTuner": "Bewegungserkennungs-Optimierer – Frigate",
|
||||
@ -28,7 +28,8 @@
|
||||
"triggers": "Auslöser",
|
||||
"roles": "Rollen",
|
||||
"cameraManagement": "Verwaltung",
|
||||
"cameraReview": "Überprüfung"
|
||||
"cameraReview": "Überprüfung",
|
||||
"system": "System"
|
||||
},
|
||||
"dialog": {
|
||||
"unsavedChanges": {
|
||||
@ -41,7 +42,7 @@
|
||||
"noCamera": "Keine Kamera"
|
||||
},
|
||||
"general": {
|
||||
"title": "Einstellungen der Benutzeroberfläche",
|
||||
"title": "Profileinstellungen",
|
||||
"liveDashboard": {
|
||||
"title": "Live Übersicht",
|
||||
"playAlertVideos": {
|
||||
@ -408,7 +409,7 @@
|
||||
}
|
||||
},
|
||||
"motionMaskLabel": "Bewegungsmaske {{number}}",
|
||||
"objectMaskLabel": "Objektmaske {{number}} ({{label}})"
|
||||
"objectMaskLabel": "Objektmaske {{number}}"
|
||||
},
|
||||
"debug": {
|
||||
"objectShapeFilterDrawing": {
|
||||
|
||||
@ -264,7 +264,11 @@
|
||||
},
|
||||
"lightning_threshold": {
|
||||
"label": "Lightning threshold",
|
||||
"description": "Threshold to detect and ignore brief lighting spikes (lower is more sensitive, values between 0.3 and 1.0)."
|
||||
"description": "Threshold to detect and ignore brief lighting spikes (lower is more sensitive, values between 0.3 and 1.0). This does not prevent motion detection entirely; it merely causes the detector to stop analyzing additional frames once the threshold is exceeded. Motion-based recordings are still created during these events."
|
||||
},
|
||||
"skip_motion_threshold": {
|
||||
"label": "Skip motion threshold",
|
||||
"description": "If more than this fraction of the image changes in a single frame, the detector will return no motion boxes and immediately recalibrate. This can save CPU and reduce false positives during lightning, storms, etc., but may miss real events such as a PTZ camera auto‑tracking an object. The trade‑off is between dropping a few megabytes of recordings versus reviewing a couple short clips. Range 0.0 to 1.0."
|
||||
},
|
||||
"improve_contrast": {
|
||||
"label": "Improve contrast",
|
||||
@ -864,7 +868,8 @@
|
||||
"description": "A user-friendly name for the zone, displayed in the Frigate UI. If not set, a formatted version of the zone name will be used."
|
||||
},
|
||||
"enabled": {
|
||||
"label": "Whether this zone is active. Disabled zones are ignored at runtime."
|
||||
"label": "Enabled",
|
||||
"description": "Enable or disable this zone. Disabled zones are ignored at runtime."
|
||||
},
|
||||
"enabled_in_config": {
|
||||
"label": "Keep track of original state of zone."
|
||||
|
||||
@ -1391,7 +1391,11 @@
|
||||
},
|
||||
"lightning_threshold": {
|
||||
"label": "Lightning threshold",
|
||||
"description": "Threshold to detect and ignore brief lighting spikes (lower is more sensitive, values between 0.3 and 1.0)."
|
||||
"description": "Threshold to detect and ignore brief lighting spikes (lower is more sensitive, values between 0.3 and 1.0). This does not prevent motion detection entirely; it merely causes the detector to stop analyzing additional frames once the threshold is exceeded. Motion-based recordings are still created during these events."
|
||||
},
|
||||
"skip_motion_threshold": {
|
||||
"label": "Skip motion threshold",
|
||||
"description": "If more than this fraction of the image changes in a single frame, the detector will return no motion boxes and immediately recalibrate. This can save CPU and reduce false positives during lightning, storms, etc., but may miss real events such as a PTZ camera auto‑tracking an object. The trade‑off is between dropping a few megabytes of recordings versus reviewing a couple short clips. Range 0.0 to 1.0."
|
||||
},
|
||||
"improve_contrast": {
|
||||
"label": "Improve contrast",
|
||||
|
||||
@ -61,5 +61,25 @@
|
||||
"detected": "detected",
|
||||
"normalActivity": "Normal",
|
||||
"needsReview": "Needs review",
|
||||
"securityConcern": "Security concern"
|
||||
"securityConcern": "Security concern",
|
||||
"motionSearch": {
|
||||
"menuItem": "Motion search",
|
||||
"openMenu": "Camera options"
|
||||
},
|
||||
"motionPreviews": {
|
||||
"menuItem": "View motion previews",
|
||||
"title": "Motion previews: {{camera}}",
|
||||
"mobileSettingsTitle": "Motion Preview Settings",
|
||||
"mobileSettingsDesc": "Adjust playback speed and dimming, and choose a date to review motion-only clips.",
|
||||
"dim": "Dim",
|
||||
"dimAria": "Adjust dimming intensity",
|
||||
"dimDesc": "Increase dimming to increase motion area visibility.",
|
||||
"speed": "Speed",
|
||||
"speedAria": "Select preview playback speed",
|
||||
"speedDesc": "Choose how quickly preview clips play.",
|
||||
"back": "Back",
|
||||
"empty": "No previews available",
|
||||
"noPreview": "Preview unavailable",
|
||||
"seekAria": "Seek {{camera}} player to {{time}}"
|
||||
}
|
||||
}
|
||||
|
||||
75
web/public/locales/en/views/motionSearch.json
Normal file
75
web/public/locales/en/views/motionSearch.json
Normal file
@ -0,0 +1,75 @@
|
||||
{
|
||||
"documentTitle": "Motion Search - Frigate",
|
||||
"title": "Motion Search",
|
||||
"description": "Draw a polygon to define the region of interest, and specify a time range to search for motion changes within that region.",
|
||||
"selectCamera": "Motion Search is loading",
|
||||
"startSearch": "Start Search",
|
||||
"searchStarted": "Search started",
|
||||
"searchCancelled": "Search cancelled",
|
||||
"cancelSearch": "Cancel",
|
||||
"searching": "Search in progress.",
|
||||
"searchComplete": "Search complete",
|
||||
"noResultsYet": "Run a search to find motion changes in the selected region",
|
||||
"noChangesFound": "No pixel changes detected in the selected region",
|
||||
"changesFound_one": "Found {{count}} motion change",
|
||||
"changesFound_other": "Found {{count}} motion changes",
|
||||
"framesProcessed": "{{count}} frames processed",
|
||||
"jumpToTime": "Jump to this time",
|
||||
"results": "Results",
|
||||
"showSegmentHeatmap": "Heatmap",
|
||||
"newSearch": "New Search",
|
||||
"clearResults": "Clear Results",
|
||||
"clearROI": "Clear polygon",
|
||||
"polygonControls": {
|
||||
"points_one": "{{count}} point",
|
||||
"points_other": "{{count}} points",
|
||||
"undo": "Undo last point",
|
||||
"reset": "Reset polygon"
|
||||
},
|
||||
"motionHeatmapLabel": "Motion Heatmap",
|
||||
"dialog": {
|
||||
"title": "Motion Search",
|
||||
"cameraLabel": "Camera",
|
||||
"previewAlt": "Camera preview for {{camera}}"
|
||||
},
|
||||
"timeRange": {
|
||||
"title": "Search Range",
|
||||
"start": "Start time",
|
||||
"end": "End time"
|
||||
},
|
||||
"settings": {
|
||||
"title": "Search Settings",
|
||||
"parallelMode": "Parallel mode",
|
||||
"parallelModeDesc": "Scan multiple recording segments at the same time (faster, but significantly more CPU intensive)",
|
||||
"threshold": "Sensitivity Threshold",
|
||||
"thresholdDesc": "Lower values detect smaller changes (1-255)",
|
||||
"minArea": "Minimum Change Area",
|
||||
"minAreaDesc": "Minimum percentage of the region of interest that must change to be considered significant",
|
||||
"frameSkip": "Frame Skip",
|
||||
"frameSkipDesc": "Process every Nth frame. Set this to your camera's frame rate to process one frame per second (e.g. 5 for a 5 FPS camera, 30 for a 30 FPS camera). Higher values will be faster, but may miss short motion events.",
|
||||
"maxResults": "Maximum Results",
|
||||
"maxResultsDesc": "Stop after this many matching timestamps"
|
||||
},
|
||||
"errors": {
|
||||
"noCamera": "Please select a camera",
|
||||
"noROI": "Please draw a region of interest",
|
||||
"noTimeRange": "Please select a time range",
|
||||
"invalidTimeRange": "End time must be after start time",
|
||||
"searchFailed": "Search failed: {{message}}",
|
||||
"polygonTooSmall": "Polygon must have at least 3 points",
|
||||
"unknown": "Unknown error"
|
||||
},
|
||||
"changePercentage": "{{percentage}}% changed",
|
||||
"metrics": {
|
||||
"title": "Search Metrics",
|
||||
"segmentsScanned": "Segments scanned",
|
||||
"segmentsProcessed": "Processed",
|
||||
"segmentsSkippedInactive": "Skipped (no activity)",
|
||||
"segmentsSkippedHeatmap": "Skipped (no ROI overlap)",
|
||||
"fallbackFullRange": "Fallback full-range scan",
|
||||
"framesDecoded": "Frames decoded",
|
||||
"wallTime": "Search time",
|
||||
"segmentErrors": "Segment errors",
|
||||
"seconds": "{{seconds}}s"
|
||||
}
|
||||
}
|
||||
@ -83,7 +83,8 @@
|
||||
"triggers": "Triggers",
|
||||
"debug": "Debug",
|
||||
"frigateplus": "Frigate+",
|
||||
"maintenance": "Maintenance"
|
||||
"mediaSync": "Media sync",
|
||||
"regionGrid": "Region grid"
|
||||
},
|
||||
"dialog": {
|
||||
"unsavedChanges": {
|
||||
@ -1232,6 +1233,16 @@
|
||||
"previews": "Previews",
|
||||
"exports": "Exports",
|
||||
"recordings": "Recordings"
|
||||
},
|
||||
"regionGrid": {
|
||||
"title": "Region Grid",
|
||||
"desc": "The region grid is an optimization that learns where objects of different sizes typically appear in each camera's field of view. Frigate uses this data to efficiently size detection regions. The grid is automatically built over time from tracked object data.",
|
||||
"clear": "Clear region grid",
|
||||
"clearConfirmTitle": "Clear Region Grid",
|
||||
"clearConfirmDesc": "Clearing the region grid is not recommended unless you have recently changed your detector model size or have changed your camera's physical position and are having object tracking issues. The grid will be automatically rebuilt over time as objects are tracked. A Frigate restart is required for changes to take effect.",
|
||||
"clearSuccess": "Region grid cleared successfully",
|
||||
"clearError": "Failed to clear region grid",
|
||||
"restartRequired": "Restart required for region grid changes to take effect"
|
||||
}
|
||||
},
|
||||
"configForm": {
|
||||
|
||||
29
web/public/locales/es/config/cameras.json
Normal file
29
web/public/locales/es/config/cameras.json
Normal file
@ -0,0 +1,29 @@
|
||||
{
|
||||
"name": {
|
||||
"label": "Nombre de cámara",
|
||||
"description": "El nombre de la cámara es necesario"
|
||||
},
|
||||
"enabled": {
|
||||
"label": "Habilitado",
|
||||
"description": "Habilitado"
|
||||
},
|
||||
"audio": {
|
||||
"label": "Eventos 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",
|
||||
"description": "Activar o deshabilitar la detección de eventos de audio para esta cámara."
|
||||
},
|
||||
"max_not_heard": {
|
||||
"label": "Finalizar el tiempo de espera",
|
||||
"description": "Cantidad de segundos sin el tipo de audio configurado antes de que finalice el evento de audio."
|
||||
},
|
||||
"min_volume": {
|
||||
"label": "Volumen mínimo"
|
||||
}
|
||||
},
|
||||
"friendly_name": {
|
||||
"label": "Nombre descriptivo",
|
||||
"description": "Nombre descriptivo de la cámara utilizado en la interfaz de usuario de Frigate"
|
||||
}
|
||||
}
|
||||
43
web/public/locales/es/config/global.json
Normal file
43
web/public/locales/es/config/global.json
Normal file
@ -0,0 +1,43 @@
|
||||
{
|
||||
"version": {
|
||||
"label": "Versión de configuración actual",
|
||||
"description": "Versión numérica o de cadena de la configuración activa para ayudar a detectar migraciones o cambios de formato."
|
||||
},
|
||||
"safe_mode": {
|
||||
"label": "Modo seguro",
|
||||
"description": "Cuando está habilitado, inicia Frigate en modo seguro con funciones reducidas para la solución de problemas."
|
||||
},
|
||||
"environment_vars": {
|
||||
"label": "Variables de entorno",
|
||||
"description": "Pares clave/valor de variables de entorno para establecer para el proceso de Frigate en el sistema operativo Home Assistant. Los usuarios que no son de HAOS deben usar la configuración de variables de entorno de Docker."
|
||||
},
|
||||
"logger": {
|
||||
"label": "Registro",
|
||||
"description": "Controla la verbosidad de registro predeterminada y la sobre-escritura de nivel de registro por componente.",
|
||||
"default": {
|
||||
"label": "Nivel de registro",
|
||||
"description": "Nivel de detalle global predeterminada del registro (depuración, información, advertencia, error)."
|
||||
},
|
||||
"logs": {
|
||||
"label": "Nivel de registro por proceso",
|
||||
"description": "Sobre-escribir el nivel de registro por componente para aumentar o disminuir el nivel de detalle de módulos específicos."
|
||||
}
|
||||
},
|
||||
"audio": {
|
||||
"label": "Eventos de audio",
|
||||
"enabled": {
|
||||
"label": "Habilitar la detección de audio"
|
||||
},
|
||||
"max_not_heard": {
|
||||
"label": "Finalizar el tiempo de espera",
|
||||
"description": "Cantidad de segundos sin el tipo de audio configurado antes de que finalice el evento de audio."
|
||||
},
|
||||
"min_volume": {
|
||||
"label": "Volumen mínimo"
|
||||
}
|
||||
},
|
||||
"auth": {
|
||||
"label": "Autenticación",
|
||||
"description": "Configuración relacionada con la autenticación y la sesión, incluidas las opciones de cookies y límite de peticiones."
|
||||
}
|
||||
}
|
||||
44
web/public/locales/es/config/groups.json
Normal file
44
web/public/locales/es/config/groups.json
Normal file
@ -0,0 +1,44 @@
|
||||
{
|
||||
"audio": {
|
||||
"global": {
|
||||
"detection": "Detección Global",
|
||||
"sensitivity": "Sensibilidad Global"
|
||||
},
|
||||
"cameras": {
|
||||
"detection": "Detección",
|
||||
"sensitivity": "Sensibilidad"
|
||||
}
|
||||
},
|
||||
"timestamp_style": {
|
||||
"global": {
|
||||
"appearance": "Apariencia Global"
|
||||
},
|
||||
"cameras": {
|
||||
"appearance": "Apariencia"
|
||||
}
|
||||
},
|
||||
"motion": {
|
||||
"global": {
|
||||
"sensitivity": "Sensibilidad Global",
|
||||
"algorithm": "Algoritmo Global"
|
||||
},
|
||||
"cameras": {
|
||||
"sensitivity": "Sensibilidad",
|
||||
"algorithm": "Algoritmo"
|
||||
}
|
||||
},
|
||||
"snapshots": {
|
||||
"global": {
|
||||
"display": "Pantalla Global"
|
||||
},
|
||||
"cameras": {
|
||||
"display": "Pantalla"
|
||||
}
|
||||
},
|
||||
"detect": {
|
||||
"global": {
|
||||
"resolution": "Resolución Global",
|
||||
"tracking": "Seguimiento Global"
|
||||
}
|
||||
}
|
||||
}
|
||||
16
web/public/locales/es/config/validation.json
Normal file
16
web/public/locales/es/config/validation.json
Normal file
@ -0,0 +1,16 @@
|
||||
{
|
||||
"minimum": "Debe ser al menos {{limit}}",
|
||||
"maximum": "Debe ser como mucho {{limit}}",
|
||||
"exclusiveMinimum": "Debe ser mayor que {{limit}}",
|
||||
"exclusiveMaximum": "Debe ser menor que {{limit}}",
|
||||
"minLength": "Debe ser al menos {{limit}} carácter(es)",
|
||||
"maxLength": "Debe ser como máximo {{limit}} carácter(es)",
|
||||
"minItems": "Debe tener al menos {{limit}} objetos",
|
||||
"maxItems": "Debe tener como máximo {{limit}} objetos",
|
||||
"pattern": "Formato no válido",
|
||||
"required": "Este campo es requerido",
|
||||
"type": "Tipo de valor no válido",
|
||||
"enum": "Debe ser uno de los valores permitidos",
|
||||
"const": "El valor no coincide con la constante esperada",
|
||||
"uniqueItems": "Todos los objetos deben ser únicos"
|
||||
}
|
||||
@ -18,6 +18,11 @@
|
||||
"shareExport": "Compartir exportación",
|
||||
"downloadVideo": "Descargar video",
|
||||
"editName": "Editar nombre",
|
||||
"deleteExport": "Eliminar exportación"
|
||||
"deleteExport": "Eliminar exportación",
|
||||
"assignToCase": "Añadir al caso"
|
||||
},
|
||||
"headings": {
|
||||
"cases": "Casos",
|
||||
"uncategorizedExports": "Exportaciones sin categorizar"
|
||||
}
|
||||
}
|
||||
|
||||
@ -12,7 +12,10 @@
|
||||
"notifications": "Configuración de Notificaciones - Frigate",
|
||||
"enrichments": "Configuración de Análisis Avanzado - Frigate",
|
||||
"cameraManagement": "Administrar Cámaras - Frigate",
|
||||
"cameraReview": "Revisar Configuración de Cámaras - Frigate"
|
||||
"cameraReview": "Revisar Configuración de Cámaras - Frigate",
|
||||
"globalConfig": "Configuración Global - Frigate",
|
||||
"cameraConfig": "Configuración de la cámara - Frigate",
|
||||
"maintenance": "Mantenimiento - Frigate"
|
||||
},
|
||||
"menu": {
|
||||
"cameras": "Configuración de Cámara",
|
||||
|
||||
@ -5,7 +5,8 @@
|
||||
"logs": {
|
||||
"frigate": "Registros de Frigate - Frigate",
|
||||
"go2rtc": "Registros de Go2RTC - Frigate",
|
||||
"nginx": "Registros de Nginx - Frigate"
|
||||
"nginx": "Registros de Nginx - Frigate",
|
||||
"websocket": "Mensajes Logs - Frigata"
|
||||
},
|
||||
"cameras": "Estadísticas de cámaras - Frigate",
|
||||
"enrichments": "Estadísticas de Enriquecimientos - Frigate"
|
||||
@ -31,6 +32,12 @@
|
||||
},
|
||||
"download": {
|
||||
"label": "Descargar registros"
|
||||
},
|
||||
"websocket": {
|
||||
"label": "Mensajes",
|
||||
"pause": "Pausar",
|
||||
"resume": "Continuar",
|
||||
"clear": "Limpiar"
|
||||
}
|
||||
},
|
||||
"title": "Sistema",
|
||||
|
||||
@ -178,7 +178,9 @@
|
||||
"export": "Ekspordi",
|
||||
"uiPlayground": "Leht kasutajaliidese katsetamiseks",
|
||||
"faceLibrary": "Näoteek",
|
||||
"classification": "Klassifikatsioon"
|
||||
"classification": "Klassifikatsioon",
|
||||
"chat": "Vestlus",
|
||||
"actions": "Tegevused"
|
||||
},
|
||||
"unit": {
|
||||
"speed": {
|
||||
@ -234,7 +236,18 @@
|
||||
"export": "Ekspordi",
|
||||
"deleteNow": "Kustuta kohe",
|
||||
"next": "Järgmine",
|
||||
"continue": "Jätka"
|
||||
"continue": "Jätka",
|
||||
"add": "Lisa",
|
||||
"undo": "Võta tegevus tagasi",
|
||||
"copiedToClipboard": "Kopeeritud lõikelauale",
|
||||
"modified": "Muudetud",
|
||||
"overridden": "Sürjutatud",
|
||||
"resetToDefault": "Lähtesta vaikimisi väärtusteks",
|
||||
"saveAll": "Salvesta kõik",
|
||||
"resetToGlobal": "Lähtesta üldiseks väärtusteks",
|
||||
"savingAll": "Salvestan kõiki…",
|
||||
"undoAll": "Pööra kõik tegevused tagasi",
|
||||
"applying": "Võtan kasutusele…"
|
||||
},
|
||||
"label": {
|
||||
"back": "Mine tagasi",
|
||||
|
||||
@ -78,6 +78,10 @@
|
||||
"fromTimeline": {
|
||||
"saveExport": "Salvesta eksporditud sisu",
|
||||
"previewExport": "Eksporditud sisu eelvaade"
|
||||
},
|
||||
"case": {
|
||||
"label": "Juhtum",
|
||||
"placeholder": "Vali juhtum"
|
||||
}
|
||||
},
|
||||
"streaming": {
|
||||
|
||||
@ -13,11 +13,25 @@
|
||||
"shareExport": "Jaga eksporditud sisu",
|
||||
"downloadVideo": "Laadi video alla",
|
||||
"editName": "Muuda nime",
|
||||
"deleteExport": "Kustuta eksporditud sisu"
|
||||
"deleteExport": "Kustuta eksporditud sisu",
|
||||
"assignToCase": "Lisa juhtumile"
|
||||
},
|
||||
"toast": {
|
||||
"error": {
|
||||
"renameExportFailed": "Eksporditud sisu nime muutmine ei õnnestunud: {{errorMessage}}"
|
||||
"renameExportFailed": "Eksporditud sisu nime muutmine ei õnnestunud: {{errorMessage}}",
|
||||
"assignCaseFailed": "Juhtumiga seose uuendamine ei õnnestunud: {{errorMessage}}"
|
||||
}
|
||||
},
|
||||
"headings": {
|
||||
"cases": "Juhtumid",
|
||||
"uncategorizedExports": "Kategooriata eksportimised"
|
||||
},
|
||||
"caseDialog": {
|
||||
"title": "Lisa juhtumile",
|
||||
"selectLabel": "Juhtum",
|
||||
"newCaseOption": "Lisa uus juhtum",
|
||||
"nameLabel": "Juhtumi nimi",
|
||||
"descriptionLabel": "Kirjeldus",
|
||||
"description": "Vali olemasolev juhtum või lisa uus."
|
||||
}
|
||||
}
|
||||
|
||||
@ -6,7 +6,8 @@
|
||||
"description": {
|
||||
"placeholder": "Sisesta nimi selle kogumiku jaoks",
|
||||
"invalidName": "Vigane nimi. Nimed võivad sisaldada ainult tähti, numbreid, tühikuid, ülakomasid, alakriipse ja sidekriipse.",
|
||||
"addFace": "Laadides üles oma esimese pildi saad lisada uue kogumiku Näoteeki."
|
||||
"addFace": "Laadides üles oma esimese pildi saad lisada uue kogumiku Näoteeki.",
|
||||
"nameCannotContainHash": "Nimi ei saa sisaldada # märki."
|
||||
},
|
||||
"documentTitle": "Näoteek - Frigate",
|
||||
"createFaceLibrary": {
|
||||
|
||||
@ -11,7 +11,8 @@
|
||||
"audioDetection": "Heli tuvastus",
|
||||
"transcription": "Heli üleskirjutus",
|
||||
"snapshots": "Hetkvõtted",
|
||||
"autotracking": "Automaatne jälgimine"
|
||||
"autotracking": "Automaatne jälgimine",
|
||||
"recording": "Salvestus"
|
||||
},
|
||||
"documentTitle": "Otseülekanne - Frigate",
|
||||
"documentTitle.withCamera": "{{camera}} - Otseülekanne - Frigate",
|
||||
@ -100,6 +101,10 @@
|
||||
"audio": {
|
||||
"available": "Selles voogedastuses on heliriba saadaval",
|
||||
"unavailable": "Selles voogedastuses pole heliriba saadaval"
|
||||
},
|
||||
"title": "Voogedastus",
|
||||
"lowBandwidth": {
|
||||
"resetStream": "Lähtesta voogedastus"
|
||||
}
|
||||
},
|
||||
"notifications": "Teavitused",
|
||||
@ -127,7 +132,8 @@
|
||||
"playInBackground": {
|
||||
"label": "Esita taustal",
|
||||
"desc": "Kasuta seda valikut, kui tahad voogedastuse jätkumist ka siis, kui pildivaade on peidetud."
|
||||
}
|
||||
},
|
||||
"debugView": "Veaotsinguvaade"
|
||||
},
|
||||
"noCameras": {
|
||||
"buttonText": "Lisa kaamera",
|
||||
@ -136,7 +142,17 @@
|
||||
"description": "Sul pole õigust ühegi selle grupi kaamera vaatamiseks."
|
||||
},
|
||||
"title": "Ühtegi kaamerat pole seadistatud",
|
||||
"description": "Alustamiseks ühenda mõni kaamera Frigate'iga."
|
||||
"description": "Alustamiseks ühenda mõni kaamera Frigate'iga.",
|
||||
"default": {
|
||||
"title": "Ühtegi kaamerat pole seadistatud",
|
||||
"description": "Alustamiseks ühenda mõni kaamera Frigate'iga.",
|
||||
"buttonText": "Lisa kaamera"
|
||||
},
|
||||
"group": {
|
||||
"title": "Grupid pole ühtegi kaamerat",
|
||||
"description": "Selles kaameragrupis pole ühtegi määratud ega kasutusel kaamerat.",
|
||||
"buttonText": "Halda gruppe"
|
||||
}
|
||||
},
|
||||
"effectiveRetainMode": {
|
||||
"modes": {
|
||||
|
||||
@ -172,7 +172,7 @@
|
||||
"default": "Seadistused - Frigate",
|
||||
"authentication": "Autentimise seadistused - Frigate",
|
||||
"cameraReview": "Kaamerate kordusvaatuste seadistused - Frigate",
|
||||
"general": "Kasutajaliidese seadistused - Frigate",
|
||||
"general": "Profiili seadistused - Frigate",
|
||||
"frigatePlus": "Frigate+ seadistused - Frigate",
|
||||
"notifications": "Teavituste seadistused - Frigate",
|
||||
"cameraManagement": "Kaamerate haldus - Frigate",
|
||||
@ -180,7 +180,7 @@
|
||||
"object": "Silumine ja veaotsing - Frigate"
|
||||
},
|
||||
"general": {
|
||||
"title": "Kasutajaliidese seadistused",
|
||||
"title": "Profiili seadistused",
|
||||
"cameraGroupStreaming": {
|
||||
"clearAll": "Kustuta kõik voogedastuse seadistused"
|
||||
},
|
||||
|
||||
@ -75,7 +75,8 @@
|
||||
},
|
||||
"inProgress": "در حال انجام",
|
||||
"invalidStartTime": "زمان شروع نامعتبر است",
|
||||
"invalidEndTime": "زمان پایان نامعتبر است"
|
||||
"invalidEndTime": "زمان پایان نامعتبر است",
|
||||
"never": "هرگز"
|
||||
},
|
||||
"unit": {
|
||||
"length": {
|
||||
|
||||
@ -129,7 +129,17 @@
|
||||
"deleteNow": "Supprimer maintenant",
|
||||
"download": "Télécharger",
|
||||
"done": "Terminé",
|
||||
"continue": "Continuer"
|
||||
"continue": "Continuer",
|
||||
"add": "Ajouter",
|
||||
"undo": "Annuler",
|
||||
"copiedToClipboard": "Copié dans le presse-papiers",
|
||||
"modified": "Modifié",
|
||||
"overridden": "Surpassé",
|
||||
"resetToGlobal": "Réinitialiser aux réglages globaux",
|
||||
"resetToDefault": "Réinitialiser aux réglages par défaut",
|
||||
"saveAll": "Tout enregistrer",
|
||||
"savingAll": "Enregistrement de tout en cours…",
|
||||
"undoAll": "Tout annuler"
|
||||
},
|
||||
"menu": {
|
||||
"configuration": "Configuration",
|
||||
|
||||
@ -77,6 +77,10 @@
|
||||
"fromTimeline": {
|
||||
"saveExport": "Enregistrer l'exportation",
|
||||
"previewExport": "Aperçu de l'exportation"
|
||||
},
|
||||
"case": {
|
||||
"label": "Dossier",
|
||||
"placeholder": "Sélectionner un dossier"
|
||||
}
|
||||
},
|
||||
"search": {
|
||||
|
||||
320
web/public/locales/fr/config/cameras.json
Normal file
320
web/public/locales/fr/config/cameras.json
Normal file
@ -0,0 +1,320 @@
|
||||
{
|
||||
"name": {
|
||||
"label": "Nom de la caméra",
|
||||
"description": "Le nom de la caméra est requis"
|
||||
},
|
||||
"friendly_name": {
|
||||
"label": "Nom convivial",
|
||||
"description": "Nom convivial de la caméra utilisé dans l'IU Frigate"
|
||||
},
|
||||
"enabled": {
|
||||
"label": "Activé",
|
||||
"description": "Activé"
|
||||
},
|
||||
"audio": {
|
||||
"label": "Événements audio",
|
||||
"description": "Réglages pour la détection des événements audio de cette caméra.",
|
||||
"enabled": {
|
||||
"label": "Activer la détection audio",
|
||||
"description": "Activer ou désactiver la détection des événements audio pour cette caméra."
|
||||
},
|
||||
"max_not_heard": {
|
||||
"description": "Nombre de secondes sans le type audio configuré avant que l'événement audio se termine.",
|
||||
"label": "Délai d'inactivité"
|
||||
},
|
||||
"min_volume": {
|
||||
"label": "Volume minimal",
|
||||
"description": "Seuil minimal d'activation du volume en moyenne quadratique requis pour exécuter la détection audio. Des valeurs plus faibles augmentent la sensibilité (p. ex. 200 est élevé, 500 est moyen et 1000 est faible)."
|
||||
},
|
||||
"listen": {
|
||||
"label": "Types d'écoute",
|
||||
"description": "Liste des types d'événements audio à détecter (p. ex. bark, fire_alarm, scream, speech, yell)."
|
||||
},
|
||||
"filters": {
|
||||
"label": "Filtres audio",
|
||||
"description": "Réglages des filtres par type audio, tels que seuils de confiance utilisé afin de réduire les faux positifs."
|
||||
},
|
||||
"enabled_in_config": {
|
||||
"label": "État audio original",
|
||||
"description": "Indique si la détection audio était initialement activée dans le fichier de configuration statique."
|
||||
},
|
||||
"num_threads": {
|
||||
"label": "Fils d'exécution pour la détection",
|
||||
"description": "Nombre de fils d'éxécution à utiliser pour le traitement de la détection audio."
|
||||
}
|
||||
},
|
||||
"audio_transcription": {
|
||||
"label": "Transcription audio",
|
||||
"description": "Réglages pour la transcription audio et vocale utilisée pour les événements et les sous-titres en temps réel.",
|
||||
"enabled": {
|
||||
"label": "Activer la transcription",
|
||||
"description": "Activer ou désactiver le déclenchement manuel de la transcription des événements audio."
|
||||
},
|
||||
"enabled_in_config": {
|
||||
"label": "État original de la transcription"
|
||||
},
|
||||
"live_enabled": {
|
||||
"label": "Transcription en temps réel",
|
||||
"description": "Activer la diffusion de la transcription en temps réel pour le flux sonore dès sa réception."
|
||||
}
|
||||
},
|
||||
"birdseye": {
|
||||
"label": "À vol d'oiseau",
|
||||
"description": "Réglages pour la vue composée à vol d'oiseau qui combine plusieurs flux de caméras dans une simple disposition.",
|
||||
"enabled": {
|
||||
"label": "Activer la vue à vol d'oiseau",
|
||||
"description": "Activer ou désactiver la fonctionalité de vue à vol d'oiseau."
|
||||
},
|
||||
"mode": {
|
||||
"label": "Mode de suivi",
|
||||
"description": "Mode pour l'inclusion des caméras dans la vue à vol d'oiseau: 'objects', 'motion', ou 'continuous'."
|
||||
},
|
||||
"order": {
|
||||
"label": "Emplacement",
|
||||
"description": "Emplacement numérique contrôlant l'ordre de la caméra dans la disposition en vue à vol d'oiseau."
|
||||
}
|
||||
},
|
||||
"detect": {
|
||||
"label": "Détection d'objets",
|
||||
"description": "Réglages pour la détection ou le rôle de détection utilisé pour exécuter la détection des objets et initialiser les traceurs.",
|
||||
"enabled": {
|
||||
"label": "Détection activée",
|
||||
"description": "Activer ou désactiver la détection des objets pour cette caméra. La détection doit être activée pour que le suivi des objets fonctionne."
|
||||
},
|
||||
"height": {
|
||||
"label": "Hauteur de détection",
|
||||
"description": "Hauteur (en pixels) des images utilisées pour le flux de détection ; garder vide pour utiliser la résolution native du flux."
|
||||
},
|
||||
"width": {
|
||||
"label": "Largeur de détection",
|
||||
"description": "Largeur (en pixels) des images utilisées pour le flux de détection ; garder vide pour utiliser la résolution native du flux."
|
||||
},
|
||||
"fps": {
|
||||
"label": "IPS de la détection",
|
||||
"description": "Nombre cible d'images par seconde à utiliser pour la détection ; des valeurs plus faibles réduisent l'utilisation de l'UCT (la valeur recommandée est 5, ne la définir à une valeur supérieure - au maximum 10, uniquement lors du suivi d'objets se déplaçant extrêmement rapidement)."
|
||||
},
|
||||
"min_initialized": {
|
||||
"label": "Minimum d'images d'initialisation",
|
||||
"description": "Nombre de détections consécutives requises avant de créer un objet suivi. Augmenter pour réduire les initialisations erronées. La valeur par défaut est fps divisé par 2."
|
||||
},
|
||||
"max_disappeared": {
|
||||
"label": "Nombre maximal d'images disparues",
|
||||
"description": "Nombre d'images sans détection avant qu'un objet suivi est considéré comme étant disparu."
|
||||
},
|
||||
"stationary": {
|
||||
"label": "Configuration des objets stationnaires",
|
||||
"description": "Réglages pour la détection et la gestion des objets qui restent stationnaires pendant un certain temps.",
|
||||
"interval": {
|
||||
"label": "Intervalle stationnaire",
|
||||
"description": "À quelle fréquence (en images) effectuer une détection pour la confirmation d'un objet stationnaire."
|
||||
},
|
||||
"threshold": {
|
||||
"label": "Seuil d'activation stationnaire",
|
||||
"description": "Nombre d'images sans changement d'emplacement requis pour marquer un objet en tant que stationnaire."
|
||||
},
|
||||
"max_frames": {
|
||||
"label": "Nombre max. d'images",
|
||||
"description": "Limite le temps pour lequel les objets stationnaires sont suivis avant d'être supprimés.",
|
||||
"default": {
|
||||
"label": "Nombre max. d'images par défaut",
|
||||
"description": "Nombre maximal d'images pour suivre un objet stationnaire avant d'arrêter."
|
||||
},
|
||||
"objects": {
|
||||
"label": "Nombre max. d'images pour l'objet",
|
||||
"description": "Remplacement des réglages par défaut par objet pour le nombre maximal d'images requis pour suivre les objets stationnaires."
|
||||
}
|
||||
},
|
||||
"classifier": {
|
||||
"label": "Activer le classificateur visuel",
|
||||
"description": "Utiliser un classificateur visuel pour détecter les objets véritablement stationnaires même lorsque les boîtes englobantes tremblent."
|
||||
}
|
||||
},
|
||||
"annotation_offset": {
|
||||
"label": "Décalage de l'annotation",
|
||||
"description": "Millisecondes pour le décalage des annotations afin de mieux aligner les boîtes englobantes de la ligne du temps avec les enregistrements ; peut être positif ou négatif."
|
||||
}
|
||||
},
|
||||
"face_recognition": {
|
||||
"label": "Reconnaissance faciale",
|
||||
"description": "Réglages pour la détection et reconnaissance faciale pour cette caméra.",
|
||||
"enabled": {
|
||||
"label": "Activer la reconnaissance faciale",
|
||||
"description": "Activer ou désactiver la reconnaissance faciale."
|
||||
},
|
||||
"min_area": {
|
||||
"label": "Surface minimale du visage",
|
||||
"description": "Surface minimale (en pixels) d'une boîte faciale détectée requise pour tenter la reconnaissance."
|
||||
}
|
||||
},
|
||||
"ffmpeg": {
|
||||
"label": "FFmpeg",
|
||||
"description": "Réglages de FFmpeg incluant l'emplacement du fichier binaire, les arguments, les options pour hwaccel et les arguments de sortie par rôle.",
|
||||
"path": {
|
||||
"label": "Emplacement de FFmpeg",
|
||||
"description": "Emplacement du fichier binaire de FFmpeg à utiliser ou un alias de version (peut être «5.0» ou «7.0»)."
|
||||
},
|
||||
"global_args": {
|
||||
"label": "Arguments globaux de FFmpeg",
|
||||
"description": "Arguments globaux transmis aux processus de FFmpeg."
|
||||
},
|
||||
"hwaccel_args": {
|
||||
"label": "Arguments pour l'accélération matérielle",
|
||||
"description": "Arguments de l'accélération matérielle pour FFmpeg. Les préréglages spécifiques au fournisseur sont recommandés."
|
||||
},
|
||||
"input_args": {
|
||||
"label": "Arguments d'entrée",
|
||||
"description": "Arguments d'entrée appliqués aux flux d'entrée FFmpeg."
|
||||
},
|
||||
"output_args": {
|
||||
"label": "Arguments de sortie",
|
||||
"description": "Arguments de sortie par défaut utilisés pour les différents rôles FFmpeg, tels que detect et record.",
|
||||
"detect": {
|
||||
"label": "Détecter les arguments de sortie",
|
||||
"description": "Arguments de sortie par défaut pour les flux du rôle detect."
|
||||
},
|
||||
"record": {
|
||||
"label": "Arguments de sortie pour l'enregistrement",
|
||||
"description": "Arguments de sortie par défaut pour les flux du rôle record."
|
||||
}
|
||||
},
|
||||
"retry_interval": {
|
||||
"label": "Temps de réessai FFmpeg",
|
||||
"description": "Nombre de secondes à attendre avant de tenter de reconnecter un flux de caméra après un échec. La valeur par défaut est 10."
|
||||
},
|
||||
"apple_compatibility": {
|
||||
"label": "Compatibilité avec Apple",
|
||||
"description": "Activer l'étiquetage HEVC pour une meilleure compatibilité avec les lecteurs Apple lors de l'enregistrement H.265."
|
||||
},
|
||||
"gpu": {
|
||||
"label": "Index de l'UTG",
|
||||
"description": "Index par défaut de l'UTG utilisé pour l'accélération matérielle si disponible."
|
||||
},
|
||||
"inputs": {
|
||||
"label": "Entrées des caméras",
|
||||
"description": "Liste des définitions des flux entrants (emplacements et rôles) pour cette caméra.",
|
||||
"path": {
|
||||
"label": "Emplacement d'entrée",
|
||||
"description": "URL ou emplacement du flux d'entrée de la caméra."
|
||||
},
|
||||
"roles": {
|
||||
"label": "Rôles d'entrée",
|
||||
"description": "Rôles pour ce flux entrant."
|
||||
},
|
||||
"global_args": {
|
||||
"label": "Arguments globaux de FFmpeg",
|
||||
"description": "Arguments globaux de FFmpeg pour ce flux entrant."
|
||||
},
|
||||
"hwaccel_args": {
|
||||
"label": "Arguments pour l'accélération matérielle",
|
||||
"description": "Arguments de l'accélération matérielle pour ce flux entrant."
|
||||
},
|
||||
"input_args": {
|
||||
"label": "Arguments d'entrée",
|
||||
"description": "Arguments d'entrée spéficiques à ce flux."
|
||||
}
|
||||
}
|
||||
},
|
||||
"live": {
|
||||
"label": "Lecture en direct",
|
||||
"description": "Réglages utilisés par l'IU Web afin de contrôler la sélection, la résolution et la qualité des flux en direct.",
|
||||
"streams": {
|
||||
"label": "Nom des flux en direct",
|
||||
"description": "Mappage des noms des flux configurés vers les noms de restream et go2rtc utilisés pour la lecture en direct."
|
||||
},
|
||||
"height": {
|
||||
"label": "Hauteur de la diffusion en direct",
|
||||
"description": "Hauteur (en pixels) à laquelle afficher le flux en direct jsmpeg dans l'IU Web ; doit être inférieure ou égale à la hauteur détectée du flux."
|
||||
},
|
||||
"quality": {
|
||||
"label": "Qualité de la diffusion en direct",
|
||||
"description": "Qualité de l'encodage pour le flux jsmpeg (1 étant la plus élevée, 31 la plus faible)."
|
||||
}
|
||||
},
|
||||
"lpr": {
|
||||
"label": "Reconnaissance des plaques d'immatriculation",
|
||||
"description": "Réglages de la reconnaissance des plaques d'immatriculation incluant les seuils de détection, le formatage et les plaques connues.",
|
||||
"enabled": {
|
||||
"label": "Activer la RPI",
|
||||
"description": "Activer ou désactiver la RPI sur cette caméra."
|
||||
},
|
||||
"expire_time": {
|
||||
"label": "Expiration en secondes",
|
||||
"description": "Temps en secondes après lequel une plaque non vue expire du système de suivi (seulement pour les caméras dédiées à la RPI)."
|
||||
},
|
||||
"min_area": {
|
||||
"label": "Surface minimale de la plaque",
|
||||
"description": "Surface minimale de la plaque (en pixels) requise pour tenter la reconnaissance."
|
||||
},
|
||||
"enhancement": {
|
||||
"label": "Niveau de l'enrichissement",
|
||||
"description": "Niveau de l'enrichissement (de 0 à 10) à appliquer aux recadrages des plaques avant la ROC. Des valeurs plus élevées n'améliorent pas nécessairement les résultats, les niveaux supérieurs à 5 peuvent ne fonctionner qu'avec des plaques la nuit et doivent être utilisés avec prudence."
|
||||
}
|
||||
},
|
||||
"motion": {
|
||||
"label": "Détection du mouvement",
|
||||
"description": "Réglages par défaut de la détection de mouvement pour cette caméra.",
|
||||
"enabled": {
|
||||
"label": "Activer la détection de mouvement",
|
||||
"description": "Activer ou désactiver la détection de mouvement pour cette caméra."
|
||||
},
|
||||
"threshold": {
|
||||
"label": "Seuil de détection du mouvement",
|
||||
"description": "Seuil de différence de pixels utilisé par le détecteur de mouvement ; les valeurs plus élevées réduisent la sensibilité (plage de 1 à 255)."
|
||||
},
|
||||
"lightning_threshold": {
|
||||
"label": "Seuil d'éclairage",
|
||||
"description": "Seuil permettant de détecter et d'ignorer les brusques pointes d'éclairage (plus la valeur est faible, plus la sensibilité est élevée, valeurs comprises entre 0.3 et 1.0)."
|
||||
},
|
||||
"improve_contrast": {
|
||||
"label": "Améliorer le contraste",
|
||||
"description": "Appliquer les amélioration du contraste aux images avant l'analyse de mouvement afin d'améliorer la détection."
|
||||
},
|
||||
"contour_area": {
|
||||
"label": "Zone de contour",
|
||||
"description": "Aire de la zone de contour minimale en pixels requise pour qu'un contour de mouvement soit comptabilisé."
|
||||
},
|
||||
"delta_alpha": {
|
||||
"label": "Delta pour alpha",
|
||||
"description": "Facteur de mélange alpha utilisé dans la différenciation d'images pour le calcul du mouvement."
|
||||
},
|
||||
"frame_alpha": {
|
||||
"label": "Alpha pour l'image",
|
||||
"description": "Valeur alpha utilisée lors du mélange d'images pour le prétraitement du mouvement."
|
||||
},
|
||||
"frame_height": {
|
||||
"label": "Hauteur de l'image",
|
||||
"description": "Hauteur en pixels à laquelle mettre à l'échelle les images lors du traitement du mouvement."
|
||||
},
|
||||
"mask": {
|
||||
"label": "Moordonnées du masque",
|
||||
"description": "Coordonnées ordonnés x et y définissant le polygone du masque de mouvement utilisé pour inclure ou exclure des aires."
|
||||
},
|
||||
"mqtt_off_delay": {
|
||||
"label": "Délai de désactivation de MQTT",
|
||||
"description": "Nombre de secondes à attendre après le dernier mouvement avant de publier un état « off » MQTT."
|
||||
},
|
||||
"enabled_in_config": {
|
||||
"label": "État original du mouvement",
|
||||
"description": "Indique si la détection de mouvement a été activée dans la configuration originale statique."
|
||||
},
|
||||
"raw_mask": {
|
||||
"label": "Masque brut"
|
||||
}
|
||||
},
|
||||
"objects": {
|
||||
"label": "Objets",
|
||||
"description": "Réglages par défaut pour le suivi des objets incluant les étiquettes à suivre et les filtres par objets.",
|
||||
"track": {
|
||||
"label": "Objets à suivre",
|
||||
"description": "Liste des étiquettes d'objets à suivre pour cette caméra."
|
||||
},
|
||||
"filters": {
|
||||
"label": "Filtres d'objets",
|
||||
"description": "Filtres appliqués aux objets détectés afin de réduire les faux positifs (aire, rapport, facteur de confiance).",
|
||||
"min_area": {
|
||||
"label": "Aire minimal de l'objet"
|
||||
}
|
||||
}
|
||||
},
|
||||
"label": "ConfigurationCamera"
|
||||
}
|
||||
5
web/public/locales/fr/config/global.json
Normal file
5
web/public/locales/fr/config/global.json
Normal file
@ -0,0 +1,5 @@
|
||||
{
|
||||
"version": {
|
||||
"label": "Version actuelle de la configuration"
|
||||
}
|
||||
}
|
||||
7
web/public/locales/fr/config/groups.json
Normal file
7
web/public/locales/fr/config/groups.json
Normal file
@ -0,0 +1,7 @@
|
||||
{
|
||||
"audio": {
|
||||
"global": {
|
||||
"detection": "Détection globale"
|
||||
}
|
||||
}
|
||||
}
|
||||
3
web/public/locales/fr/config/validation.json
Normal file
3
web/public/locales/fr/config/validation.json
Normal file
@ -0,0 +1,3 @@
|
||||
{
|
||||
"minimum": "Doit être au minimum {{limit}}"
|
||||
}
|
||||
@ -11,13 +11,27 @@
|
||||
},
|
||||
"toast": {
|
||||
"error": {
|
||||
"renameExportFailed": "Échec du renommage de l'exportation : {{errorMessage}}"
|
||||
"renameExportFailed": "Échec du renommage de l'exportation : {{errorMessage}}",
|
||||
"assignCaseFailed": "Échec de la mise à jour de l'affectation au dossier : {{errorMessage}}"
|
||||
}
|
||||
},
|
||||
"tooltip": {
|
||||
"shareExport": "Partager l'exportation",
|
||||
"downloadVideo": "Télécharger la vidéo",
|
||||
"editName": "Modifier le nom",
|
||||
"deleteExport": "Supprimer l'exportation"
|
||||
"deleteExport": "Supprimer l'exportation",
|
||||
"assignToCase": "Ajouter à un dossier"
|
||||
},
|
||||
"headings": {
|
||||
"cases": "Dossiers",
|
||||
"uncategorizedExports": "Exportations non classées"
|
||||
},
|
||||
"caseDialog": {
|
||||
"title": "Ajouter à un dossier",
|
||||
"description": "Choisissez un dossier existant ou créez en un nouveau.",
|
||||
"selectLabel": "Dossier",
|
||||
"newCaseOption": "Créer un nouveau dossier",
|
||||
"nameLabel": "Nom du dossier",
|
||||
"descriptionLabel": "Description"
|
||||
}
|
||||
}
|
||||
|
||||
@ -5,20 +5,23 @@
|
||||
"camera": "Paramètres des caméras - Frigate",
|
||||
"classification": "Paramètres de classification - Frigate",
|
||||
"motionTuner": "Réglage de la détection de mouvement - Frigate",
|
||||
"general": "Paramètres de l'interface utilisateur - Frigate",
|
||||
"general": "Paramètres du profil - Frigate",
|
||||
"masksAndZones": "Éditeur de masques et de zones - Frigate",
|
||||
"object": "Débogage - Frigate",
|
||||
"frigatePlus": "Paramètres Frigate+ - Frigate",
|
||||
"notifications": "Paramètres de notification - Frigate",
|
||||
"enrichments": "Paramètres d'enrichissements - Frigate",
|
||||
"cameraManagement": "Gestion des caméras - Frigate",
|
||||
"cameraReview": "Paramètres des activités caméra - Frigate"
|
||||
"cameraReview": "Paramètres des activités caméra - Frigate",
|
||||
"globalConfig": "Configuration globale - Frigate",
|
||||
"cameraConfig": "Configuration de la caméra - Frigate",
|
||||
"maintenance": "Maintenance - Frigate"
|
||||
},
|
||||
"menu": {
|
||||
"ui": "Interface utilisateur",
|
||||
"classification": "Classification",
|
||||
"masksAndZones": "Masques / Zones",
|
||||
"motionTuner": "Réglage de la détection de mouvement",
|
||||
"motionTuner": "Ajusteur de la détection de mouvement",
|
||||
"debug": "Débogage",
|
||||
"cameras": "Paramètres des caméras",
|
||||
"users": "Utilisateurs",
|
||||
@ -28,7 +31,62 @@
|
||||
"triggers": "Déclencheurs",
|
||||
"roles": "Rôles",
|
||||
"cameraManagement": "Gestion",
|
||||
"cameraReview": "Activités"
|
||||
"cameraReview": "Activités",
|
||||
"general": "Général",
|
||||
"globalConfig": "Configuration globale",
|
||||
"system": "Système",
|
||||
"integrations": "Intégrations",
|
||||
"profileSettings": "Paramètres du profil",
|
||||
"globalDetect": "Détection d'objets",
|
||||
"globalRecording": "Enregistrement",
|
||||
"globalSnapshots": "Instantanés",
|
||||
"globalFfmpeg": "FFmpeg",
|
||||
"globalMotion": "Détection de mouvement",
|
||||
"globalObjects": "Objets",
|
||||
"globalReview": "Activités",
|
||||
"globalAudioEvents": "Événements audio",
|
||||
"globalLivePlayback": "Lecture en direct",
|
||||
"globalTimestampStyle": "Format d'horodatage",
|
||||
"systemDatabase": "Base de données",
|
||||
"systemTls": "TLS",
|
||||
"systemAuthentication": "Authentification",
|
||||
"systemNetworking": "Réseau",
|
||||
"systemProxy": "Proxy",
|
||||
"systemUi": "Interface",
|
||||
"systemLogging": "Journalisation",
|
||||
"systemEnvironmentVariables": "Variables d'environnement",
|
||||
"systemTelemetry": "Télémétrie",
|
||||
"systemBirdseye": "Birdseye",
|
||||
"systemFfmpeg": "FFmpeg",
|
||||
"systemDetectorHardware": "Matériel de détection",
|
||||
"systemDetectionModel": "Modèle de détection",
|
||||
"systemMqtt": "MQTT",
|
||||
"integrationSemanticSearch": "Recherche sémantique",
|
||||
"integrationGenerativeAi": "IA générative",
|
||||
"integrationFaceRecognition": "Reconnaissance faciale",
|
||||
"integrationLpr": "Lecture de plaques d'immatriculation",
|
||||
"integrationObjectClassification": "Classification d'objets",
|
||||
"integrationAudioTranscription": "Transcription audio",
|
||||
"cameraDetect": "Détection d'objets",
|
||||
"cameraFfmpeg": "FFmpeg",
|
||||
"cameraRecording": "Enregistrement",
|
||||
"cameraSnapshots": "Instantanés",
|
||||
"cameraMotion": "Détection de mouvement",
|
||||
"cameraObjects": "Objets",
|
||||
"cameraConfigReview": "Activités",
|
||||
"cameraAudioEvents": "Évènements audio",
|
||||
"cameraAudioTranscription": "Transcription audio",
|
||||
"cameraNotifications": "Notifications",
|
||||
"cameraLivePlayback": "Lecture en direct",
|
||||
"cameraBirdseye": "Birdseye",
|
||||
"cameraFaceRecognition": "Reconnaissance faciale",
|
||||
"cameraLpr": "Lecture de plaques d'immatriculation",
|
||||
"cameraMqttConfig": "MQTT",
|
||||
"cameraOnvif": "ONVIF",
|
||||
"cameraUi": "Interface de la caméra",
|
||||
"cameraTimestampStyle": "Style d'horodatage",
|
||||
"cameraMqtt": "MQTT de la caméra",
|
||||
"maintenance": "Maintenance"
|
||||
},
|
||||
"dialog": {
|
||||
"unsavedChanges": {
|
||||
@ -41,7 +99,7 @@
|
||||
"noCamera": "Aucune caméra"
|
||||
},
|
||||
"general": {
|
||||
"title": "Paramètres de l'interface utilisateur",
|
||||
"title": "Paramètres du profil",
|
||||
"liveDashboard": {
|
||||
"title": "Tableau de bord en direct",
|
||||
"automaticLiveView": {
|
||||
@ -723,7 +781,7 @@
|
||||
"readTheDocumentation": "Lire la documentation",
|
||||
"reindexNow": {
|
||||
"label": "Réindexer maintenant",
|
||||
"desc": "La réindexation va régénérer les embeddings pour tous les objets suivis. Ce processus s'exécute en arrière-plan et peut saturer votre processeur et prendre un temps considérable en fonction du nombre d'objets suivis.",
|
||||
"desc": "La réindexation va régénérer les intégrations pour tous les objets suivis. Ce processus s'exécute en arrière-plan et peut saturer votre processeur et prendre un temps considérable en fonction du nombre d'objets suivis.",
|
||||
"confirmTitle": "Confirmer la réindexation",
|
||||
"confirmButton": "Réindexer",
|
||||
"success": "La réindexation a démarré avec succès.",
|
||||
@ -1315,5 +1373,22 @@
|
||||
"success": "La configuration de la classification des activités a été enregistrée. Redémarrez Frigate pour appliquer les modifications."
|
||||
}
|
||||
}
|
||||
},
|
||||
"saveAllPreview": {
|
||||
"title": "Modifications à enregistrer",
|
||||
"triggerLabel": "Examiner les modifications en attente",
|
||||
"empty": "Aucune modification en attente",
|
||||
"scope": {
|
||||
"label": "Portée",
|
||||
"global": "Global",
|
||||
"camera": "Caméra : {{cameraName}}"
|
||||
},
|
||||
"field": {
|
||||
"label": "Champ"
|
||||
},
|
||||
"value": {
|
||||
"label": "Nouvelle valeur",
|
||||
"reset": "Réinitialiser"
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
Some files were not shown because too many files have changed in this diff Show More
Loading…
Reference in New Issue
Block a user