Make ffprobe button show dialog with output and option to copy

This commit is contained in:
Nick Mowen 2022-11-10 07:13:10 -07:00
parent 7f35205f3d
commit 0772210bef
3 changed files with 50 additions and 19 deletions

View File

@ -627,21 +627,21 @@ def ffprobe(camera_name):
{ {
"input_roles": input.roles, "input_roles": input.roles,
"return_code": ffprobe.returncode, "return_code": ffprobe.returncode,
"stderr": ffprobe.stderr, "stderr": ffprobe.stderr.decode().strip(),
"stdout": ffprobe.stdout, "stdout": ffprobe.stdout.decode().strip(),
} }
) )
return jsonify(output) return jsonify(output)
else: else:
# user has single stream # user has single stream
ffprobe = ffprobe_stream(config.ffmpeg.inputs[0].path) ffprobe: sp.CompletedProcess = ffprobe_stream(config.ffmpeg.inputs[0].path)
return jsonify( return jsonify(
{ {
"input_roles": config.ffmpeg.inputs[0].roles, "input_roles": config.ffmpeg.inputs[0].roles,
"return_code": ffprobe.returncode, "return_code": ffprobe.returncode,
"stderr": ffprobe.stderr, "stderr": ffprobe.stderr.decode().strip(),
"stdout": ffprobe.stdout, "stdout": ffprobe.stdout.decode().strip(),
} }
) )

View File

@ -716,6 +716,8 @@ def ffprobe_stream(path: str) -> sp.CompletedProcess:
"""Run ffprobe on stream.""" """Run ffprobe on stream."""
ffprobe_cmd = [ ffprobe_cmd = [
"ffprobe", "ffprobe",
"-print_format",
"json",
"-show_entries", "-show_entries",
"stream=codec_long_name,width,height,bit_rate,duration,display_aspect_ratio,avg_frame_rate", "stream=codec_long_name,width,height,bit_rate,duration,display_aspect_ratio,avg_frame_rate",
"-loglevel", "-loglevel",

View File

@ -7,11 +7,13 @@ import { useMqtt } from '../api/mqtt';
import useSWR from 'swr'; import useSWR from 'swr';
import axios from 'axios'; import axios from 'axios';
import { Table, Tbody, Thead, Tr, Th, Td } from '../components/Table'; import { Table, Tbody, Thead, Tr, Th, Td } from '../components/Table';
import { useCallback } from 'preact/hooks'; import { useCallback, useState } from 'preact/hooks';
import Dialog from '../components/Dialog';
const emptyObject = Object.freeze({}); const emptyObject = Object.freeze({});
export default function Debug() { export default function Debug() {
const [state, setState] = useState({ showFfprobe: false, ffprobe: '' });
const { data: config } = useSWR('config'); const { data: config } = useSWR('config');
const { const {
@ -22,7 +24,6 @@ export default function Debug() {
const { cpu_usages, detectors, service = {}, detection_fps: _, ...cameras } = stats || initialStats || emptyObject; const { cpu_usages, detectors, service = {}, detection_fps: _, ...cameras } = stats || initialStats || emptyObject;
const detectorNames = Object.keys(detectors || emptyObject); const detectorNames = Object.keys(detectors || emptyObject);
const detectorDataKeys = Object.keys(detectors ? detectors[detectorNames[0]] : emptyObject);
const cameraNames = Object.keys(cameras || emptyObject); const cameraNames = Object.keys(cameras || emptyObject);
const handleCopyConfig = useCallback(() => { const handleCopyConfig = useCallback(() => {
@ -32,23 +33,51 @@ export default function Debug() {
copy(); copy();
}, [config]); }, [config]);
const onCopyFfprobe = async (camera, e) => { const onHandleFfprobe = async (camera, e) => {
if (e) { if (e) {
e.stopPropagation(); e.stopPropagation();
} }
setState({ ...state, showFfprobe: true });
const response = await axios.get(`${camera}/ffprobe`); const response = await axios.get(`${camera}/ffprobe`);
if (response.status === 200) { if (response.status === 200) {
await window.navigator.clipboard.writeText(JSON.stringify(response.data, null, 2)); setState({ showFfprobe: true, ffprobe: JSON.stringify(response.data, null, 2) });
} else {
setState({ ...state, ffprobe: 'There was an error getting the ffprobe output.' });
} }
}; };
const onCopyFfprobe = async () => {
await window.navigator.clipboard.writeText(JSON.stringify(state.ffprobe, null, 2));
setState({ ...state, ffprobe: '', showFfprobe: false });
};
return ( return (
<div className="space-y-4 p-2 px-4"> <div className="space-y-4 p-2 px-4">
<Heading> <Heading>
Debug <span className="text-sm">{service.version}</span> Debug <span className="text-sm">{service.version}</span>
</Heading> </Heading>
{state.showFfprobe && (
<Dialog>
<div className="p-4">
<Heading size="lg">Ffprobe Output</Heading>
{state.ffprobe != '' ? <p className="mb-2">{state.ffprobe}</p> : <ActivityIndicator />}
</div>
<div className="p-2 flex justify-start flex-row-reverse space-x-2">
<Button className="ml-2" onClick={() => onCopyFfprobe()} type="text">
Copy
</Button>
<Button
className="ml-2"
onClick={() => setState({ ...state, ffprobe: '', showFfprobe: false })}
type="text"
>
Close
</Button>
</div>
</Dialog>
)}
{!detectors ? ( {!detectors ? (
<div> <div>
@ -56,7 +85,7 @@ export default function Debug() {
</div> </div>
) : ( ) : (
<Fragment> <Fragment>
<Heading>Detectors</Heading> <Heading size="lg">Detectors</Heading>
<div data-testid="detectors" className="grid grid-cols-1 3xl:grid-cols-3 md:grid-cols-2 gap-4"> <div data-testid="detectors" className="grid grid-cols-1 3xl:grid-cols-3 md:grid-cols-2 gap-4">
{detectorNames.map((detector) => ( {detectorNames.map((detector) => (
<div key={detector} className="dark:bg-gray-800 shadow-md hover:shadow-lg rounded-lg transition-shadow"> <div key={detector} className="dark:bg-gray-800 shadow-md hover:shadow-lg rounded-lg transition-shadow">
@ -65,16 +94,16 @@ export default function Debug() {
<Table className="w-full"> <Table className="w-full">
<Thead> <Thead>
<Tr> <Tr>
{detectorDataKeys.map((name) => ( <Th>P-ID</Th>
<Th key={name}>{name.replace('_', ' ')}</Th> <Th>Detection Start</Th>
))} <Th>Inference Speed</Th>
</Tr> </Tr>
</Thead> </Thead>
<Tbody> <Tbody>
<Tr> <Tr>
{detectorDataKeys.map((name) => ( <Td>{detectors[detector]['pid']}</Td>
<Td key={`${name}-${detector}`}>{detectors[detector][name]}</Td> <Td>{detectors[detector]['detection_start']}</Td>
))} <Td>{detectors[detector]['inference_speed']}</Td>
</Tr> </Tr>
</Tbody> </Tbody>
</Table> </Table>
@ -83,20 +112,20 @@ export default function Debug() {
))} ))}
</div> </div>
<Heading>Cameras</Heading> <Heading size="lg">Cameras</Heading>
<div data-testid="cameras" className="grid grid-cols-1 3xl:grid-cols-3 md:grid-cols-2 gap-4"> <div data-testid="cameras" className="grid grid-cols-1 3xl:grid-cols-3 md:grid-cols-2 gap-4">
{cameraNames.map((camera) => ( {cameraNames.map((camera) => (
<div key={camera} className="dark:bg-gray-800 shadow-md hover:shadow-lg rounded-lg transition-shadow"> <div key={camera} className="dark:bg-gray-800 shadow-md hover:shadow-lg rounded-lg transition-shadow">
<div className="text-lg flex justify-between p-4"> <div className="text-lg flex justify-between p-4">
<Link href={`/cameras/${camera}`}>{camera.replaceAll('_', ' ')}</Link> <Link href={`/cameras/${camera}`}>{camera.replaceAll('_', ' ')}</Link>
<Button onClick={(e) => onCopyFfprobe(camera, e)}>copy ffprobe</Button> <Button onClick={(e) => onHandleFfprobe(camera, e)}>ffprobe</Button>
</div> </div>
<div className="p-2"> <div className="p-2">
<Table className="w-full"> <Table className="w-full">
<Thead> <Thead>
<Tr> <Tr>
<Th>Process</Th> <Th>Process</Th>
<Th>Process ID</Th> <Th>P-ID</Th>
<Th>fps</Th> <Th>fps</Th>
<Th>Cpu %</Th> <Th>Cpu %</Th>
<Th>Memory %</Th> <Th>Memory %</Th>