mirror of
https://github.com/blakeblackshear/frigate.git
synced 2026-10-06 06:42:51 +03:00
clean up
This commit is contained in:
@@ -121,8 +121,7 @@ class TestIntelGeneration(HwaccelRecommendationTestCase):
|
|||||||
self.assertEqual(self.recommend(["openvino:GPU"], codecs={"h264"}), "intel-qsv")
|
self.assertEqual(self.recommend(["openvino:GPU"], codecs={"h264"}), "intel-qsv")
|
||||||
|
|
||||||
def test_gen13_prefers_qsv_for_mixed_codecs(self):
|
def test_gen13_prefers_qsv_for_mixed_codecs(self):
|
||||||
# every camera resolves the family to its own codec, so mixed streams
|
# each camera resolves the family to its own codec
|
||||||
# are no longer a reason to drop to vaapi
|
|
||||||
self.write_cpuinfo("13th Gen Intel(R) Core(TM) i5-13500")
|
self.write_cpuinfo("13th Gen Intel(R) Core(TM) i5-13500")
|
||||||
self.assertEqual(
|
self.assertEqual(
|
||||||
self.recommend(["openvino:GPU"], codecs={"h264", "h265"}), "intel-qsv"
|
self.recommend(["openvino:GPU"], codecs={"h264", "h265"}), "intel-qsv"
|
||||||
|
|||||||
@@ -26,7 +26,6 @@ logger = logging.getLogger(__name__)
|
|||||||
# root the /proc reads use, so tests can point them at a fixture tree
|
# root the /proc reads use, so tests can point them at a fixture tree
|
||||||
PROC_ROOT = "/proc"
|
PROC_ROOT = "/proc"
|
||||||
|
|
||||||
# stands in for the codec of a preset that decodes anything
|
|
||||||
ANY_CODEC = "any"
|
ANY_CODEC = "any"
|
||||||
|
|
||||||
# a Raspberry Pi has no detection hardware of its own, so it gets a key here
|
# a Raspberry Pi has no detection hardware of its own, so it gets a key here
|
||||||
@@ -35,11 +34,10 @@ RASPBERRY_PI = "raspberrypi"
|
|||||||
# ffprobe names h265 streams hevc
|
# ffprobe names h265 streams hevc
|
||||||
CODEC_ALIASES = {"hevc": "h265"}
|
CODEC_ALIASES = {"hevc": "h265"}
|
||||||
|
|
||||||
# marketing name of a newer Intel CPU, e.g. "13th Gen Intel(R) Core(TM) i5-13500"
|
# e.g. "13th Gen Intel(R) Core(TM) i5-13500"
|
||||||
INTEL_GEN_PATTERN = re.compile(r"(\d+)th Gen")
|
INTEL_GEN_PATTERN = re.compile(r"(\d+)th Gen")
|
||||||
# Core Ultra dropped that prefix and is newer than every numbered generation
|
# Core Ultra dropped the generation prefix and is newer than all of them
|
||||||
INTEL_ULTRA_PATTERN = re.compile(r"Core\(TM\) Ultra")
|
INTEL_ULTRA_PATTERN = re.compile(r"Core\(TM\) Ultra")
|
||||||
# stands in for a generation newer than any numbered one
|
|
||||||
INTEL_GEN_LATEST = 99
|
INTEL_GEN_LATEST = 99
|
||||||
|
|
||||||
# per the hwaccel docs, gen13+ and Arc prefer qsv while older is safest on
|
# per the hwaccel docs, gen13+ and Arc prefer qsv while older is safest on
|
||||||
@@ -47,8 +45,7 @@ INTEL_GEN_LATEST = 99
|
|||||||
INTEL_QSV_MIN_GEN = 13
|
INTEL_QSV_MIN_GEN = 13
|
||||||
INTEL_QSV_SUPPORTED_GEN = 8
|
INTEL_QSV_SUPPORTED_GEN = 8
|
||||||
|
|
||||||
# detection hardware whose GPU also decodes video, in recommendation priority
|
# decode capable detection hardware, in recommendation priority order
|
||||||
# order
|
|
||||||
DECODE_HARDWARE = (
|
DECODE_HARDWARE = (
|
||||||
"onnx:nvidia",
|
"onnx:nvidia",
|
||||||
"tensorrt",
|
"tensorrt",
|
||||||
@@ -150,7 +147,6 @@ def _intel_families(generation: int | None) -> list[HwaccelFamily]:
|
|||||||
if generation is not None and generation >= INTEL_QSV_MIN_GEN:
|
if generation is not None and generation >= INTEL_QSV_MIN_GEN:
|
||||||
return [FAMILY_QSV, FAMILY_VAAPI]
|
return [FAMILY_QSV, FAMILY_VAAPI]
|
||||||
|
|
||||||
# gen8 to gen12 can do either, and the docs call vaapi the safer default
|
|
||||||
return [FAMILY_VAAPI, FAMILY_QSV]
|
return [FAMILY_VAAPI, FAMILY_QSV]
|
||||||
|
|
||||||
|
|
||||||
@@ -253,8 +249,8 @@ def hwaccel_options(
|
|||||||
def _recommend(families: list[HwaccelFamily], codecs_known: bool) -> str:
|
def _recommend(families: list[HwaccelFamily], codecs_known: bool) -> str:
|
||||||
"""Pick the family to default to out of the ones this hardware can use."""
|
"""Pick the family to default to out of the ones this hardware can use."""
|
||||||
if not codecs_known:
|
if not codecs_known:
|
||||||
# nothing says which codec a camera will send, and a codec specific
|
# a codec specific family would have to guess a codec for cameras
|
||||||
# family would have to guess one, so anything that decodes them all wins
|
# that do not exist yet
|
||||||
for family in families:
|
for family in families:
|
||||||
if ANY_CODEC in family.presets:
|
if ANY_CODEC in family.presets:
|
||||||
return family.key
|
return family.key
|
||||||
|
|||||||
@@ -57,16 +57,12 @@ async function captureRestarts(page: Page): Promise<string[]> {
|
|||||||
}
|
}
|
||||||
|
|
||||||
async function gotoDetectorStep(page: Page) {
|
async function gotoDetectorStep(page: Page) {
|
||||||
// welcome -> camera -> detector
|
|
||||||
await page.getByRole("button", { name: "Get Started" }).click();
|
await page.getByRole("button", { name: "Get Started" }).click();
|
||||||
await expect(page.getByText("Add Your First Camera")).toBeVisible();
|
await expect(page.getByText("Add Your First Camera")).toBeVisible();
|
||||||
|
|
||||||
// The camera step's Next button only renders once addedCameras is
|
// the camera step's Next only renders once its local addedCameras fills,
|
||||||
// non-empty, and that state is local to the step (it isn't seeded from
|
// which SetupCamera does by refetching config when the dialog closes, so
|
||||||
// the cameras already in config). Opening and cancelling the add-camera
|
// opening and cancelling the dialog is what reveals Next
|
||||||
// dialog is enough: its close handler force-refetches /api/config, which
|
|
||||||
// picks up the cameras already present in the mocked config and reveals
|
|
||||||
// Next -- see SetupCamera.tsx's handleClose.
|
|
||||||
await page.getByRole("button", { name: "Add Camera" }).click();
|
await page.getByRole("button", { name: "Add Camera" }).click();
|
||||||
await page.getByRole("button", { name: "Cancel" }).click();
|
await page.getByRole("button", { name: "Cancel" }).click();
|
||||||
|
|
||||||
@@ -112,7 +108,6 @@ test.describe("setup wizard hardware @high @mobile", () => {
|
|||||||
|
|
||||||
await page.getByRole("button", { name: "Next" }).click();
|
await page.getByRole("button", { name: "Next" }).click();
|
||||||
|
|
||||||
// detector -> hwaccel
|
|
||||||
await expect(page.getByText("Hardware Acceleration")).toBeVisible();
|
await expect(page.getByText("Hardware Acceleration")).toBeVisible();
|
||||||
|
|
||||||
const detectorSave = saves.find((save) => save.config_data?.models);
|
const detectorSave = saves.find((save) => save.config_data?.models);
|
||||||
@@ -121,8 +116,7 @@ test.describe("setup wizard hardware @high @mobile", () => {
|
|||||||
]);
|
]);
|
||||||
expect(detectorSave?.config_data?.detect).toEqual({ enabled: true });
|
expect(detectorSave?.config_data?.detect).toEqual({ enabled: true });
|
||||||
|
|
||||||
// Auto names the family derived from the chosen hardware and writes its
|
// VAAPI decodes any codec, so one global value covers every camera
|
||||||
// preset; VAAPI decodes any codec, so one global value covers every camera
|
|
||||||
await expect(page.getByText("Will use VAAPI (Intel/AMD)")).toBeVisible();
|
await expect(page.getByText("Will use VAAPI (Intel/AMD)")).toBeVisible();
|
||||||
await page.getByRole("button", { name: "Next" }).click();
|
await page.getByRole("button", { name: "Next" }).click();
|
||||||
|
|
||||||
@@ -131,8 +125,7 @@ test.describe("setup wizard hardware @high @mobile", () => {
|
|||||||
hwaccel_args: "preset-vaapi",
|
hwaccel_args: "preset-vaapi",
|
||||||
});
|
});
|
||||||
|
|
||||||
// hwaccel -> recording (skipped) -> complete, where the saved steps only
|
// the saved steps only take effect after a restart
|
||||||
// take effect after a restart and the primary action says so
|
|
||||||
const restarts = await captureRestarts(page);
|
const restarts = await captureRestarts(page);
|
||||||
await page.getByRole("button", { name: "Skip" }).click();
|
await page.getByRole("button", { name: "Skip" }).click();
|
||||||
await expect(page.getByText("You're All Set!")).toBeVisible();
|
await expect(page.getByText("You're All Set!")).toBeVisible();
|
||||||
@@ -171,8 +164,7 @@ test.describe("setup wizard hardware @high @mobile", () => {
|
|||||||
await expect(page.getByText("Hardware Acceleration")).toBeVisible();
|
await expect(page.getByText("Hardware Acceleration")).toBeVisible();
|
||||||
expect(saves.filter((save) => save.config_data?.models)).toHaveLength(0);
|
expect(saves.filter((save) => save.config_data?.models)).toHaveLength(0);
|
||||||
|
|
||||||
// with nothing derived, Auto advances without writing; skipping recording
|
// nothing derived and nothing saved, so finishing needs no restart
|
||||||
// leaves no pending saves, so finishing needs no restart
|
|
||||||
const restarts = await captureRestarts(page);
|
const restarts = await captureRestarts(page);
|
||||||
await expect(page.getByText("No supported video card found")).toBeVisible();
|
await expect(page.getByText("No supported video card found")).toBeVisible();
|
||||||
await page.getByRole("button", { name: "Next" }).click();
|
await page.getByRole("button", { name: "Next" }).click();
|
||||||
@@ -184,8 +176,8 @@ test.describe("setup wizard hardware @high @mobile", () => {
|
|||||||
).toBeHidden();
|
).toBeHidden();
|
||||||
await page.getByRole("button", { name: "Go to Live View" }).click();
|
await page.getByRole("button", { name: "Go to Live View" }).click();
|
||||||
|
|
||||||
// hands off without restarting; the mocked config still reports
|
// the mocked config still reports onboarding incomplete, so the reload
|
||||||
// onboarding incomplete, so the reload lands back on the wizard
|
// lands back on the wizard
|
||||||
await expect(page.getByText("Welcome to Frigate")).toBeVisible();
|
await expect(page.getByText("Welcome to Frigate")).toBeVisible();
|
||||||
expect(restarts).toHaveLength(0);
|
expect(restarts).toHaveLength(0);
|
||||||
const finishSave = saves.find((save) => save.config_data?.onboarding);
|
const finishSave = saves.find((save) => save.config_data?.onboarding);
|
||||||
@@ -257,15 +249,12 @@ test.describe("setup wizard hardware @high @mobile", () => {
|
|||||||
await page.getByRole("button", { name: "Next" }).click();
|
await page.getByRole("button", { name: "Next" }).click();
|
||||||
await expect(page.getByText("Hardware Acceleration")).toBeVisible();
|
await expect(page.getByText("Hardware Acceleration")).toBeVisible();
|
||||||
|
|
||||||
// the family is named once, not split into codec variants the user would
|
|
||||||
// have to choose between
|
|
||||||
await expect(
|
await expect(
|
||||||
page.getByRole("radio", { name: "NVIDIA Jetson" }),
|
page.getByRole("radio", { name: "NVIDIA Jetson" }),
|
||||||
).toBeVisible();
|
).toBeVisible();
|
||||||
await page.getByRole("button", { name: "Next" }).click();
|
await page.getByRole("button", { name: "Next" }).click();
|
||||||
|
|
||||||
// no camera was added through the wizard, so there is no codec to match
|
// no camera was added, so there is no codec to match
|
||||||
// and the family's first preset stands in
|
|
||||||
const hwaccelSave = saves.find((save) => save.config_data?.ffmpeg);
|
const hwaccelSave = saves.find((save) => save.config_data?.ffmpeg);
|
||||||
expect(hwaccelSave?.config_data?.ffmpeg).toEqual({
|
expect(hwaccelSave?.config_data?.ffmpeg).toEqual({
|
||||||
hwaccel_args: "preset-jetson-h264",
|
hwaccel_args: "preset-jetson-h264",
|
||||||
|
|||||||
@@ -74,8 +74,7 @@ const STEPS = [
|
|||||||
type CameraWizardDialogProps = {
|
type CameraWizardDialogProps = {
|
||||||
open: boolean;
|
open: boolean;
|
||||||
onClose: () => void;
|
onClose: () => void;
|
||||||
// reports each camera the wizard saves, so callers can act on the streams
|
// lets callers reuse what was probed here instead of probing again
|
||||||
// that were probed without probing them again
|
|
||||||
onCameraAdded?: (camera: { name: string; detectCodec?: string }) => void;
|
onCameraAdded?: (camera: { name: string; detectCodec?: string }) => void;
|
||||||
};
|
};
|
||||||
|
|
||||||
|
|||||||
@@ -25,8 +25,8 @@ export default function SetupCamera({ onNext, onBack }: SetupCameraProps) {
|
|||||||
|
|
||||||
const handleClose = useCallback(() => {
|
const handleClose = useCallback(() => {
|
||||||
setShowWizard(false);
|
setShowWizard(false);
|
||||||
// Delay to let CameraWizardDialog's config save and go2rtc setup complete,
|
// the dialog's config save and go2rtc setup need a moment to land, and
|
||||||
// then force a fresh fetch from the server (bypassing SWR cache)
|
// the SWR cache would serve the pre-save config
|
||||||
setTimeout(() => {
|
setTimeout(() => {
|
||||||
fetch(`${window.baseUrl || ""}api/config`)
|
fetch(`${window.baseUrl || ""}api/config`)
|
||||||
.then((res) => res.json())
|
.then((res) => res.json())
|
||||||
@@ -37,9 +37,7 @@ export default function SetupCamera({ onNext, onBack }: SetupCameraProps) {
|
|||||||
setAddedCameras(cameraNames);
|
setAddedCameras(cameraNames);
|
||||||
}
|
}
|
||||||
})
|
})
|
||||||
.catch(() => {
|
.catch(() => {});
|
||||||
// Fetch failed, stay on this step
|
|
||||||
});
|
|
||||||
}, 1000);
|
}, 1000);
|
||||||
}, [mutateConfig, addedCameras]);
|
}, [mutateConfig, addedCameras]);
|
||||||
|
|
||||||
|
|||||||
@@ -87,7 +87,6 @@ export default function SetupComplete({
|
|||||||
const handleFinish = useCallback(async () => {
|
const handleFinish = useCallback(async () => {
|
||||||
setFinishing(true);
|
setFinishing(true);
|
||||||
try {
|
try {
|
||||||
// Mark setup as complete
|
|
||||||
await axios.put("config/set", {
|
await axios.put("config/set", {
|
||||||
config_data: {
|
config_data: {
|
||||||
onboarding: { setup_complete: true },
|
onboarding: { setup_complete: true },
|
||||||
@@ -95,8 +94,7 @@ export default function SetupComplete({
|
|||||||
requires_restart: 0,
|
requires_restart: 0,
|
||||||
});
|
});
|
||||||
|
|
||||||
// camera adds were applied live, so with no pending requires_restart
|
// camera adds were applied live, so nothing is waiting on a restart
|
||||||
// saves the wizard can hand off without restarting
|
|
||||||
if (!restartRequired) {
|
if (!restartRequired) {
|
||||||
window.location.href = window.baseUrl || "/";
|
window.location.href = window.baseUrl || "/";
|
||||||
return;
|
return;
|
||||||
@@ -104,10 +102,8 @@ export default function SetupComplete({
|
|||||||
|
|
||||||
setRestarting(true);
|
setRestarting(true);
|
||||||
|
|
||||||
// Trigger restart
|
|
||||||
await axios.post("restart");
|
await axios.post("restart");
|
||||||
|
|
||||||
// Poll for server availability with max retries
|
|
||||||
let retries = 0;
|
let retries = 0;
|
||||||
const maxRetries = 60; // 2 minutes max
|
const maxRetries = 60; // 2 minutes max
|
||||||
pollRef.current = setInterval(async () => {
|
pollRef.current = setInterval(async () => {
|
||||||
@@ -116,7 +112,6 @@ export default function SetupComplete({
|
|||||||
if (pollRef.current) {
|
if (pollRef.current) {
|
||||||
clearInterval(pollRef.current);
|
clearInterval(pollRef.current);
|
||||||
}
|
}
|
||||||
// Give up polling, redirect anyway
|
|
||||||
window.location.href = window.baseUrl || "/";
|
window.location.href = window.baseUrl || "/";
|
||||||
return;
|
return;
|
||||||
}
|
}
|
||||||
@@ -129,7 +124,7 @@ export default function SetupComplete({
|
|||||||
window.location.href = window.baseUrl || "/";
|
window.location.href = window.baseUrl || "/";
|
||||||
}
|
}
|
||||||
} catch {
|
} catch {
|
||||||
// Server not ready yet
|
// not back yet
|
||||||
}
|
}
|
||||||
}, 2000);
|
}, 2000);
|
||||||
} catch {
|
} catch {
|
||||||
|
|||||||
@@ -20,8 +20,8 @@ import { LuExternalLink } from "react-icons/lu";
|
|||||||
import { toast } from "sonner";
|
import { toast } from "sonner";
|
||||||
import useSWR from "swr";
|
import useSWR from "swr";
|
||||||
|
|
||||||
// detectors that ship no default model; configuring them without one leaves
|
// these ship no default model, so configuring one without a model leaves the
|
||||||
// the detector unable to start
|
// detector unable to start
|
||||||
const MODEL_REQUIRED_DETECTORS = ["onnx", "tensorrt"];
|
const MODEL_REQUIRED_DETECTORS = ["onnx", "tensorrt"];
|
||||||
|
|
||||||
const CPU_FALLBACK: DetectionHardware[] = [
|
const CPU_FALLBACK: DetectionHardware[] = [
|
||||||
@@ -64,7 +64,7 @@ export default function SetupDetector({
|
|||||||
});
|
});
|
||||||
const plusEnabled = Boolean(config?.plus?.enabled);
|
const plusEnabled = Boolean(config?.plus?.enabled);
|
||||||
|
|
||||||
// the cpu is always probed, so an empty or failed probe leaves a fallback
|
// the cpu is always probed, so an empty list means the probe failed
|
||||||
const options = useMemo(
|
const options = useMemo(
|
||||||
() => (hardware && hardware.length > 0 ? hardware : CPU_FALLBACK),
|
() => (hardware && hardware.length > 0 ? hardware : CPU_FALLBACK),
|
||||||
[hardware],
|
[hardware],
|
||||||
@@ -121,7 +121,6 @@ export default function SetupDetector({
|
|||||||
|
|
||||||
const handleSave = useCallback(async () => {
|
const handleSave = useCallback(async () => {
|
||||||
if (needsModel && !plusModelId) {
|
if (needsModel && !plusModelId) {
|
||||||
// no model to run on this hardware yet, so leave the config untouched
|
|
||||||
onSkip(selected.key);
|
onSkip(selected.key);
|
||||||
return;
|
return;
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -11,7 +11,6 @@ import useSWR from "swr";
|
|||||||
const AUTO = "auto";
|
const AUTO = "auto";
|
||||||
const NONE = "none";
|
const NONE = "none";
|
||||||
|
|
||||||
// the codec key of a preset that decodes anything
|
|
||||||
const ANY_CODEC = "any";
|
const ANY_CODEC = "any";
|
||||||
|
|
||||||
// ffprobe names h265 streams hevc
|
// ffprobe names h265 streams hevc
|
||||||
@@ -24,11 +23,9 @@ function normalizeCodec(codec: string): string {
|
|||||||
|
|
||||||
type SetupHwAccelProps = {
|
type SetupHwAccelProps = {
|
||||||
detectorHardwareKey?: string;
|
detectorHardwareKey?: string;
|
||||||
// detect stream codec of each camera added in the wizard, keyed by camera
|
// camera name -> detect stream codec, the only stream hwaccel applies to
|
||||||
// name. hwaccel only applies to the detect stream.
|
|
||||||
detectCodecs: Record<string, string>;
|
detectCodecs: Record<string, string>;
|
||||||
// saved reports whether a config write happened, so the wizard knows
|
// saved tells the wizard whether finishing needs a restart
|
||||||
// whether finishing requires a restart
|
|
||||||
onNext: (saved: boolean) => void;
|
onNext: (saved: boolean) => void;
|
||||||
onBack: () => void;
|
onBack: () => void;
|
||||||
onSkip: () => void;
|
onSkip: () => void;
|
||||||
@@ -99,14 +96,11 @@ export default function SetupHwAccel({
|
|||||||
return { ffmpeg: { hwaccel_args: shared } };
|
return { ffmpeg: { hwaccel_args: shared } };
|
||||||
}
|
}
|
||||||
|
|
||||||
// this family decodes one codec per preset, so each camera needs the
|
|
||||||
// preset matching its own detect stream
|
|
||||||
const perCamera = cameraCodecs
|
const perCamera = cameraCodecs
|
||||||
.map((entry) => ({ ...entry, preset: family.presets[entry.codec] }))
|
.map((entry) => ({ ...entry, preset: family.presets[entry.codec] }))
|
||||||
.filter((entry) => entry.preset);
|
.filter((entry) => entry.preset);
|
||||||
|
|
||||||
if (perCamera.length === 0) {
|
if (perCamera.length === 0) {
|
||||||
// no camera to match, so fall back to the family's first preset
|
|
||||||
const fallback = Object.values(family.presets)[0];
|
const fallback = Object.values(family.presets)[0];
|
||||||
return fallback ? { ffmpeg: { hwaccel_args: fallback } } : null;
|
return fallback ? { ffmpeg: { hwaccel_args: fallback } } : null;
|
||||||
}
|
}
|
||||||
@@ -114,12 +108,11 @@ export default function SetupHwAccel({
|
|||||||
const presets = new Set(perCamera.map((entry) => entry.preset));
|
const presets = new Set(perCamera.map((entry) => entry.preset));
|
||||||
|
|
||||||
if (presets.size === 1 && perCamera.length === cameraCodecs.length) {
|
if (presets.size === 1 && perCamera.length === cameraCodecs.length) {
|
||||||
// every camera wants the same preset, so one global value says it
|
|
||||||
return { ffmpeg: { hwaccel_args: [...presets][0] } };
|
return { ffmpeg: { hwaccel_args: [...presets][0] } };
|
||||||
}
|
}
|
||||||
|
|
||||||
// the global stays on auto, so cameras added later still get resolved
|
// the global stays on auto so cameras added later resolve at startup
|
||||||
// at startup rather than inheriting one camera's codec
|
// instead of inheriting one camera's codec
|
||||||
return {
|
return {
|
||||||
cameras: Object.fromEntries(
|
cameras: Object.fromEntries(
|
||||||
perCamera.map((entry) => [
|
perCamera.map((entry) => [
|
||||||
@@ -137,13 +130,12 @@ export default function SetupHwAccel({
|
|||||||
|
|
||||||
const configData =
|
const configData =
|
||||||
selected === NONE
|
selected === NONE
|
||||||
? // an empty string would make config/set delete the key (reviving the
|
? // an empty string would make config/set delete the key, reviving
|
||||||
// "auto" default), so an explicit no-hwaccel is an empty list
|
// the "auto" default
|
||||||
{ ffmpeg: { hwaccel_args: [] } }
|
{ ffmpeg: { hwaccel_args: [] } }
|
||||||
: configFor(families.find((family) => family.key === key));
|
: configFor(families.find((family) => family.key === key));
|
||||||
|
|
||||||
// Auto with nothing derived writes nothing: the config default of "auto"
|
// nothing to write leaves the config default of "auto" in place
|
||||||
// stays in place and the backend decides at startup
|
|
||||||
if (!configData) {
|
if (!configData) {
|
||||||
onNext(false);
|
onNext(false);
|
||||||
return;
|
return;
|
||||||
|
|||||||
@@ -35,7 +35,6 @@ export default function SetupRecording({
|
|||||||
|
|
||||||
const { data: stats } = useSWR("stats", { revalidateOnFocus: false });
|
const { data: stats } = useSWR("stats", { revalidateOnFocus: false });
|
||||||
|
|
||||||
// Calculate storage estimate
|
|
||||||
const storageInfo = stats?.service?.storage?.["/tmp/frigate/recordings"];
|
const storageInfo = stats?.service?.storage?.["/tmp/frigate/recordings"];
|
||||||
const freeGb = storageInfo ? Math.round(storageInfo.free / 1024) : null;
|
const freeGb = storageInfo ? Math.round(storageInfo.free / 1024) : null;
|
||||||
const cameraCount = cameraNames.length;
|
const cameraCount = cameraNames.length;
|
||||||
@@ -53,8 +52,7 @@ export default function SetupRecording({
|
|||||||
if (enabled) {
|
if (enabled) {
|
||||||
record.alerts = { retain: { days: retentionDays } };
|
record.alerts = { retain: { days: retentionDays } };
|
||||||
record.detections = { retain: { days: retentionDays } };
|
record.detections = { retain: { days: retentionDays } };
|
||||||
// continuous keeps every segment, so it is the one that needs turning
|
// written even when off, so switching modes back turns it off again
|
||||||
// on. Its default of 0 already means "only what was detected".
|
|
||||||
record.continuous = { days: mode === CONTINUOUS ? retentionDays : 0 };
|
record.continuous = { days: mode === CONTINUOUS ? retentionDays : 0 };
|
||||||
}
|
}
|
||||||
|
|
||||||
@@ -140,8 +138,7 @@ export default function SetupRecording({
|
|||||||
min={1}
|
min={1}
|
||||||
max={365}
|
max={365}
|
||||||
value={retentionDays}
|
value={retentionDays}
|
||||||
// the spinner arrows are noise at this size, and the field is
|
// drop the spinner arrows; typing and arrow keys still work
|
||||||
// still typeable and arrow-key steppable without them
|
|
||||||
className="[appearance:textfield] [&::-webkit-inner-spin-button]:appearance-none [&::-webkit-outer-spin-button]:appearance-none"
|
className="[appearance:textfield] [&::-webkit-inner-spin-button]:appearance-none [&::-webkit-outer-spin-button]:appearance-none"
|
||||||
onChange={(e) =>
|
onChange={(e) =>
|
||||||
setRetentionDays(Math.max(1, parseInt(e.target.value) || 1))
|
setRetentionDays(Math.max(1, parseInt(e.target.value) || 1))
|
||||||
|
|||||||
@@ -24,10 +24,9 @@ type WizardState = {
|
|||||||
currentStep: number;
|
currentStep: number;
|
||||||
cameraNames: string[];
|
cameraNames: string[];
|
||||||
detectorHardwareKey?: string;
|
detectorHardwareKey?: string;
|
||||||
// detect stream codec of each added camera, keyed by camera name
|
// camera name -> detect stream codec
|
||||||
detectCodecs: Record<string, string>;
|
detectCodecs: Record<string, string>;
|
||||||
// whether any step wrote config with requires_restart, which only takes
|
// camera adds apply live, so they don't count toward needing a restart
|
||||||
// effect after a restart (camera adds apply live and don't count)
|
|
||||||
restartRequired: boolean;
|
restartRequired: boolean;
|
||||||
configuredSteps: {
|
configuredSteps: {
|
||||||
camera: boolean;
|
camera: boolean;
|
||||||
|
|||||||
@@ -14,8 +14,7 @@ export type DetectionHardware = {
|
|||||||
|
|
||||||
export type HwaccelFamily = {
|
export type HwaccelFamily = {
|
||||||
key: string;
|
key: string;
|
||||||
// the preset for each codec this family decodes, or a single "any" preset
|
// keyed by codec, or a single "any" preset when it decodes every codec
|
||||||
// when it decodes every codec
|
|
||||||
presets: Record<string, string>;
|
presets: Record<string, string>;
|
||||||
};
|
};
|
||||||
|
|
||||||
|
|||||||
Reference in New Issue
Block a user