From 549b40d7c70f3fa219703e0d9df51e085ccf52a0 Mon Sep 17 00:00:00 2001 From: Nicolas Mowen Date: Tue, 26 Mar 2024 13:04:38 -0600 Subject: [PATCH] Add ability to name exports --- frigate/api/media.py | 3 +++ frigate/record/export.py | 22 +++++++++++++-------- web/src/components/overlay/ExportDialog.tsx | 11 ++++++++++- 3 files changed, 27 insertions(+), 9 deletions(-) diff --git a/frigate/api/media.py b/frigate/api/media.py index 78e8c711e..c72d9b933 100644 --- a/frigate/api/media.py +++ b/frigate/api/media.py @@ -8,6 +8,7 @@ import re import subprocess as sp import time from datetime import datetime, timedelta, timezone +from typing import Optional from urllib.parse import unquote import cv2 @@ -618,6 +619,7 @@ def export_recording(camera_name: str, start_time, end_time): json: dict[str, any] = request.get_json(silent=True) or {} playback_factor = json.get("playback", "realtime") + name: Optional[str] = json.get("name") recordings_count = ( Recordings.select() @@ -641,6 +643,7 @@ def export_recording(camera_name: str, start_time, end_time): exporter = RecordingExporter( current_app.frigate_config, camera_name, + secure_filename(name.replace(" ", "_")) if name else None, int(start_time), int(end_time), ( diff --git a/frigate/record/export.py b/frigate/record/export.py index 65ebf13c9..f5861d4f7 100644 --- a/frigate/record/export.py +++ b/frigate/record/export.py @@ -38,6 +38,7 @@ class RecordingExporter(threading.Thread): self, config: FrigateConfig, camera: str, + name: str, start_time: int, end_time: int, playback_factor: PlaybackFactorEnum, @@ -45,6 +46,7 @@ class RecordingExporter(threading.Thread): threading.Thread.__init__(self) self.config = config self.camera = camera + self.user_provided_name = name self.start_time = start_time self.end_time = end_time self.playback_factor = playback_factor @@ -57,8 +59,12 @@ class RecordingExporter(threading.Thread): logger.debug( f"Beginning export for {self.camera} from {self.start_time} to {self.end_time}" ) - file_name = f"{EXPORT_DIR}/in_progress.{self.camera}@{self.get_datetime_from_timestamp(self.start_time)}__{self.get_datetime_from_timestamp(self.end_time)}.mp4" - final_file_name = f"{EXPORT_DIR}/{self.camera}_{self.get_datetime_from_timestamp(self.start_time)}__{self.get_datetime_from_timestamp(self.end_time)}.mp4" + file_name = ( + self.user_provided_name + or f"{self.camera}@{self.get_datetime_from_timestamp(self.start_time)}__{self.get_datetime_from_timestamp(self.end_time)}" + ) + file_path = f"{EXPORT_DIR}/in_progress.{file_name}.mp4" + final_file_path = f"{EXPORT_DIR}/{file_name}.mp4" if (self.end_time - self.start_time) <= MAX_PLAYLIST_SECONDS: playlist_lines = f"http://127.0.0.1:5000/vod/{self.camera}/start/{self.start_time}/end/{self.end_time}/index.m3u8" @@ -97,14 +103,14 @@ class RecordingExporter(threading.Thread): if self.playback_factor == PlaybackFactorEnum.realtime: ffmpeg_cmd = ( - f"ffmpeg -hide_banner {ffmpeg_input} -c copy {file_name}" + f"ffmpeg -hide_banner {ffmpeg_input} -c copy {file_path}" ).split(" ") elif self.playback_factor == PlaybackFactorEnum.timelapse_25x: ffmpeg_cmd = ( parse_preset_hardware_acceleration_encode( self.config.ffmpeg.hwaccel_args, f"{TIMELAPSE_DATA_INPUT_ARGS} {ffmpeg_input}", - f"{self.config.cameras[self.camera].record.export.timelapse_args} {file_name}", + f"{self.config.cameras[self.camera].record.export.timelapse_args} {file_path}", EncodeTypeEnum.timelapse, ) ).split(" ") @@ -122,9 +128,9 @@ class RecordingExporter(threading.Thread): f"Failed to export recording for command {' '.join(ffmpeg_cmd)}" ) logger.error(p.stderr) - Path(file_name).unlink(missing_ok=True) + Path(file_path).unlink(missing_ok=True) return - logger.debug(f"Updating finalized export {file_name}") - os.rename(file_name, final_file_name) - logger.debug(f"Finished exporting {file_name}") + logger.debug(f"Updating finalized export {file_path}") + os.rename(file_path, final_file_path) + logger.debug(f"Finished exporting {file_path}") diff --git a/web/src/components/overlay/ExportDialog.tsx b/web/src/components/overlay/ExportDialog.tsx index 8bf7f13a9..217e68fbb 100644 --- a/web/src/components/overlay/ExportDialog.tsx +++ b/web/src/components/overlay/ExportDialog.tsx @@ -15,6 +15,7 @@ import { ExportMode } from "@/types/filter"; import { FaArrowDown } from "react-icons/fa"; import axios from "axios"; import { toast } from "sonner"; +import { Input } from "../ui/input"; const EXPORT_OPTIONS = [ "1", @@ -38,6 +39,7 @@ export default function ExportDialog({ setMode, }: ExportDialogProps) { const [selectedOption, setSelectedOption] = useState("1"); + const [name, setName] = useState(""); const onStartExport = useCallback(() => { const now = new Date(); @@ -73,6 +75,7 @@ export default function ExportDialog({ axios .post(`export/${camera}/start/${start}/end/${end}`, { playback: "realtime", + name, }) .then((response) => { if (response.status == 200) { @@ -94,7 +97,7 @@ export default function ExportDialog({ }); } }); - }, [camera, selectedOption]); + }, [camera, name, selectedOption]); return ( @@ -138,6 +141,12 @@ export default function ExportDialog({ ); })} + setName(e.target.value)} + /> setMode("none")}>Cancel