Add API and WebUI to export recordings (#6550)

* Add ability to export frigate clips

* Add http endpoint

* Add dir to nginx

* Add webUI

* Formatting

* Cleanup unused

* Optimize timelapse

* Fix pts

* Use JSON body for params

* Use hwaccel to encode when available

* Print ffmpeg command when fail

* Print ffmpeg command when fail

* Add separate ffmpeg preset for timelapse

* Add docs outlining the export directory

* Add export docs

* Use ''

* Fix playlist max time

* Lower max playlist time

* Add api docs for export

* isort fixes
This commit is contained in:
Nicolas Mowen
2023-06-08 05:32:35 -06:00
committed by GitHub
parent d1701e127e
commit d3949eebfa
13 changed files with 290 additions and 8 deletions

View File

@@ -44,6 +44,7 @@ export default function Sidebar() {
</Match>
{birdseye?.enabled ? <Destination href="/birdseye" text="Birdseye" /> : null}
<Destination href="/events" text="Events" />
<Destination href="/exports" text="Exports" />
<Separator />
<Destination href="/storage" text="Storage" />
<Destination href="/system" text="System" />

View File

@@ -31,6 +31,7 @@ export default function App() {
<AsyncRoute path="/cameras/:camera" getComponent={cameraComponent} />
<AsyncRoute path="/birdseye" getComponent={Routes.getBirdseye} />
<AsyncRoute path="/events" getComponent={Routes.getEvents} />
<AsyncRoute path="/exports" getComponent={Routes.getExports} />
<AsyncRoute
path="/recording/:camera/:date?/:hour?/:minute?/:second?"
getComponent={Routes.getRecording}

83
web/src/routes/Export.jsx Normal file
View File

@@ -0,0 +1,83 @@
import { h } from 'preact';
import Heading from '../components/Heading';
import { useState } from 'preact/hooks';
import useSWR from 'swr';
import Button from '../components/Button';
import axios from 'axios';
export default function Export() {
const { data: config } = useSWR('config');
const [camera, setCamera] = useState('select');
const [playback, setPlayback] = useState('select');
const [message, setMessage] = useState({ text: '', error: false });
const onHandleExport = () => {
if (camera == 'select') {
setMessage({ text: 'A camera needs to be selected.', error: true });
return;
}
if (playback == 'select') {
setMessage({ text: 'A playback factor needs to be selected.', error: true });
return;
}
const start = new Date(document.getElementById('start').value).getTime() / 1000;
const end = new Date(document.getElementById('end').value).getTime() / 1000;
if (!start || !end) {
setMessage({ text: 'A start and end time needs to be selected', error: true });
return;
}
setMessage({ text: 'Successfully started export. View the file in the /exports folder.', error: false });
axios.post(`export/${camera}/start/${start}/end/${end}`, { playback });
};
return (
<div className="space-y-4 p-2 px-4 w-full">
<Heading>Export</Heading>
{message.text && (
<div className={`max-h-20 ${message.error ? 'text-red-500' : 'text-green-500'}`}>{message.text}</div>
)}
<div>
<select
className="me-2 cursor-pointer rounded dark:bg-slate-800"
value={camera}
onChange={(e) => setCamera(e.target.value)}
>
<option value="select">Select A Camera</option>
{Object.keys(config?.cameras || {}).map((item) => (
<option key={item} value={item}>
{item.replaceAll('_', ' ')}
</option>
))}
</select>
<select
className="ms-2 cursor-pointer rounded dark:bg-slate-800"
value={playback}
onChange={(e) => setPlayback(e.target.value)}
>
<option value="select">Select A Playback Factor</option>
<option value="realtime">Realtime</option>
<option value="timelapse_25x">Timelapse</option>
</select>
</div>
<div>
<Heading className="py-2" size="sm">
From:
</Heading>
<input className="dark:bg-slate-800" id="start" type="datetime-local" />
<Heading className="py-2" size="sm">
To:
</Heading>
<input className="dark:bg-slate-800" id="end" type="datetime-local" />
</div>
<Button onClick={() => onHandleExport()}>Submit</Button>
</div>
);
}

View File

@@ -23,6 +23,11 @@ export async function getEvents(_url, _cb, _props) {
return module.default;
}
export async function getExports(_url, _cb, _props) {
const module = await import('./Export.jsx');
return module.default;
}
export async function getRecording(_url, _cb, _props) {
const module = await import('./Recording.jsx');
return module.default;