Compare commits

..

7 Commits

Author SHA1 Message Date
Nicolas Mowen
be7b858cbd Remove JELLYFIN from intel driver name hwaccel docs (#3855) 2022-09-16 10:47:22 -06:00
Aksel Skauge Mellbye
a6a0e4d1de Bring recording playlist in front of video.js modals
Ensure that the playlist doesn't get covered by video.js error messages,
preventing interaction.
2022-09-15 06:36:32 -05:00
Aksel
14faf0b2f6 Show snapshots on events page (#3763)
* Add tabs to show snapshot or thumbnail as part of event details,
  even if event has a clip available.
* Add ability for TextTab to render as disabled.
2022-09-15 06:35:51 -05:00
Nicolas Mowen
bdfe4a961a Add documentation for custom ffmpeg builds (#3771)
* Add instructions for custom ffmpeg build

* Add note about mapping /bin
2022-09-15 06:34:07 -05:00
Nick Mowen
1bc8d94312 Fix edge of frame after region adjustments 2022-09-15 06:33:32 -05:00
Nick Mowen
7e9f913ff6 Fix edge case where browser sends multiple requests to download file. 2022-09-15 06:32:54 -05:00
Nick Mowen
b6f799e641 Add camera specific for unify protect cameras 2022-08-28 11:14:01 -05:00
8 changed files with 97 additions and 49 deletions

View File

@@ -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.
## 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`.

View File

@@ -112,3 +112,14 @@ If your cameras do not support TCP connections for RTSP, you can use UDP.
ffmpeg:
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
```

View File

@@ -21,7 +21,7 @@ ffmpeg:
ffmpeg:
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

View File

@@ -786,33 +786,38 @@ def recording_clip(camera_name, start_ts, end_ts):
file_name = f"clip_{camera_name}_{start_ts}-{end_ts}.mp4"
path = f"/tmp/cache/{file_name}"
ffmpeg_cmd = [
"ffmpeg",
"-y",
"-protocol_whitelist",
"pipe,file",
"-f",
"concat",
"-safe",
"0",
"-i",
"/dev/stdin",
"-c",
"copy",
"-movflags",
"+faststart",
path,
]
if not os.path.exists(path):
ffmpeg_cmd = [
"ffmpeg",
"-y",
"-protocol_whitelist",
"pipe,file",
"-f",
"concat",
"-safe",
"0",
"-i",
"/dev/stdin",
"-c",
"copy",
"-movflags",
"+faststart",
path,
]
p = sp.run(
ffmpeg_cmd,
input="\n".join(playlist_lines),
encoding="ascii",
capture_output=True,
)
p = sp.run(
ffmpeg_cmd,
input="\n".join(playlist_lines),
encoding="ascii",
capture_output=True,
)
if p.returncode != 0:
logger.error(p.stderr)
return f"Could not create clip from recordings for {camera_name}.", 500
if p.returncode != 0:
logger.error(p.stderr)
return f"Could not create clip from recordings for {camera_name}.", 500
else:
logger.debug(
f"Ignoring subsequent request for {path} as it already exists in the cache."
)
response = make_response()
response.headers["Content-Description"] = "File Transfer"

View File

@@ -440,7 +440,13 @@ def intersects_any(box_a, boxes):
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)
@@ -449,10 +455,10 @@ def detect(
for d in region_detections:
box = d[2]
size = region[2] - region[0]
x_min = int((box[1] * size) + region[0])
y_min = int((box[0] * size) + region[1])
x_max = int((box[3] * size) + region[0])
y_max = int((box[2] * size) + region[1])
x_min = int(max(0, (box[1] * size) + region[0]))
y_min = int(max(0, (box[0] * size) + region[1]))
x_max = int(min(detect_config.width, (box[3] * size) + region[0]))
y_max = int(min(detect_config.height, (box[2] * size) + region[1]))
width = x_max - x_min
height = y_max - y_min
area = width * height
@@ -620,6 +626,7 @@ def process_frames(
for region in regions:
detections.extend(
detect(
detect_config,
object_detector,
frame,
model_shape,
@@ -679,6 +686,7 @@ def process_frames(
selected_objects.extend(
detect(
detect_config,
object_detector,
frame,
model_shape,

View File

@@ -41,7 +41,7 @@ export default function RecordingPlaylist({ camera, recordings, selectedDate })
const openClass = active ? '-left-6' : 'right-0';
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
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`}

View File

@@ -27,12 +27,14 @@ export function Tabs({ children, selectedIndex: selectedIndexProp, onChange, cla
);
}
export function TextTab({ selected, text, onClick }) {
const selectedStyle = selected
? 'text-white bg-blue-500 dark:text-black dark:bg-white'
: 'text-black dark:text-white bg-transparent';
export function TextTab({ selected, text, onClick, disabled }) {
const selectedStyle = disabled
? 'text-gray-400 dark:text-gray-600 bg-transparent'
: selected
? 'text-white bg-blue-500 dark:text-black dark:bg-white'
: 'text-black dark:text-white bg-transparent';
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>
</button>
);

View File

@@ -2,6 +2,7 @@ import { h, Fragment } from 'preact';
import { route } from 'preact-router';
import ActivityIndicator from '../components/ActivityIndicator';
import Heading from '../components/Heading';
import { Tabs, TextTab } from '../components/Tabs';
import { useApiHost } from '../api';
import useSWR from 'swr';
import useSWRInfinite from 'swr/infinite';
@@ -54,6 +55,7 @@ export default function Events({ path, ...props }) {
});
const [uploading, setUploading] = useState([]);
const [viewEvent, setViewEvent] = useState();
const [eventDetailType, setEventDetailType] = useState('clip');
const [downloadEvent, setDownloadEvent] = useState({
id: null,
has_clip: false,
@@ -235,6 +237,10 @@ export default function Events({ path, ...props }) {
}
};
const handleEventDetailTabChange = (index) => {
setEventDetailType(index == 0 ? 'clip' : 'image');
};
if (!config) {
return <ActivityIndicator />;
}
@@ -495,9 +501,15 @@ export default function Events({ path, ...props }) {
{viewEvent !== event.id ? null : (
<div className="space-y-4">
<div className="mx-auto max-w-7xl">
{event.has_clip ? (
<>
<Heading size="lg">Clip</Heading>
<div className='flex justify-center w-full py-2'>
<Tabs selectedIndex={event.has_clip && eventDetailType == 'clip' ? 0 : 1} onChange={handleEventDetailTabChange} className='justify'>
<TextTab text='Clip' disabled={!event.has_clip} />
<TextTab text={event.has_snapshot ? 'Snapshot' : 'Thumbnail'} />
</Tabs>
</div>
<div>
{((eventDetailType == 'clip') && event.has_clip) ? (
<VideoPlayer
options={{
preload: 'auto',
@@ -512,23 +524,22 @@ export default function Events({ path, ...props }) {
seekOptions={{ forward: 10, back: 5 }}
onReady={() => {}}
/>
</>
) : (
<div className="flex justify-center">
<div>
<Heading size="sm">{event.has_snapshot ? 'Best Image' : 'Thumbnail'}</Heading>
) : null }
{((eventDetailType == 'image') || !event.has_clip) ? (
<div className="flex justify-center">
<img
className="flex-grow-0"
src={
event.has_snapshot
? `${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`}
/>
</div>
</div>
)}
) : null }
</div>
</div>
</div>
)}