mirror of
https://github.com/blakeblackshear/frigate.git
synced 2026-02-11 13:45:25 +03:00
Show dialog with current frame to be uploaded
This commit is contained in:
parent
fba0f66d79
commit
40faa35199
@ -7,7 +7,6 @@ import os
|
|||||||
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 io import BytesIO
|
|
||||||
from urllib.parse import unquote
|
from urllib.parse import unquote
|
||||||
|
|
||||||
import cv2
|
import cv2
|
||||||
@ -15,7 +14,6 @@ import numpy as np
|
|||||||
import pytz
|
import pytz
|
||||||
from flask import Blueprint, Response, current_app, jsonify, make_response, request
|
from flask import Blueprint, Response, current_app, jsonify, make_response, request
|
||||||
from peewee import DoesNotExist, fn
|
from peewee import DoesNotExist, fn
|
||||||
from PIL import Image
|
|
||||||
from tzlocal import get_localzone_name
|
from tzlocal import get_localzone_name
|
||||||
from werkzeug.utils import secure_filename
|
from werkzeug.utils import secure_filename
|
||||||
|
|
||||||
|
|||||||
21
web/src/components/icons/FrigatePlusIcon.tsx
Normal file
21
web/src/components/icons/FrigatePlusIcon.tsx
Normal file
@ -0,0 +1,21 @@
|
|||||||
|
import { LuPlus } from "react-icons/lu";
|
||||||
|
import Logo from "../Logo";
|
||||||
|
|
||||||
|
type FrigatePlusIconProps = {
|
||||||
|
className?: string;
|
||||||
|
onClick?: () => void;
|
||||||
|
};
|
||||||
|
export default function FrigatePlusIcon({
|
||||||
|
className,
|
||||||
|
onClick,
|
||||||
|
}: FrigatePlusIconProps) {
|
||||||
|
return (
|
||||||
|
<div
|
||||||
|
className={`relative flex items-center ${className ?? ""}`}
|
||||||
|
onClick={onClick}
|
||||||
|
>
|
||||||
|
<Logo className="size-full" />
|
||||||
|
<LuPlus className="absolute size-2 translate-x-3 translate-y-3/4" />
|
||||||
|
</div>
|
||||||
|
);
|
||||||
|
}
|
||||||
@ -137,10 +137,15 @@ export default function HlsVideoPlayer({
|
|||||||
className="absolute bottom-5 left-1/2 -translate-x-1/2 z-50"
|
className="absolute bottom-5 left-1/2 -translate-x-1/2 z-50"
|
||||||
video={videoRef.current}
|
video={videoRef.current}
|
||||||
isPlaying={isPlaying}
|
isPlaying={isPlaying}
|
||||||
show={visible && controls}
|
show={visible && (controls || controlsOpen)}
|
||||||
muted={muted}
|
muted={muted}
|
||||||
volume={volume}
|
volume={volume}
|
||||||
controlsOpen={controlsOpen}
|
features={{
|
||||||
|
volume: true,
|
||||||
|
seek: true,
|
||||||
|
playbackRate: true,
|
||||||
|
plusUpload: true,
|
||||||
|
}}
|
||||||
setControlsOpen={setControlsOpen}
|
setControlsOpen={setControlsOpen}
|
||||||
setMuted={setMuted}
|
setMuted={setMuted}
|
||||||
playbackRate={videoRef.current?.playbackRate ?? 1}
|
playbackRate={videoRef.current?.playbackRate ?? 1}
|
||||||
|
|||||||
@ -1,4 +1,4 @@
|
|||||||
import { useCallback, useMemo } from "react";
|
import { useCallback, useMemo, useState } from "react";
|
||||||
import { isSafari } from "react-device-detect";
|
import { isSafari } from "react-device-detect";
|
||||||
import { LuPause, LuPlay } from "react-icons/lu";
|
import { LuPause, LuPlay } from "react-icons/lu";
|
||||||
import {
|
import {
|
||||||
@ -18,17 +18,30 @@ import {
|
|||||||
} from "react-icons/md";
|
} from "react-icons/md";
|
||||||
import useKeyboardListener from "@/hooks/use-keyboard-listener";
|
import useKeyboardListener from "@/hooks/use-keyboard-listener";
|
||||||
import { VolumeSlider } from "../ui/slider";
|
import { VolumeSlider } from "../ui/slider";
|
||||||
|
import FrigatePlusIcon from "../icons/FrigatePlusIcon";
|
||||||
|
import {
|
||||||
|
AlertDialog,
|
||||||
|
AlertDialogAction,
|
||||||
|
AlertDialogCancel,
|
||||||
|
AlertDialogContent,
|
||||||
|
AlertDialogFooter,
|
||||||
|
AlertDialogHeader,
|
||||||
|
AlertDialogTitle,
|
||||||
|
AlertDialogTrigger,
|
||||||
|
} from "../ui/alert-dialog";
|
||||||
|
|
||||||
type VideoControls = {
|
type VideoControls = {
|
||||||
volume?: boolean;
|
volume?: boolean;
|
||||||
seek?: boolean;
|
seek?: boolean;
|
||||||
playbackRate?: boolean;
|
playbackRate?: boolean;
|
||||||
|
plusUpload?: boolean;
|
||||||
};
|
};
|
||||||
|
|
||||||
const CONTROLS_DEFAULT: VideoControls = {
|
const CONTROLS_DEFAULT: VideoControls = {
|
||||||
volume: true,
|
volume: true,
|
||||||
seek: true,
|
seek: true,
|
||||||
playbackRate: true,
|
playbackRate: true,
|
||||||
|
plusUpload: false,
|
||||||
};
|
};
|
||||||
const PLAYBACK_RATE_DEFAULT = isSafari ? [0.5, 1, 2] : [0.5, 1, 2, 4, 8, 16];
|
const PLAYBACK_RATE_DEFAULT = isSafari ? [0.5, 1, 2] : [0.5, 1, 2, 4, 8, 16];
|
||||||
|
|
||||||
@ -40,7 +53,6 @@ type VideoControlsProps = {
|
|||||||
show: boolean;
|
show: boolean;
|
||||||
muted?: boolean;
|
muted?: boolean;
|
||||||
volume?: number;
|
volume?: number;
|
||||||
controlsOpen?: boolean;
|
|
||||||
playbackRates?: number[];
|
playbackRates?: number[];
|
||||||
playbackRate: number;
|
playbackRate: number;
|
||||||
hotKeys?: boolean;
|
hotKeys?: boolean;
|
||||||
@ -58,7 +70,6 @@ export default function VideoControls({
|
|||||||
show,
|
show,
|
||||||
muted,
|
muted,
|
||||||
volume,
|
volume,
|
||||||
controlsOpen,
|
|
||||||
playbackRates = PLAYBACK_RATE_DEFAULT,
|
playbackRates = PLAYBACK_RATE_DEFAULT,
|
||||||
playbackRate,
|
playbackRate,
|
||||||
hotKeys = true,
|
hotKeys = true,
|
||||||
@ -189,7 +200,6 @@ export default function VideoControls({
|
|||||||
)}
|
)}
|
||||||
{features.playbackRate && (
|
{features.playbackRate && (
|
||||||
<DropdownMenu
|
<DropdownMenu
|
||||||
open={controlsOpen == true}
|
|
||||||
onOpenChange={(open) => {
|
onOpenChange={(open) => {
|
||||||
if (setControlsOpen) {
|
if (setControlsOpen) {
|
||||||
setControlsOpen(open);
|
setControlsOpen(open);
|
||||||
@ -214,6 +224,81 @@ export default function VideoControls({
|
|||||||
</DropdownMenuContent>
|
</DropdownMenuContent>
|
||||||
</DropdownMenu>
|
</DropdownMenu>
|
||||||
)}
|
)}
|
||||||
|
{features.plusUpload && (
|
||||||
|
<FrigatePlusUploadButton
|
||||||
|
video={video}
|
||||||
|
onClose={() => {
|
||||||
|
if (setControlsOpen) {
|
||||||
|
setControlsOpen(false);
|
||||||
|
}
|
||||||
|
}}
|
||||||
|
onOpen={() => {
|
||||||
|
onPlayPause(false);
|
||||||
|
|
||||||
|
if (setControlsOpen) {
|
||||||
|
setControlsOpen(true);
|
||||||
|
}
|
||||||
|
}}
|
||||||
|
/>
|
||||||
|
)}
|
||||||
</div>
|
</div>
|
||||||
);
|
);
|
||||||
}
|
}
|
||||||
|
|
||||||
|
type FrigatePlusUploadButtonProps = {
|
||||||
|
video?: HTMLVideoElement | null;
|
||||||
|
onOpen: () => void;
|
||||||
|
onClose: () => void;
|
||||||
|
};
|
||||||
|
function FrigatePlusUploadButton({
|
||||||
|
video,
|
||||||
|
onOpen,
|
||||||
|
onClose,
|
||||||
|
}: FrigatePlusUploadButtonProps) {
|
||||||
|
const [videoImg, setVideoImg] = useState<string>();
|
||||||
|
|
||||||
|
return (
|
||||||
|
<AlertDialog
|
||||||
|
onOpenChange={(open) => {
|
||||||
|
if (!open) {
|
||||||
|
onClose();
|
||||||
|
}
|
||||||
|
}}
|
||||||
|
>
|
||||||
|
<AlertDialogTrigger asChild>
|
||||||
|
<FrigatePlusIcon
|
||||||
|
className="size-5 cursor-pointer"
|
||||||
|
onClick={() => {
|
||||||
|
onOpen();
|
||||||
|
|
||||||
|
if (video) {
|
||||||
|
const videoSize = [video.clientWidth, video.clientHeight];
|
||||||
|
const canvas = document.createElement("canvas");
|
||||||
|
canvas.width = videoSize[0];
|
||||||
|
canvas.height = videoSize[1];
|
||||||
|
|
||||||
|
const context = canvas?.getContext("2d");
|
||||||
|
|
||||||
|
if (context) {
|
||||||
|
context.drawImage(video, 0, 0, videoSize[0], videoSize[1]);
|
||||||
|
setVideoImg(canvas.toDataURL("image/webp"));
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}}
|
||||||
|
/>
|
||||||
|
</AlertDialogTrigger>
|
||||||
|
<AlertDialogContent className="md:max-w-[80%]">
|
||||||
|
<AlertDialogHeader>
|
||||||
|
<AlertDialogTitle>
|
||||||
|
Submit this frame to Frigate Plus?
|
||||||
|
</AlertDialogTitle>
|
||||||
|
</AlertDialogHeader>
|
||||||
|
<img className="w-full object-contain" src={videoImg} />
|
||||||
|
<AlertDialogFooter>
|
||||||
|
<AlertDialogAction>Submit</AlertDialogAction>
|
||||||
|
<AlertDialogCancel>Cancel</AlertDialogCancel>
|
||||||
|
</AlertDialogFooter>
|
||||||
|
</AlertDialogContent>
|
||||||
|
</AlertDialog>
|
||||||
|
);
|
||||||
|
}
|
||||||
|
|||||||
Loading…
Reference in New Issue
Block a user