From e50a1eb9055632f88363ba83ef2fe579079faf0b Mon Sep 17 00:00:00 2001 From: Gergely Szell Date: Mon, 4 Nov 2024 16:48:35 +0100 Subject: [PATCH] Add option to export Timelapses from History view --- docs/docs/configuration/record.md | 2 +- web/src/components/overlay/ExportDialog.tsx | 41 +++++++++++++++++-- .../overlay/MobileReviewSettingsDrawer.tsx | 14 +++++-- 3 files changed, 49 insertions(+), 8 deletions(-) diff --git a/docs/docs/configuration/record.md b/docs/docs/configuration/record.md index fd7de42d0..ba22d62c3 100644 --- a/docs/docs/configuration/record.md +++ b/docs/docs/configuration/record.md @@ -154,7 +154,7 @@ Footage can be exported from Frigate by right-clicking (desktop) or long pressin ### Time-lapse export -Time lapse exporting is available only via the [HTTP API](../integrations/api/export-recording-export-camera-name-start-start-time-end-end-time-post.api.mdx). +Time lapse exporting is available while exporting from the History view, or via the [HTTP API](../integrations/api/export-recording-export-camera-name-start-start-time-end-end-time-post.api.mdx). When exporting a time-lapse the default speed-up is 25x with 30 FPS. This means that every 25 seconds of (real-time) recording is condensed into 1 second of time-lapse video (always without audio) with a smoothness of 30 FPS. diff --git a/web/src/components/overlay/ExportDialog.tsx b/web/src/components/overlay/ExportDialog.tsx index 8fc5eeea5..bd4365fba 100644 --- a/web/src/components/overlay/ExportDialog.tsx +++ b/web/src/components/overlay/ExportDialog.tsx @@ -11,6 +11,7 @@ import { import { Label } from "../ui/label"; import { RadioGroup, RadioGroupItem } from "../ui/radio-group"; import { Button } from "../ui/button"; +import { Checkbox } from "../ui/checkbox"; import { ExportMode } from "@/types/filter"; import { FaArrowDown, FaArrowRight, FaCalendarAlt } from "react-icons/fa"; import axios from "axios"; @@ -65,6 +66,7 @@ export default function ExportDialog({ setShowPreview, }: ExportDialogProps) { const [name, setName] = useState(""); + const [isTimelapse, setIsTimelapse] = useState(false); const onStartExport = useCallback(() => { if (!range) { @@ -83,7 +85,7 @@ export default function ExportDialog({ .post( `export/${camera}/start/${Math.round(range.after)}/end/${Math.round(range.before)}`, { - playback: "realtime", + playback: isTimelapse ? "timelapse_25x" : "realtime", name, }, ) @@ -96,6 +98,7 @@ export default function ExportDialog({ setName(""); setRange(undefined); setMode("none"); + setIsTimelapse(false); } }) .catch((error) => { @@ -110,7 +113,7 @@ export default function ExportDialog({ }); } }); - }, [camera, name, range, setRange, setName, setMode]); + }, [camera, name, range, isTimelapse, setRange, setName, setMode]); const Overlay = isDesktop ? Dialog : Drawer; const Trigger = isDesktop ? DialogTrigger : DrawerTrigger; @@ -129,13 +132,17 @@ export default function ExportDialog({ show={mode == "timeline"} onPreview={() => setShowPreview(true)} onSave={() => onStartExport()} - onCancel={() => setMode("none")} + onCancel={() => { + setMode("none"); + setIsTimelapse(false); + }} /> { if (!open) { setMode("none"); + setIsTimelapse(false); } }} > @@ -172,11 +179,16 @@ export default function ExportDialog({ currentTime={currentTime} range={range} name={name} + isTimelapse={isTimelapse} onStartExport={onStartExport} setName={setName} setRange={setRange} setMode={setMode} - onCancel={() => setMode("none")} + setIsTimelapse={setIsTimelapse} + onCancel={() => { + setMode("none"); + setIsTimelapse(false); + }} /> @@ -189,10 +201,12 @@ type ExportContentProps = { currentTime: number; range?: TimeRange; name: string; + isTimelapse: boolean; onStartExport: () => void; setName: (name: string) => void; setRange: (range: TimeRange | undefined) => void; setMode: (mode: ExportMode) => void; + setIsTimelapse: (isTimelapse: boolean) => void; onCancel: () => void; }; export function ExportContent({ @@ -200,10 +214,12 @@ export function ExportContent({ currentTime, range, name, + isTimelapse, onStartExport, setName, setRange, setMode, + setIsTimelapse, onCancel, }: ExportContentProps) { const [selectedOption, setSelectedOption] = useState("1"); @@ -289,6 +305,22 @@ export function ExportContent({ setRange={setRange} /> )} + {isDesktop && } +
+ setIsTimelapse(checked as boolean)} + /> + +
diff --git a/web/src/components/overlay/MobileReviewSettingsDrawer.tsx b/web/src/components/overlay/MobileReviewSettingsDrawer.tsx index d58d485b9..96300a2aa 100644 --- a/web/src/components/overlay/MobileReviewSettingsDrawer.tsx +++ b/web/src/components/overlay/MobileReviewSettingsDrawer.tsx @@ -62,6 +62,7 @@ export default function MobileReviewSettingsDrawer({ setShowExportPreview, }: MobileReviewSettingsDrawerProps) { const [drawerMode, setDrawerMode] = useState("none"); + const [isTimelapse, setIsTimelapse] = useState(false); // exports @@ -83,7 +84,7 @@ export default function MobileReviewSettingsDrawer({ .post( `export/${camera}/start/${Math.round(range.after)}/end/${Math.round(range.before)}`, { - playback: "realtime", + playback: isTimelapse ? "timelapse_25x" : "realtime", name, }, ) @@ -96,6 +97,7 @@ export default function MobileReviewSettingsDrawer({ setName(""); setRange(undefined); setMode("none"); + setIsTimelapse(false); } }) .catch((error) => { @@ -110,7 +112,7 @@ export default function MobileReviewSettingsDrawer({ }); } }); - }, [camera, name, range, setRange, setName, setMode]); + }, [camera, name, range, isTimelapse, setRange, setName, setMode]); // filters @@ -177,6 +179,7 @@ export default function MobileReviewSettingsDrawer({ currentTime={currentTime} range={range} name={name} + isTimelapse={isTimelapse} onStartExport={onStartExport} setName={setName} setRange={setRange} @@ -187,10 +190,12 @@ export default function MobileReviewSettingsDrawer({ setDrawerMode("none"); } }} + setIsTimelapse={setIsTimelapse} onCancel={() => { setMode("none"); setRange(undefined); setDrawerMode("select"); + setIsTimelapse(false); }} /> ); @@ -289,7 +294,10 @@ export default function MobileReviewSettingsDrawer({ className="pointer-events-none absolute left-1/2 top-8 z-50 -translate-x-1/2" show={mode == "timeline"} onSave={() => onStartExport()} - onCancel={() => setMode("none")} + onCancel={() => { + setMode("none"); + setIsTimelapse(false); + }} onPreview={() => setShowExportPreview(true)} />