From 13122fc2b18dfe42235854cad3913d7acffa54dd Mon Sep 17 00:00:00 2001 From: k1n6b0b Date: Sun, 19 Feb 2023 08:37:37 -0500 Subject: [PATCH 01/42] Update ha_notifications.md (#5457) Home Assistant Entity ID uses _ not - and is lower case. This works for me - there was a ticket about this by someone else --- docs/docs/guides/ha_notifications.md | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/docs/docs/guides/ha_notifications.md b/docs/docs/guides/ha_notifications.md index 2145e6e7c..45047b537 100644 --- a/docs/docs/guides/ha_notifications.md +++ b/docs/docs/guides/ha_notifications.md @@ -45,7 +45,7 @@ automation: https://your.public.hass.address.com/api/frigate/notifications/{{trigger.payload_json["after"]["id"]}}/thumbnail.jpg tag: '{{trigger.payload_json["after"]["id"]}}' when: '{{trigger.payload_json["after"]["start_time"]|int}}' - entity_id: camera.{{trigger.payload_json["after"]["camera"]}} + entity_id: camera.{{trigger.payload_json["after"]["camera"] | replace("-","_") | lower}} mode: single ``` From a554b22968f524cd883fdec7ae6dbde4b5a2f2c7 Mon Sep 17 00:00:00 2001 From: herostrat Date: Fri, 24 Feb 2023 04:29:14 +0100 Subject: [PATCH 02/42] Add docs about additions (#4504) * Add docs about additions * Fix review findings: Spelling * Add suggestions from PR --- .../integrations/third_party_extensions.md | 19 +++++++++++++++++++ docs/sidebars.js | 1 + 2 files changed, 20 insertions(+) create mode 100644 docs/docs/integrations/third_party_extensions.md diff --git a/docs/docs/integrations/third_party_extensions.md b/docs/docs/integrations/third_party_extensions.md new file mode 100644 index 000000000..6c708d27d --- /dev/null +++ b/docs/docs/integrations/third_party_extensions.md @@ -0,0 +1,19 @@ +--- +id: third_party_extensions +title: Third Party Extensions +--- + +Being open source, others have the possibility to modify and extend the rich functionality Frigate already offers. +This page is meant to be an overview over additions one can make to the home NVR setup. The list is not exhaustive and can be extended via PR to the Frigate docs. + +:::caution + +This page does not recommend or rate the presented projects. +Please use your own knowledge to assess and vet them before you install anything on your system. + +::: + +## [Double Take](https://github.com/jakowenko/double-take) + +[Double Take](https://github.com/jakowenko/double-take) provides an unified UI and API for processing and training images for facial recognition. +It supports automatically setting the sub labels in Frigate for person objects that are detected and recognized. diff --git a/docs/sidebars.js b/docs/sidebars.js index 14794c910..adc3688ee 100644 --- a/docs/sidebars.js +++ b/docs/sidebars.js @@ -34,6 +34,7 @@ module.exports = { "integrations/home-assistant", "integrations/api", "integrations/mqtt", + "integrations/third_party_extensions", ], Troubleshooting: [ "troubleshooting/faqs", From 19a65eaaacf35e734351f7b1dfdc0a4181e812d9 Mon Sep 17 00:00:00 2001 From: Nicolas Mowen Date: Sat, 8 Apr 2023 09:50:55 -0600 Subject: [PATCH 03/42] Update npm command (#5938) --- docs/docs/development/contributing.md | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/docs/docs/development/contributing.md b/docs/docs/development/contributing.md index 931e9c116..a2e861b44 100644 --- a/docs/docs/development/contributing.md +++ b/docs/docs/development/contributing.md @@ -79,7 +79,7 @@ Create and place these files in a `debug` folder in the root of the repo. This i VSCode will start the docker compose file for you and open a terminal window connected to `frigate-dev`. - Run `python3 -m frigate` to start the backend. -- In a separate terminal window inside VS Code, change into the `web` directory and run `npm install && npm start` to start the frontend. +- In a separate terminal window inside VS Code, change into the `web` directory and run `npm install && npm run dev` to start the frontend. #### 5. Teardown From e3eae53cb9589f7e53c4f2779e5dc9f806286af7 Mon Sep 17 00:00:00 2001 From: Nicolas Mowen Date: Wed, 12 Apr 2023 19:37:56 -0600 Subject: [PATCH 04/42] Fix tensorrt script url (#6015) --- docs/docs/configuration/detectors.md | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/docs/docs/configuration/detectors.md b/docs/docs/configuration/detectors.md index b7f442c31..63004846c 100644 --- a/docs/docs/configuration/detectors.md +++ b/docs/docs/configuration/detectors.md @@ -198,7 +198,7 @@ To generate model files, create a new folder to save the models, download the sc ```bash mkdir trt-models -wget https://raw.githubusercontent.com/blakeblackshear/frigate/docker/tensorrt_models.sh +wget https://github.com/blakeblackshear/frigate/raw/master/docker/tensorrt_models.sh chmod +x tensorrt_models.sh docker run --gpus=all --rm -it -v `pwd`/trt-models:/tensorrt_models -v `pwd`/tensorrt_models.sh:/tensorrt_models.sh nvcr.io/nvidia/tensorrt:22.07-py3 /tensorrt_models.sh ``` From dee471e9e94c4ab1bf0b766270350f95180d57af Mon Sep 17 00:00:00 2001 From: Bernt Christian Egeland Date: Fri, 14 Apr 2023 14:14:28 +0200 Subject: [PATCH 05/42] [Rework RelativeModal] calculate available window height (#6000) * overflow-auto * removed the restrict menu height from #5601. * remove top from the equations due to scroll height * calculate available height --- web/src/components/MultiSelect.jsx | 39 ++++++++++++++++++---------- web/src/components/RelativeModal.jsx | 23 +++++++++++----- web/src/routes/System.jsx | 4 +-- 3 files changed, 43 insertions(+), 23 deletions(-) diff --git a/web/src/components/MultiSelect.jsx b/web/src/components/MultiSelect.jsx index 226316966..1ff30a231 100644 --- a/web/src/components/MultiSelect.jsx +++ b/web/src/components/MultiSelect.jsx @@ -7,54 +7,65 @@ import Button from './Button'; import CameraIcon from '../icons/Camera'; export default function MultiSelect({ className, title, options, selection, onToggle, onShowAll, onSelectSingle }) { - const popupRef = useRef(null); const [state, setState] = useState({ showMenu: false, }); - const isOptionSelected = (item) => { return selection == "all" || selection.split(',').indexOf(item) > -1; } + const isOptionSelected = (item) => { + return selection == 'all' || selection.split(',').indexOf(item) > -1; + }; const menuHeight = Math.round(window.innerHeight * 0.55); return (
-
setState({ showMenu: true })} - > +
setState({ showMenu: true })}>
{state.showMenu ? ( - setState({ showMenu: false })}> + setState({ showMenu: false })} + >
- {title} -
{options.map((item) => (
-
))}
- ): null} + ) : null}
); } diff --git a/web/src/components/RelativeModal.jsx b/web/src/components/RelativeModal.jsx index 93ebe2a26..6ccfccbe1 100644 --- a/web/src/components/RelativeModal.jsx +++ b/web/src/components/RelativeModal.jsx @@ -57,7 +57,7 @@ export default function RelativeModal({ x: relativeToX, y: relativeToY, width: relativeToWidth, - // height: relativeToHeight, + height: relativeToHeight, } = relativeTo.current.getBoundingClientRect(); const _width = widthRelative ? relativeToWidth : menuWidth; @@ -78,10 +78,13 @@ export default function RelativeModal({ newLeft = windowWidth - width - WINDOW_PADDING; } - // too close to bottom - if (top + menuHeight > windowHeight - WINDOW_PADDING + window.scrollY) { - // If the pop-up modal would extend beyond the bottom of the visible window, - // reposition the modal to appear above the clicked icon instead + // This condition checks if the menu overflows the bottom of the page and + // if there's enough space to position the menu above the clicked icon. + // If both conditions are met, the menu will be positioned above the clicked icon + if ( + top + menuHeight > windowHeight - WINDOW_PADDING + window.scrollY && + top - menuHeight - relativeToHeight >= WINDOW_PADDING + ) { newTop = top - menuHeight; } @@ -89,7 +92,13 @@ export default function RelativeModal({ newTop = WINDOW_PADDING; } - const maxHeight = windowHeight - WINDOW_PADDING * 2 > menuHeight ? null : windowHeight - WINDOW_PADDING * 2; + // This calculation checks if there's enough space below the clicked icon for the menu to fit. + // If there is, it sets the maxHeight to null(meaning no height constraint). If not, it calculates the maxHeight based on the remaining space in the window + const maxHeight = + windowHeight - WINDOW_PADDING * 2 - top > menuHeight + ? null + : windowHeight - WINDOW_PADDING * 2 - top + window.scrollY; + const newPosition = { left: newLeft, top: newTop, maxHeight }; if (widthRelative) { newPosition.width = relativeToWidth; @@ -115,7 +124,7 @@ export default function RelativeModal({
-
+
Ffprobe Output {state.ffprobe != '' ? (
@@ -175,7 +175,7 @@ export default function System() { {state.showVainfo && ( -
+
Vainfo Output {state.vainfo != '' ? (
From d6c9538859c3478bb51aadb5800c05b6cfc74450 Mon Sep 17 00:00:00 2001 From: Juliafin Date: Fri, 14 Apr 2023 10:02:57 -0400 Subject: [PATCH 06/42] Improve documentation on web rtc (#6005) * Improve documentation on web rtc * Update docs/docs/configuration/live.md Co-authored-by: Nicolas Mowen * Specify code type in markdown * Match docker config * Add indication that there are other attributes --------- Co-authored-by: Nicolas Mowen Co-authored-by: Blake Blackshear --- docs/docs/configuration/live.md | 12 ++++++++++++ 1 file changed, 12 insertions(+) diff --git a/docs/docs/configuration/live.md b/docs/docs/configuration/live.md index 9cf30cd1a..eac8bb470 100644 --- a/docs/docs/configuration/live.md +++ b/docs/docs/configuration/live.md @@ -99,6 +99,18 @@ If you are having difficulties getting WebRTC to work and you are running Frigat - `network: host`, in this mode you don't need to forward any ports. The services inside of the Frigate container will have full access to the network interfaces of your host machine as if they were running natively and not in a container. Any port conflicts will need to be resolved. This network mode is recommended by go2rtc, but we recommend you only use it if necessary. - `network: bridge` creates a virtual network interface for the container, and the container will have full access to it. You also don't need to forward any ports, however, the IP for accessing Frigate locally will differ from the IP of the host machine. Your router will see Frigate as if it was a new device connected in the network. +If not running in host mode, port 8555 will need to be mapped for the container: + +docker-compose.yml +```yaml +services: + frigate: + ... + ports: + - "8555:8555/tcp" # WebRTC over tcp + - "8555:8555/udp" # WebRTC over udp +``` + ::: See [go2rtc WebRTC docs](https://github.com/AlexxIT/go2rtc/tree/v1.2.0#module-webrtc) for more information about this. From 6e0faa930af540ee2a80d0cd3593c6b13887a62d Mon Sep 17 00:00:00 2001 From: SgtBatten Date: Sat, 15 Apr 2023 22:20:31 +1000 Subject: [PATCH 07/42] Change blueprint url to newest version. (#6091) When home assistant mate the default values change last year it broke the original blueprint when it looked for the camera fps as no default was coded. I've been maintaining a fixed version with new features in hunterjms absence. Yesterday the mods split the thread so this is the latest post and blueprint now. --- docs/docs/guides/ha_notifications.md | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/docs/docs/guides/ha_notifications.md b/docs/docs/guides/ha_notifications.md index 876b6fb57..cf3e03349 100644 --- a/docs/docs/guides/ha_notifications.md +++ b/docs/docs/guides/ha_notifications.md @@ -3,7 +3,7 @@ id: ha_notifications title: Home Assistant notifications --- -The best way to get started with notifications for Frigate is to use the [Blueprint](https://community.home-assistant.io/t/frigate-mobile-app-notifications/311091). You can use the yaml generated from the Blueprint as a starting point and customize from there. +The best way to get started with notifications for Frigate is to use the [Blueprint](https://community.home-assistant.io/t/frigate-mobile-app-notifications-2-0/559732). You can use the yaml generated from the Blueprint as a starting point and customize from there. It is generally recommended to trigger notifications based on the `frigate/events` mqtt topic. This provides the event_id needed to fetch [thumbnails/snapshots/clips](../integrations/home-assistant.md#notification-api) and other useful information to customize when and where you want to receive alerts. The data is published in the form of a change feed, which means you can reference the "previous state" of the object in the `before` section and the "current state" of the object in the `after` section. You can see an example [here](../integrations/mqtt.md#frigateevents). From 3b62ff093a49864ff939198b1e4e16b7f2d3170b Mon Sep 17 00:00:00 2001 From: Nicolas Mowen Date: Sat, 15 Apr 2023 06:24:13 -0600 Subject: [PATCH 08/42] Ensure the frigate config validators are also checked when saving config (#6069) --- frigate/http.py | 1 + 1 file changed, 1 insertion(+) diff --git a/frigate/http.py b/frigate/http.py index f59e341df..d3a059a73 100644 --- a/frigate/http.py +++ b/frigate/http.py @@ -759,6 +759,7 @@ def config_save(): # Validate the config schema try: new_yaml = FrigateConfig.parse_raw(new_config) + check_runtime = new_yaml.runtime_config except Exception as e: return make_response( jsonify( From 75e0ed38eb725fa3789790f0790a28e3bf05f738 Mon Sep 17 00:00:00 2001 From: Nicolas Mowen Date: Sun, 16 Apr 2023 08:49:34 -0600 Subject: [PATCH 09/42] Correct bridge network explanation (#6102) --- docs/docs/configuration/live.md | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/docs/docs/configuration/live.md b/docs/docs/configuration/live.md index eac8bb470..086350d4b 100644 --- a/docs/docs/configuration/live.md +++ b/docs/docs/configuration/live.md @@ -97,7 +97,7 @@ However, it is recommended if issues occur to define the candidates manually. Yo If you are having difficulties getting WebRTC to work and you are running Frigate with docker, you may want to try changing the container network mode: - `network: host`, in this mode you don't need to forward any ports. The services inside of the Frigate container will have full access to the network interfaces of your host machine as if they were running natively and not in a container. Any port conflicts will need to be resolved. This network mode is recommended by go2rtc, but we recommend you only use it if necessary. -- `network: bridge` creates a virtual network interface for the container, and the container will have full access to it. You also don't need to forward any ports, however, the IP for accessing Frigate locally will differ from the IP of the host machine. Your router will see Frigate as if it was a new device connected in the network. +- `network: bridge` is the default network driver, a bridge network is a Link Layer device which forwards traffic between network segments. You need to forward any ports that you want to be accessible from the host IP. If not running in host mode, port 8555 will need to be mapped for the container: From 98384789d49de21753040d2ed6f9ab8c7ea4c8ec Mon Sep 17 00:00:00 2001 From: Blake Blackshear Date: Mon, 17 Apr 2023 07:14:06 -0500 Subject: [PATCH 10/42] update api key for search (#6119) --- docs/docusaurus.config.js | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/docs/docusaurus.config.js b/docs/docusaurus.config.js index cdb369438..954f51b9a 100644 --- a/docs/docusaurus.config.js +++ b/docs/docusaurus.config.js @@ -13,7 +13,7 @@ module.exports = { themeConfig: { algolia: { appId: 'WIURGBNBPY', - apiKey: '81ec882db78f7fed05c51daf973f0362', + apiKey: 'd02cc0a6a61178b25da550212925226b', indexName: 'frigate', }, docs: { From c820badb40e9bb8fd731cafbf1b23d775300573d Mon Sep 17 00:00:00 2001 From: imne Date: Mon, 17 Apr 2023 23:19:43 +0200 Subject: [PATCH 11/42] Update installation.md (#6128) * Update installation.md * Update installation.md * Update docs/docs/frigate/installation.md Co-authored-by: Nicolas Mowen * Update installation.md addressed comment * Update installation.md updated documentation as per comment * Update installation.md * Update installation.md formatting * Update docs/docs/frigate/installation.md Co-authored-by: Nicolas Mowen * Update docs/docs/frigate/installation.md Co-authored-by: Nicolas Mowen * Update docs/docs/frigate/installation.md Co-authored-by: Nicolas Mowen * Update docs/docs/frigate/installation.md Co-authored-by: Nicolas Mowen * Clarify that password template is optional * Typo --------- Co-authored-by: Nicolas Mowen Co-authored-by: Blake Blackshear --- docs/docs/frigate/installation.md | 40 +++++++++++++++++++++++++++++++ 1 file changed, 40 insertions(+) diff --git a/docs/docs/frigate/installation.md b/docs/docs/frigate/installation.md index 03481cfd7..08d74bcde 100644 --- a/docs/docs/frigate/installation.md +++ b/docs/docs/frigate/installation.md @@ -237,3 +237,43 @@ It is recommended to run Frigate in LXC for maximum performance. See [this discu ## ESX For details on running Frigate under ESX, see details [here](https://github.com/blakeblackshear/frigate/issues/305). + +## Synology NAS on DSM 7 + +These settings were tested on DSM 7.1.1-42962 Update 4 + + +**General:** + +The `Execute container using high privilege` option needs to be enabled in order to give the frigate container the elevated privileges it may need. + +The `Enable auto-restart` option can be enabled if you want the container to automatically restart whenever it improperly shuts down due to an error. + +![image](https://user-images.githubusercontent.com/4516296/232586790-0b659a82-561d-4bc5-899b-0f5b39c6b11d.png) + + +**Advanced Settings:** + +If you want to use the password template feature, you should add the "FRIGATE_RTSP_PASSWORD" environment variable and set it to your preferred password under advanced settings. The rest of the environment variables should be left as default for now. + +![image](https://user-images.githubusercontent.com/4516296/232587163-0eb662d4-5e28-4914-852f-9db1ec4b9c3d.png) + + +**Port Settings:** + +The network mode should be set to `bridge`. You need to map the default frigate container ports to your local Synology NAS ports that you want to use to access Frigate. + +There may be other services running on your NAS that are using the same ports that frigate uses. In that instance you can set the ports to auto or a specific port. + +![image](https://user-images.githubusercontent.com/4516296/232582642-773c0e37-7ef5-4373-8ce3-41401b1626e6.png) + + +**Volume Settings:** + +You need to configure 2 paths: + +- The location of your config file in yaml format, this needs to be file and you need to go to the location of where your config.yml is located, this will be different depending on your NAS folder structure e.g. `/docker/frigate/config/config.yml` will mount to `/config/config.yml` within the container. +- The location on your NAS where the recordings will be saved this needs to be a folder e.g. `/docker/volumes/frigate-0-media` + +![image](https://user-images.githubusercontent.com/4516296/232585872-44431d15-55e0-4004-b78b-1e512702b911.png) + From 433bf690e31019b0d8992ec4a2b0ee9885721e8b Mon Sep 17 00:00:00 2001 From: felikcat <29991266+felikcat@users.noreply.github.com> Date: Mon, 17 Apr 2023 17:49:10 -0700 Subject: [PATCH 12/42] live.md: Add a note for Tailscale usage (#6121) --- docs/docs/configuration/live.md | 2 ++ 1 file changed, 2 insertions(+) diff --git a/docs/docs/configuration/live.md b/docs/docs/configuration/live.md index 086350d4b..b2eff6129 100644 --- a/docs/docs/configuration/live.md +++ b/docs/docs/configuration/live.md @@ -78,6 +78,8 @@ WebRTC works by creating a TCP or UDP connection on port `8555`. However, it req - 192.168.1.10:8555 - stun:8555 ``` + +- For access through Tailscale, the Frigate system's Tailscale IP must be added as a WebRTC candidate. Tailscale IPs all start with `100.`, and are reserved within the `100.0.0.0/8` CIDR block. :::tip From 03d37fe8303731b0122a08f98ca7822bb2fbeac9 Mon Sep 17 00:00:00 2001 From: Nicolas Mowen Date: Wed, 19 Apr 2023 16:48:39 -0600 Subject: [PATCH 13/42] Make camera naming more clear (#6164) --- docs/docs/guides/getting_started.md | 12 ++++++------ 1 file changed, 6 insertions(+), 6 deletions(-) diff --git a/docs/docs/guides/getting_started.md b/docs/docs/guides/getting_started.md index b40baef33..adbddb9c2 100644 --- a/docs/docs/guides/getting_started.md +++ b/docs/docs/guides/getting_started.md @@ -14,7 +14,7 @@ mqtt: enabled: False cameras: - camera_1: # <------ Name the camera + name_of_your_camera: # <------ Name the camera ffmpeg: inputs: - path: rtsp://10.0.10.10:554/rtsp # <----- The stream you want to use for detection @@ -44,7 +44,7 @@ Here is an example configuration with hardware acceleration configured for Intel mqtt: ... cameras: - camera_1: + name_of_your_camera: ffmpeg: inputs: ... hwaccel_args: preset-vaapi @@ -64,7 +64,7 @@ detectors: # <---- add detectors device: usb cameras: - camera_1: + name_of_your_camera: ffmpeg: ... detect: enabled: True # <---- turn on detection @@ -99,7 +99,7 @@ detectors: device: usb cameras: - camera_1: + name_of_your_camera: ffmpeg: inputs: - path: rtsp://10.0.10.10:554/rtsp @@ -127,7 +127,7 @@ mqtt: ... detectors: ... cameras: - camera_1: + name_of_your_camera: ffmpeg: inputs: - path: rtsp://10.0.10.10:554/rtsp @@ -156,7 +156,7 @@ mqtt: ... detectors: ... cameras: - camera_1: ... + name_of_your_camera: ... detect: ... record: ... snapshots: # <----- Enable snapshots From 9b109a7d140838df46d5e17b527e56768827bcb1 Mon Sep 17 00:00:00 2001 From: Chandler Date: Wed, 19 Apr 2023 16:54:42 -0600 Subject: [PATCH 14/42] Add docs for getting intel_gpu_top to work without privileged mode (#6166) * Add docs for getting intel_gpu_top to work without privileged mode * Apply suggestions from code review Co-authored-by: Nicolas Mowen * Address more review comments * Rename sections Co-authored-by: Nicolas Mowen --------- Co-authored-by: Nicolas Mowen --- .../configuration/hardware_acceleration.md | 83 +++++++++++++++++-- 1 file changed, 75 insertions(+), 8 deletions(-) diff --git a/docs/docs/configuration/hardware_acceleration.md b/docs/docs/configuration/hardware_acceleration.md index c01bf74eb..0ada027a9 100644 --- a/docs/docs/configuration/hardware_acceleration.md +++ b/docs/docs/configuration/hardware_acceleration.md @@ -15,7 +15,9 @@ ffmpeg: hwaccel_args: preset-rpi-64-h264 ``` -### Intel-based CPUs (<10th Generation) via VAAPI +### Intel-based CPUs + +#### Via VAAPI VAAPI supports automatic profile selection so it will work automatically with both H.264 and H.265 streams. VAAPI is recommended for all generations of Intel-based CPUs if QSV does not work. @@ -26,24 +28,89 @@ ffmpeg: **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 or [in the frigate.yml for HA OS users](advanced.md#environment_vars). -### Intel-based CPUs (>=10th Generation) via Quicksync +#### Via Quicksync (>=10th Generation only) QSV must be set specifically based on the video encoding of the stream. -#### H.264 streams +##### H.264 streams ```yaml ffmpeg: hwaccel_args: preset-intel-qsv-h264 ``` -#### H.265 streams +##### H.265 streams ```yaml ffmpeg: hwaccel_args: preset-intel-qsv-h265 ``` +#### Configuring Intel GPU Stats in Docker + +Additional configuration is needed for the Docker container to be able to access the `intel_gpu_top` command for GPU stats. Three possible changes can be made: + +1. Run the container as privileged. +2. Adding the `CAP_PERFMON` capability. +3. Setting the `perf_event_paranoid` low enough to allow access to the performance event system. + +##### Run as privileged + +This method works, but it gives more permissions to the container than are actually needed. + +###### Docker Compose - Privileged + +```yaml +services: + frigate: + ... + image: ghcr.io/blakeblackshear/frigate:stable + privileged: true +``` + +###### Docker Run CLI - Privileged + +```bash +docker run -d \ + --name frigate \ + ... + --privileged \ + ghcr.io/blakeblackshear/frigate:stable +``` + +##### CAP_PERFMON + +Only recent versions of Docker support the `CAP_PERFMON` capability. You can test to see if yours supports it by running: `docker run --cap-add=CAP_PERFMON hello-world` + +###### Docker Compose - CAP_PERFMON + +```yaml +services: + frigate: + ... + image: ghcr.io/blakeblackshear/frigate:stable + cap_add: + - CAP_PERFMON +``` + +###### Docker Run CLI - CAP_PERFMON + +```bash +docker run -d \ + --name frigate \ + ... + --cap-add=CAP_PERFMON \ + ghcr.io/blakeblackshear/frigate:stable +``` + +##### perf_event_paranoid + +_Note: This setting must be changed for the entire system._ + +For more information on the various values across different distributions, see https://askubuntu.com/questions/1400874/what-does-perf-paranoia-level-four-do. + +Depending on your OS and kernel configuration, you may need to change the `/proc/sys/kernel/perf_event_paranoid` kernel tunable. You can test the change by running `sudo sh -c 'echo 2 >/proc/sys/kernel/perf_event_paranoid'` which will persist until a reboot. Make it permanent by running `sudo sh -c 'echo kernel.perf_event_paranoid=1 >> /etc/sysctl.d/local.conf'` + ### AMD/ATI GPUs (Radeon HD 2000 and newer GPUs) via libva-mesa-driver VAAPI supports automatic profile selection so it will work automatically with both H.264 and H.265 streams. @@ -59,15 +126,15 @@ ffmpeg: While older GPUs may work, it is recommended to use modern, supported GPUs. NVIDIA provides a [matrix of supported GPUs and features](https://developer.nvidia.com/video-encode-and-decode-gpu-support-matrix-new). If your card is on the list and supports CUVID/NVDEC, it will most likely work with Frigate for decoding. However, you must also use [a driver version that will work with FFmpeg](https://github.com/FFmpeg/nv-codec-headers/blob/master/README). Older driver versions may be missing symbols and fail to work, and older cards are not supported by newer driver versions. The only way around this is to [provide your own FFmpeg](/configuration/advanced#custom-ffmpeg-build) that will work with your driver version, but this is unsupported and may not work well if at all. -A more complete list of cards and ther compatible drivers is available in the [driver release readme](https://download.nvidia.com/XFree86/Linux-x86_64/525.85.05/README/supportedchips.html). +A more complete list of cards and their compatible drivers is available in the [driver release readme](https://download.nvidia.com/XFree86/Linux-x86_64/525.85.05/README/supportedchips.html). If your distribution does not offer NVIDIA driver packages, you can [download them here](https://www.nvidia.com/en-us/drivers/unix/). -#### Docker Configuration +#### Configuring Nvidia GPUs in Docker Additional configuration is needed for the Docker container to be able to access the NVIDIA GPU. The supported method for this is to install the [NVIDIA Container Toolkit](https://docs.nvidia.com/datacenter/cloud-native/container-toolkit/install-guide.html#docker) and specify the GPU to Docker. How you do this depends on how Docker is being run: -##### Docker Compose +##### Docker Compose - Nvidia GPU ```yaml services: @@ -84,7 +151,7 @@ services: capabilities: [gpu] ``` -##### Docker Run CLI +##### Docker Run CLI - Nvidia GPU ```bash docker run -d \ From 7960090409ce9e54a533a21877a14ce672b53290 Mon Sep 17 00:00:00 2001 From: my-umd Date: Sun, 23 Apr 2023 08:24:23 -0400 Subject: [PATCH 15/42] Added instruction to install Frigate on QNAP NAS. (#6196) * Added instruction to install Frigate on QNAP NAS. * Incorporate PR review suggestions, minor text changes * Remove LIBVA_DRIVER_NAME from sample command. --- docs/docs/frigate/installation.md | 66 +++++++++++++++++++++++++++++++ 1 file changed, 66 insertions(+) diff --git a/docs/docs/frigate/installation.md b/docs/docs/frigate/installation.md index 08d74bcde..27e200541 100644 --- a/docs/docs/frigate/installation.md +++ b/docs/docs/frigate/installation.md @@ -277,3 +277,69 @@ You need to configure 2 paths: ![image](https://user-images.githubusercontent.com/4516296/232585872-44431d15-55e0-4004-b78b-1e512702b911.png) +## QNAP NAS + +These instructions were tested on a QNAP with an Intel J3455 CPU and 16G RAM, running QTS 4.5.4.2117. + +QNAP has a graphic tool named Container Station to intall and manage docker containers. However, there are two limitations with Container Station that make it unsuitable to install Frigate: + +1. Container Station does not incorporate GitHub Container Registry (ghcr), which hosts Frigate docker image version 0.12.0 and above. +2. Container Station uses default 64 Mb shared memory size (shm-size), and does not have a mechanism to adjust it. Frigate requires a larger shm-size to be able to work properly with more than two high resolution cameras. + +Because of above limitations, the installation has to be done from command line. Here are the steps: + +**Preparation** +1. Install Container Station from QNAP App Center if it is not installed. +2. Enable ssh on your QNAP (please do an Internet search on how to do this). +3. Prepare Frigate config file, name it `config.yml`. +4. Calculate shared memory size according to [documentation](https://docs.frigate.video/frigate/installation). +5. Find your time zone value from https://en.wikipedia.org/wiki/List_of_tz_database_time_zones +6. ssh to QNAP. + +**Installation** + +Run the following commands to install Frigate (using `stable` version as example): +```bash +# Download Frigate image +docker pull ghcr.io/blakeblackshear/frigate:stable +# Create directory to host Frigate config file on QNAP file system. +# E.g., you can choose to create it under /share/Container. +mkdir -p /share/Container/frigate/config +# Copy the config file prepared in step 2 into the newly created config directory. +cp path/to/your/config/file /share/Container/frigate/config +# Create directory to host Frigate media files on QNAP file system. +# (if you have a surveilliance disk, create media directory on the surveilliance disk. +# Example command assumes share_vol2 is the surveilliance drive +mkdir -p /share/share_vol2/frigate/media +# Create Frigate docker container. Replace shm-size value with the value from preparation step 3. +# Also replace the time zone value for 'TZ' in the sample command. +# Example command will create a docker container that uses at most 2 CPUs and 4G RAM. +# You may need to add "--env=LIBVA_DRIVER_NAME=i965 \" to the following docker run command if you +# have certain CPU (e.g., J4125). See https://docs.frigate.video/configuration/hardware_acceleration. +docker run \ + --name=frigate \ + --shm-size=256m \ + --restart=unless-stopped \ + --env=TZ=America/New_York \ + --volume=/share/Container/frigate/config:/config:rw \ + --volume=/share/share_vol2/frigate/media:/media/frigate:rw \ + --network=bridge \ + --privileged \ + --workdir=/opt/frigate \ + -p 1935:1935 \ + -p 5000:5000 \ + -p 8554:8554 \ + -p 8555:8555 \ + -p 8555:8555/udp \ + --label='com.qnap.qcs.network.mode=nat' \ + --label='com.qnap.qcs.gpu=False' \ + --memory="4g" \ + --cpus="2" \ + --detach=true \ + -t \ + ghcr.io/blakeblackshear/frigate:stable +``` + +Log into QNAP, open Container Station. Frigate docker container should be listed under 'Overview' and running. Visit Frigate Web UI by clicking Frigate docker, and then clicking the URL shown at the top of the detail page. + + From ba6794fb99a85d5beab88fc9fbe6dfee7bd2ad19 Mon Sep 17 00:00:00 2001 From: vajonam <152501+vajonam@users.noreply.github.com> Date: Sun, 23 Apr 2023 08:38:21 -0400 Subject: [PATCH 16/42] adding instructions for Nginix reverse proxy (#6159) * adding instructions for Nginix reverse proxy adding an example of subdomain reverse proxy for nginx * Update docs/docs/guides/reverse_proxy.md Co-authored-by: Nicolas Mowen * Update docs/docs/guides/reverse_proxy.md Co-authored-by: Nicolas Mowen * add some descriptions for steps add more information on each of the reverse proxy sections. * cleanup --------- Co-authored-by: Nicolas Mowen Co-authored-by: Blake Blackshear --- docs/docs/guides/reverse_proxy.md | 58 +++++++++++++++++++++++++++++++ 1 file changed, 58 insertions(+) diff --git a/docs/docs/guides/reverse_proxy.md b/docs/docs/guides/reverse_proxy.md index 2e284eede..165b4d517 100644 --- a/docs/docs/guides/reverse_proxy.md +++ b/docs/docs/guides/reverse_proxy.md @@ -84,3 +84,61 @@ There are many ways to authenticate a website but a straightforward approach is ``` + +## Nginx Reverse Proxy + +This method shows a working example for subdomain type reverse proxy with SSL enabled. + +### Setup server and port to reverse proxy + +This is set in `$server` and `$port` this should match your ports you have exposed to your docker container. Optionally you listen on port `443` and enable `SSL` + +``` +# ------------------------------------------------------------ +# frigate.domain.com +# ------------------------------------------------------------ + +server { + set $forward_scheme http; + set $server "192.168.100.2"; # FRIGATE SERVER LOCATION + set $port 5000; + + listen 80; + listen 443 ssl http2; + + server_name frigate.domain.com; +} +``` + +### Setup SSL (optional) + +This section points to your SSL files, the example below shows locations to a default Lets Encrypt SSL certificate. + +``` + # Let's Encrypt SSL + include conf.d/include/letsencrypt-acme-challenge.conf; + include conf.d/include/ssl-ciphers.conf; + ssl_certificate /etc/letsencrypt/live/npm-1/fullchain.pem; + ssl_certificate_key /etc/letsencrypt/live/npm-1/privkey.pem; +``` + + +### Setup reverse proxy settings + +Thhe settings below enabled connection upgrade, sets up logging (optional) and proxies everything from the `/` context to the docker host and port specified earlier in the configuration + +``` + proxy_set_header Upgrade $http_upgrade; + proxy_set_header Connection $http_connection; + proxy_http_version 1.1; + + access_log /data/logs/proxy-host-40_access.log proxy; + error_log /data/logs/proxy-host-40_error.log warn; + + location / { + proxy_set_header Upgrade $http_upgrade; + proxy_set_header Connection $http_connection; + proxy_http_version 1.1; + } + +``` From 35ddc896faf1a1f78f1e03f2390851e8e3fa1b43 Mon Sep 17 00:00:00 2001 From: David Buezas Date: Sun, 23 Apr 2023 14:41:22 +0200 Subject: [PATCH 17/42] Docs: access go2rtc stream externally while using add-on #6208 (#6209) It took me quite a while to figure out why I couldn't access the go2rtc streams, maybe this saves others some time too. Original question: #6148 --- docs/docs/guides/configuring_go2rtc.md | 6 ++++++ 1 file changed, 6 insertions(+) diff --git a/docs/docs/guides/configuring_go2rtc.md b/docs/docs/guides/configuring_go2rtc.md index fa4c30782..7c7bf663d 100644 --- a/docs/docs/guides/configuring_go2rtc.md +++ b/docs/docs/guides/configuring_go2rtc.md @@ -71,6 +71,12 @@ go2rtc: - "ffmpeg:rtsp://user:password@10.0.10.10:554/cam/realmonitor?channel=1&subtype=2#video=copy#audio=copy#audio=aac" ``` +:::caution + +To access the go2rtc stream externally when utilizing the Frigate Add-On, for instance, through a WebRTC card or VLC, you must first enable the RTSP Restream port. You can do this by visiting the Frigate Add-On configuration page within Home Assistant and revealing the hidden options under the "Show disabled ports" section. + +::: + ## Next steps 1. If the stream you added to go2rtc is also used by Frigate for the `record` or `detect` role, you can migrate your config to pull from the RTSP restream to reduce the number of connections to your camera as shown [here](/configuration/restream#reduce-connections-to-camera). From 83aad5262a8b1cc92c3224c66fc84e6937299fe3 Mon Sep 17 00:00:00 2001 From: Kevin David Date: Sun, 23 Apr 2023 09:09:18 -0400 Subject: [PATCH 18/42] contributing.md: add note about extra coral device (#6071) * contributing.md: add note about extra coral device I was banging my head against the wall until I found this comment: https://github.com/blakeblackshear/frigate/issues/132#issuecomment-712170307 * Update docs/docs/development/contributing.md Co-authored-by: Nicolas Mowen --------- Co-authored-by: Nicolas Mowen --- docs/docs/development/contributing.md | 8 +++++++- 1 file changed, 7 insertions(+), 1 deletion(-) diff --git a/docs/docs/development/contributing.md b/docs/docs/development/contributing.md index a2e861b44..0955af56a 100644 --- a/docs/docs/development/contributing.md +++ b/docs/docs/development/contributing.md @@ -36,7 +36,13 @@ Fork [blakeblackshear/frigate-hass-integration](https://github.com/blakeblackshe - [Frigate source code](#frigate-core-web-and-docs) - GNU make - Docker -- Extra Coral device (optional, but very helpful to simulate real world performance) +- An extra detector (Coral, OpenVINO, etc.) is optional but recommended to simulate real world performance. + +:::note + +A Coral device can only be used by a single process at a time, so an extra Coral device is recommended if using a coral for development purposes. + +::: ### Setup From 6bcf44aee8493dd22bccbb438b7cc726a03c8b3b Mon Sep 17 00:00:00 2001 From: David Buezas Date: Tue, 25 Apr 2023 04:54:53 +0200 Subject: [PATCH 19/42] Update configuring_go2rtc.md (#6232) Remove reference to opening the port to use via WebRTCCamera card, since this is not the correct way of using it. --- docs/docs/guides/configuring_go2rtc.md | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/docs/docs/guides/configuring_go2rtc.md b/docs/docs/guides/configuring_go2rtc.md index 7c7bf663d..cc3ff8681 100644 --- a/docs/docs/guides/configuring_go2rtc.md +++ b/docs/docs/guides/configuring_go2rtc.md @@ -73,7 +73,7 @@ go2rtc: :::caution -To access the go2rtc stream externally when utilizing the Frigate Add-On, for instance, through a WebRTC card or VLC, you must first enable the RTSP Restream port. You can do this by visiting the Frigate Add-On configuration page within Home Assistant and revealing the hidden options under the "Show disabled ports" section. +To access the go2rtc stream externally when utilizing the Frigate Add-On (for instance through VLC), you must first enable the RTSP Restream port. You can do this by visiting the Frigate Add-On configuration page within Home Assistant and revealing the hidden options under the "Show disabled ports" section. ::: From df016ddd0dc1d59b7af419d59e5959594fa4f3c2 Mon Sep 17 00:00:00 2001 From: Nicolas Mowen Date: Fri, 28 Apr 2023 06:11:01 -0600 Subject: [PATCH 20/42] Fix http-jpeg template insertion (#6291) --- frigate/ffmpeg_presets.py | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/frigate/ffmpeg_presets.py b/frigate/ffmpeg_presets.py index 67348569c..185ce0d86 100644 --- a/frigate/ffmpeg_presets.py +++ b/frigate/ffmpeg_presets.py @@ -320,7 +320,7 @@ def parse_preset_input(arg: Any, detect_fps: int) -> list[str]: if arg == "preset-http-jpeg-generic": input = PRESETS_INPUT[arg].copy() - input[1] = str(detect_fps) + input[len(_user_agent_args) + 1] = str(detect_fps) return input return PRESETS_INPUT.get(arg, None) From cbd07696b5776562a07dd920005fdf3e550b66c0 Mon Sep 17 00:00:00 2001 From: Nicolas Mowen Date: Fri, 28 Apr 2023 06:48:13 -0600 Subject: [PATCH 21/42] Clarify stationary object behavior (#6273) * Clarify stationary object behavior * typo * tweak --------- Co-authored-by: Blake Blackshear --- docs/docs/guides/stationary_objects.md | 8 +++++++- 1 file changed, 7 insertions(+), 1 deletion(-) diff --git a/docs/docs/guides/stationary_objects.md b/docs/docs/guides/stationary_objects.md index 757e6c118..5d45e58c5 100644 --- a/docs/docs/guides/stationary_objects.md +++ b/docs/docs/guides/stationary_objects.md @@ -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 over the course of multiple days (for example if the car is lost at night and detected again the following morning. +Many people use Frigate to detect cars entering their driveway, and they often run into an issue with repeated notifications or 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. @@ -15,6 +15,12 @@ Frigate is designed to track objects as they move and over-masking can prevent i ::: +:::info + +Once a vehicle crosses the entrance into the parking area, that event will stay `In Progress` until it is no longer seen in the frame. Frigate is designed to have an event last as long as an object is visible in the frame, an event being `In Progress` does not mean the event is being constantly recorded. You can define the recording behavior by adjusting the [recording retention settings](../configuration/record.md). + +::: + To only be notified of cars that enter your driveway from the street, you could create multiple zones that cover your driveway. For cars, you would only notify if `entered_zones` from the events MQTT topic has more than 1 zone. See [this example](../configuration/zones.md#restricting-zones-to-specific-objects) from the Zones documentation to see how to restrict zones to certain object types. From 82aa238ecaeff0697c3bc50972519bedab86de06 Mon Sep 17 00:00:00 2001 From: Nicolas Mowen Date: Sun, 30 Apr 2023 14:04:19 -0600 Subject: [PATCH 22/42] Add instructions for custom go2rtc build (#6333) * Add instructions for custom go2rtc build * Fix numbering --- docs/docs/configuration/advanced.md | 11 +++++++++++ 1 file changed, 11 insertions(+) diff --git a/docs/docs/configuration/advanced.md b/docs/docs/configuration/advanced.md index 7cedfce3c..8404c3cf0 100644 --- a/docs/docs/configuration/advanced.md +++ b/docs/docs/configuration/advanced.md @@ -108,3 +108,14 @@ To do this: 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`. + +## Custom go2rtc version + +Frigate currently includes go2rtc v1.2.0, there may be certain cases where you want to run a different version of go2rtc. + +To do this: + +1. Download the go2rtc build to the /config folder. +2. Rename the build to `go2rtc`. +3. Give `go2rtc` execute permission. +4. Restart Frigate and the custom version will be used, you can verify by checking go2rtc logs. From 85015d9409170800197bc50980c60d1afe3a36c4 Mon Sep 17 00:00:00 2001 From: Nicolas Mowen Date: Thu, 4 May 2023 16:55:19 -0600 Subject: [PATCH 23/42] Update version to 0.12.1 (#6386) --- Makefile | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/Makefile b/Makefile index 506b11247..7ec0c71aa 100644 --- a/Makefile +++ b/Makefile @@ -1,7 +1,7 @@ default_target: local COMMIT_HASH := $(shell git log -1 --pretty=format:"%h"|tail -1) -VERSION = 0.12.0 +VERSION = 0.12.1 IMAGE_REPO ?= ghcr.io/blakeblackshear/frigate CURRENT_UID := $(shell id -u) CURRENT_GID := $(shell id -g) From 305323c9e9297c62a969f0dbb3630edec3a13693 Mon Sep 17 00:00:00 2001 From: Nicolas Mowen Date: Thu, 4 May 2023 16:55:44 -0600 Subject: [PATCH 24/42] Update Rpi preset (#6385) * Update Rpi preset * Fix rpi test --- frigate/ffmpeg_presets.py | 4 ++-- frigate/test/test_ffmpeg_presets.py | 2 +- 2 files changed, 3 insertions(+), 3 deletions(-) diff --git a/frigate/ffmpeg_presets.py b/frigate/ffmpeg_presets.py index 185ce0d86..353a8b922 100644 --- a/frigate/ffmpeg_presets.py +++ b/frigate/ffmpeg_presets.py @@ -53,8 +53,8 @@ _user_agent_args = [ ] PRESETS_HW_ACCEL_DECODE = { - "preset-rpi-32-h264": ["-c:v", "h264_v4l2m2m"], - "preset-rpi-64-h264": ["-c:v", "h264_v4l2m2m"], + "preset-rpi-32-h264": ["-c:v:1", "h264_v4l2m2m"], + "preset-rpi-64-h264": ["-c:v:1", "h264_v4l2m2m"], "preset-vaapi": [ "-hwaccel_flags", "allow_profile_mismatch", diff --git a/frigate/test/test_ffmpeg_presets.py b/frigate/test/test_ffmpeg_presets.py index 6ea623790..92e0fa3bd 100644 --- a/frigate/test/test_ffmpeg_presets.py +++ b/frigate/test/test_ffmpeg_presets.py @@ -52,7 +52,7 @@ class TestFfmpegPresets(unittest.TestCase): assert "preset-rpi-64-h264" not in ( " ".join(frigate_config.cameras["back"].ffmpeg_cmds[0]["cmd"]) ) - assert "-c:v h264_v4l2m2m" in ( + assert "-c:v:1 h264_v4l2m2m" in ( " ".join(frigate_config.cameras["back"].ffmpeg_cmds[0]["cmd"]) ) From 5951a740d29ba12913d3b38ae165e9faaef0eefd Mon Sep 17 00:00:00 2001 From: Nicolas Mowen Date: Mon, 15 May 2023 06:36:26 -0600 Subject: [PATCH 25/42] Prevent recordings from being turned on if disabled in config (#6444) * Prevent enabling recordings if not enabled in config * Fix conflict * Fix spacing * Update wording * Update wording --------- Co-authored-by: Blake Blackshear --- frigate/comms/dispatcher.py | 6 ++++++ frigate/config.py | 6 ++++++ web/__test__/handlers.js | 4 ++-- web/src/routes/Cameras.jsx | 18 ++++++++++-------- 4 files changed, 24 insertions(+), 10 deletions(-) diff --git a/frigate/comms/dispatcher.py b/frigate/comms/dispatcher.py index d304509e4..447a35cb8 100644 --- a/frigate/comms/dispatcher.py +++ b/frigate/comms/dispatcher.py @@ -180,6 +180,12 @@ class Dispatcher: record_settings = self.config.cameras[camera_name].record if payload == "ON": + if not self.config.cameras[camera_name].record.enabled_in_config: + logger.error( + f"Recordings must be enabled in the config to be turned on via MQTT." + ) + return + if not record_settings.enabled: logger.info(f"Turning on recordings for {camera_name}") record_settings.enabled = True diff --git a/frigate/config.py b/frigate/config.py index d19dfbfd0..d62c6efda 100644 --- a/frigate/config.py +++ b/frigate/config.py @@ -172,6 +172,9 @@ class RecordConfig(FrigateBaseModel): events: EventsConfig = Field( default_factory=EventsConfig, title="Event specific settings." ) + enabled_in_config: Optional[bool] = Field( + title="Keep track of original state of recording." + ) class MotionConfig(FrigateBaseModel): @@ -952,6 +955,9 @@ class FrigateConfig(FrigateBaseModel): for input in camera_config.ffmpeg.inputs: input.path = input.path.format(**FRIGATE_ENV_VARS) + # set config recording value + camera_config.record.enabled_in_config = camera_config.record.enabled + # Add default filters object_keys = camera_config.objects.track if camera_config.objects.filters is None: diff --git a/web/__test__/handlers.js b/web/__test__/handlers.js index 23433d840..d7f2fb5a6 100644 --- a/web/__test__/handlers.js +++ b/web/__test__/handlers.js @@ -16,7 +16,7 @@ export const handlers = [ front: { name: 'front', objects: { track: ['taco', 'cat', 'dog'] }, - record: { enabled: true }, + record: { enabled: true, enabled_in_config: true }, detect: { width: 1280, height: 720 }, snapshots: {}, restream: { enabled: true, jsmpeg: { height: 720 } }, @@ -25,7 +25,7 @@ export const handlers = [ side: { name: 'side', objects: { track: ['taco', 'cat', 'dog'] }, - record: { enabled: false }, + record: { enabled: false, enabled_in_config: true }, detect: { width: 1280, height: 720 }, snapshots: {}, restream: { enabled: true, jsmpeg: { height: 720 } }, diff --git a/web/src/routes/Cameras.jsx b/web/src/routes/Cameras.jsx index 4b1a5ed34..1e2bbf903 100644 --- a/web/src/routes/Cameras.jsx +++ b/web/src/routes/Cameras.jsx @@ -16,12 +16,12 @@ export default function Cameras() { ) : (
- +
); } -function SortedCameras({ unsortedCameras }) { +function SortedCameras({ config, unsortedCameras }) { const sortedCameras = useMemo( () => Object.entries(unsortedCameras) @@ -33,13 +33,13 @@ function SortedCameras({ unsortedCameras }) { return ( {sortedCameras.map(([camera, conf]) => ( - + ))} ); } -function Camera({ name }) { +function Camera({ name, config }) { const { payload: detectValue, send: sendDetect } = useDetectState(name); const { payload: recordValue, send: sendRecordings } = useRecordingsState(name); const { payload: snapshotValue, send: sendSnapshots } = useSnapshotsState(name); @@ -65,11 +65,13 @@ function Camera({ name }) { }, }, { - name: `Toggle recordings ${recordValue === 'ON' ? 'off' : 'on'}`, + name: config.record.enabled_in_config ? `Toggle recordings ${recordValue === 'ON' ? 'off' : 'on'}` : 'Recordings must be enabled in the config to be turned on in the UI.', icon: ClipIcon, - color: recordValue === 'ON' ? 'blue' : 'gray', + color: config.record.enabled_in_config ? (recordValue === 'ON' ? 'blue' : 'gray') : 'red', onClick: () => { - sendRecordings(recordValue === 'ON' ? 'OFF' : 'ON', true); + if (config.record.enabled_in_config) { + sendRecordings(recordValue === 'ON' ? 'OFF' : 'ON', true); + } }, }, { @@ -81,7 +83,7 @@ function Camera({ name }) { }, }, ], - [detectValue, sendDetect, recordValue, sendRecordings, snapshotValue, sendSnapshots] + [config, detectValue, sendDetect, recordValue, sendRecordings, snapshotValue, sendSnapshots] ); return ( From 181b53a55d5377366ecb99b1d312b286146576b8 Mon Sep 17 00:00:00 2001 From: Sergey Krashevich Date: Mon, 15 May 2023 15:37:34 +0300 Subject: [PATCH 26/42] Fix error in parsing DeepStack response JSON and handle cases where predictions field is missing (#6463) --- frigate/detectors/plugins/deepstack.py | 5 ++++- 1 file changed, 4 insertions(+), 1 deletion(-) diff --git a/frigate/detectors/plugins/deepstack.py b/frigate/detectors/plugins/deepstack.py index 716065903..9d2c69540 100644 --- a/frigate/detectors/plugins/deepstack.py +++ b/frigate/detectors/plugins/deepstack.py @@ -56,8 +56,11 @@ class DeepStack(DetectionApi): ) response_json = response.json() detections = np.zeros((20, 6), np.float32) + if response_json.get("predictions") is None: + logger.debug(f"Error in parsing response json: {response_json}") + return detections - for i, detection in enumerate(response_json["predictions"]): + for i, detection in enumerate(response_json.get("predictions")): logger.debug(f"Response: {detection}") if detection["confidence"] < 0.4: logger.debug(f"Break due to confidence < 0.4") From 5fb96c777a9c4faeac44942f3f5a638e9bee5299 Mon Sep 17 00:00:00 2001 From: Sergey Krashevich Date: Mon, 15 May 2023 15:39:03 +0300 Subject: [PATCH 27/42] Add cmdline information to CPU usage stats in get_cpu_stats() function and display it in the System page's ffmpeg table with a copy-to-clipboard button (#6430) --- frigate/util.py | 4 +++- web/src/routes/System.jsx | 10 +++++++++- 2 files changed, 12 insertions(+), 2 deletions(-) diff --git a/frigate/util.py b/frigate/util.py index d98cee106..a6b4c912a 100755 --- a/frigate/util.py +++ b/frigate/util.py @@ -800,10 +800,11 @@ def get_cpu_stats() -> dict[str, dict]: docker_memlimit = get_docker_memlimit_bytes() / 1024 total_mem = os.sysconf("SC_PAGE_SIZE") * os.sysconf("SC_PHYS_PAGES") / 1024 - for process in psutil.process_iter(["pid", "name", "cpu_percent"]): + for process in psutil.process_iter(["pid", "name", "cpu_percent", "cmdline"]): pid = process.info["pid"] try: cpu_percent = process.info["cpu_percent"] + cmdline = process.info["cmdline"] with open(f"/proc/{pid}/stat", "r") as f: stats = f.readline().split() @@ -837,6 +838,7 @@ def get_cpu_stats() -> dict[str, dict]: "cpu": str(cpu_percent), "cpu_average": str(round(cpu_average_usage, 2)), "mem": f"{mem_pct}", + "cmdline": " ".join(cmdline), } except: continue diff --git a/web/src/routes/System.jsx b/web/src/routes/System.jsx index 7ec238596..b6d78dd54 100644 --- a/web/src/routes/System.jsx +++ b/web/src/routes/System.jsx @@ -347,7 +347,15 @@ export default function System() { - ffmpeg + ffmpeg + + {cameras[camera]['ffmpeg_pid'] || '- '} {cameras[camera]['camera_fps'] || '- '} {cpu_usages[cameras[camera]['ffmpeg_pid']]?.['cpu'] || '- '}% From 6634be1f79e4ce5e1d8d2a7bd43f4cf1861d815d Mon Sep 17 00:00:00 2001 From: Nicolas Mowen Date: Wed, 17 May 2023 06:38:27 -0600 Subject: [PATCH 28/42] Make note of mapping rpi device (#6511) --- docs/docs/configuration/hardware_acceleration.md | 14 ++++++++++++++ 1 file changed, 14 insertions(+) diff --git a/docs/docs/configuration/hardware_acceleration.md b/docs/docs/configuration/hardware_acceleration.md index 0ada027a9..bb50d9cfe 100644 --- a/docs/docs/configuration/hardware_acceleration.md +++ b/docs/docs/configuration/hardware_acceleration.md @@ -15,6 +15,20 @@ ffmpeg: hwaccel_args: preset-rpi-64-h264 ``` +:::note + +If running Frigate in docker, you either need to run in priviliged mode or be sure to map the /dev/video1x devices to Frigate + +```yaml +docker run -d \ + --name frigate \ + ... + --device /dev/video10 \ + ghcr.io/blakeblackshear/frigate:stable +``` + +::: + ### Intel-based CPUs #### Via VAAPI From b4038821cc3e18e21bd3b5ed684a59bce62a258f Mon Sep 17 00:00:00 2001 From: Sergey Krashevich Date: Wed, 17 May 2023 15:40:41 +0300 Subject: [PATCH 29/42] Update DeepStack detector to set width and height from input image size (#6429) --- frigate/detectors/plugins/deepstack.py | 4 +--- 1 file changed, 1 insertion(+), 3 deletions(-) diff --git a/frigate/detectors/plugins/deepstack.py b/frigate/detectors/plugins/deepstack.py index 9d2c69540..9f3d323a2 100644 --- a/frigate/detectors/plugins/deepstack.py +++ b/frigate/detectors/plugins/deepstack.py @@ -33,9 +33,6 @@ class DeepStack(DetectionApi): self.api_key = detector_config.api_key self.labels = detector_config.model.merged_labelmap - self.h = detector_config.model.height - self.w = detector_config.model.width - def get_label_index(self, label_value): if label_value.lower() == "truck": label_value = "car" @@ -47,6 +44,7 @@ class DeepStack(DetectionApi): def detect_raw(self, tensor_input): image_data = np.squeeze(tensor_input).astype(np.uint8) image = Image.fromarray(image_data) + self.w, self.h = image.size with io.BytesIO() as output: image.save(output, format="JPEG") image_bytes = output.getvalue() From b568a29fa8eb24e46b9ba623c10016689de7ec20 Mon Sep 17 00:00:00 2001 From: Nicolas Mowen Date: Wed, 17 May 2023 06:42:56 -0600 Subject: [PATCH 30/42] Don't fail if camera does not support presets (#6497) --- frigate/ptz.py | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/frigate/ptz.py b/frigate/ptz.py index e2c21618e..a52006b5c 100644 --- a/frigate/ptz.py +++ b/frigate/ptz.py @@ -79,8 +79,8 @@ class OnvifController: try: presets: list[dict] = ptz.GetPresets({"ProfileToken": profile.token}) except ONVIFError as e: - logger.error(f"Unable to get presets from camera: {camera_name}: {e}") - return False + logger.warning(f"Unable to get presets from camera: {camera_name}: {e}") + presets = [] for preset in presets: self.cams[camera_name]["presets"][preset["Name"].lower()] = preset["token"] From 76dbab6a8bf15fa86ea4a8d7ec70ce81ee639a17 Mon Sep 17 00:00:00 2001 From: Robert Lagus Date: Wed, 17 May 2023 15:45:09 +0300 Subject: [PATCH 31/42] [Docs] Snapshot config explanation. (#6439) * Snapshot config explanation. related to: https://github.com/blakeblackshear/frigate/issues/6434 * Update docs/docs/configuration/snapshots.md Co-authored-by: Nicolas Mowen * Update docs/docs/configuration/snapshots.md Co-authored-by: Blake Blackshear --------- Co-authored-by: Nicolas Mowen Co-authored-by: Blake Blackshear --- docs/docs/configuration/snapshots.md | 2 ++ 1 file changed, 2 insertions(+) diff --git a/docs/docs/configuration/snapshots.md b/docs/docs/configuration/snapshots.md index 73b8f779d..6145812db 100644 --- a/docs/docs/configuration/snapshots.md +++ b/docs/docs/configuration/snapshots.md @@ -4,3 +4,5 @@ title: Snapshots --- Frigate can save a snapshot image to `/media/frigate/clips` for each event named as `-.jpg`. + +Snapshots sent via MQTT are configured in the [config file](https://docs.frigate.video/configuration/) under `cameras -> your_camera -> mqtt` From 17e8a46c7d1d164715a5f4e0db18efcd5bd1a8c8 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Korneliusz=20Jarz=C4=99bski?= Date: Thu, 18 May 2023 03:01:56 +0200 Subject: [PATCH 32/42] add ffmpeg bandwidth stats (#6492) MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit * add ffmpeg bandwidth stats * add ffmpeg bandwidth stats * Change column name Co-authored-by: Nicolas Mowen * fix lint formatting --------- Co-authored-by: Korneliusz Jarzębski Co-authored-by: Nicolas Mowen --- docker/install_deps.sh | 3 ++- frigate/stats.py | 11 ++++++++++- frigate/util.py | 29 +++++++++++++++++++++++++++++ web/src/routes/System.jsx | 5 +++++ 4 files changed, 46 insertions(+), 2 deletions(-) diff --git a/docker/install_deps.sh b/docker/install_deps.sh index 9a0e80dfe..3443329dd 100755 --- a/docker/install_deps.sh +++ b/docker/install_deps.sh @@ -12,7 +12,8 @@ apt-get -qq install --no-install-recommends -y \ unzip locales tzdata libxml2 xz-utils \ python3-pip \ curl \ - jq + jq \ + nethogs mkdir -p -m 600 /root/.gnupg diff --git a/frigate/stats.py b/frigate/stats.py index 4287dab0c..55db809d3 100644 --- a/frigate/stats.py +++ b/frigate/stats.py @@ -16,7 +16,7 @@ from frigate.const import DRIVER_AMD, DRIVER_ENV_VAR, RECORD_DIR, CLIPS_DIR, CAC from frigate.types import StatsTrackingTypes, CameraMetricsTypes from frigate.util import get_amd_gpu_stats, get_intel_gpu_stats, get_nvidia_gpu_stats from frigate.version import VERSION -from frigate.util import get_cpu_stats +from frigate.util import get_cpu_stats, get_bandwidth_stats from frigate.object_detection import ObjectDetectProcess logger = logging.getLogger(__name__) @@ -101,6 +101,7 @@ def get_processing_stats( [ asyncio.create_task(set_gpu_stats(config, stats, hwaccel_errors)), asyncio.create_task(set_cpu_stats(stats)), + asyncio.create_task(set_bandwidth_stats(stats)), ] ) @@ -118,6 +119,14 @@ async def set_cpu_stats(all_stats: dict[str, Any]) -> None: all_stats["cpu_usages"] = cpu_stats +async def set_bandwidth_stats(all_stats: dict[str, Any]) -> None: + """Set bandwidth from nethogs.""" + bandwidth_stats = get_bandwidth_stats() + + if bandwidth_stats: + all_stats["bandwidth_usages"] = bandwidth_stats + + async def set_gpu_stats( config: FrigateConfig, all_stats: dict[str, Any], hwaccel_errors: list[str] ) -> None: diff --git a/frigate/util.py b/frigate/util.py index a6b4c912a..f82476ebc 100755 --- a/frigate/util.py +++ b/frigate/util.py @@ -846,6 +846,35 @@ def get_cpu_stats() -> dict[str, dict]: return usages +def get_bandwidth_stats() -> dict[str, dict]: + """Get bandwidth usages for each ffmpeg process id""" + usages = {} + top_command = ["nethogs", "-t", "-v0", "-c5", "-d1"] + + p = sp.run( + top_command, + encoding="ascii", + capture_output=True, + ) + + if p.returncode != 0: + return usages + else: + lines = p.stdout.split("\n") + for line in lines: + stats = list(filter(lambda a: a != "", line.strip().split("\t"))) + try: + if re.search("^ffmpeg/([0-9]+)/", stats[0]): + process = stats[0].split("/") + usages[process[1]] = { + "bandwidth": round(float(stats[2]), 1), + } + except: + continue + + return usages + + def get_amd_gpu_stats() -> dict[str, str]: """Get stats using radeontop.""" radeontop_command = ["radeontop", "-d", "-", "-l", "1"] diff --git a/web/src/routes/System.jsx b/web/src/routes/System.jsx index b6d78dd54..8d5c4ef80 100644 --- a/web/src/routes/System.jsx +++ b/web/src/routes/System.jsx @@ -27,6 +27,7 @@ export default function System() { const { cpu_usages, gpu_usages, + bandwidth_usages, detectors, service = {}, detection_fps: _, @@ -343,6 +344,7 @@ export default function System() { FPS CPU % Memory % + Network Bandwidth @@ -360,6 +362,7 @@ export default function System() { {cameras[camera]['camera_fps'] || '- '} {cpu_usages[cameras[camera]['ffmpeg_pid']]?.['cpu'] || '- '}% {cpu_usages[cameras[camera]['ffmpeg_pid']]?.['mem'] || '- '}% + {bandwidth_usages[cameras[camera]['ffmpeg_pid']]?.['bandwidth'] || '- '}KB/s Capture @@ -367,6 +370,7 @@ export default function System() { {cameras[camera]['process_fps'] || '- '} {cpu_usages[cameras[camera]['capture_pid']]?.['cpu'] || '- '}% {cpu_usages[cameras[camera]['capture_pid']]?.['mem'] || '- '}% + - Detect @@ -387,6 +391,7 @@ export default function System() { {cpu_usages[cameras[camera]['pid']]?.['cpu'] || '- '}% {cpu_usages[cameras[camera]['pid']]?.['mem'] || '- '}% + - From 6d0c2ec5c877a50bd1c46da768f9cb073add99e9 Mon Sep 17 00:00:00 2001 From: Sergey Krashevich Date: Thu, 18 May 2023 14:40:24 +0300 Subject: [PATCH 33/42] Add go2rtc & remote detectors network bandwidth usage to System table (#6526) * Add network bandwidth usage to System table display in System.jsx and update get_bandwidth_stats function in util.py to include go2rtc processes * black... * Add network bandwidth usage to system table in web UI and improve regex in get_bandwidth_stats function to include frigate detector processes * black... * Update bandwidth calculation to include both incoming and outgoing traffic * black:( --- frigate/util.py | 8 +++++--- web/src/routes/System.jsx | 4 ++++ 2 files changed, 9 insertions(+), 3 deletions(-) diff --git a/frigate/util.py b/frigate/util.py index f82476ebc..47b7b4323 100755 --- a/frigate/util.py +++ b/frigate/util.py @@ -864,10 +864,12 @@ def get_bandwidth_stats() -> dict[str, dict]: for line in lines: stats = list(filter(lambda a: a != "", line.strip().split("\t"))) try: - if re.search("^ffmpeg/([0-9]+)/", stats[0]): + if re.search( + "(^ffmpeg|\/go2rtc|frigate\.detector\.[a-z]+)/([0-9]+)/", stats[0] + ): process = stats[0].split("/") - usages[process[1]] = { - "bandwidth": round(float(stats[2]), 1), + usages[process[len(process) - 2]] = { + "bandwidth": round(float(stats[1]) + float(stats[2]), 1), } except: continue diff --git a/web/src/routes/System.jsx b/web/src/routes/System.jsx index 8d5c4ef80..012d1be03 100644 --- a/web/src/routes/System.jsx +++ b/web/src/routes/System.jsx @@ -239,6 +239,7 @@ export default function System() { Inference Speed CPU % Memory % + Network Bandwidth @@ -247,6 +248,7 @@ export default function System() { {detectors[detector]['inference_speed']} ms {cpu_usages[detectors[detector]['pid']]?.['cpu'] || '- '}% {cpu_usages[detectors[detector]['pid']]?.['mem'] || '- '}% + {bandwidth_usages[detectors[detector]['pid']]?.['bandwidth'] || '- '}KB/s @@ -428,6 +430,7 @@ export default function System() { CPU % Avg CPU % Memory % + Network Bandwidth @@ -436,6 +439,7 @@ export default function System() { {cpu_usages[processes[process]['pid']]?.['cpu'] || '- '}% {cpu_usages[processes[process]['pid']]?.['cpu_average'] || '- '}% {cpu_usages[processes[process]['pid']]?.['mem'] || '- '}% + {bandwidth_usages[processes[process]['pid']]?.['bandwidth'] || '- '}KB/s From e357715a8c908e70c50bd472aded8f39d5378bb5 Mon Sep 17 00:00:00 2001 From: Nicolas Mowen Date: Fri, 19 May 2023 04:16:11 -0600 Subject: [PATCH 34/42] Ability to manually create events through the API (#3184) * Move to events package * Improve handling of external events * Handle external events in the event queue * Pass in event processor * Check event json * Fix json parsing and change defaults * Fix snapshot saving * Hide % score when not available * Correct docs and add json example * Save event png db * Adjust image * Formatting * Add catch for failure ending event * Add init to modules * Fix naming * Formatting * Fix http creation * fix test * Change to PUT and include response in docs * Add ability to set bounding box locations in snapshot * Support multiple box annotations * Cleanup docs example response Co-authored-by: Blake Blackshear * Cleanup docs wording Co-authored-by: Blake Blackshear * Store full frame for thumbnail * Formatting * Set thumbnail height to 175 * Formatting --------- Co-authored-by: Blake Blackshear --- docs/docs/integrations/api.md | 38 ++++ frigate/app.py | 11 +- frigate/events/__init__.py | 0 frigate/events/cleanup.py | 176 ++++++++++++++++++ frigate/events/external.py | 132 ++++++++++++++ frigate/{events.py => events/maintainer.py} | 190 ++++---------------- frigate/http.py | 81 +++++++-- frigate/object_processing.py | 2 +- frigate/record/__init__.py | 0 frigate/test/test_http.py | 10 ++ frigate/timeline.py | 2 +- web/src/routes/Events.jsx | 4 +- 12 files changed, 466 insertions(+), 180 deletions(-) create mode 100644 frigate/events/__init__.py create mode 100644 frigate/events/cleanup.py create mode 100644 frigate/events/external.py rename frigate/{events.py => events/maintainer.py} (51%) create mode 100644 frigate/record/__init__.py diff --git a/docs/docs/integrations/api.md b/docs/docs/integrations/api.md index b5b80f3ea..3e5fdfe3e 100644 --- a/docs/docs/integrations/api.md +++ b/docs/docs/integrations/api.md @@ -295,3 +295,41 @@ Get ffprobe output for camera feed paths. ### `GET /api//ptz/info` Get PTZ info for the camera. + +### `POST /api/events//
From fec1dd3f461c9c2674484887338ece4ebe6f7092 Mon Sep 17 00:00:00 2001 From: Nicolas Mowen Date: Fri, 19 May 2023 10:59:24 -0600 Subject: [PATCH 35/42] Fix breakages by manual_event_api branch (#6542) * Fix breakages by manual_event_api branch * More fixes --- frigate/http.py | 21 +++++++++++++++------ 1 file changed, 15 insertions(+), 6 deletions(-) diff --git a/frigate/http.py b/frigate/http.py index 3ad8fb7af..df65d7797 100644 --- a/frigate/http.py +++ b/frigate/http.py @@ -198,7 +198,7 @@ def send_to_plus(id): return make_response(jsonify({"success": False, "message": message}), 404) # events from before the conversion to relative dimensions cant include annotations - if any(d > 1 for d in event.box): + if event.data.get("box") is None: include_annotation = None if event.end_time is None: @@ -254,8 +254,7 @@ def send_to_plus(id): event.save() if not include_annotation is None: - region = event.region - box = event.box + box = event.data["box"] try: current_app.plus_api.add_annotation( @@ -313,11 +312,15 @@ def false_positive(id): # need to refetch the event now that it has a plus_id event = Event.get(Event.id == id) - region = event.region - box = event.box + region = event.data["region"] + box = event.data["box"] # provide top score if score is unavailable - score = event.top_score if event.score is None else event.score + score = ( + (event.data["top_score"] if event.data["top_score"] else event.top_score) + if event.data["score"] is None + else event.data["score"] + ) try: current_app.plus_api.add_false_positive( @@ -758,6 +761,7 @@ def events(): Event.top_score, Event.false_positive, Event.box, + Event.data, ] if camera != "all": @@ -916,6 +920,11 @@ def config(): config["plus"] = {"enabled": current_app.plus_api.is_active()} + for detector, detector_config in config["detectors"].items(): + detector_config["model"][ + "labelmap" + ] = current_app.frigate_config.model.merged_labelmap + return jsonify(config) From 4940826011ee8dc515e4b70f771f3d626d941cb2 Mon Sep 17 00:00:00 2001 From: Nicolas Mowen Date: Fri, 19 May 2023 13:26:19 -0600 Subject: [PATCH 36/42] Fix false positives (#6544) --- frigate/http.py | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/frigate/http.py b/frigate/http.py index df65d7797..de9fd033c 100644 --- a/frigate/http.py +++ b/frigate/http.py @@ -295,7 +295,7 @@ def false_positive(id): return make_response(jsonify({"success": False, "message": message}), 404) # events from before the conversion to relative dimensions cant include annotations - if any(d > 1 for d in event.box): + if event.data.get("box") is None: message = f"Events prior to 0.13 cannot be submitted as false positives" logger.error(message) return make_response(jsonify({"success": False, "message": message}), 400) From deccc4fd46c4dec6a015eec26d6cf996ce8dca43 Mon Sep 17 00:00:00 2001 From: Blake Blackshear Date: Sun, 21 May 2023 07:14:53 -0500 Subject: [PATCH 37/42] fix coral libs (#6558) --- docker/install_deps.sh | 5 +++-- 1 file changed, 3 insertions(+), 2 deletions(-) diff --git a/docker/install_deps.sh b/docker/install_deps.sh index 3443329dd..25b6951b5 100755 --- a/docker/install_deps.sh +++ b/docker/install_deps.sh @@ -18,8 +18,9 @@ apt-get -qq install --no-install-recommends -y \ mkdir -p -m 600 /root/.gnupg # add coral repo -wget --quiet -O /usr/share/keyrings/google-edgetpu.gpg https://packages.cloud.google.com/apt/doc/apt-key.gpg -echo "deb [signed-by=/usr/share/keyrings/google-edgetpu.gpg] https://packages.cloud.google.com/apt coral-edgetpu-stable main" | tee /etc/apt/sources.list.d/coral-edgetpu.list +curl -fsSLo - https://packages.cloud.google.com/apt/doc/apt-key.gpg | \ + gpg --dearmor -o /etc/apt/trusted.gpg.d/google-cloud-packages-archive-keyring.gpg +echo "deb https://packages.cloud.google.com/apt coral-edgetpu-stable main" | tee /etc/apt/sources.list.d/coral-edgetpu.list echo "libedgetpu1-max libedgetpu/accepted-eula select true" | debconf-set-selections # enable non-free repo From 53d63e0f75c63a1b0e7ff80c8b824515b4affe96 Mon Sep 17 00:00:00 2001 From: Nicolas Mowen Date: Sun, 21 May 2023 06:53:25 -0600 Subject: [PATCH 38/42] Update go2rtc to 1.5.0 (#5814) * Update go2rtc to 1.3.0 * Increment to 1.3.1 * Increment to 1.3.2 * Update webrtc player to match latest * Update version to 1.4.0 * Update mse player * Update birdseye mse player * remove logs * Update docs to link to new version * Final web lint fixes * Update versions --- Dockerfile | 2 +- docs/docs/configuration/index.md | 2 +- docs/docs/configuration/live.md | 2 +- docs/docs/configuration/restream.md | 6 +- docs/docs/guides/configuring_go2rtc.md | 4 +- web/src/components/MsePlayer.js | 640 +++++++++++++++++++++++++ web/src/components/MsePlayer.jsx | 79 --- web/src/components/WebRtcPlayer.jsx | 121 +++-- web/src/routes/Birdseye.jsx | 14 +- web/src/routes/Camera.jsx | 8 +- 10 files changed, 735 insertions(+), 143 deletions(-) create mode 100644 web/src/components/MsePlayer.js delete mode 100644 web/src/components/MsePlayer.jsx diff --git a/Dockerfile b/Dockerfile index 4059a8059..bb010c780 100644 --- a/Dockerfile +++ b/Dockerfile @@ -27,7 +27,7 @@ RUN --mount=type=tmpfs,target=/tmp --mount=type=tmpfs,target=/var/cache/apt \ FROM wget AS go2rtc ARG TARGETARCH WORKDIR /rootfs/usr/local/go2rtc/bin -RUN wget -qO go2rtc "https://github.com/AlexxIT/go2rtc/releases/download/v1.2.0/go2rtc_linux_${TARGETARCH}" \ +RUN wget -qO go2rtc "https://github.com/AlexxIT/go2rtc/releases/download/v1.5.0/go2rtc_linux_${TARGETARCH}" \ && chmod +x go2rtc diff --git a/docs/docs/configuration/index.md b/docs/docs/configuration/index.md index 5a1284ebb..0a1b230aa 100644 --- a/docs/docs/configuration/index.md +++ b/docs/docs/configuration/index.md @@ -377,7 +377,7 @@ rtmp: enabled: False # Optional: Restream configuration -# Uses https://github.com/AlexxIT/go2rtc (v1.2.0) +# Uses https://github.com/AlexxIT/go2rtc (v1.5.0) go2rtc: # Optional: jsmpeg stream configuration for WebUI diff --git a/docs/docs/configuration/live.md b/docs/docs/configuration/live.md index b2eff6129..697b11347 100644 --- a/docs/docs/configuration/live.md +++ b/docs/docs/configuration/live.md @@ -115,4 +115,4 @@ services: ::: -See [go2rtc WebRTC docs](https://github.com/AlexxIT/go2rtc/tree/v1.2.0#module-webrtc) for more information about this. +See [go2rtc WebRTC docs](https://github.com/AlexxIT/go2rtc/tree/v1.5.0#module-webrtc) for more information about this. diff --git a/docs/docs/configuration/restream.md b/docs/docs/configuration/restream.md index e7db71634..2d5c565b2 100644 --- a/docs/docs/configuration/restream.md +++ b/docs/docs/configuration/restream.md @@ -7,7 +7,7 @@ title: Restream Frigate can restream your video feed as an RTSP feed for other applications such as Home Assistant to utilize it at `rtsp://:8554/`. Port 8554 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](#reduce-connections-to-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. -Frigate uses [go2rtc](https://github.com/AlexxIT/go2rtc/tree/v1.2.0) to provide its restream and MSE/WebRTC capabilities. The go2rtc config is hosted at the `go2rtc` in the config, see [go2rtc docs](https://github.com/AlexxIT/go2rtc/tree/v1.2.0#configuration) for more advanced configurations and features. +Frigate uses [go2rtc](https://github.com/AlexxIT/go2rtc/tree/v1.5.0) to provide its restream and MSE/WebRTC capabilities. The go2rtc config is hosted at the `go2rtc` in the config, see [go2rtc docs](https://github.com/AlexxIT/go2rtc/tree/v1.5.0#configuration) for more advanced configurations and features. :::note @@ -86,7 +86,7 @@ Two connections are made to the camera. One for the sub stream, one for the rest ```yaml go2rtc: streams: - rtsp_cam: + rtsp_cam: - rtsp://192.168.1.5:554/live0 # <- stream which supports video & aac audio. This is only supported for rtsp streams, http must use ffmpeg - "ffmpeg:rtsp_cam#audio=opus" # <- copy of the stream which transcodes audio to opus rtsp_cam_sub: @@ -130,7 +130,7 @@ cameras: ## Advanced Restream Configurations -The [exec](https://github.com/AlexxIT/go2rtc/tree/v1.2.0#source-exec) source in go2rtc can be used for custom ffmpeg commands. An example is below: +The [exec](https://github.com/AlexxIT/go2rtc/tree/v1.5.0#source-exec) source in go2rtc can be used for custom ffmpeg commands. An example is below: NOTE: The output will need to be passed with two curly braces `{{output}}` diff --git a/docs/docs/guides/configuring_go2rtc.md b/docs/docs/guides/configuring_go2rtc.md index cc3ff8681..55adc48c7 100644 --- a/docs/docs/guides/configuring_go2rtc.md +++ b/docs/docs/guides/configuring_go2rtc.md @@ -10,7 +10,7 @@ Use of the bundled go2rtc is optional. You can still configure FFmpeg to connect # Setup a go2rtc stream -First, you will want to configure go2rtc to connect to your camera stream by adding the stream you want to use for live view in your Frigate config file. If you set the stream name under go2rtc to match the name of your camera, it will automatically be mapped and you will get additional live view options for the camera. Avoid changing any other parts of your config at this step. Note that go2rtc supports [many different stream types](https://github.com/AlexxIT/go2rtc/tree/v1.2.0#module-streams), not just rtsp. +First, you will want to configure go2rtc to connect to your camera stream by adding the stream you want to use for live view in your Frigate config file. If you set the stream name under go2rtc to match the name of your camera, it will automatically be mapped and you will get additional live view options for the camera. Avoid changing any other parts of your config at this step. Note that go2rtc supports [many different stream types](https://github.com/AlexxIT/go2rtc/tree/v1.5.0#module-streams), not just rtsp. ```yaml go2rtc: @@ -23,7 +23,7 @@ The easiest live view to get working is MSE. After adding this to the config, re ### What if my video doesn't play? -If you are unable to see your video feed, first check the go2rtc logs in the Frigate UI under Logs in the sidebar. If go2rtc is having difficulty connecting to your camera, you should see some error messages in the log. If you do not see any errors, then the video codec of the stream may not be supported in your browser. If your camera stream is set to H265, try switching to H264. You can see more information about [video codec compatibility](https://github.com/AlexxIT/go2rtc/tree/v1.2.0#codecs-madness) in the go2rtc documentation. If you are not able to switch your camera settings from H265 to H264 or your stream is a different format such as MJPEG, you can use go2rtc to re-encode the video using the [FFmpeg parameters](https://github.com/AlexxIT/go2rtc/tree/v1.2.0#source-ffmpeg). It supports rotating and resizing video feeds and hardware acceleration. Keep in mind that transcoding video from one format to another is a resource intensive task and you may be better off using the built-in jsmpeg view. Here is an example of a config that will re-encode the stream to H264 without hardware acceleration: +If you are unable to see your video feed, first check the go2rtc logs in the Frigate UI under Logs in the sidebar. If go2rtc is having difficulty connecting to your camera, you should see some error messages in the log. If you do not see any errors, then the video codec of the stream may not be supported in your browser. If your camera stream is set to H265, try switching to H264. You can see more information about [video codec compatibility](https://github.com/AlexxIT/go2rtc/tree/v1.5.0#codecs-madness) in the go2rtc documentation. If you are not able to switch your camera settings from H265 to H264 or your stream is a different format such as MJPEG, you can use go2rtc to re-encode the video using the [FFmpeg parameters](https://github.com/AlexxIT/go2rtc/tree/v1.5.0#source-ffmpeg). It supports rotating and resizing video feeds and hardware acceleration. Keep in mind that transcoding video from one format to another is a resource intensive task and you may be better off using the built-in jsmpeg view. Here is an example of a config that will re-encode the stream to H264 without hardware acceleration: ```yaml go2rtc: diff --git a/web/src/components/MsePlayer.js b/web/src/components/MsePlayer.js new file mode 100644 index 000000000..a95e8de34 --- /dev/null +++ b/web/src/components/MsePlayer.js @@ -0,0 +1,640 @@ +class VideoRTC extends HTMLElement { + constructor() { + super(); + + this.DISCONNECT_TIMEOUT = 5000; + this.RECONNECT_TIMEOUT = 30000; + + this.CODECS = [ + 'avc1.640029', // H.264 high 4.1 (Chromecast 1st and 2nd Gen) + 'avc1.64002A', // H.264 high 4.2 (Chromecast 3rd Gen) + 'avc1.640033', // H.264 high 5.1 (Chromecast with Google TV) + 'hvc1.1.6.L153.B0', // H.265 main 5.1 (Chromecast Ultra) + 'mp4a.40.2', // AAC LC + 'mp4a.40.5', // AAC HE + 'flac', // FLAC (PCM compatible) + 'opus', // OPUS Chrome, Firefox + ]; + + /** + * [config] Supported modes (webrtc, mse, mp4, mjpeg). + * @type {string} + */ + this.mode = 'webrtc,mse,mp4,mjpeg'; + + /** + * [config] Run stream when not displayed on the screen. Default `false`. + * @type {boolean} + */ + this.background = false; + + /** + * [config] Run stream only when player in the viewport. Stop when user scroll out player. + * Value is percentage of visibility from `0` (not visible) to `1` (full visible). + * Default `0` - disable; + * @type {number} + */ + this.visibilityThreshold = 0; + + /** + * [config] Run stream only when browser page on the screen. Stop when user change browser + * tab or minimise browser windows. + * @type {boolean} + */ + this.visibilityCheck = true; + + /** + * [config] WebRTC configuration + * @type {RTCConfiguration} + */ + this.pcConfig = { + iceServers: [{ urls: 'stun:stun.l.google.com:19302' }], + sdpSemantics: 'unified-plan', // important for Chromecast 1 + }; + + /** + * [info] WebSocket connection state. Values: CONNECTING, OPEN, CLOSED + * @type {number} + */ + this.wsState = WebSocket.CLOSED; + + /** + * [info] WebRTC connection state. + * @type {number} + */ + this.pcState = WebSocket.CLOSED; + + /** + * @type {HTMLVideoElement} + */ + this.video = null; + + /** + * @type {WebSocket} + */ + this.ws = null; + + /** + * @type {string|URL} + */ + this.wsURL = ''; + + /** + * @type {RTCPeerConnection} + */ + this.pc = null; + + /** + * @type {number} + */ + this.connectTS = 0; + + /** + * @type {string} + */ + this.mseCodecs = ''; + + /** + * [internal] Disconnect TimeoutID. + * @type {number} + */ + this.disconnectTID = 0; + + /** + * [internal] Reconnect TimeoutID. + * @type {number} + */ + this.reconnectTID = 0; + + /** + * [internal] Handler for receiving Binary from WebSocket. + * @type {Function} + */ + this.ondata = null; + + /** + * [internal] Handlers list for receiving JSON from WebSocket + * @type {Object.}} + */ + this.onmessage = null; + } + + /** + * Set video source (WebSocket URL). Support relative path. + * @param {string|URL} value + */ + set src(value) { + if (typeof value !== 'string') value = value.toString(); + if (value.startsWith('http')) { + value = `ws${value.substring(4)}`; + } else if (value.startsWith('/')) { + value = `ws${location.origin.substring(4)}${value}`; + } + + this.wsURL = value; + + this.onconnect(); + } + + /** + * Play video. Support automute when autoplay blocked. + * https://developer.chrome.com/blog/autoplay/ + */ + play() { + this.video.play().catch((er) => { + if (er.name === 'NotAllowedError' && !this.video.muted) { + this.video.muted = true; + this.video.play().catch(() => { }); + } + }); + } + + /** + * Send message to server via WebSocket + * @param {Object} value + */ + send(value) { + if (this.ws) this.ws.send(JSON.stringify(value)); + } + + codecs(type) { + const test = + type === 'mse' + ? (codec) => MediaSource.isTypeSupported(`video/mp4; codecs="${codec}"`) + : (codec) => this.video.canPlayType(`video/mp4; codecs="${codec}"`); + return this.CODECS.filter(test).join(); + } + + /** + * `CustomElement`. Invoked each time the custom element is appended into a + * document-connected element. + */ + connectedCallback() { + if (this.disconnectTID) { + clearTimeout(this.disconnectTID); + this.disconnectTID = 0; + } + + // because video autopause on disconnected from DOM + if (this.video) { + const seek = this.video.seekable; + if (seek.length > 0) { + this.video.currentTime = seek.end(seek.length - 1); + } + this.play(); + } else { + this.oninit(); + } + + this.onconnect(); + } + + /** + * `CustomElement`. Invoked each time the custom element is disconnected from the + * document's DOM. + */ + disconnectedCallback() { + if (this.background || this.disconnectTID) return; + if (this.wsState === WebSocket.CLOSED && this.pcState === WebSocket.CLOSED) return; + + this.disconnectTID = setTimeout(() => { + if (this.reconnectTID) { + clearTimeout(this.reconnectTID); + this.reconnectTID = 0; + } + + this.disconnectTID = 0; + + this.ondisconnect(); + }, this.DISCONNECT_TIMEOUT); + } + + /** + * Creates child DOM elements. Called automatically once on `connectedCallback`. + */ + oninit() { + this.video = document.createElement('video'); + this.video.controls = true; + this.video.playsInline = true; + this.video.preload = 'auto'; + + this.video.style.display = 'block'; // fix bottom margin 4px + this.video.style.width = '100%'; + this.video.style.height = '100%'; + + this.appendChild(this.video); + + if (this.background) return; + + if ('hidden' in document && this.visibilityCheck) { + document.addEventListener('visibilitychange', () => { + if (document.hidden) { + this.disconnectedCallback(); + } else if (this.isConnected) { + this.connectedCallback(); + } + }); + } + + if ('IntersectionObserver' in window && this.visibilityThreshold) { + const observer = new IntersectionObserver( + (entries) => { + entries.forEach((entry) => { + if (!entry.isIntersecting) { + this.disconnectedCallback(); + } else if (this.isConnected) { + this.connectedCallback(); + } + }); + }, + { threshold: this.visibilityThreshold } + ); + observer.observe(this); + } + } + + /** + * Connect to WebSocket. Called automatically on `connectedCallback`. + * @return {boolean} true if the connection has started. + */ + onconnect() { + if (!this.isConnected || !this.wsURL || this.ws || this.pc) return false; + + // CLOSED or CONNECTING => CONNECTING + this.wsState = WebSocket.CONNECTING; + + this.connectTS = Date.now(); + + this.ws = new WebSocket(this.wsURL); + this.ws.binaryType = 'arraybuffer'; + this.ws.addEventListener('open', (ev) => this.onopen(ev)); + this.ws.addEventListener('close', (ev) => this.onclose(ev)); + + return true; + } + + ondisconnect() { + this.wsState = WebSocket.CLOSED; + if (this.ws) { + this.ws.close(); + this.ws = null; + } + + this.pcState = WebSocket.CLOSED; + if (this.pc) { + this.pc.close(); + this.pc = null; + } + } + + /** + * @returns {Array.} of modes (mse, webrtc, etc.) + */ + onopen() { + // CONNECTING => OPEN + this.wsState = WebSocket.OPEN; + + this.ws.addEventListener('message', (ev) => { + if (typeof ev.data === 'string') { + const msg = JSON.parse(ev.data); + for (const mode in this.onmessage) { + this.onmessage[mode](msg); + } + } else { + this.ondata(ev.data); + } + }); + + this.ondata = null; + this.onmessage = {}; + + const modes = []; + + if (this.mode.indexOf('mse') >= 0 && 'MediaSource' in window) { + // iPhone + modes.push('mse'); + this.onmse(); + } else if (this.mode.indexOf('mp4') >= 0) { + modes.push('mp4'); + this.onmp4(); + } + + if (this.mode.indexOf('webrtc') >= 0 && 'RTCPeerConnection' in window) { + // macOS Desktop app + modes.push('webrtc'); + this.onwebrtc(); + } + + if (this.mode.indexOf('mjpeg') >= 0) { + if (modes.length) { + this.onmessage['mjpeg'] = (msg) => { + if (msg.type !== 'error' || msg.value.indexOf(modes[0]) !== 0) return; + this.onmjpeg(); + }; + } else { + modes.push('mjpeg'); + this.onmjpeg(); + } + } + + return modes; + } + + /** + * @return {boolean} true if reconnection has started. + */ + onclose() { + if (this.wsState === WebSocket.CLOSED) return false; + + // CONNECTING, OPEN => CONNECTING + this.wsState = WebSocket.CONNECTING; + this.ws = null; + + // reconnect no more than once every X seconds + const delay = Math.max(this.RECONNECT_TIMEOUT - (Date.now() - this.connectTS), 0); + + this.reconnectTID = setTimeout(() => { + this.reconnectTID = 0; + this.onconnect(); + }, delay); + + return true; + } + + onmse() { + const ms = new MediaSource(); + ms.addEventListener( + 'sourceopen', + () => { + URL.revokeObjectURL(this.video.src); + this.send({ type: 'mse', value: this.codecs('mse') }); + }, + { once: true } + ); + + this.video.src = URL.createObjectURL(ms); + this.video.srcObject = null; + this.play(); + + this.mseCodecs = ''; + + this.onmessage['mse'] = (msg) => { + if (msg.type !== 'mse') return; + + this.mseCodecs = msg.value; + + const sb = ms.addSourceBuffer(msg.value); + sb.mode = 'segments'; // segments or sequence + sb.addEventListener('updateend', () => { + if (sb.updating) return; + + try { + if (bufLen > 0) { + const data = buf.slice(0, bufLen); + bufLen = 0; + sb.appendBuffer(data); + } else if (sb.buffered && sb.buffered.length) { + const end = sb.buffered.end(sb.buffered.length - 1) - 15; + const start = sb.buffered.start(0); + if (end > start) { + sb.remove(start, end); + ms.setLiveSeekableRange(end, end + 15); + } + // console.debug("VideoRTC.buffered", start, end); + } + } catch (e) { + // console.debug(e); + } + }); + + const buf = new Uint8Array(2 * 1024 * 1024); + let bufLen = 0; + + this.ondata = (data) => { + if (sb.updating || bufLen > 0) { + const b = new Uint8Array(data); + buf.set(b, bufLen); + bufLen += b.byteLength; + // console.debug("VideoRTC.buffer", b.byteLength, bufLen); + } else { + try { + sb.appendBuffer(data); + } catch (e) { + // console.debug(e); + } + } + }; + }; + } + + onwebrtc() { + const pc = new RTCPeerConnection(this.pcConfig); + + /** @type {HTMLVideoElement} */ + const video2 = document.createElement('video'); + video2.addEventListener('loadeddata', (ev) => this.onpcvideo(ev), { once: true }); + + pc.addEventListener('icecandidate', (ev) => { + const candidate = ev.candidate ? ev.candidate.toJSON().candidate : ''; + this.send({ type: 'webrtc/candidate', value: candidate }); + }); + + pc.addEventListener('track', (ev) => { + // when stream already init + if (video2.srcObject !== null) return; + + // when audio track not exist in Chrome + if (ev.streams.length === 0) return; + + // when audio track not exist in Firefox + if (ev.streams[0].id[0] === '{') return; + + video2.srcObject = ev.streams[0]; + }); + + pc.addEventListener('connectionstatechange', () => { + if (pc.connectionState === 'failed' || pc.connectionState === 'disconnected') { + pc.close(); // stop next events + + this.pcState = WebSocket.CLOSED; + this.pc = null; + + this.onconnect(); + } + }); + + this.onmessage['webrtc'] = (msg) => { + switch (msg.type) { + case 'webrtc/candidate': + pc.addIceCandidate({ + candidate: msg.value, + sdpMid: '0', + }).catch(() => { }); + break; + case 'webrtc/answer': + pc.setRemoteDescription({ + type: 'answer', + sdp: msg.value, + }).catch(() => { }); + break; + case 'error': + if (msg.value.indexOf('webrtc/offer') < 0) return; + pc.close(); + } + }; + + // Safari doesn't support "offerToReceiveVideo" + pc.addTransceiver('video', { direction: 'recvonly' }); + pc.addTransceiver('audio', { direction: 'recvonly' }); + + pc.createOffer().then((offer) => { + pc.setLocalDescription(offer).then(() => { + this.send({ type: 'webrtc/offer', value: offer.sdp }); + }); + }); + + this.pcState = WebSocket.CONNECTING; + this.pc = pc; + } + + /** + * @param ev {Event} + */ + onpcvideo(ev) { + if (!this.pc) return; + + /** @type {HTMLVideoElement} */ + const video2 = ev.target; + const state = this.pc.connectionState; + + // Firefox doesn't support pc.connectionState + if (state === 'connected' || state === 'connecting' || !state) { + // Video+Audio > Video, H265 > H264, Video > Audio, WebRTC > MSE + let rtcPriority = 0, + msePriority = 0; + + /** @type {MediaStream} */ + const ms = video2.srcObject; + if (ms.getVideoTracks().length > 0) rtcPriority += 0x220; + if (ms.getAudioTracks().length > 0) rtcPriority += 0x102; + + if (this.mseCodecs.indexOf('hvc1.') >= 0) msePriority += 0x230; + if (this.mseCodecs.indexOf('avc1.') >= 0) msePriority += 0x210; + if (this.mseCodecs.indexOf('mp4a.') >= 0) msePriority += 0x101; + + if (rtcPriority >= msePriority) { + this.video.srcObject = ms; + this.play(); + + this.pcState = WebSocket.OPEN; + + this.wsState = WebSocket.CLOSED; + this.ws.close(); + this.ws = null; + } else { + this.pcState = WebSocket.CLOSED; + this.pc.close(); + this.pc = null; + } + } + + video2.srcObject = null; + } + + onmjpeg() { + this.ondata = (data) => { + this.video.controls = false; + this.video.poster = `data:image/jpeg;base64,${VideoRTC.btoa(data)}`; + }; + + this.send({ type: 'mjpeg' }); + } + + onmp4() { + /** @type {HTMLCanvasElement} **/ + const canvas = document.createElement('canvas'); + /** @type {CanvasRenderingContext2D} */ + let context; + + /** @type {HTMLVideoElement} */ + const video2 = document.createElement('video'); + video2.autoplay = true; + video2.playsInline = true; + video2.muted = true; + + video2.addEventListener('loadeddata', (_) => { + if (!context) { + canvas.width = video2.videoWidth; + canvas.height = video2.videoHeight; + context = canvas.getContext('2d'); + } + + context.drawImage(video2, 0, 0, canvas.width, canvas.height); + + this.video.controls = false; + this.video.poster = canvas.toDataURL('image/jpeg'); + }); + + this.ondata = (data) => { + video2.src = `data:video/mp4;base64,${VideoRTC.btoa(data)}`; + }; + + this.send({ type: 'mp4', value: this.codecs('mp4') }); + } + + static btoa(buffer) { + const bytes = new Uint8Array(buffer); + const len = bytes.byteLength; + let binary = ''; + for (let i = 0; i < len; i++) { + binary += String.fromCharCode(bytes[i]); + } + return window.btoa(binary); + } +} + +class VideoStream extends VideoRTC { + + + /** + * Custom GUI + */ + oninit() { + super.oninit(); + const info = this.querySelector('.info'); + this.insertBefore(this.video, info); + } + + onconnect() { + const result = super.onconnect(); + if (result) this.divMode = 'loading'; + return result; + } + + ondisconnect() {; + super.ondisconnect(); + } + + onopen() { + const result = super.onopen(); + + this.onmessage['stream'] = (_) => { + }; + + return result; + } + + onclose() { + return super.onclose(); + } + + onpcvideo(ev) { + super.onpcvideo(ev); + + if (this.pcState !== WebSocket.CLOSED) { + this.divMode = 'RTC'; + } + } +} + +customElements.define('video-stream', VideoStream); diff --git a/web/src/components/MsePlayer.jsx b/web/src/components/MsePlayer.jsx deleted file mode 100644 index 27e8d7047..000000000 --- a/web/src/components/MsePlayer.jsx +++ /dev/null @@ -1,79 +0,0 @@ -import { h } from 'preact'; -import { baseUrl } from '../api/baseUrl'; -import { useEffect } from 'preact/hooks'; - -export default function MsePlayer({ camera, width, height }) { - const url = `${baseUrl.replace(/^http/, 'ws')}live/mse/api/ws?src=${camera}`; - - useEffect(() => { - const video = document.querySelector('#video'); - - // support api_path - const ws = new WebSocket(url); - ws.binaryType = 'arraybuffer'; - let mediaSource, - sourceBuffer, - queueBuffer = []; - - ws.onopen = () => { - mediaSource = new MediaSource(); - video.src = URL.createObjectURL(mediaSource); - mediaSource.onsourceopen = () => { - mediaSource.onsourceopen = null; - URL.revokeObjectURL(video.src); - ws.send(JSON.stringify({ type: 'mse' })); - }; - }; - - ws.onmessage = (ev) => { - if (typeof ev.data === 'string') { - const data = JSON.parse(ev.data); - - if (data.type === 'mse') { - sourceBuffer = mediaSource.addSourceBuffer(data.value); - sourceBuffer.mode = 'segments'; // segments or sequence - sourceBuffer.onupdateend = () => { - if (!sourceBuffer.updating && queueBuffer.length > 0) { - try { - sourceBuffer.appendBuffer(queueBuffer.shift()); - } catch (e) { - // console.warn(e); - } - } - }; - } - } else if (sourceBuffer.updating || queueBuffer.length > 0) { - queueBuffer.push(ev.data); - } else { - try { - sourceBuffer.appendBuffer(ev.data); - } catch (e) { - // console.warn(e); - } - } - - if (video.seekable.length > 0) { - const delay = video.seekable.end(video.seekable.length - 1) - video.currentTime; - if (delay < 1) { - video.playbackRate = 1; - } else if (delay > 10) { - video.playbackRate = 10; - } else if (delay > 2) { - video.playbackRate = Math.floor(delay); - } - } - }; - - return () => { - const video = document.getElementById('video'); - video.srcObject = null; - ws.close(); - }; - }, [url]); - - return ( -
-
- ); -} diff --git a/web/src/components/WebRtcPlayer.jsx b/web/src/components/WebRtcPlayer.jsx index c6c1b750f..a454c67d3 100644 --- a/web/src/components/WebRtcPlayer.jsx +++ b/web/src/components/WebRtcPlayer.jsx @@ -1,68 +1,95 @@ import { h } from 'preact'; import { baseUrl } from '../api/baseUrl'; -import { useEffect } from 'preact/hooks'; +import { useCallback, useEffect } from 'preact/hooks'; export default function WebRtcPlayer({ camera, width, height }) { const url = `${baseUrl.replace(/^http/, 'ws')}live/webrtc/api/ws?src=${camera}`; - useEffect(() => { + const PeerConnection = useCallback(async (media) => { + const pc = new RTCPeerConnection({ + iceServers: [{ urls: 'stun:stun.l.google.com:19302' }], + }); + + const localTracks = []; + + if (/camera|microphone/.test(media)) { + const tracks = await getMediaTracks('user', { + video: media.indexOf('camera') >= 0, + audio: media.indexOf('microphone') >= 0, + }); + tracks.forEach((track) => { + pc.addTransceiver(track, { direction: 'sendonly' }); + if (track.kind === 'video') localTracks.push(track); + }); + } + + if (media.indexOf('display') >= 0) { + const tracks = await getMediaTracks('display', { + video: true, + audio: media.indexOf('speaker') >= 0, + }); + tracks.forEach((track) => { + pc.addTransceiver(track, { direction: 'sendonly' }); + if (track.kind === 'video') localTracks.push(track); + }); + } + + if (/video|audio/.test(media)) { + const tracks = ['video', 'audio'] + .filter((kind) => media.indexOf(kind) >= 0) + .map((kind) => pc.addTransceiver(kind, { direction: 'recvonly' }).receiver.track); + localTracks.push(...tracks); + } + + document.getElementById('video').srcObject = new MediaStream(localTracks); + + return pc; + }, []); + + async function getMediaTracks(media, constraints) { + try { + const stream = + media === 'user' + ? await navigator.mediaDevices.getUserMedia(constraints) + : await navigator.mediaDevices.getDisplayMedia(constraints); + return stream.getTracks(); + } catch (e) { + return []; + } + } + + const connect = useCallback(async () => { + const pc = await PeerConnection('video+audio'); const ws = new WebSocket(url); - ws.onopen = () => { - pc.createOffer().then((offer) => { - pc.setLocalDescription(offer).then(() => { + + ws.addEventListener('open', () => { + pc.addEventListener('icecandidate', (ev) => { + if (!ev.candidate) return; + const msg = { type: 'webrtc/candidate', value: ev.candidate.candidate }; + ws.send(JSON.stringify(msg)); + }); + + pc.createOffer() + .then((offer) => pc.setLocalDescription(offer)) + .then(() => { const msg = { type: 'webrtc/offer', value: pc.localDescription.sdp }; ws.send(JSON.stringify(msg)); }); - }); - }; - ws.onmessage = (ev) => { - const msg = JSON.parse(ev.data); + }); + ws.addEventListener('message', (ev) => { + const msg = JSON.parse(ev.data); if (msg.type === 'webrtc/candidate') { pc.addIceCandidate({ candidate: msg.value, sdpMid: '0' }); } else if (msg.type === 'webrtc/answer') { pc.setRemoteDescription({ type: 'answer', sdp: msg.value }); } - }; - - const pc = new RTCPeerConnection({ - iceServers: [{ urls: 'stun:stun.l.google.com:19302' }], }); - pc.onicecandidate = (ev) => { - if (ev.candidate !== null) { - ws.send( - JSON.stringify({ - type: 'webrtc/candidate', - value: ev.candidate.toJSON().candidate, - }) - ); - } - }; - pc.ontrack = (ev) => { - const video = document.getElementById('video'); + }, [PeerConnection, url]); - // when audio track not exist in Chrome - if (ev.streams.length === 0) return; - // when audio track not exist in Firefox - if (ev.streams[0].id[0] === '{') return; - // when stream already init - if (video.srcObject !== null) return; - - video.srcObject = ev.streams[0]; - }; - - // Safari don't support "offerToReceiveVideo" - // so need to create transeivers manually - pc.addTransceiver('video', { direction: 'recvonly' }); - pc.addTransceiver('audio', { direction: 'recvonly' }); - - return () => { - const video = document.getElementById('video'); - video.srcObject = null; - pc.close(); - ws.close(); - }; - }, [url]); + useEffect(() => { + connect(); + }, [connect]); return (
diff --git a/web/src/routes/Birdseye.jsx b/web/src/routes/Birdseye.jsx index c2742e14b..4ed800437 100644 --- a/web/src/routes/Birdseye.jsx +++ b/web/src/routes/Birdseye.jsx @@ -4,18 +4,16 @@ import ActivityIndicator from '../components/ActivityIndicator'; import JSMpegPlayer from '../components/JSMpegPlayer'; import Heading from '../components/Heading'; import WebRtcPlayer from '../components/WebRtcPlayer'; -import MsePlayer from '../components/MsePlayer'; +import '../components/MsePlayer'; import useSWR from 'swr'; import { useMemo } from 'preact/hooks'; import CameraControlPanel from '../components/CameraControlPanel'; +import { baseUrl } from '../api/baseUrl'; export default function Birdseye() { const { data: config } = useSWR('config'); - const [viewSource, setViewSource, sourceIsLoaded] = usePersistence( - 'birdseye-source', - getDefaultLiveMode(config) - ); + const [viewSource, setViewSource, sourceIsLoaded] = usePersistence('birdseye-source', getDefaultLiveMode(config)); const sourceValues = ['mse', 'webrtc', 'jsmpeg']; const ptzCameras = useMemo(() => { @@ -38,7 +36,10 @@ export default function Birdseye() { player = (
- +
); @@ -110,7 +111,6 @@ export default function Birdseye() { ); } - function getDefaultLiveMode(config) { if (config) { if (config.birdseye.restream) { diff --git a/web/src/routes/Camera.jsx b/web/src/routes/Camera.jsx index 4a415e32d..f8652ba05 100644 --- a/web/src/routes/Camera.jsx +++ b/web/src/routes/Camera.jsx @@ -14,8 +14,9 @@ import { useCallback, useMemo, useState } from 'preact/hooks'; import { useApiHost } from '../api'; import useSWR from 'swr'; import WebRtcPlayer from '../components/WebRtcPlayer'; -import MsePlayer from '../components/MsePlayer'; +import '../components/MsePlayer'; import CameraControlPanel from '../components/CameraControlPanel'; +import { baseUrl } from '../api/baseUrl'; const emptyObject = Object.freeze({}); @@ -118,7 +119,10 @@ export default function Camera({ camera }) { player = (
- +
); From 11738110dc2c3eee2302449ff52ed9ff378c1d32 Mon Sep 17 00:00:00 2001 From: Sergey Krashevich Date: Sun, 21 May 2023 16:29:57 +0300 Subject: [PATCH 39/42] Add handling for non-existent or empty model file paths (#6525) * Add handling for non-existent or empty model file paths in `compute_model_hash()` method * Remove print() in detector_config.py Co-authored-by: Blake Blackshear --------- Co-authored-by: Blake Blackshear --- frigate/detectors/detector_config.py | 13 ++++++++----- 1 file changed, 8 insertions(+), 5 deletions(-) diff --git a/frigate/detectors/detector_config.py b/frigate/detectors/detector_config.py index 16ffe5fd4..e949065b4 100644 --- a/frigate/detectors/detector_config.py +++ b/frigate/detectors/detector_config.py @@ -118,11 +118,14 @@ class ModelConfig(BaseModel): } def compute_model_hash(self) -> None: - with open(self.path, "rb") as f: - file_hash = hashlib.md5() - while chunk := f.read(8192): - file_hash.update(chunk) - self._model_hash = file_hash.hexdigest() + if not self.path or not os.path.exists(self.path): + self._model_hash = hashlib.md5(b"unknown").hexdigest() + else: + with open(self.path, "rb") as f: + file_hash = hashlib.md5() + while chunk := f.read(8192): + file_hash.update(chunk) + self._model_hash = file_hash.hexdigest() def create_colormap(self, enabled_labels: set[str]) -> None: """Get a list of colors for enabled labels.""" From 846a180a7b7f8f2936a2f252028370b03217f5b9 Mon Sep 17 00:00:00 2001 From: Sergey Krashevich Date: Sun, 21 May 2023 20:12:31 +0300 Subject: [PATCH 40/42] go2rtc 1.5.0 forgotten changes (#6561) --- docs/docs/configuration/advanced.md | 2 +- docs/docs/configuration/camera_specific.md | 2 +- 2 files changed, 2 insertions(+), 2 deletions(-) diff --git a/docs/docs/configuration/advanced.md b/docs/docs/configuration/advanced.md index ab15d06c1..cbec076d4 100644 --- a/docs/docs/configuration/advanced.md +++ b/docs/docs/configuration/advanced.md @@ -110,7 +110,7 @@ NOTE: The folder that is mapped from the host needs to be the folder that contai ## Custom go2rtc version -Frigate currently includes go2rtc v1.2.0, there may be certain cases where you want to run a different version of go2rtc. +Frigate currently includes go2rtc v1.5.0, there may be certain cases where you want to run a different version of go2rtc. To do this: diff --git a/docs/docs/configuration/camera_specific.md b/docs/docs/configuration/camera_specific.md index 8de3837f5..eb704358f 100644 --- a/docs/docs/configuration/camera_specific.md +++ b/docs/docs/configuration/camera_specific.md @@ -141,7 +141,7 @@ go2rtc: - rtspx://192.168.1.1:7441/abcdefghijk ``` -[See the go2rtc docs for more information](https://github.com/AlexxIT/go2rtc/tree/v1.2.0#source-rtsp) +[See the go2rtc docs for more information](https://github.com/AlexxIT/go2rtc/tree/v1.5.0#source-rtsp) 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 if used directly with unifi protect. From f66ccb04f6416c9cd59d1287e179efe30cde0bb5 Mon Sep 17 00:00:00 2001 From: Nicolas Mowen Date: Tue, 23 May 2023 08:00:21 -0600 Subject: [PATCH 41/42] Fix pydantic (#6584) --- frigate/detectors/plugins/cpu_tfl.py | 2 +- frigate/detectors/plugins/deepstack.py | 2 +- frigate/detectors/plugins/edgetpu_tfl.py | 2 +- frigate/detectors/plugins/openvino.py | 2 +- frigate/detectors/plugins/tensorrt.py | 2 +- 5 files changed, 5 insertions(+), 5 deletions(-) diff --git a/frigate/detectors/plugins/cpu_tfl.py b/frigate/detectors/plugins/cpu_tfl.py index b22ac9a54..a831a9190 100644 --- a/frigate/detectors/plugins/cpu_tfl.py +++ b/frigate/detectors/plugins/cpu_tfl.py @@ -3,7 +3,7 @@ import numpy as np from frigate.detectors.detection_api import DetectionApi from frigate.detectors.detector_config import BaseDetectorConfig -from typing import Literal +from typing_extensions import Literal from pydantic import Extra, Field try: diff --git a/frigate/detectors/plugins/deepstack.py b/frigate/detectors/plugins/deepstack.py index 9f3d323a2..2a9024a49 100644 --- a/frigate/detectors/plugins/deepstack.py +++ b/frigate/detectors/plugins/deepstack.py @@ -5,7 +5,7 @@ import io from frigate.detectors.detection_api import DetectionApi from frigate.detectors.detector_config import BaseDetectorConfig -from typing import Literal +from typing_extensions import Literal from pydantic import Extra, Field from PIL import Image diff --git a/frigate/detectors/plugins/edgetpu_tfl.py b/frigate/detectors/plugins/edgetpu_tfl.py index 6837adcb0..d671dea4d 100644 --- a/frigate/detectors/plugins/edgetpu_tfl.py +++ b/frigate/detectors/plugins/edgetpu_tfl.py @@ -3,7 +3,7 @@ import numpy as np from frigate.detectors.detection_api import DetectionApi from frigate.detectors.detector_config import BaseDetectorConfig -from typing import Literal +from typing_extensions import Literal from pydantic import Extra, Field try: diff --git a/frigate/detectors/plugins/openvino.py b/frigate/detectors/plugins/openvino.py index 993b2b8ff..fe87ac567 100644 --- a/frigate/detectors/plugins/openvino.py +++ b/frigate/detectors/plugins/openvino.py @@ -4,7 +4,7 @@ import openvino.runtime as ov from frigate.detectors.detection_api import DetectionApi from frigate.detectors.detector_config import BaseDetectorConfig, ModelTypeEnum -from typing import Literal +from typing_extensions import Literal from pydantic import Extra, Field diff --git a/frigate/detectors/plugins/tensorrt.py b/frigate/detectors/plugins/tensorrt.py index 2d2a6788f..ac0c4befc 100644 --- a/frigate/detectors/plugins/tensorrt.py +++ b/frigate/detectors/plugins/tensorrt.py @@ -13,7 +13,7 @@ except ModuleNotFoundError as e: from frigate.detectors.detection_api import DetectionApi from frigate.detectors.detector_config import BaseDetectorConfig -from typing import Literal +from typing_extensions import Literal from pydantic import Field logger = logging.getLogger(__name__) From 1e17dbaa91d1987bbfb8bf28e7fade90b7e6b68e Mon Sep 17 00:00:00 2001 From: Martin Weinelt Date: Wed, 24 May 2023 00:32:08 +0200 Subject: [PATCH 42/42] deepstack: pass api key in request (#6579) The POST data was prepared, but not passed into the request call. --- frigate/detectors/plugins/deepstack.py | 5 ++++- 1 file changed, 4 insertions(+), 1 deletion(-) diff --git a/frigate/detectors/plugins/deepstack.py b/frigate/detectors/plugins/deepstack.py index 2a9024a49..639cd396c 100644 --- a/frigate/detectors/plugins/deepstack.py +++ b/frigate/detectors/plugins/deepstack.py @@ -50,7 +50,10 @@ class DeepStack(DetectionApi): image_bytes = output.getvalue() data = {"api_key": self.api_key} response = requests.post( - self.api_url, files={"image": image_bytes}, timeout=self.api_timeout + self.api_url, + data=data, + files={"image": image_bytes}, + timeout=self.api_timeout, ) response_json = response.json() detections = np.zeros((20, 6), np.float32)