mirror of
https://github.com/blakeblackshear/frigate.git
synced 2026-03-07 02:18:07 +01:00
Camera wizard improvements (#20636)
* use avg_frame_rate * probe metadata and snapshot separately * improve ffprobe error reporting * show error messages in toaster
This commit is contained in:
@@ -65,6 +65,7 @@ export default function Step1NameCamera({
|
||||
const { data: config } = useSWR<FrigateConfig>("config");
|
||||
const [showPassword, setShowPassword] = useState(false);
|
||||
const [isTesting, setIsTesting] = useState(false);
|
||||
const [testStatus, setTestStatus] = useState<string>("");
|
||||
const [testResult, setTestResult] = useState<TestResult | null>(null);
|
||||
|
||||
const existingCameraNames = useMemo(() => {
|
||||
@@ -88,7 +89,13 @@ export default function Step1NameCamera({
|
||||
username: z.string().optional(),
|
||||
password: z.string().optional(),
|
||||
brandTemplate: z.enum(CAMERA_BRAND_VALUES).optional(),
|
||||
customUrl: z.string().optional(),
|
||||
customUrl: z
|
||||
.string()
|
||||
.optional()
|
||||
.refine(
|
||||
(val) => !val || val.startsWith("rtsp://"),
|
||||
t("cameraWizard.step1.errors.customUrlRtspRequired"),
|
||||
),
|
||||
})
|
||||
.refine(
|
||||
(data) => {
|
||||
@@ -204,24 +211,17 @@ export default function Step1NameCamera({
|
||||
}
|
||||
|
||||
setIsTesting(true);
|
||||
setTestStatus("");
|
||||
setTestResult(null);
|
||||
|
||||
// First get probe data for metadata
|
||||
const probePromise = axios.get("ffprobe", {
|
||||
params: { paths: streamUrl, detailed: true },
|
||||
timeout: 10000,
|
||||
});
|
||||
|
||||
// Then get snapshot for preview
|
||||
const snapshotPromise = axios.get("ffprobe/snapshot", {
|
||||
params: { url: streamUrl },
|
||||
responseType: "blob",
|
||||
timeout: 10000,
|
||||
});
|
||||
|
||||
try {
|
||||
// First get probe data for metadata
|
||||
const probeResponse = await probePromise;
|
||||
setTestStatus(t("cameraWizard.step1.testing.probingMetadata"));
|
||||
const probeResponse = await axios.get("ffprobe", {
|
||||
params: { paths: streamUrl, detailed: true },
|
||||
timeout: 10000,
|
||||
});
|
||||
|
||||
let probeData = null;
|
||||
if (
|
||||
probeResponse.data &&
|
||||
@@ -234,8 +234,13 @@ export default function Step1NameCamera({
|
||||
// Then get snapshot for preview (only if probe succeeded)
|
||||
let snapshotBlob = null;
|
||||
if (probeData) {
|
||||
setTestStatus(t("cameraWizard.step1.testing.fetchingSnapshot"));
|
||||
try {
|
||||
const snapshotResponse = await snapshotPromise;
|
||||
const snapshotResponse = await axios.get("ffprobe/snapshot", {
|
||||
params: { url: streamUrl },
|
||||
responseType: "blob",
|
||||
timeout: 10000,
|
||||
});
|
||||
snapshotBlob = snapshotResponse.data;
|
||||
} catch (snapshotError) {
|
||||
// Snapshot is optional, don't fail if it doesn't work
|
||||
@@ -295,12 +300,18 @@ export default function Step1NameCamera({
|
||||
setTestResult(testResult);
|
||||
toast.success(t("cameraWizard.step1.testSuccess"));
|
||||
} else {
|
||||
const error = probeData?.stderr || "Unknown error";
|
||||
const error =
|
||||
Array.isArray(probeResponse.data?.[0]?.stderr) &&
|
||||
probeResponse.data[0].stderr.length > 0
|
||||
? probeResponse.data[0].stderr.join("\n")
|
||||
: "Unable to probe stream";
|
||||
setTestResult({
|
||||
success: false,
|
||||
error: error,
|
||||
});
|
||||
toast.error(t("cameraWizard.commonErrors.testFailed", { error }));
|
||||
toast.error(t("cameraWizard.commonErrors.testFailed", { error }), {
|
||||
duration: 6000,
|
||||
});
|
||||
}
|
||||
} catch (error) {
|
||||
const axiosError = error as {
|
||||
@@ -318,9 +329,13 @@ export default function Step1NameCamera({
|
||||
});
|
||||
toast.error(
|
||||
t("cameraWizard.commonErrors.testFailed", { error: errorMessage }),
|
||||
{
|
||||
duration: 10000,
|
||||
},
|
||||
);
|
||||
} finally {
|
||||
setIsTesting(false);
|
||||
setTestStatus("");
|
||||
}
|
||||
}, [form, generateStreamUrl, t]);
|
||||
|
||||
@@ -610,7 +625,9 @@ export default function Step1NameCamera({
|
||||
className="flex items-center justify-center gap-2 sm:flex-1"
|
||||
>
|
||||
{isTesting && <ActivityIndicator className="size-4" />}
|
||||
{t("cameraWizard.step1.testConnection")}
|
||||
{isTesting && testStatus
|
||||
? testStatus
|
||||
: t("cameraWizard.step1.testConnection")}
|
||||
</Button>
|
||||
)}
|
||||
</div>
|
||||
|
||||
@@ -151,9 +151,9 @@ export default function Step2StreamConfig({
|
||||
? `${videoStream.width}x${videoStream.height}`
|
||||
: undefined;
|
||||
|
||||
const fps = videoStream?.r_frame_rate
|
||||
? parseFloat(videoStream.r_frame_rate.split("/")[0]) /
|
||||
parseFloat(videoStream.r_frame_rate.split("/")[1])
|
||||
const fps = videoStream?.avg_frame_rate
|
||||
? parseFloat(videoStream.avg_frame_rate.split("/")[0]) /
|
||||
parseFloat(videoStream.avg_frame_rate.split("/")[1])
|
||||
: undefined;
|
||||
|
||||
const testResult: TestResult = {
|
||||
|
||||
@@ -85,9 +85,9 @@ export default function Step3Validation({
|
||||
? `${videoStream.width}x${videoStream.height}`
|
||||
: undefined;
|
||||
|
||||
const fps = videoStream?.r_frame_rate
|
||||
? parseFloat(videoStream.r_frame_rate.split("/")[0]) /
|
||||
parseFloat(videoStream.r_frame_rate.split("/")[1])
|
||||
const fps = videoStream?.avg_frame_rate
|
||||
? parseFloat(videoStream.avg_frame_rate.split("/")[0]) /
|
||||
parseFloat(videoStream.avg_frame_rate.split("/")[1])
|
||||
: undefined;
|
||||
|
||||
return {
|
||||
@@ -323,7 +323,7 @@ export default function Step3Validation({
|
||||
)}
|
||||
|
||||
<div className="mb-2 flex flex-col justify-between gap-1 md:flex-row md:items-center">
|
||||
<span className="text-sm text-muted-foreground">
|
||||
<span className="break-all text-sm text-muted-foreground">
|
||||
{stream.url}
|
||||
</span>
|
||||
<Button
|
||||
|
||||
Reference in New Issue
Block a user