Compare commits

..
Author SHA1 Message Date
dependabot[bot]andGitHub 905b85ae8e Bump compression from 1.8.1 to 1.8.2 in /docs
Bumps [compression](https://github.com/expressjs/compression) from 1.8.1 to 1.8.2.
- [Release notes](https://github.com/expressjs/compression/releases)
- [Changelog](https://github.com/expressjs/compression/blob/master/HISTORY.md)
- [Commits](https://github.com/expressjs/compression/compare/v1.8.1...v1.8.2)

---
updated-dependencies:
- dependency-name: compression
  dependency-version: 1.8.2
  dependency-type: indirect
...

Signed-off-by: dependabot[bot] <support@github.com>
2026-10-06 21:22:02 +00:00
8 changed files with 51 additions and 217 deletions
+3 -3
View File
@@ -169,10 +169,10 @@ The [Add Camera Wizard](cameras.md#adding-a-camera-with-the-add-camera-wizard) i
1. Click **Add Camera** in <NavPath path="Settings > Global configuration > Camera management" />.
2. Choose **Manual selection** as the stream detection method and select **Reolink** as the camera brand.
3. The wizard queries the camera and automatically uses an http-flv stream for cameras 5MP and lower. For higher resolution cameras, it tries the http-flv stream first and falls back to RTSP when the camera does not support it.
4. The wizard turns on **Use stream compatibility mode** for the http-flv stream it selects. Enable it for any other http-flv stream you add, such as the sub stream.
3. The wizard queries the camera and automatically uses an http-flv stream for cameras 5MP and lower, or an RTSP stream for higher resolution cameras.
4. In the validation step, enable **Use stream compatibility mode** for http-flv streams when the wizard recommends it.
If you use the **Probe camera** method instead, the discovered stream URLs will be RTSP. For Reolink cameras 5MP and lower, where http-flv is recommended, the wizard will show a warning in the validation step.
If you use the **Probe camera** method instead, the discovered stream URLs will be RTSP. For Reolink cameras where http-flv is recommended, the wizard will show a warning in the validation step.
The wizard covers standard single-camera setups. For two way talk, cameras connected through a Reolink NVR, or audio transcoding for WebRTC live view, configure the camera manually as shown below.
+1 -1
View File
@@ -189,7 +189,7 @@ Frigate is able to utilize an Nvidia GPU which supports the 12.x series of CUDA
#### Minimum Hardware Support
12.x series of CUDA libraries are used which have minor version compatibility. The minimum driver version on the host system must be `>=545`. The oldest supported GPU generation for object detection is Pascal (Compute Capability 6.0 or greater). Older generations such as Maxwell are not supported but can still be used by following a [community workaround](https://github.com/blakeblackshear/frigate/discussions/20088). For other GPUs, check the NVIDIA GPU Compute Capability table linked below.
12.x series of CUDA libraries are used which have minor version compatibility. The minimum driver version on the host system must be `>=545`. Also the GPU must support a Compute Capability of `5.0` or greater. This generally correlates to a Maxwell-era GPU or newer, check the NVIDIA GPU Compute Capability table linked below.
Make sure your host system has the [nvidia-container-runtime](https://docs.docker.com/config/containers/resource_constraints/#access-an-nvidia-gpu) installed to pass through the GPU to the container and the host system has a compatible driver installed for your GPU.
+8 -3
View File
@@ -7698,14 +7698,15 @@
}
},
"node_modules/compression": {
"version": "1.8.1",
"resolved": "https://registry.npmjs.org/compression/-/compression-1.8.1.tgz",
"integrity": "sha512-9mAqGPHLakhCLeNyxPkK4xVo746zQ/czLH1Ky+vkitMnWfWZps8r0qXuwhwizagCRttsL4lfG4pIOvaWLpAP0w==",
"version": "1.8.2",
"resolved": "https://registry.npmjs.org/compression/-/compression-1.8.2.tgz",
"integrity": "sha512-o8vI5RE5A6EVVOd9o41jKp41aJom+QTEO/Bx8MYNjexMo/Bv2WOjUfZr+aL0WnYSgymUy6zeguqLTsIhV0gMvQ==",
"license": "MIT",
"dependencies": {
"bytes": "3.1.2",
"compressible": "~2.0.18",
"debug": "2.6.9",
"destroy": "1.2.0",
"negotiator": "~0.6.4",
"on-headers": "~1.1.0",
"safe-buffer": "5.2.1",
@@ -7713,6 +7714,10 @@
},
"engines": {
"node": ">= 0.8.0"
},
"funding": {
"type": "opencollective",
"url": "https://opencollective.com/express"
}
},
"node_modules/compression/node_modules/bytes": {
@@ -1,155 +0,0 @@
/**
* Add-camera wizard - Reolink stream selection with the brand template.
*
* The wizard asks the camera for its resolution, then probes http-flv first
* above 5MP and falls back to RTSP. The Step 4 RTSP warning is only for
* cameras that should be on http-flv. An http-flv stream the wizard selects
* is registered with go2rtc through the ffmpeg module.
*/
import { test, expect } from "../../fixtures/frigate-test";
import type { Page } from "@playwright/test";
const FLV_PATH = "channel0_main.bcs";
const RTSP_PATH = "Preview_01_main";
const RTSP_WARNING = "Reolink RTSP is not recommended";
const HTTP_WARNING = "Reolink HTTP streams should use FFmpeg";
const FFPROBE_OK = [
{
return_code: 0,
stderr: [],
stdout: {
streams: [
{
codec_type: "video",
codec_name: "hevc",
width: 3840,
height: 2160,
avg_frame_rate: "15/1",
},
{ codec_type: "audio", codec_name: "aac" },
],
},
},
];
const FFPROBE_FAILED = [
{ return_code: 1, stderr: ["probe failed"], stdout: "" },
];
/**
* Mock the camera's answers and drive the wizard to Step 3. Returns the
* dialog and the stream paths the wizard probed, in order.
*/
async function gotoStep3(
page: Page,
{ protocol, flvProbes }: { protocol: string | null; flvProbes: boolean },
) {
const probed: string[] = [];
await page.route("**/api/reolink/detect**", (route) =>
route.fulfill({ json: { success: protocol !== null, protocol } }),
);
await page.route("**/api/ffprobe**", (route) => {
const paths = new URL(route.request().url()).searchParams.get("paths");
const isFlv = !!paths?.includes(FLV_PATH);
probed.push(isFlv ? FLV_PATH : RTSP_PATH);
return route.fulfill({
json: isFlv && !flvProbes ? FFPROBE_FAILED : FFPROBE_OK,
});
});
await page.route("**/api/ffprobe/snapshot**", (route) =>
route.fulfill({ status: 500 }),
);
await page.getByRole("button", { name: /Add New Camera/i }).click();
const dialog = page.getByRole("dialog");
await expect(dialog).toBeVisible();
await dialog.getByPlaceholder(/front_door/i).fill("reolink_test_camera");
await dialog.getByPlaceholder("192.168.1.100").fill("192.168.1.100");
await dialog.getByPlaceholder("Optional").first().fill("admin");
await dialog.getByPlaceholder("Optional").last().fill("pw");
await dialog.getByText("Manual selection").click();
await dialog.getByRole("combobox").click();
await page.getByRole("option", { name: "Reolink" }).click();
await dialog.getByRole("button", { name: /^Continue$/i }).click();
// Step 2 tests the connection on its own, then offers Continue
const next = dialog.getByRole("button", { name: /^Continue$/i });
await expect(next).toBeEnabled({ timeout: 10_000 });
await next.click();
await expect(
dialog.getByRole("button", { name: /Add Another Stream/i }),
).toBeVisible();
return { dialog, probed };
}
test.describe("Camera wizard Reolink stream selection @medium @mobile", () => {
test.beforeEach(async ({ frigateApp }) => {
// not in the default mock; unmocked it 500s and trips the error collector
await frigateApp.page.route("**/api/config/raw_paths", (route) =>
route.fulfill({ json: {} }),
);
await frigateApp.goto("/settings?page=cameraManagement");
await expect(
frigateApp.page.getByRole("heading", { name: /Manage Cameras/i }),
).toBeVisible();
});
test("above 5MP keeps http-flv when it probes", async ({ frigateApp }) => {
const { dialog, probed } = await gotoStep3(frigateApp.page, {
protocol: "rtsp",
flvProbes: true,
});
expect(probed).toEqual([FLV_PATH]);
await expect(dialog.locator(`input[value*="${FLV_PATH}"]`)).toBeVisible();
const registered: string[] = [];
await frigateApp.page.route("**/api/go2rtc/streams/**", (route) => {
const src = new URL(route.request().url()).searchParams.get("src");
if (src) registered.push(src);
return route.fulfill({ json: {} });
});
await dialog.getByRole("button", { name: /^Next$/i }).click();
await expect(
dialog.getByRole("button", { name: /Save New Camera/i }),
).toBeVisible();
await expect.poll(() => registered[0]).toMatch(/^ffmpeg:http:\/\//);
await expect(dialog.getByText(HTTP_WARNING)).toHaveCount(0);
});
test("above 5MP falls back to RTSP without a warning", async ({
frigateApp,
}) => {
const { dialog, probed } = await gotoStep3(frigateApp.page, {
protocol: "rtsp",
flvProbes: false,
});
expect(probed).toEqual([FLV_PATH, RTSP_PATH]);
await expect(dialog.locator(`input[value*="${RTSP_PATH}"]`)).toBeVisible();
await dialog.getByRole("button", { name: /^Next$/i }).click();
await expect(
dialog.getByRole("button", { name: /Save New Camera/i }),
).toBeVisible();
await expect(dialog.getByText(RTSP_WARNING)).toHaveCount(0);
});
test("failed detection uses RTSP and warns", async ({ frigateApp }) => {
const { dialog, probed } = await gotoStep3(frigateApp.page, {
protocol: null,
flvProbes: true,
});
expect(probed).toEqual([RTSP_PATH]);
await dialog.getByRole("button", { name: /^Next$/i }).click();
await expect(dialog.getByText(RTSP_WARNING)).toBeVisible();
});
});
@@ -222,7 +222,6 @@ export default function Step2ProbeOrSnapshot({
wizardData.username,
wizardData.password,
);
update.reolinkProtocol = protocol;
if (protocol === "http-flv") {
update.brandTemplate = "reolink";
}
@@ -295,72 +294,73 @@ export default function Step2ProbeOrSnapshot({
[probeUri],
);
const generateDynamicStreamUrls = useCallback(
async (data: Partial<WizardFormData>): Promise<string[]> => {
const generateDynamicStreamUrl = useCallback(
async (data: Partial<WizardFormData>): Promise<string | null> => {
const brand = CAMERA_BRANDS.find((b) => b.value === data.brandTemplate);
const host = data.host;
if (!brand || !host) return [];
if (!brand || !data.host) return null;
let protocol = undefined;
if (data.brandTemplate === "reolink" && data.username && data.password) {
try {
protocol = await detectReolinkCamera(
host,
data.host,
data.username,
data.password,
);
} catch {
return [];
return null;
}
onUpdate({ reolinkProtocol: protocol });
}
// Only some Reolink cameras above 5MP serve http-flv, so RTSP is the
// fallback when the http-flv stream does not probe.
const protocolKeys =
protocol === "rtsp" ? ["http-flv", "rtsp"] : [protocol || "rtsp"];
const protocolKey = protocol || "rtsp";
const templates: Record<string, string> = brand.dynamicTemplates || {};
return protocolKeys
.filter((key) => key in templates)
.map((key) =>
templates[key]
.replace("{username}", data.username || "")
.replace("{password}", data.password || "")
.replace("{host}", host),
);
if (Object.keys(templates).includes(protocolKey)) {
const template =
templates[protocolKey as keyof typeof brand.dynamicTemplates];
return template
.replace("{username}", data.username || "")
.replace("{password}", data.password || "")
.replace("{host}", data.host);
}
return null;
},
[onUpdate],
[],
);
const generateStreamUrls = useCallback(
async (data: Partial<WizardFormData>): Promise<string[]> => {
const generateStreamUrl = useCallback(
async (data: Partial<WizardFormData>): Promise<string> => {
if (data.brandTemplate === "other") {
return data.customUrl ? [data.customUrl] : [];
return data.customUrl || "";
}
const brand = CAMERA_BRANDS.find((b) => b.value === data.brandTemplate);
if (!brand || !data.host) return [];
if (!brand || !data.host) return "";
if (brand.template === "dynamic" && "dynamicTemplates" in brand) {
return generateDynamicStreamUrls(data);
const dynamicUrl = await generateDynamicStreamUrl(data);
if (dynamicUrl) {
return dynamicUrl;
}
return "";
}
return [
brand.template
.replace("{username}", data.username || "")
.replace("{password}", data.password || "")
.replace("{host}", data.host),
];
return brand.template
.replace("{username}", data.username || "")
.replace("{password}", data.password || "")
.replace("{host}", data.host);
},
[generateDynamicStreamUrls],
[generateDynamicStreamUrl],
);
const testConnection = useCallback(
async (showToast = true) => {
const streamUrls = await generateStreamUrls(wizardData);
const streamUrl = await generateStreamUrl(wizardData);
if (streamUrls.length === 0) {
if (!streamUrl) {
toast.error(t("cameraWizard.commonErrors.noUrl"));
return;
}
@@ -370,18 +370,8 @@ export default function Step2ProbeOrSnapshot({
setTestResult(null);
try {
let streamUrl = streamUrls[0];
let result: TestResult | undefined;
for (const url of streamUrls) {
streamUrl = url;
setTestStatus(t("cameraWizard.step2.testing.probingMetadata"));
result = await probeUri(url, true, setTestStatus);
if (result.success && result.resolution) {
break;
}
}
setTestStatus(t("cameraWizard.step2.testing.probingMetadata"));
const result = await probeUri(streamUrl, true, setTestStatus);
if (result && result.success) {
setTestResult(result);
@@ -393,9 +383,6 @@ export default function Step2ProbeOrSnapshot({
url: streamUrl,
roles: ["detect"] as StreamRole[],
testResult: result,
useFfmpeg:
wizardData.brandTemplate === "reolink" &&
streamUrl.startsWith("http://"),
},
],
});
@@ -447,7 +434,7 @@ export default function Step2ProbeOrSnapshot({
setTestStatus("");
}
},
[wizardData, generateStreamUrls, t, onUpdate, probeUri],
[wizardData, generateStreamUrl, t, onUpdate, probeUri],
);
const handleContinue = useCallback(() => {
@@ -499,7 +499,6 @@ function StreamIssues({
url: stream.url,
roles: stream.roles,
brand: wizardData.brandTemplate,
reolinkProtocol: wizardData.reolinkProtocol,
useFfmpeg: stream.useFfmpeg,
restream: stream.restream,
testResult: stream.testResult,
-1
View File
@@ -110,7 +110,6 @@ export type WizardFormData = {
username?: string;
password?: string;
brandTemplate?: CameraBrand;
reolinkProtocol?: "http-flv" | "rtsp" | null; // null when detection failed
customUrl?: string;
streams?: StreamConfig[];
probeMode?: boolean; // true for probe, false for manual
+1 -2
View File
@@ -13,7 +13,6 @@ export type StreamIssueInput = {
url: string;
roles: StreamRole[];
brand?: CameraBrand;
reolinkProtocol?: "http-flv" | "rtsp" | null;
useFfmpeg?: boolean;
restream?: boolean;
testResult?: TestResult;
@@ -97,7 +96,7 @@ export function getStreamIssues(
if (input.brand === "reolink") {
const streamUrl = input.url.toLowerCase();
if (streamUrl.startsWith("rtsp://") && input.reolinkProtocol !== "rtsp") {
if (streamUrl.startsWith("rtsp://")) {
result.push({
type: "warning",
rule: "reolink-rtsp",