forked from Github/frigate
Compare commits
29 Commits
v0.11.0-be
...
v0.11.0-rc
| Author | SHA1 | Date | |
|---|---|---|---|
|
|
c461c9e700 | ||
|
|
9df415b3f2 | ||
|
|
f7c5e02a35 | ||
|
|
656e6a2a89 | ||
|
|
afefa3ec02 | ||
|
|
8c45dab9b8 | ||
|
|
0d6dd1ed0f | ||
|
|
911d6fdfa7 | ||
|
|
0cf759acad | ||
|
|
7c57c8c2da | ||
|
|
89c04acdff | ||
|
|
05d5f13f0e | ||
|
|
4682af81fb | ||
|
|
e649a1eb98 | ||
|
|
01482d791b | ||
|
|
6e2e297aeb | ||
|
|
5577ef081f | ||
|
|
2d5d3bdaf4 | ||
|
|
3376e85be6 | ||
|
|
7a1215d581 | ||
|
|
a94297ac93 | ||
|
|
37325c70ba | ||
|
|
e68f80b44a | ||
|
|
653c2274e1 | ||
|
|
93cd973e59 | ||
|
|
53bf3cd2e6 | ||
|
|
f59871a189 | ||
|
|
2dda7608bb | ||
|
|
10db9faff9 |
@@ -46,7 +46,6 @@ RUN pip3 wheel --wheel-dir=/wheels -r requirements-wheels.txt
|
||||
FROM debian:11-slim
|
||||
ARG TARGETARCH
|
||||
|
||||
ARG JELLYFIN_FFMPEG_VERSION=5.0.1-7
|
||||
# https://askubuntu.com/questions/972516/debian-frontend-environment-variable
|
||||
ARG DEBIAN_FRONTEND="noninteractive"
|
||||
# http://stackoverflow.com/questions/48162574/ddg#49462622
|
||||
@@ -80,14 +79,25 @@ RUN apt-get -qq update \
|
||||
# coral drivers
|
||||
libedgetpu1-max python3-tflite-runtime python3-pycoral \
|
||||
&& pip3 install -U /wheels/*.whl \
|
||||
# jellyfin-ffmpeg
|
||||
&& wget -O jellyfin.deb "https://repo.jellyfin.org/releases/server/debian/versions/jellyfin-ffmpeg/${JELLYFIN_FFMPEG_VERSION}/jellyfin-ffmpeg5_${JELLYFIN_FFMPEG_VERSION}-$( awk -F'=' '/^VERSION_CODENAME=/{ print $NF }' /etc/os-release )_$( dpkg --print-architecture ).deb" \
|
||||
&& apt-get -qq install --no-install-recommends --no-install-suggests -y ./jellyfin.deb \
|
||||
&& rm jellyfin.deb \
|
||||
# btbn-ffmpeg -> amd64 / arm64
|
||||
&& if [ "${TARGETARCH}" = "amd64" ] || [ "${TARGETARCH}" = "arm64" ]; then \
|
||||
mkdir -p /usr/lib/btbn-ffmpeg \
|
||||
&& wget -O btbn-ffmpeg.tar.xz "https://github.com/BtbN/FFmpeg-Builds/releases/download/autobuild-2022-07-31-12-37/ffmpeg-n5.1-2-g915ef932a3-linux$( [ "$TARGETARCH" = "amd64" ] && echo "64" || echo "arm64" )-gpl-5.1.tar.xz" \
|
||||
&& tar -xf btbn-ffmpeg.tar.xz -C /usr/lib/btbn-ffmpeg --strip-components 1 \
|
||||
&& rm btbn-ffmpeg.tar.xz; \
|
||||
fi \
|
||||
# ffmpeg -> arm32
|
||||
&& if [ "${TARGETARCH}" = "arm" ]; then \
|
||||
apt-get -qq install --no-install-recommends --no-install-suggests -y ffmpeg; \
|
||||
fi \
|
||||
# arch specific packages
|
||||
&& if [ "${TARGETARCH}" = "amd64" ]; then \
|
||||
apt-get -qq install --no-install-recommends --no-install-suggests -y \
|
||||
mesa-va-drivers intel-media-va-driver-non-free; \
|
||||
mesa-va-drivers libva-drm2 intel-media-va-driver-non-free i965-va-driver libmfx1; \
|
||||
fi \
|
||||
&& if [ "${TARGETARCH}" = "arm64" ]; then \
|
||||
apt-get -qq install --no-install-recommends --no-install-suggests -y \
|
||||
libva-drm2 mesa-va-drivers; \
|
||||
fi \
|
||||
# not sure why 32bit arm requires all these
|
||||
&& if [ "${TARGETARCH}" = "arm" ]; then \
|
||||
@@ -105,7 +115,7 @@ RUN apt-get -qq update \
|
||||
&& apt-get autoremove -y \
|
||||
&& rm -rf /var/lib/apt/lists/*
|
||||
|
||||
ENV PATH=$PATH:/usr/lib/jellyfin-ffmpeg
|
||||
ENV PATH=$PATH:/usr/lib/btbn-ffmpeg/bin
|
||||
|
||||
COPY --from=nginx /usr/local/nginx/ /usr/local/nginx/
|
||||
|
||||
|
||||
@@ -55,6 +55,7 @@ http {
|
||||
vod_upstream_location /api;
|
||||
vod_align_segments_to_key_frames on;
|
||||
vod_manifest_segment_durations_mode accurate;
|
||||
vod_ignore_edit_list on;
|
||||
|
||||
# vod caches
|
||||
vod_metadata_cache metadata_cache 512m;
|
||||
@@ -172,10 +173,11 @@ http {
|
||||
proxy_set_header Host $host;
|
||||
}
|
||||
|
||||
location ~* /api/(.*\.(jpg|jpeg|png)$) {
|
||||
location ~* /api/.*\.(jpg|jpeg|png)$ {
|
||||
add_header 'Access-Control-Allow-Origin' '*';
|
||||
add_header 'Access-Control-Allow-Methods' 'GET, POST, PUT, DELETE, OPTIONS';
|
||||
proxy_pass http://frigate_api/$1$is_args$args;
|
||||
rewrite ^/api/(.*)$ $1 break;
|
||||
proxy_pass http://frigate_api;
|
||||
proxy_pass_request_headers on;
|
||||
proxy_set_header Host $host;
|
||||
proxy_set_header X-Forwarded-For $proxy_add_x_forwarded_for;
|
||||
|
||||
@@ -58,18 +58,17 @@ ffmpeg:
|
||||
|
||||
### Reolink 410/520 (possibly others)
|
||||
|
||||
According to [this discussion](https://github.com/blakeblackshear/frigate/issues/1713#issuecomment-932976305), the http video streams seem to be the most reliable for Reolink.
|
||||
According to [this discussion](https://github.com/blakeblackshear/frigate/issues/3235#issuecomment-1135876973), the http video streams seem to be the most reliable for Reolink.
|
||||
|
||||
```yaml
|
||||
cameras:
|
||||
reolink:
|
||||
ffmpeg:
|
||||
hwaccel_args:
|
||||
input_args:
|
||||
- -avoid_negative_ts
|
||||
- make_zero
|
||||
- -fflags
|
||||
- nobuffer+genpts+discardcorrupt
|
||||
- +genpts+discardcorrupt
|
||||
- -flags
|
||||
- low_delay
|
||||
- -strict
|
||||
|
||||
@@ -41,6 +41,8 @@ ffmpeg:
|
||||
|
||||
### NVIDIA GPU
|
||||
|
||||
[Supported Nvidia GPUs for Decoding](https://developer.nvidia.com/video-encode-and-decode-gpu-support-matrix-new)
|
||||
|
||||
These instructions are based on the [jellyfin documentation](https://jellyfin.org/docs/general/administration/hardware-acceleration.html#nvidia-hardware-acceleration-on-docker-linux)
|
||||
|
||||
Add `--gpus all` to your docker run command or update your compose file.
|
||||
|
||||
@@ -25,6 +25,10 @@ cameras:
|
||||
height: 720
|
||||
```
|
||||
|
||||
### VSCode Configuration Schema
|
||||
|
||||
VSCode (and VSCode addon) supports the JSON schemas which will automatically validate the config. This can be added by adding `# yaml-language-server: $schema=http://frigate_host:5000/api/config/schema` to the top of the config file. `frigate_host` being the IP address of frigate or `ccab4aaf-frigate` if running in the addon.
|
||||
|
||||
### Full configuration reference:
|
||||
|
||||
:::caution
|
||||
@@ -311,6 +315,8 @@ snapshots:
|
||||
# Optional: Enable writing jpg snapshot to /media/frigate/clips (default: shown below)
|
||||
# This value can be set via MQTT and will be updated in startup based on retained value
|
||||
enabled: False
|
||||
# Optional: save a clean PNG copy of the snapshot image (default: shown below)
|
||||
clean_copy: True
|
||||
# Optional: print a timestamp on the snapshots (default: shown below)
|
||||
timestamp: False
|
||||
# Optional: draw bounding box on the snapshots (default: shown below)
|
||||
|
||||
@@ -42,3 +42,35 @@ The same options are available with events. Let's consider a scenario where you
|
||||
- With the `all` option all segments for the duration of the event would be saved for the event. This event would have 4 hours of footage.
|
||||
- With the `motion` option all segments for the duration of the event with motion would be saved. This means any segment where a car drove by in the street, person walked by, lighting changed, etc. would be saved.
|
||||
- With the `active_objects` it would only keep segments where the object was active. In this case the only segments that would be saved would be the ones where the car was driving up, you going inside, you coming outside, and the car driving away. Essentially reducing the 4 hours to a minute or two of event footage.
|
||||
|
||||
A configuration example of the above retain modes where all `motion` segments are stored for 7 days and `active objects` are stored for 14 days would be as follows:
|
||||
```yaml
|
||||
record:
|
||||
enabled: True
|
||||
retain:
|
||||
days: 7
|
||||
mode: motion
|
||||
events:
|
||||
retain:
|
||||
default: 14
|
||||
mode: active_objects
|
||||
```
|
||||
The above configuration example can be added globally or on a per camera basis.
|
||||
|
||||
### Object Specific Retention
|
||||
|
||||
You can also set specific retention length for an object type. The below configuration example builds on from above but also specifies that recordings of dogs only need to be kept for 2 days and recordings of cars should be kept for 7 days.
|
||||
```yaml
|
||||
record:
|
||||
enabled: True
|
||||
retain:
|
||||
days: 7
|
||||
mode: motion
|
||||
events:
|
||||
retain:
|
||||
default: 14
|
||||
mode: active_objects
|
||||
objects:
|
||||
dog: 2
|
||||
car: 7
|
||||
```
|
||||
|
||||
@@ -5,4 +5,4 @@ title: RTMP
|
||||
|
||||
Frigate can re-stream your video feed as a RTMP feed for other applications such as Home Assistant to utilize it at `rtmp://<frigate_host>/live/<camera_name>`. Port 1935 must be open. This allows you to use a video feed for detection in frigate and Home Assistant live view at the same time without having to make two separate connections to the camera. The video feed is copied from the original video feed directly to avoid re-encoding. This feed does not include any annotation by Frigate.
|
||||
|
||||
Some video feeds are not compatible with RTMP. If you are experiencing issues, check to make sure your camera feed is h264 with AAC audio. If your camera doesn't support a compatible format for RTMP, you can use the ffmpeg args to re-encode it on the fly at the expense of increased CPU utilization. Some more information about it can be found [here](../faqs#audio-in-recordings).
|
||||
Some video feeds are not compatible with RTMP. If you are experiencing issues, check to make sure your camera feed is h264 with AAC audio. If your camera doesn't support a compatible format for RTMP, you can use the ffmpeg args to re-encode it on the fly at the expense of increased CPU utilization. Some more information about it can be found [here](/faqs#audio-in-recordings).
|
||||
|
||||
@@ -47,3 +47,7 @@ These messages in the logs are expected in certain situations. Frigate checks th
|
||||
### "On connect called"
|
||||
|
||||
If you see repeated "On connect called" messages in your config, check for another instance of frigate. This happens when multiple frigate containers are trying to connect to mqtt with the same client_id.
|
||||
|
||||
### Error: Database Is Locked
|
||||
|
||||
sqlite does not work well on a network share, if the `/media` folder is mapped to a network share then [this guide](/configuration/advanced#database) should be used to move the database to a location on the internal drive.
|
||||
|
||||
10
docs/docs/guides/events_setup.md
Normal file
10
docs/docs/guides/events_setup.md
Normal file
@@ -0,0 +1,10 @@
|
||||
---
|
||||
id: events_setup
|
||||
title: Setting Up Events
|
||||
---
|
||||
|
||||
[Snapshots](../configuration/snapshots.md) and/or [Recordings](../configuration/record.md) must be enabled for events to be created for detected objects.
|
||||
|
||||
## Limiting Events to Areas of Interest
|
||||
|
||||
The best way to limit events to areas of interest is to use [zones](../configuration/zones.md) along with `required_zones` for events and snapshots to only have events created in areas of interest.
|
||||
@@ -3,7 +3,7 @@ id: stationary_objects
|
||||
title: Avoiding stationary objects
|
||||
---
|
||||
|
||||
Many people use Frigate to detect cars entering their driveway, and they often run into an issue with repeated events of a parked car being repeatedly detected. This is because object tracking stops when motion ends and the event ends. Motion detection works by determining if a sufficient number of pixels have changed between frames. Shadows or other lighting changes will be detected as motion. This will often cause a new event for a parked car.
|
||||
Many people use Frigate to detect cars entering their driveway, and they often run into an issue with repeated events of a parked car being repeatedly detected over the course of multiple days (for example if the car is lost at night and detected again the following morning.
|
||||
|
||||
You can use zones to restrict events and notifications to objects that have entered specific areas.
|
||||
|
||||
|
||||
@@ -23,15 +23,15 @@ I may earn a small commission for my endorsement, recommendation, testimonial, o
|
||||
|
||||
My current favorite is the Minisforum GK41 because of the dual NICs that allow you to setup a dedicated private network for your cameras where they can be blocked from accessing the internet. There are many used workstation options on eBay that work very well. Anything with an Intel CPU and capable of running Debian should work fine. As a bonus, you may want to look for devices with a M.2 or PCIe express slot that is compatible with the Google Coral. I may earn a small commission for my endorsement, recommendation, testimonial, or link to any products or services from this website.
|
||||
|
||||
| Name | Inference Speed | Coral Compatibility | Notes |
|
||||
| ------------------------------------------------------------------------------------------------------------------------------- | --------------- | ------------------- | --------------------------------------------------------------------------------------------------------------------------------------- |
|
||||
| <a href="https://amzn.to/3oH4BKi" target="_blank" rel="nofollow noopener sponsored">Odyssey X86 Blue J4125</a> (affiliate link) | 9-10ms | M.2 B+M | Dual gigabit NICs for easy isolated camera network. Easily handles several 1080p cameras. |
|
||||
| <a href="https://amzn.to/3ptnb8D" target="_blank" rel="nofollow noopener sponsored">Minisforum GK41</a> (affiliate link) | 9-10ms | USB | Dual gigabit NICs for easy isolated camera network. Easily handles several 1080p cameras. |
|
||||
| <a href="https://amzn.to/35E79BC" target="_blank" rel="nofollow noopener sponsored">Beelink GK55</a> (affiliate link) | 9-10ms | USB | Dual gigabit NICs for easy isolated camera network. Easily handles several 1080p cameras. |
|
||||
| <a href="https://amzn.to/3psFlHi" target="_blank" rel="nofollow noopener sponsored">Intel NUC</a> (affiliate link) | 8-10ms | USB | Overkill for most, but great performance. Can handle many cameras at 5fps depending on typical amounts of motion. Requires extra parts. |
|
||||
| <a href="https://amzn.to/3a6TBh8" target="_blank" rel="nofollow noopener sponsored">BMAX B2 Plus</a> (affiliate link) | 10-12ms | USB | Good balance of performance and cost. Also capable of running many other services at the same time as frigate. |
|
||||
| <a href="https://amzn.to/2YjpY9m" target="_blank" rel="nofollow noopener sponsored">Atomic Pi</a> (affiliate link) | 16ms | USB | Good option for a dedicated low power board with a small number of cameras. Can leverage Intel QuickSync for stream decoding. |
|
||||
| <a href="https://amzn.to/2YhSGHH" target="_blank" rel="nofollow noopener sponsored">Raspberry Pi 4 (64bit)</a> (affiliate link) | 10-15ms | USB | Can handle a small number of cameras. |
|
||||
| Name | Inference Speed | Coral Compatibility | Notes |
|
||||
| ----------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------- | --------------- | ------------------- | --------------------------------------------------------------------------------------------------------------------------------------- |
|
||||
| Odyssey X86 Blue J4125 (<a href="https://amzn.to/3oH4BKi" target="_blank" rel="nofollow noopener sponsored">Amazon</a>) (<a href="https://www.seeedstudio.com/Odyssey-Blue-J4125-128GB-p-4921.html?utm_source=Frigate" target="_blank" rel="nofollow noopener sponsored">SeeedStudio</a>) | 9-10ms | M.2 B+M | Dual gigabit NICs for easy isolated camera network. Easily handles several 1080p cameras. |
|
||||
| Minisforum GK41 (<a href="https://amzn.to/3ptnb8D" target="_blank" rel="nofollow noopener sponsored">Amazon</a>) | 9-10ms | USB | Dual gigabit NICs for easy isolated camera network. Easily handles several 1080p cameras. |
|
||||
| Beelink GK55 (<a href="https://amzn.to/35E79BC" target="_blank" rel="nofollow noopener sponsored">Amazon</a>) | 9-10ms | USB | Dual gigabit NICs for easy isolated camera network. Easily handles several 1080p cameras. |
|
||||
| Intel NUC (<a href="https://amzn.to/3psFlHi" target="_blank" rel="nofollow noopener sponsored">Amazon</a>) | 8-10ms | USB | Overkill for most, but great performance. Can handle many cameras at 5fps depending on typical amounts of motion. Requires extra parts. |
|
||||
| BMAX B2 Plus (<a href="https://amzn.to/3a6TBh8" target="_blank" rel="nofollow noopener sponsored">Amazon</a>) | 10-12ms | USB | Good balance of performance and cost. Also capable of running many other services at the same time as frigate. |
|
||||
| Atomic Pi (<a href="https://amzn.to/2YjpY9m" target="_blank" rel="nofollow noopener sponsored">Amazon</a>) | 16ms | USB | Good option for a dedicated low power board with a small number of cameras. Can leverage Intel QuickSync for stream decoding. |
|
||||
| Raspberry Pi 4 (64bit) (<a href="https://amzn.to/2YhSGHH" target="_blank" rel="nofollow noopener sponsored">Amazon</a>) | 10-15ms | USB | Can handle a small number of cameras. |
|
||||
|
||||
## Google Coral TPU
|
||||
|
||||
|
||||
@@ -100,18 +100,7 @@ Additionally, the USB Coral draws a considerable amount of power. If using any o
|
||||
|
||||
## Docker
|
||||
|
||||
Running in Docker directly is the recommended install method.
|
||||
|
||||
Make sure you choose the right image for your architecture:
|
||||
|
||||
| Arch | Image Name |
|
||||
| ----------- | ------------------------------------------ |
|
||||
| amd64 | blakeblackshear/frigate:stable-amd64 |
|
||||
| amd64nvidia | blakeblackshear/frigate:stable-amd64nvidia |
|
||||
| armv7 | blakeblackshear/frigate:stable-armv7 |
|
||||
| aarch64 | blakeblackshear/frigate:stable-aarch64 |
|
||||
|
||||
It is recommended to run with docker-compose:
|
||||
Running in Docker with compose is the recommended install method:
|
||||
|
||||
```yaml
|
||||
version: "3.9"
|
||||
@@ -120,7 +109,7 @@ services:
|
||||
container_name: frigate
|
||||
privileged: true # this may not be necessary for all setups
|
||||
restart: unless-stopped
|
||||
image: blakeblackshear/frigate:<specify_version_tag>
|
||||
image: blakeblackshear/frigate:stable
|
||||
shm_size: "64mb" # update for your cameras based on calculation above
|
||||
devices:
|
||||
- /dev/bus/usb:/dev/bus/usb # passes the USB Coral, needs to be modified for other versions
|
||||
@@ -157,7 +146,7 @@ docker run -d \
|
||||
-e FRIGATE_RTSP_PASSWORD='password' \
|
||||
-p 5000:5000 \
|
||||
-p 1935:1935 \
|
||||
blakeblackshear/frigate:<specify_version_tag>
|
||||
blakeblackshear/frigate:stable
|
||||
```
|
||||
|
||||
## Home Assistant Operating System (HassOS)
|
||||
|
||||
@@ -85,6 +85,17 @@ The integration provides:
|
||||
|
||||
This is accessible via "Media Browser" on the left menu panel in Home Assistant.
|
||||
|
||||
## Casting Clips To Media Devices
|
||||
|
||||
The integration supports casting clips and camera streams to supported media devices.
|
||||
|
||||
:::tip
|
||||
For clips to be castable to media devices, audio is required and may need to be [enabled for recordings](../faqs.md#audio-in-recordings).
|
||||
|
||||
**NOTE: Even if you camera does not support audio, audio will need to be enabled for Casting to be accepted.**
|
||||
|
||||
:::
|
||||
|
||||
<a name="api"></a>
|
||||
|
||||
## Notification API
|
||||
|
||||
@@ -4,6 +4,7 @@ module.exports = {
|
||||
Guides: [
|
||||
"guides/camera_setup",
|
||||
"guides/getting_started",
|
||||
"guides/events_setup",
|
||||
"guides/false_positives",
|
||||
"guides/ha_notifications",
|
||||
"guides/stationary_objects",
|
||||
|
||||
@@ -8,6 +8,7 @@ import subprocess as sp
|
||||
import time
|
||||
from functools import reduce
|
||||
from pathlib import Path
|
||||
from urllib.parse import unquote
|
||||
|
||||
import cv2
|
||||
|
||||
@@ -25,7 +26,7 @@ from flask import (
|
||||
from peewee import SqliteDatabase, operator, fn, DoesNotExist
|
||||
from playhouse.shortcuts import model_to_dict
|
||||
|
||||
from frigate.const import CLIPS_DIR, PLUS_ENV_VAR
|
||||
from frigate.const import CLIPS_DIR
|
||||
from frigate.models import Event, Recordings
|
||||
from frigate.stats import stats_snapshot
|
||||
from frigate.version import VERSION
|
||||
@@ -341,6 +342,7 @@ def event_thumbnail(id, max_cache_age=2592000):
|
||||
@bp.route("/<camera_name>/<label>/best.jpg")
|
||||
@bp.route("/<camera_name>/<label>/thumbnail.jpg")
|
||||
def label_thumbnail(camera_name, label):
|
||||
label = unquote(label)
|
||||
if label == "any":
|
||||
event_query = (
|
||||
Event.select()
|
||||
@@ -424,6 +426,7 @@ def event_snapshot(id):
|
||||
|
||||
@bp.route("/<camera_name>/<label>/snapshot.jpg")
|
||||
def label_snapshot(camera_name, label):
|
||||
label = unquote(label)
|
||||
if label == "any":
|
||||
event_query = (
|
||||
Event.select()
|
||||
@@ -491,7 +494,7 @@ def event_clip(id):
|
||||
def events():
|
||||
limit = request.args.get("limit", 100)
|
||||
camera = request.args.get("camera", "all")
|
||||
label = request.args.get("label", "all")
|
||||
label = unquote(request.args.get("label", "all"))
|
||||
sub_label = request.args.get("sub_label", "all")
|
||||
zone = request.args.get("zone", "all")
|
||||
after = request.args.get("after", type=float)
|
||||
@@ -571,7 +574,7 @@ def config():
|
||||
for cmd in camera_dict["ffmpeg_cmds"]:
|
||||
cmd["cmd"] = " ".join(cmd["cmd"])
|
||||
|
||||
config["plus"] = {"enabled": PLUS_ENV_VAR in os.environ}
|
||||
config["plus"] = {"enabled": current_app.plus_api.is_active()}
|
||||
|
||||
return jsonify(config)
|
||||
|
||||
@@ -753,9 +756,9 @@ def recordings(camera_name):
|
||||
return jsonify([e for e in recordings.dicts()])
|
||||
|
||||
|
||||
@bp.route("/<camera>/start/<int:start_ts>/end/<int:end_ts>/clip.mp4")
|
||||
@bp.route("/<camera>/start/<float:start_ts>/end/<float:end_ts>/clip.mp4")
|
||||
def recording_clip(camera, start_ts, end_ts):
|
||||
@bp.route("/<camera_name>/start/<int:start_ts>/end/<int:end_ts>/clip.mp4")
|
||||
@bp.route("/<camera_name>/start/<float:start_ts>/end/<float:end_ts>/clip.mp4")
|
||||
def recording_clip(camera_name, start_ts, end_ts):
|
||||
download = request.args.get("download", type=bool)
|
||||
|
||||
recordings = (
|
||||
@@ -765,7 +768,7 @@ def recording_clip(camera, start_ts, end_ts):
|
||||
| (Recordings.end_time.between(start_ts, end_ts))
|
||||
| ((start_ts > Recordings.start_time) & (end_ts < Recordings.end_time))
|
||||
)
|
||||
.where(Recordings.camera == camera)
|
||||
.where(Recordings.camera == camera_name)
|
||||
.order_by(Recordings.start_time.asc())
|
||||
)
|
||||
|
||||
@@ -780,7 +783,7 @@ def recording_clip(camera, start_ts, end_ts):
|
||||
if clip.end_time > end_ts:
|
||||
playlist_lines.append(f"outpoint {int(end_ts - clip.start_time)}")
|
||||
|
||||
file_name = f"clip_{camera}_{start_ts}-{end_ts}.mp4"
|
||||
file_name = f"clip_{camera_name}_{start_ts}-{end_ts}.mp4"
|
||||
path = f"/tmp/cache/{file_name}"
|
||||
|
||||
ffmpeg_cmd = [
|
||||
@@ -809,7 +812,7 @@ def recording_clip(camera, start_ts, end_ts):
|
||||
)
|
||||
if p.returncode != 0:
|
||||
logger.error(p.stderr)
|
||||
return f"Could not create clip from recordings for {camera}.", 500
|
||||
return f"Could not create clip from recordings for {camera_name}.", 500
|
||||
|
||||
response = make_response()
|
||||
response.headers["Content-Description"] = "File Transfer"
|
||||
@@ -825,9 +828,9 @@ def recording_clip(camera, start_ts, end_ts):
|
||||
return response
|
||||
|
||||
|
||||
@bp.route("/vod/<camera>/start/<int:start_ts>/end/<int:end_ts>")
|
||||
@bp.route("/vod/<camera>/start/<float:start_ts>/end/<float:end_ts>")
|
||||
def vod_ts(camera, start_ts, end_ts):
|
||||
@bp.route("/vod/<camera_name>/start/<int:start_ts>/end/<int:end_ts>")
|
||||
@bp.route("/vod/<camera_name>/start/<float:start_ts>/end/<float:end_ts>")
|
||||
def vod_ts(camera_name, start_ts, end_ts):
|
||||
recordings = (
|
||||
Recordings.select()
|
||||
.where(
|
||||
@@ -835,7 +838,7 @@ def vod_ts(camera, start_ts, end_ts):
|
||||
| Recordings.end_time.between(start_ts, end_ts)
|
||||
| ((start_ts > Recordings.start_time) & (end_ts < Recordings.end_time))
|
||||
)
|
||||
.where(Recordings.camera == camera)
|
||||
.where(Recordings.camera == camera_name)
|
||||
.order_by(Recordings.start_time.asc())
|
||||
)
|
||||
|
||||
@@ -846,16 +849,13 @@ def vod_ts(camera, start_ts, end_ts):
|
||||
for recording in recordings:
|
||||
clip = {"type": "source", "path": recording.path}
|
||||
duration = int(recording.duration * 1000)
|
||||
# Determine if offset is needed for first clip
|
||||
if recording.start_time < start_ts:
|
||||
offset = int((start_ts - recording.start_time) * 1000)
|
||||
clip["clipFrom"] = offset
|
||||
duration -= offset
|
||||
|
||||
# Determine if we need to end the last clip early
|
||||
if recording.end_time > end_ts:
|
||||
duration -= int((recording.end_time - end_ts) * 1000)
|
||||
|
||||
if duration > 0:
|
||||
clip["keyFrameDurations"] = [duration]
|
||||
clips.append(clip)
|
||||
durations.append(duration)
|
||||
else:
|
||||
@@ -876,14 +876,14 @@ def vod_ts(camera, start_ts, end_ts):
|
||||
)
|
||||
|
||||
|
||||
@bp.route("/vod/<year_month>/<day>/<hour>/<camera>")
|
||||
def vod_hour(year_month, day, hour, camera):
|
||||
@bp.route("/vod/<year_month>/<day>/<hour>/<camera_name>")
|
||||
def vod_hour(year_month, day, hour, camera_name):
|
||||
start_date = datetime.strptime(f"{year_month}-{day} {hour}", "%Y-%m-%d %H")
|
||||
end_date = start_date + timedelta(hours=1) - timedelta(milliseconds=1)
|
||||
start_ts = start_date.timestamp()
|
||||
end_ts = end_date.timestamp()
|
||||
|
||||
return vod_ts(camera, start_ts, end_ts)
|
||||
return vod_ts(camera_name, start_ts, end_ts)
|
||||
|
||||
|
||||
@bp.route("/vod/event/<id>")
|
||||
|
||||
@@ -13,6 +13,7 @@ from playhouse.shortcuts import model_to_dict
|
||||
from frigate.config import FrigateConfig
|
||||
from frigate.http import create_app
|
||||
from frigate.models import Event, Recordings
|
||||
from frigate.plus import PlusApi
|
||||
|
||||
from frigate.test.const import TEST_DB, TEST_DB_CLEANUPS
|
||||
|
||||
@@ -113,7 +114,7 @@ class TestHttp(unittest.TestCase):
|
||||
|
||||
def test_get_event_list(self):
|
||||
app = create_app(
|
||||
FrigateConfig(**self.minimal_config), self.db, None, None, None
|
||||
FrigateConfig(**self.minimal_config), self.db, None, None, PlusApi()
|
||||
)
|
||||
id = "123456.random"
|
||||
id2 = "7890.random"
|
||||
@@ -142,7 +143,7 @@ class TestHttp(unittest.TestCase):
|
||||
|
||||
def test_get_good_event(self):
|
||||
app = create_app(
|
||||
FrigateConfig(**self.minimal_config), self.db, None, None, None
|
||||
FrigateConfig(**self.minimal_config), self.db, None, None, PlusApi()
|
||||
)
|
||||
id = "123456.random"
|
||||
|
||||
@@ -156,7 +157,7 @@ class TestHttp(unittest.TestCase):
|
||||
|
||||
def test_get_bad_event(self):
|
||||
app = create_app(
|
||||
FrigateConfig(**self.minimal_config), self.db, None, None, None
|
||||
FrigateConfig(**self.minimal_config), self.db, None, None, PlusApi()
|
||||
)
|
||||
id = "123456.random"
|
||||
bad_id = "654321.other"
|
||||
@@ -169,7 +170,7 @@ class TestHttp(unittest.TestCase):
|
||||
|
||||
def test_delete_event(self):
|
||||
app = create_app(
|
||||
FrigateConfig(**self.minimal_config), self.db, None, None, None
|
||||
FrigateConfig(**self.minimal_config), self.db, None, None, PlusApi()
|
||||
)
|
||||
id = "123456.random"
|
||||
|
||||
@@ -184,7 +185,7 @@ class TestHttp(unittest.TestCase):
|
||||
|
||||
def test_event_retention(self):
|
||||
app = create_app(
|
||||
FrigateConfig(**self.minimal_config), self.db, None, None, None
|
||||
FrigateConfig(**self.minimal_config), self.db, None, None, PlusApi()
|
||||
)
|
||||
id = "123456.random"
|
||||
|
||||
@@ -203,7 +204,7 @@ class TestHttp(unittest.TestCase):
|
||||
|
||||
def test_set_delete_sub_label(self):
|
||||
app = create_app(
|
||||
FrigateConfig(**self.minimal_config), self.db, None, None, None
|
||||
FrigateConfig(**self.minimal_config), self.db, None, None, PlusApi()
|
||||
)
|
||||
id = "123456.random"
|
||||
sub_label = "sub"
|
||||
@@ -231,7 +232,7 @@ class TestHttp(unittest.TestCase):
|
||||
|
||||
def test_sub_label_list(self):
|
||||
app = create_app(
|
||||
FrigateConfig(**self.minimal_config), self.db, None, None, None
|
||||
FrigateConfig(**self.minimal_config), self.db, None, None, PlusApi()
|
||||
)
|
||||
id = "123456.random"
|
||||
sub_label = "sub"
|
||||
@@ -253,7 +254,7 @@ class TestHttp(unittest.TestCase):
|
||||
self.db,
|
||||
None,
|
||||
None,
|
||||
None,
|
||||
PlusApi(),
|
||||
)
|
||||
|
||||
with app.test_client() as client:
|
||||
@@ -267,7 +268,7 @@ class TestHttp(unittest.TestCase):
|
||||
self.db,
|
||||
None,
|
||||
None,
|
||||
None,
|
||||
PlusApi(),
|
||||
)
|
||||
id = "123456.random"
|
||||
|
||||
@@ -284,7 +285,7 @@ class TestHttp(unittest.TestCase):
|
||||
self.db,
|
||||
None,
|
||||
None,
|
||||
None,
|
||||
PlusApi(),
|
||||
)
|
||||
mock_stats.return_value = self.test_stats
|
||||
|
||||
|
||||
@@ -647,12 +647,13 @@ def process_frames(
|
||||
|
||||
# apply non-maxima suppression to suppress weak, overlapping bounding boxes
|
||||
# o[2] is the box of the object: xmin, ymin, xmax, ymax
|
||||
# apply max/min to ensure values do not exceed the known frame size
|
||||
boxes = [
|
||||
(
|
||||
o[2][0],
|
||||
o[2][1],
|
||||
o[2][2] - o[2][0],
|
||||
o[2][3] - o[2][1],
|
||||
max(o[2][0], 0),
|
||||
max(o[2][1], 0),
|
||||
min(o[2][2] - o[2][0], detect_config.width - 1),
|
||||
min(o[2][3] - o[2][1], detect_config.height - 1),
|
||||
)
|
||||
for o in group
|
||||
]
|
||||
|
||||
@@ -68,6 +68,7 @@ export const handlers = [
|
||||
top_score: Math.random(),
|
||||
zones: ['front_patio'],
|
||||
thumbnail: '/9j/4aa...',
|
||||
camera: 'camera_name',
|
||||
}))
|
||||
)
|
||||
);
|
||||
|
||||
176
web/package-lock.json
generated
176
web/package-lock.json
generated
@@ -17,9 +17,9 @@
|
||||
"preact-async-route": "^2.2.1",
|
||||
"preact-router": "^4.0.1",
|
||||
"swr": "^1.2.2",
|
||||
"video.js": "^7.17.0",
|
||||
"video.js": "^7.20.2",
|
||||
"videojs-playlist": "^5.0.0",
|
||||
"videojs-seek-buttons": "^2.2.0"
|
||||
"videojs-seek-buttons": "^2.2.1"
|
||||
},
|
||||
"devDependencies": {
|
||||
"@babel/preset-env": "^7.16.11",
|
||||
@@ -30,7 +30,7 @@
|
||||
"@testing-library/preact": "^2.0.1",
|
||||
"@testing-library/preact-hooks": "^1.1.0",
|
||||
"@testing-library/user-event": "^13.5.0",
|
||||
"@types/video.js": "^7.3.42",
|
||||
"@types/video.js": "^7.3.44",
|
||||
"@typescript-eslint/eslint-plugin": "^5.18.0",
|
||||
"@typescript-eslint/parser": "^5.18.0",
|
||||
"autoprefixer": "^10.4.2",
|
||||
@@ -3236,9 +3236,9 @@
|
||||
}
|
||||
},
|
||||
"node_modules/@types/video.js": {
|
||||
"version": "7.3.42",
|
||||
"resolved": "https://registry.npmjs.org/@types/video.js/-/video.js-7.3.42.tgz",
|
||||
"integrity": "sha512-AD6AQNMgLTqrgoayC6SshKh8EDkDd9x5pmEuiY9YsniHlhn5jPXdkVqrzKLwviapaRhQF15TQYxo1JWpqXCUBg==",
|
||||
"version": "7.3.44",
|
||||
"resolved": "https://registry.npmjs.org/@types/video.js/-/video.js-7.3.44.tgz",
|
||||
"integrity": "sha512-ov1HXNOjUkt38al/ybw8cj1181I5P3sOXdrqBR8AkDCqQX6GYwxOCzdmsGn/LDwKHTZ/3veNC9Ad6BjR5wSq4g==",
|
||||
"dev": true
|
||||
},
|
||||
"node_modules/@types/yargs": {
|
||||
@@ -3874,17 +3874,17 @@
|
||||
}
|
||||
},
|
||||
"node_modules/@videojs/http-streaming": {
|
||||
"version": "2.12.0",
|
||||
"resolved": "https://registry.npmjs.org/@videojs/http-streaming/-/http-streaming-2.12.0.tgz",
|
||||
"integrity": "sha512-vdQA0lDYBXGJqV2T02AGqg1w4dcgyRoN+bYG+G8uF4DpCEMhEtUI0BA4tRu4/Njar8w/9D5k0a1KX40pcvM3fA==",
|
||||
"version": "2.14.2",
|
||||
"resolved": "https://registry.npmjs.org/@videojs/http-streaming/-/http-streaming-2.14.2.tgz",
|
||||
"integrity": "sha512-K1raSfO/pq5r8iUas3OSYni0kXOj91n8ealIpV02khghzGv9LQ6O3YUqYd/eAhJ1HIrmZWOnrYpK/P+mhUExXQ==",
|
||||
"dependencies": {
|
||||
"@babel/runtime": "^7.12.5",
|
||||
"@videojs/vhs-utils": "3.0.4",
|
||||
"aes-decrypter": "3.1.2",
|
||||
"@videojs/vhs-utils": "3.0.5",
|
||||
"aes-decrypter": "3.1.3",
|
||||
"global": "^4.4.0",
|
||||
"m3u8-parser": "4.7.0",
|
||||
"mpd-parser": "0.19.2",
|
||||
"mux.js": "5.14.1",
|
||||
"m3u8-parser": "4.7.1",
|
||||
"mpd-parser": "0.21.1",
|
||||
"mux.js": "6.0.1",
|
||||
"video.js": "^6 || ^7"
|
||||
},
|
||||
"engines": {
|
||||
@@ -3896,9 +3896,9 @@
|
||||
}
|
||||
},
|
||||
"node_modules/@videojs/vhs-utils": {
|
||||
"version": "3.0.4",
|
||||
"resolved": "https://registry.npmjs.org/@videojs/vhs-utils/-/vhs-utils-3.0.4.tgz",
|
||||
"integrity": "sha512-hui4zOj2I1kLzDgf8QDVxD3IzrwjS/43KiS8IHQO0OeeSsb4pB/lgNt1NG7Dv0wMQfCccUpMVLGcK618s890Yg==",
|
||||
"version": "3.0.5",
|
||||
"resolved": "https://registry.npmjs.org/@videojs/vhs-utils/-/vhs-utils-3.0.5.tgz",
|
||||
"integrity": "sha512-PKVgdo8/GReqdx512F+ombhS+Bzogiofy1LgAj4tN8PfdBx3HSS7V5WfJotKTqtOWGwVfSWsrYN/t09/DSryrw==",
|
||||
"dependencies": {
|
||||
"@babel/runtime": "^7.12.5",
|
||||
"global": "^4.4.0",
|
||||
@@ -3985,12 +3985,12 @@
|
||||
}
|
||||
},
|
||||
"node_modules/aes-decrypter": {
|
||||
"version": "3.1.2",
|
||||
"resolved": "https://registry.npmjs.org/aes-decrypter/-/aes-decrypter-3.1.2.tgz",
|
||||
"integrity": "sha512-42nRwfQuPRj9R1zqZBdoxnaAmnIFyDi0MNyTVhjdFOd8fifXKKRfwIHIZ6AMn1or4x5WONzjwRTbTWcsIQ0O4A==",
|
||||
"version": "3.1.3",
|
||||
"resolved": "https://registry.npmjs.org/aes-decrypter/-/aes-decrypter-3.1.3.tgz",
|
||||
"integrity": "sha512-VkG9g4BbhMBy+N5/XodDeV6F02chEk9IpgRTq/0bS80y4dzy79VH2Gtms02VXomf3HmyRe3yyJYkJ990ns+d6A==",
|
||||
"dependencies": {
|
||||
"@babel/runtime": "^7.12.5",
|
||||
"@videojs/vhs-utils": "^3.0.0",
|
||||
"@videojs/vhs-utils": "^3.0.5",
|
||||
"global": "^4.4.0",
|
||||
"pkcs7": "^1.0.4"
|
||||
}
|
||||
@@ -9416,12 +9416,12 @@
|
||||
}
|
||||
},
|
||||
"node_modules/m3u8-parser": {
|
||||
"version": "4.7.0",
|
||||
"resolved": "https://registry.npmjs.org/m3u8-parser/-/m3u8-parser-4.7.0.tgz",
|
||||
"integrity": "sha512-48l/OwRyjBm+QhNNigEEcRcgbRvnUjL7rxs597HmW9QSNbyNvt+RcZ9T/d9vxi9A9z7EZrB1POtZYhdRlwYQkQ==",
|
||||
"version": "4.7.1",
|
||||
"resolved": "https://registry.npmjs.org/m3u8-parser/-/m3u8-parser-4.7.1.tgz",
|
||||
"integrity": "sha512-pbrQwiMiq+MmI9bl7UjtPT3AK603PV9bogNlr83uC+X9IoxqL5E4k7kU7fMQ0dpRgxgeSMygqUa0IMLQNXLBNA==",
|
||||
"dependencies": {
|
||||
"@babel/runtime": "^7.12.5",
|
||||
"@videojs/vhs-utils": "^3.0.0",
|
||||
"@videojs/vhs-utils": "^3.0.5",
|
||||
"global": "^4.4.0"
|
||||
}
|
||||
},
|
||||
@@ -9552,12 +9552,12 @@
|
||||
"dev": true
|
||||
},
|
||||
"node_modules/mpd-parser": {
|
||||
"version": "0.19.2",
|
||||
"resolved": "https://registry.npmjs.org/mpd-parser/-/mpd-parser-0.19.2.tgz",
|
||||
"integrity": "sha512-M5tAIdtBM2TN+OSTz/37T7V+h9ZLvhyNqq4TNIdtjAQ/Hg8UnMRf5nJQDjffcXag3POXi31yUJQEKOXdcAM/nw==",
|
||||
"version": "0.21.1",
|
||||
"resolved": "https://registry.npmjs.org/mpd-parser/-/mpd-parser-0.21.1.tgz",
|
||||
"integrity": "sha512-BxlSXWbKE1n7eyEPBnTEkrzhS3PdmkkKdM1pgKbPnPOH0WFZIc0sPOWi7m0Uo3Wd2a4Or8Qf4ZbS7+ASqQ49fw==",
|
||||
"dependencies": {
|
||||
"@babel/runtime": "^7.12.5",
|
||||
"@videojs/vhs-utils": "^3.0.2",
|
||||
"@videojs/vhs-utils": "^3.0.5",
|
||||
"@xmldom/xmldom": "^0.7.2",
|
||||
"global": "^4.4.0"
|
||||
},
|
||||
@@ -9722,11 +9722,12 @@
|
||||
"dev": true
|
||||
},
|
||||
"node_modules/mux.js": {
|
||||
"version": "5.14.1",
|
||||
"resolved": "https://registry.npmjs.org/mux.js/-/mux.js-5.14.1.tgz",
|
||||
"integrity": "sha512-38kA/xjWRDzMbcpHQfhKbJAME8eTZVsb9U2Puk890oGvGqnyu8B/AkKdICKPHkigfqYX9MY20vje88TP14nhog==",
|
||||
"version": "6.0.1",
|
||||
"resolved": "https://registry.npmjs.org/mux.js/-/mux.js-6.0.1.tgz",
|
||||
"integrity": "sha512-22CHb59rH8pWGcPGW5Og7JngJ9s+z4XuSlYvnxhLuc58cA1WqGDQPzuG8I+sPm1/p0CdgpzVTaKW408k5DNn8w==",
|
||||
"dependencies": {
|
||||
"@babel/runtime": "^7.11.2"
|
||||
"@babel/runtime": "^7.11.2",
|
||||
"global": "^4.4.0"
|
||||
},
|
||||
"bin": {
|
||||
"muxjs-transmux": "bin/transmux.js"
|
||||
@@ -11762,20 +11763,20 @@
|
||||
}
|
||||
},
|
||||
"node_modules/video.js": {
|
||||
"version": "7.17.0",
|
||||
"resolved": "https://registry.npmjs.org/video.js/-/video.js-7.17.0.tgz",
|
||||
"integrity": "sha512-8RbLu9+Pdpep9OTPncUHIvZXFgn/7hKdPnSTE/lGSnlFSucXtTUBp41R7NDwncscMLQ0WgazUbmFlvr4MNWMbA==",
|
||||
"version": "7.20.2",
|
||||
"resolved": "https://registry.npmjs.org/video.js/-/video.js-7.20.2.tgz",
|
||||
"integrity": "sha512-hdvAHKAyaL6bCDkeu0pPtFYKi1EDaOUovm7FN1xqBDolUxgH8FKy1WIgTS+Ouuaw7R54SCTcSeXjZEizhy9ouQ==",
|
||||
"dependencies": {
|
||||
"@babel/runtime": "^7.12.5",
|
||||
"@videojs/http-streaming": "2.12.0",
|
||||
"@videojs/vhs-utils": "^3.0.3",
|
||||
"@videojs/http-streaming": "2.14.2",
|
||||
"@videojs/vhs-utils": "^3.0.4",
|
||||
"@videojs/xhr": "2.6.0",
|
||||
"aes-decrypter": "3.1.2",
|
||||
"aes-decrypter": "3.1.3",
|
||||
"global": "^4.4.0",
|
||||
"keycode": "^2.2.0",
|
||||
"m3u8-parser": "4.7.0",
|
||||
"mpd-parser": "0.19.2",
|
||||
"mux.js": "5.14.1",
|
||||
"m3u8-parser": "4.7.1",
|
||||
"mpd-parser": "0.21.1",
|
||||
"mux.js": "6.0.1",
|
||||
"safe-json-parse": "4.0.0",
|
||||
"videojs-font": "3.2.0",
|
||||
"videojs-vtt.js": "^0.15.3"
|
||||
@@ -11799,9 +11800,9 @@
|
||||
}
|
||||
},
|
||||
"node_modules/videojs-seek-buttons": {
|
||||
"version": "2.2.0",
|
||||
"resolved": "https://registry.npmjs.org/videojs-seek-buttons/-/videojs-seek-buttons-2.2.0.tgz",
|
||||
"integrity": "sha512-yjCA6ntq+8fRKgZi/H6QJlghQWgA1x9oSRl6wfLODAcujhynDXetwMgRKGgl4NlV5af2bKY6erNtJ0kOBko/nQ==",
|
||||
"version": "2.2.1",
|
||||
"resolved": "https://registry.npmjs.org/videojs-seek-buttons/-/videojs-seek-buttons-2.2.1.tgz",
|
||||
"integrity": "sha512-tXInD8ElUddyc1vxkGsVNhxlFtz8JC4VagPOSyL8mj7LI7oXjLGxni2pjx2wqzsV3PaIBMMH47MBtsNWf+wbtw==",
|
||||
"dependencies": {
|
||||
"global": "^4.4.0",
|
||||
"video.js": "^6 || ^7"
|
||||
@@ -14512,9 +14513,9 @@
|
||||
}
|
||||
},
|
||||
"@types/video.js": {
|
||||
"version": "7.3.42",
|
||||
"resolved": "https://registry.npmjs.org/@types/video.js/-/video.js-7.3.42.tgz",
|
||||
"integrity": "sha512-AD6AQNMgLTqrgoayC6SshKh8EDkDd9x5pmEuiY9YsniHlhn5jPXdkVqrzKLwviapaRhQF15TQYxo1JWpqXCUBg==",
|
||||
"version": "7.3.44",
|
||||
"resolved": "https://registry.npmjs.org/@types/video.js/-/video.js-7.3.44.tgz",
|
||||
"integrity": "sha512-ov1HXNOjUkt38al/ybw8cj1181I5P3sOXdrqBR8AkDCqQX6GYwxOCzdmsGn/LDwKHTZ/3veNC9Ad6BjR5wSq4g==",
|
||||
"dev": true
|
||||
},
|
||||
"@types/yargs": {
|
||||
@@ -14894,24 +14895,24 @@
|
||||
}
|
||||
},
|
||||
"@videojs/http-streaming": {
|
||||
"version": "2.12.0",
|
||||
"resolved": "https://registry.npmjs.org/@videojs/http-streaming/-/http-streaming-2.12.0.tgz",
|
||||
"integrity": "sha512-vdQA0lDYBXGJqV2T02AGqg1w4dcgyRoN+bYG+G8uF4DpCEMhEtUI0BA4tRu4/Njar8w/9D5k0a1KX40pcvM3fA==",
|
||||
"version": "2.14.2",
|
||||
"resolved": "https://registry.npmjs.org/@videojs/http-streaming/-/http-streaming-2.14.2.tgz",
|
||||
"integrity": "sha512-K1raSfO/pq5r8iUas3OSYni0kXOj91n8ealIpV02khghzGv9LQ6O3YUqYd/eAhJ1HIrmZWOnrYpK/P+mhUExXQ==",
|
||||
"requires": {
|
||||
"@babel/runtime": "^7.12.5",
|
||||
"@videojs/vhs-utils": "3.0.4",
|
||||
"aes-decrypter": "3.1.2",
|
||||
"@videojs/vhs-utils": "3.0.5",
|
||||
"aes-decrypter": "3.1.3",
|
||||
"global": "^4.4.0",
|
||||
"m3u8-parser": "4.7.0",
|
||||
"mpd-parser": "0.19.2",
|
||||
"mux.js": "5.14.1",
|
||||
"m3u8-parser": "4.7.1",
|
||||
"mpd-parser": "0.21.1",
|
||||
"mux.js": "6.0.1",
|
||||
"video.js": "^6 || ^7"
|
||||
}
|
||||
},
|
||||
"@videojs/vhs-utils": {
|
||||
"version": "3.0.4",
|
||||
"resolved": "https://registry.npmjs.org/@videojs/vhs-utils/-/vhs-utils-3.0.4.tgz",
|
||||
"integrity": "sha512-hui4zOj2I1kLzDgf8QDVxD3IzrwjS/43KiS8IHQO0OeeSsb4pB/lgNt1NG7Dv0wMQfCccUpMVLGcK618s890Yg==",
|
||||
"version": "3.0.5",
|
||||
"resolved": "https://registry.npmjs.org/@videojs/vhs-utils/-/vhs-utils-3.0.5.tgz",
|
||||
"integrity": "sha512-PKVgdo8/GReqdx512F+ombhS+Bzogiofy1LgAj4tN8PfdBx3HSS7V5WfJotKTqtOWGwVfSWsrYN/t09/DSryrw==",
|
||||
"requires": {
|
||||
"@babel/runtime": "^7.12.5",
|
||||
"global": "^4.4.0",
|
||||
@@ -14980,12 +14981,12 @@
|
||||
"dev": true
|
||||
},
|
||||
"aes-decrypter": {
|
||||
"version": "3.1.2",
|
||||
"resolved": "https://registry.npmjs.org/aes-decrypter/-/aes-decrypter-3.1.2.tgz",
|
||||
"integrity": "sha512-42nRwfQuPRj9R1zqZBdoxnaAmnIFyDi0MNyTVhjdFOd8fifXKKRfwIHIZ6AMn1or4x5WONzjwRTbTWcsIQ0O4A==",
|
||||
"version": "3.1.3",
|
||||
"resolved": "https://registry.npmjs.org/aes-decrypter/-/aes-decrypter-3.1.3.tgz",
|
||||
"integrity": "sha512-VkG9g4BbhMBy+N5/XodDeV6F02chEk9IpgRTq/0bS80y4dzy79VH2Gtms02VXomf3HmyRe3yyJYkJ990ns+d6A==",
|
||||
"requires": {
|
||||
"@babel/runtime": "^7.12.5",
|
||||
"@videojs/vhs-utils": "^3.0.0",
|
||||
"@videojs/vhs-utils": "^3.0.5",
|
||||
"global": "^4.4.0",
|
||||
"pkcs7": "^1.0.4"
|
||||
}
|
||||
@@ -18886,12 +18887,12 @@
|
||||
"dev": true
|
||||
},
|
||||
"m3u8-parser": {
|
||||
"version": "4.7.0",
|
||||
"resolved": "https://registry.npmjs.org/m3u8-parser/-/m3u8-parser-4.7.0.tgz",
|
||||
"integrity": "sha512-48l/OwRyjBm+QhNNigEEcRcgbRvnUjL7rxs597HmW9QSNbyNvt+RcZ9T/d9vxi9A9z7EZrB1POtZYhdRlwYQkQ==",
|
||||
"version": "4.7.1",
|
||||
"resolved": "https://registry.npmjs.org/m3u8-parser/-/m3u8-parser-4.7.1.tgz",
|
||||
"integrity": "sha512-pbrQwiMiq+MmI9bl7UjtPT3AK603PV9bogNlr83uC+X9IoxqL5E4k7kU7fMQ0dpRgxgeSMygqUa0IMLQNXLBNA==",
|
||||
"requires": {
|
||||
"@babel/runtime": "^7.12.5",
|
||||
"@videojs/vhs-utils": "^3.0.0",
|
||||
"@videojs/vhs-utils": "^3.0.5",
|
||||
"global": "^4.4.0"
|
||||
}
|
||||
},
|
||||
@@ -18992,12 +18993,12 @@
|
||||
"dev": true
|
||||
},
|
||||
"mpd-parser": {
|
||||
"version": "0.19.2",
|
||||
"resolved": "https://registry.npmjs.org/mpd-parser/-/mpd-parser-0.19.2.tgz",
|
||||
"integrity": "sha512-M5tAIdtBM2TN+OSTz/37T7V+h9ZLvhyNqq4TNIdtjAQ/Hg8UnMRf5nJQDjffcXag3POXi31yUJQEKOXdcAM/nw==",
|
||||
"version": "0.21.1",
|
||||
"resolved": "https://registry.npmjs.org/mpd-parser/-/mpd-parser-0.21.1.tgz",
|
||||
"integrity": "sha512-BxlSXWbKE1n7eyEPBnTEkrzhS3PdmkkKdM1pgKbPnPOH0WFZIc0sPOWi7m0Uo3Wd2a4Or8Qf4ZbS7+ASqQ49fw==",
|
||||
"requires": {
|
||||
"@babel/runtime": "^7.12.5",
|
||||
"@videojs/vhs-utils": "^3.0.2",
|
||||
"@videojs/vhs-utils": "^3.0.5",
|
||||
"@xmldom/xmldom": "^0.7.2",
|
||||
"global": "^4.4.0"
|
||||
}
|
||||
@@ -19120,11 +19121,12 @@
|
||||
"dev": true
|
||||
},
|
||||
"mux.js": {
|
||||
"version": "5.14.1",
|
||||
"resolved": "https://registry.npmjs.org/mux.js/-/mux.js-5.14.1.tgz",
|
||||
"integrity": "sha512-38kA/xjWRDzMbcpHQfhKbJAME8eTZVsb9U2Puk890oGvGqnyu8B/AkKdICKPHkigfqYX9MY20vje88TP14nhog==",
|
||||
"version": "6.0.1",
|
||||
"resolved": "https://registry.npmjs.org/mux.js/-/mux.js-6.0.1.tgz",
|
||||
"integrity": "sha512-22CHb59rH8pWGcPGW5Og7JngJ9s+z4XuSlYvnxhLuc58cA1WqGDQPzuG8I+sPm1/p0CdgpzVTaKW408k5DNn8w==",
|
||||
"requires": {
|
||||
"@babel/runtime": "^7.11.2"
|
||||
"@babel/runtime": "^7.11.2",
|
||||
"global": "^4.4.0"
|
||||
}
|
||||
},
|
||||
"nanoid": {
|
||||
@@ -20612,20 +20614,20 @@
|
||||
}
|
||||
},
|
||||
"video.js": {
|
||||
"version": "7.17.0",
|
||||
"resolved": "https://registry.npmjs.org/video.js/-/video.js-7.17.0.tgz",
|
||||
"integrity": "sha512-8RbLu9+Pdpep9OTPncUHIvZXFgn/7hKdPnSTE/lGSnlFSucXtTUBp41R7NDwncscMLQ0WgazUbmFlvr4MNWMbA==",
|
||||
"version": "7.20.2",
|
||||
"resolved": "https://registry.npmjs.org/video.js/-/video.js-7.20.2.tgz",
|
||||
"integrity": "sha512-hdvAHKAyaL6bCDkeu0pPtFYKi1EDaOUovm7FN1xqBDolUxgH8FKy1WIgTS+Ouuaw7R54SCTcSeXjZEizhy9ouQ==",
|
||||
"requires": {
|
||||
"@babel/runtime": "^7.12.5",
|
||||
"@videojs/http-streaming": "2.12.0",
|
||||
"@videojs/vhs-utils": "^3.0.3",
|
||||
"@videojs/http-streaming": "2.14.2",
|
||||
"@videojs/vhs-utils": "^3.0.4",
|
||||
"@videojs/xhr": "2.6.0",
|
||||
"aes-decrypter": "3.1.2",
|
||||
"aes-decrypter": "3.1.3",
|
||||
"global": "^4.4.0",
|
||||
"keycode": "^2.2.0",
|
||||
"m3u8-parser": "4.7.0",
|
||||
"mpd-parser": "0.19.2",
|
||||
"mux.js": "5.14.1",
|
||||
"m3u8-parser": "4.7.1",
|
||||
"mpd-parser": "0.21.1",
|
||||
"mux.js": "6.0.1",
|
||||
"safe-json-parse": "4.0.0",
|
||||
"videojs-font": "3.2.0",
|
||||
"videojs-vtt.js": "^0.15.3"
|
||||
@@ -20646,9 +20648,9 @@
|
||||
}
|
||||
},
|
||||
"videojs-seek-buttons": {
|
||||
"version": "2.2.0",
|
||||
"resolved": "https://registry.npmjs.org/videojs-seek-buttons/-/videojs-seek-buttons-2.2.0.tgz",
|
||||
"integrity": "sha512-yjCA6ntq+8fRKgZi/H6QJlghQWgA1x9oSRl6wfLODAcujhynDXetwMgRKGgl4NlV5af2bKY6erNtJ0kOBko/nQ==",
|
||||
"version": "2.2.1",
|
||||
"resolved": "https://registry.npmjs.org/videojs-seek-buttons/-/videojs-seek-buttons-2.2.1.tgz",
|
||||
"integrity": "sha512-tXInD8ElUddyc1vxkGsVNhxlFtz8JC4VagPOSyL8mj7LI7oXjLGxni2pjx2wqzsV3PaIBMMH47MBtsNWf+wbtw==",
|
||||
"requires": {
|
||||
"global": "^4.4.0",
|
||||
"video.js": "^6 || ^7"
|
||||
|
||||
@@ -19,9 +19,9 @@
|
||||
"preact-async-route": "^2.2.1",
|
||||
"preact-router": "^4.0.1",
|
||||
"swr": "^1.2.2",
|
||||
"video.js": "^7.17.0",
|
||||
"video.js": "^7.20.2",
|
||||
"videojs-playlist": "^5.0.0",
|
||||
"videojs-seek-buttons": "^2.2.0"
|
||||
"videojs-seek-buttons": "^2.2.1"
|
||||
},
|
||||
"devDependencies": {
|
||||
"@babel/preset-env": "^7.16.11",
|
||||
@@ -32,7 +32,7 @@
|
||||
"@testing-library/preact": "^2.0.1",
|
||||
"@testing-library/preact-hooks": "^1.1.0",
|
||||
"@testing-library/user-event": "^13.5.0",
|
||||
"@types/video.js": "^7.3.42",
|
||||
"@types/video.js": "^7.3.44",
|
||||
"@typescript-eslint/eslint-plugin": "^5.18.0",
|
||||
"@typescript-eslint/parser": "^5.18.0",
|
||||
"autoprefixer": "^10.4.2",
|
||||
|
||||
@@ -65,7 +65,7 @@ function CameraSection({ sortedCameras }) {
|
||||
<Fragment>
|
||||
<Separator />
|
||||
{sortedCameras.map(([camera]) => (
|
||||
<Destination key={camera} href={`/cameras/${camera}`} text={camera} />
|
||||
<Destination key={camera} href={`/cameras/${camera}`} text={camera.replaceAll('_', ' ')} />
|
||||
))}
|
||||
<Separator />
|
||||
</Fragment>
|
||||
@@ -83,7 +83,7 @@ function RecordingSection({ sortedCameras }) {
|
||||
key={camera}
|
||||
path={`/recording/${camera}/:date?/:hour?/:seconds?`}
|
||||
href={`/recording/${camera}`}
|
||||
text={camera}
|
||||
text={camera.replaceAll('_', ' ')}
|
||||
/>
|
||||
);
|
||||
})}
|
||||
|
||||
@@ -57,7 +57,7 @@ export const HistoryVideo = ({
|
||||
}
|
||||
|
||||
video.src({
|
||||
src: `${apiHost}/vod/event/${id}/index.m3u8`,
|
||||
src: `${apiHost}/vod/event/${id}/master.m3u8`,
|
||||
type: 'application/vnd.apple.mpegurl',
|
||||
});
|
||||
video.poster(`${apiHost}/api/events/${id}/snapshot.jpg`);
|
||||
|
||||
@@ -120,7 +120,7 @@ export default function Camera({ camera }) {
|
||||
|
||||
return (
|
||||
<div className="space-y-4 p-2 px-4">
|
||||
<Heading size="2xl">{camera}</Heading>
|
||||
<Heading size="2xl">{camera.replaceAll('_', ' ')}</Heading>
|
||||
<ButtonsTabbed viewModes={['live', 'debug']} setViewMode={setViewMode} />
|
||||
|
||||
{player}
|
||||
@@ -133,8 +133,8 @@ export default function Camera({ camera }) {
|
||||
className="mb-4 mr-4"
|
||||
key={objectType}
|
||||
header={objectType}
|
||||
href={`/events?camera=${camera}&label=${objectType}`}
|
||||
media={<img src={`${apiHost}/api/${camera}/${objectType}/thumbnail.jpg`} />}
|
||||
href={`/events?camera=${camera}&label=${encodeURIComponent(objectType)}`}
|
||||
media={<img src={`${apiHost}/api/${camera}/${encodeURIComponent(objectType)}/thumbnail.jpg`} />}
|
||||
/>
|
||||
))}
|
||||
</div>
|
||||
|
||||
@@ -50,6 +50,10 @@ function Camera({ name }) {
|
||||
{ name: 'Recordings', href: `/recording/${name}` },
|
||||
];
|
||||
}, [name]);
|
||||
const cleanName = useMemo(
|
||||
() => { return `${name.replaceAll('_', ' ')}` },
|
||||
[name]
|
||||
);
|
||||
const icons = useMemo(
|
||||
() => [
|
||||
{
|
||||
@@ -81,6 +85,6 @@ function Camera({ name }) {
|
||||
);
|
||||
|
||||
return (
|
||||
<Card buttons={buttons} href={href} header={name} icons={icons} media={<CameraImage camera={name} stretch />} />
|
||||
<Card buttons={buttons} href={href} header={cleanName} icons={icons} media={<CameraImage camera={name} stretch />} />
|
||||
);
|
||||
}
|
||||
|
||||
@@ -81,7 +81,7 @@ export default function Debug() {
|
||||
{cameraNames.map((camera, i) => (
|
||||
<Tr key={i} index={i}>
|
||||
<Td>
|
||||
<Link href={`/cameras/${camera}`}>{camera}</Link>
|
||||
<Link href={`/cameras/${camera}`}>{camera.replaceAll('_', ' ')}</Link>
|
||||
</Td>
|
||||
{cameraDataKeys.map((name) => (
|
||||
<Td key={`${name}-${camera}`}>{cameras[camera][name]}</Td>
|
||||
|
||||
@@ -251,7 +251,7 @@ export default function Events({ path, ...props }) {
|
||||
<option value="all">all cameras</option>
|
||||
{filterValues.cameras.map((item) => (
|
||||
<option key={item} value={item}>
|
||||
{item}
|
||||
{item.replaceAll('_', ' ')}
|
||||
</option>
|
||||
))}
|
||||
</select>
|
||||
@@ -262,7 +262,7 @@ export default function Events({ path, ...props }) {
|
||||
>
|
||||
<option value="all">all labels</option>
|
||||
{filterValues.labels.map((item) => (
|
||||
<option key={item} value={item}>
|
||||
<option key={item.replaceAll('_', ' ')} value={item}>
|
||||
{item}
|
||||
</option>
|
||||
))}
|
||||
@@ -275,7 +275,7 @@ export default function Events({ path, ...props }) {
|
||||
<option value="all">all zones</option>
|
||||
{filterValues.zones.map((item) => (
|
||||
<option key={item} value={item}>
|
||||
{item}
|
||||
{item.replaceAll('_', ' ')}
|
||||
</option>
|
||||
))}
|
||||
</select>
|
||||
@@ -457,11 +457,11 @@ export default function Events({ path, ...props }) {
|
||||
</div>
|
||||
<div className="capitalize text-sm flex align-center mt-1">
|
||||
<Camera className="h-5 w-5 mr-2 inline" />
|
||||
{event.camera}
|
||||
{event.camera.replaceAll('_', ' ')}
|
||||
</div>
|
||||
<div className="capitalize text-sm flex align-center">
|
||||
<Zone className="w-5 h-5 mr-2 inline" />
|
||||
{event.zones.join(',')}
|
||||
{event.zones.join(', ').replaceAll('_', ' ')}
|
||||
</div>
|
||||
</div>
|
||||
<div class="hidden sm:flex flex-col justify-end mr-2">
|
||||
@@ -504,7 +504,7 @@ export default function Events({ path, ...props }) {
|
||||
autoplay: true,
|
||||
sources: [
|
||||
{
|
||||
src: `${apiHost}/vod/event/${event.id}/index.m3u8`,
|
||||
src: `${apiHost}/vod/event/${event.id}/master.m3u8`,
|
||||
type: 'application/vnd.apple.mpegurl',
|
||||
},
|
||||
],
|
||||
|
||||
@@ -62,7 +62,7 @@ export default function Recording({ camera, date, hour = '00', minute = '00', se
|
||||
description: `${camera} recording @ ${h.hour}:00.`,
|
||||
sources: [
|
||||
{
|
||||
src: `${apiHost}/vod/${year}-${month}/${day}/${h.hour}/${camera}/index.m3u8`,
|
||||
src: `${apiHost}/vod/${year}-${month}/${day}/${h.hour}/${camera}/master.m3u8`,
|
||||
type: 'application/vnd.apple.mpegurl',
|
||||
},
|
||||
],
|
||||
@@ -103,7 +103,7 @@ export default function Recording({ camera, date, hour = '00', minute = '00', se
|
||||
}
|
||||
}, [seekSeconds, playlistIndex]);
|
||||
|
||||
if (!recordingsSummary) {
|
||||
if (!recordingsSummary || !recordings) {
|
||||
return <ActivityIndicator />;
|
||||
}
|
||||
|
||||
@@ -121,7 +121,7 @@ export default function Recording({ camera, date, hour = '00', minute = '00', se
|
||||
|
||||
return (
|
||||
<div className="space-y-4 p-2 px-4">
|
||||
<Heading>{camera} Recordings</Heading>
|
||||
<Heading>{camera.replaceAll('_', ' ')} Recordings</Heading>
|
||||
|
||||
<VideoPlayer
|
||||
onReady={(player) => {
|
||||
|
||||
Reference in New Issue
Block a user