mirror of
https://github.com/blakeblackshear/frigate.git
synced 2026-10-02 21:06:52 +03:00
Compare commits
32
Commits
b4d5035b79
...
master
| Author | SHA1 | Date | |
|---|---|---|---|
|
|
e912d0852b | ||
|
|
321e78e65b | ||
|
|
17a8efa09c | ||
|
|
160d213025 | ||
|
|
b749b0bbc5 | ||
|
|
e37041c879 | ||
|
|
61e50a366f | ||
|
|
4e196516dd | ||
|
|
3941355051 | ||
|
|
1a278630da | ||
|
|
9d0d8a99bb | ||
|
|
bbc412763d | ||
|
|
ac9ac50df5 | ||
|
|
93aa6c4174 | ||
|
|
26e6adee88 | ||
|
|
de416b7ae7 | ||
|
|
06967fec91 | ||
|
|
d69107de33 | ||
|
|
04480a18b6 | ||
|
|
b02aea03cd | ||
|
|
51171319a4 | ||
|
|
b1b725b80a | ||
|
|
77a66e75c6 | ||
|
|
4ac92dac72 | ||
|
|
6ceb370abb | ||
|
|
dfefc5a64b | ||
|
|
97dec9d046 | ||
|
|
ffcadb440e | ||
|
|
a381a88b29 | ||
|
|
9e74adf812 | ||
|
|
287fc42404 | ||
|
|
50a2b6729e |
@@ -1,7 +1,7 @@
|
|||||||
default_target: local
|
default_target: local
|
||||||
|
|
||||||
COMMIT_HASH := $(shell git log -1 --pretty=format:"%h"|tail -1)
|
COMMIT_HASH := $(shell git log -1 --pretty=format:"%h"|tail -1)
|
||||||
VERSION = 0.18.0
|
VERSION = 0.18.1
|
||||||
IMAGE_REPO ?= ghcr.io/blakeblackshear/frigate
|
IMAGE_REPO ?= ghcr.io/blakeblackshear/frigate
|
||||||
GITHUB_REF_NAME ?= $(shell git rev-parse --abbrev-ref HEAD)
|
GITHUB_REF_NAME ?= $(shell git rev-parse --abbrev-ref HEAD)
|
||||||
BOARDS= #Initialized empty
|
BOARDS= #Initialized empty
|
||||||
|
|||||||
@@ -150,7 +150,9 @@ http {
|
|||||||
include auth_request.conf;
|
include auth_request.conf;
|
||||||
types {
|
types {
|
||||||
video/mp4 mp4;
|
video/mp4 mp4;
|
||||||
image/jpeg jpg;
|
image/jpeg jpg jpeg;
|
||||||
|
image/png png;
|
||||||
|
image/webp webp;
|
||||||
}
|
}
|
||||||
|
|
||||||
expires 7d;
|
expires 7d;
|
||||||
|
|||||||
@@ -894,6 +894,41 @@ deepstack:
|
|||||||
api_url: http://<your_codeproject_ai_server_ip>:<port>/v1/vision/detection
|
api_url: http://<your_codeproject_ai_server_ip>:<port>/v1/vision/detection
|
||||||
type: deepstack
|
type: deepstack
|
||||||
api_timeout: 0.1 # seconds
|
api_timeout: 0.1 # seconds
|
||||||
|
xdna2:
|
||||||
|
title: AMD XDNA2
|
||||||
|
models:
|
||||||
|
- key: yolov9
|
||||||
|
label: YOLOv9
|
||||||
|
recommended: true
|
||||||
|
download: |-
|
||||||
|
Prepare the model using the frigate-xdna setup instructions linked above. For local YOLO models, Frigate must have access to the same ONNX file bytes as the sidecar. The example below uses YOLOv9-C at 320x320. Frigate+ models may instead use the same `plus://MODEL_ID` in Frigate and the sidecar.
|
||||||
|
ui: |-
|
||||||
|
Navigate to **Settings > System > Detectors and model** and select **ZMQ IPC** from the detector type dropdown and click **Add**, then set the endpoint to `tcp://xdna:5555`. Then on the same page, in the **Custom Model** tab, configure:
|
||||||
|
|
||||||
|
| Field | Value |
|
||||||
|
| ---------------------------------------- | ------------------------------------------ |
|
||||||
|
| **Custom object detector model path** | `/config/models/yolov9-c-320.onnx` |
|
||||||
|
| **Label map for custom object detector** | `/labelmap/coco-80.txt` |
|
||||||
|
| **Object detection model input width** | `320` |
|
||||||
|
| **Object detection model input height** | `320` |
|
||||||
|
| **Model Input Pixel Color Format** | `rgb` (Frigate's default value) |
|
||||||
|
| **Model Input Tensor Shape** | `nchw` |
|
||||||
|
| **Model Input D Type** | `float` |
|
||||||
|
| **Object Detection Model Type** | `yolo-generic` |
|
||||||
|
yaml: |-
|
||||||
|
detectors:
|
||||||
|
xdna:
|
||||||
|
type: zmq
|
||||||
|
endpoint: tcp://xdna:5555
|
||||||
|
|
||||||
|
model:
|
||||||
|
model_type: yolo-generic
|
||||||
|
width: 320
|
||||||
|
height: 320
|
||||||
|
input_tensor: nchw
|
||||||
|
input_dtype: float
|
||||||
|
path: /config/models/yolov9-c-320.onnx
|
||||||
|
labelmap_path: /labelmap/coco-80.txt
|
||||||
memryx:
|
memryx:
|
||||||
title: MemryX
|
title: MemryX
|
||||||
models:
|
models:
|
||||||
|
|||||||
@@ -286,7 +286,7 @@ ffmpeg:
|
|||||||
# Optional: output args for detect streams (default: shown below)
|
# Optional: output args for detect streams (default: shown below)
|
||||||
detect: -threads 2 -f rawvideo -pix_fmt yuv420p
|
detect: -threads 2 -f rawvideo -pix_fmt yuv420p
|
||||||
# Optional: output args for record streams (default: shown below)
|
# Optional: output args for record streams (default: shown below)
|
||||||
record: preset-record-generic
|
record: preset-record-generic-audio-aac
|
||||||
# Optional: Time in seconds to wait before ffmpeg retries connecting to the camera. (default: shown below)
|
# Optional: Time in seconds to wait before ffmpeg retries connecting to the camera. (default: shown below)
|
||||||
# If set too low, frigate will retry a connection to the camera's stream too frequently, using up the limited streams some cameras can allow at once
|
# If set too low, frigate will retry a connection to the camera's stream too frequently, using up the limited streams some cameras can allow at once
|
||||||
# If set too high, then if a ffmpeg crash or camera stream timeout occurs, you could potentially lose up to a maximum of retry_interval second(s) of footage
|
# If set too high, then if a ffmpeg crash or camera stream timeout occurs, you could potentially lose up to a maximum of retry_interval second(s) of footage
|
||||||
|
|||||||
@@ -378,10 +378,10 @@ Navigate to <NavPath path="Settings > Camera configuration > Object detection" /
|
|||||||
|
|
||||||
Navigate to <NavPath path="Settings > Camera configuration > Objects" />.
|
Navigate to <NavPath path="Settings > Camera configuration > Objects" />.
|
||||||
|
|
||||||
| Field | Description |
|
| Field | Description |
|
||||||
| ---------------------------------------------- | ------------------- |
|
| --------------------------------------------------------- | ------------------- |
|
||||||
| **Objects to track** | Add `license_plate` |
|
| **Objects to track** | Add `license_plate` |
|
||||||
| **Object filters > License Plate > Threshold** | Set to `0.7` |
|
| **Object filters > License Plate > Confidence threshold** | Set to `0.7` |
|
||||||
|
|
||||||
Navigate to <NavPath path="Settings > Camera configuration > Motion detection" />.
|
Navigate to <NavPath path="Settings > Camera configuration > Motion detection" />.
|
||||||
|
|
||||||
|
|||||||
@@ -29,6 +29,7 @@ Frigate supports multiple different detectors that work on different types of ha
|
|||||||
|
|
||||||
- [ROCm](#amdrocm-gpu-detector): ROCm can run on AMD Discrete GPUs to provide efficient object detection.
|
- [ROCm](#amdrocm-gpu-detector): ROCm can run on AMD Discrete GPUs to provide efficient object detection.
|
||||||
- [ONNX](#onnx): ROCm will automatically be detected and used as a detector in the `-rocm` Frigate image when a supported ONNX model is configured.
|
- [ONNX](#onnx): ROCm will automatically be detected and used as a detector in the `-rocm` Frigate image when a supported ONNX model is configured.
|
||||||
|
- <CommunityBadge /> [XDNA2](#amd-xdna2): AMD Ryzen AI / XDNA2 NPUs can run object detection through the community-maintained `frigate-xdna` ZMQ sidecar.
|
||||||
|
|
||||||
**Apple Silicon**
|
**Apple Silicon**
|
||||||
|
|
||||||
@@ -508,6 +509,28 @@ To verify that the integration is working correctly, start Frigate and observe t
|
|||||||
|
|
||||||
# Community Supported Detectors
|
# Community Supported Detectors
|
||||||
|
|
||||||
|
## AMD XDNA2
|
||||||
|
|
||||||
|
AMD Ryzen AI / XDNA2 NPUs can be used through the community-maintained
|
||||||
|
[frigate-xdna](https://github.com/mitchins/frigate-xdna) detector sidecar.
|
||||||
|
The sidecar runs separately from Frigate and connects using Frigate's ZMQ
|
||||||
|
detector interface.
|
||||||
|
|
||||||
|
Currently qualified on **Ryzen AI Max 300 / Strix Halo**. Other XDNA2 devices
|
||||||
|
are not yet qualified; XDNA1 is unsupported.
|
||||||
|
|
||||||
|
Follow the frigate-xdna setup instructions to prepare and start the sidecar
|
||||||
|
before starting Frigate.
|
||||||
|
|
||||||
|
### Configuration {#configuration-xdna2}
|
||||||
|
|
||||||
|
Using the detector config below will connect Frigate to the sidecar:
|
||||||
|
|
||||||
|
<ModelConfigDropdown detectorTitle="AMD XDNA2" models={objectDetectorsModels.xdna2.models} />
|
||||||
|
|
||||||
|
The example assumes Frigate and the sidecar share a Docker network where the
|
||||||
|
sidecar is named `xdna`.
|
||||||
|
|
||||||
## MemryX MX3
|
## MemryX MX3
|
||||||
|
|
||||||
This detector is available for use with the MemryX MX3 accelerator M.2 module. Frigate supports the MX3 on compatible hardware platforms, providing efficient and high-performance object detection.
|
This detector is available for use with the MemryX MX3 accelerator M.2 module. Frigate supports the MX3 on compatible hardware platforms, providing efficient and high-performance object detection.
|
||||||
|
|||||||
@@ -45,10 +45,10 @@ Any detection below `min_score` will be immediately thrown out and never tracked
|
|||||||
|
|
||||||
Navigate to <NavPath path="Settings > Global configuration > Objects" /> to set score filters globally.
|
Navigate to <NavPath path="Settings > Global configuration > Objects" /> to set score filters globally.
|
||||||
|
|
||||||
| Field | Description |
|
| Field | Description |
|
||||||
| --------------------------------------- | ---------------------------------------------------------------- |
|
| -------------------------------------------------- | ---------------------------------------------------------------- |
|
||||||
| **Object filters > Person > Min Score** | Minimum score for a single detection to initiate tracking |
|
| **Object filters > Person > Minimum confidence** | Minimum score for a single detection to initiate tracking |
|
||||||
| **Object filters > Person > Threshold** | Minimum computed (median) score to be considered a true positive |
|
| **Object filters > Person > Confidence threshold** | Minimum computed (median) score to be considered a true positive |
|
||||||
|
|
||||||
To override score filters for a specific camera, navigate to <NavPath path="Settings > Camera configuration > Objects" /> and select the camera.
|
To override score filters for a specific camera, navigate to <NavPath path="Settings > Camera configuration > Objects" /> and select the camera.
|
||||||
|
|
||||||
@@ -103,12 +103,12 @@ Conceptually, a ratio of 1 is a square, 0.5 is a "tall skinny" box, and 2 is a "
|
|||||||
|
|
||||||
Navigate to <NavPath path="Settings > Global configuration > Objects" /> to set shape filters globally.
|
Navigate to <NavPath path="Settings > Global configuration > Objects" /> to set shape filters globally.
|
||||||
|
|
||||||
| Field | Description |
|
| Field | Description |
|
||||||
| --------------------------------------- | ------------------------------------------------------------------------ |
|
| -------------------------------------------------- | ------------------------------------------------------------------------ |
|
||||||
| **Object filters > Person > Min Area** | Minimum bounding box area in pixels (or decimal for percentage of frame) |
|
| **Object filters > Person > Minimum object area** | Minimum bounding box area in pixels (or decimal for percentage of frame) |
|
||||||
| **Object filters > Person > Max Area** | Maximum bounding box area in pixels (or decimal for percentage of frame) |
|
| **Object filters > Person > Maximum object area** | Maximum bounding box area in pixels (or decimal for percentage of frame) |
|
||||||
| **Object filters > Person > Min Ratio** | Minimum width/height ratio of the bounding box |
|
| **Object filters > Person > Minimum aspect ratio** | Minimum width/height ratio of the bounding box |
|
||||||
| **Object filters > Person > Max Ratio** | Maximum width/height ratio of the bounding box |
|
| **Object filters > Person > Maximum aspect ratio** | Maximum width/height ratio of the bounding box |
|
||||||
|
|
||||||
To override shape filters for a specific camera, navigate to <NavPath path="Settings > Camera configuration > Objects" /> and select the camera.
|
To override shape filters for a specific camera, navigate to <NavPath path="Settings > Camera configuration > Objects" /> and select the camera.
|
||||||
|
|
||||||
|
|||||||
@@ -70,14 +70,14 @@ Object filters help reduce false positives by constraining the size, shape, and
|
|||||||
|
|
||||||
Navigate to <NavPath path="Settings > Global configuration > Objects" />.
|
Navigate to <NavPath path="Settings > Global configuration > Objects" />.
|
||||||
|
|
||||||
| Field | Description |
|
| Field | Description |
|
||||||
| --------------------------------------- | ------------------------------------------------------------------------ |
|
| -------------------------------------------------- | ------------------------------------------------------------------------ |
|
||||||
| **Object filters > Person > Min Area** | Minimum bounding box area in pixels (or decimal for percentage of frame) |
|
| **Object filters > Person > Minimum object area** | Minimum bounding box area in pixels (or decimal for percentage of frame) |
|
||||||
| **Object filters > Person > Max Area** | Maximum bounding box area in pixels (or decimal for percentage of frame) |
|
| **Object filters > Person > Maximum object area** | Maximum bounding box area in pixels (or decimal for percentage of frame) |
|
||||||
| **Object filters > Person > Min Ratio** | Minimum width/height ratio of the bounding box |
|
| **Object filters > Person > Minimum aspect ratio** | Minimum width/height ratio of the bounding box |
|
||||||
| **Object filters > Person > Max Ratio** | Maximum width/height ratio of the bounding box |
|
| **Object filters > Person > Maximum aspect ratio** | Maximum width/height ratio of the bounding box |
|
||||||
| **Object filters > Person > Min Score** | Minimum score for the object to initiate tracking |
|
| **Object filters > Person > Minimum confidence** | Minimum score for the object to initiate tracking |
|
||||||
| **Object filters > Person > Threshold** | Minimum computed score to be considered a true positive |
|
| **Object filters > Person > Confidence threshold** | Minimum computed score to be considered a true positive |
|
||||||
|
|
||||||
To override filters for a specific camera, navigate to <NavPath path="Settings > Camera configuration > Objects" />.
|
To override filters for a specific camera, navigate to <NavPath path="Settings > Camera configuration > Objects" />.
|
||||||
|
|
||||||
|
|||||||
@@ -191,14 +191,12 @@ cameras:
|
|||||||
detect:
|
detect:
|
||||||
enabled: false
|
enabled: false
|
||||||
record:
|
record:
|
||||||
enabled: false
|
enabled: true
|
||||||
profiles:
|
profiles:
|
||||||
away:
|
away:
|
||||||
enabled: true
|
enabled: true
|
||||||
detect:
|
detect:
|
||||||
enabled: true
|
enabled: true
|
||||||
record:
|
|
||||||
enabled: true
|
|
||||||
home:
|
home:
|
||||||
enabled: false
|
enabled: false
|
||||||
```
|
```
|
||||||
@@ -251,6 +249,12 @@ Leaving the `objects` section empty (or omitting `track`) does not clear the lis
|
|||||||
|
|
||||||
Fields that require a Frigate restart to take effect cannot be overridden by profiles, since profiles are applied at runtime without restarting. Those fields are hidden when editing a profile override and can only be changed on the base configuration.
|
Fields that require a Frigate restart to take effect cannot be overridden by profiles, since profiles are applied at runtime without restarting. Those fields are hidden when editing a profile override and can only be changed on the base configuration.
|
||||||
|
|
||||||
|
### Why can't a profile enable recording when it's disabled in the base config?
|
||||||
|
|
||||||
|
Frigate only sets up a camera's recording stream at startup when recording is enabled in the base config, so enabling it later from a profile has no effect. The same applies to turning recording on from the UI or MQTT.
|
||||||
|
|
||||||
|
To keep recording off by default, leave `record.enabled: true` in the base config and create a profile that sets `record.enabled: false`. Activate that profile and it will be restored automatically when Frigate starts.
|
||||||
|
|
||||||
### Can I schedule profiles to be enabled or disabled at certain times?
|
### Can I schedule profiles to be enabled or disabled at certain times?
|
||||||
|
|
||||||
Not within Frigate itself. Frigate is an NVR, not an automation platform, so it intentionally does not include a scheduler for activating profiles. Instead, activate profiles from an automation platform that already handles time- and event-based triggers well, such as [Home Assistant](https://www.home-assistant.io/) or [Node-RED](https://nodered.org/). These integrate with Frigate and give you far more robust and flexible scheduling than a built-in scheduler could.
|
Not within Frigate itself. Frigate is an NVR, not an automation platform, so it intentionally does not include a scheduler for activating profiles. Instead, activate profiles from an automation platform that already handles time- and event-based triggers well, such as [Home Assistant](https://www.home-assistant.io/) or [Node-RED](https://nodered.org/). These integrate with Frigate and give you far more robust and flexible scheduling than a built-in scheduler could.
|
||||||
|
|||||||
@@ -245,8 +245,8 @@ Triggers are best configured through the Frigate UI.
|
|||||||
1. Navigate to <NavPath path="Settings > Enrichments > Triggers" /> and select a camera from the dropdown menu.
|
1. Navigate to <NavPath path="Settings > Enrichments > Triggers" /> and select a camera from the dropdown menu.
|
||||||
2. Click **Add Trigger** to create a new trigger or use the pencil icon to edit an existing one.
|
2. Click **Add Trigger** to create a new trigger or use the pencil icon to edit an existing one.
|
||||||
3. In the **Create Trigger** wizard:
|
3. In the **Create Trigger** wizard:
|
||||||
- Enter a **Name** for the trigger (e.g., "Red Car Alert").
|
- Enter a **Name** for the trigger (e.g., "Red Car Alert"). Frigate derives the trigger's
|
||||||
- Enter a descriptive **Friendly Name** for the trigger (e.g., "Red car on the driveway camera").
|
internal **ID** from this name, which can be revealed and edited with the show/hide toggle.
|
||||||
- Select the **Type** (`Thumbnail` or `Description`).
|
- Select the **Type** (`Thumbnail` or `Description`).
|
||||||
- For `Thumbnail`, select an image to trigger this action when a similar thumbnail image is detected, based on the threshold.
|
- For `Thumbnail`, select an image to trigger this action when a similar thumbnail image is detected, based on the threshold.
|
||||||
- For `Description`, enter text to trigger this action when a similar tracked object description is detected.
|
- For `Description`, enter text to trigger this action when a similar tracked object description is detected.
|
||||||
|
|||||||
@@ -28,7 +28,7 @@ During testing, enable the Zones option for the [Debug view](/usage/live#the-sin
|
|||||||
1. Navigate to <NavPath path="Settings > Camera configuration > Masks / Zones" /> and select the desired camera.
|
1. Navigate to <NavPath path="Settings > Camera configuration > Masks / Zones" /> and select the desired camera.
|
||||||
2. Under the **Zones** section, click the plus icon to add a new zone.
|
2. Under the **Zones** section, click the plus icon to add a new zone.
|
||||||
3. Click on the camera's latest image to create the points for the zone boundary. Click the first point again to close the polygon.
|
3. Click on the camera's latest image to create the points for the zone boundary. Click the first point again to close the polygon.
|
||||||
4. Configure zone options such as **Friendly name**, **Objects**, **Loitering time**, and **Inertia** in the zone editor.
|
4. Configure zone options such as **Name**, **Objects**, **Loitering Time**, and **Inertia** in the zone editor.
|
||||||
5. Press **Save** when finished.
|
5. Press **Save** when finished.
|
||||||
|
|
||||||
</TabItem>
|
</TabItem>
|
||||||
@@ -200,7 +200,7 @@ When using loitering zones, a review item will behave in the following way:
|
|||||||
|
|
||||||
1. Navigate to <NavPath path="Settings > Camera configuration > Masks / Zones" /> and select the desired camera.
|
1. Navigate to <NavPath path="Settings > Camera configuration > Masks / Zones" /> and select the desired camera.
|
||||||
2. Edit or create the zone (e.g., `sidewalk`).
|
2. Edit or create the zone (e.g., `sidewalk`).
|
||||||
- Set **Loitering time** to the desired number of seconds (e.g., `4`)
|
- Set **Loitering Time** to the desired number of seconds (e.g., `4`)
|
||||||
- Under **Objects**, add the relevant object types (e.g., `person`)
|
- Under **Objects**, add the relevant object types (e.g., `person`)
|
||||||
|
|
||||||
</TabItem>
|
</TabItem>
|
||||||
@@ -291,7 +291,7 @@ Accurate real-world distance measurements are required to estimate speeds. These
|
|||||||
|
|
||||||
1. Navigate to <NavPath path="Settings > Camera configuration > Masks / Zones" /> and select the desired camera.
|
1. Navigate to <NavPath path="Settings > Camera configuration > Masks / Zones" /> and select the desired camera.
|
||||||
2. Create or edit a zone with exactly 4 points aligned to the ground plane.
|
2. Create or edit a zone with exactly 4 points aligned to the ground plane.
|
||||||
3. In the zone editor, enter the real-world **Distances** between each pair of consecutive points.
|
3. In the zone editor, enable **Speed Estimation** and enter the real-world **Line A distance**, **Line B distance**, **Line C distance**, and **Line D distance** between each pair of consecutive points.
|
||||||
- For example, if the distance between the first and second points is 10 meters, between the second and third is 12 meters, etc.
|
- For example, if the distance between the first and second points is 10 meters, between the second and third is 12 meters, etc.
|
||||||
4. Distances are measured in meters (metric) or feet (imperial), depending on the **Unit system** setting.
|
4. Distances are measured in meters (metric) or feet (imperial), depending on the **Unit system** setting.
|
||||||
|
|
||||||
@@ -358,7 +358,7 @@ Zones can be configured with a minimum speed requirement, meaning an object must
|
|||||||
|
|
||||||
1. Navigate to <NavPath path="Settings > Camera configuration > Masks / Zones" /> and select the desired camera.
|
1. Navigate to <NavPath path="Settings > Camera configuration > Masks / Zones" /> and select the desired camera.
|
||||||
2. Edit or create the zone with distances configured.
|
2. Edit or create the zone with distances configured.
|
||||||
- Set **Speed threshold** to the desired minimum speed (e.g., `20`)
|
- Set **Speed Threshold** to the desired minimum speed (e.g., `20`)
|
||||||
- The unit is kph or mph, depending on the **Unit system** setting
|
- The unit is kph or mph, depending on the **Unit system** setting
|
||||||
|
|
||||||
</TabItem>
|
</TabItem>
|
||||||
|
|||||||
@@ -54,7 +54,7 @@ An object filter mask drops any [bounding box](#bounding-box) whose bottom cente
|
|||||||
|
|
||||||
## Min Score
|
## Min Score
|
||||||
|
|
||||||
The lowest score a detected object can have to be kept during tracking. Anything scoring below the minimum is assumed to be a [false positive](#false-positive) and discarded.
|
The lowest score a detected object can have to be kept during tracking. Anything scoring below the minimum is assumed to be a [false positive](#false-positive) and discarded. Set with `min_score` in the config, shown as **Minimum confidence** in the settings UI.
|
||||||
|
|
||||||
## Model
|
## Model
|
||||||
|
|
||||||
@@ -86,7 +86,7 @@ A more specific identity assigned to a [tracked object](#tracked-object-event-in
|
|||||||
|
|
||||||
## Threshold
|
## Threshold
|
||||||
|
|
||||||
The median score an object must reach to be considered a true positive.
|
The median score an object must reach to be considered a true positive. Set with `threshold` in the config, shown as **Confidence threshold** in the settings UI.
|
||||||
|
|
||||||
## Top Score
|
## Top Score
|
||||||
|
|
||||||
|
|||||||
@@ -70,6 +70,9 @@ Frigate supports multiple different detectors that work on different types of ha
|
|||||||
- [ROCm](#rocm---amd-gpu): ROCm can run on AMD Discrete GPUs to provide efficient object detection
|
- [ROCm](#rocm---amd-gpu): ROCm can run on AMD Discrete GPUs to provide efficient object detection
|
||||||
- [Supports limited model architectures](../../configuration/object_detectors#amdrocm-gpu-detector)
|
- [Supports limited model architectures](../../configuration/object_detectors#amdrocm-gpu-detector)
|
||||||
- Runs best on discrete AMD GPUs
|
- Runs best on discrete AMD GPUs
|
||||||
|
- <CommunityBadge /> [XDNA2 (Ryzen AI)](#amd-xdna2): AMD XDNA2 NPU (sub-watt power AI/ML processor separate to the GPU) inside Strix and other "AI" branded AMD platforms
|
||||||
|
- Has only been tested with YOLOv9, in theory other graphs may be compiled too.
|
||||||
|
- Runs via ZMQ proxy which adds some latency, only recommended for local connection
|
||||||
|
|
||||||
**Apple Silicon**
|
**Apple Silicon**
|
||||||
|
|
||||||
@@ -296,6 +299,32 @@ The inference time of a rk3588 with all 3 cores enabled is typically 25-30 ms fo
|
|||||||
| ---------------- | ----------------------------------- |
|
| ---------------- | ----------------------------------- |
|
||||||
| yolov9-tiny | ~ 4 ms |
|
| yolov9-tiny | ~ 4 ms |
|
||||||
|
|
||||||
|
### AMD Ryzen AI / XDNA2
|
||||||
|
|
||||||
|
Frigate supports AMD XDNA2 NPUs through the community-maintained
|
||||||
|
frigate-xdna ZMQ sidecar. It works with stock Frigate and supports
|
||||||
|
Frigate+ models or compatible local YOLO ONNX models. Models are compiled
|
||||||
|
once on the target system and cached for subsequent use.
|
||||||
|
|
||||||
|
Currently qualified on **Ryzen AI Max 300 / Strix Halo**. Other XDNA2
|
||||||
|
devices are not yet qualified; XDNA1 is unsupported.
|
||||||
|
|
||||||
|
Measured YOLOv9 detector latency on Strix Halo:
|
||||||
|
|
||||||
|
| Model | 320 | 640 |
|
||||||
|
| ----- | ---: | ---: |
|
||||||
|
| YOLOv9-T | ~7.4 ms | unsupported |
|
||||||
|
| YOLOv9-S | ~9.0 ms | ~20.0 ms |
|
||||||
|
| YOLOv9-M | ~13.1 ms | ~34.4 ms |
|
||||||
|
| YOLOv9-C | ~14.1 ms | ~35.2 ms |
|
||||||
|
| YOLOv9-E | ~69.4 ms | ~224.8 ms |
|
||||||
|
|
||||||
|
**YOLOv9-C at 320 is the recommended quality/performance balance.**
|
||||||
|
C at 640 is also usable where the lower throughput is acceptable.
|
||||||
|
|
||||||
|
Setup, model preparation, and compatibility details are available
|
||||||
|
[in the frigate-xdna documentation](https://github.com/mitchins/frigate-xdna).
|
||||||
|
|
||||||
## What does Frigate use the CPU for and what does it use a detector for? (ELI5 Version)
|
## What does Frigate use the CPU for and what does it use a detector for? (ELI5 Version)
|
||||||
|
|
||||||
This is taken from a [user question on reddit](https://www.reddit.com/r/homeassistant/comments/q8mgau/comment/hgqbxh5/?utm_source=share&utm_medium=web2x&context=3). Modified slightly for clarity.
|
This is taken from a [user question on reddit](https://www.reddit.com/r/homeassistant/comments/q8mgau/comment/hgqbxh5/?utm_source=share&utm_medium=web2x&context=3). Modified slightly for clarity.
|
||||||
|
|||||||
@@ -11,6 +11,12 @@ MQTT requires a network connection to your broker. This is typically local, but
|
|||||||
|
|
||||||
:::
|
:::
|
||||||
|
|
||||||
|
:::note
|
||||||
|
|
||||||
|
Wherever a topic below includes a camera, mask, or zone name, use its `ID` from the config, not its `friendly_name`. For example, a camera with `friendly_name: "Back Yard"` and ID `back_yard` publishes to `frigate/back_yard/...`, not `frigate/Back Yard/...`.
|
||||||
|
|
||||||
|
:::
|
||||||
|
|
||||||
## General Frigate Topics
|
## General Frigate Topics
|
||||||
|
|
||||||
### `frigate/available`
|
### `frigate/available`
|
||||||
|
|||||||
@@ -27,6 +27,10 @@ The [Advanced Camera Card](https://card.camera/#/README) is a Home Assistant das
|
|||||||
It supports automatically setting the sub labels in Frigate for person objects that are detected and recognized.
|
It supports automatically setting the sub labels in Frigate for person objects that are detected and recognized.
|
||||||
This is a fork (with fixed errors and new features) of [original Double Take](https://github.com/jakowenko/double-take) project which, unfortunately, isn't being maintained by author.
|
This is a fork (with fixed errors and new features) of [original Double Take](https://github.com/jakowenko/double-take) project which, unfortunately, isn't being maintained by author.
|
||||||
|
|
||||||
|
## [frigate-abr](https://github.com/007hacky007/frigate-abr)
|
||||||
|
|
||||||
|
[frigate-abr](https://github.com/007hacky007/frigate-abr) is a drop-in Docker image of Frigate that adds adaptive bitrate (ABR) playback for recordings: a sidecar transcodes footage to lower quality tiers on demand, for reviewing over slow remote connections. Segments are transcoded when played and cached, so no additional stream is recorded. Frigate itself is not modified.
|
||||||
|
|
||||||
## [Frigate Notify](https://github.com/0x2142/frigate-notify)
|
## [Frigate Notify](https://github.com/0x2142/frigate-notify)
|
||||||
|
|
||||||
[Frigate Notify](https://github.com/0x2142/frigate-notify) is a simple app designed to send notifications from Frigate to your favorite platforms. Intended to be used with standalone Frigate installations - Home Assistant not required, MQTT is optional but recommended.
|
[Frigate Notify](https://github.com/0x2142/frigate-notify) is a simple app designed to send notifications from Frigate to your favorite platforms. Intended to be used with standalone Frigate installations - Home Assistant not required, MQTT is optional but recommended.
|
||||||
|
|||||||
@@ -21,7 +21,13 @@ Yes. Models and metadata are stored in the `model_cache` directory within the co
|
|||||||
|
|
||||||
### Can I keep using my Frigate+ models even if I do not renew my subscription?
|
### Can I keep using my Frigate+ models even if I do not renew my subscription?
|
||||||
|
|
||||||
Yes. Subscriptions to Frigate+ provide access to the infrastructure used to train the models. Models trained with your subscription are yours to keep and use forever. However, do note that the terms and conditions prohibit you from sharing, reselling, or creating derivative products from the models.
|
Yes. Subscriptions to Frigate+ provide access to the infrastructure used to train the models. Models you train during an active subscription remain licensed for your continued use even after your subscription ends — models already in your model cache will keep working indefinitely. An active subscription is required to train new models and download new versions.
|
||||||
|
|
||||||
|
### Can I use Frigate+ models commercially?
|
||||||
|
|
||||||
|
A standard subscription covers use on camera systems you own or operate, including for your business. A shop, restaurant, warehouse, or office running Frigate+ at its own locations (including multiple locations) is exactly the kind of use the subscription is for.
|
||||||
|
What the standard subscription does not cover is using Frigate+ models to provide a product or service to others. If you're deploying models at your customers' sites, bundling them with hardware you sell, or running them as part of a hosted or managed service, even if your customers never receive the model files themselves, you'll need a commercial license.
|
||||||
|
Note that professional installers are fine under standard subscriptions when each customer holds their own Frigate+ subscription. The commercial license is for cases where your license powers your customers' sites.
|
||||||
|
|
||||||
### Why can't I submit images to Frigate+?
|
### Why can't I submit images to Frigate+?
|
||||||
|
|
||||||
|
|||||||
@@ -64,20 +64,20 @@ Frigate+ models generally have much higher scores than the default model provide
|
|||||||
<ConfigTabs>
|
<ConfigTabs>
|
||||||
<TabItem value="ui">
|
<TabItem value="ui">
|
||||||
|
|
||||||
Navigate to <NavPath path="Settings > Global configuration > Objects" />. Under **Object filters**, set **Min Score** and **Threshold** for each object type, then click **Save**.
|
Navigate to <NavPath path="Settings > Global configuration > Objects" />. Under **Object filters**, set **Minimum confidence** and **Confidence threshold** for each object type, then click **Save**.
|
||||||
|
|
||||||
| Object | Min Score | Threshold |
|
| Object | Minimum confidence | Confidence threshold |
|
||||||
| ----------------- | --------- | --------- |
|
| ----------------- | ------------------ | -------------------- |
|
||||||
| **dog** | .7 | .9 |
|
| **dog** | .7 | .9 |
|
||||||
| **cat** | .65 | .8 |
|
| **cat** | .65 | .8 |
|
||||||
| **face** | .7 | |
|
| **face** | .7 | |
|
||||||
| **package** | .65 | .9 |
|
| **package** | .65 | .9 |
|
||||||
| **license_plate** | .6 | |
|
| **license_plate** | .6 | |
|
||||||
| **amazon** | .75 | |
|
| **amazon** | .75 | |
|
||||||
| **ups** | .75 | |
|
| **ups** | .75 | |
|
||||||
| **fedex** | .75 | |
|
| **fedex** | .75 | |
|
||||||
| **person** | .65 | .85 |
|
| **person** | .65 | .85 |
|
||||||
| **car** | .65 | .85 |
|
| **car** | .65 | .85 |
|
||||||
|
|
||||||
</TabItem>
|
</TabItem>
|
||||||
<TabItem value="yaml">
|
<TabItem value="yaml">
|
||||||
|
|||||||
@@ -65,11 +65,11 @@ Some users may find that Frigate+ models result in more false positives initiall
|
|||||||
|
|
||||||
Frigate+ models support a more relevant set of objects for security cameras. The labels for annotation in Frigate+ are configurable by editing the camera in the Cameras section of Frigate+. Currently, the following objects are supported:
|
Frigate+ models support a more relevant set of objects for security cameras. The labels for annotation in Frigate+ are configurable by editing the camera in the Cameras section of Frigate+. Currently, the following objects are supported:
|
||||||
|
|
||||||
- **People**: `person`, `face`
|
- **People**: `person`, `face`, `baby`
|
||||||
- **Vehicles**: `car`, `motorcycle`, `bicycle`, `boat`, `school_bus`, `license_plate`
|
- **Vehicles**: `car`, `motorcycle`, `bicycle`, `boat`, `school_bus`, `garbage truck`, `license_plate`
|
||||||
- **Delivery Logos**: `amazon`, `usps`, `ups`, `fedex`, `dhl`, `an_post`, `purolator`, `postnl`, `nzpost`, `postnord`, `gls`, `dpd`, `canada_post`, `royal_mail`
|
- **Delivery Logos**: `amazon`, `usps`, `ups`, `fedex`, `dhl`, `an_post`, `purolator`, `postnl`, `nzpost`, `postnord`, `gls`, `dpd`, `canada_post`, `royal_mail`
|
||||||
- **Animals**: `dog`, `cat`, `deer`, `horse`, `bird`, `raccoon`, `fox`, `bear`, `cow`, `squirrel`, `goat`, `rabbit`, `skunk`, `kangaroo`
|
- **Animals**: `dog`, `cat`, `deer`, `horse`, `bird`, `raccoon`, `fox`, `bear`, `cow`, `squirrel`, `goat`, `rabbit`, `skunk`, `kangaroo`, `possum`, `rodent`
|
||||||
- **Other**: `package`, `waste_bin`, `bbq_grill`, `robot_lawnmower`, `umbrella`
|
- **Other**: `package`, `waste_bin`, `bbq_grill`, `robot_lawnmower`, `umbrella`, `baby_stroller`
|
||||||
|
|
||||||
Other object types available in the default Frigate model are not available. Additional object types will be added in future releases.
|
Other object types available in the default Frigate model are not available. Additional object types will be added in future releases.
|
||||||
|
|
||||||
@@ -77,9 +77,12 @@ Other object types available in the default Frigate model are not available. Add
|
|||||||
|
|
||||||
Candidate labels are also available for annotation. These labels don't have enough data to be included in the model yet, but using them will help add support sooner. You can enable these labels by editing the camera settings.
|
Candidate labels are also available for annotation. These labels don't have enough data to be included in the model yet, but using them will help add support sooner. You can enable these labels by editing the camera settings.
|
||||||
|
|
||||||
Where possible, these labels are mapped to existing labels during training. For example, any `baby` labels are mapped to `person` until support for new labels is added.
|
Where possible, these labels are mapped to existing labels during training. For example, any `duck` labels are mapped to `bird` until support for new labels is added.
|
||||||
|
|
||||||
The candidate labels are: `baby`, `bpost`, `badger`, `possum`, `rodent`, `chicken`, `groundhog`, `boar`, `hedgehog`, `tractor`, `golf cart`, `garbage truck`, `bus`, `sports ball`, `la_poste`, `lawnmower`, `heron`, `rickshaw`, `wombat`, `auspost`, `aramex`, `bobcat`, `mustelid`, `transoflex`, `airplane`, `drone`, `mountain_lion`, `crocodile`, `turkey`, `baby_stroller`, `monkey`, `coyote`, `porcupine`, `parcelforce`, `sheep`, `snake`, `helicopter`, `lizard`, `duck`, `hermes`, `cargus`, `fan_courier`, `sameday`
|
- **Vehicles**: `tractor`, `golf_cart`, `bus`, `airplane`, `helicopter`, `rickshaw`, `scooter`
|
||||||
|
- **Delivery Logos**: `bpost`, `auspost`, `aramex`, `transoflex`, `parcelforce`, `hermes`, `cargus`, `fan_courier`, `sameday`, `la_poste`
|
||||||
|
- **Animals**: `badger`, `chicken`, `duck`, `turkey`, `groundhog`, `boar`, `hedgehog`, `wombat`, `bobcat`, `mustelid`, `mountain_lion`, `crocodile`, `monkey`, `coyote`, `porcupine`, `sheep`, `snake`, `lizard`, `heron`, `elk`, `moose`, `pig`, `donkey`, `civet`
|
||||||
|
- **Other**: `sports_ball`, `drone`, `lawnmower`
|
||||||
|
|
||||||
Candidate labels are not available for automatic suggestions.
|
Candidate labels are not available for automatic suggestions.
|
||||||
|
|
||||||
|
|||||||
@@ -397,19 +397,11 @@ dmesg | grep -i -E "gpu|drm|reset|hang"
|
|||||||
|
|
||||||
Messages like `trying reset from guc_exec_queue_timedout_job` or similar GPU reset/hang messages indicate a driver or hardware issue. Ensure your kernel and GPU drivers (especially Intel) are up to date.
|
Messages like `trying reset from guc_exec_queue_timedout_job` or similar GPU reset/hang messages indicate a driver or hardware issue. Ensure your kernel and GPU drivers (especially Intel) are up to date.
|
||||||
|
|
||||||
#### Step 6: Verify hardware acceleration configuration
|
#### Step 6: Verify go2rtc stream configuration
|
||||||
|
|
||||||
An incorrect `hwaccel_args` preset can cause ffmpeg to fail silently or consume excessive CPU, starving the detector of resources.
|
|
||||||
|
|
||||||
- After upgrading Frigate, verify your preset matches your hardware (e.g., `preset-intel-qsv-h264` instead of the deprecated `preset-vaapi`).
|
|
||||||
- For h265 cameras, use the corresponding h265 preset (e.g., `preset-intel-qsv-h265`).
|
|
||||||
- Note that `hwaccel_args` are only relevant for the detect stream. Frigate does not decode the record stream.
|
|
||||||
|
|
||||||
#### Step 7: Verify go2rtc stream configuration
|
|
||||||
|
|
||||||
Ensure that the ffmpeg source names in your go2rtc configuration match the correct camera stream. A misconfigured stream name (e.g., copying a config from one camera to another without updating the stream reference) will cause the wrong stream to be used or the stream to fail entirely.
|
Ensure that the ffmpeg source names in your go2rtc configuration match the correct camera stream. A misconfigured stream name (e.g., copying a config from one camera to another without updating the stream reference) will cause the wrong stream to be used or the stream to fail entirely.
|
||||||
|
|
||||||
#### Step 8: Check system resources
|
#### Step 7: Check system resources
|
||||||
|
|
||||||
If none of the above apply, the issue may be a general resource constraint. Monitor the following on your host:
|
If none of the above apply, the issue may be a general resource constraint. Monitor the following on your host:
|
||||||
|
|
||||||
|
|||||||
+30
-22
@@ -3,6 +3,9 @@ import * as path from "node:path";
|
|||||||
import type { Config, PluginConfig } from "@docusaurus/types";
|
import type { Config, PluginConfig } from "@docusaurus/types";
|
||||||
import type * as OpenApiPlugin from "docusaurus-plugin-openapi-docs";
|
import type * as OpenApiPlugin from "docusaurus-plugin-openapi-docs";
|
||||||
|
|
||||||
|
// Bump when a new stable release ships
|
||||||
|
const STABLE_VERSION = "0.18";
|
||||||
|
|
||||||
const config: Config = {
|
const config: Config = {
|
||||||
title: "Frigate",
|
title: "Frigate",
|
||||||
tagline: "NVR With Realtime Object Detection for IP Cameras",
|
tagline: "NVR With Realtime Object Detection for IP Cameras",
|
||||||
@@ -23,17 +26,17 @@ const config: Config = {
|
|||||||
mermaid: true,
|
mermaid: true,
|
||||||
},
|
},
|
||||||
i18n: {
|
i18n: {
|
||||||
defaultLocale: 'en',
|
defaultLocale: "en",
|
||||||
locales: ['en'],
|
locales: ["en"],
|
||||||
localeConfigs: {
|
localeConfigs: {
|
||||||
en: {
|
en: {
|
||||||
label: 'English',
|
label: "English",
|
||||||
}
|
},
|
||||||
},
|
},
|
||||||
},
|
},
|
||||||
themeConfig: {
|
themeConfig: {
|
||||||
announcementBar: {
|
announcementBar: {
|
||||||
id: 'frigate_plus',
|
id: "frigate_plus",
|
||||||
content: `
|
content: `
|
||||||
<span style="margin-right: 8px; display: inline-block; animation: pulse 2s infinite;">🚀</span>
|
<span style="margin-right: 8px; display: inline-block; animation: pulse 2s infinite;">🚀</span>
|
||||||
Get more relevant and accurate detections with Frigate+ models.
|
Get more relevant and accurate detections with Frigate+ models.
|
||||||
@@ -45,8 +48,8 @@ const config: Config = {
|
|||||||
50% { transform: scale(1.1); }
|
50% { transform: scale(1.1); }
|
||||||
}
|
}
|
||||||
</style>`,
|
</style>`,
|
||||||
backgroundColor: '#005f73',
|
backgroundColor: "#005f73",
|
||||||
textColor: '#e0fbfc',
|
textColor: "#e0fbfc",
|
||||||
isCloseable: false,
|
isCloseable: false,
|
||||||
},
|
},
|
||||||
docs: {
|
docs: {
|
||||||
@@ -83,15 +86,15 @@ const config: Config = {
|
|||||||
},
|
},
|
||||||
},
|
},
|
||||||
prism: {
|
prism: {
|
||||||
magicComments:[
|
magicComments: [
|
||||||
{
|
{
|
||||||
className: 'theme-code-block-highlighted-line',
|
className: "theme-code-block-highlighted-line",
|
||||||
line: 'highlight-next-line',
|
line: "highlight-next-line",
|
||||||
block: {start: 'highlight-start', end: 'highlight-end'},
|
block: { start: "highlight-start", end: "highlight-end" },
|
||||||
},
|
},
|
||||||
{
|
{
|
||||||
className: 'code-block-error-line',
|
className: "code-block-error-line",
|
||||||
line: 'highlight-error-line',
|
line: "highlight-error-line",
|
||||||
},
|
},
|
||||||
],
|
],
|
||||||
additionalLanguages: ["bash", "json"],
|
additionalLanguages: ["bash", "json"],
|
||||||
@@ -131,6 +134,11 @@ const config: Config = {
|
|||||||
srcDark: "img/branding/logo-dark.svg",
|
srcDark: "img/branding/logo-dark.svg",
|
||||||
},
|
},
|
||||||
items: [
|
items: [
|
||||||
|
{
|
||||||
|
href: "https://github.com/blakeblackshear/frigate/releases",
|
||||||
|
label: `${STABLE_VERSION}`,
|
||||||
|
position: "left",
|
||||||
|
},
|
||||||
{
|
{
|
||||||
to: "/",
|
to: "/",
|
||||||
activeBasePath: "docs",
|
activeBasePath: "docs",
|
||||||
@@ -148,19 +156,19 @@ const config: Config = {
|
|||||||
position: "right",
|
position: "right",
|
||||||
},
|
},
|
||||||
{
|
{
|
||||||
type: 'localeDropdown',
|
type: "localeDropdown",
|
||||||
position: 'right',
|
position: "right",
|
||||||
dropdownItemsAfter: [
|
dropdownItemsAfter: [
|
||||||
{
|
{
|
||||||
label: '简体中文(社区翻译)',
|
label: "简体中文(社区翻译)",
|
||||||
href: 'https://docs.frigate-cn.video',
|
href: "https://docs.frigate-cn.video",
|
||||||
}
|
},
|
||||||
]
|
],
|
||||||
},
|
},
|
||||||
{
|
{
|
||||||
href: 'https://github.com/blakeblackshear/frigate',
|
href: "https://github.com/blakeblackshear/frigate",
|
||||||
label: 'GitHub',
|
label: "GitHub",
|
||||||
position: 'right',
|
position: "right",
|
||||||
},
|
},
|
||||||
],
|
],
|
||||||
},
|
},
|
||||||
|
|||||||
Vendored
+20
@@ -4073,6 +4073,16 @@ paths:
|
|||||||
- type: 'null'
|
- type: 'null'
|
||||||
default: 100
|
default: 100
|
||||||
title: Limit
|
title: Limit
|
||||||
|
- name: offset
|
||||||
|
in: query
|
||||||
|
required: false
|
||||||
|
schema:
|
||||||
|
anyOf:
|
||||||
|
- type: integer
|
||||||
|
minimum: 0
|
||||||
|
- type: 'null'
|
||||||
|
default: 0
|
||||||
|
title: Offset
|
||||||
- name: after
|
- name: after
|
||||||
in: query
|
in: query
|
||||||
required: false
|
required: false
|
||||||
@@ -4378,6 +4388,16 @@ paths:
|
|||||||
- type: 'null'
|
- type: 'null'
|
||||||
default: 50
|
default: 50
|
||||||
title: Limit
|
title: Limit
|
||||||
|
- name: offset
|
||||||
|
in: query
|
||||||
|
required: false
|
||||||
|
schema:
|
||||||
|
anyOf:
|
||||||
|
- type: integer
|
||||||
|
minimum: 0
|
||||||
|
- type: 'null'
|
||||||
|
default: 0
|
||||||
|
title: Offset
|
||||||
- name: cameras
|
- name: cameras
|
||||||
in: query
|
in: query
|
||||||
required: false
|
required: false
|
||||||
|
|||||||
+12
-17
@@ -127,23 +127,18 @@ def require_admin_by_default():
|
|||||||
if path.startswith(EXEMPT_PREFIXES):
|
if path.startswith(EXEMPT_PREFIXES):
|
||||||
return
|
return
|
||||||
|
|
||||||
# Dynamic camera path exemption:
|
# Camera routes enforce per-camera access via route-level dependencies
|
||||||
# Any path whose first segment matches a configured camera name should
|
# (e.g. require_camera_access). Match on the route template, not the raw
|
||||||
# bypass the global admin requirement. These endpoints enforce access
|
# path, so a camera named like another namespace (e.g. "faces") can't
|
||||||
# via route-level dependencies (e.g. require_camera_access) to ensure
|
# waive the admin check for that namespace's routes.
|
||||||
# per-camera authorization. This allows non-admin authenticated users
|
route = request.scope.get("route")
|
||||||
# (e.g. viewer role) to access camera-specific resources without
|
if (
|
||||||
# needing admin privileges.
|
route is not None
|
||||||
try:
|
and route.path.startswith("/{camera_name}")
|
||||||
if path.startswith("/"):
|
and request.path_params.get("camera_name")
|
||||||
first_segment = path.split("/", 2)[1]
|
in request.app.frigate_config.cameras
|
||||||
if (
|
):
|
||||||
first_segment
|
return
|
||||||
and first_segment in request.app.frigate_config.cameras
|
|
||||||
):
|
|
||||||
return
|
|
||||||
except Exception:
|
|
||||||
pass
|
|
||||||
|
|
||||||
# For all other paths, require admin role
|
# For all other paths, require admin role
|
||||||
# Internal port requests have admin role set automatically
|
# Internal port requests have admin role set automatically
|
||||||
|
|||||||
@@ -35,7 +35,11 @@ from frigate.config.camera.updater import (
|
|||||||
)
|
)
|
||||||
from frigate.config.env import substitute_frigate_vars
|
from frigate.config.env import substitute_frigate_vars
|
||||||
from frigate.models import User
|
from frigate.models import User
|
||||||
from frigate.util.builtin import clean_camera_user_pass, get_record_segment_time
|
from frigate.util.builtin import (
|
||||||
|
clean_camera_user_pass,
|
||||||
|
clear_orphaned_comments,
|
||||||
|
get_record_segment_time,
|
||||||
|
)
|
||||||
from frigate.util.camera_cleanup import cleanup_camera_db, cleanup_camera_files
|
from frigate.util.camera_cleanup import cleanup_camera_db, cleanup_camera_files
|
||||||
from frigate.util.config import find_config_file
|
from frigate.util.config import find_config_file
|
||||||
from frigate.util.image import run_ffmpeg_snapshot
|
from frigate.util.image import run_ffmpeg_snapshot
|
||||||
@@ -1201,6 +1205,7 @@ async def delete_camera(
|
|||||||
# Remove camera from config
|
# Remove camera from config
|
||||||
if "cameras" in data and camera_name in data["cameras"]:
|
if "cameras" in data and camera_name in data["cameras"]:
|
||||||
del data["cameras"][camera_name]
|
del data["cameras"][camera_name]
|
||||||
|
clear_orphaned_comments(data["cameras"], data, "cameras")
|
||||||
|
|
||||||
# Remove camera from auth roles
|
# Remove camera from auth roles
|
||||||
auth = data.get("auth", {})
|
auth = data.get("auth", {})
|
||||||
|
|||||||
@@ -14,6 +14,7 @@ class EventsQueryParams(BaseModel):
|
|||||||
zone: str | None = "all"
|
zone: str | None = "all"
|
||||||
zones: str | None = "all"
|
zones: str | None = "all"
|
||||||
limit: int | None = 100
|
limit: int | None = 100
|
||||||
|
offset: int | None = Field(0, ge=0)
|
||||||
after: float | None = None
|
after: float | None = None
|
||||||
before: float | None = None
|
before: float | None = None
|
||||||
time_range: str | None = DEFAULT_TIME_RANGE
|
time_range: str | None = DEFAULT_TIME_RANGE
|
||||||
@@ -55,6 +56,7 @@ class EventsSearchQueryParams(BaseModel):
|
|||||||
deprecated=True,
|
deprecated=True,
|
||||||
)
|
)
|
||||||
limit: int | None = 50
|
limit: int | None = 50
|
||||||
|
offset: int | None = Field(0, ge=0)
|
||||||
cameras: str | None = "all"
|
cameras: str | None = "all"
|
||||||
labels: str | None = "all"
|
labels: str | None = "all"
|
||||||
sub_labels: str | None = "all"
|
sub_labels: str | None = "all"
|
||||||
|
|||||||
+11
-2
@@ -129,6 +129,7 @@ def events(
|
|||||||
zones = zone
|
zones = zone
|
||||||
|
|
||||||
limit = params.limit
|
limit = params.limit
|
||||||
|
offset = params.offset
|
||||||
after = params.after
|
after = params.after
|
||||||
before = params.before
|
before = params.before
|
||||||
time_range = params.time_range
|
time_range = params.time_range
|
||||||
@@ -361,11 +362,15 @@ def events(
|
|||||||
else:
|
else:
|
||||||
order_by = Event.start_time.desc()
|
order_by = Event.start_time.desc()
|
||||||
|
|
||||||
|
# offset paging needs a stable order when scores or speeds tie
|
||||||
|
tiebreaker = [Event.id] if sort and sort.startswith(("score", "speed")) else []
|
||||||
|
|
||||||
events = (
|
events = (
|
||||||
Event.select(*selected_columns)
|
Event.select(*selected_columns)
|
||||||
.where(reduce(operator.and_, clauses))
|
.where(reduce(operator.and_, clauses))
|
||||||
.order_by(order_by)
|
.order_by(order_by, *tiebreaker)
|
||||||
.limit(limit)
|
.limit(limit)
|
||||||
|
.offset(offset)
|
||||||
.dicts()
|
.dicts()
|
||||||
.iterator()
|
.iterator()
|
||||||
)
|
)
|
||||||
@@ -518,6 +523,7 @@ def events_search(
|
|||||||
search_type = params.search_type
|
search_type = params.search_type
|
||||||
include_thumbnails = params.include_thumbnails
|
include_thumbnails = params.include_thumbnails
|
||||||
limit = params.limit
|
limit = params.limit
|
||||||
|
offset = params.offset
|
||||||
sort = params.sort
|
sort = params.sort
|
||||||
|
|
||||||
# Filters
|
# Filters
|
||||||
@@ -824,6 +830,9 @@ def events_search(
|
|||||||
if search_results:
|
if search_results:
|
||||||
events_query = events_query.where(Event.id << list(search_results.keys()))
|
events_query = events_query.where(Event.id << list(search_results.keys()))
|
||||||
|
|
||||||
|
# sorts below are stable, so this orders ties for offset paging
|
||||||
|
events_query = events_query.order_by(Event.id)
|
||||||
|
|
||||||
# Fetch events and process them in a single pass
|
# Fetch events and process them in a single pass
|
||||||
processed_events = []
|
processed_events = []
|
||||||
for event in events_query.dicts():
|
for event in events_query.dicts():
|
||||||
@@ -881,7 +890,7 @@ def events_search(
|
|||||||
processed_events.sort(key=lambda x: x["start_time"], reverse=True)
|
processed_events.sort(key=lambda x: x["start_time"], reverse=True)
|
||||||
|
|
||||||
# Limit the number of events returned
|
# Limit the number of events returned
|
||||||
processed_events = processed_events[:limit]
|
processed_events = processed_events[offset:][:limit]
|
||||||
|
|
||||||
return JSONResponse(content=processed_events)
|
return JSONResponse(content=processed_events)
|
||||||
|
|
||||||
|
|||||||
+20
-11
@@ -43,6 +43,22 @@ logger = logging.getLogger(__name__)
|
|||||||
router = APIRouter(tags=[Tags.review])
|
router = APIRouter(tags=[Tags.review])
|
||||||
|
|
||||||
|
|
||||||
|
def get_label_clause(label: str, include_audio: bool = True):
|
||||||
|
"""Build a clause matching a label within a review segment's data.
|
||||||
|
|
||||||
|
Verified objects are stored with a `-verified` suffix (eg. `person-verified`)
|
||||||
|
so that variant is matched as well.
|
||||||
|
"""
|
||||||
|
clause = (ReviewSegment.data["objects"].cast("text") % f'*"{label}"*') | (
|
||||||
|
ReviewSegment.data["objects"].cast("text") % f'*"{label}-verified"*'
|
||||||
|
)
|
||||||
|
|
||||||
|
if include_audio:
|
||||||
|
clause |= ReviewSegment.data["audio"].cast("text") % f'*"{label}"*'
|
||||||
|
|
||||||
|
return clause
|
||||||
|
|
||||||
|
|
||||||
@router.get(
|
@router.get(
|
||||||
"/review",
|
"/review",
|
||||||
response_model=list[ReviewSegmentResponse],
|
response_model=list[ReviewSegmentResponse],
|
||||||
@@ -92,10 +108,7 @@ async def review(
|
|||||||
filtered_labels = labels.split(",")
|
filtered_labels = labels.split(",")
|
||||||
|
|
||||||
for label in filtered_labels:
|
for label in filtered_labels:
|
||||||
label_clauses.append(
|
label_clauses.append(get_label_clause(label))
|
||||||
(ReviewSegment.data["objects"].cast("text") % f'*"{label}"*')
|
|
||||||
| (ReviewSegment.data["audio"].cast("text") % f'*"{label}"*')
|
|
||||||
)
|
|
||||||
clauses.append(reduce(operator.or_, label_clauses))
|
clauses.append(reduce(operator.or_, label_clauses))
|
||||||
|
|
||||||
if zones != "all":
|
if zones != "all":
|
||||||
@@ -236,10 +249,7 @@ async def review_summary(
|
|||||||
filtered_labels = labels.split(",")
|
filtered_labels = labels.split(",")
|
||||||
|
|
||||||
for label in filtered_labels:
|
for label in filtered_labels:
|
||||||
label_clauses.append(
|
label_clauses.append(get_label_clause(label))
|
||||||
(ReviewSegment.data["objects"].cast("text") % f'*"{label}"*')
|
|
||||||
| (ReviewSegment.data["audio"].cast("text") % f'*"{label}"*')
|
|
||||||
)
|
|
||||||
clauses.append(reduce(operator.or_, label_clauses))
|
clauses.append(reduce(operator.or_, label_clauses))
|
||||||
if zones != "all":
|
if zones != "all":
|
||||||
# use matching so segments with multiple zones
|
# use matching so segments with multiple zones
|
||||||
@@ -337,9 +347,8 @@ async def review_summary(
|
|||||||
filtered_labels = labels.split(",")
|
filtered_labels = labels.split(",")
|
||||||
|
|
||||||
for label in filtered_labels:
|
for label in filtered_labels:
|
||||||
label_clauses.append(
|
label_clauses.append(get_label_clause(label, include_audio=False))
|
||||||
ReviewSegment.data["objects"].cast("text") % f'*"{label}"*'
|
|
||||||
)
|
|
||||||
clauses.append(reduce(operator.or_, label_clauses))
|
clauses.append(reduce(operator.or_, label_clauses))
|
||||||
|
|
||||||
# Find the time range of available data
|
# Find the time range of available data
|
||||||
|
|||||||
@@ -103,12 +103,13 @@ class CameraActivityManager:
|
|||||||
all_objects: list[dict[str, Any]] = []
|
all_objects: list[dict[str, Any]] = []
|
||||||
|
|
||||||
for camera in new_activity.keys():
|
for camera in new_activity.keys():
|
||||||
if camera not in self.config.cameras:
|
camera_config = self.config.cameras.get(camera)
|
||||||
|
if camera_config is None:
|
||||||
continue
|
continue
|
||||||
|
|
||||||
# handle cameras that were added dynamically
|
# handle cameras that were added dynamically
|
||||||
if camera not in self.camera_all_object_counts:
|
if camera not in self.camera_all_object_counts:
|
||||||
self.__init_camera(self.config.cameras[camera])
|
self.__init_camera(camera_config)
|
||||||
|
|
||||||
new_objects = new_activity[camera].get("objects", [])
|
new_objects = new_activity[camera].get("objects", [])
|
||||||
all_objects.extend(new_objects)
|
all_objects.extend(new_objects)
|
||||||
@@ -233,12 +234,13 @@ class AudioActivityManager:
|
|||||||
now = datetime.datetime.now().timestamp()
|
now = datetime.datetime.now().timestamp()
|
||||||
|
|
||||||
for camera in new_activity.keys():
|
for camera in new_activity.keys():
|
||||||
if camera not in self.config.cameras:
|
camera_config = self.config.cameras.get(camera)
|
||||||
|
if camera_config is None:
|
||||||
continue
|
continue
|
||||||
|
|
||||||
# handle cameras that were added dynamically
|
# handle cameras that were added dynamically
|
||||||
if camera not in self.current_audio_detections:
|
if camera not in self.current_audio_detections:
|
||||||
self.__init_camera(self.config.cameras[camera])
|
self.__init_camera(camera_config)
|
||||||
|
|
||||||
new_detections = new_activity[camera].get("detections", [])
|
new_detections = new_activity[camera].get("detections", [])
|
||||||
if self.compare_audio_activity(camera, new_detections, now):
|
if self.compare_audio_activity(camera, new_detections, now):
|
||||||
|
|||||||
+76
-15
@@ -1,8 +1,10 @@
|
|||||||
import logging
|
import logging
|
||||||
import sqlite3
|
import sqlite3
|
||||||
|
import threading
|
||||||
from typing import Any
|
from typing import Any
|
||||||
|
|
||||||
import regex
|
import regex
|
||||||
|
from peewee import DatabaseError
|
||||||
from playhouse.sqliteq import SqliteQueueDatabase
|
from playhouse.sqliteq import SqliteQueueDatabase
|
||||||
|
|
||||||
logger = logging.getLogger(__name__)
|
logger = logging.getLogger(__name__)
|
||||||
@@ -17,6 +19,7 @@ class SqliteVecQueueDatabase(SqliteQueueDatabase):
|
|||||||
self.load_vec_extension: bool = load_vec_extension
|
self.load_vec_extension: bool = load_vec_extension
|
||||||
# no extension necessary, sqlite will load correctly for each platform
|
# no extension necessary, sqlite will load correctly for each platform
|
||||||
self.sqlite_vec_path = "/usr/local/lib/vec0"
|
self.sqlite_vec_path = "/usr/local/lib/vec0"
|
||||||
|
self.upsert_lock = threading.Lock()
|
||||||
super().__init__(*args, **kwargs)
|
super().__init__(*args, **kwargs)
|
||||||
|
|
||||||
def _connect(self, *args: Any, **kwargs: Any) -> sqlite3.Connection:
|
def _connect(self, *args: Any, **kwargs: Any) -> sqlite3.Connection:
|
||||||
@@ -53,6 +56,22 @@ class SqliteVecQueueDatabase(SqliteQueueDatabase):
|
|||||||
|
|
||||||
conn.create_function("REGEXP", 2, regexp)
|
conn.create_function("REGEXP", 2, regexp)
|
||||||
|
|
||||||
|
def execute_write(self, sql: str, params: Any = None) -> None:
|
||||||
|
"""Run a write and wait for it, so that failures are raised here.
|
||||||
|
|
||||||
|
SqliteQueueDatabase hands non-SELECT statements to a writer thread and
|
||||||
|
stores any exception on the cursor it returns, so callers that ignore
|
||||||
|
that cursor never learn the write failed.
|
||||||
|
"""
|
||||||
|
self.execute_sql(sql, params).fetchall()
|
||||||
|
|
||||||
|
def _table_exists(self, table: str) -> bool:
|
||||||
|
cursor = self.execute_sql(
|
||||||
|
"SELECT name FROM sqlite_master WHERE type = 'table' AND name = ?",
|
||||||
|
(table,),
|
||||||
|
)
|
||||||
|
return cursor.fetchone() is not None
|
||||||
|
|
||||||
def _delete_embeddings(self, table: str, event_ids: list[str]) -> None:
|
def _delete_embeddings(self, table: str, event_ids: list[str]) -> None:
|
||||||
"""Delete embeddings for the given events, if the table exists.
|
"""Delete embeddings for the given events, if the table exists.
|
||||||
|
|
||||||
@@ -63,17 +82,17 @@ class SqliteVecQueueDatabase(SqliteQueueDatabase):
|
|||||||
return
|
return
|
||||||
|
|
||||||
# the embeddings tables are only created once semantic search has run
|
# the embeddings tables are only created once semantic search has run
|
||||||
cursor = self.execute_sql(
|
if not self._table_exists(table):
|
||||||
"SELECT name FROM sqlite_master WHERE type = 'table' AND name = ?",
|
|
||||||
(table,),
|
|
||||||
)
|
|
||||||
|
|
||||||
if cursor.fetchone() is None:
|
|
||||||
logger.debug("Skipping %s cleanup, table does not exist", table)
|
logger.debug("Skipping %s cleanup, table does not exist", table)
|
||||||
return
|
return
|
||||||
|
|
||||||
ids = ",".join(["?" for _ in event_ids])
|
ids = ",".join(["?" for _ in event_ids])
|
||||||
self.execute_sql(f"DELETE FROM {table} WHERE id IN ({ids})", event_ids)
|
|
||||||
|
# callers treat cleanup as best effort, so log rather than propagate
|
||||||
|
try:
|
||||||
|
self.execute_write(f"DELETE FROM {table} WHERE id IN ({ids})", event_ids)
|
||||||
|
except DatabaseError:
|
||||||
|
logger.exception("Failed to delete embeddings from %s", table)
|
||||||
|
|
||||||
def delete_embeddings_thumbnail(self, event_ids: list[str]) -> None:
|
def delete_embeddings_thumbnail(self, event_ids: list[str]) -> None:
|
||||||
self._delete_embeddings("vec_thumbnails", event_ids)
|
self._delete_embeddings("vec_thumbnails", event_ids)
|
||||||
@@ -81,25 +100,67 @@ class SqliteVecQueueDatabase(SqliteQueueDatabase):
|
|||||||
def delete_embeddings_description(self, event_ids: list[str]) -> None:
|
def delete_embeddings_description(self, event_ids: list[str]) -> None:
|
||||||
self._delete_embeddings("vec_descriptions", event_ids)
|
self._delete_embeddings("vec_descriptions", event_ids)
|
||||||
|
|
||||||
|
def _restore_vec_info_table(self, table: str) -> None:
|
||||||
|
"""Recreate the _info shadow table a legacy vec0 table is missing.
|
||||||
|
|
||||||
|
sqlite-vec added _info in 0.1.6 and drops it unconditionally when a
|
||||||
|
table is destroyed, so tables written by Frigate 0.17 and earlier fail
|
||||||
|
to drop. An empty stub is enough, and leaving it unseeded keeps the
|
||||||
|
table reading as pre-0.1.10 if the drop does not follow.
|
||||||
|
"""
|
||||||
|
if not self._table_exists(table) or self._table_exists(f"{table}_info"):
|
||||||
|
return
|
||||||
|
|
||||||
|
logger.debug("Restoring the %s_info shadow table before dropping", table)
|
||||||
|
self.execute_write(
|
||||||
|
f'CREATE TABLE "{table}_info" (key TEXT PRIMARY KEY, value ANY)'
|
||||||
|
)
|
||||||
|
|
||||||
def drop_embeddings_tables(self) -> None:
|
def drop_embeddings_tables(self) -> None:
|
||||||
self.execute_sql("""
|
for table in ("vec_descriptions", "vec_thumbnails"):
|
||||||
DROP TABLE vec_descriptions;
|
self._restore_vec_info_table(table)
|
||||||
""")
|
self.execute_write(f"DROP TABLE IF EXISTS {table}")
|
||||||
self.execute_sql("""
|
|
||||||
DROP TABLE vec_thumbnails;
|
|
||||||
""")
|
|
||||||
|
|
||||||
def create_embeddings_tables(self) -> None:
|
def create_embeddings_tables(self) -> None:
|
||||||
"""Create vec0 virtual table for embeddings"""
|
"""Create vec0 virtual table for embeddings"""
|
||||||
self.execute_sql("""
|
self.execute_write("""
|
||||||
CREATE VIRTUAL TABLE IF NOT EXISTS vec_thumbnails USING vec0(
|
CREATE VIRTUAL TABLE IF NOT EXISTS vec_thumbnails USING vec0(
|
||||||
id TEXT PRIMARY KEY,
|
id TEXT PRIMARY KEY,
|
||||||
thumbnail_embedding FLOAT[768] distance_metric=cosine
|
thumbnail_embedding FLOAT[768] distance_metric=cosine
|
||||||
);
|
);
|
||||||
""")
|
""")
|
||||||
self.execute_sql("""
|
self.execute_write("""
|
||||||
CREATE VIRTUAL TABLE IF NOT EXISTS vec_descriptions USING vec0(
|
CREATE VIRTUAL TABLE IF NOT EXISTS vec_descriptions USING vec0(
|
||||||
id TEXT PRIMARY KEY,
|
id TEXT PRIMARY KEY,
|
||||||
description_embedding FLOAT[768] distance_metric=cosine
|
description_embedding FLOAT[768] distance_metric=cosine
|
||||||
);
|
);
|
||||||
""")
|
""")
|
||||||
|
|
||||||
|
def upsert_embeddings(
|
||||||
|
self, table: str, column: str, embeddings: dict[str, bytes]
|
||||||
|
) -> None:
|
||||||
|
"""Write embeddings for the given event ids, replacing any that exist.
|
||||||
|
|
||||||
|
vec0 implements neither REPLACE nor UPSERT, so rows that are already
|
||||||
|
there have to be deleted first.
|
||||||
|
"""
|
||||||
|
if not embeddings:
|
||||||
|
return
|
||||||
|
|
||||||
|
event_ids = list(embeddings.keys())
|
||||||
|
ids = ",".join(["?" for _ in event_ids])
|
||||||
|
params: list[Any] = []
|
||||||
|
|
||||||
|
for event_id in event_ids:
|
||||||
|
params.extend((event_id, embeddings[event_id]))
|
||||||
|
|
||||||
|
values = ", ".join(["(?, ?)"] * len(event_ids))
|
||||||
|
|
||||||
|
# reindexing and live embedding run on separate threads, and each write
|
||||||
|
# is queued separately, so the delete and the insert have to be held
|
||||||
|
# together or an interleaved pair fails on the vec0 primary key
|
||||||
|
with self.upsert_lock:
|
||||||
|
self.execute_write(f"DELETE FROM {table} WHERE id IN ({ids})", event_ids)
|
||||||
|
self.execute_write(
|
||||||
|
f"INSERT INTO {table}(id, {column}) VALUES {values}", params
|
||||||
|
)
|
||||||
|
|||||||
@@ -199,15 +199,20 @@ class CudaGraphRunner(BaseModelRunner):
|
|||||||
EnrichmentModelTypeEnum.yolov9_license_plate.value,
|
EnrichmentModelTypeEnum.yolov9_license_plate.value,
|
||||||
]
|
]
|
||||||
|
|
||||||
|
# ORT performs two regular runs before it starts capturing, but on some
|
||||||
|
# driver / cuDNN combinations the arena still has to extend on the run that
|
||||||
|
# captures, and cudaMalloc is not allowed during capture. Running with
|
||||||
|
# capture disabled first keeps those allocations outside of the capture.
|
||||||
|
GRAPH_FREE_WARMUP_RUNS = 2
|
||||||
|
|
||||||
def __init__(self, session: ort.InferenceSession, cuda_device_id: int):
|
def __init__(self, session: ort.InferenceSession, cuda_device_id: int):
|
||||||
self._session = session
|
self._session = session
|
||||||
self._cuda_device_id = cuda_device_id
|
self._cuda_device_id = cuda_device_id
|
||||||
self._captured = False
|
self._prepared = False
|
||||||
self._io_binding: ort.IOBinding | None = None
|
self._io_binding: ort.IOBinding | None = None
|
||||||
self._input_name: str | None = None
|
self._input_name: str | None = None
|
||||||
self._output_names: list[str] | None = None
|
self._output_names: list[str] | None = None
|
||||||
self._input_ortvalue: ort.OrtValue | None = None
|
self._input_ortvalue: ort.OrtValue | None = None
|
||||||
self._output_ortvalues: ort.OrtValue | None = None
|
|
||||||
|
|
||||||
def get_input_names(self) -> list[str]:
|
def get_input_names(self) -> list[str]:
|
||||||
"""Get input names for the model."""
|
"""Get input names for the model."""
|
||||||
@@ -217,35 +222,41 @@ class CudaGraphRunner(BaseModelRunner):
|
|||||||
"""Get the input width of the model."""
|
"""Get the input width of the model."""
|
||||||
return self._session.get_inputs()[0].shape[3]
|
return self._session.get_inputs()[0].shape[3]
|
||||||
|
|
||||||
|
def _prepare(self, input_name: str, tensor_input: np.ndarray) -> None:
|
||||||
|
"""Bind CUDA buffers and warm the session up with capture disabled."""
|
||||||
|
self._io_binding = self._session.io_binding()
|
||||||
|
self._input_name = input_name
|
||||||
|
self._output_names = [o.name for o in self._session.get_outputs()]
|
||||||
|
|
||||||
|
self._input_ortvalue = ort.OrtValue.ortvalue_from_numpy(
|
||||||
|
tensor_input, "cuda", self._cuda_device_id
|
||||||
|
)
|
||||||
|
self._io_binding.bind_ortvalue_input(self._input_name, self._input_ortvalue)
|
||||||
|
|
||||||
|
for name in self._output_names:
|
||||||
|
# Bind outputs to CUDA and allow ORT to allocate appropriately
|
||||||
|
self._io_binding.bind_output(name, "cuda", self._cuda_device_id)
|
||||||
|
|
||||||
|
# gpu_graph_id -1 disables capture and replay for the run
|
||||||
|
warmup_options = ort.RunOptions()
|
||||||
|
warmup_options.add_run_config_entry("gpu_graph_id", "-1")
|
||||||
|
|
||||||
|
for _ in range(self.GRAPH_FREE_WARMUP_RUNS):
|
||||||
|
self._session.run_with_iobinding(self._io_binding, warmup_options)
|
||||||
|
|
||||||
|
self._prepared = True
|
||||||
|
|
||||||
def run(self, input: dict[str, Any]):
|
def run(self, input: dict[str, Any]):
|
||||||
# Extract the single tensor input (assuming one input)
|
# Extract the single tensor input (assuming one input)
|
||||||
input_name = list(input.keys())[0]
|
input_name = list(input.keys())[0]
|
||||||
tensor_input = input[input_name]
|
tensor_input = np.ascontiguousarray(input[input_name])
|
||||||
tensor_input = np.ascontiguousarray(tensor_input)
|
|
||||||
|
|
||||||
if not self._captured:
|
if not self._prepared:
|
||||||
# Prepare IOBinding with CUDA buffers and let ORT allocate outputs on device
|
self._prepare(input_name, tensor_input)
|
||||||
self._io_binding = self._session.io_binding()
|
else:
|
||||||
self._input_name = input_name
|
# Replay using updated input
|
||||||
self._output_names = [o.name for o in self._session.get_outputs()]
|
self._input_ortvalue.update_inplace(tensor_input)
|
||||||
|
|
||||||
self._input_ortvalue = ort.OrtValue.ortvalue_from_numpy(
|
|
||||||
tensor_input, "cuda", self._cuda_device_id
|
|
||||||
)
|
|
||||||
self._io_binding.bind_ortvalue_input(self._input_name, self._input_ortvalue)
|
|
||||||
|
|
||||||
for name in self._output_names:
|
|
||||||
# Bind outputs to CUDA and allow ORT to allocate appropriately
|
|
||||||
self._io_binding.bind_output(name, "cuda", self._cuda_device_id)
|
|
||||||
|
|
||||||
# First IOBinding run to allocate, execute, and capture CUDA Graph
|
|
||||||
ro = ort.RunOptions()
|
|
||||||
self._session.run_with_iobinding(self._io_binding, ro)
|
|
||||||
self._captured = True
|
|
||||||
return self._io_binding.copy_outputs_to_cpu()
|
|
||||||
|
|
||||||
# Replay using updated input, copy results to CPU
|
|
||||||
self._input_ortvalue.update_inplace(tensor_input)
|
|
||||||
ro = ort.RunOptions()
|
ro = ort.RunOptions()
|
||||||
self._session.run_with_iobinding(self._io_binding, ro)
|
self._session.run_with_iobinding(self._io_binding, ro)
|
||||||
return self._io_binding.copy_outputs_to_cpu()
|
return self._io_binding.copy_outputs_to_cpu()
|
||||||
|
|||||||
@@ -6,9 +6,10 @@ import logging
|
|||||||
import os
|
import os
|
||||||
import threading
|
import threading
|
||||||
import time
|
import time
|
||||||
|
from typing import Any
|
||||||
|
|
||||||
import numpy as np
|
import numpy as np
|
||||||
from peewee import DoesNotExist, IntegrityError
|
from peewee import DatabaseError, DoesNotExist, IntegrityError
|
||||||
from PIL import Image
|
from PIL import Image
|
||||||
from playhouse.shortcuts import model_to_dict
|
from playhouse.shortcuts import model_to_dict
|
||||||
|
|
||||||
@@ -207,12 +208,10 @@ class Embeddings:
|
|||||||
embedding = self.vision_embedding([thumbnail])[0]
|
embedding = self.vision_embedding([thumbnail])[0]
|
||||||
|
|
||||||
if upsert:
|
if upsert:
|
||||||
self.db.execute_sql(
|
self.db.upsert_embeddings(
|
||||||
"""
|
"vec_thumbnails",
|
||||||
INSERT OR REPLACE INTO vec_thumbnails(id, thumbnail_embedding)
|
"thumbnail_embedding",
|
||||||
VALUES(?, ?)
|
{event_id: serialize(embedding)},
|
||||||
""",
|
|
||||||
(event_id, serialize(embedding)),
|
|
||||||
)
|
)
|
||||||
|
|
||||||
self.image_inference_speed.update(datetime.datetime.now().timestamp() - start)
|
self.image_inference_speed.update(datetime.datetime.now().timestamp() - start)
|
||||||
@@ -251,19 +250,12 @@ class Embeddings:
|
|||||||
embeddings = self.vision_embedding(valid_thumbs)
|
embeddings = self.vision_embedding(valid_thumbs)
|
||||||
|
|
||||||
if upsert:
|
if upsert:
|
||||||
items = []
|
items = {}
|
||||||
for i in range(len(valid_ids)):
|
for i in range(len(valid_ids)):
|
||||||
items.append(valid_ids[i])
|
items[valid_ids[i]] = serialize(embeddings[i])
|
||||||
items.append(serialize(embeddings[i]))
|
|
||||||
self.image_eps.update()
|
self.image_eps.update()
|
||||||
|
|
||||||
self.db.execute_sql(
|
self.db.upsert_embeddings("vec_thumbnails", "thumbnail_embedding", items)
|
||||||
"""
|
|
||||||
INSERT OR REPLACE INTO vec_thumbnails(id, thumbnail_embedding)
|
|
||||||
VALUES {}
|
|
||||||
""".format(", ".join(["(?, ?)"] * len(valid_ids))),
|
|
||||||
items,
|
|
||||||
)
|
|
||||||
|
|
||||||
duration = datetime.datetime.now().timestamp() - start
|
duration = datetime.datetime.now().timestamp() - start
|
||||||
self.image_inference_speed.update(duration / len(valid_ids))
|
self.image_inference_speed.update(duration / len(valid_ids))
|
||||||
@@ -277,12 +269,10 @@ class Embeddings:
|
|||||||
embedding = self.text_embedding([description])[0]
|
embedding = self.text_embedding([description])[0]
|
||||||
|
|
||||||
if upsert:
|
if upsert:
|
||||||
self.db.execute_sql(
|
self.db.upsert_embeddings(
|
||||||
"""
|
"vec_descriptions",
|
||||||
INSERT OR REPLACE INTO vec_descriptions(id, description_embedding)
|
"description_embedding",
|
||||||
VALUES(?, ?)
|
{event_id: serialize(embedding)},
|
||||||
""",
|
|
||||||
(event_id, serialize(embedding)),
|
|
||||||
)
|
)
|
||||||
|
|
||||||
self.text_inference_speed.update(datetime.datetime.now().timestamp() - start)
|
self.text_inference_speed.update(datetime.datetime.now().timestamp() - start)
|
||||||
@@ -302,19 +292,14 @@ class Embeddings:
|
|||||||
|
|
||||||
if upsert:
|
if upsert:
|
||||||
ids = list(event_descriptions.keys())
|
ids = list(event_descriptions.keys())
|
||||||
items = []
|
items = {}
|
||||||
|
|
||||||
for i in range(len(ids)):
|
for i in range(len(ids)):
|
||||||
items.append(ids[i])
|
items[ids[i]] = serialize(embeddings[i])
|
||||||
items.append(serialize(embeddings[i]))
|
|
||||||
self.text_eps.update()
|
self.text_eps.update()
|
||||||
|
|
||||||
self.db.execute_sql(
|
self.db.upsert_embeddings(
|
||||||
"""
|
"vec_descriptions", "description_embedding", items
|
||||||
INSERT OR REPLACE INTO vec_descriptions(id, description_embedding)
|
|
||||||
VALUES {}
|
|
||||||
""".format(", ".join(["(?, ?)"] * len(ids))),
|
|
||||||
items,
|
|
||||||
)
|
)
|
||||||
|
|
||||||
self.text_inference_speed.update(datetime.datetime.now().timestamp() - start)
|
self.text_inference_speed.update(datetime.datetime.now().timestamp() - start)
|
||||||
@@ -322,6 +307,17 @@ class Embeddings:
|
|||||||
return embeddings
|
return embeddings
|
||||||
|
|
||||||
def reindex(self) -> None:
|
def reindex(self) -> None:
|
||||||
|
"""Rebuild every tracked object embedding from scratch."""
|
||||||
|
totals: dict[str, Any] = {"status": "indexing"}
|
||||||
|
|
||||||
|
try:
|
||||||
|
self._reindex(totals)
|
||||||
|
except DatabaseError:
|
||||||
|
logger.exception("Unable to reindex tracked object embeddings")
|
||||||
|
totals["status"] = "failed"
|
||||||
|
self.requestor.send_data(UPDATE_EMBEDDINGS_REINDEX_PROGRESS, totals)
|
||||||
|
|
||||||
|
def _reindex(self, totals: dict[str, Any]) -> None:
|
||||||
logger.info("Indexing tracked object embeddings...")
|
logger.info("Indexing tracked object embeddings...")
|
||||||
|
|
||||||
self.db.drop_embeddings_tables()
|
self.db.drop_embeddings_tables()
|
||||||
@@ -346,14 +342,18 @@ class Embeddings:
|
|||||||
batch_size = 32
|
batch_size = 32
|
||||||
current_page = 1
|
current_page = 1
|
||||||
|
|
||||||
totals = {
|
totals.update(
|
||||||
"thumbnails": 0,
|
{
|
||||||
"descriptions": 0,
|
"thumbnails": 0,
|
||||||
"processed_objects": total_events - 1 if total_events < batch_size else 0,
|
"descriptions": 0,
|
||||||
"total_objects": total_events,
|
"processed_objects": total_events - 1
|
||||||
"time_remaining": 0 if total_events < batch_size else -1,
|
if total_events < batch_size
|
||||||
"status": "indexing",
|
else 0,
|
||||||
}
|
"total_objects": total_events,
|
||||||
|
"time_remaining": 0 if total_events < batch_size else -1,
|
||||||
|
"status": "indexing",
|
||||||
|
}
|
||||||
|
)
|
||||||
|
|
||||||
self.requestor.send_data(UPDATE_EMBEDDINGS_REINDEX_PROGRESS, totals)
|
self.requestor.send_data(UPDATE_EMBEDDINGS_REINDEX_PROGRESS, totals)
|
||||||
|
|
||||||
|
|||||||
@@ -365,6 +365,7 @@ class EventCleanup(threading.Thread):
|
|||||||
chunk = ids_to_delete[i : i + CHUNK_SIZE]
|
chunk = ids_to_delete[i : i + CHUNK_SIZE]
|
||||||
logger.debug(f"Deleting {len(chunk)} events from the database")
|
logger.debug(f"Deleting {len(chunk)} events from the database")
|
||||||
Event.delete().where(Event.id << chunk).execute()
|
Event.delete().where(Event.id << chunk).execute()
|
||||||
|
Timeline.delete().where(Timeline.source_id << chunk).execute()
|
||||||
|
|
||||||
# embeddings are always cleaned up, even when semantic search
|
# embeddings are always cleaned up, even when semantic search
|
||||||
# is disabled, so that they don't outlive their events
|
# is disabled, so that they don't outlive their events
|
||||||
|
|||||||
@@ -121,8 +121,8 @@ PRESETS_HW_ACCEL_SCALE = {
|
|||||||
"preset-rpi-64-h264": "-r {0} -vf fps={0},scale={1}:{2}",
|
"preset-rpi-64-h264": "-r {0} -vf fps={0},scale={1}:{2}",
|
||||||
"preset-rpi-64-h265": "-r {0} -vf fps={0},scale={1}:{2}",
|
"preset-rpi-64-h265": "-r {0} -vf fps={0},scale={1}:{2}",
|
||||||
FFMPEG_HWACCEL_VAAPI: "-r {0} -vf fps={0},scale_vaapi=w={1}:h={2},hwdownload,format=nv12",
|
FFMPEG_HWACCEL_VAAPI: "-r {0} -vf fps={0},scale_vaapi=w={1}:h={2},hwdownload,format=nv12",
|
||||||
"preset-intel-qsv-h264": "-r {0} -vf vpp_qsv=w={1}:h={2}:format=nv12,hwdownload,format=nv12,fps={0},format=yuv420p",
|
"preset-intel-qsv-h264": "-r {0} -vf fps={0},vpp_qsv=w={1}:h={2}:format=nv12,hwdownload,format=nv12,format=yuv420p",
|
||||||
"preset-intel-qsv-h265": "-r {0} -vf vpp_qsv=w={1}:h={2}:format=nv12,hwdownload,format=nv12,fps={0},format=yuv420p",
|
"preset-intel-qsv-h265": "-r {0} -vf fps={0},vpp_qsv=w={1}:h={2}:format=nv12,hwdownload,format=nv12,format=yuv420p",
|
||||||
FFMPEG_HWACCEL_NVIDIA: "-r {0} -vf fps={0},scale_cuda=w={1}:h={2},hwdownload,format=nv12",
|
FFMPEG_HWACCEL_NVIDIA: "-r {0} -vf fps={0},scale_cuda=w={1}:h={2},hwdownload,format=nv12",
|
||||||
"preset-jetson-h264": "-r {0}", # scaled in decoder
|
"preset-jetson-h264": "-r {0}", # scaled in decoder
|
||||||
"preset-jetson-h265": "-r {0}", # scaled in decoder
|
"preset-jetson-h265": "-r {0}", # scaled in decoder
|
||||||
|
|||||||
@@ -388,6 +388,22 @@ class RecordingMaintainer(threading.Thread):
|
|||||||
RecordingsDataTypeEnum.valid.value,
|
RecordingsDataTypeEnum.valid.value,
|
||||||
)
|
)
|
||||||
|
|
||||||
|
# assume that empty means the relevant recording info has not been received yet
|
||||||
|
camera_info = self.object_recordings_info[camera]
|
||||||
|
most_recently_processed_frame_time = (
|
||||||
|
camera_info[-1][0] if len(camera_info) > 0 else 0
|
||||||
|
)
|
||||||
|
|
||||||
|
# ensure delayed segment info does not lead to lost segments, every
|
||||||
|
# retention decision below depends on complete stats for the segment
|
||||||
|
if (
|
||||||
|
datetime.datetime.fromtimestamp(
|
||||||
|
most_recently_processed_frame_time
|
||||||
|
).astimezone(datetime.UTC)
|
||||||
|
< end_time
|
||||||
|
):
|
||||||
|
return None
|
||||||
|
|
||||||
record_config = self.config.cameras[camera].record
|
record_config = self.config.cameras[camera].record
|
||||||
segment_stats: SegmentInfo | None = None
|
segment_stats: SegmentInfo | None = None
|
||||||
highest = None
|
highest = None
|
||||||
@@ -401,37 +417,22 @@ class RecordingMaintainer(threading.Thread):
|
|||||||
# we should first just check if this segment matches that
|
# we should first just check if this segment matches that
|
||||||
# and avoid any DB calls
|
# and avoid any DB calls
|
||||||
if highest is not None:
|
if highest is not None:
|
||||||
# assume that empty means the relevant recording info has not been received yet
|
record_mode = (
|
||||||
camera_info = self.object_recordings_info[camera]
|
RetainModeEnum.all if highest == "continuous" else RetainModeEnum.motion
|
||||||
most_recently_processed_frame_time = (
|
|
||||||
camera_info[-1][0] if len(camera_info) > 0 else 0
|
|
||||||
)
|
)
|
||||||
|
segment_stats = self.segment_stats(camera, start_time, end_time)
|
||||||
|
|
||||||
# ensure delayed segment info does not lead to lost segments
|
# Here we only check if we should move the segment based on non-object recording retention
|
||||||
if (
|
# we will always want to check for overlapping review items below before dropping the segment
|
||||||
datetime.datetime.fromtimestamp(
|
if not segment_stats.should_discard_segment(record_mode):
|
||||||
most_recently_processed_frame_time
|
return await self.move_segment(
|
||||||
).astimezone(datetime.UTC)
|
camera,
|
||||||
>= end_time
|
start_time,
|
||||||
):
|
end_time,
|
||||||
record_mode = (
|
duration,
|
||||||
RetainModeEnum.all
|
cache_path,
|
||||||
if highest == "continuous"
|
segment_stats,
|
||||||
else RetainModeEnum.motion
|
|
||||||
)
|
)
|
||||||
segment_stats = self.segment_stats(camera, start_time, end_time)
|
|
||||||
|
|
||||||
# Here we only check if we should move the segment based on non-object recording retention
|
|
||||||
# we will always want to check for overlapping review items below before dropping the segment
|
|
||||||
if not segment_stats.should_discard_segment(record_mode):
|
|
||||||
return await self.move_segment(
|
|
||||||
camera,
|
|
||||||
start_time,
|
|
||||||
end_time,
|
|
||||||
duration,
|
|
||||||
cache_path,
|
|
||||||
segment_stats,
|
|
||||||
)
|
|
||||||
|
|
||||||
# we fell through the continuous / motion check, so we need to check the review items
|
# we fell through the continuous / motion check, so we need to check the review items
|
||||||
# if the cached segment overlaps with the review items:
|
# if the cached segment overlaps with the review items:
|
||||||
@@ -487,10 +488,6 @@ class RecordingMaintainer(threading.Thread):
|
|||||||
# continuous/motion retention (either disabled or segment_stats said
|
# continuous/motion retention (either disabled or segment_stats said
|
||||||
# discard), so waiting longer just fills the cache.
|
# discard), so waiting longer just fills the cache.
|
||||||
else:
|
else:
|
||||||
camera_info = self.object_recordings_info[camera]
|
|
||||||
most_recently_processed_frame_time = (
|
|
||||||
camera_info[-1][0] if len(camera_info) > 0 else 0
|
|
||||||
)
|
|
||||||
retain_cutoff = datetime.datetime.fromtimestamp(
|
retain_cutoff = datetime.datetime.fromtimestamp(
|
||||||
most_recently_processed_frame_time - record_config.event_pre_capture
|
most_recently_processed_frame_time - record_config.event_pre_capture
|
||||||
).astimezone(datetime.UTC)
|
).astimezone(datetime.UTC)
|
||||||
|
|||||||
@@ -352,8 +352,9 @@ def stats_snapshot(
|
|||||||
total_camera_fps = total_process_fps = total_skipped_fps = total_detection_fps = 0
|
total_camera_fps = total_process_fps = total_skipped_fps = total_detection_fps = 0
|
||||||
|
|
||||||
stats["cameras"] = {}
|
stats["cameras"] = {}
|
||||||
for name, camera_stats in camera_metrics.items():
|
for name, camera_stats in list(camera_metrics.items()):
|
||||||
if name not in config.cameras:
|
camera_config = config.cameras.get(name)
|
||||||
|
if camera_config is None:
|
||||||
continue
|
continue
|
||||||
|
|
||||||
total_camera_fps += camera_stats.camera_fps.value
|
total_camera_fps += camera_stats.camera_fps.value
|
||||||
@@ -370,7 +371,7 @@ def stats_snapshot(
|
|||||||
# Calculate connection quality based on current state
|
# Calculate connection quality based on current state
|
||||||
# This is computed at stats-collection time so offline cameras
|
# This is computed at stats-collection time so offline cameras
|
||||||
# correctly show as unusable rather than excellent
|
# correctly show as unusable rather than excellent
|
||||||
expected_fps = config.cameras[name].detect.fps
|
expected_fps = camera_config.detect.fps
|
||||||
current_fps = camera_stats.camera_fps.value
|
current_fps = camera_stats.camera_fps.value
|
||||||
reconnects = camera_stats.reconnects_last_hour.value
|
reconnects = camera_stats.reconnects_last_hour.value
|
||||||
stalls = camera_stats.stalls_last_hour.value
|
stalls = camera_stats.stalls_last_hour.value
|
||||||
@@ -398,7 +399,7 @@ def stats_snapshot(
|
|||||||
"process_fps": round(camera_stats.process_fps.value, 2),
|
"process_fps": round(camera_stats.process_fps.value, 2),
|
||||||
"skipped_fps": round(camera_stats.skipped_fps.value, 2),
|
"skipped_fps": round(camera_stats.skipped_fps.value, 2),
|
||||||
"detection_fps": round(camera_stats.detection_fps.value, 2),
|
"detection_fps": round(camera_stats.detection_fps.value, 2),
|
||||||
"detection_enabled": config.cameras[name].detect.enabled,
|
"detection_enabled": camera_config.detect.enabled,
|
||||||
"pid": pid,
|
"pid": pid,
|
||||||
"capture_pid": capture_pid,
|
"capture_pid": capture_pid,
|
||||||
"ffmpeg_pid": ffmpeg_pid,
|
"ffmpeg_pid": ffmpeg_pid,
|
||||||
|
|||||||
@@ -144,7 +144,9 @@ class BaseTestHttp(unittest.TestCase):
|
|||||||
except OSError:
|
except OSError:
|
||||||
pass
|
pass
|
||||||
|
|
||||||
def create_app(self, stats=None, event_metadata_publisher=None):
|
def create_app(
|
||||||
|
self, stats=None, event_metadata_publisher=None, enforce_default_admin=False
|
||||||
|
):
|
||||||
from frigate.api.auth import get_allowed_cameras_for_filter, get_current_user
|
from frigate.api.auth import get_allowed_cameras_for_filter, get_current_user
|
||||||
|
|
||||||
app = create_fastapi_app(
|
app = create_fastapi_app(
|
||||||
@@ -158,7 +160,7 @@ class BaseTestHttp(unittest.TestCase):
|
|||||||
event_metadata_publisher,
|
event_metadata_publisher,
|
||||||
None,
|
None,
|
||||||
DebugReplayManager(),
|
DebugReplayManager(),
|
||||||
enforce_default_admin=False,
|
enforce_default_admin=enforce_default_admin,
|
||||||
)
|
)
|
||||||
|
|
||||||
# Default test mocks for authentication
|
# Default test mocks for authentication
|
||||||
|
|||||||
@@ -47,6 +47,25 @@ class TestHttpApp(BaseTestHttp):
|
|||||||
assert response.status_code == 200
|
assert response.status_code == 200
|
||||||
assert response.json()["front_door"]["usage_percent"] == 25.0
|
assert response.json()["front_door"]["usage_percent"] == 25.0
|
||||||
|
|
||||||
|
def test_camera_name_collision_keeps_admin_default(self):
|
||||||
|
self.minimal_config["cameras"]["faces"] = self.minimal_config["cameras"].pop(
|
||||||
|
"front_door"
|
||||||
|
)
|
||||||
|
app = super().create_app(enforce_default_admin=True)
|
||||||
|
viewer = {"remote-user": "viewer", "remote-role": "viewer"}
|
||||||
|
|
||||||
|
with AuthTestClient(app) as client:
|
||||||
|
assert client.get("/faces", headers=viewer).status_code == 403
|
||||||
|
assert client.get("/faces").status_code == 200
|
||||||
|
assert (
|
||||||
|
client.post("/faces/train/person/classify", headers=viewer).status_code
|
||||||
|
== 403
|
||||||
|
)
|
||||||
|
|
||||||
|
# Camera routes for the same name stay reachable by viewers
|
||||||
|
response = client.get("/faces/recordings/summary", headers=viewer)
|
||||||
|
assert response.status_code == 200
|
||||||
|
|
||||||
def test_config_set_in_memory_replaces_objects_track_list(self):
|
def test_config_set_in_memory_replaces_objects_track_list(self):
|
||||||
self.minimal_config["cameras"]["front_door"]["objects"] = {
|
self.minimal_config["cameras"]["front_door"]["objects"] = {
|
||||||
"track": ["person", "car"],
|
"track": ["person", "car"],
|
||||||
|
|||||||
@@ -168,6 +168,29 @@ class TestHttpApp(BaseTestHttp):
|
|||||||
assert events[0]["id"] == id
|
assert events[0]["id"] == id
|
||||||
assert events[1]["id"] == id2
|
assert events[1]["id"] == id2
|
||||||
|
|
||||||
|
def test_get_event_list_offset_pages_score_sort(self):
|
||||||
|
now = datetime.now().timestamp()
|
||||||
|
scores = [0.6, 0.9, 0.7, 0.95, 0.8]
|
||||||
|
|
||||||
|
with AuthTestClient(self.app) as client:
|
||||||
|
for i, score in enumerate(scores):
|
||||||
|
super().insert_mock_event(
|
||||||
|
f"event-{i}", start_time=now + i, data={"score": score}
|
||||||
|
)
|
||||||
|
|
||||||
|
params = {"sort": "score_desc"}
|
||||||
|
full = [e["id"] for e in client.get("/events", params=params).json()]
|
||||||
|
paged = [
|
||||||
|
e["id"]
|
||||||
|
for offset in (0, 2, 4)
|
||||||
|
for e in client.get(
|
||||||
|
"/events", params={**params, "limit": 2, "offset": offset}
|
||||||
|
).json()
|
||||||
|
]
|
||||||
|
|
||||||
|
assert full == ["event-3", "event-1", "event-4", "event-2", "event-0"]
|
||||||
|
assert paged == full
|
||||||
|
|
||||||
def test_get_event_list_match_multilingual_attribute(self):
|
def test_get_event_list_match_multilingual_attribute(self):
|
||||||
event_id = "123456.zh"
|
event_id = "123456.zh"
|
||||||
attribute = "中文标签"
|
attribute = "中文标签"
|
||||||
@@ -219,6 +242,85 @@ class TestHttpApp(BaseTestHttp):
|
|||||||
assert len(events) == 1
|
assert len(events) == 1
|
||||||
assert events[0]["id"] == event_id
|
assert events[0]["id"] == event_id
|
||||||
|
|
||||||
|
def test_events_search_offset_pages_score_sort(self):
|
||||||
|
now = datetime.now().timestamp()
|
||||||
|
scores = [0.6, 0.9, 0.7, 0.95, 0.8]
|
||||||
|
ids = [f"event-{i}" for i in range(len(scores))]
|
||||||
|
mock_embeddings = Mock()
|
||||||
|
mock_embeddings.search_thumbnail.return_value = [
|
||||||
|
(event_id, 0.1 * i) for i, event_id in enumerate(ids)
|
||||||
|
]
|
||||||
|
|
||||||
|
self.app.frigate_config.semantic_search.enabled = True
|
||||||
|
self.app.embeddings = mock_embeddings
|
||||||
|
|
||||||
|
with AuthTestClient(self.app) as client:
|
||||||
|
for i, score in enumerate(scores):
|
||||||
|
super().insert_mock_event(
|
||||||
|
ids[i], start_time=now + i, data={"score": score}
|
||||||
|
)
|
||||||
|
|
||||||
|
params = {
|
||||||
|
"search_type": "similarity",
|
||||||
|
"event_id": ids[0],
|
||||||
|
"sort": "score_desc",
|
||||||
|
}
|
||||||
|
paged = [
|
||||||
|
e["id"]
|
||||||
|
for offset in (0, 2, 4)
|
||||||
|
for e in client.get(
|
||||||
|
"/events/search",
|
||||||
|
params={**params, "limit": 2, "offset": offset},
|
||||||
|
).json()
|
||||||
|
]
|
||||||
|
|
||||||
|
assert paged == ["event-3", "event-1", "event-4", "event-2", "event-0"]
|
||||||
|
|
||||||
|
def test_events_search_offset_pages_orders_ties_by_id(self):
|
||||||
|
now = datetime.now().timestamp()
|
||||||
|
ids = ["event-c", "event-a", "event-b"]
|
||||||
|
mock_embeddings = Mock()
|
||||||
|
mock_embeddings.search_thumbnail.return_value = [
|
||||||
|
(event_id, 0.1) for event_id in ids
|
||||||
|
]
|
||||||
|
|
||||||
|
self.app.frigate_config.semantic_search.enabled = True
|
||||||
|
self.app.embeddings = mock_embeddings
|
||||||
|
|
||||||
|
with AuthTestClient(self.app) as client:
|
||||||
|
for i, event_id in enumerate(ids):
|
||||||
|
super().insert_mock_event(
|
||||||
|
event_id, start_time=now + i, data={"score": 0.8}
|
||||||
|
)
|
||||||
|
|
||||||
|
for sort in ("score_desc", "relevance"):
|
||||||
|
params = {
|
||||||
|
"search_type": "similarity",
|
||||||
|
"event_id": ids[0],
|
||||||
|
"sort": sort,
|
||||||
|
}
|
||||||
|
paged = [
|
||||||
|
e["id"]
|
||||||
|
for offset in (0, 1, 2)
|
||||||
|
for e in client.get(
|
||||||
|
"/events/search",
|
||||||
|
params={**params, "limit": 1, "offset": offset},
|
||||||
|
).json()
|
||||||
|
]
|
||||||
|
|
||||||
|
assert paged == ["event-a", "event-b", "event-c"]
|
||||||
|
|
||||||
|
def test_event_list_rejects_negative_offset(self):
|
||||||
|
with AuthTestClient(self.app) as client:
|
||||||
|
response = client.get("/events", params={"offset": -5})
|
||||||
|
assert response.status_code == 422
|
||||||
|
|
||||||
|
response = client.get(
|
||||||
|
"/events/search",
|
||||||
|
params={"query": "car", "offset": -5},
|
||||||
|
)
|
||||||
|
assert response.status_code == 422
|
||||||
|
|
||||||
def test_similarity_search_hides_unauthorized_anchor_event(self):
|
def test_similarity_search_hides_unauthorized_anchor_event(self):
|
||||||
mock_embeddings = Mock()
|
mock_embeddings = Mock()
|
||||||
self.app.frigate_config.semantic_search.enabled = True
|
self.app.frigate_config.semantic_search.enabled = True
|
||||||
|
|||||||
@@ -240,9 +240,101 @@ class TestHttpReview(BaseTestHttp):
|
|||||||
assert len(response_json) == 1
|
assert len(response_json) == 1
|
||||||
assert response_json[0]["id"] == id_reviewed
|
assert response_json[0]["id"] == id_reviewed
|
||||||
|
|
||||||
|
def test_get_review_with_label_filter_matches_verified(self):
|
||||||
|
"""Test that a label filter also matches the `-verified` variant."""
|
||||||
|
now = datetime.now().timestamp()
|
||||||
|
|
||||||
|
with AuthTestClient(self.app) as client:
|
||||||
|
super().insert_mock_review_segment(
|
||||||
|
"123456.person", now, now + 2, data={"objects": ["person"]}
|
||||||
|
)
|
||||||
|
super().insert_mock_review_segment(
|
||||||
|
"123456.verified", now, now + 2, data={"objects": ["person-verified"]}
|
||||||
|
)
|
||||||
|
super().insert_mock_review_segment(
|
||||||
|
"123456.car", now, now + 2, data={"objects": ["car"]}
|
||||||
|
)
|
||||||
|
|
||||||
|
params = {
|
||||||
|
"labels": "person",
|
||||||
|
"after": now - 1,
|
||||||
|
"before": now + 3,
|
||||||
|
}
|
||||||
|
response = client.get("/review", params=params)
|
||||||
|
assert response.status_code == 200
|
||||||
|
response_json = response.json()
|
||||||
|
assert {r["id"] for r in response_json} == {
|
||||||
|
"123456.person",
|
||||||
|
"123456.verified",
|
||||||
|
}
|
||||||
|
|
||||||
|
def test_get_review_with_label_filter_does_not_match_prefix(self):
|
||||||
|
"""Test that a label filter does not match labels that only share a prefix."""
|
||||||
|
now = datetime.now().timestamp()
|
||||||
|
|
||||||
|
with AuthTestClient(self.app) as client:
|
||||||
|
super().insert_mock_review_segment(
|
||||||
|
"123456.carrot", now, now + 2, data={"objects": ["carrot"]}
|
||||||
|
)
|
||||||
|
|
||||||
|
params = {
|
||||||
|
"labels": "car",
|
||||||
|
"after": now - 1,
|
||||||
|
"before": now + 3,
|
||||||
|
}
|
||||||
|
response = client.get("/review", params=params)
|
||||||
|
assert response.status_code == 200
|
||||||
|
assert len(response.json()) == 0
|
||||||
|
|
||||||
|
def test_get_review_with_audio_label_filter(self):
|
||||||
|
"""Test that a label filter still matches audio labels."""
|
||||||
|
now = datetime.now().timestamp()
|
||||||
|
|
||||||
|
with AuthTestClient(self.app) as client:
|
||||||
|
super().insert_mock_review_segment(
|
||||||
|
"123456.audio", now, now + 2, data={"audio": ["speech"]}
|
||||||
|
)
|
||||||
|
|
||||||
|
params = {
|
||||||
|
"labels": "speech",
|
||||||
|
"after": now - 1,
|
||||||
|
"before": now + 3,
|
||||||
|
}
|
||||||
|
response = client.get("/review", params=params)
|
||||||
|
assert response.status_code == 200
|
||||||
|
response_json = response.json()
|
||||||
|
assert len(response_json) == 1
|
||||||
|
assert response_json[0]["id"] == "123456.audio"
|
||||||
|
|
||||||
####################################################################################################################
|
####################################################################################################################
|
||||||
################################### GET /review/summary Endpoint #################################################
|
################################### GET /review/summary Endpoint #################################################
|
||||||
####################################################################################################################
|
####################################################################################################################
|
||||||
|
def test_get_review_summary_label_filter_matches_verified(self):
|
||||||
|
"""Test that the summary label filter also matches the `-verified` variant."""
|
||||||
|
with AuthTestClient(self.app) as client:
|
||||||
|
super().insert_mock_review_segment(
|
||||||
|
"123456.verified", data={"objects": ["person-verified"]}
|
||||||
|
)
|
||||||
|
super().insert_mock_review_segment(
|
||||||
|
"123456.car", data={"objects": ["car"]}, severity=SeverityEnum.detection
|
||||||
|
)
|
||||||
|
|
||||||
|
params = {
|
||||||
|
"cameras": "front_door",
|
||||||
|
"labels": "person",
|
||||||
|
"zones": "all",
|
||||||
|
"timezone": "utc",
|
||||||
|
}
|
||||||
|
response = client.get("/review/summary", params=params)
|
||||||
|
assert response.status_code == 200
|
||||||
|
response_json = response.json()
|
||||||
|
assert response_json["last24Hours"]["total_alert"] == 1
|
||||||
|
assert response_json["last24Hours"]["total_detection"] == 0
|
||||||
|
|
||||||
|
today_formatted = datetime.today().strftime("%Y-%m-%d")
|
||||||
|
assert response_json[today_formatted]["total_alert"] == 1
|
||||||
|
assert response_json[today_formatted]["total_detection"] == 0
|
||||||
|
|
||||||
def test_get_review_summary_all_filters(self):
|
def test_get_review_summary_all_filters(self):
|
||||||
with AuthTestClient(self.app) as client:
|
with AuthTestClient(self.app) as client:
|
||||||
super().insert_mock_review_segment("123456.random")
|
super().insert_mock_review_segment("123456.random")
|
||||||
|
|||||||
@@ -1,10 +1,15 @@
|
|||||||
"""Tests for ONNX Runtime session option selection."""
|
"""Tests for ONNX Runtime session option selection."""
|
||||||
|
|
||||||
import unittest
|
import unittest
|
||||||
|
from unittest.mock import MagicMock, patch
|
||||||
|
|
||||||
|
import numpy as np
|
||||||
import onnxruntime as ort
|
import onnxruntime as ort
|
||||||
|
|
||||||
from frigate.detectors.detection_runners import get_ort_session_options
|
from frigate.detectors.detection_runners import (
|
||||||
|
CudaGraphRunner,
|
||||||
|
get_ort_session_options,
|
||||||
|
)
|
||||||
from frigate.detectors.detector_config import ModelTypeEnum
|
from frigate.detectors.detector_config import ModelTypeEnum
|
||||||
from frigate.embeddings.types import EnrichmentModelTypeEnum
|
from frigate.embeddings.types import EnrichmentModelTypeEnum
|
||||||
|
|
||||||
@@ -39,3 +44,45 @@ class TestGetOrtSessionOptions(unittest.TestCase):
|
|||||||
]:
|
]:
|
||||||
with self.subTest(model_type=model_type):
|
with self.subTest(model_type=model_type):
|
||||||
self.assertIsNone(get_ort_session_options(model_type))
|
self.assertIsNone(get_ort_session_options(model_type))
|
||||||
|
|
||||||
|
|
||||||
|
class TestCudaGraphRunner(unittest.TestCase):
|
||||||
|
"""CUDA graph capture fails if the arena has to allocate during capture, so
|
||||||
|
the session is warmed up with capture disabled before the first real run."""
|
||||||
|
|
||||||
|
def setUp(self):
|
||||||
|
self.session = MagicMock()
|
||||||
|
self.session.get_outputs.return_value = [MagicMock(name="output")]
|
||||||
|
self.io_binding = self.session.io_binding.return_value
|
||||||
|
self.input = {"images": np.zeros((1, 3, 320, 320), np.float32)}
|
||||||
|
|
||||||
|
def _annotations(self) -> list[str | None]:
|
||||||
|
"""Graph annotation id passed with each run, None when unset."""
|
||||||
|
annotations = []
|
||||||
|
|
||||||
|
for call in self.session.run_with_iobinding.call_args_list:
|
||||||
|
try:
|
||||||
|
annotations.append(call.args[1].get_run_config_entry("gpu_graph_id"))
|
||||||
|
except RuntimeError:
|
||||||
|
annotations.append(None)
|
||||||
|
|
||||||
|
return annotations
|
||||||
|
|
||||||
|
def test_first_run_warms_up_with_capture_disabled(self):
|
||||||
|
with patch.object(ort.OrtValue, "ortvalue_from_numpy"):
|
||||||
|
CudaGraphRunner(self.session, 0).run(self.input)
|
||||||
|
|
||||||
|
self.assertEqual(
|
||||||
|
self._annotations(),
|
||||||
|
["-1"] * CudaGraphRunner.GRAPH_FREE_WARMUP_RUNS + [None],
|
||||||
|
)
|
||||||
|
|
||||||
|
def test_later_runs_allow_capture(self):
|
||||||
|
with patch.object(ort.OrtValue, "ortvalue_from_numpy"):
|
||||||
|
runner = CudaGraphRunner(self.session, 0)
|
||||||
|
runner.run(self.input)
|
||||||
|
self.session.run_with_iobinding.reset_mock()
|
||||||
|
runner.run(self.input)
|
||||||
|
|
||||||
|
self.assertEqual(self._annotations(), [None])
|
||||||
|
runner._input_ortvalue.update_inplace.assert_called_once()
|
||||||
|
|||||||
@@ -1,7 +1,7 @@
|
|||||||
import datetime
|
import datetime
|
||||||
import sys
|
import sys
|
||||||
import unittest
|
import unittest
|
||||||
from unittest.mock import MagicMock, patch
|
from unittest.mock import AsyncMock, MagicMock, patch
|
||||||
|
|
||||||
# Mock complex imports before importing maintainer, saving originals so we can
|
# Mock complex imports before importing maintainer, saving originals so we can
|
||||||
# restore them after import and avoid polluting sys.modules for other tests.
|
# restore them after import and avoid polluting sys.modules for other tests.
|
||||||
@@ -16,7 +16,7 @@ for name in _MOCKED_MODULES:
|
|||||||
sys.modules[name] = MagicMock()
|
sys.modules[name] = MagicMock()
|
||||||
|
|
||||||
# Now import the class under test
|
# Now import the class under test
|
||||||
from frigate.config import FrigateConfig # noqa: E402
|
from frigate.config import FrigateConfig, RetainModeEnum # noqa: E402
|
||||||
from frigate.record.maintainer import RecordingMaintainer # noqa: E402
|
from frigate.record.maintainer import RecordingMaintainer # noqa: E402
|
||||||
|
|
||||||
# Restore original modules (or remove mock if there was no original)
|
# Restore original modules (or remove mock if there was no original)
|
||||||
@@ -115,6 +115,70 @@ class TestMaintainer(unittest.IsolatedAsyncioTestCase):
|
|||||||
self.assertIsNone(result)
|
self.assertIsNone(result)
|
||||||
maintainer.drop_segment.assert_called_once_with(cache_path)
|
maintainer.drop_segment.assert_called_once_with(cache_path)
|
||||||
|
|
||||||
|
async def test_defers_review_overlap_segment_until_metadata_catches_up(self):
|
||||||
|
# Regression: a segment overlapping an active_objects review must not
|
||||||
|
# be dropped while detection metadata lags behind the segment end,
|
||||||
|
# the missing frames may hold the active objects (or continuous
|
||||||
|
# retention would keep it anyway).
|
||||||
|
config = MagicMock(spec=FrigateConfig)
|
||||||
|
|
||||||
|
camera_config = MagicMock()
|
||||||
|
camera_config.record.enabled = True
|
||||||
|
camera_config.record.continuous.days = 7
|
||||||
|
camera_config.record.motion.days = 0
|
||||||
|
camera_config.record.alerts.retain.mode = RetainModeEnum.active_objects
|
||||||
|
camera_config.record.get_review_pre_capture.return_value = 5
|
||||||
|
camera_config.record.get_review_post_capture.return_value = 5
|
||||||
|
config.cameras = {"test_cam": camera_config}
|
||||||
|
|
||||||
|
stop_event = MagicMock()
|
||||||
|
maintainer = RecordingMaintainer(config, stop_event)
|
||||||
|
|
||||||
|
now = datetime.datetime.now(datetime.UTC)
|
||||||
|
start_time = now - datetime.timedelta(seconds=20)
|
||||||
|
end_time = now - datetime.timedelta(seconds=10)
|
||||||
|
cache_path = "/tmp/cache/test_cam@20260417150000+0000.mp4"
|
||||||
|
|
||||||
|
maintainer.end_time_cache = {cache_path: (end_time, 10.0)}
|
||||||
|
# Metadata has only reached partway into the segment.
|
||||||
|
maintainer.object_recordings_info["test_cam"] = [
|
||||||
|
(end_time.timestamp() - 8, [], [], [])
|
||||||
|
]
|
||||||
|
maintainer.audio_recordings_info["test_cam"] = []
|
||||||
|
|
||||||
|
maintainer.drop_segment = MagicMock()
|
||||||
|
maintainer.move_segment = AsyncMock(return_value=None)
|
||||||
|
maintainer.recordings_publisher = MagicMock()
|
||||||
|
|
||||||
|
review = MagicMock()
|
||||||
|
review.severity = "alert"
|
||||||
|
review.start_time = start_time.timestamp() - 30
|
||||||
|
review.end_time = None
|
||||||
|
|
||||||
|
result = await maintainer.validate_and_move_segment(
|
||||||
|
"test_cam",
|
||||||
|
reviews=[review],
|
||||||
|
recording={"start_time": start_time, "cache_path": cache_path},
|
||||||
|
)
|
||||||
|
|
||||||
|
self.assertIsNone(result)
|
||||||
|
maintainer.drop_segment.assert_not_called()
|
||||||
|
maintainer.move_segment.assert_not_awaited()
|
||||||
|
|
||||||
|
# Once metadata passes the segment end, continuous retention keeps it.
|
||||||
|
maintainer.object_recordings_info["test_cam"].append(
|
||||||
|
(now.timestamp(), [], [], [])
|
||||||
|
)
|
||||||
|
|
||||||
|
await maintainer.validate_and_move_segment(
|
||||||
|
"test_cam",
|
||||||
|
reviews=[review],
|
||||||
|
recording={"start_time": start_time, "cache_path": cache_path},
|
||||||
|
)
|
||||||
|
|
||||||
|
maintainer.drop_segment.assert_not_called()
|
||||||
|
maintainer.move_segment.assert_awaited_once()
|
||||||
|
|
||||||
async def test_expire_stale_recordings_info_drops_only_absent_cameras(self):
|
async def test_expire_stale_recordings_info_drops_only_absent_cameras(self):
|
||||||
config = MagicMock(spec=FrigateConfig)
|
config = MagicMock(spec=FrigateConfig)
|
||||||
config.cameras = {}
|
config.cameras = {}
|
||||||
|
|||||||
@@ -1,16 +1,24 @@
|
|||||||
"""Tests for embedding cleanup on the main Frigate database.
|
"""Tests for embedding storage and cleanup on the main Frigate database.
|
||||||
|
|
||||||
Embeddings are deleted whether or not semantic search is currently enabled, so
|
Embeddings are deleted whether or not semantic search is currently enabled, so
|
||||||
the delete path has to tolerate databases where the vec0 tables were never
|
the delete path has to tolerate databases where the vec0 tables were never
|
||||||
created and installs where the sqlite-vec extension is unavailable.
|
created and installs where the sqlite-vec extension is unavailable.
|
||||||
|
|
||||||
|
The write paths need the real extension, since the behavior under test belongs
|
||||||
|
to vec0 itself, so those tests are skipped when it is not installed.
|
||||||
"""
|
"""
|
||||||
|
|
||||||
import os
|
import os
|
||||||
|
import struct
|
||||||
import tempfile
|
import tempfile
|
||||||
import unittest
|
import unittest
|
||||||
|
|
||||||
|
from peewee import OperationalError
|
||||||
|
|
||||||
from frigate.db.sqlitevecq import SqliteVecQueueDatabase
|
from frigate.db.sqlitevecq import SqliteVecQueueDatabase
|
||||||
|
|
||||||
|
VEC_EXTENSION_PATH = "/usr/local/lib/vec0.so"
|
||||||
|
|
||||||
|
|
||||||
class TestDeleteEmbeddings(unittest.TestCase):
|
class TestDeleteEmbeddings(unittest.TestCase):
|
||||||
def setUp(self) -> None:
|
def setUp(self) -> None:
|
||||||
@@ -52,6 +60,21 @@ class TestDeleteEmbeddings(unittest.TestCase):
|
|||||||
|
|
||||||
self.assertEqual(self._thumbnail_ids(), ["b"])
|
self.assertEqual(self._thumbnail_ids(), ["b"])
|
||||||
|
|
||||||
|
def test_delete_failure_is_logged_not_raised(self) -> None:
|
||||||
|
self._create_thumbnails_table()
|
||||||
|
self.db.execute_sql(
|
||||||
|
"""
|
||||||
|
CREATE TRIGGER vec_thumbnails_no_delete BEFORE DELETE ON vec_thumbnails
|
||||||
|
BEGIN SELECT RAISE(ABORT, 'delete blocked'); END
|
||||||
|
"""
|
||||||
|
).fetchall()
|
||||||
|
|
||||||
|
with self.assertLogs("frigate.db.sqlitevecq", level="ERROR") as logs:
|
||||||
|
self.db.delete_embeddings_thumbnail(event_ids=["a"])
|
||||||
|
|
||||||
|
self.assertIn("Failed to delete embeddings", logs.output[0])
|
||||||
|
self.assertEqual(self._thumbnail_ids(), ["a", "b"])
|
||||||
|
|
||||||
def test_delete_skipped_without_extension(self) -> None:
|
def test_delete_skipped_without_extension(self) -> None:
|
||||||
self._create_thumbnails_table()
|
self._create_thumbnails_table()
|
||||||
self.db.load_vec_extension = False
|
self.db.load_vec_extension = False
|
||||||
@@ -61,3 +84,104 @@ class TestDeleteEmbeddings(unittest.TestCase):
|
|||||||
|
|
||||||
# the vec0 tables cannot be written without the extension
|
# the vec0 tables cannot be written without the extension
|
||||||
self.assertEqual(self._thumbnail_ids(), ["a", "b"])
|
self.assertEqual(self._thumbnail_ids(), ["a", "b"])
|
||||||
|
|
||||||
|
|
||||||
|
def _vector(value: float) -> bytes:
|
||||||
|
return struct.pack("768f", *([value] * 768))
|
||||||
|
|
||||||
|
|
||||||
|
@unittest.skipUnless(
|
||||||
|
os.path.exists(VEC_EXTENSION_PATH), "sqlite-vec extension is not installed"
|
||||||
|
)
|
||||||
|
class TestEmbeddingsTableWrites(unittest.TestCase):
|
||||||
|
"""Covers the vec0 writes behind semantic search reindexing."""
|
||||||
|
|
||||||
|
def setUp(self) -> None:
|
||||||
|
self.tmp_dir = tempfile.TemporaryDirectory()
|
||||||
|
self.db = SqliteVecQueueDatabase(
|
||||||
|
os.path.join(self.tmp_dir.name, "test.db"), load_vec_extension=True
|
||||||
|
)
|
||||||
|
self.db.start()
|
||||||
|
self.db.create_embeddings_tables()
|
||||||
|
|
||||||
|
def tearDown(self) -> None:
|
||||||
|
self.db.stop()
|
||||||
|
self.db.close()
|
||||||
|
self.tmp_dir.cleanup()
|
||||||
|
|
||||||
|
def _vec_tables(self) -> list[str]:
|
||||||
|
return [
|
||||||
|
row[0]
|
||||||
|
for row in self.db.execute_sql(
|
||||||
|
"SELECT name FROM sqlite_master WHERE name LIKE 'vec_%' ORDER BY name"
|
||||||
|
)
|
||||||
|
]
|
||||||
|
|
||||||
|
def _make_legacy(self, table: str) -> None:
|
||||||
|
# sqlite-vec added the _info shadow table in 0.1.6, so tables written by
|
||||||
|
# Frigate 0.17 and earlier do not have one
|
||||||
|
self.db.execute_sql(f"DROP TABLE {table}_info").fetchall()
|
||||||
|
|
||||||
|
def _stored(self, table: str, column: str, event_id: str) -> str | None:
|
||||||
|
row = self.db.execute_sql(
|
||||||
|
f"SELECT vec_to_json({column}) FROM {table} WHERE id = ?", (event_id,)
|
||||||
|
).fetchone()
|
||||||
|
return row[0] if row else None
|
||||||
|
|
||||||
|
def test_write_error_is_raised(self) -> None:
|
||||||
|
# queued writes hide their exception in the returned cursor
|
||||||
|
with self.assertRaises(OperationalError):
|
||||||
|
self.db.execute_write("INSERT INTO vec_missing(id) VALUES ('a')")
|
||||||
|
|
||||||
|
def test_drop_tables_removes_legacy_tables(self) -> None:
|
||||||
|
self._make_legacy("vec_thumbnails")
|
||||||
|
self._make_legacy("vec_descriptions")
|
||||||
|
|
||||||
|
self.db.drop_embeddings_tables()
|
||||||
|
|
||||||
|
self.assertEqual(self._vec_tables(), [])
|
||||||
|
|
||||||
|
def test_drop_tables_without_any_tables_does_not_raise(self) -> None:
|
||||||
|
self.db.drop_embeddings_tables()
|
||||||
|
|
||||||
|
self.db.drop_embeddings_tables()
|
||||||
|
|
||||||
|
def test_upsert_replaces_existing_embedding(self) -> None:
|
||||||
|
self.db.upsert_embeddings(
|
||||||
|
"vec_thumbnails", "thumbnail_embedding", {"evt1": _vector(0.01)}
|
||||||
|
)
|
||||||
|
|
||||||
|
self.db.upsert_embeddings(
|
||||||
|
"vec_thumbnails", "thumbnail_embedding", {"evt1": _vector(0.99)}
|
||||||
|
)
|
||||||
|
|
||||||
|
stored = self._stored("vec_thumbnails", "thumbnail_embedding", "evt1")
|
||||||
|
self.assertTrue(stored.startswith("[0.990000"), stored)
|
||||||
|
|
||||||
|
def test_upsert_keeps_one_row_per_event(self) -> None:
|
||||||
|
for _ in range(3):
|
||||||
|
self.db.upsert_embeddings(
|
||||||
|
"vec_descriptions", "description_embedding", {"evt1": _vector(0.5)}
|
||||||
|
)
|
||||||
|
|
||||||
|
count = self.db.execute_sql(
|
||||||
|
"SELECT count(*) FROM vec_descriptions WHERE id = 'evt1'"
|
||||||
|
).fetchone()[0]
|
||||||
|
self.assertEqual(count, 1)
|
||||||
|
|
||||||
|
def test_reindex_cycle_rewrites_legacy_tables(self) -> None:
|
||||||
|
"""The 0.18 upgrade path: old vectors in, new vectors out."""
|
||||||
|
self.db.upsert_embeddings(
|
||||||
|
"vec_thumbnails", "thumbnail_embedding", {"evt1": _vector(0.01)}
|
||||||
|
)
|
||||||
|
self._make_legacy("vec_thumbnails")
|
||||||
|
self._make_legacy("vec_descriptions")
|
||||||
|
|
||||||
|
self.db.drop_embeddings_tables()
|
||||||
|
self.db.create_embeddings_tables()
|
||||||
|
self.db.upsert_embeddings(
|
||||||
|
"vec_thumbnails", "thumbnail_embedding", {"evt1": _vector(0.99)}
|
||||||
|
)
|
||||||
|
|
||||||
|
stored = self._stored("vec_thumbnails", "thumbnail_embedding", "evt1")
|
||||||
|
self.assertTrue(stored.startswith("[0.990000"), stored)
|
||||||
|
|||||||
@@ -0,0 +1,66 @@
|
|||||||
|
import unittest
|
||||||
|
from unittest.mock import MagicMock, patch
|
||||||
|
|
||||||
|
from frigate.events.types import EventStateEnum
|
||||||
|
from frigate.models import Timeline
|
||||||
|
from frigate.timeline import TimelineProcessor
|
||||||
|
|
||||||
|
|
||||||
|
def make_event(has_clip: bool, has_snapshot: bool) -> dict:
|
||||||
|
return {
|
||||||
|
"id": "event-1",
|
||||||
|
"frame_time": 1000.0,
|
||||||
|
"box": [0, 0, 10, 10],
|
||||||
|
"region": [0, 0, 100, 100],
|
||||||
|
"label": "car",
|
||||||
|
"sub_label": None,
|
||||||
|
"score": 0.8,
|
||||||
|
"has_clip": has_clip,
|
||||||
|
"has_snapshot": has_snapshot,
|
||||||
|
"current_zones": [],
|
||||||
|
"stationary": False,
|
||||||
|
"attributes": {},
|
||||||
|
"current_attributes": [],
|
||||||
|
}
|
||||||
|
|
||||||
|
|
||||||
|
class TestTimelineProcessor(unittest.TestCase):
|
||||||
|
def setUp(self):
|
||||||
|
camera_config = MagicMock()
|
||||||
|
camera_config.detect.width = 1280
|
||||||
|
camera_config.detect.height = 720
|
||||||
|
config = MagicMock()
|
||||||
|
config.cameras.get.return_value = camera_config
|
||||||
|
self.processor = TimelineProcessor(config, MagicMock(), MagicMock())
|
||||||
|
|
||||||
|
@patch.object(Timeline, "insert")
|
||||||
|
def test_unsaved_event_writes_no_timeline_rows(self, insert):
|
||||||
|
event = make_event(has_clip=False, has_snapshot=False)
|
||||||
|
self.processor.handle_object_detection(
|
||||||
|
"front", EventStateEnum.start, None, event
|
||||||
|
)
|
||||||
|
self.processor.handle_object_detection(
|
||||||
|
"front", EventStateEnum.end, event, event
|
||||||
|
)
|
||||||
|
|
||||||
|
insert.assert_not_called()
|
||||||
|
self.assertEqual(self.processor.pre_event_cache, {})
|
||||||
|
|
||||||
|
@patch.object(Timeline, "insert")
|
||||||
|
def test_cached_entries_flush_when_event_is_saved(self, insert):
|
||||||
|
start = make_event(has_clip=False, has_snapshot=False)
|
||||||
|
self.processor.handle_object_detection(
|
||||||
|
"front", EventStateEnum.start, None, start
|
||||||
|
)
|
||||||
|
insert.assert_not_called()
|
||||||
|
|
||||||
|
end = make_event(has_clip=True, has_snapshot=False)
|
||||||
|
self.processor.handle_object_detection("front", EventStateEnum.end, start, end)
|
||||||
|
|
||||||
|
class_types = [c.args[0][Timeline.class_type] for c in insert.call_args_list]
|
||||||
|
self.assertEqual(class_types, ["visible", "gone"])
|
||||||
|
self.assertEqual(self.processor.pre_event_cache, {})
|
||||||
|
|
||||||
|
|
||||||
|
if __name__ == "__main__":
|
||||||
|
unittest.main()
|
||||||
@@ -6,7 +6,7 @@ import unittest
|
|||||||
|
|
||||||
from ruamel.yaml import YAML
|
from ruamel.yaml import YAML
|
||||||
|
|
||||||
from frigate.util.builtin import update_yaml_file_bulk
|
from frigate.util.builtin import clear_orphaned_comments, update_yaml_file_bulk
|
||||||
|
|
||||||
|
|
||||||
class TestUpdateYaml(unittest.TestCase):
|
class TestUpdateYaml(unittest.TestCase):
|
||||||
@@ -178,6 +178,27 @@ class TestUpdateYaml(unittest.TestCase):
|
|||||||
assert data["cameras"]["cam1"]["detect"]["fps"] == 5
|
assert data["cameras"]["cam1"]["detect"]["fps"] == 5
|
||||||
assert "# tuned for the pi" in self._read()
|
assert "# tuned for the pi" in self._read()
|
||||||
|
|
||||||
|
def test_delete_only_camera_with_comment_on_cameras_key(self):
|
||||||
|
"""Deleting the only camera stays parseable when the cameras key
|
||||||
|
carries the default config's trailing comment."""
|
||||||
|
self._write(
|
||||||
|
"cameras: # No cameras defined, UI wizard should be used\n"
|
||||||
|
" cam1:\n"
|
||||||
|
" enabled: true\n"
|
||||||
|
"version: 0.18-0\n"
|
||||||
|
)
|
||||||
|
yaml = YAML()
|
||||||
|
with open(self.config_path) as f:
|
||||||
|
data = yaml.load(f)
|
||||||
|
del data["cameras"]["cam1"]
|
||||||
|
clear_orphaned_comments(data["cameras"], data, "cameras")
|
||||||
|
with open(self.config_path, "w") as f:
|
||||||
|
yaml.dump(data, f)
|
||||||
|
|
||||||
|
data = self._load()
|
||||||
|
assert data["cameras"] == {}
|
||||||
|
assert data["version"] == "0.18-0"
|
||||||
|
|
||||||
|
|
||||||
if __name__ == "__main__":
|
if __name__ == "__main__":
|
||||||
unittest.main(verbosity=2)
|
unittest.main(verbosity=2)
|
||||||
|
|||||||
@@ -191,6 +191,9 @@ class TimelineProcessor(threading.Thread):
|
|||||||
timeline_entry[Timeline.class_type] = "gone"
|
timeline_entry[Timeline.class_type] = "gone"
|
||||||
self.insert_or_save(timeline_entry, prev_event_data, event_data)
|
self.insert_or_save(timeline_entry, prev_event_data, event_data)
|
||||||
|
|
||||||
|
# drop entries for events that ended without being saved
|
||||||
|
self.pre_event_cache.pop(event_id, None)
|
||||||
|
|
||||||
def handle_api_entry(
|
def handle_api_entry(
|
||||||
self,
|
self,
|
||||||
camera: str,
|
camera: str,
|
||||||
|
|||||||
@@ -24,6 +24,7 @@ from frigate.comms.event_metadata_updater import (
|
|||||||
from frigate.comms.events_updater import EventEndSubscriber, EventUpdatePublisher
|
from frigate.comms.events_updater import EventEndSubscriber, EventUpdatePublisher
|
||||||
from frigate.comms.inter_process import InterProcessRequestor
|
from frigate.comms.inter_process import InterProcessRequestor
|
||||||
from frigate.config import (
|
from frigate.config import (
|
||||||
|
CameraConfig,
|
||||||
CameraMqttConfig,
|
CameraMqttConfig,
|
||||||
FrigateConfig,
|
FrigateConfig,
|
||||||
RecordConfig,
|
RecordConfig,
|
||||||
@@ -128,8 +129,10 @@ class TrackedObjectProcessor(threading.Thread):
|
|||||||
)
|
)
|
||||||
|
|
||||||
def update(camera: str, obj: TrackedObject, frame_name: str) -> None:
|
def update(camera: str, obj: TrackedObject, frame_name: str) -> None:
|
||||||
obj.has_snapshot = self.should_save_snapshot(camera, obj)
|
obj.has_snapshot = self.should_save_snapshot(
|
||||||
obj.has_clip = self.should_retain_recording(camera, obj)
|
camera_state.camera_config, obj
|
||||||
|
)
|
||||||
|
obj.has_clip = self.should_retain_recording(camera_state.camera_config, obj)
|
||||||
after = obj.to_dict()
|
after = obj.to_dict()
|
||||||
message = {
|
message = {
|
||||||
"before": obj.previous,
|
"before": obj.previous,
|
||||||
@@ -153,8 +156,10 @@ class TrackedObjectProcessor(threading.Thread):
|
|||||||
|
|
||||||
def end(camera: str, obj: TrackedObject, frame_name: str) -> None:
|
def end(camera: str, obj: TrackedObject, frame_name: str) -> None:
|
||||||
# populate has_snapshot
|
# populate has_snapshot
|
||||||
obj.has_snapshot = self.should_save_snapshot(camera, obj)
|
obj.has_snapshot = self.should_save_snapshot(
|
||||||
obj.has_clip = self.should_retain_recording(camera, obj)
|
camera_state.camera_config, obj
|
||||||
|
)
|
||||||
|
obj.has_clip = self.should_retain_recording(camera_state.camera_config, obj)
|
||||||
|
|
||||||
# write thumbnail to disk if it will be saved as an event
|
# write thumbnail to disk if it will be saved as an event
|
||||||
if obj.has_snapshot or obj.has_clip:
|
if obj.has_snapshot or obj.has_clip:
|
||||||
@@ -184,8 +189,8 @@ class TrackedObjectProcessor(threading.Thread):
|
|||||||
)
|
)
|
||||||
|
|
||||||
def snapshot(camera: str, obj: TrackedObject) -> bool:
|
def snapshot(camera: str, obj: TrackedObject) -> bool:
|
||||||
mqtt_config: CameraMqttConfig = self.config.cameras[camera].mqtt
|
mqtt_config: CameraMqttConfig = camera_state.camera_config.mqtt
|
||||||
if mqtt_config.enabled and self.should_mqtt_snapshot(camera, obj):
|
if mqtt_config.enabled and self.should_mqtt_snapshot(mqtt_config, obj):
|
||||||
jpg_bytes, _ = obj.get_img_bytes(
|
jpg_bytes, _ = obj.get_img_bytes(
|
||||||
ext="jpg",
|
ext="jpg",
|
||||||
timestamp=mqtt_config.timestamp,
|
timestamp=mqtt_config.timestamp,
|
||||||
@@ -238,11 +243,13 @@ class TrackedObjectProcessor(threading.Thread):
|
|||||||
camera_state.on("camera_activity", camera_activity)
|
camera_state.on("camera_activity", camera_activity)
|
||||||
self.camera_states[camera] = camera_state
|
self.camera_states[camera] = camera_state
|
||||||
|
|
||||||
def should_save_snapshot(self, camera: str, obj: TrackedObject) -> bool:
|
def should_save_snapshot(
|
||||||
|
self, camera_config: CameraConfig, obj: TrackedObject
|
||||||
|
) -> bool:
|
||||||
if obj.false_positive:
|
if obj.false_positive:
|
||||||
return False
|
return False
|
||||||
|
|
||||||
snapshot_config: SnapshotsConfig = self.config.cameras[camera].snapshots
|
snapshot_config: SnapshotsConfig = camera_config.snapshots
|
||||||
|
|
||||||
if not snapshot_config.enabled:
|
if not snapshot_config.enabled:
|
||||||
return False
|
return False
|
||||||
@@ -261,11 +268,13 @@ class TrackedObjectProcessor(threading.Thread):
|
|||||||
|
|
||||||
return True
|
return True
|
||||||
|
|
||||||
def should_retain_recording(self, camera: str, obj: TrackedObject) -> bool:
|
def should_retain_recording(
|
||||||
|
self, camera_config: CameraConfig, obj: TrackedObject
|
||||||
|
) -> bool:
|
||||||
if obj.false_positive:
|
if obj.false_positive:
|
||||||
return False
|
return False
|
||||||
|
|
||||||
record_config: RecordConfig = self.config.cameras[camera].record
|
record_config: RecordConfig = camera_config.record
|
||||||
|
|
||||||
# Recording is disabled
|
# Recording is disabled
|
||||||
if not record_config.enabled:
|
if not record_config.enabled:
|
||||||
@@ -281,13 +290,15 @@ class TrackedObjectProcessor(threading.Thread):
|
|||||||
|
|
||||||
return True
|
return True
|
||||||
|
|
||||||
def should_mqtt_snapshot(self, camera: str, obj: TrackedObject) -> bool:
|
def should_mqtt_snapshot(
|
||||||
|
self, mqtt_config: CameraMqttConfig, obj: TrackedObject
|
||||||
|
) -> bool:
|
||||||
# object never changed position
|
# object never changed position
|
||||||
if obj.is_stationary():
|
if obj.is_stationary():
|
||||||
return False
|
return False
|
||||||
|
|
||||||
# if there are required zones and there is no overlap
|
# if there are required zones and there is no overlap
|
||||||
required_zones = self.config.cameras[camera].mqtt.required_zones
|
required_zones = mqtt_config.required_zones
|
||||||
if len(required_zones) > 0 and not set(obj.entered_zones) & set(required_zones):
|
if len(required_zones) > 0 and not set(obj.entered_zones) & set(required_zones):
|
||||||
logger.debug(
|
logger.debug(
|
||||||
f"Not sending mqtt for {obj.obj_data['id']} because it did not enter required zones"
|
f"Not sending mqtt for {obj.obj_data['id']} because it did not enter required zones"
|
||||||
@@ -297,7 +308,11 @@ class TrackedObjectProcessor(threading.Thread):
|
|||||||
return True
|
return True
|
||||||
|
|
||||||
def update_mqtt_motion(
|
def update_mqtt_motion(
|
||||||
self, camera: str, frame_time: float, motion_boxes: list
|
self,
|
||||||
|
camera: str,
|
||||||
|
camera_config: CameraConfig,
|
||||||
|
frame_time: float,
|
||||||
|
motion_boxes: list,
|
||||||
) -> None:
|
) -> None:
|
||||||
# publish if motion is currently being detected
|
# publish if motion is currently being detected
|
||||||
if motion_boxes:
|
if motion_boxes:
|
||||||
@@ -312,7 +327,7 @@ class TrackedObjectProcessor(threading.Thread):
|
|||||||
# always updated latest motion
|
# always updated latest motion
|
||||||
self.last_motion_detected[camera] = frame_time
|
self.last_motion_detected[camera] = frame_time
|
||||||
elif self.last_motion_detected.get(camera, 0) > 0:
|
elif self.last_motion_detected.get(camera, 0) > 0:
|
||||||
mqtt_delay = self.config.cameras[camera].motion.mqtt_off_delay
|
mqtt_delay = camera_config.motion.mqtt_off_delay
|
||||||
|
|
||||||
# If no motion, make sure the off_delay has passed
|
# If no motion, make sure the off_delay has passed
|
||||||
if frame_time - self.last_motion_detected.get(camera, 0) >= mqtt_delay:
|
if frame_time - self.last_motion_detected.get(camera, 0) >= mqtt_delay:
|
||||||
@@ -783,7 +798,7 @@ class TrackedObjectProcessor(threading.Thread):
|
|||||||
frame_name, frame_time, current_tracked_objects, motion_boxes, regions
|
frame_name, frame_time, current_tracked_objects, motion_boxes, regions
|
||||||
)
|
)
|
||||||
|
|
||||||
self.update_mqtt_motion(camera, frame_time, motion_boxes)
|
self.update_mqtt_motion(camera, camera_config, frame_time, motion_boxes)
|
||||||
|
|
||||||
tracked_objects = [
|
tracked_objects = [
|
||||||
o.to_dict() for o in camera_state.tracked_objects.values()
|
o.to_dict() for o in camera_state.tracked_objects.values()
|
||||||
|
|||||||
+45
-21
@@ -18,6 +18,7 @@ from frigate.comms.recordings_updater import (
|
|||||||
RecordingsDataTypeEnum,
|
RecordingsDataTypeEnum,
|
||||||
)
|
)
|
||||||
from frigate.config import CameraConfig, LoggerConfig
|
from frigate.config import CameraConfig, LoggerConfig
|
||||||
|
from frigate.config.camera.ffmpeg import CameraRoleEnum
|
||||||
from frigate.config.camera.updater import (
|
from frigate.config.camera.updater import (
|
||||||
CameraConfigUpdateEnum,
|
CameraConfigUpdateEnum,
|
||||||
CameraConfigUpdateSubscriber,
|
CameraConfigUpdateSubscriber,
|
||||||
@@ -34,6 +35,8 @@ from frigate.util.process import FrigateProcess
|
|||||||
|
|
||||||
logger = logging.getLogger(__name__)
|
logger = logging.getLogger(__name__)
|
||||||
|
|
||||||
|
RECORD_GRACE_SECONDS = 90
|
||||||
|
|
||||||
|
|
||||||
def capture_frames(
|
def capture_frames(
|
||||||
ffmpeg_process: sp.Popen[Any],
|
ffmpeg_process: sp.Popen[Any],
|
||||||
@@ -164,6 +167,7 @@ class CameraWatchdog(threading.Thread):
|
|||||||
self.latest_invalid_segment_time: float = 0
|
self.latest_invalid_segment_time: float = 0
|
||||||
self.latest_cache_segment_time: float = 0
|
self.latest_cache_segment_time: float = 0
|
||||||
self.record_enable_time: datetime | None = None
|
self.record_enable_time: datetime | None = None
|
||||||
|
self.record_grace_until: datetime | None = None
|
||||||
|
|
||||||
# `valid` segments are published with the segment's start time, so the
|
# `valid` segments are published with the segment's start time, so the
|
||||||
# gap between consecutive publishes can reach 2 * segment_time. Pad the
|
# gap between consecutive publishes can reach 2 * segment_time. Pad the
|
||||||
@@ -178,27 +182,38 @@ class CameraWatchdog(threading.Thread):
|
|||||||
# Status caching to reduce message volume
|
# Status caching to reduce message volume
|
||||||
self._last_detect_status: str | None = None
|
self._last_detect_status: str | None = None
|
||||||
self._last_record_status: str | None = None
|
self._last_record_status: str | None = None
|
||||||
self._last_status_update_time: float = 0.0
|
self._last_detect_status_update_time: float = 0.0
|
||||||
|
self._last_record_status_update_time: float = 0.0
|
||||||
|
|
||||||
def _send_detect_status(self, status: str, now: float) -> None:
|
def _send_detect_status(self, status: str, now: float) -> None:
|
||||||
"""Send detect status only if changed or retry_interval has elapsed."""
|
"""Send detect status only if changed or retry_interval has elapsed."""
|
||||||
if (
|
if (
|
||||||
status != self._last_detect_status
|
status != self._last_detect_status
|
||||||
or (now - self._last_status_update_time) >= self.sleeptime
|
or (now - self._last_detect_status_update_time) >= self.sleeptime
|
||||||
):
|
):
|
||||||
self.requestor.send_data(f"{self.config.name}/status/detect", status)
|
self.requestor.send_data(f"{self.config.name}/status/detect", status)
|
||||||
self._last_detect_status = status
|
self._last_detect_status = status
|
||||||
self._last_status_update_time = now
|
self._last_detect_status_update_time = now
|
||||||
|
|
||||||
def _send_record_status(self, status: str, now: float) -> None:
|
def _send_record_status(self, status: str, now: float) -> None:
|
||||||
"""Send record status only if changed or retry_interval has elapsed."""
|
"""Send record status only if changed or retry_interval has elapsed."""
|
||||||
if (
|
if (
|
||||||
status != self._last_record_status
|
status != self._last_record_status
|
||||||
or (now - self._last_status_update_time) >= self.sleeptime
|
or (now - self._last_record_status_update_time) >= self.sleeptime
|
||||||
):
|
):
|
||||||
self.requestor.send_data(f"{self.config.name}/status/record", status)
|
self.requestor.send_data(f"{self.config.name}/status/record", status)
|
||||||
self._last_record_status = status
|
self._last_record_status = status
|
||||||
self._last_status_update_time = now
|
self._last_record_status_update_time = now
|
||||||
|
|
||||||
|
def _send_roles_offline(self, roles: list[CameraRoleEnum], now: float) -> None:
|
||||||
|
"""Send offline status for each role of a restarted ffmpeg process."""
|
||||||
|
for role in roles:
|
||||||
|
if role == CameraRoleEnum.record:
|
||||||
|
self._send_record_status("offline", now)
|
||||||
|
else:
|
||||||
|
self.requestor.send_data(
|
||||||
|
f"{self.config.name}/status/{role.value}", "offline"
|
||||||
|
)
|
||||||
|
|
||||||
def _check_config_updates(self) -> dict[str, list[str]]:
|
def _check_config_updates(self) -> dict[str, list[str]]:
|
||||||
"""Check for config updates and return the update dict."""
|
"""Check for config updates and return the update dict."""
|
||||||
@@ -280,6 +295,7 @@ class CameraWatchdog(threading.Thread):
|
|||||||
self.latest_valid_segment_time = 0
|
self.latest_valid_segment_time = 0
|
||||||
self.latest_invalid_segment_time = 0
|
self.latest_invalid_segment_time = 0
|
||||||
self.latest_cache_segment_time = 0
|
self.latest_cache_segment_time = 0
|
||||||
|
self.record_grace_until = None
|
||||||
self.record_enable_time = datetime.now().astimezone(UTC)
|
self.record_enable_time = datetime.now().astimezone(UTC)
|
||||||
last_restart_time = datetime.now().timestamp()
|
last_restart_time = datetime.now().timestamp()
|
||||||
continue
|
continue
|
||||||
@@ -294,6 +310,7 @@ class CameraWatchdog(threading.Thread):
|
|||||||
self.latest_valid_segment_time = 0
|
self.latest_valid_segment_time = 0
|
||||||
self.latest_invalid_segment_time = 0
|
self.latest_invalid_segment_time = 0
|
||||||
self.latest_cache_segment_time = 0
|
self.latest_cache_segment_time = 0
|
||||||
|
self.record_grace_until = None
|
||||||
self.record_enable_time = datetime.now().astimezone(UTC)
|
self.record_enable_time = datetime.now().astimezone(UTC)
|
||||||
else:
|
else:
|
||||||
self.logger.debug(f"Disabling camera {self.config.name}")
|
self.logger.debug(f"Disabling camera {self.config.name}")
|
||||||
@@ -318,6 +335,7 @@ class CameraWatchdog(threading.Thread):
|
|||||||
self.latest_valid_segment_time = 0
|
self.latest_valid_segment_time = 0
|
||||||
self.latest_invalid_segment_time = 0
|
self.latest_invalid_segment_time = 0
|
||||||
self.latest_cache_segment_time = 0
|
self.latest_cache_segment_time = 0
|
||||||
|
self.record_grace_until = None
|
||||||
self.record_enable_time = datetime.now().astimezone(UTC)
|
self.record_enable_time = datetime.now().astimezone(UTC)
|
||||||
last_restart_time = datetime.now().timestamp()
|
last_restart_time = datetime.now().timestamp()
|
||||||
self.was_record_enabled_in_config = record_enabled_in_config
|
self.was_record_enabled_in_config = record_enabled_in_config
|
||||||
@@ -404,11 +422,16 @@ class CameraWatchdog(threading.Thread):
|
|||||||
if self.config.record.enabled and "record" in p["roles"]:
|
if self.config.record.enabled and "record" in p["roles"]:
|
||||||
now_utc = datetime.now().astimezone(UTC)
|
now_utc = datetime.now().astimezone(UTC)
|
||||||
|
|
||||||
# Check if we're within the grace period after enabling recording
|
# ffmpeg needs time to create a first segment after
|
||||||
# Grace period: 90 seconds allows time for ffmpeg to start and create first segment
|
# recording is enabled and after a restart
|
||||||
in_grace_period = self.record_enable_time is not None and (
|
in_grace_period = (
|
||||||
now_utc - self.record_enable_time
|
self.record_enable_time is not None
|
||||||
) < timedelta(seconds=90)
|
and (now_utc - self.record_enable_time)
|
||||||
|
< timedelta(seconds=RECORD_GRACE_SECONDS)
|
||||||
|
) or (
|
||||||
|
self.record_grace_until is not None
|
||||||
|
and now_utc < self.record_grace_until
|
||||||
|
)
|
||||||
|
|
||||||
latest_cache_dt = (
|
latest_cache_dt = (
|
||||||
datetime.fromtimestamp(self.latest_cache_segment_time, tz=UTC)
|
datetime.fromtimestamp(self.latest_cache_segment_time, tz=UTC)
|
||||||
@@ -445,8 +468,9 @@ class CameraWatchdog(threading.Thread):
|
|||||||
<= self.latest_invalid_segment_time
|
<= self.latest_invalid_segment_time
|
||||||
)
|
)
|
||||||
invalid_stale = invalid_stale_condition
|
invalid_stale = invalid_stale_condition
|
||||||
|
stale = cache_stale or valid_stale or invalid_stale
|
||||||
|
|
||||||
if cache_stale or valid_stale or invalid_stale:
|
if stale and can_restart:
|
||||||
if cache_stale:
|
if cache_stale:
|
||||||
reason = "No new recording segments were created"
|
reason = "No new recording segments were created"
|
||||||
elif valid_stale:
|
elif valid_stale:
|
||||||
@@ -466,23 +490,23 @@ class CameraWatchdog(threading.Thread):
|
|||||||
ffmpeg_process=p["process"],
|
ffmpeg_process=p["process"],
|
||||||
)
|
)
|
||||||
|
|
||||||
for role in p["roles"]:
|
self._send_roles_offline(p["roles"], now)
|
||||||
self.requestor.send_data(
|
|
||||||
f"{self.config.name}/status/{role.value}", "offline"
|
self.record_grace_until = now_utc + timedelta(
|
||||||
)
|
seconds=RECORD_GRACE_SECONDS
|
||||||
|
)
|
||||||
|
last_restart_time = now
|
||||||
|
|
||||||
continue
|
continue
|
||||||
else:
|
elif not stale:
|
||||||
self._send_record_status("online", now)
|
if poll is None:
|
||||||
|
self._send_record_status("online", now)
|
||||||
p["latest_segment_time"] = self.latest_cache_segment_time
|
p["latest_segment_time"] = self.latest_cache_segment_time
|
||||||
|
|
||||||
if poll is None:
|
if poll is None:
|
||||||
continue
|
continue
|
||||||
|
|
||||||
for role in p["roles"]:
|
self._send_roles_offline(p["roles"], now)
|
||||||
self.requestor.send_data(
|
|
||||||
f"{self.config.name}/status/{role.value}", "offline"
|
|
||||||
)
|
|
||||||
|
|
||||||
p["logpipe"].dump()
|
p["logpipe"].dump()
|
||||||
p["process"] = start_or_restart_ffmpeg(
|
p["process"] = start_or_restart_ffmpeg(
|
||||||
|
|||||||
@@ -54,8 +54,11 @@ export class FrigateApp {
|
|||||||
});
|
});
|
||||||
|
|
||||||
await this.ws.install(this.page);
|
await this.ws.install(this.page);
|
||||||
await this.media.install();
|
|
||||||
await this.api.install(overrides);
|
await this.api.install(overrides);
|
||||||
|
// media goes last so its per-event routes win over the broader
|
||||||
|
// `**/api/events**` list route, which otherwise answers thumbnail and
|
||||||
|
// snapshot requests with the events JSON
|
||||||
|
await this.media.install();
|
||||||
}
|
}
|
||||||
|
|
||||||
/** Navigate to a page. Always call installDefaults() first. */
|
/** Navigate to a page. Always call installDefaults() first. */
|
||||||
|
|||||||
@@ -52,6 +52,9 @@ function deepMerge<T extends Record<string, unknown>>(
|
|||||||
export const BASE_CONFIG = {
|
export const BASE_CONFIG = {
|
||||||
...configSnapshot,
|
...configSnapshot,
|
||||||
version: "0.15.0-test",
|
version: "0.15.0-test",
|
||||||
|
// injected by the /config endpoint rather than the Pydantic model, so it
|
||||||
|
// is absent from the snapshot
|
||||||
|
plus: { enabled: false },
|
||||||
cameras: {
|
cameras: {
|
||||||
...configSnapshot.cameras,
|
...configSnapshot.cameras,
|
||||||
front_door: {
|
front_door: {
|
||||||
|
|||||||
@@ -249,8 +249,9 @@ export class MediaMocker {
|
|||||||
}),
|
}),
|
||||||
);
|
);
|
||||||
|
|
||||||
// Event thumbnails
|
// Event thumbnails. The explore grid and detail dialog request .webp,
|
||||||
await this.page.route("**/api/events/*/thumbnail.jpg**", (route) =>
|
// everything else requests .jpg.
|
||||||
|
await this.page.route("**/api/events/*/thumbnail.{jpg,webp}**", (route) =>
|
||||||
route.fulfill({
|
route.fulfill({
|
||||||
contentType: "image/png",
|
contentType: "image/png",
|
||||||
body: PLACEHOLDER_PNG,
|
body: PLACEHOLDER_PNG,
|
||||||
|
|||||||
@@ -216,6 +216,18 @@ test.describe("Explore — content @high", () => {
|
|||||||
// Similarity search URL param
|
// Similarity search URL param
|
||||||
// ---------------------------------------------------------------------------
|
// ---------------------------------------------------------------------------
|
||||||
|
|
||||||
|
test.describe("Explore: back button @high", () => {
|
||||||
|
test("direct visits do not show a back button", async ({ frigateApp }) => {
|
||||||
|
await frigateApp.goto("/explore?labels=person");
|
||||||
|
await expect(frigateApp.page.getByLabel("Labels").first()).toBeVisible({
|
||||||
|
timeout: 10_000,
|
||||||
|
});
|
||||||
|
await expect(
|
||||||
|
frigateApp.page.getByRole("button", { name: "Go back" }),
|
||||||
|
).toHaveCount(0);
|
||||||
|
});
|
||||||
|
});
|
||||||
|
|
||||||
test.describe("Explore — similarity search (desktop) @high", () => {
|
test.describe("Explore — similarity search (desktop) @high", () => {
|
||||||
test.skip(
|
test.skip(
|
||||||
({ frigateApp }) => frigateApp.isMobile,
|
({ frigateApp }) => frigateApp.isMobile,
|
||||||
@@ -263,3 +275,71 @@ test.describe("Explore — mobile @high @mobile", () => {
|
|||||||
await expect(searchInput).toBeFocused();
|
await expect(searchInput).toBeFocused();
|
||||||
});
|
});
|
||||||
});
|
});
|
||||||
|
|
||||||
|
// ---------------------------------------------------------------------------
|
||||||
|
// Frigate+ submission — desktop only
|
||||||
|
// The detail dialog's previous/next arrows only render on desktop.
|
||||||
|
// ---------------------------------------------------------------------------
|
||||||
|
|
||||||
|
test.describe("Explore — Frigate+ submission (desktop) @high", () => {
|
||||||
|
test.skip(
|
||||||
|
({ frigateApp }) => frigateApp.isMobile,
|
||||||
|
"Detail dialog navigation arrows are desktop-only",
|
||||||
|
);
|
||||||
|
|
||||||
|
test("in-flight submission does not mark the next tracked object as submitted", async ({
|
||||||
|
frigateApp,
|
||||||
|
}) => {
|
||||||
|
await frigateApp.installDefaults({ config: { plus: { enabled: true } } });
|
||||||
|
const page = frigateApp.page;
|
||||||
|
|
||||||
|
// Hold the submission open so it is still in flight while the user moves
|
||||||
|
// on to the next tracked object.
|
||||||
|
let releaseSubmission: () => void = () => {};
|
||||||
|
const submissionHeld = new Promise<void>((resolve) => {
|
||||||
|
releaseSubmission = resolve;
|
||||||
|
});
|
||||||
|
let submissions = 0;
|
||||||
|
await page.route("**/api/events/*/plus", async (route) => {
|
||||||
|
submissions += 1;
|
||||||
|
await submissionHeld;
|
||||||
|
await route.fulfill({ json: { success: true } });
|
||||||
|
});
|
||||||
|
|
||||||
|
await frigateApp.goto("/explore?labels=person");
|
||||||
|
|
||||||
|
const firstResult = page.locator("[data-start]").first();
|
||||||
|
await expect(firstResult).toBeVisible({ timeout: 10_000 });
|
||||||
|
await firstResult.click();
|
||||||
|
|
||||||
|
// The label being confirmed is rendered in a <code> tag inside the
|
||||||
|
// "Is this object a <label>?" question.
|
||||||
|
const dialog = page.getByRole("dialog");
|
||||||
|
await expect(dialog.locator("code")).toHaveText("person");
|
||||||
|
|
||||||
|
await dialog.getByRole("button", { name: "Yes", exact: true }).click();
|
||||||
|
await expect.poll(() => submissions, { timeout: 5_000 }).toBe(1);
|
||||||
|
|
||||||
|
await page.getByRole("button", { name: "Next tracked object" }).click();
|
||||||
|
await expect(dialog.locator("code")).toHaveText("car");
|
||||||
|
|
||||||
|
const submissionLanded = page.waitForResponse(/\/api\/events\/.*\/plus/);
|
||||||
|
releaseSubmission();
|
||||||
|
await submissionLanded;
|
||||||
|
// two frames is enough for React to flush the response handler's state
|
||||||
|
// updates, so the assertions below can't pass by racing ahead of them
|
||||||
|
await page.evaluate(
|
||||||
|
() =>
|
||||||
|
new Promise((resolve) =>
|
||||||
|
requestAnimationFrame(() => requestAnimationFrame(resolve)),
|
||||||
|
),
|
||||||
|
);
|
||||||
|
|
||||||
|
// The car was never submitted, so its question must be untouched.
|
||||||
|
expect(submissions).toBe(1);
|
||||||
|
await expect(dialog.getByText("Submitted")).toHaveCount(0);
|
||||||
|
await expect(
|
||||||
|
dialog.getByRole("button", { name: "Yes", exact: true }),
|
||||||
|
).toBeVisible();
|
||||||
|
});
|
||||||
|
});
|
||||||
|
|||||||
@@ -30,40 +30,51 @@ function groupedFacesMock() {
|
|||||||
});
|
});
|
||||||
}
|
}
|
||||||
|
|
||||||
async function installGroupedFaces(app: FrigateApp) {
|
const GROUPED_EVENT = {
|
||||||
|
id: GROUPED_EVENT_ID,
|
||||||
|
label: "person",
|
||||||
|
sub_label: null,
|
||||||
|
camera: "front_door",
|
||||||
|
start_time: 1775487131.3863528,
|
||||||
|
end_time: 1775487161.3863528,
|
||||||
|
false_positive: false,
|
||||||
|
zones: ["front_yard"],
|
||||||
|
thumbnail: null,
|
||||||
|
has_clip: true,
|
||||||
|
has_snapshot: true,
|
||||||
|
retain_indefinitely: false,
|
||||||
|
plus_id: null,
|
||||||
|
model_hash: "abc123",
|
||||||
|
detector_type: "cpu",
|
||||||
|
model_type: "ssd",
|
||||||
|
data: {
|
||||||
|
top_score: 0.92,
|
||||||
|
score: 0.92,
|
||||||
|
region: [0.1, 0.1, 0.5, 0.8],
|
||||||
|
box: [0.2, 0.15, 0.45, 0.75],
|
||||||
|
area: 0.18,
|
||||||
|
ratio: 0.6,
|
||||||
|
type: "object",
|
||||||
|
path_data: [],
|
||||||
|
},
|
||||||
|
};
|
||||||
|
|
||||||
|
async function installGroupedFaces(
|
||||||
|
app: FrigateApp,
|
||||||
|
opts: { withEventIds?: boolean } = {},
|
||||||
|
) {
|
||||||
await app.api.install({
|
await app.api.install({
|
||||||
events: [
|
events: [GROUPED_EVENT],
|
||||||
{
|
|
||||||
id: GROUPED_EVENT_ID,
|
|
||||||
label: "person",
|
|
||||||
sub_label: null,
|
|
||||||
camera: "front_door",
|
|
||||||
start_time: 1775487131.3863528,
|
|
||||||
end_time: 1775487161.3863528,
|
|
||||||
false_positive: false,
|
|
||||||
zones: ["front_yard"],
|
|
||||||
thumbnail: null,
|
|
||||||
has_clip: true,
|
|
||||||
has_snapshot: true,
|
|
||||||
retain_indefinitely: false,
|
|
||||||
plus_id: null,
|
|
||||||
model_hash: "abc123",
|
|
||||||
detector_type: "cpu",
|
|
||||||
model_type: "ssd",
|
|
||||||
data: {
|
|
||||||
top_score: 0.92,
|
|
||||||
score: 0.92,
|
|
||||||
region: [0.1, 0.1, 0.5, 0.8],
|
|
||||||
box: [0.2, 0.15, 0.45, 0.75],
|
|
||||||
area: 0.18,
|
|
||||||
ratio: 0.6,
|
|
||||||
type: "object",
|
|
||||||
path_data: [],
|
|
||||||
},
|
|
||||||
},
|
|
||||||
],
|
|
||||||
faces: groupedFacesMock(),
|
faces: groupedFacesMock(),
|
||||||
});
|
});
|
||||||
|
|
||||||
|
// api-mocker does not cover /api/event_ids, which the card needs to link to
|
||||||
|
// Explore. Registered after install so it takes precedence.
|
||||||
|
if (opts.withEventIds) {
|
||||||
|
await app.page.route("**/api/event_ids**", (route) =>
|
||||||
|
route.fulfill({ json: [GROUPED_EVENT] }),
|
||||||
|
);
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
async function openGroupedFaceDialog(app: FrigateApp): Promise<Locator> {
|
async function openGroupedFaceDialog(app: FrigateApp): Promise<Locator> {
|
||||||
@@ -512,6 +523,34 @@ test.describe("FaceSelectionDialog @high", () => {
|
|||||||
});
|
});
|
||||||
});
|
});
|
||||||
|
|
||||||
|
test.describe("Face Library: return from Explore @high", () => {
|
||||||
|
test("Explore back button returns to an outlined collection", async ({
|
||||||
|
frigateApp,
|
||||||
|
}) => {
|
||||||
|
await installGroupedFaces(frigateApp, { withEventIds: true });
|
||||||
|
await frigateApp.goto("/faces");
|
||||||
|
|
||||||
|
// Mobile opens the collection as a MobilePage, which has no dialog role
|
||||||
|
const card = frigateApp.page
|
||||||
|
.locator('img[src*="clips/faces/train/"]')
|
||||||
|
.first()
|
||||||
|
.locator("xpath=..");
|
||||||
|
await card.click();
|
||||||
|
await frigateApp.page.getByLabel("View in Explore").click();
|
||||||
|
await expect(frigateApp.page).toHaveURL(
|
||||||
|
new RegExp(`/explore\\?event_id=${GROUPED_EVENT_ID}`),
|
||||||
|
);
|
||||||
|
|
||||||
|
const back = frigateApp.page.getByRole("button", { name: "Go back" });
|
||||||
|
await expect(back).toBeVisible({ timeout: 5_000 });
|
||||||
|
await back.click();
|
||||||
|
await expect(frigateApp.page).toHaveURL(/\/faces/);
|
||||||
|
|
||||||
|
await expect(card).toHaveClass(/outline-selected/, { timeout: 5_000 });
|
||||||
|
await expect(card).not.toHaveClass(/outline-selected/, { timeout: 5_000 });
|
||||||
|
});
|
||||||
|
});
|
||||||
|
|
||||||
test.describe("Face Library — mobile @high @mobile", () => {
|
test.describe("Face Library — mobile @high @mobile", () => {
|
||||||
test.skip(({ frigateApp }) => !frigateApp.isMobile, "Mobile-only");
|
test.skip(({ frigateApp }) => !frigateApp.isMobile, "Mobile-only");
|
||||||
|
|
||||||
|
|||||||
@@ -27,6 +27,7 @@
|
|||||||
<link rel="mask-icon" href="/images/branding/favicon.svg" color="#3b82f7" />
|
<link rel="mask-icon" href="/images/branding/favicon.svg" color="#3b82f7" />
|
||||||
<meta name="theme-color" content="#ffffff" media="(prefers-color-scheme: light)" />
|
<meta name="theme-color" content="#ffffff" media="(prefers-color-scheme: light)" />
|
||||||
<meta name="theme-color" content="#000000" media="(prefers-color-scheme: dark)" />
|
<meta name="theme-color" content="#000000" media="(prefers-color-scheme: dark)" />
|
||||||
|
<meta name="apple-mobile-web-app-status-bar-style" content="default" />
|
||||||
</head>
|
</head>
|
||||||
<body>
|
<body>
|
||||||
<div id="root"></div>
|
<div id="root"></div>
|
||||||
|
|||||||
@@ -27,6 +27,7 @@
|
|||||||
<link rel="mask-icon" href="/images/branding/favicon.svg" color="#3b82f7" />
|
<link rel="mask-icon" href="/images/branding/favicon.svg" color="#3b82f7" />
|
||||||
<meta name="theme-color" content="#ffffff" media="(prefers-color-scheme: light)" />
|
<meta name="theme-color" content="#ffffff" media="(prefers-color-scheme: light)" />
|
||||||
<meta name="theme-color" content="#000000" media="(prefers-color-scheme: dark)" />
|
<meta name="theme-color" content="#000000" media="(prefers-color-scheme: dark)" />
|
||||||
|
<meta name="apple-mobile-web-app-status-bar-style" content="default" />
|
||||||
</head>
|
</head>
|
||||||
<body>
|
<body>
|
||||||
<div id="root"></div>
|
<div id="root"></div>
|
||||||
|
|||||||
@@ -1952,7 +1952,11 @@
|
|||||||
"modelSizeLarge": "The 'large' model is optimized for multi-line license plates. The 'small' model provides better performance over 'large' and should be used unless your region uses multi-line plate formats."
|
"modelSizeLarge": "The 'large' model is optimized for multi-line license plates. The 'small' model provides better performance over 'large' and should be used unless your region uses multi-line plate formats."
|
||||||
},
|
},
|
||||||
"record": {
|
"record": {
|
||||||
"noRecordRole": "No streams have the record role defined. Recording will not function."
|
"noRecordRole": "No streams have the record role defined. Recording will not function.",
|
||||||
|
"profileBaseDisabled": "Recording is disabled in this camera's base config, so enabling it in a profile has no effect. Enable recording in the base config and use a profile to disable it instead."
|
||||||
|
},
|
||||||
|
"notifications": {
|
||||||
|
"profileBaseDisabled": "No cameras have notifications enabled in their base config, so enabling them in a profile has no effect. Enable notifications in the base config of at least one camera."
|
||||||
},
|
},
|
||||||
"birdseye": {
|
"birdseye": {
|
||||||
"objectsModeDetectDisabled": "Birdseye is set to 'objects' mode, but object detection is disabled for this camera. The camera will not appear in Birdseye."
|
"objectsModeDetectDisabled": "Birdseye is set to 'objects' mode, but object detection is disabled for this camera. The camera will not appear in Birdseye."
|
||||||
|
|||||||
@@ -233,6 +233,7 @@
|
|||||||
"detectHighCpuUsage": "{{camera}} has high detect CPU usage ({{detectAvg}}%)",
|
"detectHighCpuUsage": "{{camera}} has high detect CPU usage ({{detectAvg}}%)",
|
||||||
"healthy": "System is healthy",
|
"healthy": "System is healthy",
|
||||||
"reindexingEmbeddings": "Reindexing embeddings ({{processed}}% complete)",
|
"reindexingEmbeddings": "Reindexing embeddings ({{processed}}% complete)",
|
||||||
|
"reindexEmbeddingsFailed": "Reindexing embeddings failed, check the logs",
|
||||||
"cameraIsOffline": "{{camera}} is offline",
|
"cameraIsOffline": "{{camera}} is offline",
|
||||||
"detectIsSlow": "{{detect}} is slow ({{speed}} ms)",
|
"detectIsSlow": "{{detect}} is slow ({{speed}} ms)",
|
||||||
"detectIsVerySlow": "{{detect}} is very slow ({{speed}} ms)",
|
"detectIsVerySlow": "{{detect}} is very slow ({{speed}} ms)",
|
||||||
|
|||||||
@@ -455,5 +455,33 @@
|
|||||||
"basketball_bounce": "Kosárlabda pattogás",
|
"basketball_bounce": "Kosárlabda pattogás",
|
||||||
"sine_wave": "Szinusz hullám",
|
"sine_wave": "Szinusz hullám",
|
||||||
"echo": "Visszhang",
|
"echo": "Visszhang",
|
||||||
"cacophony": "Kakófónia"
|
"cacophony": "Kakófónia",
|
||||||
|
"squish": "Facsarás",
|
||||||
|
"drip": "Csöpögés",
|
||||||
|
"pour": "Öntés",
|
||||||
|
"fill": "Töltödés",
|
||||||
|
"spray": "permet",
|
||||||
|
"pump": "Pumpál",
|
||||||
|
"boiling": "Forr",
|
||||||
|
"sonar": "hanglokátor",
|
||||||
|
"whoosh": "suhogás",
|
||||||
|
"electronic_tuner": "Elektronikus hangoló",
|
||||||
|
"effects_unit": "Effektmodul",
|
||||||
|
"chorus_effect": "Kórus-effektus",
|
||||||
|
"bang": "Bumm",
|
||||||
|
"slap": "Pofon",
|
||||||
|
"whack": "Pofon",
|
||||||
|
"breaking": "Törés",
|
||||||
|
"whip": "Ostor",
|
||||||
|
"tearing": "Szakadás",
|
||||||
|
"ping": "Pattan",
|
||||||
|
"ding": "Csing",
|
||||||
|
"squeal": "Visítás",
|
||||||
|
"creak": "Nyikorgás",
|
||||||
|
"rustle": "Suhogás",
|
||||||
|
"whir": "Zúgás",
|
||||||
|
"shofar": "Sófar",
|
||||||
|
"clatter": "csörgés",
|
||||||
|
"sizzle": "Sistergés",
|
||||||
|
"sidetone": "Mellékhang"
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -75,5 +75,10 @@
|
|||||||
"enabled": {
|
"enabled": {
|
||||||
"label": "Hangról szövegre alakítás engedélyezése"
|
"label": "Hangról szövegre alakítás engedélyezése"
|
||||||
}
|
}
|
||||||
|
},
|
||||||
|
"zones": {
|
||||||
|
"speed_threshold": {
|
||||||
|
"label": "Minimum sebesség"
|
||||||
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|||||||
+289
-226
@@ -2,16 +2,16 @@
|
|||||||
"crying": "울음",
|
"crying": "울음",
|
||||||
"snoring": "코골이",
|
"snoring": "코골이",
|
||||||
"singing": "노래",
|
"singing": "노래",
|
||||||
"yell": "비명",
|
"yell": "고함",
|
||||||
"speech": "음성",
|
"speech": "음성",
|
||||||
"babbling": "옹알이",
|
"babbling": "옹알이",
|
||||||
"bicycle": "자전거",
|
"bicycle": "자전거",
|
||||||
"a_capella": "아카펠라",
|
"a_capella": "아카펠라",
|
||||||
"accelerating": "가속",
|
"accelerating": "가속음",
|
||||||
"accordion": "아코디언",
|
"accordion": "아코디언",
|
||||||
"acoustic_guitar": "통기타",
|
"acoustic_guitar": "어쿠스틱 기타",
|
||||||
"car": "차량",
|
"car": "승용차",
|
||||||
"motorcycle": "원동기",
|
"motorcycle": "오토바이",
|
||||||
"bus": "버스",
|
"bus": "버스",
|
||||||
"train": "기차",
|
"train": "기차",
|
||||||
"boat": "보트",
|
"boat": "보트",
|
||||||
@@ -22,26 +22,26 @@
|
|||||||
"sheep": "양",
|
"sheep": "양",
|
||||||
"skateboard": "스케이트보드",
|
"skateboard": "스케이트보드",
|
||||||
"door": "문",
|
"door": "문",
|
||||||
"mouse": "마우스",
|
"mouse": "생쥐",
|
||||||
"keyboard": "키보드",
|
"keyboard": "키보드",
|
||||||
"sink": "싱크대",
|
"sink": "싱크대",
|
||||||
"blender": "블렌더",
|
"blender": "믹서기",
|
||||||
"clock": "시계",
|
"clock": "시계",
|
||||||
"scissors": "가위",
|
"scissors": "가위",
|
||||||
"hair_dryer": "헤어 드라이어",
|
"hair_dryer": "헤어드라이어",
|
||||||
"toothbrush": "칫솔",
|
"toothbrush": "칫솔",
|
||||||
"vehicle": "탈 것",
|
"vehicle": "탈것",
|
||||||
"animal": "동물",
|
"animal": "동물",
|
||||||
"bark": "짖는 소리",
|
"bark": "짖는 소리",
|
||||||
"goat": "염소",
|
"goat": "염소",
|
||||||
"bellow": "포효",
|
"bellow": "큰 외침",
|
||||||
"whoop": "환성",
|
"whoop": "환호",
|
||||||
"whispering": "속삭임",
|
"whispering": "속삭임",
|
||||||
"laughter": "웃음",
|
"laughter": "웃음",
|
||||||
"snicker": "낄낄 웃음",
|
"snicker": "낄낄거림",
|
||||||
"sigh": "한숨",
|
"sigh": "한숨",
|
||||||
"choir": "합창",
|
"choir": "합창",
|
||||||
"yodeling": "요들링",
|
"yodeling": "요들",
|
||||||
"chant": "성가",
|
"chant": "성가",
|
||||||
"mantra": "만트라",
|
"mantra": "만트라",
|
||||||
"child_singing": "어린이 노래",
|
"child_singing": "어린이 노래",
|
||||||
@@ -55,271 +55,271 @@
|
|||||||
"wheeze": "헐떡임",
|
"wheeze": "헐떡임",
|
||||||
"gasp": "헐떡임",
|
"gasp": "헐떡임",
|
||||||
"pant": "거친숨",
|
"pant": "거친숨",
|
||||||
"snort": "코골이",
|
"snort": "코웃음",
|
||||||
"cough": "기침",
|
"cough": "기침",
|
||||||
"throat_clearing": "목 긁는 소리",
|
"throat_clearing": "목 가다듬는 소리",
|
||||||
"sneeze": "재채기",
|
"sneeze": "재채기",
|
||||||
"sniff": "훌쩍",
|
"sniff": "훌쩍임",
|
||||||
"run": "달리기",
|
"run": "달리기",
|
||||||
"shuffle": "임의 재생",
|
"shuffle": "발 끄는 소리",
|
||||||
"footsteps": "발소리",
|
"footsteps": "발소리",
|
||||||
"chewing": "씹는 소리",
|
"chewing": "씹는 소리",
|
||||||
"biting": "치는 소리",
|
"biting": "깨무는 소리",
|
||||||
"gargling": "가글",
|
"gargling": "가글 소리",
|
||||||
"stomach_rumble": "배 꼬르륵",
|
"stomach_rumble": "배 꼬르륵 소리",
|
||||||
"burping": "트림",
|
"burping": "트림",
|
||||||
"camera": "카메라",
|
"camera": "카메라",
|
||||||
"hiccup": "딸꾹질",
|
"hiccup": "딸꾹질",
|
||||||
"fart": "방귀",
|
"fart": "방귀",
|
||||||
"hands": "손",
|
"hands": "손 소리",
|
||||||
"finger_snapping": "손가락 튕기기",
|
"finger_snapping": "손가락 튕기기",
|
||||||
"clapping": "박수",
|
"clapping": "박수",
|
||||||
"heartbeat": "심장 박동",
|
"heartbeat": "심장 박동",
|
||||||
"heart_murmur": "심장 잡음",
|
"heart_murmur": "심장 잡음",
|
||||||
"cheering": "환호",
|
"cheering": "환호",
|
||||||
"applause": "환호",
|
"applause": "박수갈채",
|
||||||
"chatter": "수다",
|
"chatter": "수다",
|
||||||
"crowd": "군중",
|
"crowd": "군중 소리",
|
||||||
"children_playing": "놀고 있는 아이들",
|
"children_playing": "놀고 있는 아이들",
|
||||||
"pets": "반려동물",
|
"pets": "반려동물",
|
||||||
"yip": "깽깽거림",
|
"yip": "깽깽거림",
|
||||||
"howl": "하울링",
|
"howl": "하울링",
|
||||||
"bow_wow": "짖는 소리",
|
"bow_wow": "멍멍",
|
||||||
"growling": "으르렁거림",
|
"growling": "으르렁거림",
|
||||||
"whimper_dog": "낑낑거림",
|
"whimper_dog": "낑낑거림",
|
||||||
"purr": "가르릉거림",
|
"purr": "골골송",
|
||||||
"meow": "야옹",
|
"meow": "야옹",
|
||||||
"hiss": "하악질",
|
"hiss": "하악질",
|
||||||
"caterwaul": "발정기 울음",
|
"caterwaul": "발정기 고양이 울음",
|
||||||
"livestock": "가축",
|
"livestock": "가축",
|
||||||
"clip_clop": "딸깍딸깍",
|
"clip_clop": "다각다각 소리",
|
||||||
"neigh": "말 울음소리",
|
"neigh": "말 울음소리",
|
||||||
"cattle": "소",
|
"cattle": "소",
|
||||||
"moo": "음메",
|
"moo": "음메",
|
||||||
"cowbell": "워낭 소리",
|
"cowbell": "워낭 소리",
|
||||||
"pig": "돼지",
|
"pig": "돼지",
|
||||||
"oink": "꿀꿀거림",
|
"oink": "꿀꿀",
|
||||||
"bleat": "메에",
|
"bleat": "매애",
|
||||||
"fowl": "새",
|
"fowl": "조류",
|
||||||
"chicken": "닭",
|
"chicken": "닭",
|
||||||
"cluck": "닭 울음소리",
|
"cluck": "꼬꼬댁",
|
||||||
"cock_a_doodle_doo": "꼬꼬댁",
|
"cock_a_doodle_doo": "꼬끼오",
|
||||||
"turkey": "칠면조",
|
"turkey": "칠면조",
|
||||||
"gobble": "칠면조 울음소리",
|
"gobble": "칠면조 울음소리",
|
||||||
"duck": "오리",
|
"duck": "오리",
|
||||||
"quack": "오리 울음소리",
|
"quack": "꽥꽥",
|
||||||
"goose": "거위",
|
"goose": "거위",
|
||||||
"honk": "거위 울음소리",
|
"honk": "거위 울음소리",
|
||||||
"wild_animals": "야생 동물",
|
"wild_animals": "야생 동물",
|
||||||
"roaring_cats": "맹수 포효",
|
"roaring_cats": "맹수의 포효",
|
||||||
"roar": "포효",
|
"roar": "포효",
|
||||||
"chirp": "새 울음소리",
|
"chirp": "새 지저귐",
|
||||||
"squawk": "지저귐",
|
"squawk": "새의 큰 울음소리",
|
||||||
"pigeon": "비둘기",
|
"pigeon": "비둘기",
|
||||||
"coo": "비둘기 울음소리",
|
"coo": "구구",
|
||||||
"crow": "까마귀",
|
"crow": "까마귀",
|
||||||
"caw": "까마귀 울음소리",
|
"caw": "까악까악",
|
||||||
"owl": "부엉이",
|
"owl": "부엉이",
|
||||||
"hoot": "부엉이 울음소리",
|
"hoot": "부엉이 울음소리",
|
||||||
"flapping_wings": "날갯짓",
|
"flapping_wings": "날갯짓 소리",
|
||||||
"bicycle_bell": "자전거 벨",
|
"bicycle_bell": "자전거 벨",
|
||||||
"tuning_fork": "소리굽쇠",
|
"tuning_fork": "소리굽쇠",
|
||||||
"chime": "차임벨",
|
"chime": "차임벨",
|
||||||
"wind_chime": "풍경",
|
"wind_chime": "풍경 소리",
|
||||||
"harmonica": "하모니카",
|
"harmonica": "하모니카",
|
||||||
"steel_guitar": "스틸 기타",
|
"steel_guitar": "스틸 기타",
|
||||||
"tapping": "두드림",
|
"tapping": "태핑",
|
||||||
"strum": "기타 스트로크",
|
"strum": "스트로크",
|
||||||
"banjo": "밴조",
|
"banjo": "밴조",
|
||||||
"sitar": "시타르",
|
"sitar": "시타르",
|
||||||
"mandolin": "만돌린",
|
"mandolin": "만돌린",
|
||||||
"zither": "지더",
|
"zither": "치터",
|
||||||
"ukulele": "우쿨렐레",
|
"ukulele": "우쿨렐레",
|
||||||
"lawn_mower": "잔디깎이",
|
"lawn_mower": "잔디깎이",
|
||||||
"chainsaw": "전기톱",
|
"chainsaw": "전기톱",
|
||||||
"medium_engine": "중형 엔진",
|
"medium_engine": "중형 엔진 소리",
|
||||||
"heavy_engine": "대형 엔진",
|
"heavy_engine": "대형 엔진 소리",
|
||||||
"engine_knocking": "엔진 노킹",
|
"engine_knocking": "엔진 노킹음",
|
||||||
"engine_starting": "엔진 시동",
|
"engine_starting": "시동 소리",
|
||||||
"idling": "공회전",
|
"idling": "공회전 소리",
|
||||||
"alarm": "알람",
|
"alarm": "경보",
|
||||||
"telephone": "전화",
|
"telephone": "전화벨",
|
||||||
"telephone_bell_ringing": "전화 소리",
|
"telephone_bell_ringing": "전화벨 울리는 소리",
|
||||||
"ringtone": "벨소리",
|
"ringtone": "벨소리",
|
||||||
"telephone_dialing": "전화 다이얼",
|
"telephone_dialing": "전화 다이얼 누르는 소리",
|
||||||
"dial_tone": "발신음",
|
"dial_tone": "발신음",
|
||||||
"cash_register": "금전 등록기",
|
"cash_register": "계산대 소리",
|
||||||
"printer": "프린터",
|
"printer": "프린터",
|
||||||
"single-lens_reflex_camera": "카메라 셔터",
|
"single-lens_reflex_camera": "DSLR 셔터음",
|
||||||
"tools": "도구들",
|
"tools": "공구 소리",
|
||||||
"hammer": "망치",
|
"hammer": "망치질 소리",
|
||||||
"jackhammer": "착암기",
|
"jackhammer": "착암기 소리",
|
||||||
"sawing": "톱질",
|
"sawing": "톱질 소리",
|
||||||
"filing": "연마",
|
"filing": "줄질 소리",
|
||||||
"sanding": "사포질",
|
"sanding": "사포질 소리",
|
||||||
"power_tool": "전동 도구",
|
"power_tool": "전동 공구 소리",
|
||||||
"drill": "드릴",
|
"drill": "드릴 소리",
|
||||||
"explosion": "폭발",
|
"explosion": "폭발음",
|
||||||
"gunshot": "총소리",
|
"gunshot": "총성",
|
||||||
"machine_gun": "기관총",
|
"machine_gun": "기관총 소리",
|
||||||
"fusillade": "연속 총성",
|
"fusillade": "일제 사격 소리",
|
||||||
"artillery_fire": "포격",
|
"artillery_fire": "포격 소리",
|
||||||
"cap_gun": "화약 총",
|
"cap_gun": "화약총 소리",
|
||||||
"fireworks": "불꽃놀이",
|
"fireworks": "폭죽 터지는 소리",
|
||||||
"firecracker": "폭죽",
|
"firecracker": "폭죽 소리",
|
||||||
"car_alarm": "차량 경보",
|
"car_alarm": "자동차 경보음",
|
||||||
"power_windows": "전동 창문",
|
"power_windows": "전동 파워 윈도우 소리",
|
||||||
"dogs": "개",
|
"dogs": "개",
|
||||||
"rats": "쥐",
|
"rats": "쥐들",
|
||||||
"insect": "벌래",
|
"insect": "곤충",
|
||||||
"mosquito": "모기",
|
"mosquito": "모기",
|
||||||
"fly": "파리",
|
"fly": "날벌레",
|
||||||
"frog": "개구리",
|
"frog": "개구리",
|
||||||
"snake": "뱀",
|
"snake": "뱀",
|
||||||
"music": "음악",
|
"music": "음악",
|
||||||
"musical_instrument": "악기",
|
"musical_instrument": "악기",
|
||||||
"guitar": "기타",
|
"guitar": "기타",
|
||||||
"electric_guitar": "전기기타",
|
"electric_guitar": "일렉트릭 기타",
|
||||||
"bass_guitar": "베이스 기타",
|
"bass_guitar": "베이스 기타",
|
||||||
"piano": "피아노",
|
"piano": "피아노",
|
||||||
"electric_piano": "전자 피아노",
|
"electric_piano": "전자 피아노",
|
||||||
"organ": "오르간",
|
"organ": "오르간",
|
||||||
"drum_kit": "드럼 키트",
|
"drum_kit": "드럼 키트",
|
||||||
"drum": "드럼",
|
"drum": "드럼",
|
||||||
"maraca": "마라카",
|
"maraca": "마라카스",
|
||||||
"marimba": "마림바",
|
"marimba": "마림바",
|
||||||
"orchestra": "오케스트라",
|
"orchestra": "오케스트라",
|
||||||
"trumpet": "트럼펫",
|
"trumpet": "트럼펫",
|
||||||
"trombone": "트럼본",
|
"trombone": "트롬본",
|
||||||
"violin": "바이올린",
|
"violin": "바이올린",
|
||||||
"cello": "첼로",
|
"cello": "첼로",
|
||||||
"flute": "플루트",
|
"flute": "플루트",
|
||||||
"saxophone": "섹소폰",
|
"saxophone": "색소폰",
|
||||||
"clarinet": "클라리넷",
|
"clarinet": "클라리넷",
|
||||||
"harp": "하프",
|
"harp": "하프",
|
||||||
"bell": "벨",
|
"bell": "벨",
|
||||||
"jingle_bell": "징글 벨",
|
"jingle_bell": "방울 소리",
|
||||||
"bagpipes": "백파이프",
|
"bagpipes": "백파이프",
|
||||||
"pop_music": "팝뮤직",
|
"pop_music": "팝 음악",
|
||||||
"hip_hop_music": "힙팝 뮤직",
|
"hip_hop_music": "힙합 음악",
|
||||||
"beatboxing": "비트박스",
|
"beatboxing": "비트박스",
|
||||||
"rock_music": "록 뮤직",
|
"rock_music": "록 음악",
|
||||||
"punk_rock": "펑크 록",
|
"punk_rock": "펑크 록",
|
||||||
"rock_and_roll": "록엔 롤",
|
"rock_and_roll": "로큰롤",
|
||||||
"rhythm_and_blues": "R&B",
|
"rhythm_and_blues": "R&B",
|
||||||
"soul_music": "소울 뮤직",
|
"soul_music": "소울 음악",
|
||||||
"swing_music": "스윙 뮤직",
|
"swing_music": "스윙 음악",
|
||||||
"bluegrass": "블루그레스",
|
"bluegrass": "블루그래스",
|
||||||
"jazz": "제즈",
|
"jazz": "재즈",
|
||||||
"disco": "디스코",
|
"disco": "디스코",
|
||||||
"classical_music": "클레식 음악",
|
"classical_music": "클래식 음악",
|
||||||
"opera": "오페라",
|
"opera": "오페라",
|
||||||
"techno": "테크노",
|
"techno": "테크노",
|
||||||
"electronic_dance_music": "Electronic Dance Music(EDM)",
|
"electronic_dance_music": "EDM",
|
||||||
"blues": "블루즈",
|
"blues": "블루스",
|
||||||
"music_for_children": "유아동 음악",
|
"music_for_children": "어린이 음악",
|
||||||
"background_music": "배경음악(BGM)",
|
"background_music": "배경음악(BGM)",
|
||||||
"soundtrack_music": "사운드트렉 음악",
|
"soundtrack_music": "사운드트랙",
|
||||||
"lullaby": "자장가",
|
"lullaby": "자장가",
|
||||||
"christmas_music": "크리스마스 음악",
|
"christmas_music": "크리스마스 캐럴",
|
||||||
"steam": "증기",
|
"steam": "증기 소리",
|
||||||
"fire": "화재",
|
"fire": "불소리",
|
||||||
"motorboat": "모터보트",
|
"motorboat": "모터보트",
|
||||||
"ship": "선박",
|
"ship": "선박",
|
||||||
"motor_vehicle": "자동차",
|
"motor_vehicle": "자동차",
|
||||||
"race_car": "경주용 자동차",
|
"race_car": "레이싱 카",
|
||||||
"truck": "트럭",
|
"truck": "트럭",
|
||||||
"air_brake": "에어 브레이크",
|
"air_brake": "에어 브레이크",
|
||||||
"reversing_beeps": "후진 경보음",
|
"reversing_beeps": "후진 경고음",
|
||||||
"ice_cream_truck": "아이스크림 트럭",
|
"ice_cream_truck": "아이스크림 트럭 음악",
|
||||||
"emergency_vehicle": "긴급 차량",
|
"emergency_vehicle": "긴급 차량",
|
||||||
"police_car": "경찰차",
|
"police_car": "경찰차",
|
||||||
"ambulance": "구급차",
|
"ambulance": "구급차",
|
||||||
"fire_engine": "소방차",
|
"fire_engine": "소방차",
|
||||||
"rail_transport": "철도 운송",
|
"rail_transport": "철도",
|
||||||
"railroad_car": "철도 차량",
|
"railroad_car": "기차 칸",
|
||||||
"subway": "지하철",
|
"subway": "지하철",
|
||||||
"aircraft": "비행기",
|
"aircraft": "항공기",
|
||||||
"aircraft_engine": "항공기 엔진",
|
"aircraft_engine": "항공기 엔진 소리",
|
||||||
"jet_engine": "제트 엔진",
|
"jet_engine": "제트 엔진 소리",
|
||||||
"propeller": "프로펠러",
|
"propeller": "프로펠러 소리",
|
||||||
"helicopter": "헬리콥터",
|
"helicopter": "헬리콥터",
|
||||||
"fixed-wing_aircraft": "고정익 항공기",
|
"fixed-wing_aircraft": "고정익 비행기",
|
||||||
"engine": "엔진",
|
"engine": "엔진 소리",
|
||||||
"light_engine": "라이트 엔진",
|
"light_engine": "소형 엔진 소리",
|
||||||
"doorbell": "초인종",
|
"doorbell": "초인종",
|
||||||
"ding-dong": "딩동",
|
"ding-dong": "딩동",
|
||||||
"sliding_door": "슬라이딩 도어",
|
"sliding_door": "미닫이문",
|
||||||
"knock": "노크",
|
"knock": "노크 소리",
|
||||||
"tap": "탭",
|
"tap": "두드리는 소리",
|
||||||
"cupboard_open_or_close": "찬장 열기 또는 닫기",
|
"cupboard_open_or_close": "찬장 여닫는 소리",
|
||||||
"drawer_open_or_close": "서랍 열기 또는 닫기",
|
"drawer_open_or_close": "서랍 여닫는 소리",
|
||||||
"microwave_oven": "전자 레인지",
|
"microwave_oven": "전자레인지",
|
||||||
"water_tap": "수도꼭지",
|
"water_tap": "수도꼭지",
|
||||||
"bathtub": "목욕통",
|
"bathtub": "욕조",
|
||||||
"toilet_flush": "변기 물내림",
|
"toilet_flush": "변기 물내리는 소리",
|
||||||
"electric_toothbrush": "전동 치솔",
|
"electric_toothbrush": "전동 칫솔",
|
||||||
"vacuum_cleaner": "진공 청소기",
|
"vacuum_cleaner": "진공청소기",
|
||||||
"zipper": "지퍼",
|
"zipper": "지퍼 여닫는 소리",
|
||||||
"coin": "동전",
|
"coin": "동전 부딪치는 소리",
|
||||||
"electric_shaver": "전동 면도기",
|
"electric_shaver": "전기면도기",
|
||||||
"typing": "타이핑",
|
"typing": "타자 소리",
|
||||||
"typewriter": "타자기",
|
"typewriter": "타자기",
|
||||||
"computer_keyboard": "컴퓨터 키보드",
|
"computer_keyboard": "컴퓨터 키보드",
|
||||||
"alarm_clock": "알람 시계",
|
"alarm_clock": "알람 시계",
|
||||||
"siren": "사이렌",
|
"siren": "사이렌",
|
||||||
"civil_defense_siren": "민방위 경보 사이렌",
|
"civil_defense_siren": "민방위 경보",
|
||||||
"buzzer": "버저",
|
"buzzer": "버저",
|
||||||
"fire_alarm": "화재 경보(기)",
|
"fire_alarm": "화재경보기",
|
||||||
"whistle": "휘슬",
|
"whistle": "호루라기",
|
||||||
"ratchet": "라쳇",
|
"ratchet": "라쳇 소리",
|
||||||
"tick": "틱",
|
"tick": "똑딱 소리",
|
||||||
"tick-tock": "틱톡",
|
"tick-tock": "똑딱똑딱",
|
||||||
"gears": "기어",
|
"gears": "톱니바퀴 소리",
|
||||||
"pulleys": "도르래",
|
"pulleys": "도르래 소리",
|
||||||
"sewing_machine": "미싱기",
|
"sewing_machine": "재봉틀",
|
||||||
"air_conditioning": "A/C",
|
"air_conditioning": "에어컨 소리",
|
||||||
"eruption": "분화",
|
"eruption": "분화 소리",
|
||||||
"boom": "붐",
|
"boom": "쿵 하는 소리",
|
||||||
"wood": "나무",
|
"wood": "나무 부딪치는 소리",
|
||||||
"splinter": "가시",
|
"splinter": "나무 쪼개지는 소리",
|
||||||
"glass": "유리",
|
"glass": "유리 소리",
|
||||||
"silence": "침묵",
|
"silence": "무음",
|
||||||
"sound_effect": "효과 음",
|
"sound_effect": "효과음",
|
||||||
"environmental_noise": "환경 소음",
|
"environmental_noise": "주변 환경 소음",
|
||||||
"white_noise": "백색 소음",
|
"white_noise": "백색 소음",
|
||||||
"pink_noise": "핑크 노이즈",
|
"pink_noise": "핑크 노이즈",
|
||||||
"television": "텔레비젼(TV)",
|
"television": "텔레비전 소리",
|
||||||
"radio": "라디오",
|
"radio": "라디오 소리",
|
||||||
"splash": "스플레쉬",
|
"splash": "첨벙거리는 소리",
|
||||||
"spray": "스프레이",
|
"spray": "스프레이 분사음",
|
||||||
"pump": "펌프",
|
"pump": "펌프 소리",
|
||||||
"arrow": "화살",
|
"arrow": "화살 날아가는 소리",
|
||||||
"electronic_tuner": "전자 튜너",
|
"electronic_tuner": "전자 조율기",
|
||||||
"smash": "스메쉬",
|
"smash": "박살 나는 소리",
|
||||||
"clicking": "클릭킹",
|
"clicking": "딸깍거리는 소리",
|
||||||
"noise": "소음",
|
"noise": "소음",
|
||||||
"mains_hum": "험 노이즈",
|
"mains_hum": "전원 험 노이즈",
|
||||||
"outside": "외부",
|
"outside": "실외",
|
||||||
"reggae": "레게",
|
"reggae": "레게",
|
||||||
"french_horn": "프렌치 호른",
|
"french_horn": "프렌치 호른",
|
||||||
"traffic_noise": "교통 소음",
|
"traffic_noise": "교통 소음",
|
||||||
"rain": "비",
|
"rain": "빗소리",
|
||||||
"raindrop": "빗물방울",
|
"raindrop": "빗방울 소리",
|
||||||
"rain_on_surface": "표면에 비",
|
"rain_on_surface": "표면에 떨어지는 빗소리",
|
||||||
"stream": "개울 하천",
|
"stream": "개울물 소리",
|
||||||
"wedding_music": "웨딩 음악",
|
"wedding_music": "결혼식 음악",
|
||||||
"harmonic": "하모닉",
|
"harmonic": "하모닉스",
|
||||||
"ocean": "오션",
|
"ocean": "바다 소리",
|
||||||
"vibration": "진동",
|
"vibration": "진동음",
|
||||||
"new-age_music": "뉴 에이지 음악",
|
"new-age_music": "뉴에이지 음악",
|
||||||
"tubular_bells": "튜블러 벨",
|
"tubular_bells": "튜블러 벨",
|
||||||
"middle_eastern_music": "중동 음악",
|
"middle_eastern_music": "중동 음악",
|
||||||
"mechanical_fan": "기계식 팬",
|
"mechanical_fan": "선풍기 소리",
|
||||||
"angry_music": "앵그리 뮤직",
|
"angry_music": "격렬한 음악",
|
||||||
"rattle": "방울",
|
"rattle": "덜그럭거리는 소리",
|
||||||
"water": "물",
|
"water": "물소리",
|
||||||
"waterfall": "폭포",
|
"waterfall": "폭포 소리",
|
||||||
"music_of_asia": "아시아 음악",
|
"music_of_asia": "아시아 음악",
|
||||||
"brass_instrument": "금관악기",
|
"brass_instrument": "금관악기",
|
||||||
"folk_music": "포크 음악",
|
"folk_music": "포크 음악",
|
||||||
@@ -334,106 +334,169 @@
|
|||||||
"flamenco": "플라멩코",
|
"flamenco": "플라멩코",
|
||||||
"bass_drum": "베이스 드럼",
|
"bass_drum": "베이스 드럼",
|
||||||
"thunderstorm": "뇌우",
|
"thunderstorm": "뇌우",
|
||||||
"thunder": "우뢰",
|
"thunder": "천둥",
|
||||||
"vibraphone": "비브라폰",
|
"vibraphone": "비브라폰",
|
||||||
"synthesizer": "신디사이저",
|
"synthesizer": "신디사이저",
|
||||||
"smoke_detector": "연기 감지기",
|
"smoke_detector": "연기 감지기",
|
||||||
"busy_signal": "통화 중 신호",
|
"busy_signal": "통화 중 신호음",
|
||||||
"shuffling_cards": "카드 섞기",
|
"shuffling_cards": "카드 섞는 소리",
|
||||||
"train_whistle": "기차 경적 소리",
|
"train_whistle": "기차 호루라기 소리",
|
||||||
"traditional_music": "전통 음악",
|
"traditional_music": "전통 음악",
|
||||||
"independent_music": "인디 음악",
|
"independent_music": "인디 음악",
|
||||||
"song": "노래",
|
"song": "노래",
|
||||||
"theme_music": "테마 음악",
|
"theme_music": "테마 음악",
|
||||||
"house_music": "하우스 뮤직",
|
"house_music": "하우스 음악",
|
||||||
"beep": "삑",
|
"beep": "삑 소리",
|
||||||
"ping": "핑",
|
"ping": "핑 소리",
|
||||||
"dance_music": "트랜스 음악",
|
"dance_music": "댄스 음악",
|
||||||
"whale_vocalization": "고래 울음소리",
|
"whale_vocalization": "고래 소리",
|
||||||
"music_of_bollywood": "볼리우드 음악",
|
"music_of_bollywood": "볼리우드 음악",
|
||||||
"ska": "스카(Ska)",
|
"ska": "스카",
|
||||||
"train_horn": "기차 경적",
|
"train_horn": "기차 경적",
|
||||||
"wind_instrument": "관악기",
|
"wind_instrument": "관악기",
|
||||||
"funk": "펑키 재즈",
|
"funk": "펑크",
|
||||||
"glockenspiel": "카리용",
|
"glockenspiel": "글로켄슈필",
|
||||||
"waves": "파도",
|
"waves": "파도 소리",
|
||||||
"tabla": "판자",
|
"tabla": "타블라",
|
||||||
"ding": "딩",
|
"ding": "딩동 소리",
|
||||||
"chorus_effect": "코러스 효과",
|
"chorus_effect": "코러스 이펙트",
|
||||||
"basketball_bounce": "농구공 튕김",
|
"basketball_bounce": "농구공 튀기는 소리",
|
||||||
"air_horn": "에어혼",
|
"air_horn": "에어혼",
|
||||||
"hi_hat": "하이햇",
|
"hi_hat": "하이햇",
|
||||||
"cacophony": "불협화음",
|
"cacophony": "불협화음 소음",
|
||||||
"drum_machine": "드럼 머신",
|
"drum_machine": "드럼 머신",
|
||||||
"drum_and_bass": "드럼 앤 베이스",
|
"drum_and_bass": "드럼 앤 베이스",
|
||||||
"drum_roll": "드럼 롤",
|
"drum_roll": "드럼 롤",
|
||||||
"echo": "에코",
|
"echo": "에코",
|
||||||
"heavy_metal": "헤비메탈",
|
"heavy_metal": "헤비메탈",
|
||||||
"progressive_rock": "프로그레시브 록",
|
"progressive_rock": "프로그레시브 록",
|
||||||
"cricket": "크리켓(귀뚜라미)",
|
"cricket": "귀뚜라미",
|
||||||
"music_of_latin_america": "라틴 아메리카 음악",
|
"music_of_latin_america": "라틴 음악",
|
||||||
"sampler": "샘플러",
|
"sampler": "샘플러",
|
||||||
"hammond_organ": "해먼드 오르간",
|
"hammond_organ": "해먼드 오르간",
|
||||||
"theremin": "테레민",
|
"theremin": "테레민",
|
||||||
"shofar": "쇼파르",
|
"shofar": "쇼파르(양각 나팔)",
|
||||||
"mechanisms": "메커니즘",
|
"mechanisms": "기계 장치",
|
||||||
"pulse": "펄스",
|
"pulse": "펄스음",
|
||||||
"liquid": "액체",
|
"liquid": "액체 소리",
|
||||||
"squish": "으깨다",
|
"squish": "질척거리는 소리",
|
||||||
"effects_unit": "이펙트 유닛",
|
"effects_unit": "이펙터",
|
||||||
"christian_music": "기독교 음악",
|
"christian_music": "기독교 음악",
|
||||||
"gospel_music": "가스펠 음악",
|
"gospel_music": "가스펠",
|
||||||
"dubstep": "덥스텝",
|
"dubstep": "덥스텝",
|
||||||
"double_bass": "더블베이스 (콘트라베이스)",
|
"double_bass": "콘트라베이스",
|
||||||
"wind": "바람",
|
"wind": "바람 소리",
|
||||||
"wood_block": "나무 블록",
|
"wood_block": "우드블록",
|
||||||
"country": "컨트리 음악",
|
"country": "컨트리 음악",
|
||||||
"cutlery": "식기류",
|
"cutlery": "수저 부딪치는 소리",
|
||||||
"afrobeat": "아프로비트",
|
"afrobeat": "아프로비트",
|
||||||
"trance_music": "트랜스 음악",
|
"trance_music": "트랜스 음악",
|
||||||
"percussion": "타악기",
|
"percussion": "타악기",
|
||||||
"electronic_music": "전자 음악",
|
"electronic_music": "전자 음악",
|
||||||
"hum": "험",
|
"hum": "웅웅거리는 험 노이즈",
|
||||||
"zing": "징",
|
"zing": "핑 날아가는 소리",
|
||||||
"boing": "보잉",
|
"boing": "용수철 뿅 소리",
|
||||||
"crunch": "크런치",
|
"crunch": "바삭거리는 소리",
|
||||||
"sine_wave": "사인파",
|
"sine_wave": "사인파 톤",
|
||||||
"gurgling": "꿀꺽꿀꺽",
|
"gurgling": "졸졸 흐르는 소리",
|
||||||
"electronic_organ": "전자 오르간",
|
"electronic_organ": "전자 오르간",
|
||||||
"grunge": "그런지",
|
"grunge": "그런지",
|
||||||
"pizzicato": "꼬집다",
|
"pizzicato": "피치카토",
|
||||||
"carnatic_music": "카르나틱 음악",
|
"carnatic_music": "카르나틱 음악",
|
||||||
"cymbal": "심벌즈",
|
"cymbal": "심벌즈",
|
||||||
"harpsichord": "하프시코드",
|
"harpsichord": "하프시코드",
|
||||||
"plucked_string_instrument": "뜯어서 연주하는 현악기",
|
"plucked_string_instrument": "발현악기",
|
||||||
"happy_music": "행복한 음악",
|
"happy_music": "밝은 음악",
|
||||||
"rustling_leaves": "바스락거리는 나뭇잎",
|
"rustling_leaves": "바스락거리는 나뭇잎",
|
||||||
"wind_noise": "바람 소리",
|
"wind_noise": "풍절음",
|
||||||
"tire_squeal": "타이어 마찰음",
|
"tire_squeal": "타이어 마찰음",
|
||||||
"buzz": "버저 소리",
|
"buzz": "윙윙거리는 소리",
|
||||||
"foghorn": "포그혼",
|
"foghorn": "무무표지(안개 경적)",
|
||||||
"vocal_music": "보컬 음악",
|
"vocal_music": "보컬 음악",
|
||||||
"chirp_tone": "짹짹거리는 소리",
|
"chirp_tone": "전자 짹짹음",
|
||||||
"plop": "퐁당",
|
"plop": "퐁당 소리",
|
||||||
"keys_jangling": "딸랑거리는 열쇠 꾸러미",
|
"keys_jangling": "열쇠 흔들리는 소리",
|
||||||
"didgeridoo": "디저리두",
|
"didgeridoo": "디저리두",
|
||||||
"tender_music": "Tender 음악",
|
"tender_music": "부드러운 음악",
|
||||||
"electronica": "일렉트로니카",
|
"electronica": "일렉트로니카",
|
||||||
"exciting_music": "신나는 음악",
|
"exciting_music": "신나는 음악",
|
||||||
"bang": "쾅",
|
"bang": "쾅 소리",
|
||||||
"slap": "슬랩",
|
"slap": "찰싹 때리는 소리",
|
||||||
"video_game_music": "비디오 게임 음악",
|
"video_game_music": "게임 음악",
|
||||||
"sad_music": "Sad 음악",
|
"sad_music": "슬픈 음악",
|
||||||
"scary_music": "스카이 음악",
|
"scary_music": "공포 음악",
|
||||||
"squeal": "끼익거리는 소리",
|
"squeal": "끼익 비명/마찰음",
|
||||||
"clatter": "덜그럭거리는 소리",
|
"clatter": "달그락거리는 소리",
|
||||||
"sidetone": "측음",
|
"sidetone": "측음",
|
||||||
"slosh": "출렁이는 물소리",
|
"slosh": "출렁거리는 소리",
|
||||||
"sonar": "소나",
|
"sonar": "소나(음파 탐지기)",
|
||||||
"patter": "타닥거리는 소리",
|
"patter": "톡톡 두드리는 소리",
|
||||||
"inside": "내부",
|
"inside": "실내",
|
||||||
"rimshot": "림샷",
|
"rimshot": "림샷",
|
||||||
"dishes": "그릇 소리",
|
"dishes": "식기 달그락거리는 소리",
|
||||||
"crack": "쾅 하는 소리",
|
"crack": "쩍 갈라지는 소리",
|
||||||
"burst": "터지는 소리"
|
"burst": "파열음",
|
||||||
|
"croak": "개구리 울음소리",
|
||||||
|
"gong": "징",
|
||||||
|
"steelpan": "스틸팬",
|
||||||
|
"bowed_string_instrument": "찰현악기",
|
||||||
|
"string_section": "현악 섹션",
|
||||||
|
"church_bell": "교회 종소리",
|
||||||
|
"singing_bowl": "싱잉볼",
|
||||||
|
"scratching": "스크래칭",
|
||||||
|
"psychedelic_rock": "사이케델릭 록",
|
||||||
|
"crackle": "타닥거리는 소리",
|
||||||
|
"sailboat": "돛단배",
|
||||||
|
"rowboat": "노 젓는 배",
|
||||||
|
"toot": "빵빵 소리",
|
||||||
|
"skidding": "미끄러지는 소리",
|
||||||
|
"car_passing_by": "지나가는 차 소리",
|
||||||
|
"train_wheels_squealing": "기차 바퀴 마찰음",
|
||||||
|
"dental_drill's_drill": "치과용 드릴",
|
||||||
|
"slam": "쾅 닫는 소리",
|
||||||
|
"squeak": "찍찍거리는 소리",
|
||||||
|
"chopping": "도마 칼질 소리",
|
||||||
|
"frying": "튀기는 소리",
|
||||||
|
"writing": "글씨 쓰는 소리",
|
||||||
|
"steam_whistle": "증기 호각",
|
||||||
|
"chop": "장작 패는 소리",
|
||||||
|
"chink": "짤랑거리는 소리",
|
||||||
|
"shatter": "유리 와장창 깨지는 소리",
|
||||||
|
"static": "지지직거리는 잡음",
|
||||||
|
"field_recording": "현장 녹음 소리",
|
||||||
|
"scream": "외마디 비명",
|
||||||
|
"chird": "새소리",
|
||||||
|
"change_ringing": "타종 소리",
|
||||||
|
"drip": "똑똑 떨어지는 소리",
|
||||||
|
"pour": "콸콸 붓는 소리",
|
||||||
|
"trickle": "졸졸 흐르는 소리",
|
||||||
|
"gush": "솟구치는 소리",
|
||||||
|
"fill": "물 차오르는 소리",
|
||||||
|
"stir": "휘젓는 소리",
|
||||||
|
"boiling": "보글보글 끓는 소리",
|
||||||
|
"whoosh": "쉭 지나가는 소리",
|
||||||
|
"thump": "털썩 하는 소리",
|
||||||
|
"thunk": "퍽 하는 소리",
|
||||||
|
"whack": "탁 치는 소리",
|
||||||
|
"breaking": "부서지는 소리",
|
||||||
|
"bouncing": "통통 튀는 소리",
|
||||||
|
"whip": "채찍 소리",
|
||||||
|
"flap": "펄럭이는 소리",
|
||||||
|
"scratch": "긁는 소리",
|
||||||
|
"scrape": "끼익 긁히는 소리",
|
||||||
|
"rub": "문지르는 소리",
|
||||||
|
"roll": "데굴데굴 굴러가는 소리",
|
||||||
|
"crushing": "으스러지는 소리",
|
||||||
|
"crumpling": "구겨지는 소리",
|
||||||
|
"tearing": "찢어지는 소리",
|
||||||
|
"clang": "쨍그랑 소리",
|
||||||
|
"creak": "삐걱거리는 소리",
|
||||||
|
"rustle": "바스락거리는 소리",
|
||||||
|
"whir": "붕붕거리는 소리",
|
||||||
|
"sizzle": "지글지글 소리",
|
||||||
|
"clickety_clack": "덜컹덜컹 소리",
|
||||||
|
"rumble": "우르릉거리는 소리",
|
||||||
|
"reverberation": "잔향음",
|
||||||
|
"distortion": "왜곡(디스토션)",
|
||||||
|
"throbbing": "두근거리는 맥박음"
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -2,39 +2,39 @@
|
|||||||
"readTheDocumentation": "문서 읽기",
|
"readTheDocumentation": "문서 읽기",
|
||||||
"time": {
|
"time": {
|
||||||
"untilForTime": "{{time}}까지",
|
"untilForTime": "{{time}}까지",
|
||||||
"untilForRestart": "Frigate가 재시작될 때 까지.",
|
"untilForRestart": "Frigate가 재시작될 때까지.",
|
||||||
"10minutes": "10분",
|
"10minutes": "10분",
|
||||||
"12hours": "12시간",
|
"12hours": "12시간",
|
||||||
"1hour": "1시간",
|
"1hour": "1시간",
|
||||||
"24hours": "24시간",
|
"24hours": "24시간",
|
||||||
"30minutes": "30분",
|
"30minutes": "30분",
|
||||||
"5minutes": "5분",
|
"5minutes": "5분",
|
||||||
"untilRestart": "재시작 될 때까지",
|
"untilRestart": "재시작될 때까지",
|
||||||
"ago": "{{timeAgo}} 전",
|
"ago": "{{timeAgo}} 전",
|
||||||
"justNow": "방금",
|
"justNow": "방금 전",
|
||||||
"today": "오늘",
|
"today": "오늘",
|
||||||
"yesterday": "어제",
|
"yesterday": "어제",
|
||||||
"last7": "최근 7일",
|
"last7": "최근 7일",
|
||||||
"last14": "최근 14일",
|
"last14": "최근 14일",
|
||||||
"last30": "최근 30일",
|
"last30": "최근 30일",
|
||||||
"thisWeek": "이번 주",
|
"thisWeek": "이번 주",
|
||||||
"lastWeek": "저번 주",
|
"lastWeek": "지난 주",
|
||||||
"thisMonth": "이번 달",
|
"thisMonth": "이번 달",
|
||||||
"lastMonth": "저번 달",
|
"lastMonth": "지난 달",
|
||||||
"pm": "오후",
|
"pm": "오후",
|
||||||
"am": "오전",
|
"am": "오전",
|
||||||
"yr": "{{time}}년",
|
"yr": "{{time}}년",
|
||||||
"year_other": "{{time}} 년",
|
"year_other": "{{time}}년",
|
||||||
"mo": "{{time}}월",
|
"mo": "{{time}}개월",
|
||||||
"month_other": "{{time}} 월",
|
"month_other": "{{time}}개월",
|
||||||
"d": "{{time}}일",
|
"d": "{{time}}일",
|
||||||
"day_other": "{{time}} 일",
|
"day_other": "{{time}}일",
|
||||||
"h": "{{time}}시",
|
"h": "{{time}}시간",
|
||||||
"hour_other": "{{time}} 시",
|
"hour_other": "{{time}}시간",
|
||||||
"m": "{{time}}분",
|
"m": "{{time}}분",
|
||||||
"minute_other": "{{time}} 분",
|
"minute_other": "{{time}}분",
|
||||||
"s": "{{time}}초",
|
"s": "{{time}}초",
|
||||||
"second_other": "{{time}} 초",
|
"second_other": "{{time}}초",
|
||||||
"formattedTimestamp": {
|
"formattedTimestamp": {
|
||||||
"12hour": "MMM d, h:mm:ss aaa",
|
"12hour": "MMM d, h:mm:ss aaa",
|
||||||
"24hour": "MMM d, HH:mm:ss"
|
"24hour": "MMM d, HH:mm:ss"
|
||||||
@@ -68,7 +68,7 @@
|
|||||||
"12hour": "MM-dd-yy-h-mm-ss-a",
|
"12hour": "MM-dd-yy-h-mm-ss-a",
|
||||||
"24hour": "MM-dd-yy-HH-mm-ss"
|
"24hour": "MM-dd-yy-HH-mm-ss"
|
||||||
},
|
},
|
||||||
"never": "한 번도 없음",
|
"never": "안 함",
|
||||||
"inProgress": "진행 중",
|
"inProgress": "진행 중",
|
||||||
"invalidStartTime": "잘못된 시작 시간",
|
"invalidStartTime": "잘못된 시작 시간",
|
||||||
"invalidEndTime": "잘못된 종료 시간"
|
"invalidEndTime": "잘못된 종료 시간"
|
||||||
@@ -76,25 +76,25 @@
|
|||||||
"notFound": {
|
"notFound": {
|
||||||
"title": "404",
|
"title": "404",
|
||||||
"documentTitle": "찾을 수 없음 - Frigate",
|
"documentTitle": "찾을 수 없음 - Frigate",
|
||||||
"desc": "페이지 찾을 수 없음"
|
"desc": "페이지를 찾을 수 없습니다"
|
||||||
},
|
},
|
||||||
"accessDenied": {
|
"accessDenied": {
|
||||||
"title": "접근 거부",
|
"title": "접근 거부",
|
||||||
"documentTitle": "접근 거부 - Frigate",
|
"documentTitle": "접근 거부 - Frigate",
|
||||||
"desc": "이 페이지 접근 권한이 없습니다."
|
"desc": "이 페이지를 볼 수 있는 권한이 없습니다."
|
||||||
},
|
},
|
||||||
"menu": {
|
"menu": {
|
||||||
"user": {
|
"user": {
|
||||||
"account": "계정",
|
"account": "계정",
|
||||||
"title": "사용자",
|
"title": "사용자",
|
||||||
"current": "현재 사용자:{{user}}",
|
"current": "현재 사용자: {{user}}",
|
||||||
"anonymous": "익명",
|
"anonymous": "익명",
|
||||||
"logout": "로그아웃",
|
"logout": "로그아웃",
|
||||||
"setPassword": "비밀번호 설정"
|
"setPassword": "비밀번호 설정"
|
||||||
},
|
},
|
||||||
"system": "시스템",
|
"system": "시스템",
|
||||||
"systemMetrics": "시스템 지표",
|
"systemMetrics": "시스템 지표",
|
||||||
"configuration": "설정",
|
"configuration": "설정 구성",
|
||||||
"systemLogs": "시스템 로그",
|
"systemLogs": "시스템 로그",
|
||||||
"settings": "설정",
|
"settings": "설정",
|
||||||
"configurationEditor": "설정 편집기",
|
"configurationEditor": "설정 편집기",
|
||||||
@@ -103,7 +103,7 @@
|
|||||||
"en": "English (영어)",
|
"en": "English (영어)",
|
||||||
"es": "Español (스페인어)",
|
"es": "Español (스페인어)",
|
||||||
"zhCN": "简体中文 (중국어 간체)",
|
"zhCN": "简体中文 (중국어 간체)",
|
||||||
"hi": "हिन्दी (힌두어)",
|
"hi": "हिन्दी (힌디어)",
|
||||||
"fr": "Français (프랑스어)",
|
"fr": "Français (프랑스어)",
|
||||||
"ar": "العربية (아랍어)",
|
"ar": "العربية (아랍어)",
|
||||||
"pt": "Português (포르투갈어)",
|
"pt": "Português (포르투갈어)",
|
||||||
@@ -117,7 +117,7 @@
|
|||||||
"sv": "Svenska (스웨덴어)",
|
"sv": "Svenska (스웨덴어)",
|
||||||
"cs": "Čeština (체코어)",
|
"cs": "Čeština (체코어)",
|
||||||
"nb": "Norsk Bokmål (노르웨이어 보크몰)",
|
"nb": "Norsk Bokmål (노르웨이어 보크몰)",
|
||||||
"ko": "한국어",
|
"ko": "한국어 (Korean)",
|
||||||
"vi": "Tiếng Việt (베트남어)",
|
"vi": "Tiếng Việt (베트남어)",
|
||||||
"fa": "فارسی (페르시아어)",
|
"fa": "فارسی (페르시아어)",
|
||||||
"pl": "Polski (폴란드어)",
|
"pl": "Polski (폴란드어)",
|
||||||
@@ -126,7 +126,7 @@
|
|||||||
"el": "Ελληνικά (그리스어)",
|
"el": "Ελληνικά (그리스어)",
|
||||||
"ro": "Română (루마니아어)",
|
"ro": "Română (루마니아어)",
|
||||||
"hu": "Magyar (헝가리어)",
|
"hu": "Magyar (헝가리어)",
|
||||||
"fi": "Suomi (Finnish)",
|
"fi": "Suomi (핀란드어)",
|
||||||
"da": "Dansk (덴마크어)",
|
"da": "Dansk (덴마크어)",
|
||||||
"sk": "Slovenčina (슬로바키아어)",
|
"sk": "Slovenčina (슬로바키아어)",
|
||||||
"yue": "粵語 (광둥어)",
|
"yue": "粵語 (광둥어)",
|
||||||
@@ -140,9 +140,12 @@
|
|||||||
"id": "Bahasa Indonesia (인도네시아어)",
|
"id": "Bahasa Indonesia (인도네시아어)",
|
||||||
"ur": "اردو (우르두어)",
|
"ur": "اردو (우르두어)",
|
||||||
"withSystem": {
|
"withSystem": {
|
||||||
"label": "시스템 설정 언어 사용"
|
"label": "시스템 언어 설정 사용"
|
||||||
},
|
},
|
||||||
"hr": "Hrvatski (크로아티아어)"
|
"hr": "Hrvatski (크로아티아어)",
|
||||||
|
"zhHant": "繁體中文 (중국어 번체)",
|
||||||
|
"be": "Беларуская (벨라루스어)",
|
||||||
|
"bs": "Bosanski (보스니아어)"
|
||||||
},
|
},
|
||||||
"appearance": "화면 설정",
|
"appearance": "화면 설정",
|
||||||
"darkMode": {
|
"darkMode": {
|
||||||
@@ -150,17 +153,17 @@
|
|||||||
"light": "라이트",
|
"light": "라이트",
|
||||||
"dark": "다크",
|
"dark": "다크",
|
||||||
"withSystem": {
|
"withSystem": {
|
||||||
"label": "시스템 설정에 따라 설정"
|
"label": "시스템 설정에 따라 라이트/다크 모드 전환"
|
||||||
}
|
}
|
||||||
},
|
},
|
||||||
"withSystem": "시스템",
|
"withSystem": "시스템",
|
||||||
"theme": {
|
"theme": {
|
||||||
"label": "테마",
|
"label": "테마",
|
||||||
"blue": "파랑",
|
"blue": "파랑",
|
||||||
"green": "녹색",
|
"green": "초록",
|
||||||
"nord": "노드 (Nord)",
|
"nord": "노드 (Nord)",
|
||||||
"red": "빨강",
|
"red": "빨강",
|
||||||
"highcontrast": "고 대비",
|
"highcontrast": "고대비",
|
||||||
"default": "기본값"
|
"default": "기본값"
|
||||||
},
|
},
|
||||||
"help": "도움말",
|
"help": "도움말",
|
||||||
@@ -174,17 +177,17 @@
|
|||||||
"allCameras": "모든 카메라",
|
"allCameras": "모든 카메라",
|
||||||
"cameras": {
|
"cameras": {
|
||||||
"title": "카메라",
|
"title": "카메라",
|
||||||
"count_other": "{{count}} 카메라"
|
"count_other": "{{count}}대 카메라"
|
||||||
}
|
}
|
||||||
},
|
},
|
||||||
"review": "다시보기",
|
"review": "다시보기",
|
||||||
"explore": "탐색",
|
"explore": "탐색",
|
||||||
"export": "내보내기",
|
"export": "내보내기",
|
||||||
"uiPlayground": "UI 실험실",
|
"uiPlayground": "UI 플레이그라운드",
|
||||||
"faceLibrary": "얼굴 라이브러리",
|
"faceLibrary": "얼굴 라이브러리",
|
||||||
"classification": "분류",
|
"classification": "분류",
|
||||||
"chat": "채팅",
|
"chat": "채팅",
|
||||||
"actions": "작업",
|
"actions": "동작",
|
||||||
"profiles": "프로필",
|
"profiles": "프로필",
|
||||||
"features": "기능"
|
"features": "기능"
|
||||||
},
|
},
|
||||||
@@ -210,14 +213,14 @@
|
|||||||
"back": "뒤로",
|
"back": "뒤로",
|
||||||
"hide": "{{item}} 숨기기",
|
"hide": "{{item}} 숨기기",
|
||||||
"show": "{{item}} 표시",
|
"show": "{{item}} 표시",
|
||||||
"ID": "아이디",
|
"ID": "ID",
|
||||||
"none": "없음",
|
"none": "없음",
|
||||||
"all": "전체",
|
"all": "전체",
|
||||||
"other": "그 외"
|
"other": "기타"
|
||||||
},
|
},
|
||||||
"button": {
|
"button": {
|
||||||
"apply": "적용",
|
"apply": "적용",
|
||||||
"reset": "리셋",
|
"reset": "재설정",
|
||||||
"done": "완료",
|
"done": "완료",
|
||||||
"enabled": "활성화됨",
|
"enabled": "활성화됨",
|
||||||
"enable": "활성화",
|
"enable": "활성화",
|
||||||
@@ -229,38 +232,38 @@
|
|||||||
"close": "닫기",
|
"close": "닫기",
|
||||||
"copy": "복사",
|
"copy": "복사",
|
||||||
"back": "뒤로",
|
"back": "뒤로",
|
||||||
"history": "히스토리",
|
"history": "기록",
|
||||||
"fullscreen": "전체화면",
|
"fullscreen": "전체화면",
|
||||||
"exitFullscreen": "전체화면 나가기",
|
"exitFullscreen": "전체화면 종료",
|
||||||
"pictureInPicture": "화면 속 화면",
|
"pictureInPicture": "화면 속 화면 (PIP)",
|
||||||
"twoWayTalk": "양방향 말하기",
|
"twoWayTalk": "양방향 대화",
|
||||||
"cameraAudio": "카메라 오디오",
|
"cameraAudio": "카메라 오디오",
|
||||||
"on": "켜기",
|
"on": "켜짐",
|
||||||
"off": "끄기",
|
"off": "꺼짐",
|
||||||
"edit": "편집",
|
"edit": "수정",
|
||||||
"copyCoordinates": "코디네이트 복사",
|
"copyCoordinates": "좌표 복사",
|
||||||
"delete": "삭제",
|
"delete": "삭제",
|
||||||
"yes": "예",
|
"yes": "예",
|
||||||
"no": "아니오",
|
"no": "아니오",
|
||||||
"download": "다운로드",
|
"download": "다운로드",
|
||||||
"info": "정보",
|
"info": "정보",
|
||||||
"suspended": "일시 정지됨",
|
"suspended": "일시 중지됨",
|
||||||
"unsuspended": "재개",
|
"unsuspended": "재개",
|
||||||
"play": "재생",
|
"play": "재생",
|
||||||
"unselect": "선택 해제",
|
"unselect": "선택 해제",
|
||||||
"export": "내보내기",
|
"export": "내보내기",
|
||||||
"deleteNow": "바로 삭제하기",
|
"deleteNow": "지금 삭제",
|
||||||
"next": "다음",
|
"next": "다음",
|
||||||
"add": "추가",
|
"add": "추가",
|
||||||
"undo": "실행 취소",
|
"undo": "실행 취소",
|
||||||
"copiedToClipboard": "클립보드에 복사",
|
"copiedToClipboard": "클립보드에 복사됨",
|
||||||
"continue": "계속하기",
|
"continue": "계속",
|
||||||
"modified": "수정됨",
|
"modified": "수정됨",
|
||||||
"overridden": "재정의됨",
|
"overridden": "재정의됨",
|
||||||
"resetToGlobal": "글로벌 설정으로 재설정",
|
"resetToGlobal": "전역 설정으로 초기화",
|
||||||
"resetToDefault": "기본값으로 재설정",
|
"resetToDefault": "기본값으로 초기화",
|
||||||
"saveAll": "모두 저장",
|
"saveAll": "모두 저장",
|
||||||
"savingAll": "모두 저장 중. …",
|
"savingAll": "모두 저장 중…",
|
||||||
"undoAll": "모두 실행 취소",
|
"undoAll": "모두 실행 취소",
|
||||||
"applying": "적용 중…",
|
"applying": "적용 중…",
|
||||||
"retry": "재시도"
|
"retry": "재시도"
|
||||||
@@ -270,8 +273,8 @@
|
|||||||
"save": {
|
"save": {
|
||||||
"title": "저장",
|
"title": "저장",
|
||||||
"error": {
|
"error": {
|
||||||
"title": "설정 저장 실패: {{errorMessage}}",
|
"title": "설정 변경 저장 실패: {{errorMessage}}",
|
||||||
"noMessage": "설정 저장이 실패했습니다"
|
"noMessage": "설정 변경 저장에 실패했습니다"
|
||||||
},
|
},
|
||||||
"success": "설정 변경이 성공적으로 저장되었습니다."
|
"success": "설정 변경이 성공적으로 저장되었습니다."
|
||||||
}
|
}
|
||||||
@@ -279,34 +282,37 @@
|
|||||||
"role": {
|
"role": {
|
||||||
"title": "역할",
|
"title": "역할",
|
||||||
"admin": "관리자",
|
"admin": "관리자",
|
||||||
"viewer": "감시자",
|
"viewer": "뷰어",
|
||||||
"desc": "관리자는 Frigate UI에 모든 접근 권한이 있습니다. 감시자는 카메라 감시, 돌아보기, 과거 영상 조회만 가능합니다."
|
"desc": "관리자는 Frigate UI의 모든 기능에 대한 전체 접근 권한을 가집니다. 뷰어는 카메라 실시간 화면, 다시보기 항목, 이전 녹화 영상 조회만 가능합니다."
|
||||||
},
|
},
|
||||||
"pagination": {
|
"pagination": {
|
||||||
"label": "페이지 번호",
|
"label": "페이지 매김",
|
||||||
"previous": {
|
"previous": {
|
||||||
"title": "이전",
|
"title": "이전",
|
||||||
"label": "이전 페이지"
|
"label": "이전 페이지로 이동"
|
||||||
},
|
},
|
||||||
"next": {
|
"next": {
|
||||||
"title": "다음",
|
"title": "다음",
|
||||||
"label": "다음 페이지"
|
"label": "다음 페이지로 이동"
|
||||||
},
|
},
|
||||||
"more": "더 많은 페이지"
|
"more": "페이지 더보기"
|
||||||
},
|
},
|
||||||
"selectItem": "{{item}} 선택",
|
"selectItem": "{{item}} 선택",
|
||||||
"information": {
|
"information": {
|
||||||
"pixels": "{{area}}px"
|
"pixels": "{{area}}px"
|
||||||
},
|
},
|
||||||
"list": {
|
"list": {
|
||||||
"two": "{{0}}과 {{1}}",
|
"two": "{{0}} 및 {{1}}",
|
||||||
"many": "{{items}} 그리고 {{last}}",
|
"many": "{{items}} 및 {{last}}",
|
||||||
"separatorWithSpace": ", "
|
"separatorWithSpace": ", "
|
||||||
},
|
},
|
||||||
"field": {
|
"field": {
|
||||||
"optional": "선택",
|
"optional": "선택 사항",
|
||||||
"internalID": "Frigate 내부 ID는 구성 및 데이터베이스에서 사용됩니다"
|
"internalID": "Frigate 설정 및 데이터베이스에서 사용되는 내부 ID"
|
||||||
},
|
},
|
||||||
"no_items": "내역 없음",
|
"no_items": "항목 없음",
|
||||||
"validation_errors": "검증 오류"
|
"validation_errors": "유효성 검사 오류",
|
||||||
|
"credentialField": {
|
||||||
|
"savedPlaceholder": "저장됨 — 현재 값을 유지하려면 비워 두세요"
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -1,16 +1,16 @@
|
|||||||
{
|
{
|
||||||
"form": {
|
"form": {
|
||||||
"user": "사용자명",
|
"user": "사용자 이름",
|
||||||
"password": "비밀번호",
|
"password": "비밀번호",
|
||||||
"login": "로그인",
|
"login": "로그인",
|
||||||
"errors": {
|
"errors": {
|
||||||
"usernameRequired": "사용자명은 필수입니다",
|
"usernameRequired": "사용자 이름을 입력해야 합니다",
|
||||||
"passwordRequired": "비밀번호는 필수입니다",
|
"passwordRequired": "비밀번호를 입력해야 합니다",
|
||||||
"rateLimit": "너무 많이 시도했습니다. 다음에 다시 시도하세요.",
|
"rateLimit": "요청 횟수 제한을 초과했습니다. 잠시 후 다시 시도하세요.",
|
||||||
"loginFailed": "로그인 실패",
|
"loginFailed": "로그인에 실패했습니다",
|
||||||
"unknownError": "알려지지 않은 에러. 로그를 확인하세요.",
|
"unknownError": "알 수 없는 오류가 발생했습니다. 로그를 확인하세요.",
|
||||||
"webUnknownError": "알려지지 않은 에러. 콘솔 로그를 확인하세요."
|
"webUnknownError": "알 수 없는 오류가 발생했습니다. 콘솔 로그를 확인하세요."
|
||||||
},
|
},
|
||||||
"firstTimeLogin": "처음 로그인하시나요? 로그인 정보는 Frigate 로그에 있습니다."
|
"firstTimeLogin": "처음 로그인하시나요? 초기 로그인 정보는 Frigate 로그에 출력되어 있습니다."
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -2,86 +2,89 @@
|
|||||||
"group": {
|
"group": {
|
||||||
"label": "카메라 그룹",
|
"label": "카메라 그룹",
|
||||||
"add": "카메라 그룹 추가",
|
"add": "카메라 그룹 추가",
|
||||||
"edit": "카메라 그룹 편집",
|
"edit": "카메라 그룹 수정",
|
||||||
"delete": {
|
"delete": {
|
||||||
"label": "카메라 그룹 삭제",
|
"label": "카메라 그룹 삭제",
|
||||||
"confirm": {
|
"confirm": {
|
||||||
"title": "삭제 확인",
|
"title": "삭제 확인",
|
||||||
"desc": "정말로 카메라 그룹을 삭제하시겠습니까 <em>{{name}}</em>?"
|
"desc": "정말로 <em>{{name}}</em> 카메라 그룹을 삭제하시겠습니까?"
|
||||||
}
|
}
|
||||||
},
|
},
|
||||||
"name": {
|
"name": {
|
||||||
"label": "이름",
|
"label": "이름",
|
||||||
"placeholder": "이름을 입력하세요…",
|
"placeholder": "이름을 입력하세요…",
|
||||||
"errorMessage": {
|
"errorMessage": {
|
||||||
"mustLeastCharacters": "카메라 그룹 이름은 최소 2자 이상 써야합니다.",
|
"mustLeastCharacters": "카메라 그룹 이름은 최소 2자 이상이어야 합니다.",
|
||||||
"exists": "이미 존재하는 카메라 그룹 이름입니다.",
|
"exists": "이미 존재하는 카메라 그룹 이름입니다.",
|
||||||
"nameMustNotPeriod": "카메라 그룹 이름에 마침표(.)를 넣을 수 없습니다.",
|
"nameMustNotPeriod": "카메라 그룹 이름에는 마침표(.)를 포함할 수 없습니다.",
|
||||||
"invalid": "설정 불가능한 카메라 그룹 이름."
|
"invalid": "유효하지 않은 카메라 그룹 이름입니다."
|
||||||
}
|
}
|
||||||
},
|
},
|
||||||
"cameras": {
|
"cameras": {
|
||||||
"label": "카메라",
|
"label": "카메라",
|
||||||
"desc": "이 그룹에 넣을 카메라 선택하기."
|
"desc": "이 그룹에 포함할 카메라를 선택하세요."
|
||||||
},
|
},
|
||||||
"icon": "아이콘",
|
"icon": "아이콘",
|
||||||
"success": "카메라 그룹 {{name}} 저장되었습니다.",
|
"success": "카메라 그룹({{name}})이(가) 저장되었습니다.",
|
||||||
"camera": {
|
"camera": {
|
||||||
"birdseye": "버드아이",
|
"birdseye": "버드아이",
|
||||||
"setting": {
|
"setting": {
|
||||||
"label": "카메라 스트리밍 설정",
|
"label": "카메라 스트리밍 설정",
|
||||||
"title": "{{cameraName}} 스트리밍 설정",
|
"title": "{{cameraName}} 스트리밍 설정",
|
||||||
"desc": "카메라 그룹 대시보드의 실시간 스트리밍 옵션을 변경하세요. <em>이 설정은 기기/브라우저에 따라 다릅니다.</em>",
|
"desc": "카메라 그룹 대시보드의 실시간 스트리밍 옵션을 변경합니다. <em>이 설정은 기기 및 브라우저별로 개별 적용됩니다.</em>",
|
||||||
"audioIsAvailable": "이 카메라는 오디오 기능을 사용할 수 있습니다",
|
"audioIsAvailable": "이 스트림에서 오디오 기능을 사용할 수 있습니다",
|
||||||
"audioIsUnavailable": "이 카메라는 오디오 기능을 사용할 수 없습니다",
|
"audioIsUnavailable": "이 스트림에서는 오디오 기능을 사용할 수 없습니다",
|
||||||
"audio": {
|
"audio": {
|
||||||
"tips": {
|
"tips": {
|
||||||
"title": "오디오를 출력하려면 카메라가 지원하거나 go2rtc에서 설정해야합니다."
|
"title": "오디오를 출력하려면 카메라 자체에서 오디오를 지원하고 go2rtc에 해당 스트림이 설정되어 있어야 합니다."
|
||||||
}
|
}
|
||||||
},
|
},
|
||||||
"stream": "스트림",
|
"stream": "스트림",
|
||||||
"placeholder": "스트림 선택",
|
"placeholder": "스트림 선택",
|
||||||
"streamMethod": {
|
"streamMethod": {
|
||||||
"label": "스트리밍 방식",
|
"label": "스트리밍 방식",
|
||||||
"placeholder": "스트리밍 방식 선택",
|
"placeholder": "스트리밍 방식을 선택하세요",
|
||||||
"method": {
|
"method": {
|
||||||
"noStreaming": {
|
"noStreaming": {
|
||||||
"label": "스트리밍 없음",
|
"label": "스트리밍 없음",
|
||||||
"desc": "카메라 이미지는 1분에 한 번만 보여지며 라이브 스트리밍은 되지 않습니다."
|
"desc": "카메라 이미지가 1분에 한 번씩만 갱신되며 실시간 스트리밍은 작동하지 않습니다."
|
||||||
},
|
},
|
||||||
"smartStreaming": {
|
"smartStreaming": {
|
||||||
"label": "스마트 스트리밍 (추천함)",
|
"label": "스마트 스트리밍 (권장)",
|
||||||
"desc": "스마트 스트리밍은 감지되는 활동이 없을 때 대역폭과 자원을 절약하기 위해 1분마다 한 번 카메라 이미지를 업데이트합니다. 활동이 감지되면, 이미지는 자동으로 라이브 스트림으로 원활하게 전환됩니다."
|
"desc": "대역폭과 시스템 자원을 절약하기 위해 감지된 동작이 없을 때는 1분마다 이미지를 갱신합니다. 동작이 감지되면 자동으로 매끄럽게 라이브 스트림으로 전환됩니다."
|
||||||
},
|
},
|
||||||
"continuousStreaming": {
|
"continuousStreaming": {
|
||||||
"label": "지속적인 스트리밍",
|
"label": "지속적 스트리밍",
|
||||||
"desc": {
|
"desc": {
|
||||||
"title": "활동이 감지되지 않더라도 카메라 이미지가 대시보드에서 항상 실시간 스트림됩니다.",
|
"title": "동작이 감지되지 않더라도 대시보드에 카메라 화면이 표시될 때 항상 실시간 스트림으로 재생됩니다.",
|
||||||
"warning": "지속적인 스트리밍은 높은 대역폭 사용과 퍼포먼스 이슈를 발생할 수 있습니다. 사용에 주의해주세요."
|
"warning": "지속적 스트리밍은 네트워크 대역폭 사용량이 증가하고 성능 저하를 유발할 수 있으므로 주의하여 사용하세요."
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
},
|
},
|
||||||
"compatibilityMode": {
|
"compatibilityMode": {
|
||||||
"label": "호환 모드",
|
"label": "호환 모드",
|
||||||
"desc": "이 옵션은 카메라 라이브 스트림 화면의 색상이 왜곡 되었거나 이미지 오른쪽에 대각선이 나타날때만 사용하세요."
|
"desc": "카메라 라이브 스트림 화면에 색상 왜곡이 있거나 화면 우측에 대각선 줄이 나타나는 경우에만 이 옵션을 활성화하세요."
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
}
|
},
|
||||||
|
"showAll": "모든 카메라 그룹 표시",
|
||||||
|
"showLess": "간략히 표시",
|
||||||
|
"editGroups": "카메라 그룹 관리"
|
||||||
},
|
},
|
||||||
"debug": {
|
"debug": {
|
||||||
"options": {
|
"options": {
|
||||||
"label": "설정",
|
"label": "설정",
|
||||||
"title": "옵션",
|
"title": "옵션",
|
||||||
"showOptions": "옵션 보기",
|
"showOptions": "옵션 표시",
|
||||||
"hideOptions": "옵션 숨기기"
|
"hideOptions": "옵션 숨기기"
|
||||||
},
|
},
|
||||||
"boundingBox": "감지 영역 상자",
|
"boundingBox": "바운딩 박스 (객체 감지 영역)",
|
||||||
"timestamp": "시간 기록",
|
"timestamp": "타임스탬프",
|
||||||
"zones": "구역 (Zones)",
|
"zones": "구역 (Zones)",
|
||||||
"mask": "마스크",
|
"mask": "마스크 (Mask)",
|
||||||
"motion": "움직임",
|
"motion": "움직임 감지 (Motion)",
|
||||||
"regions": "영역 (Regions)",
|
"regions": "영역 (Regions)",
|
||||||
"paths": "경로"
|
"paths": "이동 경로 (Paths)"
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -1,41 +1,44 @@
|
|||||||
{
|
{
|
||||||
"restart": {
|
"restart": {
|
||||||
"title": "Frigate을 정말로 다시 시작할까요?",
|
"title": "Frigate을 정말로 다시 시작하시겠습니까?",
|
||||||
"button": "재시작",
|
"button": "재시작",
|
||||||
"restarting": {
|
"restarting": {
|
||||||
"title": "Frigate이 재시작 중입니다",
|
"title": "Frigate 재시작 중",
|
||||||
"content": "이 페이지는 {{countdown}} 뒤에 새로 고침 됩니다.",
|
"content": "이 페이지는 {{countdown}}초 후에 자동으로 새로고침됩니다.",
|
||||||
"button": "강제 재시작"
|
"button": "지금 즉시 새로고침"
|
||||||
},
|
},
|
||||||
"description": "이 작업은 Frigate가 재시작 되는 동안 잠시 작동이 중지됩니다."
|
"description": "재시작하는 동안 Frigate 동작이 일시적으로 중단됩니다."
|
||||||
},
|
},
|
||||||
"explore": {
|
"explore": {
|
||||||
"plus": {
|
"plus": {
|
||||||
"submitToPlus": {
|
"submitToPlus": {
|
||||||
"label": "Frigate+에 등록하기",
|
"label": "Frigate+에 등록하기",
|
||||||
"desc": "제외하려는 위치에서 감지된 객체는 '오감지(False Positive)'가 아닙니다. 이를 오감지로 제출하면 모델 학습에 혼선을 줄 수 있습니다."
|
"desc": "감지를 제외하려는 위치에 있는 객체는 오감지(False Positive)가 아닙니다. 이를 오감지로 제출하면 모델 학습에 혼선을 줄 수 있습니다."
|
||||||
},
|
},
|
||||||
"review": {
|
"review": {
|
||||||
"question": {
|
"question": {
|
||||||
"label": "Frigate +에 이 레이블 등록하기",
|
"label": "Frigate+용 레이블 확인",
|
||||||
"ask_a": "이 것은 <code>{{label}}</code> 인가요?",
|
"ask_a": "이 객체가 <code>{{label}}</code> 맞습니까?",
|
||||||
"ask_an": "이 것은 <code>{{label}}</code> 인가요?",
|
"ask_an": "이 객체가 <code>{{label}}</code> 맞습니까?",
|
||||||
"ask_full": "이 것은 <code>{{untranslatedLabel}}</code> ({{translatedLabel}}) 인가요?"
|
"ask_full": "이 객체가 <code>{{untranslatedLabel}}</code> ({{translatedLabel}}) 맞습니까?"
|
||||||
},
|
},
|
||||||
"state": {
|
"state": {
|
||||||
"submitted": "제출됨"
|
"submitted": "제출됨"
|
||||||
|
},
|
||||||
|
"toast": {
|
||||||
|
"error": "Frigate+ 제출에 실패했습니다. 네트워크 연결을 확인하고 다시 시도하세요."
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
},
|
},
|
||||||
"video": {
|
"video": {
|
||||||
"viewInHistory": "히스토리 보기"
|
"viewInHistory": "기록에서 보기"
|
||||||
}
|
}
|
||||||
},
|
},
|
||||||
"export": {
|
"export": {
|
||||||
"time": {
|
"time": {
|
||||||
"fromTimeline": "타임라인에서 선택하기",
|
"fromTimeline": "타임라인에서 선택",
|
||||||
"lastHour_other": "지난 {{count}} 시간",
|
"lastHour_other": "지난 {{count}}시간",
|
||||||
"custom": "커스텀",
|
"custom": "사용자 지정",
|
||||||
"start": {
|
"start": {
|
||||||
"title": "시작 시간",
|
"title": "시작 시간",
|
||||||
"label": "시작 시간 선택"
|
"label": "시작 시간 선택"
|
||||||
@@ -46,53 +49,105 @@
|
|||||||
}
|
}
|
||||||
},
|
},
|
||||||
"name": {
|
"name": {
|
||||||
"placeholder": "내보내기 이름"
|
"placeholder": "내보내기 이름 지정"
|
||||||
},
|
},
|
||||||
"select": "선택",
|
"select": "선택",
|
||||||
"export": "내보내기",
|
"export": "내보내기",
|
||||||
"selectOrExport": "선택 또는 내보내기",
|
"selectOrExport": "선택 또는 내보내기",
|
||||||
"toast": {
|
"toast": {
|
||||||
"success": "내보내기가 성공적으로 시작되었습니다. 내보내기 페이지에서 파일을 보실 수 있습니다.",
|
"success": "내보내기가 성공적으로 시작되었습니다. 내보내기 페이지에서 파일을 확인하세요.",
|
||||||
"error": {
|
"error": {
|
||||||
"failed": "내보내기 시작 실패:{{error}}",
|
"failed": "내보내기 대기열 등록 실패: {{error}}",
|
||||||
"endTimeMustAfterStartTime": "종료 시간은 시작 시간보다 뒤에 있어야합니다",
|
"endTimeMustAfterStartTime": "종료 시간은 시작 시간 이후여야 합니다",
|
||||||
"noVaildTimeSelected": "유효한 시간 범위가 선택되지 않았습니다"
|
"noVaildTimeSelected": "유효한 시간 범위가 선택되지 않았습니다",
|
||||||
|
"noValidTimeSelected": "유효한 시간 범위가 선택되지 않았습니다"
|
||||||
},
|
},
|
||||||
"view": "보기"
|
"view": "보기",
|
||||||
|
"queued": "내보내기가 대기열에 등록되었습니다. 진행 상황은 내보내기 페이지에서 확인하세요.",
|
||||||
|
"batchSuccess_other": "내보내기 {{count}}건을 시작했습니다. 케이스를 여는 중입니다.",
|
||||||
|
"batchPartial": "{{total}}건 중 {{successful}}건의 내보내기를 시작했습니다. 실패한 카메라: {{failedCameras}}",
|
||||||
|
"batchFailed": "{{total}}건의 내보내기 시작에 실패했습니다. 실패한 카메라: {{failedCameras}}",
|
||||||
|
"batchQueuedSuccess_other": "내보내기 {{count}}건을 대기열에 추가했습니다.",
|
||||||
|
"batchQueuedPartial": "{{total}}건 중 {{successful}}건의 내보내기를 대기열에 추가했습니다. 실패한 카메라: {{failedCameras}}",
|
||||||
|
"batchQueueFailed": "{{total}}건의 내보내기 대기열 등록에 실패했습니다. 실패한 카메라: {{failedCameras}}"
|
||||||
},
|
},
|
||||||
"fromTimeline": {
|
"fromTimeline": {
|
||||||
"saveExport": "내보내기 저장",
|
"saveExport": "내보내기 저장",
|
||||||
"previewExport": "내보내기 미리보기"
|
"previewExport": "내보내기 미리보기",
|
||||||
|
"queueingExport": "내보내기 대기열 등록 중...",
|
||||||
|
"useThisRange": "이 범위 사용"
|
||||||
},
|
},
|
||||||
"case": {
|
"case": {
|
||||||
"label": "유형",
|
"label": "케이스",
|
||||||
"placeholder": "유형 선택"
|
"placeholder": "케이스 선택",
|
||||||
|
"newCaseOption": "새 케이스 생성",
|
||||||
|
"newCaseNamePlaceholder": "새 케이스 이름",
|
||||||
|
"newCaseDescriptionPlaceholder": "케이스 설명",
|
||||||
|
"nonAdminHelp": "이 내보내기를 위한 새 케이스가 생성됩니다."
|
||||||
|
},
|
||||||
|
"queueing": "내보내기 대기열 추가 중...",
|
||||||
|
"tabs": {
|
||||||
|
"export": "단일 카메라",
|
||||||
|
"multiCamera": "다중 카메라"
|
||||||
|
},
|
||||||
|
"multiCamera": {
|
||||||
|
"timeRange": "시간 범위",
|
||||||
|
"selectFromTimeline": "타임라인에서 선택",
|
||||||
|
"cameraSelection": "카메라",
|
||||||
|
"cameraSelectionHelp": "해당 시간 범위에 추적된 객체가 있는 카메라가 기본 선택됩니다",
|
||||||
|
"searchOrSelectGroup": "검색 또는 카메라 그룹 선택...",
|
||||||
|
"selectAll": "모든 카메라 선택",
|
||||||
|
"clearSelection": "선택 해제",
|
||||||
|
"selectWithActivity": "추적된 객체가 있는 카메라",
|
||||||
|
"selectGroup": "그룹 선택",
|
||||||
|
"noMatchingCameras": "일치하는 카메라가 없습니다",
|
||||||
|
"selectedCount": "{{selected}} / {{total}}대 선택됨",
|
||||||
|
"checkingActivity": "카메라 활동 확인 중...",
|
||||||
|
"noCameras": "사용 가능한 카메라가 없습니다",
|
||||||
|
"detectionCount_other": "추적된 객체 {{count}}개",
|
||||||
|
"nameLabel": "내보내기 이름",
|
||||||
|
"namePlaceholder": "내보내기에 사용할 기본 이름 (선택 사항)",
|
||||||
|
"queueingButton": "내보내기 대기열 추가 중...",
|
||||||
|
"exportButton_other": "카메라 {{count}}대 내보내기"
|
||||||
|
},
|
||||||
|
"multi": {
|
||||||
|
"title_other": "다시보기 {{count}}건 내보내기",
|
||||||
|
"description": "선택한 각 다시보기를 내보냅니다. 모든 내보내기 항목은 단일 케이스로 묶입니다.",
|
||||||
|
"descriptionNoCase": "선택한 각 다시보기를 내보냅니다.",
|
||||||
|
"caseNamePlaceholder": "다시보기 내보내기 - {{date}}",
|
||||||
|
"exportButton_other": "다시보기 {{count}}건 내보내기",
|
||||||
|
"exportingButton": "내보내는 중...",
|
||||||
|
"toast": {
|
||||||
|
"started_other": "내보내기 {{count}}건을 시작했습니다.",
|
||||||
|
"partial": "{{total}}건 중 {{successful}}건의 내보내기를 시작했습니다. 실패 항목: {{failedItems}}",
|
||||||
|
"failed": "{{total}}건의 내보내기 시작에 실패했습니다. 실패 항목: {{failedItems}}"
|
||||||
|
}
|
||||||
}
|
}
|
||||||
},
|
},
|
||||||
"streaming": {
|
"streaming": {
|
||||||
"label": "스트림",
|
"label": "스트림",
|
||||||
"restreaming": {
|
"restreaming": {
|
||||||
"disabled": "이 카메라는 재 스트리밍이 되지 않습니다.",
|
"disabled": "이 카메라는 재스트리밍이 활성화되어 있지 않습니다.",
|
||||||
"desc": {
|
"desc": {
|
||||||
"title": "이 카메라를 위해 추가적인 라이브 뷰 옵션과 오디오를 go2rtc에서 설정하세요."
|
"title": "추가 라이브 뷰 옵션 및 오디오 기능을 사용하려면 go2rtc 설정을 진행하세요."
|
||||||
}
|
}
|
||||||
},
|
},
|
||||||
"showStats": {
|
"showStats": {
|
||||||
"label": "스트림 통계 보기",
|
"label": "스트림 통계 표시",
|
||||||
"desc": "이 옵션을 활성화하면 스트림 통계가 카메라 피드에 나타납니다."
|
"desc": "카메라 피드 위에 실시간 스트림 통계 정보를 오버레이로 표시합니다."
|
||||||
},
|
},
|
||||||
"debugView": "디버그 뷰"
|
"debugView": "디버그 뷰"
|
||||||
},
|
},
|
||||||
"search": {
|
"search": {
|
||||||
"saveSearch": {
|
"saveSearch": {
|
||||||
"label": "검색 저장",
|
"label": "검색 조건 저장",
|
||||||
"desc": "저장된 검색에 이름을 지정해주세요.",
|
"desc": "저장할 검색 조건의 이름을 입력하세요.",
|
||||||
"placeholder": "검색에 이름 입력하기",
|
"placeholder": "검색 이름 입력",
|
||||||
"overwrite": "{{searchName}} (이/가) 이미 존재합니다. 값을 덮어 씌웁니다.",
|
"overwrite": "'{{searchName}}'이(가) 이미 존재합니다. 저장 시 기존 항목을 덮어씁니다.",
|
||||||
"success": "{{searchName}} 검색이 저장되었습니다.",
|
"success": "검색 조건({{searchName}})이 저장되었습니다.",
|
||||||
"button": {
|
"button": {
|
||||||
"save": {
|
"save": {
|
||||||
"label": "이 검색 저장하기"
|
"label": "이 검색 조건 저장"
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
@@ -101,26 +156,34 @@
|
|||||||
"confirmDelete": {
|
"confirmDelete": {
|
||||||
"title": "삭제 확인",
|
"title": "삭제 확인",
|
||||||
"desc": {
|
"desc": {
|
||||||
"selected": "이 리뷰 항목과 관련된 모든 녹화된 영상을 삭제하시겠습니까?<br /><br />다음에 이 팝업을 건너뛰려면 <em>Shift</em> 키를 누르고 삭제하세요."
|
"selected": "이 다시보기 항목과 연결된 모든 녹화 영상을 삭제하시겠습니까?<br /><br />이후 확인 창을 건너뛰려면 <em>Shift</em> 키를 누른 상태로 클릭하세요."
|
||||||
},
|
},
|
||||||
"toast": {
|
"toast": {
|
||||||
"success": "선택한 리뷰 항목과 관련된 동영상 파일이 성공적으로 삭제되었습니다.",
|
"success": "선택한 다시보기 항목과 연결된 영상이 성공적으로 삭제되었습니다.",
|
||||||
"error": "삭제 실패: {{error}}"
|
"error": "삭제 실패: {{error}}"
|
||||||
}
|
}
|
||||||
},
|
},
|
||||||
"button": {
|
"button": {
|
||||||
"export": "내보내기",
|
"export": "내보내기",
|
||||||
"markAsReviewed": "검토 완료로 표시",
|
"markAsReviewed": "검토 완료로 표시",
|
||||||
"markAsUnreviewed": "검토 안 함 표시",
|
"markAsUnreviewed": "미검토 상태로 표시",
|
||||||
"deleteNow": "지금 삭제"
|
"deleteNow": "지금 삭제"
|
||||||
|
},
|
||||||
|
"shareTimestamp": {
|
||||||
|
"label": "타임스탬프 공유",
|
||||||
|
"title": "타임스탬프 공유",
|
||||||
|
"description": "현재 재생 위치가 포함된 URL을 공유하거나 특정 타임스탬프를 지정합니다. 이 URL은 공개 링크가 아니며 Frigate 및 해당 카메라 접근 권한이 있는 사용자만 접근할 수 있습니다.",
|
||||||
|
"custom": "사용자 지정 타임스탬프",
|
||||||
|
"button": "타임스탬프 URL 공유",
|
||||||
|
"shareTitle": "Frigate 다시보기 타임스탬프: {{camera}}"
|
||||||
}
|
}
|
||||||
},
|
},
|
||||||
"imagePicker": {
|
"imagePicker": {
|
||||||
"selectImage": "추적된 객체의 썸네일을 선택하세요",
|
"selectImage": "추적된 객체의 썸네일을 선택하세요",
|
||||||
"unknownLabel": "저장된 트리거 이미지",
|
"unknownLabel": "저장된 트리거 이미지",
|
||||||
"search": {
|
"search": {
|
||||||
"placeholder": "레이블 또는 서브 레이블로 검색..."
|
"placeholder": "레이블 또는 하위 레이블로 검색..."
|
||||||
},
|
},
|
||||||
"noImages": "표시할 썸네일이 없습니다"
|
"noImages": "이 카메라에 등록된 썸네일이 없습니다"
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -1,7 +1,7 @@
|
|||||||
{
|
{
|
||||||
"filter": "필터",
|
"filter": "필터",
|
||||||
"labels": {
|
"labels": {
|
||||||
"label": "레이블",
|
"label": "레이블 (Labels)",
|
||||||
"all": {
|
"all": {
|
||||||
"title": "모든 레이블",
|
"title": "모든 레이블",
|
||||||
"short": "레이블"
|
"short": "레이블"
|
||||||
@@ -10,34 +10,131 @@
|
|||||||
"count_other": "레이블 {{count}}개"
|
"count_other": "레이블 {{count}}개"
|
||||||
},
|
},
|
||||||
"zones": {
|
"zones": {
|
||||||
"label": "구역",
|
"label": "구역 (Zones)",
|
||||||
"all": {
|
"all": {
|
||||||
"title": "모든 구역",
|
"title": "모든 구역",
|
||||||
"short": "구역"
|
"short": "구역"
|
||||||
}
|
}
|
||||||
},
|
},
|
||||||
"dates": {
|
"dates": {
|
||||||
"selectPreset": "프리셋 선택…",
|
"selectPreset": "사전 설정 선택…",
|
||||||
"all": {
|
"all": {
|
||||||
"title": "모든 날짜",
|
"title": "모든 날짜",
|
||||||
"short": "날짜"
|
"short": "날짜"
|
||||||
}
|
}
|
||||||
},
|
},
|
||||||
"timeRange": "시간 구역",
|
"timeRange": "시간 범위",
|
||||||
"subLabels": {
|
"subLabels": {
|
||||||
"label": "서브 레이블",
|
"label": "하위 레이블",
|
||||||
"all": "모든 서브 레이블"
|
"all": "모든 하위 레이블"
|
||||||
},
|
},
|
||||||
"more": "더 많은 필터",
|
"more": "추가 필터",
|
||||||
"classes": {
|
"classes": {
|
||||||
"label": "분류",
|
"label": "분류 (Classes)",
|
||||||
"all": {
|
"all": {
|
||||||
"title": "모든 분류"
|
"title": "모든 분류"
|
||||||
},
|
},
|
||||||
"count_one": "{{count}}개 클래스",
|
"count_one": "분류 {{count}}개",
|
||||||
"count_other": "{{count}}개 클래스"
|
"count_other": "분류 {{count}}개"
|
||||||
},
|
},
|
||||||
"reset": {
|
"reset": {
|
||||||
"label": "기본값으로 필터 초기화"
|
"label": "필터를 기본값으로 초기화"
|
||||||
|
},
|
||||||
|
"attributes": {
|
||||||
|
"label": "분류 속성",
|
||||||
|
"all": "모든 속성"
|
||||||
|
},
|
||||||
|
"score": "신뢰도 점수",
|
||||||
|
"estimatedSpeed": "예상 속도 ({{unit}})",
|
||||||
|
"features": {
|
||||||
|
"label": "특징",
|
||||||
|
"hasSnapshot": "스냅샷 있음",
|
||||||
|
"hasVideoClip": "비디오 클립 있음",
|
||||||
|
"submittedToFrigatePlus": {
|
||||||
|
"label": "Frigate+에 제출됨",
|
||||||
|
"tips": "먼저 스냅샷이 있는 추적 객체로 필터링해야 합니다.<br /><br />스냅샷이 없는 추적 객체는 Frigate+에 제출할 수 없습니다."
|
||||||
|
}
|
||||||
|
},
|
||||||
|
"sort": {
|
||||||
|
"label": "정렬",
|
||||||
|
"dateAsc": "날짜 (오름차순)",
|
||||||
|
"dateDesc": "날짜 (내림차순)",
|
||||||
|
"scoreAsc": "객체 점수 (오름차순)",
|
||||||
|
"scoreDesc": "객체 점수 (내림차순)",
|
||||||
|
"speedAsc": "예상 속도 (오름차순)",
|
||||||
|
"speedDesc": "예상 속도 (내림차순)",
|
||||||
|
"relevance": "관련성"
|
||||||
|
},
|
||||||
|
"cameras": {
|
||||||
|
"label": "카메라 필터",
|
||||||
|
"all": {
|
||||||
|
"title": "모든 카메라",
|
||||||
|
"short": "카메라"
|
||||||
|
}
|
||||||
|
},
|
||||||
|
"review": {
|
||||||
|
"showReviewed": "검토 완료된 항목 표시"
|
||||||
|
},
|
||||||
|
"motion": {
|
||||||
|
"showMotionOnly": "움직임만 표시"
|
||||||
|
},
|
||||||
|
"explore": {
|
||||||
|
"settings": {
|
||||||
|
"title": "설정",
|
||||||
|
"defaultView": {
|
||||||
|
"title": "기본 보기",
|
||||||
|
"desc": "선택된 필터가 없을 때 레이블별 가장 최근 추적 객체의 요약을 표시하거나 필터링되지 않은 격자(Grid)로 표시합니다.",
|
||||||
|
"summary": "요약 보기",
|
||||||
|
"unfilteredGrid": "전체 격자 보기"
|
||||||
|
},
|
||||||
|
"gridColumns": {
|
||||||
|
"title": "격자 열 개수",
|
||||||
|
"desc": "격자 보기에서 표시할 열(Column) 개수를 선택하세요."
|
||||||
|
},
|
||||||
|
"searchSource": {
|
||||||
|
"label": "검색 대상",
|
||||||
|
"desc": "추적된 객체의 썸네일 이미지로 검색할지 설명 텍스트로 검색할지 선택하세요.",
|
||||||
|
"options": {
|
||||||
|
"thumbnailImage": "썸네일 이미지",
|
||||||
|
"description": "설명"
|
||||||
|
}
|
||||||
|
}
|
||||||
|
},
|
||||||
|
"date": {
|
||||||
|
"selectDateBy": {
|
||||||
|
"label": "필터링할 날짜 선택"
|
||||||
|
}
|
||||||
|
}
|
||||||
|
},
|
||||||
|
"logSettings": {
|
||||||
|
"label": "로그 레벨 필터",
|
||||||
|
"filterBySeverity": "심각도별 로그 필터링",
|
||||||
|
"loading": {
|
||||||
|
"title": "로딩 중",
|
||||||
|
"desc": "로그 창을 맨 아래로 스크롤하면 새로운 로그가 추가될 때 자동으로 스트리밍됩니다."
|
||||||
|
},
|
||||||
|
"disableLogStreaming": "로그 스트리밍 비활성화",
|
||||||
|
"allLogs": "모든 로그"
|
||||||
|
},
|
||||||
|
"trackedObjectDelete": {
|
||||||
|
"title": "삭제 확인",
|
||||||
|
"desc": "이 {{objectLength}}개의 추적 객체를 삭제하면 스냅샷, 저장된 임베딩 및 연결된 모든 객체 수명주기 항목이 제거됩니다. 기록 보기의 녹화 영상은 삭제되지 <em>않습니다</em>.<br /><br />정말로 계속 진행하시겠습니까?<br /><br />이후 확인 창을 건너뛰려면 <em>Shift</em> 키를 누른 상태로 클릭하세요.",
|
||||||
|
"toast": {
|
||||||
|
"success": "추적 객체가 성공적으로 삭제되었습니다.",
|
||||||
|
"error": "추적 객체 삭제 실패: {{errorMessage}}"
|
||||||
|
}
|
||||||
|
},
|
||||||
|
"zoneMask": {
|
||||||
|
"filterBy": "구역 마스크별 필터"
|
||||||
|
},
|
||||||
|
"recognizedLicensePlates": {
|
||||||
|
"title": "인식된 차량 번호판",
|
||||||
|
"loadFailed": "인식된 번호판 목록을 불러오지 못했습니다.",
|
||||||
|
"loading": "인식된 번호판 목록 불러오는 중…",
|
||||||
|
"placeholder": "번호판 검색어 입력…",
|
||||||
|
"noLicensePlatesFound": "발견된 번호판이 없습니다.",
|
||||||
|
"selectPlatesFromList": "목록에서 하나 이상의 번호판을 선택하세요.",
|
||||||
|
"selectAll": "모두 선택",
|
||||||
|
"clearAll": "모두 지우기"
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -1,8 +1,8 @@
|
|||||||
{
|
{
|
||||||
"iconPicker": {
|
"iconPicker": {
|
||||||
"selectIcon": "아이콘을 선택해주세요",
|
"selectIcon": "아이콘 선택",
|
||||||
"search": {
|
"search": {
|
||||||
"placeholder": "아이콘 검색 중…"
|
"placeholder": "아이콘 검색…"
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -2,7 +2,7 @@
|
|||||||
"submitFrigatePlus": {
|
"submitFrigatePlus": {
|
||||||
"submit": "제출",
|
"submit": "제출",
|
||||||
"title": "이 프레임을 Frigate+에 제출하시겠습니까?",
|
"title": "이 프레임을 Frigate+에 제출하시겠습니까?",
|
||||||
"previewError": "스냅샷 미리보기를 불러올 수 없습니다. 현재 녹화된 영상을 사용할 수 없을 수 있습니다."
|
"previewError": "스냅샷 미리보기를 불러올 수 없습니다. 해당 시점의 녹화 영상을 사용할 수 없는 상태일 수 있습니다."
|
||||||
},
|
},
|
||||||
"stats": {
|
"stats": {
|
||||||
"bandwidth": {
|
"bandwidth": {
|
||||||
@@ -12,10 +12,10 @@
|
|||||||
"latency": {
|
"latency": {
|
||||||
"short": {
|
"short": {
|
||||||
"title": "지연",
|
"title": "지연",
|
||||||
"value": "{{seconds}} 초"
|
"value": "{{seconds}}초"
|
||||||
},
|
},
|
||||||
"title": "지연:",
|
"title": "지연 시간:",
|
||||||
"value": "{{seconds}} 초"
|
"value": "{{seconds}}초"
|
||||||
},
|
},
|
||||||
"streamType": {
|
"streamType": {
|
||||||
"short": "종류",
|
"short": "종류",
|
||||||
@@ -25,28 +25,29 @@
|
|||||||
"droppedFrames": {
|
"droppedFrames": {
|
||||||
"title": "손실 프레임:",
|
"title": "손실 프레임:",
|
||||||
"short": {
|
"short": {
|
||||||
"title": "손실됨",
|
"title": "손실",
|
||||||
"value": "{{droppedFrames}} 프레임"
|
"value": "{{droppedFrames}}프레임"
|
||||||
}
|
}
|
||||||
},
|
},
|
||||||
"decodedFrames": "복원된 프레임:",
|
"decodedFrames": "디코딩된 프레임:",
|
||||||
"droppedFrameRate": "프레임 손실률:"
|
"droppedFrameRate": "프레임 손실률:"
|
||||||
},
|
},
|
||||||
"noRecordingsFoundForThisTime": "이 시간대에는 녹화본이 없습니다",
|
"noRecordingsFoundForThisTime": "해당 시간대에 저장된 녹화 영상이 없습니다",
|
||||||
"noPreviewFound": "미리 보기를 찾을 수 없습니다",
|
"noPreviewFound": "미리보기를 찾을 수 없습니다",
|
||||||
"noPreviewFoundFor": "{{cameraName}}에 미리보기를 찾을 수 없습니다",
|
"noPreviewFoundFor": "{{cameraName}} 카메라의 미리보기를 찾을 수 없습니다",
|
||||||
"livePlayerRequiredIOSVersion": "이 라이브 스트림 방식은 iOS 17.1 이거나 높은 버전이 필요합니다.",
|
"livePlayerRequiredIOSVersion": "이 실시간 스트림 유형을 사용하려면 iOS 17.1 이상 버전이 필요합니다.",
|
||||||
"streamOffline": {
|
"streamOffline": {
|
||||||
"title": "스트림 오프라인",
|
"title": "스트림 오프라인",
|
||||||
"desc": "{{cameraName}} 카메라에 <code>감지(detect)</code> 스트림의 프레임을 얻지 못했습니다. 에러 로그를 확인하세요"
|
"desc": "{{cameraName}} 카메라의 <code>detect</code> 스트림에서 수신된 프레임이 없습니다. 오류 로그를 확인하세요"
|
||||||
},
|
},
|
||||||
"cameraDisabled": "카메라를 이용할 수 없습니다",
|
"cameraDisabled": "카메라를 이용할 수 없습니다",
|
||||||
"toast": {
|
"toast": {
|
||||||
"success": {
|
"success": {
|
||||||
"submittedFrigatePlus": "Frigate+에 프레임이 성공적으로 제출됐습니다"
|
"submittedFrigatePlus": "Frigate+에 프레임을 성공적으로 제출했습니다"
|
||||||
},
|
},
|
||||||
"error": {
|
"error": {
|
||||||
"submitFrigatePlusFailed": "Frigate+에 프레임을 보내지 못했습니다"
|
"submitFrigatePlusFailed": "Frigate+에 프레임을 제출하지 못했습니다"
|
||||||
}
|
}
|
||||||
}
|
},
|
||||||
|
"cameraOff": "카메라가 꺼져 있습니다"
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -10,10 +10,10 @@
|
|||||||
},
|
},
|
||||||
"enabled": {
|
"enabled": {
|
||||||
"label": "활성화됨",
|
"label": "활성화됨",
|
||||||
"description": "활성화됨"
|
"description": "활성화 여부"
|
||||||
},
|
},
|
||||||
"audio": {
|
"audio": {
|
||||||
"label": "오디오 이벤트",
|
"label": "오디오 감지",
|
||||||
"description": "이 카메라의 오디오 기반 이벤트 감지 설정입니다.",
|
"description": "이 카메라의 오디오 기반 이벤트 감지 설정입니다.",
|
||||||
"enabled": {
|
"enabled": {
|
||||||
"label": "오디오 감지 활성화",
|
"label": "오디오 감지 활성화",
|
||||||
@@ -25,39 +25,932 @@
|
|||||||
},
|
},
|
||||||
"min_volume": {
|
"min_volume": {
|
||||||
"label": "최소 볼륨",
|
"label": "최소 볼륨",
|
||||||
"description": "오디오 감지를 실행하는 데 필요한 최소 RMS 볼륨 임계값으로, 낮을수록 민감도가 높아집니다(예: 200 높음, 500 보통, 1000 낮음)."
|
"description": "오디오 감지를 실행하는 데 필요한 최소 RMS 볼륨 임계값입니다. 낮을수록 민감도가 높아집니다 (예: 200 높음, 500 보통, 1000 낮음)."
|
||||||
},
|
},
|
||||||
"listen": {
|
"listen": {
|
||||||
"label": "청취 유형",
|
"label": "감지 대상 소리",
|
||||||
"description": "감지할 오디오 이벤트 유형 목록입니다(예: bark, fire_alarm, scream, speech, yell)."
|
"description": "감지할 오디오 이벤트 유형 목록입니다 (예: bark, fire_alarm, speech, yell)."
|
||||||
},
|
},
|
||||||
"filters": {
|
"filters": {
|
||||||
"label": "오디오 필터",
|
"label": "오디오 필터",
|
||||||
"description": "오탐지를 줄이기 위해 사용되는 신뢰도 임계값과 같은 오디오 유형별 필터 설정입니다."
|
"description": "오감지를 줄이기 위한 신뢰도 임계값 등 오디오 유형별 필터 설정입니다.",
|
||||||
|
"threshold": {
|
||||||
|
"label": "최소 오디오 신뢰도",
|
||||||
|
"description": "오디오 이벤트로 카운트되기 위한 최소 신뢰도 임계값입니다."
|
||||||
|
}
|
||||||
},
|
},
|
||||||
"enabled_in_config": {
|
"enabled_in_config": {
|
||||||
"label": "원래 오디오 상태"
|
"label": "초기 오디오 상태",
|
||||||
|
"description": "정적 설정 파일에서 오디오 감지가 기본 활성화되어 있었는지 여부를 나타냅니다."
|
||||||
|
},
|
||||||
|
"num_threads": {
|
||||||
|
"label": "감지 스레드 수",
|
||||||
|
"description": "오디오 감지 처리에 사용할 스레드 수입니다."
|
||||||
}
|
}
|
||||||
},
|
},
|
||||||
"mqtt": {
|
"mqtt": {
|
||||||
"label": "MQTT"
|
"label": "MQTT",
|
||||||
|
"description": "MQTT 이미지 발행 설정입니다.",
|
||||||
|
"enabled": {
|
||||||
|
"label": "이미지 전송",
|
||||||
|
"description": "이 카메라의 객체 이미지 스냅샷을 MQTT 토픽으로 발행하도록 활성화합니다."
|
||||||
|
},
|
||||||
|
"timestamp": {
|
||||||
|
"label": "타임스탬프 추가",
|
||||||
|
"description": "MQTT로 발행되는 이미지에 타임스탬프를 덧씌웁니다."
|
||||||
|
},
|
||||||
|
"bounding_box": {
|
||||||
|
"label": "바운딩 박스 추가",
|
||||||
|
"description": "MQTT로 발행되는 이미지에 바운딩 박스를 그립니다."
|
||||||
|
},
|
||||||
|
"crop": {
|
||||||
|
"label": "이미지 크롭",
|
||||||
|
"description": "MQTT로 발행되는 이미지를 감지된 객체의 바운딩 박스 크기로 잘라냅니다."
|
||||||
|
},
|
||||||
|
"height": {
|
||||||
|
"label": "이미지 높이",
|
||||||
|
"description": "MQTT로 발행되는 이미지를 리사이즈할 높이(픽셀)입니다."
|
||||||
|
},
|
||||||
|
"required_zones": {
|
||||||
|
"label": "필수 구역",
|
||||||
|
"description": "MQTT 이미지가 발행되기 위해 객체가 진입해야 하는 구역입니다."
|
||||||
|
},
|
||||||
|
"quality": {
|
||||||
|
"label": "JPEG 품질",
|
||||||
|
"description": "MQTT로 발행되는 이미지의 JPEG 압축 품질입니다 (0~100)."
|
||||||
|
}
|
||||||
},
|
},
|
||||||
"notifications": {
|
"notifications": {
|
||||||
"label": "알림",
|
"label": "알림",
|
||||||
"enabled": {
|
"enabled": {
|
||||||
"label": "알림 활성화"
|
"label": "알림 활성화",
|
||||||
|
"description": "이 카메라의 알림 기능을 활성화하거나 비활성화합니다."
|
||||||
},
|
},
|
||||||
"email": {
|
"email": {
|
||||||
"label": "알림 이메일",
|
"label": "알림 수신 이메일",
|
||||||
"description": "푸시 알림에 사용되거나 특정 알림 제공업체에서 요구하는 이메일 주소입니다."
|
"description": "푸시 알림에 사용되거나 특정 알림 제공업체에서 요구하는 이메일 주소입니다."
|
||||||
},
|
},
|
||||||
"cooldown": {
|
"cooldown": {
|
||||||
"label": "알림 재발송 대기 시간",
|
"label": "알림 재발송 대기 시간",
|
||||||
"description": "수신자에게 스팸 메일을 보내는 것을 방지하기 위해 알림 재발송 대기시간(초)을 설정합니다."
|
"description": "수신자에게 알림이 연속 도배되는 것을 방지하기 위한 대기 시간(초)입니다."
|
||||||
},
|
},
|
||||||
"enabled_in_config": {
|
"enabled_in_config": {
|
||||||
"label": "초기 알림 활성 상태",
|
"label": "초기 알림 상태",
|
||||||
"description": "초기 구성에서 알림이 활성화되었는지 여부를 나타냅니다."
|
"description": "초기 정적 구성에서 알림이 활성화되어 있었는지 여부를 나타냅니다."
|
||||||
|
},
|
||||||
|
"description": "이 카메라의 알림을 활성화하고 제어하기 위한 설정입니다."
|
||||||
|
},
|
||||||
|
"zones": {
|
||||||
|
"label": "구역 (Zones)",
|
||||||
|
"description": "프레임의 특정 영역을 정의하여 객체가 해당 영역 내에 있는지 여부를 판정할 수 있도록 합니다.",
|
||||||
|
"friendly_name": {
|
||||||
|
"label": "구역 별칭",
|
||||||
|
"description": "Frigate UI에 표시될 구역 이름입니다. 지정하지 않으면 구역 고유 식별자 이름이 사용됩니다."
|
||||||
|
},
|
||||||
|
"enabled": {
|
||||||
|
"label": "활성화됨",
|
||||||
|
"description": "이 구역을 활성화하거나 비활성화합니다. 비활성화된 구역은 런타임에 무시됩니다."
|
||||||
|
},
|
||||||
|
"enabled_in_config": {
|
||||||
|
"label": "구역의 초기 상태를 추적합니다."
|
||||||
|
},
|
||||||
|
"filters": {
|
||||||
|
"label": "구역 필터",
|
||||||
|
"description": "이 구역 내의 객체에 적용할 필터입니다. 오탐지를 줄이거나 구역 내 진입으로 인정할 객체 조건을 제한하는 데 사용됩니다.",
|
||||||
|
"min_area": {
|
||||||
|
"label": "최소 객체 면적",
|
||||||
|
"description": "이 객체 유형에 필요한 최소 바운딩 박스 면적입니다. 픽셀 수(정수) 또는 비율(0.000001~0.99 사이의 실수)로 지정할 수 있습니다."
|
||||||
|
},
|
||||||
|
"max_area": {
|
||||||
|
"label": "최대 객체 면적",
|
||||||
|
"description": "이 객체 유형에 허용되는 최대 바운딩 박스 면적입니다. 픽셀 수(정수) 또는 비율(0.000001~0.99 사이의 실수)로 지정할 수 있습니다."
|
||||||
|
},
|
||||||
|
"min_ratio": {
|
||||||
|
"label": "최소 가로세로 비율",
|
||||||
|
"description": "바운딩 박스에 요구되는 최소 너비/높이 비율입니다."
|
||||||
|
},
|
||||||
|
"max_ratio": {
|
||||||
|
"label": "최대 가로세로 비율",
|
||||||
|
"description": "바운딩 박스에 허용되는 최대 너비/높이 비율입니다."
|
||||||
|
},
|
||||||
|
"threshold": {
|
||||||
|
"label": "신뢰도 임계값",
|
||||||
|
"description": "객체를 실제 탐지로 인정하기 위해 요구되는 평균 감지 신뢰도 임계값입니다."
|
||||||
|
},
|
||||||
|
"min_score": {
|
||||||
|
"label": "최소 신뢰도",
|
||||||
|
"description": "객체로 카운트되기 위해 필요한 단일 프레임 최소 감지 신뢰도입니다."
|
||||||
|
},
|
||||||
|
"mask": {
|
||||||
|
"label": "필터 마스크",
|
||||||
|
"description": "프레임 내에서 이 필터가 적용될 다각형 좌표입니다."
|
||||||
|
},
|
||||||
|
"raw_mask": {
|
||||||
|
"label": "원시 마스크"
|
||||||
|
}
|
||||||
|
},
|
||||||
|
"coordinates": {
|
||||||
|
"label": "좌표",
|
||||||
|
"description": "구역 영역을 정의하는 다각형 좌표입니다. 쉼표로 구분된 문자열 또는 좌표 문자열 목록일 수 있습니다. 좌표는 상대값(0~1) 또는 절대값(레거시)이어야 합니다."
|
||||||
|
},
|
||||||
|
"distances": {
|
||||||
|
"label": "실제 거리",
|
||||||
|
"description": "속도 또는 거리 계산에 사용되는 구역 사각형 각 변의 실제 거리값(선택 사항)입니다. 설정할 경우 정확히 4개의 값이어야 합니다."
|
||||||
|
},
|
||||||
|
"inertia": {
|
||||||
|
"label": "관성 프레임",
|
||||||
|
"description": "구역 내에 있는 것으로 인정하기 위해 객체가 연속으로 감지되어야 하는 프레임 수입니다. 순간적인 일시적 오탐을 걸러내는 데 유용합니다."
|
||||||
|
},
|
||||||
|
"loitering_time": {
|
||||||
|
"label": "배회 감지 시간(초)",
|
||||||
|
"description": "객체가 구역 내에 머무르고 있는 것으로 간주할 시간(초)입니다. 배회 감지를 비활성화하려면 0으로 설정하세요."
|
||||||
|
},
|
||||||
|
"speed_threshold": {
|
||||||
|
"label": "최소 이동 속도",
|
||||||
|
"description": "구역 내에 존재하는 것으로 인정되기 위해 객체에 요구되는 최소 속도(거리 설정 시 실제 단위 기준)입니다. 속도 기반 구역 트리거에 사용됩니다."
|
||||||
|
},
|
||||||
|
"objects": {
|
||||||
|
"label": "트리거 대상 객체",
|
||||||
|
"description": "이 구역을 작동시킬 수 있는 객체 유형 목록(labelmap 기준)입니다. 비워 두면 모든 객체에 대해 작동합니다."
|
||||||
}
|
}
|
||||||
|
},
|
||||||
|
"audio_transcription": {
|
||||||
|
"label": "오디오 텍스트 변환 (STT)",
|
||||||
|
"description": "이벤트 및 실시간 자막 처리에 사용되는 실시간 음성 전사 설정입니다.",
|
||||||
|
"enabled": {
|
||||||
|
"label": "텍스트 변환 활성화",
|
||||||
|
"description": "수동으로 트리거된 오디오 이벤트의 텍스트 변환을 활성화하거나 비활성화합니다."
|
||||||
|
},
|
||||||
|
"enabled_in_config": {
|
||||||
|
"label": "초기 텍스트 변환 상태"
|
||||||
|
},
|
||||||
|
"live_enabled": {
|
||||||
|
"label": "실시간 텍스트 변환",
|
||||||
|
"description": "수신되는 오디오에 대한 실시간 스트리밍 텍스트 변환을 활성화합니다."
|
||||||
|
}
|
||||||
|
},
|
||||||
|
"birdseye": {
|
||||||
|
"label": "버드아이 (Birdseye)",
|
||||||
|
"description": "여러 카메라 피드를 단일 레이아웃으로 합성하는 버드아이 뷰 설정입니다.",
|
||||||
|
"enabled": {
|
||||||
|
"label": "버드아이 활성화",
|
||||||
|
"description": "버드아이 뷰 기능을 활성화하거나 비활성화합니다."
|
||||||
|
},
|
||||||
|
"mode": {
|
||||||
|
"label": "추적 모드",
|
||||||
|
"description": "카메라를 버드아이에 포함하는 기준 모드: 'objects', 'motion', 또는 'continuous'."
|
||||||
|
},
|
||||||
|
"order": {
|
||||||
|
"label": "표시 순서",
|
||||||
|
"description": "버드아이 레이아웃 내에서 카메라 배치 순서를 제어하는 숫자입니다."
|
||||||
|
}
|
||||||
|
},
|
||||||
|
"detect": {
|
||||||
|
"label": "객체 감지",
|
||||||
|
"description": "객체 감지를 실행하고 트래커를 초기화하는 데 사용되는 detect 역할 설정입니다.",
|
||||||
|
"enabled": {
|
||||||
|
"label": "객체 감지 활성화",
|
||||||
|
"description": "이 카메라의 객체 감지를 활성화하거나 비활성화합니다."
|
||||||
|
},
|
||||||
|
"height": {
|
||||||
|
"label": "감지 높이",
|
||||||
|
"description": "감지 스트림에 사용할 프레임 높이(픽셀)입니다. 비워 두면 원본 스트림 해상도를 사용합니다."
|
||||||
|
},
|
||||||
|
"width": {
|
||||||
|
"label": "감지 너비",
|
||||||
|
"description": "감지 스트림에 사용할 프레임 너비(픽셀)입니다. 비워 두면 원본 스트림 해상도를 사용합니다."
|
||||||
|
},
|
||||||
|
"fps": {
|
||||||
|
"label": "감지 FPS",
|
||||||
|
"description": "감지를 실행할 목표 초당 프레임 수입니다. 낮을수록 CPU 사용량이 감소합니다 (권장값은 5이며, 매우 빠른 물체를 추적할 때만 최대 10까지 권장)."
|
||||||
|
},
|
||||||
|
"min_initialized": {
|
||||||
|
"label": "최소 초기화 프레임",
|
||||||
|
"description": "추적 객체를 생성하기 전 연속 감지되어야 하는 프레임 수입니다. 값을 높이면 오탐 초기화를 줄일 수 있습니다 (기본값: fps / 2)."
|
||||||
|
},
|
||||||
|
"max_disappeared": {
|
||||||
|
"label": "최대 사라짐 프레임",
|
||||||
|
"description": "추적 중인 객체가 시야에서 완전히 사라진 것으로 판단하기까지 감지되지 않는 프레임 수입니다."
|
||||||
|
},
|
||||||
|
"stationary": {
|
||||||
|
"label": "정지 객체 설정",
|
||||||
|
"description": "일정 시간 정지해 있는 객체를 감지하고 관리하기 위한 설정입니다.",
|
||||||
|
"interval": {
|
||||||
|
"label": "정지 확인 주기",
|
||||||
|
"description": "정지 객체임을 재확인하기 위해 감지 검사를 실행하는 간격(프레임 단위)입니다."
|
||||||
|
},
|
||||||
|
"threshold": {
|
||||||
|
"label": "정지 임계값",
|
||||||
|
"description": "객체를 정지 상태로 분류하기 위해 위치 이동이 없어야 하는 프레임 수입니다."
|
||||||
|
},
|
||||||
|
"max_frames": {
|
||||||
|
"label": "최대 추적 프레임",
|
||||||
|
"description": "정지 객체를 폐기하기 전까지 추적할 최대 프레임 한도입니다.",
|
||||||
|
"default": {
|
||||||
|
"label": "기본 최대 프레임",
|
||||||
|
"description": "추적을 중단하기 전 정지 객체를 추적할 기본 최대 프레임 수입니다."
|
||||||
|
},
|
||||||
|
"objects": {
|
||||||
|
"label": "객체별 최대 프레임",
|
||||||
|
"description": "정지 객체를 추적할 객체 유형별 최대 프레임 수 재정의 설정입니다."
|
||||||
|
}
|
||||||
|
},
|
||||||
|
"classifier": {
|
||||||
|
"label": "시각적 분류기 활성화",
|
||||||
|
"description": "바운딩 박스가 미세하게 흔들려도 시각적 분류기를 사용해 실제로 정지된 객체인지 확인합니다."
|
||||||
|
}
|
||||||
|
},
|
||||||
|
"annotation_offset": {
|
||||||
|
"label": "어노테이션 오프셋",
|
||||||
|
"description": "녹화 영상과 타임라인 바운딩 박스의 싱크를 맞추기 위해 감지 어노테이션을 전후로 이동할 밀리초(ms) 단위 시간입니다 (양수 또는 음수 가능)."
|
||||||
|
}
|
||||||
|
},
|
||||||
|
"face_recognition": {
|
||||||
|
"label": "얼굴 인식",
|
||||||
|
"description": "이 카메라의 얼굴 감지 및 인식 설정입니다.",
|
||||||
|
"enabled": {
|
||||||
|
"label": "얼굴 인식 활성화",
|
||||||
|
"description": "얼굴 인식을 활성화하거나 비활성화합니다."
|
||||||
|
},
|
||||||
|
"min_area": {
|
||||||
|
"label": "최소 얼굴 크기",
|
||||||
|
"description": "인식을 시도하기 위해 필요한 감지된 얼굴 박스의 최소 면적(픽셀)입니다."
|
||||||
|
}
|
||||||
|
},
|
||||||
|
"ffmpeg": {
|
||||||
|
"label": "스트림 (FFmpeg)",
|
||||||
|
"description": "카메라 스트림 입력 및 바이너리 경로, 인수, 하드웨어 가속, 역할별 출력 인수를 포함한 FFmpeg 설정입니다.",
|
||||||
|
"path": {
|
||||||
|
"label": "FFmpeg 경로",
|
||||||
|
"description": "사용할 FFmpeg 바이너리 경로 또는 버전 별칭(\"7.0\" 또는 \"8.0\")입니다."
|
||||||
|
},
|
||||||
|
"global_args": {
|
||||||
|
"label": "FFmpeg 글로벌 인수",
|
||||||
|
"description": "FFmpeg 프로세스 전역에 전달할 공통 인수입니다."
|
||||||
|
},
|
||||||
|
"hwaccel_args": {
|
||||||
|
"label": "하드웨어 가속 인수",
|
||||||
|
"description": "FFmpeg 하드웨어 가속 인수입니다. 하드웨어 환경에 맞는 프리셋 사용을 권장합니다."
|
||||||
|
},
|
||||||
|
"input_args": {
|
||||||
|
"label": "입력 인수",
|
||||||
|
"description": "FFmpeg 입력 스트림에 적용할 인수입니다."
|
||||||
|
},
|
||||||
|
"output_args": {
|
||||||
|
"label": "출력 인수",
|
||||||
|
"description": "detect, record 등 서로 다른 FFmpeg 역할에 사용할 기본 출력 인수입니다.",
|
||||||
|
"detect": {
|
||||||
|
"label": "감지(detect) 출력 인수",
|
||||||
|
"description": "detect 역할 스트림에 적용할 기본 출력 인수입니다."
|
||||||
|
},
|
||||||
|
"record": {
|
||||||
|
"label": "녹화(record) 출력 인수",
|
||||||
|
"description": "record 역할 스트림에 적용할 기본 출력 인수입니다."
|
||||||
|
}
|
||||||
|
},
|
||||||
|
"retry_interval": {
|
||||||
|
"label": "FFmpeg 재연결 대기 시간",
|
||||||
|
"description": "카메라 스트림 연결 실패 시 재연결을 시도하기까지 대기할 시간(초)입니다 (기본값: 10)."
|
||||||
|
},
|
||||||
|
"apple_compatibility": {
|
||||||
|
"label": "Apple 호환성",
|
||||||
|
"description": "H.265 녹화 시 Apple 기기 플레이어와의 호환성을 위해 HEVC 태깅을 활성화합니다."
|
||||||
|
},
|
||||||
|
"gpu": {
|
||||||
|
"label": "GPU 인덱스",
|
||||||
|
"description": "하드웨어 가속에 사용할 기본 GPU 번호(인덱스)입니다."
|
||||||
|
},
|
||||||
|
"inputs": {
|
||||||
|
"label": "카메라 입력 스트림",
|
||||||
|
"description": "이 카메라에 설정된 입력 스트림 정의(경로 및 역할) 목록입니다.",
|
||||||
|
"path": {
|
||||||
|
"label": "입력 경로",
|
||||||
|
"description": "카메라 입력 스트림 URL 또는 디바이스 경로입니다."
|
||||||
|
},
|
||||||
|
"roles": {
|
||||||
|
"label": "입력 역할",
|
||||||
|
"description": "이 입력 스트림에 할당할 역할입니다."
|
||||||
|
},
|
||||||
|
"global_args": {
|
||||||
|
"label": "FFmpeg 글로벌 인수",
|
||||||
|
"description": "이 입력 스트림에만 적용할 FFmpeg 글로벌 인수입니다."
|
||||||
|
},
|
||||||
|
"hwaccel_args": {
|
||||||
|
"label": "하드웨어 가속 인수",
|
||||||
|
"description": "이 입력 스트림에만 적용할 하드웨어 가속 인수입니다."
|
||||||
|
},
|
||||||
|
"input_args": {
|
||||||
|
"label": "입력 인수",
|
||||||
|
"description": "이 스트림에만 개별 적용할 입력 인수입니다."
|
||||||
|
}
|
||||||
|
}
|
||||||
|
},
|
||||||
|
"live": {
|
||||||
|
"label": "실시간 재생",
|
||||||
|
"description": "웹 UI에서 실시간 스트림 선택, 해상도 및 화질을 제어하기 위한 설정입니다.",
|
||||||
|
"streams": {
|
||||||
|
"label": "실시간 스트림 이름",
|
||||||
|
"description": "실시간 재생에 사용할 restream/go2rtc 이름과 설정된 스트림 이름의 매핑입니다."
|
||||||
|
},
|
||||||
|
"height": {
|
||||||
|
"label": "실시간 재생 높이",
|
||||||
|
"description": "웹 UI에서 jsmpeg 실시간 스트림을 렌더링할 높이(픽셀)입니다 (감지 스트림 높이 이하)."
|
||||||
|
},
|
||||||
|
"quality": {
|
||||||
|
"label": "실시간 품질",
|
||||||
|
"description": "jsmpeg 스트림의 인코딩 품질입니다 (1 최고 품질, 31 최저 품질)."
|
||||||
|
}
|
||||||
|
},
|
||||||
|
"lpr": {
|
||||||
|
"label": "차량 번호판 인식",
|
||||||
|
"description": "감지 임계값, 포맷, 등록된 번호판 등을 포함한 차량 번호판 인식 설정입니다.",
|
||||||
|
"enabled": {
|
||||||
|
"label": "LPR 활성화",
|
||||||
|
"description": "이 카메라에서 LPR 기능을 활성화하거나 비활성화합니다."
|
||||||
|
},
|
||||||
|
"expire_time": {
|
||||||
|
"label": "만료 시간(초)",
|
||||||
|
"description": "시야에서 사라진 번호판이 트래커에서 만료되기까지의 시간(초)입니다 (전용 LPR 카메라 전용)."
|
||||||
|
},
|
||||||
|
"min_area": {
|
||||||
|
"label": "최소 번호판 크기",
|
||||||
|
"description": "인식을 시도하기 위해 필요한 최소 번호판 면적(픽셀)입니다."
|
||||||
|
},
|
||||||
|
"enhancement": {
|
||||||
|
"label": "이미지 보정 레벨",
|
||||||
|
"description": "OCR 수행 전 번호판 크롭 이미지에 적용할 보정 강도(0~10)입니다 (값이 높다고 항상 인식률이 좋아지지는 않으며, 5 초과는 야간 전용이므로 주의해야 합니다)."
|
||||||
|
}
|
||||||
|
},
|
||||||
|
"motion": {
|
||||||
|
"label": "움직임 감지",
|
||||||
|
"description": "이 카메라의 기본 움직임 감지 설정입니다.",
|
||||||
|
"enabled": {
|
||||||
|
"label": "움직임 감지 활성화",
|
||||||
|
"description": "이 카메라의 움직임 감지를 활성화하거나 비활성화합니다."
|
||||||
|
},
|
||||||
|
"threshold": {
|
||||||
|
"label": "움직임 임계값",
|
||||||
|
"description": "움직임 감지기가 사용하는 픽셀 차이 임계값입니다. 값이 클수록 민감도가 낮아집니다 (범위: 1~255)."
|
||||||
|
},
|
||||||
|
"lightning_threshold": {
|
||||||
|
"label": "번개/빛 변화 임계값",
|
||||||
|
"description": "순간적인 조명 변화나 번개를 감지하여 무시하기 위한 임계값입니다 (낮을수록 민감하며 0.3~1.0 사이 값). 움직임 감지 자체를 완전히 끄는 것이 아니라 임계값을 초과할 때 추가 프레임 분석을 일시 중단하는 것이며, 이 동안에도 움직임 기반 녹화는 정상 생성됩니다."
|
||||||
|
},
|
||||||
|
"skip_motion_threshold": {
|
||||||
|
"label": "움직임 건너뛰기 임계값",
|
||||||
|
"description": "0.0~1.0 사이로 설정 시 단일 프레임에서 이 비율 이상의 화면이 바뀌면 움직임 박스를 생성하지 않고 즉시 재보정합니다. 폭풍이나 번개 시 CPU를 절약하고 오탐을 줄일 수 있지만, PTZ 카메라 자동 추적과 같은 실제 이벤트를 놓칠 수 있습니다. 사용하지 않으려면 설정하지 마세요(None)."
|
||||||
|
},
|
||||||
|
"improve_contrast": {
|
||||||
|
"label": "대비 향상",
|
||||||
|
"description": "움직임 분석 전 프레임 대비를 향상하여 감지 성능을 보조합니다."
|
||||||
|
},
|
||||||
|
"contour_area": {
|
||||||
|
"label": "윤곽선 면적",
|
||||||
|
"description": "움직임 윤곽선으로 인정되기 위한 최소 픽셀 면적입니다."
|
||||||
|
},
|
||||||
|
"delta_alpha": {
|
||||||
|
"label": "델타 알파",
|
||||||
|
"description": "움직임 계산 시 프레임 차분화에 사용할 알파 블렌딩 계수입니다."
|
||||||
|
},
|
||||||
|
"frame_alpha": {
|
||||||
|
"label": "프레임 알파",
|
||||||
|
"description": "움직임 전처리용 프레임 블렌딩에 사용할 알파 값입니다."
|
||||||
|
},
|
||||||
|
"frame_height": {
|
||||||
|
"label": "프레임 높이",
|
||||||
|
"description": "움직임 연산 시 프레임을 리사이즈할 높이(픽셀)입니다."
|
||||||
|
},
|
||||||
|
"mask": {
|
||||||
|
"label": "마스크 좌표",
|
||||||
|
"description": "움직임 감지 영역을 포함하거나 제외하기 위해 다각형을 정의하는 정렬된 x,y 좌표입니다."
|
||||||
|
},
|
||||||
|
"mqtt_off_delay": {
|
||||||
|
"label": "MQTT 꺼짐 지연",
|
||||||
|
"description": "마지막 움직임이 감지된 후 MQTT에 'off' 상태를 발행하기까지 대기할 시간(초)입니다."
|
||||||
|
},
|
||||||
|
"enabled_in_config": {
|
||||||
|
"label": "초기 움직임 상태",
|
||||||
|
"description": "초기 정적 설정 파일에서 움직임 감지가 활성화되어 있었는지 여부를 나타냅니다."
|
||||||
|
},
|
||||||
|
"raw_mask": {
|
||||||
|
"label": "원시 마스크"
|
||||||
|
}
|
||||||
|
},
|
||||||
|
"objects": {
|
||||||
|
"label": "객체",
|
||||||
|
"description": "추적할 레이블 목록과 객체별 필터를 포함한 기본 객체 추적 설정입니다.",
|
||||||
|
"track": {
|
||||||
|
"label": "추적 대상 객체",
|
||||||
|
"description": "이 카메라에서 추적할 객체 레이블 목록입니다."
|
||||||
|
},
|
||||||
|
"filters": {
|
||||||
|
"label": "객체 필터",
|
||||||
|
"description": "오탐지를 줄이기 위해 감지된 객체에 적용할 필터 설정(면적, 비율, 신뢰도)입니다.",
|
||||||
|
"min_area": {
|
||||||
|
"label": "최소 객체 면적",
|
||||||
|
"description": "이 객체 유형에 필요한 최소 바운딩 박스 면적입니다. 픽셀 수(정수) 또는 비율(0.000001~0.99 사이의 실수)로 지정할 수 있습니다."
|
||||||
|
},
|
||||||
|
"max_area": {
|
||||||
|
"label": "최대 객체 면적",
|
||||||
|
"description": "이 객체 유형에 허용되는 최대 바운딩 박스 면적입니다. 픽셀 수(정수) 또는 비율(0.000001~0.99 사이의 실수)로 지정할 수 있습니다."
|
||||||
|
},
|
||||||
|
"min_ratio": {
|
||||||
|
"label": "최소 가로세로 비율",
|
||||||
|
"description": "바운딩 박스에 요구되는 최소 너비/높이 비율입니다."
|
||||||
|
},
|
||||||
|
"max_ratio": {
|
||||||
|
"label": "최대 가로세로 비율",
|
||||||
|
"description": "바운딩 박스에 허용되는 최대 너비/높이 비율입니다."
|
||||||
|
},
|
||||||
|
"threshold": {
|
||||||
|
"label": "신뢰도 임계값",
|
||||||
|
"description": "객체를 실제 탐지로 인정하기 위해 요구되는 평균 감지 신뢰도 임계값입니다."
|
||||||
|
},
|
||||||
|
"min_score": {
|
||||||
|
"label": "최소 신뢰도",
|
||||||
|
"description": "객체로 카운트되기 위해 필요한 단일 프레임 최소 감지 신뢰도입니다."
|
||||||
|
},
|
||||||
|
"mask": {
|
||||||
|
"label": "필터 마스크",
|
||||||
|
"description": "프레임 내에서 이 필터가 적용될 다각형 좌표입니다."
|
||||||
|
},
|
||||||
|
"raw_mask": {
|
||||||
|
"label": "원시 마스크"
|
||||||
|
}
|
||||||
|
},
|
||||||
|
"mask": {
|
||||||
|
"label": "객체 마스크",
|
||||||
|
"description": "지정된 영역에서 객체 감지를 방지하기 위한 마스크 다각형입니다."
|
||||||
|
},
|
||||||
|
"raw_mask": {
|
||||||
|
"label": "원시 마스크"
|
||||||
|
},
|
||||||
|
"genai": {
|
||||||
|
"label": "생성형 AI (GenAI) 객체 설정",
|
||||||
|
"description": "추적된 객체를 설명하고 분석용 프레임을 전송하기 위한 GenAI 옵션입니다.",
|
||||||
|
"enabled": {
|
||||||
|
"label": "GenAI 활성화",
|
||||||
|
"description": "기본적으로 추적된 객체에 대한 GenAI 설명 생성을 활성화합니다."
|
||||||
|
},
|
||||||
|
"use_snapshot": {
|
||||||
|
"label": "스냅샷 사용",
|
||||||
|
"description": "GenAI 설명 생성 시 썸네일 대신 객체 스냅샷을 사용합니다."
|
||||||
|
},
|
||||||
|
"prompt": {
|
||||||
|
"label": "캡션 프롬프트",
|
||||||
|
"description": "GenAI로 설명을 생성할 때 사용할 기본 프롬프트 템플릿입니다."
|
||||||
|
},
|
||||||
|
"object_prompts": {
|
||||||
|
"label": "객체별 프롬프트",
|
||||||
|
"description": "특정 레이블에 대한 GenAI 결과를 맞춤화하기 위한 객체별 개별 프롬프트입니다."
|
||||||
|
},
|
||||||
|
"objects": {
|
||||||
|
"label": "GenAI 대상 객체",
|
||||||
|
"description": "기본적으로 GenAI로 전송할 객체 레이블 목록입니다."
|
||||||
|
},
|
||||||
|
"required_zones": {
|
||||||
|
"label": "필수 구역",
|
||||||
|
"description": "GenAI 설명 생성 대상이 되기 위해 객체가 반드시 진입해야 하는 구역입니다."
|
||||||
|
},
|
||||||
|
"debug_save_thumbnails": {
|
||||||
|
"label": "썸네일 저장",
|
||||||
|
"description": "디버깅 및 검토를 위해 GenAI로 전송된 썸네일을 저장합니다."
|
||||||
|
},
|
||||||
|
"send_triggers": {
|
||||||
|
"label": "GenAI 전송 트리거",
|
||||||
|
"description": "프레임을 GenAI로 전송할 시점(추적 종료 시, 주요 업데이트 후 등)을 정의합니다.",
|
||||||
|
"tracked_object_end": {
|
||||||
|
"label": "종료 시 전송",
|
||||||
|
"description": "추적 객체의 동작이 종료되었을 때 GenAI에 요청을 보냅니다."
|
||||||
|
},
|
||||||
|
"after_significant_updates": {
|
||||||
|
"label": "조기 GenAI 트리거",
|
||||||
|
"description": "추적 객체에 지정된 횟수만큼 주요 변화가 발생한 후 조기에 GenAI 요청을 보냅니다."
|
||||||
|
}
|
||||||
|
},
|
||||||
|
"enabled_in_config": {
|
||||||
|
"label": "초기 GenAI 상태",
|
||||||
|
"description": "초기 정적 설정에서 GenAI가 활성화되었는지 여부를 나타냅니다."
|
||||||
|
}
|
||||||
|
}
|
||||||
|
},
|
||||||
|
"record": {
|
||||||
|
"label": "녹화",
|
||||||
|
"description": "이 카메라의 녹화 및 보관 주기 설정입니다.",
|
||||||
|
"enabled": {
|
||||||
|
"label": "녹화 활성화",
|
||||||
|
"description": "이 카메라의 녹화를 활성화하거나 비활성화합니다."
|
||||||
|
},
|
||||||
|
"expire_interval": {
|
||||||
|
"label": "녹화 파일 정리 주기",
|
||||||
|
"description": "만료된 녹화 세그먼트를 삭제하는 정리 작업 간격(분)입니다."
|
||||||
|
},
|
||||||
|
"continuous": {
|
||||||
|
"label": "상시 녹화 보관",
|
||||||
|
"description": "객체 추적이나 움직임 여부와 상관없이 녹화본을 보관할 일수입니다. 알림이나 감지 이벤트만 보관하려면 0으로 설정하세요.",
|
||||||
|
"days": {
|
||||||
|
"label": "보관 일수",
|
||||||
|
"description": "녹화본 보관 일수입니다."
|
||||||
|
}
|
||||||
|
},
|
||||||
|
"motion": {
|
||||||
|
"label": "움직임 녹화 보관",
|
||||||
|
"description": "추적 객체가 없더라도 움직임에 의해 트리거된 녹화본을 보관할 일수입니다. 알림이나 감지 이벤트만 보관하려면 0으로 설정하세요.",
|
||||||
|
"days": {
|
||||||
|
"label": "보관 일수",
|
||||||
|
"description": "녹화본 보관 일수입니다."
|
||||||
|
}
|
||||||
|
},
|
||||||
|
"detections": {
|
||||||
|
"label": "감지 녹화 보관",
|
||||||
|
"description": "이벤트 전/후 버퍼 캡처 시간을 포함한 감지 이벤트의 녹화 보관 설정입니다.",
|
||||||
|
"pre_capture": {
|
||||||
|
"label": "사전 캡처 시간",
|
||||||
|
"description": "감지 이벤트 시작 전 녹화에 포함할 시간(초)입니다."
|
||||||
|
},
|
||||||
|
"post_capture": {
|
||||||
|
"label": "사후 캡처 시간",
|
||||||
|
"description": "감지 이벤트 종료 후 녹화에 포함할 시간(초)입니다."
|
||||||
|
},
|
||||||
|
"retain": {
|
||||||
|
"label": "이벤트 보관",
|
||||||
|
"description": "감지 이벤트 녹화본의 보관 설정입니다.",
|
||||||
|
"days": {
|
||||||
|
"label": "보관 일수",
|
||||||
|
"description": "감지 이벤트 녹화본을 보관할 일수입니다."
|
||||||
|
},
|
||||||
|
"mode": {
|
||||||
|
"label": "보관 모드",
|
||||||
|
"description": "보관 모드: all(모든 구간 저장), motion(움직임이 있는 구간 저장), active_objects(활동 중인 객체가 있는 구간 저장)."
|
||||||
|
}
|
||||||
|
}
|
||||||
|
},
|
||||||
|
"alerts": {
|
||||||
|
"label": "알림 녹화 보관",
|
||||||
|
"description": "이벤트 전/후 버퍼 캡처 시간을 포함한 알림 이벤트의 녹화 보관 설정입니다.",
|
||||||
|
"pre_capture": {
|
||||||
|
"label": "사전 캡처 시간",
|
||||||
|
"description": "알림 이벤트 시작 전 녹화에 포함할 시간(초)입니다."
|
||||||
|
},
|
||||||
|
"post_capture": {
|
||||||
|
"label": "사후 캡처 시간",
|
||||||
|
"description": "알림 이벤트 종료 후 녹화에 포함할 시간(초)입니다."
|
||||||
|
},
|
||||||
|
"retain": {
|
||||||
|
"label": "이벤트 보관",
|
||||||
|
"description": "알림 이벤트 녹화본의 보관 설정입니다.",
|
||||||
|
"days": {
|
||||||
|
"label": "보관 일수",
|
||||||
|
"description": "알림 이벤트 녹화본을 보관할 일수입니다."
|
||||||
|
},
|
||||||
|
"mode": {
|
||||||
|
"label": "보관 모드",
|
||||||
|
"description": "보관 모드: all(모든 구간 저장), motion(움직임이 있는 구간 저장), active_objects(활동 중인 객체가 있는 구간 저장)."
|
||||||
|
}
|
||||||
|
}
|
||||||
|
},
|
||||||
|
"export": {
|
||||||
|
"label": "내보내기 설정",
|
||||||
|
"description": "타임랩스 및 하드웨어 가속 등 녹화본 내보내기 시 적용할 설정입니다.",
|
||||||
|
"hwaccel_args": {
|
||||||
|
"label": "내보내기 하드웨어 가속 인수",
|
||||||
|
"description": "내보내기 및 트랜스코딩 작업에 사용할 하드웨어 가속 인수입니다."
|
||||||
|
},
|
||||||
|
"max_concurrent": {
|
||||||
|
"label": "최대 동시 내보내기 수",
|
||||||
|
"description": "동시에 처리할 수 있는 최대 내보내기 작업 수입니다."
|
||||||
|
},
|
||||||
|
"chapters": {
|
||||||
|
"label": "내보낸 녹화 영상에 삽입할 챕터 메타데이터"
|
||||||
|
}
|
||||||
|
},
|
||||||
|
"preview": {
|
||||||
|
"label": "미리보기 설정",
|
||||||
|
"description": "UI에 표시되는 녹화 미리보기의 화질을 제어하는 설정입니다.",
|
||||||
|
"quality": {
|
||||||
|
"label": "미리보기 화질",
|
||||||
|
"description": "미리보기 화질 레벨 (very_low, low, medium, high, very_high)."
|
||||||
|
}
|
||||||
|
},
|
||||||
|
"enabled_in_config": {
|
||||||
|
"label": "초기 녹화 상태",
|
||||||
|
"description": "초기 정적 구성에서 녹화가 활성화되어 있었는지 여부를 나타냅니다."
|
||||||
|
}
|
||||||
|
},
|
||||||
|
"review": {
|
||||||
|
"label": "다시보기",
|
||||||
|
"description": "UI 및 저장소에서 사용하는 알림, 감지 및 GenAI 요약을 제어하는 설정입니다.",
|
||||||
|
"alerts": {
|
||||||
|
"label": "알림 설정",
|
||||||
|
"description": "어떤 추적 객체가 알림을 생성하고 알림을 어떻게 보관할지 결정하는 설정입니다.",
|
||||||
|
"enabled": {
|
||||||
|
"label": "알림 활성화",
|
||||||
|
"description": "이 카메라의 알림 생성을 활성화하거나 비활성화합니다."
|
||||||
|
},
|
||||||
|
"labels": {
|
||||||
|
"label": "알림 대상 레이블",
|
||||||
|
"description": "알림을 트리거할 객체 레이블 목록입니다 (예: car, person)."
|
||||||
|
},
|
||||||
|
"required_zones": {
|
||||||
|
"label": "필수 구역",
|
||||||
|
"description": "알림으로 분류되기 위해 객체가 진입해야 하는 구역입니다. 모든 구역을 허용하려면 비워 두세요."
|
||||||
|
},
|
||||||
|
"enabled_in_config": {
|
||||||
|
"label": "초기 알림 상태",
|
||||||
|
"description": "정적 설정에서 알림이 원래 활성화되어 있었는지 여부를 추적합니다."
|
||||||
|
},
|
||||||
|
"cutoff_time": {
|
||||||
|
"label": "알림 종료 대기 시간",
|
||||||
|
"description": "알림 유발 활동이 멈춘 후 알림을 공식 종료하기까지 대기할 시간(초)입니다."
|
||||||
|
}
|
||||||
|
},
|
||||||
|
"detections": {
|
||||||
|
"label": "감지 이벤트 설정",
|
||||||
|
"description": "어떤 추적 객체가 일반 감지(알림 제외) 이벤트를 생성하고 어떻게 보관할지 결정하는 설정입니다.",
|
||||||
|
"enabled": {
|
||||||
|
"label": "감지 이벤트 활성화",
|
||||||
|
"description": "이 카메라의 감지 이벤트를 활성화하거나 비활성화합니다."
|
||||||
|
},
|
||||||
|
"labels": {
|
||||||
|
"label": "감지 대상 레이블",
|
||||||
|
"description": "일반 감지 이벤트로 인정할 객체 레이블 목록입니다."
|
||||||
|
},
|
||||||
|
"required_zones": {
|
||||||
|
"label": "필수 구역",
|
||||||
|
"description": "감지 이벤트로 처리되기 위해 객체가 진입해야 하는 구역입니다. 모든 구역을 허용하려면 비워 두세요."
|
||||||
|
},
|
||||||
|
"cutoff_time": {
|
||||||
|
"label": "감지 종료 대기 시간",
|
||||||
|
"description": "감지 유발 활동이 멈춘 후 이벤트를 공식 종료하기까지 대기할 시간(초)입니다."
|
||||||
|
},
|
||||||
|
"enabled_in_config": {
|
||||||
|
"label": "초기 감지 상태",
|
||||||
|
"description": "정적 설정에서 감지 이벤트가 원래 활성화되어 있었는지 여부를 추적합니다."
|
||||||
|
}
|
||||||
|
},
|
||||||
|
"genai": {
|
||||||
|
"label": "GenAI 다시보기 설정",
|
||||||
|
"description": "다시보기 항목의 설명과 요약을 생성하기 위한 생성형 AI 사용을 제어합니다.",
|
||||||
|
"enabled": {
|
||||||
|
"label": "GenAI 설명 활성화",
|
||||||
|
"description": "다시보기 항목에 대한 GenAI 기반 설명 및 요약 생성을 활성화하거나 비활성화합니다."
|
||||||
|
},
|
||||||
|
"alerts": {
|
||||||
|
"label": "알림에 GenAI 활성화",
|
||||||
|
"description": "알림 항목에 대해 GenAI로 설명을 생성합니다."
|
||||||
|
},
|
||||||
|
"detections": {
|
||||||
|
"label": "감지에 GenAI 활성화",
|
||||||
|
"description": "감지 항목에 대해 GenAI로 설명을 생성합니다."
|
||||||
|
},
|
||||||
|
"image_source": {
|
||||||
|
"label": "다시보기 이미지 소스",
|
||||||
|
"description": "GenAI로 전송할 이미지 소스 ('preview' 또는 'recordings'). 'recordings'는 화질이 더 높지만 토큰 사용량이 증가합니다."
|
||||||
|
},
|
||||||
|
"additional_concerns": {
|
||||||
|
"label": "추가 주의 사항",
|
||||||
|
"description": "이 카메라의 활동을 평가할 때 GenAI가 고려해야 할 추가 주의 사항이나 참고 사항 목록입니다."
|
||||||
|
},
|
||||||
|
"debug_save_thumbnails": {
|
||||||
|
"label": "썸네일 저장",
|
||||||
|
"description": "디버깅 및 검토를 위해 GenAI 제공업체로 전송된 썸네일을 저장합니다."
|
||||||
|
},
|
||||||
|
"enabled_in_config": {
|
||||||
|
"label": "초기 GenAI 상태",
|
||||||
|
"description": "정적 설정에서 GenAI 다시보기가 원래 활성화되어 있었는지 여부를 추적합니다."
|
||||||
|
},
|
||||||
|
"preferred_language": {
|
||||||
|
"label": "선호 언어",
|
||||||
|
"description": "GenAI가 생성할 응답에 요청할 선호 언어입니다."
|
||||||
|
},
|
||||||
|
"activity_context_prompt": {
|
||||||
|
"label": "활동 맥락 프롬프트",
|
||||||
|
"description": "GenAI 요약에 맥락을 제공하기 위해 어떤 활동이 의심스럽거나 정상적인지 설명하는 커스텀 프롬프트입니다."
|
||||||
|
}
|
||||||
|
}
|
||||||
|
},
|
||||||
|
"semantic_search": {
|
||||||
|
"label": "시맨틱(의미 기반) 검색",
|
||||||
|
"description": "유사한 항목을 찾기 위해 객체 임베딩을 구축하고 쿼리하는 시맨틱 검색 설정입니다.",
|
||||||
|
"triggers": {
|
||||||
|
"label": "트리거",
|
||||||
|
"description": "카메라별 시맨틱 검색 트리거를 위한 동작 및 매칭 기준입니다.",
|
||||||
|
"friendly_name": {
|
||||||
|
"label": "트리거 별칭",
|
||||||
|
"description": "UI에 표시될 이 트리거의 친숙한 이름(선택 사항)입니다."
|
||||||
|
},
|
||||||
|
"enabled": {
|
||||||
|
"label": "트리거 활성화",
|
||||||
|
"description": "이 시맨틱 검색 트리거를 활성화하거나 비활성화합니다."
|
||||||
|
},
|
||||||
|
"type": {
|
||||||
|
"label": "트리거 유형",
|
||||||
|
"description": "트리거 유형: 'thumbnail'(이미지 비교) 또는 'description'(텍스트 비교)."
|
||||||
|
},
|
||||||
|
"data": {
|
||||||
|
"label": "트리거 내용",
|
||||||
|
"description": "추적 객체와 매칭할 텍스트 문구 또는 썸네일 ID입니다."
|
||||||
|
},
|
||||||
|
"threshold": {
|
||||||
|
"label": "트리거 임계값",
|
||||||
|
"description": "이 트리거를 작동시키기 위해 요구되는 최소 유사도 점수(0~1)입니다."
|
||||||
|
},
|
||||||
|
"actions": {
|
||||||
|
"label": "트리거 동작",
|
||||||
|
"description": "트리거가 매칭되었을 때 실행할 동작 목록입니다 (notification, sub_label, attribute)."
|
||||||
|
}
|
||||||
|
}
|
||||||
|
},
|
||||||
|
"snapshots": {
|
||||||
|
"label": "스냅샷",
|
||||||
|
"description": "이 카메라에서 API로 생성되는 추적 객체 스냅샷 설정입니다.",
|
||||||
|
"enabled": {
|
||||||
|
"label": "스냅샷 활성화",
|
||||||
|
"description": "이 카메라의 스냅샷 저장을 활성화하거나 비활성화합니다."
|
||||||
|
},
|
||||||
|
"timestamp": {
|
||||||
|
"label": "타임스탬프 오버레이",
|
||||||
|
"description": "API 스냅샷에 타임스탬프를 덧씌워 표시합니다."
|
||||||
|
},
|
||||||
|
"bounding_box": {
|
||||||
|
"label": "바운딩 박스 오버레이",
|
||||||
|
"description": "API 스냅샷에 추적 객체의 바운딩 박스를 그립니다."
|
||||||
|
},
|
||||||
|
"crop": {
|
||||||
|
"label": "스냅샷 크롭",
|
||||||
|
"description": "API 스냅샷을 감지된 객체의 바운딩 박스 크기로 잘라냅니다."
|
||||||
|
},
|
||||||
|
"required_zones": {
|
||||||
|
"label": "필수 구역",
|
||||||
|
"description": "스냅샷을 저장하기 위해 객체가 반드시 진입해야 하는 구역입니다."
|
||||||
|
},
|
||||||
|
"height": {
|
||||||
|
"label": "스냅샷 높이",
|
||||||
|
"description": "API 스냅샷을 리사이즈할 높이(픽셀)입니다. 비워 두면 원본 크기를 유지합니다."
|
||||||
|
},
|
||||||
|
"retain": {
|
||||||
|
"label": "스냅샷 보관",
|
||||||
|
"description": "기본 보관 일수 및 객체별 재정의를 포함한 스냅샷 보관 설정입니다.",
|
||||||
|
"default": {
|
||||||
|
"label": "기본 보관 일수",
|
||||||
|
"description": "스냅샷을 보관할 기본 일수입니다."
|
||||||
|
},
|
||||||
|
"objects": {
|
||||||
|
"label": "객체별 보관 일수",
|
||||||
|
"description": "스냅샷 보관 일수를 객체 유형별로 개별 재정의합니다."
|
||||||
|
}
|
||||||
|
},
|
||||||
|
"quality": {
|
||||||
|
"label": "스냅샷 화질",
|
||||||
|
"description": "저장할 스냅샷의 인코딩 품질입니다 (0~100)."
|
||||||
|
}
|
||||||
|
},
|
||||||
|
"timestamp_style": {
|
||||||
|
"label": "타임스탬프 스타일",
|
||||||
|
"description": "스냅샷 및 디버그 뷰에 적용되는 타임스탬프 텍스트 스타일 옵션입니다.",
|
||||||
|
"position": {
|
||||||
|
"label": "타임스탬프 위치",
|
||||||
|
"description": "이미지 내 타임스탬프 위치 (tl: 좌상단, tr: 우상단, bl: 좌하단, br: 우하단)."
|
||||||
|
},
|
||||||
|
"format": {
|
||||||
|
"label": "타임스탬프 형식",
|
||||||
|
"description": "타임스탬프 표시에 사용할 날짜/시간 형식 문자열입니다 (Python datetime 포맷 코드)."
|
||||||
|
},
|
||||||
|
"color": {
|
||||||
|
"label": "타임스탬프 색상",
|
||||||
|
"description": "타임스탬프 텍스트의 RGB 색상값입니다 (모든 값은 0~255).",
|
||||||
|
"red": {
|
||||||
|
"label": "빨강 (Red)",
|
||||||
|
"description": "타임스탬프 색상의 빨간색 성분 (0-255)."
|
||||||
|
},
|
||||||
|
"green": {
|
||||||
|
"label": "초록 (Green)",
|
||||||
|
"description": "타임스탬프 색상의 초록색 성분 (0-255)."
|
||||||
|
},
|
||||||
|
"blue": {
|
||||||
|
"label": "파랑 (Blue)",
|
||||||
|
"description": "타임스탬프 색상의 파란색 성분 (0-255)."
|
||||||
|
}
|
||||||
|
},
|
||||||
|
"thickness": {
|
||||||
|
"label": "타임스탬프 두께",
|
||||||
|
"description": "타임스탬프 글자의 선 두께입니다."
|
||||||
|
},
|
||||||
|
"effect": {
|
||||||
|
"label": "타임스탬프 효과",
|
||||||
|
"description": "타임스탬프 텍스트에 적용할 시각 효과입니다 (none, solid, shadow)."
|
||||||
|
}
|
||||||
|
},
|
||||||
|
"best_image_timeout": {
|
||||||
|
"label": "최적 이미지 대기 시간",
|
||||||
|
"description": "가장 높은 신뢰도 점수를 가진 최적 이미지를 선별하기 위해 대기할 시간입니다."
|
||||||
|
},
|
||||||
|
"onvif": {
|
||||||
|
"label": "ONVIF",
|
||||||
|
"description": "이 카메라의 ONVIF 연결 및 PTZ 자동 추적 설정입니다.",
|
||||||
|
"host": {
|
||||||
|
"label": "ONVIF 호스트",
|
||||||
|
"description": "이 카메라의 ONVIF 서비스 호스트 주소(및 선택적 프로토콜 스키마)입니다."
|
||||||
|
},
|
||||||
|
"port": {
|
||||||
|
"label": "ONVIF 포트",
|
||||||
|
"description": "ONVIF 서비스 포트 번호입니다."
|
||||||
|
},
|
||||||
|
"user": {
|
||||||
|
"label": "ONVIF 사용자 이름",
|
||||||
|
"description": "ONVIF 인증에 사용할 사용자 이름입니다 (일부 기기는 관리자 계정이 필요함)."
|
||||||
|
},
|
||||||
|
"password": {
|
||||||
|
"label": "ONVIF 비밀번호",
|
||||||
|
"description": "ONVIF 인증 비밀번호입니다."
|
||||||
|
},
|
||||||
|
"tls_insecure": {
|
||||||
|
"label": "TLS 검증 비활성화",
|
||||||
|
"description": "ONVIF 통신 시 TLS 인증서 검증을 건너뛰고 Digest 인증을 끕니다 (안전하지 않으므로 신뢰할 수 있는 네트워크에서만 사용하세요)."
|
||||||
|
},
|
||||||
|
"profile": {
|
||||||
|
"label": "ONVIF 프로필",
|
||||||
|
"description": "PTZ 제어에 사용할 특정 ONVIF 미디어 프로필의 토큰 또는 이름입니다. 지정하지 않으면 유효한 PTZ 구성이 있는 첫 번째 프로필이 자동 선택됩니다."
|
||||||
|
},
|
||||||
|
"autotracking": {
|
||||||
|
"label": "자동 추적 (Autotracking)",
|
||||||
|
"description": "PTZ 카메라를 움직여 움직이는 객체를 프레임 중앙에 자동으로 추적하고 유지합니다.",
|
||||||
|
"enabled": {
|
||||||
|
"label": "자동 추적 활성화",
|
||||||
|
"description": "감지된 객체에 대한 PTZ 카메라의 자동 추적을 활성화하거나 비활성화합니다."
|
||||||
|
},
|
||||||
|
"calibrate_on_startup": {
|
||||||
|
"label": "시작 시 모터 캘리브레이션",
|
||||||
|
"description": "추적 정확도를 향상하기 위해 부팅 시 PTZ 모터 속도를 측정합니다. 보정 완료 후 Frigate가 movement_weights 값을 자동으로 업데이트합니다."
|
||||||
|
},
|
||||||
|
"zooming": {
|
||||||
|
"label": "줌 모드",
|
||||||
|
"description": "줌 동작 제어: disabled(팬/틸트만 사용), absolute(가장 안정적인 호환성), relative(팬/틸트/줌 동시 작동)."
|
||||||
|
},
|
||||||
|
"zoom_factor": {
|
||||||
|
"label": "줌 배율",
|
||||||
|
"description": "추적 중인 객체의 확대 수준을 제어합니다. 값이 낮으면 주변 시야를 넓게 유지하고, 높으면 대상을 가깝게 확대하지만 추적을 놓칠 위험이 있습니다 (0.1~0.75 사이 값)."
|
||||||
|
},
|
||||||
|
"track": {
|
||||||
|
"label": "추적 대상 객체",
|
||||||
|
"description": "자동 추적을 작동시킬 객체 유형 목록입니다."
|
||||||
|
},
|
||||||
|
"required_zones": {
|
||||||
|
"label": "필수 구역",
|
||||||
|
"description": "자동 추적이 시작되기 위해 객체가 먼저 진입해야 하는 구역입니다."
|
||||||
|
},
|
||||||
|
"return_preset": {
|
||||||
|
"label": "복귀 프리셋",
|
||||||
|
"description": "추적이 끝난 후 카메라가 돌아갈 펌웨어상의 ONVIF 프리셋 이름입니다."
|
||||||
|
},
|
||||||
|
"timeout": {
|
||||||
|
"label": "복귀 대기 시간",
|
||||||
|
"description": "객체를 놓친 후 카메라가 프리셋 위치로 복귀하기까지 대기할 시간(초)입니다."
|
||||||
|
},
|
||||||
|
"movement_weights": {
|
||||||
|
"label": "이동 가중치",
|
||||||
|
"description": "카메라 캘리브레이션을 통해 자동 생성된 보정값입니다. 수동으로 수정하지 마세요."
|
||||||
|
},
|
||||||
|
"enabled_in_config": {
|
||||||
|
"label": "초기 자동 추적 상태",
|
||||||
|
"description": "설정에서 자동 추적이 활성화되어 있었는지 여부를 추적하는 내부 필드입니다."
|
||||||
|
}
|
||||||
|
},
|
||||||
|
"ignore_time_mismatch": {
|
||||||
|
"label": "시간 불일치 무시",
|
||||||
|
"description": "ONVIF 통신 시 카메라와 Frigate 서버 간의 시간 동기화 차이를 무시합니다."
|
||||||
|
}
|
||||||
|
},
|
||||||
|
"type": {
|
||||||
|
"label": "카메라 유형",
|
||||||
|
"description": "카메라 유형"
|
||||||
|
},
|
||||||
|
"ui": {
|
||||||
|
"label": "카메라 UI",
|
||||||
|
"description": "UI에서 이 카메라의 표시 순서와 가시성을 제어합니다. 순서는 기본 대시보드에 영향을 미칩니다. 더 세부적인 설정은 카메라 그룹을 사용하세요.",
|
||||||
|
"order": {
|
||||||
|
"label": "UI 순서",
|
||||||
|
"description": "기본 대시보드 및 목록에서 카메라를 정렬하는 데 사용되는 숫자 순서입니다 (큰 숫자일수록 뒤에 표시됨)."
|
||||||
|
},
|
||||||
|
"dashboard": {
|
||||||
|
"label": "실시간 대시보드에 표시",
|
||||||
|
"description": "기본 '모든 카메라' 실시간 대시보드에 이 카메라를 표시할지 여부를 전환합니다. 비활성화해도 카메라 그룹이나 설정 등 UI의 다른 곳에서는 계속 접근할 수 있습니다."
|
||||||
|
},
|
||||||
|
"review": {
|
||||||
|
"label": "다시보기에 표시",
|
||||||
|
"description": "다시보기 페이지(카메라 필터, 모션 검토 및 기록 뷰 포함)에서 이 카메라를 표시할지 여부를 전환합니다."
|
||||||
|
}
|
||||||
|
},
|
||||||
|
"webui_url": {
|
||||||
|
"label": "카메라 관리 URL",
|
||||||
|
"description": "시스템 페이지에서 카메라 웹 관리 페이지로 바로 연결되는 URL입니다"
|
||||||
|
},
|
||||||
|
"profiles": {
|
||||||
|
"label": "프로필",
|
||||||
|
"description": "런타임 중에 활성화할 수 있는 부분 재정의를 포함한 명명된 설정 프로필입니다."
|
||||||
|
},
|
||||||
|
"enabled_in_config": {
|
||||||
|
"label": "초기 카메라 상태",
|
||||||
|
"description": "카메라의 원래 구성 상태를 추적합니다."
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|||||||
File diff suppressed because it is too large
Load Diff
@@ -1,8 +1,8 @@
|
|||||||
{
|
{
|
||||||
"audio": {
|
"audio": {
|
||||||
"global": {
|
"global": {
|
||||||
"detection": "전체 감지",
|
"detection": "전역 감지",
|
||||||
"sensitivity": "전체 민감도"
|
"sensitivity": "전역 민감도"
|
||||||
},
|
},
|
||||||
"cameras": {
|
"cameras": {
|
||||||
"detection": "감지",
|
"detection": "감지",
|
||||||
@@ -51,7 +51,23 @@
|
|||||||
"filtering": "전역 필터링"
|
"filtering": "전역 필터링"
|
||||||
},
|
},
|
||||||
"cameras": {
|
"cameras": {
|
||||||
"tracking": "추적"
|
"tracking": "추적",
|
||||||
|
"filtering": "필터링"
|
||||||
|
}
|
||||||
|
},
|
||||||
|
"record": {
|
||||||
|
"global": {
|
||||||
|
"retention": "전역 보관",
|
||||||
|
"events": "전역 이벤트"
|
||||||
|
},
|
||||||
|
"cameras": {
|
||||||
|
"retention": "보관",
|
||||||
|
"events": "이벤트"
|
||||||
|
}
|
||||||
|
},
|
||||||
|
"ffmpeg": {
|
||||||
|
"cameras": {
|
||||||
|
"cameraFfmpeg": "카메라별 FFmpeg 인수"
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -1,32 +1,35 @@
|
|||||||
{
|
{
|
||||||
"minimum": "최소 {{limit}} 이상",
|
"minimum": "{{limit}} 이상이어야 합니다",
|
||||||
"maximum": "최대 {{limit}} 이하",
|
"maximum": "{{limit}} 이하여야 합니다",
|
||||||
"exclusiveMinimum": "최소 {{limit}} 초과",
|
"exclusiveMinimum": "{{limit}} 초과여야 합니다",
|
||||||
"exclusiveMaximum": "최대 {{limit}} 미만",
|
"exclusiveMaximum": "{{limit}} 미만이어야 합니다",
|
||||||
"minLength": "{{limit}}자 이상이어야 합니다",
|
"minLength": "최소 {{limit}}자 이상이어야 합니다",
|
||||||
"maxLength": "{{limit}}자 이하여야 합니다",
|
"maxLength": "최대 {{limit}}자 이하여야 합니다",
|
||||||
"minItems": "{{limit}}개 이상이어야 합니다",
|
"minItems": "최소 {{limit}}개 항목이 있어야 합니다",
|
||||||
"maxItems": "{{limit}}개 이하여야 합니다",
|
"maxItems": "최대 {{limit}}개 항목까지 가능합니다",
|
||||||
"pattern": "잘못된 형식",
|
"pattern": "잘못된 형식입니다",
|
||||||
"required": "이 항목은 필수 입력 사항입니다",
|
"required": "이 항목은 필수입니다",
|
||||||
"type": "잘못된 유형입니다",
|
"type": "유효하지 않은 값 형식입니다",
|
||||||
"enum": "허용된 값 중 하나여야 합니다",
|
"enum": "허용된 값 중 하나여야 합니다",
|
||||||
"const": "값이 예상된 상수와 일치하지 않습니다",
|
"const": "예상된 상수 값과 일치하지 않습니다",
|
||||||
"uniqueItems": "모든 항목은 고유해야 합니다",
|
"uniqueItems": "모든 항목은 고유해야 합니다",
|
||||||
"format": "잘못된 형식",
|
"format": "유효하지 않은 형식입니다",
|
||||||
"additionalProperties": "알 수 없는 속성은 허용되지 않습니다",
|
"additionalProperties": "알 수 없는 속성은 허용되지 않습니다",
|
||||||
"oneOf": "허용된 형식 중 하나와 일치해야 합니다",
|
"oneOf": "허용된 스키마 중 정확히 하나와 일치해야 합니다",
|
||||||
"anyOf": "허용된 형식 중 최소 하나와 일치해야 합니다",
|
"anyOf": "허용된 스키마 중 최소 하나와 일치해야 합니다",
|
||||||
"proxy": {
|
"proxy": {
|
||||||
"header_map": {
|
"header_map": {
|
||||||
"roleHeaderRequired": "역할 매핑이 설정된 경우 역할 헤더(Role header)가 필수입니다."
|
"roleHeaderRequired": "역할 매핑이 구성된 경우 역할 헤더가 필수입니다."
|
||||||
}
|
}
|
||||||
},
|
},
|
||||||
"ffmpeg": {
|
"ffmpeg": {
|
||||||
"inputs": {
|
"inputs": {
|
||||||
"rolesUnique": "각 역할은 하나의 입력 스트림에만 할당할 수 있습니다.",
|
"rolesUnique": "각 역할은 하나의 입력 스트림에만 할당할 수 있습니다.",
|
||||||
"detectRequired": "최소 하나의 입력 스트림에 'detect' 역할이 할당되어야 합니다.",
|
"detectRequired": "최소 하나의 입력 스트림에 'detect' 역할을 지정해야 합니다.",
|
||||||
"hwaccelDetectOnly": "하드웨어 가속 설정은 'detect' 스트림에서만 가능합니다."
|
"hwaccelDetectOnly": "하드웨어 가속 인수(hwaccel_args)는 detect 역할을 가진 입력 스트림에서만 정의할 수 있습니다."
|
||||||
}
|
}
|
||||||
|
},
|
||||||
|
"detect": {
|
||||||
|
"dimensionMustBeEven": "짝수여야 합니다."
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -1,8 +1,8 @@
|
|||||||
{
|
{
|
||||||
"person": "사람",
|
"person": "사람",
|
||||||
"bicycle": "자전거",
|
"bicycle": "자전거",
|
||||||
"car": "차량",
|
"car": "승용차",
|
||||||
"motorcycle": "원동기",
|
"motorcycle": "오토바이",
|
||||||
"airplane": "비행기",
|
"airplane": "비행기",
|
||||||
"bus": "버스",
|
"bus": "버스",
|
||||||
"train": "기차",
|
"train": "기차",
|
||||||
@@ -24,31 +24,31 @@
|
|||||||
"zebra": "얼룩말",
|
"zebra": "얼룩말",
|
||||||
"giraffe": "기린",
|
"giraffe": "기린",
|
||||||
"hat": "모자",
|
"hat": "모자",
|
||||||
"backpack": "백팩",
|
"backpack": "배낭",
|
||||||
"umbrella": "우산",
|
"umbrella": "우산",
|
||||||
"shoe": "신발",
|
"shoe": "신발",
|
||||||
"eye_glasses": "안경",
|
"eye_glasses": "안경",
|
||||||
"handbag": "핸드백",
|
"handbag": "핸드백",
|
||||||
"tie": "타이",
|
"tie": "넥타이",
|
||||||
"suitcase": "슈트케이스",
|
"suitcase": "여행 가방",
|
||||||
"frisbee": "프리스비",
|
"frisbee": "프리스비(원반)",
|
||||||
"skis": "스키",
|
"skis": "스키",
|
||||||
"snowboard": "스노우보드",
|
"snowboard": "스노우보드",
|
||||||
"sports_ball": "스포츠 볼",
|
"sports_ball": "공",
|
||||||
"kite": "연",
|
"kite": "연",
|
||||||
"baseball_bat": "야구 방망이",
|
"baseball_bat": "야구 방망이",
|
||||||
"baseball_glove": "야구 글로브",
|
"baseball_glove": "야구 글러브",
|
||||||
"skateboard": "스케이트보드",
|
"skateboard": "스케이트보드",
|
||||||
"surfboard": "서핑보드",
|
"surfboard": "서핑보드",
|
||||||
"tennis_racket": "테니스 라켓",
|
"tennis_racket": "테니스 라켓",
|
||||||
"bottle": "병",
|
"bottle": "병",
|
||||||
"plate": "번호판",
|
"plate": "접시",
|
||||||
"wine_glass": "와인잔",
|
"wine_glass": "와인잔",
|
||||||
"cup": "컵",
|
"cup": "컵",
|
||||||
"fork": "포크",
|
"fork": "포크",
|
||||||
"knife": "칼",
|
"knife": "칼",
|
||||||
"spoon": "숟가락",
|
"spoon": "숟가락",
|
||||||
"bowl": "보울",
|
"bowl": "그릇",
|
||||||
"banana": "바나나",
|
"banana": "바나나",
|
||||||
"apple": "사과",
|
"apple": "사과",
|
||||||
"sandwich": "샌드위치",
|
"sandwich": "샌드위치",
|
||||||
@@ -67,29 +67,29 @@
|
|||||||
"dining_table": "식탁",
|
"dining_table": "식탁",
|
||||||
"window": "창문",
|
"window": "창문",
|
||||||
"desk": "책상",
|
"desk": "책상",
|
||||||
"toilet": "화장실",
|
"toilet": "변기",
|
||||||
"door": "문",
|
"door": "문",
|
||||||
"tv": "TV",
|
"tv": "TV",
|
||||||
"laptop": "랩탑",
|
"laptop": "노트북",
|
||||||
"mouse": "마우스",
|
"mouse": "생쥐",
|
||||||
"remote": "리모콘",
|
"remote": "리모컨",
|
||||||
"keyboard": "키보드",
|
"keyboard": "키보드",
|
||||||
"cell_phone": "휴대폰",
|
"cell_phone": "휴대전화",
|
||||||
"microwave": "전자레인지",
|
"microwave": "전자레인지",
|
||||||
"oven": "오븐",
|
"oven": "오븐",
|
||||||
"toaster": "토스터기",
|
"toaster": "토스터",
|
||||||
"sink": "싱크대",
|
"sink": "싱크대",
|
||||||
"refrigerator": "냉장고",
|
"refrigerator": "냉장고",
|
||||||
"blender": "블렌더",
|
"blender": "믹서기",
|
||||||
"book": "책",
|
"book": "책",
|
||||||
"clock": "시계",
|
"clock": "시계",
|
||||||
"vase": "꽃병",
|
"vase": "꽃병",
|
||||||
"scissors": "가위",
|
"scissors": "가위",
|
||||||
"teddy_bear": "테디베어",
|
"teddy_bear": "곰 인형",
|
||||||
"hair_dryer": "헤어 드라이어",
|
"hair_dryer": "헤어드라이어",
|
||||||
"toothbrush": "칫솔",
|
"toothbrush": "칫솔",
|
||||||
"hair_brush": "빗",
|
"hair_brush": "헤어브러시",
|
||||||
"vehicle": "탈 것",
|
"vehicle": "탈것",
|
||||||
"squirrel": "다람쥐",
|
"squirrel": "다람쥐",
|
||||||
"deer": "사슴",
|
"deer": "사슴",
|
||||||
"animal": "동물",
|
"animal": "동물",
|
||||||
@@ -98,17 +98,17 @@
|
|||||||
"goat": "염소",
|
"goat": "염소",
|
||||||
"rabbit": "토끼",
|
"rabbit": "토끼",
|
||||||
"raccoon": "라쿤",
|
"raccoon": "라쿤",
|
||||||
"robot_lawnmower": "로봇 잔디깎기",
|
"robot_lawnmower": "로봇 잔디깎이",
|
||||||
"waste_bin": "쓰레기통",
|
"waste_bin": "쓰레기통",
|
||||||
"on_demand": "수동",
|
"on_demand": "수동 감지",
|
||||||
"face": "얼굴",
|
"face": "얼굴",
|
||||||
"license_plate": "차량 번호판",
|
"license_plate": "차량 번호판",
|
||||||
"package": "패키지",
|
"package": "택배 상자",
|
||||||
"bbq_grill": "바베큐 그릴",
|
"bbq_grill": "바베큐 그릴",
|
||||||
"amazon": "Amazon",
|
"amazon": "Amazon",
|
||||||
"usps": "USPS",
|
"usps": "USPS",
|
||||||
"ups": "UPS",
|
"ups": "UPS",
|
||||||
"fedex": "FedEx",
|
"fedex": "페덱스",
|
||||||
"dhl": "DHL",
|
"dhl": "DHL",
|
||||||
"an_post": "An Post",
|
"an_post": "An Post",
|
||||||
"purolator": "Purolator",
|
"purolator": "Purolator",
|
||||||
@@ -116,5 +116,16 @@
|
|||||||
"nzpost": "NZPost",
|
"nzpost": "NZPost",
|
||||||
"postnord": "PostNord",
|
"postnord": "PostNord",
|
||||||
"gls": "GLS",
|
"gls": "GLS",
|
||||||
"dpd": "DPD"
|
"dpd": "DPD",
|
||||||
|
"canada_post": "Canada Post",
|
||||||
|
"royal_mail": "Royal Mail",
|
||||||
|
"school_bus": "스쿨버스",
|
||||||
|
"skunk": "스컹크",
|
||||||
|
"kangaroo": "캥거루",
|
||||||
|
"baby": "아기",
|
||||||
|
"baby_stroller": "유모차",
|
||||||
|
"rickshaw": "인력거",
|
||||||
|
"rodent": "설치류",
|
||||||
|
"possum": "주머니쥐",
|
||||||
|
"garbage_truck": "청소차"
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -1 +1,72 @@
|
|||||||
{}
|
{
|
||||||
|
"documentTitle": "채팅 - Frigate",
|
||||||
|
"title": "Frigate 채팅",
|
||||||
|
"subtitle": "카메라 관리 및 인사이트 분석을 돕는 AI 어시스턴트입니다",
|
||||||
|
"placeholder": "무엇이든 물어보세요...",
|
||||||
|
"error": "문제가 발생했습니다. 다시 시도해 주세요.",
|
||||||
|
"processing": "처리 중입니다...",
|
||||||
|
"toolsUsed": "사용된 도구: {{tools}}",
|
||||||
|
"showTools": "도구 보기 ({{count}}개)",
|
||||||
|
"hideTools": "도구 숨기기",
|
||||||
|
"call": "호출",
|
||||||
|
"result": "결과",
|
||||||
|
"arguments": "인수:",
|
||||||
|
"response": "응답:",
|
||||||
|
"attachment_chip_label": "{{camera}}의 {{label}}",
|
||||||
|
"attachment_chip_remove": "첨부 파일 삭제",
|
||||||
|
"open_in_explore": "탐색에서 열기",
|
||||||
|
"attach_event_aria": "{{eventId}} 이벤트 첨부",
|
||||||
|
"attachment_picker_paste_label": "또는 이벤트 ID를 붙여넣으세요",
|
||||||
|
"attachment_picker_attach": "첨부하기",
|
||||||
|
"attachment_picker_placeholder": "이벤트를 첨부해 주세요",
|
||||||
|
"quick_reply_find_similar": "유사한 감지 기록 찾기",
|
||||||
|
"quick_reply_tell_me_more": "자세한 설명 요청",
|
||||||
|
"quick_reply_when_else": "다른 시간대 감지 확인",
|
||||||
|
"quick_reply_find_similar_text": "이와 유사한 감지 기록을 찾아주세요.",
|
||||||
|
"quick_reply_tell_me_more_text": "이 내용에 대해 더 자세히 알려주세요.",
|
||||||
|
"quick_reply_when_else_text": "이 대상이 다른 때에 또 감지된 적이 있나요?",
|
||||||
|
"anchor": "참조",
|
||||||
|
"similarity_score": "유사도",
|
||||||
|
"no_similar_objects_found": "유사한 객체를 찾을 수 없습니다.",
|
||||||
|
"semantic_search_required": "유사한 객체를 찾으려면 시맨틱 검색(Semantic search) 기능이 활성화되어 있어야 합니다.",
|
||||||
|
"send": "전송",
|
||||||
|
"suggested_requests": "다음과 같이 질문해 보세요:",
|
||||||
|
"starting_requests": {
|
||||||
|
"show_recent_events": "최근 이벤트 표시",
|
||||||
|
"show_camera_status": "카메라 상태 확인",
|
||||||
|
"recap": "부재 중 발생한 이벤트 요약",
|
||||||
|
"watch_camera": "카메라 활동 감시"
|
||||||
|
},
|
||||||
|
"starting_requests_prompts": {
|
||||||
|
"show_recent_events": "최근 1시간 동안 발생한 이벤트를 보여주세요",
|
||||||
|
"show_camera_status": "현재 카메라 상태는 어떤가요?",
|
||||||
|
"recap": "제가 자리를 비운 사이에 어떤 일이 있었나요?",
|
||||||
|
"watch_camera": "현관문을 주시하고 누군가 나타나면 저에게 알려주세요"
|
||||||
|
},
|
||||||
|
"new_chat": "새 채팅",
|
||||||
|
"settings": {
|
||||||
|
"title": "채팅 설정",
|
||||||
|
"show_stats": {
|
||||||
|
"title": "통계 표시",
|
||||||
|
"desc": "채팅 응답의 토큰 생성 속도 및 컨텍스트 크기를 표시합니다.",
|
||||||
|
"while_generating": "생성 중에만 표시",
|
||||||
|
"always": "항상 표시"
|
||||||
|
},
|
||||||
|
"auto_scroll": {
|
||||||
|
"title": "자동 스크롤",
|
||||||
|
"desc": "새 메시지가 도착하면 화면을 자동으로 아래로 이동합니다."
|
||||||
|
}
|
||||||
|
},
|
||||||
|
"stats": {
|
||||||
|
"context": "{{tokens}} 토큰",
|
||||||
|
"tokens_per_second": "{{rate}} t/s"
|
||||||
|
},
|
||||||
|
"reasoning": {
|
||||||
|
"active": "추론 중입니다…",
|
||||||
|
"show": "추론 과정 보기",
|
||||||
|
"hide": "추론 과정 숨기기"
|
||||||
|
},
|
||||||
|
"thinking": {
|
||||||
|
"toggle": "생각 과정 전환"
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|||||||
@@ -1,7 +1,7 @@
|
|||||||
{
|
{
|
||||||
"documentTitle": "분류 모델 - Frigate",
|
"documentTitle": "분류 모델 - Frigate",
|
||||||
"details": {
|
"details": {
|
||||||
"scoreInfo": "점수는 해당 객체에 대한 모든 탐지 결과의 평균 분류 신뢰도를 나타냅니다.",
|
"scoreInfo": "점수는 이 객체에 대해 감지된 모든 결과의 평균 분류 신뢰도를 나타냅니다.",
|
||||||
"none": "없음",
|
"none": "없음",
|
||||||
"unknown": "알 수 없음"
|
"unknown": "알 수 없음"
|
||||||
},
|
},
|
||||||
@@ -10,26 +10,196 @@
|
|||||||
"renameCategory": "클래스 이름 변경",
|
"renameCategory": "클래스 이름 변경",
|
||||||
"deleteCategory": "클래스 삭제",
|
"deleteCategory": "클래스 삭제",
|
||||||
"deleteImages": "이미지 삭제",
|
"deleteImages": "이미지 삭제",
|
||||||
"trainModel": "모델 훈련",
|
"trainModel": "모델 학습",
|
||||||
"addClassification": "분류 추가",
|
"addClassification": "분류 추가",
|
||||||
"deleteModels": "모델 삭제",
|
"deleteModels": "모델 삭제",
|
||||||
"editModel": "모델 편집"
|
"editModel": "모델 수정"
|
||||||
},
|
},
|
||||||
"tooltip": {
|
"tooltip": {
|
||||||
"trainingInProgress": "모델이 현재 학습 중입니다",
|
"trainingInProgress": "모델 학습이 진행 중입니다",
|
||||||
"noNewImages": "훈련할 새 이미지가 없습니다. 먼저 데이터셋에서 더 많은 이미지를 분류하세요.",
|
"noNewImages": "학습할 새 이미지가 없습니다. 데이터셋에서 이미지를 먼저 분류하세요.",
|
||||||
"noChanges": "마지막 훈련 이후 데이터셋에 변경 사항이 없습니다.",
|
"noChanges": "마지막 학습 이후 데이터셋에 변경 사항이 없습니다.",
|
||||||
"modelNotReady": "모델이 훈련 준비가 되지 않았습니다"
|
"modelNotReady": "모델이 학습할 준비가 되지 않았습니다"
|
||||||
},
|
},
|
||||||
"toast": {
|
"toast": {
|
||||||
"success": {
|
"success": {
|
||||||
"deletedModel_other": "{{count}}개 모델을 성공적으로 삭제했습니다",
|
"deletedModel_other": "모델 {{count}}개를 성공적으로 삭제했습니다",
|
||||||
"categorizedImage": "이미지 분류 성공",
|
"categorizedImage": "이미지를 성공적으로 분류했습니다",
|
||||||
"reclassifiedImage": "이미지 재분류 성공",
|
"reclassifiedImage": "이미지를 성공적으로 재분류했습니다",
|
||||||
"trainedModel": "모델 훈련 완료."
|
"trainedModel": "모델 학습을 성공적으로 완료했습니다.",
|
||||||
|
"trainingModel": "모델 학습을 성공적으로 시작했습니다.",
|
||||||
|
"updatedModel": "모델 설정을 성공적으로 업데이트했습니다",
|
||||||
|
"renamedCategory": "클래스 이름을 '{{name}}'(으)로 성공적으로 변경했습니다",
|
||||||
|
"deletedCategory_other": "클래스 {{count}}개를 삭제했습니다",
|
||||||
|
"deletedImage_other": "이미지 {{count}}장을 삭제했습니다"
|
||||||
|
},
|
||||||
|
"error": {
|
||||||
|
"deleteImageFailed": "삭제 실패: {{errorMessage}}",
|
||||||
|
"deleteCategoryFailed": "클래스 삭제 실패: {{errorMessage}}",
|
||||||
|
"deleteModelFailed": "모델 삭제 실패: {{errorMessage}}",
|
||||||
|
"categorizeFailed": "이미지 분류 실패: {{errorMessage}}",
|
||||||
|
"trainingFailed": "모델 학습에 실패했습니다. 자세한 내용은 Frigate 로그를 확인하세요.",
|
||||||
|
"trainingFailedToStart": "모델 학습 시작 실패: {{errorMessage}}",
|
||||||
|
"updateModelFailed": "모델 업데이트 실패: {{errorMessage}}",
|
||||||
|
"renameCategoryFailed": "클래스 이름 변경 실패: {{errorMessage}}",
|
||||||
|
"reclassifyFailed": "이미지 재분류 실패: {{errorMessage}}"
|
||||||
}
|
}
|
||||||
},
|
},
|
||||||
"train": {
|
"train": {
|
||||||
"titleShort": "최근"
|
"titleShort": "최근",
|
||||||
|
"title": "최근 분류",
|
||||||
|
"aria": "최근 분류 선택"
|
||||||
|
},
|
||||||
|
"disabled": "비활성화됨",
|
||||||
|
"deleteCategory": {
|
||||||
|
"title": "클래스 삭제",
|
||||||
|
"desc": "정말로 '{{name}}' 클래스를 삭제하시겠습니까? 연결된 모든 이미지가 영구적으로 삭제되며 모델을 다시 학습해야 합니다.",
|
||||||
|
"minClassesTitle": "클래스를 삭제할 수 없음",
|
||||||
|
"minClassesDesc": "분류 모델은 최소 2개 이상의 클래스가 필요합니다. 이 클래스를 삭제하기 전에 다른 클래스를 추가하세요."
|
||||||
|
},
|
||||||
|
"deleteModel": {
|
||||||
|
"title": "분류 모델 삭제",
|
||||||
|
"single": "정말로 '{{name}}' 모델을 삭제하시겠습니까? 이미지 및 학습 데이터를 포함한 모든 연결된 데이터가 영구적으로 삭제됩니다. 이 작업은 되돌릴 수 없습니다.",
|
||||||
|
"desc_other": "정말로 모델 {{count}}개를 삭제하시겠습니까? 이미지 및 학습 데이터를 포함한 모든 연결된 데이터가 영구적으로 삭제됩니다. 이 작업은 되돌릴 수 없습니다."
|
||||||
|
},
|
||||||
|
"edit": {
|
||||||
|
"title": "분류 모델 수정",
|
||||||
|
"descriptionState": "이 상태 분류 모델의 클래스를 수정합니다. 변경 사항을 적용하려면 모델을 다시 학습해야 합니다.",
|
||||||
|
"descriptionObject": "이 객체 분류 모델의 객체 유형 및 분류 유형을 수정합니다.",
|
||||||
|
"enabled": "활성화됨",
|
||||||
|
"enabledDesc": "이 모델을 실행합니다. 비활성화하면 모델 실행이 중단되고 더 이상 분류 작업을 수행하지 않습니다.",
|
||||||
|
"saveAttempts": "시도 기록 저장 수",
|
||||||
|
"saveAttemptsDesc": "최근 분류 UI에 유지할 분류 시도 이미지의 개수입니다.",
|
||||||
|
"motion": "움직임 감지 시 실행",
|
||||||
|
"motionDesc": "설정된 크롭 영역 내에서 움직임이 감지될 때 분류를 실행합니다.",
|
||||||
|
"interval": "주기",
|
||||||
|
"intervalDesc": "주기적 분류 실행 간격(초)입니다. 움직임 감지 시에만 실행하려면 비워 두세요.",
|
||||||
|
"intervalPlaceholder": "주기 없음",
|
||||||
|
"stateClassesInfo": "모델이 업데이트되었습니다. 클래스 변경 사항을 적용하려면 모델을 다시 학습하세요.",
|
||||||
|
"errors": {
|
||||||
|
"saveAttemptsInvalid": "시도 기록 저장 수는 0 이상의 정수여야 합니다",
|
||||||
|
"intervalInvalid": "주기는 0보다 큰 정수여야 합니다"
|
||||||
|
}
|
||||||
|
},
|
||||||
|
"deleteDatasetImages": {
|
||||||
|
"title": "데이터셋 이미지 삭제",
|
||||||
|
"desc_other": "정말로 {{dataset}}에서 이미지 {{count}}장을 삭제하시겠습니까? 이 작업은 되돌릴 수 없으며 모델을 다시 학습해야 합니다."
|
||||||
|
},
|
||||||
|
"deleteTrainImages": {
|
||||||
|
"title": "학습 이미지 삭제",
|
||||||
|
"desc_other": "정말로 이미지 {{count}}장을 삭제하시겠습니까? 이 작업은 되돌릴 수 없습니다."
|
||||||
|
},
|
||||||
|
"renameCategory": {
|
||||||
|
"title": "클래스 이름 변경",
|
||||||
|
"desc": "'{{name}}'의 새 이름을 입력하세요. 이름 변경 사항을 적용하려면 모델을 다시 학습해야 합니다."
|
||||||
|
},
|
||||||
|
"description": {
|
||||||
|
"invalidName": "유효하지 않은 이름입니다. 이름에는 문자, 숫자, 공백, 아포스트로피('), 밑줄(_), 하이픈(-)만 포함할 수 있습니다."
|
||||||
|
},
|
||||||
|
"categories": "클래스",
|
||||||
|
"createCategory": {
|
||||||
|
"new": "새 클래스 만들기"
|
||||||
|
},
|
||||||
|
"categorizeImageAs": "이미지를 다음으로 분류:",
|
||||||
|
"categorizeImage": "이미지 분류",
|
||||||
|
"reclassifyImageAs": "이미지를 다음으로 재분류:",
|
||||||
|
"reclassifyImage": "이미지 재분류",
|
||||||
|
"menu": {
|
||||||
|
"objects": "객체",
|
||||||
|
"states": "상태"
|
||||||
|
},
|
||||||
|
"noModels": {
|
||||||
|
"object": {
|
||||||
|
"title": "객체 분류 모델 없음",
|
||||||
|
"description": "감지된 객체를 분류하기 위한 커스텀 모델을 생성하세요.",
|
||||||
|
"buttonText": "객체 모델 생성"
|
||||||
|
},
|
||||||
|
"state": {
|
||||||
|
"title": "상태 분류 모델 없음",
|
||||||
|
"description": "특정 카메라 영역의 상태 변화를 모니터링하고 분류하기 위한 커스텀 모델을 생성하세요.",
|
||||||
|
"buttonText": "상태 모델 생성"
|
||||||
|
}
|
||||||
|
},
|
||||||
|
"wizard": {
|
||||||
|
"title": "새 분류 생성",
|
||||||
|
"steps": {
|
||||||
|
"nameAndDefine": "이름 및 정의",
|
||||||
|
"stateArea": "상태 영역",
|
||||||
|
"chooseExamples": "예시 선택"
|
||||||
|
},
|
||||||
|
"step1": {
|
||||||
|
"description": "상태 모델은 고정된 카메라 영역의 변화(예: 문 열림/닫힘)를 모니터링합니다. 객체 모델은 감지된 객체에 세부 분류(예: 특정 동물, 배달원 등)를 추가합니다.",
|
||||||
|
"name": "이름",
|
||||||
|
"namePlaceholder": "모델 이름 입력...",
|
||||||
|
"type": "유형",
|
||||||
|
"typeState": "상태",
|
||||||
|
"typeObject": "객체",
|
||||||
|
"objectLabel": "객체 레이블",
|
||||||
|
"objectLabelPlaceholder": "객체 유형 선택...",
|
||||||
|
"classificationType": "분류 유형",
|
||||||
|
"classificationTypeTip": "분류 유형 자세히 알아보기",
|
||||||
|
"classificationTypeDesc": "하위 레이블은 객체 레이블에 추가 텍스트를 부여합니다 (예: 'Person: UPS'). 속성은 객체 메타데이터에 별도로 저장되는 검색 가능한 메타데이터입니다.",
|
||||||
|
"classificationSubLabel": "하위 레이블",
|
||||||
|
"classificationAttribute": "속성",
|
||||||
|
"classes": "클래스",
|
||||||
|
"states": "상태",
|
||||||
|
"classesTip": "클래스 자세히 알아보기",
|
||||||
|
"classesStateDesc": "카메라 영역이 가질 수 있는 서로 다른 상태를 정의합니다. 예를 들어 차고 문의 경우 'open'과 'closed'입니다.",
|
||||||
|
"classesObjectDesc": "감지된 객체를 분류할 서로 다른 범주를 정의합니다. 예를 들어 사람 분류의 경우 'delivery_person', 'resident', 'stranger'입니다.",
|
||||||
|
"classPlaceholder": "클래스 이름 입력...",
|
||||||
|
"errors": {
|
||||||
|
"nameRequired": "모델 이름은 필수입니다",
|
||||||
|
"nameLength": "모델 이름은 64자 이하여야 합니다",
|
||||||
|
"nameOnlyNumbers": "모델 이름은 숫자로만 구성될 수 없습니다",
|
||||||
|
"classRequired": "최소 1개 이상의 클래스가 필요합니다",
|
||||||
|
"classesUnique": "클래스 이름은 중복될 수 없습니다",
|
||||||
|
"noneNotAllowed": "'none' 클래스는 사용할 수 없습니다",
|
||||||
|
"stateRequiresTwoClasses": "상태 모델은 최소 2개 이상의 클래스가 필요합니다",
|
||||||
|
"objectLabelRequired": "객체 레이블을 선택하세요",
|
||||||
|
"objectTypeRequired": "분류 유형을 선택하세요"
|
||||||
|
}
|
||||||
|
},
|
||||||
|
"step2": {
|
||||||
|
"description": "카메라를 선택하고 각 카메라별로 모니터링할 영역을 지정하세요. 모델이 해당 영역의 상태를 분류합니다.",
|
||||||
|
"cameras": "카메라",
|
||||||
|
"selectCamera": "카메라 선택",
|
||||||
|
"noCameras": "+ 버튼을 눌러 카메라를 추가하세요",
|
||||||
|
"selectCameraPrompt": "목록에서 카메라를 선택하여 모니터링 영역을 설정하세요"
|
||||||
|
},
|
||||||
|
"step3": {
|
||||||
|
"selectImagesPrompt": "다음과 일치하는 모든 이미지를 선택하세요: {{className}}",
|
||||||
|
"selectImagesDescription": "이미지를 클릭하여 선택하세요. 이 클래스에 대한 선택을 마쳤으면 계속을 클릭하세요.",
|
||||||
|
"allImagesRequired_other": "모든 이미지를 분류해 주세요. 남은 이미지: {{count}}장.",
|
||||||
|
"generating": {
|
||||||
|
"title": "샘플 이미지 생성 중",
|
||||||
|
"description": "Frigate이 녹화 영상에서 대표 이미지를 추출하고 있습니다. 잠시만 기다려 주세요..."
|
||||||
|
},
|
||||||
|
"training": {
|
||||||
|
"title": "모델 학습 중",
|
||||||
|
"description": "백그라운드에서 모델을 학습하고 있습니다. 이 대화 상자를 닫아도 되며, 학습이 완료되는 즉시 모델이 실행됩니다."
|
||||||
|
},
|
||||||
|
"retryGenerate": "생성 재시도",
|
||||||
|
"noImages": "생성된 샘플 이미지가 없습니다",
|
||||||
|
"classifying": "분류 및 학습 진행 중...",
|
||||||
|
"trainingStarted": "학습이 성공적으로 시작되었습니다",
|
||||||
|
"modelCreated": "모델이 성공적으로 생성되었습니다. '최근 분류' 보기에서 누락된 상태의 이미지를 추가한 후 모델을 학습시키세요.",
|
||||||
|
"errors": {
|
||||||
|
"noCameras": "설정된 카메라가 없습니다",
|
||||||
|
"noObjectLabel": "선택된 객체 레이블이 없습니다",
|
||||||
|
"generateFailed": "예시 생성 실패: {{error}}",
|
||||||
|
"generationFailed": "생성에 실패했습니다. 다시 시도해 주세요.",
|
||||||
|
"classifyFailed": "이미지 분류 실패: {{error}}"
|
||||||
|
},
|
||||||
|
"generateSuccess": "샘플 이미지를 성공적으로 생성했습니다",
|
||||||
|
"refreshExamples": "새로운 예시 생성",
|
||||||
|
"refreshConfirm": {
|
||||||
|
"title": "새로운 예시를 생성하시겠습니까?",
|
||||||
|
"description": "새 이미지 세트가 생성되며 이전 클래스를 포함한 모든 선택 항목이 지워집니다. 모든 클래스에 대해 예시를 다시 선택해야 합니다."
|
||||||
|
},
|
||||||
|
"missingStatesWarning": {
|
||||||
|
"title": "클래스 예시 누락",
|
||||||
|
"description": "모든 클래스에 예시가 존재하는 것은 아닙니다. 새로운 예시를 생성하여 누락된 클래스를 찾거나, 계속 진행한 후 '최근 분류' 보기에서 나중에 이미지를 추가하세요."
|
||||||
|
}
|
||||||
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -2,17 +2,17 @@
|
|||||||
"documentTitle": "설정 편집기 - Frigate",
|
"documentTitle": "설정 편집기 - Frigate",
|
||||||
"configEditor": "설정 편집기",
|
"configEditor": "설정 편집기",
|
||||||
"safeConfigEditor": "설정 편집기 (안전 모드)",
|
"safeConfigEditor": "설정 편집기 (안전 모드)",
|
||||||
"safeModeDescription": "설정 오류로 인해 Frigate가 안전 모드로 전환되었습니다.",
|
"safeModeDescription": "설정 유효성 검사 오류로 인해 Frigate가 안전 모드로 실행 중입니다.",
|
||||||
"copyConfig": "설정 복사",
|
"copyConfig": "설정 복사",
|
||||||
"saveAndRestart": "저장 & 재시작",
|
"saveAndRestart": "저장 및 재시작",
|
||||||
"saveOnly": "저장만 하기",
|
"saveOnly": "저장만 하기",
|
||||||
"confirm": "저장 없이 나갈까요?",
|
"confirm": "저장하지 않고 종료하시겠습니까?",
|
||||||
"toast": {
|
"toast": {
|
||||||
"success": {
|
"success": {
|
||||||
"copyToClipboard": "설정이 클립보드에 저장되었습니다."
|
"copyToClipboard": "설정이 클립보드에 복사되었습니다."
|
||||||
},
|
},
|
||||||
"error": {
|
"error": {
|
||||||
"savingError": "설정 저장 오류"
|
"savingError": "설정 저장 중 오류가 발생했습니다"
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -1,18 +1,18 @@
|
|||||||
{
|
{
|
||||||
"alerts": "경보",
|
"alerts": "알림",
|
||||||
"detections": "대상 감지",
|
"detections": "감지",
|
||||||
"motion": {
|
"motion": {
|
||||||
"label": "움직임 감지",
|
"label": "움직임",
|
||||||
"only": "움직임 감지만"
|
"only": "움직임만"
|
||||||
},
|
},
|
||||||
"allCameras": "모든 카메라",
|
"allCameras": "모든 카메라",
|
||||||
"empty": {
|
"empty": {
|
||||||
"alert": "다시 볼 '경보' 영상이 없습니다",
|
"alert": "검토할 알림이 없습니다",
|
||||||
"detection": "다시 볼 '대상 감지' 영상이 없습니다",
|
"detection": "검토할 감지 내역이 없습니다",
|
||||||
"motion": "움직임 감지 데이터가 없습니다",
|
"motion": "움직임 데이터가 없습니다",
|
||||||
"recordingsDisabled": {
|
"recordingsDisabled": {
|
||||||
"title": "녹화가 활성화되어야 합니다",
|
"title": "녹화가 활성화되어 있어야 합니다",
|
||||||
"description": "다시 보기 항목은 해당 카메라에서 녹화가 활성화된 경우에만 카메라에 대해 생성할 수 있습니다."
|
"description": "해당 카메라의 녹화 기능이 활성화되어 있어야 다시보기 항목이 생성될 수 있습니다."
|
||||||
}
|
}
|
||||||
},
|
},
|
||||||
"timeline": {
|
"timeline": {
|
||||||
@@ -22,39 +22,76 @@
|
|||||||
"events": {
|
"events": {
|
||||||
"label": "이벤트",
|
"label": "이벤트",
|
||||||
"aria": "이벤트 선택",
|
"aria": "이벤트 선택",
|
||||||
"noFoundForTimePeriod": "이 시간대에 이벤트가 없습니다."
|
"noFoundForTimePeriod": "해당 기간에 발생한 이벤트가 없습니다."
|
||||||
},
|
},
|
||||||
"detail": {
|
"detail": {
|
||||||
"noDataFound": "다시 볼 상세 데이터가 없습니다",
|
"noDataFound": "검토할 세부 정보 데이터가 없습니다",
|
||||||
"aria": "상세 보기",
|
"aria": "세부 정보 보기 전환",
|
||||||
"trackedObject_one": "추적 대상",
|
"trackedObject_one": "객체 {{count}}개",
|
||||||
"trackedObject_other": "추적 대상",
|
"trackedObject_other": "객체 {{count}}개",
|
||||||
"noObjectDetailData": "상세 보기 데이터가 없습니다.",
|
"noObjectDetailData": "사용 가능한 객체 세부 정보가 없습니다.",
|
||||||
"label": "세부 정보"
|
"label": "세부 정보",
|
||||||
|
"settings": "세부 정보 보기 설정",
|
||||||
|
"alwaysExpandActive": {
|
||||||
|
"title": "활성 항목 항상 펼치기",
|
||||||
|
"desc": "사용 가능한 경우 활성 다시보기 항목의 객체 세부 정보를 항상 펼쳐서 표시합니다."
|
||||||
|
}
|
||||||
},
|
},
|
||||||
"objectTrack": {
|
"objectTrack": {
|
||||||
"trackedPoint": "추적 포인트",
|
"trackedPoint": "추적 지점",
|
||||||
"clickToSeek": "이 시점으로 이동"
|
"clickToSeek": "이 시간대로 이동하려면 클릭하세요"
|
||||||
},
|
},
|
||||||
"documentTitle": "다시 보기 - Frigate",
|
"documentTitle": "다시보기 - Frigate",
|
||||||
"recordings": {
|
"recordings": {
|
||||||
"documentTitle": "녹화 - Frigate"
|
"documentTitle": "녹화 - Frigate",
|
||||||
|
"invalidSharedLink": "파싱 오류로 인해 타임스탬프 녹화 링크를 열 수 없습니다.",
|
||||||
|
"invalidSharedCamera": "알 수 없거나 권한이 없는 카메라이므로 타임스탬프 녹화 링크를 열 수 없습니다."
|
||||||
},
|
},
|
||||||
"calendarFilter": {
|
"calendarFilter": {
|
||||||
"last24Hours": "최근 24시간"
|
"last24Hours": "최근 24시간"
|
||||||
},
|
},
|
||||||
"markAsReviewed": "'다시 봤음'으로 표시",
|
"markAsReviewed": "검토 완료로 표시",
|
||||||
"markTheseItemsAsReviewed": "이 영상들을 '다시 봤음'으로 표시",
|
"markTheseItemsAsReviewed": "이 항목들을 검토 완료로 표시",
|
||||||
"newReviewItems": {
|
"newReviewItems": {
|
||||||
"label": "새로운 '다시 보기' 영상 보기",
|
"label": "새로운 다시보기 항목 보기",
|
||||||
"button": "새로운 '다시 보기' 영상"
|
"button": "검토할 새 항목"
|
||||||
},
|
},
|
||||||
"selected_one": "{{count}} 선택됨",
|
"selected_one": "{{count}}개 선택됨",
|
||||||
"selected_other": "{{count}} 선택됨",
|
"selected_other": "{{count}}개 선택됨",
|
||||||
"camera": "카메라",
|
"camera": "카메라",
|
||||||
"detected": "감지됨",
|
"detected": "감지됨",
|
||||||
"suspiciousActivity": "수상한 행동",
|
"suspiciousActivity": "수상한 행동",
|
||||||
"threateningActivity": "위협적인 행동",
|
"threateningActivity": "위협적인 행동",
|
||||||
"zoomIn": "확대",
|
"zoomIn": "확대",
|
||||||
"zoomOut": "축소"
|
"zoomOut": "축소",
|
||||||
|
"select_all": "전체",
|
||||||
|
"normalActivity": "정상 활동",
|
||||||
|
"needsReview": "검토 필요",
|
||||||
|
"securityConcern": "보안 주의",
|
||||||
|
"motionSearch": {
|
||||||
|
"menuItem": "움직임 검색",
|
||||||
|
"openMenu": "카메라 옵션"
|
||||||
|
},
|
||||||
|
"motionPreviews": {
|
||||||
|
"menuItem": "움직임 미리보기 보기",
|
||||||
|
"title": "움직임 미리보기: {{camera}}",
|
||||||
|
"mobileSettingsTitle": "움직임 미리보기 설정",
|
||||||
|
"mobileSettingsDesc": "재생 속도, 어둡기(Dim), 자르기(Crop)를 조절하고 움직임 전용 클립을 검토할 날짜를 선택하세요.",
|
||||||
|
"dim": "어둡기",
|
||||||
|
"dimAria": "어둡기 강도 조절",
|
||||||
|
"dimDesc": "배경 어둡기를 높여 움직임 영역의 가시성을 높입니다.",
|
||||||
|
"speed": "재생 속도",
|
||||||
|
"speedAria": "미리보기 재생 속도 선택",
|
||||||
|
"speedDesc": "미리보기 클립의 재생 속도를 선택하세요.",
|
||||||
|
"back": "뒤로",
|
||||||
|
"empty": "사용 가능한 미리보기가 없습니다",
|
||||||
|
"noPreview": "미리보기를 사용할 수 없습니다",
|
||||||
|
"seekAria": "{{camera}} 플레이어를 {{time}} 위치로 이동",
|
||||||
|
"filter": "필터",
|
||||||
|
"filterDesc": "선택한 영역에 움직임이 있는 클립만 표시합니다.",
|
||||||
|
"filterClear": "초기화",
|
||||||
|
"crop": "필터 영역으로 확대",
|
||||||
|
"cropAria": "미리보기를 필터 영역으로 자르기 전환",
|
||||||
|
"cropDesc": "전체 화면 대신 선택한 필터 영역으로 미리보기를 확대합니다."
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -1,39 +1,263 @@
|
|||||||
{
|
{
|
||||||
"documentTitle": "탐색 - Frigate",
|
"documentTitle": "탐색 - Frigate",
|
||||||
"generativeAI": "생성형 AI",
|
"generativeAI": "생성형 AI",
|
||||||
"exploreMore": "{{label}} 더 많은 감지 대상 탐색하기",
|
"exploreMore": "더 많은 {{label}} 객체 탐색",
|
||||||
"exploreIsUnavailable": {
|
"exploreIsUnavailable": {
|
||||||
"title": "탐색을 사용할 수 없습니다",
|
"title": "탐색 기능을 사용할 수 없음",
|
||||||
"embeddingsReindexing": {
|
"embeddingsReindexing": {
|
||||||
"context": "추적된 객체의 임베딩 색인 재구성이 완료된 후 '탐색' 기능을 사용할 수 있습니다.",
|
"context": "추적 객체 임베딩 재색인이 완료된 후 탐색 기능을 사용할 수 있습니다.",
|
||||||
"startingUp": "시작 중…",
|
"startingUp": "시작 중…",
|
||||||
"estimatedTime": "예상 남은시간:",
|
"estimatedTime": "예상 남은 시간:",
|
||||||
"finishingShortly": "곧 완료됩니다",
|
"finishingShortly": "곧 완료됩니다",
|
||||||
"step": {
|
"step": {
|
||||||
"thumbnailsEmbedded": "처리된 썸네일: ",
|
"thumbnailsEmbedded": "임베딩된 썸네일: ",
|
||||||
"descriptionsEmbedded": "처리된 설명: ",
|
"descriptionsEmbedded": "임베딩된 설명: ",
|
||||||
"trackedObjectsProcessed": "처리된 추적 감지: "
|
"trackedObjectsProcessed": "처리된 추적 객체: "
|
||||||
}
|
}
|
||||||
},
|
},
|
||||||
"downloadingModels": {
|
"downloadingModels": {
|
||||||
"context": "Frigate가 시맨틱 검색 기능을 지원하기 위해 필요한 임베딩 모델을 다운로드하고 있습니다. 네트워크 연결 속도에 따라 몇 분 정도 소요될 수 있습니다.",
|
"context": "Frigate이 시맨틱 검색 기능을 지원하기 위해 필요한 임베딩 모델을 다운로드하고 있습니다. 네트워크 연결 속도에 따라 몇 분 정도 걸릴 수 있습니다.",
|
||||||
"setup": {
|
"setup": {
|
||||||
"visionModel": "비전 모델",
|
"visionModel": "비전 모델",
|
||||||
"visionModelFeatureExtractor": "비전 모델 특징 추출기",
|
"visionModelFeatureExtractor": "비전 모델 특징 추출기",
|
||||||
"textModel": "Text model",
|
"textModel": "텍스트 모델",
|
||||||
"textTokenizer": "텍스트 토크나이저"
|
"textTokenizer": "텍스트 토크나이저"
|
||||||
},
|
},
|
||||||
"tips": {
|
"tips": {
|
||||||
"context": "모델이 다운로드된 후 추적 객체의 임베딩을 색인 재구성하는 것이 좋습니다."
|
"context": "모델 다운로드가 완료되면 추적 객체의 임베딩을 다시 색인하는 것이 좋습니다."
|
||||||
},
|
},
|
||||||
"error": "오류가 발생했습니다. Frigate 로그를 확인하세요."
|
"error": "오류가 발생했습니다. Frigate 로그를 확인하세요."
|
||||||
}
|
}
|
||||||
},
|
},
|
||||||
"details": {
|
"details": {
|
||||||
"timestamp": "시간 기록"
|
"timestamp": "타임스탬프",
|
||||||
|
"item": {
|
||||||
|
"title": "다시보기 항목 세부 정보",
|
||||||
|
"desc": "다시보기 항목 세부 정보",
|
||||||
|
"button": {
|
||||||
|
"share": "이 다시보기 항목 공유",
|
||||||
|
"viewInExplore": "탐색에서 보기"
|
||||||
|
},
|
||||||
|
"tips": {
|
||||||
|
"mismatch_other": "사용할 수 없는 객체 {{count}}개가 감지되어 이 다시보기 항목에 포함되었습니다. 해당 객체는 알림 또는 감지 기준을 충족하지 못했거나 이미 정리/삭제되었습니다.",
|
||||||
|
"hasMissingObjects": "다음 레이블에 대해 Frigate이 추적 객체를 저장하도록 하려면 설정을 조정하세요: <em>{{objects}}</em>"
|
||||||
|
},
|
||||||
|
"toast": {
|
||||||
|
"success": {
|
||||||
|
"regenerate": "{{provider}}에 새로운 설명을 요청했습니다. 제공업체의 처리 속도에 따라 새 설명을 다시 생성하는 데 시간이 걸릴 수 있습니다.",
|
||||||
|
"updatedSublabel": "하위 레이블을 성공적으로 업데이트했습니다.",
|
||||||
|
"updatedLPR": "차량 번호판을 성공적으로 업데이트했습니다.",
|
||||||
|
"updatedAttributes": "속성을 성공적으로 업데이트했습니다.",
|
||||||
|
"audioTranscription": "오디오 텍스트 변환을 성공적으로 요청했습니다. Frigate 서버 성능에 따라 변환이 완료되기까지 시간이 걸릴 수 있습니다."
|
||||||
|
},
|
||||||
|
"error": {
|
||||||
|
"regenerate": "{{provider}}에 새 설명 요청 실패: {{errorMessage}}",
|
||||||
|
"updatedSublabelFailed": "하위 레이블 업데이트 실패: {{errorMessage}}",
|
||||||
|
"updatedLPRFailed": "차량 번호판 업데이트 실패: {{errorMessage}}",
|
||||||
|
"updatedAttributesFailed": "속성 업데이트 실패: {{errorMessage}}",
|
||||||
|
"audioTranscription": "오디오 텍스트 변환 요청 실패: {{errorMessage}}"
|
||||||
|
}
|
||||||
|
}
|
||||||
|
},
|
||||||
|
"label": "레이블",
|
||||||
|
"editSubLabel": {
|
||||||
|
"title": "하위 레이블 수정",
|
||||||
|
"desc": "이 {{label}}의 새 하위 레이블 입력",
|
||||||
|
"descNoLabel": "이 추적 객체의 새 하위 레이블 입력"
|
||||||
|
},
|
||||||
|
"editLPR": {
|
||||||
|
"title": "차량 번호판 수정",
|
||||||
|
"desc": "이 {{label}}의 새 번호판 번호 입력",
|
||||||
|
"descNoLabel": "이 추적 객체의 새 번호판 번호 입력"
|
||||||
|
},
|
||||||
|
"editAttributes": {
|
||||||
|
"title": "속성 수정",
|
||||||
|
"desc": "이 {{label}}의 분류 속성 선택"
|
||||||
|
},
|
||||||
|
"snapshotScore": {
|
||||||
|
"label": "스냅샷 점수"
|
||||||
|
},
|
||||||
|
"topScore": {
|
||||||
|
"label": "최고 점수",
|
||||||
|
"info": "최고 점수는 추적 객체의 가장 높은 중간값(median) 점수이므로 검색 결과 썸네일에 표시된 점수와 다를 수 있습니다."
|
||||||
|
},
|
||||||
|
"score": {
|
||||||
|
"label": "신뢰도 점수"
|
||||||
|
},
|
||||||
|
"recognizedLicensePlate": "인식된 차량 번호판",
|
||||||
|
"attributes": "분류 속성",
|
||||||
|
"estimatedSpeed": "예상 속도",
|
||||||
|
"objects": "객체",
|
||||||
|
"camera": "카메라",
|
||||||
|
"zones": "구역",
|
||||||
|
"button": {
|
||||||
|
"findSimilar": "유사 항목 찾기",
|
||||||
|
"regenerate": {
|
||||||
|
"title": "다시 생성",
|
||||||
|
"label": "추적 객체 설명 다시 생성"
|
||||||
|
}
|
||||||
|
},
|
||||||
|
"description": {
|
||||||
|
"label": "설명",
|
||||||
|
"placeholder": "추적된 객체에 대한 설명",
|
||||||
|
"aiTips": "Frigate은 추적 객체의 수명주기가 완전히 끝날 때까지 생성형 AI 제공업체에 설명을 요청하지 않습니다."
|
||||||
|
},
|
||||||
|
"expandRegenerationMenu": "설명 재생성 메뉴 펼치기",
|
||||||
|
"regenerateFromSnapshot": "스냅샷 기반으로 다시 생성",
|
||||||
|
"regenerateFromThumbnails": "썸네일 기반으로 다시 생성",
|
||||||
|
"tips": {
|
||||||
|
"descriptionSaved": "설명을 성공적으로 저장했습니다",
|
||||||
|
"saveDescriptionFailed": "설명 업데이트 실패: {{errorMessage}}"
|
||||||
|
},
|
||||||
|
"title": {
|
||||||
|
"label": "제목"
|
||||||
|
},
|
||||||
|
"scoreInfo": "점수 정보"
|
||||||
},
|
},
|
||||||
"trackedObjectDetails": "추적 객체 세부 정보",
|
"trackedObjectDetails": "추적 객체 세부 정보",
|
||||||
"type": {
|
"type": {
|
||||||
"details": "세부 정보"
|
"details": "세부 정보",
|
||||||
|
"snapshot": "스냅샷",
|
||||||
|
"thumbnail": "썸네일",
|
||||||
|
"video": "동영상",
|
||||||
|
"tracking_details": "추적 세부 정보"
|
||||||
|
},
|
||||||
|
"trackingDetails": {
|
||||||
|
"title": "추적 세부 정보",
|
||||||
|
"noImageFound": "이 타임스탬프의 이미지를 찾을 수 없습니다.",
|
||||||
|
"createObjectMask": "객체 마스크 생성",
|
||||||
|
"adjustAnnotationSettings": "어노테이션 설정 조정",
|
||||||
|
"scrollViewTips": "이 객체 수명주기의 주요 순간을 보려면 클릭하세요.",
|
||||||
|
"autoTrackingTips": "자동 추적 카메라의 경우 바운딩 박스 위치가 부정확할 수 있습니다.",
|
||||||
|
"count": "{{second}}개 중 {{first}}",
|
||||||
|
"trackedPoint": "추적 지점",
|
||||||
|
"lifecycleItemDesc": {
|
||||||
|
"visible": "{{label}} 감지됨",
|
||||||
|
"entered_zone": "{{label}}이(가) {{zones}}에 진입함",
|
||||||
|
"active": "{{label}}이(가) 활동 상태로 전환됨",
|
||||||
|
"stationary": "{{label}}이(가) 정지 상태로 전환됨",
|
||||||
|
"attribute": {
|
||||||
|
"faceOrLicense_plate": "{{label}}의 {{attribute}} 감지됨",
|
||||||
|
"other": "{{label}}이(가) {{attribute}}(으)로 인식됨"
|
||||||
|
},
|
||||||
|
"gone": "{{label}}이(가) 사라짐",
|
||||||
|
"heard": "{{label}} 소리 감지됨",
|
||||||
|
"external": "{{label}} 감지됨",
|
||||||
|
"header": {
|
||||||
|
"zones": "구역",
|
||||||
|
"ratio": "비율",
|
||||||
|
"area": "면적",
|
||||||
|
"score": "점수",
|
||||||
|
"computedScore": "계산된 점수",
|
||||||
|
"topScore": "최고 점수",
|
||||||
|
"toggleAdvancedScores": "고급 점수 전환"
|
||||||
|
}
|
||||||
|
},
|
||||||
|
"annotationSettings": {
|
||||||
|
"title": "어노테이션 설정",
|
||||||
|
"showAllZones": {
|
||||||
|
"title": "모든 구역 표시",
|
||||||
|
"desc": "객체가 특정 구역에 진입한 프레임에서 항상 모든 구역을 표시합니다."
|
||||||
|
},
|
||||||
|
"offset": {
|
||||||
|
"label": "어노테이션 오프셋",
|
||||||
|
"desc": "이 데이터는 카메라의 감지(detect) 피드에서 생성되지만 녹화(record) 피드의 영상 위에 덧씌워집니다. 두 스트림이 완벽히 동기화되지 않을 수 있어 바운딩 박스와 녹화 영상이 정확히 일치하지 않을 수 있습니다. 이 설정을 사용하여 어노테이션을 시간상 앞뒤로 이동해 녹화 영상과의 싱크를 맞출 수 있습니다.",
|
||||||
|
"millisecondsToOffset": "감지 어노테이션을 오프셋할 밀리초(ms) 단위 시간입니다. <em>기본값: 0</em>",
|
||||||
|
"tips": "영상 재생이 박스 및 이동 경로 지점보다 앞서면 값을 낮추고, 영상 재생이 뒤처지면 값을 높이세요. 음수 값도 가능합니다.",
|
||||||
|
"toast": {
|
||||||
|
"success": "{{camera}} 카메라의 어노테이션 오프셋이 설정 파일에 저장되었습니다."
|
||||||
|
}
|
||||||
|
}
|
||||||
|
},
|
||||||
|
"carousel": {
|
||||||
|
"previous": "이전 슬라이드",
|
||||||
|
"next": "다음 슬라이드"
|
||||||
|
}
|
||||||
|
},
|
||||||
|
"itemMenu": {
|
||||||
|
"downloadVideo": {
|
||||||
|
"label": "동영상 다운로드",
|
||||||
|
"aria": "동영상 다운로드"
|
||||||
|
},
|
||||||
|
"downloadSnapshot": {
|
||||||
|
"label": "스냅샷 다운로드",
|
||||||
|
"aria": "스냅샷 다운로드"
|
||||||
|
},
|
||||||
|
"downloadCleanSnapshot": {
|
||||||
|
"label": "클린 스냅샷(박스 제외) 다운로드",
|
||||||
|
"aria": "클린 스냅샷 다운로드"
|
||||||
|
},
|
||||||
|
"viewTrackingDetails": {
|
||||||
|
"label": "추적 세부 정보 보기",
|
||||||
|
"aria": "추적 세부 정보 표시"
|
||||||
|
},
|
||||||
|
"findSimilar": {
|
||||||
|
"label": "유사 항목 찾기",
|
||||||
|
"aria": "유사한 추적 객체 찾기"
|
||||||
|
},
|
||||||
|
"addTrigger": {
|
||||||
|
"label": "트리거 추가",
|
||||||
|
"aria": "이 추적 객체에 대한 트리거 추가"
|
||||||
|
},
|
||||||
|
"audioTranscription": {
|
||||||
|
"label": "음성 변환",
|
||||||
|
"aria": "오디오 텍스트 변환 요청"
|
||||||
|
},
|
||||||
|
"submitToPlus": {
|
||||||
|
"label": "Frigate+에 제출",
|
||||||
|
"aria": "Frigate Plus에 제출"
|
||||||
|
},
|
||||||
|
"viewInHistory": {
|
||||||
|
"label": "기록에서 보기",
|
||||||
|
"aria": "기록에서 보기"
|
||||||
|
},
|
||||||
|
"deleteTrackedObject": {
|
||||||
|
"label": "이 추적 객체 삭제"
|
||||||
|
},
|
||||||
|
"showObjectDetails": {
|
||||||
|
"label": "객체 이동 경로 표시"
|
||||||
|
},
|
||||||
|
"hideObjectDetails": {
|
||||||
|
"label": "객체 이동 경로 숨기기"
|
||||||
|
},
|
||||||
|
"debugReplay": {
|
||||||
|
"label": "디버그 리플레이",
|
||||||
|
"aria": "디버그 리플레이 보기에서 이 추적 객체 보기"
|
||||||
|
},
|
||||||
|
"more": {
|
||||||
|
"aria": "더보기"
|
||||||
|
}
|
||||||
|
},
|
||||||
|
"dialog": {
|
||||||
|
"confirmDelete": {
|
||||||
|
"title": "삭제 확인",
|
||||||
|
"desc": "이 추적 객체를 삭제하면 스냅샷, 저장된 임베딩 및 연결된 모든 추적 세부 정보 항목이 제거됩니다. 기록 보기의 녹화 영상은 삭제되지 <em>않습니다</em>.<br /><br />정말로 계속 진행하시겠습니까?"
|
||||||
|
},
|
||||||
|
"toast": {
|
||||||
|
"error": "이 추적 객체 삭제 중 오류 발생: {{errorMessage}}"
|
||||||
|
}
|
||||||
|
},
|
||||||
|
"noTrackedObjects": "추적된 객체를 찾을 수 없음",
|
||||||
|
"fetchingTrackedObjectsFailed": "추적 객체 불러오기 오류: {{errorMessage}}",
|
||||||
|
"trackedObjectsCount_other": "추적된 객체 {{count}}개 ",
|
||||||
|
"searchResult": {
|
||||||
|
"tooltip": "{{type}} 기준 {{confidence}}% 일치",
|
||||||
|
"previousTrackedObject": "이전 추적 객체",
|
||||||
|
"nextTrackedObject": "다음 추적 객체",
|
||||||
|
"deleteTrackedObject": {
|
||||||
|
"toast": {
|
||||||
|
"success": "추적 객체가 성공적으로 삭제되었습니다.",
|
||||||
|
"error": "추적 객체 삭제 실패: {{errorMessage}}"
|
||||||
|
}
|
||||||
|
}
|
||||||
|
},
|
||||||
|
"aiAnalysis": {
|
||||||
|
"title": "AI 분석"
|
||||||
|
},
|
||||||
|
"concerns": {
|
||||||
|
"label": "주의 사항"
|
||||||
|
},
|
||||||
|
"objectLifecycle": {
|
||||||
|
"noImageFound": "이 추적 객체에 대한 이미지를 찾을 수 없습니다."
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -1,11 +1,11 @@
|
|||||||
{
|
{
|
||||||
"documentTitle": "내보내기 - Frigate",
|
"documentTitle": "내보내기 - Frigate",
|
||||||
"search": "검색",
|
"search": "검색",
|
||||||
"noExports": "내보내기가 없습니다",
|
"noExports": "내보내기 항목을 찾을 수 없음",
|
||||||
"deleteExport": {
|
"deleteExport": {
|
||||||
"label": "내보내기 삭제"
|
"label": "내보내기 삭제"
|
||||||
},
|
},
|
||||||
"deleteExport.desc": "{{exportName}}을 지우시겠습니까?",
|
"deleteExport.desc": "정말로 {{exportName}} 항목을 삭제하시겠습니까?",
|
||||||
"editExport": {
|
"editExport": {
|
||||||
"title": "내보내기 이름 변경",
|
"title": "내보내기 이름 변경",
|
||||||
"desc": "이 내보내기의 새 이름을 입력하세요.",
|
"desc": "이 내보내기의 새 이름을 입력하세요.",
|
||||||
@@ -13,24 +13,116 @@
|
|||||||
},
|
},
|
||||||
"toast": {
|
"toast": {
|
||||||
"error": {
|
"error": {
|
||||||
"renameExportFailed": "내보내기 이름 변경에 실패했습니다: {{errorMessage}}",
|
"renameExportFailed": "내보내기 이름 변경 실패: {{errorMessage}}",
|
||||||
"assignCaseFailed": "케이스 할당 업데이트 실패: {{errorMessage}}"
|
"assignCaseFailed": "케이스 지정 업데이트 실패: {{errorMessage}}",
|
||||||
|
"caseSaveFailed": "케이스 저장 실패: {{errorMessage}}",
|
||||||
|
"caseDeleteFailed": "케이스 삭제 실패: {{errorMessage}}"
|
||||||
}
|
}
|
||||||
},
|
},
|
||||||
"headings": {
|
"headings": {
|
||||||
"uncategorizedExports": "분류되지 않은 내보내기",
|
"uncategorizedExports": "미분류 내보내기",
|
||||||
"cases": "케이스"
|
"cases": "케이스"
|
||||||
},
|
},
|
||||||
"tooltip": {
|
"tooltip": {
|
||||||
"shareExport": "내보내기 공유",
|
"shareExport": "내보내기 공유",
|
||||||
"downloadVideo": "동영상 다운로드",
|
"downloadVideo": "동영상 다운로드",
|
||||||
"editName": "이름 편집",
|
"editName": "이름 수정",
|
||||||
"deleteExport": "내보내기 삭제",
|
"deleteExport": "내보내기 삭제",
|
||||||
"assignToCase": "케이스에 추가"
|
"assignToCase": "케이스에 추가",
|
||||||
|
"removeFromCase": "케이스에서 지우기"
|
||||||
},
|
},
|
||||||
"caseDialog": {
|
"caseDialog": {
|
||||||
"title": "케이스에 추가",
|
"title": "케이스에 추가",
|
||||||
"description": "기존 케이스를 선택하거나 새 케이스를 만드세요.",
|
"description": "기존 케이스를 선택하거나 새 케이스를 생성하세요.",
|
||||||
"selectLabel": "케이스"
|
"selectLabel": "케이스",
|
||||||
|
"newCaseOption": "새 케이스 생성",
|
||||||
|
"nameLabel": "케이스 이름",
|
||||||
|
"descriptionLabel": "설명"
|
||||||
|
},
|
||||||
|
"selected_one": "{{count}}개 선택됨",
|
||||||
|
"selected_other": "{{count}}개 선택됨",
|
||||||
|
"toolbar": {
|
||||||
|
"newCase": "새 케이스",
|
||||||
|
"addExport": "내보내기 추가",
|
||||||
|
"editCase": "케이스 수정",
|
||||||
|
"deleteCase": "케이스 지우기"
|
||||||
|
},
|
||||||
|
"deleteCase": {
|
||||||
|
"label": "케이스 삭제",
|
||||||
|
"desc": "정말로 {{caseName}} 케이스를 삭제하시겠습니까?",
|
||||||
|
"descKeepExports": "내보내기 파일들은 미분류 내보내기로 계속 유지됩니다.",
|
||||||
|
"descDeleteExports": "이 케이스에 포함된 모든 내보내기 파일이 영구적으로 삭제됩니다.",
|
||||||
|
"deleteExports": "내보내기 파일도 함께 삭제"
|
||||||
|
},
|
||||||
|
"caseCard": {
|
||||||
|
"emptyCase": "아직 내보내기 항목이 없습니다"
|
||||||
|
},
|
||||||
|
"jobCard": {
|
||||||
|
"defaultName": "{{camera}} 내보내기",
|
||||||
|
"queued": "대기 중",
|
||||||
|
"running": "실행 중",
|
||||||
|
"preparing": "준비 중",
|
||||||
|
"copying": "복사 중",
|
||||||
|
"encoding": "인코딩 중",
|
||||||
|
"encodingRetry": "인코딩 재시도 중",
|
||||||
|
"finalizing": "마무리 중"
|
||||||
|
},
|
||||||
|
"caseView": {
|
||||||
|
"noDescription": "설명 없음",
|
||||||
|
"createdAt": "{{value}} 생성됨",
|
||||||
|
"exportCount_one": "내보내기 1건",
|
||||||
|
"exportCount_other": "내보내기 {{count}}건",
|
||||||
|
"cameraCount_one": "카메라 1대",
|
||||||
|
"cameraCount_other": "카메라 {{count}}대",
|
||||||
|
"showMore": "더보기",
|
||||||
|
"showLess": "간략히 보기",
|
||||||
|
"emptyTitle": "이 케이스가 비어 있습니다",
|
||||||
|
"emptyDescription": "케이스를 정리하려면 기존의 미분류 내보내기 항목을 추가하세요.",
|
||||||
|
"emptyDescriptionNoExports": "추가할 수 있는 미분류 내보내기 항목이 아직 없습니다."
|
||||||
|
},
|
||||||
|
"caseEditor": {
|
||||||
|
"createTitle": "케이스 생성",
|
||||||
|
"editTitle": "케이스 수정",
|
||||||
|
"namePlaceholder": "케이스 이름",
|
||||||
|
"descriptionPlaceholder": "이 케이스에 대한 메모나 컨텍스트 추가"
|
||||||
|
},
|
||||||
|
"addExportDialog": {
|
||||||
|
"title": "{{caseName}}에 내보내기 추가",
|
||||||
|
"searchPlaceholder": "미분류 내보내기 검색",
|
||||||
|
"empty": "검색 결과와 일치하는 미분류 내보내기가 없습니다.",
|
||||||
|
"addButton_one": "내보내기 1건 추가",
|
||||||
|
"addButton_other": "내보내기 {{count}}건 추가",
|
||||||
|
"adding": "추가 중..."
|
||||||
|
},
|
||||||
|
"bulkActions": {
|
||||||
|
"addToCase": "케이스에 추가",
|
||||||
|
"moveToCase": "케이스로 이동",
|
||||||
|
"removeFromCase": "케이스에서 제거",
|
||||||
|
"delete": "삭제",
|
||||||
|
"deleteNow": "지금 삭제"
|
||||||
|
},
|
||||||
|
"bulkDelete": {
|
||||||
|
"title": "내보내기 일괄 삭제",
|
||||||
|
"desc_one": "정말로 내보내기 {{count}}건을 삭제하시겠습니까?",
|
||||||
|
"desc_other": "정말로 내보내기 {{count}}건을 삭제하시겠습니까?"
|
||||||
|
},
|
||||||
|
"bulkRemoveFromCase": {
|
||||||
|
"title": "케이스에서 제거",
|
||||||
|
"desc_one": "이 케이스에서 내보내기 {{count}}건을 제거하시겠습니까?",
|
||||||
|
"desc_other": "이 케이스에서 내보내기 {{count}}건을 제거하시겠습니까?",
|
||||||
|
"descKeepExports": "내보내기 파일이 미분류 상태로 이동됩니다.",
|
||||||
|
"descDeleteExports": "내보내기 파일이 영구적으로 삭제됩니다.",
|
||||||
|
"deleteExports": "대신 내보내기 파일 영구 삭제"
|
||||||
|
},
|
||||||
|
"bulkToast": {
|
||||||
|
"success": {
|
||||||
|
"delete": "내보내기 항목을 성공적으로 삭제했습니다",
|
||||||
|
"reassign": "케이스 지정을 성공적으로 업데이트했습니다",
|
||||||
|
"remove": "케이스에서 내보내기 항목을 성공적으로 제거했습니다"
|
||||||
|
},
|
||||||
|
"error": {
|
||||||
|
"deleteFailed": "내보내기 삭제 실패: {{errorMessage}}",
|
||||||
|
"reassignFailed": "케이스 지정 업데이트 실패: {{errorMessage}}"
|
||||||
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -1,92 +1,104 @@
|
|||||||
{
|
{
|
||||||
"description": {
|
"description": {
|
||||||
"placeholder": "이 모음집의 이름을 입력해주세요",
|
"placeholder": "이 컬렉션의 이름 입력",
|
||||||
"addFace": "안면인식 라이브러리에서 첫 사진을 업로드해 새로운 컬렉션을 만들어보세요.",
|
"addFace": "첫 번째 이미지를 업로드하여 얼굴 라이브러리에 새 컬렉션을 추가하세요.",
|
||||||
"invalidName": "잘못된 이름입니다. 이름은 문자, 숫자, 공백, 따옴표 ('), 밑줄 (_), 그리고 붙임표 (-)만 포함이 가능합니다.",
|
"invalidName": "유효하지 않은 이름입니다. 이름에는 문자, 숫자, 공백, 아포스트로피('), 밑줄(_), 하이픈(-)만 포함할 수 있습니다.",
|
||||||
"nameCannotContainHash": "이름에 #을 포함할 수 없습니다."
|
"nameCannotContainHash": "이름에는 # 기호를 포함할 수 없습니다."
|
||||||
},
|
},
|
||||||
"details": {
|
"details": {
|
||||||
"person": "사람",
|
"person": "사람",
|
||||||
"subLabelScore": "보조 레이블 신뢰도",
|
"subLabelScore": "보조 레이블 신뢰도",
|
||||||
"face": "얼굴 상세정보",
|
"face": "얼굴 상세정보",
|
||||||
"timestamp": "시간 기록",
|
"timestamp": "타임스탬프",
|
||||||
"unknown": "알 수 없음",
|
"unknown": "알 수 없음",
|
||||||
"scoreInfo": "점수는 각 이미지에서 얼굴의 크기를 가중치로 적용하여 모든 얼굴 점수의 평균을 낸 값입니다."
|
"scoreInfo": "점수는 각 이미지에서 얼굴이 차지하는 크기로 가중치를 부여한 모든 얼굴 점수의 가중 평균입니다."
|
||||||
},
|
},
|
||||||
"selectItem": "{{item}} 선택",
|
"selectItem": "{{item}} 선택",
|
||||||
"documentTitle": "얼굴 라이브러리 - Frigate",
|
"documentTitle": "얼굴 라이브러리 - Frigate",
|
||||||
"uploadFaceImage": {
|
"uploadFaceImage": {
|
||||||
"title": "얼굴 사진 올리기",
|
"title": "얼굴 이미지 업로드",
|
||||||
"desc": "얼굴을 스캔하고 {{pageToggle}}에 포함하기 위해 이미지를 업로드하세요"
|
"desc": "얼굴을 스캔하여 {{pageToggle}}에 추가할 이미지를 업로드하세요"
|
||||||
},
|
},
|
||||||
"collections": "모음집",
|
"collections": "컬렉션",
|
||||||
"createFaceLibrary": {
|
"createFaceLibrary": {
|
||||||
"title": "모음집 만들기",
|
"title": "모음집 만들기",
|
||||||
"desc": "새로운 모음집 만들기",
|
"desc": "새로운 모음집 만들기",
|
||||||
"new": "새 얼굴 만들기",
|
"new": "새 얼굴 등록",
|
||||||
"nextSteps": "강력한 기반을 구축하려면:<li>최근 인식 탭을 사용하여 감지된 각 사람의 이미지를 선택하고 학습하세요.</li><li>최상의 결과를 위해 정면 이미지에 집중하고, 각도가 있는 얼굴이 촬영된 이미지는 학습에 사용하지 마세요.</li></ul>"
|
"nextSteps": "정확도를 높이기 위한 권장 사항:<li>'최근 인식' 탭을 사용하여 감지된 인물별 이미지를 선택하고 학습시키세요.</li><li>최상의 결과를 위해 정면을 바라보는 사진 위주로 등록하고, 각도가 틀어진 얼굴 이미지는 피하세요.</li></ul>"
|
||||||
},
|
},
|
||||||
"steps": {
|
"steps": {
|
||||||
"faceName": "얼굴 이름 입력",
|
"faceName": "얼굴 이름 입력",
|
||||||
"uploadFace": "얼굴 사진 올리기",
|
"uploadFace": "얼굴 이미지 업로드",
|
||||||
"nextSteps": "다음 단계",
|
"nextSteps": "다음 단계",
|
||||||
"description": {
|
"description": {
|
||||||
"uploadFace": "정면을 향한 {{name}}의 얼굴이 보이는 이미지를 업로드하세요. 이미지를 얼굴만 자를 필요는 없습니다."
|
"uploadFace": "{{name}}의 얼굴이 정면에서 보이는 이미지를 업로드하세요. 얼굴 부분만 따로 크롭하지 않아도 됩니다."
|
||||||
}
|
}
|
||||||
},
|
},
|
||||||
"train": {
|
"train": {
|
||||||
"title": "최근 인식",
|
"title": "최근 인식",
|
||||||
"aria": "최근 인식 선택",
|
"aria": "최근 인식 선택",
|
||||||
"titleShort": "최근"
|
"titleShort": "최근",
|
||||||
|
"empty": "최근 얼굴 인식 시도 내역이 없습니다",
|
||||||
|
"emptyNoLibrary": {
|
||||||
|
"title": "얼굴 업로드",
|
||||||
|
"description": "얼굴 인식 기능이 작동하려면 라이브러리에 최소 한 개 이상의 얼굴을 등록해야 합니다."
|
||||||
|
}
|
||||||
},
|
},
|
||||||
"selectFace": "얼굴 선택",
|
"selectFace": "얼굴 선택",
|
||||||
"deleteFaceLibrary": {
|
"deleteFaceLibrary": {
|
||||||
"title": "이름 삭제"
|
"title": "이름 삭제",
|
||||||
|
"desc": "정말로 '{{name}}' 컬렉션을 삭제하시겠습니까? 연결된 모든 얼굴이 영구적으로 삭제됩니다."
|
||||||
},
|
},
|
||||||
"deleteFaceAttempts": {
|
"deleteFaceAttempts": {
|
||||||
"title": "얼굴 삭제"
|
"title": "얼굴 삭제",
|
||||||
|
"desc_other": "정말로 얼굴 {{count}}개를 삭제하시겠습니까? 이 작업은 되돌릴 수 없습니다."
|
||||||
},
|
},
|
||||||
"renameFace": {
|
"renameFace": {
|
||||||
"title": "얼굴 이름 바꾸기",
|
"title": "얼굴 이름 변경",
|
||||||
"desc": "{{name}}의 새 이름을 입력하세요"
|
"desc": "'{{name}}'의 새 이름을 입력하세요"
|
||||||
},
|
},
|
||||||
"button": {
|
"button": {
|
||||||
"deleteFaceAttempts": "얼굴 삭제",
|
"deleteFaceAttempts": "얼굴 삭제",
|
||||||
"addFace": "얼굴 추가",
|
"addFace": "얼굴 추가",
|
||||||
"renameFace": "얼굴 이름 바꾸기",
|
"renameFace": "얼굴 이름 변경",
|
||||||
"deleteFace": "얼굴 삭제",
|
"deleteFace": "얼굴 삭제",
|
||||||
"uploadImage": "이미지 올리기",
|
"uploadImage": "이미지 업로드",
|
||||||
"reprocessFace": "얼굴 재조정"
|
"reprocessFace": "얼굴 재처리"
|
||||||
},
|
},
|
||||||
"imageEntry": {
|
"imageEntry": {
|
||||||
"validation": {
|
"validation": {
|
||||||
"selectImage": "이미지 파일을 선택해주세요."
|
"selectImage": "이미지 파일을 선택해 주세요."
|
||||||
},
|
},
|
||||||
"dropActive": "여기에 이미지 놓기…",
|
"dropActive": "여기에 이미지를 놓으세요…",
|
||||||
"dropInstructions": "이미지를 끌어다 놓거나 여기에 붙여넣으세요. 선택할 수도 있습니다.",
|
"dropInstructions": "이미지를 여기로 드래그 앤 드롭하거나 붙여넣기, 또는 클릭하여 선택하세요",
|
||||||
"maxSize": "최대 용량: {{size}}MB"
|
"maxSize": "최대 크기: {{size}}MB"
|
||||||
},
|
},
|
||||||
"nofaces": "얼굴을 찾을 수 없습니다",
|
"nofaces": "사용 가능한 얼굴 없음",
|
||||||
"pixels": "{{area}}px",
|
"pixels": "{{area}}px",
|
||||||
"trainFaceAs": "얼굴을 다음과 같이 훈련하기:",
|
"trainFaceAs": "다음으로 얼굴 학습:",
|
||||||
"trainFace": "얼굴 훈련하기",
|
"trainFace": "얼굴 학습",
|
||||||
"toast": {
|
"toast": {
|
||||||
"success": {
|
"success": {
|
||||||
"uploadedImage": "이미지 업로드에 성공했습니다.",
|
"uploadedImage": "이미지를 성공적으로 업로드했습니다.",
|
||||||
"addFaceLibrary": "{{name}} 을 성공적으로 얼굴 라이브러리에 추가했습니다!",
|
"addFaceLibrary": "'{{name}}'이(가) 얼굴 라이브러리에 성공적으로 추가되었습니다!",
|
||||||
"deletedFace_other": "{{count}} 얼굴을 성공적으로 삭제했습니다.",
|
"deletedFace_other": "얼굴 {{count}}개를 성공적으로 삭제했습니다.",
|
||||||
"renamedFace": "얼굴 이름을 {{name}} 으로 성공적으로 바꿨습니다",
|
"renamedFace": "얼굴 이름을 '{{name}}'(으)로 성공적으로 변경했습니다",
|
||||||
"trainedFace": "얼굴 훈련을 성공적으로 마쳤습니다.",
|
"trainedFace": "얼굴 학습을 성공적으로 완료했습니다.",
|
||||||
"updatedFaceScore": "{{name}} 얼굴 점수 업데이트 성공 {{score}}."
|
"updatedFaceScore": "얼굴 점수를 '{{name}}' ({{score}})(으)로 성공적으로 업데이트했습니다.",
|
||||||
|
"deletedName_other": "얼굴 {{count}}개를 성공적으로 삭제했습니다.",
|
||||||
|
"reclassifiedFace": "얼굴 재분류를 성공적으로 완료했습니다."
|
||||||
},
|
},
|
||||||
"error": {
|
"error": {
|
||||||
"uploadingImageFailed": "이미지 업로드 실패:{{errorMessage}}",
|
"uploadingImageFailed": "이미지 업로드 실패: {{errorMessage}}",
|
||||||
"addFaceLibraryFailed": "얼굴 이름 설정 실패:{{errorMessage}}",
|
"addFaceLibraryFailed": "얼굴 이름 설정 실패: {{errorMessage}}",
|
||||||
"deleteFaceFailed": "삭제 실패:{{errorMessage}}",
|
"deleteFaceFailed": "삭제 실패: {{errorMessage}}",
|
||||||
"deleteNameFailed": "이름 삭제 실패:{{errorMessage}}",
|
"deleteNameFailed": "이름 삭제 실패: {{errorMessage}}",
|
||||||
"renameFaceFailed": "이름 바꾸기 실패:{{errorMessage}}",
|
"renameFaceFailed": "얼굴 이름 변경 실패: {{errorMessage}}",
|
||||||
"trainFailed": "훈련 실패:{{errorMessage}}",
|
"trainFailed": "학습 실패: {{errorMessage}}",
|
||||||
"updateFaceScoreFailed": "얼굴 신뢰도 업데이트 실패:{{errorMessage}}"
|
"updateFaceScoreFailed": "얼굴 점수 업데이트 실패: {{errorMessage}}",
|
||||||
|
"reclassifyFailed": "얼굴 재분류 실패: {{errorMessage}}"
|
||||||
}
|
}
|
||||||
}
|
},
|
||||||
|
"reclassifyFaceAs": "다음으로 얼굴 재분류:",
|
||||||
|
"reclassifyFace": "얼굴 재분류"
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -1,22 +1,24 @@
|
|||||||
{
|
{
|
||||||
"documentTitle": "실시간 보기 - Frigate",
|
"documentTitle": {
|
||||||
"documentTitle.withCamera": "{{camera}} - 실시간 보기 - Frigate",
|
"default": "실시간 - Frigate"
|
||||||
|
},
|
||||||
|
"documentTitle.withCamera": "{{camera}} - 실시간 - Frigate",
|
||||||
"lowBandwidthMode": "저대역폭 모드",
|
"lowBandwidthMode": "저대역폭 모드",
|
||||||
"twoWayTalk": {
|
"twoWayTalk": {
|
||||||
"enable": "양방향 말하기 활성화",
|
"enable": "양방향 대화 켜기",
|
||||||
"disable": "양방향 말하기 비활성화"
|
"disable": "양방향 대화 끄기"
|
||||||
},
|
},
|
||||||
"cameraAudio": {
|
"cameraAudio": {
|
||||||
"enable": "카메라 오디오 활성화",
|
"enable": "카메라 오디오 켜기",
|
||||||
"disable": "카메라 오디오 비활성화"
|
"disable": "카메라 오디오 끄기"
|
||||||
},
|
},
|
||||||
"ptz": {
|
"ptz": {
|
||||||
"move": {
|
"move": {
|
||||||
"clickMove": {
|
"clickMove": {
|
||||||
"label": "클릭해서 카메라 중앙 배치",
|
"label": "화면을 클릭하여 카메라 중앙 맞추기",
|
||||||
"enable": "클릭해서 움직이기 기능 활성화",
|
"enable": "클릭 이동 활성화",
|
||||||
"disable": "클릭해서 움직이기 기능 비활성화",
|
"disable": "클릭 이동 비활성화",
|
||||||
"enableWithZoom": "클릭하여 이동 / 드래그하여 확대 활성화"
|
"enableWithZoom": "클릭 이동 / 드래그 줌 활성화"
|
||||||
},
|
},
|
||||||
"left": {
|
"left": {
|
||||||
"label": "PTZ 카메라 왼쪽으로 이동"
|
"label": "PTZ 카메라 왼쪽으로 이동"
|
||||||
@@ -41,158 +43,162 @@
|
|||||||
},
|
},
|
||||||
"focus": {
|
"focus": {
|
||||||
"in": {
|
"in": {
|
||||||
"label": "PTZ 카메라 포커스 인"
|
"label": "PTZ 카메라 초점 당기기"
|
||||||
},
|
},
|
||||||
"out": {
|
"out": {
|
||||||
"label": "PTZ 카메라 포커스 아웃"
|
"label": "PTZ 카메라 초점 밀기"
|
||||||
}
|
}
|
||||||
},
|
},
|
||||||
"frame": {
|
"frame": {
|
||||||
"center": {
|
"center": {
|
||||||
"label": "클릭해서 PTZ 카메라 중앙 배치"
|
"label": "화면을 클릭하여 PTZ 카메라 중앙 맞추기"
|
||||||
}
|
}
|
||||||
},
|
},
|
||||||
"presets": "PTZ 카메라 프리셋"
|
"presets": "PTZ 카메라 프리셋"
|
||||||
},
|
},
|
||||||
"camera": {
|
"camera": {
|
||||||
"enable": "카메라 활성화",
|
"enable": "카메라 활성화",
|
||||||
"disable": "카메라 비활성화"
|
"disable": "카메라 비활성화",
|
||||||
|
"turnOn": "카메라 켜기",
|
||||||
|
"turnOff": "카메라 끄기"
|
||||||
},
|
},
|
||||||
"muteCameras": {
|
"muteCameras": {
|
||||||
"enable": "모든 카메라 음소거",
|
"enable": "모든 카메라 음소거",
|
||||||
"disable": "모든 카메라 음소거 해제"
|
"disable": "모든 카메라 음소거 해제"
|
||||||
},
|
},
|
||||||
"detect": {
|
"detect": {
|
||||||
"enable": "감지 활성화",
|
"enable": "감지(Detect) 켜기",
|
||||||
"disable": "감지 비활성화"
|
"disable": "감지(Detect) 끄기"
|
||||||
},
|
},
|
||||||
"recording": {
|
"recording": {
|
||||||
"enable": "녹화 활성화",
|
"enable": "녹화 켜기",
|
||||||
"disable": "녹화 비활성화"
|
"disable": "녹화 끄기",
|
||||||
|
"disabledInConfig": "이 카메라의 녹화를 활성화하려면 먼저 설정에서 켜야 합니다."
|
||||||
},
|
},
|
||||||
"snapshots": {
|
"snapshots": {
|
||||||
"enable": "스냅샷 활성화",
|
"enable": "스냅샷 켜기",
|
||||||
"disable": "스냅샷 비활성화"
|
"disable": "스냅샷 끄기"
|
||||||
},
|
},
|
||||||
"audioDetect": {
|
"audioDetect": {
|
||||||
"enable": "오디오 감지 활성화",
|
"enable": "오디오 감지 켜기",
|
||||||
"disable": "오디오 감지 비활성화"
|
"disable": "오디오 감지 끄기"
|
||||||
},
|
},
|
||||||
"transcription": {
|
"transcription": {
|
||||||
"enable": "실시간 오디오 자막 활성화",
|
"enable": "실시간 음성 전사 켜기",
|
||||||
"disable": "실시간 오디오 자막 비활성화"
|
"disable": "실시간 음성 전사 끄기"
|
||||||
},
|
},
|
||||||
"autotracking": {
|
"autotracking": {
|
||||||
"enable": "자동 추적 활성화",
|
"enable": "자동 추적 켜기",
|
||||||
"disable": "자동 추적 비활성화"
|
"disable": "자동 추적 끄기"
|
||||||
},
|
},
|
||||||
"streamStats": {
|
"streamStats": {
|
||||||
"enable": "스트림 통계 보기",
|
"enable": "스트림 통계 보기",
|
||||||
"disable": "스트림 통계 숨기기"
|
"disable": "스트림 통계 숨기기"
|
||||||
},
|
},
|
||||||
"manualRecording": {
|
"manualRecording": {
|
||||||
"title": "수동 녹화",
|
"title": "수동 감지/녹화",
|
||||||
"tips": "이 카메라의 녹화 보관 설정에 따라 인스턴트 스냅샷을 다운로드하거나 수동 녹화를 시작할 수 있습니다.",
|
"tips": "즉시 스냅샷을 다운로드하거나 이 카메라의 녹화 보관 설정을 기반으로 수동 이벤트를 시작합니다.",
|
||||||
"playInBackground": {
|
"playInBackground": {
|
||||||
"label": "백그라운드에서 재생",
|
"label": "백그라운드에서 재생",
|
||||||
"desc": "이 옵션을 활성화하면 플레이어가 숨겨져도 계속 스트리밍됩니다."
|
"desc": "플레이어가 화면에서 가려져 있을 때도 스트리밍을 계속 유지합니다."
|
||||||
},
|
},
|
||||||
"showStats": {
|
"showStats": {
|
||||||
"label": "통계 보기",
|
"label": "통계 표시",
|
||||||
"desc": "이 옵션을 활성화하면 카메라 피드에 스트림 통계가 나타납니다."
|
"desc": "카메라 피드 위에 실시간 스트림 통계 정보를 오버레이로 표시합니다."
|
||||||
},
|
},
|
||||||
"debugView": "디버그 보기",
|
"debugView": "디버그 뷰",
|
||||||
"start": "수동 녹화 시작",
|
"start": "수동 녹화 시작",
|
||||||
"started": "수동 녹화 시작되었습니다.",
|
"started": "수동 녹화를 시작했습니다.",
|
||||||
"failedToStart": "수동 녹화 시작이 실패했습니다.",
|
"failedToStart": "수동 녹화 시작에 실패했습니다.",
|
||||||
"recordDisabledTips": "이 카메라 설정에서 녹화가 비활성화 되었거나 제한되어 있어 스냅샷만 저장됩니다.",
|
"recordDisabledTips": "이 카메라는 설정에서 녹화가 비활성화되었거나 제한되어 있어 스냅샷만 저장됩니다.",
|
||||||
"end": "수동 녹화 종료",
|
"end": "수동 녹화 종료",
|
||||||
"ended": "수동 녹화가 종료되었습니다.",
|
"ended": "수동 녹화를 종료했습니다.",
|
||||||
"failedToEnd": "수동 녹화 종료가 실패했습니다."
|
"failedToEnd": "수동 녹화 종료에 실패했습니다."
|
||||||
},
|
},
|
||||||
"streamingSettings": "스트리밍 설정",
|
"streamingSettings": "스트리밍 설정",
|
||||||
"notifications": "알림",
|
"notifications": "알림",
|
||||||
"audio": "오디오",
|
"audio": "오디오",
|
||||||
"suspend": {
|
"suspend": {
|
||||||
"forTime": "일시정지 시간: "
|
"forTime": "일시 중지 시간: "
|
||||||
},
|
},
|
||||||
"stream": {
|
"stream": {
|
||||||
"title": "스트림",
|
"title": "스트림",
|
||||||
"audio": {
|
"audio": {
|
||||||
"available": "이 스트림에서 오디오를 사용할 수 있습니다",
|
"available": "이 스트림에서 오디오 기능을 사용할 수 있습니다",
|
||||||
"unavailable": "이 스트림에서 오디오를 사용할 수 없습니다",
|
"unavailable": "이 스트림에서는 오디오 기능을 사용할 수 없습니다",
|
||||||
"tips": {
|
"tips": {
|
||||||
"title": "이 스트림에서 오디오를 사용하려면 카메라에서 오디오를 출력하고 go2rtc에서 설정해야 합니다."
|
"title": "오디오를 출력하려면 카메라에서 오디오를 지원하고 go2rtc에 해당 스트림이 설정되어 있어야 합니다."
|
||||||
}
|
}
|
||||||
},
|
},
|
||||||
"debug": {
|
"debug": {
|
||||||
"picker": "디버그 모드에선 스트림 모드를 선택할 수 없습니다. 디버그 뷰에서는 항상 감지(Detect) 역할로 설정한 스트림을 사용합니다."
|
"picker": "디버그 모드에서는 스트림을 선택할 수 없습니다. 디버그 뷰는 항상 detect 역할이 할당된 스트림을 사용합니다."
|
||||||
},
|
},
|
||||||
"twoWayTalk": {
|
"twoWayTalk": {
|
||||||
"tips": "양방향 말하기 기능을 사용하려면 기기에서 기능을 지원해야하며 WebRTC를 설정해야합니다.",
|
"tips": "기기에서 해당 기능을 지원해야 하며 양방향 대화를 위해 WebRTC가 설정되어 있어야 합니다.",
|
||||||
"available": "이 기기는 양방향 말하기 기능을 사용할 수 있습니다",
|
"available": "이 스트림에서 양방향 대화를 사용할 수 있습니다",
|
||||||
"unavailable": "이 기기는 양방향 말하기 기능을 사용할 수 없습니다"
|
"unavailable": "이 스트림에서는 양방향 대화를 사용할 수 없습니다"
|
||||||
},
|
},
|
||||||
"lowBandwidth": {
|
"lowBandwidth": {
|
||||||
"tips": "버퍼링 또는 스트림 오류로 실시간 화면을 저대역폭 모드로 변경되었습니다.",
|
"tips": "버퍼링 또는 스트림 오류로 인해 실시간 화면이 저대역폭 모드로 전환되었습니다.",
|
||||||
"resetStream": "스트림 리셋"
|
"resetStream": "스트림 초기화"
|
||||||
},
|
},
|
||||||
"playInBackground": {
|
"playInBackground": {
|
||||||
"label": "백그라운드에서 재생",
|
"label": "백그라운드에서 재생",
|
||||||
"tips": "이 옵션을 활성화하면 플레이어가 숨겨져도 스트리밍이 지속됩니다."
|
"tips": "플레이어가 화면에서 가려져 있을 때도 스트리밍을 계속 유지합니다."
|
||||||
}
|
}
|
||||||
},
|
},
|
||||||
"cameraSettings": {
|
"cameraSettings": {
|
||||||
"title": "{{camera}} 설정",
|
"title": "{{camera}} 설정",
|
||||||
"cameraEnabled": "카메라 활성화",
|
"cameraEnabled": "카메라 활성화",
|
||||||
"objectDetection": "대상 감지",
|
"objectDetection": "객체 감지",
|
||||||
"recording": "녹화",
|
"recording": "녹화",
|
||||||
"snapshots": "스냅샷",
|
"snapshots": "스냅샷",
|
||||||
"audioDetection": "오디오 감지",
|
"audioDetection": "오디오 감지",
|
||||||
"transcription": "오디오 자막",
|
"transcription": "오디오 텍스트 변환",
|
||||||
"autotracking": "자동 추적"
|
"autotracking": "자동 추적",
|
||||||
|
"camera": "카메라"
|
||||||
},
|
},
|
||||||
"history": {
|
"history": {
|
||||||
"label": "이전 영상 보기"
|
"label": "과거 녹화 영상 보기"
|
||||||
},
|
},
|
||||||
"effectiveRetainMode": {
|
"effectiveRetainMode": {
|
||||||
"modes": {
|
"modes": {
|
||||||
"all": "전체",
|
"all": "전체",
|
||||||
"motion": "움직임 감지",
|
"motion": "움직임",
|
||||||
"active_objects": "활성 대상"
|
"active_objects": "활동 중인 객체"
|
||||||
},
|
},
|
||||||
"notAllTips": "{{source}} 녹화 보관 설정이 <code>mode: {{effectiveRetainMode}}</code>로 되어 있어, 이 수동 녹화는 {{effectiveRetainModeName}}이(가) 있는 구간만 저장됩니다."
|
"notAllTips": "{{source}} 녹화 보관 설정이 <code>mode: {{effectiveRetainMode}}</code>로 되어 있어, 이 수동 녹화는 {{effectiveRetainModeName}}이(가) 있는 구간만 저장됩니다."
|
||||||
},
|
},
|
||||||
"editLayout": {
|
"editLayout": {
|
||||||
"label": "레이아웃 편집",
|
"label": "레이아웃 편집",
|
||||||
"group": {
|
"group": {
|
||||||
"label": "카메라 그룹 편집"
|
"label": "카메라 그룹 수정"
|
||||||
},
|
},
|
||||||
"exitEdit": "편집 종료"
|
"exitEdit": "편집 종료"
|
||||||
},
|
},
|
||||||
"noCameras": {
|
"noCameras": {
|
||||||
"title": "설정된 카메라 없음",
|
"title": "설정된 카메라 없음",
|
||||||
"description": "카메라를 연결해 시작하세요.",
|
"description": "Frigate에 카메라를 연결하여 시작하세요.",
|
||||||
"buttonText": "카메라 추가",
|
"buttonText": "카메라 추가",
|
||||||
"restricted": {
|
"restricted": {
|
||||||
"title": "연결된 카메라 없음",
|
"title": "사용 가능한 카메라 없음",
|
||||||
"description": "이 그룹의 카메라를 볼 권한이 없습니다."
|
"description": "이 그룹의 카메라를 볼 수 있는 권한이 없습니다."
|
||||||
},
|
},
|
||||||
"default": {
|
"default": {
|
||||||
"title": "설정된 카메라 없음",
|
"title": "설정된 카메라 없음",
|
||||||
"description": "카메라를 연결하여 Frigate을 시작하세요.",
|
"description": "Frigate에 카메라를 연결하여 시작하세요.",
|
||||||
"buttonText": "카메라 추가"
|
"buttonText": "카메라 추가"
|
||||||
},
|
},
|
||||||
"group": {
|
"group": {
|
||||||
"title": "그룹에 카메라 없음",
|
"title": "그룹 내 카메라 없음",
|
||||||
"description": "이 그룹에 할당되거나 활성화된 카메라가 없습니다.",
|
"description": "이 카메라 그룹에 할당되거나 활성화된 카메라가 없습니다.",
|
||||||
"buttonText": "그룹 관리"
|
"buttonText": "그룹 관리"
|
||||||
}
|
}
|
||||||
},
|
},
|
||||||
"snapshot": {
|
"snapshot": {
|
||||||
"takeSnapshot": "인스턴트 스냅샷 다운로드",
|
"takeSnapshot": "즉시 스냅샷 다운로드",
|
||||||
"noVideoSource": "스냅샷 찍을 비디오 소스가 없습니다.",
|
"noVideoSource": "스냅샷을 캡처할 비디오 소스가 없습니다.",
|
||||||
"captureFailed": "스냅샷 캡쳐를 하지 못했습니다.",
|
"captureFailed": "스냅샷 캡처에 실패했습니다.",
|
||||||
"downloadStarted": "스냅샷 다운로드가 시작됐습니다."
|
"downloadStarted": "스냅샷 다운로드가 시작되었습니다."
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -1 +1,76 @@
|
|||||||
{}
|
{
|
||||||
|
"documentTitle": "움직임 검색 - Frigate",
|
||||||
|
"title": "움직임 검색",
|
||||||
|
"description": "다각형을 그려 관심 영역(ROI)을 지정하고, 해당 영역 내에서 움직임 변화를 검색할 시간 범위를 지정하세요.",
|
||||||
|
"selectCamera": "움직임 검색을 불러오는 중입니다",
|
||||||
|
"startSearch": "검색 시작",
|
||||||
|
"searchStarted": "검색이 시작되었습니다",
|
||||||
|
"searchCancelled": "검색이 취소되었습니다",
|
||||||
|
"cancelSearch": "취소",
|
||||||
|
"searching": "검색 진행 중입니다.",
|
||||||
|
"scanning": "{{time}} 스캔 중",
|
||||||
|
"searchComplete": "검색 완료",
|
||||||
|
"noResultsYet": "선택한 영역의 움직임 변화를 찾으려면 검색을 실행하세요",
|
||||||
|
"noChangesFound": "선택한 영역에서 감지된 픽셀 변화가 없습니다",
|
||||||
|
"changesFound_other": "움직임 변화 {{count}}건 발견",
|
||||||
|
"framesProcessed": "{{count}}개 프레임 처리됨",
|
||||||
|
"jumpToTime": "이 시간대로 이동",
|
||||||
|
"results": "결과",
|
||||||
|
"showSegmentHeatmap": "히트맵",
|
||||||
|
"newSearch": "새 검색",
|
||||||
|
"clearResults": "결과 지우기",
|
||||||
|
"clearROI": "다각형 지우기",
|
||||||
|
"polygonControls": {
|
||||||
|
"points_other": "점 {{count}}개",
|
||||||
|
"undo": "마지막 점 취소",
|
||||||
|
"reset": "다각형 초기화",
|
||||||
|
"drawMode": "그리기",
|
||||||
|
"moveMode": "이동"
|
||||||
|
},
|
||||||
|
"motionHeatmapLabel": "움직임 히트맵",
|
||||||
|
"dialog": {
|
||||||
|
"title": "움직임 검색",
|
||||||
|
"cameraLabel": "카메라",
|
||||||
|
"previewAlt": "{{camera}} 카메라 미리보기"
|
||||||
|
},
|
||||||
|
"timeRange": {
|
||||||
|
"title": "검색 범위",
|
||||||
|
"start": "시작 시간",
|
||||||
|
"end": "종료 시간"
|
||||||
|
},
|
||||||
|
"settings": {
|
||||||
|
"title": "검색 설정",
|
||||||
|
"parallelMode": "병렬 모드",
|
||||||
|
"parallelModeDesc": "여러 녹화 구간을 동시에 스캔합니다 (더 빠르지만 디코딩 자원을 더 많이 소모함)",
|
||||||
|
"threshold": "민감도 임계값",
|
||||||
|
"thresholdDesc": "값이 낮을수록 더 미세한 변화를 감지합니다 (1-255)",
|
||||||
|
"minArea": "최소 변화 면적",
|
||||||
|
"minAreaDesc": "단일 움직임 영역의 최소 크기(관심 영역 대비 백분율)",
|
||||||
|
"maxResults": "최대 결과 수",
|
||||||
|
"maxResultsDesc": "일치하는 타임스탬프가 이 개수에 도달하면 검색을 중단합니다"
|
||||||
|
},
|
||||||
|
"errors": {
|
||||||
|
"noCamera": "카메라를 선택해 주세요",
|
||||||
|
"noROI": "관심 영역(ROI)을 그려 주세요",
|
||||||
|
"noTimeRange": "시간 범위를 선택해 주세요",
|
||||||
|
"invalidTimeRange": "종료 시간은 시작 시간 이후여야 합니다",
|
||||||
|
"searchFailed": "검색 실패: {{message}}",
|
||||||
|
"polygonTooSmall": "다각형은 최소 3개 이상의 점이 필요합니다",
|
||||||
|
"unknown": "알 수 없는 오류"
|
||||||
|
},
|
||||||
|
"changePercentage": "{{percentage}}% 변화됨",
|
||||||
|
"metrics": {
|
||||||
|
"title": "검색 지표",
|
||||||
|
"segmentsScanned": "스캔한 세그먼트",
|
||||||
|
"segmentsProcessed": "처리됨",
|
||||||
|
"segmentsSkippedInactive": "건너뜀 (활동 없음)",
|
||||||
|
"segmentsSkippedHeatmap": "건너뜀 (ROI 겹침 없음)",
|
||||||
|
"fallbackFullRange": "전체 범위 폴백 스캔",
|
||||||
|
"framesDecoded": "디코딩된 프레임",
|
||||||
|
"wallTime": "검색 소요 시간",
|
||||||
|
"segmentErrors": "세그먼트 오류",
|
||||||
|
"seconds": "{{seconds}}초",
|
||||||
|
"minutesSeconds": "{{minutes}}분 {{seconds}}초",
|
||||||
|
"scanSummary": "{{segments}}개 세그먼트 · {{time}}"
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|||||||
@@ -1,12 +1,12 @@
|
|||||||
{
|
{
|
||||||
"filter": "필터",
|
"filter": "필터",
|
||||||
"export": "내보내기",
|
"export": "내보내기",
|
||||||
"calendar": "날짜",
|
"calendar": "달력",
|
||||||
"filters": "필터",
|
"filters": "필터",
|
||||||
"toast": {
|
"toast": {
|
||||||
"error": {
|
"error": {
|
||||||
"noValidTimeSelected": "올바른 시간 범위를 선택하세요",
|
"noValidTimeSelected": "유효한 시간 범위가 선택되지 않았습니다",
|
||||||
"endTimeMustAfterStartTime": "종료 시간은 시작 시간보다 뒤에 있어야합니다"
|
"endTimeMustAfterStartTime": "종료 시간은 시작 시간 이후여야 합니다"
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -1 +1,60 @@
|
|||||||
{}
|
{
|
||||||
|
"title": "디버그 리플레이",
|
||||||
|
"description": "디버깅을 위해 카메라 녹화본을 다시 재생합니다. 객체 목록에는 감지된 객체의 시간 지연 요약이 표시되며, 메시지 탭에는 리플레이 영상에 대한 Frigate 내부 메시지 스트림이 표시됩니다.",
|
||||||
|
"websocket_messages": "메시지",
|
||||||
|
"dialog": {
|
||||||
|
"title": "디버그 리플레이 시작",
|
||||||
|
"description": "객체 감지 및 추적 문제 디버깅을 위해 지난 녹화본을 반복 재생하는 임시 리플레이 카메라를 생성합니다. 리플레이 카메라는 원본 카메라와 동일한 감지 설정을 가집니다. 시작할 시간 범위를 선택하세요.",
|
||||||
|
"camera": "원본 카메라",
|
||||||
|
"timeRange": "시간 범위",
|
||||||
|
"preset": {
|
||||||
|
"1m": "최근 1분",
|
||||||
|
"5m": "최근 5분",
|
||||||
|
"timeline": "타임라인에서 선택",
|
||||||
|
"custom": "사용자 정의"
|
||||||
|
},
|
||||||
|
"startButton": "리플레이 시작",
|
||||||
|
"selectFromTimeline": "선택",
|
||||||
|
"starting": "리플레이 시작 중...",
|
||||||
|
"startLabel": "시작",
|
||||||
|
"endLabel": "종료",
|
||||||
|
"toast": {
|
||||||
|
"error": "디버그 리플레이 시작 실패: {{error}}",
|
||||||
|
"alreadyActive": "이미 활성화된 리플레이 세션이 있습니다",
|
||||||
|
"noRecordings": "선택한 시간 범위에서 녹화본을 찾을 수 없습니다",
|
||||||
|
"stopError": "디버그 리플레이 중지 실패: {{error}}",
|
||||||
|
"goToReplay": "리플레이로 이동"
|
||||||
|
}
|
||||||
|
},
|
||||||
|
"page": {
|
||||||
|
"noSession": "활성화된 디버그 리플레이 세션 없음",
|
||||||
|
"noSessionDesc": "기록(History) 보기 툴바에서 작업(Actions) 버튼을 클릭하고 '디버그 리플레이'를 선택하여 시작하세요.",
|
||||||
|
"goToRecordings": "기록으로 이동",
|
||||||
|
"preparingClip": "클립 준비 중…",
|
||||||
|
"preparingClipDesc": "Frigate이 선택한 시간 범위의 녹화본을 이어 붙이는 중입니다. 긴 시간 범위의 경우 약 1분 정도 소요될 수 있습니다.",
|
||||||
|
"startingCamera": "디버그 리플레이 시작 중…",
|
||||||
|
"startError": {
|
||||||
|
"title": "디버그 리플레이 시작 실패",
|
||||||
|
"back": "기록으로 돌아가기"
|
||||||
|
},
|
||||||
|
"sourceCamera": "원본 카메라",
|
||||||
|
"replayCamera": "리플레이 카메라",
|
||||||
|
"initializingReplay": "디버그 리플레이 초기화 중...",
|
||||||
|
"stoppingReplay": "디버그 리플레이 중지 중...",
|
||||||
|
"stopReplay": "리플레이 중지",
|
||||||
|
"confirmStop": {
|
||||||
|
"title": "디버그 리플레이를 중지하시겠습니까?",
|
||||||
|
"description": "세션이 중지되고 모든 임시 데이터가 정리됩니다. 계속하시겠습니까?",
|
||||||
|
"confirm": "리플레이 중지",
|
||||||
|
"cancel": "취소"
|
||||||
|
},
|
||||||
|
"activity": "활동",
|
||||||
|
"objects": "객체 목록",
|
||||||
|
"audioDetections": "오디오 감지",
|
||||||
|
"noActivity": "감지된 활동 없음",
|
||||||
|
"activeTracking": "활성 추적 중",
|
||||||
|
"noActiveTracking": "활성 추적 없음",
|
||||||
|
"configuration": "설정",
|
||||||
|
"configurationDesc": "디버그 리플레이 카메라의 움직임 감지 및 객체 추적 설정을 미세 조정합니다. Frigate 설정 파일에는 변경 사항이 저장되지 않습니다."
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|||||||
@@ -1,14 +1,14 @@
|
|||||||
{
|
{
|
||||||
"search": "검색",
|
"search": "검색",
|
||||||
"savedSearches": "저장된 검색들",
|
"savedSearches": "저장된 검색 조건",
|
||||||
"button": {
|
"button": {
|
||||||
"clear": "검색 초기화",
|
"clear": "검색어 지우기",
|
||||||
"save": "검색 저장",
|
"save": "검색 조건 저장",
|
||||||
"filterInformation": "필터 정보",
|
"filterInformation": "필터 정보",
|
||||||
"delete": "저장된 검색 삭제",
|
"delete": "저장된 검색 조건 삭제",
|
||||||
"filterActive": "필터 활성화됨"
|
"filterActive": "필터 적용됨"
|
||||||
},
|
},
|
||||||
"searchFor": "{{inputValue}} 검색",
|
"searchFor": "'{{inputValue}}' 검색",
|
||||||
"trackedObjectId": "추적 객체 ID",
|
"trackedObjectId": "추적 객체 ID",
|
||||||
"filter": {
|
"filter": {
|
||||||
"label": {
|
"label": {
|
||||||
@@ -22,7 +22,52 @@
|
|||||||
"before": "이전",
|
"before": "이전",
|
||||||
"after": "이후",
|
"after": "이후",
|
||||||
"min_score": "최소 점수",
|
"min_score": "최소 점수",
|
||||||
"max_score": "최대 점수"
|
"max_score": "최대 점수",
|
||||||
|
"min_speed": "최소 속도",
|
||||||
|
"max_speed": "최대 속도",
|
||||||
|
"recognized_license_plate": "인식된 차량 번호판",
|
||||||
|
"has_clip": "비디오 클립 있음",
|
||||||
|
"has_snapshot": "스냅샷 있음"
|
||||||
|
},
|
||||||
|
"searchType": {
|
||||||
|
"thumbnail": "썸네일",
|
||||||
|
"description": "설명"
|
||||||
|
},
|
||||||
|
"toast": {
|
||||||
|
"error": {
|
||||||
|
"beforeDateBeLaterAfter": "'이전(before)' 날짜는 '이후(after)' 날짜보다 뒤여야 합니다.",
|
||||||
|
"afterDatebeEarlierBefore": "'이후(after)' 날짜는 '이전(before)' 날짜보다 앞서야 합니다.",
|
||||||
|
"minScoreMustBeLessOrEqualMaxScore": "'최소 점수'는 '최대 점수'보다 작거나 같아야 합니다.",
|
||||||
|
"maxScoreMustBeGreaterOrEqualMinScore": "'최대 점수'는 '최소 점수'보다 크거나 같아야 합니다.",
|
||||||
|
"minSpeedMustBeLessOrEqualMaxSpeed": "'최소 속도'는 '최대 속도'보다 작거나 같아야 합니다.",
|
||||||
|
"maxSpeedMustBeGreaterOrEqualMinSpeed": "'최대 속도'는 '최소 속도'보다 크거나 같아야 합니다."
|
||||||
|
}
|
||||||
|
},
|
||||||
|
"tips": {
|
||||||
|
"title": "텍스트 필터 사용 방법",
|
||||||
|
"desc": {
|
||||||
|
"text": "필터를 사용하면 검색 결과를 좁힐 수 있습니다. 입력창에서 다음과 같이 사용하세요:",
|
||||||
|
"step1": "필터 키 이름 뒤에 콜론을 입력합니다 (예: \"cameras:\").",
|
||||||
|
"step2": "추천 목록에서 값을 선택하거나 직접 입력합니다.",
|
||||||
|
"step3": "필터 사이에 공백을 두어 여러 개의 필터를 연속으로 사용할 수 있습니다.",
|
||||||
|
"step4": "날짜 필터(before:, after:)는 {{DateFormat}} 형식을 사용합니다.",
|
||||||
|
"step5": "시간 범위 필터는 {{exampleTime}} 형식을 사용합니다.",
|
||||||
|
"step6": "필터 옆의 'x'를 클릭하여 필터를 제거합니다.",
|
||||||
|
"exampleLabel": "예시:"
|
||||||
|
}
|
||||||
|
},
|
||||||
|
"header": {
|
||||||
|
"currentFilterType": "필터 값",
|
||||||
|
"noFilters": "필터",
|
||||||
|
"activeFilters": "적용된 필터"
|
||||||
}
|
}
|
||||||
|
},
|
||||||
|
"similaritySearch": {
|
||||||
|
"title": "유사도 검색",
|
||||||
|
"active": "유사도 검색 활성 상태",
|
||||||
|
"clear": "유사도 검색 초기화"
|
||||||
|
},
|
||||||
|
"placeholder": {
|
||||||
|
"search": "검색…"
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|||||||
File diff suppressed because it is too large
Load Diff
@@ -1,198 +1,247 @@
|
|||||||
{
|
{
|
||||||
"documentTitle": {
|
"documentTitle": {
|
||||||
"cameras": "카메라 통계 - Frigate",
|
"cameras": "카메라 통계 - Frigate",
|
||||||
"storage": "저장소 통계 - Frigate",
|
"storage": "저장 공간 통계 - Frigate",
|
||||||
"general": "기본 통계 - Frigate",
|
"general": "일반 통계 - Frigate",
|
||||||
"enrichments": "데이터 보강 통계 - Frigate",
|
"enrichments": "확장 기능 통계 - Frigate",
|
||||||
"logs": {
|
"logs": {
|
||||||
"frigate": "Frigate 로그 -Frigate",
|
"frigate": "Frigate 로그 - Frigate",
|
||||||
"go2rtc": "Go2RTC 로그 - Frigate",
|
"go2rtc": "Go2RTC 로그 - Frigate",
|
||||||
"nginx": "Nginx 로그 - Frigate",
|
"nginx": "Nginx 로그 - Frigate",
|
||||||
"websocket": "메세지 로그 - Frigate"
|
"websocket": "메시지 로그 - Frigate"
|
||||||
}
|
}
|
||||||
},
|
},
|
||||||
"title": "시스템",
|
"title": "시스템",
|
||||||
"metrics": "시스템 통계",
|
"metrics": "시스템 지표",
|
||||||
"logs": {
|
"logs": {
|
||||||
"download": {
|
"download": {
|
||||||
"label": "다운로드 로그"
|
"label": "로그 다운로드"
|
||||||
},
|
},
|
||||||
"copy": {
|
"copy": {
|
||||||
"label": "클립보드에 복사하기",
|
"label": "클립보드에 복사",
|
||||||
"success": "클립보드에 로그가 복사되었습니다",
|
"success": "로그를 클립보드에 복사했습니다",
|
||||||
"error": "클립보드에 로그를 저장할 수 없습니다"
|
"error": "로그를 클립보드에 복사할 수 없습니다"
|
||||||
},
|
},
|
||||||
"type": {
|
"type": {
|
||||||
"label": "타입",
|
"label": "유형",
|
||||||
"timestamp": "시간 기록",
|
"timestamp": "타임스탬프",
|
||||||
"tag": "태그",
|
"tag": "태그",
|
||||||
"message": "메시지"
|
"message": "메시지"
|
||||||
},
|
},
|
||||||
"tips": "서버에서 로그 스트리밍 중",
|
"tips": "서버에서 로그가 실시간 스트리밍 중입니다",
|
||||||
"toast": {
|
"toast": {
|
||||||
"error": {
|
"error": {
|
||||||
"fetchingLogsFailed": "로그 가져오기 오류: {{errorMessage}}",
|
"fetchingLogsFailed": "로그 가져오기 오류: {{errorMessage}}",
|
||||||
"whileStreamingLogs": "스크리밍 로그 중 오류: {{errorMessage}}"
|
"whileStreamingLogs": "로그 스트리밍 중 오류 발생: {{errorMessage}}"
|
||||||
}
|
}
|
||||||
},
|
},
|
||||||
"websocket": {
|
"websocket": {
|
||||||
"label": "메세지",
|
"label": "메시지",
|
||||||
"pause": "일시중지",
|
"pause": "일시 중지",
|
||||||
"resume": "재개",
|
"resume": "다시 시작",
|
||||||
"clear": "비우기",
|
"clear": "지우기",
|
||||||
"filter": {
|
"filter": {
|
||||||
"all": "전체 항목",
|
"all": "모든 토픽",
|
||||||
"topics": "항목",
|
"topics": "토픽",
|
||||||
"events": "이벤트",
|
"events": "이벤트",
|
||||||
"reviews": "리뷰",
|
"reviews": "다시보기",
|
||||||
"classification": "분류",
|
"classification": "분류",
|
||||||
"face_recognition": "얼굴 인식",
|
"face_recognition": "얼굴 인식",
|
||||||
"lpr": "번호판 인식",
|
"lpr": "차량 번호판 인식 (LPR)",
|
||||||
"system": "시스템",
|
"system": "시스템",
|
||||||
"camera": "카메라",
|
"camera": "카메라",
|
||||||
"all_cameras": "모든 카메라",
|
"all_cameras": "모든 카메라",
|
||||||
"cameras_count_one": "{{count}} 카메라",
|
"cameras_count_one": "카메라 {{count}}대",
|
||||||
"cameras_count_other": "{{count}} 카메라"
|
"cameras_count_other": "카메라 {{count}}대",
|
||||||
|
"camera_activity": "카메라 활동"
|
||||||
},
|
},
|
||||||
"empty": "수신된 메시지 없음",
|
"empty": "수집된 메시지가 아직 없습니다",
|
||||||
"count_one": "{{count}} 메세지",
|
"count_one": "메시지 {{count}}개",
|
||||||
"count_other": "{{count}} 메세지"
|
"count_other": "메시지 {{count}}개",
|
||||||
|
"expanded": {
|
||||||
|
"payload": "페이로드"
|
||||||
|
}
|
||||||
}
|
}
|
||||||
},
|
},
|
||||||
"general": {
|
"general": {
|
||||||
"title": "기본",
|
"title": "일반",
|
||||||
"detector": {
|
"detector": {
|
||||||
"title": "감지기",
|
"title": "감지기",
|
||||||
"inferenceSpeed": "감지 추론 속도",
|
"inferenceSpeed": "감지기 추론 속도",
|
||||||
"temperature": "감지기 온도",
|
"temperature": "감지기 온도",
|
||||||
"cpuUsage": "감지기 CPU 사용률",
|
"cpuUsage": "감지기 CPU 사용량",
|
||||||
"memoryUsage": "감지기 메모리 사용률",
|
"memoryUsage": "감지기 메모리 사용량",
|
||||||
"cpuUsageInformation": "감지 모델로 데이터를 입력/출력하기 위한 전처리 과정에서 사용되는 CPU 사용량입니다. GPU나 가속기를 사용하는 경우에도 추론 자체의 사용량은 포함되지 않습니다."
|
"cpuUsageInformation": "감지 모델과의 입출력 데이터를 준비하는 데 사용된 CPU 리소스입니다. GPU 또는 전용 가속기를 사용하더라도 추론 연산 자체의 사용량은 측정되지 않습니다."
|
||||||
},
|
},
|
||||||
"hardwareInfo": {
|
"hardwareInfo": {
|
||||||
"title": "하드웨어 정보",
|
"title": "하드웨어 정보",
|
||||||
"gpuUsage": "GPU 사용률",
|
"gpuUsage": "GPU 사용량",
|
||||||
"gpuMemory": "GPU 메모리",
|
"gpuMemory": "GPU 메모리",
|
||||||
"gpuEncoder": "GPU 인코더",
|
"gpuEncoder": "GPU 인코더",
|
||||||
"gpuDecoder": "GPU 디코더",
|
"gpuDecoder": "GPU 디코더",
|
||||||
"gpuInfo": {
|
"gpuInfo": {
|
||||||
"vainfoOutput": {
|
"vainfoOutput": {
|
||||||
"title": "Vainfo 출력",
|
"title": "Vainfo 출력 결과",
|
||||||
"processOutput": "프로세스 출력:",
|
"processOutput": "프로세스 출력:",
|
||||||
"processError": "프로세스 오류:",
|
"processError": "프로세스 오류:",
|
||||||
"returnCode": "리턴 코드:{{code}}"
|
"returnCode": "반환 코드: {{code}}"
|
||||||
},
|
},
|
||||||
"nvidiaSMIOutput": {
|
"nvidiaSMIOutput": {
|
||||||
"title": "Nvidia SMI 출력",
|
"title": "Nvidia SMI 출력 결과",
|
||||||
"name": "이름:{{name}}",
|
"name": "이름: {{name}}",
|
||||||
"driver": "드라이버:{{driver}}",
|
"driver": "드라이버: {{driver}}",
|
||||||
"cudaComputerCapability": "CUDA Compute Capability:{{cuda_compute}}",
|
"cudaComputerCapability": "CUDA 연산 능력: {{cuda_compute}}",
|
||||||
"vbios": "VBios Info: {{vbios}}"
|
"vbios": "VBIOS 정보: {{vbios}}"
|
||||||
},
|
},
|
||||||
"copyInfo": {
|
"copyInfo": {
|
||||||
"label": "GPU 정보 복사"
|
"label": "GPU 정보 복사"
|
||||||
},
|
},
|
||||||
"toast": {
|
"toast": {
|
||||||
"success": "GPU 정보가 클립보드에 복사되었습니다"
|
"success": "GPU 정보를 클립보드에 복사했습니다"
|
||||||
},
|
},
|
||||||
"closeInfo": {
|
"closeInfo": {
|
||||||
"label": "GPU 정보 닫기"
|
"label": "GPU 정보 닫기"
|
||||||
}
|
}
|
||||||
},
|
},
|
||||||
"npuUsage": "NPU 사용률",
|
"npuUsage": "NPU 사용량",
|
||||||
"npuMemory": "NPU 메모리"
|
"npuMemory": "NPU 메모리",
|
||||||
|
"gpuCompute": "GPU 연산 / 인코딩",
|
||||||
|
"gpuTemperature": "GPU 온도",
|
||||||
|
"npuTemperature": "NPU 온도"
|
||||||
},
|
},
|
||||||
"otherProcesses": {
|
"otherProcesses": {
|
||||||
"title": "다른 프로세스들",
|
"title": "기타 프로세스",
|
||||||
"processCpuUsage": "사용중인 CPU 사용률",
|
"processCpuUsage": "프로세스 CPU 사용량",
|
||||||
"processMemoryUsage": "사용중인 메모리 사용률"
|
"processMemoryUsage": "프로세스 메모리 사용량",
|
||||||
|
"series": {
|
||||||
|
"go2rtc": "go2rtc",
|
||||||
|
"recording": "녹화",
|
||||||
|
"review_segment": "다시보기 세그먼트",
|
||||||
|
"embeddings": "임베딩",
|
||||||
|
"audio_detector": "오디오 감지기"
|
||||||
|
}
|
||||||
}
|
}
|
||||||
},
|
},
|
||||||
"storage": {
|
"storage": {
|
||||||
"title": "스토리지",
|
"title": "저장 공간",
|
||||||
"overview": "전체 현황",
|
"overview": "개요",
|
||||||
"recordings": {
|
"recordings": {
|
||||||
"title": "녹화",
|
"title": "녹화 파일",
|
||||||
"tips": "이 값은 Frigate 데이터베이스의 녹화 영상이 사용 중인 전체 저장 공간입니다. Frigate는 디스크 내 다른 파일들의 저장 공간은 추적하지 않습니다.",
|
"tips": "이 수치는 Frigate 데이터베이스에 기록된 녹화 파일이 사용하는 총 저장 공간을 나타냅니다. Frigate은 디스크 상의 모든 파일에 대한 사용량을 추적하지는 않습니다.",
|
||||||
"earliestRecording": "가장 오래된 녹화 영상:"
|
"earliestRecording": "가장 오래된 녹화 시점:"
|
||||||
},
|
},
|
||||||
"cameraStorage": {
|
"cameraStorage": {
|
||||||
"title": "카메라 저장소",
|
"title": "카메라별 저장 공간",
|
||||||
"camera": "카메라",
|
"camera": "카메라",
|
||||||
"unusedStorageInformation": "미사용 저장소 정보",
|
"unusedStorageInformation": "여유 공간 정보",
|
||||||
"storageUsed": "용량",
|
"storageUsed": "저장 공간",
|
||||||
"percentageOfTotalUsed": "전체 대비 비율",
|
"percentageOfTotalUsed": "전체 대비 사용 비율",
|
||||||
"bandwidth": "대역폭",
|
"bandwidth": "대역폭",
|
||||||
"unused": {
|
"unused": {
|
||||||
"title": "미사용",
|
"title": "여유 공간",
|
||||||
"tips": "드라이브에 Frigate 녹화 영상 외에 다른 파일이 저장되어 있는 경우, 이 값은 Frigate에서 실제 사용 가능한 여유 공간을 정확히 나타내지 않을 수 있습니다. Frigate는 녹화 영상 외의 저장 공간 사용량을 추적하지 않습니다."
|
"tips": "드라이브에 Frigate 녹화본 외에 다른 파일이 저장되어 있는 경우 이 수치가 Frigate에서 사용 가능한 실제 여유 공간과 정확히 일치하지 않을 수 있습니다. Frigate은 자체 녹화본 외부의 저장 공간 사용량을 추적하지 않습니다."
|
||||||
}
|
}
|
||||||
},
|
},
|
||||||
"shm": {
|
"shm": {
|
||||||
"title": "SHM (공유 메모리) 할당량",
|
"title": "공유 메모리 (SHM) 할당",
|
||||||
"warning": "현재 SHM 사이즈가 {{total}}MB로 너무 적습니다. 최소 {{min_shm}}MB 이상 올려주세요."
|
"warning": "현재 공유 메모리 크기({{total}}MB)가 너무 작습니다. 최소 {{min_shm}}MB 이상으로 늘려주세요.",
|
||||||
|
"frameLifetime": {
|
||||||
|
"title": "프레임 유지 시간",
|
||||||
|
"description": "각 카메라는 공유 메모리에 {{frames}}개의 프레임 슬롯을 가집니다. 가장 빠른 카메라의 프레임 속도를 기준으로, 각 프레임은 덮어쓰기 전까지 약 {{lifetime}}초 동안 유지됩니다."
|
||||||
|
}
|
||||||
}
|
}
|
||||||
},
|
},
|
||||||
"cameras": {
|
"cameras": {
|
||||||
"title": "카메라",
|
"title": "카메라",
|
||||||
"overview": "전체 현황",
|
"overview": "개요",
|
||||||
"info": {
|
"info": {
|
||||||
"aspectRatio": "종횡비",
|
"aspectRatio": "화면비",
|
||||||
"fetching": "카메라 데이터 수집 중",
|
"fetching": "카메라 데이터 가져오는 중",
|
||||||
"stream": "스트림 {{idx}}",
|
"stream": "스트림 {{idx}}",
|
||||||
"streamDataFromFFPROBE": "스트림 데이터는 <code>ffprobe</code>에서 받습니다.",
|
"streamDataFromFFPROBE": "스트림 데이터는 <code>ffprobe</code>를 통해 가져옵니다.",
|
||||||
"video": "비디오:",
|
"video": "비디오:",
|
||||||
"codec": "코덱:",
|
"codec": "코덱:",
|
||||||
"resolution": "해상도:",
|
"resolution": "해상도:",
|
||||||
"fps": "FPS:",
|
"fps": "FPS:",
|
||||||
"unknown": "알 수 없음",
|
"unknown": "알 수 없음",
|
||||||
"audio": "오디오:",
|
"audio": "오디오:",
|
||||||
"error": "오류:{{error}}",
|
"error": "오류: {{error}}",
|
||||||
"cameraProbeInfo": "{{camera}} 카메라 장치 정보",
|
"cameraProbeInfo": "{{camera}} 카메라 탐색 정보",
|
||||||
"tips": {
|
"tips": {
|
||||||
"title": "카메라 장치 정보"
|
"title": "카메라 탐색 정보"
|
||||||
|
},
|
||||||
|
"keyframes": {
|
||||||
|
"title": "키프레임 분석",
|
||||||
|
"analyzing": "키프레임 분석 중... {{seconds}}초 남음",
|
||||||
|
"stillAnalyzing": "키프레임을 계속 분석하고 있습니다...",
|
||||||
|
"recordStream": "녹화 스트림:",
|
||||||
|
"keyframeCount": "감지된 키프레임 수:",
|
||||||
|
"observedDuration": "측정 기간:",
|
||||||
|
"gap": "키프레임 간격 (최소 / 평균 / 최대):",
|
||||||
|
"segmentLength": "녹화 세그먼트 길이:",
|
||||||
|
"ok": "키프레임이 약 {{seconds}}초마다 배치되어 녹화 및 재생에 적합합니다.",
|
||||||
|
"warning": "키프레임 간격이 불규칙하거나 멉니다(최대 간격 약 {{seconds}}초). 스마트 코덱(H.264+/H.265+)일 가능성이 높으며 권장되지 않습니다.",
|
||||||
|
"error": "키프레임 간격(약 {{seconds}}초)이 녹화 세그먼트 길이({{segmentTime}}초)를 초과합니다. 일부 세그먼트에 키프레임이 없어 재생에 문제가 발생할 수 있습니다. 카메라에서 스마트/+ 코덱을 끄거나 키프레임 간격을 줄이세요.",
|
||||||
|
"unknown": "키프레임 간격을 확인할 수 없습니다.",
|
||||||
|
"recordDisabled": "이 카메라의 녹화 기능이 비활성화되어 있습니다."
|
||||||
}
|
}
|
||||||
},
|
},
|
||||||
"framesAndDetections": "프레임 / 감지 (Detections)",
|
"framesAndDetections": "프레임 / 감지",
|
||||||
"label": {
|
"label": {
|
||||||
"camera": "카메라",
|
"camera": "카메라",
|
||||||
"detect": "감지",
|
"detect": "감지",
|
||||||
"skipped": "건너뜀",
|
"skipped": "건너뜀",
|
||||||
"ffmpeg": "FFmpeg",
|
"ffmpeg": "FFmpeg",
|
||||||
"capture": "캡쳐",
|
"capture": "캡처",
|
||||||
"overallFramesPerSecond": "전체 초당 프레임",
|
"overallFramesPerSecond": "전체 초당 프레임 수",
|
||||||
"overallDetectionsPerSecond": "전체 초당 감지",
|
"overallDetectionsPerSecond": "전체 초당 감지 수",
|
||||||
"overallSkippedDetectionsPerSecond": "전체 초당 건너뛴 감지",
|
"overallSkippedDetectionsPerSecond": "전체 초당 건너뛴 감지 수",
|
||||||
"cameraFfmpeg": "{{camName}} FFmpeg",
|
"cameraFfmpeg": "{{camName}} FFmpeg",
|
||||||
"cameraCapture": "{{camName}} 캡쳐",
|
"cameraCapture": "{{camName}} 캡처",
|
||||||
"cameraDetect": "{{camName}} 감지",
|
"cameraDetect": "{{camName}} 감지",
|
||||||
"cameraFramesPerSecond": "{{camName}} 초당 프레임",
|
"cameraFramesPerSecond": "{{camName}} 초당 프레임 수",
|
||||||
"cameraDetectionsPerSecond": "{{camName}} 초당 감지",
|
"cameraDetectionsPerSecond": "{{camName}} 초당 감지 수",
|
||||||
"cameraSkippedDetectionsPerSecond": "{{camName}} 초당 건너뛴 감지"
|
"cameraSkippedDetectionsPerSecond": "{{camName}} 초당 건너뛴 감지 수",
|
||||||
|
"cameraGpu": "{{camName}} GPU"
|
||||||
},
|
},
|
||||||
"toast": {
|
"toast": {
|
||||||
"success": {
|
"success": {
|
||||||
"copyToClipboard": "데이터 정보가 클립보드에 복사되었습니다."
|
"copyToClipboard": "탐색 데이터를 클립보드에 복사했습니다."
|
||||||
},
|
},
|
||||||
"error": {
|
"error": {
|
||||||
"unableToProbeCamera": "카메라 정보 알 수 없음: {{errorMessage}}"
|
"unableToProbeCamera": "카메라를 탐색할 수 없음: {{errorMessage}}"
|
||||||
}
|
}
|
||||||
|
},
|
||||||
|
"noCameras": {
|
||||||
|
"title": "발견된 카메라 없음"
|
||||||
|
},
|
||||||
|
"connectionQuality": {
|
||||||
|
"title": "연결 상태",
|
||||||
|
"excellent": "매우 좋음",
|
||||||
|
"fair": "보통",
|
||||||
|
"poor": "불안정",
|
||||||
|
"unusable": "사용 불가",
|
||||||
|
"fps": "FPS",
|
||||||
|
"expectedFps": "예상 FPS",
|
||||||
|
"reconnectsLastHour": "재연결 횟수 (최근 1시간)",
|
||||||
|
"stallsLastHour": "지연/끊김 횟수 (최근 1시간)"
|
||||||
}
|
}
|
||||||
},
|
},
|
||||||
"lastRefreshed": "마지막 새로고침: ",
|
"lastRefreshed": "마지막 갱신: ",
|
||||||
"stats": {
|
"stats": {
|
||||||
"ffmpegHighCpuUsage": "{{camera}} FFmpeg CPU 사용량이 높습니다 ({{ffmpegAvg}}%)",
|
"ffmpegHighCpuUsage": "{{camera}} 카메라의 FFmpeg CPU 사용량이 높습니다 ({{ffmpegAvg}}%)",
|
||||||
"detectHighCpuUsage": "{{camera}} 감지 CPU 사용량이 높습니다 ({{detectAvg}}%)",
|
"detectHighCpuUsage": "{{camera}} 카메라의 감지(detect) CPU 사용량이 높습니다 ({{detectAvg}}%)",
|
||||||
"healthy": "시스템 정상",
|
"healthy": "시스템 상태가 양호합니다",
|
||||||
"reindexingEmbeddings": "검색 데이터 재정리 중 ({{processed}}% 완료)",
|
"reindexingEmbeddings": "임베딩 재색인 중 ({{processed}}% 완료)",
|
||||||
"cameraIsOffline": "{{camera}} 오프라인입니다",
|
"cameraIsOffline": "{{camera}} 카메라가 오프라인 상태입니다",
|
||||||
"detectIsSlow": "{{detect}} (이/가) 느립니다 ({{speed}} ms)",
|
"detectIsSlow": "{{detect}} 처리가 느립니다 ({{speed}}ms)",
|
||||||
"detectIsVerySlow": "{{detect}} (이/가) 매우 느립니다 ({{speed}} ms)",
|
"detectIsVerySlow": "{{detect}} 처리가 매우 느립니다 ({{speed}}ms)",
|
||||||
"shmTooLow": "/dev/shm 할당량을 ({{total}} MB) 최소 {{min}} MB 이상 증가시켜야합니다."
|
"shmTooLow": "/dev/shm 할당량({{total}}MB)을 최소 {{min}}MB 이상으로 늘려야 합니다.",
|
||||||
|
"debugReplayActive": "디버그 리플레이 세션이 실행 중입니다"
|
||||||
},
|
},
|
||||||
"enrichments": {
|
"enrichments": {
|
||||||
"title": "데이터 보강",
|
"title": "확장 기능",
|
||||||
"infPerSecond": "초당 추론 속도",
|
"infPerSecond": "초당 추론 횟수",
|
||||||
"embeddings": {
|
"embeddings": {
|
||||||
"image_embedding": "이미지 임베딩",
|
"image_embedding": "이미지 임베딩",
|
||||||
"text_embedding": "텍스트 임베딩",
|
"text_embedding": "텍스트 임베딩",
|
||||||
@@ -203,8 +252,18 @@
|
|||||||
"face_recognition_speed": "얼굴 인식 속도",
|
"face_recognition_speed": "얼굴 인식 속도",
|
||||||
"plate_recognition_speed": "번호판 인식 속도",
|
"plate_recognition_speed": "번호판 인식 속도",
|
||||||
"text_embedding_speed": "텍스트 임베딩 속도",
|
"text_embedding_speed": "텍스트 임베딩 속도",
|
||||||
"yolov9_plate_detection_speed": "YOLOv9 플레이트 감지 속도",
|
"yolov9_plate_detection_speed": "YOLOv9 번호판 감지 속도",
|
||||||
"yolov9_plate_detection": "YOLOv9 플레이트 감지"
|
"yolov9_plate_detection": "YOLOv9 번호판 감지",
|
||||||
}
|
"review_description": "다시보기 설명",
|
||||||
|
"review_description_speed": "다시보기 설명 생성 속도",
|
||||||
|
"review_description_events_per_second": "다시보기 설명",
|
||||||
|
"object_description": "객체 설명",
|
||||||
|
"object_description_speed": "객체 설명 생성 속도",
|
||||||
|
"object_description_events_per_second": "객체 설명",
|
||||||
|
"classification": "{{name}} 분류",
|
||||||
|
"classification_speed": "{{name}} 분류 속도",
|
||||||
|
"classification_events_per_second": "{{name}} 초당 분류 이벤트 수"
|
||||||
|
},
|
||||||
|
"averageInf": "평균 추론 시간"
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -496,5 +496,6 @@
|
|||||||
"sidetone": "Retorno de Voz",
|
"sidetone": "Retorno de Voz",
|
||||||
"cacophony": "Cacofonia",
|
"cacophony": "Cacofonia",
|
||||||
"throbbing": "Latejante",
|
"throbbing": "Latejante",
|
||||||
"vibration": "Vibração"
|
"vibration": "Vibração",
|
||||||
|
"chird": "Vista Global"
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -82,10 +82,20 @@
|
|||||||
},
|
},
|
||||||
"case": {
|
"case": {
|
||||||
"newCaseOption": "Criar um novo caso",
|
"newCaseOption": "Criar um novo caso",
|
||||||
"newCaseNamePlaceholder": "Nome do novo caso"
|
"newCaseNamePlaceholder": "Nome do novo caso",
|
||||||
|
"newCaseDescriptionPlaceholder": "Descrição do caso",
|
||||||
|
"label": "Caso",
|
||||||
|
"nonAdminHelp": "Será criado um novo caso para estas exportações.",
|
||||||
|
"placeholder": "Selecionar um caso"
|
||||||
},
|
},
|
||||||
"multiCamera": {
|
"multiCamera": {
|
||||||
"selectFromTimeline": "Selecionar da Timeline"
|
"selectFromTimeline": "Selecionar da Timeline",
|
||||||
|
"timeRange": "Intervalo de tempo"
|
||||||
|
},
|
||||||
|
"queueing": "A colocar na fila de exportação...",
|
||||||
|
"tabs": {
|
||||||
|
"export": "Câmara única",
|
||||||
|
"multiCamera": "Múltiplas câmaras"
|
||||||
}
|
}
|
||||||
},
|
},
|
||||||
"streaming": {
|
"streaming": {
|
||||||
|
|||||||
@@ -20,12 +20,12 @@
|
|||||||
"description": "Aktivera eller inaktivera ljudbaserad detektering för den här kameran."
|
"description": "Aktivera eller inaktivera ljudbaserad detektering för den här kameran."
|
||||||
},
|
},
|
||||||
"max_not_heard": {
|
"max_not_heard": {
|
||||||
"description": "Antalet sekunder utan den konfigurerade ljudtypen innan en ljudbaserad händelse avslutas.",
|
"description": "Antal sekunder utan den konfigurerade ljudtypen innan en ljudbaserad händelse avslutas.",
|
||||||
"label": "Avsluta timeout"
|
"label": "Tidsgräns för avslut"
|
||||||
},
|
},
|
||||||
"min_volume": {
|
"min_volume": {
|
||||||
"label": "Minsta ljudvolym",
|
"label": "Minsta ljudvolym",
|
||||||
"description": "Lägsta RMS-volymtröskel som krävs för att köra ljuddetektering; lägre värden ökar känsligheten (t.ex. 200 hög, 500 medel, 1000 låg)."
|
"description": "Lägsta RMS-volymtröskel som krävs för att köra ljuddetektering; lägre värden ökar känsligheten (t.ex. 200 = hög, 500 = medel, 1000 = låg)."
|
||||||
},
|
},
|
||||||
"listen": {
|
"listen": {
|
||||||
"label": "Ljudtyper",
|
"label": "Ljudtyper",
|
||||||
@@ -33,14 +33,14 @@
|
|||||||
},
|
},
|
||||||
"filters": {
|
"filters": {
|
||||||
"label": "Ljudfilter",
|
"label": "Ljudfilter",
|
||||||
"description": "Filter för ljudtyper. Exempelvis konfidens för att minska falska positiva resultat.",
|
"description": "Filter för ljudtyper, till exempel konfidensgränser för att minska falska positiva detekteringar.",
|
||||||
"threshold": {
|
"threshold": {
|
||||||
"label": "Minsta konfidens för ljud",
|
"label": "Minsta ljudkonfidens",
|
||||||
"description": "Lägsta konfidenströskel för att ljudhändelsen ska räknas."
|
"description": "Lägsta konfidensgräns för att en ljudhändelse ska räknas."
|
||||||
}
|
}
|
||||||
},
|
},
|
||||||
"enabled_in_config": {
|
"enabled_in_config": {
|
||||||
"label": "Ursprunglig ljudkonfiguration",
|
"label": "Ursprunglig inställning för ljuddetektering",
|
||||||
"description": "Anger om ljuddetektering ursprungligen var aktiverad i den statiska konfigurationsfilen."
|
"description": "Anger om ljuddetektering ursprungligen var aktiverad i den statiska konfigurationsfilen."
|
||||||
},
|
},
|
||||||
"num_threads": {
|
"num_threads": {
|
||||||
@@ -49,8 +49,8 @@
|
|||||||
}
|
}
|
||||||
},
|
},
|
||||||
"audio_transcription": {
|
"audio_transcription": {
|
||||||
"label": "Ljudöversättning",
|
"label": "Ljudtranskribering",
|
||||||
"description": "Inställningar för ljudtranskribering som används för händelser och liveundertexter.",
|
"description": "Inställningar för transkribering av liveljud och tal som används för händelser och liveundertexter.",
|
||||||
"enabled": {
|
"enabled": {
|
||||||
"label": "Aktivera transkribrering",
|
"label": "Aktivera transkribrering",
|
||||||
"description": "Aktivera eller inaktivera manuellt utlöst transkription av ljudhändelser."
|
"description": "Aktivera eller inaktivera manuellt utlöst transkription av ljudhändelser."
|
||||||
@@ -59,84 +59,84 @@
|
|||||||
"label": "Ursprungligt transkriptionstillstånd"
|
"label": "Ursprungligt transkriptionstillstånd"
|
||||||
},
|
},
|
||||||
"live_enabled": {
|
"live_enabled": {
|
||||||
"label": "Live-transkription",
|
"label": "Livetranskribering",
|
||||||
"description": "Aktivera direktuppspelning av ljud när det tas emot."
|
"description": "Aktivera transkribering av liveljud medan ljudet tas emot."
|
||||||
}
|
}
|
||||||
},
|
},
|
||||||
"birdseye": {
|
"birdseye": {
|
||||||
"label": "Fågelöga",
|
"label": "Fågelperspektiv",
|
||||||
"description": "Inställningar för Fågelöga-sammansattvyn som sammanställer flera kameraflöden i en enda layout.",
|
"description": "Inställningar för den sammansatta fågelperspektivvyn, som visar flera kameraflöden i en enda layout.",
|
||||||
"enabled": {
|
"enabled": {
|
||||||
"label": "Aktivera Fågelöga",
|
"label": "Aktivera fågelperspektiv",
|
||||||
"description": "Aktivera eller inaktivera Fågelöga-vyn."
|
"description": "Aktivera eller inaktivera fågelperspektivvyn."
|
||||||
},
|
},
|
||||||
"mode": {
|
"mode": {
|
||||||
"label": "Spårningsläge",
|
"label": "Visningsläge",
|
||||||
"description": "Läge för att inkludera kameror i Fågelöga: 'objekt', 'rörelse' eller 'kontinuerlig'."
|
"description": "Läge för vilka kameror som ska tas med i fågelperspektivet: \"objects\" (objekt), \"motion\" (rörelse) eller \"continuous\" (kontinuerligt)."
|
||||||
},
|
},
|
||||||
"order": {
|
"order": {
|
||||||
"label": "Position",
|
"label": "Position",
|
||||||
"description": "Numerisk position som styr kamerans ordning i Fågelöga-layouten."
|
"description": "Numerisk position som styr kamerans ordning i fågelperspektivlayouten."
|
||||||
}
|
}
|
||||||
},
|
},
|
||||||
"detect": {
|
"detect": {
|
||||||
"label": "Objektdetektering",
|
"label": "Objektdetektering",
|
||||||
"description": "Inställningar för rollen detektering/identifiering som används för att köra objektdetektering och initiera spårare.",
|
"description": "Inställningar för detekteringsrollen som används för att köra objektdetektering och initiera spårning.",
|
||||||
"enabled": {
|
"enabled": {
|
||||||
"label": "Aktivera objektdetektering",
|
"label": "Aktivera objektdetektering",
|
||||||
"description": "Aktivera eller inaktivera objektdetektering för den här kameran."
|
"description": "Aktivera eller inaktivera objektdetektering för den här kameran."
|
||||||
},
|
},
|
||||||
"height": {
|
"height": {
|
||||||
"label": "Detektera höjd",
|
"label": "Detekteringshöjd",
|
||||||
"description": "Höjd (pixlar) på bildrutor som används för detekteringsströmmen; lämna tomt för att använda den ursprungliga strömupplösningen."
|
"description": "Höjd i pixlar på bildrutorna i detekteringsströmmen; lämna tomt för att använda strömmens ursprungliga upplösning."
|
||||||
},
|
},
|
||||||
"width": {
|
"width": {
|
||||||
"label": "Detektera bredd",
|
"label": "Detekteringsbredd",
|
||||||
"description": "Bredd (pixlar) på bildrutor som används för detekteringsströmmen; lämna tomt för att använda den ursprungliga strömupplösningen."
|
"description": "Bredd i pixlar på bildrutorna i detekteringsströmmen; lämna tomt för att använda strömmens ursprungliga upplösning."
|
||||||
},
|
},
|
||||||
"fps": {
|
"fps": {
|
||||||
"label": "Identifiera FPS",
|
"label": "Detekterings-FPS",
|
||||||
"description": "Önskat antal bildrutor per sekund att köra detektering på; lägre värden minskar CPU-användningen (rekommenderat värde är 5, sätt endast högre - högst 10 - om man spårar objekt som rör sig extremt snabbt)."
|
"description": "Önskat antal bildrutor per sekund för detektering; lägre värden minskar CPU-användningen. Rekommenderat värde är 5. Använd endast ett högre värde, högst 10, om mycket snabbrörliga objekt ska spåras."
|
||||||
},
|
},
|
||||||
"min_initialized": {
|
"min_initialized": {
|
||||||
"label": "Minsta initialiseringsramar",
|
"label": "Minsta antal initialiseringsbildrutor",
|
||||||
"description": "Antal på varandra följande detekteringsträffar som krävs innan ett spårat objekt skapas. Öka för att minska falska initialiseringar. Standardvärdet är fps dividerat med 2."
|
"description": "Antal på varandra följande detekteringsträffar som krävs innan ett spårat objekt skapas. Öka värdet för att minska falska initialiseringar. Standardvärdet är FPS dividerat med 2."
|
||||||
},
|
},
|
||||||
"max_disappeared": {
|
"max_disappeared": {
|
||||||
"label": "Maximalt antal försvunna bildrutor",
|
"label": "Maximalt antal försvunna bildrutor",
|
||||||
"description": "Antal bildrutor utan detektering innan ett spårat objekt anses vara borta."
|
"description": "Antal bildrutor utan detektering innan ett spårat objekt anses vara borta."
|
||||||
},
|
},
|
||||||
"stationary": {
|
"stationary": {
|
||||||
"label": "Konfiguration av stationära objekt",
|
"label": "Inställningar för stationära objekt",
|
||||||
"description": "Inställningar för att upptäcka och hantera objekt som förblir stillastående under en viss tidsperiod.",
|
"description": "Inställningar för att identifiera och hantera objekt som förblir stillastående under en viss tid.",
|
||||||
"interval": {
|
"interval": {
|
||||||
"label": "Stationärt intervall",
|
"label": "Intervall för stationära objekt",
|
||||||
"description": "Hur ofta (i bildrutor) en detektionskontroll ska köras för att bekräfta ett stillastående objekt."
|
"description": "Hur ofta, räknat i bildrutor, en detekteringskontroll ska köras för att bekräfta att ett stationärt objekt fortfarande finns kvar."
|
||||||
},
|
},
|
||||||
"threshold": {
|
"threshold": {
|
||||||
"label": "Stationär tröskel",
|
"label": "Tröskel för stationärt objekt",
|
||||||
"description": "Antal bildrutor utan positionsändring som krävs för att markera ett objekt som stillastående."
|
"description": "Antal bildrutor utan positionsändring som krävs för att markera ett objekt som stillastående."
|
||||||
},
|
},
|
||||||
"max_frames": {
|
"max_frames": {
|
||||||
"label": "Max antal bildrutor",
|
"label": "Maximalt antal bildrutor",
|
||||||
"description": "Begränsar hur länge stationära objekt spåras innan de kasseras.",
|
"description": "Begränsar hur länge stationära objekt spåras innan spårningen avslutas.",
|
||||||
"default": {
|
"default": {
|
||||||
"label": "Standardmaximum för bildrutor",
|
"label": "Standardgräns för bildrutor",
|
||||||
"description": "Standardmaximalt antal bildrutor för att spåra ett stillastående objekt innan det stoppas."
|
"description": "Standardvärde för det maximala antal bildrutor som ett stationärt objekt ska spåras."
|
||||||
},
|
},
|
||||||
"objects": {
|
"objects": {
|
||||||
"label": "Objekt max bildrutor",
|
"label": "Maximalt antal bildrutor per objekt",
|
||||||
"description": "Åsidosättningar per objekt för maximalt antal bildrutor för att spåra stationära objekt."
|
"description": "Åsidosättningar per objekt för det maximala antal bildrutor som stationära objekt ska spåras."
|
||||||
}
|
}
|
||||||
},
|
},
|
||||||
"classifier": {
|
"classifier": {
|
||||||
"label": "Aktivera visuell klassificerare",
|
"label": "Aktivera visuell klassificerare",
|
||||||
"description": "Använd en visuell klassificerare för att upptäcka verkligt stationära objekt även när avgränsningsrutor vibrerar."
|
"description": "Använd en visuell klassificerare för att identifiera verkligt stationära objekt även när avgränsningsrutorna rör sig något."
|
||||||
}
|
}
|
||||||
},
|
},
|
||||||
"annotation_offset": {
|
"annotation_offset": {
|
||||||
"label": "Annoteringsförskjutning",
|
"label": "Annoteringsförskjutning",
|
||||||
"description": "Millisekunder för att identifiera annoteringar för att bättre justera tidslinjens avgränsningsrutor med inspelningar; kan vara positiva eller negativa."
|
"description": "Förskjutning i millisekunder för detekteringsannoteringar, så att tidslinjens avgränsningsrutor bättre linjerar med inspelningarna. Värdet kan vara positivt eller negativt."
|
||||||
}
|
}
|
||||||
},
|
},
|
||||||
"face_recognition": {
|
"face_recognition": {
|
||||||
@@ -147,8 +147,8 @@
|
|||||||
"description": "Aktivera eller inaktivera ansiktsigenkänning."
|
"description": "Aktivera eller inaktivera ansiktsigenkänning."
|
||||||
},
|
},
|
||||||
"min_area": {
|
"min_area": {
|
||||||
"label": "Minsta ansiktsyta",
|
"label": "Minsta ansiktsarea",
|
||||||
"description": "Minsta area (pixlar) av en detekterad ansiktsruta som krävs för att försöka igenkänna."
|
"description": "Minsta area i pixlar för en detekterad ansiktsruta innan igenkänning försöks."
|
||||||
}
|
}
|
||||||
},
|
},
|
||||||
"ffmpeg": {
|
"ffmpeg": {
|
||||||
@@ -159,96 +159,96 @@
|
|||||||
"description": "Sökväg till FFmpeg-binärfilen som ska användas eller ett versionsalias (\"7.0\" eller \"8.0\")."
|
"description": "Sökväg till FFmpeg-binärfilen som ska användas eller ett versionsalias (\"7.0\" eller \"8.0\")."
|
||||||
},
|
},
|
||||||
"global_args": {
|
"global_args": {
|
||||||
"label": "FFmpeg globala argument",
|
"label": "Globala FFmpeg-argument",
|
||||||
"description": "Globala argument skickades till FFmpeg-processer."
|
"description": "Globala argument som skickas till FFmpeg-processer."
|
||||||
},
|
},
|
||||||
"hwaccel_args": {
|
"hwaccel_args": {
|
||||||
"label": "Argument för hårdvaruacceleration",
|
"label": "Argument för maskinvaruacceleration",
|
||||||
"description": "Argument för hårdvaruacceleration för FFmpeg. Leverantörsspecifika förinställningar rekommenderas."
|
"description": "Argument för maskinvaruacceleration i FFmpeg. Leverantörsspecifika förinställningar rekommenderas."
|
||||||
},
|
},
|
||||||
"input_args": {
|
"input_args": {
|
||||||
"label": "Inmatningsargument",
|
"label": "Indataargument",
|
||||||
"description": "Indataargument tillämpade på FFmpeg-indataströmmar."
|
"description": "Indataargument som tillämpas på FFmpeg-indataströmmar."
|
||||||
},
|
},
|
||||||
"output_args": {
|
"output_args": {
|
||||||
"label": "Utdataargument",
|
"label": "Utdataargument",
|
||||||
"description": "Standardutdataargument som används för olika FFmpeg-roller, till exempel detektera och spela in.",
|
"description": "Standardargument för utdata som används för olika FFmpeg-roller, till exempel detektering och inspelning.",
|
||||||
"detect": {
|
"detect": {
|
||||||
"label": "Identifiera utdataargument",
|
"label": "Utdataargument för detektering",
|
||||||
"description": "Standardutdataargument för att identifiera rollströmmar."
|
"description": "Standardargument för utdata i strömmar med detekteringsrollen."
|
||||||
},
|
},
|
||||||
"record": {
|
"record": {
|
||||||
"label": "Spela in utdataargument",
|
"label": "Utdataargument för inspelning",
|
||||||
"description": "Standardutdataargument för postrollströmmar."
|
"description": "Standardargument för utdata i strömmar med inspelningsrollen."
|
||||||
}
|
}
|
||||||
},
|
},
|
||||||
"retry_interval": {
|
"retry_interval": {
|
||||||
"label": "FFmpeg-återförsökstid",
|
"label": "Återförsöksintervall för FFmpeg",
|
||||||
"description": "Sekunder att vänta innan man försöker återansluta en kameraström efter ett misslyckande. Standardinställningen är 10."
|
"description": "Antal sekunder att vänta innan Frigate försöker återansluta en kameraström efter ett fel. Standardvärdet är 10."
|
||||||
},
|
},
|
||||||
"apple_compatibility": {
|
"apple_compatibility": {
|
||||||
"label": "Apple-kompatibilitet",
|
"label": "Apple-kompatibilitet",
|
||||||
"description": "Aktivera HEVC-taggning för bättre kompatibilitet med Apple-spelare vid inspelning av H.265."
|
"description": "Aktivera HEVC-taggning för bättre kompatibilitet med Apple-spelare vid inspelning av H.265."
|
||||||
},
|
},
|
||||||
"gpu": {
|
"gpu": {
|
||||||
"label": "GPU index",
|
"label": "GPU-index",
|
||||||
"description": "Standard-GPU-index som används för hårdvaruacceleration om tillgängligt."
|
"description": "Standard-GPU-index som används för maskinvaruacceleration när det är tillgängligt."
|
||||||
},
|
},
|
||||||
"inputs": {
|
"inputs": {
|
||||||
"label": "Kameraingångar",
|
"label": "Kameraindata",
|
||||||
"description": "Lista över definitioner av indataströmmar (sökvägar och roller) för den här kameran.",
|
"description": "Lista över definitioner av indataströmmar, med sökvägar och roller, för den här kameran.",
|
||||||
"path": {
|
"path": {
|
||||||
"label": "Inmatningsväg",
|
"label": "Indatasökväg",
|
||||||
"description": "URL eller sökväg för kameraingångsström."
|
"description": "URL eller sökväg till kamerans indataström."
|
||||||
},
|
},
|
||||||
"roles": {
|
"roles": {
|
||||||
"label": "Inmatningsroller",
|
"label": "Indataroller",
|
||||||
"description": "Roller för denna indataström."
|
"description": "Roller för denna indataström."
|
||||||
},
|
},
|
||||||
"global_args": {
|
"global_args": {
|
||||||
"label": "FFmpeg globala argument",
|
"label": "Globala FFmpeg-argument",
|
||||||
"description": "FFmpegs globala argument för denna indataström."
|
"description": "Globala FFmpeg-argument för den här indataströmmen."
|
||||||
},
|
},
|
||||||
"hwaccel_args": {
|
"hwaccel_args": {
|
||||||
"label": "Argument för hårdvaruacceleration",
|
"label": "Argument för maskinvaruacceleration",
|
||||||
"description": "Argument för hårdvaruacceleration för denna indataström."
|
"description": "Argument för maskinvaruacceleration för den här indataströmmen."
|
||||||
},
|
},
|
||||||
"input_args": {
|
"input_args": {
|
||||||
"label": "Inmatningsargument",
|
"label": "Indataargument",
|
||||||
"description": "Inmatningsargument specifika för den här strömmen."
|
"description": "Indataargument som är specifika för den här strömmen."
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
},
|
},
|
||||||
"live": {
|
"live": {
|
||||||
"label": "Liveuppspelning",
|
"label": "Livevisning",
|
||||||
"description": "Inställningar som används av webbgränssnittet för att styra val, upplösning och kvalitet för liveströmmar.",
|
"description": "Inställningar som används av webbgränssnittet för att styra val, upplösning och kvalitet för liveströmmar.",
|
||||||
"streams": {
|
"streams": {
|
||||||
"label": "Namn på livestreams",
|
"label": "Namn på liveströmmar",
|
||||||
"description": "Mappning av konfigurerade strömnamn till restream-/go2rtc-namn som används för liveuppspelning."
|
"description": "Mappning av konfigurerade strömnamn till namn för vidareströmning/go2rtc som används vid livevisning."
|
||||||
},
|
},
|
||||||
"height": {
|
"height": {
|
||||||
"label": "Live höjd",
|
"label": "Höjd för livevisning",
|
||||||
"description": "Höjd (pixlar) för att rendera jsmpeg-livestreamen i webbgränssnittet; måste vara <= detektera strömhöjd."
|
"description": "Höjd i pixlar för jsmpeg-liveströmmen i webbgränssnittet; får inte överstiga detekteringsströmmens höjd."
|
||||||
},
|
},
|
||||||
"quality": {
|
"quality": {
|
||||||
"label": "Livekvalitet",
|
"label": "Kvalitet för livevisning",
|
||||||
"description": "Kodningskvalitet för jsmpeg-strömmen (1 högsta, 31 lägsta)."
|
"description": "Kodningskvalitet för jsmpeg-strömmen (1 = högst, 31 = lägst)."
|
||||||
}
|
}
|
||||||
},
|
},
|
||||||
"lpr": {
|
"lpr": {
|
||||||
"label": "Registreringsskyltigenkänning",
|
"label": "Registreringsskyltsigenkänning",
|
||||||
"description": "Inställningar för registreringsskyltsigenkänning inklusive tröskelvärden för detektering, formatering och kända registreringsskyltar.",
|
"description": "Inställningar för registreringsskyltsigenkänning, inklusive tröskelvärden för detektering, formatering och kända registreringsskyltar.",
|
||||||
"enabled": {
|
"enabled": {
|
||||||
"label": "Aktivera LPR",
|
"label": "Aktivera LPR",
|
||||||
"description": "Aktivera eller inaktivera LPR på den här kameran."
|
"description": "Aktivera eller inaktivera LPR på den här kameran."
|
||||||
},
|
},
|
||||||
"expire_time": {
|
"expire_time": {
|
||||||
"label": "Upphöra sekunder",
|
"label": "Förfallotid",
|
||||||
"description": "Tid i sekunder efter att en osynlig platta från spåraren har löpt ut (endast för dedikerade LPR-kameror)."
|
"description": "Tid i sekunder innan en registreringsskylt som inte längre syns tas bort från spåraren (endast för dedikerade LPR-kameror)."
|
||||||
},
|
},
|
||||||
"min_area": {
|
"min_area": {
|
||||||
"label": "Minsta area på registreringskylt",
|
"label": "Minsta registreringsskyltsarea",
|
||||||
"description": "Minsta yta för registreringsskylt (pixlar) som krävs för att försöka utföra igenkänning."
|
"description": "Minsta area i pixlar för en registreringsskylt innan igenkänning försöks."
|
||||||
},
|
},
|
||||||
"enhancement": {
|
"enhancement": {
|
||||||
"label": "Förbättringsnivå",
|
"label": "Förbättringsnivå",
|
||||||
@@ -264,46 +264,46 @@
|
|||||||
},
|
},
|
||||||
"threshold": {
|
"threshold": {
|
||||||
"label": "Rörelsetröskel",
|
"label": "Rörelsetröskel",
|
||||||
"description": "Pixelskillnadströskeln som används av rörelsedetektorn; högre värden minskar känsligheten (intervall 1–255)."
|
"description": "Tröskel för pixelskillnad som används av rörelsedetektorn; högre värden minskar känsligheten (intervall 1–255)."
|
||||||
},
|
},
|
||||||
"lightning_threshold": {
|
"lightning_threshold": {
|
||||||
"label": "Blixtgränsvärde",
|
"label": "Blixttröskel",
|
||||||
"description": "Tröskelvärde för att detektera och ignorera korta ljustoppar (lägre är känsligare, värden mellan 0,3 och 1,0). Detta förhindrar inte rörelsedetektering helt; det gör bara att detektorn slutar analysera ytterligare bildrutor när tröskelvärdet överskrids. Rörelsebaserade inspelningar skapas fortfarande under dessa händelser."
|
"description": "Andel av bilden som används för att identifiera blixtar eller andra stora bildförändringar där rörelsedetekteringen behöver kalibreras om och rörelsekontrollerna stoppas för den bildrutan. Värdet ska ligga mellan 0.0 och 1.0. Inspelningar påverkas inte."
|
||||||
},
|
},
|
||||||
"skip_motion_threshold": {
|
"skip_motion_threshold": {
|
||||||
"label": "Tröskelvärde för hoppande rörelse",
|
"label": "Tröskel för att hoppa över rörelse",
|
||||||
"description": "Om värdet är satt till ett värde mellan 0,0 och 1,0, och mer än denna andel av bilden ändras i en enda bildruta, kommer detektorn att returnera rutor för att inte röra sig och omedelbart kalibrera om. Detta kan spara CPU och minska falska positiva resultat vid blixtnedslag, stormar etc., men kan missa verkliga händelser, till exempel en PTZ-kamera som automatiskt spårar ett objekt. Avvägningen är mellan att tappa några megabyte inspelningar kontra att granska ett par korta klipp. Lämna oinställd (Ingen) för att inaktivera den här funktionen."
|
"description": "Andel av bildrutan som måste ändras i en enda uppdatering innan rörelserutor ignoreras helt. Värdet ska ligga mellan 0.0 och 1.0. När tröskeln överskrids rapporteras inga rörelserutor och ingen rörelsebaserad inspelning skapas för den bildrutan. Lämna tomt (null) för att inaktivera funktionen. Använd med försiktighet för PTZ-kameror eller andra situationer där varje bildruta måste sparas."
|
||||||
},
|
},
|
||||||
"improve_contrast": {
|
"improve_contrast": {
|
||||||
"label": "Förbättra kontrasten",
|
"label": "Förbättra kontrasten",
|
||||||
"description": "Tillämpa kontrastförbättring på bildrutor före rörelseanalys för att underlätta detektering."
|
"description": "Tillämpa kontrastförbättring på bildrutor före rörelseanalys för att underlätta detektering."
|
||||||
},
|
},
|
||||||
"contour_area": {
|
"contour_area": {
|
||||||
"label": "Konturområde",
|
"label": "Konturarea",
|
||||||
"description": "Minsta konturarea i pixlar som krävs för att en rörelsekontur ska räknas."
|
"description": "Minsta konturarea i pixlar i den skalade rörelsebilden för att området ska räknas som rörelse."
|
||||||
},
|
},
|
||||||
"delta_alpha": {
|
"delta_alpha": {
|
||||||
"label": "Delta alfa",
|
"label": "Delta-alfa",
|
||||||
"description": "Alfablandningsfaktor som används i bilddifferentiering för rörelseberäkning."
|
"description": "Alfablandningsfaktor som används vid bildrutedifferens för rörelseberäkning."
|
||||||
},
|
},
|
||||||
"frame_alpha": {
|
"frame_alpha": {
|
||||||
"label": "Bildalfa",
|
"label": "Bildrutealfa",
|
||||||
"description": "Alfavärde som används vid blandning av bildrutor för rörelseförbehandling."
|
"description": "Alfavärde som används vid utjämning av bildrutor för rörelsebearbetning."
|
||||||
},
|
},
|
||||||
"frame_height": {
|
"frame_height": {
|
||||||
"label": "Ramhöjd",
|
"label": "Bildrutehöjd",
|
||||||
"description": "Höjd i pixlar att skala bildrutor till vid beräkning av rörelse."
|
"description": "Höjd i pixlar som bildrutorna skalas till vid rörelseberäkning."
|
||||||
},
|
},
|
||||||
"mask": {
|
"mask": {
|
||||||
"label": "Maskkoordinater",
|
"label": "Maskkoordinater",
|
||||||
"description": "Ordnade x-, y-koordinater som definierar rörelsemaskpolygonen som används för att inkludera/exkludera områden."
|
"description": "Ordnade x- och y-koordinater som definierar polygonen för rörelsemasken och vilka områden som ska inkluderas eller exkluderas."
|
||||||
},
|
},
|
||||||
"mqtt_off_delay": {
|
"mqtt_off_delay": {
|
||||||
"label": "MQTT-avstängningsfördröjning",
|
"label": "Fördröjning innan MQTT stängs av",
|
||||||
"description": "Sekunder att vänta efter sista rörelsen innan ett MQTT-tillstånd \"av\" publiceras."
|
"description": "Antal sekunder efter den senaste rörelsen innan MQTT-tillståndet \"av\" publiceras."
|
||||||
},
|
},
|
||||||
"enabled_in_config": {
|
"enabled_in_config": {
|
||||||
"label": "Ursprungligt rörelsetillstånd",
|
"label": "Ursprunglig inställning för rörelsedetektering",
|
||||||
"description": "Anger om rörelsedetektering var aktiverad i den ursprungliga statiska konfigurationen."
|
"description": "Anger om rörelsedetektering var aktiverad i den ursprungliga statiska konfigurationen."
|
||||||
},
|
},
|
||||||
"raw_mask": {
|
"raw_mask": {
|
||||||
@@ -319,34 +319,34 @@
|
|||||||
},
|
},
|
||||||
"filters": {
|
"filters": {
|
||||||
"label": "Objektfilter",
|
"label": "Objektfilter",
|
||||||
"description": "Filter tillämpas på detekterade objekt för att minska falska positiva resultat (area, förhållande, konfidens).",
|
"description": "Filter som tillämpas på detekterade objekt för att minska falska positiva detekteringar, till exempel area, bildförhållande och konfidens.",
|
||||||
"min_area": {
|
"min_area": {
|
||||||
"label": "Minsta objektarea",
|
"label": "Minsta objektarea",
|
||||||
"description": "Minsta area för avgränsningsrutan (pixlar eller procent) som krävs för denna objekttyp. Kan vara pixlar (heltal) eller procent (flyttal mellan 0.000001 och 0.99)."
|
"description": "Minsta area för avgränsningsrutan som krävs för den här objekttypen. Kan anges i pixlar (heltal) eller som andel av bildytan (flyttal mellan 0.000001 och 0.99)."
|
||||||
},
|
},
|
||||||
"max_area": {
|
"max_area": {
|
||||||
"label": "Maximal objektarea",
|
"label": "Maximal objektarea",
|
||||||
"description": "Maximalt tillåtet område för avgränsningsrutan (pixlar eller procent) för denna objekttyp. Kan vara pixlar (heltal) eller procent (flyttal mellan 0.000001 och 0.99)."
|
"description": "Största tillåtna area för avgränsningsrutan för den här objekttypen. Kan anges i pixlar (heltal) eller som andel av bildytan (flyttal mellan 0.000001 och 0.99)."
|
||||||
},
|
},
|
||||||
"min_ratio": {
|
"min_ratio": {
|
||||||
"label": "Minsta bildförhållande",
|
"label": "Minsta bredd-/höjdförhållande",
|
||||||
"description": "Minsta förhållande mellan bredd och höjd som krävs för att avgränsningsramen ska kvalificera."
|
"description": "Minsta förhållande mellan avgränsningsrutans bredd och höjd för att objektet ska godkännas."
|
||||||
},
|
},
|
||||||
"max_ratio": {
|
"max_ratio": {
|
||||||
"label": "Maximalt bildförhållande",
|
"label": "Största bredd-/höjdförhållande",
|
||||||
"description": "Maximalt bredd/höjd-förhållande som tillåts för att avgränsningsramen ska kvalificera."
|
"description": "Största tillåtna förhållande mellan avgränsningsrutans bredd och höjd för att objektet ska godkännas."
|
||||||
},
|
},
|
||||||
"threshold": {
|
"threshold": {
|
||||||
"label": "Konfidensgräns",
|
"label": "Konfidenströskel",
|
||||||
"description": "Genomsnittligt tröskelvärde för detektionskonfidens som krävs för att objektet ska betraktas som ett verkligt positivt."
|
"description": "Minsta genomsnittliga detektionskonfidens som krävs för att objektet ska betraktas som en sann positiv detektering."
|
||||||
},
|
},
|
||||||
"min_score": {
|
"min_score": {
|
||||||
"label": "Minsta säkerhet",
|
"label": "Minsta konfidens",
|
||||||
"description": "Minsta konfidens för detektering av en enda bildruta som krävs för att objektet ska räknas."
|
"description": "Minsta detektionskonfidens i en enskild bildruta som krävs för att objektet ska räknas."
|
||||||
},
|
},
|
||||||
"mask": {
|
"mask": {
|
||||||
"label": "Filtermask",
|
"label": "Filtermask",
|
||||||
"description": "Polygonkoordinater som definierar var detta filter gäller inom ramen."
|
"description": "Polygonkoordinater som definierar var filtret gäller i bildrutan."
|
||||||
},
|
},
|
||||||
"raw_mask": {
|
"raw_mask": {
|
||||||
"label": "Rå mask"
|
"label": "Rå mask"
|
||||||
@@ -360,51 +360,51 @@
|
|||||||
"label": "Rå mask"
|
"label": "Rå mask"
|
||||||
},
|
},
|
||||||
"genai": {
|
"genai": {
|
||||||
"label": "GenAI-objektkonfiguration",
|
"label": "GenAI-inställningar för objekt",
|
||||||
"description": "GenAI-alternativ för att beskriva spårade objekt och skicka ramar för generering.",
|
"description": "GenAI-inställningar för att beskriva spårade objekt och skicka bildrutor för generering.",
|
||||||
"enabled": {
|
"enabled": {
|
||||||
"label": "Aktivera GenAI",
|
"label": "Aktivera GenAI",
|
||||||
"description": "Aktivera GenAI-generering av beskrivningar för spårade objekt som standard."
|
"description": "Aktivera generering av GenAI-beskrivningar för spårade objekt som standard."
|
||||||
},
|
},
|
||||||
"use_snapshot": {
|
"use_snapshot": {
|
||||||
"label": "Använd ögonblicksbilder",
|
"label": "Använd ögonblicksbild",
|
||||||
"description": "Använd objektbilder istället för miniatyrbilder för att generera GenAI-beskrivningar."
|
"description": "Använd objektets ögonblicksbild i stället för miniatyrbilder när GenAI-beskrivningar genereras."
|
||||||
},
|
},
|
||||||
"prompt": {
|
"prompt": {
|
||||||
"label": "Textningsprompt",
|
"label": "Beskrivningsprompt",
|
||||||
"description": "Standardmall för prompt som används vid generering av beskrivningar med GenAI."
|
"description": "Standardmall för den prompt som används när GenAI genererar beskrivningar."
|
||||||
},
|
},
|
||||||
"object_prompts": {
|
"object_prompts": {
|
||||||
"label": "Objektuppmaningar",
|
"label": "Objektspecifika prompter",
|
||||||
"description": "Uppmanar per objekt att anpassa GenAI-utdata för specifika etiketter."
|
"description": "Prompter per objekttyp för att anpassa GenAI-utdata för specifika etiketter."
|
||||||
},
|
},
|
||||||
"objects": {
|
"objects": {
|
||||||
"label": "GenAI-objekt",
|
"label": "GenAI-objekt",
|
||||||
"description": "Lista över objektetiketter som ska skickas till GenAI som standard."
|
"description": "Lista över objektetiketter som som standard ska skickas till GenAI."
|
||||||
},
|
},
|
||||||
"required_zones": {
|
"required_zones": {
|
||||||
"label": "Obligatoriska zoner",
|
"label": "Obligatoriska zoner",
|
||||||
"description": "Zoner som måste anges för att objekt ska kvalificera sig för GenAI-beskrivningsgenerering."
|
"description": "Zoner som objekt måste ha gått in i för att kvalificera sig för generering av GenAI-beskrivningar."
|
||||||
},
|
},
|
||||||
"debug_save_thumbnails": {
|
"debug_save_thumbnails": {
|
||||||
"label": "Spara miniatyrer",
|
"label": "Spara miniatyrer",
|
||||||
"description": "Spara miniatyrbilder som skickats till GenAI för felsökning och granskning."
|
"description": "Spara miniatyrbilder som skickas till GenAI för felsökning och granskning."
|
||||||
},
|
},
|
||||||
"send_triggers": {
|
"send_triggers": {
|
||||||
"label": "GenAI-utlösare",
|
"label": "GenAI-utlösare",
|
||||||
"description": "Definierar när ramar ska skickas till GenAI (på slutet, efter uppdateringar, osv.).",
|
"description": "Anger när bildrutor ska skickas till GenAI, till exempel när spårningen avslutas eller efter betydande uppdateringar.",
|
||||||
"tracked_object_end": {
|
"tracked_object_end": {
|
||||||
"label": "Skicka på slutet",
|
"label": "Skicka när spårningen avslutas",
|
||||||
"description": "Skicka en begäran till GenAI när det spårade objektet avslutas."
|
"description": "Skicka en begäran till GenAI när spårningen av objektet avslutas."
|
||||||
},
|
},
|
||||||
"after_significant_updates": {
|
"after_significant_updates": {
|
||||||
"label": "Tidig GenAI-utlösare",
|
"label": "Tidig GenAI-utlösare",
|
||||||
"description": "Skicka en begäran till GenAI efter ett angivet antal betydande uppdateringar för det spårade objektet."
|
"description": "Skicka en begäran till GenAI efter ett angivet antal betydande uppdateringar av det spårade objektet."
|
||||||
}
|
}
|
||||||
},
|
},
|
||||||
"enabled_in_config": {
|
"enabled_in_config": {
|
||||||
"label": "Ursprungligt GenAI-tillstånd",
|
"label": "Ursprunglig GenAI-inställning",
|
||||||
"description": "Anger om GenAI var aktiverat i den ursprungliga statiska konfigurationen."
|
"description": "Anger om GenAI ursprungligen var aktiverat i den statiska konfigurationen."
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
},
|
},
|
||||||
@@ -416,190 +416,190 @@
|
|||||||
"description": "Aktivera eller inaktivera inspelning för den här kameran."
|
"description": "Aktivera eller inaktivera inspelning för den här kameran."
|
||||||
},
|
},
|
||||||
"expire_interval": {
|
"expire_interval": {
|
||||||
"label": "Intervall för rensning av inspelningar",
|
"label": "Rensningsintervall för inspelningar",
|
||||||
"description": "Minuter mellan rensningspass som tar bort utgångna inspelningssegment."
|
"description": "Antal minuter mellan rensningar som tar bort utgångna inspelningssegment."
|
||||||
},
|
},
|
||||||
"continuous": {
|
"continuous": {
|
||||||
"label": "Kontinuerlig retention",
|
"label": "Kontinuerlig lagring",
|
||||||
"description": "Antal dagar som inspelningar ska sparas oavsett spårade objekt eller rörelse. Ställ in på 0 om du bara vill spara inspelningar av varningar och detekteringar.",
|
"description": "Antal dagar som inspelningar ska sparas oavsett spårade objekt eller rörelse. Ange 0 om du endast vill spara inspelningar av varningar och detektioner.",
|
||||||
"days": {
|
"days": {
|
||||||
"label": "Lagringsdagar",
|
"label": "Lagringstid i dagar",
|
||||||
"description": "Dagar att behålla inspelningar."
|
"description": "Antal dagar som inspelningar ska sparas."
|
||||||
}
|
}
|
||||||
},
|
},
|
||||||
"motion": {
|
"motion": {
|
||||||
"label": "Rörelseretention",
|
"label": "Lagring av rörelseinspelningar",
|
||||||
"description": "Antal dagar som inspelningar som utlöses av rörelse ska sparas oavsett spårade objekt. Ställ in på 0 om du bara vill spara inspelningar av varningar och detekteringar.",
|
"description": "Antal dagar som rörelseutlösta inspelningar ska sparas oavsett spårade objekt. Ange 0 om du endast vill spara inspelningar av varningar och detektioner.",
|
||||||
"days": {
|
"days": {
|
||||||
"label": "Lagringsdagar",
|
"label": "Lagringstid i dagar",
|
||||||
"description": "Dagar att behålla inspelningar."
|
"description": "Antal dagar som inspelningar ska sparas."
|
||||||
}
|
}
|
||||||
},
|
},
|
||||||
"detections": {
|
"detections": {
|
||||||
"label": "Detektionskvarhållning",
|
"label": "Lagring av detektioner",
|
||||||
"description": "Inställningar för inspelningslagring för detekteringshändelser, inklusive varaktigheter före/efter inspelning.",
|
"description": "Inställningar för inspelningar av detektionshändelser, inklusive hur många sekunder före och efter händelsen som ska sparas.",
|
||||||
"pre_capture": {
|
"pre_capture": {
|
||||||
"label": "Förinspelningssekunder",
|
"label": "Sekunder före detektion",
|
||||||
"description": "Antal sekunder före detekteringshändelsen som ska inkluderas i inspelningen."
|
"description": "Antal sekunder före detektionshändelsen som ska tas med i inspelningen."
|
||||||
},
|
},
|
||||||
"post_capture": {
|
"post_capture": {
|
||||||
"label": "Sekunder efter inspelning",
|
"label": "Sekunder efter detektion",
|
||||||
"description": "Antal sekunder efter detekteringshändelsen som ska inkluderas i inspelningen."
|
"description": "Antal sekunder efter detektionshändelsen som ska tas med i inspelningen."
|
||||||
},
|
},
|
||||||
"retain": {
|
"retain": {
|
||||||
"label": "Händelselagring",
|
"label": "Lagring",
|
||||||
"description": "Lagringsinställningar för inspelningar av detekteringshändelser.",
|
"description": "Lagringsinställningar för inspelningar av detektionshändelser.",
|
||||||
"days": {
|
"days": {
|
||||||
"label": "Lagringsdagar",
|
"label": "Lagringstid i dagar",
|
||||||
"description": "Antal dagar att spara inspelningar av detekteringshändelser."
|
"description": "Antal dagar som inspelningar av detektionshändelser ska sparas."
|
||||||
},
|
},
|
||||||
"mode": {
|
"mode": {
|
||||||
"label": "Lagringsläge",
|
"label": "Lagringsläge",
|
||||||
"description": "Läge för kvarhållning: alla (spara alla segment), rörelse (spara segment med rörelse) eller aktiva_objekt (spara segment med aktiva objekt)."
|
"description": "Lagringsläge: \"all\" (spara alla segment), \"motion\" (spara segment med rörelse) eller \"active_objects\" (spara segment med aktiva objekt)."
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
},
|
},
|
||||||
"alerts": {
|
"alerts": {
|
||||||
"label": "Lagring av varningar",
|
"label": "Lagring av varningar",
|
||||||
"description": "Inställningar för inspelningslagring för aviseringshändelser, inklusive varaktigheter före/efter inspelning.",
|
"description": "Inställningar för inspelningar av varningshändelser, inklusive hur många sekunder före och efter händelsen som ska sparas.",
|
||||||
"pre_capture": {
|
"pre_capture": {
|
||||||
"label": "Förinspelningssekunder",
|
"label": "Sekunder före varning",
|
||||||
"description": "Antal sekunder före detekteringshändelsen som ska inkluderas i inspelningen."
|
"description": "Antal sekunder före varningshändelsen som ska tas med i inspelningen."
|
||||||
},
|
},
|
||||||
"post_capture": {
|
"post_capture": {
|
||||||
"label": "Sekunder efter inspelning",
|
"label": "Sekunder efter varning",
|
||||||
"description": "Antal sekunder efter detekteringshändelsen som ska inkluderas i inspelningen."
|
"description": "Antal sekunder efter varningshändelsen som ska tas med i inspelningen."
|
||||||
},
|
},
|
||||||
"retain": {
|
"retain": {
|
||||||
"label": "Händelselagring",
|
"label": "Lagring",
|
||||||
"description": "Lagringsinställningar för inspelningar av detekteringshändelser.",
|
"description": "Lagringsinställningar för inspelningar av varningshändelser.",
|
||||||
"days": {
|
"days": {
|
||||||
"label": "Lagringsdagar",
|
"label": "Lagringstid i dagar",
|
||||||
"description": "Antal dagar att spara inspelningar av detekteringshändelser."
|
"description": "Antal dagar som inspelningar av varningshändelser ska sparas."
|
||||||
},
|
},
|
||||||
"mode": {
|
"mode": {
|
||||||
"label": "Lagringsläge",
|
"label": "Lagringsläge",
|
||||||
"description": "Läge för kvarhållning: alla (spara alla segment), rörelse (spara segment med rörelse) eller aktiva_objekt (spara segment med aktiva objekt)."
|
"description": "Lagringsläge: \"all\" (spara alla segment), \"motion\" (spara segment med rörelse) eller \"active_objects\" (spara segment med aktiva objekt)."
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
},
|
},
|
||||||
"export": {
|
"export": {
|
||||||
"label": "Exportera konfiguration",
|
"label": "Exportinställningar",
|
||||||
"description": "Inställningar som används vid export av inspelningar, såsom timelapse och hårdvaruacceleration.",
|
"description": "Inställningar för export av inspelningar, till exempel timelapse och maskinvaruacceleration.",
|
||||||
"hwaccel_args": {
|
"hwaccel_args": {
|
||||||
"label": "Exportera hwaccel-argument",
|
"label": "Argument för maskinvaruacceleration vid export",
|
||||||
"description": "Hårdvaruaccelerationsargument som ska användas för export-/transkodningsoperationer."
|
"description": "Argument för maskinvaruacceleration som används vid export och transkodning."
|
||||||
},
|
},
|
||||||
"max_concurrent": {
|
"max_concurrent": {
|
||||||
"label": "Maximalt antal samtidiga exporter",
|
"label": "Maximalt antal samtidiga exporter",
|
||||||
"description": "Maximalt antal exportjobb att bearbeta samtidigt."
|
"description": "Maximalt antal exportjobb som får bearbetas samtidigt."
|
||||||
},
|
},
|
||||||
"chapters": {
|
"chapters": {
|
||||||
"label": "Kapitelmetadata att bädda in i exporterade inspelningar"
|
"label": "Kapitelmetadata att bädda in i exporterade inspelningar"
|
||||||
}
|
}
|
||||||
},
|
},
|
||||||
"preview": {
|
"preview": {
|
||||||
"label": "Förhandsgranska konfiguration",
|
"label": "Förhandsgranskningsinställningar",
|
||||||
"description": "Inställningar som styr kvaliteten på förhandsgranskningar av inspelningar som visas i användargränssnittet.",
|
"description": "Inställningar som styr kvaliteten på inspelningsförhandsvisningar i användargränssnittet.",
|
||||||
"quality": {
|
"quality": {
|
||||||
"label": "Förhandsgranskningskvalitet",
|
"label": "Förhandsgranskningskvalitet",
|
||||||
"description": "Förhandsgranskningskvalitetsnivå (mycket_låg, låg, medel, hög, mycket_hög)."
|
"description": "Kvalitetsnivå för förhandsvisningar: \"very_low\", \"low\", \"medium\", \"high\" eller \"very_high\"."
|
||||||
}
|
}
|
||||||
},
|
},
|
||||||
"enabled_in_config": {
|
"enabled_in_config": {
|
||||||
"label": "Ursprungligt inspelningsläge",
|
"label": "Ursprunglig inspelningsinställning",
|
||||||
"description": "Anger om inspelning var aktiverad i den ursprungliga statiska konfigurationen."
|
"description": "Anger om inspelning var aktiverad i den ursprungliga statiska konfigurationen."
|
||||||
}
|
}
|
||||||
},
|
},
|
||||||
"review": {
|
"review": {
|
||||||
"label": "Granska",
|
"label": "Granskning",
|
||||||
"description": "Inställningar som styr varningar, detekteringar och GenAI-granskningssammanfattningar som används av användargränssnittet och lagring för den här kameran.",
|
"description": "Inställningar som styr varningar, detekteringar och GenAI-granskningssammanfattningar som används av användargränssnittet och lagring för den här kameran.",
|
||||||
"alerts": {
|
"alerts": {
|
||||||
"label": "Varningskonfiguration",
|
"label": "Varningsinställningar",
|
||||||
"description": "Inställningar för vilka spårade objekt som genererar aviseringar och hur aviseringar behålls.",
|
"description": "Inställningar för vilka spårade objekt som genererar varningar och hur länge varningarna behålls.",
|
||||||
"enabled": {
|
"enabled": {
|
||||||
"label": "Aktivera aviseringar",
|
"label": "Aktivera varningar",
|
||||||
"description": "Aktivera eller inaktivera varningsgenerering för den här kameran."
|
"description": "Aktivera eller inaktivera varningsgenerering för den här kameran."
|
||||||
},
|
},
|
||||||
"labels": {
|
"labels": {
|
||||||
"label": "Varningsetiketter",
|
"label": "Varningsetiketter",
|
||||||
"description": "Lista över objektetiketter som kvalificerar som varningar (till exempel: bil, person)."
|
"description": "Lista över objektetiketter som kvalificerar sig som varningar, till exempel bil och person."
|
||||||
},
|
},
|
||||||
"required_zones": {
|
"required_zones": {
|
||||||
"label": "Obligatoriska zoner",
|
"label": "Obligatoriska zoner",
|
||||||
"description": "Zoner som ett objekt måste komma in i för att betraktas som en varning; lämna tomt för att tillåta valfri zon."
|
"description": "Zoner som ett objekt måste gå in i för att betraktas som en varning; lämna tomt om ingen särskild zon ska krävas."
|
||||||
},
|
},
|
||||||
"enabled_in_config": {
|
"enabled_in_config": {
|
||||||
"label": "Ursprungligt varningsstatus",
|
"label": "Ursprunglig varningsinställning",
|
||||||
"description": "Spårar om aviseringar ursprungligen var aktiverade i den statiska konfigurationen."
|
"description": "Anger om varningar ursprungligen var aktiverade i den statiska konfigurationen."
|
||||||
},
|
},
|
||||||
"cutoff_time": {
|
"cutoff_time": {
|
||||||
"label": "Avstängningstid för varningar",
|
"label": "Tidsgräns för varningar",
|
||||||
"description": "Sekunder att vänta efter att ingen aktivitet som orsakar en varning har pågått innan en varning avbryts."
|
"description": "Antal sekunder att vänta efter att varningsutlösande aktivitet har upphört innan varningen avslutas."
|
||||||
}
|
}
|
||||||
},
|
},
|
||||||
"detections": {
|
"detections": {
|
||||||
"label": "Konfiguration för detektioner",
|
"label": "Detektionsinställningar",
|
||||||
"description": "Inställningar för vilka spårade objekt genererar detektioner (ej aviseringar) och hur detektioner behålls.",
|
"description": "Inställningar för vilka spårade objekt som genererar detektioner, men inte varningar, och hur länge detektionerna behålls.",
|
||||||
"enabled": {
|
"enabled": {
|
||||||
"label": "Aktivera detektioner",
|
"label": "Aktivera detektioner",
|
||||||
"description": "Aktivera eller inaktivera detekteringshändelser för den här kameran."
|
"description": "Aktivera eller inaktivera detekteringshändelser för den här kameran."
|
||||||
},
|
},
|
||||||
"labels": {
|
"labels": {
|
||||||
"label": "Detektionsetiketter",
|
"label": "Detektionsetiketter",
|
||||||
"description": "Lista över objektetiketter som kvalificerar som detekteringshändelser."
|
"description": "Lista över objektetiketter som kvalificerar sig som detektionshändelser."
|
||||||
},
|
},
|
||||||
"required_zones": {
|
"required_zones": {
|
||||||
"label": "Obligatoriska zoner",
|
"label": "Obligatoriska zoner",
|
||||||
"description": "Zoner som ett objekt måste komma in i för att betraktas som en detektering; lämna tomt för att tillåta valfri zon."
|
"description": "Zoner som ett objekt måste gå in i för att betraktas som en detektion; lämna tomt om ingen särskild zon ska krävas."
|
||||||
},
|
},
|
||||||
"cutoff_time": {
|
"cutoff_time": {
|
||||||
"label": "Avstängningstid för detektioner",
|
"label": "Tidsgräns för detektioner",
|
||||||
"description": "Sekunder att vänta efter att ingen detekteringsorsakande aktivitet har skett innan en detektering avbryts."
|
"description": "Antal sekunder att vänta efter att detektionsutlösande aktivitet har upphört innan detektionen avslutas."
|
||||||
},
|
},
|
||||||
"enabled_in_config": {
|
"enabled_in_config": {
|
||||||
"label": "Ursprungligt detektionstillstånd",
|
"label": "Ursprunglig detektionsinställning",
|
||||||
"description": "Spårar om detektioner ursprungligen var aktiverade i den statiska konfigurationen."
|
"description": "Anger om detektioner ursprungligen var aktiverade i den statiska konfigurationen."
|
||||||
}
|
}
|
||||||
},
|
},
|
||||||
"genai": {
|
"genai": {
|
||||||
"label": "GenAI-konfiguration",
|
"label": "GenAI-inställningar",
|
||||||
"description": "Styr användningen av generativ AI för att producera beskrivningar och sammanfattningar av granskningsobjekt.",
|
"description": "Styr användningen av generativ AI för att skapa beskrivningar och sammanfattningar av granskningsobjekt.",
|
||||||
"enabled": {
|
"enabled": {
|
||||||
"label": "Aktivera GenAI-beskrivningar",
|
"label": "Aktivera GenAI-beskrivningar",
|
||||||
"description": "Aktivera eller inaktivera GenAI-genererade beskrivningar och sammanfattningar för granskningsobjekt."
|
"description": "Aktivera eller inaktivera GenAI-genererade beskrivningar och sammanfattningar av granskningsobjekt."
|
||||||
},
|
},
|
||||||
"alerts": {
|
"alerts": {
|
||||||
"label": "Aktivera GenAI för aviseringar",
|
"label": "Aktivera GenAI för varningar",
|
||||||
"description": "Använd GenAI för att generera beskrivningar för varningsobjekt."
|
"description": "Använd GenAI för att generera beskrivningar av varningsobjekt."
|
||||||
},
|
},
|
||||||
"detections": {
|
"detections": {
|
||||||
"label": "Aktivera GenAI för detektioner",
|
"label": "Aktivera GenAI för detektioner",
|
||||||
"description": "Använd GenAI för att generera beskrivningar för detektionsobjekt."
|
"description": "Använd GenAI för att generera beskrivningar av detektionsobjekt."
|
||||||
},
|
},
|
||||||
"image_source": {
|
"image_source": {
|
||||||
"label": "Recensera bildkälla",
|
"label": "Bildkälla för granskning",
|
||||||
"description": "Källa för bilder som skickats till GenAI ('förhandsvisning' eller 'inspelningar'); 'inspelningar' använder bildrutor av högre kvalitet men fler tokens."
|
"description": "Källa för bilder som skickas till GenAI: \"preview\" (förhandsvisning) eller \"recordings\" (inspelningar). \"recordings\" använder bildrutor med högre kvalitet men förbrukar fler token."
|
||||||
},
|
},
|
||||||
"additional_concerns": {
|
"additional_concerns": {
|
||||||
"label": "Ytterligare farhågor",
|
"label": "Ytterligare saker att uppmärksamma",
|
||||||
"description": "En lista över ytterligare problem eller anmärkningar som GenAI bör beakta vid utvärdering av aktivitet på den här kameran."
|
"description": "Lista över ytterligare problem eller omständigheter som GenAI ska uppmärksamma vid utvärdering av aktivitet från den här kameran."
|
||||||
},
|
},
|
||||||
"debug_save_thumbnails": {
|
"debug_save_thumbnails": {
|
||||||
"label": "Spara miniatyrer",
|
"label": "Spara miniatyrer",
|
||||||
"description": "Spara miniatyrbilder som skickas till GenAI-leverantören för felsökning och granskning."
|
"description": "Spara miniatyrbilder som skickas till GenAI-leverantören för felsökning och granskning."
|
||||||
},
|
},
|
||||||
"enabled_in_config": {
|
"enabled_in_config": {
|
||||||
"label": "Ursprungligt GenAI-tillstånd",
|
"label": "Ursprunglig GenAI-inställning",
|
||||||
"description": "Spårar om GenAI-granskning ursprungligen var aktiverad i den statiska konfigurationen."
|
"description": "Anger om GenAI-granskning ursprungligen var aktiverad i den statiska konfigurationen."
|
||||||
},
|
},
|
||||||
"preferred_language": {
|
"preferred_language": {
|
||||||
"label": "Önskat språk",
|
"label": "Föredraget språk",
|
||||||
"description": "Föredraget språk att begära från GenAI-leverantören för genererade svar."
|
"description": "Språk som GenAI-leverantören ska uppmanas att använda för genererade svar."
|
||||||
},
|
},
|
||||||
"activity_context_prompt": {
|
"activity_context_prompt": {
|
||||||
"label": "Uppmaning till aktivitetskontext",
|
"label": "Prompt för aktivitetskontext",
|
||||||
"description": "Anpassad prompt som beskriver vad som är och inte är misstänkt aktivitet för att ge sammanhang för GenAI-sammanfattningar."
|
"description": "Anpassad prompt som beskriver vad som ska och inte ska betraktas som misstänkt aktivitet och ger kontext för GenAI-sammanfattningar."
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
},
|
},
|
||||||
@@ -608,30 +608,30 @@
|
|||||||
"description": "Inställningar för semantisk sökning som bygger och frågar efter objektinbäddningar för att hitta liknande objekt.",
|
"description": "Inställningar för semantisk sökning som bygger och frågar efter objektinbäddningar för att hitta liknande objekt.",
|
||||||
"triggers": {
|
"triggers": {
|
||||||
"label": "Utlösare",
|
"label": "Utlösare",
|
||||||
"description": "Åtgärder och matchningskriterier för kameraspecifika semantiska sökutlösare.",
|
"description": "Åtgärder och matchningskriterier för kameraspecifika utlösare för semantisk sökning.",
|
||||||
"friendly_name": {
|
"friendly_name": {
|
||||||
"label": "Vänligt namn",
|
"label": "Visningsnamn",
|
||||||
"description": "Valfritt vänligt namn som visas i användargränssnittet för den här utlösaren."
|
"description": "Valfritt användarvänligt namn som visas i gränssnittet för den här utlösaren."
|
||||||
},
|
},
|
||||||
"enabled": {
|
"enabled": {
|
||||||
"label": "Aktivera den här utlösaren",
|
"label": "Aktivera den här utlösaren",
|
||||||
"description": "Aktivera eller inaktivera denna semantiska sökutlösare."
|
"description": "Aktivera eller inaktivera den här utlösaren för semantisk sökning."
|
||||||
},
|
},
|
||||||
"type": {
|
"type": {
|
||||||
"label": "Utlösartyp",
|
"label": "Utlösartyp",
|
||||||
"description": "Typ av utlösare: 'miniatyr' (matchning mot bild) eller 'beskrivning' (matchning mot text)."
|
"description": "Typ av utlösare: \"thumbnail\" (matchning mot bild) eller \"description\" (matchning mot text)."
|
||||||
},
|
},
|
||||||
"data": {
|
"data": {
|
||||||
"label": "Utlösarinnehåll",
|
"label": "Utlösarinnehåll",
|
||||||
"description": "Textfras- eller miniatyr-ID för att matcha mot spårade objekt."
|
"description": "Textfras eller miniatyr-ID som ska matchas mot spårade objekt."
|
||||||
},
|
},
|
||||||
"threshold": {
|
"threshold": {
|
||||||
"label": "Utlösartröskel",
|
"label": "Likhetströskel",
|
||||||
"description": "Minsta likhetspoäng (0-1) krävs för att aktivera denna utlösare."
|
"description": "Minsta likhetspoäng (0–1) som krävs för att aktivera utlösaren."
|
||||||
},
|
},
|
||||||
"actions": {
|
"actions": {
|
||||||
"label": "Utlösande åtgärder",
|
"label": "Åtgärder vid utlösning",
|
||||||
"description": "Lista över åtgärder som ska utföras när utlösaren matchar (meddelande, underetikett, attribut)."
|
"description": "Lista över åtgärder som ska utföras när utlösaren matchar: \"notification\" (avisering), \"sub_label\" (underetikett) eller \"attribute\" (attribut)."
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
},
|
},
|
||||||
@@ -643,40 +643,40 @@
|
|||||||
"description": "Aktivera eller inaktivera sparning av ögonblicksbilder för den här kameran."
|
"description": "Aktivera eller inaktivera sparning av ögonblicksbilder för den här kameran."
|
||||||
},
|
},
|
||||||
"timestamp": {
|
"timestamp": {
|
||||||
"label": "Tidsstämpelöverlagring",
|
"label": "Tidsstämpel",
|
||||||
"description": "Lägg en tidsstämpel över ögonblicksbilder från API:et."
|
"description": "Lägg en tidsstämpel på ögonblicksbilder från API:et."
|
||||||
},
|
},
|
||||||
"bounding_box": {
|
"bounding_box": {
|
||||||
"label": "Begränsningsbox överlägg",
|
"label": "Avgränsningsruta",
|
||||||
"description": "Rita begränsningsrutor för spårade objekt på ögonblicksbilder från API."
|
"description": "Rita avgränsningsrutor för spårade objekt på ögonblicksbilder från API:et."
|
||||||
},
|
},
|
||||||
"crop": {
|
"crop": {
|
||||||
"label": "Beskär ögonblicksbild",
|
"label": "Beskär ögonblicksbild",
|
||||||
"description": "Beskär ögonblicksbilder från API till det upptäckta objektets begränsningsram."
|
"description": "Beskär ögonblicksbilder från API:et till det detekterade objektets avgränsningsruta."
|
||||||
},
|
},
|
||||||
"required_zones": {
|
"required_zones": {
|
||||||
"label": "Obligatoriskt zoner",
|
"label": "Obligatoriska zoner",
|
||||||
"description": "Zoner som ett objekt måste gå in i för att en ögonblicksbild ska sparas."
|
"description": "Zoner som ett objekt måste gå in i för att en ögonblicksbild ska sparas."
|
||||||
},
|
},
|
||||||
"height": {
|
"height": {
|
||||||
"label": "Snapshot höjd",
|
"label": "Höjd på ögonblicksbild",
|
||||||
"description": "Höjd (pixlar) för att ändra storlek på ögonblicksbilder från API till; lämna tomt för att bevara originalstorlek."
|
"description": "Höjd i pixlar som ögonblicksbilder från API:et ska skalas till; lämna tomt för att behålla originalstorleken."
|
||||||
},
|
},
|
||||||
"retain": {
|
"retain": {
|
||||||
"label": "Bevarande av ögonblicksbilder",
|
"label": "Lagring av ögonblicksbilder",
|
||||||
"description": "Inställningar för kvarhållning av ögonblicksbilder, inklusive standarddagar och åsidosättningar per objekt.",
|
"description": "Inställningar för hur länge ögonblicksbilder ska sparas, inklusive standardvärde och åsidosättningar per objekt.",
|
||||||
"default": {
|
"default": {
|
||||||
"label": "Standardlagring",
|
"label": "Standardlagringstid",
|
||||||
"description": "Standardantalet dagar för att behålla ögonblicksbilder."
|
"description": "Standardantal dagar som ögonblicksbilder ska sparas."
|
||||||
},
|
},
|
||||||
"objects": {
|
"objects": {
|
||||||
"label": "Objektretention",
|
"label": "Lagringstid per objekt",
|
||||||
"description": "Åsidosättningar per objekt för lagringsdagar för ögonblicksbilder."
|
"description": "Åsidosättningar per objekt för hur många dagar ögonblicksbilder ska sparas."
|
||||||
}
|
}
|
||||||
},
|
},
|
||||||
"quality": {
|
"quality": {
|
||||||
"label": "Ögonblicksbildskvalitet",
|
"label": "Ögonblicksbildskvalitet",
|
||||||
"description": "Kodningskvalitet för sparade ögonblicksbilder (0-100)."
|
"description": "Kodningskvalitet för sparade ögonblicksbilder (0–100)."
|
||||||
}
|
}
|
||||||
},
|
},
|
||||||
"timestamp_style": {
|
"timestamp_style": {
|
||||||
@@ -684,35 +684,35 @@
|
|||||||
"description": "Stilalternativ för tidsstämplar som tillämpas på ögonblicksbilder och felsökningsvyn.",
|
"description": "Stilalternativ för tidsstämplar som tillämpas på ögonblicksbilder och felsökningsvyn.",
|
||||||
"position": {
|
"position": {
|
||||||
"label": "Tidsstämpelposition",
|
"label": "Tidsstämpelposition",
|
||||||
"description": "Tidsstämpelns position på bilden (tl/tr/bl/br)."
|
"description": "Tidsstämpelns position i bilden (tl/tr/bl/br)."
|
||||||
},
|
},
|
||||||
"format": {
|
"format": {
|
||||||
"label": "Tidsstämpelformat",
|
"label": "Tidsstämpelformat",
|
||||||
"description": "Datum- och tidsformatsträng som används för tidsstämplar (Python-koder för datum- och tidsformat)."
|
"description": "Datum- och tidsformatsträng för tidsstämplar, med Pythons formatkoder för datum och tid."
|
||||||
},
|
},
|
||||||
"color": {
|
"color": {
|
||||||
"label": "Tidsstämpelns färg",
|
"label": "Tidsstämpelns färg",
|
||||||
"description": "RGB-färgvärden för tidsstämpeltexten (alla värden 0–255).",
|
"description": "RGB-färgvärden för tidsstämpeltexten (alla värden 0–255).",
|
||||||
"red": {
|
"red": {
|
||||||
"label": "Röd",
|
"label": "Röd",
|
||||||
"description": "Röd komponent (0-255) för tidsstämpelns färg."
|
"description": "Röd färgkomponent (0–255) för tidsstämpeln."
|
||||||
},
|
},
|
||||||
"green": {
|
"green": {
|
||||||
"label": "Grön",
|
"label": "Grön",
|
||||||
"description": "Grön komponent (0-255) för tidsstämpelns färg."
|
"description": "Grön färgkomponent (0–255) för tidsstämpeln."
|
||||||
},
|
},
|
||||||
"blue": {
|
"blue": {
|
||||||
"label": "Blå",
|
"label": "Blå",
|
||||||
"description": "Blå komponent (0-255) för tidsstämpelns färg."
|
"description": "Blå färgkomponent (0–255) för tidsstämpeln."
|
||||||
}
|
}
|
||||||
},
|
},
|
||||||
"thickness": {
|
"thickness": {
|
||||||
"label": "Tidsstämpelns tjocklek",
|
"label": "Tidsstämpelns tjocklek",
|
||||||
"description": "Linjetjockleken på tidsstämpelns text."
|
"description": "Linjetjocklek för tidsstämpelns text."
|
||||||
},
|
},
|
||||||
"effect": {
|
"effect": {
|
||||||
"label": "Tidsstämpeleffekt",
|
"label": "Tidsstämpeleffekt",
|
||||||
"description": "Visuell effekt för tidsstämpeltexten (ingen, heldragen, skugga)."
|
"description": "Visuell effekt för tidsstämpeltexten: ingen effekt, \"solid\" (heldragen bakgrund) eller \"shadow\" (skugga)."
|
||||||
}
|
}
|
||||||
},
|
},
|
||||||
"best_image_timeout": {
|
"best_image_timeout": {
|
||||||
@@ -759,24 +759,24 @@
|
|||||||
"description": "Aktivera eller inaktivera aviseringar för den här kameran."
|
"description": "Aktivera eller inaktivera aviseringar för den här kameran."
|
||||||
},
|
},
|
||||||
"email": {
|
"email": {
|
||||||
"label": "E-postmeddelande med avisering",
|
"label": "E-postadress för aviseringar",
|
||||||
"description": "E-postadress som används för push-meddelanden eller krävs av vissa aviseringsleverantörer."
|
"description": "E-postadress som används för push-aviseringar eller krävs av vissa aviseringsleverantörer."
|
||||||
},
|
},
|
||||||
"cooldown": {
|
"cooldown": {
|
||||||
"label": "Nedkylningsperiod",
|
"label": "Väntetid mellan aviseringar",
|
||||||
"description": "Nedkylningstid (sekunder) mellan aviseringar för att undvika att skicka spammare."
|
"description": "Minsta antal sekunder mellan aviseringar för att undvika att skicka för många meddelanden."
|
||||||
},
|
},
|
||||||
"enabled_in_config": {
|
"enabled_in_config": {
|
||||||
"label": "Ursprungligt aviseringsstatus",
|
"label": "Ursprunglig aviseringsinställning",
|
||||||
"description": "Anger om aviseringar var aktiverade i den ursprungliga statiska konfigurationen."
|
"description": "Anger om aviseringar ursprungligen var aktiverade i den statiska konfigurationen."
|
||||||
}
|
}
|
||||||
},
|
},
|
||||||
"onvif": {
|
"onvif": {
|
||||||
"label": "ONVIF",
|
"label": "ONVIF",
|
||||||
"description": "ONVIF-anslutning och PTZ-autospårningsinställningar för den här kameran.",
|
"description": "Inställningar för ONVIF-anslutning och automatisk PTZ-spårning för den här kameran.",
|
||||||
"host": {
|
"host": {
|
||||||
"label": "ONVIF-värd",
|
"label": "ONVIF-värd",
|
||||||
"description": "Värd (och valfritt schema) för ONVIF-tjänsten för den här kameran."
|
"description": "Värd, med valfritt schema, för kamerans ONVIF-tjänst."
|
||||||
},
|
},
|
||||||
"port": {
|
"port": {
|
||||||
"label": "ONVIF-port",
|
"label": "ONVIF-port",
|
||||||
@@ -784,7 +784,7 @@
|
|||||||
},
|
},
|
||||||
"user": {
|
"user": {
|
||||||
"label": "ONVIF-användarnamn",
|
"label": "ONVIF-användarnamn",
|
||||||
"description": "Användarnamn för ONVIF-autentisering; vissa enheter kräver administratörsanvändare för ONVIF."
|
"description": "Användarnamn för ONVIF-autentisering; vissa enheter kräver ett administratörskonto för ONVIF."
|
||||||
},
|
},
|
||||||
"password": {
|
"password": {
|
||||||
"label": "ONVIF-lösenord",
|
"label": "ONVIF-lösenord",
|
||||||
@@ -792,30 +792,30 @@
|
|||||||
},
|
},
|
||||||
"tls_insecure": {
|
"tls_insecure": {
|
||||||
"label": "Inaktivera TLS-verifiering",
|
"label": "Inaktivera TLS-verifiering",
|
||||||
"description": "Hoppa över TLS-verifiering och inaktivera digest-autentisering för ONVIF (osäkert; använd endast i säkra nätverk)."
|
"description": "Hoppa över TLS-verifiering och inaktivera digest-autentisering för ONVIF. Detta är osäkert och bör endast användas i betrodda nätverk."
|
||||||
},
|
},
|
||||||
"profile": {
|
"profile": {
|
||||||
"label": "ONVIF-profil",
|
"label": "ONVIF-profil",
|
||||||
"description": "Specifik ONVIF-medieprofil som ska användas för PTZ-kontroll, matchad av token eller namn. Om den inte är inställd väljs den första profilen med giltig PTZ-konfiguration automatiskt."
|
"description": "ONVIF-medieprofil som ska användas för PTZ-styrning, matchad efter token eller namn. Om ingen profil anges väljs automatiskt den första profilen med giltig PTZ-konfiguration."
|
||||||
},
|
},
|
||||||
"autotracking": {
|
"autotracking": {
|
||||||
"label": "Automatisk spårning",
|
"label": "Automatisk spårning",
|
||||||
"description": "Spåra rörliga objekt automatiskt och håll dem centrerade i bilden med hjälp av PTZ-kamerarörelser.",
|
"description": "Spåra rörliga objekt automatiskt och håll dem centrerade i bilden med PTZ-kamerarörelser.",
|
||||||
"enabled": {
|
"enabled": {
|
||||||
"label": "Aktivera automatisk spårning",
|
"label": "Aktivera automatisk spårning",
|
||||||
"description": "Aktivera eller inaktivera automatisk PTZ-kameraspårning av detekterade objekt."
|
"description": "Aktivera eller inaktivera automatisk PTZ-spårning av detekterade objekt."
|
||||||
},
|
},
|
||||||
"calibrate_on_startup": {
|
"calibrate_on_startup": {
|
||||||
"label": "Kalibrera vid start",
|
"label": "Kalibrera vid start",
|
||||||
"description": "Mät PTZ-motorhastigheter vid uppstart för att förbättra spårningsnoggrannheten. Fregate kommer att uppdatera konfigurationen med movement_weights efter kalibrering."
|
"description": "Mät PTZ-motorernas hastigheter vid start för att förbättra spårningsnoggrannheten. Frigate uppdaterar konfigurationen med movement_weights efter kalibreringen."
|
||||||
},
|
},
|
||||||
"zooming": {
|
"zooming": {
|
||||||
"label": "Zoomläge",
|
"label": "Zoomläge",
|
||||||
"description": "Kontroll zoom beteende: inaktiverat (endast panorering/tiltning), absolut (mest kompatibelt) eller relativt (samtidig panorering/tiltning/zoomning)."
|
"description": "Styr zoombeteendet: inaktiverat (endast panorering och tiltning), absolut (mest kompatibelt) eller relativt (samtidig panorering, tiltning och zoomning)."
|
||||||
},
|
},
|
||||||
"zoom_factor": {
|
"zoom_factor": {
|
||||||
"label": "Zoomfaktor",
|
"label": "Zoomfaktor",
|
||||||
"description": "Kontrollera zoom nivån på spårade objekt. Lägre värden håller mer av motivet synligt; högre värden zoomar in närmare men kan förlora spårningen. Värden mellan 0,1 och 0,75."
|
"description": "Styr zoomnivån för spårade objekt. Lägre värden håller mer av motivet synligt; högre värden zoomar närmare men kan göra att spårningen tappas. Tillåtna värden är 0.1–0.75."
|
||||||
},
|
},
|
||||||
"track": {
|
"track": {
|
||||||
"label": "Spårade objekt",
|
"label": "Spårade objekt",
|
||||||
@@ -826,25 +826,25 @@
|
|||||||
"description": "Objekt måste komma in i en av dessa zoner innan automatisk spårning börjar."
|
"description": "Objekt måste komma in i en av dessa zoner innan automatisk spårning börjar."
|
||||||
},
|
},
|
||||||
"return_preset": {
|
"return_preset": {
|
||||||
"label": "Återgå förinställning",
|
"label": "Återgångsförinställning",
|
||||||
"description": "ONVIF-förinställt namn som konfigurerats i kamerans firmware att återgå till efter att spårningen avslutats."
|
"description": "Namn på en ONVIF-förinställning i kamerans firmware som kameran ska återgå till när spårningen avslutas."
|
||||||
},
|
},
|
||||||
"timeout": {
|
"timeout": {
|
||||||
"label": "Retur tidsgräns",
|
"label": "Tidsgräns för återgång",
|
||||||
"description": "Vänta så här många sekunder efter att du tappat spårningen innan du återställer kameran till förinställd position."
|
"description": "Antal sekunder att vänta efter att spårningen tappats innan kameran återgår till den förinställda positionen."
|
||||||
},
|
},
|
||||||
"movement_weights": {
|
"movement_weights": {
|
||||||
"label": "Rörelsevikter",
|
"label": "Rörelsevikter",
|
||||||
"description": "Kalibreringsvärden genereras automatiskt av kamerakalibrering. Ändra inte manuellt."
|
"description": "Kalibreringsvärden som genereras automatiskt vid kamerakalibrering. Ändra dem inte manuellt."
|
||||||
},
|
},
|
||||||
"enabled_in_config": {
|
"enabled_in_config": {
|
||||||
"label": "Ursprungligt autospårningsläge",
|
"label": "Ursprunglig inställning för automatisk spårning",
|
||||||
"description": "Internt fält för att spåra om automatisk spårning var aktiverad i konfigurationen."
|
"description": "Internt fält som anger om automatisk spårning var aktiverad i konfigurationen."
|
||||||
}
|
}
|
||||||
},
|
},
|
||||||
"ignore_time_mismatch": {
|
"ignore_time_mismatch": {
|
||||||
"label": "Ignorera tidsavvikelse",
|
"label": "Ignorera tidsavvikelse",
|
||||||
"description": "Ignorera skillnader i tidssynkronisering mellan kamera och fregattserver för ONVIF-kommunikation."
|
"description": "Ignorera skillnader i tidssynkronisering mellan kameran och Frigate-servern vid ONVIF-kommunikation."
|
||||||
}
|
}
|
||||||
},
|
},
|
||||||
"type": {
|
"type": {
|
||||||
|
|||||||
File diff suppressed because it is too large
Load Diff
@@ -27,28 +27,36 @@
|
|||||||
"description": "Поріг мінімального середньоквадратичного значення необхідний для запуску розпізнавання звуку; чим нижче значення, тим вища чутливість (наприклад, 200 — висока, 500 — середня, 1000 — низька)."
|
"description": "Поріг мінімального середньоквадратичного значення необхідний для запуску розпізнавання звуку; чим нижче значення, тим вища чутливість (наприклад, 200 — висока, 500 — середня, 1000 — низька)."
|
||||||
},
|
},
|
||||||
"num_threads": {
|
"num_threads": {
|
||||||
"description": "Поріг мінімального середньоквадратичного значення необхідний для запуску розпізнавання звуку; чим нижче значення, тим вища чутливість (наприклад, 200 — висока, 500 — середня, 1000 — низька)."
|
"description": "Поріг мінімального середньоквадратичного значення необхідний для запуску розпізнавання звуку; чим нижче значення, тим вища чутливість (наприклад, 200 — висока, 500 — середня, 1000 — низька).",
|
||||||
|
"label": "Потоки виявлення"
|
||||||
},
|
},
|
||||||
"listen": {
|
"listen": {
|
||||||
"description": "Список звукових подій для виявлення (наприклад, bark, fire_alarm, speech, yell).",
|
"description": "Список звукових подій для виявлення (наприклад, bark, fire_alarm, speech, yell).",
|
||||||
"label": "Типи звукових подій"
|
"label": "Типи звукових подій"
|
||||||
},
|
},
|
||||||
"filters": {
|
"filters": {
|
||||||
"label": "Фільтри звуку"
|
"label": "Фільтри звуку",
|
||||||
|
"description": "Налаштування фільтрів для кожного типу аудіо, наприклад пороги впевненості, що використовуються для зменшення кількості хибних спрацьовувань.",
|
||||||
|
"threshold": {
|
||||||
|
"description": "Мінімальний поріг впевненості, за якого аудіоподія буде зарахована."
|
||||||
|
}
|
||||||
},
|
},
|
||||||
"description": "Налаштування звукових подій для цієї камери.",
|
"description": "Налаштування звукових подій для цієї камери.",
|
||||||
"enabled_in_config": {
|
"enabled_in_config": {
|
||||||
"label": "Оригінальний стан аудіо"
|
"label": "Оригінальний стан аудіо",
|
||||||
|
"description": "Вказує, чи було виявлення аудіо початково ввімкнено у статичному файлі конфігурації."
|
||||||
}
|
}
|
||||||
},
|
},
|
||||||
"audio_transcription": {
|
"audio_transcription": {
|
||||||
"label": "Транскрипція аудіо",
|
"label": "Транскрипція аудіо",
|
||||||
"enabled": {
|
"enabled": {
|
||||||
"label": "Увімкнути транскрипцію"
|
"label": "Увімкнути транскрипцію",
|
||||||
|
"description": "Увімкнути або вимкнути транскрибування аудіоподій, запущених вручну."
|
||||||
},
|
},
|
||||||
"live_enabled": {
|
"live_enabled": {
|
||||||
"description": "Увімкнути потокову транскрипцію аудіо в реальному часі коли вона надходить."
|
"description": "Увімкнути потокову транскрипцію аудіо в реальному часі коли вона надходить."
|
||||||
}
|
},
|
||||||
|
"description": "Налаштування транскрибування аудіо в реальному часі та мовлення, що використовуються для подій і субтитрів наживо."
|
||||||
},
|
},
|
||||||
"birdseye": {
|
"birdseye": {
|
||||||
"label": "Режим Birdseye",
|
"label": "Режим Birdseye",
|
||||||
@@ -70,6 +78,21 @@
|
|||||||
"label": "Виявлення об'єктів",
|
"label": "Виявлення об'єктів",
|
||||||
"enabled": {
|
"enabled": {
|
||||||
"label": "Увімкнути виявлення об'єктів"
|
"label": "Увімкнути виявлення об'єктів"
|
||||||
|
},
|
||||||
|
"min_initialized": {
|
||||||
|
"label": "Мінімальна кількість кадрів для ініціалізації",
|
||||||
|
"description": "Кількість послідовних виявлень, необхідних для створення відстежуваного об’єкта. Збільште значення, щоб зменшити кількість хибних ініціалізацій. Типове значення — fps, поділене на 2."
|
||||||
|
},
|
||||||
|
"max_disappeared": {
|
||||||
|
"label": "Максимальна кількість кадрів, у яких об’єкт може бути відсутнім",
|
||||||
|
"description": "Кількість кадрів без виявлення, після якої відстежуваний об’єкт вважається зниклим."
|
||||||
|
},
|
||||||
|
"stationary": {
|
||||||
|
"label": "Налаштування нерухомих об’єктів",
|
||||||
|
"description": "Налаштування для виявлення та керування об’єктами, що залишаються нерухомими протягом певного часу.",
|
||||||
|
"interval": {
|
||||||
|
"label": "Інтервал перевірки нерухомості"
|
||||||
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -13,24 +13,31 @@
|
|||||||
"description": "Поріг мінімального середньоквадратичного значення необхідний для запуску розпізнавання звуку; чим нижче значення, тим вища чутливість (наприклад, 200 — висока, 500 — середня, 1000 — низька)."
|
"description": "Поріг мінімального середньоквадратичного значення необхідний для запуску розпізнавання звуку; чим нижче значення, тим вища чутливість (наприклад, 200 — висока, 500 — середня, 1000 — низька)."
|
||||||
},
|
},
|
||||||
"num_threads": {
|
"num_threads": {
|
||||||
"description": "Поріг мінімального середньоквадратичного значення необхідний для запуску розпізнавання звуку; чим нижче значення, тим вища чутливість (наприклад, 200 — висока, 500 — середня, 1000 — низька)."
|
"description": "Поріг мінімального середньоквадратичного значення необхідний для запуску розпізнавання звуку; чим нижче значення, тим вища чутливість (наприклад, 200 — висока, 500 — середня, 1000 — низька).",
|
||||||
|
"label": "Потоки виявлення"
|
||||||
},
|
},
|
||||||
"listen": {
|
"listen": {
|
||||||
"description": "Список звукових подій для виявлення (наприклад, bark, fire_alarm, speech, yell).",
|
"description": "Список звукових подій для виявлення (наприклад, bark, fire_alarm, speech, yell).",
|
||||||
"label": "Типи звукових подій"
|
"label": "Типи звукових подій"
|
||||||
},
|
},
|
||||||
"filters": {
|
"filters": {
|
||||||
"label": "Фільтри звуку"
|
"label": "Фільтри звуку",
|
||||||
|
"description": "Налаштування фільтрів для кожного типу аудіо, наприклад пороги впевненості, що використовуються для зменшення кількості хибних спрацьовувань.",
|
||||||
|
"threshold": {
|
||||||
|
"description": "Мінімальний поріг впевненості, за якого аудіоподія буде зарахована."
|
||||||
|
}
|
||||||
},
|
},
|
||||||
"enabled_in_config": {
|
"enabled_in_config": {
|
||||||
"label": "Оригінальний стан аудіо"
|
"label": "Оригінальний стан аудіо",
|
||||||
|
"description": "Вказує, чи було виявлення аудіо початково ввімкнено у статичному файлі конфігурації."
|
||||||
}
|
}
|
||||||
},
|
},
|
||||||
"audio_transcription": {
|
"audio_transcription": {
|
||||||
"label": "Транскрипція аудіо",
|
"label": "Транскрипція аудіо",
|
||||||
"live_enabled": {
|
"live_enabled": {
|
||||||
"description": "Увімкнути потокову транскрипцію аудіо в реальному часі коли вона надходить."
|
"description": "Увімкнути потокову транскрипцію аудіо в реальному часі коли вона надходить."
|
||||||
}
|
},
|
||||||
|
"description": "Налаштування транскрибування аудіо в реальному часі та мовлення, що використовуються для подій і субтитрів наживо."
|
||||||
},
|
},
|
||||||
"birdseye": {
|
"birdseye": {
|
||||||
"label": "Режим Birdseye",
|
"label": "Режим Birdseye",
|
||||||
@@ -52,6 +59,21 @@
|
|||||||
"label": "Виявлення об'єктів",
|
"label": "Виявлення об'єктів",
|
||||||
"enabled": {
|
"enabled": {
|
||||||
"label": "Увімкнути виявлення об'єктів"
|
"label": "Увімкнути виявлення об'єктів"
|
||||||
|
},
|
||||||
|
"min_initialized": {
|
||||||
|
"label": "Мінімальна кількість кадрів для ініціалізації",
|
||||||
|
"description": "Кількість послідовних виявлень, необхідних для створення відстежуваного об’єкта. Збільште значення, щоб зменшити кількість хибних ініціалізацій. Типове значення — fps, поділене на 2."
|
||||||
|
},
|
||||||
|
"max_disappeared": {
|
||||||
|
"label": "Максимальна кількість кадрів, у яких об’єкт може бути відсутнім",
|
||||||
|
"description": "Кількість кадрів без виявлення, після якої відстежуваний об’єкт вважається зниклим."
|
||||||
|
},
|
||||||
|
"stationary": {
|
||||||
|
"label": "Налаштування нерухомих об’єктів",
|
||||||
|
"description": "Налаштування для виявлення та керування об’єктами, що залишаються нерухомими протягом певного часу.",
|
||||||
|
"interval": {
|
||||||
|
"label": "Інтервал перевірки нерухомості"
|
||||||
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -71,8 +71,9 @@ export default function Statusbar() {
|
|||||||
|
|
||||||
useEffect(() => {
|
useEffect(() => {
|
||||||
if (reindexState) {
|
if (reindexState) {
|
||||||
if (reindexState.status == "indexing") {
|
clearMessages("embeddings-reindex");
|
||||||
clearMessages("embeddings-reindex");
|
|
||||||
|
if (reindexState.status === "indexing") {
|
||||||
addMessage(
|
addMessage(
|
||||||
"embeddings-reindex",
|
"embeddings-reindex",
|
||||||
t("stats.reindexingEmbeddings", {
|
t("stats.reindexingEmbeddings", {
|
||||||
@@ -82,9 +83,8 @@ export default function Statusbar() {
|
|||||||
),
|
),
|
||||||
}),
|
}),
|
||||||
);
|
);
|
||||||
}
|
} else if (reindexState.status === "failed") {
|
||||||
if (reindexState.status === "completed") {
|
addMessage("embeddings-reindex", t("stats.reindexEmbeddingsFailed"));
|
||||||
clearMessages("embeddings-reindex");
|
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
}, [reindexState, addMessage, clearMessages, t]);
|
}, [reindexState, addMessage, clearMessages, t]);
|
||||||
|
|||||||
@@ -1,12 +1,20 @@
|
|||||||
import { baseUrl } from "@/api/baseUrl";
|
import { baseUrl } from "@/api/baseUrl";
|
||||||
import useContextMenu from "@/hooks/use-contextmenu";
|
import useContextMenu from "@/hooks/use-contextmenu";
|
||||||
|
import { useOverlayState } from "@/hooks/use-overlay-state";
|
||||||
import { cn } from "@/lib/utils";
|
import { cn } from "@/lib/utils";
|
||||||
import {
|
import {
|
||||||
ClassificationItemData,
|
ClassificationItemData,
|
||||||
ClassificationThreshold,
|
ClassificationThreshold,
|
||||||
ClassifiedEvent,
|
ClassifiedEvent,
|
||||||
} from "@/types/classification";
|
} from "@/types/classification";
|
||||||
import { forwardRef, useEffect, useMemo, useRef, useState } from "react";
|
import {
|
||||||
|
forwardRef,
|
||||||
|
useEffect,
|
||||||
|
useImperativeHandle,
|
||||||
|
useMemo,
|
||||||
|
useRef,
|
||||||
|
useState,
|
||||||
|
} from "react";
|
||||||
import { isDesktop, isIOS, isMobile, isMobileOnly } from "react-device-detect";
|
import { isDesktop, isIOS, isMobile, isMobileOnly } from "react-device-detect";
|
||||||
import { useTranslation } from "react-i18next";
|
import { useTranslation } from "react-i18next";
|
||||||
import TimeAgo from "../dynamic/TimeAgo";
|
import TimeAgo from "../dynamic/TimeAgo";
|
||||||
@@ -16,6 +24,7 @@ import { LuSearch, LuInfo } from "react-icons/lu";
|
|||||||
import { TooltipPortal } from "@radix-ui/react-tooltip";
|
import { TooltipPortal } from "@radix-ui/react-tooltip";
|
||||||
import { useNavigate } from "react-router-dom";
|
import { useNavigate } from "react-router-dom";
|
||||||
import { HiSquare2Stack } from "react-icons/hi2";
|
import { HiSquare2Stack } from "react-icons/hi2";
|
||||||
|
import scrollIntoView from "scroll-into-view-if-needed";
|
||||||
import { ImageShadowOverlay } from "../overlay/ImageShadowOverlay";
|
import { ImageShadowOverlay } from "../overlay/ImageShadowOverlay";
|
||||||
import {
|
import {
|
||||||
Dialog,
|
Dialog,
|
||||||
@@ -85,9 +94,14 @@ export const ClassificationCard = forwardRef<
|
|||||||
|
|
||||||
// interaction
|
// interaction
|
||||||
|
|
||||||
|
const cardRef = useRef<HTMLDivElement | null>(null);
|
||||||
const imgRef = useRef<HTMLImageElement | null>(null);
|
const imgRef = useRef<HTMLImageElement | null>(null);
|
||||||
|
|
||||||
useContextMenu(imgRef, () => {
|
useImperativeHandle(ref, () => cardRef.current!);
|
||||||
|
|
||||||
|
// Listen on the whole card, since overlays cover most of the image
|
||||||
|
|
||||||
|
useContextMenu(cardRef, () => {
|
||||||
onClick(data, true);
|
onClick(data, true);
|
||||||
});
|
});
|
||||||
|
|
||||||
@@ -101,9 +115,9 @@ export const ClassificationCard = forwardRef<
|
|||||||
|
|
||||||
return (
|
return (
|
||||||
<div
|
<div
|
||||||
ref={ref}
|
ref={cardRef}
|
||||||
className={cn(
|
className={cn(
|
||||||
"relative flex size-full flex-col overflow-hidden rounded-lg outline outline-[3px]",
|
"relative flex size-full select-none flex-col overflow-hidden rounded-lg outline outline-[3px]",
|
||||||
className,
|
className,
|
||||||
selected
|
selected
|
||||||
? "shadow-selected outline-selected"
|
? "shadow-selected outline-selected"
|
||||||
@@ -117,11 +131,7 @@ export const ClassificationCard = forwardRef<
|
|||||||
}
|
}
|
||||||
onClick(data, isMeta);
|
onClick(data, isMeta);
|
||||||
}}
|
}}
|
||||||
onContextMenu={(e) => {
|
style={isIOS ? { WebkitTouchCallout: "none" } : undefined}
|
||||||
e.preventDefault();
|
|
||||||
e.stopPropagation();
|
|
||||||
onClick(data, true);
|
|
||||||
}}
|
|
||||||
>
|
>
|
||||||
<img
|
<img
|
||||||
ref={imgRef}
|
ref={imgRef}
|
||||||
@@ -130,14 +140,6 @@ export const ClassificationCard = forwardRef<
|
|||||||
imgClassName,
|
imgClassName,
|
||||||
isMobile && "w-full",
|
isMobile && "w-full",
|
||||||
)}
|
)}
|
||||||
style={
|
|
||||||
isIOS
|
|
||||||
? {
|
|
||||||
WebkitUserSelect: "none",
|
|
||||||
WebkitTouchCallout: "none",
|
|
||||||
}
|
|
||||||
: undefined
|
|
||||||
}
|
|
||||||
draggable={false}
|
draggable={false}
|
||||||
loading="lazy"
|
loading="lazy"
|
||||||
onLoad={() => setImageLoaded(true)}
|
onLoad={() => setImageLoaded(true)}
|
||||||
@@ -156,7 +158,7 @@ export const ClassificationCard = forwardRef<
|
|||||||
</div>
|
</div>
|
||||||
)}
|
)}
|
||||||
<div className="absolute bottom-0 left-0 right-0 h-[50%] bg-gradient-to-t from-black/60 to-transparent" />
|
<div className="absolute bottom-0 left-0 right-0 h-[50%] bg-gradient-to-t from-black/60 to-transparent" />
|
||||||
<div className="absolute bottom-0 flex w-full select-none flex-row items-center justify-between gap-2 p-2">
|
<div className="absolute bottom-0 flex w-full flex-row items-center justify-between gap-2 p-2">
|
||||||
<div
|
<div
|
||||||
className={cn(
|
className={cn(
|
||||||
"flex flex-col items-start text-white",
|
"flex flex-col items-start text-white",
|
||||||
@@ -216,6 +218,41 @@ export function GroupedClassificationCard({
|
|||||||
const { t } = useTranslation(["views/explore", i18nLibrary]);
|
const { t } = useTranslation(["views/explore", i18nLibrary]);
|
||||||
const [detailOpen, setDetailOpen] = useState(false);
|
const [detailOpen, setDetailOpen] = useState(false);
|
||||||
|
|
||||||
|
// Explore stores this event in history state so going back can point out the
|
||||||
|
// card the user came from
|
||||||
|
|
||||||
|
const cardRef = useRef<HTMLDivElement | null>(null);
|
||||||
|
const [returnEventId, setReturnEventId] = useOverlayState<string | undefined>(
|
||||||
|
"returnEventId",
|
||||||
|
);
|
||||||
|
const [highlighted, setHighlighted] = useState(false);
|
||||||
|
|
||||||
|
useEffect(() => {
|
||||||
|
if (!returnEventId || classifiedEvent?.id !== returnEventId) {
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
|
||||||
|
setReturnEventId(undefined, true);
|
||||||
|
setHighlighted(true);
|
||||||
|
}, [classifiedEvent?.id, returnEventId, setReturnEventId]);
|
||||||
|
|
||||||
|
useEffect(() => {
|
||||||
|
if (!highlighted) {
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
|
||||||
|
if (cardRef.current) {
|
||||||
|
scrollIntoView(cardRef.current, {
|
||||||
|
block: "center",
|
||||||
|
behavior: "smooth",
|
||||||
|
scrollMode: "if-needed",
|
||||||
|
});
|
||||||
|
}
|
||||||
|
|
||||||
|
const timeout = setTimeout(() => setHighlighted(false), 3000);
|
||||||
|
return () => clearTimeout(timeout);
|
||||||
|
}, [highlighted]);
|
||||||
|
|
||||||
// If the component unmounts while the detail overlay is open, we need to
|
// If the component unmounts while the detail overlay is open, we need to
|
||||||
// pop the history state that was pushed by useHistoryBack, otherwise it
|
// pop the history state that was pushed by useHistoryBack, otherwise it
|
||||||
// leaves a stale entry that breaks back navigation.
|
// leaves a stale entry that breaks back navigation.
|
||||||
@@ -308,9 +345,10 @@ export function GroupedClassificationCard({
|
|||||||
return (
|
return (
|
||||||
<>
|
<>
|
||||||
<ClassificationCard
|
<ClassificationCard
|
||||||
|
ref={cardRef}
|
||||||
data={bestItem}
|
data={bestItem}
|
||||||
threshold={threshold}
|
threshold={threshold}
|
||||||
selected={selectedItems.includes(bestItem.filename)}
|
selected={highlighted || selectedItems.includes(bestItem.filename)}
|
||||||
clickable={true}
|
clickable={true}
|
||||||
i18nLibrary={i18nLibrary}
|
i18nLibrary={i18nLibrary}
|
||||||
count={group.length}
|
count={group.length}
|
||||||
@@ -404,13 +442,19 @@ export function GroupedClassificationCard({
|
|||||||
isMobile && "absolute right-4 top-8",
|
isMobile && "absolute right-4 top-8",
|
||||||
)}
|
)}
|
||||||
>
|
>
|
||||||
<Tooltip>
|
<Tooltip open={isDesktop ? undefined : false}>
|
||||||
<TooltipTrigger asChild>
|
<TooltipTrigger asChild>
|
||||||
<div
|
<div
|
||||||
className="cursor-pointer"
|
className="cursor-pointer"
|
||||||
tabIndex={-1}
|
tabIndex={-1}
|
||||||
|
aria-label={t("details.item.button.viewInExplore", {
|
||||||
|
ns: "views/explore",
|
||||||
|
})}
|
||||||
onClick={() => {
|
onClick={() => {
|
||||||
navigate(`/explore?event_id=${classifiedEvent.id}`);
|
setReturnEventId(classifiedEvent.id, true);
|
||||||
|
navigate(`/explore?event_id=${classifiedEvent.id}`, {
|
||||||
|
state: { canGoBack: true },
|
||||||
|
});
|
||||||
}}
|
}}
|
||||||
>
|
>
|
||||||
<LuSearch className="size-4 text-secondary-foreground" />
|
<LuSearch className="size-4 text-secondary-foreground" />
|
||||||
|
|||||||
@@ -8,6 +8,23 @@ const notifications: SectionConfigOverrides = {
|
|||||||
fieldGroups: {},
|
fieldGroups: {},
|
||||||
hiddenFields: ["enabled_in_config"],
|
hiddenFields: ["enabled_in_config"],
|
||||||
advancedFields: [],
|
advancedFields: [],
|
||||||
|
fieldMessages: [
|
||||||
|
{
|
||||||
|
key: "profile-base-notifications-disabled",
|
||||||
|
field: "enabled",
|
||||||
|
messageKey: "configMessages.notifications.profileBaseDisabled",
|
||||||
|
severity: "warning",
|
||||||
|
position: "after",
|
||||||
|
condition: (ctx) =>
|
||||||
|
!!ctx.profileName &&
|
||||||
|
ctx.formData?.enabled === true &&
|
||||||
|
!Object.values(ctx.fullConfig.cameras).some(
|
||||||
|
(camera) =>
|
||||||
|
camera.enabled_in_config &&
|
||||||
|
camera.notifications.enabled_in_config,
|
||||||
|
),
|
||||||
|
},
|
||||||
|
],
|
||||||
},
|
},
|
||||||
global: {
|
global: {
|
||||||
uiSchema: {
|
uiSchema: {
|
||||||
|
|||||||
@@ -16,6 +16,21 @@ const record: SectionConfigOverrides = {
|
|||||||
},
|
},
|
||||||
},
|
},
|
||||||
],
|
],
|
||||||
|
fieldMessages: [
|
||||||
|
{
|
||||||
|
key: "profile-base-record-disabled",
|
||||||
|
field: "enabled",
|
||||||
|
messageKey: "configMessages.record.profileBaseDisabled",
|
||||||
|
severity: "warning",
|
||||||
|
position: "after",
|
||||||
|
docLink:
|
||||||
|
"/configuration/profiles#why-cant-a-profile-enable-recording-when-its-disabled-in-the-base-config",
|
||||||
|
condition: (ctx) =>
|
||||||
|
!!ctx.profileName &&
|
||||||
|
ctx.formData?.enabled === true &&
|
||||||
|
ctx.fullCameraConfig?.record.enabled_in_config === false,
|
||||||
|
},
|
||||||
|
],
|
||||||
fieldDocs: {
|
fieldDocs: {
|
||||||
"alerts.pre_capture":
|
"alerts.pre_capture":
|
||||||
"/configuration/record#pre-capture-and-post-capture",
|
"/configuration/record#pre-capture-and-post-capture",
|
||||||
|
|||||||
@@ -8,6 +8,7 @@ export type MessageConditionContext = {
|
|||||||
fullCameraConfig?: CameraConfig;
|
fullCameraConfig?: CameraConfig;
|
||||||
level: "global" | "camera";
|
level: "global" | "camera";
|
||||||
cameraName?: string;
|
cameraName?: string;
|
||||||
|
profileName?: string;
|
||||||
formData: ConfigSectionData;
|
formData: ConfigSectionData;
|
||||||
};
|
};
|
||||||
|
|
||||||
|
|||||||
@@ -619,9 +619,10 @@ export function ConfigSection({
|
|||||||
: undefined,
|
: undefined,
|
||||||
level: effectiveLevel,
|
level: effectiveLevel,
|
||||||
cameraName,
|
cameraName,
|
||||||
|
profileName,
|
||||||
formData: currentFormData as ConfigSectionData,
|
formData: currentFormData as ConfigSectionData,
|
||||||
};
|
};
|
||||||
}, [config, currentFormData, effectiveLevel, cameraName]);
|
}, [config, currentFormData, effectiveLevel, cameraName, profileName]);
|
||||||
|
|
||||||
const { activeMessages, activeFieldMessages } = useConfigMessages(
|
const { activeMessages, activeFieldMessages } = useConfigMessages(
|
||||||
sectionConfig.messages,
|
sectionConfig.messages,
|
||||||
|
|||||||
@@ -133,8 +133,9 @@ function StatusAlertNav({ className, large }: StatusAlertNavProps) {
|
|||||||
|
|
||||||
useEffect(() => {
|
useEffect(() => {
|
||||||
if (reindexState) {
|
if (reindexState) {
|
||||||
if (reindexState.status == "indexing") {
|
clearMessages("embeddings-reindex");
|
||||||
clearMessages("embeddings-reindex");
|
|
||||||
|
if (reindexState.status === "indexing") {
|
||||||
addMessage(
|
addMessage(
|
||||||
"embeddings-reindex",
|
"embeddings-reindex",
|
||||||
t("stats.reindexingEmbeddings", {
|
t("stats.reindexingEmbeddings", {
|
||||||
@@ -144,9 +145,8 @@ function StatusAlertNav({ className, large }: StatusAlertNavProps) {
|
|||||||
),
|
),
|
||||||
}),
|
}),
|
||||||
);
|
);
|
||||||
}
|
} else if (reindexState.status === "failed") {
|
||||||
if (reindexState.status === "completed") {
|
addMessage("embeddings-reindex", t("stats.reindexEmbeddingsFailed"));
|
||||||
clearMessages("embeddings-reindex");
|
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
}, [reindexState, addMessage, clearMessages, t]);
|
}, [reindexState, addMessage, clearMessages, t]);
|
||||||
|
|||||||
@@ -1240,10 +1240,14 @@ function ObjectDetailsTab({
|
|||||||
search?.plus_id ? "submitted" : "reviewing",
|
search?.plus_id ? "submitted" : "reviewing",
|
||||||
);
|
);
|
||||||
|
|
||||||
useEffect(
|
// a submission request outlives the object it was made for, so the
|
||||||
() => setState(search?.plus_id ? "submitted" : "reviewing"),
|
// response handler needs to know which object is on screen now
|
||||||
[search],
|
const displayedIdRef = useRef(search?.id);
|
||||||
);
|
|
||||||
|
useEffect(() => {
|
||||||
|
displayedIdRef.current = search?.id;
|
||||||
|
setState(search?.plus_id ? "submitted" : "reviewing");
|
||||||
|
}, [search]);
|
||||||
|
|
||||||
const onSubmitToPlus = useCallback(
|
const onSubmitToPlus = useCallback(
|
||||||
async (falsePositive: boolean) => {
|
async (falsePositive: boolean) => {
|
||||||
@@ -1251,10 +1255,12 @@ function ObjectDetailsTab({
|
|||||||
return;
|
return;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
const eventId = search.id;
|
||||||
|
|
||||||
try {
|
try {
|
||||||
const resp = falsePositive
|
const resp = falsePositive
|
||||||
? await axios.put(`events/${search.id}/false_positive`)
|
? await axios.put(`events/${eventId}/false_positive`)
|
||||||
: await axios.post(`events/${search.id}/plus`, {
|
: await axios.post(`events/${eventId}/plus`, {
|
||||||
include_annotation: 1,
|
include_annotation: 1,
|
||||||
});
|
});
|
||||||
|
|
||||||
@@ -1262,12 +1268,15 @@ function ObjectDetailsTab({
|
|||||||
throw new Error();
|
throw new Error();
|
||||||
}
|
}
|
||||||
|
|
||||||
setState("submitted");
|
if (displayedIdRef.current === eventId) {
|
||||||
|
setState("submitted");
|
||||||
|
}
|
||||||
|
|
||||||
mutate(
|
mutate(
|
||||||
(key) => isEventsKey(key),
|
(key) => isEventsKey(key),
|
||||||
(currentData: SearchResult[][] | SearchResult[] | undefined) =>
|
(currentData: SearchResult[][] | SearchResult[] | undefined) =>
|
||||||
mapSearchResults(currentData, (event) =>
|
mapSearchResults(currentData, (event) =>
|
||||||
event.id === search.id
|
event.id === eventId
|
||||||
? { ...event, plus_id: "new_upload" }
|
? { ...event, plus_id: "new_upload" }
|
||||||
: event,
|
: event,
|
||||||
),
|
),
|
||||||
@@ -1278,7 +1287,12 @@ function ObjectDetailsTab({
|
|||||||
},
|
},
|
||||||
);
|
);
|
||||||
} catch {
|
} catch {
|
||||||
setState("reviewing");
|
if (displayedIdRef.current === eventId) {
|
||||||
|
setState("reviewing");
|
||||||
|
}
|
||||||
|
|
||||||
|
// the toast is not object specific, so it is always shown to avoid
|
||||||
|
// silently dropping a failed submission
|
||||||
toast.error(
|
toast.error(
|
||||||
t("explore.plus.review.toast.error", { ns: "components/dialog" }),
|
t("explore.plus.review.toast.error", { ns: "components/dialog" }),
|
||||||
{ position: "top-center" },
|
{ position: "top-center" },
|
||||||
|
|||||||
@@ -845,6 +845,7 @@ export function TrackingDetails({
|
|||||||
<div className="text-sm text-secondary-foreground">
|
<div className="text-sm text-secondary-foreground">
|
||||||
<Link
|
<Link
|
||||||
to={`/explore?recognized_license_plate=${event.data.recognized_license_plate}`}
|
to={`/explore?recognized_license_plate=${event.data.recognized_license_plate}`}
|
||||||
|
state={{ canGoBack: true }}
|
||||||
className="text-sm"
|
className="text-sm"
|
||||||
>
|
>
|
||||||
{event.data.recognized_license_plate}
|
{event.data.recognized_license_plate}
|
||||||
|
|||||||
@@ -727,6 +727,7 @@ function EventList({
|
|||||||
<div className="text-sm text-secondary-foreground">
|
<div className="text-sm text-secondary-foreground">
|
||||||
<Link
|
<Link
|
||||||
to={`/explore?recognized_license_plate=${event.data.recognized_license_plate}`}
|
to={`/explore?recognized_license_plate=${event.data.recognized_license_plate}`}
|
||||||
|
state={{ canGoBack: true }}
|
||||||
className="text-sm"
|
className="text-sm"
|
||||||
>
|
>
|
||||||
{event.data.recognized_license_plate}
|
{event.data.recognized_license_plate}
|
||||||
|
|||||||
Some files were not shown because too many files have changed in this diff Show More
Reference in New Issue
Block a user