From 34bdf2fc105bd0a249bc9f1dfa7e0ffe1be4abaf Mon Sep 17 00:00:00 2001 From: Nicolas Mowen Date: Sun, 26 Feb 2023 06:05:27 -0700 Subject: [PATCH 01/47] Clean up output for vainfo and ffprobe (#5586) * Clean up output for vainfo and ffprobe * Fix cleanup * Format vainfo display * Fix ffprobe output * Fix stringification * remove unused --- frigate/http.py | 10 ++--- web/src/components/Dialog.jsx | 2 +- web/src/routes/System.jsx | 69 +++++++++++++++++++++++++++++++---- 3 files changed, 67 insertions(+), 14 deletions(-) diff --git a/frigate/http.py b/frigate/http.py index a5f9834eb..1649ab55e 100644 --- a/frigate/http.py +++ b/frigate/http.py @@ -1296,12 +1296,12 @@ def ffprobe(): output.append( { "return_code": ffprobe.returncode, - "stderr": json.loads(ffprobe.stderr.decode("unicode_escape").strip()) - if ffprobe.stderr.decode() - else {}, + "stderr": ffprobe.stderr.decode("unicode_escape").strip() + if ffprobe.returncode != 0 + else "", "stdout": json.loads(ffprobe.stdout.decode("unicode_escape").strip()) - if ffprobe.stdout.decode() - else {}, + if ffprobe.returncode == 0 + else "", } ) diff --git a/web/src/components/Dialog.jsx b/web/src/components/Dialog.jsx index ad4f57d72..6bf9e3105 100644 --- a/web/src/components/Dialog.jsx +++ b/web/src/components/Dialog.jsx @@ -21,7 +21,7 @@ export default function Dialog({ children, portalRootID = 'dialogs' }) { >
diff --git a/web/src/routes/System.jsx b/web/src/routes/System.jsx index e6b383114..8a5f2c28f 100644 --- a/web/src/routes/System.jsx +++ b/web/src/routes/System.jsx @@ -49,14 +49,14 @@ export default function System() { }); if (response.status === 200) { - setState({ ...state, showFfprobe: true, ffprobe: JSON.stringify(response.data, null, 2) }); + setState({ ...state, showFfprobe: true, ffprobe: response.data }); } else { setState({ ...state, showFfprobe: true, ffprobe: 'There was an error getting the ffprobe output.' }); } }; const onCopyFfprobe = async () => { - copy(JSON.stringify(state.ffprobe, null, 2)); + copy(JSON.stringify(state.ffprobe).replace(/[\\\s]+/gi, '')); setState({ ...state, ffprobe: '', showFfprobe: false }); }; @@ -68,14 +68,18 @@ export default function System() { const response = await axios.get('vainfo'); if (response.status === 200) { - setState({ ...state, showVainfo: true, vainfo: JSON.stringify(response.data, null, 2) }); + setState({ + ...state, + showVainfo: true, + vainfo: response.data, + }); } else { setState({ ...state, showVainfo: true, vainfo: 'There was an error getting the vainfo output.' }); } }; const onCopyVainfo = async () => { - copy(JSON.stringify(state.vainfo, null, 2)); + copy(JSON.stringify(state.vainfo).replace(/[\\\s]+/gi, '')); setState({ ...state, vainfo: '', showVainfo: false }); }; @@ -107,9 +111,52 @@ export default function System() { {state.showFfprobe && ( -
+
Ffprobe Output - {state.ffprobe != '' ?

{state.ffprobe}

: } + {state.ffprobe != '' ? ( +
+ {state.ffprobe.map((stream, idx) => ( +
+
Stream {idx}:
+
Return Code: {stream.return_code}
+
+ {stream.return_code == 0 ? ( +
+ {stream.stdout.streams.map((codec, idx) => ( +
+ {codec.width ? ( +
+
Video:
+
+
Codec: {codec.codec_long_name}
+
+ Resolution: {codec.width}x{codec.height} +
+
FPS: {codec.avg_frame_rate == '0/0' ? 'Unknown' : codec.avg_frame_rate}
+
+
+ ) : ( +
+
Audio:
+
+
Codec: {codec.codec_long_name}
+
+
+ )} +
+ ))} +
+ ) : ( +
+
Error: {stream.stderr}
+
+ )} +
+ ))} +
+ ) : ( + + )}
+ ) : ( + + ) + )} +
+
+ {timeIndex >= 0 ? ( +
+ Disclaimer: This data comes from the detect feed but is shown on the recordings, it is unlikely that the + streams are perfectly in sync so the bounding box and the footage will not line up perfectly. +
+ ) : null} +
+ ); +} + +function getTimelineItemDescription(config, timelineItem, event) { + if (timelineItem.class_type == 'visible') { + return `${event.label} detected at ${formatUnixTimestampToDateTime(timelineItem.timestamp, { + date_style: 'short', + time_style: 'medium', + time_format: config.ui.time_format, + })}`; + } else if (timelineItem.class_type == 'entered_zone') { + return `${event.label.replaceAll('_', ' ')} entered ${timelineItem.data.zones + .join(' and ') + .replaceAll('_', ' ')} at ${formatUnixTimestampToDateTime(timelineItem.timestamp, { + date_style: 'short', + time_style: 'medium', + time_format: config.ui.time_format, + })}`; + } + + return `${event.label} left at ${formatUnixTimestampToDateTime(timelineItem.timestamp, { + date_style: 'short', + time_style: 'medium', + time_format: config.ui.time_format, + })}`; +} diff --git a/web/src/components/Tooltip.jsx b/web/src/components/Tooltip.jsx index 034623af2..ba56f20b8 100644 --- a/web/src/components/Tooltip.jsx +++ b/web/src/components/Tooltip.jsx @@ -49,7 +49,7 @@ export default function Tooltip({ relativeTo, text }) { const tooltip = (
= 0 ? 'opacity-100 scale-100' : '' }`} ref={ref} diff --git a/web/src/icons/Exit.jsx b/web/src/icons/Exit.jsx new file mode 100644 index 000000000..35878d833 --- /dev/null +++ b/web/src/icons/Exit.jsx @@ -0,0 +1,12 @@ +import { h } from 'preact'; +import { memo } from 'preact/compat'; + +function Exit({ className = '' }) { + return ( + + + + ); +} + +export default memo(Exit); diff --git a/web/src/icons/Play.jsx b/web/src/icons/Play.jsx index 995b68d66..5d1182631 100644 --- a/web/src/icons/Play.jsx +++ b/web/src/icons/Play.jsx @@ -1,9 +1,9 @@ import { h } from 'preact'; import { memo } from 'preact/compat'; -export function Play() { +export function Play({ className = '' }) { return ( - + ); diff --git a/web/src/routes/Events.jsx b/web/src/routes/Events.jsx index 25036f505..c676cb108 100644 --- a/web/src/routes/Events.jsx +++ b/web/src/routes/Events.jsx @@ -26,6 +26,7 @@ import Dialog from '../components/Dialog'; import MultiSelect from '../components/MultiSelect'; import { formatUnixTimestampToDateTime, getDurationFromTimestamps } from '../utils/dateUtil'; import TimeAgo from '../components/TimeAgo'; +import TimelineSummary from '../components/TimelineSummary'; const API_LIMIT = 25; @@ -60,6 +61,7 @@ export default function Events({ path, ...props }) { }); const [uploading, setUploading] = useState([]); const [viewEvent, setViewEvent] = useState(); + const [eventOverlay, setEventOverlay] = useState(); const [eventDetailType, setEventDetailType] = useState('clip'); const [downloadEvent, setDownloadEvent] = useState({ id: null, @@ -180,6 +182,18 @@ export default function Events({ path, ...props }) { onFilter(name, items); }; + const onEventFrameSelected = (event, frame) => { + const eventDuration = event.end_time - event.start_time; + + if (this.player) { + this.player.pause(); + const videoOffset = this.player.duration() - eventDuration; + const startTime = videoOffset + (frame.timestamp - event.start_time); + this.player.currentTime(startTime); + setEventOverlay(frame); + } + }; + const datePicker = useRef(); const downloadButton = useRef(); @@ -526,7 +540,7 @@ export default function Events({ path, ...props }) {