diff --git a/frigate/api/media.py b/frigate/api/media.py index 4aae06672..d493b6fa9 100644 --- a/frigate/api/media.py +++ b/frigate/api/media.py @@ -234,7 +234,7 @@ def get_snapshot_from_recording(camera_name: str, frame_time: str): ) -@MediaBp.route("//plus/") +@MediaBp.route("//plus/", methods=("POST",)) def submit_recording_snapshot_to_plus(camera_name: str, frame_time: str): if camera_name not in current_app.frigate_config.cameras: return make_response( diff --git a/frigate/util/image.py b/frigate/util/image.py index ac32f095e..3962d9600 100644 --- a/frigate/util/image.py +++ b/frigate/util/image.py @@ -361,17 +361,14 @@ def yuv_crop_and_resize(frame, region, height=None): # copy u2 yuv_cropped_frame[ size + uv_channel_y_offset : size + uv_channel_y_offset + uv_crop_height, - size // 2 - + uv_channel_x_offset : size // 2 + size // 2 + uv_channel_x_offset : size // 2 + uv_channel_x_offset + uv_crop_width, ] = frame[u2[1] : u2[3], u2[0] : u2[2]] # copy v1 yuv_cropped_frame[ - size - + size // 4 - + uv_channel_y_offset : size + size + size // 4 + uv_channel_y_offset : size + size // 4 + uv_channel_y_offset + uv_crop_height, @@ -380,14 +377,11 @@ def yuv_crop_and_resize(frame, region, height=None): # copy v2 yuv_cropped_frame[ - size - + size // 4 - + uv_channel_y_offset : size + size + size // 4 + uv_channel_y_offset : size + size // 4 + uv_channel_y_offset + uv_crop_height, - size // 2 - + uv_channel_x_offset : size // 2 + size // 2 + uv_channel_x_offset : size // 2 + uv_channel_x_offset + uv_crop_width, ] = frame[v2[1] : v2[3], v2[0] : v2[2]] diff --git a/web/src/components/player/HlsVideoPlayer.tsx b/web/src/components/player/HlsVideoPlayer.tsx index 7024ff971..879506936 100644 --- a/web/src/components/player/HlsVideoPlayer.tsx +++ b/web/src/components/player/HlsVideoPlayer.tsx @@ -10,6 +10,10 @@ import { isAndroid, isDesktop, isMobile } from "react-device-detect"; import { TransformComponent, TransformWrapper } from "react-zoom-pan-pinch"; import VideoControls from "./VideoControls"; import { VideoResolutionType } from "@/types/live"; +import useSWR from "swr"; +import { FrigateConfig } from "@/types/frigateConfig"; +import { AxiosResponse } from "axios"; +import { toast } from "sonner"; // Android native hls does not seek correctly const USE_NATIVE_HLS = !isAndroid; @@ -29,6 +33,7 @@ type HlsVideoPlayerProps = { onTimeUpdate?: (time: number) => void; onPlaying?: () => void; setFullResolution?: React.Dispatch>; + onUploadFrame?: (playTime: number) => Promise | undefined; }; export default function HlsVideoPlayer({ videoRef, @@ -40,7 +45,10 @@ export default function HlsVideoPlayer({ onTimeUpdate, onPlaying, setFullResolution, + onUploadFrame, }: HlsVideoPlayerProps) { + const { data: config } = useSWR("config"); + // playback const hlsRef = useRef(); @@ -144,7 +152,7 @@ export default function HlsVideoPlayer({ volume: true, seek: true, playbackRate: true, - plusUpload: true, + plusUpload: config?.plus?.enabled == true, }} setControlsOpen={setControlsOpen} setMuted={setMuted} @@ -173,6 +181,21 @@ export default function HlsVideoPlayer({ onSetPlaybackRate={(rate) => videoRef.current ? (videoRef.current.playbackRate = rate) : null } + onUploadFrame={async () => { + if (videoRef.current && onUploadFrame) { + const resp = await onUploadFrame(videoRef.current.currentTime); + + if (resp && resp.status == 200) { + toast.success("Successfully submitted frame to Frigate Plus", { + position: "top-center", + }); + } else { + toast.success("Failed to submit frame to Frigate Plus", { + position: "top-center", + }); + } + } + }} /> void; onSeek: (diff: number) => void; onSetPlaybackRate: (rate: number) => void; + onUploadFrame?: () => void; }; export default function VideoControls({ className, @@ -78,6 +79,7 @@ export default function VideoControls({ onPlayPause, onSeek, onSetPlaybackRate, + onUploadFrame, }: VideoControlsProps) { const onReplay = useCallback( (e: React.MouseEvent) => { @@ -224,7 +226,7 @@ export default function VideoControls({ )} - {features.plusUpload && ( + {features.plusUpload && onUploadFrame && ( { @@ -239,6 +241,7 @@ export default function VideoControls({ setControlsOpen(true); } }} + onUploadFrame={onUploadFrame} /> )} @@ -249,11 +252,13 @@ type FrigatePlusUploadButtonProps = { video?: HTMLVideoElement | null; onOpen: () => void; onClose: () => void; + onUploadFrame: () => void; }; function FrigatePlusUploadButton({ video, onOpen, onClose, + onUploadFrame, }: FrigatePlusUploadButtonProps) { const [videoImg, setVideoImg] = useState(); @@ -295,7 +300,9 @@ function FrigatePlusUploadButton({ - Submit + + Submit + Cancel diff --git a/web/src/components/player/dynamic/DynamicVideoController.ts b/web/src/components/player/dynamic/DynamicVideoController.ts index e8772c49e..9ccc06c23 100644 --- a/web/src/components/player/dynamic/DynamicVideoController.ts +++ b/web/src/components/player/dynamic/DynamicVideoController.ts @@ -101,7 +101,7 @@ export class DynamicVideoController { this.playerController.pause(); } } else { - console.log(`seek time is 0`); + // no op } } diff --git a/web/src/components/player/dynamic/DynamicVideoPlayer.tsx b/web/src/components/player/dynamic/DynamicVideoPlayer.tsx index 3f144e04b..0c095de97 100644 --- a/web/src/components/player/dynamic/DynamicVideoPlayer.tsx +++ b/web/src/components/player/dynamic/DynamicVideoPlayer.tsx @@ -10,6 +10,7 @@ import HlsVideoPlayer from "../HlsVideoPlayer"; import { TimeRange } from "@/types/timeline"; import ActivityIndicator from "@/components/indicators/activity-indicator"; import { VideoResolutionType } from "@/types/live"; +import axios from "axios"; /** * Dynamically switches between video playback and scrubbing preview player. @@ -127,6 +128,18 @@ export default function DynamicVideoPlayer({ [controller, onTimestampUpdate, isScrubbing, isLoading], ); + const onUploadFrameToPlus = useCallback( + (playTime: number) => { + if (!controller) { + return; + } + + const time = controller.getProgress(playTime); + return axios.post(`/${camera}/plus/${time}`); + }, + [camera, controller], + ); + // state of playback player const recordingParams = useMemo(() => { @@ -186,6 +199,7 @@ export default function DynamicVideoPlayer({ setNoRecording(false); }} setFullResolution={setFullResolution} + onUploadFrame={onUploadFrameToPlus} />