mirror of
https://github.com/blakeblackshear/frigate.git
synced 2026-11-13 17:13:01 +03:00
Compare commits
53
Commits
52d469ed20
..
dev
| Author | SHA1 | Date | |
|---|---|---|---|
|
|
bb04e6d49d | ||
|
|
b2f5315e09 | ||
|
|
a655ddcb2e | ||
|
|
806ee66562 | ||
|
|
42f5a7560b | ||
|
|
21836c7444 | ||
|
|
e70eba2fb8 | ||
|
|
e0b5a6c965 | ||
|
|
6733a757e8 | ||
|
|
94e700fbac | ||
|
|
5f59f7c53c | ||
|
|
0e2da4e24a | ||
|
|
dade7a6b3e | ||
|
|
329430760e | ||
|
|
2fbb66e475 | ||
|
|
5019984a33 | ||
|
|
db58f9f5cc | ||
|
|
f83b8b57db | ||
|
|
146d61017e | ||
|
|
86c9b4bf74 | ||
|
|
ed1a515c1b | ||
|
|
c013f6dcb8 | ||
|
|
bb1736bb4d | ||
|
|
68988780cd | ||
|
|
c148063e76 | ||
|
|
37ca717f63 | ||
|
|
b6d16fb6f3 | ||
|
|
e45b7de79b | ||
|
|
a1f8f95497 | ||
|
|
cabbfd5d73 | ||
|
|
20a3bd7ac5 | ||
|
|
e287833e8d | ||
|
|
e150c5d1dd | ||
|
|
60c4e37303 | ||
|
|
3ac35fe777 | ||
|
|
8b0325aefb | ||
|
|
c57b48e977 | ||
|
|
74a2aac866 | ||
|
|
2af6dc34da | ||
|
|
9eb336a3d8 | ||
|
|
bb0e2c0d5e | ||
|
|
06d1365056 | ||
|
|
8a88cefc9a | ||
|
|
fba967c74b | ||
|
|
33c55649a4 | ||
|
|
c88434dc29 | ||
|
|
e7256d58a1 | ||
|
|
b2a7e992c9 | ||
|
|
11f4dd194c | ||
|
|
0e7da1bee4 | ||
|
|
14870f5237 | ||
|
|
ca851a9238 | ||
|
|
81cf7be89a |
@@ -4,6 +4,8 @@ inputs:
|
||||
GITHUB_TOKEN:
|
||||
required: true
|
||||
outputs:
|
||||
image-repo:
|
||||
value: ghcr.io/${{ steps.lowercaseRepo.outputs.lowercase }}
|
||||
image-name:
|
||||
value: ghcr.io/${{ steps.lowercaseRepo.outputs.lowercase }}:${{ steps.create-short-sha.outputs.SHORT_SHA }}
|
||||
cache-name:
|
||||
|
||||
@@ -136,9 +136,6 @@ updates:
|
||||
# Later versions need Node 22 and extract false keys
|
||||
- dependency-name: "i18next-cli"
|
||||
versions: [">1.5.11"]
|
||||
# 6.11.0 refills the saved model in the GenAI settings form
|
||||
- dependency-name: "@rjsf/*"
|
||||
versions: ["6.11.0"]
|
||||
|
||||
- package-ecosystem: "npm"
|
||||
directory: "/docs"
|
||||
|
||||
@@ -423,6 +423,11 @@ jobs:
|
||||
run: docker rm -f frigate || true
|
||||
arm64_build:
|
||||
runs-on: ubuntu-22.04-arm
|
||||
permissions:
|
||||
contents: read
|
||||
packages: write
|
||||
attestations: write
|
||||
id-token: write
|
||||
name: ARM Build
|
||||
steps:
|
||||
- name: Check out code
|
||||
@@ -435,6 +440,7 @@ jobs:
|
||||
with:
|
||||
GITHUB_TOKEN: ${{ secrets.GITHUB_TOKEN }}
|
||||
- name: Build and push arm64 standard build
|
||||
id: standard-arm64
|
||||
uses: docker/build-push-action@v7
|
||||
with:
|
||||
context: .
|
||||
@@ -445,6 +451,13 @@ jobs:
|
||||
tags: |
|
||||
${{ steps.setup.outputs.image-name }}-standard-arm64
|
||||
cache-from: type=registry,ref=${{ steps.setup.outputs.cache-name }}-arm64
|
||||
- name: Attest arm64 standard build
|
||||
uses: actions/attest@v4
|
||||
with:
|
||||
subject-name: ${{ steps.setup.outputs.image-repo }}
|
||||
subject-digest: ${{ steps.standard-arm64.outputs.digest }}
|
||||
push-to-registry: true
|
||||
create-storage-record: false
|
||||
- name: Build and push RPi build
|
||||
uses: docker/bake-action@v7
|
||||
with:
|
||||
@@ -458,6 +471,11 @@ jobs:
|
||||
*.cache-to=type=registry,ref=${{ steps.setup.outputs.cache-name }}-arm64,mode=max
|
||||
jetson_jp6_build:
|
||||
runs-on: ubuntu-22.04-arm
|
||||
permissions:
|
||||
contents: read
|
||||
packages: write
|
||||
attestations: write
|
||||
id-token: write
|
||||
name: Jetson Jetpack 6
|
||||
steps:
|
||||
- name: Check out code
|
||||
@@ -470,6 +488,7 @@ jobs:
|
||||
with:
|
||||
GITHUB_TOKEN: ${{ secrets.GITHUB_TOKEN }}
|
||||
- name: Build and push TensorRT (Jetson, Jetpack 6)
|
||||
id: tensorrt-jp6
|
||||
env:
|
||||
ARCH: arm64
|
||||
BASE_IMAGE: nvcr.io/nvidia/tensorrt:23.12-py3-igpu
|
||||
@@ -485,8 +504,20 @@ jobs:
|
||||
tensorrt.tags=${{ steps.setup.outputs.image-name }}-tensorrt-jp6
|
||||
*.cache-from=type=registry,ref=${{ steps.setup.outputs.cache-name }}-jp6
|
||||
*.cache-to=type=registry,ref=${{ steps.setup.outputs.cache-name }}-jp6,mode=max
|
||||
- name: Attest TensorRT (Jetson, Jetpack 6)
|
||||
uses: actions/attest@v4
|
||||
with:
|
||||
subject-name: ${{ steps.setup.outputs.image-repo }}
|
||||
subject-digest: ${{ fromJSON(steps.tensorrt-jp6.outputs.metadata)['tensorrt']['containerimage.digest'] }}
|
||||
push-to-registry: true
|
||||
create-storage-record: false
|
||||
amd64_extra_builds:
|
||||
runs-on: ubuntu-22.04
|
||||
permissions:
|
||||
contents: read
|
||||
packages: write
|
||||
attestations: write
|
||||
id-token: write
|
||||
name: AMD64 Extra Build
|
||||
needs:
|
||||
- amd64_build
|
||||
@@ -501,6 +532,7 @@ jobs:
|
||||
with:
|
||||
GITHUB_TOKEN: ${{ secrets.GITHUB_TOKEN }}
|
||||
- name: Build and push TensorRT (x86 GPU)
|
||||
id: tensorrt
|
||||
env:
|
||||
COMPUTE_LEVEL: "50 60 70 80 90"
|
||||
uses: docker/bake-action@v7
|
||||
@@ -513,7 +545,15 @@ jobs:
|
||||
tensorrt.tags=${{ steps.setup.outputs.image-name }}-tensorrt
|
||||
*.cache-from=type=registry,ref=${{ steps.setup.outputs.cache-name }}-tensorrt
|
||||
*.cache-to=type=registry,ref=${{ steps.setup.outputs.cache-name }}-tensorrt,mode=max
|
||||
- name: Attest TensorRT (x86 GPU)
|
||||
uses: actions/attest@v4
|
||||
with:
|
||||
subject-name: ${{ steps.setup.outputs.image-repo }}
|
||||
subject-digest: ${{ fromJSON(steps.tensorrt.outputs.metadata)['tensorrt']['containerimage.digest'] }}
|
||||
push-to-registry: true
|
||||
create-storage-record: false
|
||||
- name: AMD/ROCm general build
|
||||
id: rocm
|
||||
env:
|
||||
HSA_OVERRIDE: 0
|
||||
uses: docker/bake-action@v7
|
||||
@@ -526,8 +566,20 @@ jobs:
|
||||
rocm.tags=${{ steps.setup.outputs.image-name }}-rocm
|
||||
*.cache-to=type=registry,ref=${{ steps.setup.outputs.cache-name }}-rocm,mode=max
|
||||
*.cache-from=type=registry,ref=${{ steps.setup.outputs.cache-name }}-rocm
|
||||
- name: Attest AMD/ROCm general build
|
||||
uses: actions/attest@v4
|
||||
with:
|
||||
subject-name: ${{ steps.setup.outputs.image-repo }}
|
||||
subject-digest: ${{ fromJSON(steps.rocm.outputs.metadata)['rocm']['containerimage.digest'] }}
|
||||
push-to-registry: true
|
||||
create-storage-record: false
|
||||
arm64_extra_builds:
|
||||
runs-on: ubuntu-22.04-arm
|
||||
permissions:
|
||||
contents: read
|
||||
packages: write
|
||||
attestations: write
|
||||
id-token: write
|
||||
name: ARM Extra Build
|
||||
needs:
|
||||
- arm64_build
|
||||
@@ -542,6 +594,7 @@ jobs:
|
||||
with:
|
||||
GITHUB_TOKEN: ${{ secrets.GITHUB_TOKEN }}
|
||||
- name: Build and push Rockchip build
|
||||
id: rk
|
||||
uses: docker/bake-action@v7
|
||||
with:
|
||||
source: .
|
||||
@@ -551,8 +604,20 @@ jobs:
|
||||
set: |
|
||||
rk.tags=${{ steps.setup.outputs.image-name }}-rk
|
||||
*.cache-from=type=gha
|
||||
- name: Attest Rockchip build
|
||||
uses: actions/attest@v4
|
||||
with:
|
||||
subject-name: ${{ steps.setup.outputs.image-repo }}
|
||||
subject-digest: ${{ fromJSON(steps.rk.outputs.metadata)['rk']['containerimage.digest'] }}
|
||||
push-to-registry: true
|
||||
create-storage-record: false
|
||||
synaptics_build:
|
||||
runs-on: ubuntu-22.04-arm
|
||||
permissions:
|
||||
contents: read
|
||||
packages: write
|
||||
attestations: write
|
||||
id-token: write
|
||||
name: Synaptics Build
|
||||
needs:
|
||||
- arm64_build
|
||||
@@ -567,6 +632,7 @@ jobs:
|
||||
with:
|
||||
GITHUB_TOKEN: ${{ secrets.GITHUB_TOKEN }}
|
||||
- name: Build and push Synaptics build
|
||||
id: synaptics
|
||||
uses: docker/bake-action@v7
|
||||
with:
|
||||
source: .
|
||||
@@ -576,10 +642,22 @@ jobs:
|
||||
set: |
|
||||
synaptics.tags=${{ steps.setup.outputs.image-name }}-synaptics
|
||||
*.cache-from=type=gha
|
||||
- name: Attest Synaptics build
|
||||
uses: actions/attest@v4
|
||||
with:
|
||||
subject-name: ${{ steps.setup.outputs.image-repo }}
|
||||
subject-digest: ${{ fromJSON(steps.synaptics.outputs.metadata)['synaptics']['containerimage.digest'] }}
|
||||
push-to-registry: true
|
||||
create-storage-record: false
|
||||
# The majority of users running arm64 are rpi users, so the rpi
|
||||
# build should be the primary arm64 image
|
||||
assemble_default_build:
|
||||
runs-on: ubuntu-22.04
|
||||
permissions:
|
||||
contents: read
|
||||
packages: write
|
||||
attestations: write
|
||||
id-token: write
|
||||
name: Assemble and push default build
|
||||
needs:
|
||||
- amd64_build
|
||||
@@ -598,8 +676,16 @@ jobs:
|
||||
- name: Create short sha
|
||||
run: echo "SHORT_SHA=${GITHUB_SHA::7}" >> $GITHUB_ENV
|
||||
- uses: int128/docker-manifest-create-action@v2
|
||||
id: manifest
|
||||
with:
|
||||
tags: ghcr.io/${{ steps.lowercaseRepo.outputs.lowercase }}:${{ env.SHORT_SHA }}
|
||||
sources: |
|
||||
ghcr.io/${{ steps.lowercaseRepo.outputs.lowercase }}:${{ env.SHORT_SHA }}-amd64
|
||||
ghcr.io/${{ steps.lowercaseRepo.outputs.lowercase }}:${{ env.SHORT_SHA }}-rpi
|
||||
- name: Attest default build
|
||||
uses: actions/attest@v4
|
||||
with:
|
||||
subject-name: ghcr.io/${{ steps.lowercaseRepo.outputs.lowercase }}
|
||||
subject-digest: ${{ steps.manifest.outputs.digest }}
|
||||
push-to-registry: true
|
||||
create-storage-record: false
|
||||
|
||||
@@ -46,3 +46,4 @@ make -C /tmp/media-driver/build -j"$(nproc)"
|
||||
|
||||
# Install driver to rootfs for COPY --from
|
||||
make -C /tmp/media-driver/build install DESTDIR=/rootfs
|
||||
strip --strip-unneeded /rootfs/usr/lib/x86_64-linux-gnu/dri/iHD_drv_video.so
|
||||
|
||||
@@ -1,4 +1,4 @@
|
||||
ruff == 0.17.3
|
||||
ruff == 0.17.4
|
||||
|
||||
# types
|
||||
types-peewee == 4.0.*
|
||||
|
||||
@@ -47,7 +47,7 @@ onnxruntime == 1.31.*
|
||||
# Embeddings
|
||||
transformers == 5.22.*
|
||||
# Generative AI
|
||||
google-genai == 2.34.*
|
||||
google-genai == 3.0.*
|
||||
ollama == 0.6.*
|
||||
openai == 3.46.*
|
||||
# push notifications
|
||||
|
||||
@@ -80,6 +80,11 @@ cp -r /usr/local/nginx/conf/. /tmp/nginx/conf/
|
||||
|
||||
set_worker_processes
|
||||
|
||||
# A non-root master ignores `user` and warns on every start
|
||||
if [[ "$runs_as_root" -eq 0 ]]; then
|
||||
sed -i '/^user root;$/d' /tmp/nginx/conf/nginx.conf
|
||||
fi
|
||||
|
||||
# TLS certs: user-mounted certs at /etc/letsencrypt/live/frigate (documented
|
||||
# contract) always win; otherwise fall back to a self-signed cert persisted in
|
||||
# /config/tls, which stays writable under a read-only root filesystem.
|
||||
|
||||
@@ -105,6 +105,8 @@ if [[ -n "$sentinel" ]]; then
|
||||
mismatch_expr+=(-a ! -path "$sentinel")
|
||||
fi
|
||||
|
||||
exec 3>&1
|
||||
|
||||
for path in "$@"; do
|
||||
# An absent root is an incomplete sweep, not a finished one: /media/frigate
|
||||
# is not in the image, so a boot before the volume is mounted would
|
||||
@@ -121,7 +123,18 @@ for path in "$@"; do
|
||||
# stale mount. Tolerate it rather than aborting under errexit, but never
|
||||
# read a failed scan as "nothing to do": that would record the sweep as
|
||||
# complete without having looked.
|
||||
if ! count=$(find "$path" -name lost+found -prune -o "${mismatch_expr[@]}" -printf '.' 2>/dev/null | wc -c); then
|
||||
# The walk prints nothing until it ends, so report every 30s through fd 3
|
||||
# (stdout) while the count itself comes back through the substitution.
|
||||
if ! count=$(find "$path" -name lost+found -prune -o "(" "${mismatch_expr[@]}" ")" -printf 'm\n' -o -printf '.\n' 2>/dev/null \
|
||||
| awk -v path="$path" '
|
||||
BEGIN { next_report = systime() + 30 }
|
||||
$0 == "m" { mismatched++ }
|
||||
systime() >= next_report {
|
||||
printf "[INFO] fix-ownership: still scanning %s, %d entries checked, %d need changes\n", path, NR, mismatched > "/dev/fd/3"
|
||||
fflush("/dev/fd/3")
|
||||
next_report = systime() + 30
|
||||
}
|
||||
END { print mismatched + 0 }'); then
|
||||
swept_clean=0
|
||||
echo "[WARN] fix-ownership: could not scan ${path}; will retry on next boot"
|
||||
continue
|
||||
|
||||
@@ -2,7 +2,7 @@
|
||||
# file, all other path directives follow --prefix and must stay absolute.
|
||||
|
||||
daemon off;
|
||||
# ignored by a non-root master; keeps workers root under FRIGATE_RUN_AS_ROOT
|
||||
# keeps workers root under FRIGATE_RUN_AS_ROOT; the run script strips it otherwise
|
||||
user root;
|
||||
worker_processes auto;
|
||||
|
||||
|
||||
@@ -71,13 +71,14 @@ Both the script and the boot sweep report progress, so you can tell a slow sweep
|
||||
|
||||
```
|
||||
[INFO] fix-ownership: scanning /media/frigate for ownership mismatches; this may take a while on large filesystems
|
||||
[INFO] fix-ownership: still scanning /media/frigate, 2481000 entries checked, 2390112 need changes
|
||||
[WARN] fix-ownership: adjusting ownership of 4823941 entries under /media/frigate
|
||||
[INFO] fix-ownership: /media/frigate 5% (241197/4823941 entries)
|
||||
[INFO] fix-ownership: /media/frigate 10% (482394/4823941 entries)
|
||||
[INFO] fix-ownership: finished /media/frigate in 12m 4s
|
||||
```
|
||||
|
||||
The scan has no percentage because the total isn't known until it finishes. Watch the boot sweep with `docker logs -f frigate`.
|
||||
The scan has no percentage because the total isn't known until it finishes, so it reports a running count every 30 seconds instead. Watch the boot sweep with `docker logs -f frigate`.
|
||||
|
||||
Once the volumes are aligned, start Frigate normally. A file at `/config/.permissions_version` records what was done, so later boots skip the sweep unless you change `PUID`/`PGID`.
|
||||
|
||||
|
||||
@@ -413,17 +413,18 @@ Avoid removing audio from only one of the two streams (for example with `-an`).
|
||||
|
||||
As a general rule, features that read recordings prefer the main stream and fall back to the sub stream for time ranges where the main recordings have expired. Analytics features use only the main stream.
|
||||
|
||||
| Feature | Stream used |
|
||||
| ---------------------------------------------------------------------------------------- | ------------------------------------------------------------------------------------------------------------- |
|
||||
| Recording playback (History and Review) | Both (main preferred with sub fallback by default), or exactly one stream when a quality is selected manually |
|
||||
| Tracking details and Explore clip playback | Main, falling back to sub where the main recordings have expired |
|
||||
| Feature | Stream used |
|
||||
| ---------------------------------------------------------------------------------------- | ------------------------------------------------------------------------------------------------------------------------- |
|
||||
| Recording playback (History and Review) | Both (main preferred with sub fallback by default), or exactly one stream when a quality is selected manually |
|
||||
| Tracking details and Explore clip playback | Main, falling back to sub where the main recordings have expired |
|
||||
| Exports | Both (main preferred with sub fallback by default), or exactly one stream when a quality is selected in the export dialog |
|
||||
| Clip downloads | Main; sub is used when no main recordings remain in the range (streams are never mixed in one file) |
|
||||
| Frames grabbed from a recording in History (download snapshot, submit frame to Frigate+) | Main preferred, sub fallback |
|
||||
| Audio extraction (e.g., transcription) | Main preferred, sub fallback |
|
||||
| Motion search | Main only |
|
||||
| Review timeline motion data | Main only |
|
||||
| Storage usage statistics | Both streams counted, and listed separately per camera |
|
||||
| Clip downloads | Main; sub is used when no main recordings remain in the range (streams are never mixed in one file) |
|
||||
| Debug replay | Main; sub is used when no main recordings remain in the range (streams are never mixed in one file) |
|
||||
| Frames grabbed from a recording in History (download snapshot, submit frame to Frigate+) | Main preferred, sub fallback |
|
||||
| Audio extraction (e.g., transcription) | Main preferred, sub fallback |
|
||||
| Motion search | Main only |
|
||||
| Review timeline motion data | Main only |
|
||||
| Storage usage statistics | Both streams counted, and listed separately per camera |
|
||||
|
||||
This table covers only features that read recordings from disk. Tracked object snapshots and thumbnails (the images shown in Explore and sent with notifications, and the images submitted to Frigate+ from a tracked object) are captured live from the `detect` stream as the object is tracked, never from recordings, so sub stream recording does not affect them.
|
||||
|
||||
|
||||
@@ -4,7 +4,7 @@ import type { Config, PluginConfig } from "@docusaurus/types";
|
||||
import type * as OpenApiPlugin from "docusaurus-plugin-openapi-docs";
|
||||
|
||||
// Bump when a new stable release ships
|
||||
const STABLE_VERSION = "0.18";
|
||||
const STABLE_VERSION = "0.19";
|
||||
|
||||
const config: Config = {
|
||||
title: "Frigate",
|
||||
|
||||
+23
-1
@@ -683,6 +683,15 @@
|
||||
"title": "FeaturesSection",
|
||||
"type": "object"
|
||||
},
|
||||
"FfmpegSource": {
|
||||
"enum": [
|
||||
"default",
|
||||
"bundled",
|
||||
"custom"
|
||||
],
|
||||
"title": "FfmpegSource",
|
||||
"type": "string"
|
||||
},
|
||||
"FpsBucket": {
|
||||
"enum": [
|
||||
"le_5",
|
||||
@@ -1017,6 +1026,17 @@
|
||||
"description": "CPU architecture",
|
||||
"x-public": true
|
||||
},
|
||||
"custom_go2rtc": {
|
||||
"description": "Whether an executable /config/go2rtc build exists",
|
||||
"title": "Custom Go2Rtc",
|
||||
"type": "boolean",
|
||||
"x-public": true
|
||||
},
|
||||
"ffmpeg": {
|
||||
"$ref": "#/$defs/FfmpegSource",
|
||||
"description": "Configured global ffmpeg.path: the default build, another bundled version, or a custom path",
|
||||
"x-public": true
|
||||
},
|
||||
"image_variant": {
|
||||
"$ref": "#/$defs/ImageVariant",
|
||||
"description": "Published image the install runs",
|
||||
@@ -1054,7 +1074,9 @@
|
||||
"install_type",
|
||||
"arch",
|
||||
"kernel",
|
||||
"run_as_root"
|
||||
"run_as_root",
|
||||
"custom_go2rtc",
|
||||
"ffmpeg"
|
||||
],
|
||||
"title": "InstallSection",
|
||||
"type": "object"
|
||||
|
||||
Vendored
+19
@@ -3148,6 +3148,25 @@ paths:
|
||||
- frigateUserAuth: []
|
||||
x-required-role: any
|
||||
description: '**Access:** Any authenticated user.'
|
||||
/plus/validate:
|
||||
post:
|
||||
tags:
|
||||
- App
|
||||
summary: Validate Plus Key
|
||||
description: |-
|
||||
**Access:** Admin role required.
|
||||
|
||||
Validate the configured Frigate+ API key with the Frigate+ server.
|
||||
operationId: validate_plus_key_plus_validate_post
|
||||
responses:
|
||||
'200':
|
||||
description: Successful Response
|
||||
content:
|
||||
application/json:
|
||||
schema: {}
|
||||
security:
|
||||
- frigateAdminAuth: []
|
||||
x-required-role: admin
|
||||
/recognized_license_plates:
|
||||
get:
|
||||
tags:
|
||||
|
||||
@@ -6,7 +6,15 @@ import re
|
||||
|
||||
from frigate.analytics.collectors.common import closed
|
||||
from frigate.analytics.context import ReportContext
|
||||
from frigate.analytics.schema import Arch, ImageVariant, InstallSection, InstallType
|
||||
from frigate.analytics.schema import (
|
||||
Arch,
|
||||
FfmpegSource,
|
||||
ImageVariant,
|
||||
InstallSection,
|
||||
InstallType,
|
||||
)
|
||||
from frigate.const import CONFIG_DIR, DEFAULT_FFMPEG_VERSION, INCLUDED_FFMPEG_VERSIONS
|
||||
from frigate.util.config import is_valid_ffmpeg_path
|
||||
from frigate.version import VERSION
|
||||
|
||||
KERNEL_PATTERN = re.compile(r"^(\d{1,3})\.(\d{1,3})")
|
||||
@@ -54,6 +62,17 @@ def kernel(release: str) -> str:
|
||||
return f"{match.group(1)}.{match.group(2)}" if match else "unknown"
|
||||
|
||||
|
||||
def ffmpeg_source(path: str) -> FfmpegSource:
|
||||
"""Classify the configured ffmpeg.path, applying resolve_ffmpeg_path's alias fallbacks."""
|
||||
if path.startswith("/") and is_valid_ffmpeg_path(path):
|
||||
return FfmpegSource.custom
|
||||
|
||||
if path != DEFAULT_FFMPEG_VERSION and path in INCLUDED_FFMPEG_VERSIONS:
|
||||
return FfmpegSource.bundled
|
||||
|
||||
return FfmpegSource.default
|
||||
|
||||
|
||||
def collect(ctx: ReportContext) -> InstallSection:
|
||||
"""Describe the Frigate version, image, install method, and platform."""
|
||||
return InstallSection(
|
||||
@@ -63,4 +82,6 @@ def collect(ctx: ReportContext) -> InstallSection:
|
||||
arch=arch(platform.machine()),
|
||||
kernel=kernel(platform.release()),
|
||||
run_as_root=os.geteuid() == 0,
|
||||
custom_go2rtc=os.access(os.path.join(CONFIG_DIR, "go2rtc"), os.X_OK),
|
||||
ffmpeg=ffmpeg_source(ctx.config.ffmpeg.path),
|
||||
)
|
||||
|
||||
@@ -47,6 +47,12 @@ class Arch(StrEnum):
|
||||
other = "other"
|
||||
|
||||
|
||||
class FfmpegSource(StrEnum):
|
||||
default = "default"
|
||||
bundled = "bundled"
|
||||
custom = "custom"
|
||||
|
||||
|
||||
class GpuVendor(StrEnum):
|
||||
intel = "intel"
|
||||
amd = "amd"
|
||||
@@ -201,6 +207,10 @@ class InstallSection(AnalyticsModel):
|
||||
"Host kernel as major.minor", pattern=r"^(\d{1,3}\.\d{1,3}|unknown)$"
|
||||
)
|
||||
run_as_root: bool = metric("Whether the main process runs as root")
|
||||
custom_go2rtc: bool = metric("Whether an executable /config/go2rtc build exists")
|
||||
ffmpeg: FfmpegSource = metric(
|
||||
"Configured global ffmpeg.path: the default build, another bundled version, or a custom path"
|
||||
)
|
||||
|
||||
|
||||
class GpuInfo(AnalyticsModel):
|
||||
|
||||
+11
-1
@@ -54,7 +54,7 @@ from frigate.jobs.media_sync import (
|
||||
start_media_sync_job,
|
||||
)
|
||||
from frigate.models import Event, Timeline
|
||||
from frigate.plus import load_plus_model_info
|
||||
from frigate.plus import PlusKeyStatus, load_plus_model_info
|
||||
from frigate.stats.prometheus import get_metrics, update_metrics
|
||||
from frigate.types import JobStatusTypesEnum
|
||||
from frigate.util.builtin import (
|
||||
@@ -1675,6 +1675,16 @@ def plusModels(request: Request, filterByCurrentModelDetector: bool = False):
|
||||
return JSONResponse(content=validModels)
|
||||
|
||||
|
||||
@router.post("/plus/validate", dependencies=[Depends(require_role(["admin"]))])
|
||||
async def validate_plus_key(request: Request):
|
||||
"""Validate the configured Frigate+ API key with the Frigate+ server."""
|
||||
status = await request.app.frigate_config.plus_api.validate_key()
|
||||
|
||||
return JSONResponse(
|
||||
content={"success": status == PlusKeyStatus.valid, "status": status.value}
|
||||
)
|
||||
|
||||
|
||||
@router.get(
|
||||
"/recognized_license_plates", dependencies=[Depends(allow_any_authenticated())]
|
||||
)
|
||||
|
||||
+14
-22
@@ -199,18 +199,14 @@ def events(
|
||||
sub_label_clauses.append(Event.sub_label.is_null())
|
||||
|
||||
for label in filtered_sub_labels:
|
||||
lowered = label.lower()
|
||||
sub_label_clauses.append(
|
||||
fn.LOWER(Event.sub_label.cast("text")) == lowered
|
||||
) # include exact matches (case-insensitive)
|
||||
# SQLite LOWER() only folds ASCII, so lower both sides in SQL to
|
||||
# keep non-ASCII names matching exactly
|
||||
stored = fn.LOWER(Event.sub_label.cast("text"))
|
||||
sub_label_clauses.append(stored == fn.LOWER(label))
|
||||
|
||||
# include this label when part of a list (LIKE is case-insensitive in sqlite for ASCII)
|
||||
sub_label_clauses.append(
|
||||
fn.LOWER(Event.sub_label.cast("text")) % f"*{lowered},*"
|
||||
)
|
||||
sub_label_clauses.append(
|
||||
fn.LOWER(Event.sub_label.cast("text")) % f"*, {lowered}*"
|
||||
)
|
||||
# include this label when part of a list
|
||||
sub_label_clauses.append(stored % fn.LOWER(f"*{label},*"))
|
||||
sub_label_clauses.append(stored % fn.LOWER(f"*, {label}*"))
|
||||
|
||||
sub_label_clause = reduce(operator.or_, sub_label_clauses)
|
||||
clauses.append(sub_label_clause)
|
||||
@@ -633,18 +629,14 @@ def events_search(
|
||||
sub_label_clauses.append(Event.sub_label.is_null())
|
||||
|
||||
for label in filtered_sub_labels:
|
||||
lowered = label.lower()
|
||||
sub_label_clauses.append(
|
||||
fn.LOWER(Event.sub_label.cast("text")) == lowered
|
||||
) # include exact matches (case-insensitive)
|
||||
# SQLite LOWER() only folds ASCII, so lower both sides in SQL to
|
||||
# keep non-ASCII names matching exactly
|
||||
stored = fn.LOWER(Event.sub_label.cast("text"))
|
||||
sub_label_clauses.append(stored == fn.LOWER(label))
|
||||
|
||||
# include this label when part of a list (LIKE is case-insensitive in sqlite for ASCII)
|
||||
sub_label_clauses.append(
|
||||
fn.LOWER(Event.sub_label.cast("text")) % f"*{lowered},*"
|
||||
)
|
||||
sub_label_clauses.append(
|
||||
fn.LOWER(Event.sub_label.cast("text")) % f"*, {lowered}*"
|
||||
)
|
||||
# include this label when part of a list
|
||||
sub_label_clauses.append(stored % fn.LOWER(f"*{label},*"))
|
||||
sub_label_clauses.append(stored % fn.LOWER(f"*, {label}*"))
|
||||
|
||||
event_filters.append(reduce(operator.or_, sub_label_clauses))
|
||||
|
||||
|
||||
@@ -84,3 +84,13 @@ class MqttConfig(FrigateBaseModel):
|
||||
if (self.user is None) != (self.password is None):
|
||||
raise ValueError("Password must be provided with username.")
|
||||
return self
|
||||
|
||||
@model_validator(mode="after")
|
||||
def tls_insecure_requires_tls(self) -> Self:
|
||||
"""Reject TLS verification settings without TLS when MQTT is enabled."""
|
||||
if self.enabled and self.tls_insecure is not None and self.tls_ca_certs is None:
|
||||
raise ValueError(
|
||||
"tls_insecure requires tls_ca_certs. Remove tls_insecure for plaintext "
|
||||
"MQTT, or set tls_ca_certs to a CA certificate path to enable TLS."
|
||||
)
|
||||
return self
|
||||
|
||||
@@ -68,6 +68,9 @@ class ReviewDescriptionProcessor(PostProcessorApi):
|
||||
self.review_desc_dps = EventsPerSecond()
|
||||
self.review_desc_dps.start()
|
||||
|
||||
# one review's ffmpeg extraction at a time, as on the maintainer loop
|
||||
self._frame_extraction_lock = threading.Lock()
|
||||
|
||||
def calculate_frame_count(
|
||||
self,
|
||||
camera: str,
|
||||
@@ -179,53 +182,72 @@ class ReviewDescriptionProcessor(PostProcessorApi):
|
||||
):
|
||||
return
|
||||
|
||||
image_source = camera_config.review.genai.image_source
|
||||
frame_mode = camera_config.review.genai.frame_mode
|
||||
threading.Thread(
|
||||
target=self._describe_finished_review,
|
||||
name=f"review_description_{id}",
|
||||
daemon=True,
|
||||
args=(
|
||||
camera_config,
|
||||
final_data,
|
||||
camera_config.review.genai.image_source,
|
||||
),
|
||||
).start()
|
||||
|
||||
if image_source == ImageSourceEnum.recordings:
|
||||
buffer_extension = get_recording_buffer_extension(
|
||||
final_data["end_time"] - final_data["start_time"]
|
||||
)
|
||||
final_data["start_time"] -= buffer_extension
|
||||
final_data["end_time"] += buffer_extension
|
||||
def _describe_finished_review(
|
||||
self,
|
||||
camera_config: CameraConfig,
|
||||
final_data: dict[str, Any],
|
||||
image_source: ImageSourceEnum,
|
||||
preview_fallback: bool = True,
|
||||
named_objects: list[tuple[str, str]] | None = None,
|
||||
) -> None:
|
||||
"""Gather frames for a finished review item and start its description.
|
||||
|
||||
Runs off the maintainer loop since recording frames shell out to ffmpeg
|
||||
once per frame.
|
||||
"""
|
||||
camera = final_data["camera"]
|
||||
id = final_data["id"]
|
||||
genai_config = camera_config.review.genai
|
||||
frames: list[tuple[bytes, float]] = []
|
||||
|
||||
if image_source == ImageSourceEnum.recordings:
|
||||
buffer_extension = get_recording_buffer_extension(
|
||||
final_data["end_time"] - final_data["start_time"]
|
||||
)
|
||||
with self._frame_extraction_lock:
|
||||
frames = self.get_recording_frames(
|
||||
camera,
|
||||
final_data["start_time"],
|
||||
final_data["end_time"],
|
||||
final_data["start_time"] - buffer_extension,
|
||||
final_data["end_time"] + buffer_extension,
|
||||
height=480, # Use 480p for good balance between quality and token usage
|
||||
frame_mode=frame_mode,
|
||||
frame_mode=genai_config.frame_mode,
|
||||
)
|
||||
|
||||
if not frames:
|
||||
# Fallback to preview frames if no recordings available
|
||||
logger.warning(
|
||||
f"No recording frames found for {camera}, falling back to preview frames"
|
||||
)
|
||||
frames = self.get_preview_frames_as_bytes(
|
||||
camera,
|
||||
final_data["start_time"],
|
||||
final_data["end_time"],
|
||||
final_data["thumb_path"],
|
||||
id,
|
||||
camera_config.review.genai.debug_save_thumbnails,
|
||||
frame_mode,
|
||||
)
|
||||
elif camera_config.review.genai.debug_save_thumbnails:
|
||||
if frames:
|
||||
if genai_config.debug_save_thumbnails:
|
||||
self.save_debug_recording_frames(id, frames)
|
||||
elif not preview_fallback:
|
||||
logger.error("No recording frames are available for review item %s", id)
|
||||
return
|
||||
else:
|
||||
# Use preview frames
|
||||
frames = self.get_preview_frames_as_bytes(
|
||||
logger.warning(
|
||||
"No recording frames found for %s, falling back to preview frames",
|
||||
camera,
|
||||
final_data["start_time"],
|
||||
final_data["end_time"],
|
||||
final_data["thumb_path"],
|
||||
id,
|
||||
camera_config.review.genai.debug_save_thumbnails,
|
||||
frame_mode,
|
||||
)
|
||||
|
||||
self.start_analysis(camera_config, final_data, frames)
|
||||
if not frames:
|
||||
frames = self.get_preview_frames_as_bytes(
|
||||
camera,
|
||||
final_data["start_time"],
|
||||
final_data["end_time"],
|
||||
final_data["thumb_path"],
|
||||
id,
|
||||
genai_config.debug_save_thumbnails,
|
||||
genai_config.frame_mode,
|
||||
)
|
||||
|
||||
self.start_analysis(camera_config, final_data, frames, named_objects)
|
||||
|
||||
def handle_request(self, topic: str, request_data: dict[str, Any]) -> str | None:
|
||||
if topic == EmbeddingsRequestEnum.regenerate_review_description.value:
|
||||
@@ -419,31 +441,12 @@ class ReviewDescriptionProcessor(PostProcessorApi):
|
||||
logger.error("Review item %s has not ended yet", review_id)
|
||||
return
|
||||
|
||||
buffer_extension = get_recording_buffer_extension(
|
||||
final_data["end_time"] - final_data["start_time"]
|
||||
)
|
||||
frames = self.get_recording_frames(
|
||||
str(review.camera),
|
||||
final_data["start_time"] - buffer_extension,
|
||||
final_data["end_time"] + buffer_extension,
|
||||
height=480,
|
||||
frame_mode=camera_config.review.genai.frame_mode,
|
||||
)
|
||||
|
||||
if not frames:
|
||||
logger.error(
|
||||
"No recording frames are available for review item %s", review_id
|
||||
)
|
||||
return
|
||||
|
||||
if camera_config.review.genai.debug_save_thumbnails:
|
||||
self.save_debug_recording_frames(review_id, frames)
|
||||
|
||||
self.start_analysis(
|
||||
self._describe_finished_review(
|
||||
camera_config,
|
||||
final_data,
|
||||
frames,
|
||||
get_named_objects(
|
||||
ImageSourceEnum.recordings,
|
||||
preview_fallback=False,
|
||||
named_objects=get_named_objects(
|
||||
final_data["data"].get("detections") or [],
|
||||
self.config.all_attributes,
|
||||
),
|
||||
|
||||
@@ -16,18 +16,21 @@ from abc import ABC, abstractmethod
|
||||
from dataclasses import dataclass
|
||||
from typing import TYPE_CHECKING, Any, Optional, cast
|
||||
|
||||
from peewee import ModelSelect
|
||||
|
||||
from frigate.config import FrigateConfig
|
||||
from frigate.config.camera.updater import CameraConfigUpdatePublisher
|
||||
from frigate.const import REPLAY_CAMERA_PREFIX, REPLAY_DIR
|
||||
from frigate.const import (
|
||||
REPLAY_CAMERA_PREFIX,
|
||||
REPLAY_DIR,
|
||||
STREAM_TYPE_MAIN,
|
||||
STREAM_TYPE_SUB,
|
||||
)
|
||||
from frigate.jobs.export import JobStatePublisher
|
||||
from frigate.jobs.job import Job
|
||||
from frigate.jobs.manager import job_is_running, set_current_job
|
||||
from frigate.models import Export, Recordings
|
||||
from frigate.models import Export
|
||||
from frigate.types import JobStatusTypesEnum
|
||||
from frigate.util.ffmpeg import run_ffmpeg_with_progress
|
||||
from frigate.util.recording_coverage import recording_overlap
|
||||
from frigate.util.recording_coverage import resolve_coverage
|
||||
|
||||
if TYPE_CHECKING:
|
||||
from frigate.debug_replay import DebugReplayManager
|
||||
@@ -94,24 +97,6 @@ class DebugReplayJob(Job):
|
||||
}
|
||||
|
||||
|
||||
def query_recordings(source_camera: str, start_ts: float, end_ts: float) -> ModelSelect:
|
||||
"""Return the Recordings query for the time range.
|
||||
|
||||
Module-level so tests can patch it without instantiating a runner.
|
||||
"""
|
||||
query = (
|
||||
Recordings.select(
|
||||
Recordings.path,
|
||||
Recordings.start_time,
|
||||
Recordings.end_time,
|
||||
)
|
||||
.where(recording_overlap(start_ts, end_ts))
|
||||
.where(Recordings.camera == source_camera)
|
||||
.order_by(Recordings.start_time.asc())
|
||||
)
|
||||
return cast(ModelSelect, query)
|
||||
|
||||
|
||||
class NoRecordingsError(ValueError):
|
||||
"""Raised when no recordings exist in the requested time range."""
|
||||
|
||||
@@ -156,7 +141,8 @@ class RecordingDebugReplaySource(DebugReplaySource):
|
||||
|
||||
Feeds ffmpeg the internal VOD endpoint so segments with mismatched
|
||||
SPS/PPS (e.g. across day/night transitions) stitch cleanly via HLS
|
||||
discontinuities.
|
||||
discontinuities. The playlist is pinned to one stream because the
|
||||
stream-copied clip cannot change codec or resolution mid-file.
|
||||
"""
|
||||
|
||||
def __init__(
|
||||
@@ -170,6 +156,7 @@ class RecordingDebugReplaySource(DebugReplaySource):
|
||||
self._start_ts = start_ts
|
||||
self._end_ts = end_ts
|
||||
self._internal_port = internal_port
|
||||
self._stream_type = STREAM_TYPE_MAIN
|
||||
|
||||
@property
|
||||
def source_camera(self) -> str:
|
||||
@@ -187,15 +174,28 @@ class RecordingDebugReplaySource(DebugReplaySource):
|
||||
if self._end_ts <= self._start_ts:
|
||||
raise ValueError("End time must be after start time")
|
||||
|
||||
if not query_recordings(self._camera, self._start_ts, self._end_ts).count():
|
||||
raise NoRecordingsError(
|
||||
f"No recordings found for camera '{self._camera}' in the specified time range"
|
||||
)
|
||||
# the VOD resolver decides what the pinned playlist serves, so it
|
||||
# also decides the stream; a row merely touching the range has no
|
||||
# interval and must not win
|
||||
intervals = resolve_coverage(self._camera, self._start_ts, self._end_ts)
|
||||
|
||||
if any(interval.main for interval in intervals):
|
||||
self._stream_type = STREAM_TYPE_MAIN
|
||||
return
|
||||
|
||||
if any(interval.sub for interval in intervals):
|
||||
self._stream_type = STREAM_TYPE_SUB
|
||||
return
|
||||
|
||||
raise NoRecordingsError(
|
||||
f"No recordings found for camera '{self._camera}' in the specified time range"
|
||||
)
|
||||
|
||||
def ffmpeg_input_args(self, working_dir: str) -> list[str]:
|
||||
playlist_url = (
|
||||
f"http://127.0.0.1:{self._internal_port}/vod/{self._camera}"
|
||||
f"/start/{self._start_ts}/end/{self._end_ts}/index.m3u8"
|
||||
f"/{self._stream_type}/start/{self._start_ts}/end/{self._end_ts}"
|
||||
"/index.m3u8"
|
||||
)
|
||||
return [
|
||||
"-protocol_whitelist",
|
||||
|
||||
+81
-1
@@ -3,9 +3,11 @@ import json
|
||||
import logging
|
||||
import os
|
||||
import re
|
||||
from enum import Enum
|
||||
from pathlib import Path
|
||||
from typing import Any
|
||||
from typing import Any, TypeGuard
|
||||
|
||||
import aiohttp
|
||||
import cv2
|
||||
import requests
|
||||
from numpy import ndarray
|
||||
@@ -17,6 +19,17 @@ from frigate.const import MODEL_CACHE_DIR, PLUS_API_HOST, PLUS_ENV_VAR
|
||||
|
||||
logger = logging.getLogger(__name__)
|
||||
|
||||
PLUS_VALIDATE_TIMEOUT = 10
|
||||
|
||||
|
||||
class PlusKeyStatus(str, Enum):
|
||||
"""Result of validating a Frigate+ API key with the server."""
|
||||
|
||||
valid = "valid"
|
||||
invalid = "invalid"
|
||||
unreachable = "unreachable"
|
||||
not_set = "not_set"
|
||||
|
||||
|
||||
def add_hailo_alias(model_info: dict[str, Any]) -> dict[str, Any]:
|
||||
"""Name the hailo detector by its current key as well as its old one.
|
||||
@@ -72,6 +85,19 @@ def get_jpg_bytes(image: ndarray, max_dim: int, quality: int) -> bytes:
|
||||
return jpg_bytes if isinstance(jpg_bytes, bytes) else b""
|
||||
|
||||
|
||||
def _is_usable_token(token_data: Any) -> TypeGuard[dict[str, Any]]:
|
||||
"""Whether a Frigate+ token response has a usable token and expiry."""
|
||||
if not isinstance(token_data, dict):
|
||||
return False
|
||||
|
||||
expires = token_data.get("expires")
|
||||
return (
|
||||
isinstance(token_data.get("accessToken"), str)
|
||||
and isinstance(expires, (int, float))
|
||||
and not isinstance(expires, bool)
|
||||
)
|
||||
|
||||
|
||||
class PlusApi:
|
||||
def __init__(self) -> None:
|
||||
self.host = PLUS_API_HOST
|
||||
@@ -153,6 +179,60 @@ class PlusApi:
|
||||
def is_active(self) -> bool:
|
||||
return self._is_active
|
||||
|
||||
async def validate_key(self) -> PlusKeyStatus:
|
||||
"""Check the configured API key against the Frigate+ server.
|
||||
|
||||
The key format is checked at startup, but only the server can tell
|
||||
whether the key is actually valid. This requests a fresh token so the
|
||||
result reflects the key's current state rather than a cached token.
|
||||
|
||||
Returns:
|
||||
The key's status as reported by the Frigate+ server
|
||||
"""
|
||||
if self.key is None:
|
||||
return PlusKeyStatus.not_set
|
||||
|
||||
key_id, _, secret = self.key.partition(":")
|
||||
|
||||
# Use a single request with a timeout instead of the retrying session,
|
||||
# since this is user initiated and should fail fast when unreachable.
|
||||
# trust_env honors proxy settings, matching the other Frigate+ requests.
|
||||
try:
|
||||
async with aiohttp.ClientSession(
|
||||
timeout=aiohttp.ClientTimeout(total=PLUS_VALIDATE_TIMEOUT),
|
||||
trust_env=True,
|
||||
) as session:
|
||||
async with session.get(
|
||||
f"{self.host}/v1/auth/token",
|
||||
auth=aiohttp.BasicAuth(key_id, secret),
|
||||
) as r:
|
||||
status = r.status
|
||||
token_data = await r.json(content_type=None) if r.ok else None
|
||||
except (aiohttp.ClientError, TimeoutError) as err:
|
||||
logger.warning("Unable to reach Frigate+ to validate API key: %s", err)
|
||||
return PlusKeyStatus.unreachable
|
||||
except ValueError:
|
||||
logger.warning("Invalid response validating Frigate+ API key")
|
||||
return PlusKeyStatus.unreachable
|
||||
|
||||
if status in (401, 403):
|
||||
return PlusKeyStatus.invalid
|
||||
|
||||
if status >= 400:
|
||||
logger.warning(
|
||||
"Unexpected response validating Frigate+ API key: %s", status
|
||||
)
|
||||
return PlusKeyStatus.unreachable
|
||||
|
||||
# Keep the cached token unless the reply is a usable one, so an
|
||||
# unexpected response can't break later Frigate+ requests
|
||||
if not _is_usable_token(token_data):
|
||||
logger.warning("Invalid response validating Frigate+ API key")
|
||||
return PlusKeyStatus.unreachable
|
||||
|
||||
self._token_data = token_data
|
||||
return PlusKeyStatus.valid
|
||||
|
||||
def upload_image(self, image: ndarray, camera: str) -> str:
|
||||
r = self._get("image/signed_urls")
|
||||
if not r.ok:
|
||||
|
||||
+40
-51
@@ -585,26 +585,36 @@ class PtzAutoTracker(threading.Thread):
|
||||
|
||||
def _predict_movement_time(self, camera, pan, tilt):
|
||||
combined_movement = abs(pan) + abs(tilt)
|
||||
input_data = np.array([self.intercept[camera], combined_movement])
|
||||
input_data = np.array([1, combined_movement])
|
||||
|
||||
return np.dot(self.move_coefficients[camera], input_data)
|
||||
|
||||
def _predict_area_after_time(self, camera, time):
|
||||
return np.dot(
|
||||
self.tracked_object_metrics[camera]["area_coefficients"],
|
||||
[self.tracked_object_history[camera][-1]["frame_time"] + time],
|
||||
)
|
||||
|
||||
def _predict_target_box(self, camera, predicted_time):
|
||||
target_box = self.tracked_object_metrics[camera]["target_box"]
|
||||
tom = self.tracked_object_metrics[camera]
|
||||
target_box = tom["target_box"]
|
||||
|
||||
if not predicted_time:
|
||||
return target_box
|
||||
|
||||
frame_shape = self.config.cameras[camera].frame_shape
|
||||
camera_config = self.config.cameras[camera]
|
||||
left, top, right, bottom = self.tracked_object_history[camera][-1]["box"]
|
||||
size = max(right - left, bottom - top)
|
||||
|
||||
return target_box + self._predict_area_after_time(camera, predicted_time) / (
|
||||
frame_shape[0] * frame_shape[1]
|
||||
if size <= 0:
|
||||
return target_box
|
||||
|
||||
# the corner velocities diverge as the object approaches the camera
|
||||
frames = camera_config.detect.fps * predicted_time
|
||||
velocity = tom["velocity"]
|
||||
predicted_size = max(
|
||||
right - left + frames * (velocity[2] - velocity[0]),
|
||||
bottom - top + frames * (velocity[3] - velocity[1]),
|
||||
1,
|
||||
)
|
||||
|
||||
# target_box is the squared size raised to the zoom factor
|
||||
return target_box * (predicted_size / size) ** (
|
||||
2 * camera_config.onvif.autotracking.zoom_factor
|
||||
)
|
||||
|
||||
def _calculate_tracked_object_metrics(self, camera, obj):
|
||||
@@ -660,26 +670,6 @@ class PtzAutoTracker(threading.Thread):
|
||||
|
||||
filtered_areas = remove_outliers(areas) if len(areas) > 3 else areas
|
||||
|
||||
# Filter entries that are not touching the frame edge
|
||||
filtered_areas_not_touching_edge = [
|
||||
entry
|
||||
for entry in filtered_areas
|
||||
if self._touching_frame_edges(camera, entry["box"]) == 0
|
||||
]
|
||||
|
||||
# Calculate regression for area change predictions
|
||||
if len(filtered_areas_not_touching_edge):
|
||||
X = np.array(
|
||||
[item["frame_time"] for item in filtered_areas_not_touching_edge]
|
||||
)
|
||||
y = np.array([item["area"] for item in filtered_areas_not_touching_edge])
|
||||
|
||||
tom["area_coefficients"] = np.linalg.lstsq(X.reshape(-1, 1), y, rcond=None)[
|
||||
0
|
||||
]
|
||||
else:
|
||||
tom["area_coefficients"] = np.array([0])
|
||||
|
||||
weights = np.arange(1, len(filtered_areas) + 1)
|
||||
weighted_area = np.average(
|
||||
[item["area"] for item in filtered_areas], weights=weights
|
||||
@@ -744,6 +734,8 @@ class PtzAutoTracker(threading.Thread):
|
||||
continue
|
||||
|
||||
else:
|
||||
pan_tilt_times = None
|
||||
|
||||
if (
|
||||
camera_config.onvif.autotracking.zooming
|
||||
== ZoomingModeEnum.relative
|
||||
@@ -755,17 +747,28 @@ class PtzAutoTracker(threading.Thread):
|
||||
|
||||
await self._wait_until_stopped(camera, metrics)
|
||||
|
||||
# _zoom_absolute resets the metrics timestamps
|
||||
pan_tilt_times = (
|
||||
metrics.start_time.value,
|
||||
metrics.stop_time.value,
|
||||
)
|
||||
|
||||
if zoom > 0 and metrics.zoom_level.value != zoom:
|
||||
await self.onvif._zoom_absolute(camera, zoom, 1)
|
||||
|
||||
await self._wait_until_stopped(camera, metrics)
|
||||
|
||||
start_time, stop_time = pan_tilt_times or (
|
||||
metrics.start_time.value,
|
||||
metrics.stop_time.value,
|
||||
)
|
||||
|
||||
if camera_config.onvif.autotracking.movement_weights:
|
||||
logger.debug(
|
||||
f"{camera}: Predicted movement time: {self._predict_movement_time(camera, pan, tilt)}"
|
||||
)
|
||||
logger.debug(
|
||||
f"{camera}: Actual movement time: {metrics.stop_time.value - metrics.start_time.value}"
|
||||
f"{camera}: Actual movement time: {stop_time - start_time}"
|
||||
)
|
||||
|
||||
# save metrics for better estimate calculations
|
||||
@@ -781,8 +784,8 @@ class PtzAutoTracker(threading.Thread):
|
||||
{
|
||||
"pan": pan,
|
||||
"tilt": tilt,
|
||||
"start_timestamp": metrics.start_time.value,
|
||||
"end_timestamp": metrics.stop_time.value,
|
||||
"start_timestamp": start_time,
|
||||
"end_timestamp": stop_time,
|
||||
}
|
||||
)
|
||||
|
||||
@@ -845,7 +848,6 @@ class PtzAutoTracker(threading.Thread):
|
||||
# Thresholds
|
||||
x_mags_thresh = camera_width / camera_fps / 2
|
||||
y_mags_thresh = camera_height / camera_fps / 2
|
||||
dir_thresh = 0.93
|
||||
delta_thresh = 20
|
||||
var_thresh = 10
|
||||
|
||||
@@ -863,24 +865,12 @@ class PtzAutoTracker(threading.Thread):
|
||||
stdev_list = np.std(velocities, axis=0)
|
||||
high_variances = np.any(stdev_list > var_thresh)
|
||||
|
||||
# Check direction difference
|
||||
# the corners of an object moving toward or away from the camera move
|
||||
# in different directions, so the delta bounds how far they may differ
|
||||
velocities = np.round(velocities)
|
||||
invalid_dirs = False
|
||||
if np.all(np.linalg.norm(velocities, axis=1)):
|
||||
cosine_sim = np.dot(velocities[0], velocities[1]) / (
|
||||
np.linalg.norm(velocities[0]) * np.linalg.norm(velocities[1])
|
||||
)
|
||||
dir_thresh = 0.6 if np.all(delta < delta_thresh / 2) else dir_thresh
|
||||
invalid_dirs = cosine_sim < dir_thresh
|
||||
|
||||
# Combine
|
||||
invalid = (
|
||||
invalid_x_mags
|
||||
or invalid_y_mags
|
||||
or invalid_dirs
|
||||
or invalid_delta
|
||||
or high_variances
|
||||
)
|
||||
invalid = invalid_x_mags or invalid_y_mags or invalid_delta or high_variances
|
||||
|
||||
if invalid:
|
||||
logger.debug(
|
||||
@@ -891,7 +881,6 @@ class PtzAutoTracker(threading.Thread):
|
||||
for var_name, is_invalid in [
|
||||
("invalid_x_mags", invalid_x_mags),
|
||||
("invalid_y_mags", invalid_y_mags),
|
||||
("invalid_dirs", invalid_dirs),
|
||||
("invalid_delta", invalid_delta),
|
||||
("high_variances", high_variances),
|
||||
]
|
||||
|
||||
@@ -485,6 +485,40 @@ class TestHttpApp(BaseTestHttp):
|
||||
assert sub_labels
|
||||
assert sub_labels == [sub_label]
|
||||
|
||||
def test_get_event_list_filter_sub_labels(self):
|
||||
sub_labels = {
|
||||
"1.cyrillic": "Кузнецова Ира",
|
||||
"2.joined": "Ира, Bob",
|
||||
"3.ascii": "Alice",
|
||||
"4.none": None,
|
||||
}
|
||||
mock_embeddings = Mock()
|
||||
mock_embeddings.search_thumbnail.return_value = [(id, 0.1) for id in sub_labels]
|
||||
|
||||
self.app.frigate_config.semantic_search.enabled = True
|
||||
self.app.embeddings = mock_embeddings
|
||||
|
||||
with AuthTestClient(self.app) as client:
|
||||
for id, sub_label in sub_labels.items():
|
||||
super().insert_mock_event(id)
|
||||
Event.update(sub_label=sub_label).where(Event.id == id).execute()
|
||||
|
||||
search_params = {"search_type": "similarity", "event_id": "1.cyrillic"}
|
||||
|
||||
for path, params in (("/events", {}), ("/events/search", search_params)):
|
||||
|
||||
def ids(query: str) -> set[str]:
|
||||
events = client.get(
|
||||
path, params={**params, "sub_labels": query}
|
||||
).json()
|
||||
return {event["id"] for event in events}
|
||||
|
||||
assert ids("Кузнецова Ира") == {"1.cyrillic"}
|
||||
assert ids("Ира") == {"2.joined"}
|
||||
assert ids("bob") == {"2.joined"}
|
||||
assert ids("ALICE") == {"3.ascii"}
|
||||
assert ids("None") == {"4.none"}
|
||||
|
||||
####################################################################################################################
|
||||
################################### GET /metrics Endpoint #########################################################
|
||||
####################################################################################################################
|
||||
|
||||
@@ -0,0 +1,51 @@
|
||||
"""Tests for the Frigate+ API endpoints."""
|
||||
|
||||
import unittest
|
||||
from unittest.mock import patch
|
||||
|
||||
from frigate.models import Event
|
||||
from frigate.plus import PlusKeyStatus
|
||||
from frigate.test.http_api.base_http_test import AuthTestClient, BaseTestHttp
|
||||
|
||||
|
||||
class TestHttpPlusValidate(BaseTestHttp):
|
||||
def setUp(self):
|
||||
super().setUp([Event])
|
||||
|
||||
def _validate(self, status: PlusKeyStatus) -> dict:
|
||||
app = self.create_app()
|
||||
|
||||
with patch.object(
|
||||
app.frigate_config.plus_api, "validate_key", return_value=status
|
||||
):
|
||||
with AuthTestClient(app) as client:
|
||||
response = client.post("/plus/validate")
|
||||
|
||||
self.assertEqual(response.status_code, 200)
|
||||
return response.json()
|
||||
|
||||
def test_a_valid_key(self):
|
||||
self.assertEqual(
|
||||
self._validate(PlusKeyStatus.valid), {"success": True, "status": "valid"}
|
||||
)
|
||||
|
||||
def test_an_invalid_key(self):
|
||||
self.assertEqual(
|
||||
self._validate(PlusKeyStatus.invalid),
|
||||
{"success": False, "status": "invalid"},
|
||||
)
|
||||
|
||||
def test_viewers_cannot_validate(self):
|
||||
app = self.create_app()
|
||||
|
||||
with AuthTestClient(app) as client:
|
||||
response = client.post(
|
||||
"/plus/validate",
|
||||
headers={"remote-user": "viewer", "remote-role": "viewer"},
|
||||
)
|
||||
|
||||
self.assertEqual(response.status_code, 403)
|
||||
|
||||
|
||||
if __name__ == "__main__":
|
||||
unittest.main(verbosity=2)
|
||||
@@ -12,6 +12,7 @@ from frigate.analytics.collectors.common import closed, histogram, rate
|
||||
from frigate.analytics.schema import (
|
||||
Arch,
|
||||
DecodeFamily,
|
||||
FfmpegSource,
|
||||
GpuVendor,
|
||||
HardwareKey,
|
||||
ImageVariant,
|
||||
@@ -56,6 +57,36 @@ class TestInstallCollector(unittest.TestCase):
|
||||
self.assertEqual(section.arch, Arch.aarch64)
|
||||
self.assertEqual(section.kernel, "6.8")
|
||||
self.assertFalse(section.run_as_root)
|
||||
self.assertEqual(section.ffmpeg, FfmpegSource.default)
|
||||
|
||||
def test_ffmpeg_source_follows_path_resolution(self):
|
||||
with (
|
||||
patch.object(install, "DEFAULT_FFMPEG_VERSION", "8.0"),
|
||||
patch.object(install, "INCLUDED_FFMPEG_VERSIONS", ["8.0", "7.0"]),
|
||||
):
|
||||
self.assertEqual(install.ffmpeg_source("default"), FfmpegSource.default)
|
||||
self.assertEqual(install.ffmpeg_source("8.0"), FfmpegSource.default)
|
||||
self.assertEqual(install.ffmpeg_source("4.4"), FfmpegSource.default)
|
||||
self.assertEqual(install.ffmpeg_source("7.0"), FfmpegSource.bundled)
|
||||
self.assertEqual(
|
||||
install.ffmpeg_source("/config/custom-ffmpeg"), FfmpegSource.custom
|
||||
)
|
||||
self.assertEqual(
|
||||
install.ffmpeg_source("/config/a b; rm"), FfmpegSource.default
|
||||
)
|
||||
|
||||
def test_custom_go2rtc_needs_an_executable_binary(self):
|
||||
with tempfile.TemporaryDirectory() as config_dir:
|
||||
binary = os.path.join(config_dir, "go2rtc")
|
||||
|
||||
with patch.object(install, "CONFIG_DIR", config_dir):
|
||||
self.assertFalse(install.collect(make_context()).custom_go2rtc)
|
||||
|
||||
open(binary, "w").close()
|
||||
self.assertFalse(install.collect(make_context()).custom_go2rtc)
|
||||
|
||||
os.chmod(binary, 0o755)
|
||||
self.assertTrue(install.collect(make_context()).custom_go2rtc)
|
||||
|
||||
def test_image_variant_is_dev_when_unset_and_other_when_unknown(self):
|
||||
self.assertEqual(install.image_variant(None), ImageVariant.dev)
|
||||
|
||||
@@ -25,6 +25,8 @@ INSTALL = InstallSection(
|
||||
arch="x86_64",
|
||||
kernel="6.8",
|
||||
run_as_root=False,
|
||||
custom_go2rtc=False,
|
||||
ffmpeg="default",
|
||||
)
|
||||
|
||||
|
||||
|
||||
@@ -1,5 +1,6 @@
|
||||
"""Tests for migrating detectors and model into the models list."""
|
||||
|
||||
import errno
|
||||
import logging
|
||||
import os
|
||||
import tempfile
|
||||
@@ -240,6 +241,42 @@ class TestMigrateConfigFile(unittest.TestCase):
|
||||
with open(self.config_file) as f:
|
||||
return YAML().load(f)
|
||||
|
||||
def test_a_failed_write_leaves_the_original_config(self):
|
||||
original = "mqtt:\n enabled: false\ncameras: {}\n"
|
||||
with open(self.config_file, "w") as f:
|
||||
f.write(original)
|
||||
|
||||
with (
|
||||
patch.object(YAML, "dump", side_effect=RuntimeError),
|
||||
self.assertRaises(RuntimeError),
|
||||
):
|
||||
migrate_frigate_config(self.config_file)
|
||||
|
||||
with open(self.config_file) as f:
|
||||
self.assertEqual(f.read(), original)
|
||||
|
||||
def test_a_file_bind_mount_is_written_in_place(self):
|
||||
with patch(
|
||||
"frigate.util.config.os.replace",
|
||||
side_effect=OSError(errno.EBUSY, "Device or resource busy"),
|
||||
):
|
||||
migrated = self._migrate("mqtt:\n enabled: false\ncameras: {}\n")
|
||||
|
||||
self.assertEqual(migrated["version"], CURRENT_CONFIG_VERSION)
|
||||
self.assertFalse(os.path.exists(f"{self.config_file}.tmp"))
|
||||
|
||||
def test_a_symlinked_config_migrates_its_target(self):
|
||||
target = os.path.join(self.temp_dir.name, "frigate.yml")
|
||||
with open(target, "w") as f:
|
||||
f.write("mqtt:\n enabled: false\ncameras: {}\n")
|
||||
os.symlink(target, self.config_file)
|
||||
|
||||
migrate_frigate_config(self.config_file)
|
||||
|
||||
self.assertTrue(os.path.islink(self.config_file))
|
||||
with open(target) as f:
|
||||
self.assertEqual(YAML().load(f)["version"], CURRENT_CONFIG_VERSION)
|
||||
|
||||
def test_migrates_a_config_already_stamped_with_the_current_version(self):
|
||||
# 0.19 is unreleased, so a dev config can be current and still use
|
||||
# the pre-models keys
|
||||
|
||||
@@ -18,6 +18,7 @@ from frigate.jobs.debug_replay import (
|
||||
from frigate.jobs.export import JobStatePublisher
|
||||
from frigate.jobs.manager import _completed_jobs, _current_jobs
|
||||
from frigate.types import JobStatusTypesEnum
|
||||
from frigate.util.recording_coverage import CoverageInterval
|
||||
|
||||
|
||||
def _reset_job_manager() -> None:
|
||||
@@ -77,6 +78,31 @@ class TestDebugReplayJob(unittest.TestCase):
|
||||
self.assertEqual(results["end_ts"], 200.0)
|
||||
|
||||
|
||||
class TestRecordingDebugReplaySource(unittest.TestCase):
|
||||
def _source_url(self, intervals: list[CoverageInterval]) -> str:
|
||||
source = RecordingDebugReplaySource(
|
||||
source_camera="front", start_ts=100.0, end_ts=200.0, internal_port=5000
|
||||
)
|
||||
with patch(
|
||||
"frigate.jobs.debug_replay.resolve_coverage", return_value=intervals
|
||||
):
|
||||
source.validate()
|
||||
return source.ffmpeg_input_args("/tmp")[-1]
|
||||
|
||||
def test_pins_main_when_main_has_recordings(self) -> None:
|
||||
url = self._source_url(
|
||||
[
|
||||
CoverageInterval(100.0, 150.0, None, MagicMock()),
|
||||
CoverageInterval(150.0, 200.0, MagicMock(), MagicMock()),
|
||||
]
|
||||
)
|
||||
self.assertIn("/vod/front/main/start/100.0/end/200.0/", url)
|
||||
|
||||
def test_falls_back_to_sub_without_main_recordings(self) -> None:
|
||||
url = self._source_url([CoverageInterval(100.0, 200.0, None, MagicMock())])
|
||||
self.assertIn("/vod/front/sub/start/100.0/end/200.0/", url)
|
||||
|
||||
|
||||
class TestStartDebugReplayJob(unittest.TestCase):
|
||||
def setUp(self) -> None:
|
||||
_reset_job_manager()
|
||||
@@ -87,9 +113,7 @@ class TestStartDebugReplayJob(unittest.TestCase):
|
||||
self.frigate_config.ffmpeg.ffmpeg_path = "/bin/true"
|
||||
self.publisher = MagicMock()
|
||||
|
||||
self.recordings_qs = MagicMock()
|
||||
self.recordings_qs.count.return_value = 1
|
||||
self.recordings_qs.__iter__.return_value = iter([MagicMock(path="/tmp/r1.mp4")])
|
||||
self.coverage = [CoverageInterval(100.0, 200.0, MagicMock(), None)]
|
||||
|
||||
def tearDown(self) -> None:
|
||||
runner = get_active_runner()
|
||||
@@ -127,9 +151,7 @@ class TestStartDebugReplayJob(unittest.TestCase):
|
||||
)
|
||||
|
||||
def test_rejects_when_no_recordings(self) -> None:
|
||||
empty_qs = MagicMock()
|
||||
empty_qs.count.return_value = 0
|
||||
with patch("frigate.jobs.debug_replay.query_recordings", return_value=empty_qs):
|
||||
with patch("frigate.jobs.debug_replay.resolve_coverage", return_value=[]):
|
||||
with self.assertRaises(NoRecordingsError):
|
||||
start_debug_replay_job(
|
||||
source=RecordingDebugReplaySource(
|
||||
@@ -152,8 +174,8 @@ class TestStartDebugReplayJob(unittest.TestCase):
|
||||
|
||||
with (
|
||||
patch(
|
||||
"frigate.jobs.debug_replay.query_recordings",
|
||||
return_value=self.recordings_qs,
|
||||
"frigate.jobs.debug_replay.resolve_coverage",
|
||||
return_value=self.coverage,
|
||||
),
|
||||
patch(
|
||||
"frigate.jobs.debug_replay.run_ffmpeg_with_progress",
|
||||
@@ -192,8 +214,8 @@ class TestStartDebugReplayJob(unittest.TestCase):
|
||||
|
||||
with (
|
||||
patch(
|
||||
"frigate.jobs.debug_replay.query_recordings",
|
||||
return_value=self.recordings_qs,
|
||||
"frigate.jobs.debug_replay.resolve_coverage",
|
||||
return_value=self.coverage,
|
||||
),
|
||||
patch(
|
||||
"frigate.jobs.debug_replay.run_ffmpeg_with_progress",
|
||||
@@ -242,9 +264,7 @@ class TestRunnerHappyPath(unittest.TestCase):
|
||||
self.frigate_config.ffmpeg.ffmpeg_path = "/bin/true"
|
||||
self.publisher = MagicMock()
|
||||
|
||||
self.recordings_qs = MagicMock()
|
||||
self.recordings_qs.count.return_value = 1
|
||||
self.recordings_qs.__iter__.return_value = iter([MagicMock(path="/tmp/r1.mp4")])
|
||||
self.coverage = [CoverageInterval(100.0, 200.0, MagicMock(), None)]
|
||||
|
||||
def tearDown(self) -> None:
|
||||
runner = get_active_runner()
|
||||
@@ -272,8 +292,8 @@ class TestRunnerHappyPath(unittest.TestCase):
|
||||
|
||||
with (
|
||||
patch(
|
||||
"frigate.jobs.debug_replay.query_recordings",
|
||||
return_value=self.recordings_qs,
|
||||
"frigate.jobs.debug_replay.resolve_coverage",
|
||||
return_value=self.coverage,
|
||||
),
|
||||
patch(
|
||||
"frigate.jobs.debug_replay.run_ffmpeg_with_progress",
|
||||
@@ -325,9 +345,7 @@ class TestRunnerFailurePath(unittest.TestCase):
|
||||
self.frigate_config.cameras = {"front": MagicMock()}
|
||||
self.frigate_config.ffmpeg.ffmpeg_path = "/bin/true"
|
||||
self.publisher = MagicMock()
|
||||
self.recordings_qs = MagicMock()
|
||||
self.recordings_qs.count.return_value = 1
|
||||
self.recordings_qs.__iter__.return_value = iter([MagicMock(path="/tmp/r1.mp4")])
|
||||
self.coverage = [CoverageInterval(100.0, 200.0, MagicMock(), None)]
|
||||
|
||||
def tearDown(self) -> None:
|
||||
runner = get_active_runner()
|
||||
@@ -350,8 +368,8 @@ class TestRunnerFailurePath(unittest.TestCase):
|
||||
|
||||
with (
|
||||
patch(
|
||||
"frigate.jobs.debug_replay.query_recordings",
|
||||
return_value=self.recordings_qs,
|
||||
"frigate.jobs.debug_replay.resolve_coverage",
|
||||
return_value=self.coverage,
|
||||
),
|
||||
patch(
|
||||
"frigate.jobs.debug_replay.run_ffmpeg_with_progress",
|
||||
@@ -399,9 +417,7 @@ class TestRunnerCancellation(unittest.TestCase):
|
||||
self.frigate_config.cameras = {"front": MagicMock()}
|
||||
self.frigate_config.ffmpeg.ffmpeg_path = "/bin/true"
|
||||
self.publisher = MagicMock()
|
||||
self.recordings_qs = MagicMock()
|
||||
self.recordings_qs.count.return_value = 1
|
||||
self.recordings_qs.__iter__.return_value = iter([MagicMock(path="/tmp/r1.mp4")])
|
||||
self.coverage = [CoverageInterval(100.0, 200.0, MagicMock(), None)]
|
||||
|
||||
def tearDown(self) -> None:
|
||||
runner = get_active_runner()
|
||||
@@ -431,8 +447,8 @@ class TestRunnerCancellation(unittest.TestCase):
|
||||
|
||||
with (
|
||||
patch(
|
||||
"frigate.jobs.debug_replay.query_recordings",
|
||||
return_value=self.recordings_qs,
|
||||
"frigate.jobs.debug_replay.resolve_coverage",
|
||||
return_value=self.coverage,
|
||||
),
|
||||
patch(
|
||||
"frigate.jobs.debug_replay.run_ffmpeg_with_progress",
|
||||
|
||||
@@ -5,6 +5,7 @@ per-camera gating those processors apply once they exist.
|
||||
"""
|
||||
|
||||
import sys
|
||||
import threading
|
||||
import unittest
|
||||
from unittest.mock import MagicMock, patch
|
||||
|
||||
@@ -19,6 +20,8 @@ for mod in _MOCK_MODULES:
|
||||
if mod not in sys.modules:
|
||||
sys.modules[mod] = MagicMock()
|
||||
|
||||
from frigate.config.camera.review import ImageSourceEnum # noqa: E402
|
||||
|
||||
# imported from the maintainer to avoid tripping the circular import between
|
||||
# the maintainer and the processor modules
|
||||
from frigate.embeddings.maintainer import ( # noqa: E402
|
||||
@@ -211,3 +214,58 @@ class TestObjectDescriptionCameraGating(unittest.TestCase):
|
||||
|
||||
mock_create_thumbnail.assert_called_once()
|
||||
self.assertEqual(len(processor.tracked_events["1234.5-abcdef"]), 1)
|
||||
|
||||
|
||||
class TestReviewDescriptionOffLoop(unittest.TestCase):
|
||||
"""Frame extraction for a finished review must not block process_data."""
|
||||
|
||||
def test_recording_frames_extracted_off_the_caller_thread(self):
|
||||
camera = MagicMock()
|
||||
camera.review.genai.enabled = True
|
||||
camera.review.genai.alerts = True
|
||||
camera.review.genai.image_source = ImageSourceEnum.recordings
|
||||
camera.review.genai.debug_save_thumbnails = False
|
||||
config = MagicMock()
|
||||
config.cameras = {"front": camera}
|
||||
processor = ReviewDescriptionProcessor(
|
||||
config, MagicMock(), MagicMock(), MagicMock()
|
||||
)
|
||||
|
||||
release = threading.Event()
|
||||
extracted = threading.Event()
|
||||
analyzed = threading.Event()
|
||||
|
||||
def slow_frames(*args, **kwargs):
|
||||
release.wait(5)
|
||||
extracted.set()
|
||||
return [(b"jpg", 1.0)]
|
||||
|
||||
with (
|
||||
patch.object(processor, "get_recording_frames", side_effect=slow_frames),
|
||||
patch.object(
|
||||
processor, "start_analysis", side_effect=lambda *a, **k: analyzed.set()
|
||||
) as start_analysis,
|
||||
):
|
||||
processor.process_data(
|
||||
{
|
||||
"type": "end",
|
||||
"after": {
|
||||
"id": "1234.5-abcdef",
|
||||
"camera": "front",
|
||||
"severity": "alert",
|
||||
"start_time": 100.0,
|
||||
"end_time": 110.0,
|
||||
},
|
||||
},
|
||||
PostProcessDataEnum.review,
|
||||
)
|
||||
|
||||
self.assertFalse(extracted.is_set())
|
||||
release.set()
|
||||
self.assertTrue(extracted.wait(5))
|
||||
|
||||
self.assertTrue(analyzed.wait(5))
|
||||
start_analysis.assert_called_once()
|
||||
final_data = start_analysis.call_args.args[1]
|
||||
self.assertEqual(final_data["start_time"], 100.0)
|
||||
self.assertEqual(final_data["end_time"], 110.0)
|
||||
|
||||
@@ -0,0 +1,24 @@
|
||||
"""Tests for MQTT TLS configuration validation."""
|
||||
|
||||
import unittest
|
||||
from contextlib import nullcontext
|
||||
|
||||
from pydantic import ValidationError
|
||||
|
||||
from frigate.config import MqttConfig
|
||||
|
||||
|
||||
class TestMqttTls(unittest.TestCase):
|
||||
def test_tls_insecure_requires_tls_only_when_enabled(self) -> None:
|
||||
"""Require TLS for either explicit verification flag only if MQTT is enabled."""
|
||||
for enabled in (True, False):
|
||||
for insecure in (True, False):
|
||||
expected = (
|
||||
self.assertRaisesRegex(
|
||||
ValidationError, "tls_insecure requires tls_ca_certs"
|
||||
)
|
||||
if enabled
|
||||
else nullcontext()
|
||||
)
|
||||
with self.subTest(enabled=enabled, tls_insecure=insecure), expected:
|
||||
MqttConfig(enabled=enabled, tls_insecure=insecure)
|
||||
+138
-1
@@ -1,12 +1,17 @@
|
||||
"""Tests for Frigate+ model metadata."""
|
||||
|
||||
import asyncio
|
||||
import json
|
||||
import os
|
||||
import tempfile
|
||||
import unittest
|
||||
from unittest.mock import MagicMock, patch
|
||||
|
||||
from frigate.plus import PlusApi, load_plus_model_info
|
||||
import aiohttp
|
||||
from aiohttp import web
|
||||
from aiohttp.test_utils import TestServer
|
||||
|
||||
from frigate.plus import PlusApi, PlusKeyStatus, load_plus_model_info
|
||||
|
||||
|
||||
class TestHailoAlias(unittest.TestCase):
|
||||
@@ -75,5 +80,137 @@ class TestLoadPlusModelInfo(unittest.TestCase):
|
||||
self.assertIsNone(load_plus_model_info("bad"))
|
||||
|
||||
|
||||
class TestValidateKey(unittest.IsolatedAsyncioTestCase):
|
||||
"""The key format is checked locally, but only the server knows if it is valid."""
|
||||
|
||||
CACHED_TOKEN = {"accessToken": "cached", "expires": 2000000000}
|
||||
|
||||
async def _serve(self, handler) -> str:
|
||||
"""Serve the token endpoint locally and return its base url."""
|
||||
app = web.Application()
|
||||
app.router.add_get("/v1/auth/token", handler)
|
||||
server = TestServer(app)
|
||||
await server.start_server()
|
||||
self.addAsyncCleanup(server.close)
|
||||
return str(server.make_url("")).rstrip("/")
|
||||
|
||||
def _api(self, host: str, key: str | None = "id:secret") -> PlusApi:
|
||||
api = PlusApi.__new__(PlusApi)
|
||||
api.host = host
|
||||
api.key = key
|
||||
api._token_data = dict(self.CACHED_TOKEN)
|
||||
return api
|
||||
|
||||
async def _validate(self, handler) -> tuple[PlusKeyStatus, PlusApi]:
|
||||
api = self._api(await self._serve(handler))
|
||||
return await api.validate_key(), api
|
||||
|
||||
def _replies(self, status: int = 200, body: object = None, text: str | None = None):
|
||||
async def handler(request: web.Request) -> web.Response:
|
||||
self.auth = request.headers.get("Authorization")
|
||||
if text is not None:
|
||||
return web.Response(status=status, text=text)
|
||||
return web.json_response(body, status=status)
|
||||
|
||||
return handler
|
||||
|
||||
async def test_an_accepted_key_is_valid(self):
|
||||
token = {"accessToken": "fresh", "expires": 2000000500.5}
|
||||
status, api = await self._validate(self._replies(body=token))
|
||||
|
||||
self.assertEqual(status, PlusKeyStatus.valid)
|
||||
self.assertEqual(api._token_data, token)
|
||||
self.assertEqual(self.auth, aiohttp.BasicAuth("id", "secret").encode())
|
||||
|
||||
async def test_a_rejected_key_is_invalid(self):
|
||||
for code in (401, 403):
|
||||
status, api = await self._validate(self._replies(code, {"error": "no"}))
|
||||
|
||||
self.assertEqual(status, PlusKeyStatus.invalid)
|
||||
self.assertEqual(api._token_data, self.CACHED_TOKEN)
|
||||
|
||||
async def test_a_server_error_is_not_reported_as_invalid(self):
|
||||
status, _ = await self._validate(self._replies(500, {"error": "down"}))
|
||||
|
||||
self.assertEqual(status, PlusKeyStatus.unreachable)
|
||||
|
||||
async def test_unusable_replies_keep_the_cached_token(self):
|
||||
# a successful reply without a usable token must not replace one that
|
||||
# later Frigate+ requests depend on
|
||||
for body in (
|
||||
None,
|
||||
[],
|
||||
{},
|
||||
{"accessToken": "t"},
|
||||
{"expires": 2000000000},
|
||||
{"accessToken": 1, "expires": 2000000000},
|
||||
{"accessToken": "t", "expires": "soon"},
|
||||
{"accessToken": "t", "expires": True},
|
||||
):
|
||||
with self.subTest(body=body):
|
||||
status, api = await self._validate(self._replies(body=body))
|
||||
|
||||
self.assertEqual(status, PlusKeyStatus.unreachable)
|
||||
self.assertEqual(api._token_data, self.CACHED_TOKEN)
|
||||
|
||||
async def test_a_non_json_reply_keeps_the_cached_token(self):
|
||||
status, api = await self._validate(self._replies(text="<html>"))
|
||||
|
||||
self.assertEqual(status, PlusKeyStatus.unreachable)
|
||||
self.assertEqual(api._token_data, self.CACHED_TOKEN)
|
||||
|
||||
async def test_a_connection_failure_is_unreachable(self):
|
||||
# serve and stop, so the port refuses connections
|
||||
server = TestServer(web.Application())
|
||||
await server.start_server()
|
||||
host = str(server.make_url("")).rstrip("/")
|
||||
await server.close()
|
||||
api = self._api(host)
|
||||
|
||||
self.assertEqual(await api.validate_key(), PlusKeyStatus.unreachable)
|
||||
|
||||
async def test_a_slow_server_times_out_as_unreachable(self):
|
||||
async def slow(request: web.Request) -> web.Response:
|
||||
await asyncio.sleep(5)
|
||||
return web.json_response(self.CACHED_TOKEN)
|
||||
|
||||
with patch("frigate.plus.PLUS_VALIDATE_TIMEOUT", 0.1):
|
||||
status, _ = await self._validate(slow)
|
||||
|
||||
self.assertEqual(status, PlusKeyStatus.unreachable)
|
||||
|
||||
async def test_the_request_goes_through_a_configured_proxy(self):
|
||||
# installs that reach Frigate+ through a proxy must validate the same way
|
||||
token = {"accessToken": "proxied", "expires": 2000000000}
|
||||
proxied = []
|
||||
|
||||
async def proxy(request: web.Request) -> web.Response:
|
||||
proxied.append(str(request.url))
|
||||
return web.json_response(token)
|
||||
|
||||
proxy_url = await self._serve(proxy)
|
||||
api = self._api("http://plus.invalid")
|
||||
|
||||
with patch.dict(
|
||||
os.environ, {"HTTP_PROXY": proxy_url, "NO_PROXY": ""}, clear=False
|
||||
):
|
||||
status = await api.validate_key()
|
||||
|
||||
self.assertEqual(status, PlusKeyStatus.valid)
|
||||
self.assertEqual(proxied, ["http://plus.invalid/v1/auth/token"])
|
||||
|
||||
async def test_no_key_is_not_sent_to_the_server(self):
|
||||
requests_seen = []
|
||||
|
||||
async def handler(request: web.Request) -> web.Response:
|
||||
requests_seen.append(request)
|
||||
return web.json_response(self.CACHED_TOKEN)
|
||||
|
||||
api = self._api(await self._serve(handler), key=None)
|
||||
|
||||
self.assertEqual(await api.validate_key(), PlusKeyStatus.not_set)
|
||||
self.assertEqual(requests_seen, [])
|
||||
|
||||
|
||||
if __name__ == "__main__":
|
||||
unittest.main(verbosity=2)
|
||||
|
||||
@@ -11,11 +11,14 @@ KeyError on the autotracker thread or silently keep the wrong state:
|
||||
running its motion estimator for a camera that could not autotrack.
|
||||
"""
|
||||
|
||||
import asyncio
|
||||
import unittest
|
||||
from unittest.mock import MagicMock
|
||||
from unittest.mock import AsyncMock, MagicMock
|
||||
|
||||
import numpy as np
|
||||
|
||||
from frigate.camera import PTZMetrics
|
||||
from frigate.config import FrigateConfig
|
||||
from frigate.config import FrigateConfig, ZoomingModeEnum
|
||||
from frigate.config.camera.updater import CameraConfigUpdateEnum
|
||||
from frigate.ptz.autotrack import PtzAutoTracker, calculate_max_target_box
|
||||
|
||||
@@ -123,6 +126,118 @@ class TestAutotrackerDisable(unittest.TestCase):
|
||||
self.assertIs(payload, autotracking)
|
||||
|
||||
|
||||
class TestAutotrackerPrediction(unittest.TestCase):
|
||||
def test_movement_time_uses_the_fitted_intercept_unchanged(self) -> None:
|
||||
tracker = _make_tracker()
|
||||
tracker.intercept = {CAMERA: 0.5}
|
||||
tracker.move_coefficients = {CAMERA: [0.3, 0.8]}
|
||||
|
||||
self.assertAlmostEqual(tracker._predict_movement_time(CAMERA, 0.25, 0.25), 0.7)
|
||||
|
||||
def test_shrinking_box_keeps_a_positive_target_box(self) -> None:
|
||||
# 60 px shrinking 6 px/frame reaches zero after 2 s at 5 fps
|
||||
target_box = self._target_box_for((0, 0, 60, 60), [3, 3, -3, -3], 2.0)
|
||||
|
||||
self.assertGreater(target_box, 0)
|
||||
self.assertTrue(np.isfinite(target_box))
|
||||
|
||||
def test_approaching_object_keeps_its_velocity(self) -> None:
|
||||
# the box grows, so the corners move in opposite directions
|
||||
tracker = _make_tracker()
|
||||
obj = MagicMock()
|
||||
obj.obj_data = {"estimate_velocity": np.array([[-3.0, -4.0], [3.0, 1.0]])}
|
||||
|
||||
valid, velocity = tracker._get_valid_velocity(CAMERA, obj)
|
||||
|
||||
self.assertTrue(valid)
|
||||
self.assertEqual(list(velocity), [-3, -4, 3, 1])
|
||||
|
||||
def test_corners_that_disagree_too_much_are_rejected(self) -> None:
|
||||
tracker = _make_tracker()
|
||||
obj = MagicMock()
|
||||
obj.obj_data = {"estimate_velocity": np.array([[-15.0, 0.0], [15.0, 0.0]])}
|
||||
|
||||
valid, velocity = tracker._get_valid_velocity(CAMERA, obj)
|
||||
|
||||
self.assertFalse(valid)
|
||||
self.assertFalse(np.any(velocity))
|
||||
|
||||
def _target_box(self, velocity: list[float], predicted_time: float) -> float:
|
||||
return self._target_box_for((900, 400, 1000, 600), velocity, predicted_time)
|
||||
|
||||
def _target_box_for(
|
||||
self,
|
||||
box: tuple[int, int, int, int],
|
||||
velocity: list[float],
|
||||
predicted_time: float,
|
||||
) -> float:
|
||||
tracker = _make_tracker()
|
||||
tracker.tracked_object_history = {CAMERA: [{"box": box}]}
|
||||
tracker.tracked_object_metrics = {
|
||||
CAMERA: {"target_box": 0.2, "velocity": np.array(velocity)}
|
||||
}
|
||||
|
||||
return tracker._predict_target_box(CAMERA, predicted_time)
|
||||
|
||||
def test_target_box_grows_with_an_approaching_object(self) -> None:
|
||||
# the 200px height gains 4px per frame at 5 fps for 1 second
|
||||
zoom_factor = 0.3
|
||||
|
||||
self.assertAlmostEqual(
|
||||
self._target_box([-1, -2, 1, 2], 1.0),
|
||||
0.2 * (220 / 200) ** (2 * zoom_factor),
|
||||
)
|
||||
|
||||
def test_target_box_is_unchanged_without_growth_or_time(self) -> None:
|
||||
self.assertAlmostEqual(self._target_box([5, 0, 5, 0], 1.0), 0.2)
|
||||
self.assertEqual(self._target_box([-1, -2, 1, 2], 0), 0.2)
|
||||
|
||||
|
||||
class TestAutotrackerMoveQueue(unittest.IsolatedAsyncioTestCase):
|
||||
async def test_absolute_zoom_does_not_overwrite_the_pan_tilt_sample(self) -> None:
|
||||
tracker = _make_tracker()
|
||||
autotracking = tracker.config.cameras[CAMERA].onvif.autotracking
|
||||
autotracking.zooming = ZoomingModeEnum.absolute
|
||||
autotracking.calibrate_on_startup = True
|
||||
metrics = tracker.ptz_metrics[CAMERA]
|
||||
|
||||
tracker.stop_event = MagicMock()
|
||||
tracker.stop_event.is_set.side_effect = [False, True]
|
||||
tracker.move_queues = {CAMERA: asyncio.Queue()}
|
||||
tracker.move_queue_locks = {CAMERA: asyncio.Lock()}
|
||||
tracker.intercept = {CAMERA: 0.5}
|
||||
tracker.move_metrics = {CAMERA: []}
|
||||
tracker._wait_until_stopped = AsyncMock()
|
||||
tracker._calculate_move_coefficients = MagicMock()
|
||||
|
||||
async def move_relative(*_args) -> None:
|
||||
metrics.start_time.value = 100.0
|
||||
metrics.stop_time.value = 102.0
|
||||
|
||||
async def zoom_absolute(*_args) -> None:
|
||||
metrics.start_time.value = 103.0
|
||||
metrics.stop_time.value = 0
|
||||
|
||||
tracker.onvif._move_relative = AsyncMock(side_effect=move_relative)
|
||||
tracker.onvif._zoom_absolute = AsyncMock(side_effect=zoom_absolute)
|
||||
|
||||
tracker.move_queues[CAMERA].put_nowait((50.0, 0.5, 0.5, 0.5))
|
||||
await tracker._process_move_queue(CAMERA)
|
||||
|
||||
tracker.onvif._zoom_absolute.assert_awaited_once()
|
||||
self.assertEqual(
|
||||
tracker.move_metrics[CAMERA],
|
||||
[
|
||||
{
|
||||
"pan": 0.5,
|
||||
"tilt": 0.5,
|
||||
"start_timestamp": 100.0,
|
||||
"end_timestamp": 102.0,
|
||||
}
|
||||
],
|
||||
)
|
||||
|
||||
|
||||
class TestMaxTargetBox(unittest.TestCase):
|
||||
def test_follows_zoom_factor(self) -> None:
|
||||
self.assertAlmostEqual(calculate_max_target_box(0.5), 0.6**2)
|
||||
|
||||
@@ -321,6 +321,36 @@ class TestLighterANE(unittest.TestCase):
|
||||
ane.assert_not_called()
|
||||
|
||||
|
||||
class TestTensorRTProvider(unittest.TestCase):
|
||||
def _trt_options(self, env):
|
||||
with (
|
||||
patch.object(
|
||||
model_util.ort,
|
||||
"get_available_providers",
|
||||
return_value=["TensorrtExecutionProvider", "CPUExecutionProvider"],
|
||||
),
|
||||
patch.object(model_util.os, "makedirs"),
|
||||
patch.dict(os.environ, env, clear=False),
|
||||
):
|
||||
if "USE_FP16" not in env:
|
||||
os.environ.pop("USE_FP16", None)
|
||||
providers, options = model_util.get_ort_providers(device="Tensorrt")
|
||||
|
||||
return options[providers.index("TensorrtExecutionProvider")]
|
||||
|
||||
def test_fp16_is_disabled_by_default(self):
|
||||
self.assertFalse(self._trt_options({})["trt_fp16_enable"])
|
||||
|
||||
def test_use_fp16_enables_fp16(self):
|
||||
for value in ("true", "True", "TRUE"):
|
||||
with self.subTest(value=value):
|
||||
options = self._trt_options({"USE_FP16": value})
|
||||
self.assertTrue(options["trt_fp16_enable"])
|
||||
|
||||
def test_use_fp16_false_keeps_fp16_disabled(self):
|
||||
self.assertFalse(self._trt_options({"USE_FP16": "False"})["trt_fp16_enable"])
|
||||
|
||||
|
||||
class TestMIGraphX(unittest.TestCase):
|
||||
"""AMD's MIGraphX plugin provider, selected through its devices in the ROCm image."""
|
||||
|
||||
|
||||
@@ -9,6 +9,7 @@ import queue
|
||||
import re
|
||||
import shlex
|
||||
import struct
|
||||
import threading
|
||||
import time
|
||||
import urllib.parse
|
||||
from collections import deque
|
||||
@@ -42,6 +43,7 @@ class EventsPerSecond:
|
||||
self._max_events = max_events
|
||||
self._last_n_seconds = last_n_seconds
|
||||
self._timestamps: deque[float] = deque(maxlen=max_events)
|
||||
self._lock = threading.Lock()
|
||||
|
||||
def start(self) -> None:
|
||||
self._start = time.monotonic()
|
||||
@@ -71,8 +73,11 @@ class EventsPerSecond:
|
||||
# remove aged out timestamps
|
||||
def expire_timestamps(self, now: float) -> None:
|
||||
threshold = now - self._last_n_seconds
|
||||
while self._timestamps and self._timestamps[0] < threshold:
|
||||
self._timestamps.popleft()
|
||||
|
||||
# update() and eps() can run on different threads
|
||||
with self._lock:
|
||||
while self._timestamps and self._timestamps[0] < threshold:
|
||||
self._timestamps.popleft()
|
||||
|
||||
|
||||
class InferenceSpeed:
|
||||
|
||||
+33
-18
@@ -1,6 +1,8 @@
|
||||
"""configuration utils."""
|
||||
|
||||
import asyncio
|
||||
import contextlib
|
||||
import errno
|
||||
import ipaddress
|
||||
import logging
|
||||
import os
|
||||
@@ -212,6 +214,28 @@ def find_config_file() -> str:
|
||||
return config_path
|
||||
|
||||
|
||||
def _write_config(config_file: str, yaml: YAML, config: dict[str, Any]) -> None:
|
||||
config_file = os.path.realpath(config_file)
|
||||
st = os.stat(config_file)
|
||||
tmp_file = f"{config_file}.tmp"
|
||||
with open(tmp_file, "w") as f:
|
||||
yaml.dump(config, f)
|
||||
os.chmod(tmp_file, st.st_mode)
|
||||
|
||||
with contextlib.suppress(PermissionError):
|
||||
os.chown(tmp_file, st.st_uid, st.st_gid)
|
||||
|
||||
try:
|
||||
os.replace(tmp_file, config_file)
|
||||
except OSError as e:
|
||||
if e.errno != errno.EBUSY:
|
||||
raise
|
||||
|
||||
# a file bind mount can't be renamed over, so write it in place
|
||||
shutil.copyfile(tmp_file, config_file)
|
||||
os.remove(tmp_file)
|
||||
|
||||
|
||||
def migrate_frigate_config(config_file: str):
|
||||
"""handle migrating the frigate config."""
|
||||
logger.info("Checking if frigate config needs migration...")
|
||||
@@ -259,8 +283,7 @@ def migrate_frigate_config(config_file: str):
|
||||
if previous_version < "0.14":
|
||||
logger.info(f"Migrating frigate config from {previous_version} to 0.14...")
|
||||
new_config = migrate_014(new_config)
|
||||
with open(config_file, "w") as f:
|
||||
yaml.dump(new_config, f)
|
||||
_write_config(config_file, yaml, new_config)
|
||||
previous_version = "0.14"
|
||||
|
||||
logger.info("Migrating export file names...")
|
||||
@@ -277,56 +300,48 @@ def migrate_frigate_config(config_file: str):
|
||||
if previous_version < "0.15-0":
|
||||
logger.info(f"Migrating frigate config from {previous_version} to 0.15-0...")
|
||||
new_config = migrate_015_0(new_config)
|
||||
with open(config_file, "w") as f:
|
||||
yaml.dump(new_config, f)
|
||||
_write_config(config_file, yaml, new_config)
|
||||
previous_version = "0.15-0"
|
||||
|
||||
if previous_version < "0.15-1":
|
||||
logger.info(f"Migrating frigate config from {previous_version} to 0.15-1...")
|
||||
new_config = migrate_015_1(new_config)
|
||||
with open(config_file, "w") as f:
|
||||
yaml.dump(new_config, f)
|
||||
_write_config(config_file, yaml, new_config)
|
||||
previous_version = "0.15-1"
|
||||
|
||||
if previous_version < "0.16-0":
|
||||
logger.info(f"Migrating frigate config from {previous_version} to 0.16-0...")
|
||||
new_config = migrate_016_0(new_config)
|
||||
with open(config_file, "w") as f:
|
||||
yaml.dump(new_config, f)
|
||||
_write_config(config_file, yaml, new_config)
|
||||
previous_version = "0.16-0"
|
||||
|
||||
if previous_version < "0.17-0":
|
||||
logger.info(f"Migrating frigate config from {previous_version} to 0.17-0...")
|
||||
new_config = migrate_017_0(new_config)
|
||||
with open(config_file, "w") as f:
|
||||
yaml.dump(new_config, f)
|
||||
_write_config(config_file, yaml, new_config)
|
||||
previous_version = "0.17-0"
|
||||
|
||||
if previous_version < "0.18-0":
|
||||
logger.info(f"Migrating frigate config from {previous_version} to 0.18-0...")
|
||||
new_config = migrate_018_0(new_config)
|
||||
with open(config_file, "w") as f:
|
||||
yaml.dump(new_config, f)
|
||||
_write_config(config_file, yaml, new_config)
|
||||
previous_version = "0.18-0"
|
||||
|
||||
if previous_version < "0.19-0":
|
||||
logger.info(f"Migrating frigate config from {previous_version} to 0.19-0...")
|
||||
new_config = migrate_019_0(new_config)
|
||||
with open(config_file, "w") as f:
|
||||
yaml.dump(new_config, f)
|
||||
_write_config(config_file, yaml, new_config)
|
||||
previous_version = "0.19-0"
|
||||
|
||||
if needs_models:
|
||||
logger.info("Migrating frigate detectors and model to models...")
|
||||
new_config = migrate_models(new_config)
|
||||
with open(config_file, "w") as f:
|
||||
yaml.dump(new_config, f)
|
||||
_write_config(config_file, yaml, new_config)
|
||||
|
||||
if needs_detector_rename:
|
||||
logger.info("Migrating renamed frigate detectors...")
|
||||
new_config = rename_hailo_detector(new_config)
|
||||
with open(config_file, "w") as f:
|
||||
yaml.dump(new_config, f)
|
||||
_write_config(config_file, yaml, new_config)
|
||||
|
||||
logger.info("Finished frigate config migration...")
|
||||
|
||||
|
||||
@@ -318,7 +318,6 @@ def post_process_yolox(
|
||||
def get_ort_providers(
|
||||
force_cpu: bool = False,
|
||||
device: str | None = "AUTO",
|
||||
requires_fp16: bool = False,
|
||||
) -> tuple[list[str], list[dict[str, Any]]]:
|
||||
if force_cpu:
|
||||
return (
|
||||
@@ -357,8 +356,8 @@ def get_ort_providers(
|
||||
options.append(
|
||||
{
|
||||
"device_id": device_id,
|
||||
"trt_fp16_enable": requires_fp16
|
||||
and os.environ.get("USE_FP16", "True") != "False",
|
||||
"trt_fp16_enable": os.environ.get("USE_FP16", "False").lower()
|
||||
== "true",
|
||||
"trt_timing_cache_enable": True,
|
||||
"trt_engine_cache_enable": True,
|
||||
"trt_timing_cache_path": os.path.join(
|
||||
|
||||
Generated
+32
-32
@@ -45,15 +45,15 @@
|
||||
"copy-to-clipboard": "^4.0.2",
|
||||
"date-fns": "^4.4.0",
|
||||
"date-fns-tz": "^3.2.0",
|
||||
"framer-motion": "^14.9.1",
|
||||
"framer-motion": "^14.10.0",
|
||||
"hls.js": "^1.7.4",
|
||||
"i18next": "^26.7.1",
|
||||
"i18next": "^26.7.2",
|
||||
"i18next-http-backend": "^4.0.10",
|
||||
"idb-keyval": "^6.3.0",
|
||||
"js-yaml": "^5.5.0",
|
||||
"konva": "^10.9.1",
|
||||
"lodash": "^4.18.1",
|
||||
"lucide-react": "^1.69.0",
|
||||
"lucide-react": "^1.70.0",
|
||||
"monaco-yaml": "^5.5.1",
|
||||
"next-themes": "^0.4.6",
|
||||
"nosleep.js": "^0.12.0",
|
||||
@@ -65,7 +65,7 @@
|
||||
"react-dropzone": "^20.1.4",
|
||||
"react-grid-layout": "^2.3.0",
|
||||
"react-hook-form": "^7.91.0",
|
||||
"react-i18next": "^17.1.12",
|
||||
"react-i18next": "^17.1.13",
|
||||
"react-icons": "^5.7.0",
|
||||
"react-konva": "^19.3.2",
|
||||
"react-markdown": "^10.1.0",
|
||||
@@ -87,7 +87,7 @@
|
||||
},
|
||||
"devDependencies": {
|
||||
"@eslint/js": "^10.0.1",
|
||||
"@playwright/test": "^1.64.1",
|
||||
"@playwright/test": "^1.65.0",
|
||||
"@types/js-yaml": "^4.0.9",
|
||||
"@types/lodash": "^4.17.27",
|
||||
"@types/node": "^26.6.6",
|
||||
@@ -1714,13 +1714,13 @@
|
||||
}
|
||||
},
|
||||
"node_modules/@playwright/test": {
|
||||
"version": "1.64.1",
|
||||
"resolved": "https://registry.npmjs.org/@playwright/test/-/test-1.64.1.tgz",
|
||||
"integrity": "sha512-IaYGml/nSOiZWROfPtuIL7neuu9qDOZ3TNvNljUIrhDMxiGTgPjzj/g9p9RxpS2tSepPDRY+UwAaEeiKtUvIAg==",
|
||||
"version": "1.65.0",
|
||||
"resolved": "https://registry.npmjs.org/@playwright/test/-/test-1.65.0.tgz",
|
||||
"integrity": "sha512-iGURLGuKDLakSEl7Pgfv8h7f+XLT+V861Wfhz0zlEl5nyFuKUopdkFBPKqTllTUp06aOwPuH8iuUEb3yp3MpbQ==",
|
||||
"dev": true,
|
||||
"license": "Apache-2.0",
|
||||
"dependencies": {
|
||||
"playwright": "1.64.1"
|
||||
"playwright": "1.65.0"
|
||||
},
|
||||
"bin": {
|
||||
"playwright": "cli.js"
|
||||
@@ -5596,12 +5596,12 @@
|
||||
}
|
||||
},
|
||||
"node_modules/framer-motion": {
|
||||
"version": "14.9.1",
|
||||
"resolved": "https://registry.npmjs.org/framer-motion/-/framer-motion-14.9.1.tgz",
|
||||
"integrity": "sha512-WDWNT2ixHdmpvGg02BZWI/SWwRYzPiMUf4aIkTBgrxxD8RYFdZO3fCM4pnLMo6j8mUpj0+BuJRHQBChI4v3Anw==",
|
||||
"version": "14.10.0",
|
||||
"resolved": "https://registry.npmjs.org/framer-motion/-/framer-motion-14.10.0.tgz",
|
||||
"integrity": "sha512-e8MjIet+IJKohYxBj8TAk9Mg5cD0HiOX87qde1YTPL67gI2ao074O6yBtQnIcWCk+bTX7XX4litepufLiYPrLg==",
|
||||
"license": "MIT",
|
||||
"dependencies": {
|
||||
"motion-dom": "14.9.1",
|
||||
"motion-dom": "14.10.0",
|
||||
"motion-utils": "14.7.0",
|
||||
"tslib": "^2.4.0"
|
||||
},
|
||||
@@ -6009,9 +6009,9 @@
|
||||
}
|
||||
},
|
||||
"node_modules/i18next": {
|
||||
"version": "26.7.1",
|
||||
"resolved": "https://registry.npmjs.org/i18next/-/i18next-26.7.1.tgz",
|
||||
"integrity": "sha512-cX+3RRVczel/V2kCMTACckSGSpqgtWYEA6beXNYVms7dOQiHojdPYHD26lT4IKimNeoGWrLvtPgjftbRvmZJVw==",
|
||||
"version": "26.7.2",
|
||||
"resolved": "https://registry.npmjs.org/i18next/-/i18next-26.7.2.tgz",
|
||||
"integrity": "sha512-RsOY3BeaCDdkk52NeJSFAXELQ2rQXBO/tOub6L8ue2ICPfR/FRDWDOfEDg4xMPd1SqO5goVYQ6i72qWEUB/PPw==",
|
||||
"funding": [
|
||||
{
|
||||
"type": "individual",
|
||||
@@ -6996,9 +6996,9 @@
|
||||
}
|
||||
},
|
||||
"node_modules/lucide-react": {
|
||||
"version": "1.69.0",
|
||||
"resolved": "https://registry.npmjs.org/lucide-react/-/lucide-react-1.69.0.tgz",
|
||||
"integrity": "sha512-UTFECm4yEgkgMN0kfLuxWcmlk+4u02Jrqehgk3ZXRx/Ud68OpP7hjkp67bvkBHmIpMaZCIveF1QDYeupO7XmYQ==",
|
||||
"version": "1.70.0",
|
||||
"resolved": "https://registry.npmjs.org/lucide-react/-/lucide-react-1.70.0.tgz",
|
||||
"integrity": "sha512-gCTPPN1MlQUBtUgdsUv+0CAUj+X8hifRUiRBw4E5v7lvDIOIpx0EZezdGQMSimS8pKA6esvullJub4GYr0mCHA==",
|
||||
"license": "ISC",
|
||||
"peerDependencies": {
|
||||
"@types/react": "*",
|
||||
@@ -8073,9 +8073,9 @@
|
||||
}
|
||||
},
|
||||
"node_modules/motion-dom": {
|
||||
"version": "14.9.1",
|
||||
"resolved": "https://registry.npmjs.org/motion-dom/-/motion-dom-14.9.1.tgz",
|
||||
"integrity": "sha512-2eCCGXH2Yv09lM8GHK3TdfChrcvHxzVY19LD9VraoHEaQl5+PuGnTNnmJaBAR+5MdxUVdsLFXygFJjR9RwRSxw==",
|
||||
"version": "14.10.0",
|
||||
"resolved": "https://registry.npmjs.org/motion-dom/-/motion-dom-14.10.0.tgz",
|
||||
"integrity": "sha512-k0LwJZ1R5xCvqrScszO4ywuZP52lRcT3QO0IUovgczom9ZHb0BaRLOX6M2O40q7TrSLvzX/fIHHmDm8TUMt3fg==",
|
||||
"license": "MIT",
|
||||
"dependencies": {
|
||||
"motion-utils": "14.7.0"
|
||||
@@ -8537,13 +8537,13 @@
|
||||
}
|
||||
},
|
||||
"node_modules/playwright": {
|
||||
"version": "1.64.1",
|
||||
"resolved": "https://registry.npmjs.org/playwright/-/playwright-1.64.1.tgz",
|
||||
"integrity": "sha512-HCrVhHr5QQuwYoswEUGTb7gGp522yflbv/ST8nF4sFWGuLjQCt17Q/EubpGGSNKYn1tsTCPZxqHhHCfVURu7UA==",
|
||||
"version": "1.65.0",
|
||||
"resolved": "https://registry.npmjs.org/playwright/-/playwright-1.65.0.tgz",
|
||||
"integrity": "sha512-F3AMWcR44qMBt4kq+6Kb6mX7bjGXshktKFOPULd7reED6ZJWevjbNRqysP7q+AATE9kdPEZBibex1BYsvpVoeA==",
|
||||
"dev": true,
|
||||
"license": "Apache-2.0",
|
||||
"dependencies": {
|
||||
"playwright-core": "1.64.1"
|
||||
"playwright-core": "1.65.0"
|
||||
},
|
||||
"bin": {
|
||||
"playwright": "cli.js"
|
||||
@@ -8553,9 +8553,9 @@
|
||||
}
|
||||
},
|
||||
"node_modules/playwright-core": {
|
||||
"version": "1.64.1",
|
||||
"resolved": "https://registry.npmjs.org/playwright-core/-/playwright-core-1.64.1.tgz",
|
||||
"integrity": "sha512-BC4s8y6SEVA6Q1rSxZTto8hI1KUI3xXlHGZgvGqP4IqPdI/a6FX9H29IDNz7vKo9l21b6LfYosScPL6M3xxyDQ==",
|
||||
"version": "1.65.0",
|
||||
"resolved": "https://registry.npmjs.org/playwright-core/-/playwright-core-1.65.0.tgz",
|
||||
"integrity": "sha512-vNSKgH6YqtN7cdT/L334zo0a+YwkrpicyrHJlWWPIm/Q+Nn2SWInil01oT0mbu96U4GgoRtCYQD9CzJQgju+lA==",
|
||||
"dev": true,
|
||||
"license": "Apache-2.0",
|
||||
"bin": {
|
||||
@@ -9077,9 +9077,9 @@
|
||||
}
|
||||
},
|
||||
"node_modules/react-i18next": {
|
||||
"version": "17.1.12",
|
||||
"resolved": "https://registry.npmjs.org/react-i18next/-/react-i18next-17.1.12.tgz",
|
||||
"integrity": "sha512-Gf8riCKF/EmqQ8I1g0N5pJOLvJCkAXGAEkzOuRNHc/sBGL3dfE3WZ7+sDLrUo/kw/xdug3g48jo7M+s/SKf0HQ==",
|
||||
"version": "17.1.13",
|
||||
"resolved": "https://registry.npmjs.org/react-i18next/-/react-i18next-17.1.13.tgz",
|
||||
"integrity": "sha512-ZxhOSlyKSKPtbUNPLwYMmE3w3bhoc0jaZJZ6bc7wGOT6cLbrbs//6m01HxCLtFdSjHvFpwFwoPhxxzIlHSs9cw==",
|
||||
"license": "MIT",
|
||||
"dependencies": {
|
||||
"@babel/runtime": "^7.29.7",
|
||||
|
||||
+5
-5
@@ -57,15 +57,15 @@
|
||||
"copy-to-clipboard": "^4.0.2",
|
||||
"date-fns": "^4.4.0",
|
||||
"date-fns-tz": "^3.2.0",
|
||||
"framer-motion": "^14.9.1",
|
||||
"framer-motion": "^14.10.0",
|
||||
"hls.js": "^1.7.4",
|
||||
"i18next": "^26.7.1",
|
||||
"i18next": "^26.7.2",
|
||||
"i18next-http-backend": "^4.0.10",
|
||||
"idb-keyval": "^6.3.0",
|
||||
"js-yaml": "^5.5.0",
|
||||
"konva": "^10.9.1",
|
||||
"lodash": "^4.18.1",
|
||||
"lucide-react": "^1.69.0",
|
||||
"lucide-react": "^1.70.0",
|
||||
"monaco-yaml": "^5.5.1",
|
||||
"next-themes": "^0.4.6",
|
||||
"nosleep.js": "^0.12.0",
|
||||
@@ -77,7 +77,7 @@
|
||||
"react-dropzone": "^20.1.4",
|
||||
"react-grid-layout": "^2.3.0",
|
||||
"react-hook-form": "^7.91.0",
|
||||
"react-i18next": "^17.1.12",
|
||||
"react-i18next": "^17.1.13",
|
||||
"react-icons": "^5.7.0",
|
||||
"react-konva": "^19.3.2",
|
||||
"react-markdown": "^10.1.0",
|
||||
@@ -99,7 +99,7 @@
|
||||
},
|
||||
"devDependencies": {
|
||||
"@eslint/js": "^10.0.1",
|
||||
"@playwright/test": "^1.64.1",
|
||||
"@playwright/test": "^1.65.0",
|
||||
"@types/js-yaml": "^4.0.9",
|
||||
"@types/lodash": "^4.17.27",
|
||||
"@types/node": "^26.6.6",
|
||||
|
||||
@@ -1289,8 +1289,6 @@
|
||||
},
|
||||
"apiKey": {
|
||||
"title": "Ключ API Frigate+",
|
||||
"validated": "Ключ API Frigate+ выяўлены і правераны",
|
||||
"notValidated": "Ключ API Frigate+ не выяўлены або не правераны",
|
||||
"desc": "Ключ API Frigate+ уключае інтэграцыю з сэрвісам Frigate+.",
|
||||
"plusLink": "Падрабязней пра Frigate+"
|
||||
},
|
||||
|
||||
@@ -1086,8 +1086,6 @@
|
||||
},
|
||||
"apiKey": {
|
||||
"title": "Frigate+ API ključ",
|
||||
"validated": "Frigate+ API ključ je detektovan i validiran",
|
||||
"notValidated": "Frigate+ API ključ nije detektovan ili nije validiran",
|
||||
"desc": "Frigate+ API ključ omogućava integraciju sa uslugom Frigate+.",
|
||||
"plusLink": "Pročitajte više o Frigate+"
|
||||
},
|
||||
|
||||
@@ -714,9 +714,14 @@
|
||||
"apiKey": {
|
||||
"plusLink": "Llegeix més sobre Frigate+",
|
||||
"title": "Clau API de Frigate+",
|
||||
"validated": "La clau API de Frigate+ ha estat detectada i validada",
|
||||
"notValidated": "La clau API de Frigate+ no ha estat detectada o no ha estat validada",
|
||||
"desc": "La clau API de Frigate+ habilita la integració amb el servei Frigate+."
|
||||
"desc": "La clau API de Frigate+ habilita la integració amb el servei Frigate+.",
|
||||
"formatValid": "S'ha detectat la clau API Frigate+ i coincideix amb el format esperat",
|
||||
"formatInvalid": "La clau API Frigate+ no es detecta o no coincideix amb el format esperat",
|
||||
"validate": "Valida amb Frigate+",
|
||||
"validating": "S'està validant…",
|
||||
"serverValid": "Frigate+ ha acceptat aquesta clau API",
|
||||
"serverInvalid": "Frigate+ ha rebutjat aquesta clau API",
|
||||
"serverUnreachable": "No s'ha pogut arribar a Frigate+ per validar aquesta clau API"
|
||||
},
|
||||
"unsavedChanges": "Canvis dels paràmetres de Frigate+ sense desar",
|
||||
"title": "Paràmetres de Frigate+",
|
||||
|
||||
@@ -58,9 +58,7 @@
|
||||
"cleanCopyWarning": "Některé kamery mají snímky zakázané"
|
||||
},
|
||||
"apiKey": {
|
||||
"notValidated": "API klíč Frigate+ nebyl rozpoznán nebo nebyl ověřen",
|
||||
"plusLink": "Přečtěte si více o Frigate+",
|
||||
"validated": "API klíč Frigate+ byl rozpoznán a ověřen",
|
||||
"desc": "API klíč Frigate+ umožňuje integraci se službou Frigate+.",
|
||||
"title": "Frigate+ API Klíč"
|
||||
},
|
||||
|
||||
@@ -639,8 +639,6 @@
|
||||
"apiKey": {
|
||||
"title": "Frigate+ API Key",
|
||||
"desc": "Der Frigate+ API Key aktiviert die Integration des Frigate+ Dienstes.",
|
||||
"validated": "Frigate+ API Key erkannt und validiert",
|
||||
"notValidated": "Frigate+ API Key nicht erkannt und validiert",
|
||||
"plusLink": "Lese mehr zu Frigate+"
|
||||
},
|
||||
"snapshotConfig": {
|
||||
|
||||
@@ -1285,8 +1285,13 @@
|
||||
},
|
||||
"apiKey": {
|
||||
"title": "Frigate+ API Key",
|
||||
"validated": "Frigate+ API key is detected and validated",
|
||||
"notValidated": "Frigate+ API key is not detected or not validated",
|
||||
"formatValid": "Frigate+ API key is detected and matches the expected format",
|
||||
"formatInvalid": "Frigate+ API key is not detected or does not match the expected format",
|
||||
"validate": "Validate with Frigate+",
|
||||
"validating": "Validating…",
|
||||
"serverValid": "Frigate+ accepted this API key",
|
||||
"serverInvalid": "Frigate+ rejected this API key",
|
||||
"serverUnreachable": "Unable to reach Frigate+ to validate this API key",
|
||||
"desc": "The Frigate+ API key enables integration with the Frigate+ service.",
|
||||
"plusLink": "Read more about Frigate+"
|
||||
},
|
||||
|
||||
@@ -621,10 +621,8 @@
|
||||
"title": "Configuración de Frigate+",
|
||||
"apiKey": {
|
||||
"title": "Clave API de Frigate+",
|
||||
"notValidated": "La clave API de Frigate+ no ha sido detectada o no ha sido validada",
|
||||
"plusLink": "Lee más sobre Frigate+",
|
||||
"desc": "La clave API de Frigate+ permite la integración con el servicio Frigate+.",
|
||||
"validated": "La clave API de Frigate+ ha sido detectada y validada"
|
||||
"desc": "La clave API de Frigate+ permite la integración con el servicio Frigate+."
|
||||
},
|
||||
"snapshotConfig": {
|
||||
"title": "Configuración de instantáneas",
|
||||
|
||||
@@ -76,7 +76,8 @@
|
||||
"24hour": "dd. MMM yyyy, HH:mm"
|
||||
},
|
||||
"formattedTimestampMonthDay": "dd. MMM",
|
||||
"never": "Mitte kunagi"
|
||||
"never": "Mitte kunagi",
|
||||
"custom": "Kohanadatud…"
|
||||
},
|
||||
"menu": {
|
||||
"user": {
|
||||
|
||||
@@ -1143,11 +1143,9 @@
|
||||
},
|
||||
"frigatePlus": {
|
||||
"apiKey": {
|
||||
"notValidated": "کلید API Frigate+ شناسایی نشده یا معتبرسازی نشده است",
|
||||
"desc": "کلید API Frigate+ امکان یکپارچهسازی با سرویس Frigate+ را فراهم میکند.",
|
||||
"plusLink": "دربارهٔ Frigate+ بیشتر بخوانید",
|
||||
"title": "کلید API فرigate+",
|
||||
"validated": "کلید API فرigate+ شناسایی و تأیید شد"
|
||||
"title": "کلید API فرigate+"
|
||||
},
|
||||
"snapshotConfig": {
|
||||
"title": "پیکربندی عکس فوری",
|
||||
|
||||
@@ -82,7 +82,8 @@
|
||||
"inProgress": "En cours",
|
||||
"invalidStartTime": "Heure de début invalide",
|
||||
"invalidEndTime": "Heure de fin invalide",
|
||||
"never": "Jamais"
|
||||
"never": "Jamais",
|
||||
"custom": "Personnalisé…"
|
||||
},
|
||||
"button": {
|
||||
"apply": "Appliquer",
|
||||
|
||||
@@ -7,11 +7,11 @@
|
||||
"label": "Supprimer le groupe de caméras",
|
||||
"confirm": {
|
||||
"title": "Confirmez la suppression",
|
||||
"desc": "Êtes-vous sûr de vouloir supprimer le groupe de caméras <em>{{name}}</em> ?"
|
||||
"desc": "Êtes-vous sûr de vouloir supprimer le groupe de caméras <em>{{name}}</em>?"
|
||||
}
|
||||
},
|
||||
"name": {
|
||||
"placeholder": "Saisissez un nom.",
|
||||
"placeholder": "Saisissez un nom…",
|
||||
"label": "Nom",
|
||||
"errorMessage": {
|
||||
"mustLeastCharacters": "Le nom du groupe de caméras doit comporter au moins 2 caractères.",
|
||||
|
||||
@@ -87,7 +87,8 @@
|
||||
"placeholder": "Sélectionner un dossier",
|
||||
"newCaseOption": "Créer un nouveau cas",
|
||||
"newCaseNamePlaceholder": "Nouveau nom de cas",
|
||||
"newCaseDescriptionPlaceholder": "Description de cas"
|
||||
"newCaseDescriptionPlaceholder": "Description de cas",
|
||||
"nameRequired": "Nom du dossier requis"
|
||||
},
|
||||
"queueing": "Mise en file d'attente de l'export...",
|
||||
"tabs": {
|
||||
@@ -116,7 +117,9 @@
|
||||
"selectWithActivity": "Caméras avec des objets suivis",
|
||||
"selectGroup": "Sélectionner un groupe",
|
||||
"noMatchingCameras": "Aucune caméra ne correspond à votre recherche",
|
||||
"selectedCount": "{{selected}} / {{total}} sélectionnées"
|
||||
"selectedCount": "{{selected}} / {{total}} sélectionnées",
|
||||
"noCamerasSelected": "Sélectionner au moins une caméra",
|
||||
"caseNameDefault": "Export Caméra multiple - {{date}}"
|
||||
},
|
||||
"multi": {
|
||||
"title_one": "Export {{count}} revue",
|
||||
|
||||
@@ -21,7 +21,7 @@
|
||||
"title": "Toutes les dates",
|
||||
"short": "Dates"
|
||||
},
|
||||
"selectPreset": "Sélectionnez un préréglage."
|
||||
"selectPreset": "Sélectionnez un préréglage…"
|
||||
},
|
||||
"more": "Plus de filtres",
|
||||
"reset": {
|
||||
@@ -105,7 +105,7 @@
|
||||
"allLogs": "Tous les journaux"
|
||||
},
|
||||
"recognizedLicensePlates": {
|
||||
"placeholder": "Tapez pour rechercher des plaques d'immatriculation.",
|
||||
"placeholder": "Tapez pour rechercher des plaques d'immatriculation…",
|
||||
"noLicensePlatesFound": "Aucune plaque d'immatriculation trouvée",
|
||||
"loading": "Chargement des plaques d'immatriculation reconnues…",
|
||||
"title": "Plaques d'immatriculation reconnues",
|
||||
|
||||
@@ -48,7 +48,7 @@
|
||||
},
|
||||
"labelmap": {
|
||||
"label": "Customisation Audio",
|
||||
"description": "Remplacements ou entrées de remappage à fusionner avec la table standard des libellés audio."
|
||||
"description": "Remplacements ou réassignations à fusionner aux étiquettes audio standard."
|
||||
}
|
||||
},
|
||||
"audio_transcription": {
|
||||
@@ -78,8 +78,8 @@
|
||||
"description": "Emplacement numérique contrôlant l'ordre de la caméra dans la disposition en vue à vol d'oiseau."
|
||||
},
|
||||
"modes": {
|
||||
"label": "Types d'activité",
|
||||
"description": "Types d’activité qui incluent des caméras dans Birdseye."
|
||||
"label": "Types d'événements",
|
||||
"description": "Types d'activité qui incluent les caméras dans Birdseye."
|
||||
}
|
||||
},
|
||||
"detect": {
|
||||
@@ -143,7 +143,7 @@
|
||||
},
|
||||
"scene": {
|
||||
"label": "Détecter la scène",
|
||||
"description": "L'environnement filmé par cette caméra, utilisé pour déterminer lequel des modèles configurés doit être exécuté sur celle-ci. Les caméras laissées sur « default » utilisent le modèle configuré avec une scène définie sur « default »."
|
||||
"description": "L'environnement filmé par cette caméra, utilisé pour choisir le modèle configuré à exécuter. Les caméras laissées sur « default » exécutent le modèle configuré avec la scène « default »."
|
||||
}
|
||||
},
|
||||
"face_recognition": {
|
||||
@@ -240,6 +240,30 @@
|
||||
"quality": {
|
||||
"label": "Qualité de la diffusion en direct",
|
||||
"description": "Qualité de l'encodage pour le flux jsmpeg (1 étant la plus élevée, 31 la plus faible)."
|
||||
},
|
||||
"transcode": {
|
||||
"label": "Flux transcodés",
|
||||
"description": "Flux en direct transcodé en qualité inférieur à la demande de go2rtc.",
|
||||
"enabled": {
|
||||
"label": "Activé les flux transcodés",
|
||||
"description": "ajouter un flux en direct de qualité inférieur que go2rtc transcode en temps réel pendant que quelqu'un regarde."
|
||||
},
|
||||
"source": {
|
||||
"label": "Flux source",
|
||||
"description": "flux à décoder par go2rtc. Par défaut le premier flux en direct."
|
||||
},
|
||||
"qualities": {
|
||||
"label": "Qualités",
|
||||
"description": "Un flux transcodé est ajouté par qualité.",
|
||||
"height": {
|
||||
"label": "Taille",
|
||||
"description": "Taille de sortie en pixels; le forma respect le ratio de la source."
|
||||
},
|
||||
"bitrate": {
|
||||
"label": "Bitrate",
|
||||
"description": "Cible et bitrate maximum de la vidéo en kbps."
|
||||
}
|
||||
}
|
||||
}
|
||||
},
|
||||
"lpr": {
|
||||
@@ -517,6 +541,41 @@
|
||||
"enabled_in_config": {
|
||||
"label": "État original de l'enregistrement",
|
||||
"description": "Indique si l'enregistrement était activé dans la configuration statique originale."
|
||||
},
|
||||
"sub": {
|
||||
"label": "Enregistrement flux secondaire",
|
||||
"description": "Paramètres pour enregistrer un second flux, de qualité moindre.",
|
||||
"enabled": {
|
||||
"label": "Activer l'enregistrement des flux secondaires",
|
||||
"description": "Activer l'enregistrement d'un flux secondaire, de qualité moindre pour une qualité adaptative et une rétention étendue."
|
||||
},
|
||||
"continuous": {
|
||||
"label": "Rétention continue des flux secondaires",
|
||||
"description": "Nombre de jour de rétention des enregistrements des flux secondaire sans prise en compte d'objet traqué ou de mouvement.",
|
||||
"days": {
|
||||
"label": "Jour de rétention",
|
||||
"description": "Combien de jour conserver les enregistrements."
|
||||
}
|
||||
},
|
||||
"motion": {
|
||||
"label": "Rétention des flux secondaires liés au mouvement",
|
||||
"description": "Nombre de jours de conservation des enregistrements des flux secondaires déclenchés par un mouvement.",
|
||||
"days": {
|
||||
"label": "Jours de rétention",
|
||||
"description": "Combien de jours conserver les enregistrements."
|
||||
}
|
||||
},
|
||||
"alerts": {
|
||||
"label": "Alerte rétention flux secondaires",
|
||||
"description": "Paramètres de conservation pour les flux secondaires liés aux alertes.",
|
||||
"days": {
|
||||
"label": "Jours de rétention",
|
||||
"description": "Nombre de jour de conservation des enregistrements lié à la détection d'événements."
|
||||
},
|
||||
"mode": {
|
||||
"label": "Mode de rétention"
|
||||
}
|
||||
}
|
||||
}
|
||||
},
|
||||
"review": {
|
||||
@@ -534,7 +593,7 @@
|
||||
},
|
||||
"required_zones": {
|
||||
"label": "Zones requises",
|
||||
"description": "Zones qu'un objet doit entrer pour être considéré comme une alerte. (Laisser vide afin d'autoriser n'importe quelle zone.)"
|
||||
"description": "Zones dans lesquelles un objet doit entrer pour être considéré comme une alarme; laisser vide pour définir l'entièreté de la zone"
|
||||
},
|
||||
"enabled_in_config": {
|
||||
"label": "État des alertes originales",
|
||||
@@ -544,13 +603,13 @@
|
||||
"label": "Temps de coupure des alertes",
|
||||
"description": "Délai en secondes après l'absence d'activité déclenchant une alerte avant de la couper."
|
||||
},
|
||||
"description": "Paramètres pour lesquels les objets suivis génèrent des alertes et comment ces alertes sont conservées."
|
||||
"description": "Paramètres définissant quels objets suivis génèrent des alertes et comment ces alertes sont conservées."
|
||||
},
|
||||
"detections": {
|
||||
"label": "Configuration des détections",
|
||||
"description": "Paramètres pour lesquels les objets suivis génèrent des détections (hors alerte) et comment les détections sont conservées.",
|
||||
"enabled": {
|
||||
"label": "Activer les détections",
|
||||
"label": "Activation des détections",
|
||||
"description": "Activer ou désactiver les événements de détection pour cette caméra."
|
||||
},
|
||||
"labels": {
|
||||
|
||||
@@ -88,8 +88,8 @@
|
||||
"label": "Qualité d'encodage"
|
||||
},
|
||||
"modes": {
|
||||
"label": "Types d'activité",
|
||||
"description": "Types d’activité qui incluent des caméras dans Birdseye."
|
||||
"label": "Types d'événements",
|
||||
"description": "Types d'activité qui incluent les caméras dans Birdseye."
|
||||
}
|
||||
},
|
||||
"detect": {
|
||||
@@ -98,7 +98,7 @@
|
||||
},
|
||||
"scene": {
|
||||
"label": "Détecter la scène",
|
||||
"description": "L'environnement filmé par cette caméra, utilisé pour déterminer lequel des modèles configurés doit être exécuté sur celle-ci. Les caméras laissées sur « default » utilisent le modèle configuré avec une scène définie sur « default »."
|
||||
"description": "L'environnement filmé par cette caméra, utilisé pour choisir le modèle configuré à exécuter. Les caméras laissées sur « default » exécutent le modèle configuré avec la scène « default »."
|
||||
}
|
||||
},
|
||||
"motion": {
|
||||
@@ -222,7 +222,7 @@
|
||||
},
|
||||
"labelmap": {
|
||||
"label": "Customisation Audio",
|
||||
"description": "Remplacements ou entrées de remappage à fusionner avec la table standard des libellés audio."
|
||||
"description": "Remplacements ou réassignations à fusionner aux étiquettes audio standard."
|
||||
}
|
||||
},
|
||||
"ffmpeg": {
|
||||
@@ -341,6 +341,41 @@
|
||||
"enabled_in_config": {
|
||||
"label": "État original de l'enregistrement",
|
||||
"description": "Indique si l'enregistrement était activé dans la configuration statique originale."
|
||||
},
|
||||
"sub": {
|
||||
"label": "Enregistrement flux secondaire",
|
||||
"description": "Paramètres pour enregistrer un second flux, de qualité moindre.",
|
||||
"enabled": {
|
||||
"label": "Activer l'enregistrement des flux secondaires",
|
||||
"description": "Activer l'enregistrement d'un flux secondaire, de qualité moindre pour une qualité adaptative et une rétention étendue."
|
||||
},
|
||||
"continuous": {
|
||||
"label": "Rétention continue des flux secondaires",
|
||||
"description": "Nombre de jour de rétention des enregistrements des flux secondaire sans prise en compte d'objet traqué ou de mouvement.",
|
||||
"days": {
|
||||
"label": "Jour de rétention",
|
||||
"description": "Combien de jour conserver les enregistrements."
|
||||
}
|
||||
},
|
||||
"motion": {
|
||||
"label": "Rétention des flux secondaires liés au mouvement",
|
||||
"description": "Nombre de jours de conservation des enregistrements des flux secondaires déclenchés par un mouvement.",
|
||||
"days": {
|
||||
"label": "Jours de rétention",
|
||||
"description": "Combien de jours conserver les enregistrements."
|
||||
}
|
||||
},
|
||||
"alerts": {
|
||||
"label": "Alerte rétention flux secondaires",
|
||||
"description": "Paramètres de conservation pour les flux secondaires liés aux alertes.",
|
||||
"days": {
|
||||
"label": "Jours de rétention",
|
||||
"description": "Nombre de jour de conservation des enregistrements lié à la détection d'événements."
|
||||
},
|
||||
"mode": {
|
||||
"label": "Mode de rétention"
|
||||
}
|
||||
}
|
||||
}
|
||||
},
|
||||
"review": {
|
||||
@@ -356,7 +391,7 @@
|
||||
},
|
||||
"required_zones": {
|
||||
"label": "Zones requises",
|
||||
"description": "Zones qu'un objet doit entrer pour être considéré comme une alerte. (Laisser vide afin d'autoriser n'importe quelle zone.)"
|
||||
"description": "Zones dans lesquelles un objet doit entrer pour être considéré comme une alarme; laisser vide pour définir l'entièreté de la zone"
|
||||
},
|
||||
"enabled_in_config": {
|
||||
"label": "État des alertes originales",
|
||||
@@ -366,13 +401,13 @@
|
||||
"label": "Temps de coupure des alertes",
|
||||
"description": "Délai en secondes après l'absence d'activité déclenchant une alerte avant de la couper."
|
||||
},
|
||||
"description": "Paramètres pour lesquels les objets suivis génèrent des alertes et comment ces alertes sont conservées."
|
||||
"description": "Paramètres définissant quels objets suivis génèrent des alertes et comment ces alertes sont conservées."
|
||||
},
|
||||
"detections": {
|
||||
"label": "Configuration des détections",
|
||||
"description": "Paramètres pour lesquels les objets suivis génèrent des détections (hors alerte) et comment les détections sont conservées.",
|
||||
"enabled": {
|
||||
"label": "Activer les détections"
|
||||
"label": "Activation des détections"
|
||||
},
|
||||
"labels": {
|
||||
"label": "Étiquettes de détection",
|
||||
@@ -415,5 +450,31 @@
|
||||
"description": "Une liste de préoccupations ou de notes supplémentaires que la GenAI devrait prendre en compte lors de l'évaluation de l'activité sur cette caméra."
|
||||
}
|
||||
}
|
||||
},
|
||||
"live": {
|
||||
"transcode": {
|
||||
"label": "Flux transcodés",
|
||||
"description": "Flux en direct transcodé en qualité inférieur à la demande de go2rtc.",
|
||||
"enabled": {
|
||||
"label": "Activé les flux transcodés",
|
||||
"description": "ajouter un flux en direct de qualité inférieur que go2rtc transcode en temps réel pendant que quelqu'un regarde."
|
||||
},
|
||||
"source": {
|
||||
"label": "Flux source",
|
||||
"description": "flux à décoder par go2rtc. Par défaut le premier flux en direct."
|
||||
},
|
||||
"qualities": {
|
||||
"label": "Qualités",
|
||||
"description": "Un flux transcodé est ajouté par qualité.",
|
||||
"height": {
|
||||
"label": "Taille",
|
||||
"description": "Taille de sortie en pixels; le forma respect le ratio de la source."
|
||||
},
|
||||
"bitrate": {
|
||||
"label": "Bitrate",
|
||||
"description": "Cible et bitrate maximum de la vidéo en kbps."
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
@@ -209,9 +209,7 @@
|
||||
},
|
||||
"frigatePlus": {
|
||||
"apiKey": {
|
||||
"notValidated": "La clé API Frigate+ n'est pas détectée ou n'est pas validée.",
|
||||
"title": "Clé API Frigate+",
|
||||
"validated": "La clé API Frigate+ est détectée et validée",
|
||||
"desc": "La clé API Frigate+ permet l'intégration avec le service Frigate+.",
|
||||
"plusLink": "En savoir plus sur Frigate+"
|
||||
},
|
||||
|
||||
@@ -573,8 +573,6 @@
|
||||
"title": "הגדרות +Frigate",
|
||||
"apiKey": {
|
||||
"title": "Frigate+ API מפתח",
|
||||
"validated": "מפתח ה-API של Frigate+ זוהה ואושר",
|
||||
"notValidated": "מפתח ה-API של Frigate+ לא זוהה או לא אומת",
|
||||
"desc": "מפתח ה-API של Frigate+ מאפשר אינטגרציה עם שירות Frigate+.",
|
||||
"plusLink": "קרא עוד על Frigate+"
|
||||
},
|
||||
|
||||
@@ -141,5 +141,7 @@
|
||||
"aircraft": "विमान",
|
||||
"bicycle": "साइकिल",
|
||||
"bellow": "गर्जना करना",
|
||||
"motorcycle": "मोटरसाइकिल"
|
||||
"motorcycle": "मोटरसाइकिल",
|
||||
"snicker": "खिलखिलाना",
|
||||
"choir": "गायक-वृन्द"
|
||||
}
|
||||
|
||||
@@ -897,8 +897,6 @@
|
||||
"title": "Frigate+ postavke",
|
||||
"apiKey": {
|
||||
"title": "Frigate+ API ključ",
|
||||
"validated": "Frigate+ API ključ je detektiran i validiran",
|
||||
"notValidated": "Frigate+ API ključ nije detektiran ili nije validiran",
|
||||
"desc": "Frigate+ API ključ omogućuje integraciju s Frigate+ uslugom.",
|
||||
"plusLink": "Pročitajte više o Frigate+"
|
||||
},
|
||||
|
||||
@@ -246,8 +246,6 @@
|
||||
"apiKey": {
|
||||
"plusLink": "Tudjon meg többet a Frigate+-ról",
|
||||
"title": "Frigate+ API kulcs",
|
||||
"validated": "Frigate+ API kulcs észlelve és ellenőrizve",
|
||||
"notValidated": "Frigate+ API kulcs nem található vagy nem ellenőrzött",
|
||||
"desc": "A Frigate+ API kulcs lehetővé teszi az integrációt a Frigate+ szolgáltatással."
|
||||
},
|
||||
"snapshotConfig": {
|
||||
|
||||
@@ -1310,8 +1310,6 @@
|
||||
},
|
||||
"apiKey": {
|
||||
"title": "Kunci API Frigate+",
|
||||
"validated": "Kunci API Frigate+ terdeteksi dan tervalidasi",
|
||||
"notValidated": "Kunci API Frigate+ tidak terdeteksi atau tidak tervalidasi",
|
||||
"desc": "Kunci API Frigate+ memungkinkan integrasi dengan layanan Frigate+.",
|
||||
"plusLink": "Baca lebih lanjut tentang Frigate+"
|
||||
},
|
||||
|
||||
@@ -29,8 +29,6 @@
|
||||
},
|
||||
"apiKey": {
|
||||
"title": "Chiave API Frigate+",
|
||||
"validated": "La chiave API Frigate+ è stata rilevata e convalidata",
|
||||
"notValidated": "La chiave API Frigate+ non è stata rilevata o non è stata convalidata",
|
||||
"desc": "La chiave API Frigate+ consente l'integrazione con il servizio Frigate+.",
|
||||
"plusLink": "Scopri di più su Frigate+"
|
||||
},
|
||||
|
||||
@@ -734,8 +734,6 @@
|
||||
"title": "Frigate+ 設定",
|
||||
"apiKey": {
|
||||
"title": "Frigate+ API キー",
|
||||
"validated": "Frigate+ API キーが検出され、検証されました",
|
||||
"notValidated": "Frigate+ API キーが検出されないか、検証されていません",
|
||||
"desc": "Frigate+ API キーは Frigate+ サービスとの統合を有効にします。",
|
||||
"plusLink": "Frigate+ の詳細を読む"
|
||||
},
|
||||
|
||||
@@ -1193,8 +1193,6 @@
|
||||
},
|
||||
"apiKey": {
|
||||
"title": "Frigate+ API Key",
|
||||
"validated": "Frigate+ API key ត្រូវបានរកឃើញ និងផ្ទៀងផ្ទាត់",
|
||||
"notValidated": "Frigate+ API key មិនត្រូវបានរកឃើញ ឬមិនបានផ្ទៀងផ្ទាត់",
|
||||
"desc": "Frigate+ API key អនុញ្ញាតឱ្យមានការរួមបញ្ចូលជាមួយសេវាកម្ម Frigate+។",
|
||||
"plusLink": "អានបន្ថែមអំពី Frigate+"
|
||||
},
|
||||
|
||||
@@ -1373,8 +1373,6 @@
|
||||
},
|
||||
"apiKey": {
|
||||
"title": "Frigate+ API 키",
|
||||
"validated": "Frigate+ API 키가 확인 및 검증되었습니다",
|
||||
"notValidated": "Frigate+ API 키가 감지되지 않았거나 유효하지 않습니다",
|
||||
"desc": "Frigate+ API 키를 통해 Frigate+ 서비스와의 연동을 활성화합니다.",
|
||||
"plusLink": "Frigate+ 자세히 알아보기"
|
||||
},
|
||||
|
||||
@@ -657,8 +657,6 @@
|
||||
"title": "Frigate+ Nustatymai",
|
||||
"apiKey": {
|
||||
"title": "Frigate+ API raktas",
|
||||
"validated": "Frigate+ API raktas aptiktas ir patvirtintas",
|
||||
"notValidated": "Frigate+ API raktas neaptiktas ir nepatvirtintas",
|
||||
"desc": "Frigate+ API raktas įgalina integraciją su Frigate+ paslauga.",
|
||||
"plusLink": "Skaityti daugiau apie Frigate+"
|
||||
},
|
||||
|
||||
@@ -616,9 +616,7 @@
|
||||
},
|
||||
"frigatePlus": {
|
||||
"apiKey": {
|
||||
"notValidated": "Frigate+ API-nøkkel er ikke detektert eller validert",
|
||||
"title": "Frigate+ API-nøkkel",
|
||||
"validated": "Frigate+ API-nøkkel er detektert og validert",
|
||||
"desc": "Frigate+ API-nøkkelen muliggjør integrasjon med Frigate+ tjenesten.",
|
||||
"plusLink": "Les mer om Frigate+"
|
||||
},
|
||||
|
||||
@@ -618,9 +618,7 @@
|
||||
"apiKey": {
|
||||
"title": "Frigate+ API-sleutel",
|
||||
"plusLink": "Lees meer over Frigate+",
|
||||
"validated": "Frigate+ API-sleutel is gedetecteerd en gevalideerd",
|
||||
"desc": "Met de Frigate+ API-sleutel is integratie met de Frigate+ service mogelijk.",
|
||||
"notValidated": "Frigate+ API-sleutel wordt niet gedetecteerd of niet gevalideerd"
|
||||
"desc": "Met de Frigate+ API-sleutel is integratie met de Frigate+ service mogelijk."
|
||||
},
|
||||
"snapshotConfig": {
|
||||
"title": "Snapshot-configuratie",
|
||||
|
||||
@@ -621,9 +621,7 @@
|
||||
"title": "Ustawienia Frigate+",
|
||||
"apiKey": {
|
||||
"title": "Klucz API Frigate+",
|
||||
"validated": "Klucz API Frigate+ został wykryty i zweryfikowany",
|
||||
"plusLink": "Dowiedz się więcej o Frigate+",
|
||||
"notValidated": "Klucz API Frigate+ nie został wykryty lub nie został zweryfikowany",
|
||||
"desc": "Klucz API Frigate+ umożliwia integrację z usługą Frigate+."
|
||||
},
|
||||
"snapshotConfig": {
|
||||
|
||||
@@ -719,8 +719,6 @@
|
||||
"title": "Configurações do Frigate+",
|
||||
"apiKey": {
|
||||
"title": "Chave de API do Frigate+",
|
||||
"validated": "A chave de API do Frigate+ detectada e validada",
|
||||
"notValidated": "Chave de API do Frigate+ não detectada ou não validada",
|
||||
"desc": "A chave de API do Frigate+ habilita a integração com o serviço do Frigate+.",
|
||||
"plusLink": "Leia mais sobre o Frigate+"
|
||||
},
|
||||
|
||||
@@ -173,8 +173,6 @@
|
||||
},
|
||||
"title": "Configurações Frigate+",
|
||||
"apiKey": {
|
||||
"validated": "A chave da API do Frigate+ foi detectada e validada",
|
||||
"notValidated": "A chave da API do Frigate+ não foi detectada ou não foi validada",
|
||||
"desc": "A chave de API do Frigate+ permite a integração com o serviço Frigate+.",
|
||||
"plusLink": "Saiba mais sobre o Frigate+",
|
||||
"title": "Chave de API do Frigate+"
|
||||
|
||||
@@ -715,9 +715,14 @@
|
||||
"apiKey": {
|
||||
"plusLink": "Citește mai multe despre Frigate+",
|
||||
"desc": "Cheia API Frigate+ permite integrarea cu serviciul Frigate+.",
|
||||
"validated": "Cheia API Frigate+ a fost detectată și validată",
|
||||
"title": "Cheie API Frigate+",
|
||||
"notValidated": "Cheia API Frigate+ nu este detectată sau validată"
|
||||
"formatValid": "Cheia API Frigate+ este detectată și corespunde formatului așteptat",
|
||||
"formatInvalid": "Cheia API Frigate+ nu este detectată sau nu corespunde formatului așteptat",
|
||||
"validate": "Validează cu Frigate+",
|
||||
"validating": "Se validează…",
|
||||
"serverValid": "Frigate+ a acceptat această cheie API",
|
||||
"serverInvalid": "Frigate+ a respins această cheie API",
|
||||
"serverUnreachable": "Nu s-a putut contacta Frigate+ pentru a valida această cheie API"
|
||||
},
|
||||
"snapshotConfig": {
|
||||
"title": "Configurare snapshot-uri",
|
||||
|
||||
@@ -631,8 +631,6 @@
|
||||
"title": "Настройки Frigate+",
|
||||
"apiKey": {
|
||||
"title": "Ключ API Frigate+",
|
||||
"validated": "Ключ API Frigate+ найден и проверен",
|
||||
"notValidated": "Ключ API Frigate+ не найден или не проверен",
|
||||
"desc": "Ключ API Frigate+ включает интеграцию с сервисом Frigate+.",
|
||||
"plusLink": "Подробнее про Frigate+"
|
||||
},
|
||||
|
||||
@@ -883,8 +883,6 @@
|
||||
"title": "Nastavenie Frigate+",
|
||||
"apiKey": {
|
||||
"title": "Frigate + API kľúč",
|
||||
"validated": "Frigate + API kľúč je detekovaný a overený",
|
||||
"notValidated": "Frigate + API kľúč nie je detekovaný alebo nie je overený",
|
||||
"desc": "Frigate+ API kľúč umožňuje integráciu s Frigate+ služby.",
|
||||
"plusLink": "Prečítajte si viac o Frigate+"
|
||||
},
|
||||
|
||||
@@ -1142,8 +1142,6 @@
|
||||
},
|
||||
"apiKey": {
|
||||
"title": "Frigate+ API ključ",
|
||||
"validated": "API ključ je zaznan in potrjen",
|
||||
"notValidated": "API ključ ni zaznan ali ni veljaven",
|
||||
"desc": "API ključ omogoča integracijo s storitvijo Frigate+.",
|
||||
"plusLink": "Preberi več o Frigate+"
|
||||
},
|
||||
|
||||
@@ -1 +1,368 @@
|
||||
{}
|
||||
{
|
||||
"time": {
|
||||
"untilForTime": "Do {{time}}",
|
||||
"untilForRestart": "Dok se Frigate ne ponovo pokrene.",
|
||||
"untilRestart": "Do ponovnog pokretanja",
|
||||
"never": "Nikada",
|
||||
"ago": "Pre {{timeAgo}}",
|
||||
"justNow": "Upravo sada",
|
||||
"today": "Danas",
|
||||
"yesterday": "Juče",
|
||||
"last7": "Poslednjih 7 dana",
|
||||
"last14": "Poslednjih 14 dana",
|
||||
"last30": "Poslednjih 30 dana",
|
||||
"thisWeek": "Ove nedelje",
|
||||
"lastWeek": "Prošle nedelje",
|
||||
"thisMonth": "Ovog meseca",
|
||||
"lastMonth": "Prošlog meseca",
|
||||
"5minutes": "5 minuta",
|
||||
"10minutes": "10 minuta",
|
||||
"30minutes": "30 minuta",
|
||||
"1hour": "1 sat",
|
||||
"12hours": "12 sati",
|
||||
"24hours": "24 sata",
|
||||
"custom": "Prilagođeno...",
|
||||
"pm": "popodne",
|
||||
"am": "pre podne",
|
||||
"yr": "{{time}} god.",
|
||||
"year_one": "{{time}} godina",
|
||||
"year_few": "{{time}} godine",
|
||||
"year_other": "{{time}} godina",
|
||||
"mo": "{{time}} mes.",
|
||||
"month_one": "{{time}} mesec",
|
||||
"month_few": "{{time}} meseca",
|
||||
"month_other": "{{time}} meseci",
|
||||
"d": "{{time}} d.",
|
||||
"day_one": "{{time}} dan",
|
||||
"day_few": "{{time}} dana",
|
||||
"day_other": "{{time}} dana",
|
||||
"h": "{{time}} č.",
|
||||
"hour_one": "{{time}} sat",
|
||||
"hour_few": "{{time}} sata",
|
||||
"hour_other": "{{time}} sati",
|
||||
"m": "{{time}} min.",
|
||||
"minute_one": "{{time}} minut",
|
||||
"minute_few": "{{time}} minuta",
|
||||
"minute_other": "{{time}} minuta",
|
||||
"s": "{{time}} s",
|
||||
"second_one": "{{time}} sekunda",
|
||||
"second_few": "{{time}} sekunde",
|
||||
"second_other": "{{time}} sekundi",
|
||||
"formattedTimestamp": {
|
||||
"12hour": "d. MMM, h:mm:ss aaa",
|
||||
"24hour": "d. MMM, HH:mm:ss"
|
||||
},
|
||||
"formattedTimestamp2": {
|
||||
"12hour": "dd.MM. h:mm:ssa",
|
||||
"24hour": "d. MMM, HH:mm:ss"
|
||||
},
|
||||
"formattedTimestampHourMinute": {
|
||||
"12hour": "h:mm aaa",
|
||||
"24hour": "HH:mm"
|
||||
},
|
||||
"formattedTimestampHourMinuteSecond": {
|
||||
"12hour": "h:mm:ss aaa",
|
||||
"24hour": "HH:mm:ss"
|
||||
},
|
||||
"formattedTimestampMonthDayHourMinute": {
|
||||
"12hour": "d. MMM, h:mm aaa",
|
||||
"24hour": "d. MMM, HH:mm"
|
||||
},
|
||||
"formattedTimestampMonthDayYear": {
|
||||
"12hour": "d. MMM yyyy.",
|
||||
"24hour": "d. MMM yyyy."
|
||||
},
|
||||
"formattedTimestampMonthDayYearHourMinute": {
|
||||
"12hour": "d. MMM yyyy, h:mm aaa",
|
||||
"24hour": "d. MMM yyyy, HH:mm"
|
||||
},
|
||||
"formattedTimestampMonthDay": "d. MMM",
|
||||
"formattedTimestampFilename": {
|
||||
"12hour": "dd-MM-yy-h-mm-ss-a",
|
||||
"24hour": "dd-MM-yy-HH-mm-ss"
|
||||
},
|
||||
"inProgress": "U toku",
|
||||
"invalidStartTime": "Nevažeće vreme početka",
|
||||
"invalidEndTime": "Nevažeće vreme završetka"
|
||||
},
|
||||
"commandMenu": {
|
||||
"action": {
|
||||
"toggleTheme": "Prebaci na svetli ili tamni režim"
|
||||
},
|
||||
"camera": {
|
||||
"review": "Pregled",
|
||||
"live": "Uživo"
|
||||
},
|
||||
"section": {
|
||||
"actions": "Akcije",
|
||||
"settings": "Podešavanja",
|
||||
"cameraGroups": "Grupe kamera",
|
||||
"cameras": "Kamere",
|
||||
"pages": "Stranice",
|
||||
"recent": "Nedavno"
|
||||
},
|
||||
"hint": "{{key}} ili / za otvaranje, Enter za pokretanje",
|
||||
"empty": "Nema rezultata",
|
||||
"placeholder": "Pretražite stranice, kamere, podešavanja i akcije",
|
||||
"description": "Pretražite stranice, kamere, podešavanja i akcije",
|
||||
"title": "Komandni meni"
|
||||
},
|
||||
"credentialField": {
|
||||
"savedPlaceholder": "Sačuvano — ostavite prazno da zadržite trenutno stanje"
|
||||
},
|
||||
"validation_errors": "Greške pri validaciji",
|
||||
"unit": {
|
||||
"speed": {
|
||||
"kph": "km/h",
|
||||
"mph": "mph"
|
||||
},
|
||||
"length": {
|
||||
"feet": "stopa",
|
||||
"meters": "metri"
|
||||
},
|
||||
"data": {
|
||||
"kbps": "kB/s",
|
||||
"mbps": "MB/s",
|
||||
"gbps": "GB/s",
|
||||
"kbph": "kB/sat",
|
||||
"mbph": "MB/sat",
|
||||
"gbph": "GB/sat"
|
||||
}
|
||||
},
|
||||
"label": {
|
||||
"back": "Idi nazad",
|
||||
"hide": "Sakrij {{item}}",
|
||||
"show": "Prikaži {{item}}",
|
||||
"ID": "ID",
|
||||
"none": "Ništa",
|
||||
"all": "Sve",
|
||||
"other": "Ostalo"
|
||||
},
|
||||
"list": {
|
||||
"two": "{{0}} i {{1}}",
|
||||
"many": "{{items}} i {{last}}",
|
||||
"separatorWithSpace": ", "
|
||||
},
|
||||
"field": {
|
||||
"optional": "Opciono",
|
||||
"internalID": "Interni ID koji Frigate koristi u konfiguraciji i bazi podataka"
|
||||
},
|
||||
"button": {
|
||||
"add": "Dodaj",
|
||||
"apply": "Primeni",
|
||||
"applying": "Primenjuje se…",
|
||||
"reset": "Resetuj",
|
||||
"undo": "Opozovi",
|
||||
"done": "Završeno",
|
||||
"enabled": "Omogućeno",
|
||||
"enable": "Omogući",
|
||||
"disabled": "Onemogućeno",
|
||||
"disable": "Onemogući",
|
||||
"save": "Sačuvaj",
|
||||
"saving": "Čuvanje…",
|
||||
"cancel": "Otkaži",
|
||||
"close": "Zatvori",
|
||||
"expand": "Proširi",
|
||||
"collapse": "Skupi",
|
||||
"clear": "Očisti",
|
||||
"copy": "Kopiraj",
|
||||
"copiedToClipboard": "Kopirano u privremenu memoriju",
|
||||
"back": "Nazad",
|
||||
"history": "Istorija",
|
||||
"fullscreen": "Ceo ekran",
|
||||
"exitFullscreen": "Izađi iz celog ekrana",
|
||||
"pictureInPicture": "Slika u slici",
|
||||
"twoWayTalk": "Dvosmerna komunikacija",
|
||||
"cameraAudio": "Zvuk kamere",
|
||||
"on": "Uključeno",
|
||||
"off": "Isključeno",
|
||||
"edit": "Izmeni",
|
||||
"copyCoordinates": "Kopiraj koordinate",
|
||||
"delete": "Obriši",
|
||||
"yes": "Da",
|
||||
"no": "Ne",
|
||||
"download": "Preuzmi",
|
||||
"info": "Informacije",
|
||||
"suspended": "Suspendovano",
|
||||
"unsuspended": "Aktiviraj ponovo",
|
||||
"play": "Pusti",
|
||||
"unselect": "Poništi izbor",
|
||||
"export": "Izvezi",
|
||||
"deleteNow": "Obriši odmah",
|
||||
"next": "Sledeće",
|
||||
"continue": "Nastavi",
|
||||
"modified": "Izmenjeno",
|
||||
"overridden": "Nadjačano",
|
||||
"resetToGlobal": "Vrati na globalna podešavanja",
|
||||
"resetToDefault": "Vrati na podrazumevano",
|
||||
"saveAll": "Sačuvaj sve",
|
||||
"savingAll": "Čuvanje svega…",
|
||||
"undoAll": "Opozovi sve",
|
||||
"retry": "Pokušaj ponovo",
|
||||
"reload": "Osveži"
|
||||
},
|
||||
"menu": {
|
||||
"system": "Sistem",
|
||||
"systemMetrics": "Stanje i metrike",
|
||||
"configuration": "Konfiguracija",
|
||||
"systemLogs": "Dnevnik",
|
||||
"profiles": "Profili",
|
||||
"settings": "Podešavanja",
|
||||
"configurationEditor": "Editor konfiguracije",
|
||||
"languages": "Jezici",
|
||||
"language": {
|
||||
"en": "Engleski (English)",
|
||||
"es": "Španski (Español)",
|
||||
"zhCN": "Pojednostavljeni kineski (简体中文)",
|
||||
"zhHant": "Tradicionalni kineski (繁體中文)",
|
||||
"hi": "Hindi (हिन्दी)",
|
||||
"fr": "Francuski (Français)",
|
||||
"ar": "Arapski (العربية)",
|
||||
"pt": "Portugalski (Português)",
|
||||
"ptBR": "Brazilski portugalski (Português brasileiro)",
|
||||
"ru": "Ruski (Русский)",
|
||||
"de": "Nemački (Deutsch)",
|
||||
"ja": "Japanski (日本語)",
|
||||
"tr": "Turski (Türkçe)",
|
||||
"it": "Italijanski (Italiano)",
|
||||
"nl": "Holandski (Nederlands)",
|
||||
"sv": "Švedski (Svenska)",
|
||||
"cs": "Češki (Čeština)",
|
||||
"nb": "Norveški bukmol (Norsk Bokmål)",
|
||||
"ko": "Korejski (한국어)",
|
||||
"vi": "Vijetnamski (Tiếng Việt)",
|
||||
"fa": "Persijski (فارسی)",
|
||||
"pl": "Poljski (Polski)",
|
||||
"uk": "Ukrajinski (Українська)",
|
||||
"be": "Beloruski (Беларуская)",
|
||||
"he": "Hebrejski (עברית)",
|
||||
"el": "Grčki (Ελληνικά)",
|
||||
"ro": "Rumunski (Română)",
|
||||
"hu": "Mađarski (Magyar)",
|
||||
"fi": "Finski (Suomi)",
|
||||
"da": "Danski (Dansk)",
|
||||
"sk": "Slovački (Slovenčina)",
|
||||
"yue": "Kantonski (粵語)",
|
||||
"th": "Tajlandski (ไทย)",
|
||||
"ca": "Katalonski (Català)",
|
||||
"hr": "Hrvatski (Hrvatski)",
|
||||
"bs": "Bosanski (Bosanski)",
|
||||
"sr": "Srpski (Српски)",
|
||||
"sl": "Slovenački (Slovenščina)",
|
||||
"lt": "Litvanski (Lietuvių)",
|
||||
"bg": "Bugarski (Български)",
|
||||
"gl": "Galicijski (Galego)",
|
||||
"id": "Indonežanski (Bahasa Indonesia)",
|
||||
"ur": "Urdu (اردو)",
|
||||
"withSystem": {
|
||||
"label": "Koristi sistemska podešavanja za jezik"
|
||||
}
|
||||
},
|
||||
"appearance": "Izgled",
|
||||
"darkMode": {
|
||||
"label": "Tamni režim",
|
||||
"light": "Svetli",
|
||||
"dark": "Tamni",
|
||||
"withSystem": {
|
||||
"label": "Koristi sistemska podešavanja za svetli ili tamni režim"
|
||||
}
|
||||
},
|
||||
"withSystem": "Sistem",
|
||||
"theme": {
|
||||
"label": "Tema",
|
||||
"blue": "Plava",
|
||||
"green": "Zelena",
|
||||
"nord": "Nord",
|
||||
"red": "Crvena",
|
||||
"highcontrast": "Visok kontrast",
|
||||
"default": "Podrazumevano"
|
||||
},
|
||||
"help": "Pomoć",
|
||||
"documentation": {
|
||||
"title": "Dokumentacija",
|
||||
"label": "Frigate dokumentacija"
|
||||
},
|
||||
"restart": "Ponovo pokreni Frigate",
|
||||
"live": {
|
||||
"title": "Uživo",
|
||||
"allCameras": "Sve kamere",
|
||||
"cameras": {
|
||||
"title": "Kamere",
|
||||
"count_one": "{{count}} kamera",
|
||||
"count_few": "{{count}} kamere",
|
||||
"count_other": "{{count}} kamera"
|
||||
}
|
||||
},
|
||||
"review": "Pregled",
|
||||
"explore": "Istraži",
|
||||
"export": "Izvezi",
|
||||
"actions": "Akcije",
|
||||
"uiPlayground": "Korisničko okruženje za testiranje",
|
||||
"features": "Funkcije",
|
||||
"faceLibrary": "Biblioteka lica",
|
||||
"classification": "Klasifikacija",
|
||||
"chat": "Ćaskanje",
|
||||
"user": {
|
||||
"title": "Korisnik",
|
||||
"account": "Nalog",
|
||||
"current": "Trenutni korisnik: {{user}}",
|
||||
"anonymous": "anoniman",
|
||||
"logout": "Odjavi se",
|
||||
"setPassword": "Postavi lozinku"
|
||||
}
|
||||
},
|
||||
"toast": {
|
||||
"copyUrlToClipboard": "URL je kopiran u privremenu memoriju.",
|
||||
"save": {
|
||||
"title": "Sačuvaj",
|
||||
"error": {
|
||||
"title": "Neuspešno čuvanje promena u konfiguraciji: {{errorMessage}}",
|
||||
"noMessage": "Neuspešno čuvanje promena u konfiguraciji"
|
||||
},
|
||||
"success": "Promene u konfiguraciji su uspešno sačuvane."
|
||||
}
|
||||
},
|
||||
"role": {
|
||||
"title": "Uloga",
|
||||
"admin": "Administrator",
|
||||
"viewer": "Posmatrač",
|
||||
"desc": "Administratori imaju potpun pristup svim funkcijama u Frigate korisničkom interfejsu. Gledaci su ograničeni na gledanje kamera, pregled stavki i istorijskih snimaka u korisničkom interfejsu."
|
||||
},
|
||||
"pagination": {
|
||||
"label": "straničenje",
|
||||
"previous": {
|
||||
"title": "Prethodna",
|
||||
"label": "Idi na prethodnu stranicu"
|
||||
},
|
||||
"next": {
|
||||
"title": "Sledeća",
|
||||
"label": "Idi na sledeću stranicu"
|
||||
},
|
||||
"more": "Više stranica"
|
||||
},
|
||||
"accessDenied": {
|
||||
"documentTitle": "Pristup odbijen - Frigate",
|
||||
"title": "Pristup odbijen",
|
||||
"desc": "Nemate dozvolu za pregled ove stranice."
|
||||
},
|
||||
"notFound": {
|
||||
"documentTitle": "Nije pronađeno - Frigate",
|
||||
"title": "404",
|
||||
"desc": "Stranica nije pronađena"
|
||||
},
|
||||
"error": {
|
||||
"title": "Ova stranica je prestala sa radom",
|
||||
"desc": "Došlo je do neočekivane greške na stranici. Osvežavanje obično rešava problem. Ako se greška ponavlja, kopirajte detalje i priložite ih diskusiji na GitHub-u.",
|
||||
"staleTitle": "Potrebno je osvežavanje",
|
||||
"staleDesc": "Deo aplikacije nije mogao da se učita, što obično znači da je Frigate ažuriran dok je ova kartica bila otvorena. Osvežite stranicu da biste prešli na novu verziju.",
|
||||
"partial": "Deo aplikacije je prestao sa radom.",
|
||||
"copyDetails": "Kopiraj detalje",
|
||||
"copyFailed": "Neuspešno kopiranje detalja u privremenu memoriju"
|
||||
},
|
||||
"selectItem": "Izaberi {{item}}",
|
||||
"readTheDocumentation": "Pročitaj dokumentaciju",
|
||||
"information": {
|
||||
"pixels": "{{area}} px"
|
||||
},
|
||||
"no_items": "Nema elemenata"
|
||||
}
|
||||
|
||||
@@ -1 +1,90 @@
|
||||
{}
|
||||
{
|
||||
"group": {
|
||||
"label": "Grupe kamera",
|
||||
"add": "Dodaj grupu kamera",
|
||||
"showAll": "Prikaži sve grupe kamera",
|
||||
"showLess": "Prikaži manje",
|
||||
"edit": "Izmeni grupu kamera",
|
||||
"editGroups": "Izmeni grupe kamera",
|
||||
"delete": {
|
||||
"label": "Obriši grupu kamera",
|
||||
"confirm": {
|
||||
"title": "Potvrdite brisanje",
|
||||
"desc": "Da li ste sigurni da želite da obrišete grupu kamera <em>{{name}}</em>?"
|
||||
}
|
||||
},
|
||||
"name": {
|
||||
"label": "Naziv",
|
||||
"placeholder": "Unesite naziv…",
|
||||
"errorMessage": {
|
||||
"mustLeastCharacters": "Naziv grupe kamera mora imati najmanje 2 karaktera.",
|
||||
"exists": "Naziv grupe kamera već postoji.",
|
||||
"nameMustNotPeriod": "Naziv grupe kamera ne sme da sadrži tačku.",
|
||||
"invalid": "Nevažeći naziv grupe kamera."
|
||||
}
|
||||
},
|
||||
"cameras": {
|
||||
"label": "Kamere",
|
||||
"desc": "Izaberite kamere za ovu grupu."
|
||||
},
|
||||
"icon": "Ikona",
|
||||
"success": "Grupa kamera ({{name}}) je sačuvana.",
|
||||
"camera": {
|
||||
"birdseye": "Birdseye",
|
||||
"setting": {
|
||||
"label": "Podešavanja strimovanja kamere",
|
||||
"title": "Podešavanja strimovanja za kameru {{cameraName}}",
|
||||
"desc": "Promenite opcije strimovanja uživo za kontrolnu tablu ove grupe kamera. <em>Ova podešavanja su specifična za uređaj/veb-pregledač.</em>",
|
||||
"audioIsAvailable": "Zvuk je dostupan za ovaj strim",
|
||||
"audioIsUnavailable": "Zvuk je nedostupan za ovaj strim",
|
||||
"audio": {
|
||||
"tips": {
|
||||
"title": "Zvuk mora biti poslat sa vaše kamere i konfigurisan u go2rtc-u za ovaj strim."
|
||||
}
|
||||
},
|
||||
"stream": "Strim",
|
||||
"placeholder": "Izaberite strim",
|
||||
"streamMethod": {
|
||||
"label": "Metod strimovanja",
|
||||
"placeholder": "Izaberite metod strimovanja",
|
||||
"method": {
|
||||
"noStreaming": {
|
||||
"label": "Bez strimovanja",
|
||||
"desc": "Slike sa kamere će se osvežavati samo jednom u minuti i neće biti strimovanja uživo."
|
||||
},
|
||||
"smartStreaming": {
|
||||
"label": "Pametno strimovanje (preporučeno)",
|
||||
"desc": "Pametno strimovanje će osvežavati sliku sa vaše kamere jednom u minuti kada nema uočljivih aktivnosti, kako bi se uštedeo protok podataka i resursi. Kada se detektuje aktivnost, prikaz se neprimetno prebacuje na strim uživo."
|
||||
},
|
||||
"continuousStreaming": {
|
||||
"label": "Neprekidno strimovanje",
|
||||
"desc": {
|
||||
"title": "Slika sa kamere će uvek biti strim uživo kada je vidljiva na kontrolnoj tabli, čak i ako se ne detektuje nikakva aktivnost.",
|
||||
"warning": "Neprekidno strimovanje može izazvati visoku potrošnju protoka podataka i probleme sa performansama. Koristite oprezno."
|
||||
}
|
||||
}
|
||||
}
|
||||
},
|
||||
"compatibilityMode": {
|
||||
"label": "Režim kompatibilnosti",
|
||||
"desc": "Omogućite ovu opciju samo ako strim uživo sa vaše kamere prikazuje izobličenja boja (artefakte) i ima dijagonalnu liniju na desnoj strani slike."
|
||||
}
|
||||
}
|
||||
}
|
||||
},
|
||||
"debug": {
|
||||
"options": {
|
||||
"label": "Podešavanja",
|
||||
"title": "Opcije",
|
||||
"showOptions": "Prikaži opcije",
|
||||
"hideOptions": "Sakrij opcije"
|
||||
},
|
||||
"boundingBox": "Uokvireni prostor",
|
||||
"timestamp": "Vremenska oznaka",
|
||||
"zones": "Zone",
|
||||
"mask": "Maska",
|
||||
"motion": "Pokret",
|
||||
"regions": "Regioni",
|
||||
"paths": "Putanje"
|
||||
}
|
||||
}
|
||||
|
||||
@@ -1 +1,222 @@
|
||||
{}
|
||||
{
|
||||
"restart": {
|
||||
"title": "Da li ste sigurni da želite da ponovo pokrenete Frigate?",
|
||||
"description": "Ovo će nakratko zaustaviti Frigate dok se ponovo pokreće.",
|
||||
"button": "Ponovo pokreni",
|
||||
"restarting": {
|
||||
"title": "Frigate se ponovo pokreće",
|
||||
"content": "Ova stranica će se ponovo učitati za {{countdown}} sekundi.",
|
||||
"button": "Osveži odmah"
|
||||
}
|
||||
},
|
||||
"explore": {
|
||||
"plus": {
|
||||
"submitToPlus": {
|
||||
"label": "Pošalji na Frigate+",
|
||||
"desc": "Objekti na lokacijama koje želite da izbegnete nisu lažno pozitivni rezultati. Njihovo slanje kao lažno pozitivnih zbuniće model."
|
||||
},
|
||||
"review": {
|
||||
"question": {
|
||||
"label": "Potvrdite ovu oznaku za Frigate Plu",
|
||||
"ask_a": "Da li je ovaj objekat <code>{{label}}</code>?",
|
||||
"ask_an": "Da li je ovaj objekat <code>{{label}}</code>?",
|
||||
"ask_full": "Da li je ovaj objekat <code>{{untranslatedLabel}}</code> ({{translatedLabel}})?"
|
||||
},
|
||||
"state": {
|
||||
"submitted": "Poslato"
|
||||
},
|
||||
"toast": {
|
||||
"error": "Slanje na Frigate+ nije uspelo. Proverite mrežnu vezu i pokušajte ponovo."
|
||||
}
|
||||
}
|
||||
},
|
||||
"video": {
|
||||
"viewInHistory": "Prikaži u istoriji"
|
||||
}
|
||||
},
|
||||
"export": {
|
||||
"name": {
|
||||
"placeholder": "Imenujte izvezeni snimak"
|
||||
},
|
||||
"stream": {
|
||||
"label": "Kvalitet",
|
||||
"auto": "Automatski",
|
||||
"main": "Izvorni",
|
||||
"sub": "Nizak",
|
||||
"autoDesc": "Koristi glavni strim, a u slučaju njegove nedostupnosti prebacuje se na sporedni strim nižeg kvaliteta.",
|
||||
"mainDesc": "Izvozi isključivo glavni strim izvornog kvaliteta. Vremenski periodi u kojima glavni strim nije bio dostupan biće izostavljeni.",
|
||||
"subDesc": "Izvozi isključivo sporedni strim nižeg kvaliteta. Vremenski periodi u kojima sporedni strim nije bio dostupan biće izostavljeni."
|
||||
},
|
||||
"case": {
|
||||
"newCaseOption": "Kreiraj novi slučaj",
|
||||
"newCaseNamePlaceholder": "Naziv novog slučaja",
|
||||
"newCaseDescriptionPlaceholder": "Opis slučaja",
|
||||
"label": "Slučaj",
|
||||
"placeholder": "Izaberite slučaj",
|
||||
"nameRequired": "Naziv slučaja je obavezan"
|
||||
},
|
||||
"select": "Izaberi",
|
||||
"export": "Izvezi",
|
||||
"queueing": "Dodavanje izvoza u red...",
|
||||
"selectOrExport": "Izaberi ili izvezi",
|
||||
"tabs": {
|
||||
"export": "Jedna kamera",
|
||||
"multiCamera": "Više kamera"
|
||||
},
|
||||
"multiCamera": {
|
||||
"timeRange": "Vremenski opseg",
|
||||
"selectFromTimeline": "Izaberite sa vremenske linije",
|
||||
"cameraSelection": "Kamere",
|
||||
"checkingActivity": "Provera aktivnosti kamera...",
|
||||
"cameraSelectionHelp": "Kamere sa praćenim objektima u ovom vremenskom rasponu su unapred izabrane",
|
||||
"searchOrSelectGroup": "Pretražite ili izaberite grupu kamera...",
|
||||
"selectAll": "Izaberi sve kamere",
|
||||
"clearSelection": "Poništi izbor",
|
||||
"selectWithActivity": "Kamere sa praćenim objektima",
|
||||
"selectGroup": "Izaberi grupu",
|
||||
"noMatchingCameras": "Nema kamera koje odgovaraju pretrazi",
|
||||
"selectedCount": "Izabrano: {{selected}} od {{total}}",
|
||||
"noCameras": "Nema dostupnih kamera",
|
||||
"noCamerasSelected": "Izaberite najmanje jednu kameru",
|
||||
"detectionCount_one": "1 praćeni objekat",
|
||||
"detectionCount_few": "{{count}} praćena objekata",
|
||||
"detectionCount_other": "{{count}} praćenih objekata",
|
||||
"nameLabel": "Naziv izvoza",
|
||||
"caseNameDefault": "Izvoz sa više kamera - {{date}}",
|
||||
"namePlaceholder": "Opcioni osnovni naziv za ove izvoze",
|
||||
"queueingButton": "Dodavanje izvoza u red...",
|
||||
"exportButton_one": "Izvezi 1 kameru",
|
||||
"exportButton_few": "Izvezi {{count}} kamere",
|
||||
"exportButton_other": "Izvezi {{count}} kamera"
|
||||
},
|
||||
"time": {
|
||||
"fromTimeline": "Izaberite sa vremenske linije",
|
||||
"lastHour_one": "Poslednji sat",
|
||||
"lastHour_few": "Poslednjih {{count}} sata",
|
||||
"lastHour_other": "Poslednjih {{count}} sati",
|
||||
"custom": "Prilagođeno",
|
||||
"start": {
|
||||
"title": "Vreme početka",
|
||||
"label": "Izaberite vreme početka"
|
||||
},
|
||||
"end": {
|
||||
"title": "Vreme završetka",
|
||||
"label": "Izaberite vreme završetka"
|
||||
}
|
||||
},
|
||||
"multi": {
|
||||
"title_one": "Izvezi 1 pregled",
|
||||
"title_few": "Izvezi {{count}} pregleda",
|
||||
"title_other": "Izvezi {{count}} pregleda",
|
||||
"description": "Izvezite svaki izabrani pregled. Svi izvezeni snimci biće grupisani pod jednim slučajem.",
|
||||
"descriptionNoCase": "Izvezite svaki izabrani pregled.",
|
||||
"caseNamePlaceholder": "Izvoz pregleda - {{date}}",
|
||||
"exportButton_one": "Izvezi 1 pregled",
|
||||
"exportButton_few": "Izvezi {{count}} pregleda",
|
||||
"exportButton_other": "Izvezi {{count}} pregleda",
|
||||
"exportingButton": "Izvoženje...",
|
||||
"toast": {
|
||||
"started_one": "Pokrenut 1 izvoz.",
|
||||
"started_few": "Pokrenuto {{count}} izvoza.",
|
||||
"started_other": "okrenuto {{count}} izvoza.",
|
||||
"partial": "Pokrenuto je {{successful}} od {{total}} izvoza. Neuspešno: {{failedItems}}",
|
||||
"failed": "Pokretanje {{total}} izvoza nije uspelo. Neuspešno: {{failedItems}}"
|
||||
}
|
||||
},
|
||||
"toast": {
|
||||
"success": "Izvoz je uspešno pokrenut. Pogledajte datoteku na stranici sa izvezenim snimcima.",
|
||||
"queued": "Izvoz je dodat u red. Pratite napredak na stranici sa izvezenim snimcima.",
|
||||
"view": "Prikaži",
|
||||
"batchSuccess_one": "Pokrenut 1 izvoz. Otvaranje slučaja...",
|
||||
"batchSuccess_few": "Pokrenuto {{count}} izvoza. Otvaranje slučaja...",
|
||||
"batchSuccess_other": "Pokrenuto {{count}} izvoza. Otvaranje slučaja...",
|
||||
"batchPartial": "Pokrenuto je {{successful}} od {{total}} izvoza. Neuspešne kamere: {{failedCameras}}",
|
||||
"batchFailed": "Pokretanje {{total}} izvoza nije uspelo. Neuspešne kamere: {{failedCameras}}",
|
||||
"batchQueuedSuccess_one": "Dodat 1 izvoz u red.",
|
||||
"batchQueuedSuccess_few": "Dodato {{count}} izvoza u red.",
|
||||
"batchQueuedSuccess_other": "Dodato {{count}} izvoza u red.",
|
||||
"batchQueuedPartial": "Dodato je {{successful}} od {{total}} izvoza u red. Neuspešne kamere: {{failedCameras}}",
|
||||
"batchQueueFailed": "Dodavanje {{total}} izvoza u red nije uspelo. Neuspešne kamere: {{failedCameras}}",
|
||||
"error": {
|
||||
"failed": "Dodavanje izvoza u red nije uspelo: {{error}}",
|
||||
"endTimeMustAfterStartTime": "Vreme završetka mora biti nakon vremena početka",
|
||||
"noValidTimeSelected": "Nije izabran važeći vremenski opseg"
|
||||
}
|
||||
},
|
||||
"fromTimeline": {
|
||||
"saveExport": "Sačuvaj izvezeni snimak",
|
||||
"queueingExport": "Dodavanje izvoza u red...",
|
||||
"previewExport": "Pregledaj izvezeni snimak",
|
||||
"useThisRange": "Koristi ovaj opseg"
|
||||
}
|
||||
},
|
||||
"streaming": {
|
||||
"label": "Strim",
|
||||
"restreaming": {
|
||||
"disabled": "Ponovno strimovanje nije omogućeno za ovu kameru.",
|
||||
"desc": {
|
||||
"title": "Podesite go2rtc za dodatne opcije prikaza uživo i zvuk za ovu kameru."
|
||||
}
|
||||
},
|
||||
"showStats": {
|
||||
"label": "Prikaži statistiku strima",
|
||||
"desc": "Omogućite ovu opciju da biste prikazali statistiku strima kao sloj preko video-izvora kamere."
|
||||
},
|
||||
"debugView": "Prikaz za otklanjanje grešaka"
|
||||
},
|
||||
"search": {
|
||||
"saveSearch": {
|
||||
"label": "Sačuvaj pretragu",
|
||||
"desc": "Unesite naziv za ovu sačuvanu pretragu.",
|
||||
"placeholder": "Unesite naziv za svoju pretragu",
|
||||
"overwrite": "{{searchName}} već postoji. Čuvanje će prepisati postojeću vrednost.",
|
||||
"success": "Pretraga ({{searchName}}) je sačuvana.",
|
||||
"button": {
|
||||
"save": {
|
||||
"label": "Sačuvaj ovu pretragu"
|
||||
}
|
||||
}
|
||||
}
|
||||
},
|
||||
"recording": {
|
||||
"shareTimestamp": {
|
||||
"label": "Deli vremensku oznaku",
|
||||
"title": "Deli vremensku oznaku",
|
||||
"description": "Podelite URL sa vremenskom oznakom trenutne pozicije plejera ili izaberite prilagođenu vremensku oznaku. Napomena: ovo nije javni URL za deljenje i dostupan je samo korisnicima koji imaju pristup Frigate-u i ovoj kameri.",
|
||||
"custom": "Prilagođena vremenska oznaka",
|
||||
"button": "URL za deljenje vremenske oznake",
|
||||
"shareTitle": "Frigate pregled vremenske oznake: {{camera}}"
|
||||
},
|
||||
"confirmDelete": {
|
||||
"title": "Potvrdite brisanje",
|
||||
"desc": {
|
||||
"selected": "Da li ste sigurni da želite da obrišete sav snimljeni video povezan sa ovom stavkom pregleda?<br /><br />Držite taster <em>Shift</em> da biste ubuduće zaobišli ovaj prozor."
|
||||
},
|
||||
"toast": {
|
||||
"success": "Video-snimci povezani sa izabranim stavkama pregleda su uspešno obrisani.",
|
||||
"error": "Neuspešno brisanje: {{error}}"
|
||||
}
|
||||
},
|
||||
"button": {
|
||||
"export": "Izvezi",
|
||||
"markAsReviewed": "Označi kao pregledano",
|
||||
"markAsUnreviewed": "Označi kao nepregledano",
|
||||
"deleteNow": "Obriši odmah",
|
||||
"generateDescription": "Generiši opis"
|
||||
},
|
||||
"genaiDescription": {
|
||||
"toast": {
|
||||
"success": "Zatražen je GenAI opis za ovu stavku pregleda.",
|
||||
"error": "Slanje zahteva za opis nije uspelo: {{error}}"
|
||||
}
|
||||
}
|
||||
},
|
||||
"imagePicker": {
|
||||
"selectImage": "Izaberite sličicu praćenog objekta",
|
||||
"unknownLabel": "Sačuvana slika pokretača",
|
||||
"search": {
|
||||
"placeholder": "Pretražite po oznaci ili podoznaci..."
|
||||
},
|
||||
"noImages": "Nema pronađenih sličica za ovu kameru"
|
||||
}
|
||||
}
|
||||
|
||||
@@ -1 +1,140 @@
|
||||
{}
|
||||
{
|
||||
"filter": "Filtriraj",
|
||||
"classes": {
|
||||
"label": "Klase",
|
||||
"all": {
|
||||
"title": "Sve klase"
|
||||
},
|
||||
"count_one": "{{count}} klasa",
|
||||
"count_other": "{{count}} klase"
|
||||
},
|
||||
"labels": {
|
||||
"label": "Oznake",
|
||||
"all": {
|
||||
"title": "Sve oznake",
|
||||
"short": "Oznake"
|
||||
},
|
||||
"count_one": "{{count}} oznaka",
|
||||
"count_other": "{{count}} oznake"
|
||||
},
|
||||
"zones": {
|
||||
"label": "Zone",
|
||||
"all": {
|
||||
"title": "Sve zone",
|
||||
"short": "Zone"
|
||||
}
|
||||
},
|
||||
"dates": {
|
||||
"selectPreset": "Izaberite unapred podešenu poziciju…",
|
||||
"all": {
|
||||
"title": "Svi datumi",
|
||||
"short": "Datumi"
|
||||
}
|
||||
},
|
||||
"more": "Više filtera",
|
||||
"reset": {
|
||||
"label": "Vrati filtere na podrazumevane vrednosti"
|
||||
},
|
||||
"timeRange": "Vremenski raspon",
|
||||
"subLabels": {
|
||||
"label": "Podoznake",
|
||||
"all": "Sve podoznake"
|
||||
},
|
||||
"attributes": {
|
||||
"label": "Atributi klasifikacije",
|
||||
"all": "Svi atributi"
|
||||
},
|
||||
"score": "Rezultat",
|
||||
"estimatedSpeed": "Procenjena brzina ({{unit}})",
|
||||
"features": {
|
||||
"label": "Funkcije",
|
||||
"hasSnapshot": "Ima snimak",
|
||||
"hasVideoClip": "Ima video-snimak",
|
||||
"submittedToFrigatePlus": {
|
||||
"label": "Poslato na Frigate+",
|
||||
"tips": "Morate prvo filtrirati praćene objekte koji imaju snimak.<br /><br />Praćeni objekti bez snimka ne mogu se poslati na Frigate+."
|
||||
}
|
||||
},
|
||||
"sort": {
|
||||
"label": "Sortiranje",
|
||||
"dateAsc": "Datum (Rastuće)",
|
||||
"dateDesc": "Datum (Opadajuće)",
|
||||
"scoreAsc": "Rezultat objekta (Rastuće)",
|
||||
"scoreDesc": "Rezultat objekta (Opadajuće)",
|
||||
"speedAsc": "Procenjena brzina (Rastuće)",
|
||||
"speedDesc": "Procenjena brzina (Opadajuće)",
|
||||
"relevance": "Relevantnost"
|
||||
},
|
||||
"cameras": {
|
||||
"label": "Filter kamera",
|
||||
"all": {
|
||||
"title": "Sve kamere",
|
||||
"short": "Kamere"
|
||||
}
|
||||
},
|
||||
"review": {
|
||||
"showReviewed": "Prikaži pregledano"
|
||||
},
|
||||
"motion": {
|
||||
"showMotionOnly": "Prikaži samo kretanje"
|
||||
},
|
||||
"explore": {
|
||||
"settings": {
|
||||
"title": "Podešavanja",
|
||||
"defaultView": {
|
||||
"title": "Podrazumevani prikaz",
|
||||
"desc": "Kada nijedan filter nije izabran, prikaži sažetak najnovijih praćenih objekata po oznaci ili prikaži nefiltriranu mrežu.",
|
||||
"summary": "Sažetak",
|
||||
"unfilteredGrid": "Nefiltrirana mreža"
|
||||
},
|
||||
"gridColumns": {
|
||||
"title": "Kolone u mreži",
|
||||
"desc": "Izaberite broj kolona u prikazu mreže."
|
||||
},
|
||||
"searchSource": {
|
||||
"label": "Izvor pretrage",
|
||||
"desc": "Izaberite da li želite da pretražujete sličice (umanjene prikaze) ili opise vaših praćenih objekata.",
|
||||
"options": {
|
||||
"thumbnailImage": "Sličica",
|
||||
"description": "Opis"
|
||||
}
|
||||
}
|
||||
},
|
||||
"date": {
|
||||
"selectDateBy": {
|
||||
"label": "Izaberite datum za filtriranje"
|
||||
}
|
||||
}
|
||||
},
|
||||
"logSettings": {
|
||||
"label": "Filtriraj nivo dnevnika",
|
||||
"filterBySeverity": "Filtriraj dnevnik prema ozbiljnosti",
|
||||
"loading": {
|
||||
"title": "Učitavanje",
|
||||
"desc": "Kada se panel dnevnika pomeri do samog dna, novi zapisi se automatski strimuju čim se dodaju."
|
||||
},
|
||||
"disableLogStreaming": "Onemogući strimovanje dnevnika",
|
||||
"allLogs": "Ceo dnevnik"
|
||||
},
|
||||
"trackedObjectDelete": {
|
||||
"title": "Potvrdite brisanje",
|
||||
"desc": "Brisanjem ovih {{objectLength}} praćenih objekata uklanja se snimak, svi sačuvani vektorski prikazi i svi povezani unosi o životnom ciklusu objekta. Snimljeni materijal ovih praćenih objekata u prikazu istorije <em>NEĆE</em> biti obrisan.<br /><br />Da li ste sigurni da želite da nastavite?<br /><br />Držite taster <em>Shift</em> da biste ubuduće zaobišli ovaj prozor.",
|
||||
"toast": {
|
||||
"success": "Praćeni objekti su uspešno obrisani.",
|
||||
"error": "Neuspešno brisanje praćenih objekata: {{errorMessage}}"
|
||||
}
|
||||
},
|
||||
"zoneMask": {
|
||||
"filterBy": "Filtriraj prema maski zone"
|
||||
},
|
||||
"recognizedLicensePlates": {
|
||||
"title": "Prepoznate registarske tablice",
|
||||
"loadFailed": "Učitavanje prepoznatih registarskih tablica nije uspelo.",
|
||||
"loading": "Učitavanje prepoznatih registarskih tablica…",
|
||||
"placeholder": "Unesite tekst za pretragu tablica…",
|
||||
"noLicensePlatesFound": "Nema pronađenih registarskih tablica.",
|
||||
"selectPlatesFromList": "Izaberite jednu ili više tablica sa liste.",
|
||||
"selectAll": "Izaberi sve",
|
||||
"clearAll": "Poništi sve"
|
||||
}
|
||||
}
|
||||
|
||||
@@ -1 +1,61 @@
|
||||
{}
|
||||
{
|
||||
"noRecordingsFoundForThisTime": "Nema pronađenih snimaka za ovaj vremenski period",
|
||||
"noPreviewFound": "Pregled nije pronađen",
|
||||
"noPreviewFoundFor": "Pregled nije pronađen za kameru {{cameraName}}",
|
||||
"submitFrigatePlus": {
|
||||
"title": "Pošalji ovaj kadar na Frigate+?",
|
||||
"submit": "Pošalji",
|
||||
"previewError": "Učitavanje predpregleda snimka nije uspelo. Snimak možda nije dostupan u ovom trenutku."
|
||||
},
|
||||
"livePlayerRequiredIOSVersion": "Za ovaj tip strima uživo potreban je iOS 17.1 ili noviji.",
|
||||
"streamOffline": {
|
||||
"title": "Strim je van mreže",
|
||||
"desc": "Nijedan kadar nije primljen na strimu za detekciju <code>detect</code> kamere {{cameraName}}, proverite dnevnik grešaka"
|
||||
},
|
||||
"cameraOff": "Kamera je isključena",
|
||||
"quality": {
|
||||
"auto": "Automatski",
|
||||
"autoLow": "Reprodukcija u niskom kvalitetu (ograničen protok podataka)",
|
||||
"autoLowCodec": "Reprodukcija u niskom kvalitetu (izvorni kvalitet nije podržan u ovom veb-pregledaču)",
|
||||
"autoLowSaveData": "Reprodukcija u niskom kvalitetu (ušteda podataka)",
|
||||
"main": "Izvorni",
|
||||
"sub": "Nizak",
|
||||
"notSupportedBrowser": "Nije podržano u ovom veb-pregledaču",
|
||||
"label": "Kvalitet",
|
||||
"noAudio": "Nema zvuka",
|
||||
"audioRate": "{{rate}} kHz audio",
|
||||
"audioCodecRate": "{{codec}} {{rate}} kHz",
|
||||
"noRecordings": "Nema snimaka u ovom vremenskom opsegu"
|
||||
},
|
||||
"stats": {
|
||||
"streamType": {
|
||||
"title": "Tip strima:",
|
||||
"short": "Tip"
|
||||
},
|
||||
"bandwidth": {
|
||||
"title": "Propusni opseg:",
|
||||
"short": "Propusni opseg"
|
||||
},
|
||||
"totalFrames": "Ukupno kadrova:",
|
||||
"droppedFrames": {
|
||||
"title": "Odbačeni kadrovi:",
|
||||
"short": {
|
||||
"title": "Odbačeno",
|
||||
"value": "{{droppedFrames}} kadrova"
|
||||
}
|
||||
},
|
||||
"decodedFrames": "Dekodirani kadrovi:",
|
||||
"droppedFrameRate": "Brzina odbacivanja kadrova:",
|
||||
"stream": "Strim:",
|
||||
"streamAuto": "{{stream}} (automatski)"
|
||||
},
|
||||
"toast": {
|
||||
"success": {
|
||||
"submittedFrigatePlus": "Kadar je uspešno poslat na Frigate+"
|
||||
},
|
||||
"error": {
|
||||
"submitFrigatePlusFailed": "Slanje kadra na Frigate+ nije uspelo",
|
||||
"playRecordingsFailed": "Reprodukcija snimaka nije uspela (greška {{code}}): {{message}}"
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
@@ -1 +1,40 @@
|
||||
{}
|
||||
{
|
||||
"minimum": "Mora biti najmanje {{limit}}",
|
||||
"maximum": "Mora biti najviše {{limit}}",
|
||||
"exclusiveMinimum": "Mora biti veće od {{limit}}",
|
||||
"exclusiveMaximum": "Mora biti manje od {{limit}}",
|
||||
"minLength": "Mora imati najmanje {{limit}} karakter(a)",
|
||||
"maxLength": "Mora imati najviše {{limit}} karakter(a)",
|
||||
"minItems": "Mora imati najmanje {{limit}} stavki",
|
||||
"maxItems": "Mora imati najviše {{limit}} stavki",
|
||||
"pattern": "Nevažeći format",
|
||||
"required": "Ovo polje je obavezno",
|
||||
"type": "Nevažeći tip vrednosti",
|
||||
"enum": "Mora biti jedna od dozvoljenih vrednosti",
|
||||
"const": "Vrednost se ne poklapa sa očekivanom konstantom",
|
||||
"uniqueItems": "Sve stavke moraju biti jedinstvene",
|
||||
"format": "Nevažeći format",
|
||||
"additionalProperties": "Nepoznato svojstvo nije dozvoljeno",
|
||||
"oneOf": "Mora se podudarati sa tačno jednom od dozvoljenih šema",
|
||||
"anyOf": "Mora se podudarati sa najmanje jednom od dozvoljenih šema",
|
||||
"proxy": {
|
||||
"header_map": {
|
||||
"roleHeaderRequired": "Zaglavlje uloge je obavezno kada su konfigurisana mapiranja uloga."
|
||||
}
|
||||
},
|
||||
"ffmpeg": {
|
||||
"inputs": {
|
||||
"rolesUnique": "Svaka uloga može biti dodeljena samo jednom ulaznom strimu.",
|
||||
"detectRequired": "Najmanje jednom ulaznom strimu mora biti dodeljena uloga „detect“.",
|
||||
"hwaccelDetectOnly": "Samo ulazni strim sa ulogom „detect“ može definisati argumente za hardversko ubrzanje."
|
||||
}
|
||||
},
|
||||
"detect": {
|
||||
"dimensionMustBeEven": "Mora biti paran broj."
|
||||
},
|
||||
"models": {
|
||||
"defaultRequired": "Jedan model mora koristiti podrazumevanu scenu. Bez nje, svaka kamera koja ne izabere scenu neće imati rezervni model na koji može da se osloni.",
|
||||
"sceneDuplicate": "Svaki model mora koristiti različitu scenu.",
|
||||
"sameModel": "Modeli za scenu {{scene}} i scenu {{other}} koriste istu datoteku modela. Pokretanje jednog modela pod više različitih scena samo deli posao detekcije na zasebne detektore, što je sporije nego pokretanje jednog modela sa svim hardverskim resursima navedenim pod njim."
|
||||
}
|
||||
}
|
||||
|
||||
@@ -1 +1,219 @@
|
||||
{}
|
||||
{
|
||||
"documentTitle": "Klasifikacioni modeli - Frigate",
|
||||
"disabled": "Onemogućeno",
|
||||
"details": {
|
||||
"scoreInfo": "Rezultat predstavlja prosečnu pouzdanost klasifikacije kroz sve detekcije ovog objekta.",
|
||||
"none": "Ništa",
|
||||
"unknown": "Nepoznato"
|
||||
},
|
||||
"button": {
|
||||
"deleteClassificationAttempts": "Obriši klasifikacione slike",
|
||||
"renameCategory": "Preimenuj klasu",
|
||||
"deleteCategory": "Obriši klasu",
|
||||
"deleteImages": "Obriši slike",
|
||||
"trainModel": "Treniraj model",
|
||||
"addClassification": "Dodaj klasifikaciju",
|
||||
"deleteModels": "Obriši modele",
|
||||
"editModel": "Izmeni model"
|
||||
},
|
||||
"tooltip": {
|
||||
"trainingInProgress": "Model se trenutno trenira",
|
||||
"noNewImages": "Nema novih slika za treniranje. Prvo klasifikujte više slika u skupu podataka.",
|
||||
"noChanges": "Nema izmena u skupu podataka od poslednjeg treniranja.",
|
||||
"modelNotReady": "Model nije spreman za treniranje"
|
||||
},
|
||||
"toast": {
|
||||
"success": {
|
||||
"deletedModel_one": "Uspešno je obrisan {{count}} model",
|
||||
"deletedModel_few": "Uspešno je obrisano {{count}} modela",
|
||||
"deletedModel_other": "Uspešno je obrisano {{count}} modela",
|
||||
"categorizedImage": "Slika je uspešno klasifikovana",
|
||||
"reclassifiedImage": "Slika je uspešno ponovo klasifikovana",
|
||||
"trainedModel": "Model je uspešno treniran.",
|
||||
"trainingModel": "Treniranje modela je uspešno pokrenuto.",
|
||||
"updatedModel": "Konfiguracija modela je uspešno ažurirana",
|
||||
"renamedCategory": "Klasa je uspešno preimenovana u {{name}}",
|
||||
"deletedCategory_one": "Obrisan {{count}} klasa",
|
||||
"deletedCategory_few": "Obrisano {{count}} klase",
|
||||
"deletedCategory_other": "Obrisano {{count}} klasa",
|
||||
"deletedImage_one": "Obrisana {{count}} slika",
|
||||
"deletedImage_few": "Obrisano {{count}} slike",
|
||||
"deletedImage_other": "Obrisano {{count}} slika"
|
||||
},
|
||||
"error": {
|
||||
"deleteImageFailed": "Brisanje nije uspelo: {{errorMessage}}",
|
||||
"deleteCategoryFailed": "Brisanje klase nije uspelo: {{errorMessage}}",
|
||||
"deleteModelFailed": "Brisanje modela nije uspelo: {{errorMessage}}",
|
||||
"categorizeFailed": "Kategorizacija slike nije uspela: {{errorMessage}}",
|
||||
"trainingFailed": "Treniranje modela nije uspelo. Proverite Frigate dnevnike za detalje.",
|
||||
"trainingFailedToStart": "Pokretanje treniranja modela nije uspelo: {{errorMessage}}",
|
||||
"updateModelFailed": "Ažuriranje modela nije uspelo: {{errorMessage}}",
|
||||
"renameCategoryFailed": "Preimenovanje klase nije uspelo: {{errorMessage}}",
|
||||
"reclassifyFailed": "Ponovna klasifikacija slike nije uspela: {{errorMessage}}"
|
||||
}
|
||||
},
|
||||
"deleteCategory": {
|
||||
"title": "Obriši klasu",
|
||||
"desc": "Da li ste sigurni da želite da obrišete klasu {{name}}? Ovo će trajno obrisati sve povezane slike i zahtevaće ponovno treniranje modela.",
|
||||
"minClassesTitle": "Nije moguće obrisati klasu",
|
||||
"minClassesDesc": "Klasifikacioni model mora imati najmanje 2 klase. Dodajte drugu klasu pre nego što obrišete ovu."
|
||||
},
|
||||
"deleteModel": {
|
||||
"title": "Obriši klasifikacioni model",
|
||||
"single": "Da li ste sigurni da želite da obrišete {{name}}? Ovo će trajno obrisati sve povezane podatke, uključujući slike i podatke o treniranju. Ova radnja se ne može poništiti.",
|
||||
"desc_one": "Da li ste sigurni da želite da obrišete {{count}} model? Ovo će trajno obrisati sve povezane podatke, uključujući slike i podatke o treniranju. Ova radnja se ne može poništiti.",
|
||||
"desc_few": "Da li ste sigurni da želite da obrišete {{count}} modela? Ovo će trajno obrisati sve povezane podatke, uključujući slike i podatke o treniranju. Ova radnja se ne može poništiti.",
|
||||
"desc_other": "Da li ste sigurni da želite da obrišete {{count}} modela? Ovo će trajno obrisati sve povezane podatke, uključujući slike i podatke o treniranju. Ova radnja se ne može poništiti."
|
||||
},
|
||||
"edit": {
|
||||
"title": "Izmeni klasifikacioni model",
|
||||
"descriptionState": "Izmenite klase za ovaj model klasifikacije stanja. Izmene će zahtevati ponovno treniranje modela.",
|
||||
"descriptionObject": "Izmenite tip objekta i tip klasifikacije za ovaj model klasifikacije objekata.",
|
||||
"enabled": "Omogućeno",
|
||||
"enabledDesc": "Pokrenite ovaj model. Kada je onemogućen, prestaje sa radom i više ne vrši klasifikaciju.",
|
||||
"saveAttempts": "Pokušaji čuvanja",
|
||||
"saveAttemptsDesc": "Broj slika pokušaja klasifikacije koje treba zadržati za korisnički interfejs nedavnih klasifikacija.",
|
||||
"motion": "Pokreni pri kretanju",
|
||||
"motionDesc": "Pokrenite klasifikaciju kada se detektuje kretanje unutar konfigurisanog isecanja kadra.",
|
||||
"interval": "Interval",
|
||||
"intervalDesc": "Broj sekundi između periodičnih pokretanja klasifikacije. Ostavite prazno da bi se pokretala samo pri kretanju.",
|
||||
"intervalPlaceholder": "Bez intervala",
|
||||
"stateClassesInfo": "Model je ažuriran. Ponovo trenirajte model kako bi izmene klasa stupile na snagu.",
|
||||
"errors": {
|
||||
"saveAttemptsInvalid": "Pokušaji čuvanja moraju biti ceo broj 0 ili veći",
|
||||
"intervalInvalid": "Interval mora biti ceo broj veći od 0"
|
||||
}
|
||||
},
|
||||
"deleteDatasetImages": {
|
||||
"title": "Obriši slike iz skupa podataka",
|
||||
"desc_one": "Da li ste sigurni da želite da obrišete {{count}} sliku iz skupa podataka {{dataset}}? Ova radnja se ne može poništiti i zahtevaće ponovno treniranje modela.",
|
||||
"desc_few": "Da li ste sigurni da želite da obrišete {{count}} slike iz skupa podataka {{dataset}}? Ova radnja se ne može poništiti i zahtevaće ponovno treniranje modela.",
|
||||
"desc_other": "Da li ste sigurni da želite da obrišete {{count}} slika iz skupa podataka {{dataset}}? Ova radnja se ne može poništiti i zahtevaće ponovno treniranje modela."
|
||||
},
|
||||
"deleteTrainImages": {
|
||||
"title": "Obriši slike za trening",
|
||||
"desc_one": "Da li ste sigurni da želite da obrišete {{count}} sliku? Ova radnja se ne može poništiti.",
|
||||
"desc_few": "Da li ste sigurni da želite da obrišete {{count}} slike? Ova radnja se ne može poništiti.",
|
||||
"desc_other": "Da li ste sigurni da želite da obrišete {{count}} slika? Ova radnja se ne može poništiti."
|
||||
},
|
||||
"renameCategory": {
|
||||
"title": "Preimenuj klasu",
|
||||
"desc": "Unesite novi naziv za {{name}}. Biće potrebno da ponovo trenirate model kako bi izmena naziva stupila na snagu."
|
||||
},
|
||||
"description": {
|
||||
"invalidName": "Neispravan naziv. Nazivi mogu sadržati samo slova, brojeve, razmake, apostrofe, donje crte i crtice."
|
||||
},
|
||||
"train": {
|
||||
"title": "Nedavne klasifikacije",
|
||||
"titleShort": "Nedavno",
|
||||
"aria": "Izaberite nedavne klasifikacije"
|
||||
},
|
||||
"categories": "Klase",
|
||||
"createCategory": {
|
||||
"new": "Kreiraj novu klasu"
|
||||
},
|
||||
"categorizeImageAs": "Klasifikuj sliku kao:",
|
||||
"categorizeImage": "Klasifikuj sliku",
|
||||
"reclassifyImageAs": "Ponovo klasifikuj sliku kao:",
|
||||
"reclassifyImage": "Ponovo klasifikuj sliku",
|
||||
"menu": {
|
||||
"objects": "Objekti",
|
||||
"states": "Stanja"
|
||||
},
|
||||
"noModels": {
|
||||
"object": {
|
||||
"title": "Nema modela za klasifikaciju objekata",
|
||||
"description": "Kreirajte prilagođeni model za klasifikaciju detektovanih objekata.",
|
||||
"buttonText": "Kreiraj model objekata"
|
||||
},
|
||||
"state": {
|
||||
"title": "Nema modela za klasifikaciju stanja",
|
||||
"description": "Kreirajte prilagođeni model za praćenje i klasifikaciju promena stanja u specifičnim zonama kamera.",
|
||||
"buttonText": "Kreiraj model stanja"
|
||||
}
|
||||
},
|
||||
"wizard": {
|
||||
"title": "Kreiraj novu klasifikaciju",
|
||||
"steps": {
|
||||
"nameAndDefine": "Naziv i definisanje",
|
||||
"stateArea": "Zona stanja",
|
||||
"chooseExamples": "Izaberi primere"
|
||||
},
|
||||
"step1": {
|
||||
"description": "Modeli stanja prate fiksne zone kamera radi uočavanja promena (npr. otvorena/zatvorena vrata). Modeli objekata dodaju klasifikacije detektovanim objektima (npr. poznate životinje, dostavljači, itd.).",
|
||||
"name": "Naziv",
|
||||
"namePlaceholder": "Unesite naziv modela...",
|
||||
"type": "Tip",
|
||||
"typeState": "Stanje",
|
||||
"typeObject": "Objekat",
|
||||
"objectLabel": "Oznaka objekta",
|
||||
"objectLabelPlaceholder": "Izaberite tip objekta...",
|
||||
"classificationType": "Tip klasifikacije",
|
||||
"classificationTypeTip": "Saznajte više o tipovima klasifikacije",
|
||||
"classificationTypeDesc": "Podoznake dodaju dodatni tekst oznaci objekta (npr. 'Osoba: UPS'). Atributi predstavljaju metapodatke pretražive kroz sistem koji se čuvaju odvojeno unutar metapodataka objekta.",
|
||||
"classificationSubLabel": "Podoznaka",
|
||||
"classificationAttribute": "Atribut",
|
||||
"classes": "Klase",
|
||||
"states": "Stanja",
|
||||
"classesTip": "Saznajte više o klasama",
|
||||
"classesStateDesc": "Definišite različita stanja u kojima se zona vaše kamere može nalaziti. Na primer: 'otvoreno' i 'zatvoreno' za garažna vrata.",
|
||||
"classesObjectDesc": "Definišite različite kategorije u koje se detektovani objekti mogu klasifikovati. Na primer: 'dostavljač', 'stanar' (resident), 'stranac' za klasifikaciju osoba.",
|
||||
"classPlaceholder": "Unesite naziv klase...",
|
||||
"errors": {
|
||||
"nameRequired": "Naziv modela je obavezan",
|
||||
"nameLength": "Naziv modela mora imati 64 karaktera ili manje",
|
||||
"nameOnlyNumbers": "Naziv modela ne može sadržati samo brojeve",
|
||||
"classRequired": "Najmanje 1 klasa je obavezna",
|
||||
"classesUnique": "Nazivi klasa moraju biti jedinstveni",
|
||||
"noneNotAllowed": "Klasa 'none' nije dozvoljena",
|
||||
"stateRequiresTwoClasses": "Modeli stanja zahtevaju najmanje 2 klase",
|
||||
"objectLabelRequired": "Molimo izaberite oznaku objekta",
|
||||
"objectTypeRequired": "Molimo izaberite tip klasifikacije"
|
||||
}
|
||||
},
|
||||
"step2": {
|
||||
"description": "Izaberite kamere i definišite zonu za praćenje za svaku kameru. Model će klasifikovati stanje ovih zona.",
|
||||
"cameras": "Kamere",
|
||||
"selectCamera": "Izaberite kameru",
|
||||
"noCameras": "Kliknite na + da dodate kamere",
|
||||
"selectCameraPrompt": "Izaberite kameru sa liste da biste definisali njenu zonu praćenja"
|
||||
},
|
||||
"step3": {
|
||||
"selectImagesPrompt": "Izaberite sve slike sa: {{className}}",
|
||||
"selectImagesDescription": "Kliknite na slike da ih izaberete. Kliknite na Nastavi kada završite sa ovom klasom.",
|
||||
"allImagesRequired_one": "Molimo klasifikujte sve slike. Preostala je još {{count}} slika.",
|
||||
"allImagesRequired_few": "Molimo klasifikujte sve slike. Preostala je još {{count}} slike.",
|
||||
"allImagesRequired_other": "Molimo klasifikujte sve slike. Preostala je još {{count}} slika.",
|
||||
"generating": {
|
||||
"title": "Generisanje uzoraka slika",
|
||||
"description": "Frigate preuzima reprezentativne slike iz vaših snimaka. Ovo može potrajati..."
|
||||
},
|
||||
"training": {
|
||||
"title": "Treniranje modela",
|
||||
"description": "Vaš model se trenira u pozadini. Zatvorite ovaj prozor, a vaš model će početi sa radom čim se treniranje završi."
|
||||
},
|
||||
"retryGenerate": "Pokušaj ponovo generisanje",
|
||||
"noImages": "Nema generisanih uzoraka slika",
|
||||
"classifying": "Klasifikacija i treniranje...",
|
||||
"trainingStarted": "Treniranje je uspešno pokrenuto",
|
||||
"modelCreated": "Model je uspešno kreiran. Koristite pregled „Nedavne klasifikacije“ da dodate slike za stanja koja nedostaju, a zatim trenirajte model.",
|
||||
"errors": {
|
||||
"noCameras": "Nema konfigurisanih kamera",
|
||||
"noObjectLabel": "Nije izabrana oznaka objekta",
|
||||
"generateFailed": "Generisanje primera nije uspelo: {{error}}",
|
||||
"generationFailed": "Generisanje nije uspelo. Molimo pokušajte ponovo.",
|
||||
"classifyFailed": "Klasifikacija slika nije uspela: {{error}}"
|
||||
},
|
||||
"generateSuccess": "Uzorci slika su uspešno generisani",
|
||||
"refreshExamples": "Generiši nove primere",
|
||||
"refreshConfirm": {
|
||||
"title": "Želite li da generišete nove primere?",
|
||||
"description": "Ovo će generisati novi skup slika i poništiti sve izbore, uključujući i sve prethodne klase. Biće potrebno da ponovo izaberete primere za sve klase."
|
||||
},
|
||||
"missingStatesWarning": {
|
||||
"title": "Nedostaju primeri klasa",
|
||||
"description": "Nemaju sve klase primere. Pokušajte da generišete nove primere kako biste pronašli klasu koja nedostaju, ili nastavite i kasnije koristite pregled „Nedavne klasifikacije“ da dodate slike."
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
@@ -1 +1,267 @@
|
||||
{}
|
||||
{
|
||||
"documentTitle": "Istraži - Frigate",
|
||||
"generativeAI": "Generativni AI",
|
||||
"exploreMore": "Istražite više objekata tipa {{label}}",
|
||||
"exploreIsUnavailable": {
|
||||
"title": "Opcija „Istraži“ je nedostupna",
|
||||
"embeddingsReindexing": {
|
||||
"context": "Opcija „Istraži“ se može koristiti nakon što se završi ponovno indeksiranje ugnježdenih podataka praćenih objekata.",
|
||||
"startingUp": "Pokretanje…",
|
||||
"estimatedTime": "Procenjeno preostalo vreme:",
|
||||
"finishingShortly": "Uskoro se završava",
|
||||
"step": {
|
||||
"thumbnailsEmbedded": "Sličice su ugnježdeni: ",
|
||||
"descriptionsEmbedded": "Opisi su ugnježdeni: ",
|
||||
"trackedObjectsProcessed": "Praćeni objekti su obrađeni: "
|
||||
}
|
||||
},
|
||||
"downloadingModels": {
|
||||
"context": "Frigate preuzima neophodne modele ugnježdenih podataka kako bi podržao funkciju semantičke pretrage. Ovo može potrajati nekoliko minuta u zavisnosti od brzine vaše mrežne veze.",
|
||||
"setup": {
|
||||
"visionModel": "Vizuelni model",
|
||||
"visionModelFeatureExtractor": "Ekstraktor funkcija vizuelnog modela",
|
||||
"textModel": "Tekstualni model",
|
||||
"textTokenizer": "Tokenizator teksta"
|
||||
},
|
||||
"tips": {
|
||||
"context": "Možda ćete želeti da ponovo indeksirate ugnježdene podatke (embeddings) vaših praćenih objekata kada se modeli preuzmu."
|
||||
},
|
||||
"error": "Došlo je do greške. Proverite Frigate dnevnike."
|
||||
}
|
||||
},
|
||||
"trackedObjectDetails": "Detalji o praćenom objektu",
|
||||
"type": {
|
||||
"details": "detalji",
|
||||
"snapshot": "snimak",
|
||||
"thumbnail": "sličica",
|
||||
"video": "video",
|
||||
"tracking_details": "detalji praćenja"
|
||||
},
|
||||
"trackingDetails": {
|
||||
"title": "Detalji praćenja",
|
||||
"noImageFound": "Nije pronađena slika za ovu vremensku oznaku.",
|
||||
"createObjectMask": "Kreiraj masku objekta",
|
||||
"adjustAnnotationSettings": "Prilagodite podešavanja označavanja",
|
||||
"scrollViewTips": "Kliknite da biste videli značajne trenutke u životnom ciklusu ovog objekta.",
|
||||
"autoTrackingTips": "Pozicije okvira za detekciju biće neprecizne za kamere sa automatskim praćenjem.",
|
||||
"count": "{{first}} od {{second}}",
|
||||
"trackedPoint": "Praćena tačka",
|
||||
"lifecycleItemDesc": {
|
||||
"visible": "detektovano {{label}}",
|
||||
"entered_zone": "{{label}} je ušao u {{zones}}",
|
||||
"active": "{{label}} je postao aktivan",
|
||||
"stationary": "{{label}} je postao stacionaran",
|
||||
"attribute": {
|
||||
"faceOrLicense_plate": "detektovan atribut {{attribute}} za {{label}}",
|
||||
"other": "{{label}} je prepoznat kao {{attribute}}"
|
||||
},
|
||||
"gone": "{{label}} je otišao",
|
||||
"heard": "registrovan zvuk {{label}}",
|
||||
"external": "detektovan {{label}}",
|
||||
"header": {
|
||||
"zones": "Zone",
|
||||
"ratio": "Odnos",
|
||||
"area": "Površina",
|
||||
"score": "Rezultat",
|
||||
"computedScore": "Izračunati rezultat",
|
||||
"topScore": "Najbolji rezultat",
|
||||
"toggleAdvancedScores": "Uključi/isključi napredne rezultate"
|
||||
}
|
||||
},
|
||||
"annotationSettings": {
|
||||
"title": "Podešavanja označavanja",
|
||||
"showAllZones": {
|
||||
"title": "Prikaži sve zone",
|
||||
"desc": "Uvek prikaži zone na kadrovima gde su objekti ušli u zonu."
|
||||
},
|
||||
"offset": {
|
||||
"label": "Pomeraj označavanja",
|
||||
"desc": "Ovi podaci dolaze sa strima za detekciju vaše kamere, ali se prikazuju preko slika sa strima za snimanje. Malo je verovatno da su ova dva strima savršeno sinhronizovana. Kao rezultat toga, okvir za detekciju i video snimak se neće savršeno poklapati. Možete koristiti ovo podešavanje da pomerite anotacije unapred ili unazad u vremenu kako biste ih bolje uskladili sa snimljenim materijalom.",
|
||||
"millisecondsToOffset": "Milisekunde za koje se pomera anotacija detekcije. <em>Podrazumevano: 0</em>",
|
||||
"tips": "Smanjite vrednost ako je reprodukcija videa ispred okvira i tačaka putanje, a povećajte vrednost ako reprodukcija videa kasni za njima. Ova vrednost može biti negativna.",
|
||||
"toast": {
|
||||
"success": "Pomeraj označavanja za {{camera}} je sačuvan u konfiguracionoj datoteci."
|
||||
}
|
||||
}
|
||||
},
|
||||
"carousel": {
|
||||
"previous": "Prethodni slajd",
|
||||
"next": "Sledeći slajd"
|
||||
}
|
||||
},
|
||||
"details": {
|
||||
"item": {
|
||||
"title": "Detalji stavke pregleda",
|
||||
"desc": "Detalji stavke pregleda",
|
||||
"button": {
|
||||
"share": "Podeli ovu stavku pregleda",
|
||||
"viewInExplore": "Prikaži u opciji „Istraži“"
|
||||
},
|
||||
"tips": {
|
||||
"mismatch_one": "{{count}} nedostupan objekat je detektovan i uključen u ovu stavku pregleda. Ti objekti ili nisu ispunili uslove za upozorenje ili detekciju, ili su već očišćeni/obrisani.",
|
||||
"mismatch_few": "{{count}} nedostupana objekta su detektovana i uključena u ovu stavku pregleda. Ti objekti ili nisu ispunili uslove za upozorenje ili detekciju, ili su već očišćeni/obrisani.",
|
||||
"mismatch_other": "{{count}} nedostupanih objekata je detektovano i uključeno u ovu stavku pregleda. Ti objekti ili nisu ispunili uslove za upozorenje ili detekciju, ili su već očišćeni/obrisani.",
|
||||
"hasMissingObjects": "Prilagodite vašu konfiguraciju ako želite da Frigate čuva praćene objekte za sledeće oznake: <em>{{objects}}</em>"
|
||||
},
|
||||
"toast": {
|
||||
"success": {
|
||||
"regenerate": "Zahtevan je novi opis od provajdera {{provider}}. U zavisnosti od brzine vašeg provajdera, ponovno generisanje opisa može potrajati.",
|
||||
"updatedSublabel": "Podoznaka je uspešno ažurirana.",
|
||||
"updatedLPR": "Registarska tablica je uspešno ažurirana.",
|
||||
"updatedAttributes": "Atributi su uspešno ažurirani.",
|
||||
"audioTranscription": "Uspešno je zatražena transkripcija zvuka. U zavisnosti od brzine vašeg Frigate servera, transkripcija može potrajati."
|
||||
},
|
||||
"error": {
|
||||
"regenerate": "Neuspelo pozivanje provajdera {{provider}} za novi opis: {{errorMessage}}",
|
||||
"updatedSublabelFailed": "Neuspelo ažuriranje podoznake: {{errorMessage}}",
|
||||
"updatedLPRFailed": "Neuspelo ažuriranje registarske tablice: {{errorMessage}}",
|
||||
"updatedAttributesFailed": "Neuspelo ažuriranje atributa: {{errorMessage}}",
|
||||
"audioTranscription": "Neuspeli zahtev za transkripciju zvuka: {{errorMessage}}"
|
||||
}
|
||||
}
|
||||
},
|
||||
"label": "Oznaka",
|
||||
"editSubLabel": {
|
||||
"title": "Izmeni podoznaku",
|
||||
"desc": "Unesite novu podoznaku za: {{label}}",
|
||||
"descNoLabel": "Unesite novu podoznaku za ovaj praćeni objekat"
|
||||
},
|
||||
"editLPR": {
|
||||
"title": "Izmeni registarsku tablicu",
|
||||
"desc": "Unesite novu vrednost registarske tablice za: {{label}}",
|
||||
"descNoLabel": "Unesite novu vrednost registarske tablice za ovaj praćeni objekat"
|
||||
},
|
||||
"editAttributes": {
|
||||
"title": "Izmeni atribute",
|
||||
"desc": "Izaberite atribute klasifikacije za: {{label}}"
|
||||
},
|
||||
"snapshotScore": {
|
||||
"label": "Rezultat snimka"
|
||||
},
|
||||
"topScore": {
|
||||
"label": "Najviši rezultat",
|
||||
"info": "Najviši rezultat predstavlja najveću srednju vrednost (medijanu) rezultata za praćeni objekat, pa se može razlikovati od rezultata prikazanog na sličici u rezultatima pretrage."
|
||||
},
|
||||
"score": {
|
||||
"label": "Rezultat"
|
||||
},
|
||||
"recognizedLicensePlate": "Prepoznata registarska tablica",
|
||||
"attributes": "Atributi klasifikacije",
|
||||
"estimatedSpeed": "Procenjena brzina",
|
||||
"objects": "Objekti",
|
||||
"camera": "Kamera",
|
||||
"zones": "Zone",
|
||||
"timestamp": "Vremenska oznaka",
|
||||
"button": {
|
||||
"findSimilar": "Pronađi slično",
|
||||
"regenerate": {
|
||||
"title": "Ponovo generiši",
|
||||
"label": "Ponovo generiši opis praćenog objekta"
|
||||
}
|
||||
},
|
||||
"description": {
|
||||
"label": "Opis",
|
||||
"placeholder": "Opis praćenog objekta",
|
||||
"aiTips": "Frigate neće zatražiti opis od vašeg provajdera generativne veštačke inteligencije dok se životni ciklus praćenog objekta ne završi."
|
||||
},
|
||||
"expandRegenerationMenu": "Proširi meni za ponovno generisanje",
|
||||
"regenerateFromSnapshot": "Ponovo generiši iz snimka",
|
||||
"regenerateFromThumbnails": "Ponovo generiši iz sličica",
|
||||
"tips": {
|
||||
"descriptionSaved": "Opis je uspešno sačuvan",
|
||||
"saveDescriptionFailed": "Neuspelo ažuriranje opisa: {{errorMessage}}"
|
||||
},
|
||||
"title": {
|
||||
"label": "Naslov"
|
||||
},
|
||||
"scoreInfo": "Informacije o rezultatu"
|
||||
},
|
||||
"itemMenu": {
|
||||
"downloadVideo": {
|
||||
"label": "Preuzmi video",
|
||||
"aria": "Preuzmi video"
|
||||
},
|
||||
"downloadSnapshot": {
|
||||
"label": "Preuzmi snimak",
|
||||
"aria": "Preuzmi snimak"
|
||||
},
|
||||
"downloadCleanSnapshot": {
|
||||
"label": "Preuzmi čist snimak",
|
||||
"aria": "Preuzmi čist snimak"
|
||||
},
|
||||
"viewTrackingDetails": {
|
||||
"label": "Prikaži detalje praćenja",
|
||||
"aria": "Prikaži detalje praćenja"
|
||||
},
|
||||
"findSimilar": {
|
||||
"label": "Pronađi slično",
|
||||
"aria": "Pronađi slične praćene objekte"
|
||||
},
|
||||
"addTrigger": {
|
||||
"label": "Dodaj okidač",
|
||||
"aria": "Dodaj okidač za ovaj praćeni objekat"
|
||||
},
|
||||
"audioTranscription": {
|
||||
"label": "Transkribuj",
|
||||
"aria": "Zatraži transkripciju zvuka"
|
||||
},
|
||||
"submitToPlus": {
|
||||
"label": "Pošalji u Frigate+",
|
||||
"aria": "Pošalji u Frigate Plus"
|
||||
},
|
||||
"viewInHistory": {
|
||||
"label": "Prikaži u istoriji",
|
||||
"aria": "Prikaži u istoriji"
|
||||
},
|
||||
"deleteTrackedObject": {
|
||||
"label": "Obriši ovaj praćeni objekat"
|
||||
},
|
||||
"showObjectDetails": {
|
||||
"label": "Prikaži putanju objekta"
|
||||
},
|
||||
"hideObjectDetails": {
|
||||
"label": "Sakrij putanju objekta"
|
||||
},
|
||||
"debugReplay": {
|
||||
"label": "Ponovna reprodukcija za otklanjanje grešaka",
|
||||
"aria": "Prikaži ovaj praćeni objekat u prikazu ponovne reprodukcije za otklanjanje grešaka"
|
||||
},
|
||||
"more": {
|
||||
"aria": "Više"
|
||||
}
|
||||
},
|
||||
"dialog": {
|
||||
"confirmDelete": {
|
||||
"title": "Potvrdi brisanje",
|
||||
"desc": "Brisanjem ovog praćenog objekta uklanjaju se snimak, svi sačuvani vektorski prikazi i svi povezani unosi detalja praćenja. Snimljeni video-zapis ovog praćenog objekta u prikazu Istorija <em>NEĆE</em> biti obrisan.<br/><br/>Da li ste sigurni da želite da nastavite?"
|
||||
},
|
||||
"toast": {
|
||||
"error": "Greška pri brisanju ovog praćenog objekta: {{errorMessage}}"
|
||||
}
|
||||
},
|
||||
"noTrackedObjects": "Nije pronađen nijedan praćeni objekat",
|
||||
"fetchingTrackedObjectsFailed": "Greška pri preuzimanju praćenih objekata: {{errorMessage}}",
|
||||
"trackedObjectsCount_one": "{{count}} praćeni objekat ",
|
||||
"trackedObjectsCount_few": "{{count}} praćena objekta ",
|
||||
"trackedObjectsCount_other": "{{count}} praćenih objekata ",
|
||||
"searchResult": {
|
||||
"tooltip": "Podudaranje: {{type}} ({{confidence}}%",
|
||||
"previousTrackedObject": "Prethodni praćeni objekat",
|
||||
"nextTrackedObject": "Sledeći praćeni objekat",
|
||||
"deleteTrackedObject": {
|
||||
"toast": {
|
||||
"success": "Praćeni objekat je uspešno obrisan.",
|
||||
"error": "Neuspelo brisanje praćenog objekta: {{errorMessage}}"
|
||||
}
|
||||
}
|
||||
},
|
||||
"aiAnalysis": {
|
||||
"title": "AI analiza"
|
||||
},
|
||||
"concerns": {
|
||||
"label": "Upozorenja"
|
||||
},
|
||||
"objectLifecycle": {
|
||||
"noImageFound": "Nije pronađena slika za ovaj praćeni objekat."
|
||||
}
|
||||
}
|
||||
|
||||
@@ -1 +1,41 @@
|
||||
{}
|
||||
{
|
||||
"selected_one": "Izabrano: {{count}}",
|
||||
"selected_other": "Izabrano: {{count}}",
|
||||
"documentTitle": "Izvoz - Frigate",
|
||||
"search": "Pretraga",
|
||||
"noExports": "Nije pronađen nijedan izvoz",
|
||||
"headings": {
|
||||
"cases": "Slučajevi",
|
||||
"uncategorizedExports": "Nekategorizovani izvozi"
|
||||
},
|
||||
"deleteExport": {
|
||||
"label": "Obriši izvoz",
|
||||
"desc": "Da li ste sigurni da želite da obrišete izvoz {{exportName}}?"
|
||||
},
|
||||
"editExport": {
|
||||
"title": "Preimenuj izvoz",
|
||||
"desc": "Unesite novi naziv za ovaj izvoz.",
|
||||
"saveExport": "Sačuvaj izvoz"
|
||||
},
|
||||
"tooltip": {
|
||||
"shareExport": "Podeli izvoz",
|
||||
"downloadVideo": "Preuzmi video",
|
||||
"editName": "Izmeni naziv",
|
||||
"deleteExport": "Obriši izvoz",
|
||||
"assignToCase": "Dodaj u slučaj",
|
||||
"removeFromCase": "Ukloni iz slučaja"
|
||||
},
|
||||
"toolbar": {
|
||||
"newCase": "Novi slučaj",
|
||||
"addExport": "Dodaj izvoz",
|
||||
"editCase": "Izmeni slučaj",
|
||||
"deleteCase": "Obriši slučaj"
|
||||
},
|
||||
"toast": {
|
||||
"error": {
|
||||
"renameExportFailed": "Neuspelo preimenovanje izvoza: {{errorMessage}}",
|
||||
"assignCaseFailed": "Neuspelo ažuriranje dodele slučaju: {{errorMessage}}",
|
||||
"caseSaveFailed": "Neuspelo čuvanje slučaja: {{errorMessage}}"
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
@@ -1 +1,12 @@
|
||||
{}
|
||||
{
|
||||
"details": {
|
||||
"unknown": "Nepoznato",
|
||||
"timestamp": "Vremenska oznaka"
|
||||
},
|
||||
"description": {
|
||||
"invalidName": "Neispravan naziv. Nazivi mogu sadržati samo slova, brojeve, razmake, apostrofe, donje crte i crtice."
|
||||
},
|
||||
"train": {
|
||||
"titleShort": "Nedavno"
|
||||
}
|
||||
}
|
||||
|
||||
@@ -1 +1,3 @@
|
||||
{}
|
||||
{
|
||||
"search": "Pretraga"
|
||||
}
|
||||
|
||||
File diff suppressed because it is too large
Load Diff
@@ -1 +1,380 @@
|
||||
{}
|
||||
{
|
||||
"documentTitle": {
|
||||
"cameras": "Statistika kamera - Frigate",
|
||||
"storage": "Statistika skladišta - Frigate",
|
||||
"general": "Opšta statistika - Frigate",
|
||||
"enrichments": "Statistika poboljšanja - Frigate",
|
||||
"logs": {
|
||||
"frigate": "Dnevnik Frigate sistema - Frigate",
|
||||
"go2rtc": "Go2RTC dnevnik - Frigate",
|
||||
"nginx": "Nginx dnevnik - Frigate",
|
||||
"websocket": "Dnevnik poruka - Frigate"
|
||||
},
|
||||
"health": "Stanje sistema - Frigate"
|
||||
},
|
||||
"title": "Sistem",
|
||||
"metrics": "Sistemska metrika",
|
||||
"showAllTabs": "Prikaži sve kartice",
|
||||
"showLessTabs": "Prikaži manje",
|
||||
"health": {
|
||||
"title": "Stanje",
|
||||
"notices": {
|
||||
"title": "Obaveštenja",
|
||||
"empty": "Vaš Frigate sistem radi ispravno",
|
||||
"acknowledge": "Uvaži",
|
||||
"acknowledgeHint": "Sakrij dok se ne ponovi",
|
||||
"mute": "Utišaj",
|
||||
"muteHint": "Nikada više ne prikazuj ovo",
|
||||
"unmute": "Uključi zvuk",
|
||||
"showAgain": "Prikaži ponovo",
|
||||
"openSettings": "Otvori podešavanja",
|
||||
"openLink": "Otvori vezu / Otvori link",
|
||||
"noMatches": "Nema obaveštenja koja odgovaraju filteru",
|
||||
"hiddenTitle": "Skriveno",
|
||||
"noneHidden": "Nema skrivenih obaveštenja",
|
||||
"showAll": "Prikaži sve ponovo",
|
||||
"showAllTitle": "Prikaži sva skrivena obaveštenja?",
|
||||
"showAllDesc": "Svako potvrđeno i utišano obaveštenje se vraća na listu obaveštenja, uključujući provere konfiguracije i strimova.",
|
||||
"occurred_one": "Dogodilo se {{time}}",
|
||||
"occurred_other": "Poslednji put se dogodilo {{lastTime}} · {{count}} puta od {{time}}",
|
||||
"acknowledgedAt": "Uvaženo {{time}}",
|
||||
"mutedAt": "Utišano {{time}}",
|
||||
"filter": {
|
||||
"showHidden": "Prikaži skriveno",
|
||||
"severity": "Ozbiljnost",
|
||||
"error": "Greška",
|
||||
"warning": "Upozorenje",
|
||||
"info": "Informacija"
|
||||
},
|
||||
"kinds": {
|
||||
"detector_stuck": "Detektor {{detector}} je ponovo pokrenut nakon što je prestao da odgovara",
|
||||
"model_download_failed": "Preuzimanje datoteke {{file}} za model {{model}} nije uspelo: {{error}}",
|
||||
"skipped_detections": "Sistem za detekciju nije uspevao da isprati brzinu i preskočio je {{pct}}% kadrova tokom perioda dužeg od jednog minuta",
|
||||
"ffmpeg_high_cpu": "Zauzeće procesora od strane FFmpeg-a je visoko (prosečno {{cpu}}%)",
|
||||
"detect_high_cpu": "Zauzeće procesora od strane sistema za detekciju je visoko (prosečno {{cpu}}%)",
|
||||
"shm_too_low": "/dev/shm dodeljena memorija (trenutno {{total}} MB) bi trebalo da se poveća na najmanje {{min}} MB",
|
||||
"object_processing_behind": "Obrada praćenih objekata nije uspevala da isprati brzinu, pa su kadrovi sa kamere odbačeni. Proverite iskorišćenost procesora i da li ograničenja procesora ili dodeljivanje jezgara na kontejneru ili virtuelnoj mašini ostavljaju premalo procesorske snage za Frigate",
|
||||
"failed_login_one": "Neuspešan pokušaj prijave za korisnika {{user}}",
|
||||
"failed_login_other": "Neuspešni pokušaji prijave za korisnika {{user}}",
|
||||
"update_available": "Dostupna je verzija Frigate {{version}}",
|
||||
"analytics_prompt": "Razmislite o deljenju anonimne statistike korišćenja kako biste pomogli programerima da unaprede Frigate"
|
||||
},
|
||||
"streamPrefix": "Strim {{index}}: {{message}}",
|
||||
"streamPrefixRestream": "Strim {{index}} (putem go2rtc): {{message}}",
|
||||
"streamProbeFailed": "Analiza strima {{index}} nije uspela: {{error}}",
|
||||
"cameraProbeFailed": "Analiza strimova nije uspela: {{error}}"
|
||||
},
|
||||
"hardware": {
|
||||
"title": "Hardver",
|
||||
"objectDetection": "Detekcija objekata",
|
||||
"hardwareAcceleration": "Hardversko ubrzanje",
|
||||
"enrichments": {
|
||||
"title": "Poboljšanja",
|
||||
"semantic_search": "Semantička pretraga",
|
||||
"face_recognition": "Prepoznavanje lica",
|
||||
"lpr": "Prepoznavanje registarskih tablica",
|
||||
"audio_transcription": "Audio transkripcija"
|
||||
},
|
||||
"cameraConnections": "Veze sa kamerama",
|
||||
"allCamerasExcellent": "Sve kamere imaju odličnu vezu.",
|
||||
"fps": "{{camera}} / {{expected}} fps",
|
||||
"nothingConfigured": "Ništa nije konfigurisano",
|
||||
"allCameras": "Sve kamere",
|
||||
"probeUnavailable": "Hardverska detekcija je nedostupna",
|
||||
"justStarted": "Frigate je upravo pokrenut",
|
||||
"deviceNotFound": "Uređaj {{devices}} nije pronađen na ovom sistemu",
|
||||
"detectorNotRunning": "Proces detektora nije pokrenut",
|
||||
"inferenceVerySlow": "Obrada (inferencija) je veoma spora ({{speed}} ms)",
|
||||
"inferenceSlow": "Obrada (inferencija) je spora ({{speed}} ms)",
|
||||
"inferenceMs": "{{speed}} ms",
|
||||
"customArgs": "Prilagođeni ffmpeg argumenti",
|
||||
"customArgsNotVerified": "Prilagođeni ffmpeg argumenti, neverifikovani",
|
||||
"hwaccelNotConfigured": "Hardversko dekodiranje se ne koristi, ali je {{family}} dostupan. Izaberite ga u ffmpeg podešavanjima",
|
||||
"hwaccelHardwareMissing": "{{family}} je konfigurisan, ali ga hardverska analiza nije prijavila",
|
||||
"unrecognizedDevice": "Nepoznat uređaj za nadjačavanje, neverifikovan",
|
||||
"decoderUsage": "Dekoder: {{usage}}",
|
||||
"remoteProvider": "Udaljeni provajder",
|
||||
"acceleratorMissing": "Konfigurisano za {{device}}, ali nije pronađen kompatibilan akcelerator",
|
||||
"modelNotRunYet": "Model još uvek nije pokrenut; uređaj se verifikuje nakon prve upotrebe",
|
||||
"fellBackToCpu": "Konfigurisano za {{device}}, ali se izvršava na procesoru",
|
||||
"cpuDespiteAccelerator": "Izvršava se na procesoru iako je akcelerator dostupan",
|
||||
"probed": "Analizirano",
|
||||
"probing": "Analiza u toku...",
|
||||
"recheck": "Ponovo proveri hardver",
|
||||
"cameraStreams": "Strimovi kamere",
|
||||
"streamsNotChecked": "Još uvek nije provereno",
|
||||
"streamsChecking": "Provera: {{done}} od {{total}}",
|
||||
"streamsClean": "Nema problema sa strimom",
|
||||
"checked": "Provereno",
|
||||
"runAgain": "Pokreni ponovo",
|
||||
"runStreamChecks": "Pokreni provere strimova",
|
||||
"streamsChecked_one": "Proverena {{count}} kamera",
|
||||
"streamsChecked_other": "Provereno {{count}} kamera",
|
||||
"streamsFlagged_one": "{{count}} sa problemima, navedena pod Obaveštenja",
|
||||
"streamsFlagged_other": "{{count}} sa problemima, navedene pod Obaveštenja"
|
||||
}
|
||||
},
|
||||
"logs": {
|
||||
"websocket": {
|
||||
"label": "Poruke",
|
||||
"pause": "Pauziraj",
|
||||
"resume": "Nastavi",
|
||||
"clear": "Očisti",
|
||||
"filter": {
|
||||
"all": "Sve teme",
|
||||
"topics": "Teme",
|
||||
"events": "Događaji",
|
||||
"reviews": "Pregledi",
|
||||
"classification": "Klasifikacija",
|
||||
"face_recognition": "Prepoznavanje lica",
|
||||
"lpr": "LPR",
|
||||
"camera_activity": "Aktivnost kamere",
|
||||
"system": "Sistem",
|
||||
"camera": "Kamera",
|
||||
"all_cameras": "Sve kamere",
|
||||
"cameras_count_one": "{{count}} kamera",
|
||||
"cameras_count_other": "{{count}} kamere"
|
||||
},
|
||||
"empty": "Još uvek nema zabeleženih poruka",
|
||||
"count_one": "{{count}} poruka",
|
||||
"count_other": "{{count}} poruke",
|
||||
"expanded": {
|
||||
"payload": "Sadržaj poruke"
|
||||
}
|
||||
},
|
||||
"download": {
|
||||
"label": "Preuzmi dnevnik"
|
||||
},
|
||||
"copy": {
|
||||
"label": "Kopiraj u privremenu memoriju",
|
||||
"success": "Dnevnik je kopiran u privremenu memoriju",
|
||||
"error": "Neuspešno kopiranje dnevnika u privremenu memoriju"
|
||||
},
|
||||
"type": {
|
||||
"label": "Tip",
|
||||
"timestamp": "Vremenska oznaka",
|
||||
"tag": "Oznaka",
|
||||
"message": "Poruka"
|
||||
},
|
||||
"tips": "Dnevnik se strimuje sa servera",
|
||||
"toast": {
|
||||
"error": {
|
||||
"fetchingLogsFailed": "Greška pri preuzimanju dnevnika: {{errorMessage}}",
|
||||
"whileStreamingLogs": "Greška tokom strimovanja dnevnika: {{errorMessage}}"
|
||||
}
|
||||
}
|
||||
},
|
||||
"general": {
|
||||
"title": "Opšte",
|
||||
"detector": {
|
||||
"title": "Detektori",
|
||||
"inferenceSpeed": "Brzina obrade detektora",
|
||||
"temperature": "Temperatura detektora",
|
||||
"cpuUsage": "Zauzeće procesora od strane detektora",
|
||||
"cpuUsageInformation": "Procesor koji se koristi za pripremu ulaznih i izlaznih podataka za modele detekcije i iz njih. Ova vrednost ne meri iskorišćenost same obrade, čak i ako se koristi grafički procesor ili akcelerator.",
|
||||
"memoryUsage": "Zauzeće memorije od strane detektora"
|
||||
},
|
||||
"hardwareInfo": {
|
||||
"title": "Informacije o hardveru",
|
||||
"gpuUsage": "GPU zauzeće",
|
||||
"gpuMemory": "GPU memorija",
|
||||
"gpuEncoder": "GPU enkoder",
|
||||
"gpuCompute": "GPU obrada / enkodiranje",
|
||||
"gpuDecoder": "GPU dekoder",
|
||||
"gpuTemperature": "GPU temperatura",
|
||||
"gpuInfo": {
|
||||
"vainfoOutput": {
|
||||
"title": "Vainfo izlazni podaci",
|
||||
"returnCode": "Izlazni kod: {{code}}",
|
||||
"processOutput": "Izlazni podaci procesa:",
|
||||
"processError": "Greška u procesu:"
|
||||
},
|
||||
"nvidiaSMIOutput": {
|
||||
"title": "Nvidia SMI izlazni podaci",
|
||||
"name": "Naziv: {{name}}",
|
||||
"driver": "Drajver: {{driver}}",
|
||||
"cudaComputerCapability": "CUDA računarska sposobnost: {{cuda_compute}}",
|
||||
"vbios": "VBios informacije: {{vbios}}"
|
||||
},
|
||||
"closeInfo": {
|
||||
"label": "Zatvori informacije o GPU"
|
||||
},
|
||||
"copyInfo": {
|
||||
"label": "Kopiraj informacije o GPU"
|
||||
},
|
||||
"toast": {
|
||||
"success": "Informacije o GPU su kopirane u privremenu memoriju"
|
||||
}
|
||||
},
|
||||
"npuUsage": "NPU zauzeće",
|
||||
"npuMemory": "NPU memorija",
|
||||
"npuTemperature": "NPU temperatura"
|
||||
},
|
||||
"otherProcesses": {
|
||||
"title": "Ostali procesi",
|
||||
"series": {
|
||||
"audio_detector": "audio detektor",
|
||||
"embeddings": "vektorski prikazi",
|
||||
"review_segment": "segment pregleda",
|
||||
"recording": "snimanje",
|
||||
"go2rtc": "go2rtc"
|
||||
},
|
||||
"processMemoryUsage": "Zauzeće memorije",
|
||||
"processCpuUsage": "Zauzeće CPU"
|
||||
}
|
||||
},
|
||||
"storage": {
|
||||
"recordings": {
|
||||
"title": "Snimci",
|
||||
"tips": "Ova vrednost predstavlja ukupno skladište koje zauzimaju snimci u Frigate bazi podataka. Frigate ne prati zauzeće skladišta za sve datoteke na vašem disku.",
|
||||
"earliestRecording": "Najraniji dostupni snimak:"
|
||||
},
|
||||
"overview": "Pregled",
|
||||
"title": "Skladište",
|
||||
"shm": {
|
||||
"title": "Dodeljena SHM (deljena) memorija",
|
||||
"warning": "Trenutna veličina SHM-a od {{total}} MB je previše mala. Povećajte je na najmanje {{min_shm}} MB.",
|
||||
"frameLifetime": {
|
||||
"title": "Vreme čuvanja kadra",
|
||||
"description": "Svaka kamera ima {{frames}} mesta za kadrove u deljenoj memoriji. Pri najvećem broju kadrova u sekundi (frame rate) te kamere, svaki kadar je dostupan približno {{lifetime}} sekundi pre nego što bude prepisan."
|
||||
}
|
||||
},
|
||||
"cameraStorage": {
|
||||
"title": "Skladište kamera",
|
||||
"camera": "Kamera",
|
||||
"unusedStorageInformation": "Informacije o neiskorišćenom prostoru",
|
||||
"storageUsed": "Skladište",
|
||||
"percentageOfTotalUsed": "Procenat od ukupnog",
|
||||
"bandwidth": "Propusni opseg",
|
||||
"unused": {
|
||||
"title": "Neiskorišćeno",
|
||||
"tips": "Ova vrednost možda neće tačno prikazati slobodan prostor dostupan Frigate-u ako na vašem disku imate sačuvane druge datoteke pored Frigate snimaka. Frigate ne prati zauzeće skladišta izvan svojih snimaka."
|
||||
},
|
||||
"subStream": {
|
||||
"information": "Informacije o skladištu sporednog strima",
|
||||
"tips": "Kamere koje snimaju sporedni strim (sub stream) odvojeno prikazuju zauzeće za izvorni i nizak kvalitet, koristeći iste nazive kao i opcije kvaliteta pri reprodukciji snimaka. Niski kvalitet snimaka ostaje na disku sve dok ne istekne period čuvanja, tako da se ovaj prikaz može i dalje pojavljivati za kameru nakon što je snimanje sporednog strima isključeno."
|
||||
}
|
||||
}
|
||||
},
|
||||
"cameras": {
|
||||
"title": "Kamere",
|
||||
"overview": "Pregled",
|
||||
"info": {
|
||||
"aspectRatio": "Odnos stranica",
|
||||
"cameraProbeInfo": "Informacije o analizi kamere {{camera}}",
|
||||
"streamDataFromFFPROBE": "Podaci o strimu su dobijeni pomoću <code>ffprobe</code>.",
|
||||
"fetching": "Preuzimanje podataka o kameri",
|
||||
"stream": "Strim {{idx}}",
|
||||
"video": "Video:",
|
||||
"codec": "Kodek:",
|
||||
"resolution": "Rezolucija:",
|
||||
"fps": "FPS:",
|
||||
"unknown": "Nepoznato",
|
||||
"audio": "Audio:",
|
||||
"error": "Greška: {{error}}",
|
||||
"tips": {
|
||||
"title": "Informacije o analizi kamere"
|
||||
},
|
||||
"keyframes": {
|
||||
"title": "Analiza ključnih kadrova",
|
||||
"analyzing": "Analiza ključnih kadrova je u toku... preostalo je {{seconds}} sekundi",
|
||||
"stillAnalyzing": "Analiza ključnih kadrova je i dalje u toku...",
|
||||
"recordStream": "Strim za snimanje:",
|
||||
"keyframeCount": "Uočeni ključni kadrovi:",
|
||||
"observedDuration": "Uočeno trajanje:",
|
||||
"gap": "Razmak između ključnih kadrova (min / prosek / maks):",
|
||||
"segmentLength": "Dužina segmenta snimanja:",
|
||||
"ok": "Ključni kadrovi na svakih ~{{seconds}} s, što je dobro za snimanje i reprodukciju.",
|
||||
"warningFixed": "Ključni kadrovi su ravnomerno raspoređeni ali su retki (na svakih ~{{seconds}} s). Snimanje i dalje radi, ali će reprodukcija uživo i premotavanje započinjati sporije. Podesite interval ključnih kadrova (I-frame / keyframe) na kameri tako da odgovara broju kadrova u sekundi.",
|
||||
"warningVariable": "Razmak između ključnih kadrova je neujednačen (od {{minSeconds}} s do {{maxSeconds}} s), što obično znači da je uključen pametni kodek (H.264+/H.265+). Ovo se ne preporučuje.",
|
||||
"errorFixed": "Ključni kadrovi na svakih ~{{seconds}} s su duži od dužine segmenta snimanja ({{segmentTime}} s), pa pojedini segmenti nemaju ključni kadar i neće moći da se reprodukuju. Skratite interval ključnih kadrova (I-frame / keyframe) na kameri tako da odgovara broju kadrova u sekundi (frame rate).",
|
||||
"errorVariable": "Razmaci između ključnih kadrova dostižu ~{{seconds}} s, što je duže od dužine segmenta snimanja ({{segmentTime}} s). Pojedini segmenti neće imati ključni kadar, što prekida reprodukciju. Onemogućite pametni/+ kodek na kameri ili skratite interval ključnih kadrova.",
|
||||
"unknown": "Nije moguće utvrditi razmak između ključnih kadrova.",
|
||||
"recordDisabled": "Snimanje je onemogućeno za ovu kameru."
|
||||
}
|
||||
},
|
||||
"framesAndDetections": "Kadrovi / Detekcije",
|
||||
"noCameras": {
|
||||
"title": "Nema pronađenih kamera"
|
||||
},
|
||||
"label": {
|
||||
"camera": "kamera",
|
||||
"detect": "detekcija",
|
||||
"skipped": "preskočeno",
|
||||
"ffmpeg": "FFmpeg",
|
||||
"capture": "snimanje",
|
||||
"overallFramesPerSecond": "ukupno kadrova u sekundi",
|
||||
"overallDetectionsPerSecond": "ukupno detekcija u sekundi",
|
||||
"overallSkippedDetectionsPerSecond": "ukupno preskočenih detekcija u sekundi",
|
||||
"cameraFfmpeg": "{{camName}} FFmpeg",
|
||||
"cameraCapture": "{{camName}} snimanje",
|
||||
"cameraDetect": "{{camName}} detekcija",
|
||||
"cameraGpu": "{{camName}} GPU",
|
||||
"cameraFramesPerSecond": "{{camName}} kadrova u sekundi",
|
||||
"cameraDetectionsPerSecond": "{{camName}} detekcija u sekundi",
|
||||
"cameraSkippedDetectionsPerSecond": "{{camName}} preskočenih detekcija u sekundi"
|
||||
},
|
||||
"connectionQuality": {
|
||||
"title": "Kvalitet veze",
|
||||
"excellent": "Odličan",
|
||||
"fair": "Zadovoljavajući",
|
||||
"poor": "Loš",
|
||||
"unusable": "Neupotrebljiv",
|
||||
"fps": "FPS",
|
||||
"expectedFps": "Očekivani FPS",
|
||||
"reconnectsLastHour": "Ponovna povezivanja (poslednji sat)",
|
||||
"stallsLastHour": "Zastoji (poslednji sat)"
|
||||
},
|
||||
"toast": {
|
||||
"success": {
|
||||
"copyToClipboard": "Podaci o analizi su kopirani u privremenu memoriju."
|
||||
},
|
||||
"error": {
|
||||
"unableToProbeCamera": "Nije moguće analizirati kameru: {{errorMessage}}"
|
||||
}
|
||||
}
|
||||
},
|
||||
"lastRefreshed": "Poslednji put osveženo: ",
|
||||
"stats": {
|
||||
"cameraSkippedDetections": "Kamera {{camera}} preskače detekciju na {{pct}}% kadrova",
|
||||
"healthy": "Sistem ispravn",
|
||||
"systemNotices_one": "{{count}} sistemsko obaveštenje",
|
||||
"systemNotices_other": "{{count}} sistemska obaveštenja",
|
||||
"moreMessages_one": "+{{count}}",
|
||||
"moreMessages_other": "+{{count}}",
|
||||
"reindexingEmbeddings": "Ponovno indeksiranje vektorskih prikaza (završeno {{processed}}%)",
|
||||
"reindexEmbeddingsFailed": "Ponovno indeksiranje vektorskih prikaza nije uspelo, proverite dnevnik",
|
||||
"cameraIsOffline": "Kamera {{camera}} je van mreže",
|
||||
"detectIsSlow": "Detekcija na {{detect}} je spora ({{speed}} ms)",
|
||||
"detectIsVerySlow": "Detekcija na {{detect}} je veoma spora ({{speed}} ms)",
|
||||
"retentionUnmet": "Snimci se brišu pre isteka perioda čuvanja kako bi se oslobodio prostor",
|
||||
"debugReplayActive": "Sesija otklanjanja grešaka i ponovnog puštanja je aktivna"
|
||||
},
|
||||
"enrichments": {
|
||||
"title": "Poboljšanja",
|
||||
"infPerSecond": "Broj obrada u sekundi",
|
||||
"averageInf": "Prosečno vreme obrade",
|
||||
"embeddings": {
|
||||
"image_embedding": "Vektorski prikaz slike",
|
||||
"text_embedding": "Vektorski prikaz teksta",
|
||||
"face_recognition": "Prepoznavanje lica",
|
||||
"plate_recognition": "Prepoznavanje tablica",
|
||||
"image_embedding_speed": "Brzina vektorskog prikaza slike",
|
||||
"face_embedding_speed": "Brzina vektorskog prikaza lica",
|
||||
"face_recognition_speed": "Brzina prepoznavanja lica",
|
||||
"plate_recognition_speed": "Brzina prepoznavanja tablica",
|
||||
"text_embedding_speed": "Brzina vektorskog prikaza teksta",
|
||||
"yolov9_plate_detection_speed": "YOLOv9 brzina detekcije tablica",
|
||||
"yolov9_plate_detection": "YOLOv9 detekcija tablica",
|
||||
"review_description": "Opis stavke pregleda",
|
||||
"review_description_speed": "Brzina opisivanja stavke pregleda",
|
||||
"review_description_events_per_second": "Opis stavke pregleda",
|
||||
"object_description": "Opis objekta",
|
||||
"object_description_speed": "Brzina opisivanja objekta",
|
||||
"object_description_events_per_second": "Opis objekta",
|
||||
"classification": "{{name}} klasifikacija",
|
||||
"classification_speed": "{{name}} brzina klasifikacije",
|
||||
"classification_events_per_second": "{{name}} događaja klasifikacije u sekundi"
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
@@ -1154,8 +1154,6 @@
|
||||
},
|
||||
"apiKey": {
|
||||
"title": "Frigate+ API кључ",
|
||||
"validated": "Frigate+ API кључ је детектован и валидиран",
|
||||
"notValidated": "Frigate+ API кључ није детектован или није валидиран",
|
||||
"desc": "Frigate+ API кључ омогућава интеграцију са Frigate+ сервисом.",
|
||||
"plusLink": "Прочитајте више о Frigate+"
|
||||
},
|
||||
|
||||
@@ -1 +1,28 @@
|
||||
{}
|
||||
{
|
||||
"bicycle": "Bicikl",
|
||||
"car": "Automobil",
|
||||
"motorcycle": "Motocikl",
|
||||
"bus": "Autobus",
|
||||
"train": "Voz",
|
||||
"boat": "Čamac",
|
||||
"bird": "Ptica",
|
||||
"cat": "Mačka",
|
||||
"dog": "Pas",
|
||||
"horse": "Konj",
|
||||
"sheep": "Ovca",
|
||||
"skateboard": "Skejtbord",
|
||||
"door": "Vrata",
|
||||
"mouse": "Miš",
|
||||
"keyboard": "Tastatura",
|
||||
"sink": "Sudopera",
|
||||
"blender": "Blender",
|
||||
"clock": "Sat",
|
||||
"scissors": "Makaze",
|
||||
"hair_dryer": "Fen za kosu",
|
||||
"toothbrush": "Četkica za zube",
|
||||
"vehicle": "Vozilo",
|
||||
"animal": "Životinja",
|
||||
"bark": "Lavež",
|
||||
"goat": "Koza",
|
||||
"camera": "Kamera"
|
||||
}
|
||||
|
||||
@@ -1 +1,16 @@
|
||||
{}
|
||||
{
|
||||
"form": {
|
||||
"user": "Korisničko ime",
|
||||
"password": "Lozinka",
|
||||
"login": "Prijava",
|
||||
"firstTimeLogin": "Pokušavate da se prijavite prvi put? Podaci za prijavu su ispisani u Frigate dnevniku.",
|
||||
"errors": {
|
||||
"usernameRequired": "Korisničko ime je obavezno",
|
||||
"passwordRequired": "Lozinka je obavezna",
|
||||
"rateLimit": "Prekoračeno je ograničenje broja pokušaja. Pokušajte ponovo kasnije.",
|
||||
"loginFailed": "Prijava nije uspela",
|
||||
"unknownError": "Nepoznata greška. Proverite dnevnik.",
|
||||
"webUnknownError": "Nepoznata greška. Proverite dnevnik konzole."
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
@@ -1 +1,8 @@
|
||||
{}
|
||||
{
|
||||
"iconPicker": {
|
||||
"selectIcon": "Izaberite ikonu",
|
||||
"search": {
|
||||
"placeholder": "Pretražite ikone…"
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
@@ -1 +1,10 @@
|
||||
{}
|
||||
{
|
||||
"button": {
|
||||
"downloadVideo": {
|
||||
"label": "Preuzmi video",
|
||||
"toast": {
|
||||
"success": "Preuzimanje video-snimka iz pregleda je počelo."
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
File diff suppressed because it is too large
Load Diff
File diff suppressed because it is too large
Load Diff
@@ -1 +1,73 @@
|
||||
{}
|
||||
{
|
||||
"audio": {
|
||||
"global": {
|
||||
"detection": "Globalna detekcija",
|
||||
"sensitivity": "Globalna osetljivost"
|
||||
},
|
||||
"cameras": {
|
||||
"detection": "Detekcija",
|
||||
"sensitivity": "Osetljivost"
|
||||
}
|
||||
},
|
||||
"timestamp_style": {
|
||||
"global": {
|
||||
"appearance": "Globalni izgled"
|
||||
},
|
||||
"cameras": {
|
||||
"appearance": "Izgled"
|
||||
}
|
||||
},
|
||||
"motion": {
|
||||
"global": {
|
||||
"sensitivity": "Globalna osetljivost",
|
||||
"algorithm": "Globalni algoritam"
|
||||
},
|
||||
"cameras": {
|
||||
"sensitivity": "Osetljivost",
|
||||
"algorithm": "Algoritam"
|
||||
}
|
||||
},
|
||||
"snapshots": {
|
||||
"global": {
|
||||
"display": "Globalni prikaz"
|
||||
},
|
||||
"cameras": {
|
||||
"display": "Prikaz"
|
||||
}
|
||||
},
|
||||
"detect": {
|
||||
"global": {
|
||||
"resolution": "Globalna rezolucija",
|
||||
"tracking": "Globalno praćenje"
|
||||
},
|
||||
"cameras": {
|
||||
"resolution": "Rezolucija",
|
||||
"tracking": "Praćenje"
|
||||
}
|
||||
},
|
||||
"objects": {
|
||||
"global": {
|
||||
"tracking": "Globalno praćenje",
|
||||
"filtering": "Globalno filtriranje"
|
||||
},
|
||||
"cameras": {
|
||||
"tracking": "Praćenje",
|
||||
"filtering": "Filtriranje"
|
||||
}
|
||||
},
|
||||
"record": {
|
||||
"global": {
|
||||
"retention": "Globalno čuvanje",
|
||||
"events": "Globalni događaji"
|
||||
},
|
||||
"cameras": {
|
||||
"retention": "Čuvanje",
|
||||
"events": "Događaji"
|
||||
}
|
||||
},
|
||||
"ffmpeg": {
|
||||
"cameras": {
|
||||
"cameraFfmpeg": "FFmpeg argumenti specifični za kameru"
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
@@ -1 +1,131 @@
|
||||
{}
|
||||
{
|
||||
"person": "Osoba",
|
||||
"bicycle": "Bicikl",
|
||||
"car": "Automobil",
|
||||
"motorcycle": "Motocikl",
|
||||
"airplane": "Avion",
|
||||
"bus": "Autobus",
|
||||
"train": "Voz",
|
||||
"boat": "Čamac",
|
||||
"traffic_light": "Semafor",
|
||||
"fire_hydrant": "Protivpožarni hidrant",
|
||||
"street_sign": "Ulični znak",
|
||||
"stop_sign": "Znak STOP",
|
||||
"parking_meter": "Parking metar",
|
||||
"bench": "Klupa",
|
||||
"bird": "Ptica",
|
||||
"cat": "Mačka",
|
||||
"dog": "Pas",
|
||||
"horse": "Konj",
|
||||
"sheep": "Ovca",
|
||||
"cow": "Krava",
|
||||
"elephant": "Slon",
|
||||
"bear": "Medved",
|
||||
"zebra": "Zebra",
|
||||
"giraffe": "Žirafa",
|
||||
"hat": "Šešir",
|
||||
"backpack": "Ranac",
|
||||
"umbrella": "Kišobran",
|
||||
"shoe": "Cipela",
|
||||
"eye_glasses": "Naočare",
|
||||
"handbag": "Torbica",
|
||||
"tie": "Kravata",
|
||||
"suitcase": "Kofer",
|
||||
"frisbee": "Frizbi",
|
||||
"skis": "Skije",
|
||||
"snowboard": "Snoubord",
|
||||
"sports_ball": "Sportska lopta",
|
||||
"kite": "Zmaj",
|
||||
"baseball_bat": "Bezbol palica",
|
||||
"baseball_glove": "Bezbol rukavica",
|
||||
"skateboard": "Skejtbord",
|
||||
"surfboard": "Daska za surfovanje",
|
||||
"tennis_racket": "Teniski reket",
|
||||
"bottle": "Flaša",
|
||||
"plate": "Tanjir",
|
||||
"wine_glass": "Čaša za vino",
|
||||
"cup": "Šolja",
|
||||
"fork": "Viljuška",
|
||||
"knife": "Nož",
|
||||
"spoon": "Kašika",
|
||||
"bowl": "Činija",
|
||||
"banana": "Banana",
|
||||
"apple": "Jabuka",
|
||||
"sandwich": "Sendvič",
|
||||
"orange": "Pomorandža",
|
||||
"broccoli": "Brokoli",
|
||||
"carrot": "Šargarepa",
|
||||
"hot_dog": "Hot-dog",
|
||||
"pizza": "Pica",
|
||||
"donut": "Krofna",
|
||||
"cake": "Kolač",
|
||||
"chair": "Stolica",
|
||||
"couch": "Kauč",
|
||||
"potted_plant": "Saksijska biljka",
|
||||
"bed": "Krevet",
|
||||
"mirror": "Ogledalo",
|
||||
"dining_table": "Trpezarijski sto",
|
||||
"window": "Prozor",
|
||||
"desk": "Radni sto",
|
||||
"toilet": "Toalet",
|
||||
"door": "Vrata",
|
||||
"tv": "Televizor",
|
||||
"laptop": "Laptop",
|
||||
"mouse": "Miš",
|
||||
"remote": "Daljinski upravljač",
|
||||
"keyboard": "Tastatura",
|
||||
"cell_phone": "Mobilni telefon",
|
||||
"microwave": "Mikrotalasna",
|
||||
"oven": "Rerna",
|
||||
"toaster": "Toster",
|
||||
"sink": "Sudopera",
|
||||
"refrigerator": "Frižider",
|
||||
"blender": "Blender",
|
||||
"book": "Knjiga",
|
||||
"clock": "Sat",
|
||||
"vase": "Vaza",
|
||||
"scissors": "Makaze",
|
||||
"teddy_bear": "Plišani medvedić",
|
||||
"hair_dryer": "Fen za kosu",
|
||||
"toothbrush": "Četkica za zube",
|
||||
"hair_brush": "Četka za kosu",
|
||||
"vehicle": "Vozilo",
|
||||
"squirrel": "Veverica",
|
||||
"deer": "Jelen",
|
||||
"animal": "Životinja",
|
||||
"bark": "Lavež",
|
||||
"fox": "Lisica",
|
||||
"goat": "Koza",
|
||||
"rabbit": "Zec",
|
||||
"raccoon": "Rakun",
|
||||
"robot_lawnmower": "Robotska kosilica",
|
||||
"waste_bin": "Kanta za smeće",
|
||||
"on_demand": "Na zahtev",
|
||||
"face": "Lice",
|
||||
"license_plate": "Registarska tablica",
|
||||
"package": "Paket",
|
||||
"bbq_grill": "Roštilj",
|
||||
"amazon": "Amazon",
|
||||
"usps": "USPS",
|
||||
"ups": "UPS",
|
||||
"fedex": "FedEx",
|
||||
"dhl": "DHL",
|
||||
"an_post": "An Post",
|
||||
"purolator": "Purolator",
|
||||
"postnl": "PostNL",
|
||||
"nzpost": "NZPost",
|
||||
"postnord": "PostNord",
|
||||
"gls": "GLS",
|
||||
"dpd": "DPD",
|
||||
"canada_post": "Canada Post",
|
||||
"royal_mail": "Royal Mail",
|
||||
"school_bus": "Školski autobus",
|
||||
"skunk": "Tvor",
|
||||
"kangaroo": "Kengur",
|
||||
"baby": "Beba",
|
||||
"baby_stroller": "Dečja kolica",
|
||||
"rickshaw": "Rikša",
|
||||
"rodent": "Glodar",
|
||||
"possum": "Oposum",
|
||||
"garbage_truck": "Kamion za odvoženje smeća"
|
||||
}
|
||||
|
||||
@@ -1 +1,88 @@
|
||||
{}
|
||||
{
|
||||
"documentTitle": "Ćaskanje - Frigate",
|
||||
"title": "Frigate ćaskanje",
|
||||
"subtitle": "Vaš AI asistent za upravljanje kamerama i uvođenje u detalje",
|
||||
"placeholder": "Pitajte bilo šta...",
|
||||
"error": "Nešto je pošlo po zlu. Pokušajte ponovo.",
|
||||
"processing": "Obrada...",
|
||||
"toolsUsed": "Korišćeno: {{tools}}",
|
||||
"showTools": "Prikaži alate ({{count}})",
|
||||
"hideTools": "Sakrij alate",
|
||||
"call": "Poziv",
|
||||
"result": "Rezultat",
|
||||
"arguments": "Argumenti:",
|
||||
"response": "Odgovor:",
|
||||
"attachment_chip_label": "{{label}} na kameri {{camera}}",
|
||||
"attachment_chip_remove": "Ukloni prilog",
|
||||
"open_in_explore": "Otvori u pretraživaču",
|
||||
"attach_event_aria": "Priloži događaj {{eventId}}",
|
||||
"attachment_picker_paste_label": "Ili nalepite ID događaja",
|
||||
"attachment_picker_attach": "Priloži",
|
||||
"attachment_picker_placeholder": "Priloži događaj",
|
||||
"quick_reply_find_similar": "Pronađi slična uočavanja",
|
||||
"quick_reply_tell_me_more": "Reci mi nešto više o ovome",
|
||||
"quick_reply_when_else": "Kada je još viđeno?",
|
||||
"quick_reply_find_similar_text": "Pronađi slična uočavanja za ovo.",
|
||||
"quick_reply_tell_me_more_text": "Reci mi nešto više o ovome.",
|
||||
"quick_reply_when_else_text": "Kada je još ovo viđeno?",
|
||||
"anchor": "Referenca",
|
||||
"similarity_score": "Sličnost",
|
||||
"no_similar_objects_found": "Nisu pronađeni slični objekti.",
|
||||
"semantic_search_required": "Semantička pretraga mora biti omogućena da bi se pronašli slični objekti.",
|
||||
"send": "Pošalji",
|
||||
"suggested_requests": "Pokušajte da pitate:",
|
||||
"starting_requests": {
|
||||
"show_recent_events": "Prikaži nedavne događaje",
|
||||
"show_camera_status": "Prikaži status kamera",
|
||||
"recap": "Šta se desilo u odsustvu?",
|
||||
"watch_camera": "Prati kameru za aktivnosti"
|
||||
},
|
||||
"starting_requests_prompts": {
|
||||
"show_recent_events": "Prikaži mi nedavne događaje iz poslednjeg sata",
|
||||
"show_camera_status": "Kakav je trenutni status kamera?",
|
||||
"recap": "Šta se desilo u odsustvu?",
|
||||
"watch_camera": "Prati ulazna vrata i javi mi ako se neko pojavi"
|
||||
},
|
||||
"new_chat": "Novo ćaskanje",
|
||||
"settings": {
|
||||
"title": "Podešavanja ćaskanja",
|
||||
"show_stats": {
|
||||
"title": "Prikaži statistiku",
|
||||
"desc": "Prikaži brzinu generisanja i veličinu konteksta za odgovore u ćaskanju.",
|
||||
"while_generating": "Tokom generisanja",
|
||||
"always": "Uvek"
|
||||
},
|
||||
"auto_scroll": {
|
||||
"title": "Automatsko pomeranje ekrana",
|
||||
"desc": "Prati nove poruke kako pristižu."
|
||||
},
|
||||
"always_allow": {
|
||||
"title": "Uvek dozvoljene akcije",
|
||||
"desc": "Akcije koje asistent može da pokrene bez prethodnog traženja dozvole.",
|
||||
"none": "Nijedna",
|
||||
"reset": "Resetuj"
|
||||
}
|
||||
},
|
||||
"stats": {
|
||||
"context": "{{tokens}} tokena",
|
||||
"tokens_per_second": "{{rate}} t/s"
|
||||
},
|
||||
"reasoning": {
|
||||
"active": "Razmišljanje…",
|
||||
"show": "Prikaži tok razmišljanja",
|
||||
"hide": "Sakrij tok razmišljanja"
|
||||
},
|
||||
"thinking": {
|
||||
"toggle": "Prebaci prikaz razmišljanja"
|
||||
},
|
||||
"approval": {
|
||||
"title": "Odobriti {{tool}}?",
|
||||
"desc": "Ova akcija menja nešto u Frigate-u. Pregledajte detalje pre odobravanja.",
|
||||
"approve": "Odobri",
|
||||
"always_allow": "Uvek dozvoli",
|
||||
"reject": "Odbij",
|
||||
"approved": "Odobreno",
|
||||
"rejected": "Odbijeno",
|
||||
"placeholder": "Odobrite ili odbijte akciju na čekanju da biste nastavili"
|
||||
}
|
||||
}
|
||||
|
||||
@@ -1 +1,18 @@
|
||||
{}
|
||||
{
|
||||
"documentTitle": "Editor konfiguracije - Frigate",
|
||||
"configEditor": "Editor konfiguracije",
|
||||
"safeConfigEditor": "Editor konfiguracije (Bezbedan režim)",
|
||||
"safeModeDescription": "Frigate je u bezbednom režimu zbog greške u validaciji konfiguracije.",
|
||||
"copyConfig": "Kopiraj konfiguraciju",
|
||||
"saveAndRestart": "Sačuvaj i ponovo pokreni",
|
||||
"saveOnly": "Samo sačuvaj",
|
||||
"confirm": "Izaći bez čuvanja?",
|
||||
"toast": {
|
||||
"success": {
|
||||
"copyToClipboard": "Konfiguracija je kopirana u privremenu memoriju."
|
||||
},
|
||||
"error": {
|
||||
"savingError": "Greška pri čuvanju konfiguracije"
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
@@ -1 +1,96 @@
|
||||
{}
|
||||
{
|
||||
"alerts": "Upozorenja",
|
||||
"detections": "Detekcije",
|
||||
"motion": {
|
||||
"label": "Kretanje",
|
||||
"only": "Samo kretanje"
|
||||
},
|
||||
"allCameras": "Sve kamere",
|
||||
"empty": {
|
||||
"alert": "Nema upozorenja za pregled",
|
||||
"detection": "Nema detekcija za pregled",
|
||||
"motion": "Nisu pronađeni podaci o kretanju",
|
||||
"recordingsDisabled": {
|
||||
"title": "Snimanje mora biti omogućeno",
|
||||
"description": "Stavke za pregled se mogu kreirati za kameru samo kada je snimanje omogućeno za tu kameru."
|
||||
}
|
||||
},
|
||||
"timeline": {
|
||||
"label": "Vremenska linija",
|
||||
"aria": "Izaberite vremensku liniju"
|
||||
},
|
||||
"zoomIn": "Uveličaj",
|
||||
"zoomOut": "Umanji",
|
||||
"subOnlyQuality": "U ovom vremenskom opsegu dostupni su samo snimci nižeg kvaliteta",
|
||||
"events": {
|
||||
"label": "Događaji",
|
||||
"aria": "Izaberite događaje",
|
||||
"noFoundForTimePeriod": "Nema pronađenih događaja za ovaj vremenski period."
|
||||
},
|
||||
"detail": {
|
||||
"label": "Detalj",
|
||||
"noDataFound": "Nema detaljnih podataka za pregled",
|
||||
"aria": "Uključi/isključi detaljan prikaz",
|
||||
"trackedObject_one": "{{count}} objekat",
|
||||
"trackedObject_other": "{{count}} objekata",
|
||||
"noObjectDetailData": "Nema dostupnih detaljnih podataka o objektu.",
|
||||
"settings": "Podešavanja detaljnog prikaza",
|
||||
"alwaysExpandActive": {
|
||||
"title": "Uvek proširi aktivno",
|
||||
"desc": "Uvek proširi detalje o objektu za aktivnu stavku pregleda kada su dostupni."
|
||||
}
|
||||
},
|
||||
"objectTrack": {
|
||||
"trackedPoint": "Praćena tačka",
|
||||
"clickToSeek": "Kliknite da premotate na ovo vreme"
|
||||
},
|
||||
"documentTitle": "Pregled - Frigate",
|
||||
"recordings": {
|
||||
"documentTitle": "Snimci - Frigate",
|
||||
"invalidSharedLink": "Nije moguće otvoriti vezu ka snimku sa vremenskom oznakom zbog greške u parsiranju.",
|
||||
"invalidSharedCamera": "Nije moguće otvoriti vezu ka snimku sa vremenskom oznakom zbog nepoznate ili neovlašćene kamere."
|
||||
},
|
||||
"calendarFilter": {
|
||||
"last24Hours": "Poslednja 24 sata"
|
||||
},
|
||||
"markAsReviewed": "Označi kao pregledano",
|
||||
"markTheseItemsAsReviewed": "Označi ove stavke kao pregledane",
|
||||
"newReviewItems": {
|
||||
"label": "Prikaži nove stavke za pregled",
|
||||
"button": "Nove stavke za pregled"
|
||||
},
|
||||
"selected_one": "Izabrano: {{count}}",
|
||||
"selected_other": "Izabrano: {{count}}",
|
||||
"select_all": "Sve",
|
||||
"camera": "Kamera",
|
||||
"detected": "detektovano",
|
||||
"normalActivity": "Normalno",
|
||||
"needsReview": "Zahteva pregled",
|
||||
"securityConcern": "Bezbednosni problem",
|
||||
"motionSearch": {
|
||||
"menuItem": "Pretraga kretanja",
|
||||
"openMenu": "Opcije kamere"
|
||||
},
|
||||
"motionPreviews": {
|
||||
"menuItem": "Prikaži preglede kretanja",
|
||||
"title": "Pregledi kretanja: {{camera}}",
|
||||
"mobileSettingsTitle": "Podešavanja pregleda kretanja",
|
||||
"mobileSettingsDesc": "Prilagodite brzinu reprodukcije, zatamnjenje i isecanje, i izaberite datum za pregled video zapisa koji sadrže samo kretanje.",
|
||||
"dim": "Zatamnjenje",
|
||||
"dimAria": "Prilagodite intenzitet zatamnjenja",
|
||||
"dimDesc": "Povećajte zatamnjenje da biste povećali vidljivost oblasti sa kretanjem.",
|
||||
"speed": "Brzina",
|
||||
"speedAria": "Izaberite brzinu reprodukcije pregleda",
|
||||
"speedDesc": "Izaberite koliko brzo se reprodukuju video zapisi pregleda.",
|
||||
"back": "Nazad",
|
||||
"empty": "Nema dostupnih pregleda",
|
||||
"noPreview": "Pregled nije dostupan",
|
||||
"seekAria": "Premotajte plejer za kameru {{camera}} na {{time}}",
|
||||
"filter": "Filter",
|
||||
"filterDesc": "Izaberite oblasti da biste prikazali samo video zapise sa kretanjem u tim regionima.",
|
||||
"filterClear": "Očisti",
|
||||
"crop": "Iseci prema filteru",
|
||||
"cropAria": "Uključi/isključi isecanje pregleda na filtrirane oblasti",
|
||||
"cropDesc": "Zumirajte preglede na izabrane oblasti filtera umesto prikazivanja celog kadra."
|
||||
}
|
||||
}
|
||||
|
||||
@@ -1 +1,12 @@
|
||||
{}
|
||||
{
|
||||
"filter": "Filtriraj",
|
||||
"export": "Izvezi",
|
||||
"calendar": "Kalendar",
|
||||
"filters": "Filteri",
|
||||
"toast": {
|
||||
"error": {
|
||||
"noValidTimeSelected": "Nije izabran važeći vremenski raspon",
|
||||
"endTimeMustAfterStartTime": "Vreme završetka mora biti nakon vremena početka"
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
Some files were not shown because too many files have changed in this diff Show More
Reference in New Issue
Block a user