Add option to export Timelapses from History view

This commit is contained in:
Gergely Szell 2024-11-04 16:48:35 +01:00
parent 77ec86d31a
commit e50a1eb905
3 changed files with 49 additions and 8 deletions

View File

@ -154,7 +154,7 @@ Footage can be exported from Frigate by right-clicking (desktop) or long pressin
### Time-lapse export ### 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. 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.

View File

@ -11,6 +11,7 @@ import {
import { Label } from "../ui/label"; import { Label } from "../ui/label";
import { RadioGroup, RadioGroupItem } from "../ui/radio-group"; import { RadioGroup, RadioGroupItem } from "../ui/radio-group";
import { Button } from "../ui/button"; import { Button } from "../ui/button";
import { Checkbox } from "../ui/checkbox";
import { ExportMode } from "@/types/filter"; import { ExportMode } from "@/types/filter";
import { FaArrowDown, FaArrowRight, FaCalendarAlt } from "react-icons/fa"; import { FaArrowDown, FaArrowRight, FaCalendarAlt } from "react-icons/fa";
import axios from "axios"; import axios from "axios";
@ -65,6 +66,7 @@ export default function ExportDialog({
setShowPreview, setShowPreview,
}: ExportDialogProps) { }: ExportDialogProps) {
const [name, setName] = useState(""); const [name, setName] = useState("");
const [isTimelapse, setIsTimelapse] = useState(false);
const onStartExport = useCallback(() => { const onStartExport = useCallback(() => {
if (!range) { if (!range) {
@ -83,7 +85,7 @@ export default function ExportDialog({
.post( .post(
`export/${camera}/start/${Math.round(range.after)}/end/${Math.round(range.before)}`, `export/${camera}/start/${Math.round(range.after)}/end/${Math.round(range.before)}`,
{ {
playback: "realtime", playback: isTimelapse ? "timelapse_25x" : "realtime",
name, name,
}, },
) )
@ -96,6 +98,7 @@ export default function ExportDialog({
setName(""); setName("");
setRange(undefined); setRange(undefined);
setMode("none"); setMode("none");
setIsTimelapse(false);
} }
}) })
.catch((error) => { .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 Overlay = isDesktop ? Dialog : Drawer;
const Trigger = isDesktop ? DialogTrigger : DrawerTrigger; const Trigger = isDesktop ? DialogTrigger : DrawerTrigger;
@ -129,13 +132,17 @@ export default function ExportDialog({
show={mode == "timeline"} show={mode == "timeline"}
onPreview={() => setShowPreview(true)} onPreview={() => setShowPreview(true)}
onSave={() => onStartExport()} onSave={() => onStartExport()}
onCancel={() => setMode("none")} onCancel={() => {
setMode("none");
setIsTimelapse(false);
}}
/> />
<Overlay <Overlay
open={mode == "select"} open={mode == "select"}
onOpenChange={(open) => { onOpenChange={(open) => {
if (!open) { if (!open) {
setMode("none"); setMode("none");
setIsTimelapse(false);
} }
}} }}
> >
@ -172,11 +179,16 @@ export default function ExportDialog({
currentTime={currentTime} currentTime={currentTime}
range={range} range={range}
name={name} name={name}
isTimelapse={isTimelapse}
onStartExport={onStartExport} onStartExport={onStartExport}
setName={setName} setName={setName}
setRange={setRange} setRange={setRange}
setMode={setMode} setMode={setMode}
onCancel={() => setMode("none")} setIsTimelapse={setIsTimelapse}
onCancel={() => {
setMode("none");
setIsTimelapse(false);
}}
/> />
</Content> </Content>
</Overlay> </Overlay>
@ -189,10 +201,12 @@ type ExportContentProps = {
currentTime: number; currentTime: number;
range?: TimeRange; range?: TimeRange;
name: string; name: string;
isTimelapse: boolean;
onStartExport: () => void; onStartExport: () => void;
setName: (name: string) => void; setName: (name: string) => void;
setRange: (range: TimeRange | undefined) => void; setRange: (range: TimeRange | undefined) => void;
setMode: (mode: ExportMode) => void; setMode: (mode: ExportMode) => void;
setIsTimelapse: (isTimelapse: boolean) => void;
onCancel: () => void; onCancel: () => void;
}; };
export function ExportContent({ export function ExportContent({
@ -200,10 +214,12 @@ export function ExportContent({
currentTime, currentTime,
range, range,
name, name,
isTimelapse,
onStartExport, onStartExport,
setName, setName,
setRange, setRange,
setMode, setMode,
setIsTimelapse,
onCancel, onCancel,
}: ExportContentProps) { }: ExportContentProps) {
const [selectedOption, setSelectedOption] = useState<ExportOption>("1"); const [selectedOption, setSelectedOption] = useState<ExportOption>("1");
@ -289,6 +305,22 @@ export function ExportContent({
setRange={setRange} setRange={setRange}
/> />
)} )}
{isDesktop && <SelectSeparator className="my-4 bg-secondary" />}
<div className="mt-4 flex items-center gap-2">
<Checkbox
className={
isTimelapse
? "bg-selected from-selected/50 to-selected/90 text-selected"
: "bg-secondary from-secondary/50 to-secondary/90 text-secondary"
}
id="timelapse"
checked={isTimelapse}
onCheckedChange={(checked) => setIsTimelapse(checked as boolean)}
/>
<Label htmlFor="timelapse" className="cursor-pointer capitalize">
Timelapse
</Label>
</div>
<Input <Input
className="text-md my-6" className="text-md my-6"
type="search" type="search"
@ -319,6 +351,7 @@ export function ExportContent({
onStartExport(); onStartExport();
setSelectedOption("1"); setSelectedOption("1");
setMode("none"); setMode("none");
setIsTimelapse(false);
} }
}} }}
> >

View File

@ -62,6 +62,7 @@ export default function MobileReviewSettingsDrawer({
setShowExportPreview, setShowExportPreview,
}: MobileReviewSettingsDrawerProps) { }: MobileReviewSettingsDrawerProps) {
const [drawerMode, setDrawerMode] = useState<DrawerMode>("none"); const [drawerMode, setDrawerMode] = useState<DrawerMode>("none");
const [isTimelapse, setIsTimelapse] = useState(false);
// exports // exports
@ -83,7 +84,7 @@ export default function MobileReviewSettingsDrawer({
.post( .post(
`export/${camera}/start/${Math.round(range.after)}/end/${Math.round(range.before)}`, `export/${camera}/start/${Math.round(range.after)}/end/${Math.round(range.before)}`,
{ {
playback: "realtime", playback: isTimelapse ? "timelapse_25x" : "realtime",
name, name,
}, },
) )
@ -96,6 +97,7 @@ export default function MobileReviewSettingsDrawer({
setName(""); setName("");
setRange(undefined); setRange(undefined);
setMode("none"); setMode("none");
setIsTimelapse(false);
} }
}) })
.catch((error) => { .catch((error) => {
@ -110,7 +112,7 @@ export default function MobileReviewSettingsDrawer({
}); });
} }
}); });
}, [camera, name, range, setRange, setName, setMode]); }, [camera, name, range, isTimelapse, setRange, setName, setMode]);
// filters // filters
@ -177,6 +179,7 @@ export default function MobileReviewSettingsDrawer({
currentTime={currentTime} currentTime={currentTime}
range={range} range={range}
name={name} name={name}
isTimelapse={isTimelapse}
onStartExport={onStartExport} onStartExport={onStartExport}
setName={setName} setName={setName}
setRange={setRange} setRange={setRange}
@ -187,10 +190,12 @@ export default function MobileReviewSettingsDrawer({
setDrawerMode("none"); setDrawerMode("none");
} }
}} }}
setIsTimelapse={setIsTimelapse}
onCancel={() => { onCancel={() => {
setMode("none"); setMode("none");
setRange(undefined); setRange(undefined);
setDrawerMode("select"); 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" className="pointer-events-none absolute left-1/2 top-8 z-50 -translate-x-1/2"
show={mode == "timeline"} show={mode == "timeline"}
onSave={() => onStartExport()} onSave={() => onStartExport()}
onCancel={() => setMode("none")} onCancel={() => {
setMode("none");
setIsTimelapse(false);
}}
onPreview={() => setShowExportPreview(true)} onPreview={() => setShowExportPreview(true)}
/> />
<ExportPreviewDialog <ExportPreviewDialog