forked from Github/frigate
Compare commits
7 Commits
v0.11.0-rc
...
v0.11.0-rc
| Author | SHA1 | Date | |
|---|---|---|---|
|
|
be7b858cbd | ||
|
|
a6a0e4d1de | ||
|
|
14faf0b2f6 | ||
|
|
bdfe4a961a | ||
|
|
1bc8d94312 | ||
|
|
7e9f913ff6 | ||
|
|
b6f799e641 |
@@ -67,3 +67,14 @@ model:
|
|||||||
```
|
```
|
||||||
|
|
||||||
Note that if you rename objects in the labelmap, you will also need to update your `objects -> track` list as well.
|
Note that if you rename objects in the labelmap, you will also need to update your `objects -> track` list as well.
|
||||||
|
|
||||||
|
## Custom ffmpeg build
|
||||||
|
|
||||||
|
Included with Frigate is a build of ffmpeg that works for the vast majority of users. However, there exists some hardware setups which have incompatibilities with the included build. In this case, a docker volume mapping can be used to overwrite the included ffmpeg build with an ffmpeg build that works for your specific hardware setup.
|
||||||
|
|
||||||
|
To do this:
|
||||||
|
1. Download your ffmpeg build and uncompress to a folder on the host (let's use `/home/appdata/frigate/custom-ffmpeg` for this example).
|
||||||
|
2. Update your docker-compose or docker CLI to include `'/home/appdata/frigate/custom-ffmpeg':'/usr/lib/btbn-ffmpeg':'ro'` in the volume mappings.
|
||||||
|
3. Restart frigate and the custom version will be used if the mapping was done correctly.
|
||||||
|
|
||||||
|
NOTE: The folder that is mapped from the host needs to be the folder that contains `/bin`. So if the full structure is `/home/appdata/frigate/custom-ffmpeg/bin/ffmpeg` then `/home/appdata/frigate/custom-ffmpeg` needs to be mapped to `/usr/lib/btbn-ffmpeg`.
|
||||||
|
|||||||
@@ -112,3 +112,14 @@ If your cameras do not support TCP connections for RTSP, you can use UDP.
|
|||||||
ffmpeg:
|
ffmpeg:
|
||||||
input_args: -avoid_negative_ts make_zero -fflags +genpts+discardcorrupt -rtsp_transport udp -timeout 5000000 -use_wallclock_as_timestamps 1
|
input_args: -avoid_negative_ts make_zero -fflags +genpts+discardcorrupt -rtsp_transport udp -timeout 5000000 -use_wallclock_as_timestamps 1
|
||||||
```
|
```
|
||||||
|
|
||||||
|
### Unifi Protect Cameras
|
||||||
|
|
||||||
|
In the Unifi 2.0 update Unifi Protect Cameras had a change in audio sample rate which causes issues for ffmpeg. The input rate needs to be set for record and rtmp.
|
||||||
|
|
||||||
|
```yaml
|
||||||
|
ffmpeg:
|
||||||
|
output_args:
|
||||||
|
record: -f segment -segment_time 10 -segment_format mp4 -reset_timestamps 1 -strftime 1 -c:v copy -ar 44100 -c:a aac
|
||||||
|
rtmp: -c:v copy -f flv -ar 44100 -c:a aac
|
||||||
|
```
|
||||||
|
|||||||
@@ -21,7 +21,7 @@ ffmpeg:
|
|||||||
ffmpeg:
|
ffmpeg:
|
||||||
hwaccel_args: -hwaccel vaapi -hwaccel_device /dev/dri/renderD128 -hwaccel_output_format yuv420p
|
hwaccel_args: -hwaccel vaapi -hwaccel_device /dev/dri/renderD128 -hwaccel_output_format yuv420p
|
||||||
```
|
```
|
||||||
**NOTICE**: With some of the processors, like the J4125, the default driver `iHD` doesn't seem to work correctly for hardware acceleration. You may need to change the driver to `i965` by adding the following environment variable `LIBVA_DRIVER_NAME_JELLYFIN=i965` to your docker-compose file.
|
**NOTICE**: With some of the processors, like the J4125, the default driver `iHD` doesn't seem to work correctly for hardware acceleration. You may need to change the driver to `i965` by adding the following environment variable `LIBVA_DRIVER_NAME=i965` to your docker-compose file.
|
||||||
|
|
||||||
### Intel-based CPUs (>=10th Generation) via Quicksync
|
### Intel-based CPUs (>=10th Generation) via Quicksync
|
||||||
|
|
||||||
|
|||||||
@@ -786,33 +786,38 @@ def recording_clip(camera_name, start_ts, end_ts):
|
|||||||
file_name = f"clip_{camera_name}_{start_ts}-{end_ts}.mp4"
|
file_name = f"clip_{camera_name}_{start_ts}-{end_ts}.mp4"
|
||||||
path = f"/tmp/cache/{file_name}"
|
path = f"/tmp/cache/{file_name}"
|
||||||
|
|
||||||
ffmpeg_cmd = [
|
if not os.path.exists(path):
|
||||||
"ffmpeg",
|
ffmpeg_cmd = [
|
||||||
"-y",
|
"ffmpeg",
|
||||||
"-protocol_whitelist",
|
"-y",
|
||||||
"pipe,file",
|
"-protocol_whitelist",
|
||||||
"-f",
|
"pipe,file",
|
||||||
"concat",
|
"-f",
|
||||||
"-safe",
|
"concat",
|
||||||
"0",
|
"-safe",
|
||||||
"-i",
|
"0",
|
||||||
"/dev/stdin",
|
"-i",
|
||||||
"-c",
|
"/dev/stdin",
|
||||||
"copy",
|
"-c",
|
||||||
"-movflags",
|
"copy",
|
||||||
"+faststart",
|
"-movflags",
|
||||||
path,
|
"+faststart",
|
||||||
]
|
path,
|
||||||
|
]
|
||||||
|
p = sp.run(
|
||||||
|
ffmpeg_cmd,
|
||||||
|
input="\n".join(playlist_lines),
|
||||||
|
encoding="ascii",
|
||||||
|
capture_output=True,
|
||||||
|
)
|
||||||
|
|
||||||
p = sp.run(
|
if p.returncode != 0:
|
||||||
ffmpeg_cmd,
|
logger.error(p.stderr)
|
||||||
input="\n".join(playlist_lines),
|
return f"Could not create clip from recordings for {camera_name}.", 500
|
||||||
encoding="ascii",
|
else:
|
||||||
capture_output=True,
|
logger.debug(
|
||||||
)
|
f"Ignoring subsequent request for {path} as it already exists in the cache."
|
||||||
if p.returncode != 0:
|
)
|
||||||
logger.error(p.stderr)
|
|
||||||
return f"Could not create clip from recordings for {camera_name}.", 500
|
|
||||||
|
|
||||||
response = make_response()
|
response = make_response()
|
||||||
response.headers["Content-Description"] = "File Transfer"
|
response.headers["Content-Description"] = "File Transfer"
|
||||||
|
|||||||
@@ -440,7 +440,13 @@ def intersects_any(box_a, boxes):
|
|||||||
|
|
||||||
|
|
||||||
def detect(
|
def detect(
|
||||||
object_detector, frame, model_shape, region, objects_to_track, object_filters
|
detect_config: DetectConfig,
|
||||||
|
object_detector,
|
||||||
|
frame,
|
||||||
|
model_shape,
|
||||||
|
region,
|
||||||
|
objects_to_track,
|
||||||
|
object_filters,
|
||||||
):
|
):
|
||||||
tensor_input = create_tensor_input(frame, model_shape, region)
|
tensor_input = create_tensor_input(frame, model_shape, region)
|
||||||
|
|
||||||
@@ -449,10 +455,10 @@ def detect(
|
|||||||
for d in region_detections:
|
for d in region_detections:
|
||||||
box = d[2]
|
box = d[2]
|
||||||
size = region[2] - region[0]
|
size = region[2] - region[0]
|
||||||
x_min = int((box[1] * size) + region[0])
|
x_min = int(max(0, (box[1] * size) + region[0]))
|
||||||
y_min = int((box[0] * size) + region[1])
|
y_min = int(max(0, (box[0] * size) + region[1]))
|
||||||
x_max = int((box[3] * size) + region[0])
|
x_max = int(min(detect_config.width, (box[3] * size) + region[0]))
|
||||||
y_max = int((box[2] * size) + region[1])
|
y_max = int(min(detect_config.height, (box[2] * size) + region[1]))
|
||||||
width = x_max - x_min
|
width = x_max - x_min
|
||||||
height = y_max - y_min
|
height = y_max - y_min
|
||||||
area = width * height
|
area = width * height
|
||||||
@@ -620,6 +626,7 @@ def process_frames(
|
|||||||
for region in regions:
|
for region in regions:
|
||||||
detections.extend(
|
detections.extend(
|
||||||
detect(
|
detect(
|
||||||
|
detect_config,
|
||||||
object_detector,
|
object_detector,
|
||||||
frame,
|
frame,
|
||||||
model_shape,
|
model_shape,
|
||||||
@@ -679,6 +686,7 @@ def process_frames(
|
|||||||
|
|
||||||
selected_objects.extend(
|
selected_objects.extend(
|
||||||
detect(
|
detect(
|
||||||
|
detect_config,
|
||||||
object_detector,
|
object_detector,
|
||||||
frame,
|
frame,
|
||||||
model_shape,
|
model_shape,
|
||||||
|
|||||||
@@ -41,7 +41,7 @@ export default function RecordingPlaylist({ camera, recordings, selectedDate })
|
|||||||
const openClass = active ? '-left-6' : 'right-0';
|
const openClass = active ? '-left-6' : 'right-0';
|
||||||
|
|
||||||
return (
|
return (
|
||||||
<div className="flex absolute inset-y-0 right-0 w-9/12 md:w-1/2 lg:w-3/5 max-w-md text-base text-white font-sans">
|
<div className="flex absolute z-10 inset-y-0 right-0 w-9/12 md:w-1/2 lg:w-3/5 max-w-md text-base text-white font-sans">
|
||||||
<div
|
<div
|
||||||
onClick={toggle}
|
onClick={toggle}
|
||||||
className={`absolute ${openClass} cursor-pointer items-center self-center rounded-tl-lg rounded-bl-lg border border-r-0 w-6 h-20 py-7 bg-gray-800 bg-opacity-70`}
|
className={`absolute ${openClass} cursor-pointer items-center self-center rounded-tl-lg rounded-bl-lg border border-r-0 w-6 h-20 py-7 bg-gray-800 bg-opacity-70`}
|
||||||
|
|||||||
@@ -27,12 +27,14 @@ export function Tabs({ children, selectedIndex: selectedIndexProp, onChange, cla
|
|||||||
);
|
);
|
||||||
}
|
}
|
||||||
|
|
||||||
export function TextTab({ selected, text, onClick }) {
|
export function TextTab({ selected, text, onClick, disabled }) {
|
||||||
const selectedStyle = selected
|
const selectedStyle = disabled
|
||||||
? 'text-white bg-blue-500 dark:text-black dark:bg-white'
|
? 'text-gray-400 dark:text-gray-600 bg-transparent'
|
||||||
: 'text-black dark:text-white bg-transparent';
|
: selected
|
||||||
|
? 'text-white bg-blue-500 dark:text-black dark:bg-white'
|
||||||
|
: 'text-black dark:text-white bg-transparent';
|
||||||
return (
|
return (
|
||||||
<button onClick={onClick} className={`rounded-full px-4 py-2 ${selectedStyle}`}>
|
<button onClick={onClick} disabled={disabled} className={`rounded-full px-4 py-2 ${selectedStyle}`}>
|
||||||
<span>{text}</span>
|
<span>{text}</span>
|
||||||
</button>
|
</button>
|
||||||
);
|
);
|
||||||
|
|||||||
@@ -2,6 +2,7 @@ import { h, Fragment } from 'preact';
|
|||||||
import { route } from 'preact-router';
|
import { route } from 'preact-router';
|
||||||
import ActivityIndicator from '../components/ActivityIndicator';
|
import ActivityIndicator from '../components/ActivityIndicator';
|
||||||
import Heading from '../components/Heading';
|
import Heading from '../components/Heading';
|
||||||
|
import { Tabs, TextTab } from '../components/Tabs';
|
||||||
import { useApiHost } from '../api';
|
import { useApiHost } from '../api';
|
||||||
import useSWR from 'swr';
|
import useSWR from 'swr';
|
||||||
import useSWRInfinite from 'swr/infinite';
|
import useSWRInfinite from 'swr/infinite';
|
||||||
@@ -54,6 +55,7 @@ export default function Events({ path, ...props }) {
|
|||||||
});
|
});
|
||||||
const [uploading, setUploading] = useState([]);
|
const [uploading, setUploading] = useState([]);
|
||||||
const [viewEvent, setViewEvent] = useState();
|
const [viewEvent, setViewEvent] = useState();
|
||||||
|
const [eventDetailType, setEventDetailType] = useState('clip');
|
||||||
const [downloadEvent, setDownloadEvent] = useState({
|
const [downloadEvent, setDownloadEvent] = useState({
|
||||||
id: null,
|
id: null,
|
||||||
has_clip: false,
|
has_clip: false,
|
||||||
@@ -235,6 +237,10 @@ export default function Events({ path, ...props }) {
|
|||||||
}
|
}
|
||||||
};
|
};
|
||||||
|
|
||||||
|
const handleEventDetailTabChange = (index) => {
|
||||||
|
setEventDetailType(index == 0 ? 'clip' : 'image');
|
||||||
|
};
|
||||||
|
|
||||||
if (!config) {
|
if (!config) {
|
||||||
return <ActivityIndicator />;
|
return <ActivityIndicator />;
|
||||||
}
|
}
|
||||||
@@ -495,9 +501,15 @@ export default function Events({ path, ...props }) {
|
|||||||
{viewEvent !== event.id ? null : (
|
{viewEvent !== event.id ? null : (
|
||||||
<div className="space-y-4">
|
<div className="space-y-4">
|
||||||
<div className="mx-auto max-w-7xl">
|
<div className="mx-auto max-w-7xl">
|
||||||
{event.has_clip ? (
|
<div className='flex justify-center w-full py-2'>
|
||||||
<>
|
<Tabs selectedIndex={event.has_clip && eventDetailType == 'clip' ? 0 : 1} onChange={handleEventDetailTabChange} className='justify'>
|
||||||
<Heading size="lg">Clip</Heading>
|
<TextTab text='Clip' disabled={!event.has_clip} />
|
||||||
|
<TextTab text={event.has_snapshot ? 'Snapshot' : 'Thumbnail'} />
|
||||||
|
</Tabs>
|
||||||
|
</div>
|
||||||
|
|
||||||
|
<div>
|
||||||
|
{((eventDetailType == 'clip') && event.has_clip) ? (
|
||||||
<VideoPlayer
|
<VideoPlayer
|
||||||
options={{
|
options={{
|
||||||
preload: 'auto',
|
preload: 'auto',
|
||||||
@@ -512,23 +524,22 @@ export default function Events({ path, ...props }) {
|
|||||||
seekOptions={{ forward: 10, back: 5 }}
|
seekOptions={{ forward: 10, back: 5 }}
|
||||||
onReady={() => {}}
|
onReady={() => {}}
|
||||||
/>
|
/>
|
||||||
</>
|
) : null }
|
||||||
) : (
|
|
||||||
<div className="flex justify-center">
|
{((eventDetailType == 'image') || !event.has_clip) ? (
|
||||||
<div>
|
<div className="flex justify-center">
|
||||||
<Heading size="sm">{event.has_snapshot ? 'Best Image' : 'Thumbnail'}</Heading>
|
|
||||||
<img
|
<img
|
||||||
className="flex-grow-0"
|
className="flex-grow-0"
|
||||||
src={
|
src={
|
||||||
event.has_snapshot
|
event.has_snapshot
|
||||||
? `${apiHost}/api/events/${event.id}/snapshot.jpg`
|
? `${apiHost}/api/events/${event.id}/snapshot.jpg`
|
||||||
: `data:image/jpeg;base64,${event.thumbnail}`
|
: `${apiHost}/api/events/${event.id}/thumbnail.jpg`
|
||||||
}
|
}
|
||||||
alt={`${event.label} at ${(event.top_score * 100).toFixed(0)}% confidence`}
|
alt={`${event.label} at ${(event.top_score * 100).toFixed(0)}% confidence`}
|
||||||
/>
|
/>
|
||||||
</div>
|
</div>
|
||||||
</div>
|
) : null }
|
||||||
)}
|
</div>
|
||||||
</div>
|
</div>
|
||||||
</div>
|
</div>
|
||||||
)}
|
)}
|
||||||
|
|||||||
Reference in New Issue
Block a user