From 3bbe24f5f8beebd0b25b0468a944cec19578aedf Mon Sep 17 00:00:00 2001 From: Josh Hawkins <32435876+hawkeye217@users.noreply.github.com> Date: Tue, 25 Nov 2025 07:34:20 -0600 Subject: [PATCH] Miscellaneous Fixes (#21033) * catch failed image embedding in triggers * move scrollbar to edge on platform aware dialog drawers * add i18n key * show negotiated mse codecs in console on error * try changing rocm * Improve toast consistency * add attribute area and score to detail stream tooltip --------- Co-authored-by: Nicolas Mowen --- docker/rocm/rocm.hcl | 2 +- docs/docs/configuration/live.md | 2 +- frigate/api/event.py | 4 +- web/public/locales/en/components/dialog.json | 1 + web/public/locales/en/views/explore.json | 3 +- web/src/components/card/ReviewCard.tsx | 2 +- .../wizard/Step3ChooseExamples.tsx | 4 +- .../components/filter/SearchFilterGroup.tsx | 18 ++- web/src/components/overlay/ExportDialog.tsx | 2 +- .../overlay/MobileReviewSettingsDrawer.tsx | 4 +- .../overlay/dialog/PlatformAwareDialog.tsx | 4 +- web/src/components/player/MsePlayer.tsx | 9 +- .../components/settings/SearchSettings.tsx | 9 +- web/src/components/timeline/DetailStream.tsx | 109 +++++++++++++++--- web/src/pages/FaceLibrary.tsx | 1 + .../classification/ModelTrainingView.tsx | 4 + web/src/views/events/EventView.tsx | 4 +- 17 files changed, 136 insertions(+), 46 deletions(-) diff --git a/docker/rocm/rocm.hcl b/docker/rocm/rocm.hcl index 6a52b9578..0e9ae2c5c 100644 --- a/docker/rocm/rocm.hcl +++ b/docker/rocm/rocm.hcl @@ -2,7 +2,7 @@ variable "AMDGPU" { default = "gfx900" } variable "ROCM" { - default = "7.1" + default = "7.1.0" } variable "HSA_OVERRIDE_GFX_VERSION" { default = "" diff --git a/docs/docs/configuration/live.md b/docs/docs/configuration/live.md index 8c310634a..be467f7f8 100644 --- a/docs/docs/configuration/live.md +++ b/docs/docs/configuration/live.md @@ -232,7 +232,7 @@ When your browser runs into problems playing back your camera streams, it will l - **mse-decode** - What it means: The browser reported a decoding error while trying to play the stream, which usually is a result of a codec incompatibility or corrupted frames. - - What to try: Ensure your camera/restream is using H.264 video and AAC audio (these are the most compatible). If your camera uses a non-standard audio codec, configure `go2rtc` to transcode the stream to AAC. Try another browser (some browsers have stricter MSE/codec support) and, for iPhone, ensure you're on iOS 17.1 or newer. + - What to try: Check the browser console for the supported and negotiated codecs. Ensure your camera/restream is using H.264 video and AAC audio (these are the most compatible). If your camera uses a non-standard audio codec, configure `go2rtc` to transcode the stream to AAC. Try another browser (some browsers have stricter MSE/codec support) and, for iPhone, ensure you're on iOS 17.1 or newer. - Possible console messages from the player code: diff --git a/frigate/api/event.py b/frigate/api/event.py index 13886af13..c084a8971 100644 --- a/frigate/api/event.py +++ b/frigate/api/event.py @@ -1753,7 +1753,7 @@ def create_trigger_embedding( body.data, (base64.b64encode(thumbnail).decode("ASCII")) ) - if embedding is None: + if not embedding: return JSONResponse( content={ "success": False, @@ -1888,7 +1888,7 @@ def update_trigger_embedding( body.data, (base64.b64encode(thumbnail).decode("ASCII")) ) - if embedding is None: + if not embedding: return JSONResponse( content={ "success": False, diff --git a/web/public/locales/en/components/dialog.json b/web/public/locales/en/components/dialog.json index a40e62db7..a56c2b1da 100644 --- a/web/public/locales/en/components/dialog.json +++ b/web/public/locales/en/components/dialog.json @@ -53,6 +53,7 @@ "selectOrExport": "Select or Export", "toast": { "success": "Successfully started export. View the file in the exports page.", + "view": "View", "error": { "failed": "Failed to start export: {{error}}", "endTimeMustAfterStartTime": "End time must be after start time", diff --git a/web/public/locales/en/views/explore.json b/web/public/locales/en/views/explore.json index 19559a43a..5335aa5ac 100644 --- a/web/public/locales/en/views/explore.json +++ b/web/public/locales/en/views/explore.json @@ -61,7 +61,8 @@ "header": { "zones": "Zones", "ratio": "Ratio", - "area": "Area" + "area": "Area", + "score": "Score" } }, "annotationSettings": { diff --git a/web/src/components/card/ReviewCard.tsx b/web/src/components/card/ReviewCard.tsx index 8fc4024db..e8d8121a8 100644 --- a/web/src/components/card/ReviewCard.tsx +++ b/web/src/components/card/ReviewCard.tsx @@ -87,7 +87,7 @@ export default function ReviewCard({ position: "top-center", action: ( - + ), }); diff --git a/web/src/components/classification/wizard/Step3ChooseExamples.tsx b/web/src/components/classification/wizard/Step3ChooseExamples.tsx index e4c157526..6e4311cec 100644 --- a/web/src/components/classification/wizard/Step3ChooseExamples.tsx +++ b/web/src/components/classification/wizard/Step3ChooseExamples.tsx @@ -148,7 +148,9 @@ export default function Step3ChooseExamples({ // Step 3: Kick off training await axios.post(`/classification/${step1Data.modelName}/train`); - toast.success(t("wizard.step3.trainingStarted")); + toast.success(t("wizard.step3.trainingStarted"), { + closeButton: true, + }); setIsTraining(true); }, [step1Data, step2Data, t], diff --git a/web/src/components/filter/SearchFilterGroup.tsx b/web/src/components/filter/SearchFilterGroup.tsx index d049480c6..3c44cad0c 100644 --- a/web/src/components/filter/SearchFilterGroup.tsx +++ b/web/src/components/filter/SearchFilterGroup.tsx @@ -314,11 +314,10 @@ function GeneralFilterButton({ { if (!open) { @@ -510,11 +509,10 @@ function SortTypeButton({ { if (!open) { diff --git a/web/src/components/overlay/ExportDialog.tsx b/web/src/components/overlay/ExportDialog.tsx index 832e9faa9..976b20042 100644 --- a/web/src/components/overlay/ExportDialog.tsx +++ b/web/src/components/overlay/ExportDialog.tsx @@ -97,7 +97,7 @@ export default function ExportDialog({ position: "top-center", action: ( - + ), }); diff --git a/web/src/components/overlay/MobileReviewSettingsDrawer.tsx b/web/src/components/overlay/MobileReviewSettingsDrawer.tsx index d7f0c7ae6..78827a99e 100644 --- a/web/src/components/overlay/MobileReviewSettingsDrawer.tsx +++ b/web/src/components/overlay/MobileReviewSettingsDrawer.tsx @@ -106,7 +106,9 @@ export default function MobileReviewSettingsDrawer({ position: "top-center", action: ( - + ), }, diff --git a/web/src/components/overlay/dialog/PlatformAwareDialog.tsx b/web/src/components/overlay/dialog/PlatformAwareDialog.tsx index ab69e1288..e0ec6efa9 100644 --- a/web/src/components/overlay/dialog/PlatformAwareDialog.tsx +++ b/web/src/components/overlay/dialog/PlatformAwareDialog.tsx @@ -44,8 +44,8 @@ export default function PlatformAwareDialog({ return ( {trigger} - - {content} + +
{content}
); diff --git a/web/src/components/player/MsePlayer.tsx b/web/src/components/player/MsePlayer.tsx index 576fc93d6..b7d1aab01 100644 --- a/web/src/components/player/MsePlayer.tsx +++ b/web/src/components/player/MsePlayer.tsx @@ -94,12 +94,19 @@ function MSEPlayer({ console.error( `${camera} - MSE error '${error}': ${description} See the documentation: https://docs.frigate.video/configuration/live/#live-player-error-messages`, ); + if (mseCodecRef.current) { // eslint-disable-next-line no-console - console.error(`${camera} - MSE codec in use: ${mseCodecRef.current}`); + console.error( + `${camera} - Browser negotiated codecs: ${mseCodecRef.current}`, + ); + // eslint-disable-next-line no-console + console.error(`${camera} - Supported codecs: ${CODECS.join(", ")}`); } onError?.(error); }, + // we know that these deps are correct + // eslint-disable-next-line react-hooks/exhaustive-deps [camera, onError], ); diff --git a/web/src/components/settings/SearchSettings.tsx b/web/src/components/settings/SearchSettings.tsx index 74301ee1d..54d1f801a 100644 --- a/web/src/components/settings/SearchSettings.tsx +++ b/web/src/components/settings/SearchSettings.tsx @@ -136,11 +136,10 @@ export default function ExploreSettings({ { setOpen(open); diff --git a/web/src/components/timeline/DetailStream.tsx b/web/src/components/timeline/DetailStream.tsx index d133aa05a..be01eb16e 100644 --- a/web/src/components/timeline/DetailStream.tsx +++ b/web/src/components/timeline/DetailStream.tsx @@ -681,22 +681,62 @@ function LifecycleItem({ }) : ""; - const ratio = - Array.isArray(item?.data.box) && item?.data.box.length >= 4 - ? (aspectRatio * (item?.data.box[2] / item?.data.box[3])).toFixed(2) - : "N/A"; - const areaPx = - Array.isArray(item?.data.box) && item?.data.box.length >= 4 - ? Math.round( - (config?.cameras[item?.camera]?.detect?.width ?? 0) * - (config?.cameras[item?.camera]?.detect?.height ?? 0) * - (item?.data.box[2] * item?.data.box[3]), - ) - : undefined; - const areaPct = - Array.isArray(item?.data.box) && item?.data.box.length >= 4 - ? (item?.data.box[2] * item?.data.box[3]).toFixed(4) - : undefined; + const ratio = useMemo( + () => + Array.isArray(item?.data.box) && item?.data.box.length >= 4 + ? (aspectRatio * (item?.data.box[2] / item?.data.box[3])).toFixed(2) + : "N/A", + [aspectRatio, item], + ); + + const areaPx = useMemo( + () => + Array.isArray(item?.data.box) && item?.data.box.length >= 4 + ? Math.round( + (config?.cameras[item?.camera]?.detect?.width ?? 0) * + (config?.cameras[item?.camera]?.detect?.height ?? 0) * + (item?.data.box[2] * item?.data.box[3]), + ) + : undefined, + [config, item], + ); + + const areaPct = useMemo( + () => + Array.isArray(item?.data.box) && item?.data.box.length >= 4 + ? (item?.data.box[2] * item?.data.box[3]).toFixed(4) + : undefined, + [item], + ); + + const attributeAreaPx = useMemo( + () => + Array.isArray(item?.data.attribute_box) && + item?.data.attribute_box.length >= 4 + ? Math.round( + (config?.cameras[item?.camera]?.detect?.width ?? 0) * + (config?.cameras[item?.camera]?.detect?.height ?? 0) * + (item?.data.attribute_box[2] * item?.data.attribute_box[3]), + ) + : undefined, + [config, item], + ); + + const attributeAreaPct = useMemo( + () => + Array.isArray(item?.data.attribute_box) && + item?.data.attribute_box.length >= 4 + ? (item?.data.attribute_box[2] * item?.data.attribute_box[3]).toFixed(4) + : undefined, + [item], + ); + + const score = useMemo(() => { + if (item?.data?.score !== undefined) { + return (item.data.score * 100).toFixed(0) + "%"; + } + return "N/A"; + }, [item?.data?.score]); return (
+
+ + {t("trackingDetails.lifecycleItemDesc.header.score")} + + {score} +
+
{t("trackingDetails.lifecycleItemDesc.header.ratio")} @@ -742,7 +789,13 @@ function LifecycleItem({
- {t("trackingDetails.lifecycleItemDesc.header.area")} + {t("trackingDetails.lifecycleItemDesc.header.area")}{" "} + {attributeAreaPx !== undefined && + attributeAreaPct !== undefined && ( + + ({getTranslatedLabel(item.data.label)}) + + )} {areaPx !== undefined && areaPct !== undefined ? ( @@ -754,6 +807,26 @@ function LifecycleItem({ N/A )}
+ + {attributeAreaPx !== undefined && + attributeAreaPct !== undefined && ( +
+ + {t("trackingDetails.lifecycleItemDesc.header.area")}{" "} + {attributeAreaPx !== undefined && + attributeAreaPct !== undefined && ( + + ({getTranslatedLabel(item.data.attribute)}) + + )} + + + {attributeAreaPx} {t("pixels", { ns: "common" })}{" "} + ยท{" "} + {attributeAreaPct}% + +
+ )}
@@ -820,7 +893,7 @@ function ObjectTimeline({ }, [config, fullTimeline, review]); if (isValidating && (!timeline || timeline.length === 0)) { - return ; + return ; } if (!timeline || timeline.length === 0) { diff --git a/web/src/pages/FaceLibrary.tsx b/web/src/pages/FaceLibrary.tsx index 78e0e5760..439a9bde4 100644 --- a/web/src/pages/FaceLibrary.tsx +++ b/web/src/pages/FaceLibrary.tsx @@ -808,6 +808,7 @@ function FaceAttemptGroup({ if (resp.status == 200) { toast.success(t("toast.success.trainedFace"), { position: "top-center", + closeButton: true, }); onRefresh(); } diff --git a/web/src/views/classification/ModelTrainingView.tsx b/web/src/views/classification/ModelTrainingView.tsx index 53328e0e2..d6d03c6cc 100644 --- a/web/src/views/classification/ModelTrainingView.tsx +++ b/web/src/views/classification/ModelTrainingView.tsx @@ -104,12 +104,14 @@ export default function ModelTrainingView({ model }: ModelTrainingViewProps) { if (modelState == "complete") { toast.success(t("toast.success.trainedModel"), { position: "top-center", + closeButton: true, }); setWasTraining(false); refreshDataset(); } else if (modelState == "failed") { toast.error(t("toast.error.trainingFailed"), { position: "top-center", + closeButton: true, }); setWasTraining(false); } @@ -182,6 +184,7 @@ export default function ModelTrainingView({ model }: ModelTrainingViewProps) { setWasTraining(true); toast.success(t("toast.success.trainingModel"), { position: "top-center", + closeButton: true, }); } }) @@ -193,6 +196,7 @@ export default function ModelTrainingView({ model }: ModelTrainingViewProps) { toast.error(t("toast.error.trainingFailedToStart", { errorMessage }), { position: "top-center", + closeButton: true, }); }); }, [model, t]); diff --git a/web/src/views/events/EventView.tsx b/web/src/views/events/EventView.tsx index d054dabc9..417c3231d 100644 --- a/web/src/views/events/EventView.tsx +++ b/web/src/views/events/EventView.tsx @@ -219,7 +219,9 @@ export default function EventView({ position: "top-center", action: ( - + ), },