Compare commits

..

5 Commits

Author SHA1 Message Date
GuoQing Liu
83158484c1
Merge d007bd0a6f into ad9092d0da 2026-04-22 23:23:26 +08:00
Josh Hawkins
ad9092d0da
Tweaks (#22965)
* use ffmpeg to probe rtsp urls instead of cv2

cv2 is faster (no subprocess launch) and will continue to be used for recording segments

* tweak faq

* change unsaved color to orange

avoids confusion with validation errors (red)

* don't use any variant of orange as a profile color

avoids confusion with unsaved changes

* more unsaved color tweaks
2026-04-22 09:19:30 -06:00
Nicolas Mowen
20705a3e97
Update oneVPL (#22966) 2026-04-22 08:50:37 -06:00
Josh Hawkins
f4ac063b37
Add camera wizard improvements (#22963)
* warn in camera wizard when detect stream resolution cannot be determined

* add timeout and tcp fallback for rtsp urls only
2026-04-22 08:15:17 -05:00
Abhilash Kishore
2dcaeb6809
fix: bump OpenVINO to 2025.4.x to resolve LXC container detector crash (#22859)
* fix: bump OpenVINO to 2025.4.x to resolve LXC container crash

* fix: replace openvino + onnxruntime with onnxruntime-openvino 1.24.*

onnxruntime-openvino 1.24.* bundles OpenVINO 2025.4.1, which fixes a
crash in constrained CPU environments (e.g. Proxmox LXC) where
lin_system_conf.cpp calls stoi("") on empty strings read from offline
CPU sysfs entries.

Consolidating to onnxruntime-openvino also ensures the OpenVINO runtime
and ONNX Runtime OpenVINO EP are always compatible versions.

* revert: restore onnxruntime, keep openvino bump

Reverting onnxruntime-openvino consolidation - onnxruntime is used with
multiple execution providers (CUDA, TensorRT, MIGraphX, CPU) and cannot
be replaced wholesale with the openvino-specific wheel.
2026-04-22 07:12:14 -06:00
17 changed files with 106 additions and 74 deletions

View File

@ -87,43 +87,43 @@ if [[ "${TARGETARCH}" == "amd64" ]]; then
# intel packages use zst compression so we need to update dpkg # intel packages use zst compression so we need to update dpkg
apt-get install -y dpkg apt-get install -y dpkg
# use intel apt intel packages # use intel apt repo for libmfx1 (legacy QSV, pre-Gen12)
wget -qO - https://repositories.intel.com/gpu/intel-graphics.key | gpg --yes --dearmor --output /usr/share/keyrings/intel-graphics.gpg wget -qO - https://repositories.intel.com/gpu/intel-graphics.key | gpg --yes --dearmor --output /usr/share/keyrings/intel-graphics.gpg
echo "deb [arch=amd64 signed-by=/usr/share/keyrings/intel-graphics.gpg] https://repositories.intel.com/gpu/ubuntu jammy client" | tee /etc/apt/sources.list.d/intel-gpu-jammy.list echo "deb [arch=amd64 signed-by=/usr/share/keyrings/intel-graphics.gpg] https://repositories.intel.com/gpu/ubuntu jammy client" | tee /etc/apt/sources.list.d/intel-gpu-jammy.list
apt-get -qq update apt-get -qq update
# intel-media-va-driver-non-free is built from source in the # intel-media-va-driver-non-free is built from source in the
# intel-media-driver Dockerfile stage for Battlemage (Xe2) support # intel-media-driver Dockerfile stage for Battlemage (Xe2) support
apt-get -qq install --no-install-recommends --no-install-suggests -y \ apt-get -qq install --no-install-recommends --no-install-suggests -y \
libmfx1 libmfxgen1 libvpl2 libmfx1
rm -f /usr/share/keyrings/intel-graphics.gpg
rm -f /etc/apt/sources.list.d/intel-gpu-jammy.list
# upgrade libva2, oneVPL runtime, and libvpl2 from trixie for Battlemage support
echo "deb http://deb.debian.org/debian trixie main" > /etc/apt/sources.list.d/trixie.list
apt-get -qq update
apt-get -qq install -y -t trixie libva2 libva-drm2 libzstd1
apt-get -qq install -y -t trixie libmfx-gen1.2 libvpl2
rm -f /etc/apt/sources.list.d/trixie.list
apt-get -qq update
apt-get -qq install -y ocl-icd-libopencl1 apt-get -qq install -y ocl-icd-libopencl1
# install libtbb12 for NPU support # install libtbb12 for NPU support
apt-get -qq install -y libtbb12 apt-get -qq install -y libtbb12
rm -f /usr/share/keyrings/intel-graphics.gpg # install legacy and standard intel compute packages
rm -f /etc/apt/sources.list.d/intel-gpu-jammy.list
# install legacy and standard intel icd and level-zero-gpu
# see https://github.com/intel/compute-runtime/blob/master/LEGACY_PLATFORMS.md for more info # see https://github.com/intel/compute-runtime/blob/master/LEGACY_PLATFORMS.md for more info
# newer intel packages (gmmlib 22.9+, igc 2.32+) require libstdc++ >= 13.1 and libzstd >= 1.5.5
echo "deb http://deb.debian.org/debian trixie main" > /etc/apt/sources.list.d/trixie.list
apt-get -qq update
apt-get -qq install -y -t trixie libstdc++6 libzstd1
rm -f /etc/apt/sources.list.d/trixie.list
apt-get -qq update
# needed core package # needed core package
wget https://github.com/intel/compute-runtime/releases/download/26.14.37833.4/libigdgmm12_22.9.0_amd64.deb wget https://github.com/intel/compute-runtime/releases/download/26.14.37833.4/libigdgmm12_22.9.0_amd64.deb
dpkg -i libigdgmm12_22.9.0_amd64.deb dpkg -i libigdgmm12_22.9.0_amd64.deb
rm libigdgmm12_22.9.0_amd64.deb rm libigdgmm12_22.9.0_amd64.deb
# legacy packages # legacy compute-runtime packages
wget https://github.com/intel/compute-runtime/releases/download/24.35.30872.36/intel-opencl-icd-legacy1_24.35.30872.36_amd64.deb wget https://github.com/intel/compute-runtime/releases/download/24.35.30872.36/intel-opencl-icd-legacy1_24.35.30872.36_amd64.deb
wget https://github.com/intel/compute-runtime/releases/download/24.35.30872.36/intel-level-zero-gpu-legacy1_1.5.30872.36_amd64.deb wget https://github.com/intel/compute-runtime/releases/download/24.35.30872.36/intel-level-zero-gpu-legacy1_1.5.30872.36_amd64.deb
wget https://github.com/intel/intel-graphics-compiler/releases/download/igc-1.0.17537.24/intel-igc-opencl_1.0.17537.24_amd64.deb wget https://github.com/intel/intel-graphics-compiler/releases/download/igc-1.0.17537.24/intel-igc-opencl_1.0.17537.24_amd64.deb
wget https://github.com/intel/intel-graphics-compiler/releases/download/igc-1.0.17537.24/intel-igc-core_1.0.17537.24_amd64.deb wget https://github.com/intel/intel-graphics-compiler/releases/download/igc-1.0.17537.24/intel-igc-core_1.0.17537.24_amd64.deb
# standard packages # standard compute-runtime packages
wget https://github.com/intel/compute-runtime/releases/download/26.14.37833.4/intel-opencl-icd_26.14.37833.4-0_amd64.deb wget https://github.com/intel/compute-runtime/releases/download/26.14.37833.4/intel-opencl-icd_26.14.37833.4-0_amd64.deb
wget https://github.com/intel/compute-runtime/releases/download/26.14.37833.4/libze-intel-gpu1_26.14.37833.4-0_amd64.deb wget https://github.com/intel/compute-runtime/releases/download/26.14.37833.4/libze-intel-gpu1_26.14.37833.4-0_amd64.deb
wget https://github.com/intel/intel-graphics-compiler/releases/download/v2.32.7/intel-igc-opencl-2_2.32.7+21184_amd64.deb wget https://github.com/intel/intel-graphics-compiler/releases/download/v2.32.7/intel-igc-opencl-2_2.32.7+21184_amd64.deb
@ -137,6 +137,10 @@ if [[ "${TARGETARCH}" == "amd64" ]]; then
dpkg -i *.deb dpkg -i *.deb
rm *.deb rm *.deb
apt-get -qq install -f -y apt-get -qq install -f -y
# Battlemage uses the xe kernel driver, but the VA-API driver is still iHD.
# The oneVPL runtime may look for a driver named after the kernel module.
ln -sf /usr/lib/x86_64-linux-gnu/dri/iHD_drv_video.so /usr/lib/x86_64-linux-gnu/dri/xe_drv_video.so
fi fi
if [[ "${TARGETARCH}" == "arm64" ]]; then if [[ "${TARGETARCH}" == "arm64" ]]; then

View File

@ -42,7 +42,7 @@ opencv-python-headless == 4.11.0.*
opencv-contrib-python == 4.11.0.* opencv-contrib-python == 4.11.0.*
scipy == 1.16.* scipy == 1.16.*
# OpenVino & ONNX # OpenVino & ONNX
openvino == 2025.3.* openvino == 2025.4.*
onnxruntime == 1.22.* onnxruntime == 1.22.*
# Embeddings # Embeddings
transformers == 4.45.* transformers == 4.45.*

View File

@ -111,26 +111,16 @@ TCP ensures that all data packets arrive in the correct order. This is crucial f
You can still configure Frigate to use UDP by using ffmpeg input args or the preset `preset-rtsp-udp`. See the [ffmpeg presets](/configuration/ffmpeg_presets) documentation. You can still configure Frigate to use UDP by using ffmpeg input args or the preset `preset-rtsp-udp`. See the [ffmpeg presets](/configuration/ffmpeg_presets) documentation.
### Frigate hangs on startup with a "probing detect stream" message in the logs ### Frigate is slow to start up with a "probing detect stream" message in the logs
On startup, Frigate probes each camera's detect stream with OpenCV to auto-detect its resolution. OpenCV's FFmpeg backend may attempt RTSP over UDP during this probe regardless of the `-rtsp_transport tcp` in your `input_args` or preset. For cameras that do not respond to UDP (common on some Reolink models and others behind firewalls that block UDP), the probe can hang indefinitely and block Frigate from finishing startup, or it can return zeroed-out dimensions that show up as width `0` and height `0` in Camera Probe Info under System Metrics. When `detect.width` and `detect.height` are not set, Frigate probes each camera's detect stream on startup (and when saving the config) to auto-detect its resolution. For RTSP streams Frigate probes with ffprobe and automatically retries over TCP if UDP doesn't respond, with a 5 second timeout per attempt. A camera that cannot be reached over either transport will add up to ~10 seconds to startup before Frigate falls through with default dimensions, which may show up as width `0` and height `0` in Camera Probe Info under System Metrics.
There are two ways to avoid this: To skip the probe entirely and make startup instant, set `detect.width` and `detect.height` explicitly in your camera config:
1. Set `detect.width` and `detect.height` explicitly in your camera config. When both are set, Frigate skips the auto-detect probe entirely: ```yaml
cameras:
```yaml
cameras:
my_camera: my_camera:
detect: detect:
width: 1280 width: 1280
height: 720 height: 720
``` ```
2. Force OpenCV's FFmpeg backend to use TCP for RTSP by setting the environment variable on your Frigate container:
```
OPENCV_FFMPEG_CAPTURE_OPTIONS=rtsp_transport;tcp
```
This is a process-wide setting and applies to all cameras. If you have any cameras that require `preset-rtsp-udp`, use option 1 instead.

View File

@ -807,10 +807,15 @@ async def get_video_properties(
) -> dict[str, Any]: ) -> dict[str, Any]:
async def probe_with_ffprobe( async def probe_with_ffprobe(
url: str, url: str,
rtsp_transport: Optional[str] = None,
) -> tuple[bool, int, int, Optional[str], float]: ) -> tuple[bool, int, int, Optional[str], float]:
"""Fallback using ffprobe: returns (valid, width, height, codec, duration).""" """Fallback using ffprobe: returns (valid, width, height, codec, duration)."""
cmd = [ cmd = [ffmpeg.ffprobe_path]
ffmpeg.ffprobe_path, if rtsp_transport:
cmd += ["-rtsp_transport", rtsp_transport]
cmd += [
"-rw_timeout",
"5000000",
"-v", "-v",
"quiet", "quiet",
"-print_format", "-print_format",
@ -872,13 +877,27 @@ async def get_video_properties(
cap.release() cap.release()
return valid, width, height, fourcc, duration return valid, width, height, fourcc, duration
# try cv2 first is_rtsp = url.startswith("rtsp://")
if is_rtsp:
# skip cv2 for RTSP: its FFmpeg backend has a hardcoded ~30s internal
# timeout that cannot be shortened per-call, and ffprobe bounded by
# -rw_timeout handles RTSP probing reliably
has_video, width, height, fourcc, duration = await probe_with_ffprobe(url)
else:
# try cv2 first for local files, HTTP, RTMP
has_video, width, height, fourcc, duration = probe_with_cv2(url) has_video, width, height, fourcc, duration = probe_with_cv2(url)
# fallback to ffprobe if needed # fallback to ffprobe if needed
if not has_video or (get_duration and duration < 0): if not has_video or (get_duration and duration < 0):
has_video, width, height, fourcc, duration = await probe_with_ffprobe(url) has_video, width, height, fourcc, duration = await probe_with_ffprobe(url)
# last resort for RTSP: try TCP transport, since default UDP may be blocked
if (not has_video or (get_duration and duration < 0)) and is_rtsp:
has_video, width, height, fourcc, duration = await probe_with_ffprobe(
url, rtsp_transport="tcp"
)
result: dict[str, Any] = {"has_valid_video": has_video} result: dict[str, Any] = {"has_valid_video": has_video}
if has_video: if has_video:
result.update({"width": width, "height": height}) result.update({"width": width, "height": height})

View File

@ -415,6 +415,7 @@
"audioCodecGood": "Audio codec is {{codec}}.", "audioCodecGood": "Audio codec is {{codec}}.",
"resolutionHigh": "A resolution of {{resolution}} may cause increased resource usage.", "resolutionHigh": "A resolution of {{resolution}} may cause increased resource usage.",
"resolutionLow": "A resolution of {{resolution}} may be too low for reliable detection of small objects.", "resolutionLow": "A resolution of {{resolution}} may be too low for reliable detection of small objects.",
"resolutionUnknown": "The resolution of this stream could not be probed. This will cause issues on startup. You should manually set the detect resolution in Settings or your config.",
"noAudioWarning": "No audio detected for this stream, recordings will not have audio.", "noAudioWarning": "No audio detected for this stream, recordings will not have audio.",
"audioCodecRecordError": "The AAC audio codec is required to support audio in recordings.", "audioCodecRecordError": "The AAC audio codec is required to support audio in recordings.",
"audioCodecRequired": "An audio stream is required to support audio detection.", "audioCodecRequired": "An audio stream is required to support audio detection.",

View File

@ -218,7 +218,7 @@ export default function CameraReviewClassification({
<Label <Label
className={cn( className={cn(
"flex flex-row items-center text-base", "flex flex-row items-center text-base",
alertsZonesModified && "text-danger", alertsZonesModified && "text-unsaved",
)} )}
> >
<Trans ns="views/settings">cameraReview.review.alerts</Trans> <Trans ns="views/settings">cameraReview.review.alerts</Trans>
@ -286,7 +286,7 @@ export default function CameraReviewClassification({
<Label <Label
className={cn( className={cn(
"flex flex-row items-center text-base", "flex flex-row items-center text-base",
detectionsZonesModified && "text-danger", detectionsZonesModified && "text-unsaved",
)} )}
> >
<Trans ns="views/settings"> <Trans ns="views/settings">

View File

@ -1012,7 +1012,7 @@ export function ConfigSection({
> >
{hasChanges && ( {hasChanges && (
<div className="flex items-center gap-2"> <div className="flex items-center gap-2">
<span className="text-sm text-danger"> <span className="text-sm text-unsaved">
{t("unsavedChanges", { {t("unsavedChanges", {
ns: "views/settings", ns: "views/settings",
defaultValue: "You have unsaved changes", defaultValue: "You have unsaved changes",
@ -1299,7 +1299,7 @@ export function ConfigSection({
{hasChanges && ( {hasChanges && (
<Badge <Badge
variant="secondary" variant="secondary"
className="cursor-default bg-danger text-xs text-white hover:bg-danger" className="cursor-default bg-unsaved text-xs text-black hover:bg-unsaved"
> >
{t("button.modified", { {t("button.modified", {
ns: "common", ns: "common",

View File

@ -154,7 +154,7 @@ export function KnownPlatesField(props: FieldProps) {
<div className="flex items-center justify-between"> <div className="flex items-center justify-between">
<div> <div>
<CardTitle <CardTitle
className={cn("text-sm", isModified && "text-danger")} className={cn("text-sm", isModified && "text-unsaved")}
> >
{title} {title}
</CardTitle> </CardTitle>

View File

@ -142,7 +142,7 @@ export function ReplaceRulesField(props: FieldProps) {
<div className="flex items-center justify-between"> <div className="flex items-center justify-between">
<div> <div>
<CardTitle <CardTitle
className={cn("text-sm", isModified && "text-danger")} className={cn("text-sm", isModified && "text-unsaved")}
> >
{title} {title}
</CardTitle> </CardTitle>

View File

@ -497,7 +497,7 @@ export function FieldTemplate(props: FieldTemplateProps) {
htmlFor={id} htmlFor={id}
className={cn( className={cn(
"text-sm font-medium", "text-sm font-medium",
isModified && "text-danger", isModified && "text-unsaved",
hasFieldErrors && "text-destructive", hasFieldErrors && "text-destructive",
)} )}
> >
@ -516,7 +516,7 @@ export function FieldTemplate(props: FieldTemplateProps) {
return ( return (
<Label <Label
htmlFor={id} htmlFor={id}
className={cn("text-sm font-medium", isModified && "text-danger")} className={cn("text-sm font-medium", isModified && "text-unsaved")}
> >
{finalLabel} {finalLabel}
{required && <span className="ml-1 text-destructive">*</span>} {required && <span className="ml-1 text-destructive">*</span>}
@ -535,7 +535,7 @@ export function FieldTemplate(props: FieldTemplateProps) {
htmlFor={id} htmlFor={id}
className={cn( className={cn(
"text-sm font-medium", "text-sm font-medium",
isModified && "text-danger", isModified && "text-unsaved",
hasFieldErrors && "text-destructive", hasFieldErrors && "text-destructive",
)} )}
> >

View File

@ -467,7 +467,7 @@ export function ObjectFieldTemplate(props: ObjectFieldTemplateProps) {
<CardTitle <CardTitle
className={cn( className={cn(
"flex items-center text-sm", "flex items-center text-sm",
hasModifiedDescendants && "text-danger", hasModifiedDescendants && "text-unsaved",
)} )}
> >
{inferredLabel} {inferredLabel}

View File

@ -607,23 +607,38 @@ function StreamIssues({
} }
} }
if (stream.roles.includes("detect") && stream.resolution) { if (stream.roles.includes("detect") && stream.testResult) {
const [width, height] = stream.resolution.split("x").map(Number); const probedResolution = stream.testResult.resolution;
if (!isNaN(width) && !isNaN(height) && width > 0 && height > 0) { let probedWidth = 0;
const minDimension = Math.min(width, height); let probedHeight = 0;
const maxDimension = Math.max(width, height); if (probedResolution) {
const [w, h] = probedResolution.split("x").map(Number);
if (!isNaN(w) && !isNaN(h)) {
probedWidth = w;
probedHeight = h;
}
}
if (probedWidth <= 0 || probedHeight <= 0) {
result.push({
type: "error",
message: t("cameraWizard.step4.issues.resolutionUnknown"),
});
} else {
const minDimension = Math.min(probedWidth, probedHeight);
const maxDimension = Math.max(probedWidth, probedHeight);
if (minDimension > 1080) { if (minDimension > 1080) {
result.push({ result.push({
type: "warning", type: "warning",
message: t("cameraWizard.step4.issues.resolutionHigh", { message: t("cameraWizard.step4.issues.resolutionHigh", {
resolution: stream.resolution, resolution: probedResolution,
}), }),
}); });
} else if (maxDimension < 640) { } else if (maxDimension < 640) {
result.push({ result.push({
type: "error", type: "error",
message: t("cameraWizard.step4.issues.resolutionLow", { message: t("cameraWizard.step4.issues.resolutionLow", {
resolution: stream.resolution, resolution: probedResolution,
}), }),
}); });
} }

View File

@ -1435,7 +1435,7 @@ export default function Settings() {
/> />
)} )}
{showUnsavedDot && ( {showUnsavedDot && (
<span className="inline-block size-2 rounded-full bg-danger" /> <span className="inline-block size-2 rounded-full bg-unsaved" />
)} )}
</div> </div>
)} )}
@ -1516,7 +1516,7 @@ export default function Settings() {
<div className="sticky bottom-0 z-50 mt-2 bg-background p-4"> <div className="sticky bottom-0 z-50 mt-2 bg-background p-4">
<div className="flex flex-col items-center gap-2"> <div className="flex flex-col items-center gap-2">
<div className="flex items-center gap-2"> <div className="flex items-center gap-2">
<span className="text-sm text-danger"> <span className="text-sm text-unsaved">
{t("unsavedChanges", { {t("unsavedChanges", {
ns: "views/settings", ns: "views/settings",
defaultValue: "You have unsaved changes", defaultValue: "You have unsaved changes",

View File

@ -79,11 +79,11 @@ const PROFILE_COLORS: ProfileColor[] = [
bgMuted: "bg-green-400/20", bgMuted: "bg-green-400/20",
}, },
{ {
bg: "bg-amber-400", bg: "bg-fuchsia-500",
text: "text-amber-400", text: "text-fuchsia-500",
dot: "bg-amber-400", dot: "bg-fuchsia-500",
border: "border-amber-400", border: "border-fuchsia-500",
bgMuted: "bg-amber-400/20", bgMuted: "bg-fuchsia-500/20",
}, },
{ {
bg: "bg-slate-400", bg: "bg-slate-400",
@ -93,11 +93,11 @@ const PROFILE_COLORS: ProfileColor[] = [
bgMuted: "bg-slate-400/20", bgMuted: "bg-slate-400/20",
}, },
{ {
bg: "bg-orange-300", bg: "bg-stone-500",
text: "text-orange-300", text: "text-stone-500",
dot: "bg-orange-300", dot: "bg-stone-500",
border: "border-orange-300", border: "border-stone-500",
bgMuted: "bg-orange-300/20", bgMuted: "bg-stone-500/20",
}, },
{ {
bg: "bg-blue-300", bg: "bg-blue-300",

View File

@ -380,7 +380,9 @@ export default function Go2RtcStreamsSettingsView({
> >
{hasChanges && ( {hasChanges && (
<div className="flex items-center gap-2"> <div className="flex items-center gap-2">
<span className="text-sm text-danger">{t("unsavedChanges")}</span> <span className="text-sm text-unsaved">
{t("unsavedChanges")}
</span>
</div> </div>
)} )}
<div className="flex w-full items-center gap-2 md:w-auto"> <div className="flex w-full items-center gap-2 md:w-auto">

View File

@ -212,7 +212,7 @@ export function SingleSectionPage({
{sectionStatus.hasChanges && ( {sectionStatus.hasChanges && (
<Badge <Badge
variant="secondary" variant="secondary"
className="cursor-default bg-danger text-xs text-white hover:bg-danger" className="cursor-default bg-unsaved text-xs text-black hover:bg-unsaved"
> >
{t("button.modified", { {t("button.modified", {
ns: "common", ns: "common",
@ -250,7 +250,7 @@ export function SingleSectionPage({
{sectionStatus.hasChanges && ( {sectionStatus.hasChanges && (
<Badge <Badge
variant="secondary" variant="secondary"
className="cursor-default bg-danger text-xs text-white hover:bg-danger" className="cursor-default bg-unsaved text-xs text-black hover:bg-unsaved"
> >
{t("button.modified", { ns: "common", defaultValue: "Modified" })} {t("button.modified", { ns: "common", defaultValue: "Modified" })}
</Badge> </Badge>

View File

@ -65,6 +65,7 @@ module.exports = {
ring: "hsl(var(--ring))", ring: "hsl(var(--ring))",
danger: "#ef4444", danger: "#ef4444",
success: "#22c55e", success: "#22c55e",
unsaved: "#f59e0b",
background: "hsl(var(--background))", background: "hsl(var(--background))",
background_alt: "hsl(var(--background-alt))", background_alt: "hsl(var(--background-alt))",
foreground: "hsl(var(--foreground))", foreground: "hsl(var(--foreground))",