mirror of
https://github.com/blakeblackshear/frigate.git
synced 2026-02-10 13:15:25 +03:00
Add ability to name exports
This commit is contained in:
parent
63df9d4338
commit
549b40d7c7
@ -8,6 +8,7 @@ import re
|
|||||||
import subprocess as sp
|
import subprocess as sp
|
||||||
import time
|
import time
|
||||||
from datetime import datetime, timedelta, timezone
|
from datetime import datetime, timedelta, timezone
|
||||||
|
from typing import Optional
|
||||||
from urllib.parse import unquote
|
from urllib.parse import unquote
|
||||||
|
|
||||||
import cv2
|
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 {}
|
json: dict[str, any] = request.get_json(silent=True) or {}
|
||||||
playback_factor = json.get("playback", "realtime")
|
playback_factor = json.get("playback", "realtime")
|
||||||
|
name: Optional[str] = json.get("name")
|
||||||
|
|
||||||
recordings_count = (
|
recordings_count = (
|
||||||
Recordings.select()
|
Recordings.select()
|
||||||
@ -641,6 +643,7 @@ def export_recording(camera_name: str, start_time, end_time):
|
|||||||
exporter = RecordingExporter(
|
exporter = RecordingExporter(
|
||||||
current_app.frigate_config,
|
current_app.frigate_config,
|
||||||
camera_name,
|
camera_name,
|
||||||
|
secure_filename(name.replace(" ", "_")) if name else None,
|
||||||
int(start_time),
|
int(start_time),
|
||||||
int(end_time),
|
int(end_time),
|
||||||
(
|
(
|
||||||
|
|||||||
@ -38,6 +38,7 @@ class RecordingExporter(threading.Thread):
|
|||||||
self,
|
self,
|
||||||
config: FrigateConfig,
|
config: FrigateConfig,
|
||||||
camera: str,
|
camera: str,
|
||||||
|
name: str,
|
||||||
start_time: int,
|
start_time: int,
|
||||||
end_time: int,
|
end_time: int,
|
||||||
playback_factor: PlaybackFactorEnum,
|
playback_factor: PlaybackFactorEnum,
|
||||||
@ -45,6 +46,7 @@ class RecordingExporter(threading.Thread):
|
|||||||
threading.Thread.__init__(self)
|
threading.Thread.__init__(self)
|
||||||
self.config = config
|
self.config = config
|
||||||
self.camera = camera
|
self.camera = camera
|
||||||
|
self.user_provided_name = name
|
||||||
self.start_time = start_time
|
self.start_time = start_time
|
||||||
self.end_time = end_time
|
self.end_time = end_time
|
||||||
self.playback_factor = playback_factor
|
self.playback_factor = playback_factor
|
||||||
@ -57,8 +59,12 @@ class RecordingExporter(threading.Thread):
|
|||||||
logger.debug(
|
logger.debug(
|
||||||
f"Beginning export for {self.camera} from {self.start_time} to {self.end_time}"
|
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"
|
file_name = (
|
||||||
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"
|
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:
|
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"
|
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:
|
if self.playback_factor == PlaybackFactorEnum.realtime:
|
||||||
ffmpeg_cmd = (
|
ffmpeg_cmd = (
|
||||||
f"ffmpeg -hide_banner {ffmpeg_input} -c copy {file_name}"
|
f"ffmpeg -hide_banner {ffmpeg_input} -c copy {file_path}"
|
||||||
).split(" ")
|
).split(" ")
|
||||||
elif self.playback_factor == PlaybackFactorEnum.timelapse_25x:
|
elif self.playback_factor == PlaybackFactorEnum.timelapse_25x:
|
||||||
ffmpeg_cmd = (
|
ffmpeg_cmd = (
|
||||||
parse_preset_hardware_acceleration_encode(
|
parse_preset_hardware_acceleration_encode(
|
||||||
self.config.ffmpeg.hwaccel_args,
|
self.config.ffmpeg.hwaccel_args,
|
||||||
f"{TIMELAPSE_DATA_INPUT_ARGS} {ffmpeg_input}",
|
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,
|
EncodeTypeEnum.timelapse,
|
||||||
)
|
)
|
||||||
).split(" ")
|
).split(" ")
|
||||||
@ -122,9 +128,9 @@ class RecordingExporter(threading.Thread):
|
|||||||
f"Failed to export recording for command {' '.join(ffmpeg_cmd)}"
|
f"Failed to export recording for command {' '.join(ffmpeg_cmd)}"
|
||||||
)
|
)
|
||||||
logger.error(p.stderr)
|
logger.error(p.stderr)
|
||||||
Path(file_name).unlink(missing_ok=True)
|
Path(file_path).unlink(missing_ok=True)
|
||||||
return
|
return
|
||||||
|
|
||||||
logger.debug(f"Updating finalized export {file_name}")
|
logger.debug(f"Updating finalized export {file_path}")
|
||||||
os.rename(file_name, final_file_name)
|
os.rename(file_path, final_file_path)
|
||||||
logger.debug(f"Finished exporting {file_name}")
|
logger.debug(f"Finished exporting {file_path}")
|
||||||
|
|||||||
@ -15,6 +15,7 @@ import { ExportMode } from "@/types/filter";
|
|||||||
import { FaArrowDown } from "react-icons/fa";
|
import { FaArrowDown } from "react-icons/fa";
|
||||||
import axios from "axios";
|
import axios from "axios";
|
||||||
import { toast } from "sonner";
|
import { toast } from "sonner";
|
||||||
|
import { Input } from "../ui/input";
|
||||||
|
|
||||||
const EXPORT_OPTIONS = [
|
const EXPORT_OPTIONS = [
|
||||||
"1",
|
"1",
|
||||||
@ -38,6 +39,7 @@ export default function ExportDialog({
|
|||||||
setMode,
|
setMode,
|
||||||
}: ExportDialogProps) {
|
}: ExportDialogProps) {
|
||||||
const [selectedOption, setSelectedOption] = useState<ExportOption>("1");
|
const [selectedOption, setSelectedOption] = useState<ExportOption>("1");
|
||||||
|
const [name, setName] = useState("");
|
||||||
|
|
||||||
const onStartExport = useCallback(() => {
|
const onStartExport = useCallback(() => {
|
||||||
const now = new Date();
|
const now = new Date();
|
||||||
@ -73,6 +75,7 @@ export default function ExportDialog({
|
|||||||
axios
|
axios
|
||||||
.post(`export/${camera}/start/${start}/end/${end}`, {
|
.post(`export/${camera}/start/${start}/end/${end}`, {
|
||||||
playback: "realtime",
|
playback: "realtime",
|
||||||
|
name,
|
||||||
})
|
})
|
||||||
.then((response) => {
|
.then((response) => {
|
||||||
if (response.status == 200) {
|
if (response.status == 200) {
|
||||||
@ -94,7 +97,7 @@ export default function ExportDialog({
|
|||||||
});
|
});
|
||||||
}
|
}
|
||||||
});
|
});
|
||||||
}, [camera, selectedOption]);
|
}, [camera, name, selectedOption]);
|
||||||
|
|
||||||
return (
|
return (
|
||||||
<Dialog open={mode == "select"}>
|
<Dialog open={mode == "select"}>
|
||||||
@ -138,6 +141,12 @@ export default function ExportDialog({
|
|||||||
);
|
);
|
||||||
})}
|
})}
|
||||||
</RadioGroup>
|
</RadioGroup>
|
||||||
|
<Input
|
||||||
|
type="search"
|
||||||
|
placeholder="Name the Export"
|
||||||
|
value={name}
|
||||||
|
onChange={(e) => setName(e.target.value)}
|
||||||
|
/>
|
||||||
<DialogFooter>
|
<DialogFooter>
|
||||||
<DialogClose onClick={() => setMode("none")}>Cancel</DialogClose>
|
<DialogClose onClick={() => setMode("none")}>Cancel</DialogClose>
|
||||||
<Button
|
<Button
|
||||||
|
|||||||
Loading…
Reference in New Issue
Block a user