mirror of
https://github.com/blakeblackshear/frigate.git
synced 2026-02-09 12:45:25 +03:00
Show animated gif for event thumb
This commit is contained in:
parent
a83f07d8c8
commit
ae8932e27e
@ -597,8 +597,6 @@ def event_thumbnail(id, max_cache_age=2592000):
|
|||||||
def event_preview(id: str, max_cache_age=2592000):
|
def event_preview(id: str, max_cache_age=2592000):
|
||||||
try:
|
try:
|
||||||
event: Event = Event.get(Event.id == id)
|
event: Event = Event.get(Event.id == id)
|
||||||
if event.end_time is not None:
|
|
||||||
event_complete = True
|
|
||||||
except DoesNotExist:
|
except DoesNotExist:
|
||||||
return make_response(
|
return make_response(
|
||||||
jsonify({"success": False, "message": "Event not found"}), 404
|
jsonify({"success": False, "message": "Event not found"}), 404
|
||||||
@ -660,7 +658,6 @@ def event_preview(id: str, max_cache_age=2592000):
|
|||||||
"gif",
|
"gif",
|
||||||
"-",
|
"-",
|
||||||
]
|
]
|
||||||
logger.info(f"running previous gif creation {' '.join(ffmpeg_cmd)}")
|
|
||||||
|
|
||||||
process = sp.run(
|
process = sp.run(
|
||||||
ffmpeg_cmd,
|
ffmpeg_cmd,
|
||||||
@ -685,9 +682,14 @@ def event_preview(id: str, max_cache_age=2592000):
|
|||||||
if file > end_file:
|
if file > end_file:
|
||||||
break
|
break
|
||||||
|
|
||||||
selected_previews.append(f"file '{file}'")
|
selected_previews.append(f"file '/tmp/cache/preview_frames/{file}'")
|
||||||
selected_previews.append("duration 0.12")
|
selected_previews.append("duration 0.12")
|
||||||
|
|
||||||
|
if not selected_previews:
|
||||||
|
return make_response(
|
||||||
|
jsonify({"success": False, "message": "Preview not found"}), 404
|
||||||
|
)
|
||||||
|
|
||||||
last_file = selected_previews[-2]
|
last_file = selected_previews[-2]
|
||||||
selected_previews.append(last_file)
|
selected_previews.append(last_file)
|
||||||
|
|
||||||
@ -713,24 +715,24 @@ def event_preview(id: str, max_cache_age=2592000):
|
|||||||
"gif",
|
"gif",
|
||||||
"-",
|
"-",
|
||||||
]
|
]
|
||||||
logger.info(
|
|
||||||
f"running current gif creation {' '.join(ffmpeg_cmd)} with files {' '.join(selected_previews)}"
|
|
||||||
)
|
|
||||||
|
|
||||||
process = sp.run(
|
process = sp.run(
|
||||||
ffmpeg_cmd,
|
ffmpeg_cmd,
|
||||||
input="\n".join(selected_previews),
|
input=str.encode("\n".join(selected_previews)),
|
||||||
encoding="ascii",
|
|
||||||
capture_output=True,
|
capture_output=True,
|
||||||
)
|
)
|
||||||
|
|
||||||
|
if process.returncode != 0:
|
||||||
|
return make_response(
|
||||||
|
jsonify({"success": False, "message": "Unable to create preview gif"}),
|
||||||
|
500,
|
||||||
|
)
|
||||||
|
|
||||||
gif_bytes = process.stdout
|
gif_bytes = process.stdout
|
||||||
|
|
||||||
response = make_response(gif_bytes)
|
response = make_response(gif_bytes)
|
||||||
response.headers["Content-Type"] = "image/gif"
|
response.headers["Content-Type"] = "image/gif"
|
||||||
if event_complete:
|
response.headers["Cache-Control"] = f"private, max-age={max_cache_age}"
|
||||||
response.headers["Cache-Control"] = f"private, max-age={max_cache_age}"
|
|
||||||
else:
|
|
||||||
response.headers["Cache-Control"] = "no-store"
|
|
||||||
return response
|
return response
|
||||||
|
|
||||||
|
|
||||||
|
|||||||
@ -4,18 +4,21 @@ import { LuStar } from "react-icons/lu";
|
|||||||
import TimeAgo from "../dynamic/TimeAgo";
|
import TimeAgo from "../dynamic/TimeAgo";
|
||||||
import { Tooltip, TooltipContent, TooltipTrigger } from "../ui/tooltip";
|
import { Tooltip, TooltipContent, TooltipTrigger } from "../ui/tooltip";
|
||||||
|
|
||||||
type EventThumbnailProps = {
|
type AnimatedEventThumbnailProps = {
|
||||||
event: FrigateEvent;
|
event: FrigateEvent;
|
||||||
onFavorite?: (e: Event, event: FrigateEvent) => void;
|
onFavorite?: (e: Event, event: FrigateEvent) => void;
|
||||||
};
|
};
|
||||||
export function EventThumbnail({ event, onFavorite }: EventThumbnailProps) {
|
export function AnimatedEventThumbnail({
|
||||||
|
event,
|
||||||
|
onFavorite,
|
||||||
|
}: AnimatedEventThumbnailProps) {
|
||||||
return (
|
return (
|
||||||
<Tooltip>
|
<Tooltip>
|
||||||
<TooltipTrigger asChild>
|
<TooltipTrigger asChild>
|
||||||
<div
|
<div
|
||||||
className="relative rounded bg-cover aspect-square h-24 bg-no-repeat bg-center mr-4"
|
className="relative rounded bg-cover aspect-video h-24 bg-no-repeat bg-center mr-4"
|
||||||
style={{
|
style={{
|
||||||
backgroundImage: `url(${baseUrl}api/events/${event.id}/thumbnail.jpg)`,
|
backgroundImage: `url(${baseUrl}api/events/${event.id}/preview.gif)`,
|
||||||
}}
|
}}
|
||||||
>
|
>
|
||||||
<LuStar
|
<LuStar
|
||||||
@ -23,7 +26,7 @@ export function EventThumbnail({ event, onFavorite }: EventThumbnailProps) {
|
|||||||
onClick={(e: Event) => (onFavorite ? onFavorite(e, event) : null)}
|
onClick={(e: Event) => (onFavorite ? onFavorite(e, event) : null)}
|
||||||
fill={event.retain_indefinitely ? "currentColor" : "none"}
|
fill={event.retain_indefinitely ? "currentColor" : "none"}
|
||||||
/>
|
/>
|
||||||
<div className="absolute bottom-0 w-full h-6 bg-gradient-to-t from-slate-900/50 to-transparent">
|
<div className="absolute bottom-0 w-full h-6 bg-gradient-to-t from-slate-900/50 to-transparent rounded">
|
||||||
<div className="absolute left-1 bottom-0 text-xs text-white w-full">
|
<div className="absolute left-1 bottom-0 text-xs text-white w-full">
|
||||||
<TimeAgo time={event.start_time * 1000} dense />
|
<TimeAgo time={event.start_time * 1000} dense />
|
||||||
</div>
|
</div>
|
||||||
|
|||||||
@ -40,22 +40,23 @@ export default function LivePlayer({
|
|||||||
const { activeMotion, activeAudio, activeTracking } =
|
const { activeMotion, activeAudio, activeTracking } =
|
||||||
useCameraActivity(cameraConfig);
|
useCameraActivity(cameraConfig);
|
||||||
|
|
||||||
|
const cameraActive = useMemo(() => activeMotion || activeTracking, [activeMotion, activeTracking])
|
||||||
const liveMode = useCameraLiveMode(cameraConfig, preferredLiveMode);
|
const liveMode = useCameraLiveMode(cameraConfig, preferredLiveMode);
|
||||||
|
|
||||||
const [liveReady, setLiveReady] = useState(false);
|
const [liveReady, setLiveReady] = useState(false);
|
||||||
useEffect(() => {
|
useEffect(() => {
|
||||||
if (!liveReady) {
|
if (!liveReady) {
|
||||||
if (activeMotion && liveMode == "jsmpeg") {
|
if (cameraActive && liveMode == "jsmpeg") {
|
||||||
setLiveReady(true);
|
setLiveReady(true);
|
||||||
}
|
}
|
||||||
|
|
||||||
return;
|
return;
|
||||||
}
|
}
|
||||||
|
|
||||||
if (!activeMotion && !activeTracking) {
|
if (!cameraActive) {
|
||||||
setLiveReady(false);
|
setLiveReady(false);
|
||||||
}
|
}
|
||||||
}, [activeMotion, activeTracking, liveReady]);
|
}, [cameraActive, liveReady]);
|
||||||
|
|
||||||
const { payload: recording } = useRecordingsState(cameraConfig.name);
|
const { payload: recording } = useRecordingsState(cameraConfig.name);
|
||||||
|
|
||||||
@ -167,7 +168,7 @@ export default function LivePlayer({
|
|||||||
: "outline-0"
|
: "outline-0"
|
||||||
} transition-all duration-500 ${className}`}
|
} transition-all duration-500 ${className}`}
|
||||||
>
|
>
|
||||||
{(showStillWithoutActivity == false || activeMotion || activeTracking) &&
|
{(showStillWithoutActivity == false || cameraActive) &&
|
||||||
player}
|
player}
|
||||||
|
|
||||||
<div
|
<div
|
||||||
@ -179,7 +180,7 @@ export default function LivePlayer({
|
|||||||
className="w-full h-full"
|
className="w-full h-full"
|
||||||
camera={cameraConfig.name}
|
camera={cameraConfig.name}
|
||||||
showFps={false}
|
showFps={false}
|
||||||
reloadInterval={30000}
|
reloadInterval={(cameraActive && !liveReady) ? 200 : 30000}
|
||||||
/>
|
/>
|
||||||
</div>
|
</div>
|
||||||
|
|
||||||
|
|||||||
@ -1,4 +1,4 @@
|
|||||||
import { EventThumbnail } from "@/components/image/EventThumbnail";
|
import { AnimatedEventThumbnail } from "@/components/image/AnimatedEventThumbnail";
|
||||||
import LivePlayer from "@/components/player/LivePlayer";
|
import LivePlayer from "@/components/player/LivePlayer";
|
||||||
import { ScrollArea, ScrollBar } from "@/components/ui/scroll-area";
|
import { ScrollArea, ScrollBar } from "@/components/ui/scroll-area";
|
||||||
import { TooltipProvider } from "@/components/ui/tooltip";
|
import { TooltipProvider } from "@/components/ui/tooltip";
|
||||||
@ -62,7 +62,7 @@ function Live() {
|
|||||||
<div className="flex">
|
<div className="flex">
|
||||||
{events.map((event) => {
|
{events.map((event) => {
|
||||||
return (
|
return (
|
||||||
<EventThumbnail
|
<AnimatedEventThumbnail
|
||||||
key={event.id}
|
key={event.id}
|
||||||
event={event}
|
event={event}
|
||||||
onFavorite={onFavorite}
|
onFavorite={onFavorite}
|
||||||
|
|||||||
Loading…
Reference in New Issue
Block a user