Compare commits

...

5 Commits

Author SHA1 Message Date
Paul Armstrong
0232a9f94a fix(web): ensure all links on events page include pathname 2021-01-25 21:15:25 -06:00
Blake Blackshear
e8586d6459 updating for docusaurus2 docs 2021-01-25 21:13:33 -06:00
Paul Armstrong
9cf49b5bc4 fix(web): make camera latest.jpg responsive 2021-01-25 21:03:46 -06:00
Blake Blackshear
4a18fc58de add search to docs 2021-01-25 21:01:43 -06:00
Blake Blackshear
5ddfde2e72 tweaking the docs 2021-01-24 08:27:43 -06:00
13 changed files with 110 additions and 34 deletions

View File

@@ -14,9 +14,25 @@ Use of a [Google Coral Accelerator](https://coral.ai/products/) is optional, but
- Uses a very low overhead motion detection to determine where to run object detection
- Object detection with TensorFlow runs in separate processes for maximum FPS
- Communicates over MQTT for easy integration into other systems
- Records video clips of detected objects
- 24/7 recording
- Re-streaming via RTMP to reduce the number of connections to your camera
## Documentation
View the documentation at https://blakeblackshear.github.io/frigate
## Screenshots
Integration into HomeAssistant
<div>
<a href="docs/static/img/media_browser.png"><img src="docs/static/img/media_browser.png" height=400></a>
<a href="docs/static/img/notification.png"><img src="docs/static/img/notification.png" height=400></a>
</div>
Also comes with a builtin UI:
<div>
<a href="docs/static/img/home-ui.png"><img src="docs/static/img/home-ui.png" height=400></a>
<a href="docs/static/img/camera-ui.png"><img src="docs/static/img/camera-ui.png" height=400></a>
</div>
![Events](docs/static/img/events-ui.png)

View File

@@ -21,6 +21,7 @@ HassOS users can install via the addon repository. Frigate requires an MQTT serv
## Docker
Make sure you choose the right image for your architecture:
|Arch|Image Name|
|-|-|
|amd64|blakeblackshear/frigate:stable-amd64|

View File

@@ -6,7 +6,6 @@ title: Troubleshooting
### My mjpeg stream or snapshots look green and crazy
This almost always means that the width/height defined for your camera are not correct. Double check the resolution with vlc or another player. Also make sure you don't have the width and height values backwards.
Example:
![mismatched-resolution](/img/mismatched-resolution.jpg)
## "[mov,mp4,m4a,3gp,3g2,mj2 @ 0x5639eeb6e140] moov atom not found"

View File

@@ -9,6 +9,10 @@ module.exports = {
organizationName: 'blakeblackshear',
projectName: 'frigate',
themeConfig: {
algolia: {
apiKey: '81ec882db78f7fed05c51daf973f0362',
indexName: 'frigate'
},
navbar: {
title: 'Frigate',
logo: {

BIN
docs/static/img/camera-ui.png vendored Normal file

Binary file not shown.

After

Width:  |  Height:  |  Size: 944 KiB

BIN
docs/static/img/events-ui.png vendored Normal file

Binary file not shown.

After

Width:  |  Height:  |  Size: 132 KiB

BIN
docs/static/img/home-ui.png vendored Normal file

Binary file not shown.

After

Width:  |  Height:  |  Size: 2.2 MiB

View File

@@ -1,6 +1,7 @@
import { h } from 'preact';
import Box from './components/Box';
import Button from './components/Button';
import CameraImage from './components/CameraImage';
import Heading from './components/Heading';
import Switch from './components/Switch';
import { route } from 'preact-router';
@@ -27,14 +28,26 @@ export default function CameraMasks({ camera, url }) {
zones,
} = cameraConfig;
const resizeObserver = useMemo(
() =>
new ResizeObserver((entries) => {
window.requestAnimationFrame(() => {
if (Array.isArray(entries) && entries.length) {
const scaledWidth = entries[0].contentRect.width;
const scale = scaledWidth / width;
setImageScale(scale);
}
});
}),
[camera, width, setImageScale]
);
useEffect(() => {
if (!imageRef.current) {
return;
}
const scaledWidth = imageRef.current.width;
const scale = scaledWidth / width;
setImageScale(scale);
}, [imageRef.current, setImageScale]);
resizeObserver.observe(imageRef.current);
}, [resizeObserver, imageRef.current]);
const [motionMaskPoints, setMotionMaskPoints] = useState(
Array.isArray(motionMask)
@@ -226,7 +239,7 @@ ${Object.keys(objectMaskPoints)
<Box className="space-y-4">
<div className="relative">
<img ref={imageRef} className="w-full" src={`${apiHost}/api/${camera}/latest.jpg`} />
<CameraImage imageRef={imageRef} camera={camera} />
<EditableMask
onChange={handleUpdateEditable}
points={editing.subkey ? editing.set[editing.key][editing.subkey] : editing.set[editing.key]}

View File

@@ -1,5 +1,6 @@
import { h } from 'preact';
import Box from './components/Box';
import CameraImage from './components/CameraImage';
import Events from './Events';
import Heading from './components/Heading';
import { route } from 'preact-router';
@@ -23,7 +24,6 @@ export default function Cameras() {
}
function Camera({ name }) {
const apiHost = useContext(ApiHost);
const href = `/cameras/${name}`;
return (
@@ -32,7 +32,7 @@ function Camera({ name }) {
href={href}
>
<Heading size="base">{name}</Heading>
<img className="w-full" src={`${apiHost}/api/${name}/latest.jpg`} />
<CameraImage camera={name} />
</Box>
);
}

View File

@@ -11,7 +11,7 @@ export default function Events({ url } = {}) {
const apiHost = useContext(ApiHost);
const [events, setEvents] = useState([]);
const searchParams = new URL(`${window.location.protocol}//${window.location.host}${url || '/events'}`).searchParams;
const { pathname, searchParams } = new URL(`${window.location.protocol}//${window.location.host}${url || '/events'}`);
const searchParamsString = searchParams.toString();
useEffect(async () => {
@@ -32,9 +32,10 @@ export default function Events({ url } = {}) {
<div className="flex flex-wrap space-x-2">
{searchKeys.map((filterKey) => (
<UnFilterable
paramName={filterKey}
searchParams={searchParamsString}
name={`${filterKey}: ${searchParams.get(filterKey)}`}
paramName={filterKey}
pathname={pathname}
searchParams={searchParamsString}
/>
))}
</div>
@@ -71,17 +72,32 @@ export default function Events({ url } = {}) {
</a>
</Td>
<Td>
<Filterable searchParams={searchParamsString} paramName="camera" name={camera} />
<Filterable
pathname={pathname}
searchParams={searchParamsString}
paramName="camera"
name={camera}
/>
</Td>
<Td>
<Filterable searchParams={searchParamsString} paramName="label" name={label} />
<Filterable
pathname={pathname}
searchParams={searchParamsString}
paramName="label"
name={label}
/>
</Td>
<Td>{(score * 100).toFixed(2)}%</Td>
<Td>
<ul>
{zones.map((zone) => (
<li>
<Filterable searchParams={searchParamsString} paramName="zone" name={zone} />
<Filterable
pathname={pathname}
searchParams={searchParamsString}
paramName="zone"
name={zone}
/>
</li>
))}
</ul>
@@ -100,19 +116,19 @@ export default function Events({ url } = {}) {
);
}
function Filterable({ searchParams, paramName, name }) {
function Filterable({ pathname, searchParams, paramName, name }) {
const params = new URLSearchParams(searchParams);
params.set(paramName, name);
return <Link href={`?${params.toString()}`}>{name}</Link>;
return <Link href={`${pathname}?${params.toString()}`}>{name}</Link>;
}
function UnFilterable({ searchParams, paramName, name }) {
function UnFilterable({ pathname, searchParams, paramName, name }) {
const params = new URLSearchParams(searchParams);
params.delete(paramName);
return (
<a
className="bg-gray-700 text-white px-3 py-1 rounded-md hover:bg-gray-300 hover:text-gray-900 dark:bg-gray-300 dark:text-gray-900 dark:hover:bg-gray-700 dark:hover:text-white"
href={`?${params.toString()}`}
href={`${pathname}?${params.toString()}`}
>
{name}
</a>

View File

@@ -1,11 +1,10 @@
import { h } from 'preact';
import CameraImage from './CameraImage';
import { ApiHost, Config } from '../context';
import { useCallback, useEffect, useContext, useState } from 'preact/hooks';
export default function AutoUpdatingCameraImage({ camera, searchParams }) {
const config = useContext(Config);
const apiHost = useContext(ApiHost);
const cameraConfig = config.cameras[camera];
const [key, setKey] = useState(Date.now());
useEffect(() => {
@@ -17,11 +16,5 @@ export default function AutoUpdatingCameraImage({ camera, searchParams }) {
};
}, [key, searchParams]);
return (
<img
className="w-full"
src={`${apiHost}/api/${camera}/latest.jpg?cache=${key}&${searchParams}`}
alt={`Auto-updating ${camera} image`}
/>
);
return <CameraImage camera={camera} searchParams={`cache=${key}&${searchParams}`} />;
}

View File

@@ -0,0 +1,38 @@
import { h } from 'preact';
import { ApiHost, Config } from '../context';
import { useCallback, useEffect, useContext, useState } from 'preact/hooks';
export default function CameraImage({ camera, searchParams = '', imageRef }) {
const config = useContext(Config);
const apiHost = useContext(ApiHost);
const { name, width, height } = config.cameras[camera];
const aspectRatio = width / height;
const innerWidth = parseInt(window.innerWidth, 10);
const responsiveWidths = [640, 768, 1024, 1280];
if (innerWidth > responsiveWidths[responsiveWidths.length - 1]) {
responsiveWidths.push(innerWidth);
}
const src = `${apiHost}/api/${camera}/latest.jpg`;
const { srcset, sizes } = responsiveWidths.reduce(
(memo, w, i) => {
memo.srcset.push(`${src}?h=${Math.ceil(w / aspectRatio)}&${searchParams} ${w}w`);
memo.sizes.push(`(max-width: ${w}) ${Math.ceil((w / innerWidth) * 100)}vw`);
return memo;
},
{ srcset: [], sizes: [] }
);
return (
<img
className="w-full"
srcset={srcset.join(', ')}
sizes={sizes.join(', ')}
src={`${srcset[srcset.length - 1]}`}
alt={name}
ref={imageRef}
/>
);
}

View File

@@ -2,13 +2,9 @@ import { h } from 'preact';
import { useCallback, useState } from 'preact/hooks';
export default function Switch({ checked, label, id, onChange }) {
const handleChange = useCallback(
(event) => {
console.log(event.target.checked, !checked);
onChange(id, !checked);
},
[id, onChange, checked]
);
const handleChange = useCallback(() => {
onChange(id, !checked);
}, [id, onChange, checked]);
return (
<label for={id} className="flex items-center cursor-pointer">