Enable event snapshot API to honour query params after event ends (#22375)

* Enable event snapshot API to honour query params

* fix unused imports

* Fixes

* Run ruff check --fix

* Web changes

* Further config and web fixes

* Further docs tweak

* Fix missing quality default in MediaEventsSnapshotQueryParams

* Manual events: don't save annotated jpeg; store frame time

* Remove unnecessary grayscale helper

* Add caveat to docs on snapshot_frame_time pre-0.18

* JPG snapshot should not be treated as clean

* Ensure tracked details uses uncropped, bbox'd snapshot

* Ensure all UI pages / menu actions use uncropped, bbox'd

* web lint

* Add missed config helper text

* Expect  SnapshotsConfig not Any

* docs: Remove pre-0.18 note

* Specify timestamp=0 in the UI

* Move tests out of http media

* Correct missed settings.json wording

Co-authored-by: Josh Hawkins <32435876+hawkeye217@users.noreply.github.com>

* Revert to default None for quality

* Correct camera snapshot config wording

Co-authored-by: Josh Hawkins <32435876+hawkeye217@users.noreply.github.com>

* Fix quality=0 handling

Co-authored-by: Josh Hawkins <32435876+hawkeye217@users.noreply.github.com>

* Fix quality=0 handling #2

Co-authored-by: Josh Hawkins <32435876+hawkeye217@users.noreply.github.com>

* ReRun generate_config_translations

---------

Co-authored-by: leccelecce <example@example.com>
Co-authored-by: Josh Hawkins <32435876+hawkeye217@users.noreply.github.com>
This commit is contained in:
leccelecce
2026-03-22 19:33:04 +00:00
committed by GitHub
parent b6c03c99de
commit ec7040bed5
32 changed files with 797 additions and 475 deletions

View File

@@ -159,25 +159,24 @@ export default function SearchResultActions({
<MenuItem aria-label={t("itemMenu.downloadSnapshot.aria")}>
<a
className="flex items-center"
href={`${baseUrl}api/events/${searchResult.id}/snapshot.jpg`}
href={`${baseUrl}api/events/${searchResult.id}/snapshot.jpg?crop=0&bbox=1&timestamp=0`}
download={`${searchResult.camera}_${searchResult.label}.jpg`}
>
<span>{t("itemMenu.downloadSnapshot.label")}</span>
</a>
</MenuItem>
)}
{searchResult.has_snapshot &&
config?.cameras[searchResult.camera].snapshots.clean_copy && (
<MenuItem aria-label={t("itemMenu.downloadCleanSnapshot.aria")}>
<a
className="flex items-center"
href={`${baseUrl}api/events/${searchResult.id}/snapshot-clean.webp`}
download={`${searchResult.camera}_${searchResult.label}-clean.webp`}
>
<span>{t("itemMenu.downloadCleanSnapshot.label")}</span>
</a>
</MenuItem>
)}
{searchResult.has_snapshot && (
<MenuItem aria-label={t("itemMenu.downloadCleanSnapshot.aria")}>
<a
className="flex items-center"
href={`${baseUrl}api/events/${searchResult.id}/snapshot-clean.webp`}
download={`${searchResult.camera}_${searchResult.label}-clean.webp`}
>
<span>{t("itemMenu.downloadCleanSnapshot.label")}</span>
</a>
</MenuItem>
)}
{searchResult.data.type == "object" && (
<MenuItem
aria-label={t("itemMenu.viewTrackingDetails.aria")}

View File

@@ -85,7 +85,7 @@ export default function DetailActionsMenu({
<DropdownMenuItem>
<a
className="w-full"
href={`${baseUrl}api/events/${search.id}/snapshot.jpg?bbox=1`}
href={`${baseUrl}api/events/${search.id}/snapshot.jpg?crop=0&bbox=1&timestamp=0`}
download={`${search.camera}_${search.label}.jpg`}
>
<div className="flex cursor-pointer items-center gap-2">
@@ -94,20 +94,19 @@ export default function DetailActionsMenu({
</a>
</DropdownMenuItem>
)}
{search.has_snapshot &&
config?.cameras[search.camera].snapshots.clean_copy && (
<DropdownMenuItem>
<a
className="w-full"
href={`${baseUrl}api/events/${search.id}/snapshot-clean.webp`}
download={`${search.camera}_${search.label}-clean.webp`}
>
<div className="flex cursor-pointer items-center gap-2">
<span>{t("itemMenu.downloadCleanSnapshot.label")}</span>
</div>
</a>
</DropdownMenuItem>
)}
{search.has_snapshot && (
<DropdownMenuItem>
<a
className="w-full"
href={`${baseUrl}api/events/${search.id}/snapshot-clean.webp`}
download={`${search.camera}_${search.label}-clean.webp`}
>
<div className="flex cursor-pointer items-center gap-2">
<span>{t("itemMenu.downloadCleanSnapshot.label")}</span>
</div>
</a>
</DropdownMenuItem>
)}
{search.has_clip && (
<DropdownMenuItem>
<a

View File

@@ -1839,7 +1839,7 @@ export function ObjectSnapshotTab({
<img
ref={imgRef}
className="mx-auto max-h-[60dvh] rounded-lg bg-background object-contain"
src={`${baseUrl}api/events/${search?.id}/snapshot.jpg`}
src={`${baseUrl}api/events/${search?.id}/snapshot.jpg?crop=0&bbox=1&timestamp=0`}
alt={`${search?.label}`}
loading={isSafari ? "eager" : "lazy"}
onLoad={() => {

View File

@@ -107,7 +107,7 @@ export function FrigatePlusDialog({
<img
ref={imgRef}
className="mx-auto max-h-[60dvh] rounded-lg bg-black object-contain"
src={`${baseUrl}api/events/${upload.id}/snapshot.jpg`}
src={`${baseUrl}api/events/${upload.id}/snapshot.jpg?crop=0&bbox=1&timestamp=0`}
alt={`${upload.label}`}
loading={isSafari ? "eager" : "lazy"}
onLoad={onImgLoad}

View File

@@ -136,7 +136,7 @@ export default function EventMenu({
download
href={
event.has_snapshot
? `${apiHost}api/events/${event.id}/snapshot.jpg`
? `${apiHost}api/events/${event.id}/snapshot.jpg?crop=0&bbox=1&timestamp=0`
: `${apiHost}api/events/${event.id}/thumbnail.webp`
}
>

View File

@@ -273,7 +273,6 @@ export interface CameraConfig {
};
snapshots: {
bounding_box: boolean;
clean_copy: boolean;
crop: boolean;
enabled: boolean;
height: number | null;
@@ -615,7 +614,6 @@ export interface FrigateConfig {
snapshots: {
bounding_box: boolean;
clean_copy: boolean;
crop: boolean;
enabled: boolean;
height: number | null;

View File

@@ -8,7 +8,6 @@ import axios from "axios";
import { FrigateConfig } from "@/types/frigateConfig";
import { CheckCircle2, XCircle } from "lucide-react";
import { Trans, useTranslation } from "react-i18next";
import { IoIosWarning } from "react-icons/io";
import { Button } from "@/components/ui/button";
import { Link } from "react-router-dom";
import { LuExternalLink } from "react-icons/lu";
@@ -197,15 +196,6 @@ export default function FrigatePlusSettingsView({
document.title = t("documentTitle.frigatePlus");
}, [t]);
const needCleanSnapshots = () => {
if (!config) {
return false;
}
return Object.values(config.cameras).some(
(camera) => camera.snapshots.enabled && !camera.snapshots.clean_copy,
);
};
if (!config) {
return <ActivityIndicator />;
}
@@ -415,11 +405,6 @@ export default function FrigatePlusSettingsView({
"frigatePlus.snapshotConfig.table.snapshots",
)}
</th>
<th className="px-4 py-2 text-center">
<Trans ns="views/settings">
frigatePlus.snapshotConfig.table.cleanCopySnapshots
</Trans>
</th>
</tr>
</thead>
<tbody>
@@ -439,32 +424,12 @@ export default function FrigatePlusSettingsView({
<XCircle className="mx-auto size-5 text-danger" />
)}
</td>
<td className="px-4 py-2 text-center">
{camera.snapshots?.enabled &&
camera.snapshots?.clean_copy ? (
<CheckCircle2 className="mx-auto size-5 text-green-500" />
) : (
<XCircle className="mx-auto size-5 text-danger" />
)}
</td>
</tr>
),
)}
</tbody>
</table>
</div>
{needCleanSnapshots() && (
<div className="rounded-lg border border-secondary-foreground bg-secondary p-4 text-sm text-danger">
<div className="flex items-center gap-2">
<IoIosWarning className="mr-2 size-5 text-danger" />
<div className="max-w-[85%] text-sm">
<Trans ns="views/settings">
frigatePlus.snapshotConfig.cleanCopyWarning
</Trans>
</div>
</div>
</div>
)}
</div>
}
/>