mirror of
https://github.com/blakeblackshear/frigate.git
synced 2025-08-18 13:47:20 +02:00
Compare commits
27 Commits
v0.16.0-rc
...
master
Author | SHA1 | Date | |
---|---|---|---|
|
9ed7ccab75 | ||
|
ceced7cc91 | ||
|
1db26cb41e | ||
|
6840415b6c | ||
|
06539c925c | ||
|
addb4e6891 | ||
|
fb290c411b | ||
|
89db960c05 | ||
|
2cde58037d | ||
|
d1be614a10 | ||
|
93c7c8c518 | ||
|
c83a35d090 | ||
|
d31a4e3443 | ||
|
c2f8de94e8 | ||
|
f46560d2bf | ||
|
a1acb504ee | ||
|
3d79eef227 | ||
|
efe0d2a931 | ||
|
cbd5bdfd88 | ||
|
6f2e6c4cb2 | ||
|
84f48ee3eb | ||
|
49793aa655 | ||
|
4869f46ab6 | ||
|
5e5beb9837 | ||
|
334b6670e1 | ||
|
b5067c07f8 | ||
|
21e9b2f2ce |
2
Makefile
2
Makefile
@ -1,7 +1,7 @@
|
||||
default_target: local
|
||||
|
||||
COMMIT_HASH := $(shell git log -1 --pretty=format:"%h"|tail -1)
|
||||
VERSION = 0.16.0
|
||||
VERSION = 0.16.1
|
||||
IMAGE_REPO ?= ghcr.io/blakeblackshear/frigate
|
||||
GITHUB_REF_NAME ?= $(shell git rev-parse --abbrev-ref HEAD)
|
||||
BOARDS= #Initialized empty
|
||||
|
@ -152,7 +152,7 @@ ARG TARGETARCH
|
||||
# Use a separate container to build wheels to prevent build dependencies in final image
|
||||
RUN apt-get -qq update \
|
||||
&& apt-get -qq install -y \
|
||||
apt-transport-https wget \
|
||||
apt-transport-https wget unzip \
|
||||
&& apt-get -qq update \
|
||||
&& apt-get -qq install -y \
|
||||
python3.11 \
|
||||
|
@ -2,18 +2,25 @@
|
||||
|
||||
set -euxo pipefail
|
||||
|
||||
SQLITE3_VERSION="96c92aba00c8375bc32fafcdf12429c58bd8aabfcadab6683e35bbb9cdebf19e" # 3.46.0
|
||||
SQLITE3_VERSION="3.46.1"
|
||||
PYSQLITE3_VERSION="0.5.3"
|
||||
|
||||
# Fetch the source code for the latest release of Sqlite.
|
||||
# Fetch the pre-built sqlite amalgamation instead of building from source
|
||||
if [[ ! -d "sqlite" ]]; then
|
||||
wget https://www.sqlite.org/src/tarball/sqlite.tar.gz?r=${SQLITE3_VERSION} -O sqlite.tar.gz
|
||||
tar xzf sqlite.tar.gz
|
||||
cd sqlite/
|
||||
LIBS="-lm" ./configure --disable-tcl --enable-tempstore=always
|
||||
make sqlite3.c
|
||||
mkdir sqlite
|
||||
cd sqlite
|
||||
|
||||
# Download the pre-built amalgamation from sqlite.org
|
||||
# For SQLite 3.46.1, the amalgamation version is 3460100
|
||||
SQLITE_AMALGAMATION_VERSION="3460100"
|
||||
|
||||
wget https://www.sqlite.org/2024/sqlite-amalgamation-${SQLITE_AMALGAMATION_VERSION}.zip -O sqlite-amalgamation.zip
|
||||
unzip sqlite-amalgamation.zip
|
||||
mv sqlite-amalgamation-${SQLITE_AMALGAMATION_VERSION}/* .
|
||||
rmdir sqlite-amalgamation-${SQLITE_AMALGAMATION_VERSION}
|
||||
rm sqlite-amalgamation.zip
|
||||
|
||||
cd ../
|
||||
rm sqlite.tar.gz
|
||||
fi
|
||||
|
||||
# Grab the pysqlite3 source code.
|
||||
|
@ -57,9 +57,17 @@ fi
|
||||
|
||||
# arch specific packages
|
||||
if [[ "${TARGETARCH}" == "amd64" ]]; then
|
||||
# Install non-free version of i965 driver
|
||||
CODENAME=$(grep VERSION_CODENAME= /etc/os-release | cut -d= -f2) \
|
||||
&& sed -i -E "s/^(deb http:\/\/deb\.debian\.org\/debian ${CODENAME} main)(.*)$/\1 contrib non-free non-free-firmware\2/" /etc/apt/sources.list \
|
||||
&& apt-get -qq update \
|
||||
&& apt-get install --no-install-recommends --no-install-suggests -y i965-va-driver-shaders \
|
||||
&& sed -i -E "s/(deb http:\/\/deb\.debian\.org\/debian ${CODENAME} main) contrib non-free non-free-firmware/\1/" /etc/apt/sources.list \
|
||||
&& apt-get update
|
||||
|
||||
# install amd / intel-i965 driver packages
|
||||
apt-get -qq install --no-install-recommends --no-install-suggests -y \
|
||||
i965-va-driver intel-gpu-tools onevpl-tools \
|
||||
intel-gpu-tools onevpl-tools \
|
||||
libva-drm2 \
|
||||
mesa-va-drivers radeontop
|
||||
|
||||
|
@ -13,6 +13,7 @@ RUN sed -i "/https:\/\//d" /requirements-wheels.txt
|
||||
RUN sed -i "/onnxruntime/d" /requirements-wheels.txt
|
||||
RUN pip3 wheel --wheel-dir=/rk-wheels -c /requirements-wheels.txt -r /requirements-wheels-rk.txt
|
||||
RUN rm -rf /rk-wheels/opencv_python-*
|
||||
RUN rm -rf /rk-wheels/torch-*
|
||||
|
||||
FROM deps AS rk-frigate
|
||||
ARG TARGETARCH
|
||||
|
@ -99,6 +99,12 @@ genai:
|
||||
model: gemini-1.5-flash
|
||||
```
|
||||
|
||||
:::note
|
||||
|
||||
To use a different Gemini-compatible API endpoint, set the `GEMINI_BASE_URL` environment variable to your provider's API URL.
|
||||
|
||||
:::
|
||||
|
||||
## OpenAI
|
||||
|
||||
OpenAI does not have a free tier for their API. With the release of gpt-4o, pricing has been reduced and each generation should cost fractions of a cent if you choose to go this route.
|
||||
|
@ -438,7 +438,7 @@ record:
|
||||
# Optional: Number of minutes to wait between cleanup runs (default: shown below)
|
||||
# This can be used to reduce the frequency of deleting recording segments from disk if you want to minimize i/o
|
||||
expire_interval: 60
|
||||
# Optional: Sync recordings with disk on startup and once a day (default: shown below).
|
||||
# Optional: Two-way sync recordings database with disk on startup and once a day (default: shown below).
|
||||
sync_recordings: False
|
||||
# Optional: Retention settings for recording
|
||||
retain:
|
||||
|
74
docs/docs/frigate/planning_setup.md
Normal file
74
docs/docs/frigate/planning_setup.md
Normal file
@ -0,0 +1,74 @@
|
||||
---
|
||||
id: planning_setup
|
||||
title: Planning a New Installation
|
||||
---
|
||||
|
||||
Choosing the right hardware for your Frigate NVR setup is important for optimal performance and a smooth experience. This guide will walk you through the key considerations, focusing on the number of cameras and the hardware required for efficient object detection.
|
||||
|
||||
## Key Considerations
|
||||
|
||||
### Number of Cameras and Simultaneous Activity
|
||||
|
||||
The most fundamental factor in your hardware decision is the number of cameras you plan to use. However, it's not just about the raw count; it's also about how many of those cameras are likely to see activity and require object detection simultaneously.
|
||||
|
||||
When motion is detected in a camera's feed, regions of that frame are sent to your chosen [object detection hardware](/configuration/object_detectors).
|
||||
|
||||
- **Low Simultaneous Activity (1-6 cameras with occasional motion)**: If you have a few cameras in areas with infrequent activity (e.g., a seldom-used backyard, a quiet interior), the demand on your object detection hardware will be lower. A single, entry-level AI accelerator will suffice.
|
||||
- **Moderate Simultaneous Activity (6-12 cameras with some overlapping motion)**: For setups with more cameras, especially in areas like a busy street or a property with multiple access points, it's more likely that several cameras will capture activity at the same time. This increases the load on your object detection hardware, requiring more processing power.
|
||||
- **High Simultaneous Activity (12+ cameras or highly active zones)**: Large installations or scenarios where many cameras frequently capture activity (e.g., busy street with overview, identification, dedicated LPR cameras, etc.) will necessitate robust object detection capabilities. You'll likely need multiple entry-level AI accelerators or a more powerful single unit such as a discrete GPU.
|
||||
- **Commercial Installations (40+ cameras)**: Commercial installations or scenarios where a substantial number of cameras capture activity (e.g., a commercial property, an active public space) will necessitate robust object detection capabilities. You'll likely need a modern discrete GPU.
|
||||
|
||||
### Video Decoding
|
||||
|
||||
Modern CPUs with integrated GPUs (Intel Quick Sync, AMD VCN) or dedicated GPUs can significantly offload video decoding from the main CPU, freeing up resources. This is highly recommended, especially for multiple cameras.
|
||||
|
||||
:::tip
|
||||
|
||||
For commercial installations it is important to verify the number of supported concurrent streams on your GPU, many consumer GPUs max out at ~20 concurrent camera streams.
|
||||
|
||||
:::
|
||||
|
||||
## Hardware Considerations
|
||||
|
||||
### Object Detection
|
||||
|
||||
There are many different hardware options for object detection depending on priorities and available hardware. See [the recommended hardware page](./hardware.md#detectors) for more specifics on what hardware is recommended for object detection.
|
||||
|
||||
### Storage
|
||||
|
||||
Storage is an important consideration when planning a new installation. To get a more precise estimate of your storage requirements, you can use an IP camera storage calculator. Websites like [IPConfigure Storage Calculator](https://calculator.ipconfigure.com/) can help you determine the necessary disk space based on your camera settings.
|
||||
|
||||
|
||||
#### SSDs (Solid State Drives)
|
||||
|
||||
SSDs are an excellent choice for Frigate, offering high speed and responsiveness. The older concern that SSDs would quickly "wear out" from constant video recording is largely no longer valid for modern consumer and enterprise-grade SSDs.
|
||||
|
||||
- Longevity: Modern SSDs are designed with advanced wear-leveling algorithms and significantly higher "Terabytes Written" (TBW) ratings than earlier models. For typical home NVR use, a good quality SSD will likely outlast the useful life of your NVR hardware itself.
|
||||
- Performance: SSDs excel at handling the numerous small write operations that occur during continuous video recording and can significantly improve the responsiveness of the Frigate UI and clip retrieval.
|
||||
- Silence and Efficiency: SSDs produce no noise and consume less power than traditional HDDs.
|
||||
|
||||
#### HDDs (Hard Disk Drives)
|
||||
|
||||
Traditional Hard Disk Drives (HDDs) remain a great and often more cost-effective option for long-term video storage, especially for larger setups where raw capacity is prioritized.
|
||||
|
||||
- Cost-Effectiveness: HDDs offer the best cost per gigabyte, making them ideal for storing many days, weeks, or months of continuous footage.
|
||||
- Capacity: HDDs are available in much larger capacities than most consumer SSDs, which is beneficial for extensive video archives.
|
||||
- NVR-Rated Drives: If choosing an HDD, consider drives specifically designed for surveillance (NVR) use, such as Western Digital Purple or Seagate SkyHawk. These drives are engineered for 24/7 operation and continuous write workloads, offering improved reliability compared to standard desktop drives.
|
||||
|
||||
Determining Your Storage Needs
|
||||
The amount of storage you need will depend on several factors:
|
||||
|
||||
- Number of Cameras: More cameras naturally require more space.
|
||||
- Resolution and Framerate: Higher resolution (e.g., 4K) and higher framerate (e.g., 30fps) streams consume significantly more storage.
|
||||
- Recording Method: Continuous recording uses the most space. motion-only recording or object-triggered recording can save space, but may miss some footage.
|
||||
- Retention Period: How many days, weeks, or months of footage do you want to keep?
|
||||
|
||||
#### Network Storage (NFS/SMB)
|
||||
|
||||
While supported, using network-attached storage (NAS) for recordings can introduce latency and network dependency considerations. For optimal performance and reliability, it is generally recommended to have local storage for your Frigate recordings. If using a NAS, ensure your network connection to it is robust and fast (Gigabit Ethernet at minimum) and that the NAS itself can handle the continuous write load.
|
||||
|
||||
### RAM (Memory)
|
||||
|
||||
- **Basic Minimum: 4GB RAM**: This is generally sufficient for a very basic Frigate setup with a few cameras and a dedicated object detection accelerator, without running any enrichments. Performance might be tight, especially with higher resolution streams or numerous detections.
|
||||
- **Minimum for Enrichments: 8GB RAM**: If you plan to utilize Frigate's enrichment features (e.g., facial recognition, license plate recognition, or other AI models that run alongside standard object detection), 8GB of RAM should be considered the minimum. Enrichments require additional memory to load and process their respective models and data.
|
||||
- **Recommended: 16GB RAM**: For most users, especially those with many cameras (8+) or who plan to heavily leverage enrichments, 16GB of RAM is highly recommended. This provides ample headroom for smooth operation, reduces the likelihood of swapping to disk (which can impact performance), and allows for future expansion.
|
@ -5,7 +5,7 @@ title: Updating
|
||||
|
||||
# Updating Frigate
|
||||
|
||||
The current stable version of Frigate is **0.15.0**. The release notes and any breaking changes for this version can be found on the [Frigate GitHub releases page](https://github.com/blakeblackshear/frigate/releases/tag/v0.15.0).
|
||||
The current stable version of Frigate is **0.16.0**. The release notes and any breaking changes for this version can be found on the [Frigate GitHub releases page](https://github.com/blakeblackshear/frigate/releases/tag/v0.16.0).
|
||||
|
||||
Keeping Frigate up to date ensures you benefit from the latest features, performance improvements, and bug fixes. The update process varies slightly depending on your installation method (Docker, Home Assistant Addon, etc.). Below are instructions for the most common setups.
|
||||
|
||||
@ -33,21 +33,21 @@ If you’re running Frigate via Docker (recommended method), follow these steps:
|
||||
2. **Update and Pull the Latest Image**:
|
||||
|
||||
- If using Docker Compose:
|
||||
- Edit your `docker-compose.yml` file to specify the desired version tag (e.g., `0.15.0` instead of `0.14.1`). For example:
|
||||
- Edit your `docker-compose.yml` file to specify the desired version tag (e.g., `0.16.0` instead of `0.15.2`). For example:
|
||||
```yaml
|
||||
services:
|
||||
frigate:
|
||||
image: ghcr.io/blakeblackshear/frigate:0.15.0
|
||||
image: ghcr.io/blakeblackshear/frigate:0.16.0
|
||||
```
|
||||
- Then pull the image:
|
||||
```bash
|
||||
docker pull ghcr.io/blakeblackshear/frigate:0.15.0
|
||||
docker pull ghcr.io/blakeblackshear/frigate:0.16.0
|
||||
```
|
||||
- **Note for `stable` Tag Users**: If your `docker-compose.yml` uses the `stable` tag (e.g., `ghcr.io/blakeblackshear/frigate:stable`), you don’t need to update the tag manually. The `stable` tag always points to the latest stable release after pulling.
|
||||
- If using `docker run`:
|
||||
- Pull the image with the appropriate tag (e.g., `0.15.0`, `0.15.0-tensorrt`, or `stable`):
|
||||
- Pull the image with the appropriate tag (e.g., `0.16.0`, `0.16.0-tensorrt`, or `stable`):
|
||||
```bash
|
||||
docker pull ghcr.io/blakeblackshear/frigate:0.15.0
|
||||
docker pull ghcr.io/blakeblackshear/frigate:0.16.0
|
||||
```
|
||||
|
||||
3. **Start the Container**:
|
||||
@ -105,8 +105,8 @@ If an update causes issues:
|
||||
1. Stop Frigate.
|
||||
2. Restore your backed-up config file and database.
|
||||
3. Revert to the previous image version:
|
||||
- For Docker: Specify an older tag (e.g., `ghcr.io/blakeblackshear/frigate:0.14.1`) in your `docker run` command.
|
||||
- For Docker Compose: Edit your `docker-compose.yml`, specify the older version tag (e.g., `ghcr.io/blakeblackshear/frigate:0.14.1`), and re-run `docker compose up -d`.
|
||||
- For Docker: Specify an older tag (e.g., `ghcr.io/blakeblackshear/frigate:0.15.2`) in your `docker run` command.
|
||||
- For Docker Compose: Edit your `docker-compose.yml`, specify the older version tag (e.g., `ghcr.io/blakeblackshear/frigate:0.15.2`), and re-run `docker compose up -d`.
|
||||
- For Home Assistant: Reinstall the previous addon version manually via the repository if needed and restart the addon.
|
||||
4. Verify the old version is running again.
|
||||
|
||||
|
@ -7,6 +7,7 @@ const sidebars: SidebarsConfig = {
|
||||
Frigate: [
|
||||
'frigate/index',
|
||||
'frigate/hardware',
|
||||
'frigate/planning_setup',
|
||||
'frigate/installation',
|
||||
'frigate/updating',
|
||||
'frigate/camera_setup',
|
||||
|
@ -20,7 +20,7 @@ from fastapi.encoders import jsonable_encoder
|
||||
from fastapi.params import Depends
|
||||
from fastapi.responses import JSONResponse, PlainTextResponse, StreamingResponse
|
||||
from markupsafe import escape
|
||||
from peewee import operator
|
||||
from peewee import SQL, operator
|
||||
from pydantic import ValidationError
|
||||
|
||||
from frigate.api.auth import require_role
|
||||
@ -685,7 +685,14 @@ def plusModels(request: Request, filterByCurrentModelDetector: bool = False):
|
||||
@router.get("/recognized_license_plates")
|
||||
def get_recognized_license_plates(split_joined: Optional[int] = None):
|
||||
try:
|
||||
events = Event.select(Event.data).distinct()
|
||||
query = (
|
||||
Event.select(
|
||||
SQL("json_extract(data, '$.recognized_license_plate') AS plate")
|
||||
)
|
||||
.where(SQL("json_extract(data, '$.recognized_license_plate') IS NOT NULL"))
|
||||
.distinct()
|
||||
)
|
||||
recognized_license_plates = [row[0] for row in query.tuples()]
|
||||
except Exception:
|
||||
return JSONResponse(
|
||||
content=(
|
||||
@ -694,14 +701,6 @@ def get_recognized_license_plates(split_joined: Optional[int] = None):
|
||||
status_code=404,
|
||||
)
|
||||
|
||||
recognized_license_plates = []
|
||||
for e in events:
|
||||
if e.data is not None and "recognized_license_plate" in e.data:
|
||||
recognized_license_plates.append(e.data["recognized_license_plate"])
|
||||
|
||||
while None in recognized_license_plates:
|
||||
recognized_license_plates.remove(None)
|
||||
|
||||
if split_joined:
|
||||
original_recognized_license_plates = recognized_license_plates.copy()
|
||||
for recognized_license_plate in original_recognized_license_plates:
|
||||
|
@ -724,15 +724,24 @@ def events_search(request: Request, params: EventsSearchQueryParams = Depends())
|
||||
|
||||
if (sort is None or sort == "relevance") and search_results:
|
||||
processed_events.sort(key=lambda x: x.get("search_distance", float("inf")))
|
||||
elif min_score is not None and max_score is not None and sort == "score_asc":
|
||||
elif sort == "score_asc":
|
||||
processed_events.sort(key=lambda x: x["data"]["score"])
|
||||
elif min_score is not None and max_score is not None and sort == "score_desc":
|
||||
elif sort == "score_desc":
|
||||
processed_events.sort(key=lambda x: x["data"]["score"], reverse=True)
|
||||
elif min_speed is not None and max_speed is not None and sort == "speed_asc":
|
||||
processed_events.sort(key=lambda x: x["data"]["average_estimated_speed"])
|
||||
elif min_speed is not None and max_speed is not None and sort == "speed_desc":
|
||||
elif sort == "speed_asc":
|
||||
processed_events.sort(
|
||||
key=lambda x: x["data"]["average_estimated_speed"], reverse=True
|
||||
key=lambda x: (
|
||||
x["data"].get("average_estimated_speed") is None,
|
||||
x["data"].get("average_estimated_speed"),
|
||||
)
|
||||
)
|
||||
elif sort == "speed_desc":
|
||||
processed_events.sort(
|
||||
key=lambda x: (
|
||||
x["data"].get("average_estimated_speed") is None,
|
||||
x["data"].get("average_estimated_speed", float("-inf")),
|
||||
),
|
||||
reverse=True,
|
||||
)
|
||||
elif sort == "date_asc":
|
||||
processed_events.sort(key=lambda x: x["start_time"])
|
||||
|
@ -142,15 +142,13 @@ def latest_frame(
|
||||
"regions": params.regions,
|
||||
}
|
||||
quality = params.quality
|
||||
mime_type = extension
|
||||
|
||||
if extension == "png":
|
||||
if extension == Extension.png:
|
||||
quality_params = None
|
||||
elif extension == "webp":
|
||||
elif extension == Extension.webp:
|
||||
quality_params = [int(cv2.IMWRITE_WEBP_QUALITY), quality]
|
||||
else:
|
||||
else: # jpg or jpeg
|
||||
quality_params = [int(cv2.IMWRITE_JPEG_QUALITY), quality]
|
||||
mime_type = "jpeg"
|
||||
|
||||
if camera_name in request.app.frigate_config.cameras:
|
||||
frame = frame_processor.get_current_frame(camera_name, draw_options)
|
||||
@ -193,18 +191,21 @@ def latest_frame(
|
||||
|
||||
frame = cv2.resize(frame, dsize=(width, height), interpolation=cv2.INTER_AREA)
|
||||
|
||||
_, img = cv2.imencode(f".{extension}", frame, quality_params)
|
||||
_, img = cv2.imencode(f".{extension.value}", frame, quality_params)
|
||||
return Response(
|
||||
content=img.tobytes(),
|
||||
media_type=f"image/{mime_type}",
|
||||
media_type=f"image/{extension.value}",
|
||||
headers={
|
||||
"Content-Type": f"image/{mime_type}",
|
||||
"Cache-Control": "no-store"
|
||||
if not params.store
|
||||
else "private, max-age=60",
|
||||
},
|
||||
)
|
||||
elif camera_name == "birdseye" and request.app.frigate_config.birdseye.restream:
|
||||
elif (
|
||||
camera_name == "birdseye"
|
||||
and request.app.frigate_config.birdseye.enabled
|
||||
and request.app.frigate_config.birdseye.restream
|
||||
):
|
||||
frame = cv2.cvtColor(
|
||||
frame_processor.get_current_frame(camera_name),
|
||||
cv2.COLOR_YUV2BGR_I420,
|
||||
@ -215,12 +216,11 @@ def latest_frame(
|
||||
|
||||
frame = cv2.resize(frame, dsize=(width, height), interpolation=cv2.INTER_AREA)
|
||||
|
||||
_, img = cv2.imencode(f".{extension}", frame, quality_params)
|
||||
_, img = cv2.imencode(f".{extension.value}", frame, quality_params)
|
||||
return Response(
|
||||
content=img.tobytes(),
|
||||
media_type=f"image/{mime_type}",
|
||||
media_type=f"image/{extension.value}",
|
||||
headers={
|
||||
"Content-Type": f"image/{mime_type}",
|
||||
"Cache-Control": "no-store"
|
||||
if not params.store
|
||||
else "private, max-age=60",
|
||||
|
@ -21,7 +21,12 @@ router = APIRouter(tags=[Tags.notifications])
|
||||
|
||||
@router.get("/notifications/pubkey")
|
||||
def get_vapid_pub_key(request: Request):
|
||||
if not request.app.frigate_config.notifications.enabled:
|
||||
config = request.app.frigate_config
|
||||
notifications_enabled = config.notifications.enabled
|
||||
camera_notifications_enabled = [
|
||||
c for c in config.cameras.values() if c.enabled and c.notifications.enabled
|
||||
]
|
||||
if not (notifications_enabled or camera_notifications_enabled):
|
||||
return JSONResponse(
|
||||
content=({"success": False, "message": "Notifications are not enabled."}),
|
||||
status_code=400,
|
||||
|
@ -250,6 +250,7 @@ class FrigateApp:
|
||||
and not genai_cameras
|
||||
and not self.config.lpr.enabled
|
||||
and not self.config.face_recognition.enabled
|
||||
and not self.config.classification.bird.enabled
|
||||
):
|
||||
return
|
||||
|
||||
|
@ -66,7 +66,7 @@ def sync_recordings(limited: bool) -> None:
|
||||
|
||||
if float(len(recordings_to_delete)) / max(1, recordings.count()) > 0.5:
|
||||
logger.warning(
|
||||
f"Deleting {(float(len(recordings_to_delete)) / recordings.count()):2f}% of recordings DB entries, could be due to configuration error. Aborting..."
|
||||
f"Deleting {(len(recordings_to_delete) / max(1, recordings.count()) * 100):.2f}% of recordings DB entries, could be due to configuration error. Aborting..."
|
||||
)
|
||||
return False
|
||||
|
||||
@ -106,7 +106,7 @@ def sync_recordings(limited: bool) -> None:
|
||||
|
||||
if float(len(files_to_delete)) / max(1, len(files_on_disk)) > 0.5:
|
||||
logger.debug(
|
||||
f"Deleting {(float(len(files_to_delete)) / len(files_on_disk)):2f}% of recordings DB entries, could be due to configuration error. Aborting..."
|
||||
f"Deleting {(len(files_to_delete) / max(1, len(files_on_disk)) * 100):.2f}% of recordings DB entries, could be due to configuration error. Aborting..."
|
||||
)
|
||||
return False
|
||||
|
||||
|
@ -140,6 +140,7 @@
|
||||
"fr": "Français (French)",
|
||||
"ar": "العربية (Arabic)",
|
||||
"pt": "Português (Portuguese)",
|
||||
"ptBR": "Português brasileiro (Brazilian Portuguese)",
|
||||
"ru": "Русский (Russian)",
|
||||
"de": "Deutsch (German)",
|
||||
"ja": "日本語 (Japanese)",
|
||||
@ -164,6 +165,13 @@
|
||||
"yue": "粵語 (Cantonese)",
|
||||
"th": "ไทย (Thai)",
|
||||
"ca": "Català (Catalan)",
|
||||
"sr": "Српски (Serbian)",
|
||||
"sl": "Slovenščina (Slovenian)",
|
||||
"lt": "Lietuvių (Lithuanian)",
|
||||
"bg": "Български (Bulgarian)",
|
||||
"gl": "Galego (Galician)",
|
||||
"id": "Bahasa Indonesia (Indonesian)",
|
||||
"ur": "اردو (Urdu)",
|
||||
"withSystem": {
|
||||
"label": "Use the system settings for language"
|
||||
}
|
||||
|
19
web/public/locales/gl/audio.json
Normal file
19
web/public/locales/gl/audio.json
Normal file
@ -0,0 +1,19 @@
|
||||
{
|
||||
"speech": "Fala",
|
||||
"babbling": "Balbuxo",
|
||||
"bicycle": "Bicicleta",
|
||||
"yell": "Berro",
|
||||
"car": "Coche",
|
||||
"crying": "Chorando",
|
||||
"sigh": "Suspiro",
|
||||
"singing": "Cantando",
|
||||
"motorcycle": "Motocicleta",
|
||||
"bus": "Bus",
|
||||
"train": "Tren",
|
||||
"boat": "Bote",
|
||||
"bird": "Paxaro",
|
||||
"cat": "Gato",
|
||||
"bellow": "Abaixo",
|
||||
"whoop": "Ei carballeira",
|
||||
"whispering": "Murmurando"
|
||||
}
|
13
web/public/locales/gl/common.json
Normal file
13
web/public/locales/gl/common.json
Normal file
@ -0,0 +1,13 @@
|
||||
{
|
||||
"time": {
|
||||
"untilForTime": "Até {{time}}",
|
||||
"untilForRestart": "Até que se reinicie Frigate.",
|
||||
"justNow": "Xusto agora",
|
||||
"last7": "Últimos 7 días",
|
||||
"last14": "Últimos 14 días",
|
||||
"thisWeek": "Esta semana",
|
||||
"today": "Hoxe",
|
||||
"untilRestart": "Ata o reinicio",
|
||||
"ago": "Fai {{timeAgo}}"
|
||||
}
|
||||
}
|
12
web/public/locales/gl/components/auth.json
Normal file
12
web/public/locales/gl/components/auth.json
Normal file
@ -0,0 +1,12 @@
|
||||
{
|
||||
"form": {
|
||||
"user": "Usuario/a",
|
||||
"password": "Contrasinal",
|
||||
"errors": {
|
||||
"passwordRequired": "Contrasinal obrigatorio",
|
||||
"unknownError": "Erro descoñecido. Revisa os logs.",
|
||||
"usernameRequired": "Usuario/a obrigatorio"
|
||||
},
|
||||
"login": "Iniciar sesión"
|
||||
}
|
||||
}
|
20
web/public/locales/gl/components/camera.json
Normal file
20
web/public/locales/gl/components/camera.json
Normal file
@ -0,0 +1,20 @@
|
||||
{
|
||||
"group": {
|
||||
"label": "Grupos de cámaras",
|
||||
"add": "Engadir Grupo de cámaras",
|
||||
"delete": {
|
||||
"confirm": {
|
||||
"title": "Confirma o borrado",
|
||||
"desc": "Seguro/a que queres borrar o Grupo de cámaras <em>{{name}}</em>?"
|
||||
},
|
||||
"label": "Borrar o Grupo de Cámaras"
|
||||
},
|
||||
"name": {
|
||||
"placeholder": "Introduce un nome…",
|
||||
"errorMessage": {
|
||||
"nameMustNotPeriod": "Grupo de Cámaras non debe conter un punto."
|
||||
}
|
||||
},
|
||||
"edit": "Editar o Grupo de Cámaras"
|
||||
}
|
||||
}
|
21
web/public/locales/gl/components/dialog.json
Normal file
21
web/public/locales/gl/components/dialog.json
Normal file
@ -0,0 +1,21 @@
|
||||
{
|
||||
"restart": {
|
||||
"title": "Estás seguro/a que queres reiniciar Frigate?",
|
||||
"button": "Reiniciar",
|
||||
"restarting": {
|
||||
"button": "Forzar reinicio",
|
||||
"content": "Esta páxina recargarase en {{countdown}} segundos.",
|
||||
"title": "Frigate está Reiniciando"
|
||||
}
|
||||
},
|
||||
"explore": {
|
||||
"plus": {
|
||||
"review": {
|
||||
"question": {
|
||||
"label": "Confirma esta etiqueta para Frigate Plus",
|
||||
"ask_an": "E isto un obxecto <code>{{label}}</code>?"
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
16
web/public/locales/gl/components/filter.json
Normal file
16
web/public/locales/gl/components/filter.json
Normal file
@ -0,0 +1,16 @@
|
||||
{
|
||||
"filter": "Filtrar",
|
||||
"labels": {
|
||||
"label": "Etiquetas",
|
||||
"count_one": "{{count}} Etiqueta",
|
||||
"all": {
|
||||
"short": "Etiquetas",
|
||||
"title": "Todas as Etiquetas"
|
||||
}
|
||||
},
|
||||
"zones": {
|
||||
"all": {
|
||||
"title": "Tódalas zonas"
|
||||
}
|
||||
}
|
||||
}
|
8
web/public/locales/gl/components/icons.json
Normal file
8
web/public/locales/gl/components/icons.json
Normal file
@ -0,0 +1,8 @@
|
||||
{
|
||||
"iconPicker": {
|
||||
"selectIcon": "Selecciona unha icona",
|
||||
"search": {
|
||||
"placeholder": "Pesquisar unha icona…"
|
||||
}
|
||||
}
|
||||
}
|
10
web/public/locales/gl/components/input.json
Normal file
10
web/public/locales/gl/components/input.json
Normal file
@ -0,0 +1,10 @@
|
||||
{
|
||||
"button": {
|
||||
"downloadVideo": {
|
||||
"label": "Descargar vídeo",
|
||||
"toast": {
|
||||
"success": "O teu vídeo de revisión comezou a descargarse."
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
14
web/public/locales/gl/components/player.json
Normal file
14
web/public/locales/gl/components/player.json
Normal file
@ -0,0 +1,14 @@
|
||||
{
|
||||
"noRecordingsFoundForThisTime": "Non se atoparon grabacións para ese período",
|
||||
"noPreviewFound": "Non se atopou previsualización",
|
||||
"submitFrigatePlus": {
|
||||
"submit": "Enviar",
|
||||
"title": "Enviar este frame a Frigate+?"
|
||||
},
|
||||
"stats": {
|
||||
"streamType": {
|
||||
"title": "Tipo de emisión:"
|
||||
}
|
||||
},
|
||||
"noPreviewFoundFor": "Vista Previa non atopada para {{cameraName}}"
|
||||
}
|
18
web/public/locales/gl/objects.json
Normal file
18
web/public/locales/gl/objects.json
Normal file
@ -0,0 +1,18 @@
|
||||
{
|
||||
"person": "Persoa",
|
||||
"bicycle": "Bicicleta",
|
||||
"airplane": "Avión",
|
||||
"motorcycle": "Motocicleta",
|
||||
"bus": "Bus",
|
||||
"train": "Tren",
|
||||
"boat": "Bote",
|
||||
"traffic_light": "Luces de tráfico",
|
||||
"fire_hydrant": "Boca de incendio",
|
||||
"street_sign": "Sinal de tráfico",
|
||||
"stop_sign": "Sinal de Stop",
|
||||
"parking_meter": "Parquímetro",
|
||||
"bench": "Banco",
|
||||
"bird": "Paxaro",
|
||||
"cat": "Gato",
|
||||
"car": "Coche"
|
||||
}
|
12
web/public/locales/gl/views/configEditor.json
Normal file
12
web/public/locales/gl/views/configEditor.json
Normal file
@ -0,0 +1,12 @@
|
||||
{
|
||||
"documentTitle": "Editor de configuración - Frigate",
|
||||
"configEditor": "Editor de Preferencias",
|
||||
"saveOnly": "Só gardar",
|
||||
"toast": {
|
||||
"error": {
|
||||
"savingError": "Erro gardando configuración"
|
||||
}
|
||||
},
|
||||
"saveAndRestart": "Gardar e Reiniciar",
|
||||
"copyConfig": "Copiar Configuración"
|
||||
}
|
10
web/public/locales/gl/views/events.json
Normal file
10
web/public/locales/gl/views/events.json
Normal file
@ -0,0 +1,10 @@
|
||||
{
|
||||
"alerts": "Alertas",
|
||||
"detections": "Deteccións",
|
||||
"allCameras": "Tódalas cámaras",
|
||||
"timeline.aria": "Selecciona liña de tempo",
|
||||
"motion": {
|
||||
"only": "Só movemento",
|
||||
"label": "Movemento"
|
||||
}
|
||||
}
|
12
web/public/locales/gl/views/explore.json
Normal file
12
web/public/locales/gl/views/explore.json
Normal file
@ -0,0 +1,12 @@
|
||||
{
|
||||
"documentTitle": "Explorar - Frigate",
|
||||
"generativeAI": "IA xenerativa",
|
||||
"exploreMore": "Explorar máis obxectos {{label}}",
|
||||
"exploreIsUnavailable": {
|
||||
"title": "Explorar non está Dispoñible",
|
||||
"embeddingsReindexing": {
|
||||
"finishingShortly": "Rematando ceo",
|
||||
"startingUp": "Comezando…"
|
||||
}
|
||||
}
|
||||
}
|
10
web/public/locales/gl/views/exports.json
Normal file
10
web/public/locales/gl/views/exports.json
Normal file
@ -0,0 +1,10 @@
|
||||
{
|
||||
"documentTitle": "Exportar - Frigate",
|
||||
"search": "Pesquisar",
|
||||
"deleteExport.desc": "Seguro que queres borrar {{exportName}}?",
|
||||
"editExport": {
|
||||
"saveExport": "Garda exportación"
|
||||
},
|
||||
"deleteExport": "Borrar exportación",
|
||||
"noExports": "Non se atoparon exportacións"
|
||||
}
|
11
web/public/locales/gl/views/faceLibrary.json
Normal file
11
web/public/locales/gl/views/faceLibrary.json
Normal file
@ -0,0 +1,11 @@
|
||||
{
|
||||
"description": {
|
||||
"addFace": "Navegar para engadir unha nova colección á Libraría de Caras.",
|
||||
"placeholder": "Introduce un nome para esta colección",
|
||||
"invalidName": "Nome non válido. Os nomes só poden incluír letras, números, espazos, apóstrofes, guións baixos e guións."
|
||||
},
|
||||
"details": {
|
||||
"unknown": "Descoñecido",
|
||||
"person": "Persoa"
|
||||
}
|
||||
}
|
19
web/public/locales/gl/views/live.json
Normal file
19
web/public/locales/gl/views/live.json
Normal file
@ -0,0 +1,19 @@
|
||||
{
|
||||
"documentTitle": "Directo - Frigate",
|
||||
"documentTitle.withCamera": "{{camera}} - Directo - Frigate",
|
||||
"twoWayTalk": {
|
||||
"disable": "Deshabilita a Conversa de dous sentidos",
|
||||
"enable": "Habilitar a Conversa de dous sentidos"
|
||||
},
|
||||
"ptz": {
|
||||
"move": {
|
||||
"clickMove": {
|
||||
"label": "Pincha no frame para centrar a cámara"
|
||||
}
|
||||
}
|
||||
},
|
||||
"cameraAudio": {
|
||||
"enable": "Habilitar Audio de cámara"
|
||||
},
|
||||
"lowBandwidthMode": "Modo de Baixa Banda Ancha"
|
||||
}
|
11
web/public/locales/gl/views/recording.json
Normal file
11
web/public/locales/gl/views/recording.json
Normal file
@ -0,0 +1,11 @@
|
||||
{
|
||||
"filter": "Filtrar",
|
||||
"export": "Exportar",
|
||||
"calendar": "Calendario",
|
||||
"toast": {
|
||||
"error": {
|
||||
"noValidTimeSelected": "Rango de tempo inválido"
|
||||
}
|
||||
},
|
||||
"filters": "Filtros"
|
||||
}
|
15
web/public/locales/gl/views/search.json
Normal file
15
web/public/locales/gl/views/search.json
Normal file
@ -0,0 +1,15 @@
|
||||
{
|
||||
"search": "Pesquisar",
|
||||
"savedSearches": "Pesquisas gardadas",
|
||||
"button": {
|
||||
"save": "Gardar pesquisa",
|
||||
"filterActive": "Filtros activos",
|
||||
"clear": "Borrar pesquisa"
|
||||
},
|
||||
"filter": {
|
||||
"label": {
|
||||
"cameras": "Cámaras"
|
||||
}
|
||||
},
|
||||
"searchFor": "Procurar por {{inputValue}}"
|
||||
}
|
11
web/public/locales/gl/views/settings.json
Normal file
11
web/public/locales/gl/views/settings.json
Normal file
@ -0,0 +1,11 @@
|
||||
{
|
||||
"documentTitle": {
|
||||
"default": "Preferencias - Frigate",
|
||||
"authentication": "Configuracións de Autenticación - Frigate",
|
||||
"camera": "Configuracións da Cámara - Frigate",
|
||||
"general": "Configuracións xerais - Frigate",
|
||||
"notifications": "Configuración de Notificacións - Frigate",
|
||||
"enrichments": "Configuración complementarias - Frigate",
|
||||
"masksAndZones": "Editor de máscaras e zonas - Frigate"
|
||||
}
|
||||
}
|
17
web/public/locales/gl/views/system.json
Normal file
17
web/public/locales/gl/views/system.json
Normal file
@ -0,0 +1,17 @@
|
||||
{
|
||||
"documentTitle": {
|
||||
"cameras": "Estatísticas de cámaras - Frigate",
|
||||
"storage": "Estatísticas de Almacenamento - Frigate",
|
||||
"general": "Estatísticas Xerais - Frigate",
|
||||
"enrichments": "Estatísticas complementarias - Frigate",
|
||||
"logs": {
|
||||
"frigate": "Rexistros de Frigate - Frigate"
|
||||
}
|
||||
},
|
||||
"title": "Sistema",
|
||||
"logs": {
|
||||
"download": {
|
||||
"label": "Descargar logs"
|
||||
}
|
||||
}
|
||||
}
|
@ -5,7 +5,7 @@
|
||||
"untilForRestart": "Amíg a Frigate újraindul.",
|
||||
"untilRestart": "Amíg újraindul",
|
||||
"justNow": "Most",
|
||||
"ago": "{{timeAgo}} ezelőtt",
|
||||
"ago": "Ennyi ideje: {{timeAgo}}",
|
||||
"today": "Ma",
|
||||
"yesterday": "Tegnap",
|
||||
"last7": "Elmúlt 7 nap",
|
||||
@ -14,15 +14,15 @@
|
||||
"thisWeek": "Ezen a héten",
|
||||
"lastWeek": "Előző héten",
|
||||
"thisMonth": "Ebben a hónapban",
|
||||
"lastMonth": "Előző hónap",
|
||||
"lastMonth": "Előző hónapban",
|
||||
"5minutes": "5 perc",
|
||||
"10minutes": "10 perc",
|
||||
"30minutes": "30 perc",
|
||||
"1hour": "1 óra",
|
||||
"12hours": "12 óra",
|
||||
"24hours": "24 óra",
|
||||
"pm": "PM",
|
||||
"am": "AM",
|
||||
"pm": "du",
|
||||
"am": "de",
|
||||
"yr": "{{time}} év",
|
||||
"mo": "{{time}} hónap",
|
||||
"d": "{{time}} nap",
|
||||
@ -41,38 +41,38 @@
|
||||
"day_one": "{{time}} nap",
|
||||
"day_other": "{{time}} napok",
|
||||
"formattedTimestamp": {
|
||||
"24hour": "HHH n, ÓÓ:pp:mm",
|
||||
"12hour": "HHH d, ó:pp:mm aaa"
|
||||
"24hour": "MMM d, HH:mm:ss",
|
||||
"12hour": "MMM d, h:mm:ss aaa"
|
||||
},
|
||||
"formattedTimestampMonthDayYear": {
|
||||
"12hour": "HHH d, éééé",
|
||||
"24hour": "HHH n, éééé"
|
||||
"12hour": "MMM d, yyyy",
|
||||
"24hour": "MMM d, yyyy"
|
||||
},
|
||||
"formattedTimestampHourMinute": {
|
||||
"24hour": "ÓÓ:pp",
|
||||
"12hour": "ó:pp aaa"
|
||||
"24hour": "HH:mm",
|
||||
"12hour": "h:mm aaa"
|
||||
},
|
||||
"formattedTimestamp2": {
|
||||
"24hour": "n HHH ÓÓ:pp:mm",
|
||||
"12hour": "HH/NN ó:pp:mma"
|
||||
"24hour": "d MMM HH:mm:ss",
|
||||
"12hour": "MM/dd h:mm:ssa"
|
||||
},
|
||||
"formattedTimestampHourMinuteSecond": {
|
||||
"24hour": "ÓÓ:pp:mm",
|
||||
"12hour": "ó:pp:mm aaa"
|
||||
"24hour": "HH:mm:ss",
|
||||
"12hour": "h:mm:ss aaa"
|
||||
},
|
||||
"formattedTimestampMonthDayYearHourMinute": {
|
||||
"24hour": "HHH n éééé, ÓÓ:pp",
|
||||
"12hour": "HHH n éééé, ó:pp aaa"
|
||||
"24hour": "MMM d yyyy, HH:mm",
|
||||
"12hour": "MMM d yyyy, h:mm aaa"
|
||||
},
|
||||
"formattedTimestampFilename": {
|
||||
"24hour": "HH-nn-éé-ÓÓ-pp-mm",
|
||||
"12hour": "HH-nn-éé-ó-pp-mm-a"
|
||||
"24hour": "yy-MM-dd-HH-mm-ss",
|
||||
"12hour": "yy-MM-dd-h-mm-ss-a"
|
||||
},
|
||||
"formattedTimestampMonthDayHourMinute": {
|
||||
"24hour": "HHH n, ÓÓ:pp",
|
||||
"12hour": "HHH n, ó:pp aaa"
|
||||
"24hour": "MMM d, HH:mm",
|
||||
"12hour": "MMM d, h:mm aaa"
|
||||
},
|
||||
"formattedTimestampMonthDay": "HHH n"
|
||||
"formattedTimestampMonthDay": "MMM d"
|
||||
},
|
||||
"menu": {
|
||||
"darkMode": {
|
||||
|
@ -22,7 +22,7 @@
|
||||
"ask_a": "Ez a tárgy egy <code>{{label}}</code>?",
|
||||
"label": "Erősítse meg ezt a cimkét a Frigate plus felé",
|
||||
"ask_an": "Ez a tárgy egy <code>{{label}}</code>?",
|
||||
"ask_full": "Ez egy <code>{{untranslatedLabel}}</code>{{translatedLabel}} tárgy?"
|
||||
"ask_full": "Ez a tárgy egy <code>{{untranslatedLabel}}</code> ({{translatedLabel}})?"
|
||||
}
|
||||
}
|
||||
},
|
||||
|
@ -31,7 +31,7 @@
|
||||
"recordings": {
|
||||
"documentTitle": "Felvételek - Frigate"
|
||||
},
|
||||
"markTheseItemsAsReviewed": "Ezen elwmek megjelölése áttekintettként",
|
||||
"markTheseItemsAsReviewed": "Ezen elemek megjelölése áttekintettként",
|
||||
"markAsReviewed": "Megjelölés Áttekintettként",
|
||||
"selected_one": "{{count}} kiválasztva",
|
||||
"selected_other": "{{count}} kiválasztva"
|
||||
|
@ -6,7 +6,7 @@
|
||||
"classification": "Osztályozási beállítások - Frigate",
|
||||
"masksAndZones": "Maszk és zónaszerkesztő - Frigate",
|
||||
"object": "Hibakeresés - Frigate",
|
||||
"general": "Áltlános beállítások - Frigate",
|
||||
"general": "Áltlános Beállítások - Frigate",
|
||||
"frigatePlus": "Frigate+ beállítások - Frigate",
|
||||
"notifications": "Értesítések beállítása - Frigate",
|
||||
"motionTuner": "Mozgás Hangoló - Frigate",
|
||||
@ -143,7 +143,7 @@
|
||||
"success": "A kiegészítő beállítások elmentésre kerültek. A módosítások alkalmazásához indítsa újra a Frigate-et."
|
||||
},
|
||||
"unsavedChanges": "Mentetlen gazdagítási beállítás változtatások",
|
||||
"title": "Kiegészítés Beállítások",
|
||||
"title": "Kiegészítők Beállítása",
|
||||
"restart_required": "Újraindítás szükséges (a kiegészítő beállítások megváltoztak)"
|
||||
},
|
||||
"notification": {
|
||||
|
@ -644,7 +644,7 @@
|
||||
}
|
||||
},
|
||||
"title": "Semantisch zoeken",
|
||||
"desc": "Semantische zoektocht in Frigate laat je opsporingsberichten vinden binnen je beoordelingsvoorwerpen met het beeld zelf, een gebruiker-definieerde tekstbeschrijving, of een automatisch gegen.",
|
||||
"desc": "Semantisch zoeken in Frigate stelt je in staat om getraceerde objecten binnen je review-items te vinden, met behulp van de afbeelding zelf, een door de gebruiker gedefinieerde tekstbeschrijving of een automatisch gegenereerde beschrijving.",
|
||||
"readTheDocumentation": "Lees de documentatie"
|
||||
},
|
||||
"faceRecognition": {
|
||||
|
@ -6,7 +6,7 @@
|
||||
"chant": "Canto",
|
||||
"babbling": "Balbuciando",
|
||||
"bellow": "Abaixo",
|
||||
"whoop": "Grito",
|
||||
"whoop": "Grito de Felicidade",
|
||||
"whispering": "Sussurrando",
|
||||
"laughter": "Risada",
|
||||
"snicker": "Risada",
|
||||
@ -69,7 +69,7 @@
|
||||
"bark": "Latido",
|
||||
"yip": "Latido / Grito Agudo",
|
||||
"howl": "Uivado",
|
||||
"bow_wow": "Bow Wow",
|
||||
"bow_wow": "Latido",
|
||||
"growling": "Rosnado",
|
||||
"whimper_dog": "Choro de Cachorro",
|
||||
"purr": "Ronronado",
|
||||
@ -129,7 +129,7 @@
|
||||
"frog": "Sapo",
|
||||
"croak": "Coaxado",
|
||||
"snake": "Cobra",
|
||||
"rattle": "Guizo",
|
||||
"rattle": "Chocalho",
|
||||
"whale_vocalization": "Vocalização de Baleia",
|
||||
"music": "Música",
|
||||
"musical_instrument": "Instrumento Musical",
|
||||
@ -139,7 +139,7 @@
|
||||
"bass_guitar": "Baixo",
|
||||
"acoustic_guitar": "Violão Acústico",
|
||||
"steel_guitar": "Guitarra Havaiana",
|
||||
"tapping": "Tapping",
|
||||
"tapping": "Batidas Leves",
|
||||
"strum": "Dedilhado",
|
||||
"banjo": "Banjo",
|
||||
"sitar": "Sitar",
|
||||
@ -163,5 +163,267 @@
|
||||
"drum_roll": "Tambores Rufando",
|
||||
"bass_drum": "Bumbo",
|
||||
"timpani": "Tímpanos (Instrumento Musical)",
|
||||
"tabla": "Tabla"
|
||||
"tabla": "Tabla",
|
||||
"wood_block": "Bloco de Madeira",
|
||||
"bagpipes": "Gaita de Fole",
|
||||
"pop_music": "Música Pop",
|
||||
"grunge": "Grunge",
|
||||
"middle_eastern_music": "Música do Oriente Médio",
|
||||
"jazz": "Jazz",
|
||||
"disco": "Disco",
|
||||
"classical_music": "Música Clássica",
|
||||
"opera": "Ópera",
|
||||
"electronic_music": "Música Eletrónica",
|
||||
"house_music": "Música House",
|
||||
"techno": "Techno",
|
||||
"dubstep": "Dubstep",
|
||||
"drum_and_bass": "Drum and Bass",
|
||||
"electronica": "Eletrónica",
|
||||
"cymbal": "Címbalo",
|
||||
"hi_hat": "Chimbau",
|
||||
"tambourine": "Pandeiro",
|
||||
"maraca": "Maraca",
|
||||
"gong": "Gongo",
|
||||
"tubular_bells": "Sinos Tubulares",
|
||||
"mallet_percussion": "Percussão de Martelo",
|
||||
"marimba": "Marimba",
|
||||
"glockenspiel": "Glockenspiel",
|
||||
"vibraphone": "Vibrafone",
|
||||
"steelpan": "Panela de Aço",
|
||||
"orchestra": "Orquestra",
|
||||
"brass_instrument": "Instrumento de Metal",
|
||||
"french_horn": "Trompa Francesa",
|
||||
"trumpet": "Trombeta",
|
||||
"trombone": "Trombone",
|
||||
"bowed_string_instrument": "Instrumento de Cordas Friccionadas",
|
||||
"string_section": "Seção de Cordas",
|
||||
"violin": "Violino",
|
||||
"pizzicato": "Pizzicato",
|
||||
"cello": "Violoncelo",
|
||||
"double_bass": "Contrabaixo",
|
||||
"wind_instrument": "Instrumento de Sopro",
|
||||
"flute": "Flauta",
|
||||
"saxophone": "Saxofone",
|
||||
"clarinet": "Clarinete",
|
||||
"harp": "Harpa",
|
||||
"bell": "Sino",
|
||||
"church_bell": "Sino de Igreja",
|
||||
"jingle_bell": "Guizo",
|
||||
"bicycle_bell": "Campainha de Bicicleta",
|
||||
"tuning_fork": "Diapasão",
|
||||
"chime": "Carrilhão",
|
||||
"wind_chime": "Sinos de Vento",
|
||||
"harmonica": "Gaita",
|
||||
"accordion": "Acordeão",
|
||||
"didgeridoo": "Didjeridu",
|
||||
"theremin": "Teremim",
|
||||
"scratching": "Arranhado",
|
||||
"hip_hop_music": "Música Hip-Hop",
|
||||
"beatboxing": "Beatbox",
|
||||
"rock_music": "Rock",
|
||||
"heavy_metal": "Heavy Metal",
|
||||
"punk_rock": "Punk Rock",
|
||||
"progressive_rock": "Rock Progressivo",
|
||||
"rock_and_roll": "Rock and Roll",
|
||||
"psychedelic_rock": "Rock Psicodélico",
|
||||
"rhythm_and_blues": "Rhythm and Blues",
|
||||
"soul_music": "Música Soul",
|
||||
"music_of_latin_america": "Music of Latin America",
|
||||
"salsa_music": "Música Salsa",
|
||||
"flamenco": "Flamenco",
|
||||
"blues": "Blues",
|
||||
"music_for_children": "Música para Crianças",
|
||||
"new-age_music": "Música New Age",
|
||||
"vocal_music": "Música Vocal",
|
||||
"a_capella": "A Capella",
|
||||
"music_of_africa": "Music of Africa",
|
||||
"afrobeat": "Afrobeat",
|
||||
"christian_music": "Música Cristã",
|
||||
"gospel_music": "Música Gospel",
|
||||
"music_of_asia": "Music of Asia",
|
||||
"carnatic_music": "Música Carnática",
|
||||
"music_of_bollywood": "Música de Bollywood",
|
||||
"ska": "Ska",
|
||||
"traditional_music": "Música Tradicional",
|
||||
"independent_music": "Música Independente",
|
||||
"song": "Música",
|
||||
"thunderstorm": "Tempestade",
|
||||
"thunder": "Trovão",
|
||||
"water": "Água",
|
||||
"rain": "Chuva",
|
||||
"raindrop": "Gota de Chuva",
|
||||
"rain_on_surface": "Chuva na Superfície",
|
||||
"stream": "Transmissão",
|
||||
"waterfall": "Cachoeira",
|
||||
"ocean": "Oceano",
|
||||
"waves": "Ondas",
|
||||
"steam": "Vapor",
|
||||
"gurgling": "Borbulhado",
|
||||
"fire": "Fogo",
|
||||
"crackle": "Estalo",
|
||||
"sailboat": "Veleiro",
|
||||
"rowboat": "Barco a Remo",
|
||||
"motorboat": "Lancha",
|
||||
"ship": "Navio",
|
||||
"motor_vehicle": "Veículo Motorizado",
|
||||
"toot": "Buzinado",
|
||||
"car_alarm": "Alarme de Carro",
|
||||
"power_windows": "Vidros Elétricos",
|
||||
"skidding": "Derrapado",
|
||||
"singing_bowl": "Tigela Tibetana",
|
||||
"reggae": "Reggae",
|
||||
"country": "País",
|
||||
"swing_music": "Música Swing",
|
||||
"bluegrass": "Música Bluegrass",
|
||||
"funk": "Funk",
|
||||
"folk_music": "Música Folk",
|
||||
"electronic_dance_music": "Música Eletrônica",
|
||||
"ambient_music": "Música Ambiente",
|
||||
"trance_music": "Música Trance",
|
||||
"background_music": "Música de Fundo",
|
||||
"theme_music": "Música Tema",
|
||||
"jingle": "Jingle",
|
||||
"soundtrack_music": "Música de Trilha Sonora",
|
||||
"lullaby": "Canção de Ninar",
|
||||
"video_game_music": "Música de Video Game",
|
||||
"christmas_music": "Música Natalina",
|
||||
"dance_music": "Música Dance",
|
||||
"wedding_music": "Música de Casamento",
|
||||
"happy_music": "Música Feliz",
|
||||
"sad_music": "Música Triste",
|
||||
"tender_music": "Música Suave",
|
||||
"exciting_music": "Música Empolgante",
|
||||
"angry_music": "Música Raivosa",
|
||||
"scary_music": "Música Assustadora",
|
||||
"wind": "Vento",
|
||||
"rustling_leaves": "Folhas Farfalhantes",
|
||||
"fixed-wing_aircraft": "Aeronave de Asa Fixa",
|
||||
"engine": "Motor",
|
||||
"light_engine": "Motor Leve",
|
||||
"dental_drill's_drill": "Broca Odontológica",
|
||||
"lawn_mower": "Cortador de Grama",
|
||||
"chainsaw": "Motosserra",
|
||||
"medium_engine": "Motor Médio",
|
||||
"heavy_engine": "Motor Pesado",
|
||||
"engine_knocking": "Motor Batendo",
|
||||
"engine_starting": "Motor Partindo",
|
||||
"idling": "Marcha Lenta",
|
||||
"chopping": "Cortando",
|
||||
"frying": "Fritando",
|
||||
"microwave_oven": "Forno Microondas",
|
||||
"water_tap": "Torneira de Água",
|
||||
"bathtub": "Banheira",
|
||||
"toilet_flush": "Descarga de Vaso Sanitário",
|
||||
"computer_keyboard": "Teclado de Computador",
|
||||
"writing": "Escrita",
|
||||
"alarm": "Alarme",
|
||||
"telephone": "Telefone",
|
||||
"telephone_bell_ringing": "Telefone Tocando",
|
||||
"ringtone": "Toque de Celular",
|
||||
"telephone_dialing": "Telefone Discando",
|
||||
"dial_tone": "Tom de Discagem",
|
||||
"busy_signal": "Sinal de Ocupado",
|
||||
"alarm_clock": "Despertador",
|
||||
"siren": "Sirene",
|
||||
"civil_defense_siren": "Sirene de Defesa Civil",
|
||||
"wind_noise": "Ruído de Vento",
|
||||
"tire_squeal": "Pneus Cantando",
|
||||
"car_passing_by": "Carro Passando",
|
||||
"race_car": "Carro de Corrida",
|
||||
"truck": "Pickup / Caminhão",
|
||||
"air_brake": "Freios a Ar",
|
||||
"air_horn": "Buzina a Ar",
|
||||
"reversing_beeps": "Alarme de Ré",
|
||||
"ice_cream_truck": "Carro de Sorvete",
|
||||
"emergency_vehicle": "Veículo de Emergência",
|
||||
"police_car": "Carro de Polícia",
|
||||
"ambulance": "Ambulância",
|
||||
"fire_engine": "Caminhão de Bombeiros",
|
||||
"traffic_noise": "Barulho de Tráfego",
|
||||
"rail_transport": "Transporte Ferroviário",
|
||||
"train_whistle": "Apito de Trem",
|
||||
"train_horn": "Buzina de Trem",
|
||||
"railroad_car": "Vagão de Trem",
|
||||
"train_wheels_squealing": "Rodas de Trem Rangendo",
|
||||
"subway": "Metrô",
|
||||
"aircraft": "Aeronave",
|
||||
"aircraft_engine": "Motor de Aeronave",
|
||||
"jet_engine": "Motor a Jato",
|
||||
"propeller": "Hélice",
|
||||
"helicopter": "Helicóptero",
|
||||
"accelerating": "Acelerando",
|
||||
"doorbell": "Campainha",
|
||||
"ding-dong": "Toque de Campainha",
|
||||
"sliding_door": "Porta de Correr",
|
||||
"slam": "Batida Forte",
|
||||
"knock": "Batida na Porta",
|
||||
"burst": "Estouro / Rajada",
|
||||
"eruption": "Erupção",
|
||||
"boom": "Estrondo",
|
||||
"wood": "Madeira",
|
||||
"chop": "Barulho de Corte",
|
||||
"splinter": "Lascado",
|
||||
"crack": "Rachado",
|
||||
"glass": "Vidro",
|
||||
"chink": "Fenda",
|
||||
"shatter": "Estilhaçado",
|
||||
"silence": "Silêncio",
|
||||
"sound_effect": "Efeito Sonoro",
|
||||
"environmental_noise": "Ruido Ambiente",
|
||||
"static": "Estático",
|
||||
"white_noise": "Ruido Branco",
|
||||
"pink_noise": "Ruido Rosa",
|
||||
"television": "Televisão",
|
||||
"radio": "Rádio",
|
||||
"field_recording": "Gravação de Campo",
|
||||
"scream": "Grito",
|
||||
"tap": "Toque",
|
||||
"squeak": "Rangido",
|
||||
"cupboard_open_or_close": "Cristaleira Abrindo ou Fechando",
|
||||
"drawer_open_or_close": "Gaveteiro Abrindo ou Fechando",
|
||||
"dishes": "Pratos",
|
||||
"cutlery": "Talheres",
|
||||
"electric_toothbrush": "Escova de Dentes Elétrica",
|
||||
"vacuum_cleaner": "Aspirador de Pó",
|
||||
"zipper": "Zíper",
|
||||
"keys_jangling": "Chaves Chacoalhando",
|
||||
"coin": "Moeda",
|
||||
"electric_shaver": "Barbeador Elétrico",
|
||||
"shuffling_cards": "Embaralhar de Cartas",
|
||||
"typing": "Digitação",
|
||||
"typewriter": "Máquina de Escrever",
|
||||
"buzzer": "Zumbador",
|
||||
"smoke_detector": "Detector de Fumaça",
|
||||
"fire_alarm": "Alarme de Incêndio",
|
||||
"foghorn": "Buzina de Nevoeiro",
|
||||
"whistle": "Apito",
|
||||
"steam_whistle": "Apito a Vapor",
|
||||
"mechanisms": "Mecanismos",
|
||||
"ratchet": "Catraca",
|
||||
"tick": "Tique",
|
||||
"tick-tock": "Tique-Toque",
|
||||
"gears": "Engrenagens",
|
||||
"pulleys": "Polias",
|
||||
"sewing_machine": "Máquina de Costura",
|
||||
"mechanical_fan": "Ventilador Mecânico",
|
||||
"air_conditioning": "Ar-Condicionado",
|
||||
"cash_register": "Caixa Registradora",
|
||||
"printer": "Impressora",
|
||||
"single-lens_reflex_camera": "Câmera Single-Lens Reflex",
|
||||
"tools": "Ferramentas",
|
||||
"hammer": "Martelo",
|
||||
"jackhammer": "Britadeira",
|
||||
"sawing": "Som de Serra",
|
||||
"filing": "Som de Lima",
|
||||
"sanding": "Lixamento",
|
||||
"power_tool": "Ferramenta Elétrica",
|
||||
"drill": "Furadeira",
|
||||
"explosion": "Explosão",
|
||||
"gunshot": "Tiro",
|
||||
"machine_gun": "Metralhadora",
|
||||
"fusillade": "Fuzilamento",
|
||||
"artillery_fire": "Fogo de Artilharia",
|
||||
"cap_gun": "Espoleta",
|
||||
"fireworks": "Fogos de Artifício",
|
||||
"firecracker": "Rojões"
|
||||
}
|
||||
|
@ -25,27 +25,27 @@
|
||||
"yr": "{{time}}ano",
|
||||
"year_one": "{{time}} ano",
|
||||
"year_many": "{{time}} anos",
|
||||
"year_other": "",
|
||||
"year_other": "{{time}} anos",
|
||||
"mo": "{{time}}mês",
|
||||
"month_one": "{{time}} mês",
|
||||
"month_many": "{{time}} meses",
|
||||
"month_other": "",
|
||||
"month_other": "{{time}} meses",
|
||||
"d": "{{time}} dia",
|
||||
"day_one": "{{time}} dia",
|
||||
"day_many": "{{time}} dias",
|
||||
"day_other": "",
|
||||
"day_other": "{{time}} dias",
|
||||
"h": "{{time}}h",
|
||||
"hour_one": "{{time}} hora",
|
||||
"hour_many": "{{time}} horas",
|
||||
"hour_other": "",
|
||||
"hour_other": "{{time}} horas",
|
||||
"m": "{{time}}m",
|
||||
"minute_one": "{{time}} minuto",
|
||||
"minute_many": "{{time}} minutos",
|
||||
"minute_other": "",
|
||||
"minute_other": "{{time}} minutos",
|
||||
"s": "{{time}}s",
|
||||
"second_one": "{{time}} segundo",
|
||||
"second_many": "{{time}} segundos",
|
||||
"second_other": "",
|
||||
"second_other": "{{time}} segundos",
|
||||
"formattedTimestamp": {
|
||||
"12hour": "d MMM,h:mm:ss aaa",
|
||||
"24hour": "d MMM, HH:mm:ss"
|
||||
@ -83,7 +83,7 @@
|
||||
"selectItem": "Selecione {{item}}",
|
||||
"unit": {
|
||||
"speed": {
|
||||
"mph": "mph",
|
||||
"mph": "mi/h",
|
||||
"kph": "km/h"
|
||||
},
|
||||
"length": {
|
||||
@ -201,7 +201,65 @@
|
||||
},
|
||||
"restart": "Reiniciar o Frigate",
|
||||
"live": {
|
||||
"title": "Ao Vivo"
|
||||
"title": "Ao Vivo",
|
||||
"allCameras": "Todas as câmeras",
|
||||
"cameras": {
|
||||
"title": "Câmeras",
|
||||
"count_one": "{{count}} Câmera",
|
||||
"count_many": "{{count}} Câmeras",
|
||||
"count_other": "{{count}} Câmeras"
|
||||
}
|
||||
},
|
||||
"review": "Revisão",
|
||||
"explore": "Explorar",
|
||||
"export": "Exportar",
|
||||
"uiPlayground": "Playground da UI",
|
||||
"faceLibrary": "Biblioteca de Rostos",
|
||||
"user": {
|
||||
"title": "Usuário",
|
||||
"account": "Conta",
|
||||
"current": "Usuário Atual: {{user}}",
|
||||
"anonymous": "anônimo",
|
||||
"logout": "Sair",
|
||||
"setPassword": "Definir Senha"
|
||||
}
|
||||
},
|
||||
"toast": {
|
||||
"copyUrlToClipboard": "URL copiada para a área de transferência.",
|
||||
"save": {
|
||||
"title": "Salvar",
|
||||
"error": {
|
||||
"title": "Falha ao salvar as alterações de configuração: {{errorMessage}}",
|
||||
"noMessage": "Falha ao salvar as alterações de configuração"
|
||||
}
|
||||
}
|
||||
},
|
||||
"role": {
|
||||
"title": "Papel",
|
||||
"admin": "Administrador",
|
||||
"viewer": "Espectador",
|
||||
"desc": "Administradores possuem acesso total a todos os recursos da interface do Frigate. Espectadores são limitados a ver as câmeras, revisar itens, e filmagens históricas na interface."
|
||||
},
|
||||
"pagination": {
|
||||
"label": "paginação",
|
||||
"previous": {
|
||||
"title": "Anterior",
|
||||
"label": "Ir para a página anterior"
|
||||
},
|
||||
"next": {
|
||||
"title": "Próximo",
|
||||
"label": "Ir para a próxima página"
|
||||
},
|
||||
"more": "Mais páginas"
|
||||
},
|
||||
"accessDenied": {
|
||||
"documentTitle": "Acesso Negado - Frigate",
|
||||
"title": "Acesso Negado",
|
||||
"desc": "Você não possui permissão para visualizar essa página."
|
||||
},
|
||||
"notFound": {
|
||||
"documentTitle": "Não Encontrado - Frigate",
|
||||
"title": "404",
|
||||
"desc": "Página não encontrada"
|
||||
}
|
||||
}
|
||||
|
@ -16,7 +16,7 @@
|
||||
},
|
||||
"review": {
|
||||
"question": {
|
||||
"label": "Confirmar essa etiqueta para Frigate Plus",
|
||||
"label": "Confirmar esse rótulo para Frigate Plus",
|
||||
"ask_a": "Este objeto é um <code>{{label}}</code>?",
|
||||
"ask_an": "Este objeto é um<code>{{label}}</code>?",
|
||||
"ask_full": "Este objeto é um<code>{{untranslatedLabel}}</code> ({{translatedLabel}})?"
|
||||
|
@ -3,11 +3,11 @@
|
||||
"labels": {
|
||||
"label": "Rótulos",
|
||||
"all": {
|
||||
"title": "Todas as Etiquetas",
|
||||
"short": "Etiquetas"
|
||||
"title": "Todos os Rótulos",
|
||||
"short": "Rótulos"
|
||||
},
|
||||
"count_one": "{{count}} Etiqueta",
|
||||
"count_other": "{{count}} Etiquetas"
|
||||
"count_one": "{{count}} Rótulo",
|
||||
"count_other": "{{count}} Rótulos"
|
||||
},
|
||||
"zones": {
|
||||
"label": "Zonas",
|
||||
@ -29,8 +29,8 @@
|
||||
},
|
||||
"timeRange": "Intervalo de Tempo",
|
||||
"subLabels": {
|
||||
"label": "Sub-etiquetas",
|
||||
"all": "Todas as Sub-etiquetas"
|
||||
"label": "Sub-Rótulos",
|
||||
"all": "Todos os Sub-Rótulos"
|
||||
},
|
||||
"score": "Pontuação",
|
||||
"estimatedSpeed": "Velocidade Estimada {{unit}}",
|
||||
|
@ -26,7 +26,7 @@
|
||||
"value": "{{seconds}} segundos",
|
||||
"short": {
|
||||
"title": "Latência",
|
||||
"value": "{{seconds}} segundos"
|
||||
"value": "{{seconds}} s"
|
||||
}
|
||||
},
|
||||
"totalFrames": "Total de Quadros:",
|
||||
|
@ -41,8 +41,8 @@
|
||||
},
|
||||
"tips": {
|
||||
"mismatch_one": "{{count}} objeto indisponível foi detectado e incluido nesse item de revisão. Esse objeto ou não se qualifica para um alerta ou detecção, ou já foi limpo/deletado.",
|
||||
"mismatch_many": "{{count}} objetos indisponíveis foram detectados e incluídos nesse item de revisão. Esses objetos ou não se qualificam para um alerta ou detecção, ou já foi limpo/deletado.",
|
||||
"mismatch_other": "",
|
||||
"mismatch_many": "{{count}} objetos indisponíveis foram detectados e incluídos nesse item de revisão. Esses objetos ou não se qualificam para um alerta ou detecção, ou já foram limpos/deletados.",
|
||||
"mismatch_other": "{{count}} objetos indisponíveis foram detectados e incluídos nesse item de revisão. Esses objetos ou não se qualificam para um alerta ou detecção, ou já foram limpos/deletados.",
|
||||
"hasMissingObjects": "Ajustar a sua configuração se quiser que o Frigate salve objetos rastreados com as seguintes categorias: <em>{{objects}}</em>"
|
||||
},
|
||||
"toast": {
|
||||
@ -196,7 +196,7 @@
|
||||
"fetchingTrackedObjectsFailed": "Erro ao buscar por objetos rastreados: {{errorMessage}}",
|
||||
"trackedObjectsCount_one": "{{count}} objeto rastreado ",
|
||||
"trackedObjectsCount_many": "{{count}} objetos rastreados ",
|
||||
"trackedObjectsCount_other": "",
|
||||
"trackedObjectsCount_other": "{{count}} objetos rastreados ",
|
||||
"searchResult": {
|
||||
"tooltip": "Correspondência com {{type}} de {{confidence}}%",
|
||||
"deleteTrackedObject": {
|
||||
|
@ -3,8 +3,8 @@
|
||||
"person": "Pessoa",
|
||||
"unknown": "Desconhecido",
|
||||
"face": "Detalhes do Rosto",
|
||||
"subLabelScore": "Pontuação do sub-rótulo",
|
||||
"scoreInfo": "A pontuação da sub etiqueta é a pontuação ponderada de todas as confidências faciais reconhecidas, então a pontuação pode ser diferente da mostrada na foto instantânea.",
|
||||
"subLabelScore": "Pontuação do Sub-Rótulo",
|
||||
"scoreInfo": "A pontuação do sub-rótulo é a pontuação ponderada de todas as confidências faciais reconhecidas, então a pontuação pode ser diferente da mostrada na foto instantânea.",
|
||||
"faceDesc": "Detalhes do objeto rastreado que gerou este rosto",
|
||||
"timestamp": "Carimbo de data e hora"
|
||||
},
|
||||
@ -53,7 +53,7 @@
|
||||
"faceName": "Digite o Nome do Rosto",
|
||||
"uploadFace": "Enviar Imagem de Rosto",
|
||||
"description": {
|
||||
"uploadFace": "Faça o upload de uma imagem de {{name}} que mostre mostre seu rosto visto de frente. A imagem não precisa estar recortada apenas com o rosto."
|
||||
"uploadFace": "Faça o upload de uma imagem de {{name}} que mostre seu rosto visto de frente. A imagem não precisa estar recortada apenas com o rosto."
|
||||
}
|
||||
},
|
||||
"description": {
|
||||
@ -87,7 +87,7 @@
|
||||
"renamedFace": "O rosto foi renomeado com sucesso para {{name}}",
|
||||
"deletedName_one": "{{count}} rosto foi deletado com sucesso.",
|
||||
"deletedName_many": "{{count}} rostos foram deletados com sucesso.",
|
||||
"deletedName_other": ""
|
||||
"deletedName_other": "{{count}} rostos foram deletados com sucesso."
|
||||
},
|
||||
"error": {
|
||||
"uploadingImageFailed": "Falha ao enviar a imagem: {{errorMessage}}",
|
||||
|
@ -13,7 +13,7 @@
|
||||
"filter": {
|
||||
"label": {
|
||||
"cameras": "Câmeras",
|
||||
"labels": "Etiquetas",
|
||||
"labels": "Rótulos",
|
||||
"zones": "Zonas",
|
||||
"before": "Antes",
|
||||
"after": "Depois",
|
||||
@ -21,7 +21,7 @@
|
||||
"max_score": "Pontuação Máxima",
|
||||
"min_speed": "Velocidade Mínima",
|
||||
"max_speed": "Velocidade Máxima",
|
||||
"sub_labels": "Sub-etiquetas",
|
||||
"sub_labels": "Sub-Rótulos",
|
||||
"search_type": "Tipo de Busca",
|
||||
"time_range": "Intervalo de Tempo",
|
||||
"recognized_license_plate": "Placa de Carro Reconhecida",
|
||||
|
@ -5,7 +5,7 @@
|
||||
"camera": "Configurações de Câmera - Frigate",
|
||||
"enrichments": "Configurações de Enriquecimento - Frigate",
|
||||
"masksAndZones": "Editor de Máscara e Zona - Frigate",
|
||||
"motionTuner": "Virada de Movimento - Frigate",
|
||||
"motionTuner": "Ajuste de Movimento - Frigate",
|
||||
"object": "Debug - Frigate",
|
||||
"general": "Configurações Gerais - Frigate",
|
||||
"frigatePlus": "Frigate+ Configurações- Frigate",
|
||||
@ -18,8 +18,8 @@
|
||||
"users": "Usuários",
|
||||
"notifications": "Notificações",
|
||||
"frigateplus": "Frigate+",
|
||||
"motionTuner": "Ajuste de Detecção de Movimento",
|
||||
"debug": "Depuração",
|
||||
"motionTuner": "Ajuste de Movimento",
|
||||
"debug": "Depurar",
|
||||
"enrichments": "Melhorias"
|
||||
},
|
||||
"dialog": {
|
||||
@ -254,7 +254,7 @@
|
||||
"edit": "Editar Zona",
|
||||
"point_one": "{{count}} ponto",
|
||||
"point_many": "{{count}} pontos",
|
||||
"point_other": "",
|
||||
"point_other": "{{count}} pontos",
|
||||
"clickDrawPolygon": "Clique para desenhar um polígono na imagem.",
|
||||
"name": {
|
||||
"title": "Nome",
|
||||
@ -275,8 +275,348 @@
|
||||
},
|
||||
"allObjects": "Todos os Objetos",
|
||||
"speedEstimation": {
|
||||
"title": "Estimativa de Velocidade"
|
||||
"title": "Estimativa de Velocidade",
|
||||
"desc": "Habilitar estimativa de velocidade para objetos nesta zona. A zona deve ter exatamente 4 pontos.",
|
||||
"docs": "Leia a documentação",
|
||||
"lineADistance": "Distância da linha A ({{unit}})",
|
||||
"lineBDistance": "Distância da Linha B ({{unit}})",
|
||||
"lineCDistance": "Distância da linha C ({{unit}})",
|
||||
"lineDDistance": "Distância da linha D ({{unit}})"
|
||||
},
|
||||
"speedThreshold": {
|
||||
"title": "Limiar de Velocidade ({{unit}})",
|
||||
"desc": "Especifique a velocidade mínima para o objeto ser considerado nessa zona.",
|
||||
"toast": {
|
||||
"error": {
|
||||
"pointLengthError": "A estimativa de velocidade foi desativada para essa zona. Zonas com estimação de velocidade devem ter exatamente 4 pontos.",
|
||||
"loiteringTimeError": "Zonas com tempo de permanência acima de 0 não devem ser usadas com estimativa de velocidade."
|
||||
}
|
||||
}
|
||||
},
|
||||
"toast": {
|
||||
"success": "A zona ({{zoneName}}) foi salva. Reinicie o Frigate para aplicar as mudanças."
|
||||
}
|
||||
},
|
||||
"objectMasks": {
|
||||
"objects": {
|
||||
"allObjectTypes": "Todos os tipos de objetos",
|
||||
"title": "Objetos",
|
||||
"desc": "O tipo de objeto que se aplica para essa máscara de objeto."
|
||||
},
|
||||
"toast": {
|
||||
"success": {
|
||||
"title": "{{polygonName}} foi salvo. Reinicie o Frigate para aplicar as alterações.",
|
||||
"noName": "A máscara de objeto foi salva. Reinicie o Frigate para aplicar as alterações."
|
||||
}
|
||||
},
|
||||
"label": "Máscaras de Objeto",
|
||||
"documentTitle": "Editar Máscara de Objeto - Frigate",
|
||||
"desc": {
|
||||
"title": "Máscaras de filtro de objetos são usadas para filtrar falsos positivos para um determinado tipo de objeto baseado na localização.",
|
||||
"documentation": "Documentação"
|
||||
},
|
||||
"add": "Adicionar Máscara de Objeto",
|
||||
"edit": "Editar Máscara de Objeto",
|
||||
"context": "Filtro de máscaras de objeto são usados para filtrar falsos positivos para um dado tipo de objeto baseado na localização.",
|
||||
"point_one": "{{count}} ponto",
|
||||
"point_many": "{{count}} pontos",
|
||||
"point_other": "{{count}} pontos",
|
||||
"clickDrawPolygon": "Clique para desenhar um polígono na imagem."
|
||||
},
|
||||
"motionMasks": {
|
||||
"label": "Máscara de Movimento",
|
||||
"documentTitle": "Editar Máscara de Movimento - Frigate",
|
||||
"desc": {
|
||||
"title": "Máscaras de movimento são usadas para prevenir tipos de movimento de ativarem uma detecção. Excesso de mascaramento tornará mais difícil que objetos sejam rastreados.",
|
||||
"documentation": "Documentação"
|
||||
},
|
||||
"add": "Nova Máscara de Movimento",
|
||||
"edit": "Editar Máscara de Movimento",
|
||||
"context": {
|
||||
"title": "Máscaras de movimento são usadas para prevenir typos de movimento não desejados de ativarem uma detecção (exemplo: galhos de árvores, timestamps de câmeras). Máscaras de movimento devem ser usadas com <em> muita parcimônia</em>, excesso de mascaramento tornará mais difícil de objetos serem rastreados.",
|
||||
"documentation": "Leia a documentação"
|
||||
},
|
||||
"point_one": "{{count}} ponto",
|
||||
"point_many": "{{count}} pontos",
|
||||
"point_other": "{{count}} pontos",
|
||||
"clickDrawPolygon": "Clique para desenhar um polígono na imagem.",
|
||||
"polygonAreaTooLarge": {
|
||||
"title": "A máscara de movimento está cobrindo {{polygonArea}}% do quadro da câmera. Máscaras de movimento grandes não são recomendadas.",
|
||||
"tips": "Máscaras de movimento não previnem objetos de serem detectados. Em vez disso você deve usar uma zona obrigatória.",
|
||||
"documentation": "Leia a documentação"
|
||||
},
|
||||
"toast": {
|
||||
"success": {
|
||||
"title": "{{polygonName}} foi salvo. Reinicie o Frigate para aplicar as alterações.",
|
||||
"noName": "Máscara de Movimento salva. Reinicie o Frigate para aplicar as alterações."
|
||||
}
|
||||
}
|
||||
}
|
||||
},
|
||||
"motionDetectionTuner": {
|
||||
"desc": {
|
||||
"title": "O Frigate usa a detecção de movimento como uma verificação de primeira linha para ver se há algo acontecendo no quadro que valha a pena verificar com a detecção de objetos.",
|
||||
"documentation": "Leia o Guia de Ajuste de Movimento"
|
||||
},
|
||||
"Threshold": {
|
||||
"title": "Limite",
|
||||
"desc": "O valor do limiar dita o quanto de mudança na luminância de um pixel é requerida para ser considerada movimento. <em>Padrão: 30</em>"
|
||||
},
|
||||
"contourArea": {
|
||||
"title": "Área de contorno",
|
||||
"desc": "O valor do contorno da área é usado para decidir quais grupos de mudança de pixel se qualificam como movimento. <em>Padrão: 10</em>"
|
||||
},
|
||||
"improveContrast": {
|
||||
"title": "Melhorar o contraste",
|
||||
"desc": "Melhorar contraste para cenas escuras. <em>Padrão: ON</em>"
|
||||
},
|
||||
"toast": {
|
||||
"success": "As configurações de movimento foram salvas."
|
||||
},
|
||||
"title": "Ajuste de Detecção de Movimento",
|
||||
"unsavedChanges": "Alterações do Ajuste de Movimento Não Salvas ({{camera}})"
|
||||
},
|
||||
"debug": {
|
||||
"detectorDesc": "O Frigate usa seus detectores ({{detectors}}) para detectar objetos no fluxo de vídeo da sua câmera.",
|
||||
"desc": "A visualização de depuração mostra uma visão em tempo real dos objetos rastreados e suas estatísticas. A lista de objetos mostra um resumo com atraso de tempo dos objetos detectados.",
|
||||
"objectList": "Lista de Objetos",
|
||||
"boundingBoxes": {
|
||||
"desc": "Mostrar caixas delimitadoras ao redor de objetos rastreados",
|
||||
"colors": {
|
||||
"label": "Cores da caixa delimitadora de objetos",
|
||||
"info": "<li>Na inicialização, cores diferentes serão atribuídas a cada rótulo de objeto</li><li>Uma linha fina azul escura indica que o objeto não foi detectado neste momento</li><li>Uma linha fina cinza indica que o objeto foi detectado como estacionário</li><li>Uma linha grossa indica que o objeto está sujeito ao rastreamento automático (quando ativado)</li>"
|
||||
},
|
||||
"title": "Caixas delimitadoras"
|
||||
},
|
||||
"zones": {
|
||||
"title": "Zonas",
|
||||
"desc": "Mostrar um esboço de quaisquer zonas definidas"
|
||||
},
|
||||
"mask": {
|
||||
"title": "Máscaras de movimento",
|
||||
"desc": "Mostrar polígonos de máscara de movimento"
|
||||
},
|
||||
"motion": {
|
||||
"title": "Caixas de movimento",
|
||||
"desc": "Mostrar caixas ao redor das áreas onde o movimento é detectado",
|
||||
"tips": "<p><strong>Caixas de movimento</strong></p><br><p>Caixas vermelhas serão sobrepostas em áreas do quadro onde o movimento está sendo detectado</p>"
|
||||
},
|
||||
"regions": {
|
||||
"title": "Regiões",
|
||||
"desc": "Mostrar uma caixa da região de interesse enviada ao detector de objetos",
|
||||
"tips": "<p><strong>Caixas de Região</strong></p><br><p>Caixas verdes claras serão sobrepostas em áreas de interesse no quadro que está sendo enviado ao detector de objetos.</p>"
|
||||
},
|
||||
"title": "Depuração",
|
||||
"debugging": "Depuração",
|
||||
"objectShapeFilterDrawing": {
|
||||
"desc": "Desenhe um retângulo na imagem para ver os detalhes da área e proporções",
|
||||
"tips": "Habilite essa opção para desenhar um retângulo na imagem da camera para mostrar a sua área e proporção. Esses valores podem ser usados para estabelecer parâmetros de filtro de formato de objetos na sua configuração.",
|
||||
"document": "Leia a documentação ",
|
||||
"score": "Pontuação",
|
||||
"ratio": "Proporção",
|
||||
"area": "Área",
|
||||
"title": "Desenho de Filtro de Formato de Objeto"
|
||||
},
|
||||
"noObjects": "Nenhum Objeto",
|
||||
"timestamp": {
|
||||
"title": "Timestamp",
|
||||
"desc": "Sobreponha um timestamp na imagem"
|
||||
}
|
||||
},
|
||||
"users": {
|
||||
"title": "Usuários",
|
||||
"management": {
|
||||
"title": "Gerenciamento de Usuário",
|
||||
"desc": "Gerencias as contas de usuário dessa instância do Frigate."
|
||||
},
|
||||
"addUser": "Adicionar Usuário",
|
||||
"updatePassword": "Atualizar Senha",
|
||||
"toast": {
|
||||
"success": {
|
||||
"createUser": "Usuário {{user}} criado com sucesso",
|
||||
"deleteUser": "Usuário {{user}} deletado com sucesso",
|
||||
"updatePassword": "Senha atualizada com sucesso.",
|
||||
"roleUpdated": "Papel atualizado para {{user}}"
|
||||
},
|
||||
"error": {
|
||||
"setPasswordFailed": "Falha ao salvar a senha: {{errorMessage}}",
|
||||
"createUserFailed": "Falha ao criar usuário: {{errorMessage}}",
|
||||
"deleteUserFailed": "Falha ao deletar usuário: {{errorMessage}}",
|
||||
"roleUpdateFailed": "Falha ao atualizar papel: {{errorMessage}}"
|
||||
}
|
||||
},
|
||||
"dialog": {
|
||||
"form": {
|
||||
"password": {
|
||||
"match": "As senhas correspondem",
|
||||
"notMatch": "As senhas são diferentes",
|
||||
"title": "Senha",
|
||||
"placeholder": "Digita a senha",
|
||||
"confirm": {
|
||||
"title": "Confirmar Senha",
|
||||
"placeholder": "Confirmar Senha"
|
||||
},
|
||||
"strength": {
|
||||
"title": "Nível de segurança da senha: ",
|
||||
"weak": "Fraca",
|
||||
"medium": "Mediana",
|
||||
"strong": "Forte",
|
||||
"veryStrong": "Muito Forte"
|
||||
}
|
||||
},
|
||||
"newPassword": {
|
||||
"title": "Senha Nova",
|
||||
"placeholder": "Digite uma senha nova",
|
||||
"confirm": {
|
||||
"placeholder": "Digite a senha novamente"
|
||||
}
|
||||
},
|
||||
"usernameIsRequired": "Nome de usuário requerido",
|
||||
"passwordIsRequired": "Senha requerida",
|
||||
"user": {
|
||||
"title": "Nome de Usuário",
|
||||
"desc": "Apenas letras, números, pontos e sublinhados são permitidos.",
|
||||
"placeholder": "Digite o nome de usuário"
|
||||
}
|
||||
},
|
||||
"createUser": {
|
||||
"title": "Criar Novo Usuário",
|
||||
"desc": "Adicionar um novo usuário e especificar um papel para acesso às áreas da interface do Frigate.",
|
||||
"usernameOnlyInclude": "O nome de usuário pode conter apenas letras, números, . ou _",
|
||||
"confirmPassword": "Por favor confirme a sua senha"
|
||||
},
|
||||
"deleteUser": {
|
||||
"title": "Deletar Usuário",
|
||||
"desc": "Essa ação não pode ser desfeita. Isso irá deletar permanentemente a conta do usuário e remover todos os dados associados.",
|
||||
"warn": "Tem certeza que quer deletar <strong>{{username}}</strong>?"
|
||||
},
|
||||
"passwordSetting": {
|
||||
"cannotBeEmpty": "A senha não pode estar vazia",
|
||||
"doNotMatch": "As senhas não correspondem",
|
||||
"updatePassword": "Atualizar Senha para {{username}}",
|
||||
"setPassword": "Definir Senha",
|
||||
"desc": "Crie uma senha forte para proteger essa conta."
|
||||
},
|
||||
"changeRole": {
|
||||
"title": "Alterar Papel do Usuário",
|
||||
"select": "Selecionar um papel",
|
||||
"desc": "Atualizar permissões para <strong>{{username}}</strong>",
|
||||
"roleInfo": {
|
||||
"intro": "Selecione o papel apropriado para esse usuário:",
|
||||
"admin": "Administrador",
|
||||
"adminDesc": "Acesso total a todos os recursos.",
|
||||
"viewer": "Espectador",
|
||||
"viewerDesc": "Limitado aos Painéis ao Vivo, Revisar, Explorar, e Exportar somente."
|
||||
}
|
||||
}
|
||||
},
|
||||
"table": {
|
||||
"username": "Nome de Usuário",
|
||||
"actions": "Ações",
|
||||
"role": "Papel",
|
||||
"noUsers": "Nenhum usuário encontrado.",
|
||||
"changeRole": "Mudar papel do usuário",
|
||||
"password": "Senha",
|
||||
"deleteUser": "Deletar usuário"
|
||||
}
|
||||
},
|
||||
"notification": {
|
||||
"suspendTime": {
|
||||
"10minutes": "Suspender por 10 minutos",
|
||||
"30minutes": "Suspender por 30 minutos",
|
||||
"1hour": "Suspender por 1 hora",
|
||||
"12hours": "Suspender por 12 horas",
|
||||
"24hours": "Suspender por 24 horas",
|
||||
"untilRestart": "Suspender até reiniciar",
|
||||
"suspend": "Suspender",
|
||||
"5minutes": "Suspender por 5 minutos"
|
||||
},
|
||||
"cancelSuspension": "Cancelar Suspensão",
|
||||
"toast": {
|
||||
"success": {
|
||||
"registered": "Registrados para notificações com sucesso. É necessário reiniciar o Frigate para que as notificações possam ser enviadas (incluindo a notificação de teste).",
|
||||
"settingSaved": "As configurações de notificações foram salvas."
|
||||
},
|
||||
"error": {
|
||||
"registerFailed": "Falha ao salvar o registro para notificações."
|
||||
}
|
||||
},
|
||||
"title": "Notificações",
|
||||
"notificationSettings": {
|
||||
"title": "Configurações de Notificação",
|
||||
"desc": "O Frigate pode enviar notificações push nativamente ao seu dispositivo quando estiver sendo executado no navegador ou instalado como um PWA.",
|
||||
"documentation": "Leia a Documentação"
|
||||
},
|
||||
"notificationUnavailable": {
|
||||
"title": "Notificações Indisponíveis",
|
||||
"desc": "Notificações push da Web exigem um contexto seguro (<code>https://…</code>). Essa é uma limitação do navegador. Acesse o Frigate com seguraça para usar as notificações.",
|
||||
"documentation": "Leia a Documentação"
|
||||
},
|
||||
"globalSettings": {
|
||||
"title": "Configurações Globais",
|
||||
"desc": "Suspender as notificações temporáriamente para câmeras específicas em todos os dispositivos registrados."
|
||||
},
|
||||
"email": {
|
||||
"title": "Email",
|
||||
"placeholder": "ex: exemplo@email.com",
|
||||
"desc": "Um email válido é requerido e será usado para notificar você caso haja algum problema com o serviço push."
|
||||
},
|
||||
"cameras": {
|
||||
"title": "Câmeras",
|
||||
"noCameras": "Nenhuma câmera disponível",
|
||||
"desc": "Selecionar para quais câmeras habilitar as notificações."
|
||||
},
|
||||
"deviceSpecific": "Configurações Específicas do Dispositivo",
|
||||
"registerDevice": "Registre Esse Dispositivo",
|
||||
"unregisterDevice": "Cancelar Registro Desse Dispositivo",
|
||||
"sendTestNotification": "Enviar uma notificação de teste",
|
||||
"unsavedRegistrations": "Registros de Notificações Não Salvos",
|
||||
"unsavedChanges": "Alterações de Notificações Não Salvas",
|
||||
"active": "Notificações Ativas",
|
||||
"suspended": "Notificações suspensas {{time}}"
|
||||
},
|
||||
"frigatePlus": {
|
||||
"title": "Configurações do Frigate+",
|
||||
"apiKey": {
|
||||
"title": "Chave de API do Frigate+",
|
||||
"validated": "A chave de API do Frigate+ detectada e validada",
|
||||
"notValidated": "A chave de API do Frigate+ não detectada ou não validada",
|
||||
"desc": "A chave de API do Frigate+ habilita a integração com o serviço do Frigate+.",
|
||||
"plusLink": "Leia mais sobre o Frigate+"
|
||||
},
|
||||
"modelInfo": {
|
||||
"plusModelType": {
|
||||
"baseModel": "Modelo Base",
|
||||
"userModel": "Ajuste Refinado"
|
||||
},
|
||||
"supportedDetectors": "Detectores Suportados",
|
||||
"cameras": "Câmeras",
|
||||
"loading": "Carregando informações do modelo…",
|
||||
"error": "Falha ao carregar as informações do modelo",
|
||||
"availableModels": "Modelos Disponíveis",
|
||||
"loadingAvailableModels": "Carregando modelos disponíveis…",
|
||||
"title": "Informação do Modelo",
|
||||
"modelType": "Tipo de Modelo",
|
||||
"trainDate": "Data do Treinamento",
|
||||
"baseModel": "Modelo Base",
|
||||
"modelSelect": "Os seus modelos disponíveis no Frigate+ podem ser selecionados aqui. Note que apenas modelos compatíveis com a sua configuração atual de detector podem ser selecionados."
|
||||
},
|
||||
"snapshotConfig": {
|
||||
"title": "Configuração de Captura de Imagem",
|
||||
"desc": "Enviar ao Frigate+ requer tanto a captura de imagem quanto a captura de imagem <code>clean_copy</code> estarem habilitadas na sua configuração.",
|
||||
"documentation": "Leia a documentação",
|
||||
"cleanCopyWarning": "Algumas câmeras possuem captura de imagem habilitada porém têm a cópia limpa desabilitada. Você precisa habilitar a <code>clean_copy</code> nas suas configurações de captura de imagem para poder submeter imagems dessa câmera ao Frigate+.",
|
||||
"table": {
|
||||
"camera": "Câmera",
|
||||
"snapshots": "Capturas de Imagem",
|
||||
"cleanCopySnapshots": "Capturas de Imagem <code>clean_copy</code>"
|
||||
}
|
||||
},
|
||||
"unsavedChanges": "Alterações de configurações do Frigate+ não salvas",
|
||||
"restart_required": "Reinicialização necessária (modelo do Frigate+ foi alterado)",
|
||||
"toast": {
|
||||
"success": "As configurações do Frigate+ foram salvas. Reinicie o Frigate para aplicar as alterações.",
|
||||
"error": "Falha ao salvar as alterações de configuração: {{errorMessage}}"
|
||||
}
|
||||
}
|
||||
}
|
||||
|
@ -24,7 +24,7 @@
|
||||
"type": {
|
||||
"label": "Tipo",
|
||||
"timestamp": "Marca temporal",
|
||||
"tag": "Etiqueta",
|
||||
"tag": "Marcador",
|
||||
"message": "Mensagem"
|
||||
},
|
||||
"tips": "Os Registros estão sendo transmitidos do servidor",
|
||||
@ -157,7 +157,7 @@
|
||||
"detectHighCpuUsage": "{{camera}} possui alta utilização de CPU para detecção ({{detectAvg}}%)",
|
||||
"healthy": "O sistema está saudável",
|
||||
"cameraIsOffline": "{{camera}} está offline",
|
||||
"reindexingEmbeddings": "Reindexando embeddings ({{processed}}% completado)",
|
||||
"reindexingEmbeddings": "Reindexando os vetores de característica de imagens ({{processed}}% completado)",
|
||||
"detectIsSlow": "{{detect}} está lento ({{speed}} ms)"
|
||||
},
|
||||
"enrichments": {
|
||||
@ -167,13 +167,13 @@
|
||||
"face_recognition": "Reconhecimento Facial",
|
||||
"plate_recognition": "Reconhecimento de Placa",
|
||||
"plate_recognition_speed": "Velocidade de Reconhecimento de Placas",
|
||||
"text_embedding_speed": "Velocidade de Incorporação de Textos",
|
||||
"text_embedding_speed": "Velocidade de Geração de Vetores de Texto",
|
||||
"yolov9_plate_detection_speed": "Velocidade de Reconhecimento de Placas do YOLOv9",
|
||||
"yolov9_plate_detection": "Detecção de Placas do YOLOv9",
|
||||
"image_embedding": "Incorporação de Imagem",
|
||||
"text_embedding": "Incorporação de Texto",
|
||||
"image_embedding_speed": "Velocidade de Incorporação de Imagem",
|
||||
"face_embedding_speed": "Velocidade de Incorporação de Rosto",
|
||||
"image_embedding": "Vetores de Características de Imagens",
|
||||
"text_embedding": "Vetor de Característica de Texto",
|
||||
"image_embedding_speed": "Velocidade de Geração de Vetores de Imagem",
|
||||
"face_embedding_speed": "Velocidade de Geração de Vetores de Rostos",
|
||||
"face_recognition_speed": "Velocidade de Reconhecimento de Rostos"
|
||||
}
|
||||
}
|
||||
|
@ -9,15 +9,15 @@
|
||||
"horse": "Cal",
|
||||
"bird": "Pasare",
|
||||
"sheep": "Oaie",
|
||||
"boat": "Barca",
|
||||
"motorcycle": "Motocicleta",
|
||||
"boat": "Barcă",
|
||||
"motorcycle": "Motocicletă",
|
||||
"bus": "Autobuz",
|
||||
"train": "Tren",
|
||||
"skateboard": "Skateboard",
|
||||
"camera": "Camera foto",
|
||||
"bicycle": "Bicicletă",
|
||||
"car": "Mașină",
|
||||
"cat": "Pisica",
|
||||
"cat": "Pisică",
|
||||
"animal": "Animal",
|
||||
"goat": "Capra",
|
||||
"keyboard": "Orga",
|
||||
|
@ -4,8 +4,8 @@
|
||||
"button": "Repornește",
|
||||
"restarting": {
|
||||
"title": "Frigate repornește",
|
||||
"content": "Această pagină se va reâncărca în {{countdown}} secunde.",
|
||||
"button": "Forteaza Reincarcarea Acum"
|
||||
"content": "Această pagină se va reâncărca automat în {{countdown}} secunde.",
|
||||
"button": "Forțează acum reîncărcarea"
|
||||
}
|
||||
},
|
||||
"explore": {
|
||||
|
@ -61,7 +61,7 @@
|
||||
"label": "Filtru camere"
|
||||
},
|
||||
"review": {
|
||||
"showReviewed": "Afișează revizuitele"
|
||||
"showReviewed": "Afișează cele revizuite"
|
||||
},
|
||||
"motion": {
|
||||
"showMotionOnly": "Afișează doar mișcarea"
|
||||
@ -98,10 +98,10 @@
|
||||
"label": "Filtrează nivelul jurnalului",
|
||||
"filterBySeverity": "Filtrează jurnalele după severitate",
|
||||
"loading": {
|
||||
"title": "Se încarcă",
|
||||
"title": "Încărcare continuă",
|
||||
"desc": "Când panoul de jurnale este derulat până jos, noile jurnale sunt afișate automat pe măsură ce sunt adăugate."
|
||||
},
|
||||
"disableLogStreaming": "Dezactivează fluxul jurnalelor",
|
||||
"disableLogStreaming": "Dezactivează încărcarea continuă",
|
||||
"allLogs": "Toate jurnalele"
|
||||
},
|
||||
"trackedObjectDelete": {
|
||||
|
@ -5,17 +5,17 @@
|
||||
"airplane": "Avion",
|
||||
"bus": "Autobuz",
|
||||
"train": "Tren",
|
||||
"boat": "Barca",
|
||||
"boat": "Barcă",
|
||||
"fire_hydrant": "Hidrant",
|
||||
"street_sign": "Semn de Circulatie",
|
||||
"stop_sign": "Semn de Stop",
|
||||
"parking_meter": "Automat de Parcare",
|
||||
"bench": "Bancheta",
|
||||
"bird": "Pasare",
|
||||
"cat": "Pisica",
|
||||
"cat": "Pisică",
|
||||
"dog": "Câine",
|
||||
"horse": "Cal",
|
||||
"cow": "Vaca",
|
||||
"cow": "Vacă",
|
||||
"elephant": "Elefant",
|
||||
"bear": "Urs",
|
||||
"giraffe": "Girafa",
|
||||
@ -46,7 +46,7 @@
|
||||
"bowl": "Castron",
|
||||
"banana": "Banana",
|
||||
"apple": "Mar",
|
||||
"motorcycle": "Motocicleta",
|
||||
"motorcycle": "Motocicletă",
|
||||
"traffic_light": "Semafor",
|
||||
"sheep": "Oaie",
|
||||
"zebra": "Zebra",
|
||||
|
@ -37,7 +37,7 @@
|
||||
},
|
||||
"objectLifecycle": {
|
||||
"lifecycleItemDesc": {
|
||||
"visible": "{{label}} detectata",
|
||||
"visible": "S-a detectat {{label}}",
|
||||
"active": "{{label}} a devenit activ",
|
||||
"entered_zone": "{{label}} a intrat în {{zones}}",
|
||||
"stationary": "{{label}} a devenit staționar",
|
||||
@ -50,7 +50,7 @@
|
||||
"ratio": "Raport",
|
||||
"area": "Suprafață"
|
||||
},
|
||||
"gone": "{{label}} a ieșit",
|
||||
"gone": "{{label}} a părasit cadrul",
|
||||
"heard": "{{label}} auzit(ă)",
|
||||
"external": "{{label}} detectat(ă)"
|
||||
},
|
||||
|
@ -67,7 +67,7 @@
|
||||
"title": "Calendar",
|
||||
"firstWeekday": {
|
||||
"label": "Prima zi a săptămânii",
|
||||
"desc": "Ziua în care încep săptămânile calendarului de revizuire.",
|
||||
"desc": "Ziua cu care încep săptămânile calendarului de revizuire.",
|
||||
"sunday": "Duminică",
|
||||
"monday": "Luni"
|
||||
}
|
||||
|
@ -66,7 +66,7 @@
|
||||
},
|
||||
"title": "Spațiu stocare",
|
||||
"cameraStorage": {
|
||||
"title": "Spațiu tocare camere",
|
||||
"title": "Spațiu stocare camere",
|
||||
"camera": "Camera",
|
||||
"unusedStorageInformation": "Informații despre stocarea neutilizată",
|
||||
"storageUsed": "Spațiu stocare",
|
||||
@ -86,12 +86,12 @@
|
||||
},
|
||||
"copy": {
|
||||
"label": "Copiază",
|
||||
"success": "Copiază jurnalul",
|
||||
"success": "Jurnalul a fost copiat",
|
||||
"error": "Jurnalul nu s-a putut copia"
|
||||
},
|
||||
"type": {
|
||||
"label": "Tip",
|
||||
"timestamp": "Marca temporală",
|
||||
"timestamp": "Data / ora",
|
||||
"tag": "Etichetă",
|
||||
"message": "Mesaj"
|
||||
},
|
||||
@ -110,7 +110,7 @@
|
||||
"image_embedding": "Încorporare imagini",
|
||||
"text_embedding": "Încorporare text",
|
||||
"plate_recognition": "Recunoaștere numere de înmatriculare",
|
||||
"image_embedding_speed": "Viteză încorporarei imaginii",
|
||||
"image_embedding_speed": "Viteză încorporare imagini",
|
||||
"face_recognition": "Recunoaștere facială",
|
||||
"face_recognition_speed": "Viteză recunoaștere facială",
|
||||
"plate_recognition_speed": "Viteză recunoaștere numere de înmatriculare",
|
||||
|
@ -131,10 +131,7 @@ export default function SearchFilterGroup({
|
||||
);
|
||||
|
||||
const availableSortTypes = useMemo(() => {
|
||||
const sortTypes = ["date_asc", "date_desc"];
|
||||
if (filter?.min_score || filter?.max_score) {
|
||||
sortTypes.push("score_desc", "score_asc");
|
||||
}
|
||||
const sortTypes = ["date_asc", "date_desc", "score_desc", "score_asc"];
|
||||
if (filter?.min_speed || filter?.max_speed) {
|
||||
sortTypes.push("speed_desc", "speed_asc");
|
||||
}
|
||||
|
6
web/src/components/input/InputWithTags.tsx
Normal file → Executable file
6
web/src/components/input/InputWithTags.tsx
Normal file → Executable file
@ -420,11 +420,11 @@ export default function InputWithTags({
|
||||
? t("button.yes", { ns: "common" })
|
||||
: t("button.no", { ns: "common" });
|
||||
} else if (filterType === "labels") {
|
||||
return getTranslatedLabel(filterValues as string);
|
||||
return getTranslatedLabel(String(filterValues));
|
||||
} else if (filterType === "search_type") {
|
||||
return t("filter.searchType." + (filterValues as string));
|
||||
return t("filter.searchType." + String(filterValues));
|
||||
} else {
|
||||
return (filterValues as string).replaceAll("_", " ");
|
||||
return String(filterValues).replaceAll("_", " ");
|
||||
}
|
||||
}
|
||||
|
||||
|
@ -85,6 +85,7 @@ export default function GeneralSettings({ className }: GeneralSettingsProps) {
|
||||
"nb-NO": "nb",
|
||||
"yue-Hant": "yue",
|
||||
"zh-CN": "zhCN",
|
||||
"pt-BR": "ptBR",
|
||||
};
|
||||
|
||||
return supportedLanguageKeys.map((key) => {
|
||||
|
@ -42,6 +42,7 @@ import {
|
||||
CommandList,
|
||||
} from "@/components/ui/command";
|
||||
import { LuCheck } from "react-icons/lu";
|
||||
import ActivityIndicator from "@/components/indicators/activity-indicator";
|
||||
|
||||
type SearchFilterDialogProps = {
|
||||
config?: FrigateConfig;
|
||||
@ -64,6 +65,9 @@ export default function SearchFilterDialog({
|
||||
const { t } = useTranslation(["components/filter"]);
|
||||
const [currentFilter, setCurrentFilter] = useState(filter ?? {});
|
||||
const { data: allSubLabels } = useSWR(["sub_labels", { split_joined: 1 }]);
|
||||
const { data: allRecognizedLicensePlates } = useSWR<string[]>(
|
||||
"recognized_license_plates",
|
||||
);
|
||||
|
||||
useEffect(() => {
|
||||
if (filter) {
|
||||
@ -130,6 +134,7 @@ export default function SearchFilterDialog({
|
||||
}
|
||||
/>
|
||||
<RecognizedLicensePlatesFilterContent
|
||||
allRecognizedLicensePlates={allRecognizedLicensePlates}
|
||||
recognizedLicensePlates={currentFilter.recognized_license_plate}
|
||||
setRecognizedLicensePlates={(plate) =>
|
||||
setCurrentFilter({
|
||||
@ -875,6 +880,7 @@ export function SnapshotClipFilterContent({
|
||||
}
|
||||
|
||||
type RecognizedLicensePlatesFilterContentProps = {
|
||||
allRecognizedLicensePlates: string[] | undefined;
|
||||
recognizedLicensePlates: string[] | undefined;
|
||||
setRecognizedLicensePlates: (
|
||||
recognizedLicensePlates: string[] | undefined,
|
||||
@ -882,18 +888,12 @@ type RecognizedLicensePlatesFilterContentProps = {
|
||||
};
|
||||
|
||||
export function RecognizedLicensePlatesFilterContent({
|
||||
allRecognizedLicensePlates,
|
||||
recognizedLicensePlates,
|
||||
setRecognizedLicensePlates,
|
||||
}: RecognizedLicensePlatesFilterContentProps) {
|
||||
const { t } = useTranslation(["components/filter"]);
|
||||
|
||||
const { data: allRecognizedLicensePlates, error } = useSWR<string[]>(
|
||||
"recognized_license_plates",
|
||||
{
|
||||
revalidateOnFocus: false,
|
||||
},
|
||||
);
|
||||
|
||||
const [selectedRecognizedLicensePlates, setSelectedRecognizedLicensePlates] =
|
||||
useState<string[]>(recognizedLicensePlates || []);
|
||||
const [inputValue, setInputValue] = useState("");
|
||||
@ -923,7 +923,7 @@ export function RecognizedLicensePlatesFilterContent({
|
||||
}
|
||||
};
|
||||
|
||||
if (!allRecognizedLicensePlates || allRecognizedLicensePlates.length === 0) {
|
||||
if (allRecognizedLicensePlates && allRecognizedLicensePlates.length === 0) {
|
||||
return null;
|
||||
}
|
||||
|
||||
@ -947,15 +947,12 @@ export function RecognizedLicensePlatesFilterContent({
|
||||
<div className="overflow-x-hidden">
|
||||
<DropdownMenuSeparator className="mb-3" />
|
||||
<div className="mb-3 text-lg">{t("recognizedLicensePlates.title")}</div>
|
||||
{error ? (
|
||||
<p className="text-sm text-red-500">
|
||||
{t("recognizedLicensePlates.loadFailed")}
|
||||
</p>
|
||||
) : !allRecognizedLicensePlates ? (
|
||||
<p className="text-sm text-muted-foreground">
|
||||
{t("recognizedLicensePlates.loading")}
|
||||
</p>
|
||||
) : (
|
||||
{allRecognizedLicensePlates == undefined ? (
|
||||
<div className="flex flex-col items-center justify-center text-sm text-muted-foreground">
|
||||
<ActivityIndicator className="mb-3 mr-2 size-5" />
|
||||
<p>{t("recognizedLicensePlates.loading")}</p>
|
||||
</div>
|
||||
) : allRecognizedLicensePlates.length == 0 ? null : (
|
||||
<>
|
||||
<Command
|
||||
className="border border-input bg-background"
|
||||
@ -1010,11 +1007,11 @@ export function RecognizedLicensePlatesFilterContent({
|
||||
))}
|
||||
</div>
|
||||
)}
|
||||
</>
|
||||
)}
|
||||
<p className="mt-1 text-sm text-muted-foreground">
|
||||
{t("recognizedLicensePlates.selectPlatesFromList")}
|
||||
</p>
|
||||
</>
|
||||
)}
|
||||
</div>
|
||||
);
|
||||
}
|
||||
|
@ -107,7 +107,9 @@ export function GenericVideoPlayer({
|
||||
>
|
||||
<HlsVideoPlayer
|
||||
videoRef={videoRef}
|
||||
currentSource={source}
|
||||
currentSource={{
|
||||
playlist: source,
|
||||
}}
|
||||
hotKeys
|
||||
visible
|
||||
frigateControls={false}
|
||||
|
@ -28,17 +28,22 @@ const unsupportedErrorCodes = [
|
||||
MediaError.MEDIA_ERR_DECODE,
|
||||
];
|
||||
|
||||
export interface HlsSource {
|
||||
playlist: string;
|
||||
startPosition?: number;
|
||||
}
|
||||
|
||||
type HlsVideoPlayerProps = {
|
||||
videoRef: MutableRefObject<HTMLVideoElement | null>;
|
||||
containerRef?: React.MutableRefObject<HTMLDivElement | null>;
|
||||
visible: boolean;
|
||||
currentSource: string;
|
||||
currentSource: HlsSource;
|
||||
hotKeys: boolean;
|
||||
supportsFullscreen: boolean;
|
||||
fullscreen: boolean;
|
||||
frigateControls?: boolean;
|
||||
inpointOffset?: number;
|
||||
onClipEnded?: () => void;
|
||||
onClipEnded?: (currentTime: number) => void;
|
||||
onPlayerLoaded?: () => void;
|
||||
onTimeUpdate?: (time: number) => void;
|
||||
onPlaying?: () => void;
|
||||
@ -113,17 +118,25 @@ export default function HlsVideoPlayer({
|
||||
const currentPlaybackRate = videoRef.current.playbackRate;
|
||||
|
||||
if (!useHlsCompat) {
|
||||
videoRef.current.src = currentSource;
|
||||
videoRef.current.src = currentSource.playlist;
|
||||
videoRef.current.load();
|
||||
return;
|
||||
}
|
||||
|
||||
if (!hlsRef.current) {
|
||||
hlsRef.current = new Hls();
|
||||
hlsRef.current.attachMedia(videoRef.current);
|
||||
// we must destroy the hlsRef every time the source changes
|
||||
// so that we can create a new HLS instance with startPosition
|
||||
// set at the optimal point in time
|
||||
if (hlsRef.current) {
|
||||
hlsRef.current.destroy();
|
||||
}
|
||||
|
||||
hlsRef.current.loadSource(currentSource);
|
||||
hlsRef.current = new Hls({
|
||||
maxBufferLength: 10,
|
||||
maxBufferSize: 20 * 1000 * 1000,
|
||||
startPosition: currentSource.startPosition,
|
||||
});
|
||||
hlsRef.current.attachMedia(videoRef.current);
|
||||
hlsRef.current.loadSource(currentSource.playlist);
|
||||
videoRef.current.playbackRate = currentPlaybackRate;
|
||||
}, [videoRef, hlsRef, useHlsCompat, currentSource]);
|
||||
|
||||
@ -374,7 +387,11 @@ export default function HlsVideoPlayer({
|
||||
}
|
||||
}
|
||||
}}
|
||||
onEnded={onClipEnded}
|
||||
onEnded={() => {
|
||||
if (onClipEnded) {
|
||||
onClipEnded(getVideoTime() ?? 0);
|
||||
}
|
||||
}}
|
||||
onError={(e) => {
|
||||
if (
|
||||
!hlsRef.current &&
|
||||
|
@ -6,7 +6,7 @@ import { Recording } from "@/types/record";
|
||||
import { Preview } from "@/types/preview";
|
||||
import PreviewPlayer, { PreviewController } from "../PreviewPlayer";
|
||||
import { DynamicVideoController } from "./DynamicVideoController";
|
||||
import HlsVideoPlayer from "../HlsVideoPlayer";
|
||||
import HlsVideoPlayer, { HlsSource } from "../HlsVideoPlayer";
|
||||
import { TimeRange } from "@/types/timeline";
|
||||
import ActivityIndicator from "@/components/indicators/activity-indicator";
|
||||
import { VideoResolutionType } from "@/types/live";
|
||||
@ -14,6 +14,7 @@ import axios from "axios";
|
||||
import { cn } from "@/lib/utils";
|
||||
import { useTranslation } from "react-i18next";
|
||||
import { calculateInpointOffset } from "@/utils/videoUtil";
|
||||
import { isFirefox } from "react-device-detect";
|
||||
|
||||
/**
|
||||
* Dynamically switches between video playback and scrubbing preview player.
|
||||
@ -98,9 +99,10 @@ export default function DynamicVideoPlayer({
|
||||
const [isLoading, setIsLoading] = useState(false);
|
||||
const [isBuffering, setIsBuffering] = useState(false);
|
||||
const [loadingTimeout, setLoadingTimeout] = useState<NodeJS.Timeout>();
|
||||
const [source, setSource] = useState(
|
||||
`${apiHost}vod/${camera}/start/${timeRange.after}/end/${timeRange.before}/master.m3u8`,
|
||||
);
|
||||
const [source, setSource] = useState<HlsSource>({
|
||||
playlist: `${apiHost}vod/${camera}/start/${timeRange.after}/end/${timeRange.before}/master.m3u8`,
|
||||
startPosition: startTimestamp ? timeRange.after - startTimestamp : 0,
|
||||
});
|
||||
|
||||
// start at correct time
|
||||
|
||||
@ -184,9 +186,28 @@ export default function DynamicVideoPlayer({
|
||||
playerRef.current.autoplay = !isScrubbing;
|
||||
}
|
||||
|
||||
setSource(
|
||||
`${apiHost}vod/${camera}/start/${recordingParams.after}/end/${recordingParams.before}/master.m3u8`,
|
||||
let startPosition = undefined;
|
||||
|
||||
if (startTimestamp) {
|
||||
const inpointOffset = calculateInpointOffset(
|
||||
recordingParams.after,
|
||||
(recordings || [])[0],
|
||||
);
|
||||
const idealStartPosition = Math.max(
|
||||
0,
|
||||
startTimestamp - timeRange.after - inpointOffset,
|
||||
);
|
||||
|
||||
if (idealStartPosition >= recordings[0].start_time - timeRange.after) {
|
||||
startPosition = idealStartPosition;
|
||||
}
|
||||
}
|
||||
|
||||
setSource({
|
||||
playlist: `${apiHost}vod/${camera}/start/${recordingParams.after}/end/${recordingParams.before}/master.m3u8`,
|
||||
startPosition,
|
||||
});
|
||||
|
||||
setLoadingTimeout(setTimeout(() => setIsLoading(true), 1000));
|
||||
|
||||
controller.newPlayback({
|
||||
@ -203,6 +224,33 @@ export default function DynamicVideoPlayer({
|
||||
[recordingParams, recordings],
|
||||
);
|
||||
|
||||
const onValidateClipEnd = useCallback(
|
||||
(currentTime: number) => {
|
||||
if (!onClipEnded || !controller || !recordings) {
|
||||
return;
|
||||
}
|
||||
|
||||
if (!isFirefox) {
|
||||
onClipEnded();
|
||||
}
|
||||
|
||||
// Firefox has a bug where clipEnded can be called prematurely due to buffering
|
||||
// we need to validate if the current play-point is truly at the end of available recordings
|
||||
|
||||
const lastRecordingTime = recordings.at(-1)?.start_time;
|
||||
|
||||
if (
|
||||
!lastRecordingTime ||
|
||||
controller.getProgress(currentTime) < lastRecordingTime
|
||||
) {
|
||||
return;
|
||||
}
|
||||
|
||||
onClipEnded();
|
||||
},
|
||||
[onClipEnded, controller, recordings],
|
||||
);
|
||||
|
||||
return (
|
||||
<>
|
||||
<HlsVideoPlayer
|
||||
@ -216,7 +264,7 @@ export default function DynamicVideoPlayer({
|
||||
inpointOffset={inpointOffset}
|
||||
onTimeUpdate={onTimeUpdate}
|
||||
onPlayerLoaded={onPlayerLoaded}
|
||||
onClipEnded={onClipEnded}
|
||||
onClipEnded={onValidateClipEnd}
|
||||
onPlaying={() => {
|
||||
if (isScrubbing) {
|
||||
playerRef.current?.pause();
|
||||
|
@ -10,6 +10,7 @@ const localeMap: Record<string, () => Promise<Locale>> = {
|
||||
fr: () => import("date-fns/locale/fr").then((module) => module.fr),
|
||||
ar: () => import("date-fns/locale/ar").then((module) => module.ar),
|
||||
pt: () => import("date-fns/locale/pt").then((module) => module.pt),
|
||||
"pt-BR": () => import("date-fns/locale/pt").then((module) => module.pt),
|
||||
ru: () => import("date-fns/locale/ru").then((module) => module.ru),
|
||||
de: () => import("date-fns/locale/de").then((module) => module.de),
|
||||
ja: () => import("date-fns/locale/ja").then((module) => module.ja),
|
||||
|
@ -17,7 +17,7 @@ export function useVideoDimensions(
|
||||
});
|
||||
|
||||
const videoAspectRatio = useMemo(() => {
|
||||
return videoResolution.width / videoResolution.height || 16 / 9;
|
||||
return videoResolution.width / videoResolution.height;
|
||||
}, [videoResolution]);
|
||||
|
||||
const containerAspectRatio = useMemo(() => {
|
||||
@ -25,7 +25,7 @@ export function useVideoDimensions(
|
||||
}, [containerWidth, containerHeight]);
|
||||
|
||||
const videoDimensions = useMemo(() => {
|
||||
if (!containerWidth || !containerHeight)
|
||||
if (!containerWidth || !containerHeight || !videoAspectRatio)
|
||||
return { width: "100%", height: "100%" };
|
||||
if (containerAspectRatio > videoAspectRatio) {
|
||||
const height = containerHeight;
|
||||
|
@ -2,6 +2,7 @@ export const supportedLanguageKeys = [
|
||||
"en",
|
||||
"es",
|
||||
"pt",
|
||||
"pt-BR",
|
||||
"fr",
|
||||
"de",
|
||||
"it",
|
||||
|
@ -26,6 +26,15 @@ import { useDocDomain } from "@/hooks/use-doc-domain";
|
||||
|
||||
const API_LIMIT = 25;
|
||||
|
||||
// always parse these as string arrays
|
||||
const SEARCH_FILTER_ARRAY_KEYS = [
|
||||
"cameras",
|
||||
"labels",
|
||||
"sub_labels",
|
||||
"recognized_license_plate",
|
||||
"zones",
|
||||
];
|
||||
|
||||
export default function Explore() {
|
||||
// search field handler
|
||||
|
||||
@ -58,13 +67,7 @@ export default function Explore() {
|
||||
const [search, setSearch] = useState("");
|
||||
|
||||
const [searchFilter, setSearchFilter, searchSearchParams] =
|
||||
useApiFilterArgs<SearchFilter>([
|
||||
"cameras",
|
||||
"labels",
|
||||
"sub_labels",
|
||||
"recognized_license_plate",
|
||||
"zones",
|
||||
]);
|
||||
useApiFilterArgs<SearchFilter>(SEARCH_FILTER_ARRAY_KEYS);
|
||||
|
||||
const searchTerm = useMemo(
|
||||
() => searchSearchParams?.["query"] || "",
|
||||
|
@ -118,50 +118,6 @@ export default function NotificationView({
|
||||
// eslint-disable-next-line react-hooks/exhaustive-deps
|
||||
}, [changedValue]);
|
||||
|
||||
// notification key handling
|
||||
|
||||
const { data: publicKey } = useSWR(
|
||||
config?.notifications?.enabled ? "notifications/pubkey" : null,
|
||||
{ revalidateOnFocus: false },
|
||||
);
|
||||
|
||||
const subscribeToNotifications = useCallback(
|
||||
(registration: ServiceWorkerRegistration) => {
|
||||
if (registration) {
|
||||
addMessage(
|
||||
"notification_settings",
|
||||
t("notification.unsavedRegistrations"),
|
||||
undefined,
|
||||
"registration",
|
||||
);
|
||||
|
||||
registration.pushManager
|
||||
.subscribe({
|
||||
userVisibleOnly: true,
|
||||
applicationServerKey: publicKey,
|
||||
})
|
||||
.then((pushSubscription) => {
|
||||
axios
|
||||
.post("notifications/register", {
|
||||
sub: pushSubscription,
|
||||
})
|
||||
.catch(() => {
|
||||
toast.error(t("notification.toast.error.registerFailed"), {
|
||||
position: "top-center",
|
||||
});
|
||||
pushSubscription.unsubscribe();
|
||||
registration.unregister();
|
||||
setRegistration(null);
|
||||
});
|
||||
toast.success(t("notification.toast.success.registered"), {
|
||||
position: "top-center",
|
||||
});
|
||||
});
|
||||
}
|
||||
},
|
||||
[publicKey, addMessage, t],
|
||||
);
|
||||
|
||||
// notification state
|
||||
|
||||
const [registration, setRegistration] =
|
||||
@ -206,8 +162,70 @@ export default function NotificationView({
|
||||
},
|
||||
});
|
||||
|
||||
const watchAllEnabled = form.watch("allEnabled");
|
||||
const watchCameras = form.watch("cameras");
|
||||
|
||||
const anyCameraNotificationsEnabled = useMemo(
|
||||
() =>
|
||||
config &&
|
||||
Object.values(config.cameras).some(
|
||||
(c) =>
|
||||
c.enabled_in_config &&
|
||||
c.notifications &&
|
||||
c.notifications.enabled_in_config,
|
||||
),
|
||||
[config],
|
||||
);
|
||||
|
||||
const shouldFetchPubKey = Boolean(
|
||||
config &&
|
||||
(config.notifications?.enabled || anyCameraNotificationsEnabled) &&
|
||||
(watchAllEnabled ||
|
||||
(Array.isArray(watchCameras) && watchCameras.length > 0)),
|
||||
);
|
||||
|
||||
const { data: publicKey } = useSWR(
|
||||
shouldFetchPubKey ? "notifications/pubkey" : null,
|
||||
{ revalidateOnFocus: false },
|
||||
);
|
||||
|
||||
const subscribeToNotifications = useCallback(
|
||||
(registration: ServiceWorkerRegistration) => {
|
||||
if (registration) {
|
||||
addMessage(
|
||||
"notification_settings",
|
||||
t("notification.unsavedRegistrations"),
|
||||
undefined,
|
||||
"registration",
|
||||
);
|
||||
|
||||
registration.pushManager
|
||||
.subscribe({
|
||||
userVisibleOnly: true,
|
||||
applicationServerKey: publicKey,
|
||||
})
|
||||
.then((pushSubscription) => {
|
||||
axios
|
||||
.post("notifications/register", {
|
||||
sub: pushSubscription,
|
||||
})
|
||||
.catch(() => {
|
||||
toast.error(t("notification.toast.error.registerFailed"), {
|
||||
position: "top-center",
|
||||
});
|
||||
pushSubscription.unsubscribe();
|
||||
registration.unregister();
|
||||
setRegistration(null);
|
||||
});
|
||||
toast.success(t("notification.toast.success.registered"), {
|
||||
position: "top-center",
|
||||
});
|
||||
});
|
||||
}
|
||||
},
|
||||
[publicKey, addMessage, t],
|
||||
);
|
||||
|
||||
useEffect(() => {
|
||||
if (watchCameras.length > 0) {
|
||||
form.setValue("allEnabled", false);
|
||||
@ -521,13 +539,7 @@ export default function NotificationView({
|
||||
</Heading>
|
||||
<Button
|
||||
aria-label={t("notification.registerDevice")}
|
||||
disabled={
|
||||
(!config?.notifications.enabled &&
|
||||
notificationCameras.length === 0 &&
|
||||
!form.watch("allEnabled") &&
|
||||
form.watch("cameras").length === 0) ||
|
||||
publicKey == undefined
|
||||
}
|
||||
disabled={!shouldFetchPubKey || publicKey == undefined}
|
||||
onClick={() => {
|
||||
if (registration == null) {
|
||||
Notification.requestPermission().then((permission) => {
|
||||
|
Loading…
Reference in New Issue
Block a user