Compare commits

...

12 Commits

Author SHA1 Message Date
Weblate (bot)
3100e488ea
Merge 2a8ada9a14 into 1b57fb15a7 2025-11-27 13:59:01 +00:00
Hosted Weblate
2a8ada9a14
Translated using Weblate (Norwegian Bokmål)
Currently translated at 100.0% (72 of 72 strings)

Translated using Weblate (Norwegian Bokmål)

Currently translated at 100.0% (639 of 639 strings)

Translated using Weblate (Norwegian Bokmål)

Currently translated at 100.0% (52 of 52 strings)

Translated using Weblate (Norwegian Bokmål)

Currently translated at 100.0% (116 of 116 strings)

Translated using Weblate (Norwegian Bokmål)

Currently translated at 100.0% (55 of 55 strings)

Translated using Weblate (Norwegian Bokmål)

Currently translated at 100.0% (128 of 128 strings)

Translated using Weblate (Norwegian Bokmål)

Currently translated at 100.0% (48 of 48 strings)

Co-authored-by: Hosted Weblate <hosted@weblate.org>
Co-authored-by: OverTheHillsAndFarAway <prosjektx@users.noreply.hosted.weblate.org>
Translate-URL: https://hosted.weblate.org/projects/frigate-nvr/components-dialog/nb_NO/
Translate-URL: https://hosted.weblate.org/projects/frigate-nvr/components-filter/nb_NO/
Translate-URL: https://hosted.weblate.org/projects/frigate-nvr/views-classificationmodel/nb_NO/
Translate-URL: https://hosted.weblate.org/projects/frigate-nvr/views-explore/nb_NO/
Translate-URL: https://hosted.weblate.org/projects/frigate-nvr/views-facelibrary/nb_NO/
Translate-URL: https://hosted.weblate.org/projects/frigate-nvr/views-search/nb_NO/
Translate-URL: https://hosted.weblate.org/projects/frigate-nvr/views-settings/nb_NO/
Translation: Frigate NVR/components-dialog
Translation: Frigate NVR/components-filter
Translation: Frigate NVR/views-classificationmodel
Translation: Frigate NVR/views-explore
Translation: Frigate NVR/views-facelibrary
Translation: Frigate NVR/views-search
Translation: Frigate NVR/views-settings
2025-11-27 13:58:53 +00:00
Hosted Weblate
78d84a35cb
Translated using Weblate (Slovak)
Currently translated at 100.0% (116 of 116 strings)

Translated using Weblate (Slovak)

Currently translated at 100.0% (55 of 55 strings)

Translated using Weblate (Slovak)

Currently translated at 100.0% (128 of 128 strings)

Translated using Weblate (Slovak)

Currently translated at 100.0% (501 of 501 strings)

Translated using Weblate (Slovak)

Currently translated at 100.0% (214 of 214 strings)

Translated using Weblate (Slovak)

Currently translated at 100.0% (52 of 52 strings)

Translated using Weblate (Slovak)

Currently translated at 100.0% (639 of 639 strings)

Translated using Weblate (Slovak)

Currently translated at 100.0% (128 of 128 strings)

Co-authored-by: Hosted Weblate <hosted@weblate.org>
Co-authored-by: Jakub K <klacanjakub0@gmail.com>
Translate-URL: https://hosted.weblate.org/projects/frigate-nvr/audio/sk/
Translate-URL: https://hosted.weblate.org/projects/frigate-nvr/common/sk/
Translate-URL: https://hosted.weblate.org/projects/frigate-nvr/components-dialog/sk/
Translate-URL: https://hosted.weblate.org/projects/frigate-nvr/views-classificationmodel/sk/
Translate-URL: https://hosted.weblate.org/projects/frigate-nvr/views-explore/sk/
Translate-URL: https://hosted.weblate.org/projects/frigate-nvr/views-facelibrary/sk/
Translate-URL: https://hosted.weblate.org/projects/frigate-nvr/views-settings/sk/
Translate-URL: https://hosted.weblate.org/projects/frigate-nvr/views-system/sk/
Translation: Frigate NVR/audio
Translation: Frigate NVR/common
Translation: Frigate NVR/components-dialog
Translation: Frigate NVR/views-classificationmodel
Translation: Frigate NVR/views-explore
Translation: Frigate NVR/views-facelibrary
Translation: Frigate NVR/views-settings
Translation: Frigate NVR/views-system
2025-11-27 13:58:52 +00:00
Hosted Weblate
3551b61ca8
Translated using Weblate (French)
Currently translated at 100.0% (55 of 55 strings)

Translated using Weblate (French)

Currently translated at 100.0% (128 of 128 strings)

Co-authored-by: Apocoloquintose <bertrand.moreux@gmail.com>
Co-authored-by: Hosted Weblate <hosted@weblate.org>
Translate-URL: https://hosted.weblate.org/projects/frigate-nvr/components-dialog/fr/
Translate-URL: https://hosted.weblate.org/projects/frigate-nvr/views-explore/fr/
Translation: Frigate NVR/components-dialog
Translation: Frigate NVR/views-explore
2025-11-27 13:58:51 +00:00
Hosted Weblate
cbed989717
Translated using Weblate (Dutch)
Currently translated at 100.0% (55 of 55 strings)

Translated using Weblate (Dutch)

Currently translated at 100.0% (128 of 128 strings)

Co-authored-by: Hosted Weblate <hosted@weblate.org>
Co-authored-by: Marijn <168113859+Marijn0@users.noreply.github.com>
Translate-URL: https://hosted.weblate.org/projects/frigate-nvr/components-dialog/nl/
Translate-URL: https://hosted.weblate.org/projects/frigate-nvr/views-explore/nl/
Translation: Frigate NVR/components-dialog
Translation: Frigate NVR/views-explore
2025-11-27 13:58:50 +00:00
Hosted Weblate
bfc1a77e9b
Translated using Weblate (Catalan)
Currently translated at 100.0% (128 of 128 strings)

Translated using Weblate (Catalan)

Currently translated at 100.0% (55 of 55 strings)

Co-authored-by: Eduardo Pastor Fernández <123eduardoneko123@gmail.com>
Co-authored-by: Hosted Weblate <hosted@weblate.org>
Translate-URL: https://hosted.weblate.org/projects/frigate-nvr/components-dialog/ca/
Translate-URL: https://hosted.weblate.org/projects/frigate-nvr/views-explore/ca/
Translation: Frigate NVR/components-dialog
Translation: Frigate NVR/views-explore
2025-11-27 13:58:49 +00:00
Hosted Weblate
0d3fe70cc7
Translated using Weblate (Ukrainian)
Currently translated at 100.0% (55 of 55 strings)

Translated using Weblate (Ukrainian)

Currently translated at 100.0% (128 of 128 strings)

Co-authored-by: Hosted Weblate <hosted@weblate.org>
Co-authored-by: Максим Горпиніч <gorpinicmaksim0@gmail.com>
Translate-URL: https://hosted.weblate.org/projects/frigate-nvr/components-dialog/uk/
Translate-URL: https://hosted.weblate.org/projects/frigate-nvr/views-explore/uk/
Translation: Frigate NVR/components-dialog
Translation: Frigate NVR/views-explore
2025-11-27 13:58:48 +00:00
Hosted Weblate
a9d3d9044d
Translated using Weblate (Romanian)
Currently translated at 100.0% (55 of 55 strings)

Translated using Weblate (Romanian)

Currently translated at 100.0% (128 of 128 strings)

Co-authored-by: Hosted Weblate <hosted@weblate.org>
Co-authored-by: lukasig <lukasig@hotmail.com>
Translate-URL: https://hosted.weblate.org/projects/frigate-nvr/components-dialog/ro/
Translate-URL: https://hosted.weblate.org/projects/frigate-nvr/views-explore/ro/
Translation: Frigate NVR/components-dialog
Translation: Frigate NVR/views-explore
2025-11-27 13:58:48 +00:00
Hosted Weblate
d6a4bd7598
Translated using Weblate (German)
Currently translated at 100.0% (128 of 128 strings)

Translated using Weblate (German)

Currently translated at 100.0% (128 of 128 strings)

Translated using Weblate (German)

Currently translated at 99.5% (213 of 214 strings)

Translated using Weblate (German)

Currently translated at 99.5% (213 of 214 strings)

Translated using Weblate (German)

Currently translated at 83.5% (534 of 639 strings)

Translated using Weblate (German)

Currently translated at 93.8% (470 of 501 strings)

Translated using Weblate (German)

Currently translated at 98.9% (91 of 92 strings)

Translated using Weblate (German)

Currently translated at 100.0% (52 of 52 strings)

Translated using Weblate (German)

Currently translated at 100.0% (39 of 39 strings)

Translated using Weblate (German)

Currently translated at 100.0% (128 of 128 strings)

Translated using Weblate (German)

Currently translated at 100.0% (128 of 128 strings)

Translated using Weblate (German)

Currently translated at 100.0% (116 of 116 strings)

Translated using Weblate (German)

Currently translated at 100.0% (116 of 116 strings)

Translated using Weblate (German)

Currently translated at 34.4% (40 of 116 strings)

Translated using Weblate (German)

Currently translated at 94.8% (37 of 39 strings)

Translated using Weblate (German)

Currently translated at 100.0% (55 of 55 strings)

Translated using Weblate (German)

Currently translated at 78.0% (499 of 639 strings)

Translated using Weblate (German)

Currently translated at 98.4% (126 of 128 strings)

Translated using Weblate (German)

Currently translated at 29.3% (34 of 116 strings)

Translated using Weblate (German)

Currently translated at 96.0% (123 of 128 strings)

Translated using Weblate (German)

Currently translated at 78.0% (499 of 639 strings)

Co-authored-by: Fuxle <moritz.hofmann2005@gmail.com>
Co-authored-by: Hosted Weblate <hosted@weblate.org>
Co-authored-by: Sebastian Sie <sebastian.neuplanitz@googlemail.com>
Co-authored-by: mvdberge <micha.vordemberge@christmann.info>
Translate-URL: https://hosted.weblate.org/projects/frigate-nvr/audio/de/
Translate-URL: https://hosted.weblate.org/projects/frigate-nvr/common/de/
Translate-URL: https://hosted.weblate.org/projects/frigate-nvr/components-dialog/de/
Translate-URL: https://hosted.weblate.org/projects/frigate-nvr/views-classificationmodel/de/
Translate-URL: https://hosted.weblate.org/projects/frigate-nvr/views-events/de/
Translate-URL: https://hosted.weblate.org/projects/frigate-nvr/views-explore/de/
Translate-URL: https://hosted.weblate.org/projects/frigate-nvr/views-facelibrary/de/
Translate-URL: https://hosted.weblate.org/projects/frigate-nvr/views-live/de/
Translate-URL: https://hosted.weblate.org/projects/frigate-nvr/views-settings/de/
Translate-URL: https://hosted.weblate.org/projects/frigate-nvr/views-system/de/
Translation: Frigate NVR/audio
Translation: Frigate NVR/common
Translation: Frigate NVR/components-dialog
Translation: Frigate NVR/views-classificationmodel
Translation: Frigate NVR/views-events
Translation: Frigate NVR/views-explore
Translation: Frigate NVR/views-facelibrary
Translation: Frigate NVR/views-live
Translation: Frigate NVR/views-settings
Translation: Frigate NVR/views-system
2025-11-27 13:58:47 +00:00
Hosted Weblate
3fc018481c
Translated using Weblate (Portuguese (Brazil))
Currently translated at 29.3% (34 of 116 strings)

Co-authored-by: Hosted Weblate <hosted@weblate.org>
Co-authored-by: Jose Machado <machado.jm4@gmail.com>
Translate-URL: https://hosted.weblate.org/projects/frigate-nvr/views-classificationmodel/pt_BR/
Translation: Frigate NVR/views-classificationmodel
2025-11-27 13:58:46 +00:00
Hosted Weblate
30a5b1a640
Translated using Weblate (Turkish)
Currently translated at 98.5% (211 of 214 strings)

Translated using Weblate (Turkish)

Currently translated at 66.3% (77 of 116 strings)

Translated using Weblate (Turkish)

Currently translated at 63.7% (74 of 116 strings)

Translated using Weblate (Turkish)

Currently translated at 97.6% (209 of 214 strings)

Translated using Weblate (Turkish)

Currently translated at 100.0% (55 of 55 strings)

Translated using Weblate (Turkish)

Currently translated at 94.5% (121 of 128 strings)

Translated using Weblate (Turkish)

Currently translated at 93.7% (120 of 128 strings)

Translated using Weblate (Turkish)

Currently translated at 94.5% (87 of 92 strings)

Translated using Weblate (Turkish)

Currently translated at 100.0% (39 of 39 strings)

Translated using Weblate (Turkish)

Currently translated at 58.9% (377 of 639 strings)

Translated using Weblate (Turkish)

Currently translated at 100.0% (52 of 52 strings)

Co-authored-by: Emircanos <emircan368@gmail.com>
Co-authored-by: Hosted Weblate <hosted@weblate.org>
Translate-URL: https://hosted.weblate.org/projects/frigate-nvr/common/tr/
Translate-URL: https://hosted.weblate.org/projects/frigate-nvr/components-dialog/tr/
Translate-URL: https://hosted.weblate.org/projects/frigate-nvr/views-classificationmodel/tr/
Translate-URL: https://hosted.weblate.org/projects/frigate-nvr/views-events/tr/
Translate-URL: https://hosted.weblate.org/projects/frigate-nvr/views-explore/tr/
Translate-URL: https://hosted.weblate.org/projects/frigate-nvr/views-facelibrary/tr/
Translate-URL: https://hosted.weblate.org/projects/frigate-nvr/views-live/tr/
Translate-URL: https://hosted.weblate.org/projects/frigate-nvr/views-settings/tr/
Translate-URL: https://hosted.weblate.org/projects/frigate-nvr/views-system/tr/
Translation: Frigate NVR/common
Translation: Frigate NVR/components-dialog
Translation: Frigate NVR/views-classificationmodel
Translation: Frigate NVR/views-events
Translation: Frigate NVR/views-explore
Translation: Frigate NVR/views-facelibrary
Translation: Frigate NVR/views-live
Translation: Frigate NVR/views-settings
Translation: Frigate NVR/views-system
2025-11-27 13:58:45 +00:00
Nicolas Mowen
1b57fb15a7
Miscellaneous Fixes (#21063)
Some checks failed
CI / AMD64 Build (push) Has been cancelled
CI / ARM Build (push) Has been cancelled
CI / Jetson Jetpack 6 (push) Has been cancelled
CI / AMD64 Extra Build (push) Has been cancelled
CI / ARM Extra Build (push) Has been cancelled
CI / Synaptics Build (push) Has been cancelled
CI / Assemble and push default build (push) Has been cancelled
* Fix history management failing when updating URL

* Handle case where user doesn't have images that represent all states

If a user selects all imags and can't proceed we show a warning that they can still proceed but the model won't be trained until they get at least one image for every state.

* Still create all classes

We stil need to create all classes even if the user didn't assign images to them.

* fix camera group access for non admin users

changes from previous PR wrongly included users from the standard viewer role (but excluded custom viewer roles)

* Adjust threat level interaction to be less strict

* use base path when fetching go2rtc data

* show config error message when starting in safe mode

* fix genai migration

* fix genai

* Fix genai migration

---------

Co-authored-by: Josh Hawkins <32435876+hawkeye217@users.noreply.github.com>
2025-11-27 07:58:35 -06:00
62 changed files with 966 additions and 227 deletions

View File

@ -870,6 +870,46 @@ def categorize_classification_image(request: Request, name: str, body: dict = No
)
@router.post(
"/classification/{name}/dataset/{category}/create",
response_model=GenericResponse,
dependencies=[Depends(require_role(["admin"]))],
summary="Create an empty classification category folder",
description="""Creates an empty folder for a classification category.
This is used to create folders for categories that don't have images yet.
Returns a success message or an error if the name is invalid.""",
)
def create_classification_category(request: Request, name: str, category: str):
config: FrigateConfig = request.app.frigate_config
if name not in config.classification.custom:
return JSONResponse(
content=(
{
"success": False,
"message": f"{name} is not a known classification model.",
}
),
status_code=404,
)
category_folder = os.path.join(
CLIPS_DIR, sanitize_filename(name), "dataset", sanitize_filename(category)
)
os.makedirs(category_folder, exist_ok=True)
return JSONResponse(
content=(
{
"success": True,
"message": f"Successfully created category folder: {category}",
}
),
status_code=200,
)
@router.post(
"/classification/{name}/train/delete",
response_model=GenericResponse,

View File

@ -375,7 +375,19 @@ class WebPushClient(Communicator):
ended = state == "end" or state == "genai"
if state == "genai" and payload["after"]["data"]["metadata"]:
title = payload["after"]["data"]["metadata"]["title"]
base_title = payload["after"]["data"]["metadata"]["title"]
threat_level = payload["after"]["data"]["metadata"].get(
"potential_threat_level", 0
)
# Add prefix for threat levels 1 and 2
if threat_level == 1:
title = f"Needs Review: {base_title}"
elif threat_level == 2:
title = f"Security Concern: {base_title}"
else:
title = base_title
message = payload["after"]["data"]["metadata"]["scene"]
else:
title = f"{titlecase(', '.join(sorted_objects).replace('_', ' '))}{' was' if state == 'end' else ''} detected in {titlecase(', '.join(payload['after']['data']['zones']).replace('_', ' '))}"

View File

@ -205,14 +205,20 @@ Rules for the report:
- Group bullets under subheadings when multiple events fall into the same category (e.g., Vehicle Activity, Porch Activity, Unusual Behavior).
- Threat levels
- Always show (threat level: X) for each event.
- Always show the threat level for each event using these labels:
- Threat level 0: "Normal"
- Threat level 1: "Needs review"
- Threat level 2: "Security concern"
- Format as (threat level: Normal), (threat level: Needs review), or (threat level: Security concern).
- If multiple events at the same time share the same threat level, only state it once.
- Final assessment
- End with a Final Assessment section.
- If all events are threat level 1 with no escalation:
- If all events are threat level 0:
Final assessment: Only normal residential activity observed during this period.
- If threat level 2+ events are present, clearly summarize them as Potential concerns requiring review.
- If threat level 1 events are present:
Final assessment: Some activity requires review but no security concerns identified.
- If threat level 2 events are present, clearly summarize them as Security concerns requiring immediate attention.
- Conciseness
- Do not repeat benign clothing/appearance details unless they distinguish individuals.

View File

@ -348,7 +348,7 @@ def migrate_016_0(config: dict[str, dict[str, Any]]) -> dict[str, dict[str, Any]
def migrate_017_0(config: dict[str, dict[str, Any]]) -> dict[str, dict[str, Any]]:
"""Handle migrating frigate config to 0.16-0"""
"""Handle migrating frigate config to 0.17-0"""
new_config = config.copy()
# migrate global to new recording configuration
@ -380,7 +380,7 @@ def migrate_017_0(config: dict[str, dict[str, Any]]) -> dict[str, dict[str, Any]
if global_genai:
new_genai_config = {}
new_object_config = config.get("objects", {})
new_object_config = new_config.get("objects", {})
new_object_config["genai"] = {}
for key in global_genai.keys():
@ -389,7 +389,8 @@ def migrate_017_0(config: dict[str, dict[str, Any]]) -> dict[str, dict[str, Any]
else:
new_object_config["genai"][key] = global_genai[key]
config["genai"] = new_genai_config
new_config["genai"] = new_genai_config
new_config["objects"] = new_object_config
for name, camera in config.get("cameras", {}).items():
camera_config: dict[str, dict[str, Any]] = camera.copy()
@ -415,8 +416,9 @@ def migrate_017_0(config: dict[str, dict[str, Any]]) -> dict[str, dict[str, Any]
camera_genai = camera_config.get("genai", {})
if camera_genai:
new_object_config = config.get("objects", {})
new_object_config["genai"] = camera_genai
camera_object_config = camera_config.get("objects", {})
camera_object_config["genai"] = camera_genai
camera_config["objects"] = camera_object_config
del camera_config["genai"]
new_config["cameras"][name] = camera_config

View File

@ -58,7 +58,8 @@
"endTimeMustAfterStartTime": "L'hora de finalització ha de ser posterior a l'hora d'inici",
"noVaildTimeSelected": "No s'ha seleccionat un rang de temps vàlid",
"failed": "No s'ha pogut inciar l'exportació: {{error}}"
}
},
"view": "Vista"
},
"fromTimeline": {
"saveExport": "Guardar exportació",

View File

@ -263,7 +263,8 @@
"header": {
"zones": "Zones",
"ratio": "Ràtio",
"area": "Àrea"
"area": "Àrea",
"score": "Puntuació"
}
},
"annotationSettings": {

View File

@ -296,7 +296,7 @@
"doorbell": "Türklingel",
"ding-dong": "BimBam",
"sliding_door": "Schiebetür",
"slam": "Knall",
"slam": "zuknallen",
"knock": "Klopfen",
"tap": "Schlag",
"squeak": "Quietschen",
@ -355,7 +355,7 @@
"shatter": "Zerspringen",
"silence": "Stille",
"environmental_noise": "Umgebungsgeräusch",
"static": "Rauschen",
"static": "Statisch",
"pink_noise": "Rosa Rauschen",
"television": "Fernsehgerät",
"radio": "Radio",
@ -441,5 +441,32 @@
"arrow": "Pfeil",
"electronic_tuner": "Elektronischer Tuner",
"effects_unit": "Effekteinheit",
"chorus_effect": "Chorus-Effekt"
"chorus_effect": "Chorus-Effekt",
"sodeling": "Verfilzen",
"chird": "Akkord",
"change_ringing": "Wechsle RingRing",
"shofar": "Schofar",
"gush": "sprudeln",
"sonar": "Sonar",
"whoosh": "Rauschen",
"thump": "Ruck",
"basketball_bounce": "Basketball Abbraller",
"bang": "Knall",
"slap": "Ohrfeige",
"whack": "verhauen",
"smash": "zerschlagen",
"breaking": "zerbrechen",
"bouncing": "Abbraller",
"whip": "Peitsche",
"flap": "Lasche",
"scratch": "Kratzer",
"scrape": "Abfall",
"rub": "scheuern",
"roll": "rollen",
"crushing": "Stauchen",
"crumpling": "zerknüllen",
"tearing": "Reißen",
"beep": "Piep",
"ping": "Ping",
"ding": "klingeln"
}

View File

@ -81,7 +81,10 @@
"formattedTimestampMonthDayYear": {
"12hour": "d. MMM yyyy",
"24hour": "d. MMM yyyy"
}
},
"inProgress": "In Bearbeitung",
"invalidStartTime": "Ungültige Startzeit",
"invalidEndTime": "Ungültige Endzeit"
},
"button": {
"save": "Speichern",
@ -118,7 +121,8 @@
"pictureInPicture": "Bild in Bild",
"on": "AN",
"suspended": "Pausierte",
"unsuspended": "fortsetzen"
"unsuspended": "fortsetzen",
"continue": "Weiter"
},
"label": {
"back": "Zurück",

View File

@ -66,7 +66,8 @@
"failed": "Fehler beim Starten des Exports: {{error}}",
"noVaildTimeSelected": "Kein gültiger Zeitraum ausgewählt"
},
"success": "Export erfolgreich gestartet. Die Datei befindet sich auf der Exportseite."
"success": "Export erfolgreich gestartet. Die Datei befindet sich auf der Exportseite.",
"view": "Ansicht"
},
"fromTimeline": {
"saveExport": "Export speichern",

View File

@ -1,10 +1,10 @@
{
"documentTitle": "Klassifizierungsmodelle",
"documentTitle": "Klassifikation Modelle",
"details": {
"scoreInfo": "Die Punktzahl gibt die durchschnittliche Klassifizierungssicherheit aller Erkennungen dieses Objekts wieder."
},
"button": {
"deleteClassificationAttempts": "Lösche Klassifizierungs-Bilder",
"deleteClassificationAttempts": "Lösche Klassifizierungsbilder",
"renameCategory": "Klasse umbenennen",
"deleteCategory": "Klasse löschen",
"deleteImages": "Bilder löschen",
@ -14,15 +14,15 @@
"editModel": "Modell bearbeiten"
},
"tooltip": {
"trainingInProgress": "Modell wird gerade trainiert",
"trainingInProgress": "Modell werden trainiert",
"noNewImages": "Keine weiteren Bilder zum trainieren. Bitte klassifiziere weitere Bilder im Datensatz.",
"noChanges": "Keine Veränderungen des Datensatzes seit dem letzten Training.",
"modelNotReady": "Modell ist nicht bereit trainiert zu werden."
"modelNotReady": "Modell ist nicht bereit zum Training"
},
"toast": {
"success": {
"deletedCategory": "Klasse gelöscht",
"deletedImage": "Gelöschte Bilder",
"deletedCategory": "Gelöschte Klasse",
"deletedImage": "Bilder gelöscht",
"deletedModel_one": "{{count}} Model erfolgreich gelöscht",
"deletedModel_other": "{{count}} Modelle erfolgreich gelöscht",
"categorizedImage": "Bild erfolgreich klassifiziert",
@ -34,7 +34,146 @@
"error": {
"deleteImageFailed": "Löschen fehlgeschlagen: {{errorMessage}}",
"deleteCategoryFailed": "Klasse konnte nicht gelöscht werden: {{errorMessage}}",
"deleteModelFailed": "Model konnte nicht gelöscht werden: {{errorMessage}}"
"deleteModelFailed": "Model konnte nicht gelöscht werden: {{errorMessage}}",
"trainingFailedToStart": "Modelltraining konnte nicht gestartet werden: {{errorMessage}}",
"updateModelFailed": "Aktualisierung des Modells fehlgeschlagen: {{errorMessage}}",
"renameCategoryFailed": "Umbenennung der Klasse fehlgeschlagen: {{errorMessage}}",
"categorizeFailed": "Bildkategorisierung fehlgeschlagen: {{errorMessage}}",
"trainingFailed": "Modelltraining fehlgeschlagen. Details sind in den Frigate-Protokollen zu finden."
}
},
"deleteCategory": {
"title": "Klasse löschen",
"desc": "Möchten Sie die Klasse {{name}} wirklich löschen? Dadurch werden alle zugehörigen Bilder dauerhaft gelöscht und das Modell muss neu trainiert werden.",
"minClassesTitle": "Klasse kann nicht gelöscht werden",
"minClassesDesc": "Ein Klassifizierungsmodell benötigt mindestens zwei Klassen. Fügen Sie eine weitere Klasse hinzu, bevor Sie diese löschen."
},
"deleteModel": {
"title": "Klassifizierungsmodell löschen",
"single": "Möchten Sie {{name}} wirklich löschen? Dadurch werden alle zugehörigen Daten, einschließlich Bilder und Trainingsdaten, dauerhaft gelöscht. Diese Aktion kann nicht rückgängig gemacht werden.",
"desc_one": "Möchtest du {{count}} Modell wirklich löschen? Dadurch werden alle zugehörigen Daten, einschließlich Bilder und Trainingsdaten, dauerhaft gelöscht. Diese Aktion kann nicht rückgängig gemacht werden.",
"desc_other": "Möchtest du {{count}} Modelle wirklich löschen? Dadurch werden alle zugehörigen Daten, einschließlich Bilder und Trainingsdaten, dauerhaft gelöscht. Diese Aktion kann nicht rückgängig gemacht werden."
},
"edit": {
"title": "Klassifikationsmodell bearbeiten",
"descriptionState": "Bearbeite die Klassen für dieses Zustandsklassifikationsmodell. Änderungen erfordern erneutes Trainieren des Modells.",
"descriptionObject": "Bearbeite den Objekttyp und Klassifizierungstyp für dieses Objektklassifikationsmodell.",
"stateClassesInfo": "Hinweis: Die Änderung der Statusklassen erfordert ein erneutes Trainieren des Modells mit den aktualisierten Klassen."
},
"deleteDatasetImages": {
"title": "Datensatz Bilder löschen",
"desc_one": "Bist du sicher, dass {{count}} Bild von {{dataset}} gelöscht werden sollen? Diese Aktion kann nicht rückgängig gemacht werden und erfordert ein erneutes Trainieren des Modells.",
"desc_other": "Bist du sicher, dass {{count}} Bilder von {{dataset}} gelöscht werden sollen? Diese Aktion kann nicht rückgängig gemacht werden und erfordert ein erneutes Trainieren des Modells."
},
"deleteTrainImages": {
"title": "Trainingsbilder löschen",
"desc_one": "Bist du sicher, dass du {{count}} Bild löschen möchten? Diese Aktion kann nicht rückgängig gemacht werden.",
"desc_other": "Bist du sicher, dass du {{count}} Bilder löschen möchten? Diese Aktion kann nicht rückgängig gemacht werden."
},
"renameCategory": {
"title": "Klasse umbenennen",
"desc": "Neuen Namen für {{name}} eingeben. Das Modell muss neu trainiert werden, damit die Änderungen wirksam werden."
},
"description": {
"invalidName": "Ungültiger Name. Namen dürfen nur Buchstaben, Zahlen, Leerzeichen, Apostrophe, Unterstriche und Bindestriche enthalten."
},
"train": {
"title": "Neue Klassifizierungen",
"titleShort": "kürzlich",
"aria": "Neue Klassifizierungen auswählen"
},
"categories": "Klassen",
"createCategory": {
"new": "Neue Klasse erstellen"
},
"categorizeImageAs": "Bild klassifizieren als:",
"categorizeImage": "Bild klassifizieren",
"menu": {
"objects": "Objekte",
"states": "Zustände"
},
"noModels": {
"object": {
"title": "Keine Objektklassifikationsmodelle",
"description": "Erstelle ein benutzerdefiniertes Modell, um erkannte Objekte zu klassifizieren.",
"buttonText": "Objektmodell erstellen"
},
"state": {
"title": "Keine Statusklassifizierungsmodelle",
"description": "Erstellen Sie ein benutzerdefiniertes Modell, um Zustandsänderungen in bestimmten Kamerabereichen zu überwachen und zu klassifizieren.",
"buttonText": "Zustandsmodell erstellen"
}
},
"wizard": {
"title": "Neue Klassifizierung erstellen",
"steps": {
"nameAndDefine": "Benennen und definieren",
"stateArea": "Gebiet",
"chooseExamples": "Beispiel auswählen"
},
"step1": {
"description": "Zustandsmodelle überwachen feste Kamerabereiche auf Veränderungen (z. B. Tür offen/geschlossen). Objektmodelle fügen den erkannten Objekten Klassifizierungen hinzu (z. B. bekannte Tiere, Lieferanten usw.).",
"name": "Name",
"namePlaceholder": "Eingeben Modell Name...",
"type": "Typ",
"typeState": "Zustand",
"typeObject": "Objekt",
"objectLabel": "Objekt Bezeichnung",
"objectLabelPlaceholder": "Auswahl Objekt Typ...",
"classificationType": "Klassifizierungstyp",
"classificationTypeTip": "Etwas über Klassifizierungstyp lernen",
"classificationTypeDesc": "Unterbezeichnungen fügen dem Objektnamen zusätzlichen Text hinzu (z. B. „Person: UPS“). Attribute sind durchsuchbare Metadaten, die separat in den Objektmetadaten gespeichert sind.",
"classificationSubLabel": "Unterlabel",
"classificationAttribute": "Merkmal",
"classes": "Klasse",
"states": "Gebiet",
"classesTip": "Über Klassen lernen",
"classesStateDesc": "Definieren Sie die verschiedenen Zustände, in denen sich Ihr Kamerabereich befinden kann. Beispiel: „offen” und „geschlossen” für ein Garagentor.",
"classesObjectDesc": "Definieren Sie die verschiedenen Kategorien, in die erkannte Objekte klassifiziert werden sollen. Beispiel: „Lieferant“, „Bewohner“, „Fremder“ für die Klassifizierung von Personen.",
"classPlaceholder": "Eingabe Klassenbezeichnung...",
"errors": {
"nameRequired": "Modellname ist erforderlich",
"nameLength": "Der Modellname darf maximal 64 Zeichen lang sein",
"nameOnlyNumbers": "Der Modellname darf nicht nur aus Zahlen bestehen",
"classRequired": "Mindestens eine Klasse ist erforderlich",
"classesUnique": "Klassenname muss eindeutig sein",
"stateRequiresTwoClasses": "Gebietsmodelle erfordern mindestens zwei Klassen",
"objectLabelRequired": "Bitte wähle eine Objektbeschriftung",
"objectTypeRequired": "Bitte wählen Sie einen Klassifizierungstyp aus"
}
},
"step2": {
"description": "Wählen Sie Kameras aus und legen Sie für jede Kamera den zu überwachenden Bereich fest. Das Modell klassifiziert den Zustand dieser Bereiche.",
"cameras": "Kameras",
"selectCamera": "Kamera auswählen",
"noCameras": "Klick + zum hinzufügen der Kameras",
"selectCameraPrompt": "Wählen Sie eine Kamera aus der Liste aus, um ihren Überwachungsbereich festzulegen"
},
"step3": {
"selectImagesPrompt": "Wählen sie alle Bilder mit: {{className}}",
"selectImagesDescription": "Klicken Sie auf die Bilder, um sie auszuwählen. Klicken Sie auf „Weiter“, wenn Sie mit diesem Kurs fertig sind.",
"allImagesRequired_one": "Bitte klassifizieren Sie alle Bilder. {{count}} Bilder verbleiben.",
"allImagesRequired_other": "Bitte klassifizieren Sie alle Bilder. {{count}} Bilder verbleiben.",
"generating": {
"title": "Beispielbilder generieren",
"description": "Frigate extrahiert repräsentative Bilder aus Ihren Aufnahmen. Dies kann einen Moment dauern..."
},
"training": {
"title": "Trainingsmodell",
"description": "Ihr Modell wird im Hintergrund trainiert. Schließen Sie diesen Dialog, und Ihr Modell wird ausgeführt, sobald das Training abgeschlossen ist."
},
"retryGenerate": "Generierung wiederholen",
"noImages": "Keine Bilder generiert",
"classifying": "Klassifizieren und Trainieren...",
"trainingStarted": "Training wurde erfolgreich gestartet",
"errors": {
"noCameras": "Keine Kameras konfiguriert",
"noObjectLabel": "Kein Objektlabel ausgewählt",
"generateFailed": "Beispiele konnten nicht generiert werden: {{error}}",
"generationFailed": "Generierung fehlgeschlagen. Bitte versuchen Sie es erneut.",
"classifyFailed": "Bilder konnten nicht klassifiziert werden: {{error}}"
},
"generateSuccess": "Erfolgreich generierte Beispielbilder"
}
}
}

View File

@ -48,10 +48,12 @@
"noDataFound": "Keine Detaildaten zur Überprüfung",
"settings": "Detailansicht Einstellungen",
"alwaysExpandActive": {
"desc": "Immer die Objektdetails des aktiven Überprüfungselements erweitern, sofern verfügbar."
"desc": "Immer die Objektdetails vom aktivem Überprüfungselement erweitern, sofern verfügbar.",
"title": "Immer aktiv erweitern"
}
},
"objectTrack": {
"trackedPoint": "Verfolgter Punkt"
"trackedPoint": "Verfolgter Punkt",
"clickToSeek": "Klicke, um zu dieser Zeit zu springen"
}
}

View File

@ -18,7 +18,7 @@
"updatedSublabel": "Unterkategorie erfolgreich aktualisiert.",
"updatedLPR": "Nummernschild erfolgreich aktualisiert.",
"regenerate": "Eine neue Beschreibung wurde von {{provider}} angefordert. Je nach Geschwindigkeit des Anbieters kann es einige Zeit dauern, bis die neue Beschreibung generiert ist.",
"audioTranscription": "Audio Transkription erfolgreich angefordert."
"audioTranscription": "Die Audio-Transkription wurde erfolgreich angefordert. Je nach Geschwindigkeit Ihres Frigate-Servers kann die Transkription einige Zeit in Anspruch nehmen."
},
"error": {
"regenerate": "Der Aufruf von {{provider}} für eine neue Beschreibung ist fehlgeschlagen: {{errorMessage}}",
@ -159,7 +159,8 @@
"video": "Video",
"object_lifecycle": "Objekt-Lebenszyklus",
"snapshot": "Snapshot",
"thumbnail": "Vorschaubild"
"thumbnail": "Vorschaubild",
"tracking_details": "Nachverfolgungs-Details"
},
"itemMenu": {
"downloadSnapshot": {
@ -249,13 +250,14 @@
"faceOrLicense_plate": "{{attribute}} erkannt für {{label}}",
"other": "{{label}} erkannt als {{attribute}}"
},
"gone": "{{label}} verließ",
"gone": "{{label}} hat verlassen",
"heard": "{{label}} wurde gehört",
"external": "{{label}} erkannt",
"header": {
"zones": "Zonen",
"ratio": "Verhältnis",
"area": "Bereich"
"area": "Bereich",
"score": "Bewertung"
}
},
"annotationSettings": {
@ -278,6 +280,10 @@
"previous": "Vorherige Anzeige",
"next": "Nächste Anzeige"
},
"title": "Verfolgungsdetails"
"title": "Verfolgungsdetails",
"adjustAnnotationSettings": "Anmerkungseinstellungen anpassen",
"autoTrackingTips": "Die Positionen der Begrenzungsrahmen sind bei Kameras mit automatischer Verfolgung ungenau.",
"count": "{{first}} von {{second}}",
"trackedPoint": "Verfolgter Punkt"
}
}

View File

@ -64,7 +64,7 @@
"deletedName_other": "{{count}} Gesichter wurden erfolgreich gelöscht.",
"addFaceLibrary": "{{name}} wurde erfolgreich in die Gesichtsbibliothek aufgenommen!",
"trainedFace": "Gesicht erfolgreich trainiert.",
"updatedFaceScore": "Gesichtsbewertung erfolgreich aktualisiert.",
"updatedFaceScore": "Gesichtsbewertung erfolgreich auf {{name}} ({{score}}) aktualisiert.",
"renamedFace": "Gesicht erfolgreich in {{name}} umbenannt"
},
"error": {

View File

@ -174,7 +174,11 @@
"noCameras": {
"title": "Keine Kameras eingerichtet",
"description": "Beginne indem du eine Kamera anschließt.",
"buttonText": "Kamera hinzufügen"
"buttonText": "Kamera hinzufügen",
"restricted": {
"title": "Keine Kamera verfügbar",
"description": "Sie haben keine Berechtigung, Kameras in dieser Gruppe anzuzeigen."
}
},
"snapshot": {
"takeSnapshot": "Sofort-Schnappschuss herunterladen",

View File

@ -41,7 +41,7 @@
"noCamera": "Keine Kamera"
},
"general": {
"title": "Allgemeine Einstellungen",
"title": "Einstellungen der Benutzeroberfläche",
"liveDashboard": {
"title": "Live Übersicht",
"playAlertVideos": {
@ -51,6 +51,14 @@
"automaticLiveView": {
"desc": "Wechsle automatisch zur Live Ansicht der Kamera, wenn einen Aktivität erkannt wurde. Wenn du diese Option deaktivierst, werden die statischen Kamerabilder auf der Liveübersicht nur einmal pro Minute aktualisiert.",
"label": "Automatische Live Ansicht"
},
"displayCameraNames": {
"label": "Immer Namen der Kamera anzeigen",
"desc": "Kamerabezeichnung immer im einem Chip im Live-View-Dashboard für mehrere Kameras anzeigen."
},
"liveFallbackTimeout": {
"label": "Live Player Ausfallzeitlimit",
"desc": "Wenn der hochwertige Live-Stream einer Kamera nicht verfügbar ist, wechsle nach dieser Anzahl von Sekunden in den Modus für geringe Bandbreite. Standard: 3."
}
},
"storedLayouts": {
@ -909,7 +917,8 @@
"steps": {
"nameAndConnection": "Name & Verbindung",
"streamConfiguration": "Stream Konfiguration",
"validationAndTesting": "Überprüfung & Testen"
"validationAndTesting": "Überprüfung & Testen",
"probeOrSnapshot": "Sondierung oder Momentaufnahme"
},
"save": {
"success": "Neue Kamera {{cameraName}} erfolgreich hinzugefügt.",
@ -926,7 +935,7 @@
"testFailed": "Stream Test fehlgeschlagen: {{error}}"
},
"step1": {
"description": "Gib deine Kameradaten ein und teste die Verbindung.",
"description": "Geben Sie Ihre Kameradaten ein und wählen Sie, ob Sie die Kamera automatisch erkennen lassen oder die Marke manuell auswählen möchten.",
"cameraName": "Kamera-Name",
"cameraNamePlaceholder": "z.B. vordere_tür oder Hof Übersicht",
"host": "Host/IP Adresse",
@ -957,14 +966,23 @@
"nameExists": "Kamera-Name existiert bereits",
"brands": {
"reolink-rtsp": "Reolink RTSP wird nicht empfohlen. Es wird empfohlen, http in den Kameraeinstellungen zu aktivieren und den Kamera-Assistenten neu zu starten."
}
},
"customUrlRtspRequired": "Benutzerdefinierte URLs müssen mit „rtsp://“ beginnen. Für Nicht-RTSP-Kamerastreams ist eine manuelle Konfiguration erforderlich."
},
"docs": {
"reolink": "https://docs.frigate.video/configuration/camera_specific.html#reolink-cameras"
}
},
"connectionSettings": "Verbindungseinstellungen",
"detectionMethod": "Stream Erkennungsmethode",
"onvifPort": "ONVIF Port",
"probeMode": "Sondenkamera",
"detectionMethodDescription": "Suchen Sie die Kamera mit ONVIF (sofern unterstützt), um die URLs der Kamerastreams zu finden, oder wählen Sie manuell die Kameramarke aus, um vordefinierte URLs zu verwenden. Um eine benutzerdefinierte RTSP-URL einzugeben, wählen Sie die manuelle Methode und dann „Andere“.",
"onvifPortDescription": "Bei Kameras, die ONVIF unterstützen, ist dies in der Regel 80 oder 8080.",
"useDigestAuth": "Digest-Authentifizierung verwenden",
"useDigestAuthDescription": "Verwenden Sie die HTTP-Digest-Authentifizierung für ONVIF. Einige Kameras erfordern möglicherweise einen speziellen ONVIF-Benutzernamen/ein spezielles ONVIF-Passwort anstelle des Standard-Admin-Benutzers."
},
"step2": {
"description": "Konfigurieren Sie Stream-Rollen und fügen Sie zusätzliche Streams für Ihre Kamera hinzu.",
"description": "Suchen Sie in der Kamera nach verfügbaren Streams oder konfigurieren Sie manuelle Einstellungen basierend auf der von Ihnen ausgewählten Erkennungsmethode.",
"streamsTitle": "Kamera Streams",
"addStream": "Stream hinzufügen",
"addAnotherStream": "Weiteren Stream hinzufügen",
@ -983,8 +1001,8 @@
"audio": "Audio"
},
"testStream": "Verbindung testen",
"testSuccess": "Stream erfolgreich getestet!",
"testFailed": "Stream-Test fehlgeschlagen",
"testSuccess": "Verbindung erfolgreich getestet!",
"testFailed": "Verbindungstest fehlgeschlagen. Bitte überprüfen sie Eingabe und versuchen sie es wieder.",
"testFailedTitle": "Test fehlgeschlagen",
"connected": "Verbunden",
"notConnected": "Nicht verbunden",
@ -1000,7 +1018,27 @@
"featuresPopover": {
"title": "Stream Funktionen",
"description": "Verwende go2rtc Restreaming, um die Verbindungen zu deiner Kamera zu reduzieren."
}
},
"streamDetails": "Verbindungsdetails",
"probing": "Kamera wird geprüft...",
"retry": "Wiederholen",
"testing": {
"probingMetadata": "Metadaten der Kamera werden überprüft...",
"fetchingSnapshot": "Kamera-Schnappschuss wird abgerufen..."
},
"probeFailed": "Fehler beim Testen der Kamera: {{error}}",
"probingDevice": "Prüfung Gerät...",
"probeSuccessful": "Sonde erfolgreich",
"probeError": "Sondenfehler",
"probeNoSuccess": "Sonde erfolglos",
"deviceInfo": "Geräteinformationen",
"manufacturer": "Hersteller",
"model": "Modell",
"firmware": "Firmware",
"profiles": "Profile",
"ptzSupport": "PTZ Unterstützung",
"autotrackingSupport": "Unterstützung für Autoverfolgung",
"presets": "Voreinstellung"
},
"step3": {
"description": "Endgültige Validierung und Analyse vor dem Speichern Ihrer neuen Kamera. Verbinde jeden Stream vor dem Speichern.",

View File

@ -31,7 +31,12 @@
"gpuDecoder": "GPU Decoder",
"gpuEncoder": "GPU Encoder",
"npuUsage": "NPU Verwendung",
"npuMemory": "NPU Speicher"
"npuMemory": "NPU Speicher",
"intelGpuWarning": {
"title": "Intel GPU Statistik Warnung",
"message": "GPU stats nicht verfügbar",
"description": "Dies ist ein bekannter Fehler in den GPU-Statistik-Tools von Intel (intel_gpu_top), bei dem das Tool ausfällt und wiederholt eine GPU-Auslastung von 0 % anzeigt, selbst wenn die Hardwarebeschleunigung und die Objekterkennung auf der (i)GPU korrekt funktionieren. Dies ist kein Fehler von Frigate. Du kannst den Host neu starten, um das Problem vorübergehend zu beheben und zu prüfen, ob die GPU korrekt funktioniert. Dies hat keine Auswirkungen auf die Leistung."
}
},
"title": "Allgemein",
"detector": {
@ -167,10 +172,17 @@
"face_recognition": "Gesichts Erkennung",
"image_embedding": "Bild Embedding",
"yolov9_plate_detection_speed": "YOLOv9 Kennzeichenerkennungsgeschwindigkeit",
"yolov9_plate_detection": "YOLOv9 Kennzeichenerkennung"
"yolov9_plate_detection": "YOLOv9 Kennzeichenerkennung",
"review_description": "Bewertung Beschreibung",
"review_description_speed": "Bewertungsbeschreibung Geschwindigkeit",
"review_description_events_per_second": "Bewertungsbeschreibung",
"object_description": "Objekt Beschreibung",
"object_description_speed": "Objektbeschreibung Geschwindigkeit",
"object_description_events_per_second": "Objektbeschreibung"
},
"title": "Optimierungen",
"infPerSecond": "Rückschlüsse pro Sekunde"
"infPerSecond": "Rückschlüsse pro Sekunde",
"averageInf": "Durchschnittliche Inferenzzeit"
},
"stats": {
"healthy": "Das System läuft problemlos",

View File

@ -166,6 +166,7 @@
"noImages": "No sample images generated",
"classifying": "Classifying & Training...",
"trainingStarted": "Training started successfully",
"modelCreated": "Model created successfully. Use the Recent Classifications view to add images for missing states, then train the model.",
"errors": {
"noCameras": "No cameras configured",
"noObjectLabel": "No object label selected",
@ -173,7 +174,11 @@
"generationFailed": "Generation failed. Please try again.",
"classifyFailed": "Failed to classify images: {{error}}"
},
"generateSuccess": "Successfully generated sample images"
"generateSuccess": "Successfully generated sample images",
"missingStatesWarning": {
"title": "Missing State Examples",
"description": "You haven't selected examples for all states. The model will not be trained until all states have images. After continuing, use the Recent Classifications view to classify images for the missing states, then train the model."
}
}
}
}

View File

@ -54,6 +54,7 @@
"selected_other": "{{count}} selected",
"camera": "Camera",
"detected": "detected",
"suspiciousActivity": "Suspicious Activity",
"threateningActivity": "Threatening Activity"
"normalActivity": "Normal",
"needsReview": "Needs review",
"securityConcern": "Security concern"
}

View File

@ -65,7 +65,8 @@
"endTimeMustAfterStartTime": "L'heure de fin doit être postérieure à l'heure de début.",
"noVaildTimeSelected": "La plage horaire sélectionnée n'est pas valide."
},
"success": "Exportation démarrée avec succès. Consultez le fichier sur la page des exportations."
"success": "Exportation démarrée avec succès. Consultez le fichier sur la page des exportations.",
"view": "Vue"
},
"select": "Sélectionner",
"name": {

View File

@ -263,7 +263,8 @@
"header": {
"zones": "Zones",
"ratio": "Ratio",
"area": "Surface"
"area": "Surface",
"score": "Score"
}
},
"annotationSettings": {

View File

@ -61,7 +61,8 @@
"failed": "Klarte ikke å starte eksport: {{error}}",
"noVaildTimeSelected": "Ingen gyldig tidsperiode valgt",
"endTimeMustAfterStartTime": "Sluttid må være etter starttid"
}
},
"view": "Vis"
},
"fromTimeline": {
"previewExport": "Forhåndsvis eksport",

View File

@ -23,8 +23,8 @@
"label": "Sorter",
"dateAsc": "Dato (Stigende)",
"dateDesc": "Dato (Synkende)",
"scoreAsc": "Objektpoengsum (Stigende)",
"scoreDesc": "Objektpoengsum (Synkende)",
"scoreAsc": "Objektscore (Stigende)",
"scoreDesc": "Objektscore (Synkende)",
"speedAsc": "Estimert hastighet (Stigende)",
"speedDesc": "Estimert hastighet (Synkende)",
"relevance": "Relevans"
@ -104,7 +104,7 @@
"label": "Underetiketter",
"all": "Alle underetiketter"
},
"score": "Poengsum",
"score": "Score",
"estimatedSpeed": "Estimert hastighet ({{unit}})",
"cameras": {
"all": {

View File

@ -12,19 +12,19 @@
},
"toast": {
"success": {
"deletedCategory": "Kategori slettet",
"deletedCategory": "Klasse slettet",
"deletedImage": "Bilder slettet",
"categorizedImage": "Bildet ble klassifisert",
"categorizedImage": "Klassifiserte bildet",
"trainedModel": "Modellen ble trent.",
"trainingModel": "Modelltrening startet.",
"deletedModel_one": "{{count}} modell ble slettet",
"deletedModel_other": "{{count}} modeller ble slettet",
"updatedModel": "Modellkonfigurasjonen ble oppdatert",
"renamedCategory": "Kategorien ble omdøpt til {{name}}"
"renamedCategory": "Klassen ble omdøpt til {{name}}"
},
"error": {
"deleteImageFailed": "Kunne ikke slette: {{errorMessage}}",
"deleteCategoryFailed": "Kunne ikke slette kategori: {{errorMessage}}",
"deleteCategoryFailed": "Kunne ikke slette klasse: {{errorMessage}}",
"categorizeFailed": "Kunne ikke klassifisere bilde: {{errorMessage}}",
"trainingFailed": "Modelltrening mislyktes. Sjekk Frigate-loggene for detaljer.",
"deleteModelFailed": "Kunne ikke slette modell: {{errorMessage}}",
@ -34,10 +34,10 @@
}
},
"deleteCategory": {
"title": "Slett kategori",
"desc": "Er du sikker på at du vil slette kategorien {{name}}? Dette vil permanent slette alle tilknyttede bilder og kreve at modellen trenes på nytt.",
"title": "Slett klasse",
"desc": "Er du sikker på at du vil slette klassen {{name}}? Dette vil permanent slette alle tilknyttede bilder og kreve at modellen trenes på nytt.",
"minClassesTitle": "Kan ikke slette klasse",
"minClassesDesc": "En klassifiseringsmodell må ha minst 2 kategorier. Legg til en ny kategori før du sletter denne."
"minClassesDesc": "En klassifiseringsmodell må ha minst 2 klasser. Legg til en ny klasse før du sletter denne."
},
"deleteDatasetImages": {
"title": "Slett datasettbilder",
@ -48,7 +48,7 @@
"desc": "Er du sikker på at du vil slette {{count}} bilder? Denne handlingen kan ikke angres."
},
"renameCategory": {
"title": "Gi nytt navn til kategori",
"title": "Omdøp klasse",
"desc": "Skriv inn et nytt navn for {{name}}. Du må trene modellen på nytt for at navneendringen skal tre i kraft."
},
"description": {
@ -59,9 +59,9 @@
"aria": "Velg nylige klassifiseringer",
"titleShort": "Nylig"
},
"categories": "Kategorier",
"categories": "Klasser",
"createCategory": {
"new": "Opprett ny kategori"
"new": "Opprett ny klasse"
},
"categorizeImageAs": "Klassifiser bilde som:",
"categorizeImage": "Klassifiser bilde",
@ -98,18 +98,18 @@
"classificationTypeDesc": "Underetiketter legger til ekstra tekst på objektetiketten (f.eks. 'Person: Posten'). Attributter er søkbare metadata som lagres separat i objektets metadata.",
"classificationSubLabel": "Underetikett",
"classificationAttribute": "Attributt",
"classes": "Kategorier",
"classesTip": "Lær om kategorier",
"classes": "Klasser",
"classesTip": "Lær om klasser",
"classesStateDesc": "Definer de ulike tilstandene kamerasonen kan være i. For eksempel: 'åpen' og 'lukket' for en garasjeport.",
"classesObjectDesc": "Definer kategoriene du vil klassifisere oppdagede objekter i. For eksempel: 'bud', 'beboer', 'fremmed' for personklassifisering.",
"classPlaceholder": "Skriv inn tilstandsnavn...",
"classesObjectDesc": "Definer klassene du vil klassifisere oppdagede objekter i. For eksempel: 'bud', 'beboer', 'fremmed' for personklassifisering.",
"classPlaceholder": "Skriv inn klassenavn...",
"errors": {
"nameRequired": "Modellnavn er påkrevd",
"nameLength": "Modellnavn må være på 64 tegn eller mindre",
"nameOnlyNumbers": "Modellnavn kan ikke bare inneholde tall",
"classRequired": "Minst én kategori er påkrevd",
"classesUnique": "Kategorinavn må være unike",
"stateRequiresTwoClasses": "Tilstandsmodeller krever minst to kategorier",
"classRequired": "Minst én klasse er påkrevd",
"classesUnique": "Klassenavn må være unike",
"stateRequiresTwoClasses": "Tilstandsmodeller krever minst to klasser",
"objectLabelRequired": "Velg en objektetikett",
"objectTypeRequired": "Velg en klassifiseringstype"
},
@ -124,7 +124,7 @@
},
"step3": {
"selectImagesPrompt": "Velg alle bilder med: {{className}}",
"selectImagesDescription": "Klikk på bilder for å velge dem. Klikk Fortsett når du er ferdig med denne kategorien.",
"selectImagesDescription": "Klikk på bilder for å velge dem. Klikk Fortsett når du er ferdig med denne klassen.",
"generating": {
"title": "Genererer eksempelbilder",
"description": "Frigate henter representative bilder fra opptakene dine. Dette kan ta litt tid..."
@ -159,7 +159,7 @@
"states": "Tilstander"
},
"details": {
"scoreInfo": "Poengsummen representerer gjennomsnittlig klassifiseringskonfidens på tvers av alle deteksjoner av dette objektet."
"scoreInfo": "Score representerer gjennomsnittlig klassifiseringskonfidens på tvers av alle deteksjoner av dette objektet."
},
"tooltip": {
"trainingInProgress": "Modellen trenes for øyeblikket",

View File

@ -90,7 +90,7 @@
"updatedSublabel": "Underetikett ble oppdatert.",
"updatedLPR": "Vellykket oppdatering av kjennemerke.",
"regenerate": "En ny beskrivelse har blitt anmodet fra {{provider}}. Avhengig av hastigheten til leverandøren din, kan den nye beskrivelsen ta litt tid å regenerere.",
"audioTranscription": "Lydtranskripsjon ble forespurt."
"audioTranscription": "Lydtranskripsjon ble forespurt. Avhengig av ytelsen på din Frigate server kan transkripsjonen ta noe tid å fullføre."
},
"error": {
"regenerate": "Feil ved anrop til {{provider}} for en ny beskrivelse: {{errorMessage}}",
@ -107,8 +107,8 @@
}
},
"topScore": {
"info": "Den høyeste poengsummen er den høyeste medianverdi for det sporede objektet, så denne kan avvike fra poengsummen som vises på miniatyrbildet for søkeresultatet.",
"label": "Høyeste poengsum"
"info": "Toppscoren er den høyeste medianverdien for det sporede objektet, så denne kan avvike fra scoren som vises på miniatyrbildet i søkeresultatet.",
"label": "Toppscore"
},
"estimatedSpeed": "Estimert hastighet",
"objects": "Objekter",
@ -147,10 +147,10 @@
"descNoLabel": "Angi en ny underetikett for dette sporede objektet"
},
"snapshotScore": {
"label": "Øyeblikksbilde poengsum"
"label": "Øyeblikksbilde score"
},
"score": {
"label": "Poengsum"
"label": "Score"
}
},
"itemMenu": {
@ -261,7 +261,8 @@
"header": {
"zones": "Soner",
"ratio": "Forhold",
"area": "Område"
"area": "Område",
"score": "Score"
}
},
"annotationSettings": {

View File

@ -11,7 +11,7 @@
"face": "Ansiktsdetaljer",
"faceDesc": "Detaljer for sporet objekt som genererte dette ansiktet",
"timestamp": "Tidsstempel",
"scoreInfo": "Under-merkelappens poengsum er basert på en vektet sum ut ifra hvor sikre gjenkjenningene av ansiktene er, så den kan avvike fra poengsummen som vises på øyeblikksbildet.",
"scoreInfo": "Score er et vektet gjennomsnitt av alle ansiktsscorer, vektet etter størrelsen på ansiktet i hvert bilde.",
"subLabelScore": "Poengsum for under-merkelapp",
"unknown": "Ukjent"
},
@ -38,7 +38,7 @@
"deleteFaceFailed": "Kunne ikke slette: {{errorMessage}}",
"uploadingImageFailed": "Kunne ikke laste opp bilde: {{errorMessage}}",
"trainFailed": "Kunne ikke trene: {{errorMessage}}",
"updateFaceScoreFailed": "Kunne ikke oppdatere ansiktsskåring: {{errorMessage}}",
"updateFaceScoreFailed": "Kunne ikke oppdatere ansiktsscore: {{errorMessage}}",
"addFaceLibraryFailed": "Kunne ikke angi ansiktsnavn: {{errorMessage}}",
"deleteNameFailed": "Kunne ikke slette navn: {{errorMessage}}",
"renameFaceFailed": "Kunne ikke gi nytt navn til ansikt: {{errorMessage}}"
@ -49,7 +49,7 @@
"deletedName_one": "{{count}} ansikt ble slettet.",
"deletedName_other": "{{count}} ansikter ble slettet.",
"trainedFace": "Ansiktet ble trent.",
"updatedFaceScore": "Ansiktsskåring ble oppdatert til {{name}} ({{score}}).",
"updatedFaceScore": "Oppdaterte ansiktsscore for {{name}} ({{score}}).",
"uploadedImage": "Bildet ble lastet opp.",
"addFaceLibrary": "{{name}} ble lagt til i ansiktsbiblioteket!",
"renamedFace": "Nytt navn ble gitt til ansikt {{name}}"

View File

@ -15,8 +15,8 @@
"labels": "Etiketter",
"search_type": "Søketype",
"after": "Etter",
"min_score": "Min. poengsum",
"max_score": "Maks. poengsum",
"min_score": "Min. score",
"max_score": "Maks. score",
"min_speed": "Min. hastighet",
"zones": "Soner",
"sub_labels": "Underetiketter",
@ -36,8 +36,8 @@
"minSpeedMustBeLessOrEqualMaxSpeed": "Minimum hastighet 'min_speed' må være mindre enn eller lik maksimum hastighet 'max_speed'.",
"beforeDateBeLaterAfter": "Før-datoen 'before' må være senere enn etter-datoen 'after'.",
"afterDatebeEarlierBefore": "Etter-datoen 'after' må være tidligere enn før-datoen 'before'.",
"minScoreMustBeLessOrEqualMaxScore": "Minimum poengsum 'min_score' må være mindre enn eller lik maksimum poengsum 'max_score'.",
"maxScoreMustBeGreaterOrEqualMinScore": "Maksimum poengsum 'max_score' må være større enn eller lik minimum poengsum 'min_score'.",
"minScoreMustBeLessOrEqualMaxScore": "Minimum score 'min_score' må være mindre enn eller lik maksimum score 'max_score'.",
"maxScoreMustBeGreaterOrEqualMinScore": "Maksimum score 'max_score' må være større enn eller lik minimum score 'min_score'.",
"maxSpeedMustBeGreaterOrEqualMinSpeed": "Maksimum hastighet 'max_speed' må være større enn eller lik minimum hastighet 'min_speed'."
}
},

View File

@ -460,7 +460,7 @@
},
"objectShapeFilterDrawing": {
"document": "Se dokumentasjonen ",
"score": "Poengsum",
"score": "Score",
"ratio": "Forhold",
"area": "Areal",
"title": "Tegning av objektformfilter",
@ -478,7 +478,7 @@
"audio": {
"title": "Lyd",
"noAudioDetections": "Ingen lyddeteksjoner",
"score": "poengsum",
"score": "score",
"currentRMS": "Nåværende RMS",
"currentdbFS": "Nåværende dbFS"
},

View File

@ -65,7 +65,8 @@
"noVaildTimeSelected": "Geen geldig tijdsbereik geselecteerd",
"endTimeMustAfterStartTime": "Eindtijd moet na starttijd zijn"
},
"success": "Export is succesvol gestart. Bekijk het bestand op de exportpagina."
"success": "Export is succesvol gestart. Bekijk het bestand op de exportpagina.",
"view": "Weergeven"
},
"fromTimeline": {
"saveExport": "Export opslaan",

View File

@ -261,7 +261,8 @@
"header": {
"zones": "Zones",
"ratio": "Verhouding",
"area": "Gebied"
"area": "Gebied",
"score": "Score"
}
},
"annotationSettings": {

View File

@ -19,19 +19,25 @@
"trainingModel": "Treinamento do modelo iniciado com sucesso.",
"deletedModel_one": "{{count}} modelo excluído com sucesso",
"deletedModel_many": "{{count}} modelos excluídos com sucesso",
"deletedModel_other": "{{count}} modelos excluídos com sucesso"
"deletedModel_other": "{{count}} modelos excluídos com sucesso",
"updatedModel": "Configuração do modelo atualizada com sucesso",
"renamedCategory": "Classe renomeada para {{name}} com sucesso"
},
"error": {
"deleteImageFailed": "Falha ao deletar:{{errorMessage}}",
"deleteCategoryFailed": "Falha ao deletar classe:{{errorMessage}}",
"categorizeFailed": "Falha ao categorizar imagem:{{errorMessage}}",
"deleteModelFailed": "Falha ao excluir o modelo: {{errorMessage}}",
"trainingFailed": "Falha ao iniciar o treinamento do modelo: {{errorMessage}}"
"trainingFailed": "Falha ao iniciar o treinamento do modelo: {{errorMessage}}",
"trainingFailedToStart": "Falha ao iniciar o treinamento do modelo: {{errorMessage}}",
"updateModelFailed": "Falha ao atualizar modelo: {{errorMessage}}",
"renameCategoryFailed": "Falha ao renomear classe: {{errorMessage}}"
}
},
"deleteCategory": {
"title": "Excluir Classe",
"desc": "Tem certeza de que deseja excluir a classe {{name}}? Isso excluirá permanentemente todas as imagens associadas e exigirá o treinamento do modelo novamente."
"desc": "Tem certeza de que deseja excluir a classe {{name}}? Isso excluirá permanentemente todas as imagens associadas e exigirá o treinamento do modelo novamente.",
"minClassesTitle": "Não é possível apagar a classe"
},
"deleteModel": {
"title": "Deletar modelo de classificação",

View File

@ -88,7 +88,8 @@
"failed": "Eroare la pornirea exportului: {{error}}",
"endTimeMustAfterStartTime": "Ora de sfârșit trebuie să fie după ora de început",
"noVaildTimeSelected": "Nu a fost selectat un interval de timp valid"
}
},
"view": "Vizualizează"
},
"fromTimeline": {
"saveExport": "Salvează exportul",

View File

@ -263,7 +263,8 @@
"header": {
"zones": "Zone",
"ratio": "Raport",
"area": "Aria"
"area": "Aria",
"score": "Scor"
}
},
"annotationSettings": {

View File

@ -445,7 +445,7 @@
"boiling": "Varenie",
"sonar": "Sonar",
"arrow": "Šípka",
"whoosh": "Whoosh",
"whoosh": "Ktoosh",
"thump": "Palec",
"thunk": "Thunk",
"electronic_tuner": "Elektronický tuner",

View File

@ -248,7 +248,8 @@
"label": "Dokumentácia Frigate"
},
"review": "Recenzia",
"explore": "Preskúmať"
"explore": "Preskúmať",
"classification": "Klasifikácia"
},
"toast": {
"copyUrlToClipboard": "Adresa URL bola skopírovaná do schránky.",

View File

@ -58,7 +58,8 @@
"failed": "Chyba spustenia exportu: {{error}}",
"endTimeMustAfterStartTime": "Čas konca musí byť po čase začiatku",
"noVaildTimeSelected": "Nie je vybrané žiadne platné časové obdobie"
}
},
"view": "Zobraziť"
},
"fromTimeline": {
"saveExport": "Uložiť Export",

View File

@ -145,7 +145,10 @@
"generationFailed": "Generovanie zlyhalo. Skúste to znova.",
"classifyFailed": "Nepodarilo sa klasifikovať obrázky: {{error}}"
},
"generateSuccess": "Vzorové obrázky boli úspešne vygenerované"
"generateSuccess": "Vzorové obrázky boli úspešne vygenerované",
"allImagesRequired_one": "Uveďte všetky obrázky. {{count}} obrázok zostáva.",
"allImagesRequired_few": "Uveďte všetky obrázky. {{count}} obrázky zostávajú.",
"allImagesRequired_other": "Uveďte všetky obrázky. {{count}} obrázkov zostávajú."
}
},
"deleteModel": {

View File

@ -21,7 +21,7 @@
"regenerate": "Od poskytovateľa {{provider}} bol vyžiadaný nový popis. V závislosti od rýchlosti vášho poskytovateľa môže jeho obnovenie chvíľu trvať.",
"updatedSublabel": "Podštítok bol úspešne aktualizovaný.",
"updatedLPR": "ŠPZ bola úspešne aktualizovaná.",
"audioTranscription": "Úspešne požiadané o prepis zvuku."
"audioTranscription": "Úspešne požiadané o prepis zvuku. V závislosti od rýchlosti vášho servera Frigate môže dokončenie prepisu trvať určitý čas."
},
"error": {
"regenerate": "Nepodarilo sa zavolať od {{provider}} pre nový popis: {{errorMessage}}",
@ -263,7 +263,8 @@
"header": {
"zones": "Zóny",
"ratio": "Pomer",
"area": "Oblasť"
"area": "Oblasť",
"score": "Skóre"
}
},
"annotationSettings": {

View File

@ -1,6 +1,6 @@
{
"description": {
"addFace": "Sprievodca pridaním novej kolekcie do Knižnice tvárí.",
"addFace": "Pridajte novú kolekciu do Face Library nahrať svoj prvý obrázok.",
"invalidName": "Neplatné meno. Mená môžu obsahovať iba písmená, čísla, medzery, apostrofy, podčiarkovníky a spojovníky.",
"placeholder": "Zadajte názov pre túto kolekciu"
},

View File

@ -551,10 +551,12 @@
"probeMode": "Probe kamera",
"manualMode": "Ručný výber",
"detectionMethodDescription": "Vyskúša cez ONVIF (ak je podporovaný) nájsť kamery streamové adresy, alebo ručne vyberte značku kamery a jej preddefinované URL. Ak chcete zadať vlastnú URL RTSP, vyberte manuálne zadanie a označte \"Ostatné\".",
"onvifPortDescription": "Pre kamery, ktoré podporujú ONVIF, to je zvyčajne 80 alebo 8080."
"onvifPortDescription": "Pre kamery, ktoré podporujú ONVIF, to je zvyčajne 80 alebo 8080.",
"useDigestAuth": "Použite overenie súhrnu",
"useDigestAuthDescription": "Použite HTTP stráviteľné overenie pre ONVIF. Niektoré kamery môžu vyžadovať vyhradený ONVIF užívateľské meno/password namiesto štandardného správcu."
},
"step2": {
"description": "Konfigurovať prúdové role a pridať ďalšie prúdy pre vašu kameru.",
"description": "Vyhľadajte dostupné streamy z kamery alebo nakonfigurujte manuálne nastavenia na základe zvolenej metódy detekcie.",
"streamsTitle": "Kamerové prúdy",
"addStream": "Pridať Stream",
"addAnotherStream": "Pridať ďalší Stream",
@ -573,8 +575,8 @@
"audio": "Zvuk"
},
"testStream": "Testovacie pripojenie",
"testSuccess": "Stream test úspešné!",
"testFailed": "Stream test zlyhal",
"testSuccess": "Test pripojenia bol úspešný!",
"testFailed": "Test pripojenia zlyhal. Skontrolujte zadané údaje a skúste to znova.",
"testFailedTitle": "Test Zlyhal",
"connected": "Pripojené",
"notConnected": "Nie je pripojený",
@ -590,6 +592,38 @@
"featuresPopover": {
"title": "Funkcie streamu",
"description": "Použite prekrytie go2rtc na zníženie pripojenia k fotoaparátu."
},
"streamDetails": "Detaily vysielania",
"probing": "Skúmajúca kamera...",
"retry": "Skúste to znova",
"testing": {
"probingMetadata": "Skúmanie metadát kamery...",
"fetchingSnapshot": "Načítava sa snímka z fotoaparátu..."
},
"probeFailed": "Nepodarilo sa otestovať kameru: {{error}}",
"probingDevice": "Snímacie zariadenie...",
"probeSuccessful": "Sonda úspešná",
"probeError": "Chyba sondy",
"probeNoSuccess": "Sonda neúspešná",
"deviceInfo": "Informácie o zariadení",
"manufacturer": "Výrobca",
"model": "Model",
"firmware": "Firmvér",
"profiles": "Profily",
"ptzSupport": "PTZ Podpora",
"autotrackingSupport": "Podpora automatického sledovania",
"presets": "Prestavby",
"rtspCandidates": "RTSP kandidátov",
"rtspCandidatesDescription": "Z kamery boli nájdené nasledujúce adresy URL RTSP. Otestujte pripojenie a zobrazte metadáta streamu.",
"noRtspCandidates": "Z kamery sa nenašli žiadne URL adresy RTSP. Vaše prihlasovacie údaje môžu byť nesprávne alebo kamera nepodporuje protokol ONVIF alebo metódu použitú na získanie URL adries RTSP. Vráťte sa späť a zadajte URL adresu RTSP manuálne.",
"candidateStreamTitle": "Kandidát {{number}}",
"useCandidate": "Použitie",
"uriCopy": "Kopírovať",
"uriCopied": "URI skopírované do schránky",
"testConnection": "Testovacie pripojenie",
"toggleUriView": "Kliknutím prepnete zobrazenie celého URI",
"errors": {
"hostRequired": "Vyžaduje sa hostiteľská/IP adresa"
}
},
"step3": {
@ -621,6 +655,57 @@
"resolutionHigh": "Rozlíšenie {{resolution}} môže spôsobiť zvýšenú spotrebu zdrojov.",
"resolutionLow": "Rozlíšenie {{resolution}} môže byť príliš nízka pre spoľahlivú detekciu malých objektov."
},
"description": "Nakonfigurujte role streamov a pridajte ďalšie streamy pre vašu kameru.",
"validationTitle": "Stream Platnosť",
"connectAllStreams": "Pripojte všetky prúdy",
"reconnectionSuccess": "Opätovné pripojenie bolo úspešné.",
"reconnectionPartial": "Niektoré prúdy sa nepodarilo prepojiť.",
"streamUnavailable": "Ukážka streamu nie je k dispozícii",
"reload": "Znovu načítať",
"connecting": "Pripája...",
"streamTitle": "Stream {{number}}",
"valid": "Platné",
"failed": "Zlyhanie",
"notTested": "Netestované",
"streamsTitle": "Kamerové prúdy",
"addStream": "Pridať Stream",
"addAnotherStream": "Pridať ďalší Stream",
"streamUrl": "Stream URL",
"streamUrlPlaceholder": "rtsp://username:password@host:port/path",
"selectStream": "Vyberte stream",
"searchCandidates": "Hľadať kandidátov...",
"noStreamFound": "Nenašiel sa žiadny stream",
"url": "URL",
"resolution": "Rozlíšenie",
"selectResolution": "Vyberte rozlíšenie",
"quality": "Kvalita",
"selectQuality": "Vyberte kvalitu",
"roleLabels": {
"detect": "Detekcia objektov",
"record": "Nahrávanie",
"audio": "Zvuk"
},
"testStream": "Testovanie pripojenia",
"testSuccess": "Stream test úspešné!",
"testFailed": "Stream test zlyhal",
"testFailedTitle": "Test Zlyhal",
"connected": "Pripojené",
"notConnected": "Nie je pripojený",
"featuresTitle": "Vlastnosti",
"go2rtc": "Znížte počet pripojení ku kamere",
"detectRoleWarning": "Aspoň jeden prúd musí mať \"detekt\" úlohu pokračovať.",
"rolesPopover": {
"title": "Roly streamu",
"detect": "Hlavné krmivo pre detekciu objektu.",
"record": "Ukladá segmenty video kanála na základe nastavení konfigurácie.",
"audio": "Kŕmenie pre detekciu zvuku."
},
"featuresPopover": {
"title": "Funkcie streamu",
"description": "Použite prekrytie go2rtc na zníženie pripojenia k fotoaparátu."
}
},
"step4": {
"description": "Záverečné overenie a analýza pred uložením nového fotoaparátu. Pripojte každý prúd pred uložením.",
"validationTitle": "Stream Platnosť",
"connectAllStreams": "Pripojte všetky prúdy",
@ -632,7 +717,40 @@
"streamTitle": "Stream {{number}}",
"valid": "Platné",
"failed": "Zlyhanie",
"notTested": "Netestované"
"notTested": "Netestované",
"connectStream": "Pripojiť",
"connectingStream": "Pripája",
"disconnectStream": "Odpojiť",
"estimatedBandwidth": "Odhadovaná šírka pásma",
"roles": "Roly",
"ffmpegModule": "Použite režim kompatibility prúdu",
"ffmpegModuleDescription": "Ak sa stream nenačíta ani po niekoľkých pokusoch, skúste túto funkciu povoliť. Keď je táto funkcia povolená, Frigate použije modul ffmpeg s go2rtc. To môže poskytnúť lepšiu kompatibilitu s niektorými streammi z kamier.",
"none": "Žiadne",
"error": "Chyba",
"streamValidated": "Stream {{number}} úspešne overený",
"streamValidationFailed": "Stream {{number}} validácia zlyhala",
"saveAndApply": "Uložiť novú kameru",
"saveError": "Neplatná konfigurácia. Skontrolujte nastavenia.",
"issues": {
"title": "Platnosť Streamu",
"videoCodecGood": "Kód videa je {{codec}}.",
"audioCodecGood": "Audio kódc je {{codec}}.",
"resolutionHigh": "Rozlíšenie {{resolution}} môže spôsobiť zvýšenú spotrebu zdrojov.",
"resolutionLow": "Rozlíšenie {{resolution}} môže byť príliš nízka pre spoľahlivú detekciu malých objektov.",
"noAudioWarning": "Žiadne audio nebolo detekovane pre tento prúd, nahrávanie nebude mať audio.",
"audioCodecRecordError": "AAC audio kodek je potrebný na podporu audio v záznamoch.",
"audioCodecRequired": "Zvukový prúd je povinný podporovať detekciu zvuku.",
"restreamingWarning": "Zníženie pripojenia ku kamery pre rekordný prúd môže mierne zvýšiť využitie CPU.",
"brands": {
"reolink-rtsp": "Reolink RTSP sa neodporúča. Odporúča sa povoliť HTTP v nastavení kamery a reštartovať sprievodca kamery."
},
"dahua": {
"substreamWarning": "Čiastkový stream 1 je uzamknutý na nízke rozlíšenie. Mnoho kamier Dahua / Amcrest / EmpireTech podporuje ďalšie čiastkové streamy, ktoré je potrebné povoliť v nastaveniach kamery. Odporúča sa skontrolovať a využiť tieto streamy, ak sú k dispozícii."
},
"hikvision": {
"substreamWarning": "Čiastkový stream 1 je uzamknutý na nízke rozlíšenie. Mnoho kamier Hikvision podporuje ďalšie čiastkové streamy, ktoré je potrebné povoliť v nastaveniach kamery. Odporúča sa skontrolovať a využiť tieto streamy, ak sú k dispozícii."
}
}
}
},
"cameraManagement": {
@ -829,9 +947,9 @@
"createRole": "Rola {{role}} bola úspešne vytvorená",
"updateCameras": "Kamery aktualizované pre rolu {{role}}",
"deleteRole": "Rola {{role}} bola úspešne odstránená",
"userRolesUpdated_one": "{{count}} užívateľ (y) priradené tejto úlohe boli aktualizované pre \"viewer\", ktorý má prístup ku všetkým kamerám.",
"userRolesUpdated_one": "",
"userRolesUpdated_few": "",
"userRolesUpdated_other": ""
"userRolesUpdated_other": "{{count}} užívatelia priradené tejto úlohe boli aktualizované pre \"viewer\", ktorý má prístup ku všetkým kamerám."
},
"error": {
"createRoleFailed": "Nepodarilo sa vytvoriť rolu: {{errorMessage}}",

View File

@ -188,7 +188,10 @@
"yolov9_plate_detection": "YOLOv9 Detekcia ŠPZ",
"review_description": "Popis recenzie",
"review_description_speed": "Popis recenzie Rýchlosťi",
"review_description_events_per_second": "Popis"
"review_description_events_per_second": "Popis",
"object_description": "Popis objektu",
"object_description_speed": "Popis objektu Rýchlosť",
"object_description_events_per_second": "Popis objektu"
},
"averageInf": "Priemerný čas inferencie"
}

View File

@ -81,7 +81,10 @@
"formattedTimestampMonthDayYear": {
"12hour": "d MMM, yyyy",
"24hour": "d MMM, yyyy"
}
},
"inProgress": "Devam ediyor",
"invalidStartTime": "Geçersiz başlangıç zamanı",
"invalidEndTime": "Geçersiz bitiş zamanı"
},
"button": {
"off": "KAPALI",
@ -222,7 +225,12 @@
"uiPlayground": "UI Deneme Alanı"
},
"label": {
"back": "Geri"
"back": "Geri",
"hide": "{{item}} öğesini gizle",
"show": "{{item}} öğesini göster",
"ID": "ID",
"none": "Yok",
"all": "Tümü"
},
"notFound": {
"documentTitle": "Bulunamadı - Frigate",
@ -237,6 +245,14 @@
"length": {
"feet": "feet",
"meters": "metre"
},
"data": {
"kbps": "kB/s",
"mbps": "MB/s",
"gbps": "GB/s",
"kbph": "kB/saat",
"mbph": "MB/saat",
"gbph": "GB/saat"
}
},
"pagination": {
@ -273,5 +289,14 @@
"admin": "Yönetici",
"desc": "Yöneticiler Frigate arayüzündeki bütün özelliklere tam erişim sahibidir. Görüntüleyiciler ise yalnızca kameraları, eski görüntüleri ve inceleme öğelerini görüntülemekle sınırlıdır."
},
"readTheDocumentation": "Dökümantasyonu oku"
"readTheDocumentation": "Dökümantasyonu oku",
"list": {
"two": "{{0}} ve {{1}}",
"many": "{{items}} ve {{last}}",
"separatorWithSpace": ", "
},
"field": {
"optional": "İsteğe bağlı",
"internalID": "Frigateın yapılandırma ve veritabanında kullandığı Dahili Kimlik"
}
}

View File

@ -64,7 +64,8 @@
"failed": "Dışa aktarım başlatılamadı: {{error}}",
"endTimeMustAfterStartTime": "Bitiş zamanı başlangıç zamanından sonra olmalıdır",
"noVaildTimeSelected": "Geçerli bir zaman aralığı seçilmedi"
}
},
"view": "Görüntüle"
},
"fromTimeline": {
"saveExport": "Dışa Aktarımı Kaydet",
@ -117,7 +118,8 @@
"button": {
"export": "Dışa Aktar",
"markAsReviewed": "İncelendi olarak işaretle",
"deleteNow": "Şimdi Sil"
"deleteNow": "Şimdi Sil",
"markAsUnreviewed": "Gözden geçirilmedi olarak işaretle"
}
},
"imagePicker": {
@ -125,6 +127,7 @@
"noImages": "Bu kamera için küçük resim bulunamadı",
"search": {
"placeholder": "Etiket/alt etiket kullanarak arama yapın..."
}
},
"unknownLabel": "Kaydedilen Tetikleme Görseli"
}
}

View File

@ -10,44 +10,53 @@
"deleteImages": "Fotoğrafları Sil",
"trainModel": "Modeli Eğit",
"addClassification": "Sınıflandırma Ekle",
"deleteModels": "Modelleri Sil"
"deleteModels": "Modelleri Sil",
"editModel": "Modeli Düzenle"
},
"toast": {
"success": {
"deletedCategory": "Silinmiş Sınıf",
"deletedImage": "Silinmiş Fotoğraflar",
"deletedModel_one": "{{tane}} model(ler) başarıyla silindi",
"deletedModel_other": "",
"deletedModel_one": "{{count}} model başarıyla silindi",
"deletedModel_other": "{{count}} model başarıyla silindi",
"categorizedImage": "Fotoğraf Başarıyla Sınıflandırıldı",
"trainedModel": "Model başarıyla eğitildi.",
"trainingModel": "Model eğitimi başarıyla başladı."
"trainingModel": "Model eğitimi başarıyla başladı.",
"updatedModel": "Model yapılandırması başarıyla güncellendi",
"renamedCategory": "Sınıf başarıyla {{name}} olarak yeniden adlandırıldı"
},
"error": {
"deleteImageFailed": "Silinirken hatayla karşılaşıldı: {{errorMessage}}",
"deleteImageFailed": "Silinemedi: {{errorMessage}}",
"deleteModelFailed": "Model silinirken hata oluştu: {{errorMessage}}",
"categorizeFailed": "Görsel sınıflandırılamadı: {{errorMessage}}",
"trainingFailed": "Model eğitimi başlatılamadı: {{errorMessage}}"
"trainingFailed": "Model eğitimi başarısız oldu. Ayrıntılar için Frigate günlüklerini kontrol edin.",
"deleteCategoryFailed": "Sınıf silinemedi: {{errorMessage}}",
"trainingFailedToStart": "Model eğitimi başlatılamadı: {{errorMessage}}",
"updateModelFailed": "Model güncellenemedi: {{errorMessage}}",
"renameCategoryFailed": "Sınıf yeniden adlandırılamadı: {{errorMessage}}"
}
},
"deleteCategory": {
"title": "Sınıfı Sil",
"desc": "{{name}} adlı sınıfı silmek istediğinizden emin misiniz? Bu işlem, sınıfa ait tüm görselleri kalıcı olarak silecek ve modelin yeniden eğitilmesini gerektirecektir."
"desc": "{{name}} adlı sınıfı silmek istediğinizden emin misiniz? Bu işlem, sınıfa ait tüm görselleri kalıcı olarak silecek ve modelin yeniden eğitilmesini gerektirecektir.",
"minClassesTitle": "Sınıf Silinemiyor",
"minClassesDesc": "Bu sınıfı silmeden önce bir sınıflandırma modelinin en az 2 sınıfa sahip olması gerekir. Bu sınıfı silmeden önce başka bir sınıf ekleyin."
},
"deleteModel": {
"title": "Sınıflandırma Modelini Sil",
"single": "{{name}} öğesini silmek istediğinizden emin misiniz? Bu işlem, görseller ve eğitim verileri dâhil olmak üzere tüm ilişkili verileri kalıcı olarak silecektir. Bu işlem geri alınamaz.",
"desc_one": "{{count}} modeli silmek istediğinizden emin misiniz? Bu işlem, görseller ve eğitim verileri dâhil olmak üzere tüm ilişkili verileri kalıcı olarak silecektir. Bu işlem geri alınamaz.",
"desc_other": ""
"desc_other": "{{count}} modeli silmek istediğinizden emin misiniz? Bu işlem, görseller ve eğitim verileri dâhil olmak üzere tüm ilişkili verileri kalıcı olarak silecektir. Bu işlem geri alınamaz."
},
"deleteDatasetImages": {
"title": "Eğitim verisi görsellerini sil",
"desc_one": "{{dataset}} veri kümesinden {{count}} görseli silmek istediğinizden emin misiniz? Bu işlem geri alınamaz ve modelin yeniden eğitilmesini gerektirecektir.",
"desc_other": ""
"desc_one": "{{dataset}} veri kümesinden {{count}} görseli silmek istediğinizden emin misiniz? Bu işlem geri alınamaz ve modelin yeniden eğitilmesini gerektirir.",
"desc_other": "{{dataset}} veri kümesinden {{count}} görseli silmek istediğinizden emin misiniz? Bu işlem geri alınamaz ve modelin yeniden eğitilmesini gerektirir."
},
"deleteTrainImages": {
"title": "Eğitim Görsellerini Sil",
"desc_one": "{{count}} görseli silmek istediğinizden emin misiniz? Bu işlem geri alınamaz.",
"desc_other": ""
"desc_other": "{{count}} görseli silmek istediğinizden emin misiniz? Bu işlem geri alınamaz."
},
"renameCategory": {
"title": "Sınıfı Yeniden Adlandır",
@ -76,6 +85,43 @@
"title": "Nesne sınıflandırma modeli mevcut değil",
"description": "Algılanan nesneleri sınıflandırmak için özel bir model oluşturun.",
"buttonText": "Nesne Modeli Oluştur"
},
"state": {
"title": "Durum Sınıflandırma Modeli Yok",
"description": "Belirli kamera alanlarındaki durum değişimlerini izlemek ve sınıflandırmak için özel bir model oluşturun.",
"buttonText": "Durum Modeli Oluştur"
}
},
"tooltip": {
"trainingInProgress": "Model şu anda eğitiliyor",
"noNewImages": "Eğitilecek yeni görsel bulunmuyor. Önce veri kümesinde daha fazla görseli sınıflandırın.",
"noChanges": "Son eğitimden bu yana veri kümesinde herhangi bir değişiklik yapılmadı.",
"modelNotReady": "Model eğitim için hazır değil"
},
"edit": {
"title": "Sınıflandırma Modelini Düzenle",
"descriptionState": "Bu durum sınıflandırma modeli için sınıfları düzenleyin. Değişiklikler, modelin yeniden eğitilmesini gerektirecektir.",
"descriptionObject": "Bu nesne sınıflandırma modeli için nesne türünü ve sınıflandırma türünü düzenleyin.",
"stateClassesInfo": "Not: Durum sınıflarını değiştirmek, modelin güncellenmiş sınıflarla yeniden eğitilmesini gerektirir."
},
"wizard": {
"title": "Yeni Sınıflandırma Oluştur",
"steps": {
"nameAndDefine": "Adlandır ve Tanımla",
"stateArea": "Durum Alanı",
"chooseExamples": "Örnekleri Seç"
},
"step1": {
"description": "State modelleri, sabit kamera alanlarındaki değişiklikleri (ör. kapının açılması/kapanması) izler. Nesne modelleri ise algılanan nesnelere ek sınıflandırmalar ekler (ör. bilinen hayvanlar, kuryeler vb.).",
"name": "Ad",
"namePlaceholder": "Model adını girin...",
"type": "Tür",
"typeState": "Durum",
"typeObject": "Nesne",
"objectLabel": "Nesne Etiketi",
"objectLabelPlaceholder": "Nesne türünü seçin...",
"classificationType": "Sınıflandırma Türü",
"classificationTypeTip": "Sınıflandırma türleri hakkında bilgi edinin"
}
}
}

View File

@ -42,14 +42,15 @@
"detail": {
"label": "Detay",
"aria": "Ayrıntı görünümünü aç/kapat",
"trackedObject_one": "Nesne",
"trackedObject_other": "nesneler",
"trackedObject_one": "{{count}} nesne",
"trackedObject_other": "{{count}} nesne",
"noObjectDetailData": "Nesneye ait ayrıntılı veri bulunmuyor.",
"settings": "Ayrıntılı Görünüm Ayarları",
"alwaysExpandActive": {
"title": "Etkin olanı her zaman genişlet",
"desc": "Varsa, etkin inceleme öğesinin nesne ayrıntılarını daima göster."
}
},
"noDataFound": "İncelenecek ayrıntılı veri bulunmuyor"
},
"objectTrack": {
"trackedPoint": "Takip edilen nokta",

View File

@ -19,7 +19,7 @@
"updatedSublabel": "Alt etiket başarıyla gücellendi.",
"regenerate": "Yeni bir açıklama {{provider}} sağlayıcısından talep edildi. Sağlayıcının hızına bağlı olarak yeni açıklamanın oluşturulması biraz zaman alabilir.",
"updatedLPR": "Plaka başarıyla güncellendi.",
"audioTranscription": "Ses çözümlemesi başarıyla talep edildi."
"audioTranscription": "Ses dökümü başarıyla istendi. Frigate sunucunuzun hızına bağlı olarak döküm işlemi tamamlanması biraz zaman alabilir."
},
"error": {
"updatedSublabelFailed": "Alt etiket güncellenemedi: {{errorMessage}}",
@ -110,7 +110,8 @@
"object_lifecycle": "nesne yaşam döngüsü",
"snapshot": "fotoğraf",
"video": "video",
"thumbnail": "küçük resim"
"thumbnail": "küçük resim",
"tracking_details": "izleme ayrıntıları"
},
"objectLifecycle": {
"title": "Nesne Yaşam Döngüsü",
@ -244,8 +245,30 @@
"external": "{{label}} tespit edildi",
"header": {
"zones": "Bölgeler",
"ratio": "Oran"
"ratio": "Oran",
"area": "Alan",
"score": "Skor"
}
},
"annotationSettings": {
"title": "Etiketleme Ayarları",
"showAllZones": {
"title": "Tüm Bölgeleri Göster",
"desc": "Herhangi bir bölgeye nesne girdiğinde, o karede bölgeleri her zaman göster."
},
"offset": {
"label": "Etiket Kaydırma Değeri",
"desc": "Bu veriler kameranızın algılama akışından gelir ancak kayıt akışındaki görüntülerin üzerine bindirilir. İki akışın tamamen senkronize olması pek olası değildir. Bu nedenle sınır kutusu ile görüntü birebir hizalı olmayabilir. Bu ayarı kullanarak anotasyonları zamansal olarak ileri veya geri kaydırabilir ve kaydedilmiş görüntülerle daha iyi hizalayabilirsiniz.",
"millisecondsToOffset": "Algılama anotasyonlarının kaydırılacağı milisaniye değeri. <em>Varsayılan: 0</em>",
"tips": "Videonun oynatımı kutulardan ve yol noktalarından öndeyse değeri düşürün; geride kalıyorsa değeri artırın. Bu değer negatif olabilir.",
"toast": {
"success": "{{camera}} için anotasyon kaydırması yapılandırma dosyasına kaydedildi. Değişikliklerin uygulanması için Frigatei yeniden başlatın."
}
}
},
"carousel": {
"previous": "Önceki slayt",
"next": "Sonraki slayt"
}
}
}

View File

@ -27,7 +27,7 @@
"nextSteps": "Sağlam bir temel oluşturmak için:<li>Her tespit edilen kişi için **Recent Recognitions (Son Tanımalar)** sekmesini kullanarak görüntüleri seçin ve eğitim gerçekleştirin.</li> <li>En iyi sonuçlar için doğrudan önden çekilmiş yüz görüntülerine odaklanın; yüzlerin açılı göründüğü fotoğrafları eğitimde kullanmaktan kaçının.</li>"
},
"train": {
"title": "Son Algılananlar",
"title": "Son Tanımalar",
"aria": "Son algılanan nesneleri seç",
"empty": "Yakın zamanda yüz tanıma denemesi olmadı"
},
@ -61,7 +61,7 @@
"addFaceLibrary": "{{name}} başarıyla Yüz Kütüphanesine eklendi!",
"trainedFace": "Yüz başarıyla eğitildi.",
"uploadedImage": "Resim başarıyla yüklendi.",
"updatedFaceScore": "Yüz skoru başarıyla güncellendi.",
"updatedFaceScore": "Yüz tanıma skoru {{name}} ({{score}}) olarak başarıyla güncellendi.",
"renamedFace": "Yüz başarıyla {{name}} olarak adlandırıldı"
},
"error": {

View File

@ -52,7 +52,10 @@
"label": "Arka planda oynat",
"tips": "Yayını oynatıcı arkadayken de devam ettirmek için bu seçeneği açın."
},
"title": "Yayın"
"title": "Yayın",
"debug": {
"picker": "Debug modunda akış seçimi kullanılamaz. Debug görünümü her zaman “detect” rolüne atanmış akışı kullanır."
}
},
"cameraSettings": {
"recording": "Kayıt",

View File

@ -8,7 +8,7 @@
"motionTuner": "Hareket Algılama Ayarları - Frigate",
"frigatePlus": "Frigate+ Ayarları - Frigate",
"object": "Hata Ayıklama - Frigate",
"general": "Genel Ayarlar - Frigate",
"general": "Kullanıcı Arayüzü Ayarları Frigate",
"notifications": "Bildirim Ayarları - Frigate",
"enrichments": "Zenginleştirme Ayarları - Frigate",
"cameraManagement": "Kameraları Yönet - Frigate",
@ -31,7 +31,7 @@
"roles": "Roller"
},
"general": {
"title": "Genel Ayarlar",
"title": "Kullanıcı Arayüzü Ayarları",
"liveDashboard": {
"automaticLiveView": {
"label": "Otomatik Canlı Görünüm",
@ -45,6 +45,10 @@
"displayCameraNames": {
"label": "Kamera Adlarını Her Zaman Göster",
"desc": "Çok kameralı canlı izleme panelinde, kamera adlarını her zaman bir etiket içinde göster."
},
"liveFallbackTimeout": {
"label": "Canlı Oynatıcı Yedekleme Zaman Aşımı",
"desc": "Bir kameranın yüksek kaliteli canlı akışı kullanılamadığında, belirtilen saniye kadar sonra düşük bant genişliği moduna geç. Varsayılan: 3."
}
},
"storedLayouts": {

View File

@ -43,7 +43,12 @@
"gpuEncoder": "GPU Kodlayıcı",
"title": "Donanım Bilgisi",
"npuUsage": "NPU Kullanımı",
"npuMemory": "NPU Bellek Kullanımı"
"npuMemory": "NPU Bellek Kullanımı",
"intelGpuWarning": {
"title": "Intel GPU İstatistik Uyarısı",
"message": "GPU istatistikleri kullanılamıyor",
"description": "Bu, Intelin GPU istatistik raporlama araçlarında (intel_gpu_top) bilinen bir hatadır; araç çalışmayı bozarak, donanımsal hızlandırma ve nesne tespiti (i)GPU üzerinde doğru şekilde çalışıyor olsa bile, GPU kullanımını tekrar tekrar %0 olarak döndürür. Bu bir Frigate hatası değildir. Sorunu geçici olarak düzeltmek ve GPUnun doğru çalıştığını doğrulamak için host sistemini yeniden başlatabilirsiniz. Bu durum performansı etkilemez."
}
},
"otherProcesses": {
"title": "Diğer İşlemler",

View File

@ -57,7 +57,8 @@
"endTimeMustAfterStartTime": "Час закінчення повинен бути після часу початку",
"noVaildTimeSelected": "Не вибрано допустимий діапазон часу"
},
"success": "Експорт успішно розпочато. Перегляньте файл на сторінці експорту."
"success": "Експорт успішно розпочато. Перегляньте файл на сторінці експорту.",
"view": "Переглянути"
},
"fromTimeline": {
"saveExport": "Зберегти експорт",

View File

@ -263,7 +263,8 @@
"header": {
"zones": "Зони",
"ratio": "Співвідношення",
"area": "Площа"
"area": "Площа",
"score": "Рахунок"
}
},
"annotationSettings": {

View File

@ -10,12 +10,8 @@ import useSWR from "swr";
import { baseUrl } from "@/api/baseUrl";
import { isMobile } from "react-device-detect";
import { cn } from "@/lib/utils";
import {
Tooltip,
TooltipContent,
TooltipTrigger,
} from "@/components/ui/tooltip";
import { TooltipPortal } from "@radix-ui/react-tooltip";
import { Alert, AlertDescription, AlertTitle } from "@/components/ui/alert";
import { IoIosWarning } from "react-icons/io";
export type Step3FormData = {
examplesGenerated: boolean;
@ -145,20 +141,67 @@ export default function Step3ChooseExamples({
);
await Promise.all(categorizePromises);
// Step 3: Kick off training
await axios.post(`/classification/${step1Data.modelName}/train`);
// Step 2.5: Create empty folders for classes that don't have any images
// This ensures all classes are available in the dataset view later
const classesWithImages = new Set(
Object.values(classifications).filter((c) => c && c !== "none"),
);
const emptyFolderPromises = step1Data.classes
.filter((className) => !classesWithImages.has(className))
.map((className) =>
axios.post(
`/classification/${step1Data.modelName}/dataset/${className}/create`,
),
);
await Promise.all(emptyFolderPromises);
toast.success(t("wizard.step3.trainingStarted"), {
closeButton: true,
});
setIsTraining(true);
// Step 3: Determine if we should train
// For state models, we need ALL states to have examples
// For object models, we need at least 2 classes with images
const allStatesHaveExamplesForTraining =
step1Data.modelType !== "state" ||
step1Data.classes.every((className) =>
classesWithImages.has(className),
);
const shouldTrain =
allStatesHaveExamplesForTraining && classesWithImages.size >= 2;
// Step 4: Kick off training only if we have enough classes with images
if (shouldTrain) {
await axios.post(`/classification/${step1Data.modelName}/train`);
toast.success(t("wizard.step3.trainingStarted"), {
closeButton: true,
});
setIsTraining(true);
} else {
// Don't train - not all states have examples
toast.success(t("wizard.step3.modelCreated"), {
closeButton: true,
});
setIsTraining(false);
onClose();
}
},
[step1Data, step2Data, t],
[step1Data, step2Data, t, onClose],
);
const handleContinueClassification = useCallback(async () => {
// Mark selected images with current class
const newClassifications = { ...imageClassifications };
// Handle user going back and de-selecting images
const imagesToCheck = unknownImages.slice(0, 24);
imagesToCheck.forEach((imageName) => {
if (
newClassifications[imageName] === currentClass &&
!selectedImages.has(imageName)
) {
delete newClassifications[imageName];
}
});
// Then, add all currently selected images to the current class
selectedImages.forEach((imageName) => {
newClassifications[imageName] = currentClass;
});
@ -329,8 +372,43 @@ export default function Step3ChooseExamples({
return unclassifiedImages.length === 0;
}, [unclassifiedImages]);
// For state models on the last class, require all images to be classified
const isLastClass = currentClassIndex === allClasses.length - 1;
const statesWithExamples = useMemo(() => {
if (step1Data.modelType !== "state") return new Set<string>();
const states = new Set<string>();
const allImages = unknownImages.slice(0, 24);
// Check which states have at least one image classified
allImages.forEach((img) => {
let className: string | undefined;
if (selectedImages.has(img)) {
className = currentClass;
} else {
className = imageClassifications[img];
}
if (className && allClasses.includes(className)) {
states.add(className);
}
});
return states;
}, [
step1Data.modelType,
unknownImages,
imageClassifications,
selectedImages,
currentClass,
allClasses,
]);
const allStatesHaveExamples = useMemo(() => {
if (step1Data.modelType !== "state") return true;
return allClasses.every((className) => statesWithExamples.has(className));
}, [step1Data.modelType, allClasses, statesWithExamples]);
// For state models on the last class, require all images to be classified
// But allow proceeding even if not all states have examples (with warning)
const canProceed = useMemo(() => {
if (step1Data.modelType === "state" && isLastClass) {
// Check if all 24 images will be classified after current selections are applied
@ -353,6 +431,28 @@ export default function Step3ChooseExamples({
selectedImages,
]);
const hasUnclassifiedImages = useMemo(() => {
if (!unknownImages) return false;
const allImages = unknownImages.slice(0, 24);
return allImages.some((img) => !imageClassifications[img]);
}, [unknownImages, imageClassifications]);
const showMissingStatesWarning = useMemo(() => {
return (
step1Data.modelType === "state" &&
isLastClass &&
!allStatesHaveExamples &&
!hasUnclassifiedImages &&
hasGenerated
);
}, [
step1Data.modelType,
isLastClass,
allStatesHaveExamples,
hasUnclassifiedImages,
hasGenerated,
]);
const handleBack = useCallback(() => {
if (currentClassIndex > 0) {
const previousClass = allClasses[currentClassIndex - 1];
@ -399,6 +499,17 @@ export default function Step3ChooseExamples({
</div>
) : hasGenerated ? (
<div className="flex flex-col gap-4">
{showMissingStatesWarning && (
<Alert variant="destructive">
<IoIosWarning className="size-5" />
<AlertTitle>
{t("wizard.step3.missingStatesWarning.title")}
</AlertTitle>
<AlertDescription>
{t("wizard.step3.missingStatesWarning.description")}
</AlertDescription>
</Alert>
)}
{!allImagesClassified && (
<div className="text-center">
<h3 className="text-lg font-medium">
@ -474,35 +585,22 @@ export default function Step3ChooseExamples({
<Button type="button" onClick={handleBack} className="sm:flex-1">
{t("button.back", { ns: "common" })}
</Button>
<Tooltip>
<TooltipTrigger asChild>
<Button
type="button"
onClick={
allImagesClassified
? handleContinue
: handleContinueClassification
}
variant="select"
className="flex items-center justify-center gap-2 sm:flex-1"
disabled={
!hasGenerated || isGenerating || isProcessing || !canProceed
}
>
{isProcessing && <ActivityIndicator className="size-4" />}
{t("button.continue", { ns: "common" })}
</Button>
</TooltipTrigger>
{!canProceed && (
<TooltipPortal>
<TooltipContent>
{t("wizard.step3.allImagesRequired", {
count: unclassifiedImages.length,
})}
</TooltipContent>
</TooltipPortal>
)}
</Tooltip>
<Button
type="button"
onClick={
allImagesClassified
? handleContinue
: handleContinueClassification
}
variant="select"
className="flex items-center justify-center gap-2 sm:flex-1"
disabled={
!hasGenerated || isGenerating || isProcessing || !canProceed
}
>
{isProcessing && <ActivityIndicator className="size-4" />}
{t("button.continue", { ns: "common" })}
</Button>
</div>
)}
</div>

View File

@ -78,7 +78,7 @@ import { useStreamingSettings } from "@/context/streaming-settings-provider";
import { Trans, useTranslation } from "react-i18next";
import { CameraNameLabel } from "../camera/FriendlyNameLabel";
import { useAllowedCameras } from "@/hooks/use-allowed-cameras";
import { useIsCustomRole } from "@/hooks/use-is-custom-role";
import { useIsAdmin } from "@/hooks/use-is-admin";
type CameraGroupSelectorProps = {
className?: string;
@ -88,7 +88,7 @@ export function CameraGroupSelector({ className }: CameraGroupSelectorProps) {
const { t } = useTranslation(["components/camera"]);
const { data: config } = useSWR<FrigateConfig>("config");
const allowedCameras = useAllowedCameras();
const isCustomRole = useIsCustomRole();
const isAdmin = useIsAdmin();
// tooltip
@ -124,7 +124,7 @@ export function CameraGroupSelector({ className }: CameraGroupSelectorProps) {
const allGroups = Object.entries(config.camera_groups);
// If custom role, filter out groups where user has no accessible cameras
if (isCustomRole) {
if (!isAdmin) {
return allGroups
.filter(([, groupConfig]) => {
// Check if user has access to at least one camera in this group
@ -136,7 +136,7 @@ export function CameraGroupSelector({ className }: CameraGroupSelectorProps) {
}
return allGroups.sort((a, b) => a[1].order - b[1].order);
}, [config, allowedCameras, isCustomRole]);
}, [config, allowedCameras, isAdmin]);
// add group
@ -153,7 +153,7 @@ export function CameraGroupSelector({ className }: CameraGroupSelectorProps) {
activeGroup={group}
setGroup={setGroup}
deleteGroup={deleteGroup}
isCustomRole={isCustomRole}
isAdmin={isAdmin}
/>
<Scroller className={`${isMobile ? "whitespace-nowrap" : ""}`}>
<div
@ -221,7 +221,7 @@ export function CameraGroupSelector({ className }: CameraGroupSelectorProps) {
);
})}
{!isCustomRole && (
{isAdmin && (
<Button
className="bg-secondary text-muted-foreground"
aria-label={t("group.add")}
@ -245,7 +245,7 @@ type NewGroupDialogProps = {
activeGroup?: string;
setGroup: (value: string | undefined, replace?: boolean | undefined) => void;
deleteGroup: () => void;
isCustomRole?: boolean;
isAdmin?: boolean;
};
function NewGroupDialog({
open,
@ -254,7 +254,7 @@ function NewGroupDialog({
activeGroup,
setGroup,
deleteGroup,
isCustomRole,
isAdmin,
}: NewGroupDialogProps) {
const { t } = useTranslation(["components/camera"]);
const { mutate: updateConfig } = useSWR<FrigateConfig>("config");
@ -390,7 +390,7 @@ function NewGroupDialog({
>
<Title>{t("group.label")}</Title>
<Description className="sr-only">{t("group.edit")}</Description>
{!isCustomRole && (
{isAdmin && (
<div
className={cn(
"absolute",
@ -422,7 +422,7 @@ function NewGroupDialog({
group={group}
onDeleteGroup={() => onDeleteGroup(group[0])}
onEditGroup={() => onEditGroup(group)}
isReadOnly={isCustomRole}
isReadOnly={!isAdmin}
/>
))}
</div>
@ -677,7 +677,7 @@ export function CameraGroupEdit({
);
const allowedCameras = useAllowedCameras();
const isCustomRole = useIsCustomRole();
const isAdmin = useIsAdmin();
const [openCamera, setOpenCamera] = useState<string | null>();
@ -867,7 +867,7 @@ export function CameraGroupEdit({
<FormMessage />
{[
...(birdseyeConfig?.enabled &&
(!isCustomRole || "birdseye" in allowedCameras)
(isAdmin || "birdseye" in allowedCameras)
? ["birdseye"]
: []),
...Object.keys(config?.cameras ?? {})

View File

@ -1,7 +1,11 @@
import { Dialog, DialogContent, DialogTrigger } from "@/components/ui/dialog";
import { Drawer, DrawerContent, DrawerTrigger } from "@/components/ui/drawer";
import { cn } from "@/lib/utils";
import { ReviewSegment, ThreatLevel } from "@/types/review";
import {
ReviewSegment,
ThreatLevel,
THREAT_LEVEL_LABELS,
} from "@/types/review";
import { useEffect, useMemo, useState } from "react";
import { isDesktop } from "react-device-detect";
import { useTranslation } from "react-i18next";
@ -55,13 +59,22 @@ export function GenAISummaryDialog({
}
let concerns = "";
switch (aiAnalysis.potential_threat_level) {
case ThreatLevel.SUSPICIOUS:
concerns = `${t("suspiciousActivity", { ns: "views/events" })}\n`;
break;
case ThreatLevel.DANGER:
concerns = `${t("threateningActivity", { ns: "views/events" })}\n`;
break;
const threatLevel = aiAnalysis.potential_threat_level ?? 0;
if (threatLevel > 0) {
let label = "";
switch (threatLevel) {
case ThreatLevel.NEEDS_REVIEW:
label = t("needsReview", { ns: "views/events" });
break;
case ThreatLevel.SECURITY_CONCERN:
label = t("securityConcern", { ns: "views/events" });
break;
default:
label = THREAT_LEVEL_LABELS[threatLevel as ThreatLevel] || "Unknown";
}
concerns = `${label}\n`;
}
(aiAnalysis.other_concerns ?? []).forEach((c) => {

View File

@ -1,7 +1,11 @@
import React, { useCallback, useEffect, useMemo, useState } from "react";
import { useApiHost } from "@/api";
import { isCurrentHour } from "@/utils/dateUtil";
import { ReviewSegment } from "@/types/review";
import {
ReviewSegment,
ThreatLevel,
THREAT_LEVEL_LABELS,
} from "@/types/review";
import { getIconForLabel } from "@/utils/iconUtil";
import TimeAgo from "../dynamic/TimeAgo";
import useSWR from "swr";
@ -44,7 +48,7 @@ export default function PreviewThumbnailPlayer({
onClick,
onTimeUpdate,
}: PreviewPlayerProps) {
const { t } = useTranslation(["components/player"]);
const { t } = useTranslation(["components/player", "views/events"]);
const apiHost = useApiHost();
const { data: config } = useSWR<FrigateConfig>("config");
const [imgRef, imgLoaded, onImgLoad] = useImageLoaded();
@ -319,11 +323,21 @@ export default function PreviewThumbnailPlayer({
</TooltipTrigger>
</div>
<TooltipContent className="smart-capitalize">
{review.data.metadata.potential_threat_level == 1 ? (
<>{t("suspiciousActivity", { ns: "views/events" })}</>
) : (
<>{t("threateningActivity", { ns: "views/events" })}</>
)}
{(() => {
const threatLevel =
review.data.metadata.potential_threat_level ?? 0;
switch (threatLevel) {
case ThreatLevel.NEEDS_REVIEW:
return t("needsReview", { ns: "views/events" });
case ThreatLevel.SECURITY_CONCERN:
return t("securityConcern", { ns: "views/events" });
default:
return (
THREAT_LEVEL_LABELS[threatLevel as ThreatLevel] ||
"Unknown"
);
}
})()}
</TooltipContent>
</Tooltip>
)}

View File

@ -1,3 +1,4 @@
import { baseUrl } from "@/api/baseUrl";
import { CameraConfig, FrigateConfig } from "@/types/frigateConfig";
import { useCallback, useEffect, useState, useMemo } from "react";
import useSWR from "swr";
@ -41,9 +42,12 @@ export default function useCameraLiveMode(
const metadataPromises = streamNames.map(async (streamName) => {
try {
const response = await fetch(`/api/go2rtc/streams/${streamName}`, {
priority: "low",
});
const response = await fetch(
`${baseUrl}api/go2rtc/streams/${streamName}`,
{
priority: "low",
},
);
if (response.ok) {
const data = await response.json();

View File

@ -17,6 +17,7 @@ export function useHistoryBack({
}: UseHistoryBackOptions): void {
const historyPushedRef = React.useRef(false);
const closedByBackRef = React.useRef(false);
const urlWhenOpenedRef = React.useRef<string | null>(null);
// Keep onClose in a ref to avoid effect re-runs that cause multiple history pushes
const onCloseRef = React.useRef(onClose);
@ -30,6 +31,9 @@ export function useHistoryBack({
if (open) {
// Only push history state if we haven't already (prevents duplicates in strict mode)
if (!historyPushedRef.current) {
// Store the current URL (pathname + search, without hash) before pushing history state
urlWhenOpenedRef.current =
window.location.pathname + window.location.search;
window.history.pushState({ overlayOpen: true }, "");
historyPushedRef.current = true;
}
@ -37,6 +41,7 @@ export function useHistoryBack({
const handlePopState = () => {
closedByBackRef.current = true;
historyPushedRef.current = false;
urlWhenOpenedRef.current = null;
onCloseRef.current();
};
@ -48,10 +53,22 @@ export function useHistoryBack({
} else {
// Overlay is closing - clean up history if we pushed and it wasn't via back button
if (historyPushedRef.current && !closedByBackRef.current) {
window.history.back();
const currentUrl = window.location.pathname + window.location.search;
const urlWhenOpened = urlWhenOpenedRef.current;
// If the URL has changed (e.g., filters were applied via search params),
// don't go back as it would undo the filter update.
// The history entry we pushed will remain, but that's acceptable compared
// to losing the user's filter changes.
if (!urlWhenOpened || currentUrl === urlWhenOpened) {
// URL hasn't changed, safe to go back and remove our history entry
window.history.back();
}
// If URL changed, we skip history.back() to preserve the filter updates
}
historyPushedRef.current = false;
closedByBackRef.current = false;
urlWhenOpenedRef.current = null;
}
}, [enabled, open]);
}

View File

@ -49,6 +49,7 @@ function ConfigEditor() {
const [restartDialogOpen, setRestartDialogOpen] = useState(false);
const { send: sendRestart } = useRestart();
const initialValidationRef = useRef(false);
const onHandleSaveConfig = useCallback(
async (save_option: SaveOptions): Promise<void> => {
@ -171,6 +172,33 @@ function ConfigEditor() {
};
}, [rawConfig, apiHost, systemTheme, theme, onHandleSaveConfig]);
// when in safe mode, attempt to validate the existing (invalid) config immediately
// so that the user sees the validation errors without needing to press save
useEffect(() => {
if (
config?.safe_mode &&
rawConfig &&
!initialValidationRef.current &&
!error
) {
initialValidationRef.current = true;
axios
.post(`config/save?save_option=saveonly`, rawConfig, {
headers: { "Content-Type": "text/plain" },
})
.then(() => {
// if this succeeds while in safe mode, we won't force any UI change
})
.catch((e: AxiosError<ApiErrorResponse>) => {
const errorMessage =
e.response?.data?.message ||
e.response?.data?.detail ||
"Unknown error";
setError(errorMessage);
});
}
}, [config?.safe_mode, rawConfig, error]);
// monitoring state
const [hasChanges, setHasChanges] = useState(false);

View File

@ -14,12 +14,12 @@ import { useTranslation } from "react-i18next";
import { useEffect, useMemo, useRef } from "react";
import useSWR from "swr";
import { useAllowedCameras } from "@/hooks/use-allowed-cameras";
import { useIsCustomRole } from "@/hooks/use-is-custom-role";
import { useIsAdmin } from "@/hooks/use-is-admin";
function Live() {
const { t } = useTranslation(["views/live"]);
const { data: config } = useSWR<FrigateConfig>("config");
const isCustomRole = useIsCustomRole();
const isAdmin = useIsAdmin();
// selection
@ -94,7 +94,7 @@ function Live() {
const includesBirdseye = useMemo(() => {
// Restricted users should never have access to birdseye
if (isCustomRole) {
if (!isAdmin) {
return false;
}
@ -109,7 +109,7 @@ function Live() {
} else {
return false;
}
}, [config, cameraGroup, isCustomRole]);
}, [config, cameraGroup, isAdmin]);
const cameras = useMemo(() => {
if (!config) {

View File

@ -87,6 +87,13 @@ export type ZoomLevel = {
};
export enum ThreatLevel {
SUSPICIOUS = 1,
DANGER = 2,
NORMAL = 0,
NEEDS_REVIEW = 1,
SECURITY_CONCERN = 2,
}
export const THREAT_LEVEL_LABELS: Record<ThreatLevel, string> = {
[ThreatLevel.NORMAL]: "Normal",
[ThreatLevel.NEEDS_REVIEW]: "Needs review",
[ThreatLevel.SECURITY_CONCERN]: "Security concern",
};

View File

@ -1,3 +1,4 @@
import { baseUrl } from "@/api/baseUrl";
import { generateFixedHash, isValidId } from "./stringUtil";
/**
@ -52,9 +53,12 @@ export async function detectReolinkCamera(
password,
});
const response = await fetch(`/api/reolink/detect?${params.toString()}`, {
method: "GET",
});
const response = await fetch(
`${baseUrl}api/reolink/detect?${params.toString()}`,
{
method: "GET",
},
);
if (!response.ok) {
return null;

View File

@ -54,7 +54,7 @@ import { useTranslation } from "react-i18next";
import { EmptyCard } from "@/components/card/EmptyCard";
import { BsFillCameraVideoOffFill } from "react-icons/bs";
import { AuthContext } from "@/context/auth-context";
import { useIsCustomRole } from "@/hooks/use-is-custom-role";
import { useIsAdmin } from "@/hooks/use-is-admin";
type LiveDashboardViewProps = {
cameras: CameraConfig[];
@ -661,10 +661,10 @@ export default function LiveDashboardView({
function NoCameraView() {
const { t } = useTranslation(["views/live"]);
const { auth } = useContext(AuthContext);
const isCustomRole = useIsCustomRole();
const isAdmin = useIsAdmin();
// Check if this is a restricted user with no cameras in this group
const isRestricted = isCustomRole && auth.isAuthenticated;
const isRestricted = !isAdmin && auth.isAuthenticated;
return (
<div className="flex size-full items-center justify-center">