mirror of
https://github.com/blakeblackshear/frigate.git
synced 2026-10-01 04:16:50 +03:00
Compare commits
18
Commits
@@ -67,6 +67,12 @@ This section can be used to set environment variables for those unable to modify
|
||||
|
||||
Variables prefixed with `FRIGATE_` can be referenced in config fields that support environment variable substitution (such as MQTT host and credentials, camera stream URLs, and ONVIF host and credentials) using the `{FRIGATE_VARIABLE_NAME}` syntax.
|
||||
|
||||
:::note
|
||||
|
||||
The `go2rtc` section is an exception. go2rtc runs as a separate process, so its stream definitions can only be substituted with variables that exist in the container's environment (set via Docker `-e`, the `environment:` section of `docker-compose.yml`, or Docker secrets). Variables defined in the `environment_vars` block above are not available to go2rtc streams. Home Assistant app users, who cannot set container environment variables, must instead put credentials directly in their go2rtc stream URLs.
|
||||
|
||||
:::
|
||||
|
||||
<ConfigTabs>
|
||||
<TabItem value="ui">
|
||||
|
||||
|
||||
+121
-65
@@ -6,6 +6,7 @@ title: Live View
|
||||
import ConfigTabs from "@site/src/components/ConfigTabs";
|
||||
import TabItem from "@theme/TabItem";
|
||||
import NavPath from "@site/src/components/NavPath";
|
||||
import FaqItem from "@site/src/components/FaqItem";
|
||||
|
||||
Frigate intelligently displays your camera streams on the Live view dashboard. By default, Frigate employs "smart streaming" where camera images update once per minute when no detectable activity is occurring to conserve bandwidth and resources. As soon as any motion or active objects are detected, cameras seamlessly switch to a live stream.
|
||||
|
||||
@@ -341,100 +342,155 @@ When your browser runs into problems playing back your camera streams, it will l
|
||||
|
||||
## Live view FAQ
|
||||
|
||||
1. **Why don't I have audio in my Live view?**
|
||||
### Getting Live View Working
|
||||
|
||||
You must use go2rtc to hear audio in your live streams. If you have go2rtc already configured, you need to ensure your camera is sending PCMA/PCMU or AAC audio. If you can't change your camera's audio codec, you need to [transcode the audio](https://github.com/AlexxIT/go2rtc?tab=readme-ov-file#source-ffmpeg) using go2rtc.
|
||||
<FaqItem id="why-dont-i-have-audio-in-my-live-view" question="Why don't I have audio in my Live view?">
|
||||
|
||||
Note that the low bandwidth mode player is a video-only stream. You should not expect to hear audio when in low bandwidth mode, even if you've set up go2rtc.
|
||||
You must use go2rtc to hear audio in your live streams. If you have go2rtc already configured, you need to ensure your camera is sending PCMA/PCMU or AAC audio. If you can't change your camera's audio codec, you need to [transcode the audio](https://github.com/AlexxIT/go2rtc?tab=readme-ov-file#source-ffmpeg) using go2rtc.
|
||||
|
||||
2. **Frigate shows that my live stream is in "low bandwidth mode". What does this mean?**
|
||||
If the audio controls don't appear in the UI at all, verify that the Live view is actually using your go2rtc stream. If your go2rtc stream names don't match your Frigate camera name, you must map them with the `live -> streams` config (see [Setting Streams For Live UI](#setting-streams-for-live-ui) above); otherwise the UI falls back to the video-only jsmpeg player.
|
||||
|
||||
Frigate intelligently selects the live streaming technology based on a number of factors (user-selected modes like two-way talk, camera settings, browser capabilities, available bandwidth) and prioritizes showing an actual up-to-date live view of your camera's stream as quickly as possible.
|
||||
Note that the low bandwidth mode player is a video-only stream. You should not expect to hear audio when in low bandwidth mode, even if you've set up go2rtc.
|
||||
|
||||
When you have go2rtc configured, Live view initially attempts to load and play back your stream with a clearer, fluent stream technology (MSE). An initial timeout, a low bandwidth condition that would cause buffering of the stream, or decoding errors in the stream will cause Frigate to switch to the stream defined by the `detect` role, using the jsmpeg format. This is what the UI labels as "low bandwidth mode". On Live dashboards, the mode will automatically reset when smart streaming is configured and activity stops. Continuous streaming mode does not have an automatic reset mechanism, but you can use the _Reset_ option to force a reload of your stream.
|
||||
</FaqItem>
|
||||
|
||||
If you are using continuous streaming or you are loading more than a few high resolution streams at once on the dashboard, your browser may struggle to begin playback of your streams before the timeout. Frigate always prioritizes showing a live stream as quickly as possible, even if it is a lower quality jsmpeg stream. You can use the "Reset" link/button to try loading your high resolution stream again.
|
||||
<FaqItem id="i-have-unmuted-some-cameras-on-my-dashboard-but-i-do-not-hear-sound-why" question="I have unmuted some cameras on my dashboard, but I do not hear sound. Why?">
|
||||
|
||||
Errors in stream playback (e.g., connection failures, codec issues, or buffering timeouts) that cause the fallback to low bandwidth mode (jsmpeg) are logged to the browser console for easier debugging. These errors may include:
|
||||
- Network issues (e.g., MSE or WebRTC network connection problems).
|
||||
- Unsupported codecs or stream formats (e.g., H.265 in WebRTC, which is not supported in some browsers).
|
||||
- Buffering timeouts or low bandwidth conditions causing fallback to jsmpeg.
|
||||
- Browser compatibility problems (e.g., iOS Safari limitations with MSE).
|
||||
If your camera is streaming (as indicated by a red dot in the upper right, or if it has been set to continuous streaming mode), your browser may be blocking audio until you interact with the page. This is an intentional browser limitation. See [this article](https://developer.mozilla.org/en-US/docs/Web/Media/Autoplay_guide#autoplay_availability). Many browsers have a whitelist feature to change this behavior.
|
||||
|
||||
To view browser console logs:
|
||||
1. Open the Frigate Live View in your browser.
|
||||
2. Open the browser's Developer Tools (F12 or right-click > Inspect > Console tab).
|
||||
3. Reproduce the error (e.g., load a problematic stream or simulate network issues).
|
||||
4. Look for messages prefixed with the camera name.
|
||||
</FaqItem>
|
||||
|
||||
These logs help identify if the issue is player-specific (MSE vs. WebRTC) or related to camera configuration (e.g., go2rtc streams, codecs). If you see frequent errors:
|
||||
- Verify your camera's H.264/AAC settings (see [Frigate's camera settings recommendations](#camera-settings-recommendations)).
|
||||
- Check go2rtc configuration for transcoding (e.g., audio to AAC/OPUS).
|
||||
- Test with a different stream via the UI dropdown (if `live -> streams` is configured).
|
||||
- For WebRTC-specific issues, ensure port 8555 is forwarded and candidates are set (see [WebRTC Extra Configuration](#webrtc-extra-configuration)).
|
||||
- If your cameras are streaming at a high resolution, your browser may be struggling to load all of the streams before the buffering timeout occurs. Frigate prioritizes showing a true live view as quickly as possible. If the fallback occurs often, change your live view settings to use a lower bandwidth substream.
|
||||
<FaqItem id="my-live-view-shows-a-black-screen-or-doesnt-load-but-the-debug-view-works-why" question="My live view shows a black screen or doesn't load, but the debug view works. Why?">
|
||||
|
||||
3. **It doesn't seem like my cameras are streaming on the Live dashboard. Why?**
|
||||
The debug view plays the `detect` stream processed by Frigate itself, while the Live view plays your go2rtc stream directly in the browser. If the debug view works but the Live view doesn't, your browser usually can't decode what the camera is sending, most often H.265 video or an incompatible audio track.
|
||||
|
||||
On the default Live dashboard ("All Cameras"), your camera images will update once per minute when no detectable activity is occurring to conserve bandwidth and resources. As soon as any activity is detected, cameras seamlessly switch to a full-resolution live stream. If you want to customize this behavior, use a camera group.
|
||||
Work through the [go2rtc troubleshooting guide](/troubleshooting/go2rtc#live-view-is-black-buffering-or-stuck-in-low-bandwidth-mode) to isolate the problem. Two fixes resolve the majority of cases:
|
||||
|
||||
4. **I see a strange diagonal line on my live view, but my recordings look fine. How can I fix it?**
|
||||
1. Restream through go2rtc's FFmpeg module by prefixing your source with `ffmpeg:`, for example `- ffmpeg:rtsp://user:password@192.168.1.5:554/stream`.
|
||||
2. If that doesn't help, transcode to compatible codecs: `- ffmpeg:rtsp://user:password@192.168.1.5:554/stream#video=h264#audio=aac#hardware`.
|
||||
|
||||
This is caused by incorrect dimensions set in your detect width or height (or incorrectly auto-detected), causing the jsmpeg player's rendering engine to display a slightly distorted image. You should enlarge the width and height of your `detect` resolution up to a standard aspect ratio (example: 640x352 becomes 640x360, and 800x443 becomes 800x450, 2688x1520 becomes 2688x1512, etc). If changing the resolution to match a standard (4:3, 16:9, or 32:9, etc) aspect ratio does not solve the issue, you can enable "compatibility mode" in your camera group dashboard's stream settings. Depending on your browser and device, more than a few cameras in compatibility mode may not be supported, so only use this option if changing your `detect` width and height fails to resolve the color artifacts and diagonal line.
|
||||
</FaqItem>
|
||||
|
||||
5. **How does "smart streaming" work?**
|
||||
<FaqItem id="how-do-i-get-the-best-live-view-experience-in-home-assistant" question="How do I get the best live view experience in Home Assistant?">
|
||||
|
||||
Because a static image of a scene looks exactly the same as a live stream with no motion or activity, smart streaming updates your camera images once per minute when no detectable activity is occurring to conserve bandwidth and resources. As soon as any activity (motion or object/audio detection) occurs, cameras seamlessly switch to a live stream.
|
||||
For a full-resolution, low-latency live view in Home Assistant dashboards, use the [Advanced Camera Card](https://card.camera) with the [go2rtc live provider](https://card.camera/#/configuration/cameras/live-provider?id=go2rtc), which streams directly from Frigate's bundled go2rtc. This also supports audio and [two-way talk](#two-way-talk) on capable cameras. See the [Home Assistant integration docs](/integrations/home-assistant) for setup.
|
||||
|
||||
This static image is pulled from the stream defined in your config with the `detect` role. When activity is detected, images from the `detect` stream immediately begin updating at ~5 frames per second so you can see the activity until the live player is loaded and begins playing. This usually only takes a second or two. If the live player times out, buffers, or has streaming errors, the jsmpeg player is loaded and plays a video-only stream from the `detect` role. When activity ends, the players are destroyed and a static image is displayed until activity is detected again, and the process repeats.
|
||||
</FaqItem>
|
||||
|
||||
Smart streaming depends on having your camera's motion `threshold` and `contour_area` config values dialed in. Use the Motion Tuner in Settings in the UI to tune these values in real-time.
|
||||
### Streaming Behavior
|
||||
|
||||
This is Frigate's default and recommended setting because it results in a significant bandwidth savings, especially for high resolution cameras.
|
||||
<FaqItem id="how-does-smart-streaming-work" question={'How does "smart streaming" work?'}>
|
||||
|
||||
6. **I have unmuted some cameras on my dashboard, but I do not hear sound. Why?**
|
||||
Because a static image of a scene looks exactly the same as a live stream with no motion or activity, smart streaming updates your camera images once per minute when no detectable activity is occurring to conserve bandwidth and resources. As soon as any activity (motion or object/audio detection) occurs, cameras seamlessly switch to a live stream.
|
||||
|
||||
If your camera is streaming (as indicated by a red dot in the upper right, or if it has been set to continuous streaming mode), your browser may be blocking audio until you interact with the page. This is an intentional browser limitation. See [this article](https://developer.mozilla.org/en-US/docs/Web/Media/Autoplay_guide#autoplay_availability). Many browsers have a whitelist feature to change this behavior.
|
||||
This static image is pulled from the stream defined in your config with the `detect` role. When activity is detected, images from the `detect` stream immediately begin updating at ~5 frames per second so you can see the activity until the live player is loaded and begins playing. This usually only takes a second or two. If the live player times out, buffers, or has streaming errors, the jsmpeg player is loaded and plays a video-only stream from the `detect` role. When activity ends, the players are destroyed and a static image is displayed until activity is detected again, and the process repeats.
|
||||
|
||||
7. **My camera streams have lots of visual artifacts / distortion.**
|
||||
Smart streaming depends on having your camera's motion `threshold` and `contour_area` config values dialed in. Use the Motion Tuner in Settings in the UI to tune these values in real-time.
|
||||
|
||||
Some cameras don't include the hardware to support multiple connections to the high resolution stream, and this can cause unexpected behavior. In this case it is recommended to [restream](./restream.md) the high resolution stream so that it can be used for live view and recordings.
|
||||
This is Frigate's default and recommended setting because it results in a significant bandwidth savings, especially for high resolution cameras.
|
||||
|
||||
8. **Why does my camera stream switch aspect ratios on the Live dashboard?**
|
||||
</FaqItem>
|
||||
|
||||
Your camera may change aspect ratios on the dashboard because Frigate uses different streams for different purposes. With go2rtc and Smart Streaming, Frigate shows a static image from the `detect` stream when no activity is present, and switches to the live stream when motion is detected. The camera image will change size if your streams use different aspect ratios.
|
||||
<FaqItem id="it-doesnt-seem-like-my-cameras-are-streaming-on-the-live-dashboard-why" question="It doesn't seem like my cameras are streaming on the Live dashboard. Why?">
|
||||
|
||||
To prevent this, make the `detect` stream match the go2rtc live stream's aspect ratio (resolution does not need to match, just the aspect ratio). You can either adjust the camera's output resolution or set the `width` and `height` values in your config's `detect` section to a resolution with an aspect ratio that matches.
|
||||
On the default Live dashboard ("All Cameras"), your camera images will update once per minute when no detectable activity is occurring to conserve bandwidth and resources. As soon as any activity is detected, cameras seamlessly switch to a full-resolution live stream. If you want to customize this behavior, use a camera group.
|
||||
|
||||
Example: Resolutions from two streams
|
||||
- Mismatched (may cause aspect ratio switching on the dashboard):
|
||||
- Live/go2rtc stream: 1920x1080 (16:9)
|
||||
- Detect stream: 640x352 (~1.82:1, not 16:9)
|
||||
</FaqItem>
|
||||
|
||||
- Matched (prevents switching):
|
||||
- Live/go2rtc stream: 1920x1080 (16:9)
|
||||
- Detect stream: 640x360 (16:9)
|
||||
<FaqItem id="frigate-shows-that-my-live-stream-is-in-low-bandwidth-mode-what-does-this-mean" question={'Frigate shows that my live stream is in "low bandwidth mode". What does this mean?'}>
|
||||
|
||||
You can update the detect settings in your camera config to match the aspect ratio of your go2rtc live stream. For example:
|
||||
Frigate intelligently selects the live streaming technology based on a number of factors (user-selected modes like two-way talk, camera settings, browser capabilities, available bandwidth) and prioritizes showing an actual up-to-date live view of your camera's stream as quickly as possible.
|
||||
|
||||
```yaml
|
||||
cameras:
|
||||
front_door:
|
||||
detect:
|
||||
width: 640
|
||||
height: 360 # set this to 360 instead of 352
|
||||
ffmpeg:
|
||||
inputs:
|
||||
- path: rtsp://127.0.0.1:8554/front_door # main stream 1920x1080
|
||||
roles:
|
||||
- record
|
||||
- path: rtsp://127.0.0.1:8554/front_door_sub # sub stream 640x352
|
||||
roles:
|
||||
- detect
|
||||
```
|
||||
When you have go2rtc configured, Live view initially attempts to load and play back your stream with a clearer, fluent stream technology (MSE). An initial timeout, a low bandwidth condition that would cause buffering of the stream, or decoding errors in the stream will cause Frigate to switch to the stream defined by the `detect` role, using the jsmpeg format. This is what the UI labels as "low bandwidth mode". On Live dashboards, the mode will automatically reset when smart streaming is configured and activity stops. Continuous streaming mode does not have an automatic reset mechanism, but you can use the _Reset_ option to force a reload of your stream.
|
||||
|
||||
The same applies to your `record` stream: if its aspect ratio differs from your `detect` stream, your recordings will appear in a different shape than the live view. For consistent framing across live view and recordings, use the same aspect ratio for all of a camera's streams (the resolution can still differ).
|
||||
If you are using continuous streaming or you are loading more than a few high resolution streams at once on the dashboard, your browser may struggle to begin playback of your streams before the timeout. Frigate always prioritizes showing a live stream as quickly as possible, even if it is a lower quality jsmpeg stream. You can use the "Reset" link/button to try loading your high resolution stream again.
|
||||
|
||||
9. **Why does Frigate prefer MSE over WebRTC for live view?**
|
||||
Errors in stream playback (e.g., connection failures, codec issues, or buffering timeouts) that cause the fallback to low bandwidth mode (jsmpeg) are logged to the browser console for easier debugging. These errors may include:
|
||||
|
||||
Frigate prefers MSE because it delivers a better out-of-the-box experience than WebRTC on nearly every axis that matters for a security camera system. MSE is an open standard optimized and supported by all modern browsers, works without any extra configuration (WebRTC requires port forwarding and candidate setup, and lacks H.265 support in some browsers), and requires no internet access for NAT traversal. More importantly, MSE runs over TCP, so every frame arrives and is decoded in order, so nothing is ever silently skipped. WebRTC optimizes for latency over UDP by discarding late or incomplete frames, which works against you on cellular or spotty Wi-Fi: you can end up with frozen video, visual corruption, or gaps in the feed without ever knowing you missed something. Frigate's enhanced MSE player has adaptive speed playback and has been tuned for latency and connection robustness that meets or exceeds WebRTC, so you get near-real-time playback with a guarantee that when the video plays, every frame is actually there - which, for an NVR whose whole purpose is letting you see what happened, matters more than shaving fractions of a second off a latency number. That's why Frigate defaults to MSE and reserves WebRTC for cases that require it, like two-way talk.
|
||||
- Network issues (e.g., MSE or WebRTC network connection problems).
|
||||
- Unsupported codecs or stream formats (e.g., H.265 in WebRTC, which is not supported in some browsers).
|
||||
- Buffering timeouts or low bandwidth conditions causing fallback to jsmpeg.
|
||||
- Browser compatibility problems (e.g., iOS Safari limitations with MSE).
|
||||
|
||||
To view browser console logs:
|
||||
|
||||
1. Open the Frigate Live View in your browser.
|
||||
2. Open the browser's Developer Tools (F12 or right-click > Inspect > Console tab).
|
||||
3. Reproduce the error (e.g., load a problematic stream or simulate network issues).
|
||||
4. Look for messages prefixed with the camera name.
|
||||
|
||||
These logs help identify if the issue is player-specific (MSE vs. WebRTC) or related to camera configuration (e.g., go2rtc streams, codecs). If you see frequent errors:
|
||||
|
||||
- Verify your camera's H.264/AAC settings (see [Frigate's camera settings recommendations](#camera-settings-recommendations)).
|
||||
- Check go2rtc configuration for transcoding (e.g., audio to AAC/OPUS).
|
||||
- Test with a different stream via the UI dropdown (if `live -> streams` is configured).
|
||||
- For WebRTC-specific issues, ensure port 8555 is forwarded and candidates are set (see [WebRTC Extra Configuration](#webrtc-extra-configuration)).
|
||||
- If your cameras are streaming at a high resolution, your browser may be struggling to load all of the streams before the buffering timeout occurs. Frigate prioritizes showing a true live view as quickly as possible. If the fallback occurs often, change your live view settings to use a lower bandwidth substream.
|
||||
|
||||
</FaqItem>
|
||||
|
||||
<FaqItem id="why-is-my-live-view-delayed-or-lagging-behind-real-time" question="Why is my live view delayed or lagging behind real time?">
|
||||
|
||||
A delay when a stream first starts is usually caused by your camera's I-frame (keyframe) interval. Playback cannot begin until a keyframe arrives, so an interval set higher than your camera's frame rate makes the stream take longer to start. Set the I-frame interval to match the frame rate (or "1x" on Reolink) per the [camera settings recommendations](#camera-settings-recommendations).
|
||||
|
||||
A stream that starts on time but falls further behind live is buffering, which is usually the browser struggling to decode too many high-resolution streams at once. Select a lower-bandwidth substream for your dashboards (see [Setting Streams For Live UI](#setting-streams-for-live-ui)), reduce the number of streams open at once, or improve the network connection between your browser and Frigate. Frigate's player automatically speeds up playback to catch up to live after buffering, and falls back to low bandwidth mode if it stalls for too long. The _Reset_ option forces a fresh connection at the live edge.
|
||||
|
||||
</FaqItem>
|
||||
|
||||
<FaqItem id="why-does-frigate-prefer-mse-over-webrtc-for-live-view" question="Why does Frigate prefer MSE over WebRTC for live view?">
|
||||
|
||||
Frigate prefers MSE because it delivers a better out-of-the-box experience than WebRTC on nearly every axis that matters for a security camera system. MSE is an open standard optimized and supported by all modern browsers, works without any extra configuration (WebRTC requires port forwarding and candidate setup, and lacks H.265 support in some browsers), and requires no internet access for NAT traversal. More importantly, MSE runs over TCP, so every frame arrives and is decoded in order, so nothing is ever silently skipped. WebRTC optimizes for latency over UDP by discarding late or incomplete frames, which works against you on cellular or spotty Wi-Fi: you can end up with frozen video, visual corruption, or gaps in the feed without ever knowing you missed something. Frigate's enhanced MSE player has adaptive speed playback and has been tuned for latency and connection robustness that meets or exceeds WebRTC, so you get near-real-time playback with a guarantee that when the video plays, every frame is actually there - which, for an NVR whose whole purpose is letting you see what happened, matters more than shaving fractions of a second off a latency number. That's why Frigate defaults to MSE and reserves WebRTC for cases that require it, like two-way talk.
|
||||
|
||||
</FaqItem>
|
||||
|
||||
### Video Quality Issues
|
||||
|
||||
<FaqItem id="i-see-a-strange-diagonal-line-on-my-live-view-but-my-recordings-look-fine-how-can-i-fix-it" question="I see a strange diagonal line on my live view, but my recordings look fine. How can I fix it?">
|
||||
|
||||
This is caused by incorrect dimensions set in your detect width or height (or incorrectly auto-detected), causing the jsmpeg player's rendering engine to display a slightly distorted image. You should enlarge the width and height of your `detect` resolution up to a standard aspect ratio (example: 640x352 becomes 640x360, and 800x443 becomes 800x450, 2688x1520 becomes 2688x1512, etc). If changing the resolution to match a standard (4:3, 16:9, or 32:9, etc) aspect ratio does not solve the issue, you can enable "compatibility mode" in your camera group dashboard's stream settings. Depending on your browser and device, more than a few cameras in compatibility mode may not be supported, so only use this option if changing your `detect` width and height fails to resolve the color artifacts and diagonal line.
|
||||
|
||||
</FaqItem>
|
||||
|
||||
<FaqItem id="my-camera-streams-have-lots-of-visual-artifacts-or-distortion" question="My camera streams have lots of visual artifacts / distortion.">
|
||||
|
||||
Some cameras don't include the hardware to support multiple connections to the high resolution stream, and this can cause unexpected behavior. In this case it is recommended to [restream](./restream.md) the high resolution stream so that it can be used for live view and recordings.
|
||||
|
||||
</FaqItem>
|
||||
|
||||
<FaqItem id="why-does-my-camera-stream-switch-aspect-ratios-on-the-live-dashboard" question="Why does my camera stream switch aspect ratios on the Live dashboard?">
|
||||
|
||||
Your camera may change aspect ratios on the dashboard because Frigate uses different streams for different purposes. With go2rtc and Smart Streaming, Frigate shows a static image from the `detect` stream when no activity is present, and switches to the live stream when motion is detected. The camera image will change size if your streams use different aspect ratios.
|
||||
|
||||
To prevent this, make the `detect` stream match the go2rtc live stream's aspect ratio (resolution does not need to match, just the aspect ratio). You can either adjust the camera's output resolution or set the `width` and `height` values in your config's `detect` section to a resolution with an aspect ratio that matches.
|
||||
|
||||
Example: Resolutions from two streams
|
||||
|
||||
- Mismatched (may cause aspect ratio switching on the dashboard):
|
||||
- Live/go2rtc stream: 1920x1080 (16:9)
|
||||
- Detect stream: 640x352 (~1.82:1, not 16:9)
|
||||
|
||||
- Matched (prevents switching):
|
||||
- Live/go2rtc stream: 1920x1080 (16:9)
|
||||
- Detect stream: 640x360 (16:9)
|
||||
|
||||
You can update the detect settings in your camera config to match the aspect ratio of your go2rtc live stream. For example:
|
||||
|
||||
```yaml
|
||||
cameras:
|
||||
front_door:
|
||||
detect:
|
||||
width: 640
|
||||
height: 360 # set this to 360 instead of 352
|
||||
ffmpeg:
|
||||
inputs:
|
||||
- path: rtsp://127.0.0.1:8554/front_door # main stream 1920x1080
|
||||
roles:
|
||||
- record
|
||||
- path: rtsp://127.0.0.1:8554/front_door_sub # sub stream 640x352
|
||||
roles:
|
||||
- detect
|
||||
```
|
||||
|
||||
The same applies to your `record` stream: if its aspect ratio differs from your `detect` stream, your recordings will appear in a different shape than the live view. For consistent framing across live view and recordings, use the same aspect ratio for all of a camera's streams (the resolution can still differ).
|
||||
|
||||
</FaqItem>
|
||||
|
||||
@@ -376,20 +376,22 @@ class EmbeddingMaintainer(threading.Thread):
|
||||
logger.info(f"Disabled classification processor for model: {model_name}")
|
||||
return
|
||||
|
||||
# Check if processor already exists
|
||||
for processor in self.realtime_processors:
|
||||
if isinstance(
|
||||
processor,
|
||||
(
|
||||
CustomStateClassificationProcessor,
|
||||
CustomObjectClassificationProcessor,
|
||||
),
|
||||
if (
|
||||
isinstance(
|
||||
processor,
|
||||
(
|
||||
CustomStateClassificationProcessor,
|
||||
CustomObjectClassificationProcessor,
|
||||
),
|
||||
)
|
||||
and processor.model_config.name == model_name
|
||||
):
|
||||
if processor.model_config.name == model_name:
|
||||
logger.debug(
|
||||
f"Classification processor for model {model_name} already exists, skipping"
|
||||
)
|
||||
return
|
||||
processor.model_config = model_config
|
||||
logger.debug(
|
||||
f"Updated config for classification processor: {model_name}"
|
||||
)
|
||||
return
|
||||
|
||||
if model_config.state_config is not None:
|
||||
processor = CustomStateClassificationProcessor(
|
||||
|
||||
@@ -57,6 +57,12 @@ class BaseEmbedding(ABC):
|
||||
def _preprocess_inputs(self, raw_inputs: Any) -> Any:
|
||||
pass
|
||||
|
||||
@staticmethod
|
||||
def _bgr_to_rgb(frame: Any) -> Any:
|
||||
if isinstance(frame, np.ndarray) and frame.ndim == 3:
|
||||
return np.ascontiguousarray(frame[:, :, ::-1])
|
||||
return frame
|
||||
|
||||
def _process_image(self, image, output: str = "RGB") -> Image.Image:
|
||||
if isinstance(image, str):
|
||||
if image.startswith("http"):
|
||||
|
||||
@@ -73,7 +73,7 @@ class FaceNetEmbedding(BaseEmbedding):
|
||||
self.tensor_output_details = self.runner.get_output_details()
|
||||
|
||||
def _preprocess_inputs(self, raw_inputs):
|
||||
pil = self._process_image(raw_inputs[0])
|
||||
pil = self._process_image(self._bgr_to_rgb(raw_inputs[0]))
|
||||
|
||||
# handle images larger than input size
|
||||
width, height = pil.size
|
||||
@@ -159,7 +159,7 @@ class ArcfaceEmbedding(BaseEmbedding):
|
||||
)
|
||||
|
||||
def _preprocess_inputs(self, raw_inputs):
|
||||
pil = self._process_image(raw_inputs[0])
|
||||
pil = self._process_image(self._bgr_to_rgb(raw_inputs[0]))
|
||||
|
||||
# handle images larger than input size
|
||||
width, height = pil.size
|
||||
|
||||
@@ -281,6 +281,11 @@ class GenAIClient:
|
||||
"""Whether the configured model exposes a per-request thinking toggle."""
|
||||
return False
|
||||
|
||||
@property
|
||||
def supports_embeddings(self) -> bool:
|
||||
"""Whether the configured model can generate embeddings via embed()."""
|
||||
return False
|
||||
|
||||
def list_models(self) -> list[str]:
|
||||
"""Return the list of model names available from this provider.
|
||||
|
||||
|
||||
@@ -121,5 +121,6 @@ class GenAIClientManager:
|
||||
"models": client.list_models(),
|
||||
"roles": [r.value for r in genai_cfg.roles],
|
||||
"supports_toggleable_thinking": client.supports_toggleable_thinking,
|
||||
"supports_embeddings": client.supports_embeddings,
|
||||
}
|
||||
return result
|
||||
|
||||
@@ -38,6 +38,37 @@ def _encode_thought_signature(signature: bytes | None) -> str | None:
|
||||
return base64.b64encode(signature).decode("ascii")
|
||||
|
||||
|
||||
def _decode_data_uri(url: str) -> tuple[str, bytes] | None:
|
||||
"""Decode a ``data:`` URI into ``(mime_type, bytes)``; None if not a data URI."""
|
||||
if not isinstance(url, str) or not url.startswith("data:"):
|
||||
return None
|
||||
try:
|
||||
header, b64 = url.split(",", 1)
|
||||
mime = header[len("data:") :].split(";")[0] or "image/jpeg"
|
||||
return mime, base64.b64decode(b64)
|
||||
except (ValueError, binascii.Error):
|
||||
return None
|
||||
|
||||
|
||||
def _parts_from_content(content: Any) -> list[types.Part]:
|
||||
"""Convert OpenAI-style message content (str or multimodal list) to Gemini parts."""
|
||||
if isinstance(content, list):
|
||||
parts: list[types.Part] = []
|
||||
for item in content:
|
||||
if not isinstance(item, dict):
|
||||
continue
|
||||
if item.get("type") == "text":
|
||||
parts.append(types.Part.from_text(text=item.get("text") or ""))
|
||||
elif item.get("type") == "image_url":
|
||||
decoded = _decode_data_uri((item.get("image_url") or {}).get("url", ""))
|
||||
if decoded is not None:
|
||||
mime, data = decoded
|
||||
parts.append(types.Part.from_bytes(data=data, mime_type=mime))
|
||||
# Gemini rejects empty parts; fall back to a single space.
|
||||
return parts or [types.Part.from_text(text=" ")]
|
||||
return [types.Part.from_text(text=content or "")]
|
||||
|
||||
|
||||
def _stats_from_gemini_usage(usage: Any) -> dict[str, Any] | None:
|
||||
"""Build a stats dict from a Gemini usage_metadata object."""
|
||||
prompt_tokens = getattr(usage, "prompt_token_count", None)
|
||||
@@ -227,9 +258,7 @@ class GeminiClient(GenAIClient):
|
||||
)
|
||||
else: # user
|
||||
gemini_messages.append(
|
||||
types.Content(
|
||||
role="user", parts=[types.Part.from_text(text=content)]
|
||||
)
|
||||
types.Content(role="user", parts=_parts_from_content(content))
|
||||
)
|
||||
|
||||
# Convert tools to Gemini format
|
||||
@@ -485,9 +514,7 @@ class GeminiClient(GenAIClient):
|
||||
)
|
||||
else: # user
|
||||
gemini_messages.append(
|
||||
types.Content(
|
||||
role="user", parts=[types.Part.from_text(text=content)]
|
||||
)
|
||||
types.Content(role="user", parts=_parts_from_content(content))
|
||||
)
|
||||
|
||||
# Convert tools to Gemini format
|
||||
@@ -553,7 +580,7 @@ class GeminiClient(GenAIClient):
|
||||
# Use streaming API
|
||||
content_parts: list[str] = []
|
||||
reasoning_parts: list[str] = []
|
||||
tool_calls_by_index: dict[int, dict[str, Any]] = {}
|
||||
tool_calls_accum: list[dict[str, Any]] = []
|
||||
finish_reason = "stop"
|
||||
usage_stats: dict[str, Any] | None = None
|
||||
|
||||
@@ -600,7 +627,11 @@ class GeminiClient(GenAIClient):
|
||||
content_parts.append(part.text)
|
||||
yield ("content_delta", part.text)
|
||||
elif part.function_call:
|
||||
# Handle function call
|
||||
# Gemini streams complete function calls (not partial
|
||||
# argument deltas), so each part is a distinct tool
|
||||
# call. Append rather than accumulate by name — the
|
||||
# latter concatenated parallel/repeated calls into one
|
||||
# invalid arguments string (e.g. `{...}{...}`).
|
||||
try:
|
||||
arguments = (
|
||||
dict(part.function_call.args)
|
||||
@@ -610,40 +641,16 @@ class GeminiClient(GenAIClient):
|
||||
except Exception:
|
||||
arguments = {}
|
||||
|
||||
# Store tool call
|
||||
tool_call_id = part.function_call.name or ""
|
||||
tool_call_name = part.function_call.name or ""
|
||||
|
||||
# Check if we already have this tool call
|
||||
found_index = None
|
||||
for idx, tc in tool_calls_by_index.items():
|
||||
if tc["name"] == tool_call_name:
|
||||
found_index = idx
|
||||
break
|
||||
|
||||
if found_index is None:
|
||||
found_index = len(tool_calls_by_index)
|
||||
tool_calls_by_index[found_index] = {
|
||||
"id": tool_call_id,
|
||||
"name": tool_call_name,
|
||||
"arguments": "",
|
||||
"thought_signature": None,
|
||||
tool_calls_accum.append(
|
||||
{
|
||||
"id": part.function_call.name or "",
|
||||
"name": part.function_call.name or "",
|
||||
"arguments": arguments,
|
||||
"thought_signature": getattr(
|
||||
part, "thought_signature", None
|
||||
),
|
||||
}
|
||||
|
||||
# Accumulate arguments
|
||||
if arguments:
|
||||
tool_calls_by_index[found_index]["arguments"] += (
|
||||
json.dumps(arguments)
|
||||
if isinstance(arguments, dict)
|
||||
else str(arguments)
|
||||
)
|
||||
|
||||
# Capture latest thought_signature for this call
|
||||
chunk_sig = getattr(part, "thought_signature", None)
|
||||
if chunk_sig:
|
||||
tool_calls_by_index[found_index][
|
||||
"thought_signature"
|
||||
] = chunk_sig
|
||||
)
|
||||
|
||||
# Build final message
|
||||
full_content = "".join(content_parts).strip() or None
|
||||
@@ -651,25 +658,20 @@ class GeminiClient(GenAIClient):
|
||||
|
||||
# Convert tool calls to list format
|
||||
tool_calls_list = None
|
||||
if tool_calls_by_index:
|
||||
tool_calls_list = []
|
||||
for tc in tool_calls_by_index.values():
|
||||
try:
|
||||
# Try to parse accumulated arguments as JSON
|
||||
parsed_args = json.loads(tc["arguments"])
|
||||
except (json.JSONDecodeError, Exception):
|
||||
parsed_args = tc["arguments"]
|
||||
|
||||
tool_calls_list.append(
|
||||
{
|
||||
"id": tc["id"],
|
||||
"name": tc["name"],
|
||||
"arguments": parsed_args,
|
||||
"thought_signature": _encode_thought_signature(
|
||||
tc.get("thought_signature")
|
||||
),
|
||||
}
|
||||
)
|
||||
if tool_calls_accum:
|
||||
tool_calls_list = [
|
||||
{
|
||||
"id": tc["id"],
|
||||
"name": tc["name"],
|
||||
"arguments": tc["arguments"]
|
||||
if isinstance(tc["arguments"], dict)
|
||||
else {},
|
||||
"thought_signature": _encode_thought_signature(
|
||||
tc.get("thought_signature")
|
||||
),
|
||||
}
|
||||
for tc in tool_calls_accum
|
||||
]
|
||||
finish_reason = "tool_calls"
|
||||
|
||||
if usage_stats is not None:
|
||||
|
||||
@@ -128,6 +128,11 @@ class LlamaCppClient(GenAIClient):
|
||||
_text_baseline_tokens: int | None
|
||||
_media_marker: str
|
||||
|
||||
@property
|
||||
def supports_embeddings(self) -> bool:
|
||||
"""llama.cpp exposes an /embeddings endpoint for any loaded model."""
|
||||
return True
|
||||
|
||||
def _init_provider(self) -> str | None:
|
||||
"""Initialize the client and query model metadata from the server."""
|
||||
self.provider_options = {
|
||||
|
||||
@@ -423,9 +423,18 @@ class OpenAIClient(GenAIClient):
|
||||
for tc in tool_calls_by_index.values():
|
||||
try:
|
||||
# Parse accumulated arguments as JSON
|
||||
parsed_args = json.loads(tc["arguments"])
|
||||
except (json.JSONDecodeError, Exception):
|
||||
parsed_args = tc["arguments"]
|
||||
parsed_args = json.loads(tc["arguments"] or "{}")
|
||||
except (json.JSONDecodeError, ValueError):
|
||||
logger.warning(
|
||||
"Failed to parse streamed tool call arguments for %s",
|
||||
tc["name"],
|
||||
)
|
||||
parsed_args = {}
|
||||
|
||||
# Downstream (ToolCall model) requires a dict; never leak a
|
||||
# partial/invalid arguments string.
|
||||
if not isinstance(parsed_args, dict):
|
||||
parsed_args = {}
|
||||
|
||||
tool_calls_list.append(
|
||||
{
|
||||
|
||||
@@ -0,0 +1,496 @@
|
||||
"""Smoke tests for GenAI chat providers.
|
||||
|
||||
Each provider's ``chat_with_tools_stream`` is driven with a canned "test
|
||||
response" so the two conversion layers are exercised without any network:
|
||||
|
||||
1. Frigate (OpenAI-style) messages -> provider-native request format
|
||||
2. provider-native response -> Frigate ``("kind", value)`` stream events
|
||||
|
||||
These guard against regressions such as tool-call arguments arriving as raw
|
||||
strings instead of dicts (which crash the ``ToolCall`` model), and multimodal
|
||||
user content (a list of text/image parts, as injected by ``get_live_context``)
|
||||
crashing message conversion.
|
||||
"""
|
||||
|
||||
import asyncio
|
||||
import base64
|
||||
import json
|
||||
import unittest
|
||||
from types import SimpleNamespace
|
||||
from unittest.mock import AsyncMock, MagicMock, patch
|
||||
|
||||
from frigate.config import GenAIConfig, GenAIProviderEnum
|
||||
from frigate.genai import PROVIDERS, load_providers
|
||||
|
||||
load_providers()
|
||||
|
||||
# A minimal but valid JPEG data URI, mirroring what get_live_context injects.
|
||||
_TINY_JPEG = base64.b64encode(b"\xff\xd8\xff\xd9").decode("ascii")
|
||||
_IMAGE_DATA_URI = f"data:image/jpeg;base64,{_TINY_JPEG}"
|
||||
|
||||
# Conversation ending in a multimodal user message (text + live image), the
|
||||
# exact shape the chat endpoint builds after a get_live_context tool result.
|
||||
MULTIMODAL_MESSAGES = [
|
||||
{"role": "system", "content": "You are a test assistant."},
|
||||
{"role": "user", "content": "what do you see on the front camera?"},
|
||||
{
|
||||
"role": "assistant",
|
||||
"content": "",
|
||||
"tool_calls": [
|
||||
{
|
||||
"id": "call_1",
|
||||
"type": "function",
|
||||
"function": {
|
||||
"name": "get_live_context",
|
||||
"arguments": json.dumps({"camera": "front"}),
|
||||
},
|
||||
}
|
||||
],
|
||||
},
|
||||
{
|
||||
"role": "tool",
|
||||
"tool_call_id": "call_1",
|
||||
"name": "get_live_context",
|
||||
"content": json.dumps({"camera": "front"}),
|
||||
},
|
||||
{
|
||||
"role": "user",
|
||||
"content": [
|
||||
{
|
||||
"type": "text",
|
||||
"text": "Here is the current live image from camera 'front'.",
|
||||
},
|
||||
{"type": "image_url", "image_url": {"url": _IMAGE_DATA_URI}},
|
||||
],
|
||||
},
|
||||
]
|
||||
|
||||
SIMPLE_MESSAGES = [
|
||||
{"role": "system", "content": "You are a test assistant."},
|
||||
{"role": "user", "content": "hello"},
|
||||
]
|
||||
|
||||
TOOLS = [
|
||||
{
|
||||
"type": "function",
|
||||
"function": {
|
||||
"name": "search_objects",
|
||||
"description": "Search tracked objects",
|
||||
"parameters": {
|
||||
"type": "object",
|
||||
"properties": {"label": {"type": "string"}},
|
||||
},
|
||||
},
|
||||
}
|
||||
]
|
||||
|
||||
|
||||
def _make_client(provider: str, **cfg_overrides):
|
||||
"""Build a provider client offline (no model validation, no network)."""
|
||||
cfg = GenAIConfig(provider=provider, **cfg_overrides)
|
||||
cls = PROVIDERS[GenAIProviderEnum(provider)]
|
||||
return cls(cfg, timeout=5, validate_model=False)
|
||||
|
||||
|
||||
def _collect(client, messages, tools=TOOLS):
|
||||
"""Drain chat_with_tools_stream into a list of (kind, value) events."""
|
||||
|
||||
async def _run():
|
||||
events = []
|
||||
async for event in client.chat_with_tools_stream(
|
||||
messages=messages, tools=tools, tool_choice="auto"
|
||||
):
|
||||
events.append(event)
|
||||
return events
|
||||
|
||||
return asyncio.run(_run())
|
||||
|
||||
|
||||
def _final_message(events) -> dict:
|
||||
messages = [value for (kind, value) in events if kind == "message"]
|
||||
assert messages, f"stream produced no final message: {events}"
|
||||
return messages[-1]
|
||||
|
||||
|
||||
def _assert_tool_args_are_dicts(final: dict) -> None:
|
||||
"""Every returned tool call must expose arguments as a dict, never a string."""
|
||||
for tool_call in final.get("tool_calls") or []:
|
||||
assert isinstance(tool_call["arguments"], dict), (
|
||||
f"tool call arguments must be a dict, got "
|
||||
f"{type(tool_call['arguments']).__name__}: {tool_call['arguments']!r}"
|
||||
)
|
||||
|
||||
|
||||
# ---------------------------------------------------------------------------
|
||||
# OpenAI
|
||||
# ---------------------------------------------------------------------------
|
||||
def _openai_tc(index, id=None, name=None, arguments=None):
|
||||
return SimpleNamespace(
|
||||
index=index,
|
||||
id=id,
|
||||
function=SimpleNamespace(name=name, arguments=arguments),
|
||||
)
|
||||
|
||||
|
||||
def _openai_chunk(content=None, tool_calls=None, finish_reason=None, usage=None):
|
||||
delta = SimpleNamespace(
|
||||
content=content,
|
||||
tool_calls=tool_calls,
|
||||
reasoning_content=None,
|
||||
reasoning=None,
|
||||
)
|
||||
choice = SimpleNamespace(delta=delta, finish_reason=finish_reason)
|
||||
return SimpleNamespace(choices=[choice], usage=usage)
|
||||
|
||||
|
||||
class TestOpenAIProvider(unittest.TestCase):
|
||||
def _client(self):
|
||||
return _make_client(
|
||||
"openai", model="gpt-4o", api_key="k", base_url="http://localhost:9999/v1"
|
||||
)
|
||||
|
||||
def test_stream_tool_call_arguments_are_dict(self):
|
||||
# Arguments arrive split across chunks, as the real API streams them.
|
||||
chunks = [
|
||||
_openai_chunk(
|
||||
tool_calls=[
|
||||
_openai_tc(0, id="c1", name="search_objects", arguments='{"label":')
|
||||
]
|
||||
),
|
||||
_openai_chunk(tool_calls=[_openai_tc(0, arguments=' "person"}')]),
|
||||
_openai_chunk(finish_reason="tool_calls"),
|
||||
]
|
||||
client = self._client()
|
||||
client.provider.chat.completions.create = MagicMock(return_value=iter(chunks))
|
||||
|
||||
final = _final_message(_collect(client, SIMPLE_MESSAGES))
|
||||
self.assertEqual(final["finish_reason"], "tool_calls")
|
||||
self.assertEqual(len(final["tool_calls"]), 1)
|
||||
_assert_tool_args_are_dicts(final)
|
||||
self.assertEqual(final["tool_calls"][0]["arguments"], {"label": "person"})
|
||||
|
||||
def test_stream_content_response(self):
|
||||
chunks = [
|
||||
_openai_chunk(content="hel"),
|
||||
_openai_chunk(content="lo"),
|
||||
_openai_chunk(finish_reason="stop"),
|
||||
]
|
||||
client = self._client()
|
||||
client.provider.chat.completions.create = MagicMock(return_value=iter(chunks))
|
||||
|
||||
events = _collect(client, SIMPLE_MESSAGES)
|
||||
deltas = [v for (k, v) in events if k == "content_delta"]
|
||||
self.assertEqual("".join(deltas), "hello")
|
||||
self.assertEqual(_final_message(events)["content"], "hello")
|
||||
|
||||
def test_multimodal_message_does_not_crash(self):
|
||||
client = self._client()
|
||||
client.provider.chat.completions.create = MagicMock(
|
||||
return_value=iter([_openai_chunk(content="ok", finish_reason="stop")])
|
||||
)
|
||||
# Passing the OpenAI-native multimodal list through must not raise.
|
||||
final = _final_message(_collect(client, MULTIMODAL_MESSAGES))
|
||||
self.assertEqual(final["content"], "ok")
|
||||
|
||||
|
||||
# ---------------------------------------------------------------------------
|
||||
# Gemini
|
||||
# ---------------------------------------------------------------------------
|
||||
def _gemini_part(text=None, thought=False, function_call=None, thought_signature=None):
|
||||
return SimpleNamespace(
|
||||
text=text,
|
||||
thought=thought,
|
||||
function_call=function_call,
|
||||
thought_signature=thought_signature,
|
||||
)
|
||||
|
||||
|
||||
def _gemini_chunk(parts, finish_reason=None, usage_metadata=None):
|
||||
candidate = SimpleNamespace(
|
||||
content=SimpleNamespace(parts=parts), finish_reason=finish_reason
|
||||
)
|
||||
return SimpleNamespace(candidates=[candidate], usage_metadata=usage_metadata)
|
||||
|
||||
|
||||
def _gemini_stream(chunks):
|
||||
async def _agen(*args, **kwargs):
|
||||
for chunk in chunks:
|
||||
yield chunk
|
||||
|
||||
return _agen
|
||||
|
||||
|
||||
class TestGeminiProvider(unittest.TestCase):
|
||||
def _client(self):
|
||||
return _make_client("gemini", model="gemini-2.5-flash", api_key="k")
|
||||
|
||||
def _patch_stream(self, client, chunks):
|
||||
client.provider = MagicMock()
|
||||
client.provider.aio.models.generate_content_stream = AsyncMock(
|
||||
side_effect=_gemini_stream(chunks)
|
||||
)
|
||||
|
||||
def test_stream_parallel_tool_calls_stay_separate_dicts(self):
|
||||
# Regression: Gemini streams complete function calls. Two calls to the
|
||||
# same tool must NOT be merged into one concatenated arguments string.
|
||||
from google.genai.types import FinishReason
|
||||
|
||||
chunks = [
|
||||
_gemini_chunk(
|
||||
parts=[
|
||||
_gemini_part(
|
||||
function_call=SimpleNamespace(
|
||||
name="search_objects", args={"label": "person"}
|
||||
)
|
||||
),
|
||||
_gemini_part(
|
||||
function_call=SimpleNamespace(
|
||||
name="search_objects", args={"limit": 1}
|
||||
)
|
||||
),
|
||||
],
|
||||
finish_reason=FinishReason.STOP,
|
||||
),
|
||||
]
|
||||
client = self._client()
|
||||
self._patch_stream(client, chunks)
|
||||
|
||||
final = _final_message(_collect(client, SIMPLE_MESSAGES))
|
||||
self.assertEqual(final["finish_reason"], "tool_calls")
|
||||
self.assertEqual(len(final["tool_calls"]), 2)
|
||||
_assert_tool_args_are_dicts(final)
|
||||
self.assertEqual(final["tool_calls"][0]["arguments"], {"label": "person"})
|
||||
self.assertEqual(final["tool_calls"][1]["arguments"], {"limit": 1})
|
||||
|
||||
def test_stream_content_response(self):
|
||||
from google.genai.types import FinishReason
|
||||
|
||||
chunks = [
|
||||
_gemini_chunk(parts=[_gemini_part(text="hel")]),
|
||||
_gemini_chunk(
|
||||
parts=[_gemini_part(text="lo")], finish_reason=FinishReason.STOP
|
||||
),
|
||||
]
|
||||
client = self._client()
|
||||
self._patch_stream(client, chunks)
|
||||
|
||||
events = _collect(client, SIMPLE_MESSAGES)
|
||||
deltas = [v for (k, v) in events if k == "content_delta"]
|
||||
self.assertEqual("".join(deltas), "hello")
|
||||
self.assertEqual(_final_message(events)["content"], "hello")
|
||||
|
||||
def test_multimodal_message_converts_without_crash(self):
|
||||
# Regression: a user message with list content (text + image_url) used
|
||||
# to be handed to Part.from_text(text=<list>) and raise ValidationError.
|
||||
from google.genai.types import FinishReason
|
||||
|
||||
client = self._client()
|
||||
self._patch_stream(
|
||||
client,
|
||||
[
|
||||
_gemini_chunk(
|
||||
parts=[_gemini_part(text="ok")], finish_reason=FinishReason.STOP
|
||||
)
|
||||
],
|
||||
)
|
||||
final = _final_message(_collect(client, MULTIMODAL_MESSAGES))
|
||||
self.assertEqual(final["content"], "ok")
|
||||
|
||||
|
||||
# ---------------------------------------------------------------------------
|
||||
# Ollama
|
||||
# ---------------------------------------------------------------------------
|
||||
class TestOllamaProvider(unittest.TestCase):
|
||||
def _client(self):
|
||||
return _make_client("ollama", model="llama3", base_url="http://localhost:9999")
|
||||
|
||||
def _run_with_response(self, client, response, messages):
|
||||
# Ollama uses a non-streaming call when tools are present, via an
|
||||
# internally-constructed async client.
|
||||
fake_async = MagicMock()
|
||||
fake_async.chat = AsyncMock(return_value=response)
|
||||
with patch(
|
||||
"frigate.genai.plugins.ollama.OllamaAsyncClient",
|
||||
return_value=fake_async,
|
||||
):
|
||||
return _collect(client, messages)
|
||||
|
||||
def test_tool_call_arguments_are_dict(self):
|
||||
response = {
|
||||
"message": {
|
||||
"content": "",
|
||||
"tool_calls": [
|
||||
{
|
||||
"function": {
|
||||
"name": "search_objects",
|
||||
"arguments": {"label": "person"},
|
||||
}
|
||||
}
|
||||
],
|
||||
},
|
||||
"done": True,
|
||||
"done_reason": "stop",
|
||||
"eval_count": 5,
|
||||
"prompt_eval_count": 3,
|
||||
"eval_duration": 1_000_000,
|
||||
}
|
||||
client = self._client()
|
||||
final = _final_message(
|
||||
self._run_with_response(client, response, SIMPLE_MESSAGES)
|
||||
)
|
||||
self.assertEqual(final["finish_reason"], "tool_calls")
|
||||
_assert_tool_args_are_dicts(final)
|
||||
self.assertEqual(final["tool_calls"][0]["arguments"], {"label": "person"})
|
||||
|
||||
def test_multimodal_message_normalizes_image(self):
|
||||
# Ollama needs content as a string with images pulled into a separate
|
||||
# field; the normalizer must extract both without crashing.
|
||||
response = {
|
||||
"message": {"content": "ok"},
|
||||
"done": True,
|
||||
"done_reason": "stop",
|
||||
}
|
||||
client = self._client()
|
||||
final = _final_message(
|
||||
self._run_with_response(client, response, MULTIMODAL_MESSAGES)
|
||||
)
|
||||
self.assertEqual(final["content"], "ok")
|
||||
|
||||
def test_normalize_multimodal_content(self):
|
||||
from frigate.genai.plugins.ollama import _normalize_multimodal_content
|
||||
|
||||
text, images = _normalize_multimodal_content(MULTIMODAL_MESSAGES[-1]["content"])
|
||||
self.assertIn("live image", text)
|
||||
self.assertEqual(len(images), 1)
|
||||
self.assertEqual(images[0], b"\xff\xd8\xff\xd9")
|
||||
|
||||
|
||||
# ---------------------------------------------------------------------------
|
||||
# llama.cpp
|
||||
# ---------------------------------------------------------------------------
|
||||
class _FakeStreamResponse:
|
||||
def __init__(self, lines):
|
||||
self._lines = lines
|
||||
|
||||
def raise_for_status(self):
|
||||
return None
|
||||
|
||||
async def aiter_lines(self):
|
||||
for line in self._lines:
|
||||
yield line
|
||||
|
||||
|
||||
class _FakeStreamCtx:
|
||||
def __init__(self, lines):
|
||||
self._resp = _FakeStreamResponse(lines)
|
||||
|
||||
async def __aenter__(self):
|
||||
return self._resp
|
||||
|
||||
async def __aexit__(self, *exc):
|
||||
return False
|
||||
|
||||
|
||||
class _FakeAsyncClient:
|
||||
def __init__(self, lines):
|
||||
self._lines = lines
|
||||
|
||||
async def __aenter__(self):
|
||||
return self
|
||||
|
||||
async def __aexit__(self, *exc):
|
||||
return False
|
||||
|
||||
def stream(self, method, url, json=None):
|
||||
return _FakeStreamCtx(self._lines)
|
||||
|
||||
|
||||
class TestLlamaCppProvider(unittest.TestCase):
|
||||
def _client(self):
|
||||
return _make_client("llamacpp", model="m", base_url="http://localhost:9999")
|
||||
|
||||
def _run_with_lines(self, client, lines, messages):
|
||||
with patch(
|
||||
"frigate.genai.plugins.llama_cpp.httpx.AsyncClient",
|
||||
return_value=_FakeAsyncClient(lines),
|
||||
):
|
||||
return _collect(client, messages)
|
||||
|
||||
def test_stream_tool_call_arguments_are_dict(self):
|
||||
lines = [
|
||||
"data: "
|
||||
+ json.dumps(
|
||||
{
|
||||
"choices": [
|
||||
{
|
||||
"delta": {
|
||||
"tool_calls": [
|
||||
{
|
||||
"index": 0,
|
||||
"id": "c1",
|
||||
"function": {
|
||||
"name": "search_objects",
|
||||
"arguments": '{"label":',
|
||||
},
|
||||
}
|
||||
]
|
||||
}
|
||||
}
|
||||
]
|
||||
}
|
||||
),
|
||||
"data: "
|
||||
+ json.dumps(
|
||||
{
|
||||
"choices": [
|
||||
{
|
||||
"delta": {
|
||||
"tool_calls": [
|
||||
{
|
||||
"index": 0,
|
||||
"function": {"arguments": ' "person"}'},
|
||||
}
|
||||
]
|
||||
}
|
||||
}
|
||||
]
|
||||
}
|
||||
),
|
||||
"data: "
|
||||
+ json.dumps({"choices": [{"delta": {}, "finish_reason": "tool_calls"}]}),
|
||||
"data: [DONE]",
|
||||
]
|
||||
client = self._client()
|
||||
final = _final_message(self._run_with_lines(client, lines, SIMPLE_MESSAGES))
|
||||
self.assertEqual(final["finish_reason"], "tool_calls")
|
||||
_assert_tool_args_are_dicts(final)
|
||||
self.assertEqual(final["tool_calls"][0]["arguments"], {"label": "person"})
|
||||
|
||||
def test_stream_content_response(self):
|
||||
lines = [
|
||||
"data: " + json.dumps({"choices": [{"delta": {"content": "hel"}}]}),
|
||||
"data: " + json.dumps({"choices": [{"delta": {"content": "lo"}}]}),
|
||||
"data: "
|
||||
+ json.dumps({"choices": [{"delta": {}, "finish_reason": "stop"}]}),
|
||||
"data: [DONE]",
|
||||
]
|
||||
client = self._client()
|
||||
events = self._run_with_lines(client, lines, SIMPLE_MESSAGES)
|
||||
deltas = [v for (k, v) in events if k == "content_delta"]
|
||||
self.assertEqual("".join(deltas), "hello")
|
||||
self.assertEqual(_final_message(events)["content"], "hello")
|
||||
|
||||
def test_multimodal_message_does_not_crash(self):
|
||||
lines = [
|
||||
"data: " + json.dumps({"choices": [{"delta": {"content": "ok"}}]}),
|
||||
"data: "
|
||||
+ json.dumps({"choices": [{"delta": {}, "finish_reason": "stop"}]}),
|
||||
"data: [DONE]",
|
||||
]
|
||||
client = self._client()
|
||||
final = _final_message(self._run_with_lines(client, lines, MULTIMODAL_MESSAGES))
|
||||
self.assertEqual(final["content"], "ok")
|
||||
|
||||
|
||||
if __name__ == "__main__":
|
||||
unittest.main()
|
||||
@@ -61,7 +61,8 @@
|
||||
"error": {
|
||||
"endTimeMustAfterStartTime": "L'hora de finalització ha de ser posterior a l'hora d'inici",
|
||||
"noVaildTimeSelected": "No s'ha seleccionat un rang de temps vàlid",
|
||||
"failed": "No s'ha pogut inciar l'exportació: {{error}}"
|
||||
"failed": "No s'ha pogut inciar l'exportació: {{error}}",
|
||||
"noValidTimeSelected": "No s'ha seleccionat cap interval de temps vàlid"
|
||||
},
|
||||
"view": "Vista",
|
||||
"queued": "Exporta a la cua. Mostra el progrés a la pàgina d'exportacions.",
|
||||
|
||||
@@ -493,6 +493,9 @@
|
||||
"max_concurrent": {
|
||||
"label": "Màxim d'exportacions concurrents",
|
||||
"description": "Nombre màxim de treballs d'exportació a processar al mateix temps."
|
||||
},
|
||||
"chapters": {
|
||||
"label": "Metadades de capítol per incrustar en els enregistraments exportats"
|
||||
}
|
||||
},
|
||||
"preview": {
|
||||
|
||||
@@ -380,6 +380,9 @@
|
||||
"max_concurrent": {
|
||||
"label": "Màxim d'exportacions concurrents",
|
||||
"description": "Nombre màxim de treballs d'exportació a processar al mateix temps."
|
||||
},
|
||||
"chapters": {
|
||||
"label": "Metadades de capítol per incrustar en els enregistraments exportats"
|
||||
}
|
||||
},
|
||||
"preview": {
|
||||
|
||||
@@ -303,7 +303,7 @@
|
||||
},
|
||||
"offset": {
|
||||
"label": "Òfset d'Anotació",
|
||||
"desc": "Aquestes dades provenen del flux de detecció de la càmera, però se superposen a les imatges del flux de gravació. És poc probable que els dos fluxos estiguin perfectament sincronitzats. Com a resultat, el quadre delimitador i les imatges no s'alinearan perfectament. Tanmateix, es pot utilitzar el camp <code>annotation_offset</code> per ajustar-ho.",
|
||||
"desc": "Aquestes dades provenen del canal de detecció de la càmera, però estan sobreposades a les imatges del canal de registre. És poc probable que els dos corrents estiguin perfectament sincronitzats. Com a resultat, la caixa contenidora i les imatges no s'alinearan perfectament. Podeu utilitzar aquest paràmetre per a compensar les anotacions cap endavant o cap enrere en el temps per a alinear-les millor amb el metratge gravat.",
|
||||
"millisecondsToOffset": "Millisegons per l'òfset de detecció d'anotacions per. <em>Per defecte: 0</em>",
|
||||
"tips": "Reduïu el valor si la reproducció del vídeo es troba per davant dels quadres i els punts de ruta, i augmenteu-lo si es troba per darrere. Aquest valor pot ser negatiu.",
|
||||
"toast": {
|
||||
|
||||
@@ -698,7 +698,7 @@
|
||||
"title": "Crear un nou usuari",
|
||||
"confirmPassword": "Siusplau, confirma la contrasenya",
|
||||
"usernameOnlyInclude": "El nom d'usuari només pot contenir lletres, números, . o _",
|
||||
"desc": "Afegeix un nou compte d'usuari i especifica un rol per accedir a àrees de la interfície de Frigate."
|
||||
"desc": "Afegeix un compte d'usuari nou i especifica un rol per a l'accés a les àrees de la interfície d'usuari de Frigate."
|
||||
}
|
||||
},
|
||||
"title": "Usuaris",
|
||||
@@ -1323,7 +1323,7 @@
|
||||
"details": {
|
||||
"edit": "Edita els detalls de la càmera",
|
||||
"title": "Edita els detalls de la càmera",
|
||||
"description": "Actualitza el nom de visualització, l'URL extern i la visibilitat utilitzada per a aquesta càmera a tota la interfície d'usuari de la Fragata.",
|
||||
"description": "Actualitza el nom de visualització, l'URL extern i la visibilitat utilitzada per a aquesta càmera a tota la interfície d'usuari de Frigate.",
|
||||
"friendlyNameLabel": "Nom a mostrar",
|
||||
"friendlyNameHelp": "Nom amistós que es mostra per a aquesta càmera a tota la interfície d'usuari de Frigate. Deixeu-ho en blanc per utilitzar l'ID de la càmera.",
|
||||
"webuiUrlLabel": "URL de la interfície web de la càmera",
|
||||
@@ -1484,7 +1484,7 @@
|
||||
"successMulti_other": "Configuració copiada a {{count}} càmeres",
|
||||
"successMultiWithRestart_one": "Configuració copiada a la càmera {{count}}. Reinicia Frigate per aplicar tots els canvis.",
|
||||
"successMultiWithRestart_many": "Configuració copiada a {{count}} càmeres. Reinicia Frigate per aplicar tots els canvis.",
|
||||
"successMultiWithRestart_other": "Configuració copiada a {{count}} càmeres. Reinicia la fragata per aplicar tots els canvis.",
|
||||
"successMultiWithRestart_other": "Configuració copiada a {{count}} càmeres. Reinicia Frigate per aplicar tots els canvis.",
|
||||
"partialFailure": "{{successCount}} seccions aplicades; «{{failedSection}}» ha fallat: {{errorMessage}}",
|
||||
"partialFailureMulti": "S'ha copiat a {{successCount}} càmera(es); ha fallat {{failed}}: {{errorMessage}}",
|
||||
"newCameraPartialFailure": "S'ha creat la càmera {{cameraName}} però no s'han pogut copiar alguns paràmetres: {{errorMessage}}",
|
||||
@@ -1641,7 +1641,14 @@
|
||||
"keyLabel": "Clau",
|
||||
"valueLabel": "Valor",
|
||||
"keyPlaceholder": "Nou valor",
|
||||
"remove": "Elimina"
|
||||
"remove": "Elimina",
|
||||
"providerNameLabel": "Nom del proveïdor",
|
||||
"providerNamePlaceholder": "p. ex., openai",
|
||||
"variableNameLabel": "Nom de la variable",
|
||||
"variableNamePlaceholder": ". ex., La_Meva_Variable",
|
||||
"loggerNameLabel": "Nom del registrador",
|
||||
"loggerNamePlaceholder": "p. ex., friagte.registre",
|
||||
"keyPatternError": "Utilitza només lletres, números, guions i guions baixos (sense espais)"
|
||||
},
|
||||
"timezone": {
|
||||
"defaultOption": "Utilitza la zona horària del navegador"
|
||||
|
||||
@@ -1,5 +1,6 @@
|
||||
{
|
||||
"documentTitle": "Classification Models - Frigate",
|
||||
"disabled": "Disabled",
|
||||
"details": {
|
||||
"scoreInfo": "Score represents the average classification confidence across all detections of this object.",
|
||||
"none": "None",
|
||||
@@ -64,7 +65,20 @@
|
||||
"title": "Edit Classification Model",
|
||||
"descriptionState": "Edit the classes for this state classification model. Changes will require retraining the model.",
|
||||
"descriptionObject": "Edit the object type and classification type for this object classification model.",
|
||||
"stateClassesInfo": "Note: Changing state classes requires retraining the model with the updated classes."
|
||||
"enabled": "Enabled",
|
||||
"enabledDesc": "Run this model. When disabled, it stops running and no longer classifies.",
|
||||
"saveAttempts": "Save Attempts",
|
||||
"saveAttemptsDesc": "Number of classification attempt images to keep for the recent classifications UI.",
|
||||
"motion": "Run on Motion",
|
||||
"motionDesc": "Run classification when motion is detected within the configured crop.",
|
||||
"interval": "Interval",
|
||||
"intervalDesc": "Seconds between periodic classification runs. Leave empty to run only on motion.",
|
||||
"intervalPlaceholder": "No interval",
|
||||
"stateClassesInfo": "Model updated. Retrain the model for the class changes to take effect.",
|
||||
"errors": {
|
||||
"saveAttemptsInvalid": "Save attempts must be a whole number of 0 or greater",
|
||||
"intervalInvalid": "Interval must be a whole number greater than 0"
|
||||
}
|
||||
},
|
||||
"deleteDatasetImages": {
|
||||
"title": "Delete Dataset Images",
|
||||
|
||||
@@ -273,5 +273,69 @@
|
||||
"sailboat": "Purjekas",
|
||||
"soundtrack_music": "Filmimuusika",
|
||||
"jingle": "Kõlisemine/tilisemine",
|
||||
"theme_music": "Tunnusmuusika"
|
||||
"theme_music": "Tunnusmuusika",
|
||||
"steel_guitar": "Steel Kitarr",
|
||||
"tapping": "Koputamine",
|
||||
"strum": "Klimberdus",
|
||||
"drum_machine": "Trummimasin",
|
||||
"drum": "Trumm",
|
||||
"maraca": "Marakas",
|
||||
"bowed_string_instrument": "Poogenkeelpill",
|
||||
"singing_bowl": "Helikauss",
|
||||
"wind_noise": "Tuulemüra",
|
||||
"rustling_leaves": "Sahisevad lehed",
|
||||
"waves": "Lained",
|
||||
"steam": "Aur",
|
||||
"ship": "Laev",
|
||||
"motor_vehicle": "Mootorsõiduk",
|
||||
"rowboat": "Sõudepaat",
|
||||
"motorboat": "Mootorpaat",
|
||||
"waterfall": "Kosk",
|
||||
"ocean": "Ookean",
|
||||
"rain_on_surface": "Vihm pinnal",
|
||||
"stream": "Oja",
|
||||
"fire": "Tuli",
|
||||
"crackle": "Praksumine",
|
||||
"car_alarm": "Auto alarm",
|
||||
"truck": "Veoauto",
|
||||
"police_car": "Politseiauto",
|
||||
"ambulance": "Kiirabi",
|
||||
"fire_engine": "Tuletõrjeauto",
|
||||
"aircraft": "Lennuk",
|
||||
"aircraft_engine": "Lennukimootor",
|
||||
"jet_engine": "Reaktiivmootor",
|
||||
"propeller": "Propeller",
|
||||
"helicopter": "Helikopter",
|
||||
"fixed-wing_aircraft": "Fikseeritud tiivaga õhusõiduk",
|
||||
"train_horn": "Rongisignaal",
|
||||
"railroad_car": "Kaubavagun",
|
||||
"lawn_mower": "Muruniiduk",
|
||||
"chainsaw": "mootorsaag",
|
||||
"engine": "Mootor",
|
||||
"knock": "Koputus",
|
||||
"alarm": "Häire",
|
||||
"siren": "Sireen",
|
||||
"fire_alarm": "Tulekahjuhäire",
|
||||
"telephone": "Telefon",
|
||||
"telephone_bell_ringing": "Helisev telefon",
|
||||
"ringtone": "Telefonihelin",
|
||||
"smoke_detector": "Suitsuandur",
|
||||
"foghorn": "Udupasun",
|
||||
"whistle": "Vile",
|
||||
"printer": "Printer",
|
||||
"drill": "Puur",
|
||||
"explosion": "Plahvatus",
|
||||
"hammer": "Haamer",
|
||||
"air_conditioning": "Õhukonditsioneer",
|
||||
"gunshot": "Püssilask",
|
||||
"glass": "Klaas",
|
||||
"boom": "Pauk",
|
||||
"fireworks": "Ilutulestik",
|
||||
"static": "Staatiline",
|
||||
"white_noise": "Valge müra",
|
||||
"radio": "Raadio",
|
||||
"television": "Televiisor",
|
||||
"scream": "Karjumine",
|
||||
"pour": "Valamine",
|
||||
"drip": "Tilkumine"
|
||||
}
|
||||
|
||||
@@ -37,6 +37,9 @@
|
||||
"ask_an": "Kas see objekt on <code>{{label}}</code>?",
|
||||
"ask_full": "Kas see objekt on <code>{{untranslatedLabel}}</code> ({{translatedLabel}})?",
|
||||
"label": "Kinnita see silt Frigate+ teenuse jaoks"
|
||||
},
|
||||
"toast": {
|
||||
"error": "Frigate+ teenusesse saatmine ebaõnnestus. Palun kontrolli oma võrguühendust ja proovi uuesti."
|
||||
}
|
||||
},
|
||||
"submitToPlus": {
|
||||
@@ -70,18 +73,79 @@
|
||||
"success": "Eksportimise käivitamine õnnestus. Faili leiad eksportimise lehelt.",
|
||||
"view": "Vaata",
|
||||
"error": {
|
||||
"failed": "Eksportimise käivitamine ei õnnestunud: {{error}}",
|
||||
"failed": "Eksportimise järjekorda lisamine ei õnnestunud: {{error}}",
|
||||
"endTimeMustAfterStartTime": "Ajavahemiku lõpp peab olema peale algust",
|
||||
"noVaildTimeSelected": "Ühtegi kehtivat ajavahemikku pole valitud"
|
||||
}
|
||||
"noVaildTimeSelected": "Ühtegi kehtivat ajavahemikku pole valitud",
|
||||
"noValidTimeSelected": "Ühtegi korrektset ajavahemikku pole valitud"
|
||||
},
|
||||
"queued": "Eksport on järjekorda lisatud. Vaata progressi eksportide lehelt.",
|
||||
"batchSuccess_one": "Alustasin 1 ekspordiga. Avan juhtumi kohe.",
|
||||
"batchSuccess_other": "Alustasin {{count}} ekspordiga. Avan juhtumi kohe.",
|
||||
"batchPartial": "Alustasin {{successful}}/{{total}} ekspordiga. Ebaõnnestunud kaamerad: {{failedCameras}}",
|
||||
"batchFailed": "{{total}} eksporti ebaõnnestus algatada. Ebaõnnestunud kaamerad: {{failedCameras}}",
|
||||
"batchQueuedSuccess_one": "1 eksport järjekorda lisatud. Avan juhtumi kohe.",
|
||||
"batchQueuedSuccess_other": "{{count}} eksporti järjekorda lisatud. Avan juhtumi kohe.",
|
||||
"batchQueuedPartial": "{{successful}}/{{total}} ekspordist on järjekorda lisatud. Ebaõnnestunud kaamerad: {{failedCameras}}",
|
||||
"batchQueueFailed": "{{total}} eksporti ebaõnnestus järjekorda lisada. Ebaõnnestunud kaamerad: {{failedCameras}}"
|
||||
},
|
||||
"fromTimeline": {
|
||||
"saveExport": "Salvesta eksporditud sisu",
|
||||
"previewExport": "Eksporditud sisu eelvaade"
|
||||
"previewExport": "Eksporditud sisu eelvaade",
|
||||
"queueingExport": "Ekspordi järjekorda lisamine...",
|
||||
"useThisRange": "Kasuta seda vahemikku"
|
||||
},
|
||||
"case": {
|
||||
"label": "Juhtum",
|
||||
"placeholder": "Vali juhtum"
|
||||
"placeholder": "Vali juhtum",
|
||||
"newCaseOption": "Ava uus juhtum",
|
||||
"newCaseNamePlaceholder": "Uue juhtumi nimi",
|
||||
"newCaseDescriptionPlaceholder": "Juhtumi kirjeldus",
|
||||
"nonAdminHelp": "Uus juhtum avatakse järnevatele eksportidele."
|
||||
},
|
||||
"queueing": "Ekspordi järjekorda lisamine...",
|
||||
"tabs": {
|
||||
"export": "Üksik kaamera",
|
||||
"multiCamera": "Mitu kaamerat"
|
||||
},
|
||||
"multiCamera": {
|
||||
"timeRange": "Ajavahemik",
|
||||
"selectFromTimeline": "Vali ajajoonelt",
|
||||
"cameraSelection": "Kaamerad",
|
||||
"cameraSelectionHelp": "Selles ajavahemikus tuvastatud objektidega kaamerad on eelvalitud",
|
||||
"searchOrSelectGroup": "Otsi või vali kaamera grupp...",
|
||||
"selectAll": "Vali kõik kaamerad",
|
||||
"clearSelection": "Tühista valik",
|
||||
"selectWithActivity": "Jälgitud objektidega kaamerad",
|
||||
"selectGroup": "Vali grupp",
|
||||
"noMatchingCameras": "Ükski kaamera ei sobitunud otsinguga",
|
||||
"selectedCount": "{{selected}} / {{total}} valitud",
|
||||
"checkingActivity": "Kaamera aktiivsuse kontrollimine...",
|
||||
"noCameras": "Ühtegi kaamerat pole saadaval",
|
||||
"detectionCount_one": "1 jälgitav objekt",
|
||||
"detectionCount_other": "{{count}} jälgitavat objekti",
|
||||
"nameLabel": "Ekspordi nimi",
|
||||
"namePlaceholder": "Valikuline baasnimi nendele eksportidele",
|
||||
"queueingButton": "Ekspordi järjekorda lisamine...",
|
||||
"exportButton_one": "Ekspordi 1 kaamera",
|
||||
"exportButton_other": "Ekspordi {{count}} kaamerat"
|
||||
},
|
||||
"multi": {
|
||||
"title_one": "Ekspordi 1 ülevaade",
|
||||
"title_other": "Espordi {{count}} ülevaadet",
|
||||
"description": "Ekspordi valitud ülevaated. Kõik ekspordid on grupeeritud ühte juhtumisse.",
|
||||
"descriptionNoCase": "Ekspordi kõik valitud juhtumid.",
|
||||
"caseNamePlaceholder": "Ülevaate eksport - {{date}}",
|
||||
"exportButton_one": "Ekspordi 1 ülevaade",
|
||||
"exportButton_other": "Ekspordi {{count}} ülevaadet",
|
||||
"exportingButton": "Ekspordin...",
|
||||
"toast": {
|
||||
"started_one": "Alustasin 1 eksporti. Avan juhtumi kohe.",
|
||||
"started_other": "Alustasin {{count}} ekspordiga. Avan juhtumi kohe.",
|
||||
"startedNoCase_one": "Alustasin 1 ekspordiga.",
|
||||
"startedNoCase_other": "Alustasin {{count}} ekspordiga.",
|
||||
"partial": "Alustasin {{successful}}/{{total}} ekspordiga. Ebaõnnestusid: {{failedItems}}",
|
||||
"failed": "{{total}} ekspordi algatamine ebaõnnestus. Ebaõnnestunud: {{failedItems}}"
|
||||
}
|
||||
}
|
||||
},
|
||||
"streaming": {
|
||||
@@ -114,6 +178,14 @@
|
||||
"success": "Selle ülevaadatava objektiga seotud videosisu on kustutatud.",
|
||||
"error": "Kustutamine ei õnnestunud: {{error}}"
|
||||
}
|
||||
},
|
||||
"shareTimestamp": {
|
||||
"label": "Jaotise ajatempel",
|
||||
"title": "Jaotise ajatempel",
|
||||
"description": "Jaga praeguse esituskoha ajatempliga URL-i või vali kohandatud ajatempel. Arvesta, et see ei ole avalik jagamislink ja on kättesaadav ainult kasutajatele, kellel on ligipääs Frigate'ile ja sellele kaamerale.",
|
||||
"custom": "Kohandatud ajatempel",
|
||||
"button": "Jaga ajatempliga linki",
|
||||
"shareTitle": "Frigate ülevaate ajatempel: {{camera}}"
|
||||
}
|
||||
},
|
||||
"imagePicker": {
|
||||
|
||||
@@ -12,11 +12,20 @@
|
||||
"description": "Kasutusel"
|
||||
},
|
||||
"audio": {
|
||||
"label": "Helisündmused"
|
||||
"label": "Heli tuvastus",
|
||||
"min_volume": {
|
||||
"label": "Minimaalne helitase"
|
||||
},
|
||||
"filters": {
|
||||
"label": "Audio filtrid"
|
||||
}
|
||||
},
|
||||
"birdseye": {
|
||||
"mode": {
|
||||
"label": "Jälgimisrežiim"
|
||||
},
|
||||
"order": {
|
||||
"label": "Positsioon"
|
||||
}
|
||||
},
|
||||
"label": "Kaameraseadistus",
|
||||
@@ -25,7 +34,8 @@
|
||||
"threshold": {
|
||||
"description": "Minimaalne sarnasuse punktiskoor (0-1), mis on vajalik selle päästiku käivitamiseks."
|
||||
}
|
||||
}
|
||||
},
|
||||
"label": "Semantiline otsing"
|
||||
},
|
||||
"lpr": {
|
||||
"label": "Sõidukite numbrimärkide tuvastus",
|
||||
@@ -34,6 +44,171 @@
|
||||
"review": {
|
||||
"genai": {
|
||||
"description": "Kontrollib generatiivse tehisaru kasutamist kirjelduste ja kokkuvõtete koostamiseks ülevaatamisele kuuluvate objektide jaoks."
|
||||
},
|
||||
"alerts": {
|
||||
"enabled": {
|
||||
"label": "Luba häired"
|
||||
}
|
||||
}
|
||||
},
|
||||
"audio_transcription": {
|
||||
"label": "Audio transkriptsioon",
|
||||
"live_enabled": {
|
||||
"label": "Reaalajas transkriptsioon"
|
||||
},
|
||||
"enabled": {
|
||||
"label": "Luba heli üleskirjutamine tekstina"
|
||||
}
|
||||
},
|
||||
"detect": {
|
||||
"label": "Objekti tuvastus",
|
||||
"enabled": {
|
||||
"label": "Luba objektituvastus"
|
||||
},
|
||||
"height": {
|
||||
"label": "Tuvastamise kõrgus"
|
||||
},
|
||||
"width": {
|
||||
"label": "Tuvastamise laius"
|
||||
},
|
||||
"fps": {
|
||||
"label": "Tuvastamise kaadrisagedus"
|
||||
},
|
||||
"stationary": {
|
||||
"label": "Püsivate objektide sätted"
|
||||
}
|
||||
},
|
||||
"face_recognition": {
|
||||
"label": "Näotuvastus"
|
||||
},
|
||||
"ffmpeg": {
|
||||
"path": {
|
||||
"label": "FFmpeg asukoht"
|
||||
},
|
||||
"gpu": {
|
||||
"label": "GPU indeks"
|
||||
},
|
||||
"inputs": {
|
||||
"global_args": {
|
||||
"label": "FFmpeg globaalsed argumendid"
|
||||
}
|
||||
}
|
||||
},
|
||||
"live": {
|
||||
"label": "Reaalajas mahamängimine",
|
||||
"height": {
|
||||
"label": "Otseülekande kõrgus"
|
||||
},
|
||||
"quality": {
|
||||
"label": "Otseülekande kvaliteet"
|
||||
},
|
||||
"streams": {
|
||||
"label": "Otseülekande voo nimed"
|
||||
}
|
||||
},
|
||||
"motion": {
|
||||
"label": "Liikumistuvastus",
|
||||
"frame_height": {
|
||||
"label": "Kaadri kõrgus"
|
||||
},
|
||||
"enabled": {
|
||||
"label": "Luba liikumistuvastus"
|
||||
}
|
||||
},
|
||||
"objects": {
|
||||
"label": "Objektid",
|
||||
"filters": {
|
||||
"min_area": {
|
||||
"label": "Minimaalne objekti ala"
|
||||
},
|
||||
"max_area": {
|
||||
"label": "Maksimaalne objekti ala"
|
||||
}
|
||||
},
|
||||
"genai": {
|
||||
"label": "GenAI objekti konfiguratsioon",
|
||||
"required_zones": {
|
||||
"label": "Nõutud tsoonid"
|
||||
},
|
||||
"debug_save_thumbnails": {
|
||||
"label": "Salvesta pisipildid"
|
||||
},
|
||||
"enabled": {
|
||||
"label": "Luba GenAI"
|
||||
},
|
||||
"use_snapshot": {
|
||||
"label": "Kasuta hetktõmmiseid"
|
||||
}
|
||||
}
|
||||
},
|
||||
"mqtt": {
|
||||
"label": "MQTT"
|
||||
},
|
||||
"notifications": {
|
||||
"enabled": {
|
||||
"label": "Luba teavitused"
|
||||
},
|
||||
"email": {
|
||||
"label": "Teavituste email"
|
||||
},
|
||||
"label": "Teavitused"
|
||||
},
|
||||
"ui": {
|
||||
"label": "Kaamera kasutajaliides"
|
||||
},
|
||||
"record": {
|
||||
"label": "Salvestus",
|
||||
"enabled": {
|
||||
"label": "Luba salvestamine"
|
||||
}
|
||||
},
|
||||
"snapshots": {
|
||||
"enabled": {
|
||||
"label": "Luba hetktõmmised"
|
||||
}
|
||||
},
|
||||
"timestamp_style": {
|
||||
"color": {
|
||||
"red": {
|
||||
"label": "Punane",
|
||||
"description": "Punase komponent (0–255) ajatempli värvi jaoks."
|
||||
},
|
||||
"green": {
|
||||
"label": "Roheline",
|
||||
"description": "Rohelise komponent (0–255) ajatempli värvi jaoks."
|
||||
},
|
||||
"blue": {
|
||||
"label": "Sinine",
|
||||
"description": "Sinise komponent (0–255) ajatempli värvi jaoks."
|
||||
},
|
||||
"label": "Ajatempli värv",
|
||||
"description": "Ajatempli teksti RGB värviväärtused (kõik väärtused 0–255)."
|
||||
},
|
||||
"thickness": {
|
||||
"label": "Ajatempli paksus",
|
||||
"description": "Ajatempli teksti joone paksus."
|
||||
},
|
||||
"effect": {
|
||||
"label": "Ajatempli efekt",
|
||||
"description": "Ajatempli teksti visuaalne efekt (puudub, ühtlane, vari)."
|
||||
}
|
||||
},
|
||||
"onvif": {
|
||||
"user": {
|
||||
"label": "ONVIF kasutajanimi"
|
||||
},
|
||||
"password": {
|
||||
"label": "ONVIF parool"
|
||||
},
|
||||
"port": {
|
||||
"label": "ONVIF port"
|
||||
},
|
||||
"label": "ONVIF",
|
||||
"host": {
|
||||
"label": "ONVIF host"
|
||||
}
|
||||
},
|
||||
"profiles": {
|
||||
"label": "Profiilid"
|
||||
}
|
||||
}
|
||||
|
||||
@@ -1,10 +1,31 @@
|
||||
{
|
||||
"audio": {
|
||||
"label": "Helisündmused"
|
||||
"label": "Heli tuvastus",
|
||||
"min_volume": {
|
||||
"label": "Minimaalne helitase"
|
||||
},
|
||||
"filters": {
|
||||
"label": "Audio filtrid"
|
||||
}
|
||||
},
|
||||
"birdseye": {
|
||||
"mode": {
|
||||
"label": "Jälgimisrežiim"
|
||||
},
|
||||
"order": {
|
||||
"label": "Positsioon"
|
||||
},
|
||||
"height": {
|
||||
"label": "Kõrgus"
|
||||
},
|
||||
"width": {
|
||||
"label": "Laius"
|
||||
},
|
||||
"layout": {
|
||||
"label": "Paigutus",
|
||||
"scaling_factor": {
|
||||
"label": "Skaleerimistegur"
|
||||
}
|
||||
}
|
||||
},
|
||||
"version": {
|
||||
@@ -22,6 +43,15 @@
|
||||
"threshold": {
|
||||
"label": "Punktiskoori lävend",
|
||||
"description": "Punktiskoori lävend, mida kasutatakse klassifitseerimise oleku muutmiseks."
|
||||
},
|
||||
"enabled": {
|
||||
"label": "Luba mudel"
|
||||
},
|
||||
"name": {
|
||||
"label": "Mudeli nimi"
|
||||
},
|
||||
"save_attempts": {
|
||||
"label": "Salvestamiskatsed"
|
||||
}
|
||||
}
|
||||
},
|
||||
@@ -30,11 +60,25 @@
|
||||
"threshold": {
|
||||
"description": "Minimaalne sarnasuse punktiskoor (0-1), mis on vajalik selle päästiku käivitamiseks."
|
||||
}
|
||||
},
|
||||
"label": "Semantiline otsing",
|
||||
"model_size": {
|
||||
"label": "Mudeli suurus"
|
||||
},
|
||||
"device": {
|
||||
"label": "Seade"
|
||||
}
|
||||
},
|
||||
"face_recognition": {
|
||||
"unknown_score": {
|
||||
"label": "Tundmatu punktiskoori lävend"
|
||||
},
|
||||
"label": "Näotuvastus",
|
||||
"model_size": {
|
||||
"label": "Mudeli suurus"
|
||||
},
|
||||
"device": {
|
||||
"label": "Seade"
|
||||
}
|
||||
},
|
||||
"lpr": {
|
||||
@@ -42,15 +86,393 @@
|
||||
"description": "Sõidukite numbrimärkide tuvastuse seadistus sisaldab tuvastuse lävendeid, vormindust ja teadaolevaid numbrimärke.",
|
||||
"enabled": {
|
||||
"description": "Lülita sõidukite numbrimärkide tuvastus kõikide kaamerate jaoks sisse; seda saad kaamerakohaselt ka sürjutada."
|
||||
},
|
||||
"model_size": {
|
||||
"label": "Mudeli suurus"
|
||||
},
|
||||
"device": {
|
||||
"label": "Seade"
|
||||
}
|
||||
},
|
||||
"genai": {
|
||||
"label": "Generatiivse tehisaru seadistus",
|
||||
"description": "Seadistsued generatiivse tehisaru teenusepakkujate kasutamisel kirjelduste ja kokkuvõtete loomiseks ülevaatamisele kuuluvate objektide jaoks."
|
||||
"description": "Seadistsued generatiivse tehisaru teenusepakkujate kasutamisel kirjelduste ja kokkuvõtete loomiseks ülevaatamisele kuuluvate objektide jaoks.",
|
||||
"model": {
|
||||
"label": "Mudel"
|
||||
},
|
||||
"roles": {
|
||||
"label": "Rollid"
|
||||
},
|
||||
"api_key": {
|
||||
"label": "API võti"
|
||||
},
|
||||
"base_url": {
|
||||
"label": "Baas-URL"
|
||||
}
|
||||
},
|
||||
"review": {
|
||||
"genai": {
|
||||
"description": "Kontrollib generatiivse tehisaru kasutamist kirjelduste ja kokkuvõtete koostamiseks ülevaatamisele kuuluvate objektide jaoks."
|
||||
},
|
||||
"alerts": {
|
||||
"enabled": {
|
||||
"label": "Luba häired",
|
||||
"description": "Luba või keela kõigi kaamerate häirete genereerimine; seda saab kaamerati eraldi muuta."
|
||||
}
|
||||
}
|
||||
},
|
||||
"audio_transcription": {
|
||||
"label": "Audio transkriptsioon",
|
||||
"live_enabled": {
|
||||
"label": "Reaalajas transkriptsioon"
|
||||
},
|
||||
"enabled": {
|
||||
"label": "Luba heli üleskirjutamine tekstina",
|
||||
"description": "Luba või keela automaatne heli üleskirjutamine tektina kõigi kaamerate jaoks; seda saad kaamerati eraldi muuta."
|
||||
},
|
||||
"language": {
|
||||
"label": "Üleskirjutuse keel",
|
||||
"description": "Üleskirjutuse/tõlkimise jaoks kasutatav keelekood (näiteks „en” inglise keele puhul). Toetatud keelekoodid leiad lehelt https://whisper-api.com/docs/languages/."
|
||||
},
|
||||
"device": {
|
||||
"label": "Üleskirjutusseade"
|
||||
},
|
||||
"model_size": {
|
||||
"label": "Mudeli suurus"
|
||||
}
|
||||
},
|
||||
"detect": {
|
||||
"label": "Objekti tuvastus",
|
||||
"enabled": {
|
||||
"label": "Luba objektituvastus"
|
||||
},
|
||||
"height": {
|
||||
"label": "Tuvastamise kõrgus"
|
||||
},
|
||||
"width": {
|
||||
"label": "Tuvastamise laius"
|
||||
},
|
||||
"fps": {
|
||||
"label": "Tuvastamise kaadrisagedus"
|
||||
},
|
||||
"stationary": {
|
||||
"label": "Püsivate objektide sätted"
|
||||
}
|
||||
},
|
||||
"ffmpeg": {
|
||||
"path": {
|
||||
"label": "FFmpeg asukoht"
|
||||
},
|
||||
"gpu": {
|
||||
"label": "GPU indeks"
|
||||
},
|
||||
"inputs": {
|
||||
"global_args": {
|
||||
"label": "FFmpeg globaalsed argumendid"
|
||||
}
|
||||
},
|
||||
"label": "FFmpeg"
|
||||
},
|
||||
"live": {
|
||||
"label": "Reaalajas mahamängimine",
|
||||
"height": {
|
||||
"label": "Otseülekande kõrgus"
|
||||
},
|
||||
"quality": {
|
||||
"label": "Otseülekande kvaliteet"
|
||||
},
|
||||
"streams": {
|
||||
"label": "Otseülekande voo nimed"
|
||||
}
|
||||
},
|
||||
"motion": {
|
||||
"label": "Liikumistuvastus",
|
||||
"frame_height": {
|
||||
"label": "Kaadri kõrgus"
|
||||
},
|
||||
"enabled": {
|
||||
"label": "Luba liikumistuvastus"
|
||||
}
|
||||
},
|
||||
"objects": {
|
||||
"label": "Objektid",
|
||||
"filters": {
|
||||
"min_area": {
|
||||
"label": "Minimaalne objekti ala"
|
||||
},
|
||||
"max_area": {
|
||||
"label": "Maksimaalne objekti ala"
|
||||
}
|
||||
},
|
||||
"genai": {
|
||||
"label": "GenAI objekti konfiguratsioon",
|
||||
"required_zones": {
|
||||
"label": "Nõutud tsoonid"
|
||||
},
|
||||
"debug_save_thumbnails": {
|
||||
"label": "Salvesta pisipildid"
|
||||
},
|
||||
"enabled": {
|
||||
"label": "Luba GenAI"
|
||||
},
|
||||
"use_snapshot": {
|
||||
"label": "Kasuta hetktõmmiseid"
|
||||
}
|
||||
}
|
||||
},
|
||||
"auth": {
|
||||
"reset_admin_password": {
|
||||
"label": "Lähtesta administraatori parool"
|
||||
},
|
||||
"cookie_name": {
|
||||
"label": "JWT küpsise nimi"
|
||||
},
|
||||
"enabled": {
|
||||
"label": "Luba autentimine"
|
||||
},
|
||||
"label": "Autentimine",
|
||||
"session_length": {
|
||||
"label": "Sessiooni pikkus"
|
||||
}
|
||||
},
|
||||
"database": {
|
||||
"label": "Andmebaas"
|
||||
},
|
||||
"mqtt": {
|
||||
"label": "MQTT",
|
||||
"enabled": {
|
||||
"label": "Luba MQTT"
|
||||
},
|
||||
"user": {
|
||||
"label": "MQTT kasutajanimi"
|
||||
},
|
||||
"tls_ca_certs": {
|
||||
"label": "TLS CA sertifikaat"
|
||||
},
|
||||
"tls_client_cert": {
|
||||
"label": "Kliendi sertifikaat"
|
||||
},
|
||||
"host": {
|
||||
"label": "MQTT host",
|
||||
"description": "MQTT maakleri hostinimi või IP-aadress."
|
||||
},
|
||||
"port": {
|
||||
"label": "MQTT port"
|
||||
},
|
||||
"client_id": {
|
||||
"label": "Kliendi ID"
|
||||
},
|
||||
"stats_interval": {
|
||||
"label": "Statistika intervall"
|
||||
},
|
||||
"tls_client_key": {
|
||||
"label": "Kliendi võti"
|
||||
},
|
||||
"qos": {
|
||||
"label": "MQTT QoS"
|
||||
}
|
||||
},
|
||||
"go2rtc": {
|
||||
"label": "go2rtc"
|
||||
},
|
||||
"notifications": {
|
||||
"enabled": {
|
||||
"label": "Luba teavitused"
|
||||
},
|
||||
"email": {
|
||||
"label": "Teavituste email"
|
||||
},
|
||||
"label": "Teavitused"
|
||||
},
|
||||
"networking": {
|
||||
"ipv6": {
|
||||
"label": "IPv6 sätted",
|
||||
"enabled": {
|
||||
"label": "Luba IPv6"
|
||||
}
|
||||
},
|
||||
"label": "Võrguühendus",
|
||||
"listen": {
|
||||
"internal": {
|
||||
"label": "Sisemine port"
|
||||
},
|
||||
"external": {
|
||||
"label": "Välimine port"
|
||||
}
|
||||
}
|
||||
},
|
||||
"proxy": {
|
||||
"label": "Vaheserver",
|
||||
"logout_url": {
|
||||
"label": "Väljalogimise url"
|
||||
},
|
||||
"default_role": {
|
||||
"label": "Vaikimisi roll"
|
||||
}
|
||||
},
|
||||
"telemetry": {
|
||||
"stats": {
|
||||
"label": "Süsteemi statistika",
|
||||
"amd_gpu_stats": {
|
||||
"label": "AMD GPU statistika"
|
||||
},
|
||||
"intel_gpu_stats": {
|
||||
"label": "Intel GPU statistika"
|
||||
},
|
||||
"intel_gpu_device": {
|
||||
"label": "Intel GPU seade"
|
||||
},
|
||||
"network_bandwidth": {
|
||||
"label": "Võrgu ribalaius"
|
||||
}
|
||||
},
|
||||
"label": "Telemeetria",
|
||||
"network_interfaces": {
|
||||
"label": "Võrguliides"
|
||||
},
|
||||
"version_check": {
|
||||
"label": "Versiooni kontroll",
|
||||
"description": "Luba väljaminev ühendus, et kontrollida uuema Frigate versooni olemasolu."
|
||||
}
|
||||
},
|
||||
"tls": {
|
||||
"label": "TLS",
|
||||
"enabled": {
|
||||
"label": "Luba TLS"
|
||||
}
|
||||
},
|
||||
"ui": {
|
||||
"timezone": {
|
||||
"label": "ajavöönd"
|
||||
},
|
||||
"label": "Kasutajaliides",
|
||||
"description": "Kasutajaliidese eelistused nagu ajavöönd, aja ja kuupäeva formaat ning ühikud.",
|
||||
"unit_system": {
|
||||
"label": "Ühikute süsteem"
|
||||
}
|
||||
},
|
||||
"detectors": {
|
||||
"label": "Detektori riistvara",
|
||||
"cpu": {
|
||||
"label": "CPU"
|
||||
},
|
||||
"deepstack": {
|
||||
"label": "DeepStack"
|
||||
},
|
||||
"edgetpu": {
|
||||
"label": "EdgeTPU"
|
||||
},
|
||||
"onnx": {
|
||||
"label": "ONNX",
|
||||
"device": {
|
||||
"label": "Seadme tüüp"
|
||||
}
|
||||
},
|
||||
"rknn": {
|
||||
"label": "RKNN"
|
||||
},
|
||||
"openvino": {
|
||||
"label": "OpenVINO"
|
||||
},
|
||||
"tensorrt": {
|
||||
"label": "TensorRT"
|
||||
},
|
||||
"teflon_tfl": {
|
||||
"label": "Teflon"
|
||||
},
|
||||
"synaptics": {
|
||||
"label": "Synaptics"
|
||||
},
|
||||
"memryx": {
|
||||
"device": {
|
||||
"label": "Seadme asukoht"
|
||||
},
|
||||
"label": "MemryX"
|
||||
},
|
||||
"hailo8l": {
|
||||
"label": "Hailo-8/Hailo-8L"
|
||||
},
|
||||
"zmq": {
|
||||
"label": "ZMQ IPC"
|
||||
},
|
||||
"axengine": {
|
||||
"label": "AXEngine NPU"
|
||||
},
|
||||
"degirum": {
|
||||
"label": "DeGirum"
|
||||
}
|
||||
},
|
||||
"logger": {
|
||||
"label": "Logimine"
|
||||
},
|
||||
"model": {
|
||||
"label": "Tuvastusmudel"
|
||||
},
|
||||
"record": {
|
||||
"label": "Salvestus",
|
||||
"enabled": {
|
||||
"label": "Luba salvestamine",
|
||||
"description": "Luba või keela salvestamine kõigi kaamerate jaoks; seda saab kaamerati eraldi muuta."
|
||||
}
|
||||
},
|
||||
"snapshots": {
|
||||
"enabled": {
|
||||
"label": "Luba hetktõmmised",
|
||||
"description": "Luba või keela kõigi kaamerate hetktõmmiste salvestamine; seda saab kaamerati eraldi muuta."
|
||||
}
|
||||
},
|
||||
"timestamp_style": {
|
||||
"color": {
|
||||
"red": {
|
||||
"label": "Punane",
|
||||
"description": "Punase komponent (0–255) ajatempli värvi jaoks."
|
||||
},
|
||||
"green": {
|
||||
"label": "Roheline",
|
||||
"description": "Rohelise komponent (0–255) ajatempli värvi jaoks."
|
||||
},
|
||||
"blue": {
|
||||
"label": "Sinine",
|
||||
"description": "Sinise komponent (0–255) ajatempli värvi jaoks."
|
||||
},
|
||||
"label": "Ajatempli värv",
|
||||
"description": "Ajatempli teksti RGB värviväärtused (kõik väärtused 0–255)."
|
||||
},
|
||||
"thickness": {
|
||||
"label": "Ajatempli paksus",
|
||||
"description": "Ajatempli teksti joone paksus."
|
||||
},
|
||||
"effect": {
|
||||
"label": "Ajatempli efekt",
|
||||
"description": "Ajatempli teksti visuaalne efekt (puudub, ühtlane, vari)."
|
||||
}
|
||||
},
|
||||
"onvif": {
|
||||
"user": {
|
||||
"label": "ONVIF kasutajanimi"
|
||||
},
|
||||
"password": {
|
||||
"label": "ONVIF parool"
|
||||
},
|
||||
"port": {
|
||||
"label": "ONVIF port"
|
||||
},
|
||||
"label": "ONVIF",
|
||||
"host": {
|
||||
"label": "ONVIF host"
|
||||
}
|
||||
},
|
||||
"camera_mqtt": {
|
||||
"quality": {
|
||||
"label": "JPEG kvaliteet",
|
||||
"description": "MQTT-sse saadetud piltide JPEG-kvaliteet (0–100)."
|
||||
},
|
||||
"enabled": {
|
||||
"label": "Saada pilt"
|
||||
},
|
||||
"label": "MQTT"
|
||||
},
|
||||
"profiles": {
|
||||
"label": "Profiilid"
|
||||
}
|
||||
}
|
||||
|
||||
@@ -6,5 +6,67 @@
|
||||
"error": "Midagi läks valesti. Palun proovi uuesti.",
|
||||
"processing": "Töötlen…",
|
||||
"toolsUsed": "Kasutatud: {{tools}}",
|
||||
"similarity_score": "Sarnasus"
|
||||
"similarity_score": "Sarnasus",
|
||||
"showTools": "Näita tööriistu ({{count}})",
|
||||
"hideTools": "Peida tööriistad",
|
||||
"call": "Kutse",
|
||||
"result": "Tulemus",
|
||||
"arguments": "Argumendid:",
|
||||
"response": "Vastus:",
|
||||
"send": "Saada",
|
||||
"new_chat": "Uus vestlus",
|
||||
"settings": {
|
||||
"title": "Vestluse sätted",
|
||||
"show_stats": {
|
||||
"title": "Näita statistikat",
|
||||
"always": "Alati",
|
||||
"desc": "Kuva vastuste genereerimise kiirus ja konteksti suurus.",
|
||||
"while_generating": "Genereerimise ajal"
|
||||
},
|
||||
"auto_scroll": {
|
||||
"title": "Automaatne kerimine",
|
||||
"desc": "Jälgi uusi sõnumeid kohe, kui need saabuvad."
|
||||
}
|
||||
},
|
||||
"stats": {
|
||||
"context": "{{tokens}} tokenit",
|
||||
"tokens_per_second": "{{rate}} t/s"
|
||||
},
|
||||
"starting_requests_prompts": {
|
||||
"recap": "Mis juhtus kui ma olin ära?",
|
||||
"watch_camera": "Jälgi ust ja anna mulle teada kui keegi tuleb",
|
||||
"show_camera_status": "Milline on minu kaamerate praegune seis?",
|
||||
"show_recent_events": "Näita mulle viimase tunni viimaseid sündmusi"
|
||||
},
|
||||
"starting_requests": {
|
||||
"show_camera_status": "Näita kaamera olekut",
|
||||
"show_recent_events": "Kuva hiljutised sündmused",
|
||||
"recap": "Mis juhtus kui ma olin ära?",
|
||||
"watch_camera": "Jälgi kaamerat aktiivsuse osas"
|
||||
},
|
||||
"suggested_requests": "Proovi küsida:",
|
||||
"semantic_search_required": "Sarnaste objektide leidmiseks peab olema lubatud semantiline otsing.",
|
||||
"no_similar_objects_found": "Sarnaseid objekte ei leitud.",
|
||||
"quick_reply_when_else": "Millal seda veel nähti?",
|
||||
"quick_reply_find_similar_text": "Leia sarnaseid vaatepilte.",
|
||||
"quick_reply_tell_me_more_text": "Räägi mulle sellest lähemalt.",
|
||||
"quick_reply_when_else_text": "Millal seda veel nähtud on?",
|
||||
"attach_event_aria": "Lisa sündmus {{eventId}}",
|
||||
"attachment_picker_paste_label": "Või kleebi sündmuse ID",
|
||||
"attachment_picker_attach": "Lisa",
|
||||
"attachment_picker_placeholder": "Lisa sündmus",
|
||||
"quick_reply_find_similar": "Leia sarnaseid vaatlusi",
|
||||
"quick_reply_tell_me_more": "Räägi mulle sellest lähemalt",
|
||||
"attachment_chip_remove": "Eemalda manus",
|
||||
"attachment_chip_label": "{{label}} kaameras {{camera}}",
|
||||
"open_in_explore": "Ava uurimisvaates",
|
||||
"anchor": "Viide",
|
||||
"reasoning": {
|
||||
"active": "Otsin põhjendust…",
|
||||
"show": "Näita põhjendust",
|
||||
"hide": "Peida põhjendus"
|
||||
},
|
||||
"thinking": {
|
||||
"toggle": "Näita mõtlemise olekut või peida see"
|
||||
}
|
||||
}
|
||||
|
||||
@@ -43,5 +43,8 @@
|
||||
},
|
||||
"tooltip": {
|
||||
"trainingInProgress": "Mudel on parasjagu õppimas"
|
||||
},
|
||||
"train": {
|
||||
"titleShort": "Hiljutised"
|
||||
}
|
||||
}
|
||||
|
||||
@@ -5,7 +5,8 @@
|
||||
"noTrackedObjects": "Ühtegi jälgitavat objekti ei leidunud",
|
||||
"itemMenu": {
|
||||
"findSimilar": {
|
||||
"aria": "Otsi sarnaseid jälgitavaid objekte"
|
||||
"aria": "Otsi sarnaseid jälgitavaid objekte",
|
||||
"label": "Leia sarnane"
|
||||
},
|
||||
"downloadSnapshot": {
|
||||
"label": "Laadi hetkvõte alla",
|
||||
@@ -14,6 +15,14 @@
|
||||
"downloadCleanSnapshot": {
|
||||
"label": "Laadi puhas hetkvõte alla",
|
||||
"aria": "Laadi puhas hetkvõte alla"
|
||||
},
|
||||
"viewTrackingDetails": {
|
||||
"label": "Vaata jälgimise üksikasju",
|
||||
"aria": "Näita jälgimise üksikasju"
|
||||
},
|
||||
"downloadVideo": {
|
||||
"label": "Laadi video alla",
|
||||
"aria": "Laadi video alla"
|
||||
}
|
||||
},
|
||||
"trackingDetails": {
|
||||
@@ -21,11 +30,22 @@
|
||||
"showAllZones": {
|
||||
"title": "Näita kõiki tsoone",
|
||||
"desc": "Kui objekt on sisenenud tsooni, siis alati näida tsooni märgistust."
|
||||
},
|
||||
"title": "Annotatsioonide seaded",
|
||||
"offset": {
|
||||
"desc": "Need andmed pärinevad teie kaamera tuvastusvoost, kuid on salvestusvoo piltide peal. On ebatõenäoline, et need kaks voogu on ideaalselt sünkroonis. Seetõttu ei joondu piirav kast ja kaader ideaalselt. Selle säte abil saad annotatsioone ajas edasi või tagasi nihutada, et need salvestatud kaadriga paremini joonduks.",
|
||||
"millisecondsToOffset": "Millisekundid annotatsioonide tuvastuse nihutamiseks. <em>Vaikimisi: 0</em>",
|
||||
"tips": "Vähendage väärtust, kui video taasesitus on kastidest ja teekonnapunktidest ees, ning suurendage väärtust, kui video taasesitus on neist maas. See väärtus võib olla negatiivne.",
|
||||
"toast": {
|
||||
"success": "Kaamera '{{camera}}' annotatsiooni nihe on konfiguratsioonifaili salvestatud."
|
||||
},
|
||||
"label": "Annotatsiooni nihe"
|
||||
}
|
||||
},
|
||||
"lifecycleItemDesc": {
|
||||
"attribute": {
|
||||
"other": "{{label}} on tuvastatud kui {{attribute}}"
|
||||
"other": "{{label}} on tuvastatud kui {{attribute}}",
|
||||
"faceOrLicense_plate": "{{attribute}} tuvastatud objektil {{label}}"
|
||||
},
|
||||
"stationary": "{{label}} jäi paigale",
|
||||
"active": "{{label}} muutus aktiivseks",
|
||||
@@ -37,7 +57,8 @@
|
||||
"area": "Ala",
|
||||
"score": "Punktiskoor",
|
||||
"computedScore": "Arvutatud punktiskoor",
|
||||
"topScore": "Suuremad punktiskoorid"
|
||||
"topScore": "Suuremad punktiskoorid",
|
||||
"toggleAdvancedScores": "Täpsemate tulemuste sisse-/väljalülitamine"
|
||||
},
|
||||
"external": "{{label}} on tuvastatud",
|
||||
"heard": "{{label}} on kuuldud",
|
||||
@@ -50,7 +71,11 @@
|
||||
"previous": "Eelmine slaid",
|
||||
"next": "Järgmine slaid"
|
||||
},
|
||||
"count": "{{first}} / {{second}}"
|
||||
"count": "{{first}} / {{second}}",
|
||||
"adjustAnnotationSettings": "Korrigeeri annotatsioonide seadeid",
|
||||
"scrollViewTips": "Klõpsake selle objekti elutsükli oluliste hetkede vaatamiseks.",
|
||||
"autoTrackingTips": "Piirdekastide asukohad on automaatselt jälgivate kaamerate puhul ebatäpsed.",
|
||||
"trackedPoint": "Jälgitav punkt"
|
||||
},
|
||||
"documentTitle": "Avasta - Frigate",
|
||||
"generativeAI": "Generatiivne tehisaru",
|
||||
@@ -64,7 +89,22 @@
|
||||
},
|
||||
"startingUp": "Käivitun…",
|
||||
"estimatedTime": "Hinnanguliselt jäänud aega:",
|
||||
"finishingShortly": "Lõpetan õige pea"
|
||||
"finishingShortly": "Lõpetan õige pea",
|
||||
"context": "Avastamist saab kasutada pärast seda, kui jälgitavate objektide manustamine on uuesti indekseerimise lõpetanud."
|
||||
},
|
||||
"title": "Avastamine pole saadaval",
|
||||
"downloadingModels": {
|
||||
"context": "Frigate laadib alla semantilise otsingu funktsiooni toetamiseks vajalikke manustamismudeleid. See võib võtta mitu minutit, olenevalt teie võrguühenduse kiirusest.",
|
||||
"setup": {
|
||||
"visionModel": "Nägemismudel",
|
||||
"visionModelFeatureExtractor": "Nägemismudeli tunnuste eraldaja",
|
||||
"textModel": "Tekstimudel",
|
||||
"textTokenizer": "Teksti tokenisaator"
|
||||
},
|
||||
"tips": {
|
||||
"context": "Pärast mudelite allalaadimist tuleks oma jälgitavate objektide manused uuesti indekseerida."
|
||||
},
|
||||
"error": "Tekkis viga. Kontrollige Frigate'i logisid."
|
||||
}
|
||||
},
|
||||
"type": {
|
||||
@@ -107,8 +147,52 @@
|
||||
},
|
||||
"recognizedLicensePlate": "Tuvastatud sõiduki numbrimärk",
|
||||
"description": {
|
||||
"aiTips": "Frigate ei küsi sinu generatiivse tehisaru teenusepakkujalt kirjeldust enne, kui jälgitava objekti elutsükkel on lõppenud."
|
||||
"aiTips": "Frigate ei küsi sinu generatiivse tehisaru teenusepakkujalt kirjeldust enne, kui jälgitava objekti elutsükkel on lõppenud.",
|
||||
"label": "Kirjeldus",
|
||||
"placeholder": "Jälgitava objekti kirjeldus"
|
||||
},
|
||||
"label": "Silt",
|
||||
"editSubLabel": {
|
||||
"title": "Muuda alamsilti",
|
||||
"desc": "Sisesta sildile '{{label}}' uus alamsilt",
|
||||
"descNoLabel": "Sisesta sellele jälgitavale objektile uus alamsilt"
|
||||
},
|
||||
"camera": "Kaamera",
|
||||
"zones": "Tsoonid",
|
||||
"title": {
|
||||
"label": "Pealkiri"
|
||||
},
|
||||
"button": {
|
||||
"findSimilar": "Leia sarnane",
|
||||
"regenerate": {
|
||||
"title": "Taasloomine",
|
||||
"label": "Jälgitava objekti kirjelduse uuesti genereerimine"
|
||||
}
|
||||
},
|
||||
"topScore": {
|
||||
"label": "Parim punktiskoor",
|
||||
"info": "Kõrgeim skoor on jälgitava objekti kõrgeim mediaanskoor, seega võib see erineda otsingutulemuste pisipildil kuvatavast skoorist."
|
||||
},
|
||||
"objects": "Objektid",
|
||||
"tips": {
|
||||
"descriptionSaved": "Kirjeldus salvestati edukalt",
|
||||
"saveDescriptionFailed": "Kirjelduse uuendamine ebaõnnestus: {{errorMessage}}"
|
||||
},
|
||||
"attributes": "Klassifikatsiooni atribuudid",
|
||||
"estimatedSpeed": "Hinnanguline kiirus",
|
||||
"editAttributes": {
|
||||
"title": "Atribuutide muutmine",
|
||||
"desc": "Valige selle sildi '{{label}}' jaoks klassifikatsiooniatribuudid"
|
||||
}
|
||||
},
|
||||
"trackedObjectDetails": "Jälgitava objekti üksikasjad"
|
||||
"trackedObjectDetails": "Jälgitava objekti üksikasjad",
|
||||
"aiAnalysis": {
|
||||
"title": "Tehisintellekti analüüs"
|
||||
},
|
||||
"concerns": {
|
||||
"label": "Mured"
|
||||
},
|
||||
"objectLifecycle": {
|
||||
"noImageFound": "Selle jälgitava objekti kohta ei leitud pilti."
|
||||
}
|
||||
}
|
||||
|
||||
@@ -16,12 +16,15 @@
|
||||
"downloadVideo": "Laadi video alla",
|
||||
"editName": "Muuda nime",
|
||||
"deleteExport": "Kustuta eksporditud sisu",
|
||||
"assignToCase": "Lisa juhtumile"
|
||||
"assignToCase": "Lisa juhtumile",
|
||||
"removeFromCase": "Eemalda juhtumist"
|
||||
},
|
||||
"toast": {
|
||||
"error": {
|
||||
"renameExportFailed": "Eksporditud sisu nime muutmine ei õnnestunud: {{errorMessage}}",
|
||||
"assignCaseFailed": "Juhtumiga seose uuendamine ei õnnestunud: {{errorMessage}}"
|
||||
"assignCaseFailed": "Juhtumiga seose uuendamine ei õnnestunud: {{errorMessage}}",
|
||||
"caseSaveFailed": "Juhtumi salvestamine ei õnnestunud: {{errorMessage}}",
|
||||
"caseDeleteFailed": "Juhtumi kustutamine ei õnnestunud: {{errorMessage}}"
|
||||
}
|
||||
},
|
||||
"headings": {
|
||||
@@ -35,5 +38,44 @@
|
||||
"nameLabel": "Juhtumi nimi",
|
||||
"descriptionLabel": "Kirjeldus",
|
||||
"description": "Vali olemasolev juhtum või lisa uus."
|
||||
},
|
||||
"toolbar": {
|
||||
"newCase": "Uus juhtum",
|
||||
"addExport": "Lisa eksportimiseks",
|
||||
"editCase": "Muuda juhtumit",
|
||||
"deleteCase": "Kustuta juhtum"
|
||||
},
|
||||
"deleteCase": {
|
||||
"label": "Kustuta juhtum",
|
||||
"desc": "Kas oled kindel, et soovid „{{caseName}}“ juhtumi kustutada?"
|
||||
},
|
||||
"caseCard": {
|
||||
"emptyCase": "Eksportimise veel pole"
|
||||
},
|
||||
"jobCard": {
|
||||
"defaultName": "Eksportimine kaamerast „{{camera}}“",
|
||||
"queued": "Lisatud järjekorda",
|
||||
"running": "Töös",
|
||||
"preparing": "Ettevalmistamisel",
|
||||
"copying": "Kopeerimisel",
|
||||
"encoding": "Kodeerimisel",
|
||||
"encodingRetry": "Kodeerimisel (uuesti)",
|
||||
"finalizing": "Lõpetamisel"
|
||||
},
|
||||
"caseView": {
|
||||
"noDescription": "Kirjeldust pole",
|
||||
"createdAt": "Loodud {{value}}",
|
||||
"exportCount_one": "1 eksportimine",
|
||||
"exportCount_other": "{{count}} eksportimist",
|
||||
"cameraCount_one": "1 kaamera",
|
||||
"cameraCount_other": "{{count}} kaamerat",
|
||||
"showMore": "Näita rohkem",
|
||||
"showLess": "Näita vähem",
|
||||
"emptyTitle": "See juhtum on tühi"
|
||||
},
|
||||
"caseEditor": {
|
||||
"namePlaceholder": "Juhtumi nimi",
|
||||
"editTitle": "Muuda juhtumit",
|
||||
"createTitle": "Lisa juhtum"
|
||||
}
|
||||
}
|
||||
|
||||
@@ -1,6 +1,11 @@
|
||||
{
|
||||
"button": {
|
||||
"uploadImage": "Laadi pilt üles"
|
||||
"uploadImage": "Laadi pilt üles",
|
||||
"deleteFaceAttempts": "Kustuta näod",
|
||||
"addFace": "Lisa nägu",
|
||||
"renameFace": "Nimeta nägu ümber",
|
||||
"deleteFace": "Kustuta nägu",
|
||||
"reprocessFace": "Käivita näotuvastus uuesti"
|
||||
},
|
||||
"collections": "Kogumikud",
|
||||
"description": {
|
||||
@@ -11,28 +16,41 @@
|
||||
},
|
||||
"documentTitle": "Näoteek - Frigate",
|
||||
"createFaceLibrary": {
|
||||
"new": "Lisa uus nägu"
|
||||
"new": "Lisa uus nägu",
|
||||
"nextSteps": "Tugeva tuvastusaluse loomiseks:<li>Kasuta vahekaarti \"Hiljutised tuvastused\", et valida pilte ja treenida isikutuvastust.</li><li>Parima tulemuse saavutamiseks keskendu otsepiltidele; väldi nurga all olevaid nägusid treenimiseks.</li></ul>"
|
||||
},
|
||||
"deleteFaceLibrary": {
|
||||
"title": "Kustuta nimi"
|
||||
"title": "Kustuta nimi",
|
||||
"desc": "Kas oled kindel, et soovid isiku '{{name}}' kollektsiooni kustutada? See kustutab jäädavalt kõik seotud näod."
|
||||
},
|
||||
"toast": {
|
||||
"error": {
|
||||
"addFaceLibraryFailed": "Näo sidumine nimega ei õnnestunud: {{errorMessage}}",
|
||||
"updateFaceScoreFailed": "Näo punktiskoori uuendamine ei õnnestunud: {{errorMessage}}"
|
||||
"updateFaceScoreFailed": "Näo punktiskoori uuendamine ei õnnestunud: {{errorMessage}}",
|
||||
"uploadingImageFailed": "Pildi üleslaadimine ebaõnnestus: {{errorMessage}}",
|
||||
"deleteFaceFailed": "Kustutamine ebaõnnestus: {{errorMessage}}",
|
||||
"deleteNameFailed": "Nime kustutamine ebaõnnestus: {{errorMessage}}",
|
||||
"renameFaceFailed": "Näo ümbernimetamine ebaõnnestus: {{errorMessage}}",
|
||||
"trainFailed": "Treenimine ebaõnnestus: {{errorMessage}}",
|
||||
"reclassifyFailed": "Näo ümberklassifitseerimine ebaõnnestus: {{errorMessage}}"
|
||||
},
|
||||
"success": {
|
||||
"addFaceLibrary": "Lisamine Näoteeki õnnestus: {{name}}!",
|
||||
"addFaceLibrary": "Lisamine nägude kogusse õnnestus: {{name}}!",
|
||||
"deletedFace_one": "{{count}} näo kustutamine õnnestus.",
|
||||
"deletedFace_other": "{{count}} näo kustutamine õnnestus.",
|
||||
"deletedName_one": "{{count}} näo kustutamine õnnestus.",
|
||||
"deletedName_other": "{{count}} näo kustutamine õnnestus.",
|
||||
"updatedFaceScore": "Näo punktiskoori uuendamine õnnestus: {{name}} ({{score}})."
|
||||
"updatedFaceScore": "Näo punktiskoori uuendamine õnnestus: {{name}} ({{score}}).",
|
||||
"uploadedImage": "Pildi üleslaadimine õnnestus.",
|
||||
"renamedFace": "Näo ümbernimetamine õnnestus, uus nimi on {{name}}",
|
||||
"trainedFace": "Edukalt treenitud nägu.",
|
||||
"reclassifiedFace": "Näo ümberklassifitseerimine õnnestus."
|
||||
}
|
||||
},
|
||||
"deleteFaceAttempts": {
|
||||
"desc_one": "Kas oled kindel, et soovid kustutada {{count}} näo? Seda tegevust ei saa tagasi pöörata.",
|
||||
"desc_other": "Kas oled kindel, et soovid kustutada {{count}} nägu? Seda tegevust ei saa tagasi pöörata."
|
||||
"desc_other": "Kas oled kindel, et soovid kustutada {{count}} nägu? Seda tegevust ei saa tagasi pöörata.",
|
||||
"title": "Kustuta näod"
|
||||
},
|
||||
"details": {
|
||||
"timestamp": "Ajatampel",
|
||||
@@ -42,5 +60,40 @@
|
||||
"uploadFaceImage": {
|
||||
"title": "Laadi näopilt üles",
|
||||
"desc": "Laadi üles pilt, et otsida sellelt nägusid ja lisada see {{pageToggle}}'i jaoks"
|
||||
}
|
||||
},
|
||||
"steps": {
|
||||
"faceName": "Lisa näole nimi",
|
||||
"uploadFace": "Lae näopilt üles",
|
||||
"nextSteps": "Järgmised sammud",
|
||||
"description": {
|
||||
"uploadFace": "Laadi üles pilt isikust '{{name}}', mis näitab tema nägu eestvaates. Ainult nägu ei pea pildilt välja lõikama."
|
||||
}
|
||||
},
|
||||
"train": {
|
||||
"title": "Hiljutised tuvastamised",
|
||||
"titleShort": "Hiljutised",
|
||||
"aria": "Vali hiljutised tuvastamised",
|
||||
"empty": "Hiljutisi näotuvastuse katseid pole",
|
||||
"emptyNoLibrary": {
|
||||
"title": "Laadi üles nägu",
|
||||
"description": "Näotuvastuse toimimiseks peate lisama vähemalt ühe näo kogusse."
|
||||
}
|
||||
},
|
||||
"renameFace": {
|
||||
"title": "Nimeta nägu ümber",
|
||||
"desc": "Sisesta uus nimi isiku '{{name}}' jaoks"
|
||||
},
|
||||
"imageEntry": {
|
||||
"validation": {
|
||||
"selectImage": "Palun vali pildifail."
|
||||
},
|
||||
"dropActive": "Lohista pilt siia…",
|
||||
"dropInstructions": "Lohistage või kleepige pilt siia või klõpsake valimiseks",
|
||||
"maxSize": "Maksimum suurus: {{size}}MB"
|
||||
},
|
||||
"nofaces": "Nägusid pole saadaval",
|
||||
"trainFaceAs": "Treeni nägu kui:",
|
||||
"trainFace": "Treeni nägu",
|
||||
"reclassifyFaceAs": "Liigita nägu ümber järgmiselt:",
|
||||
"reclassifyFace": "Näo ümberklassifitseerimine"
|
||||
}
|
||||
|
||||
@@ -1,4 +1,78 @@
|
||||
{
|
||||
"documentTitle": "Liikumise tuvastus - Frigate",
|
||||
"title": "Liikumise otsing"
|
||||
"title": "Liikumise otsing",
|
||||
"cancelSearch": "Tühista",
|
||||
"startSearch": "Alusta otsingut",
|
||||
"selectCamera": "Liikumisotsingut laaditakse",
|
||||
"description": "Joonesta hulknurk, et määratleda huvipakkuv piirkond, ja määra ajavahemik, mille jooksul otsida liikumise muutusi selles piirkonnas.",
|
||||
"searchStarted": "Otsing alustatud",
|
||||
"searchCancelled": "Otsing tühistatud",
|
||||
"searching": "Otsing on pooleli.",
|
||||
"searchComplete": "Otsing lõpetatud",
|
||||
"noResultsYet": "Käivita otsing valitud piirkonnas liikumise muutuste leidmiseks",
|
||||
"noChangesFound": "Valitud piirkonnas ei tuvastatud pikslimuutusi",
|
||||
"results": "Tulemused",
|
||||
"polygonControls": {
|
||||
"drawMode": "Joonista",
|
||||
"moveMode": "Liiguta",
|
||||
"reset": "Lähtesta hulknurk",
|
||||
"undo": "Tühista viimane punkt",
|
||||
"points_one": "{{count}} punkt",
|
||||
"points_other": "{{count}} punkti"
|
||||
},
|
||||
"newSearch": "Uus otsing",
|
||||
"clearResults": "Tühista tulemused",
|
||||
"clearROI": "Tühista hulknurk",
|
||||
"dialog": {
|
||||
"cameraLabel": "Kaamera",
|
||||
"title": "Liikumisotsing",
|
||||
"previewAlt": "Kaamera '{{camera}}' eelvaade"
|
||||
},
|
||||
"timeRange": {
|
||||
"title": "Otsinguvahemik",
|
||||
"start": "Algusaeg",
|
||||
"end": "Lõppaeg"
|
||||
},
|
||||
"settings": {
|
||||
"title": "Otsinguseaded",
|
||||
"parallelMode": "Paralleelrežiim",
|
||||
"parallelModeDesc": "Skanni mitut salvestusvahemikku korraga (kiirem; kasutab rohkem dekodeerimisressursse)",
|
||||
"threshold": "Tundlikkuse lävi",
|
||||
"thresholdDesc": "Väiksemad väärtused tuvastavad väiksemaid muutusi (1–255)",
|
||||
"minArea": "Minimaalne muutusala",
|
||||
"minAreaDesc": "Ühe liikuva piirkonna minimaalne suurus protsentides huvipakkuvast piirkonnast",
|
||||
"maxResults": "Maksimaalselt tulemusi",
|
||||
"maxResultsDesc": "Peata pärast nii paljude ajatemplite sobivust"
|
||||
},
|
||||
"errors": {
|
||||
"noCamera": "Palun vali kaamera",
|
||||
"noROI": "Palun joonistage huvipakkuv piirkond",
|
||||
"noTimeRange": "Palun valige ajavahemik",
|
||||
"invalidTimeRange": "Lõppaeg peab olema pärast algusaega",
|
||||
"searchFailed": "Otsing ebaõnnestus: {{message}}",
|
||||
"polygonTooSmall": "Hulknurgal peab olema vähemalt 3 punkti",
|
||||
"unknown": "Tundmatu viga"
|
||||
},
|
||||
"changePercentage": "{{percentage}}% muutus",
|
||||
"metrics": {
|
||||
"title": "Otsingumõõdikud",
|
||||
"segmentsScanned": "Skannitud segmente",
|
||||
"segmentsProcessed": "Töödeldud",
|
||||
"segmentsSkippedInactive": "Vahele jäetud (tegevust pole)",
|
||||
"segmentsSkippedHeatmap": "Vahele jäetud (kattuvaid piirkondi pole)",
|
||||
"fallbackFullRange": "Varurežiimis täisulatusega skaneerimine",
|
||||
"framesDecoded": "Kaadreid dekodeeritud",
|
||||
"wallTime": "Otsingu aeg",
|
||||
"seconds": "{{seconds}}s",
|
||||
"minutesSeconds": "{{minutes}}m {{seconds}}s",
|
||||
"segmentErrors": "Segmendi vead",
|
||||
"scanSummary": "{{segments}} segmenti · {{time}}"
|
||||
},
|
||||
"jumpToTime": "Hüppa sellele ajale",
|
||||
"framesProcessed": "{{count}} kaadrit töödeldud",
|
||||
"changesFound_one": "Tuvastatud {{count}} liikumine",
|
||||
"changesFound_other": "Tuvastatud {{count}} liikumist",
|
||||
"motionHeatmapLabel": "Liikumise soojakaart",
|
||||
"showSegmentHeatmap": "Soojakaart",
|
||||
"scanning": "Skannimine {{time}}"
|
||||
}
|
||||
|
||||
@@ -12,7 +12,48 @@
|
||||
"selectFromTimeline": "Vali",
|
||||
"starting": "Käivitan kordust…",
|
||||
"startLabel": "Algus",
|
||||
"endLabel": "Lõpp"
|
||||
"endLabel": "Lõpp",
|
||||
"title": "Alusta silumise taasesitust",
|
||||
"description": "Looge ajutine taasesituskaamera, mis kordab ajaloolist salvestist objektide tuvastamise ja jälgimise probleemide silumiseks. Taasesituskaameral on sama tuvastusseadistus mis lähtekaameral. Valige algusaeg.",
|
||||
"toast": {
|
||||
"error": "Silumise taasesitus ebaõnnestus: {{error}}",
|
||||
"alreadyActive": "Kordusseanss on juba aktiivne",
|
||||
"stopError": "Silumise taasesituse peatamine ebaõnnestus: {{error}}",
|
||||
"goToReplay": "Mine kordusesse"
|
||||
}
|
||||
},
|
||||
"title": "Kordus veaotsinguks"
|
||||
"title": "Kordus veaotsinguks",
|
||||
"websocket_messages": "Sõnumid",
|
||||
"description": "Mängi kaamera salvestisi veaotsinguks. Objektide loend näitab tuvastatud objektide ajalist viivitust ja vahekaart „Sõnumid” näitab Fregati sisemiste sõnumite voogu taasesituse materjalist.",
|
||||
"page": {
|
||||
"noSession": "Aktiivset silumis- ja taasesitusseanssi pole",
|
||||
"noSessionDesc": "Alusta silumissalvestise taasesitust ajaloo vaates, klõpsates tööriistaribal nupul \"Toimingud\" ja valides silumissalvestise taasesituse.",
|
||||
"goToRecordings": "Mine ajaloo vaatesse",
|
||||
"preparingClip": "Klipi ettevalmistamine…",
|
||||
"preparingClipDesc": "Frigate koondab valitud ajavahemiku salvestisi. Pikemate vahemike puhul võib see võtta kauem.",
|
||||
"startingCamera": "Silumise taasesituse käivitamine…",
|
||||
"startError": {
|
||||
"title": "Silumise taasesituse käivitamine ebaõnnestus",
|
||||
"back": "Tagasi ajaloo vaatesse"
|
||||
},
|
||||
"sourceCamera": "Lähtekaamera",
|
||||
"replayCamera": "Taasesituse kaamera",
|
||||
"initializingReplay": "Silumise taasesituse initsialiseerimine...",
|
||||
"stoppingReplay": "Silumise taasesituse peatamine...",
|
||||
"stopReplay": "Peata kordusesitus",
|
||||
"confirmStop": {
|
||||
"title": "Peata silumise kordusesitus?",
|
||||
"description": "See peatab seansi ja kustutab kõik ajutised andmed. Kas oled kindel?",
|
||||
"confirm": "Peata kordusesitus",
|
||||
"cancel": "Tühista"
|
||||
},
|
||||
"activity": "Toimingud",
|
||||
"objects": "Objekti loend",
|
||||
"audioDetections": "Audio tuvastused",
|
||||
"noActivity": "Ühtegi tegevust ei tuvastatud",
|
||||
"activeTracking": "Aktiivne jälgimine",
|
||||
"noActiveTracking": "Aktiivset jälgimist pole",
|
||||
"configuration": "Seaded",
|
||||
"configurationDesc": "Peenhäälesta liikumistuvastuse ja objektide jälgimise sätteid silumis- ja taasesituskaamera jaoks. Frigate'i konfiguratsiooni faili muudatusi ei salvestata."
|
||||
}
|
||||
}
|
||||
|
||||
@@ -7,7 +7,28 @@
|
||||
"connectionSettings": "Ühenduse seadistused",
|
||||
"port": "Port",
|
||||
"username": "Kasutajanimi",
|
||||
"usernamePlaceholder": "Valikuline"
|
||||
"usernamePlaceholder": "Valikuline",
|
||||
"cameraName": "Kaamera nimi",
|
||||
"cameraNamePlaceholder": "näiteks: eesmine_uks või Hoovi ülevaade",
|
||||
"host": "Host/IP-aadress",
|
||||
"cameraBrand": "Kaamera bränd",
|
||||
"selectBrand": "Vali URL malli jaoks kaamera bränd",
|
||||
"customUrl": "Kohandatud voo URL",
|
||||
"brandInformation": "Brändi teave",
|
||||
"brandUrlFormat": "Kaamerate puhul, mille RTSP URL-i vorming on järgmine: {{exampleUrl}}",
|
||||
"selectTransport": "Valige transpordiprotokoll",
|
||||
"description": "Sisestage oma kaamera andmed ja valige, kas soovite kaamerat tuvastada või valida tootja käsitsi.",
|
||||
"onvifPort": "ONVIF port",
|
||||
"probeMode": "Tuvasta kaamera",
|
||||
"manualMode": "Manuaalne valik",
|
||||
"errors": {
|
||||
"nameRequired": "Kaamera nimi on kohustuslik",
|
||||
"nameLength": "Kaamera nimi peab olema kuni 64 tähemärki pikk",
|
||||
"invalidCharacters": "Kaamera nimi sisaldab sobimatuid märke",
|
||||
"nameExists": "Kaamera nimi on juba olemas",
|
||||
"brandOrCustomUrlRequired": "Valige kas kaamera bränd koos hosti/IP-aadressiga või valige „Muu” kohandatud URL-iga"
|
||||
},
|
||||
"detectionMethod": "Vootuvastus meetod"
|
||||
},
|
||||
"step3": {
|
||||
"streamUrlPlaceholder": "rtsp://kasutajanimi:salasõna@host:port/asukoht",
|
||||
@@ -17,17 +38,37 @@
|
||||
"roles": "Rollid",
|
||||
"roleLabels": {
|
||||
"record": "Salvestamine",
|
||||
"audio": "Heliriba"
|
||||
"audio": "Heliriba",
|
||||
"detect": "Objektituvastus"
|
||||
},
|
||||
"connected": "Ühendatud",
|
||||
"featuresTitle": "Funktsionaalsused"
|
||||
"featuresTitle": "Funktsionaalsused",
|
||||
"selectStream": "Vali voog",
|
||||
"selectQuality": "Valige kvaliteet",
|
||||
"notConnected": "Pole ühendatud",
|
||||
"testFailedTitle": "Test ebaõnnestus",
|
||||
"testStream": "Testi ühendust",
|
||||
"testSuccess": "Voo test edukas!",
|
||||
"testFailed": "Voo test ebaõnnestus",
|
||||
"selectResolution": "Vali resolutsioon",
|
||||
"noStreamFound": "Voogu ei leitud",
|
||||
"addAnotherStream": "Lisa järgmine voog",
|
||||
"streamTitle": "Voog {{number}}",
|
||||
"streamUrl": "Voo URL",
|
||||
"addStream": "Lisa voog",
|
||||
"description": "Seadista voogedastusrolle ja lisa oma kaamerale täiendavaid vooge.",
|
||||
"streamsTitle": "Kaamera vood"
|
||||
},
|
||||
"steps": {
|
||||
"probeOrSnapshot": "Võta proov või tee hetkvõte"
|
||||
"probeOrSnapshot": "Võta proov või tee hetkvõte",
|
||||
"nameAndConnection": "Nimi ja ühendus",
|
||||
"streamConfiguration": "Voo seaded",
|
||||
"validationAndTesting": "Valideerimine ja testimine"
|
||||
},
|
||||
"step2": {
|
||||
"testing": {
|
||||
"fetchingSnapshot": "Laadin kaamera hetkvõtet alla..."
|
||||
"fetchingSnapshot": "Laadin kaamera hetkvõtet alla...",
|
||||
"probingMetadata": "Tuvastan kaamera metaandmeid..."
|
||||
},
|
||||
"retry": "Proovi uuesti",
|
||||
"manufacturer": "Tootja",
|
||||
@@ -37,7 +78,30 @@
|
||||
"presets": "Eelseadistused",
|
||||
"useCandidate": "Kasuta",
|
||||
"uriCopy": "Kopeeri",
|
||||
"connected": "Ühendatud"
|
||||
"connected": "Ühendatud",
|
||||
"testSuccess": "Ühenduse test õnnestus!",
|
||||
"testFailed": "Ühenduse test ebaõnnestus. Palun kontrollige sisestatud andmeid ja proovige uuesti.",
|
||||
"testFailedTitle": "Test ebaõnnestus",
|
||||
"streamDetails": "Voo üksikasjad",
|
||||
"probing": "Tuvastan kaamerat...",
|
||||
"ptzSupport": "PTZ tugi",
|
||||
"testConnection": "Testi ühendust",
|
||||
"toggleUriView": "Klõpsake täieliku URI vaate sisse/välja lülitamiseks",
|
||||
"notConnected": "Pole ühendatud",
|
||||
"errors": {
|
||||
"hostRequired": "Hosti/IP-aadress on kohustuslik"
|
||||
},
|
||||
"candidateStreamTitle": "Kandidaat {{number}}",
|
||||
"probeSuccessful": "Tuvastamine õnnestus",
|
||||
"probeError": "Tuvastamise viga",
|
||||
"probeNoSuccess": "Tuvastamine ebaõnnestus",
|
||||
"deviceInfo": "Seadme info",
|
||||
"autotrackingSupport": "Automaatse jälgimise tugi",
|
||||
"uriCopied": "URI kopeeriti lõikelauale",
|
||||
"rtspCandidates": "RTSP kandidaadid",
|
||||
"probingDevice": "Tuvastan seadet...",
|
||||
"description": "Tuvasta kaamera vooge või seadista käsitsi seaded vastavalt valitud tuvastusmeetodile.",
|
||||
"probeFailed": "Kaamera tuvastamine ebaõnnestus: {{error}}"
|
||||
},
|
||||
"testResultLabels": {
|
||||
"resolution": "Resolutsioon",
|
||||
@@ -56,7 +120,17 @@
|
||||
"roles": "Rollid",
|
||||
"none": "Määramata",
|
||||
"error": "Viga"
|
||||
}
|
||||
},
|
||||
"commonErrors": {
|
||||
"noUrl": "Palun sisestage kehtiv voogesituse URL",
|
||||
"testFailed": "Voo test ebaõnnestus: {{error}}"
|
||||
},
|
||||
"save": {
|
||||
"success": "Uue kaamera '{{cameraName}}' salvestamine õnnestus.",
|
||||
"failure": "Viga kaamera '{{cameraName}}' salvestamisel."
|
||||
},
|
||||
"title": "Lisa kaamera",
|
||||
"description": "Uue kaamera lisamiseks oma Frigate paigaldisesse järgige alltoodud samme."
|
||||
},
|
||||
"users": {
|
||||
"updatePassword": "Lähtesta salasõna",
|
||||
@@ -172,24 +246,32 @@
|
||||
},
|
||||
"documentTitle": {
|
||||
"default": "Seadistused - Frigate",
|
||||
"authentication": "Autentimise seadistused - Frigate",
|
||||
"cameraReview": "Kaamerate kordusvaatuste seadistused - Frigate",
|
||||
"general": "Profiili seadistused - Frigate",
|
||||
"frigatePlus": "Frigate+ seadistused - Frigate",
|
||||
"notifications": "Teavituste seadistused - Frigate",
|
||||
"authentication": "Autentimise seaded - Frigate",
|
||||
"cameraReview": "Kaamerate kordusvaatuste seaded - Frigate",
|
||||
"general": "Profiili seaded - Frigate",
|
||||
"frigatePlus": "Frigate+ seaded - Frigate",
|
||||
"notifications": "Teavituste seaded - Frigate",
|
||||
"cameraManagement": "Kaamerate haldus - Frigate",
|
||||
"masksAndZones": "Maskide ja tsoonide haldus - Frigate",
|
||||
"object": "Silumine ja veaotsing - Frigate"
|
||||
"object": "Silumine ja veaotsing - Frigate",
|
||||
"enrichments": "Rikastamise seaded - Frigate",
|
||||
"motionTuner": "Liikumise häälestaja – Frigate",
|
||||
"globalConfig": "Globaalsed seaded – Frigate",
|
||||
"cameraConfig": "Kaamera seaded - Frigate",
|
||||
"detectorsAndModel": "Detektorid ja mudel – Frigate",
|
||||
"maintenance": "Hooldus – Frigate",
|
||||
"profiles": "Profiilid - Frigate"
|
||||
},
|
||||
"general": {
|
||||
"title": "Profiili seadistused",
|
||||
"title": "Kasutajaliidese seadistused",
|
||||
"cameraGroupStreaming": {
|
||||
"clearAll": "Kustuta kõik voogedastuse seadistused"
|
||||
},
|
||||
"liveDashboard": {
|
||||
"title": "Töölaud reaalajas",
|
||||
"automaticLiveView": {
|
||||
"label": "Automaatne otseülekande vaade"
|
||||
"label": "Automaatne otseülekande vaade",
|
||||
"desc": "Aktiveerib automaatselt kaamera reaalajas vaate, kui tuvastatakse tegevus. Selle valiku keelamisel uuendatakse kaamerapilti töölaual ainult üks kord minutis."
|
||||
}
|
||||
},
|
||||
"calendar": {
|
||||
@@ -335,7 +417,7 @@
|
||||
},
|
||||
"menu": {
|
||||
"ui": "Kasutajaliides",
|
||||
"cameraManagement": "Haldus",
|
||||
"cameraManagement": "Kaamera haldus",
|
||||
"masksAndZones": "Maskid ja tsoonid",
|
||||
"triggers": "Päästikud",
|
||||
"debug": "Silumine ja veaotsing",
|
||||
@@ -373,7 +455,37 @@
|
||||
"systemMqtt": "MQTT",
|
||||
"systemGo2rtcStreams": "go2rtc voogedastus",
|
||||
"integrationSemanticSearch": "Semantiline otsing",
|
||||
"integrationGenerativeAi": "Generatiivne tehisaru"
|
||||
"integrationGenerativeAi": "Generatiivne tehisaru",
|
||||
"general": "Üldine",
|
||||
"globalConfig": "Globaalsed seaded",
|
||||
"system": "Süsteem",
|
||||
"integrations": "Integratsioonid",
|
||||
"cameras": "Kaamera seaded",
|
||||
"integrationFaceRecognition": "Nöotuvastus",
|
||||
"cameraDetect": "Objektituvastus",
|
||||
"cameraFfmpeg": "Vood (FFmpeg)",
|
||||
"cameraRecording": "Salvestus",
|
||||
"cameraSnapshots": "Hetktõmmised",
|
||||
"cameraMotion": "Liikumistuvastus",
|
||||
"cameraObjects": "Objektid",
|
||||
"cameraConfigReview": "Ülevaade",
|
||||
"cameraAudioEvents": "audiotuvastus",
|
||||
"cameraAudioTranscription": "Audio transkriptsioon",
|
||||
"cameraNotifications": "Teated",
|
||||
"cameraLivePlayback": "Otseülekanne",
|
||||
"integrationAudioTranscription": "Audio transkriptsioon",
|
||||
"integrationObjectClassification": "Objektide klassifitseerimine",
|
||||
"cameraFaceRecognition": "Näotuvastus",
|
||||
"cameraMqttConfig": "MQTT",
|
||||
"cameraOnvif": "ONVIF",
|
||||
"cameraUi": "Kaamera kasutajaliides",
|
||||
"cameraTimestampStyle": "Ajatempli stiil",
|
||||
"cameraMqtt": "Kaamera MQTT",
|
||||
"maintenance": "Hooldus",
|
||||
"mediaSync": "Meedia sünkroonimine",
|
||||
"regionGrid": "Regioonide ruudustik",
|
||||
"enrichments": "Andmete rikastamine",
|
||||
"motionTuner": "Liikumistuvastuse seadistaja"
|
||||
},
|
||||
"dialog": {
|
||||
"unsavedChanges": {
|
||||
@@ -389,7 +501,11 @@
|
||||
"semanticSearch": {
|
||||
"reindexNow": {
|
||||
"confirmButton": "Indekseeri uuesti",
|
||||
"label": "Indekseeri uuesti kohe"
|
||||
"label": "Indekseeri kohe uuesti",
|
||||
"alreadyInProgress": "Ümberindekseerimine on juba käimas.",
|
||||
"error": "Uuesti indekseerimise alustamine ebaõnnestus: {{errorMessage}}",
|
||||
"success": "Ümberindekseerimine algas edukalt.",
|
||||
"confirmTitle": "Kinnita uuesti indekseerimine"
|
||||
},
|
||||
"modelSize": {
|
||||
"small": {
|
||||
@@ -397,7 +513,9 @@
|
||||
},
|
||||
"large": {
|
||||
"title": "suur"
|
||||
}
|
||||
},
|
||||
"label": "Mudeli suurus",
|
||||
"desc": "Semantilise otsingu manustamiseks kasutatava mudeli suurus."
|
||||
},
|
||||
"title": "Semantiline otsing"
|
||||
},
|
||||
@@ -408,14 +526,23 @@
|
||||
},
|
||||
"large": {
|
||||
"title": "suur"
|
||||
}
|
||||
}
|
||||
},
|
||||
"label": "Mudeli suurus",
|
||||
"desc": "Näotuvastuseks kasutatava mudeli suurus."
|
||||
},
|
||||
"title": "Näotuvastus"
|
||||
},
|
||||
"birdClassification": {
|
||||
"title": "Lindude klassifikatsioon"
|
||||
},
|
||||
"licensePlateRecognition": {
|
||||
"title": "Sõidukite numbrimärkide tuvastus"
|
||||
},
|
||||
"title": "Andmerikastuse seaded",
|
||||
"restart_required": "Taaskäivitamine on vajalik (Andmerikastuse seadeid on muudetud)",
|
||||
"toast": {
|
||||
"success": "Andmerikastamise seaded on salvestatud. Muudatuste rakendamiseks taaskäivitage Frigate.",
|
||||
"error": "Seadete muudatuste salvestamine ebaõnnestus: {{errorMessage}}"
|
||||
}
|
||||
},
|
||||
"cameraReview": {
|
||||
@@ -497,5 +624,31 @@
|
||||
"lpr": {
|
||||
"vehicleNotTracked": "Sõidukite numbrimärkide tuvastus eeldab, et auto või mootorratas on jälgitav. Lülita menüüst Objektid sell kaamera jaoks sisse valikud „auto“ või „mootorratas“."
|
||||
}
|
||||
},
|
||||
"button": {
|
||||
"overriddenGlobal": "Ülekirjutatud (Globaalne)",
|
||||
"overriddenGlobalTooltip": "Käesolev kaamera kirjutab selles jaotises üle globaalsed seaded",
|
||||
"overriddenGlobalHeading_one": "See kaamera sürjutab {{count}} üldise seadistuse välja:",
|
||||
"overriddenGlobalHeading_other": "See kaamera sürjutab {{count}} üldise seadistuse välja:"
|
||||
},
|
||||
"saveAllPreview": {
|
||||
"title": "Salvestatavad muudatused",
|
||||
"triggerLabel": "Vaadake üle ootel olevad muudatused",
|
||||
"empty": "Ootel muudatusi pole.",
|
||||
"scope": {
|
||||
"label": "Ulatus",
|
||||
"global": "Globaalne",
|
||||
"camera": "Kaamera: {{cameraName}}"
|
||||
},
|
||||
"profile": {
|
||||
"label": "Profiil"
|
||||
},
|
||||
"field": {
|
||||
"label": "Väli"
|
||||
},
|
||||
"value": {
|
||||
"label": "Uus väärtus",
|
||||
"reset": "Lähtesta"
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
@@ -2,7 +2,14 @@
|
||||
"documentTitle": {
|
||||
"general": "Üldine statistika - Frigate",
|
||||
"cameras": "Kaamerate statistika - Frigate",
|
||||
"storage": "Andmeruumi statistika - Frigate"
|
||||
"storage": "Andmeruumi statistika - Frigate",
|
||||
"logs": {
|
||||
"frigate": "Frigate logid - Frigate",
|
||||
"go2rtc": "Go2RTC logid - Frigate",
|
||||
"nginx": "Nginx logid - Frigate",
|
||||
"websocket": "Sõnumite logid - Frigate"
|
||||
},
|
||||
"enrichments": "Rikastus statistika - Frigate"
|
||||
},
|
||||
"logs": {
|
||||
"download": {
|
||||
@@ -16,11 +23,29 @@
|
||||
"websocket": {
|
||||
"filter": {
|
||||
"cameras_count_one": "{{count}} kaamera",
|
||||
"cameras_count_other": "{{count}} kaamerat"
|
||||
"cameras_count_other": "{{count}} kaamerat",
|
||||
"camera": "Kaamera",
|
||||
"all_cameras": "Kõik kaamerad",
|
||||
"events": "Sündmused",
|
||||
"reviews": "Ülevaated",
|
||||
"system": "Süsteem",
|
||||
"topics": "Teemad",
|
||||
"all": "Kõik teemad",
|
||||
"face_recognition": "Näotuvastus",
|
||||
"classification": "Klassifikatsioon",
|
||||
"camera_activity": "Kaamera aktiivsus",
|
||||
"lpr": "Numbrimärgituvastus"
|
||||
},
|
||||
"empty": "Ühtegi sõnumit pole veel hõivatud",
|
||||
"count_one": "{{count}} sõnum",
|
||||
"count_other": "{{count}} sõnumit"
|
||||
"count_other": "{{count}} sõnumit",
|
||||
"pause": "Peata",
|
||||
"resume": "Jätka",
|
||||
"label": "Sõnumid",
|
||||
"clear": "Tühista",
|
||||
"expanded": {
|
||||
"payload": "Last"
|
||||
}
|
||||
},
|
||||
"type": {
|
||||
"label": "Tüüp",
|
||||
@@ -36,5 +61,170 @@
|
||||
}
|
||||
}
|
||||
},
|
||||
"title": "Süsteem"
|
||||
"title": "Süsteem",
|
||||
"enrichments": {
|
||||
"embeddings": {
|
||||
"object_description_events_per_second": "Objekti kirjeldus",
|
||||
"face_recognition": "Näotuvastus",
|
||||
"plate_recognition": "Numbrimärgi tuvastus",
|
||||
"object_description": "Objekti kirjeldus",
|
||||
"review_description_events_per_second": "Ülevaate kirjeldus",
|
||||
"review_description": "Ülevaate kirjeldus",
|
||||
"yolov9_plate_detection": "YOLOv9 numbrimärgi tuvastus",
|
||||
"yolov9_plate_detection_speed": "YOLOv9 numbrimärgi tuvastuskiirus",
|
||||
"plate_recognition_speed": "Numbrimärgituvastuse kiirus",
|
||||
"face_recognition_speed": "Näotuvastuse kiirus"
|
||||
},
|
||||
"title": "Andmerikastused"
|
||||
},
|
||||
"cameras": {
|
||||
"connectionQuality": {
|
||||
"reconnectsLastHour": "Uuesti ühendumisi (viimase tunni jooksul)",
|
||||
"stallsLastHour": "Seiskumisi (viimase tunni jooksul)",
|
||||
"title": "Ühenduse kvaliteet",
|
||||
"excellent": "Suurepärane",
|
||||
"fair": "Rahuldav",
|
||||
"poor": "Kehv",
|
||||
"unusable": "Mittekasutatav",
|
||||
"fps": "Kaadrisagedus",
|
||||
"expectedFps": "Eeldatav kaadrisagedus (FPS)"
|
||||
},
|
||||
"label": {
|
||||
"camera": "kaamera",
|
||||
"detect": "tuvasta",
|
||||
"skipped": "vahele jäetud",
|
||||
"ffmpeg": "FFmpeg",
|
||||
"cameraFfmpeg": "{{camName}} FFmpeg",
|
||||
"cameraGpu": "{{camName}} GPU",
|
||||
"cameraDetect": "{{camName}} tuvastus",
|
||||
"overallFramesPerSecond": "kaadreid sekundis kokku",
|
||||
"overallDetectionsPerSecond": "kogutuvastusi sekundis",
|
||||
"overallSkippedDetectionsPerSecond": "vahelejäänud tuvastusi sekundis kokku",
|
||||
"cameraFramesPerSecond": "{{camName}} kaadreid sekundis",
|
||||
"cameraDetectionsPerSecond": "{{camName}} tuvastusi sekundis",
|
||||
"cameraSkippedDetectionsPerSecond": "{{camName}} vahelejäänud tuvastusi sekundis",
|
||||
"capture": "jäädvustamine"
|
||||
},
|
||||
"noCameras": {
|
||||
"title": "Ühtegi kaamerat ei leitud"
|
||||
},
|
||||
"framesAndDetections": "Kaadreid / Tuvastusi",
|
||||
"info": {
|
||||
"keyframes": {
|
||||
"recordDisabled": "Selle kaamera salvestamine on välja lülitatud.",
|
||||
"segmentLength": "Salvestuse segmendi pikkus:",
|
||||
"recordStream": "Salvestusvoog:",
|
||||
"keyframeCount": "Vaadeldud võtmekaadrid:",
|
||||
"observedDuration": "Vaadeldud kestus:"
|
||||
},
|
||||
"audio": "Audio:",
|
||||
"error": "Viga: {{error}}",
|
||||
"codec": "Koodek:",
|
||||
"resolution": "Resolutsioon:",
|
||||
"video": "Video:",
|
||||
"aspectRatio": "kuvasuhe",
|
||||
"unknown": "Tundmatu",
|
||||
"fetching": "Kaameraandmete toomine",
|
||||
"stream": "Voog {{idx}}",
|
||||
"streamDataFromFFPROBE": "Vooandmed saadakse <code>ffprobe</code> abil.",
|
||||
"fps": "Kaadrisagedus (FPS):"
|
||||
},
|
||||
"title": "Kaamerad",
|
||||
"overview": "Ülevaade"
|
||||
},
|
||||
"lastRefreshed": "Viimati uuendatud: ",
|
||||
"stats": {
|
||||
"healthy": "Süsteem on töökorras",
|
||||
"detectIsSlow": "{{detect}} on aeglane ({{speed}} ms)",
|
||||
"detectIsVerySlow": "{{detect}} on väga aeglane ({{speed}} ms)",
|
||||
"cameraIsOffline": "{{camera}} on ühenduseta"
|
||||
},
|
||||
"storage": {
|
||||
"cameraStorage": {
|
||||
"camera": "Kaamera",
|
||||
"unused": {
|
||||
"title": "Kasutamata",
|
||||
"tips": "See väärtus ei pruugi Frigate'i jaoks saadaolevat vaba ruumi täpselt kajastada, kui teie draivil on lisaks Frigate'i salvestistele ka muid faile. Frigate ei jälgi salvestusruumi kasutamist väljaspool oma salvestisi."
|
||||
},
|
||||
"bandwidth": "Ribalaius",
|
||||
"storageUsed": "Salvestusmaht",
|
||||
"percentageOfTotalUsed": "protsent kogusummast",
|
||||
"unusedStorageInformation": "Kasutamata salvestusmahu info",
|
||||
"title": "Kaamera salvestusmaht"
|
||||
},
|
||||
"title": "Säilitus",
|
||||
"overview": "Ülevaade",
|
||||
"recordings": {
|
||||
"title": "Salvestused",
|
||||
"earliestRecording": "Varaseim saadaolev salvestis:"
|
||||
},
|
||||
"shm": {
|
||||
"title": "SHM (jagatud mälu) eraldus",
|
||||
"frameLifetime": {
|
||||
"title": "Kaadri eluiga"
|
||||
}
|
||||
}
|
||||
},
|
||||
"metrics": "Süsteemi mõõdikud",
|
||||
"general": {
|
||||
"title": "Üldine",
|
||||
"hardwareInfo": {
|
||||
"gpuUsage": "GPU kasutus",
|
||||
"gpuMemory": "GPU mälu",
|
||||
"gpuInfo": {
|
||||
"vainfoOutput": {
|
||||
"title": "Vainfo väljund",
|
||||
"processOutput": "Protsessi väljund:",
|
||||
"processError": "Protsessi viga:",
|
||||
"returnCode": "Tagastuskood: {{code}}"
|
||||
},
|
||||
"closeInfo": {
|
||||
"label": "Sulge GPU info"
|
||||
},
|
||||
"copyInfo": {
|
||||
"label": "Kopeeri GPU info"
|
||||
},
|
||||
"nvidiaSMIOutput": {
|
||||
"title": "Nvidia SMI väljund",
|
||||
"name": "Nimi: {{name}}",
|
||||
"driver": "Draiver: {{driver}}",
|
||||
"cudaComputerCapability": "CUDA arvutusvõimekus: {{cuda_compute}}",
|
||||
"vbios": "VBios info: {{vbios}}"
|
||||
},
|
||||
"toast": {
|
||||
"success": "GPU info kopeeriti lõikelauale"
|
||||
}
|
||||
},
|
||||
"gpuDecoder": "GPU dekooder",
|
||||
"gpuTemperature": "GPU temperatuur",
|
||||
"gpuEncoder": "GPU kodeerija",
|
||||
"gpuCompute": "GPU arvutus / kodeerimine",
|
||||
"title": "Riistvara info",
|
||||
"npuUsage": "NPU kasutus",
|
||||
"npuMemory": "NPU mälu",
|
||||
"npuTemperature": "NPU temperatuur",
|
||||
"intelGpuWarning": {
|
||||
"title": "Inteli GPU statistika hoiatus",
|
||||
"message": "GPU statistika pole saadaval"
|
||||
}
|
||||
},
|
||||
"otherProcesses": {
|
||||
"series": {
|
||||
"go2rtc": "go2rtc",
|
||||
"recording": "salvestus",
|
||||
"review_segment": "Segmendi ülevaade",
|
||||
"audio_detector": "audio detektor"
|
||||
},
|
||||
"processCpuUsage": "Protsessi CPU kasutus",
|
||||
"processMemoryUsage": "Protsessi mälukasutus"
|
||||
},
|
||||
"detector": {
|
||||
"title": "Detektorid",
|
||||
"memoryUsage": "Detektori mälukasutus",
|
||||
"cpuUsage": "Detektori CPU kasutus",
|
||||
"temperature": "Detektori temperatuur",
|
||||
"inferenceSpeed": "Detektori järelduskiirus",
|
||||
"cpuUsageInformation": "Protsessori võimsus, mida kasutatakse tuvastusmudelite sisend- ja väljundandmete ettevalmistamisel. See väärtus ei mõõda järelduste kasutamist isegi siis, kui kasutatakse graafikaprotsessorit või kiirendit."
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
@@ -67,7 +67,8 @@
|
||||
"error": {
|
||||
"failed": "Échec du démarrage de l'exportation : {{error}}",
|
||||
"endTimeMustAfterStartTime": "L'heure de fin doit être postérieure à l'heure de début.",
|
||||
"noVaildTimeSelected": "La plage horaire sélectionnée n'est pas valide."
|
||||
"noVaildTimeSelected": "La plage horaire sélectionnée n'est pas valide.",
|
||||
"noValidTimeSelected": "Interval de temps invalide"
|
||||
},
|
||||
"success": "Exportation démarrée avec succès. Consultez le fichier sur la page des exportations.",
|
||||
"view": "Vue",
|
||||
@@ -88,7 +89,9 @@
|
||||
"export": "Exporter",
|
||||
"fromTimeline": {
|
||||
"saveExport": "Enregistrer l'exportation",
|
||||
"previewExport": "Aperçu de l'exportation"
|
||||
"previewExport": "Aperçu de l'exportation",
|
||||
"queueingExport": "Traitement de l'export...",
|
||||
"useThisRange": "Utiliser cet interval"
|
||||
},
|
||||
"case": {
|
||||
"label": "Dossier",
|
||||
@@ -195,6 +198,10 @@
|
||||
"markAsReviewed": "Marquer comme traité",
|
||||
"deleteNow": "Supprimer maintenant",
|
||||
"markAsUnreviewed": "Marquer comme non traité"
|
||||
},
|
||||
"shareTimestamp": {
|
||||
"label": "Partager cette date",
|
||||
"title": "Partager cette date"
|
||||
}
|
||||
},
|
||||
"imagePicker": {
|
||||
|
||||
@@ -24,6 +24,9 @@
|
||||
},
|
||||
"state": {
|
||||
"submitted": "Terkirim"
|
||||
},
|
||||
"toast": {
|
||||
"error": "Gagal submit ke Frigate+. Harap periksa koneksi jaringan Anda dan coba lagi."
|
||||
}
|
||||
}
|
||||
},
|
||||
@@ -57,7 +60,8 @@
|
||||
"view": "Melihat",
|
||||
"batchSuccess_other": "{{count}} Ekspor dimulai. Membuka kasusnya sekarang.",
|
||||
"batchPartial": "Ekspor berhasil dimulai sebanyak {{successful}} dari total {{total}} ekspor. Kamera yang gagal: {{failedCameras}}",
|
||||
"batchFailed": "Gagal memulai ekspor sebanyak {{total}}. Kamera yang gagal: {{failedCameras}}"
|
||||
"batchFailed": "Gagal memulai ekspor sebanyak {{total}}. Kamera yang gagal: {{failedCameras}}",
|
||||
"batchQueuedPartial": "Antrian ekspor berhasil sebanyak {{successful}} dari total {{total}} ekspor. Kamera yang gagal: {{failedCameras}}"
|
||||
},
|
||||
"case": {
|
||||
"newCaseOption": "Membuat Kasus Baru",
|
||||
@@ -87,7 +91,9 @@
|
||||
"selectGroup": "Pilih grup",
|
||||
"noMatchingCameras": "Tidak ada kamera yang sesuai dengan pencarian Anda",
|
||||
"selectedCount": "{{terpilih}} / {{total}} terpilih",
|
||||
"namePlaceholder": "Nama dasar opsional untuk ekspor ini"
|
||||
"namePlaceholder": "Nama dasar opsional untuk ekspor ini",
|
||||
"searchOrSelectGroup": "Cari, atau pilih grup kamera...",
|
||||
"selectAll": "Pilih semua kamera"
|
||||
},
|
||||
"multi": {
|
||||
"title_other": "Ekspor {{count}} Ulasan",
|
||||
|
||||
@@ -68,8 +68,8 @@
|
||||
"choir": "Koris",
|
||||
"yodeling": "Jodelēšana",
|
||||
"mantra": "Mantra",
|
||||
"rapping": "Repot",
|
||||
"whistling": "Svilpot",
|
||||
"rapping": "Repošana",
|
||||
"whistling": "Svilpošana",
|
||||
"sniff": "Ošņāt",
|
||||
"hands": "Rokas",
|
||||
"animal": "Dzīvnieks",
|
||||
@@ -122,5 +122,17 @@
|
||||
"fireworks": "Uguņošana",
|
||||
"glass": "Stikls",
|
||||
"white_noise": "Baltais troksnis",
|
||||
"radio": "Radio"
|
||||
"radio": "Radio",
|
||||
"chant": "Piedziedājums",
|
||||
"synthetic_singing": "Sintētiskā dziedāšana",
|
||||
"groan": "Vaidēšana",
|
||||
"grunt": "Rukšķēšana",
|
||||
"breathing": "Elpošana",
|
||||
"wheeze": "Gārgšana",
|
||||
"snoring": "Krākšana",
|
||||
"gasp": "Elsošana",
|
||||
"pant": "Elšana",
|
||||
"snort": "Šņaukt",
|
||||
"cough": "Klepus",
|
||||
"throat_clearing": "Kakla tīrīšana"
|
||||
}
|
||||
|
||||
@@ -64,7 +64,8 @@
|
||||
"error": {
|
||||
"failed": "Kunne ikke legge eksport i kø: {{error}}",
|
||||
"noVaildTimeSelected": "Ingen gyldig tidsperiode valgt",
|
||||
"endTimeMustAfterStartTime": "Sluttid må være etter starttid"
|
||||
"endTimeMustAfterStartTime": "Sluttid må være etter starttid",
|
||||
"noValidTimeSelected": "Ingen gyldig tidsperiode valgt"
|
||||
},
|
||||
"view": "Vis",
|
||||
"queued": "Eksport lagt i kø. Se fremdrift på eksportsiden.",
|
||||
|
||||
@@ -34,6 +34,9 @@
|
||||
"label": "Bevestig dit label voor Frigate Plus",
|
||||
"ask_a": "Is dit object een <code>{{label}}</code>?",
|
||||
"ask_full": "Is dit object een <code>{{untranslatedLabel}}</code> ({{translatedLabel}})?"
|
||||
},
|
||||
"toast": {
|
||||
"error": "Gefaald bij Frigate+ in te dienen. Controleer je netwerkverbinding en probeer opnieuw."
|
||||
}
|
||||
}
|
||||
},
|
||||
@@ -104,7 +107,14 @@
|
||||
"namePlaceholder": "Optionele basisnaam voor deze exporten",
|
||||
"queueingButton": "Exporten in wachtrij plaatsen...",
|
||||
"exportButton_one": "Export 1 Camera",
|
||||
"exportButton_other": "{{count}} camera's exporteren"
|
||||
"exportButton_other": "{{count}} camera's exporteren",
|
||||
"searchOrSelectGroup": "Zoek of selecteer een cameragroep...",
|
||||
"selectAll": "Selecteer alle camera's",
|
||||
"clearSelection": "Wis selectie",
|
||||
"selectWithActivity": "Camera's met gevolgde objecten",
|
||||
"selectGroup": "Selecteer groep",
|
||||
"noMatchingCameras": "Geen camera's komen overeen met je zoekopdracht",
|
||||
"selectedCount": "{{selected}} / {{total}} geselecteerd"
|
||||
},
|
||||
"multi": {
|
||||
"title_one": "Review 1 exporteren",
|
||||
|
||||
@@ -19,8 +19,8 @@
|
||||
"question": {
|
||||
"label": "Confirmar esse rótulo para Frigate Plus",
|
||||
"ask_a": "Este objeto é um <code>{{label}}</code>?",
|
||||
"ask_an": "Este objeto é um<code>{{label}}</code>?",
|
||||
"ask_full": "Este objeto é um<code>{{untranslatedLabel}}</code> ({{translatedLabel}})?"
|
||||
"ask_an": "Este objeto é um <code>{{label}}</code>?",
|
||||
"ask_full": "Este objeto é um <code>{{untranslatedLabel}}</code> ({{translatedLabel}})?"
|
||||
},
|
||||
"state": {
|
||||
"submitted": "Enviado"
|
||||
@@ -71,7 +71,10 @@
|
||||
"batchQueueFailed": "Falha ao enfileirar {{total}} exportações. Câmeras com falha: {{failedCameras}}",
|
||||
"batchPartial": "Iniciadas {{successful}} de {{total}} exportações. Câmeras com falha: {{failedCameras}}",
|
||||
"batchFailed": "Falha ao iniciar {{total}} exportações. Câmeras com falha: {{failedCameras}}",
|
||||
"queued": "Exportação na fila. Acompanhe o progresso na página de exportações."
|
||||
"queued": "Exportação na fila. Acompanhe o progresso na página de exportações.",
|
||||
"batchSuccess_one": "1 exportação iniciada. Abrindo o caso agora.",
|
||||
"batchSuccess_many": "{{count}} exportações iniciadas. Abrindo o caso agora.",
|
||||
"batchSuccess_other": "{{count}} exportações iniciadas. Abrindo o caso agora."
|
||||
},
|
||||
"fromTimeline": {
|
||||
"saveExport": "Salvar Exportação",
|
||||
@@ -132,7 +135,10 @@
|
||||
"started_many": "Iniciadas {{count}} exportações. Abrindo o caso agora.",
|
||||
"started_other": "",
|
||||
"failed": "Falha ao iniciar {{total}} exportações. Falhas: {{failedItems}}",
|
||||
"partial": "Iniciadas {{successful}} de {{total}} exportações. Falhas: {{failedItems}}"
|
||||
"partial": "Iniciadas {{successful}} de {{total}} exportações. Falhas: {{failedItems}}",
|
||||
"startedNoCase_one": "1 exportação iniciada.",
|
||||
"startedNoCase_many": "{{count}} exportações iniciadas.",
|
||||
"startedNoCase_other": "{{count}} exportações iniciadas."
|
||||
}
|
||||
}
|
||||
},
|
||||
|
||||
@@ -215,7 +215,8 @@
|
||||
"label": "Birdseye",
|
||||
"description": "Configurações para a visualização composta \"Birdseye\", que combina múltiplas transmissões de câmera em um único layout.",
|
||||
"enabled": {
|
||||
"label": "Ativar Birdseye"
|
||||
"label": "Ativar Birdseye",
|
||||
"description": "Habilita ou desabilita a função Birdseye."
|
||||
}
|
||||
},
|
||||
"live": {
|
||||
@@ -401,7 +402,9 @@
|
||||
"enhancement": {
|
||||
"label": "Nível de aprimoramento",
|
||||
"description": "Nível de aprimoramento (0-10) a ser aplicado aos recortes da placa antes do OCR; valores mais altos nem sempre melhoram os resultados, e níveis acima de 5 podem funcionar apenas com placas noturnas, devendo ser utilizados com cautela."
|
||||
}
|
||||
},
|
||||
"label": "Reconhecimento de placa veicular",
|
||||
"description": "Configurações de reconhecimento de placa veicular, incluindo limites de detecção, formatação e placas conhecidas."
|
||||
},
|
||||
"record": {
|
||||
"label": "Gravação",
|
||||
@@ -539,6 +542,18 @@
|
||||
"enabled": {
|
||||
"label": "Ativar detecções",
|
||||
"description": "Ative ou desative eventos de detecção para esta câmera."
|
||||
},
|
||||
"labels": {
|
||||
"label": "Rótulos de detecção",
|
||||
"description": "Lista de rótulos de objetos que são considerados eventos de detecção."
|
||||
},
|
||||
"required_zones": {
|
||||
"label": "Zonas requeridas",
|
||||
"description": "Zonas em que um objeto deve entrar para ser considerado detectado. Deixe em branco para permitir qualquer zona."
|
||||
},
|
||||
"cutoff_time": {
|
||||
"label": "Tempo limite de detecção",
|
||||
"description": "Segundos a esperar após o fim da atividade antes de encerrar a detecção."
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
@@ -239,7 +239,8 @@
|
||||
"label": "Birdseye",
|
||||
"description": "Configurações para a visualização composta \"Birdseye\", que combina múltiplas transmissões de câmera em um único layout.",
|
||||
"enabled": {
|
||||
"label": "Ativar Birdseye"
|
||||
"label": "Ativar Birdseye",
|
||||
"description": "Habilita ou desabilita a função Birdseye."
|
||||
}
|
||||
},
|
||||
"live": {
|
||||
@@ -420,7 +421,9 @@
|
||||
"enhancement": {
|
||||
"label": "Nível de aprimoramento",
|
||||
"description": "Nível de aprimoramento (0-10) a ser aplicado aos recortes da placa antes do OCR; valores mais altos nem sempre melhoram os resultados, e níveis acima de 5 podem funcionar apenas com placas noturnas, devendo ser utilizados com cautela."
|
||||
}
|
||||
},
|
||||
"label": "Reconhecimento de placa veicular",
|
||||
"description": "Configurações de reconhecimento de placa veicular, incluindo limites de detecção, formatação e placas conhecidas."
|
||||
},
|
||||
"record": {
|
||||
"label": "Gravação",
|
||||
@@ -553,6 +556,18 @@
|
||||
"description": "Configurações que definem para quais objetos rastreados são geradas detecções (sem gerar alerta) e como essas detecções são retidas.",
|
||||
"enabled": {
|
||||
"label": "Ativar detecções"
|
||||
},
|
||||
"labels": {
|
||||
"label": "Rótulos de detecção",
|
||||
"description": "Lista de rótulos de objetos que são considerados eventos de detecção."
|
||||
},
|
||||
"required_zones": {
|
||||
"label": "Zonas requeridas",
|
||||
"description": "Zonas em que um objeto deve entrar para ser considerado detectado. Deixe em branco para permitir qualquer zona."
|
||||
},
|
||||
"cutoff_time": {
|
||||
"label": "Tempo limite de detecção",
|
||||
"description": "Segundos a esperar após o fim da atividade antes de encerrar a detecção."
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
@@ -12,12 +12,12 @@
|
||||
},
|
||||
"toast": {
|
||||
"success": {
|
||||
"deletedCategory_one": "Classe Apagada",
|
||||
"deletedCategory_many": "Classes apagadas",
|
||||
"deletedCategory_other": "",
|
||||
"deletedImage_one": "Imagen Apagada",
|
||||
"deletedImage_many": "Imagens Apagadas",
|
||||
"deletedImage_other": "",
|
||||
"deletedCategory_one": "{{count}} classe removida",
|
||||
"deletedCategory_many": "{{count}} classes removidas",
|
||||
"deletedCategory_other": "{{count}} classes removidas",
|
||||
"deletedImage_one": "{{count}} imagem removida",
|
||||
"deletedImage_many": "{{count}} imagens removidas",
|
||||
"deletedImage_other": "{{count}} imagens removidas",
|
||||
"categorizedImage": "Imagem Classificada com Sucesso",
|
||||
"trainedModel": "Modelo treinado com sucesso.",
|
||||
"trainingModel": "Treinamento do modelo iniciado com sucesso.",
|
||||
|
||||
@@ -296,7 +296,7 @@
|
||||
}
|
||||
},
|
||||
"motionMaskLabel": "Máscara de Movimento {{number}}",
|
||||
"objectMaskLabel": "Máscara de Objeto {{number}} ({{label}})",
|
||||
"objectMaskLabel": "Máscara de Objeto {{number}}",
|
||||
"form": {
|
||||
"zoneName": {
|
||||
"error": {
|
||||
@@ -437,7 +437,7 @@
|
||||
"add": "Nova Máscara de Movimento",
|
||||
"edit": "Editar Máscara de Movimento",
|
||||
"context": {
|
||||
"title": "Máscaras de movimento são usadas para prevenir tipos de movimento não desejados de ativarem uma detecção (exemplo: galhos de árvores, timestamps de câmeras). Máscaras de movimento devem ser usadas <em> com moderação </em>. Excesso de mascaramento tornará o rastreamento de objetos mais difícil.",
|
||||
"title": "Máscaras de movimento são usadas para prevenir tipos de movimento não desejados de ativarem uma detecção (exemplo: galhos de árvores, timestamps de câmeras). Máscaras de movimento devem ser usadas <em>com moderação</em>. Excesso de mascaramento tornará o rastreamento de objetos mais difícil.",
|
||||
"documentation": "Leia a documentação"
|
||||
},
|
||||
"point_one": "{{count}} ponto",
|
||||
@@ -723,7 +723,7 @@
|
||||
"title": "Configuração de Captura de Imagem",
|
||||
"desc": "Envios ao Frigate+ requerem tanto a captura de imagem normais quanto a captura de imagem <code>clean_copy</code> estarem habilitadas na sua configuração.",
|
||||
"documentation": "Leia a documentação",
|
||||
"cleanCopyWarning": "Algumas câmeras possuem captura de imagem habilitada porém têm a cópia limpa desabilitada. Você precisa habilitar a <code>clean_copy</code> nas suas configurações de captura de imagem para poder submeter imagems dessa câmera ao Frigate+.",
|
||||
"cleanCopyWarning": "Algumas câmeras estão com os snapshots desativados",
|
||||
"table": {
|
||||
"camera": "Câmera",
|
||||
"snapshots": "Capturas de Imagem",
|
||||
|
||||
@@ -32,6 +32,9 @@
|
||||
"ask_a": "Este acest obiect un <code>{{label}}</code>?",
|
||||
"ask_an": "Este acest obiect un <code>{{label}}</code>?",
|
||||
"ask_full": "Este acest obiect un <code>{{untranslatedLabel}}</code> ({{translatedLabel}})?"
|
||||
},
|
||||
"toast": {
|
||||
"error": "Trimiterea către Frigate+ a eșuat. Te rog să verifici conexiunea la rețea și să încerci din nou."
|
||||
}
|
||||
},
|
||||
"submitToPlus": {
|
||||
@@ -96,7 +99,8 @@
|
||||
"error": {
|
||||
"failed": "Nu s-a putut adăuga exportul în coadă: {{error}}",
|
||||
"endTimeMustAfterStartTime": "Ora de sfârșit trebuie să fie după ora de început",
|
||||
"noVaildTimeSelected": "Nu a fost selectat un interval de timp valid"
|
||||
"noVaildTimeSelected": "Nu a fost selectat un interval de timp valid",
|
||||
"noValidTimeSelected": "Niciun interval de timp valid selectat"
|
||||
},
|
||||
"view": "Vizualizează",
|
||||
"queued": "Export pus la coadă. Vezi progresul pe pagina de exporturi.",
|
||||
@@ -145,7 +149,14 @@
|
||||
"queueingButton": "Se adaugă exporturile la coadă...",
|
||||
"exportButton_one": "Exportă 1 cameră",
|
||||
"exportButton_few": "Exportă {{count}} camere",
|
||||
"exportButton_other": "Exportă {{count}} de camere"
|
||||
"exportButton_other": "Exportă {{count}} de camere",
|
||||
"searchOrSelectGroup": "Caută sau selectează un grup de camere...",
|
||||
"selectAll": "Selectează toate camerele",
|
||||
"clearSelection": "Șterge selecția",
|
||||
"selectWithActivity": "Camere cu obiecte urmărite",
|
||||
"selectGroup": "Selectează grupul",
|
||||
"noMatchingCameras": "Nicio cameră nu corespunde căutării tale",
|
||||
"selectedCount": "{{selected}} / {{total}} selectate"
|
||||
},
|
||||
"multi": {
|
||||
"title_one": "Exportă o revizuire",
|
||||
|
||||
@@ -493,6 +493,9 @@
|
||||
"max_concurrent": {
|
||||
"description": "Numărul maxim de sarcini de export de procesat în același timp.",
|
||||
"label": "Număr maxim de exporturi simultane"
|
||||
},
|
||||
"chapters": {
|
||||
"label": "Metadate de capitol de încorporat în înregistrările exportate"
|
||||
}
|
||||
},
|
||||
"preview": {
|
||||
|
||||
@@ -637,6 +637,9 @@
|
||||
"max_concurrent": {
|
||||
"description": "Numărul maxim de sarcini de export de procesat în același timp.",
|
||||
"label": "Număr maxim de exporturi simultane"
|
||||
},
|
||||
"chapters": {
|
||||
"label": "Metadate de capitol de încorporat în înregistrările exportate"
|
||||
}
|
||||
},
|
||||
"preview": {
|
||||
|
||||
@@ -2098,6 +2098,10 @@
|
||||
},
|
||||
"ffmpeg": {
|
||||
"hwaccelManualNotRecommended": "Argumentele manuale pentru accelerarea hardware nu sunt recomandate. Dacă nu există o cerință specifică, selectează presetarea care se potrivește cu hardware-ul tău."
|
||||
},
|
||||
"model": {
|
||||
"optimizedFor320": "Frigate este optimizat pentru un model de 320x320, care este cea mai bună alegere pentru majoritatea configurațiilor. Un model de 640x640 este mai lent și ajută doar în scenarii specifice.",
|
||||
"inputDimensionsNotDetectResolution": "Lățimea și înălțimea de intrare ale modelului reprezintă dimensiunile de intrare ale modelului de detectare a obiectelor, nu rezoluția de detecție a camerei tale. Acestea ar trebui să se potrivească cu dimensiunile modelului pe care îl folosești — de obicei o dimensiune pătrată, cum ar fi 320x320 sau 640x640."
|
||||
}
|
||||
},
|
||||
"birdseye": {
|
||||
|
||||
@@ -1,9 +1,9 @@
|
||||
{
|
||||
"time": {
|
||||
"untilForTime": "Do{{time}}",
|
||||
"untilForTime": "Do {{time}}",
|
||||
"untilForRestart": "Do reštartu Frigate.",
|
||||
"untilRestart": "Do reštartu",
|
||||
"ago": "{{timeAgo}} pred časom",
|
||||
"ago": "pred {{timeAgo}}",
|
||||
"justNow": "Práve teraz",
|
||||
"today": "Dnes",
|
||||
"yesterday": "Včera",
|
||||
@@ -23,32 +23,32 @@
|
||||
"am": "ráno",
|
||||
"yr": "{{time}}r",
|
||||
"pm": "popoludní",
|
||||
"year_one": "{{time}}rok",
|
||||
"year_few": "{{time}}rokov",
|
||||
"year_other": "{{time}}rokov",
|
||||
"year_one": "{{time}} rok",
|
||||
"year_few": "{{time}} roky",
|
||||
"year_other": "{{time}} rokov",
|
||||
"mo": "{{time}}mes",
|
||||
"month_one": "{{time}}mesiac",
|
||||
"month_one": "{{time}} mesiac",
|
||||
"month_few": "{{time}} mesiace",
|
||||
"month_other": "{{time}} mesiaca",
|
||||
"month_other": "{{time}} mesiacov",
|
||||
"d": "{{time}}d",
|
||||
"day_one": "{{time}}deň",
|
||||
"day_few": "{{time}}dni",
|
||||
"day_other": "{{time}}dni",
|
||||
"day_one": "{{time}} deň",
|
||||
"day_few": "{{time}} dni",
|
||||
"day_other": "{{time}} dní",
|
||||
"h": "{{time}}h",
|
||||
"hour_one": "{{time}}hodina",
|
||||
"hour_few": "{{time}}hodiny",
|
||||
"hour_other": "{{time}}hodin",
|
||||
"hour_one": "{{time}} hodina",
|
||||
"hour_few": "{{time}} hodiny",
|
||||
"hour_other": "{{time}} hodín",
|
||||
"m": "{{time}} min",
|
||||
"s": "{{time}}s",
|
||||
"minute_one": "{{time}}minuta",
|
||||
"minute_few": "{{time}}minuty",
|
||||
"minute_other": "{{time}}minut",
|
||||
"second_one": "{{time}}sekunda",
|
||||
"second_few": "{{time}}sekundy",
|
||||
"second_other": "{{time}}sekund",
|
||||
"minute_one": "{{time}} minúta",
|
||||
"minute_few": "{{time}} minúty",
|
||||
"minute_other": "{{time}} minút",
|
||||
"second_one": "{{time}} sekunda",
|
||||
"second_few": "{{time}} sekundy",
|
||||
"second_other": "{{time}} sekúnd",
|
||||
"formattedTimestamp": {
|
||||
"12hour": "Deň MMM, h:mm:ss aaa",
|
||||
"24hour": "Deň MMM, HH:mm:ss"
|
||||
"12hour": "d MMM, h:mm:ss aaa",
|
||||
"24hour": "d MMM, HH:mm:ss"
|
||||
},
|
||||
"formattedTimestamp2": {
|
||||
"12hour": "MM/dd h:mm:ssa",
|
||||
@@ -219,9 +219,9 @@
|
||||
"allCameras": "Všetky kamery",
|
||||
"cameras": {
|
||||
"title": "Kamery",
|
||||
"count_one": "{{count}}kamera",
|
||||
"count_few": "{{count}}kamery",
|
||||
"count_other": "{{count}}kamier"
|
||||
"count_one": "{{count}} kamera",
|
||||
"count_few": "{{count}} kamery",
|
||||
"count_other": "{{count}} kamier"
|
||||
}
|
||||
},
|
||||
"export": "Exportovať",
|
||||
|
||||
@@ -43,9 +43,9 @@
|
||||
"title": "Čas ukončenia",
|
||||
"label": "Vybrat čas ukončenia"
|
||||
},
|
||||
"lastHour_one": "Minulu hodinu",
|
||||
"lastHour_few": "Minule{{count}}hodiny",
|
||||
"lastHour_other": "Minulych{{count}}hodin"
|
||||
"lastHour_one": "Posledná hodina",
|
||||
"lastHour_few": "Posledné {{count}} hodiny",
|
||||
"lastHour_other": "Posledných {{count}} hodín"
|
||||
},
|
||||
"name": {
|
||||
"placeholder": "Pomenujte Export"
|
||||
|
||||
@@ -12,12 +12,12 @@
|
||||
},
|
||||
"toast": {
|
||||
"success": {
|
||||
"deletedCategory_one": "Vymazaná Trieda",
|
||||
"deletedCategory_few": "",
|
||||
"deletedCategory_other": "",
|
||||
"deletedImage_one": "Vymazané Obrázky",
|
||||
"deletedImage_few": "",
|
||||
"deletedImage_other": "",
|
||||
"deletedCategory_one": "Vymazaná {{count}} trieda",
|
||||
"deletedCategory_few": "Vymazané {{count}} triedy",
|
||||
"deletedCategory_other": "Vymazaných {{count}} tried",
|
||||
"deletedImage_one": "Vymazaný {{count}} obrázok",
|
||||
"deletedImage_few": "Vymazané {{count}} obrázky",
|
||||
"deletedImage_other": "Vymazaných {{count}} obrázkov",
|
||||
"categorizedImage": "Obrázok bol úspešne klasifikovaný",
|
||||
"trainedModel": "Úspešne vyškolený model.",
|
||||
"trainingModel": "Úspešne spustené trénovanie modelu.",
|
||||
@@ -81,7 +81,7 @@
|
||||
"buttonText": "Vytvorte objektový model"
|
||||
},
|
||||
"state": {
|
||||
"title": "Žiadne modely klasifikácie štátov",
|
||||
"title": "Žiadne modely klasifikácie stavov",
|
||||
"description": "Vytvorte si vlastný model na monitorovanie a klasifikáciu zmien stavu v špecifických oblastiach kamery.",
|
||||
"buttonText": "Vytvorte model stavu"
|
||||
}
|
||||
@@ -90,7 +90,7 @@
|
||||
"title": "Vytvorte novú klasifikáciu",
|
||||
"steps": {
|
||||
"nameAndDefine": "Názov a definícia",
|
||||
"stateArea": "Štátna oblasť",
|
||||
"stateArea": "Oblasť stavu",
|
||||
"chooseExamples": "Vyberte Príklady"
|
||||
},
|
||||
"step1": {
|
||||
@@ -98,7 +98,7 @@
|
||||
"name": "Meno",
|
||||
"namePlaceholder": "Zadajte názov modelu...",
|
||||
"type": "Typ",
|
||||
"typeState": "štátu",
|
||||
"typeState": "Stav",
|
||||
"typeObject": "Objekt",
|
||||
"objectLabel": "Označenie objektu",
|
||||
"objectLabelPlaceholder": "Vyberte typ objektu...",
|
||||
@@ -118,12 +118,12 @@
|
||||
"nameOnlyNumbers": "Názov modelu nemôže obsahovať iba čísla",
|
||||
"classRequired": "Vyžaduje sa aspoň 1 kurz",
|
||||
"classesUnique": "Názvy tried musia byť jedinečné",
|
||||
"stateRequiresTwoClasses": "Modely štátov vyžadujú aspoň 2 triedy",
|
||||
"stateRequiresTwoClasses": "Stavové modely vyžadujú aspoň 2 triedy",
|
||||
"objectLabelRequired": "Vyberte označenie objektu",
|
||||
"objectTypeRequired": "Vyberte typ klasifikácie",
|
||||
"noneNotAllowed": "Trieda 'none' nie je povolená"
|
||||
},
|
||||
"states": "Štátov"
|
||||
"states": "Stavy"
|
||||
},
|
||||
"step2": {
|
||||
"description": "Vyberte kamery a definujte oblasť, ktorú chcete pre každú kameru monitorovať. Model klasifikuje stav týchto oblastí.",
|
||||
@@ -134,7 +134,7 @@
|
||||
},
|
||||
"step3": {
|
||||
"selectImagesPrompt": "Vybrať všetky obrázky s: {{className}}",
|
||||
"selectImagesDescription": "Kliknite na obrázky a vyberte ich. Po dokončení tejto hodiny kliknite na tlačidlo Pokračovať.",
|
||||
"selectImagesDescription": "Kliknite na obrázky a vyberte ich. Po dokončení tejto triedy kliknite na tlačidlo Pokračovať.",
|
||||
"generating": {
|
||||
"title": "Generovanie vzorových obrázkov",
|
||||
"description": "Frigate načítava reprezentatívne obrázky z vašich nahrávok. Môže to chvíľu trvať..."
|
||||
@@ -155,9 +155,9 @@
|
||||
"classifyFailed": "Nepodarilo sa klasifikovať obrázky: {{error}}"
|
||||
},
|
||||
"generateSuccess": "Vzorové obrázky boli úspešne vygenerované",
|
||||
"allImagesRequired_one": "Uveďte všetky obrázky. {{count}} obrázok zostáva.",
|
||||
"allImagesRequired_few": "Uveďte všetky obrázky. {{count}} obrázky zostávajú.",
|
||||
"allImagesRequired_other": "Uveďte všetky obrázky. {{count}} obrázkov zostávajú.",
|
||||
"allImagesRequired_one": "Klasifikujte všetky obrázky. Zostáva {{count}} obrázok.",
|
||||
"allImagesRequired_few": "Klasifikujte všetky obrázky. Zostávajú {{count}} obrázky.",
|
||||
"allImagesRequired_other": "Klasifikujte všetky obrázky. Zostáva {{count}} obrázkov.",
|
||||
"modelCreated": "Model vytvorený úspešne. Použite aktuálne klasifikácie na pridanie obrázkov pre chýbajúce stavy a nasledne dajte trénovať model.",
|
||||
"missingStatesWarning": {
|
||||
"title": "Chýbajúce príklady stavov",
|
||||
@@ -174,7 +174,7 @@
|
||||
},
|
||||
"menu": {
|
||||
"objects": "Objekty",
|
||||
"states": "Štátov"
|
||||
"states": "Stavy"
|
||||
},
|
||||
"details": {
|
||||
"scoreInfo": "Skóre predstavuje priemernú istotu klasifikácie naprieč všetkými detekciami tohoto objektu.",
|
||||
|
||||
@@ -40,8 +40,8 @@
|
||||
"detail": {
|
||||
"noDataFound": "Žiadne podrobné údaje na kontrolu",
|
||||
"aria": "Prepnúť zobrazenie detailov",
|
||||
"trackedObject_one": "objekt",
|
||||
"trackedObject_other": "objekty",
|
||||
"trackedObject_one": "{{count}} objekt",
|
||||
"trackedObject_other": "{{count}} objektov",
|
||||
"noObjectDetailData": "Nie sú k dispozícii žiadne podrobné údaje o objekte.",
|
||||
"label": "Detail",
|
||||
"settings": "Nastavenia podrobného zobrazenia",
|
||||
|
||||
@@ -18,12 +18,12 @@
|
||||
"title": "Nahrať obrázok tváre",
|
||||
"desc": "Nahrajte obrázok na skenovanie tvárí a zahrňte ho do {{pageToggle}}"
|
||||
},
|
||||
"collections": "Zbierky",
|
||||
"collections": "Kolekcie",
|
||||
"createFaceLibrary": {
|
||||
"title": "Vytvoriť Zbierku",
|
||||
"desc": "Vytvoriť novú zbierku",
|
||||
"new": "Vytvoriť novú tvár",
|
||||
"nextSteps": "Vybudovanie silného základu:<li>Použite kartu Nedávne rozpoznania na výber a trénovanie obrázkov pre každú rozpoznanú osobu.</li><li>Pre dosiahnutie najlepších výsledkov sa zamerajte na priame obrázky; vyhnite sa trénovaniu obrázkov, ktoré zachytávajú tváre pod uhlom.</li></ul>"
|
||||
"nextSteps": "Vybudovanie silného základu:<li>Použite kartu Nedávno rozpoznané tváre na výber a trénovanie obrázkov pre každú rozpoznanú osobu.</li><li>Pre dosiahnutie najlepších výsledkov sa zamerajte na priame obrázky; vyhnite sa trénovaniu obrázkov, ktoré zachytávajú tváre pod uhlom.</li></ul>"
|
||||
},
|
||||
"steps": {
|
||||
"faceName": "Zadajte Meno tváre",
|
||||
@@ -34,8 +34,8 @@
|
||||
}
|
||||
},
|
||||
"train": {
|
||||
"title": "Nedávne uznania",
|
||||
"aria": "Vyberte posledné rozpoznania",
|
||||
"title": "Nedávno rozpoznané tváre",
|
||||
"aria": "Vyberte nedávno rozpoznané tváre",
|
||||
"empty": "Neexistujú žiadne predchádzajúce pokusy o rozpoznávanie tváre"
|
||||
},
|
||||
"selectItem": "Vyberte {{item}}",
|
||||
@@ -85,9 +85,10 @@
|
||||
"deletedName_one": "{{count}} tvár bola úspešne odstránená.",
|
||||
"deletedName_few": "{{count}} tváre boli úspešne odstránené.",
|
||||
"deletedName_other": "{{count}} tvárí bolo úspešne odstránených.",
|
||||
"renamedFace": "Úspešne premenovaná tvár na {{name}}",
|
||||
"trainedFace": "Úspešne natrénovaná tvár.",
|
||||
"updatedFaceScore": "Úspešne aktualizované skóre tváre."
|
||||
"renamedFace": "Tvár bola úspešne premenovaná na {{name}}.",
|
||||
"trainedFace": "Tvár bola úspešne natrénovaná.",
|
||||
"updatedFaceScore": "Skóre tváre bolo úspešne aktualizované na {{name}} ({{score}}).",
|
||||
"reclassifiedFace": "Tvár bola úspešne preklasifikovaná."
|
||||
},
|
||||
"error": {
|
||||
"uploadingImageFailed": "Nepodarilo sa nahrať obrázok: {{errorMessage}}",
|
||||
@@ -95,8 +96,9 @@
|
||||
"deleteFaceFailed": "Nepodarilo sa odstrániť: {{errorMessage}}",
|
||||
"deleteNameFailed": "Nepodarilo sa odstrániť meno: {{errorMessage}}",
|
||||
"renameFaceFailed": "Nepodarilo sa premenovať tvár: {{errorMessage}}",
|
||||
"trainFailed": "Nepodarilo sa trénovať: {{errorMessage}}",
|
||||
"updateFaceScoreFailed": "Nepodarilo sa aktualizovať skóre tváre: {{errorMessage}}"
|
||||
"trainFailed": "Nepodarilo sa natrénovať tvár: {{errorMessage}}",
|
||||
"updateFaceScoreFailed": "Nepodarilo sa aktualizovať skóre tváre: {{errorMessage}}",
|
||||
"reclassifyFailed": "Nepodarilo sa preklasifikovať tvár: {{errorMessage}}"
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
@@ -339,9 +339,9 @@
|
||||
},
|
||||
"add": "Pridať zónu",
|
||||
"edit": "Upraviť zónu",
|
||||
"point_one": "{{count}}bod",
|
||||
"point_few": "{{count}}body",
|
||||
"point_other": "{{count}}bodov"
|
||||
"point_one": "{{count}} bod",
|
||||
"point_few": "{{count}} body",
|
||||
"point_other": "{{count}} bodov"
|
||||
},
|
||||
"motionMasks": {
|
||||
"label": "Maska Detekcia pohybu",
|
||||
@@ -380,8 +380,8 @@
|
||||
"add": "Pridať Masku Objektu",
|
||||
"edit": "Upraviť Masku Objektu",
|
||||
"context": "Masky filtrovania objektov slúžia na odfiltrovanie falošných poplachov konkrétneho typu objektu na základe jeho umiestnenia.",
|
||||
"point_one": "{{count}}bod",
|
||||
"point_few": "{{count}}body",
|
||||
"point_one": "{{count}} bod",
|
||||
"point_few": "{{count}} body",
|
||||
"point_other": "{{count}} bodov",
|
||||
"clickDrawPolygon": "Kliknutím nakreslite polygón do obrázku.",
|
||||
"objects": {
|
||||
@@ -397,7 +397,7 @@
|
||||
}
|
||||
},
|
||||
"motionMaskLabel": "Maska Detekcia pohybu {{number}}",
|
||||
"objectMaskLabel": "Maska Objektu {{number}} {{label}}"
|
||||
"objectMaskLabel": "Maska objektu {{number}}"
|
||||
},
|
||||
"motionDetectionTuner": {
|
||||
"title": "Ladenie detekcie pohybu",
|
||||
|
||||
@@ -3,7 +3,7 @@
|
||||
"cameras": "Štatistiky kamier - Frigate",
|
||||
"storage": "Štatistiky úložiska - Frigate",
|
||||
"general": "Všeobecné štatistiky - Frigate",
|
||||
"enrichments": "Štatistiky obohatenia - Frigate",
|
||||
"enrichments": "Štatistiky AI funkcií - Frigate",
|
||||
"logs": {
|
||||
"frigate": "Protokoly Frigate - Frigate",
|
||||
"go2rtc": "Protokoly Go2RTC - Frigate",
|
||||
@@ -36,7 +36,7 @@
|
||||
}
|
||||
},
|
||||
"general": {
|
||||
"title": "Hlavný",
|
||||
"title": "Všeobecné",
|
||||
"detector": {
|
||||
"title": "Detektory",
|
||||
"inferenceSpeed": "Detekčná rýchlosť",
|
||||
@@ -96,7 +96,7 @@
|
||||
}
|
||||
},
|
||||
"storage": {
|
||||
"title": "Skladovanie",
|
||||
"title": "Úložisko",
|
||||
"overview": "Prehľad",
|
||||
"recordings": {
|
||||
"title": "Nahrávky",
|
||||
@@ -108,11 +108,11 @@
|
||||
"warning": "Aktuálna veľkosť SHM {{total}}MB je príliš malá. Zvýšte ju aspoň na {{min_shm}}MB."
|
||||
},
|
||||
"cameraStorage": {
|
||||
"title": "Úložisko kamery",
|
||||
"title": "Úložisko kamier",
|
||||
"camera": "Kamera",
|
||||
"unusedStorageInformation": "Nepoužité informácie o úložisku",
|
||||
"storageUsed": "Skladovanie",
|
||||
"percentageOfTotalUsed": "Percento z celkového počtu",
|
||||
"unusedStorageInformation": "Informácie o nevyužitom úložisku",
|
||||
"storageUsed": "Úložisko",
|
||||
"percentageOfTotalUsed": "Percento z celku",
|
||||
"bandwidth": "Šírka pásma",
|
||||
"unused": {
|
||||
"title": "Nepoužité",
|
||||
@@ -125,7 +125,7 @@
|
||||
"overview": "Prehľad",
|
||||
"info": {
|
||||
"aspectRatio": "pomer strán",
|
||||
"cameraProbeInfo": "{{camera}} Informácie o sonde kamery",
|
||||
"cameraProbeInfo": "Údaje o kamere {{camera}}",
|
||||
"streamDataFromFFPROBE": "Údaje zo streamu sa získavajú pomocou príkazu <code>ffprobe</code>.",
|
||||
"fetching": "Načítavajú sa údaje z kamery",
|
||||
"stream": "Stream {{idx}}",
|
||||
@@ -137,32 +137,33 @@
|
||||
"audio": "Zvuk:",
|
||||
"error": "Chyba: {{error}}",
|
||||
"tips": {
|
||||
"title": "Informácie o kamerovej sonde"
|
||||
"title": "Údaje o kamere"
|
||||
}
|
||||
},
|
||||
"framesAndDetections": "Rámy / Detekcie",
|
||||
"framesAndDetections": "Snímky / Detekcie",
|
||||
"label": {
|
||||
"camera": "kamera",
|
||||
"detect": "odhaliť",
|
||||
"detect": "detekcie",
|
||||
"skipped": "preskočené",
|
||||
"ffmpeg": "FFmpeg",
|
||||
"capture": "zachytiť",
|
||||
"capture": "zachytávanie",
|
||||
"cameraFfmpeg": "{{camName}} FFmpeg",
|
||||
"cameraCapture": "zachytiť{{camName}}",
|
||||
"cameraDetect": "Detekcia {{camName}}",
|
||||
"cameraCapture": "{{camName}} zachytávanie",
|
||||
"cameraDetect": "{{camName}} detekcie",
|
||||
"overallFramesPerSecond": "celkový počet snímok za sekundu",
|
||||
"overallDetectionsPerSecond": "celkový počet detekcií za sekundu",
|
||||
"overallSkippedDetectionsPerSecond": "celkový počet vynechaných detekcií za sekundu",
|
||||
"overallSkippedDetectionsPerSecond": "celkový počet preskočených detekcií za sekundu",
|
||||
"cameraFramesPerSecond": "{{camName}} snímky za sekundu",
|
||||
"cameraDetectionsPerSecond": "{{camName}}detekcie za sekundu",
|
||||
"cameraSkippedDetectionsPerSecond": "{{camName}} vynechaných detekcií za sekundu"
|
||||
"cameraDetectionsPerSecond": "{{camName}} detekcie za sekundu",
|
||||
"cameraSkippedDetectionsPerSecond": "{{camName}} preskočené detekcie za sekundu",
|
||||
"cameraGpu": "{{camName}} GPU"
|
||||
},
|
||||
"toast": {
|
||||
"success": {
|
||||
"copyToClipboard": "Dáta sondy boli skopírované do schránky."
|
||||
"copyToClipboard": "Údaje o kamere boli skopírované do schránky."
|
||||
},
|
||||
"error": {
|
||||
"unableToProbeCamera": "Nepodarilo sa overiť kameru: {{errorMessage}}"
|
||||
"unableToProbeCamera": "Nepodarilo sa načítať údaje z kamery: {{errorMessage}}"
|
||||
}
|
||||
}
|
||||
},
|
||||
@@ -178,7 +179,7 @@
|
||||
"shmTooLow": "Alokácia /dev/shm ({{total}} MB) by sa mala zvýšiť aspoň na {{min}} MB."
|
||||
},
|
||||
"enrichments": {
|
||||
"title": "Obohatenia",
|
||||
"title": "AI funkcie",
|
||||
"infPerSecond": "Inferencie za sekundu",
|
||||
"embeddings": {
|
||||
"image_embedding": "Vkladanie obrázkov",
|
||||
|
||||
@@ -75,5 +75,9 @@
|
||||
"24hour": "MM-dd-yy-HH-mm-ss"
|
||||
}
|
||||
},
|
||||
"readTheDocumentation": "Прочитајте документацију"
|
||||
"readTheDocumentation": "Прочитајте документацију",
|
||||
"menu": {
|
||||
"system": "Систем",
|
||||
"profiles": "Профили"
|
||||
}
|
||||
}
|
||||
|
||||
File diff suppressed because it is too large
Load Diff
@@ -24,6 +24,9 @@
|
||||
},
|
||||
"state": {
|
||||
"submitted": "Inskickad"
|
||||
},
|
||||
"toast": {
|
||||
"error": "Misslyckades att skicka till Frigate+. Kontrollera din nätverksanslutning och försök igen."
|
||||
}
|
||||
}
|
||||
},
|
||||
@@ -106,7 +109,10 @@
|
||||
"selectAll": "Välj alla kameror",
|
||||
"selectWithActivity": "Kameror med spårade objekt",
|
||||
"selectGroup": "Välj grupp",
|
||||
"noMatchingCameras": "Ingen kamera matchar din sökning"
|
||||
"noMatchingCameras": "Ingen kamera matchar din sökning",
|
||||
"searchOrSelectGroup": "Sök, eller välj en kameragrupp...",
|
||||
"clearSelection": "Rensa val",
|
||||
"selectedCount": "{{selected}} / {{total}} valda"
|
||||
},
|
||||
"multi": {
|
||||
"title_one": "Exportera 1 recension",
|
||||
|
||||
@@ -20,10 +20,35 @@
|
||||
"description": "Aktivera eller avaktivera ljudbaserad detektering för denna kamera."
|
||||
},
|
||||
"max_not_heard": {
|
||||
"description": "Antal sekunder utan den konfigurerade ljudtypen innan en ljudbaserad händelse slutar."
|
||||
"description": "Antal sekunder utan den konfigurerade ljudtypen innan en ljudbaserad händelse slutar.",
|
||||
"label": "Avbryt paus"
|
||||
},
|
||||
"min_volume": {
|
||||
"label": "Minsta ljudvolym"
|
||||
"label": "Minsta ljudvolym",
|
||||
"description": "Lägsta RMS volym för ljuddetektion; lägre värde ökar känslighet (t.e.x. 200 högt, 500 medium, 1000 lågt)."
|
||||
},
|
||||
"listen": {
|
||||
"label": "Lyssningstyper",
|
||||
"description": "Lista på typer av ljudevent att detektera (till exempel: skälla, brandlarm, tal, skrik)."
|
||||
},
|
||||
"filters": {
|
||||
"label": "Ljudfilter",
|
||||
"description": "Filterinställningar per ljudtyp, till exempel konfidinströskel som används för att minska falska positiva resultat.",
|
||||
"threshold": {
|
||||
"label": "Lägsta ljud-konfidinströskel",
|
||||
"description": "Lägsta konfidinströskel för att ljudhändelsen ska räknas."
|
||||
}
|
||||
},
|
||||
"enabled_in_config": {
|
||||
"label": "Ursprungligt ljudtillstånd",
|
||||
"description": "Indikerar om ljuddetektion ursprungligen var aktiverat i den statiska konfigurationsfilen."
|
||||
},
|
||||
"num_threads": {
|
||||
"label": "Detekteringstrådar",
|
||||
"description": "Antal trådar att använda för bearbetning av ljuddetektering."
|
||||
}
|
||||
},
|
||||
"audio_transcription": {
|
||||
"label": "Ljudtranskribering"
|
||||
}
|
||||
}
|
||||
|
||||
@@ -8,10 +8,35 @@
|
||||
"label": "Aktivera ljuddetektering"
|
||||
},
|
||||
"max_not_heard": {
|
||||
"description": "Antal sekunder utan den konfigurerade ljudtypen innan en ljudbaserad händelse slutar."
|
||||
"description": "Antal sekunder utan den konfigurerade ljudtypen innan en ljudbaserad händelse slutar.",
|
||||
"label": "Avbryt paus"
|
||||
},
|
||||
"min_volume": {
|
||||
"label": "Minsta ljudvolym"
|
||||
"label": "Minsta ljudvolym",
|
||||
"description": "Lägsta RMS volym för ljuddetektion; lägre värde ökar känslighet (t.e.x. 200 högt, 500 medium, 1000 lågt)."
|
||||
},
|
||||
"listen": {
|
||||
"label": "Lyssningstyper",
|
||||
"description": "Lista på typer av ljudevent att detektera (till exempel: skälla, brandlarm, tal, skrik)."
|
||||
},
|
||||
"filters": {
|
||||
"label": "Ljudfilter",
|
||||
"description": "Filterinställningar per ljudtyp, till exempel konfidinströskel som används för att minska falska positiva resultat.",
|
||||
"threshold": {
|
||||
"label": "Lägsta ljud-konfidinströskel",
|
||||
"description": "Lägsta konfidinströskel för att ljudhändelsen ska räknas."
|
||||
}
|
||||
},
|
||||
"enabled_in_config": {
|
||||
"label": "Ursprungligt ljudtillstånd",
|
||||
"description": "Indikerar om ljuddetektion ursprungligen var aktiverat i den statiska konfigurationsfilen."
|
||||
},
|
||||
"num_threads": {
|
||||
"label": "Detekteringstrådar",
|
||||
"description": "Antal trådar att använda för bearbetning av ljuddetektering."
|
||||
}
|
||||
},
|
||||
"audio_transcription": {
|
||||
"label": "Ljudtranskribering"
|
||||
}
|
||||
}
|
||||
|
||||
@@ -67,7 +67,8 @@
|
||||
"error": {
|
||||
"failed": "未能加入导出队列:{{error}}",
|
||||
"endTimeMustAfterStartTime": "结束时间必须在开始时间之后",
|
||||
"noVaildTimeSelected": "未选择有效的时间范围"
|
||||
"noVaildTimeSelected": "未选择有效的时间范围",
|
||||
"noValidTimeSelected": "未选择有效的时间范围"
|
||||
},
|
||||
"view": "查看",
|
||||
"queued": "导出已加入队列。请在导出页面查看进度。",
|
||||
|
||||
@@ -452,15 +452,15 @@
|
||||
},
|
||||
"continuous": {
|
||||
"label": "持续保留",
|
||||
"description": "无论是否有追踪目标或动作,保留录像的天数。如果只想保留警报和检测的录像,请设置为 0。",
|
||||
"description": "无论是否有追踪目标或画面变动,保留录像的天数。如果只想保留警报和检测的录制,请设置为 0。",
|
||||
"days": {
|
||||
"label": "保留天数",
|
||||
"description": "保留录像的天数。"
|
||||
}
|
||||
},
|
||||
"motion": {
|
||||
"label": "动作保留",
|
||||
"description": "无论是否有追踪目标,由动作触发的录像保留天数。如果只想保留警报和检测的录像,请设置为 0。",
|
||||
"label": "画面变动录制保留",
|
||||
"description": "无论是否有追踪目标,只要画面变动触发保存的录制保留天数。如果只想保留警报和检测的录制,请设置为 0。",
|
||||
"days": {
|
||||
"label": "保留天数",
|
||||
"description": "保留录像的天数。"
|
||||
@@ -486,7 +486,7 @@
|
||||
},
|
||||
"mode": {
|
||||
"label": "保留模式",
|
||||
"description": "保留模式:all(保存所有片段)、motion(保存有动作的片段)或 active_objects(保存有活动目标的片段)。"
|
||||
"description": "保留模式:全部(保存所有片段)、画面变动(保存有画面变动的片段)或 活动目标(保存有活动目标的片段)。"
|
||||
}
|
||||
}
|
||||
},
|
||||
@@ -510,7 +510,7 @@
|
||||
},
|
||||
"mode": {
|
||||
"label": "保留模式",
|
||||
"description": "保留模式:all(保存所有片段)、motion(保存有动作的片段)或 active_objects(保存有活动目标的片段)。"
|
||||
"description": "保留模式:全部(保存所有片段)、画面变动(保存有画面变动的片段)或 活动目标(保存有活动目标的片段)。"
|
||||
}
|
||||
}
|
||||
},
|
||||
|
||||
@@ -1507,10 +1507,10 @@
|
||||
},
|
||||
"motion": {
|
||||
"label": "画面变动检测",
|
||||
"description": "应用于摄像头的默认动作检测设置,除非按摄像头覆盖。",
|
||||
"description": "默认情况下,画面变动检测配置统一应用于各摄像头,除非对特定摄像头单独进行自定义。",
|
||||
"enabled": {
|
||||
"label": "开启画面变动检测",
|
||||
"description": "为所有摄像头启用或禁用动作检测;可按摄像头覆盖。"
|
||||
"description": "为所有摄像头启用或禁用画面变动检测。可以给指定摄像头单独覆盖此设置。"
|
||||
},
|
||||
"threshold": {
|
||||
"label": "画面变动阈值",
|
||||
@@ -1706,15 +1706,15 @@
|
||||
},
|
||||
"continuous": {
|
||||
"label": "持续保留",
|
||||
"description": "无论是否有追踪目标或动作,保留录像的天数。如果只想保留警报和检测的录像,请设置为 0。",
|
||||
"description": "无论是否有追踪目标或画面变动,保留录像的天数。如果只想保留警报和检测的录制,请设置为 0。",
|
||||
"days": {
|
||||
"label": "保留天数",
|
||||
"description": "保留录像的天数。"
|
||||
}
|
||||
},
|
||||
"motion": {
|
||||
"label": "动作保留",
|
||||
"description": "无论是否有追踪目标,由动作触发的录像保留天数。如果只想保留警报和检测的录像,请设置为 0。",
|
||||
"label": "画面变动录制保留",
|
||||
"description": "无论是否有追踪目标,只要画面变动触发保存的录制保留天数。如果只想保留警报和检测的录制,请设置为 0。",
|
||||
"days": {
|
||||
"label": "保留天数",
|
||||
"description": "保留录像的天数。"
|
||||
@@ -1740,7 +1740,7 @@
|
||||
},
|
||||
"mode": {
|
||||
"label": "保留模式",
|
||||
"description": "保留模式:all(保存所有片段)、motion(保存有动作的片段)或 active_objects(保存有活动目标的片段)。"
|
||||
"description": "保留模式:全部(保存所有片段)、画面变动(保存有画面变动的片段)或 活动目标(保存有活动目标的片段)。"
|
||||
}
|
||||
}
|
||||
},
|
||||
@@ -1764,7 +1764,7 @@
|
||||
},
|
||||
"mode": {
|
||||
"label": "保留模式",
|
||||
"description": "保留模式:all(保存所有片段)、motion(保存有动作的片段)或 active_objects(保存有活动目标的片段)。"
|
||||
"description": "保留模式:全部(保存所有片段)、画面变动(保存有画面变动的片段)或 活动目标(保存有活动目标的片段)。"
|
||||
}
|
||||
}
|
||||
},
|
||||
@@ -2030,8 +2030,8 @@
|
||||
}
|
||||
},
|
||||
"motion": {
|
||||
"label": "动作时运行",
|
||||
"description": "启用后,当在指定裁剪区域内检测到动作时运行分类。"
|
||||
"label": "画面变动时运行",
|
||||
"description": "启用后,当在指定裁剪区域内检测到画面变动时进行分类。"
|
||||
},
|
||||
"interval": {
|
||||
"label": "分类间隔",
|
||||
|
||||
@@ -1655,7 +1655,14 @@
|
||||
"keyLabel": "键",
|
||||
"valueLabel": "值",
|
||||
"keyPlaceholder": "新键名",
|
||||
"remove": "移除"
|
||||
"remove": "移除",
|
||||
"providerNameLabel": "提供商名称",
|
||||
"providerNamePlaceholder": "例如:openai、deepseek",
|
||||
"variableNameLabel": "变量名",
|
||||
"variableNamePlaceholder": "例如:MY_VARIABLE",
|
||||
"loggerNameLabel": "日志模块名称",
|
||||
"loggerNamePlaceholder": "例如:frigate.record",
|
||||
"keyPatternError": "只能使用字母、数字、连字符和下划线(不包含空格)"
|
||||
},
|
||||
"roleMap": {
|
||||
"empty": "未配置权限组映射",
|
||||
|
||||
@@ -59,7 +59,8 @@
|
||||
"error": {
|
||||
"failed": "匯出失敗:{{error}}",
|
||||
"endTimeMustAfterStartTime": "結束時間必須要在開始時間之後",
|
||||
"noVaildTimeSelected": "沒有選取有效的時間範圍"
|
||||
"noVaildTimeSelected": "沒有選取有效的時間範圍",
|
||||
"noValidTimeSelected": "未選擇有效的時間範圍"
|
||||
},
|
||||
"view": "查看",
|
||||
"queued": "匯出已加入佇列。請在匯出頁面檢視進度。",
|
||||
|
||||
@@ -497,6 +497,9 @@
|
||||
"max_concurrent": {
|
||||
"label": "最大併發匯出數",
|
||||
"description": "同時可處理的最大匯出任務數量。"
|
||||
},
|
||||
"chapters": {
|
||||
"label": "匯出錄影時要嵌入的章節資訊"
|
||||
}
|
||||
},
|
||||
"preview": {
|
||||
|
||||
@@ -1043,6 +1043,9 @@
|
||||
"max_concurrent": {
|
||||
"label": "最大併發匯出數",
|
||||
"description": "同時可處理的最大匯出任務數量。"
|
||||
},
|
||||
"chapters": {
|
||||
"label": "匯出錄影時要嵌入的章節資訊"
|
||||
}
|
||||
},
|
||||
"preview": {
|
||||
|
||||
@@ -1484,7 +1484,14 @@
|
||||
"keyLabel": "鍵",
|
||||
"valueLabel": "值",
|
||||
"keyPlaceholder": "新鍵名",
|
||||
"remove": "移除"
|
||||
"remove": "移除",
|
||||
"providerNameLabel": "服務提供者名稱",
|
||||
"providerNamePlaceholder": "例如:openai",
|
||||
"variableNameLabel": "變數名稱",
|
||||
"variableNamePlaceholder": "例如:MY_VARIABLE",
|
||||
"loggerNameLabel": "日誌名稱",
|
||||
"loggerNamePlaceholder": "例如:frigate.record",
|
||||
"keyPatternError": "僅能使用英文字母、數字、連字號(-)和底線(_),且不得包含空格"
|
||||
},
|
||||
"knownPlates": {
|
||||
"namePlaceholder": "例如:老婆的車",
|
||||
|
||||
@@ -9,6 +9,7 @@ import {
|
||||
import {
|
||||
Form,
|
||||
FormControl,
|
||||
FormDescription,
|
||||
FormField,
|
||||
FormItem,
|
||||
FormLabel,
|
||||
@@ -17,6 +18,7 @@ import {
|
||||
import { Input } from "@/components/ui/input";
|
||||
import { Label } from "@/components/ui/label";
|
||||
import { RadioGroup, RadioGroupItem } from "@/components/ui/radio-group";
|
||||
import { Switch } from "@/components/ui/switch";
|
||||
import {
|
||||
Select,
|
||||
SelectContent,
|
||||
@@ -50,14 +52,25 @@ type ClassificationModelEditDialogProps = {
|
||||
type ObjectClassificationType = "sub_label" | "attribute";
|
||||
|
||||
type ObjectFormData = {
|
||||
enabled: boolean;
|
||||
saveAttempts: number;
|
||||
objectLabel: string;
|
||||
objectType: ObjectClassificationType;
|
||||
};
|
||||
|
||||
type StateFormData = {
|
||||
enabled: boolean;
|
||||
saveAttempts: number;
|
||||
motion: boolean;
|
||||
interval?: number;
|
||||
classes: string[];
|
||||
};
|
||||
|
||||
const DEFAULT_SAVE_ATTEMPTS = {
|
||||
object: 200,
|
||||
state: 100,
|
||||
} as const;
|
||||
|
||||
export default function ClassificationModelEditDialog({
|
||||
open,
|
||||
model,
|
||||
@@ -71,6 +84,10 @@ export default function ClassificationModelEditDialog({
|
||||
const isStateModel = model.state_config !== undefined;
|
||||
const isObjectModel = model.object_config !== undefined;
|
||||
|
||||
const defaultSaveAttempts = isObjectModel
|
||||
? DEFAULT_SAVE_ATTEMPTS.object
|
||||
: DEFAULT_SAVE_ATTEMPTS.state;
|
||||
|
||||
const objectLabels = useMemo(() => {
|
||||
if (!config) return [];
|
||||
|
||||
@@ -93,8 +110,17 @@ export default function ClassificationModelEditDialog({
|
||||
|
||||
// Define form schema based on model type
|
||||
const formSchema = useMemo(() => {
|
||||
const sharedFields = {
|
||||
enabled: z.boolean(),
|
||||
saveAttempts: z.coerce
|
||||
.number({ message: t("edit.errors.saveAttemptsInvalid") })
|
||||
.int(t("edit.errors.saveAttemptsInvalid"))
|
||||
.min(0, t("edit.errors.saveAttemptsInvalid")),
|
||||
};
|
||||
|
||||
if (isObjectModel) {
|
||||
return z.object({
|
||||
...sharedFields,
|
||||
objectLabel: z
|
||||
.string()
|
||||
.min(1, t("wizard.step1.errors.objectLabelRequired")),
|
||||
@@ -103,6 +129,17 @@ export default function ClassificationModelEditDialog({
|
||||
} else {
|
||||
// State model
|
||||
return z.object({
|
||||
...sharedFields,
|
||||
motion: z.boolean(),
|
||||
interval: z.preprocess(
|
||||
(val) =>
|
||||
val === "" || val === null || val === undefined ? undefined : val,
|
||||
z.coerce
|
||||
.number({ message: t("edit.errors.intervalInvalid") })
|
||||
.int(t("edit.errors.intervalInvalid"))
|
||||
.positive(t("edit.errors.intervalInvalid"))
|
||||
.optional(),
|
||||
),
|
||||
classes: z
|
||||
.array(z.string())
|
||||
.min(1, t("wizard.step1.errors.classRequired"))
|
||||
@@ -129,12 +166,18 @@ export default function ClassificationModelEditDialog({
|
||||
resolver: zodResolver(formSchema),
|
||||
defaultValues: isObjectModel
|
||||
? ({
|
||||
enabled: model.enabled,
|
||||
saveAttempts: model.save_attempts ?? defaultSaveAttempts,
|
||||
objectLabel: model.object_config?.objects?.[0] || "",
|
||||
objectType:
|
||||
(model.object_config
|
||||
?.classification_type as ObjectClassificationType) || "sub_label",
|
||||
} as ObjectFormData)
|
||||
: ({
|
||||
enabled: model.enabled,
|
||||
saveAttempts: model.save_attempts ?? defaultSaveAttempts,
|
||||
motion: model.state_config?.motion ?? false,
|
||||
interval: model.state_config?.interval,
|
||||
classes: [""], // Will be populated from dataset
|
||||
} as StateFormData),
|
||||
mode: "onChange",
|
||||
@@ -151,6 +194,8 @@ export default function ClassificationModelEditDialog({
|
||||
if (open) {
|
||||
if (isObjectModel) {
|
||||
form.reset({
|
||||
enabled: model.enabled,
|
||||
saveAttempts: model.save_attempts ?? defaultSaveAttempts,
|
||||
objectLabel: model.object_config?.objects?.[0] || "",
|
||||
objectType:
|
||||
(model.object_config
|
||||
@@ -158,6 +203,10 @@ export default function ClassificationModelEditDialog({
|
||||
} as ObjectFormData);
|
||||
} else {
|
||||
form.reset({
|
||||
enabled: model.enabled,
|
||||
saveAttempts: model.save_attempts ?? defaultSaveAttempts,
|
||||
motion: model.state_config?.motion ?? false,
|
||||
interval: model.state_config?.interval,
|
||||
classes: [""],
|
||||
} as StateFormData);
|
||||
}
|
||||
@@ -166,7 +215,15 @@ export default function ClassificationModelEditDialog({
|
||||
mutateDataset();
|
||||
}
|
||||
}
|
||||
}, [open, isObjectModel, isStateModel, model, form, mutateDataset]);
|
||||
}, [
|
||||
open,
|
||||
isObjectModel,
|
||||
isStateModel,
|
||||
model,
|
||||
form,
|
||||
mutateDataset,
|
||||
defaultSaveAttempts,
|
||||
]);
|
||||
|
||||
// Update form with classes from dataset when loaded
|
||||
useEffect(() => {
|
||||
@@ -233,6 +290,7 @@ export default function ClassificationModelEditDialog({
|
||||
setIsSaving(true);
|
||||
try {
|
||||
if (isObjectModel) {
|
||||
// object model save
|
||||
const objectData = data as ObjectFormData;
|
||||
|
||||
// Update the config
|
||||
@@ -243,9 +301,10 @@ export default function ClassificationModelEditDialog({
|
||||
classification: {
|
||||
custom: {
|
||||
[model.name]: {
|
||||
enabled: model.enabled,
|
||||
enabled: objectData.enabled,
|
||||
name: model.name,
|
||||
threshold: model.threshold,
|
||||
save_attempts: objectData.saveAttempts,
|
||||
object_config: {
|
||||
objects: [objectData.objectLabel],
|
||||
classification_type: objectData.objectType,
|
||||
@@ -260,7 +319,34 @@ export default function ClassificationModelEditDialog({
|
||||
position: "top-center",
|
||||
});
|
||||
} else {
|
||||
// state model save
|
||||
const stateData = data as StateFormData;
|
||||
|
||||
const stateConfig: { motion: boolean; interval?: number | null } = {
|
||||
motion: stateData.motion,
|
||||
};
|
||||
if (stateData.interval != null) {
|
||||
stateConfig.interval = stateData.interval;
|
||||
} else if (model.state_config?.interval != null) {
|
||||
stateConfig.interval = null;
|
||||
}
|
||||
|
||||
await axios.put("/config/set", {
|
||||
requires_restart: 0,
|
||||
update_topic: `config/classification/custom/${model.name}`,
|
||||
config_data: {
|
||||
classification: {
|
||||
custom: {
|
||||
[model.name]: {
|
||||
enabled: stateData.enabled,
|
||||
save_attempts: stateData.saveAttempts,
|
||||
state_config: stateConfig,
|
||||
},
|
||||
},
|
||||
},
|
||||
},
|
||||
});
|
||||
|
||||
const newClasses = stateData.classes.filter(
|
||||
(c) => c.trim().length > 0,
|
||||
);
|
||||
@@ -307,11 +393,11 @@ export default function ClassificationModelEditDialog({
|
||||
if (renamePromises.length > 0) {
|
||||
await Promise.all(renamePromises);
|
||||
await mutate(`classification/${model.name}/dataset`);
|
||||
toast.success(t("toast.success.updatedModel"), {
|
||||
toast.success(t("edit.stateClassesInfo"), {
|
||||
position: "top-center",
|
||||
});
|
||||
} else {
|
||||
toast.info(t("edit.stateClassesInfo"), {
|
||||
toast.success(t("toast.success.updatedModel"), {
|
||||
position: "top-center",
|
||||
});
|
||||
}
|
||||
@@ -359,6 +445,29 @@ export default function ClassificationModelEditDialog({
|
||||
<div className="space-y-6">
|
||||
<Form {...form}>
|
||||
<form onSubmit={form.handleSubmit(onSubmit)} className="space-y-4">
|
||||
<FormField
|
||||
control={form.control}
|
||||
name="enabled"
|
||||
render={({ field }) => (
|
||||
<FormItem className="flex flex-row items-center justify-between gap-4">
|
||||
<div className="space-y-0.5">
|
||||
<FormLabel className="text-primary-variant">
|
||||
{t("edit.enabled")}
|
||||
</FormLabel>
|
||||
<FormDescription className="text-xs">
|
||||
{t("edit.enabledDesc")}
|
||||
</FormDescription>
|
||||
</div>
|
||||
<FormControl>
|
||||
<Switch
|
||||
checked={field.value}
|
||||
onCheckedChange={field.onChange}
|
||||
/>
|
||||
</FormControl>
|
||||
</FormItem>
|
||||
)}
|
||||
/>
|
||||
|
||||
{isObjectModel && (
|
||||
<>
|
||||
<FormField
|
||||
@@ -520,6 +629,77 @@ export default function ClassificationModelEditDialog({
|
||||
</div>
|
||||
)}
|
||||
|
||||
{isStateModel && (
|
||||
<>
|
||||
<FormField
|
||||
control={form.control}
|
||||
name="motion"
|
||||
render={({ field }) => (
|
||||
<FormItem className="flex flex-row items-center justify-between gap-4">
|
||||
<div className="space-y-0.5">
|
||||
<FormLabel className="text-primary-variant">
|
||||
{t("edit.motion")}
|
||||
</FormLabel>
|
||||
<FormDescription className="text-xs">
|
||||
{t("edit.motionDesc")}
|
||||
</FormDescription>
|
||||
</div>
|
||||
<FormControl>
|
||||
<Switch
|
||||
checked={field.value}
|
||||
onCheckedChange={field.onChange}
|
||||
/>
|
||||
</FormControl>
|
||||
</FormItem>
|
||||
)}
|
||||
/>
|
||||
|
||||
<FormField
|
||||
control={form.control}
|
||||
name="interval"
|
||||
render={({ field }) => (
|
||||
<FormItem>
|
||||
<FormLabel className="text-primary-variant">
|
||||
{t("edit.interval")}
|
||||
</FormLabel>
|
||||
<FormControl>
|
||||
<Input
|
||||
className="h-8"
|
||||
inputMode="numeric"
|
||||
placeholder={t("edit.intervalPlaceholder")}
|
||||
{...field}
|
||||
value={field.value ?? ""}
|
||||
/>
|
||||
</FormControl>
|
||||
<FormDescription className="text-xs">
|
||||
{t("edit.intervalDesc")}
|
||||
</FormDescription>
|
||||
<FormMessage />
|
||||
</FormItem>
|
||||
)}
|
||||
/>
|
||||
</>
|
||||
)}
|
||||
|
||||
<FormField
|
||||
control={form.control}
|
||||
name="saveAttempts"
|
||||
render={({ field }) => (
|
||||
<FormItem>
|
||||
<FormLabel className="text-primary-variant">
|
||||
{t("edit.saveAttempts")}
|
||||
</FormLabel>
|
||||
<FormControl>
|
||||
<Input className="h-8" inputMode="numeric" {...field} />
|
||||
</FormControl>
|
||||
<FormDescription className="text-xs">
|
||||
{t("edit.saveAttemptsDesc")}
|
||||
</FormDescription>
|
||||
<FormMessage />
|
||||
</FormItem>
|
||||
)}
|
||||
/>
|
||||
|
||||
<div className="flex flex-col gap-3 pt-3 sm:flex-row sm:justify-end sm:gap-4">
|
||||
<Button
|
||||
type="button"
|
||||
|
||||
@@ -1,8 +1,10 @@
|
||||
import type { WidgetProps } from "@rjsf/utils";
|
||||
import { useMemo } from "react";
|
||||
import { useEffect, useMemo } from "react";
|
||||
import { useTranslation } from "react-i18next";
|
||||
import useSWR from "swr";
|
||||
import { Switch } from "@/components/ui/switch";
|
||||
import type { ConfigFormContext } from "@/types/configForm";
|
||||
import type { GenAIModelsResponse } from "@/types/chat";
|
||||
|
||||
const GENAI_ROLES = ["embeddings", "descriptions", "chat"] as const;
|
||||
|
||||
@@ -37,10 +39,24 @@ export function GenAIRolesWidget(props: WidgetProps) {
|
||||
const selectedRoles = useMemo(() => normalizeValue(value), [value]);
|
||||
const providerKey = useMemo(() => getProviderKey(id), [id]);
|
||||
|
||||
// Compute occupied roles directly from formData. The computation is
|
||||
// trivially cheap (iterate providers × 3 roles max) so we skip an
|
||||
// intermediate memoization layer whose formData dependency would
|
||||
// never produce a cache hit (new object reference on every change).
|
||||
const { data: genaiInfo } = useSWR<GenAIModelsResponse>("genai/models", {
|
||||
revalidateOnFocus: false,
|
||||
});
|
||||
|
||||
const embeddingsSupported = useMemo(() => {
|
||||
if (!providerKey) return true;
|
||||
const info = genaiInfo?.[providerKey];
|
||||
return info ? info.supports_embeddings : true;
|
||||
}, [genaiInfo, providerKey]);
|
||||
|
||||
const availableRoles = useMemo(
|
||||
() =>
|
||||
embeddingsSupported
|
||||
? GENAI_ROLES
|
||||
: GENAI_ROLES.filter((role) => role !== "embeddings"),
|
||||
[embeddingsSupported],
|
||||
);
|
||||
|
||||
const occupiedRoles = useMemo(() => {
|
||||
const occupied = new Set<string>();
|
||||
const fd = formContext?.formData;
|
||||
@@ -64,6 +80,12 @@ export function GenAIRolesWidget(props: WidgetProps) {
|
||||
return occupied;
|
||||
}, [formContext?.formData, providerKey]);
|
||||
|
||||
useEffect(() => {
|
||||
if (!embeddingsSupported && selectedRoles.includes("embeddings")) {
|
||||
onChange(selectedRoles.filter((role) => role !== "embeddings"));
|
||||
}
|
||||
}, [embeddingsSupported, selectedRoles, onChange]);
|
||||
|
||||
const toggleRole = (role: string, enabled: boolean) => {
|
||||
if (enabled) {
|
||||
if (!selectedRoles.includes(role)) {
|
||||
@@ -78,7 +100,7 @@ export function GenAIRolesWidget(props: WidgetProps) {
|
||||
return (
|
||||
<div className="rounded-lg border border-secondary-highlight bg-background_alt p-2 pr-0 md:max-w-md">
|
||||
<div className="grid gap-2">
|
||||
{GENAI_ROLES.map((role) => {
|
||||
{availableRoles.map((role) => {
|
||||
const checked = selectedRoles.includes(role);
|
||||
const roleDisabled = !checked && occupiedRoles.has(role);
|
||||
const label = t(`configForm.genaiRoles.options.${role}`, {
|
||||
|
||||
@@ -43,6 +43,7 @@ export type GenAIProviderInfo = {
|
||||
models: string[];
|
||||
roles: string[];
|
||||
supports_toggleable_thinking: boolean;
|
||||
supports_embeddings: boolean;
|
||||
};
|
||||
|
||||
export type GenAIModelsResponse = Record<string, GenAIProviderInfo>;
|
||||
|
||||
@@ -370,6 +370,7 @@ export type CustomClassificationModelConfig = {
|
||||
};
|
||||
};
|
||||
motion: boolean;
|
||||
interval?: number;
|
||||
};
|
||||
};
|
||||
|
||||
|
||||
@@ -3,6 +3,7 @@ import ClassificationModelWizardDialog from "@/components/classification/Classif
|
||||
import ClassificationModelEditDialog from "@/components/classification/ClassificationModelEditDialog";
|
||||
import ActivityIndicator from "@/components/indicators/activity-indicator";
|
||||
import { ImageShadowOverlay } from "@/components/overlay/ImageShadowOverlay";
|
||||
import { Badge } from "@/components/ui/badge";
|
||||
import { Button, buttonVariants } from "@/components/ui/button";
|
||||
import { ToggleGroup, ToggleGroupItem } from "@/components/ui/toggle-group";
|
||||
import useOptimisticState from "@/hooks/use-optimistic-state";
|
||||
@@ -330,7 +331,10 @@ function ModelCard({ config, onClick, onUpdate, onDelete }: ModelCardProps) {
|
||||
{coverImage ? (
|
||||
<>
|
||||
<img
|
||||
className="size-full"
|
||||
className={cn(
|
||||
"size-full",
|
||||
!config.enabled && "opacity-50 grayscale",
|
||||
)}
|
||||
src={`${baseUrl}clips/${config.name}/dataset/${coverImage.name}/${coverImage.img}`}
|
||||
/>
|
||||
<ImageShadowOverlay lowerClassName="h-[30%] z-0" />
|
||||
@@ -338,6 +342,14 @@ function ModelCard({ config, onClick, onUpdate, onDelete }: ModelCardProps) {
|
||||
) : (
|
||||
<Skeleton className="flex size-full items-center justify-center" />
|
||||
)}
|
||||
{!config.enabled && (
|
||||
<Badge
|
||||
variant="secondary"
|
||||
className="absolute right-2 top-2 z-40 text-primary-variant"
|
||||
>
|
||||
{t("disabled")}
|
||||
</Badge>
|
||||
)}
|
||||
<div className="absolute bottom-2 left-3 text-lg text-white smart-capitalize">
|
||||
{config.name}
|
||||
</div>
|
||||
|
||||
@@ -348,6 +348,19 @@ function MotionPreviewClip({
|
||||
}
|
||||
}, [clipStart, clipEnd, playbackRate, preview]);
|
||||
|
||||
useEffect(() => {
|
||||
if (!videoRef.current || !preview || !videoPlaying) {
|
||||
return;
|
||||
}
|
||||
|
||||
if (isSafari || (isFirefox && isMobile)) {
|
||||
// These browsers step frames manually; rebuild the interval at the new rate
|
||||
resetPlayback();
|
||||
} else {
|
||||
videoRef.current.playbackRate = playbackRate;
|
||||
}
|
||||
}, [playbackRate, preview, videoPlaying, resetPlayback]);
|
||||
|
||||
const drawDimOverlay = useCallback(() => {
|
||||
if (!dimOverlayCanvasRef.current) {
|
||||
return;
|
||||
|
||||
Reference in New Issue
Block a user