diff --git a/frigate/api/export.py b/frigate/api/export.py index 18f9264ea..d697709c5 100644 --- a/frigate/api/export.py +++ b/frigate/api/export.py @@ -1,6 +1,8 @@ """Export apis.""" import logging +import random +import string from pathlib import Path from typing import Optional @@ -72,8 +74,10 @@ def export_recording( status_code=400, ) + export_id = f"{camera_name}_{''.join(random.choices(string.ascii_lowercase + string.digits, k=6))}" exporter = RecordingExporter( request.app.frigate_config, + export_id, camera_name, friendly_name, existing_image, @@ -91,6 +95,7 @@ def export_recording( { "success": True, "message": "Starting export of recording.", + "export_id": export_id, } ), status_code=200, diff --git a/frigate/record/export.py b/frigate/record/export.py index 7d38e60f1..cbdb53014 100644 --- a/frigate/record/export.py +++ b/frigate/record/export.py @@ -49,6 +49,7 @@ class RecordingExporter(threading.Thread): def __init__( self, config: FrigateConfig, + id: str, camera: str, name: Optional[str], image: Optional[str], @@ -58,6 +59,7 @@ class RecordingExporter(threading.Thread): ) -> None: threading.Thread.__init__(self) self.config = config + self.export_id = id self.camera = camera self.user_provided_name = name self.user_provided_image = image @@ -172,18 +174,17 @@ class RecordingExporter(threading.Thread): logger.debug( f"Beginning export for {self.camera} from {self.start_time} to {self.end_time}" ) - export_id = f"{self.camera}_{''.join(random.choices(string.ascii_lowercase + string.digits, k=6))}" export_name = ( self.user_provided_name or f"{self.camera.replace('_', ' ')} {self.get_datetime_from_timestamp(self.start_time)} {self.get_datetime_from_timestamp(self.end_time)}" ) - video_path = f"{EXPORT_DIR}/{export_id}.mp4" + video_path = f"{EXPORT_DIR}/{self.export_id}.mp4" - thumb_path = self.save_thumbnail(export_id) + thumb_path = self.save_thumbnail(self.export_id) Export.insert( { - Export.id: export_id, + Export.id: self.export_id, Export.camera: self.camera, Export.name: export_name, Export.date: self.start_time, @@ -257,12 +258,12 @@ class RecordingExporter(threading.Thread): ) logger.error(p.stderr) Path(video_path).unlink(missing_ok=True) - Export.delete().where(Export.id == export_id).execute() + Export.delete().where(Export.id == self.export_id).execute() Path(thumb_path).unlink(missing_ok=True) return else: Export.update({Export.in_progress: False}).where( - Export.id == export_id + Export.id == self.export_id ).execute() logger.debug(f"Finished exporting {video_path}") diff --git a/web/src/hooks/use-keyboard-listener.tsx b/web/src/hooks/use-keyboard-listener.tsx index ad776b303..d5f68bda3 100644 --- a/web/src/hooks/use-keyboard-listener.tsx +++ b/web/src/hooks/use-keyboard-listener.tsx @@ -14,7 +14,8 @@ export default function useKeyboardListener( ) { const keyDownListener = useCallback( (e: KeyboardEvent) => { - if (!e) { + // @ts-expect-error we know this field exists + if (!e || e.target.tagName == "INPUT") { return; } diff --git a/web/src/pages/Live.tsx b/web/src/pages/Live.tsx index 9852852a7..e19dccd07 100644 --- a/web/src/pages/Live.tsx +++ b/web/src/pages/Live.tsx @@ -1,4 +1,5 @@ import { useFullscreen } from "@/hooks/use-fullscreen"; +import useKeyboardListener from "@/hooks/use-keyboard-listener"; import { useHashState, usePersistedOverlayState, @@ -43,6 +44,18 @@ function Live() { const { fullscreen, toggleFullscreen, supportsFullScreen } = useFullscreen(mainRef); + useKeyboardListener(["f"], (key, modifiers) => { + if (!modifiers.down) { + return; + } + + switch (key) { + case "f": + toggleFullscreen(); + break; + } + }); + // document title useEffect(() => { diff --git a/web/src/views/live/LiveCameraView.tsx b/web/src/views/live/LiveCameraView.tsx index 15e08cebf..8a8146e14 100644 --- a/web/src/views/live/LiveCameraView.tsx +++ b/web/src/views/live/LiveCameraView.tsx @@ -236,6 +236,25 @@ export default function LiveCameraView({ return "mse"; }, [lowBandwidth, mic, webRTC, isRestreamed]); + useKeyboardListener(["m"], (key, modifiers) => { + if (!modifiers.down) { + return; + } + + switch (key) { + case "m": + if (supportsAudioOutput) { + setAudio(!audio); + } + break; + case "t": + if (supports2WayTalk) { + setMic(!mic); + } + break; + } + }); + // layout state const windowAspectRatio = useMemo(() => {