mirror of
https://github.com/blakeblackshear/frigate.git
synced 2025-12-07 05:54:10 +03:00
Compare commits
16 Commits
3f8fba4aa9
...
115cc0a5b4
| Author | SHA1 | Date | |
|---|---|---|---|
|
|
115cc0a5b4 | ||
|
|
c149f92135 | ||
|
|
8f11a67be3 | ||
|
|
db569f4449 | ||
|
|
c2fec7d739 | ||
|
|
748f2121eb | ||
|
|
2781c1dbed | ||
|
|
36d71e0548 | ||
|
|
d7fd245162 | ||
|
|
2b96237158 | ||
|
|
9e8932201f | ||
|
|
77a8544f5a | ||
|
|
6bbef5134d | ||
|
|
d21d1a71f1 | ||
|
|
1a75251ffb | ||
|
|
048475e750 |
@ -159,7 +159,7 @@ Inference speeds vary greatly depending on the CPU or GPU used, some known examp
|
||||
| Intel HD 530 | 15 - 35 ms | | | | Can only run one detector instance |
|
||||
| Intel HD 620 | 15 - 25 ms | | 320: ~ 35 ms | | |
|
||||
| Intel HD 630 | ~ 15 ms | | 320: ~ 30 ms | | |
|
||||
| Intel UHD 730 | ~ 10 ms | | 320: ~ 19 ms 640: ~ 54 ms | | |
|
||||
| Intel UHD 730 | ~ 10 ms | t-320: 14ms s-320: 24ms t-640: 34ms s-640: 65ms | 320: ~ 19 ms 640: ~ 54 ms | | |
|
||||
| Intel UHD 770 | ~ 15 ms | t-320: ~ 16 ms s-320: ~ 20 ms s-640: ~ 40 ms | 320: ~ 20 ms 640: ~ 46 ms | | |
|
||||
| Intel N100 | ~ 15 ms | s-320: 30 ms | 320: ~ 25 ms | | Can only run one detector instance |
|
||||
| Intel N150 | ~ 15 ms | t-320: 16 ms s-320: 24 ms | | | |
|
||||
|
||||
@ -62,8 +62,8 @@ def require_admin_by_default():
|
||||
"/",
|
||||
"/version",
|
||||
"/config/schema.json",
|
||||
"/metrics",
|
||||
# Authenticated user endpoints (allow_any_authenticated)
|
||||
"/metrics",
|
||||
"/stats",
|
||||
"/stats/history",
|
||||
"/config",
|
||||
@ -76,22 +76,28 @@ def require_admin_by_default():
|
||||
"/recognized_license_plates",
|
||||
"/timeline",
|
||||
"/timeline/hourly",
|
||||
"/events/summary",
|
||||
"/recordings/storage",
|
||||
"/recordings/summary",
|
||||
"/recordings/unavailable",
|
||||
"/go2rtc/streams",
|
||||
"/event_ids",
|
||||
"/events",
|
||||
"/exports",
|
||||
}
|
||||
|
||||
# Path prefixes that should be exempt (for paths with parameters)
|
||||
EXEMPT_PREFIXES = (
|
||||
"/logs/", # /logs/{service}
|
||||
"/review", # /review, /review/{id}, /review_ids, /review/summary, etc.
|
||||
"/review", # /review, /review/{id}, /review/summary, /review_ids, etc.
|
||||
"/reviews/", # /reviews/viewed, /reviews/delete
|
||||
"/events/", # /events/{id}/thumbnail, etc. (camera-scoped)
|
||||
"/events/", # /events/{id}/thumbnail, /events/summary, etc. (camera-scoped)
|
||||
"/export/", # /export/{camera}/start/..., /export/{id}/rename, /export/{id}
|
||||
"/go2rtc/streams/", # /go2rtc/streams/{camera}
|
||||
"/users/", # /users/{username}/password (has own auth)
|
||||
"/preview/", # /preview/{file}/thumbnail.jpg
|
||||
"/exports/", # /exports/{export_id}
|
||||
"/vod/", # /vod/{camera_name}/...
|
||||
"/notifications/", # /notifications/pubkey, /notifications/register
|
||||
)
|
||||
|
||||
async def admin_checker(request: Request):
|
||||
@ -105,6 +111,24 @@ def require_admin_by_default():
|
||||
if path.startswith(EXEMPT_PREFIXES):
|
||||
return
|
||||
|
||||
# Dynamic camera path exemption:
|
||||
# Any path whose first segment matches a configured camera name should
|
||||
# bypass the global admin requirement. These endpoints enforce access
|
||||
# via route-level dependencies (e.g. require_camera_access) to ensure
|
||||
# per-camera authorization. This allows non-admin authenticated users
|
||||
# (e.g. viewer role) to access camera-specific resources without
|
||||
# needing admin privileges.
|
||||
try:
|
||||
if path.startswith("/"):
|
||||
first_segment = path.split("/", 2)[1]
|
||||
if (
|
||||
first_segment
|
||||
and first_segment in request.app.frigate_config.cameras
|
||||
):
|
||||
return
|
||||
except Exception:
|
||||
pass
|
||||
|
||||
# For all other paths, require admin role
|
||||
# Port 5000 (internal) requests have admin role set automatically
|
||||
role = request.headers.get("remote-role")
|
||||
@ -113,7 +137,7 @@ def require_admin_by_default():
|
||||
|
||||
raise HTTPException(
|
||||
status_code=403,
|
||||
detail="Admin role required for this endpoint",
|
||||
detail="Access denied. A user with the admin role is required.",
|
||||
)
|
||||
|
||||
return admin_checker
|
||||
|
||||
@ -70,6 +70,7 @@ router = APIRouter(tags=[Tags.events])
|
||||
@router.get(
|
||||
"/events",
|
||||
response_model=list[EventResponse],
|
||||
dependencies=[Depends(allow_any_authenticated())],
|
||||
summary="Get events",
|
||||
description="Returns a list of events.",
|
||||
)
|
||||
@ -344,6 +345,7 @@ def events(
|
||||
@router.get(
|
||||
"/events/explore",
|
||||
response_model=list[EventResponse],
|
||||
dependencies=[Depends(allow_any_authenticated())],
|
||||
summary="Get summary of objects.",
|
||||
description="""Gets a summary of objects from the database.
|
||||
Returns a list of objects with a max of `limit` objects for each label.
|
||||
@ -436,6 +438,7 @@ def events_explore(
|
||||
@router.get(
|
||||
"/event_ids",
|
||||
response_model=list[EventResponse],
|
||||
dependencies=[Depends(allow_any_authenticated())],
|
||||
summary="Get events by ids.",
|
||||
description="""Gets events by a list of ids.
|
||||
Returns a list of events.
|
||||
@ -469,6 +472,7 @@ async def event_ids(ids: str, request: Request):
|
||||
|
||||
@router.get(
|
||||
"/events/search",
|
||||
dependencies=[Depends(allow_any_authenticated())],
|
||||
summary="Search events.",
|
||||
description="""Searches for events in the database.
|
||||
Returns a list of events.
|
||||
@ -919,6 +923,7 @@ def events_summary(
|
||||
@router.get(
|
||||
"/events/{event_id}",
|
||||
response_model=EventResponse,
|
||||
dependencies=[Depends(allow_any_authenticated())],
|
||||
summary="Get event by id.",
|
||||
description="Gets an event by its id.",
|
||||
)
|
||||
@ -962,6 +967,7 @@ def set_retain(event_id: str):
|
||||
@router.post(
|
||||
"/events/{event_id}/plus",
|
||||
response_model=EventUploadPlusResponse,
|
||||
dependencies=[Depends(require_role(["admin"]))],
|
||||
summary="Send event to Frigate+.",
|
||||
description="""Sends an event to Frigate+.
|
||||
Returns a success message or an error if the event is not found.
|
||||
@ -1102,6 +1108,7 @@ async def send_to_plus(request: Request, event_id: str, body: SubmitPlusBody = N
|
||||
@router.put(
|
||||
"/events/{event_id}/false_positive",
|
||||
response_model=EventUploadPlusResponse,
|
||||
dependencies=[Depends(require_role(["admin"]))],
|
||||
summary="Submit false positive to Frigate+",
|
||||
description="""Submit an event as a false positive to Frigate+.
|
||||
This endpoint is the same as the standard Frigate+ submission endpoint,
|
||||
|
||||
@ -14,6 +14,7 @@ from peewee import DoesNotExist
|
||||
from playhouse.shortcuts import model_to_dict
|
||||
|
||||
from frigate.api.auth import (
|
||||
allow_any_authenticated,
|
||||
get_allowed_cameras_for_filter,
|
||||
require_camera_access,
|
||||
require_role,
|
||||
@ -44,6 +45,7 @@ router = APIRouter(tags=[Tags.export])
|
||||
@router.get(
|
||||
"/exports",
|
||||
response_model=ExportsResponse,
|
||||
dependencies=[Depends(allow_any_authenticated())],
|
||||
summary="Get exports",
|
||||
description="""Gets all exports from the database for cameras the user has access to.
|
||||
Returns a list of exports ordered by date (most recent first).""",
|
||||
@ -272,6 +274,7 @@ async def export_delete(event_id: str, request: Request):
|
||||
@router.get(
|
||||
"/exports/{export_id}",
|
||||
response_model=ExportModel,
|
||||
dependencies=[Depends(allow_any_authenticated())],
|
||||
summary="Get a single export",
|
||||
description="""Gets a specific export by ID. The user must have access to the camera
|
||||
associated with the export.""",
|
||||
|
||||
@ -945,6 +945,7 @@ async def vod_hour(
|
||||
|
||||
@router.get(
|
||||
"/vod/event/{event_id}",
|
||||
dependencies=[Depends(allow_any_authenticated())],
|
||||
description="Returns an HLS playlist for the specified object. Append /master.m3u8 or /index.m3u8 for HLS playback.",
|
||||
)
|
||||
async def vod_event(
|
||||
|
||||
@ -5,11 +5,12 @@ import os
|
||||
from typing import Any
|
||||
|
||||
from cryptography.hazmat.primitives import serialization
|
||||
from fastapi import APIRouter, Request
|
||||
from fastapi import APIRouter, Depends, Request
|
||||
from fastapi.responses import JSONResponse
|
||||
from peewee import DoesNotExist
|
||||
from py_vapid import Vapid01, utils
|
||||
|
||||
from frigate.api.auth import allow_any_authenticated
|
||||
from frigate.api.defs.tags import Tags
|
||||
from frigate.const import CONFIG_DIR
|
||||
from frigate.models import User
|
||||
@ -21,6 +22,7 @@ router = APIRouter(tags=[Tags.notifications])
|
||||
|
||||
@router.get(
|
||||
"/notifications/pubkey",
|
||||
dependencies=[Depends(allow_any_authenticated())],
|
||||
summary="Get VAPID public key",
|
||||
description="""Gets the VAPID public key for the notifications.
|
||||
Returns the public key or an error if notifications are not enabled.
|
||||
@ -47,6 +49,7 @@ def get_vapid_pub_key(request: Request):
|
||||
|
||||
@router.post(
|
||||
"/notifications/register",
|
||||
dependencies=[Depends(allow_any_authenticated())],
|
||||
summary="Register notifications",
|
||||
description="""Registers a notifications subscription.
|
||||
Returns a success message or an error if the subscription is not provided.
|
||||
|
||||
@ -577,7 +577,9 @@ def delete_reviews(body: ReviewModifyMultipleBody):
|
||||
|
||||
|
||||
@router.get(
|
||||
"/review/activity/motion", response_model=list[ReviewActivityMotionResponse]
|
||||
"/review/activity/motion",
|
||||
response_model=list[ReviewActivityMotionResponse],
|
||||
dependencies=[Depends(allow_any_authenticated())],
|
||||
)
|
||||
def motion_activity(
|
||||
params: ReviewActivityMotionQueryParams = Depends(),
|
||||
@ -739,6 +741,7 @@ async def set_not_reviewed(
|
||||
|
||||
@router.post(
|
||||
"/review/summarize/start/{start_ts}/end/{end_ts}",
|
||||
dependencies=[Depends(allow_any_authenticated())],
|
||||
description="Use GenAI to summarize review items over a period of time.",
|
||||
)
|
||||
def generate_review_summary(request: Request, start_ts: float, end_ts: float):
|
||||
|
||||
@ -468,5 +468,36 @@
|
||||
"tearing": "Reißen",
|
||||
"beep": "Piep",
|
||||
"ping": "Ping",
|
||||
"ding": "klingeln"
|
||||
"ding": "klingeln",
|
||||
"thunk": "dumpfes Geräusch",
|
||||
"clang": "Geklirr",
|
||||
"squeal": "Ausruf",
|
||||
"creak": "Knarren",
|
||||
"rustle": "Geknister",
|
||||
"whir": "schwirren",
|
||||
"clatter": "Geratter",
|
||||
"sizzle": "brutzeln",
|
||||
"clicking": "Klicken",
|
||||
"clickety_clack": "Klappergeräuschen",
|
||||
"rumble": "Grollen",
|
||||
"plop": "plumpsen",
|
||||
"hum": "brummen",
|
||||
"zing": "Schwung",
|
||||
"boing": "ferderndes Geräusch",
|
||||
"crunch": "knirschendes",
|
||||
"sine_wave": "Sinus Kurve",
|
||||
"harmonic": "harmonisch",
|
||||
"chirp_tone": "Frequenzwobbelung",
|
||||
"pulse": "Takt",
|
||||
"inside": "drinnen",
|
||||
"outside": "draußen",
|
||||
"reverberation": "Widerhall",
|
||||
"echo": "Echo",
|
||||
"noise": "Lärm",
|
||||
"mains_hum": "Netzbrummen",
|
||||
"distortion": "Verzerrung",
|
||||
"sidetone": "Nebengeräusch",
|
||||
"cacophony": "Dissonanz",
|
||||
"throbbing": "Pochen",
|
||||
"vibration": "Vibration"
|
||||
}
|
||||
|
||||
@ -231,7 +231,8 @@
|
||||
"logout": "Abmelden"
|
||||
},
|
||||
"uiPlayground": "Testgebiet für Benutzeroberfläche",
|
||||
"export": "Exportieren"
|
||||
"export": "Exportieren",
|
||||
"classification": "Klassifizierung"
|
||||
},
|
||||
"unit": {
|
||||
"speed": {
|
||||
|
||||
@ -173,7 +173,12 @@
|
||||
"generationFailed": "Generierung fehlgeschlagen. Bitte versuchen Sie es erneut.",
|
||||
"classifyFailed": "Bilder konnten nicht klassifiziert werden: {{error}}"
|
||||
},
|
||||
"generateSuccess": "Erfolgreich generierte Beispielbilder"
|
||||
"generateSuccess": "Erfolgreich generierte Beispielbilder",
|
||||
"modelCreated": "Modell erfolgreich erstellt. Verwenden Sie die Ansicht „Aktuelle Klassifizierungen“, um Bilder für fehlende Zustände hinzuzufügen, und trainieren Sie dann das Modell.",
|
||||
"missingStatesWarning": {
|
||||
"title": "Beispiele für fehlende Zustände",
|
||||
"description": "Sie haben nicht für alle Zustände Beispiele ausgewählt. Das Modell wird erst trainiert, wenn für alle Zustände Bilder vorhanden sind. Fahren Sie fort und verwenden Sie die Ansicht „Aktuelle Klassifizierungen“, um Bilder für die fehlenden Zustände zu klassifizieren. Trainieren Sie anschließend das Modell."
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
@ -55,5 +55,8 @@
|
||||
"objectTrack": {
|
||||
"trackedPoint": "Verfolgter Punkt",
|
||||
"clickToSeek": "Klicke, um zu dieser Zeit zu springen"
|
||||
}
|
||||
},
|
||||
"normalActivity": "normal",
|
||||
"needsReview": "benötigt Überprüfung",
|
||||
"securityConcern": "Sicherheitsbedenken"
|
||||
}
|
||||
|
||||
@ -172,7 +172,7 @@
|
||||
"disable": "Live Audio Transkription ausschalten"
|
||||
},
|
||||
"noCameras": {
|
||||
"title": "Keine Kameras eingerichtet",
|
||||
"title": "Keine Kameras konfiguriert",
|
||||
"description": "Beginne indem du eine Kamera anschließt.",
|
||||
"buttonText": "Kamera hinzufügen",
|
||||
"restricted": {
|
||||
|
||||
@ -240,7 +240,8 @@
|
||||
"alreadyExists": "Für diese Kamera existiert bereits eine Zone mit diesem Namen.",
|
||||
"mustBeAtLeastTwoCharacters": "Der Zonenname muss aus mindestens 2 Zeichen bestehen.",
|
||||
"mustNotBeSameWithCamera": "Der Zonenname darf nicht mit dem Kameranamen identisch sein.",
|
||||
"mustNotContainPeriod": "Der Zonenname darf keine Punkte enthalten."
|
||||
"mustNotContainPeriod": "Der Zonenname darf keine Punkte enthalten.",
|
||||
"mustHaveAtLeastOneLetter": "Der Name der Zone muss mindestens einen Buchstaben enthalten."
|
||||
}
|
||||
},
|
||||
"loiteringTime": {
|
||||
@ -319,7 +320,7 @@
|
||||
"name": {
|
||||
"title": "Name",
|
||||
"inputPlaceHolder": "Geben Sie einen Namen ein…",
|
||||
"tips": "Der Name muss aus mindestens 2 Zeichen bestehen und sollte nicht den Namen einer Kamera oder anderen Zone entsprechen."
|
||||
"tips": "Der Name muss mindestens 2 Zeichen lang sein, mindestens einen Buchstaben enthalten und darf nicht der Name einer Kamera oder einer anderen Zone sein."
|
||||
},
|
||||
"objects": {
|
||||
"title": "Objekte",
|
||||
@ -749,7 +750,7 @@
|
||||
"triggers": {
|
||||
"documentTitle": "Auslöser",
|
||||
"management": {
|
||||
"title": "Auslöser Verwaltung",
|
||||
"title": "Auslöser",
|
||||
"desc": "Auslöser für {{camera}} verwalten. Verwenden Sie den Vorschaubild Typ, um ähnliche Vorschaubilder wie das ausgewählte verfolgte Objekt auszulösen, und den Beschreibungstyp, um ähnliche Beschreibungen wie den von Ihnen angegebenen Text auszulösen."
|
||||
},
|
||||
"addTrigger": "Auslöser hinzufügen",
|
||||
@ -770,7 +771,9 @@
|
||||
},
|
||||
"actions": {
|
||||
"alert": "Als Alarm markieren",
|
||||
"notification": "Benachrichtigung senden"
|
||||
"notification": "Benachrichtigung senden",
|
||||
"sub_label": "Unterlabel hinzufügen",
|
||||
"attribute": "Attribut hinzufügen"
|
||||
},
|
||||
"dialog": {
|
||||
"createTrigger": {
|
||||
@ -788,25 +791,28 @@
|
||||
"form": {
|
||||
"name": {
|
||||
"title": "Name",
|
||||
"placeholder": "Auslöser Name eingeben",
|
||||
"placeholder": "Benennen Sie diesen Auslöser",
|
||||
"error": {
|
||||
"minLength": "Der Name muss mindestens 2 Zeichen lang sein.",
|
||||
"invalidCharacters": "Der Name darf nur Buchstaben, Zahlen, Unterstriche und Bindestriche enthalten.",
|
||||
"alreadyExists": "Ein Auslöser mit diesem Namen existiert bereits für diese Kamera."
|
||||
}
|
||||
},
|
||||
"description": "Geben Sie einen eindeutigen Namen oder eine Beschreibung ein, um diesen Auslöser zu identifizieren"
|
||||
},
|
||||
"enabled": {
|
||||
"description": "Diesen Auslöser aktivieren oder deaktivieren"
|
||||
},
|
||||
"type": {
|
||||
"title": "Typ",
|
||||
"placeholder": "Auslöser Typ wählen"
|
||||
"placeholder": "Auslöser Typ wählen",
|
||||
"description": "Auslösen, wenn eine ähnliche Beschreibung eines verfolgten Objekts erkannt wird",
|
||||
"thumbnail": "Auslösen, wenn eine ähnliche Miniaturansicht eines verfolgten Objekts erkannt wird"
|
||||
},
|
||||
"content": {
|
||||
"title": "Inhalt",
|
||||
"imagePlaceholder": "Ein Bild auswählen",
|
||||
"imagePlaceholder": "Miniaturansicht auswählen",
|
||||
"textPlaceholder": "Inhaltstext eingeben",
|
||||
"imageDesc": "Ein Bild auswählen, um diese Aktion auszulösen, wenn ein ähnliches Bild erkannt wird.",
|
||||
"imageDesc": "Es werden nur die letzten 100 Miniaturansichten angezeigt. Wenn Sie die gewünschte Miniaturansicht nicht finden können, überprüfen Sie bitte frühere Objekte in „Explore“ und richten Sie dort über das Menü einen Trigger ein.",
|
||||
"textDesc": "Einen Text eingeben, um diese Aktion auszulösen, wenn eine ähnliche Beschreibung eines verfolgten Objekts erkannt wird.",
|
||||
"error": {
|
||||
"required": "Inhalt ist erforderlich."
|
||||
@ -817,11 +823,12 @@
|
||||
"error": {
|
||||
"min": "Schwellenwert muss mindestens 0 sein",
|
||||
"max": "Schwellenwert darf höchstens 1 sein"
|
||||
}
|
||||
},
|
||||
"desc": "Legen Sie den Ähnlichkeitsschwellenwert für diesen Trigger fest. Ein höherer Schwellenwert bedeutet, dass eine größere Übereinstimmung erforderlich ist, um den Trigger auszulösen."
|
||||
},
|
||||
"actions": {
|
||||
"title": "Aktionen",
|
||||
"desc": "Standardmäßig sendet Frigate eine MQTT-Nachricht für alle Trigger. Wähle eine zusätzliche Aktion aus, die ausgeführt werden soll, wenn dieser Trigger ausgelöst wird.",
|
||||
"desc": "Standardmäßig sendet Frigate für alle Trigger eine MQTT-Nachricht. Unterbezeichnungen fügen den Triggernamen zur Objektbezeichnung hinzu. Attribute sind durchsuchbare Metadaten, die separat in den Metadaten des verfolgten Objekts gespeichert werden.",
|
||||
"error": {
|
||||
"min": "Mindesten eine Aktion muss ausgewählt sein."
|
||||
}
|
||||
@ -848,6 +855,23 @@
|
||||
"semanticSearch": {
|
||||
"title": "Semantische Suche ist deaktiviert",
|
||||
"desc": "Semantische Suche muss aktiviert sein um Auslöser nutzen zu können."
|
||||
},
|
||||
"wizard": {
|
||||
"title": "Auslöser erstellen",
|
||||
"step1": {
|
||||
"description": "Konfigurieren Sie die Grundeinstellungen für Ihren Auslöser."
|
||||
},
|
||||
"step2": {
|
||||
"description": "Legen Sie den Inhalt fest, der diese Aktion auslöst."
|
||||
},
|
||||
"step3": {
|
||||
"description": "Konfigurieren Sie den Schwellenwert und die Aktionen für diesen Trigger."
|
||||
},
|
||||
"steps": {
|
||||
"nameAndType": "Name und Typ",
|
||||
"configureData": "Daten konfigurieren",
|
||||
"thresholdAndActions": "Schwellenwert und Maßnahmen"
|
||||
}
|
||||
}
|
||||
},
|
||||
"roles": {
|
||||
@ -901,7 +925,7 @@
|
||||
"updateCameras": "Kameras für Rolle {{role}} aktualisiert",
|
||||
"deleteRole": "Rolle {{role}} erfolgreich gelöscht",
|
||||
"userRolesUpdated_one": "{{count}} Benutzer, denen diese Rolle zugewiesen wurde, wurden auf „Zuschauer“ aktualisiert, der Zugriff auf alle Kameras hat.",
|
||||
"userRolesUpdated_other": ""
|
||||
"userRolesUpdated_other": "{{count}} Benutzer, denen diese Rollen zugewiesen wurde, wurden auf „Zuschauer“ aktualisiert, der Zugriff auf alle Kameras habem."
|
||||
},
|
||||
"error": {
|
||||
"createRoleFailed": "Fehler beim Erstellen der Rolle: {{errorMessage}}",
|
||||
@ -979,7 +1003,8 @@
|
||||
"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."
|
||||
"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.",
|
||||
"manualMode": "Manuelle Auswahl"
|
||||
},
|
||||
"step2": {
|
||||
"description": "Suchen Sie in der Kamera nach verfügbaren Streams oder konfigurieren Sie manuelle Einstellungen basierend auf der von Ihnen ausgewählten Erkennungsmethode.",
|
||||
@ -1038,10 +1063,22 @@
|
||||
"profiles": "Profile",
|
||||
"ptzSupport": "PTZ Unterstützung",
|
||||
"autotrackingSupport": "Unterstützung für Autoverfolgung",
|
||||
"presets": "Voreinstellung"
|
||||
"presets": "Voreinstellung",
|
||||
"rtspCandidates": "RTSP Kandidaten",
|
||||
"rtspCandidatesDescription": "Die folgenden RTSP-URLs wurden bei der Kameraprobe gefunden. Testen Sie die Verbindung, um die Stream-Metadaten anzuzeigen.",
|
||||
"noRtspCandidates": "Es wurden keine RTSP-URLs von der Kamera gefunden. Möglicherweise sind Ihre Anmeldedaten falsch oder die Kamera unterstützt ONVIF oder die Methode zum Abrufen von RTSP-URLs nicht. Gehen Sie zurück und geben Sie die RTSP-URL manuell ein.",
|
||||
"candidateStreamTitle": "Kandidate {{number}}",
|
||||
"useCandidate": "Verwenden",
|
||||
"uriCopy": "Kopieren",
|
||||
"uriCopied": "ULR in Zwischenablage kopiert",
|
||||
"testConnection": "Test Verbindung",
|
||||
"toggleUriView": "Klicken Sie hier, um die vollständige URI zu sehen",
|
||||
"errors": {
|
||||
"hostRequired": "Host/IP adresse wird benötigt"
|
||||
}
|
||||
},
|
||||
"step3": {
|
||||
"description": "Endgültige Validierung und Analyse vor dem Speichern Ihrer neuen Kamera. Verbinde jeden Stream vor dem Speichern.",
|
||||
"description": "Konfigurieren Sie Stream-Rollen und fügen Sie zusätzliche Streams für Ihre Kamera hinzu",
|
||||
"validationTitle": "Stream Validierung",
|
||||
"connectAllStreams": "Verbinde alle Streams",
|
||||
"reconnectionSuccess": "Wiederverbindung erfolgreich.",
|
||||
@ -1078,6 +1115,90 @@
|
||||
"hikvision": {
|
||||
"substreamWarning": "Substream 1 ist auf eine niedrige Auflösung festgelegt. Viele Hikvision-Kameras unterstützen zusätzliche Substreams, die in den Kameraeinstellungen aktiviert werden müssen. Es wird empfohlen, diese Streams zu nutzen, sofern sie verfügbar sind."
|
||||
}
|
||||
},
|
||||
"streamsTitle": "Kamera Stream",
|
||||
"addStream": "Hizufügen Stream",
|
||||
"addAnotherStream": "weiteren Stream hinzufügen",
|
||||
"streamUrl": "Stream URL",
|
||||
"streamUrlPlaceholder": "rtsp://benutzername:passwort@host:port/path",
|
||||
"selectStream": "Auswahl Stream",
|
||||
"searchCandidates": "Suche Kandidaten...",
|
||||
"noStreamFound": "Kein Stream gefunden",
|
||||
"url": "URL",
|
||||
"resolution": "Auflösung",
|
||||
"selectResolution": "Wähle Auflösung",
|
||||
"quality": "Qualität",
|
||||
"selectQuality": "Wähle Qualität",
|
||||
"roleLabels": {
|
||||
"detect": "Objekt Erkennung",
|
||||
"record": "Aufnahme",
|
||||
"audio": "Ton"
|
||||
},
|
||||
"testStream": "Verbindungstest",
|
||||
"testSuccess": "Verbindungstest erfolgreich!",
|
||||
"testFailed": "Verbindungstest fehlgeschlagen",
|
||||
"testFailedTitle": "Test fehlgeschlagen",
|
||||
"connected": "Verbunden",
|
||||
"notConnected": "nicht Verbunden",
|
||||
"featuresTitle": "Funktionen",
|
||||
"go2rtc": "Verbindungen zur Kamera reduzieren",
|
||||
"detectRoleWarning": "Mindestens ein Stream muss die Rolle „detect“ haben, um fortfahren zu können.",
|
||||
"rolesPopover": {
|
||||
"title": "Stream Rollen",
|
||||
"detect": "Hauptfeed für die Objekterkennung.",
|
||||
"record": "Speichert Segmente des Video-Feeds basierend auf den Konfigurationseinstellungen.",
|
||||
"audio": "Feed für audiobasierte Erkennung."
|
||||
},
|
||||
"featuresPopover": {
|
||||
"title": "Stream Funktionen",
|
||||
"description": "Verwenden Sie go2rtc-Restreaming, um die Verbindungen zu Ihrer Kamera zu reduzieren."
|
||||
}
|
||||
},
|
||||
"step4": {
|
||||
"description": "Endgültige Validierung und Analyse vor dem Speichern Ihrer neuen Kamera. Verbinden Sie jeden Stream vor dem Speichern.",
|
||||
"validationTitle": "Stream-Validierung",
|
||||
"connectAllStreams": "Alle Streams verbinden",
|
||||
"reconnectionSuccess": "Wiederverbindung erfolgreich.",
|
||||
"reconnectionPartial": "Einige Streams konnten nicht wieder verbunden werden.",
|
||||
"streamUnavailable": "Stream Vorschau nicht verfügbar",
|
||||
"reload": "neu Laden",
|
||||
"connecting": "Verbinden...",
|
||||
"streamTitle": "Stream {{number}}",
|
||||
"valid": "gültig",
|
||||
"failed": "fehlgeschlagen",
|
||||
"notTested": "nicht getestet",
|
||||
"connectStream": "Verbinden",
|
||||
"connectingStream": "Verbinden",
|
||||
"disconnectStream": "getrennt",
|
||||
"estimatedBandwidth": "Voraussichtliche Bandbreite",
|
||||
"roles": "Rollen",
|
||||
"ffmpegModule": "Stream-Kompatibilitätsmodus verwenden",
|
||||
"ffmpegModuleDescription": "Wenn der Stream nach mehreren Versuchen nicht geladen wird, versuchen Sie, diese Option zu aktivieren. Wenn diese Option aktiviert ist, verwendet Frigate das ffmpeg-Modul mit go2rtc. Dies kann zu einer besseren Kompatibilität mit einigen Kamerastreams führen.",
|
||||
"none": "keiner",
|
||||
"error": "Fehler",
|
||||
"streamValidated": "Steeam {{number}} Erfolgreich validiert",
|
||||
"streamValidationFailed": "Stream {{number}} Validierung fehlgeschlagen",
|
||||
"saveAndApply": "Neue Kamera speichern",
|
||||
"saveError": "Ungültige Konfiguration. Bitte überprüfen Sie Ihre Einstellungen.",
|
||||
"issues": {
|
||||
"title": "Stream-Validierung",
|
||||
"videoCodecGood": "Video codec ist {{codec}}.",
|
||||
"audioCodecGood": "Audio codec ist {{codec}}.",
|
||||
"resolutionHigh": "Eine Auflösung von {{resolution}} kann zu einem erhöhten Ressourcenverbrauch führen.",
|
||||
"resolutionLow": "Eine Auflösung von {{resolution}} ist möglicherweise zu gering, um kleine Objekte zuverlässig zu erkennen.",
|
||||
"noAudioWarning": "Für diesen Stream wurde kein Ton erkannt, die Aufzeichnungen enthalten keinen Ton.",
|
||||
"audioCodecRecordError": "Der AAC-Audio-Codec ist erforderlich, um Audio in Aufnahmen zu unterstützen.",
|
||||
"audioCodecRequired": "Ein Audiostream ist erforderlich, um die Audioerkennung zu unterstützen.",
|
||||
"restreamingWarning": "Die Reduzierung der Verbindungen zur Kamera für den Aufzeichnungsstream kann zu einer geringfügigen Erhöhung der CPU-Auslastung führen.",
|
||||
"brands": {
|
||||
"reolink-rtsp": "Reolink RTSP wird nicht empfohlen. Aktivieren Sie HTTP in den Firmware-Einstellungen der Kamera und starten Sie den Assistenten neu."
|
||||
},
|
||||
"dahua": {
|
||||
"substreamWarning": "Substream 1 ist auf eine niedrige Auflösung festgelegt. Viele Kameras von Dahua / Amcrest / EmpireTech unterstützen zusätzliche Substreams, die in den Kameraeinstellungen aktiviert werden müssen. Es wird empfohlen, diese Streams zu überprüfen und zu nutzen, sofern sie verfügbar sind."
|
||||
},
|
||||
"hikvision": {
|
||||
"substreamWarning": "Substream 1 ist auf eine niedrige Auflösung festgelegt. Viele Hikvision-Kameras unterstützen zusätzliche Substreams, die in den Kameraeinstellungen aktiviert werden müssen. Es wird empfohlen, diese Streams zu überprüfen und zu nutzen, sofern sie verfügbar sind."
|
||||
}
|
||||
}
|
||||
}
|
||||
},
|
||||
|
||||
@ -1299,7 +1299,8 @@ function ObjectDetailsTab({
|
||||
</div>
|
||||
</div>
|
||||
|
||||
{search.data.type === "object" &&
|
||||
{isAdmin &&
|
||||
search.data.type === "object" &&
|
||||
config?.plus?.enabled &&
|
||||
search.end_time != undefined &&
|
||||
search.has_snapshot && (
|
||||
|
||||
@ -38,6 +38,7 @@ import { isDesktop, isIOS, isMobileOnly, isSafari } from "react-device-detect";
|
||||
import { useApiHost } from "@/api";
|
||||
import ImageLoadingIndicator from "@/components/indicators/ImageLoadingIndicator";
|
||||
import ObjectTrackOverlay from "../ObjectTrackOverlay";
|
||||
import { useIsAdmin } from "@/hooks/use-is-admin";
|
||||
|
||||
type TrackingDetailsProps = {
|
||||
className?: string;
|
||||
@ -777,6 +778,7 @@ function LifecycleIconRow({
|
||||
const { data: config } = useSWR<FrigateConfig>("config");
|
||||
const [isOpen, setIsOpen] = useState(false);
|
||||
const navigate = useNavigate();
|
||||
const isAdmin = useIsAdmin();
|
||||
|
||||
const aspectRatio = useMemo(() => {
|
||||
if (!config) {
|
||||
@ -993,7 +995,7 @@ function LifecycleIconRow({
|
||||
<div className="ml-3 flex-shrink-0 px-1 text-right text-xs text-primary-variant">
|
||||
<div className="flex flex-row items-center gap-3">
|
||||
<div className="whitespace-nowrap">{formattedEventTimestamp}</div>
|
||||
{(config?.plus?.enabled || item.data.box) && (
|
||||
{((isAdmin && config?.plus?.enabled) || item.data.box) && (
|
||||
<DropdownMenu open={isOpen} onOpenChange={setIsOpen}>
|
||||
<DropdownMenuTrigger>
|
||||
<div className="rounded p-1 pr-2" role="button">
|
||||
@ -1002,7 +1004,7 @@ function LifecycleIconRow({
|
||||
</DropdownMenuTrigger>
|
||||
<DropdownMenuPortal>
|
||||
<DropdownMenuContent>
|
||||
{config?.plus?.enabled && (
|
||||
{isAdmin && config?.plus?.enabled && (
|
||||
<DropdownMenuItem
|
||||
className="cursor-pointer"
|
||||
onSelect={async () => {
|
||||
|
||||
@ -20,6 +20,7 @@ import ImageLoadingIndicator from "@/components/indicators/ImageLoadingIndicator
|
||||
import { baseUrl } from "@/api/baseUrl";
|
||||
import { getTranslatedLabel } from "@/utils/i18n";
|
||||
import useImageLoaded from "@/hooks/use-image-loaded";
|
||||
import { useIsAdmin } from "@/hooks/use-is-admin";
|
||||
|
||||
export type FrigatePlusDialogProps = {
|
||||
upload?: Event;
|
||||
@ -57,7 +58,9 @@ export function FrigatePlusDialog({
|
||||
);
|
||||
|
||||
const [imgRef, imgLoaded, onImgLoad] = useImageLoaded();
|
||||
const isAdmin = useIsAdmin();
|
||||
const showCard =
|
||||
isAdmin &&
|
||||
!!upload &&
|
||||
upload.data.type === "object" &&
|
||||
upload.plus_id !== "not_enabled" &&
|
||||
|
||||
@ -20,6 +20,7 @@ import { cn } from "@/lib/utils";
|
||||
import { ASPECT_VERTICAL_LAYOUT, RecordingPlayerError } from "@/types/record";
|
||||
import { useTranslation } from "react-i18next";
|
||||
import ObjectTrackOverlay from "@/components/overlay/ObjectTrackOverlay";
|
||||
import { useIsAdmin } from "@/hooks/use-is-admin";
|
||||
|
||||
// Android native hls does not seek correctly
|
||||
const USE_NATIVE_HLS = false;
|
||||
@ -83,6 +84,7 @@ export default function HlsVideoPlayer({
|
||||
}: HlsVideoPlayerProps) {
|
||||
const { t } = useTranslation("components/player");
|
||||
const { data: config } = useSWR<FrigateConfig>("config");
|
||||
const isAdmin = useIsAdmin();
|
||||
|
||||
// for detail stream context in History
|
||||
const currentTime = currentTimeOverride;
|
||||
@ -285,7 +287,7 @@ export default function HlsVideoPlayer({
|
||||
volume: true,
|
||||
seek: true,
|
||||
playbackRate: true,
|
||||
plusUpload: config?.plus?.enabled == true,
|
||||
plusUpload: isAdmin && config?.plus?.enabled == true,
|
||||
fullscreen: supportsFullscreen,
|
||||
}}
|
||||
setControlsOpen={setControlsOpen}
|
||||
|
||||
@ -13,6 +13,7 @@ import { useTranslation } from "react-i18next";
|
||||
import { Event } from "@/types/event";
|
||||
import { FrigateConfig } from "@/types/frigateConfig";
|
||||
import { useState } from "react";
|
||||
import { useIsAdmin } from "@/hooks/use-is-admin";
|
||||
|
||||
type EventMenuProps = {
|
||||
event: Event;
|
||||
@ -35,6 +36,7 @@ export default function EventMenu({
|
||||
const navigate = useNavigate();
|
||||
const { t } = useTranslation("views/explore");
|
||||
const [isOpen, setIsOpen] = useState(false);
|
||||
const isAdmin = useIsAdmin();
|
||||
|
||||
const handleObjectSelect = () => {
|
||||
if (isSelected) {
|
||||
@ -85,7 +87,8 @@ export default function EventMenu({
|
||||
</a>
|
||||
</DropdownMenuItem>
|
||||
|
||||
{event.has_snapshot &&
|
||||
{isAdmin &&
|
||||
event.has_snapshot &&
|
||||
event.plus_id == undefined &&
|
||||
event.data.type == "object" &&
|
||||
config?.plus?.enabled && (
|
||||
|
||||
Loading…
Reference in New Issue
Block a user