From 2cdd4831260c30b6c2512528342aa4cdd2be516d Mon Sep 17 00:00:00 2001 From: jameson_uk <1040621+jamesonuk@users.noreply.github.com> Date: Thu, 18 Jul 2024 23:30:24 +0100 Subject: [PATCH 01/81] Update automation (#12487) `data_template` has been deprecated for sometime in HA an no longer works. This should just be `data` --- 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 cf3e03349..8ed7a6e39 100644 --- a/docs/docs/guides/ha_notifications.md +++ b/docs/docs/guides/ha_notifications.md @@ -17,7 +17,7 @@ automation: topic: frigate/events action: - service: notify.mobile_app_pixel_3 - data_template: + data: message: 'A {{trigger.payload_json["after"]["label"]}} was detected.' data: image: 'https://your.public.hass.address.com/api/frigate/notifications/{{trigger.payload_json["after"]["id"]}}/thumbnail.jpg?format=android' From 9b1fb33ac6de259c819fa4e072ffdae8cd38461a Mon Sep 17 00:00:00 2001 From: Josh Hawkins <32435876+hawkeye217@users.noreply.github.com> Date: Fri, 9 Aug 2024 09:41:12 -0500 Subject: [PATCH 02/81] Fix camera group icon name in reference config (#12883) --- docs/docs/configuration/reference.md | 6 +++--- 1 file changed, 3 insertions(+), 3 deletions(-) diff --git a/docs/docs/configuration/reference.md b/docs/docs/configuration/reference.md index a90a3241e..2cbbc4903 100644 --- a/docs/docs/configuration/reference.md +++ b/docs/docs/configuration/reference.md @@ -613,8 +613,8 @@ cameras: user: admin # Optional: password for login. password: admin - # Optional: Ignores time synchronization mismatches between the camera and the server during authentication. - # Using NTP on both ends is recommended and this should only be set to True in a "safe" environment due to the security risk it represents. + # Optional: Ignores time synchronization mismatches between the camera and the server during authentication. + # Using NTP on both ends is recommended and this should only be set to True in a "safe" environment due to the security risk it represents. ignore_time_mismatch: False # Optional: PTZ camera object autotracking. Keeps a moving object in # the center of the frame by automatically moving the PTZ camera. @@ -719,7 +719,7 @@ camera_groups: - side_cam - front_doorbell_cam # Required: icon used for group - icon: car + icon: LuCar # Required: index of this group order: 0 ``` From e891f2ad6d40ef6a326e65b25456a02050bc76ad Mon Sep 17 00:00:00 2001 From: axyzs Date: Sun, 11 Aug 2024 23:21:39 +1000 Subject: [PATCH 03/81] correct github colab url (#12948) --- docs/docs/configuration/object_detectors.md | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/docs/docs/configuration/object_detectors.md b/docs/docs/configuration/object_detectors.md index 8e5dab36b..36f063fae 100644 --- a/docs/docs/configuration/object_detectors.md +++ b/docs/docs/configuration/object_detectors.md @@ -149,7 +149,7 @@ This detector also supports YOLOX. Frigate does not come with any YOLOX models p #### YOLO-NAS -[YOLO-NAS](https://github.com/Deci-AI/super-gradients/blob/master/YOLONAS.md) models are supported, but not included by default. You can build and download a compatible model with pre-trained weights using [this notebook](https://github.com/frigate/blob/dev/notebooks/YOLO_NAS_Pretrained_Export.ipynb) [![Open In Colab](https://colab.research.google.com/assets/colab-badge.svg)](https://colab.research.google.com/github/blakeblackshear/frigate/blob/dev/notebooks/YOLO_NAS_Pretrained_Export.ipynb). +[YOLO-NAS](https://github.com/Deci-AI/super-gradients/blob/master/YOLONAS.md) models are supported, but not included by default. You can build and download a compatible model with pre-trained weights using [this notebook](https://github.com/blakeblackshear/frigate/blob/dev/notebooks/YOLO_NAS_Pretrained_Export.ipynb) [![Open In Colab](https://colab.research.google.com/assets/colab-badge.svg)](https://colab.research.google.com/github/blakeblackshear/frigate/blob/dev/notebooks/YOLO_NAS_Pretrained_Export.ipynb). :::warning From b309287087e194403c096b345dc91ab6991cf726 Mon Sep 17 00:00:00 2001 From: Nicolas Mowen Date: Mon, 12 Aug 2024 07:21:02 -0600 Subject: [PATCH 04/81] Add docs for installing Frigate as PWA (#12995) * Add docs for installing Frigate as PWA * Add to sidebar --- docs/docs/configuration/pwa.md | 24 ++++++++++++++++++++++++ docs/sidebars.js | 1 + 2 files changed, 25 insertions(+) create mode 100644 docs/docs/configuration/pwa.md diff --git a/docs/docs/configuration/pwa.md b/docs/docs/configuration/pwa.md new file mode 100644 index 000000000..abe8d6934 --- /dev/null +++ b/docs/docs/configuration/pwa.md @@ -0,0 +1,24 @@ +--- +id: pwa +title: Installing Frigate App +--- + +Frigate supports being installed as a [Progressive Web App](https://web.dev/explore/progressive-web-apps) on Desktop, Android, and iOS. + +This adds features including the ability to deep link directly into the app. + +## Requirements + +In order to install Frigate as a PWA, the following requirements must be met: + +- Frigate must be accessed via a secure context (localhost, secure https, etc.) +- On Android, Firefox, Chrome, Edge, Opera, and Samsung Internet Browser all support installing PWAs. +- On iOS 16.4 and later, PWAs can be installed from the Share menu in Safari, Chrome, Edge, Firefox, and Orion. + +## Installation + +Installation varies slightly based on the device that is being used: + +- Desktop: Use the install button typically found in right edge of the address bar +- Android: Use the `Install as App` button in the more options menu +- iOS: Use the `Add to Homescreen` button in the share menu \ No newline at end of file diff --git a/docs/sidebars.js b/docs/sidebars.js index da41564ca..cd3306c2d 100644 --- a/docs/sidebars.js +++ b/docs/sidebars.js @@ -52,6 +52,7 @@ module.exports = { "configuration/authentication", "configuration/hardware_acceleration", "configuration/ffmpeg_presets", + "configuration/pwa", "configuration/tls", "configuration/advanced", ], From 0504e9ef7969ac5b7e52a2bf07c37e39217929a2 Mon Sep 17 00:00:00 2001 From: Emil Sandnabba Date: Wed, 14 Aug 2024 23:51:15 +0200 Subject: [PATCH 05/81] Update reverse proxy documentation (#13075) * Cleanup the reverse proxy overview * Adding a Traefik example * Adding a note about TLS * Update docs/docs/guides/reverse_proxy.md Co-authored-by: Nicolas Mowen * Update docs/docs/guides/reverse_proxy.md Co-authored-by: Nicolas Mowen * Update docs/docs/guides/reverse_proxy.md Co-authored-by: Josh Hawkins <32435876+hawkeye217@users.noreply.github.com> * Update docs/docs/guides/reverse_proxy.md Co-authored-by: Josh Hawkins <32435876+hawkeye217@users.noreply.github.com> --------- Co-authored-by: Nicolas Mowen Co-authored-by: Josh Hawkins <32435876+hawkeye217@users.noreply.github.com> --- docs/docs/guides/reverse_proxy.md | 60 ++++++++++++++++++++++++------- 1 file changed, 48 insertions(+), 12 deletions(-) diff --git a/docs/docs/guides/reverse_proxy.md b/docs/docs/guides/reverse_proxy.md index 012d6b228..d408a1444 100644 --- a/docs/docs/guides/reverse_proxy.md +++ b/docs/docs/guides/reverse_proxy.md @@ -3,25 +3,38 @@ id: reverse_proxy title: Setting up a reverse proxy --- -This guide outlines the basic configuration steps needed to expose your Frigate UI to the internet. -A common way of accomplishing this is to use a reverse proxy webserver between your router and your Frigate instance. -A reverse proxy accepts HTTP requests from the public internet and redirects them transparently to internal webserver(s) on your network. +This guide outlines the basic configuration steps needed to set up a reverse proxy in front of your Frigate instance. -The suggested steps are: +A reverse proxy is typically needed if you want to set up Frigate on a custom URL, on a subdomain, or on a host serving multiple sites. It could also be used to set up your own authentication provider or for more advanced HTTP routing. -- **Configure** a 'proxy' HTTP webserver (such as [Apache2](https://httpd.apache.org/docs/current/) or [NPM](https://github.com/NginxProxyManager/nginx-proxy-manager)) and only expose ports 80/443 from this webserver to the internet -- **Encrypt** content from the proxy webserver by installing SSL (such as with [Let's Encrypt](https://letsencrypt.org/)). Note that SSL is then not required on your Frigate webserver as the proxy encrypts all requests for you -- **Restrict** access to your Frigate instance at the proxy using, for example, password authentication +Before setting up a reverse proxy, check if any of the built-in functionality in Frigate suits your needs: +|Topic|Docs| +|-|-| +|TLS|Please see the `tls` [configuration option](../configuration/tls.md)| +|Authentication|Please see the [authentication](../configuration/authentication.md) documentation| +|IPv6|[Enabling IPv6](../configuration/advanced.md#enabling-ipv6) + +**Note about TLS** +When using a reverse proxy, the TLS session is usually terminated at the proxy, sending the internal request over plain HTTP. If this is the desired behavior, TLS must first be disabled in Frigate, or you will encounter an HTTP 400 error: "The plain HTTP request was sent to HTTPS port." +To disable TLS, set the following in your Frigate configuration: +```yml +tls: + enabled: false +``` :::warning -A reverse proxy can be used to secure access to an internal webserver but the user will be entirely reliant -on the steps they have taken. You must ensure you are following security best practices. -This page does not attempt to outline the specific steps needed to secure your internal website. +A reverse proxy can be used to secure access to an internal web server, but the user will be entirely reliant on the steps they have taken. You must ensure you are following security best practices. +This page does not attempt to outline the specific steps needed to secure your internal website. Please use your own knowledge to assess and vet the reverse proxy software before you install anything on your system. ::: -There are several technologies available to implement reverse proxies. This document currently suggests one, using Apache2, -and the community is invited to document others through a contribution to this page. +## Proxies + +There are many solutions available to implement reverse proxies and the community is invited to help out documenting others through a contribution to this page. + +* [Apache2](#apache2-reverse-proxy) +* [Nginx](#nginx-reverse-proxy) +* [Traefik](#traefik-reverse-proxy) ## Apache2 Reverse Proxy @@ -141,3 +154,26 @@ The settings below enabled connection upgrade, sets up logging (optional) and pr } ``` + +## Traefik Reverse Proxy + +This example shows how to add a `label` to the Frigate Docker compose file, enabling Traefik to automatically discover your Frigate instance. +Before using the example below, you must first set up Traefik with the [Docker provider](https://doc.traefik.io/traefik/providers/docker/) + +```yml +services: + frigate: + container_name: frigate + image: ghcr.io/blakeblackshear/frigate:stable + ... + ... + labels: + - "traefik.enable=true" + - "traefik.http.services.frigate.loadbalancer.server.port=8971" + - "traefik.http.routers.frigate.rule=Host(`traefik.example.com`)" +``` + +The above configuration will create a "service" in Traefik, automatically adding your container's IP on port 8971 as a backend. +It will also add a router, routing requests to "traefik.example.com" to your local container. + +Note that with this approach, you don't need to expose any ports for the Frigate instance since all traffic will be routed over the internal Docker network. From 3ffbdb35a21be7aea8283e7489d7dbd287b26886 Mon Sep 17 00:00:00 2001 From: elreydetoda <10230166+elreydetoda@users.noreply.github.com> Date: Sat, 17 Aug 2024 08:14:45 -0400 Subject: [PATCH 06/81] updating HACS installation instructions (#13136) --- docs/docs/integrations/home-assistant.md | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/docs/docs/integrations/home-assistant.md b/docs/docs/integrations/home-assistant.md index 4d9fdc744..f28e78721 100644 --- a/docs/docs/integrations/home-assistant.md +++ b/docs/docs/integrations/home-assistant.md @@ -25,7 +25,7 @@ Available via HACS as a default repository. To install: - Use [HACS](https://hacs.xyz/) to install the integration: ``` -Home Assistant > HACS > Integrations > "Explore & Add Integrations" > Frigate +Home Assistant > HACS > Click in the Search bar and type "Frigate" > Frigate ``` - Restart Home Assistant. From b44354ad295b4be0328e48f0e053366f75a0add9 Mon Sep 17 00:00:00 2001 From: Josh Hawkins <32435876+hawkeye217@users.noreply.github.com> Date: Sat, 17 Aug 2024 16:04:12 -0500 Subject: [PATCH 07/81] Update configuring go2rtc docs to reflect 0.14 changes (#13147) --- docs/docs/guides/configuring_go2rtc.md | 17 ++++++++--------- 1 file changed, 8 insertions(+), 9 deletions(-) diff --git a/docs/docs/guides/configuring_go2rtc.md b/docs/docs/guides/configuring_go2rtc.md index 5410422de..8316376f2 100644 --- a/docs/docs/guides/configuring_go2rtc.md +++ b/docs/docs/guides/configuring_go2rtc.md @@ -13,7 +13,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.9.4#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. For the best experience, you should set the stream name under go2rtc to match the name of your camera so that Frigate will automatically map it and be able to use better 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.9.4#module-streams), not just rtsp. ```yaml go2rtc: @@ -22,7 +22,7 @@ go2rtc: - rtsp://user:password@10.0.10.10:554/cam/realmonitor?channel=1&subtype=2 ``` -The easiest live view to get working is MSE. After adding this to the config, restart Frigate and try to watch the live stream by selecting MSE in the dropdown after clicking on the camera. +After adding this to the config, restart Frigate and try to watch the live stream for a single camera by clicking on it from the dashboard. It should look much clearer and more fluent than the original jsmpeg stream. ### What if my video doesn't play? @@ -46,7 +46,7 @@ The easiest live view to get working is MSE. After adding this to the config, re streams: back: - rtsp://user:password@10.0.10.10:554/cam/realmonitor?channel=1&subtype=2 - - "ffmpeg:back#video=h264" + - "ffmpeg:back#video=h264#hardware" ``` - Switch to FFmpeg if needed: @@ -58,9 +58,8 @@ The easiest live view to get working is MSE. After adding this to the config, re - ffmpeg:rtsp://user:password@10.0.10.10:554/cam/realmonitor?channel=1&subtype=2 ``` -- If you can see the video but do not have audio, this is most likely because your -camera's audio stream is not AAC. - - If possible, update your camera's audio settings to AAC. + - If you can see the video but do not have audio, this is most likely because your camera's audio stream codec is not AAC. + - If possible, update your camera's audio settings to AAC in your camera's firmware. - If your cameras do not support AAC audio, you will need to tell go2rtc to re-encode the audio to AAC on demand if you want audio. This will use additional CPU and add some latency. To add AAC audio on demand, you can update your go2rtc config as follows: ```yaml go2rtc: @@ -77,7 +76,7 @@ camera's audio stream is not AAC. streams: back: - rtsp://user:password@10.0.10.10:554/cam/realmonitor?channel=1&subtype=2 - - "ffmpeg:back#video=h264#audio=aac" + - "ffmpeg:back#video=h264#audio=aac#hardware" ``` When using the ffmpeg module, you would add AAC audio like this: @@ -86,7 +85,7 @@ camera's audio stream is not AAC. go2rtc: streams: back: - - "ffmpeg:rtsp://user:password@10.0.10.10:554/cam/realmonitor?channel=1&subtype=2#video=copy#audio=copy#audio=aac" + - "ffmpeg:rtsp://user:password@10.0.10.10:554/cam/realmonitor?channel=1&subtype=2#video=copy#audio=copy#audio=aac#hardware" ``` :::warning @@ -102,4 +101,4 @@ 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). -1. You may also prefer to [setup WebRTC](/configuration/live#webrtc-extra-configuration) for slightly lower latency than MSE. Note that WebRTC only supports h264 and specific audio formats. +2. You may also prefer to [setup WebRTC](/configuration/live#webrtc-extra-configuration) for slightly lower latency than MSE. Note that WebRTC only supports h264 and specific audio formats and may require opening ports on your router. From 22fe261dd6ea9d90d803c88214e408fd429a288d Mon Sep 17 00:00:00 2001 From: Peter Riemersma <35268498+GroteGehaktBal@users.noreply.github.com> Date: Tue, 20 Aug 2024 21:52:35 +0200 Subject: [PATCH 08/81] Update cameras.md (#13218) --- docs/docs/configuration/cameras.md | 1 + 1 file changed, 1 insertion(+) diff --git a/docs/docs/configuration/cameras.md b/docs/docs/configuration/cameras.md index 52361e9bf..445c8973c 100644 --- a/docs/docs/configuration/cameras.md +++ b/docs/docs/configuration/cameras.md @@ -83,6 +83,7 @@ This list of working and non-working PTZ cameras is based on user feedback. | Amcrest ASH21 | ❌ | ❌ | No ONVIF support | | Ctronics PTZ | ✅ | ❌ | | | Dahua | ✅ | ✅ | | +| Dahua DH-SD2A500HB | ✅ | ❌ | | | Foscam R5 | ✅ | ❌ | | | Hanwha XNP-6550RH | ✅ | ❌ | | | Hikvision | ✅ | ❌ | Incomplete ONVIF support (MoveStatus won't update even on latest firmware) - reported with HWP-N4215IH-DE and DS-2DE3304W-DE, but likely others | From a2deeb0d124fdcb674726c0badca648bad516bf7 Mon Sep 17 00:00:00 2001 From: Josh Hawkins <32435876+hawkeye217@users.noreply.github.com> Date: Wed, 21 Aug 2024 08:01:15 -0500 Subject: [PATCH 09/81] Update live player docs (#13245) * Clarify live modes in 0.14 * change column name * clarify wording --- docs/docs/configuration/live.md | 10 +++++----- 1 file changed, 5 insertions(+), 5 deletions(-) diff --git a/docs/docs/configuration/live.md b/docs/docs/configuration/live.md index efd970a1d..7c429f039 100644 --- a/docs/docs/configuration/live.md +++ b/docs/docs/configuration/live.md @@ -11,11 +11,11 @@ Frigate intelligently uses three different streaming technologies to display you The jsmpeg live view will use more browser and client GPU resources. Using go2rtc is highly recommended and will provide a superior experience. -| Source | Latency | Frame Rate | Resolution | Audio | Requires go2rtc | Other Limitations | -| ------ | ------- | ------------------------------------- | ---------- | ---------------------------- | --------------- | ------------------------------------------------------------------------------------ | -| jsmpeg | low | same as `detect -> fps`, capped at 10 | 720p | no | no | resolution is configurable, but go2rtc is recommended if you want higher resolutions | -| mse | low | native | native | yes (depends on audio codec) | yes | iPhone requires iOS 17.1+, Firefox is h.264 only | -| webrtc | lowest | native | native | yes (depends on audio codec) | yes | requires extra config, doesn't support h.265 | +| Source | Frame Rate | Resolution | Audio | Requires go2rtc | Notes | +| ------ | ------------------------------------- | ---------- | ---------------------------- | --------------- | ------------------------------------------------------------------------------------------------------------------------------------------------------------------- | +| jsmpeg | same as `detect -> fps`, capped at 10 | 720p | no | no | Resolution is configurable, but go2rtc is recommended if you want higher resolutions and better frame rates. jsmpeg is Frigate's default without go2rtc configured. | +| mse | native | native | yes (depends on audio codec) | yes | iPhone requires iOS 17.1+, Firefox is h.264 only. This is Frigate's default when go2rtc is configured. | +| webrtc | native | native | yes (depends on audio codec) | yes | Requires extra configuration, doesn't support h.265. Frigate attempts to use WebRTC when MSE fails or when using a camera's two-way talk feature. | ### Audio Support From 540d66af57c8d6dc28df92418dbade9fe45b3e02 Mon Sep 17 00:00:00 2001 From: Nicolas Mowen Date: Wed, 21 Aug 2024 19:10:12 -0600 Subject: [PATCH 10/81] Update reference config to include motion enabled field (#13255) --- docs/docs/configuration/reference.md | 5 +++++ 1 file changed, 5 insertions(+) diff --git a/docs/docs/configuration/reference.md b/docs/docs/configuration/reference.md index 2cbbc4903..2be78aacc 100644 --- a/docs/docs/configuration/reference.md +++ b/docs/docs/configuration/reference.md @@ -335,6 +335,11 @@ review: # Optional: Motion configuration # NOTE: Can be overridden at the camera level motion: + # Optional: enables detection for the camera (default: True) + # NOTE: Motion detection is required for object detection, + # setting this to False and leaving detect enabled + # will result in an error on startup. + enabled: False # Optional: The threshold passed to cv2.threshold to determine if a pixel is different enough to be counted as motion. (default: shown below) # Increasing this value will make motion detection less sensitive and decreasing it will make motion detection more sensitive. # The value should be between 1 and 255. From b94b08a33c128ea3a05c46dbbe82e0256119224b Mon Sep 17 00:00:00 2001 From: Nicolas Mowen Date: Thu, 22 Aug 2024 06:47:53 -0600 Subject: [PATCH 11/81] Add comment about global zones behavior (#13269) --- docs/docs/configuration/reference.md | 6 ++++++ 1 file changed, 6 insertions(+) diff --git a/docs/docs/configuration/reference.md b/docs/docs/configuration/reference.md index 2be78aacc..60f21abaa 100644 --- a/docs/docs/configuration/reference.md +++ b/docs/docs/configuration/reference.md @@ -320,6 +320,9 @@ review: - car - person # Optional: required zones for an object to be marked as an alert (default: none) + # NOTE: when settings required zones globally, this zone must exist on all cameras + # or the config will be considered invalid. In that case the required_zones + # should be configured at the camera level. required_zones: - driveway # Optional: detections configuration @@ -329,6 +332,9 @@ review: - car - person # Optional: required zones for an object to be marked as a detection (default: none) + # NOTE: when settings required zones globally, this zone must exist on all cameras + # or the config will be considered invalid. In that case the required_zones + # should be configured at the camera level. required_zones: - driveway From 34382ac38e3eaf1276604cf38e7b406183b620ad Mon Sep 17 00:00:00 2001 From: ghxstxch <107282306+ghxstxch@users.noreply.github.com> Date: Sat, 24 Aug 2024 07:07:25 -0500 Subject: [PATCH 12/81] Update cameras.md (#13309) --- docs/docs/configuration/cameras.md | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/docs/docs/configuration/cameras.md b/docs/docs/configuration/cameras.md index 445c8973c..49eae7a5b 100644 --- a/docs/docs/configuration/cameras.md +++ b/docs/docs/configuration/cameras.md @@ -80,7 +80,7 @@ This list of working and non-working PTZ cameras is based on user feedback. | Brand or specific camera | PTZ Controls | Autotracking | Notes | | ------------------------ | :----------: | :----------: | ----------------------------------------------------------------------------------------------------------------------------------------------- | | Amcrest | ✅ | ✅ | ⛔️ Generally, Amcrest should work, but some older models (like the common IP2M-841) don't support autotracking | -| Amcrest ASH21 | ❌ | ❌ | No ONVIF support | +| Amcrest ASH21 | ✅ | ❌ | ONVIF service port: 80 | | Ctronics PTZ | ✅ | ❌ | | | Dahua | ✅ | ✅ | | | Dahua DH-SD2A500HB | ✅ | ❌ | | From 67e692a7f3f2dfe64fceba30b8857bfd25d88f9a Mon Sep 17 00:00:00 2001 From: Nicolas Mowen Date: Sun, 25 Aug 2024 06:56:05 -0600 Subject: [PATCH 13/81] Add edgetpu docs for synology specific issue (#13335) --- docs/docs/troubleshooting/edgetpu.md | 12 ++++++++++++ 1 file changed, 12 insertions(+) diff --git a/docs/docs/troubleshooting/edgetpu.md b/docs/docs/troubleshooting/edgetpu.md index bbb3ffa3d..8c917fa2f 100644 --- a/docs/docs/troubleshooting/edgetpu.md +++ b/docs/docs/troubleshooting/edgetpu.md @@ -28,6 +28,18 @@ The USB coral has different IDs when it is uninitialized and initialized. - When running Frigate in a VM, Proxmox lxc, etc. you must ensure both device IDs are mapped. - When running HA OS you may need to run the Full Access version of the Frigate addon with the `Protected Mode` switch disabled so that the coral can be accessed. +### Synology 716+II running DSM 7.2.1-69057 Update 5 + +Some users have reported that this older device runs an older kernel causing issues with the coral not being detected. The following steps allowed it to be detected correctly: + +1. Plug in the coral TPU in any of the USB ports on the NAS +2. Open the control panel - info screen. The coral TPU would be shown as a generic device. +3. Start the docker container with Coral TPU enabled in the config +4. The TPU would be detected but a few moments later it would disconnect. +5. While leaving the TPU device plugged in, restart the NAS using the reboot command in the UI. Do NOT unplug the NAS/power it off etc. +6. Open the control panel - info scree. The coral TPU will now be recognised as a USB Device - google inc +7. Start the frigate container. Everything should work now! + ## USB Coral Detection Appears to be Stuck The USB Coral can become stuck and need to be restarted, this can happen for a number of reasons depending on hardware and software setup. Some common reasons are: From 70ea6fc9a12a9de45487acc5c620f57036a0bc9d Mon Sep 17 00:00:00 2001 From: Josh Hawkins <32435876+hawkeye217@users.noreply.github.com> Date: Tue, 27 Aug 2024 08:00:54 -0500 Subject: [PATCH 14/81] Update live view docs with camera firmware settings recommendations (#13370) * Update live view docs with camera firmware settings recommendations * video/audio * capitalization * Video only cams * clarify higher iframes * update wording * fix wording * Add note on camera specific page * change note --- docs/docs/configuration/camera_specific.md | 6 ++++++ docs/docs/configuration/live.md | 19 +++++++++++++++++++ 2 files changed, 25 insertions(+) diff --git a/docs/docs/configuration/camera_specific.md b/docs/docs/configuration/camera_specific.md index a3619c5fb..74f2a22b0 100644 --- a/docs/docs/configuration/camera_specific.md +++ b/docs/docs/configuration/camera_specific.md @@ -9,6 +9,12 @@ This page makes use of presets of FFmpeg args. For more information on presets, ::: +:::note + +Many cameras support encoding options which greatly affect the live view experience, see the [Live view](/configuration/live) page for more info. + +::: + ## MJPEG Cameras Note that mjpeg cameras require encoding the video into h264 for recording, and restream roles. This will use significantly more CPU than if the cameras supported h264 feeds directly. It is recommended to use the restream role to create an h264 restream and then use that as the source for ffmpeg. diff --git a/docs/docs/configuration/live.md b/docs/docs/configuration/live.md index 7c429f039..e543e5ada 100644 --- a/docs/docs/configuration/live.md +++ b/docs/docs/configuration/live.md @@ -17,6 +17,16 @@ The jsmpeg live view will use more browser and client GPU resources. Using go2rt | mse | native | native | yes (depends on audio codec) | yes | iPhone requires iOS 17.1+, Firefox is h.264 only. This is Frigate's default when go2rtc is configured. | | webrtc | native | native | yes (depends on audio codec) | yes | Requires extra configuration, doesn't support h.265. Frigate attempts to use WebRTC when MSE fails or when using a camera's two-way talk feature. | +### Camera Settings Recommendations + +If you are using go2rtc, you should adjust the following settings in your camera's firmware for the best experience with Live view: + +- Video codec: H.264 - provides the most compatible video codec with all Live view technologies and browsers. +- Audio codec: AAC - provides the most compatible audio codec with all Live view technologies and browsers. +- I-frame interval (sometimes called the keyframe interval, the interframe space, or the GOP length): match your camera's frame rate, or choose "1x" (for interframe space on Reolink cameras). For example, if your stream outputs 20fps, your i-frame interval should be 20 (or 1x on Reolink). Values higher than the frame rate will cause the stream to take longer to begin playback. See [this page](https://gardinal.net/understanding-the-keyframe-interval/) for more on keyframes. + +The default video and audio codec on your camera may not always be compatible with your browser, which is why setting them to H.264 and AAC is recommended. See the [go2rtc docs](https://github.com/AlexxIT/go2rtc?tab=readme-ov-file#codecs-madness) for codec support information. + ### Audio Support MSE Requires AAC audio, WebRTC requires PCMU/PCMA, or opus audio. If you want to support both MSE and WebRTC then your restream config needs to make sure both are enabled. @@ -32,6 +42,15 @@ go2rtc: - "ffmpeg:http_cam#audio=opus" # <- copy of the stream which transcodes audio to the missing codec (usually will be opus) ``` +If your camera does not have audio and you are having problems with Live view, you should have go2rtc send video only: + +```yaml +go2rtc: + streams: + no_audio_camera: + - ffmpeg:rtsp://192.168.1.5:554/live0#video=copy +``` + ### Setting Stream For Live UI There may be some cameras that you would prefer to use the sub stream for live view, but the main stream for recording. This can be done via `live -> stream_name`. From 37680c317c683e13d9690268db3af0fee74f1618 Mon Sep 17 00:00:00 2001 From: Josh Hawkins <32435876+hawkeye217@users.noreply.github.com> Date: Wed, 28 Aug 2024 09:22:57 -0500 Subject: [PATCH 15/81] Change wording of offline message to account for broker lwt timeout (#13403) --- docs/docs/integrations/mqtt.md | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/docs/docs/integrations/mqtt.md b/docs/docs/integrations/mqtt.md index 6c9d112fa..8b6664c1f 100644 --- a/docs/docs/integrations/mqtt.md +++ b/docs/docs/integrations/mqtt.md @@ -11,7 +11,7 @@ These are the MQTT messages generated by Frigate. The default topic_prefix is `f Designed to be used as an availability topic with Home Assistant. Possible message are: "online": published when Frigate is running (on startup) -"offline": published right before Frigate stops +"offline": published after Frigate has stopped ### `frigate/restart` From 90221e8c9443ba6b70c88c1eca38abf776524857 Mon Sep 17 00:00:00 2001 From: cvroque <65680394+cvroque@users.noreply.github.com> Date: Thu, 29 Aug 2024 12:10:47 -0300 Subject: [PATCH 16/81] Remove duplicated text (#13416) --- docs/docs/configuration/review.md | 2 -- 1 file changed, 2 deletions(-) diff --git a/docs/docs/configuration/review.md b/docs/docs/configuration/review.md index 4a39924eb..15ea0349e 100644 --- a/docs/docs/configuration/review.md +++ b/docs/docs/configuration/review.md @@ -41,8 +41,6 @@ review: By default all detections that do not qualify as an alert qualify as a detection. However, detections can further be filtered to only include certain labels or certain zones. -By default a review item will only be marked as an alert if a person or car is detected. This can be configured to include any object or audio label using the following config: - ```yaml # can be overridden at the camera level review: From da0f63f0952c165ca7e16cbdb8ce3107f622c4c1 Mon Sep 17 00:00:00 2001 From: Josh Hawkins <32435876+hawkeye217@users.noreply.github.com> Date: Sun, 4 Aug 2024 08:05:26 -0500 Subject: [PATCH 17/81] Fix large tablet recording view layout (#12753) --- web/src/views/events/RecordingView.tsx | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/web/src/views/events/RecordingView.tsx b/web/src/views/events/RecordingView.tsx index ce416b873..c1b30b98e 100644 --- a/web/src/views/events/RecordingView.tsx +++ b/web/src/views/events/RecordingView.tsx @@ -507,7 +507,7 @@ export function RecordingView({ "pt-2 portrait:w-full", mainCameraAspect == "wide" ? "aspect-wide landscape:w-full" - : "aspect-video landscape:h-[94%]", + : "aspect-video landscape:h-[94%] landscape:xl:h-[65%]", ), )} style={{ From 7db6ed9ad52f18674e54b43606cb0f3bb9c036c8 Mon Sep 17 00:00:00 2001 From: Josh Hawkins <32435876+hawkeye217@users.noreply.github.com> Date: Sun, 4 Aug 2024 08:06:11 -0500 Subject: [PATCH 18/81] Use radix css var to limit desktop menu height (#12743) --- web/src/components/menu/GeneralSettings.tsx | 12 +++++++++++- 1 file changed, 11 insertions(+), 1 deletion(-) diff --git a/web/src/components/menu/GeneralSettings.tsx b/web/src/components/menu/GeneralSettings.tsx index baab171cc..34637d57e 100644 --- a/web/src/components/menu/GeneralSettings.tsx +++ b/web/src/components/menu/GeneralSettings.tsx @@ -139,8 +139,18 @@ export default function GeneralSettings({ className }: GeneralSettingsProps) {
From b7e0d14b838e3f0f6976e1d8ac4f561fba54365e Mon Sep 17 00:00:00 2001 From: Josh Hawkins <32435876+hawkeye217@users.noreply.github.com> Date: Mon, 5 Aug 2024 08:01:42 -0500 Subject: [PATCH 19/81] Only use dense property on phones for motion review timeline (#12768) --- web/src/views/events/EventView.tsx | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/web/src/views/events/EventView.tsx b/web/src/views/events/EventView.tsx index a6c5e6bc8..8b4569be0 100644 --- a/web/src/views/events/EventView.tsx +++ b/web/src/views/events/EventView.tsx @@ -1057,7 +1057,7 @@ function MotionReview({ setScrubbing(scrubbing); }} - dense={isMobile} + dense={isMobileOnly} /> ) : ( From 69fe6cdc054dfbaf1123babf3b3e8be4a7f3d456 Mon Sep 17 00:00:00 2001 From: Nicolas Mowen Date: Mon, 5 Aug 2024 07:20:21 -0600 Subject: [PATCH 20/81] Fix iOS export buttons (#12755) * Fix iOS export buttons * Use layering instead of z index --- web/src/components/card/ExportCard.tsx | 41 +++++++++++++------------- 1 file changed, 20 insertions(+), 21 deletions(-) diff --git a/web/src/components/card/ExportCard.tsx b/web/src/components/card/ExportCard.tsx index a2c92b5fb..d39cbbeda 100644 --- a/web/src/components/card/ExportCard.tsx +++ b/web/src/components/card/ExportCard.tsx @@ -124,13 +124,27 @@ export default function ExportCard({ onMouseLeave={isDesktop ? () => setHovered(false) : undefined} onClick={isDesktop ? undefined : () => setHovered(!hovered)} > - {hovered && ( + {exportedRecording.in_progress ? ( + + ) : ( <> -
+ {exportedRecording.thumb_path.length > 0 ? ( + setLoading(false)} + /> + ) : ( +
+ )} + + )} + {hovered && ( +
+
{!exportedRecording.in_progress && ( @@ -167,7 +181,7 @@ export default function ExportCard({ {!exportedRecording.in_progress && ( )} - - )} - {exportedRecording.in_progress ? ( - - ) : ( - <> - {exportedRecording.thumb_path.length > 0 ? ( - setLoading(false)} - /> - ) : ( -
- )} - +
)} {loading && ( )} -
+
{exportedRecording.name.replaceAll("_", " ")}
From ff2e46650cd3f267d6706a28656b98556d1e1a60 Mon Sep 17 00:00:00 2001 From: Nicolas Mowen Date: Tue, 6 Aug 2024 06:40:20 -0600 Subject: [PATCH 21/81] Update version --- Makefile | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/Makefile b/Makefile index 0cfd9b282..1c1d19a0c 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.14.0 +VERSION = 0.14.1 IMAGE_REPO ?= ghcr.io/blakeblackshear/frigate GITHUB_REF_NAME ?= $(shell git rev-parse --abbrev-ref HEAD) CURRENT_UID := $(shell id -u) From b1806b0a7c3cc58cd58d2fac854215c0f0df1a98 Mon Sep 17 00:00:00 2001 From: Nicolas Mowen Date: Tue, 6 Aug 2024 09:08:14 -0600 Subject: [PATCH 22/81] Handle case where sub label was null (#12785) --- web/src/components/player/LivePlayer.tsx | 5 +---- 1 file changed, 1 insertion(+), 4 deletions(-) diff --git a/web/src/components/player/LivePlayer.tsx b/web/src/components/player/LivePlayer.tsx index 88730b8fb..67057a278 100644 --- a/web/src/components/player/LivePlayer.tsx +++ b/web/src/components/player/LivePlayer.tsx @@ -266,10 +266,7 @@ export default function LivePlayer({ ), ]), ] - .filter( - (label) => - label !== undefined && !label.includes("-verified"), - ) + .filter((label) => label?.includes("-verified") == false) .map((label) => capitalizeFirstLetter(label)) .sort() .join(", ") From 7b274b6974042249003a80bc77c02a414e0bbb32 Mon Sep 17 00:00:00 2001 From: Nicolas Mowen Date: Tue, 6 Aug 2024 09:08:43 -0600 Subject: [PATCH 23/81] Use camera status to get state of camera config (#12787) * Use camera status to get state of camera config * Fix spelling --- frigate/comms/dispatcher.py | 15 ++++++++++++- web/src/api/ws.tsx | 44 ++++++++++++++++++++++--------------- 2 files changed, 40 insertions(+), 19 deletions(-) diff --git a/frigate/comms/dispatcher.py b/frigate/comms/dispatcher.py index db6c44c11..26922f284 100644 --- a/frigate/comms/dispatcher.py +++ b/frigate/comms/dispatcher.py @@ -129,7 +129,20 @@ class Dispatcher: elif topic == UPDATE_CAMERA_ACTIVITY: self.camera_activity = payload elif topic == "onConnect": - self.publish("camera_activity", json.dumps(self.camera_activity)) + camera_status = self.camera_activity.copy() + + for camera in camera_status.keys(): + camera_status[camera]["config"] = { + "detect": self.config.cameras[camera].detect.enabled, + "snapshots": self.config.cameras[camera].snapshots.enabled, + "record": self.config.cameras[camera].record.enabled, + "audio": self.config.cameras[camera].audio.enabled, + "autotracking": self.config.cameras[ + camera + ].onvif.autotracking.enabled, + } + + self.publish("camera_activity", json.dumps(camera_status)) else: self.publish(topic, payload, retain=False) diff --git a/web/src/api/ws.tsx b/web/src/api/ws.tsx index afcbaa0c0..94f381ada 100644 --- a/web/src/api/ws.tsx +++ b/web/src/api/ws.tsx @@ -1,7 +1,6 @@ import { baseUrl } from "./baseUrl"; import { useCallback, useEffect, useState } from "react"; import useWebSocket, { ReadyState } from "react-use-websocket"; -import { FrigateConfig } from "@/types/frigateConfig"; import { FrigateCameraState, FrigateEvent, @@ -9,7 +8,6 @@ import { ToggleableSetting, } from "@/types/ws"; import { FrigateStats } from "@/types/stats"; -import useSWR from "swr"; import { createContainer } from "react-tracked"; import useDeepMemo from "@/hooks/use-deep-memo"; @@ -26,40 +24,50 @@ type WsState = { type useValueReturn = [WsState, (update: Update) => void]; function useValue(): useValueReturn { - // basic config - const { data: config } = useSWR("config", { - revalidateOnFocus: false, - }); const wsUrl = `${baseUrl.replace(/^http/, "ws")}ws`; // main state + + const [hasCameraState, setHasCameraState] = useState(false); const [wsState, setWsState] = useState({}); useEffect(() => { - if (!config) { + if (hasCameraState) { + return; + } + + const activityValue: string = wsState["camera_activity"] as string; + + if (!activityValue) { + return; + } + + const cameraActivity: { [key: string]: object } = JSON.parse(activityValue); + + if (!cameraActivity) { return; } const cameraStates: WsState = {}; - Object.keys(config.cameras).forEach((camera) => { - const { name, record, detect, snapshots, audio, onvif } = - config.cameras[camera]; - cameraStates[`${name}/recordings/state`] = record.enabled ? "ON" : "OFF"; - cameraStates[`${name}/detect/state`] = detect.enabled ? "ON" : "OFF"; - cameraStates[`${name}/snapshots/state`] = snapshots.enabled - ? "ON" - : "OFF"; - cameraStates[`${name}/audio/state`] = audio.enabled ? "ON" : "OFF"; - cameraStates[`${name}/ptz_autotracker/state`] = onvif.autotracking.enabled + Object.entries(cameraActivity).forEach(([name, state]) => { + const { record, detect, snapshots, audio, autotracking } = + // @ts-expect-error we know this is correct + state["config"]; + cameraStates[`${name}/recordings/state`] = record ? "ON" : "OFF"; + cameraStates[`${name}/detect/state`] = detect ? "ON" : "OFF"; + cameraStates[`${name}/snapshots/state`] = snapshots ? "ON" : "OFF"; + cameraStates[`${name}/audio/state`] = audio ? "ON" : "OFF"; + cameraStates[`${name}/ptz_autotracker/state`] = autotracking ? "ON" : "OFF"; }); setWsState({ ...wsState, ...cameraStates }); + setHasCameraState(true); // we only want this to run initially when the config is loaded // eslint-disable-next-line react-hooks/exhaustive-deps - }, [config]); + }, [wsState]); // ws handler const { sendJsonMessage, readyState } = useWebSocket(wsUrl, { From f47984818f42cd339ab5e7751c1de3c676421efb Mon Sep 17 00:00:00 2001 From: Josh Hawkins <32435876+hawkeye217@users.noreply.github.com> Date: Tue, 6 Aug 2024 10:41:11 -0500 Subject: [PATCH 24/81] Ensure review cameras are sorted by config ui order if specified (#12789) --- web/src/components/filter/ReviewFilterGroup.tsx | 6 +++++- 1 file changed, 5 insertions(+), 1 deletion(-) diff --git a/web/src/components/filter/ReviewFilterGroup.tsx b/web/src/components/filter/ReviewFilterGroup.tsx index 5ee0ac9bb..d01ea384f 100644 --- a/web/src/components/filter/ReviewFilterGroup.tsx +++ b/web/src/components/filter/ReviewFilterGroup.tsx @@ -136,7 +136,11 @@ export default function ReviewFilterGroup({ const filterValues = useMemo( () => ({ - cameras: Object.keys(config?.cameras || {}), + cameras: Object.keys(config?.cameras ?? {}).sort( + (a, b) => + (config?.cameras[a]?.ui?.order ?? 0) - + (config?.cameras[b]?.ui?.order ?? 0), + ), labels: Object.values(allLabels || {}), zones: Object.values(allZones || {}), }), From fe188bd64692ef4570cfd17cccf2e130ac00cd74 Mon Sep 17 00:00:00 2001 From: Nicolas Mowen Date: Tue, 6 Aug 2024 14:15:00 -0600 Subject: [PATCH 25/81] Handle case where user stops scrubbing but remains hovering (#12794) * Handle case where user stops scrubbing but remains hovering * Add type --- .../player/PreviewThumbnailPlayer.tsx | 148 +++++++++++------- web/src/types/timeline.ts | 2 + 2 files changed, 94 insertions(+), 56 deletions(-) diff --git a/web/src/components/player/PreviewThumbnailPlayer.tsx b/web/src/components/player/PreviewThumbnailPlayer.tsx index 20eefe361..d2192e85d 100644 --- a/web/src/components/player/PreviewThumbnailPlayer.tsx +++ b/web/src/components/player/PreviewThumbnailPlayer.tsx @@ -21,7 +21,7 @@ import { Tooltip, TooltipContent, TooltipTrigger } from "../ui/tooltip"; import ImageLoadingIndicator from "../indicators/ImageLoadingIndicator"; import useContextMenu from "@/hooks/use-contextmenu"; import ActivityIndicator from "../indicators/activity-indicator"; -import { TimeRange } from "@/types/timeline"; +import { TimelineScrubMode, TimeRange } from "@/types/timeline"; import { NoThumbSlider } from "../ui/slider"; import { PREVIEW_FPS, PREVIEW_PADDING } from "@/types/preview"; import { capitalizeFirstLetter } from "@/utils/stringUtil"; @@ -414,7 +414,7 @@ export function VideoPreview({ if (isSafari || (isFirefox && isMobile)) { playerRef.current.pause(); - setManualPlayback(true); + setPlaybackMode("compat"); } else { playerRef.current.currentTime = playerStartTime; playerRef.current.playbackRate = PREVIEW_FPS; @@ -453,9 +453,9 @@ export function VideoPreview({ setReviewed(); if (loop && playerRef.current) { - if (manualPlayback) { - setManualPlayback(false); - setTimeout(() => setManualPlayback(true), 100); + if (playbackMode != "auto") { + setPlaybackMode("auto"); + setTimeout(() => setPlaybackMode("compat"), 100); } playerRef.current.currentTime = playerStartTime; @@ -472,7 +472,7 @@ export function VideoPreview({ playerRef.current?.pause(); } - setManualPlayback(false); + setPlaybackMode("auto"); setProgress(100.0); } else { setProgress(playerPercent); @@ -486,9 +486,10 @@ export function VideoPreview({ // safari is incapable of playing at a speed > 2x // so manual seeking is required on iOS - const [manualPlayback, setManualPlayback] = useState(false); + const [playbackMode, setPlaybackMode] = useState("auto"); + useEffect(() => { - if (!manualPlayback || !playerRef.current) { + if (playbackMode != "compat" || !playerRef.current) { return; } @@ -503,10 +504,14 @@ export function VideoPreview({ // we know that these deps are correct // eslint-disable-next-line react-hooks/exhaustive-deps - }, [manualPlayback, playerRef]); + }, [playbackMode, playerRef]); // user interaction + useEffect(() => { + setIgnoreClick(playbackMode != "auto" && playbackMode != "compat"); + }, [playbackMode, setIgnoreClick]); + const onManualSeek = useCallback( (values: number[]) => { const value = values[0]; @@ -515,14 +520,8 @@ export function VideoPreview({ return; } - if (manualPlayback) { - setManualPlayback(false); - setIgnoreClick(true); - } - if (playerRef.current.paused == false) { playerRef.current.pause(); - setIgnoreClick(true); } if (setReviewed) { @@ -536,27 +535,21 @@ export function VideoPreview({ // we know that these deps are correct // eslint-disable-next-line react-hooks/exhaustive-deps - [ - manualPlayback, - playerDuration, - playerRef, - playerStartTime, - setIgnoreClick, - ], + [playerDuration, playerRef, playerStartTime, setIgnoreClick], ); const onStopManualSeek = useCallback(() => { setTimeout(() => { - setIgnoreClick(false); setHoverTimeout(undefined); if (isSafari || (isFirefox && isMobile)) { - setManualPlayback(true); + setPlaybackMode("compat"); } else { + setPlaybackMode("auto"); playerRef.current?.play(); } }, 500); - }, [playerRef, setIgnoreClick]); + }, [playerRef]); const onProgressHover = useCallback( (event: React.MouseEvent) => { @@ -572,10 +565,8 @@ export function VideoPreview({ if (hoverTimeout) { clearTimeout(hoverTimeout); } - - setHoverTimeout(setTimeout(() => onStopManualSeek(), 500)); }, - [sliderRef, hoverTimeout, onManualSeek, onStopManualSeek, setHoverTimeout], + [sliderRef, hoverTimeout, onManualSeek], ); return ( @@ -597,14 +588,37 @@ export function VideoPreview({ {showProgress && ( { + setPlaybackMode("drag"); + onManualSeek(event); + }} onValueCommit={onStopManualSeek} min={0} step={1} max={100} - onMouseMove={isMobile ? undefined : onProgressHover} + onMouseMove={ + isMobile + ? undefined + : (event) => { + if (playbackMode != "drag") { + setPlaybackMode("hover"); + onProgressHover(event); + } + } + } + onMouseLeave={ + isMobile + ? undefined + : () => { + if (!sliderRef.current) { + return; + } + + setHoverTimeout(setTimeout(() => onStopManualSeek(), 500)); + } + } /> )}
@@ -642,7 +656,8 @@ export function InProgressPreview({ }/frames`, { revalidateOnFocus: false }, ); - const [manualFrame, setManualFrame] = useState(false); + + const [playbackMode, setPlaybackMode] = useState("auto"); const [hoverTimeout, setHoverTimeout] = useState(); const [key, setKey] = useState(0); @@ -655,7 +670,7 @@ export function InProgressPreview({ onTimeUpdate(review.start_time - PREVIEW_PADDING + key); } - if (manualFrame) { + if (playbackMode != "auto") { return; } @@ -692,19 +707,18 @@ export function InProgressPreview({ // we know that these deps are correct // eslint-disable-next-line react-hooks/exhaustive-deps - }, [key, manualFrame, previewFrames]); + }, [key, playbackMode, previewFrames]); // user interaction + useEffect(() => { + setIgnoreClick(playbackMode != "auto"); + }, [playbackMode, setIgnoreClick]); + const onManualSeek = useCallback( (values: number[]) => { const value = values[0]; - if (!manualFrame) { - setManualFrame(true); - setIgnoreClick(true); - } - if (!review.has_been_reviewed) { setReviewed(review.id); } @@ -714,19 +728,18 @@ export function InProgressPreview({ // we know that these deps are correct // eslint-disable-next-line react-hooks/exhaustive-deps - [manualFrame, setIgnoreClick, setManualFrame, setKey], + [setIgnoreClick, setKey], ); const onStopManualSeek = useCallback( (values: number[]) => { const value = values[0]; setTimeout(() => { - setIgnoreClick(false); - setManualFrame(false); + setPlaybackMode("auto"); setKey(value - 1); }, 500); }, - [setManualFrame, setIgnoreClick], + [setPlaybackMode], ); const onProgressHover = useCallback( @@ -744,17 +757,8 @@ export function InProgressPreview({ if (hoverTimeout) { clearTimeout(hoverTimeout); } - - setHoverTimeout(setTimeout(() => onStopManualSeek(progress), 500)); }, - [ - sliderRef, - hoverTimeout, - previewFrames, - onManualSeek, - onStopManualSeek, - setHoverTimeout, - ], + [sliderRef, hoverTimeout, previewFrames, onManualSeek], ); if (!previewFrames || previewFrames.length == 0) { @@ -776,14 +780,46 @@ export function InProgressPreview({ {showProgress && ( { + setPlaybackMode("drag"); + onManualSeek(event); + }} onValueCommit={onStopManualSeek} min={0} step={1} max={previewFrames.length - 1} - onMouseMove={isMobile ? undefined : onProgressHover} + onMouseMove={ + isMobile + ? undefined + : (event) => { + if (playbackMode != "drag") { + setPlaybackMode("hover"); + onProgressHover(event); + } + } + } + onMouseLeave={ + isMobile + ? undefined + : (event) => { + if (!sliderRef.current || !previewFrames) { + return; + } + + const rect = sliderRef.current.getBoundingClientRect(); + const positionX = event.clientX - rect.left; + const width = sliderRef.current.clientWidth; + const progress = [ + Math.round((positionX / width) * previewFrames.length), + ]; + + setHoverTimeout( + setTimeout(() => onStopManualSeek(progress), 500), + ); + } + } /> )}
diff --git a/web/src/types/timeline.ts b/web/src/types/timeline.ts index b4e02304c..b7945314d 100644 --- a/web/src/types/timeline.ts +++ b/web/src/types/timeline.ts @@ -26,3 +26,5 @@ export type Timeline = { export type TimeRange = { before: number; after: number }; export type TimelineType = "timeline" | "events"; + +export type TimelineScrubMode = "auto" | "drag" | "hover" | "compat"; From ad5c3741e976c85610b3b3d681008e94cfabd5a6 Mon Sep 17 00:00:00 2001 From: Nicolas Mowen Date: Tue, 6 Aug 2024 16:11:20 -0600 Subject: [PATCH 26/81] Add camera name to audio debug line (#12799) * Add camera name to audio debug line * Formatting --- frigate/events/audio.py | 4 +++- 1 file changed, 3 insertions(+), 1 deletion(-) diff --git a/frigate/events/audio.py b/frigate/events/audio.py index fca16f364..471d403b8 100644 --- a/frigate/events/audio.py +++ b/frigate/events/audio.py @@ -209,7 +209,9 @@ class AudioEventMaintainer(threading.Thread): audio_detections = [] for label, score, _ in model_detections: - logger.debug(f"Heard {label} with a score of {score}") + logger.debug( + f"{self.config.name} heard {label} with a score of {score}" + ) if label not in self.config.audio.listen: continue From 9d456ccfcf20c015ffd2c2378e1254a78b9e127b Mon Sep 17 00:00:00 2001 From: Marc Altmann <40744649+MarcA711@users.noreply.github.com> Date: Wed, 7 Aug 2024 15:28:12 +0200 Subject: [PATCH 27/81] fix default model for rknn detector (#12807) --- frigate/detectors/plugins/rknn.py | 5 +++-- 1 file changed, 3 insertions(+), 2 deletions(-) diff --git a/frigate/detectors/plugins/rknn.py b/frigate/detectors/plugins/rknn.py index af22ca358..7606313b5 100644 --- a/frigate/detectors/plugins/rknn.py +++ b/frigate/detectors/plugins/rknn.py @@ -23,7 +23,6 @@ model_chache_dir = "/config/model_cache/rknn_cache/" class RknnDetectorConfig(BaseDetectorConfig): type: Literal[DETECTOR_KEY] num_cores: int = Field(default=0, ge=0, le=3, title="Number of NPU cores to use.") - purge_model_cache: bool = Field(default=True) class Rknn(DetectionApi): @@ -36,7 +35,9 @@ class Rknn(DetectionApi): core_mask = 2**config.num_cores - 1 soc = self.get_soc() - model_props = self.parse_model_input(config.model.path, soc) + model_path = config.model.path or "deci-fp16-yolonas_s" + + model_props = self.parse_model_input(model_path, soc) if model_props["preset"]: config.model.model_type = model_props["model_type"] From 096e2791f523e56a869cbb6ef7a1f65174bf1f95 Mon Sep 17 00:00:00 2001 From: Josh Hawkins <32435876+hawkeye217@users.noreply.github.com> Date: Wed, 7 Aug 2024 13:31:39 -0500 Subject: [PATCH 28/81] Ensure review card icon color for event view is visible in light mode (#12812) --- web/src/components/card/ReviewCard.tsx | 10 ++++++++-- 1 file changed, 8 insertions(+), 2 deletions(-) diff --git a/web/src/components/card/ReviewCard.tsx b/web/src/components/card/ReviewCard.tsx index fe221e112..64f52bd1b 100644 --- a/web/src/components/card/ReviewCard.tsx +++ b/web/src/components/card/ReviewCard.tsx @@ -130,10 +130,16 @@ export default function ReviewCard({
{event.data.objects.map((object) => { - return getIconForLabel(object, "size-3 text-white"); + return getIconForLabel( + object, + "size-3 text-primary dark:text-white", + ); })} {event.data.audio.map((audio) => { - return getIconForLabel(audio, "size-3 text-white"); + return getIconForLabel( + audio, + "size-3 text-primary dark:text-white", + ); })}
{formattedDate}
From 07b3160dffe184aae18202ed027876ebd2b0e4b1 Mon Sep 17 00:00:00 2001 From: Josh Hawkins <32435876+hawkeye217@users.noreply.github.com> Date: Fri, 9 Aug 2024 07:46:18 -0500 Subject: [PATCH 29/81] Add right click to delete points in desktop mask/zone editor (#12744) --- web/src/components/settings/PolygonCanvas.tsx | 26 +++++++++++++++++++ .../settings/PolygonEditControls.tsx | 2 +- 2 files changed, 27 insertions(+), 1 deletion(-) diff --git a/web/src/components/settings/PolygonCanvas.tsx b/web/src/components/settings/PolygonCanvas.tsx index 6121293e5..e6851b63c 100644 --- a/web/src/components/settings/PolygonCanvas.tsx +++ b/web/src/components/settings/PolygonCanvas.tsx @@ -114,6 +114,29 @@ export function PolygonCanvas({ const mousePos = stage.getPointerPosition() ?? { x: 0, y: 0 }; const intersection = stage.getIntersection(mousePos); + // right click on desktops to delete a point + if ( + e.evt instanceof MouseEvent && + e.evt.button === 2 && + intersection?.getClassName() == "Circle" + ) { + const pointIndex = parseInt(intersection.name()?.split("-")[1]); + if (!isNaN(pointIndex)) { + const updatedPoints = activePolygon.points.filter( + (_, index) => index !== pointIndex, + ); + updatedPolygons[activePolygonIndex] = { + ...activePolygon, + points: updatedPoints, + pointsOrder: activePolygon.pointsOrder?.filter( + (_, index) => index !== pointIndex, + ), + }; + setPolygons(updatedPolygons); + } + return; + } + if ( activePolygon.points.length >= 3 && intersection?.getClassName() == "Circle" && @@ -236,6 +259,9 @@ export function PolygonCanvas({ onMouseDown={handleMouseDown} onTouchStart={handleMouseDown} onMouseOver={handleStageMouseOver} + onContextMenu={(e) => { + e.evt.preventDefault(); + }} > - Undo + Remove last point From 87b69c373a755acaba2c21d965640ac21bd3451d Mon Sep 17 00:00:00 2001 From: Josh Hawkins <32435876+hawkeye217@users.noreply.github.com> Date: Fri, 9 Aug 2024 07:46:39 -0500 Subject: [PATCH 30/81] Persist live view muted/unmuted for session only (#12727) * Persist live view muted/unmuted for session only * consistent naming --- web/src/hooks/use-session-persistence.ts | 39 ++++++++++++++++++++++++ web/src/views/live/LiveCameraView.tsx | 5 +-- 2 files changed, 42 insertions(+), 2 deletions(-) create mode 100644 web/src/hooks/use-session-persistence.ts diff --git a/web/src/hooks/use-session-persistence.ts b/web/src/hooks/use-session-persistence.ts new file mode 100644 index 000000000..3662c799c --- /dev/null +++ b/web/src/hooks/use-session-persistence.ts @@ -0,0 +1,39 @@ +import { useCallback, useState } from "react"; + +type useSessionPersistenceReturn = [ + value: S | undefined, + setValue: (value: S | undefined) => void, +]; + +export function useSessionPersistence( + key: string, + defaultValue: S | undefined = undefined, +): useSessionPersistenceReturn { + const [storedValue, setStoredValue] = useState(() => { + try { + const value = window.sessionStorage.getItem(key); + + if (value) { + return JSON.parse(value); + } else { + window.sessionStorage.setItem(key, JSON.stringify(defaultValue)); + return defaultValue; + } + } catch (err) { + return defaultValue; + } + }); + + const setValue = useCallback( + (newValue: S | undefined) => { + try { + window.sessionStorage.setItem(key, JSON.stringify(newValue)); + // eslint-disable-next-line no-empty + } catch (err) {} + setStoredValue(newValue); + }, + [key], + ); + + return [storedValue, setValue]; +} diff --git a/web/src/views/live/LiveCameraView.tsx b/web/src/views/live/LiveCameraView.tsx index 033b0d71c..cd7190e86 100644 --- a/web/src/views/live/LiveCameraView.tsx +++ b/web/src/views/live/LiveCameraView.tsx @@ -78,6 +78,7 @@ import { useNavigate } from "react-router-dom"; import { TransformWrapper, TransformComponent } from "react-zoom-pan-pinch"; import useSWR from "swr"; import { cn } from "@/lib/utils"; +import { useSessionPersistence } from "@/hooks/use-session-persistence"; type LiveCameraViewProps = { config?: FrigateConfig; @@ -194,7 +195,7 @@ export default function LiveCameraView({ // playback state - const [audio, setAudio] = useState(false); + const [audio, setAudio] = useSessionPersistence("liveAudio", false); const [mic, setMic] = useState(false); const [webRTC, setWebRTC] = useState(false); const [pip, setPip] = useState(false); @@ -404,7 +405,7 @@ export default function LiveCameraView({ className="p-2 md:p-0" variant={fullscreen ? "overlay" : "primary"} Icon={audio ? GiSpeaker : GiSpeakerOff} - isActive={audio} + isActive={audio ?? false} title={`${audio ? "Disable" : "Enable"} Camera Audio`} onClick={() => setAudio(!audio)} /> From b01ce31903fc4094bb177a6dfc45feb7a3c71382 Mon Sep 17 00:00:00 2001 From: "Soren L. Hansen" Date: Fri, 9 Aug 2024 06:26:26 -0700 Subject: [PATCH 31/81] Fix auth when serving Frigate at a subpath (#12815) Ensure axios.defaults.baseURL is set when accessing login form. Drop `/api` prefix in login form's `axios.post` call, since `/api` is part of the baseURL. Redirect to subpath on succesful authentication. Prepend subpath to default logout url. Fixes #12814 --- web/src/components/auth/AuthForm.tsx | 5 +++-- web/src/components/menu/AccountSettings.tsx | 3 ++- web/src/login.tsx | 1 + 3 files changed, 6 insertions(+), 3 deletions(-) diff --git a/web/src/components/auth/AuthForm.tsx b/web/src/components/auth/AuthForm.tsx index 805085774..f3a435828 100644 --- a/web/src/components/auth/AuthForm.tsx +++ b/web/src/components/auth/AuthForm.tsx @@ -2,6 +2,7 @@ import * as React from "react"; +import { baseUrl } from "../../api/baseUrl"; import { cn } from "@/lib/utils"; import { Input } from "@/components/ui/input"; import { Button } from "@/components/ui/button"; @@ -43,7 +44,7 @@ export function UserAuthForm({ className, ...props }: UserAuthFormProps) { setIsLoading(true); try { await axios.post( - "/api/login", + "/login", { user: values.user, password: values.password, @@ -54,7 +55,7 @@ export function UserAuthForm({ className, ...props }: UserAuthFormProps) { }, }, ); - window.location.href = "/"; + window.location.href = baseUrl; } catch (error) { if (axios.isAxiosError(error)) { const err = error as AxiosError; diff --git a/web/src/components/menu/AccountSettings.tsx b/web/src/components/menu/AccountSettings.tsx index 20c852e65..1b7470b9b 100644 --- a/web/src/components/menu/AccountSettings.tsx +++ b/web/src/components/menu/AccountSettings.tsx @@ -3,6 +3,7 @@ import { TooltipContent, TooltipTrigger, } from "@/components/ui/tooltip"; +import { baseUrl } from "../../api/baseUrl"; import { cn } from "@/lib/utils"; import { TooltipPortal } from "@radix-ui/react-tooltip"; import { isDesktop } from "react-device-detect"; @@ -26,7 +27,7 @@ type AccountSettingsProps = { export default function AccountSettings({ className }: AccountSettingsProps) { const { data: profile } = useSWR("profile"); const { data: config } = useSWR("config"); - const logoutUrl = config?.proxy?.logout_url || "/api/logout"; + const logoutUrl = config?.proxy?.logout_url || `${baseUrl}api/logout`; const Container = isDesktop ? DropdownMenu : Drawer; const Trigger = isDesktop ? DropdownMenuTrigger : DrawerTrigger; diff --git a/web/src/login.tsx b/web/src/login.tsx index cea5e3e42..4906b58c6 100644 --- a/web/src/login.tsx +++ b/web/src/login.tsx @@ -1,6 +1,7 @@ import React from "react"; import ReactDOM from "react-dom/client"; import LoginPage from "@/pages/LoginPage.tsx"; +import "@/api"; import "./index.css"; ReactDOM.createRoot(document.getElementById("root")!).render( From 4ecc0e15cebf42b17fe61e4996ff91001edb6e1b Mon Sep 17 00:00:00 2001 From: Nicolas Mowen Date: Fri, 9 Aug 2024 07:29:35 -0600 Subject: [PATCH 32/81] Add button to mark review item as reviewed in filmstrip (#12878) * Add button to mark review item as reviewd in filmstrip * Add tooltip --- web/src/components/card/AnimatedEventCard.tsx | 26 ++++++++++++++++++- web/src/views/live/LiveDashboardView.tsx | 1 + 2 files changed, 26 insertions(+), 1 deletion(-) diff --git a/web/src/components/card/AnimatedEventCard.tsx b/web/src/components/card/AnimatedEventCard.tsx index 552aa9f26..85d6eb6d4 100644 --- a/web/src/components/card/AnimatedEventCard.tsx +++ b/web/src/components/card/AnimatedEventCard.tsx @@ -12,17 +12,21 @@ import { isCurrentHour } from "@/utils/dateUtil"; import { useCameraPreviews } from "@/hooks/use-camera-previews"; import { baseUrl } from "@/api/baseUrl"; import { useApiHost } from "@/api"; -import { isSafari } from "react-device-detect"; +import { isDesktop, isSafari } from "react-device-detect"; import { usePersistence } from "@/hooks/use-persistence"; import { Skeleton } from "../ui/skeleton"; +import { Button } from "../ui/button"; +import { FaCircleCheck } from "react-icons/fa6"; type AnimatedEventCardProps = { event: ReviewSegment; selectedGroup?: string; + updateEvents: () => void; }; export function AnimatedEventCard({ event, selectedGroup, + updateEvents, }: AnimatedEventCardProps) { const { data: config } = useSWR("config"); const apiHost = useApiHost(); @@ -59,6 +63,7 @@ export function AnimatedEventCard({ }, [visibilityListener]); const [isLoaded, setIsLoaded] = useState(false); + const [isHovered, setIsHovered] = useState(false); // interaction @@ -102,7 +107,26 @@ export function AnimatedEventCard({ style={{ aspectRatio: aspectRatio, }} + onMouseEnter={isDesktop ? () => setIsHovered(true) : undefined} + onMouseLeave={isDesktop ? () => setIsHovered(false) : undefined} > + {isHovered && ( + + + + + Mark as Reviewed + + )}
); })} From e86788034dd7a641edc7ee39539caa550fc13409 Mon Sep 17 00:00:00 2001 From: Nicolas Mowen Date: Fri, 9 Aug 2024 15:59:55 -0600 Subject: [PATCH 33/81] Fix use experimental migrator (#12906) --- frigate/util/config.py | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/frigate/util/config.py b/frigate/util/config.py index acb7a9cb9..e7744e56d 100644 --- a/frigate/util/config.py +++ b/frigate/util/config.py @@ -90,7 +90,7 @@ def migrate_014(config: dict[str, dict[str, any]]) -> dict[str, dict[str, any]]: # Remove UI fields if new_config.get("ui"): if new_config["ui"].get("use_experimental"): - del new_config["ui"]["experimental"] + del new_config["ui"]["use_experimental"] if new_config["ui"].get("live_mode"): del new_config["ui"]["live_mode"] From 8f2cbe261b28d52febb28a4b379a265da8b2f8fb Mon Sep 17 00:00:00 2001 From: Nicolas Mowen Date: Fri, 9 Aug 2024 16:12:07 -0600 Subject: [PATCH 34/81] Web deps (#12908) * Update web compnent deps * Update other web deps --- web/package-lock.json | 424 +++++++++++++++++++++--------------------- web/package.json | 32 ++-- 2 files changed, 232 insertions(+), 224 deletions(-) diff --git a/web/package-lock.json b/web/package-lock.json index b49d6ad83..6b8a0d2a4 100644 --- a/web/package-lock.json +++ b/web/package-lock.json @@ -8,7 +8,7 @@ "name": "web-new", "version": "0.0.0", "dependencies": { - "@cycjimmy/jsmpeg-player": "^6.0.5", + "@cycjimmy/jsmpeg-player": "^6.1.1", "@hookform/resolvers": "^3.9.0", "@radix-ui/react-alert-dialog": "^1.1.1", "@radix-ui/react-aspect-ratio": "^1.1.0", @@ -30,19 +30,19 @@ "@radix-ui/react-toggle": "^1.1.0", "@radix-ui/react-toggle-group": "^1.1.0", "@radix-ui/react-tooltip": "^1.1.2", - "apexcharts": "^3.50.0", - "axios": "^1.7.2", + "apexcharts": "^3.52.0", + "axios": "^1.7.3", "class-variance-authority": "^0.7.0", "clsx": "^2.1.1", "copy-to-clipboard": "^3.3.3", "date-fns": "^3.6.0", - "hls.js": "^1.5.13", + "hls.js": "^1.5.14", "idb-keyval": "^6.2.1", "immer": "^10.1.1", - "konva": "^9.3.13", + "konva": "^9.3.14", "lodash": "^4.17.21", "lucide-react": "^0.407.0", - "monaco-yaml": "^5.1.1", + "monaco-yaml": "^5.2.2", "next-themes": "^0.3.0", "nosleep.js": "^0.12.0", "react": "^18.3.1", @@ -54,7 +54,7 @@ "react-hook-form": "^7.52.1", "react-icons": "^5.2.1", "react-konva": "^18.2.10", - "react-router-dom": "^6.24.1", + "react-router-dom": "^6.26.0", "react-swipeable": "^7.0.1", "react-tracked": "^2.0.0", "react-transition-group": "^4.4.5", @@ -76,7 +76,7 @@ "devDependencies": { "@tailwindcss/forms": "^0.5.7", "@testing-library/jest-dom": "^6.4.6", - "@types/lodash": "^4.17.6", + "@types/lodash": "^4.17.7", "@types/node": "^20.14.10", "@types/react": "^18.3.2", "@types/react-dom": "^18.3.0", @@ -87,8 +87,8 @@ "@typescript-eslint/eslint-plugin": "^7.5.0", "@typescript-eslint/parser": "^7.5.0", "@vitejs/plugin-react-swc": "^3.6.0", - "@vitest/coverage-v8": "^2.0.2", - "autoprefixer": "^10.4.19", + "@vitest/coverage-v8": "^2.0.5", + "autoprefixer": "^10.4.20", "eslint": "^8.57.0", "eslint-config-prettier": "^9.1.0", "eslint-plugin-jest": "^28.2.0", @@ -98,15 +98,15 @@ "eslint-plugin-vitest-globals": "^1.5.0", "fake-indexeddb": "^6.0.0", "jest-websocket-mock": "^2.5.0", - "jsdom": "^24.0.0", - "msw": "^2.3.0", + "jsdom": "^24.1.1", + "msw": "^2.3.5", "postcss": "^8.4.39", "prettier": "^3.3.2", "prettier-plugin-tailwindcss": "^0.6.5", - "tailwindcss": "^3.4.3", - "typescript": "^5.5.3", - "vite": "^5.3.3", - "vitest": "^2.0.2" + "tailwindcss": "^3.4.9", + "typescript": "^5.5.4", + "vite": "^5.4.0", + "vitest": "^2.0.5" } }, "node_modules/@aashutoshrathi/word-wrap": { @@ -233,10 +233,22 @@ "statuses": "^2.0.1" } }, + "node_modules/@bundled-es-modules/tough-cookie": { + "version": "0.1.6", + "resolved": "https://registry.npmjs.org/@bundled-es-modules/tough-cookie/-/tough-cookie-0.1.6.tgz", + "integrity": "sha512-dvMHbL464C0zI+Yqxbz6kZ5TOEp7GLW+pry/RWndAR8MJQAXZ2rPmIs8tziTZjeIyhSNZgZbCePtfSbdWqStJw==", + "dev": true, + "license": "ISC", + "dependencies": { + "@types/tough-cookie": "^4.0.5", + "tough-cookie": "^4.1.4" + } + }, "node_modules/@cycjimmy/jsmpeg-player": { - "version": "6.0.5", - "resolved": "https://registry.npmjs.org/@cycjimmy/jsmpeg-player/-/jsmpeg-player-6.0.5.tgz", - "integrity": "sha512-bVNHQ7VN9ecKT5AI/6RC7zpW/y4ca68a9txeR5Wiin+jKpUn/7buMe+5NPub89A8NNeNnKPQfrD2+c76ch36mA==" + "version": "6.1.1", + "resolved": "https://registry.npmjs.org/@cycjimmy/jsmpeg-player/-/jsmpeg-player-6.1.1.tgz", + "integrity": "sha512-YqT7U/3LxGu+6ikd+GGPe3rA2o6P4xrBHsWi/WRqv4n58h91fWDxS/3smneod+u6H2RnWlmXvZqx960dQ9T9gQ==", + "license": "MIT" }, "node_modules/@esbuild/aix-ppc64": { "version": "0.21.5", @@ -986,15 +998,6 @@ "@jridgewell/sourcemap-codec": "^1.4.14" } }, - "node_modules/@mswjs/cookies": { - "version": "1.1.0", - "resolved": "https://registry.npmjs.org/@mswjs/cookies/-/cookies-1.1.0.tgz", - "integrity": "sha512-0ZcCVQxifZmhwNBoQIrystCb+2sWBY2Zw8lpfJBPCHGCA/HWqehITeCRVIv4VMy8MPlaHo2w2pTHFV2pFfqKPw==", - "dev": true, - "engines": { - "node": ">=18" - } - }, "node_modules/@mswjs/interceptors": { "version": "0.29.1", "resolved": "https://registry.npmjs.org/@mswjs/interceptors/-/interceptors-0.29.1.tgz", @@ -2200,9 +2203,9 @@ "license": "MIT" }, "node_modules/@remix-run/router": { - "version": "1.17.1", - "resolved": "https://registry.npmjs.org/@remix-run/router/-/router-1.17.1.tgz", - "integrity": "sha512-mCOMec4BKd6BRGBZeSnGiIgwsbLGp3yhVqAD8H+PxiRNEHgDpZb8J1TnrSDlg97t0ySKMQJTHCWBCmBpSmkF6Q==", + "version": "1.19.0", + "resolved": "https://registry.npmjs.org/@remix-run/router/-/router-1.19.0.tgz", + "integrity": "sha512-zDICCLKEwbVYTS6TjYaWtHXxkdoUvD/QXvyVZjGCsWz5vyH7aFeONlPffPdW+Y/t6KT0MgXb2Mfjun9YpWN1dA==", "license": "MIT", "engines": { "node": ">=14.0.0" @@ -2692,15 +2695,10 @@ "integrity": "sha512-/kYRxGDLWzHOB7q+wtSUQlFrtcdUccpfy+X+9iMBpHK8QLLhx2wIPYuS5DYtR9Wa/YlZAbIovy7qVdB1Aq6Lyw==", "dev": true }, - "node_modules/@types/json-schema": { - "version": "7.0.15", - "resolved": "https://registry.npmjs.org/@types/json-schema/-/json-schema-7.0.15.tgz", - "integrity": "sha512-5+fP8P8MFNC+AyZCDxrB2pkZFPGzqQWUzpSeuuVLvm8VMcorNYavBqoFcxK8bQz4Qsbn4oUEEem4wDLfcysGHA==" - }, "node_modules/@types/lodash": { - "version": "4.17.6", - "resolved": "https://registry.npmjs.org/@types/lodash/-/lodash-4.17.6.tgz", - "integrity": "sha512-OpXEVoCKSS3lQqjx9GGGOapBeuW5eUboYHRlHP9urXPX25IKZ6AnP5ZRxtVf63iieUbsHxLn8NQ5Nlftc6yzAA==", + "version": "4.17.7", + "resolved": "https://registry.npmjs.org/@types/lodash/-/lodash-4.17.7.tgz", + "integrity": "sha512-8wTvZawATi/lsmNu10/j2hk1KEP0IvjubqPE3cu1Xz7xfXXt5oCq3SNUz4fMIP4XGF9Ky+Ue2tBA3hcS7LSBlA==", "dev": true, "license": "MIT" }, @@ -2795,6 +2793,13 @@ "integrity": "sha512-QIvDlGAKyF3YJbT3QZnfC+RIvV5noyDbi+ZJ5rkaSRqxCGrYJefgXm3leZAjtoQOutZe1hCXbAg+p89/Vj4HlQ==", "dev": true }, + "node_modules/@types/tough-cookie": { + "version": "4.0.5", + "resolved": "https://registry.npmjs.org/@types/tough-cookie/-/tough-cookie-4.0.5.tgz", + "integrity": "sha512-/Ad8+nIOV7Rl++6f1BdKxFSMgmoqEoYbHRpPcx3JEfv8VRsQe9Z4mCXeJBzxs7mbHY/XOZZuXlRNfhpVPbs6ZA==", + "dev": true, + "license": "MIT" + }, "node_modules/@types/wrap-ansi": { "version": "3.0.0", "resolved": "https://registry.npmjs.org/@types/wrap-ansi/-/wrap-ansi-3.0.0.tgz", @@ -3041,9 +3046,9 @@ } }, "node_modules/@vitest/coverage-v8": { - "version": "2.0.2", - "resolved": "https://registry.npmjs.org/@vitest/coverage-v8/-/coverage-v8-2.0.2.tgz", - "integrity": "sha512-iA8eb4PMid3bMc++gfQSTvYE1QL//fC8pz+rKsTUDBFjdDiy/gH45hvpqyDu5K7FHhvgG0GNNCJzTMMSFKhoxg==", + "version": "2.0.5", + "resolved": "https://registry.npmjs.org/@vitest/coverage-v8/-/coverage-v8-2.0.5.tgz", + "integrity": "sha512-qeFcySCg5FLO2bHHSa0tAZAOnAUbp4L6/A5JDuj9+bt53JREl8hpLjLHEWF0e/gWc8INVpJaqA7+Ene2rclpZg==", "dev": true, "license": "MIT", "dependencies": { @@ -3057,7 +3062,6 @@ "magic-string": "^0.30.10", "magicast": "^0.3.4", "std-env": "^3.7.0", - "strip-literal": "^2.1.0", "test-exclude": "^7.0.1", "tinyrainbow": "^1.2.0" }, @@ -3065,18 +3069,18 @@ "url": "https://opencollective.com/vitest" }, "peerDependencies": { - "vitest": "2.0.2" + "vitest": "2.0.5" } }, "node_modules/@vitest/expect": { - "version": "2.0.2", - "resolved": "https://registry.npmjs.org/@vitest/expect/-/expect-2.0.2.tgz", - "integrity": "sha512-nKAvxBYqcDugYZ4nJvnm5OR8eDJdgWjk4XM9owQKUjzW70q0icGV2HVnQOyYsp906xJaBDUXw0+9EHw2T8e0mQ==", + "version": "2.0.5", + "resolved": "https://registry.npmjs.org/@vitest/expect/-/expect-2.0.5.tgz", + "integrity": "sha512-yHZtwuP7JZivj65Gxoi8upUN2OzHTi3zVfjwdpu2WrvCZPLwsJ2Ey5ILIPccoW23dd/zQBlJ4/dhi7DWNyXCpA==", "dev": true, "license": "MIT", "dependencies": { - "@vitest/spy": "2.0.2", - "@vitest/utils": "2.0.2", + "@vitest/spy": "2.0.5", + "@vitest/utils": "2.0.5", "chai": "^5.1.1", "tinyrainbow": "^1.2.0" }, @@ -3085,9 +3089,9 @@ } }, "node_modules/@vitest/pretty-format": { - "version": "2.0.2", - "resolved": "https://registry.npmjs.org/@vitest/pretty-format/-/pretty-format-2.0.2.tgz", - "integrity": "sha512-SBCyOXfGVvddRd9r2PwoVR0fonQjh9BMIcBMlSzbcNwFfGr6ZhOhvBzurjvi2F4ryut2HcqiFhNeDVGwru8tLg==", + "version": "2.0.5", + "resolved": "https://registry.npmjs.org/@vitest/pretty-format/-/pretty-format-2.0.5.tgz", + "integrity": "sha512-h8k+1oWHfwTkyTkb9egzwNMfJAEx4veaPSnMeKbVSjp4euqGSbQlm5+6VHwTr7u4FJslVVsUG5nopCaAYdOmSQ==", "dev": true, "license": "MIT", "dependencies": { @@ -3098,13 +3102,13 @@ } }, "node_modules/@vitest/runner": { - "version": "2.0.2", - "resolved": "https://registry.npmjs.org/@vitest/runner/-/runner-2.0.2.tgz", - "integrity": "sha512-OCh437Vi8Wdbif1e0OvQcbfM3sW4s2lpmOjAE7qfLrpzJX2M7J1IQlNvEcb/fu6kaIB9n9n35wS0G2Q3en5kHg==", + "version": "2.0.5", + "resolved": "https://registry.npmjs.org/@vitest/runner/-/runner-2.0.5.tgz", + "integrity": "sha512-TfRfZa6Bkk9ky4tW0z20WKXFEwwvWhRY+84CnSEtq4+3ZvDlJyY32oNTJtM7AW9ihW90tX/1Q78cb6FjoAs+ig==", "dev": true, "license": "MIT", "dependencies": { - "@vitest/utils": "2.0.2", + "@vitest/utils": "2.0.5", "pathe": "^1.1.2" }, "funding": { @@ -3112,13 +3116,13 @@ } }, "node_modules/@vitest/snapshot": { - "version": "2.0.2", - "resolved": "https://registry.npmjs.org/@vitest/snapshot/-/snapshot-2.0.2.tgz", - "integrity": "sha512-Yc2ewhhZhx+0f9cSUdfzPRcsM6PhIb+S43wxE7OG0kTxqgqzo8tHkXFuFlndXeDMp09G3sY/X5OAo/RfYydf1g==", + "version": "2.0.5", + "resolved": "https://registry.npmjs.org/@vitest/snapshot/-/snapshot-2.0.5.tgz", + "integrity": "sha512-SgCPUeDFLaM0mIUHfaArq8fD2WbaXG/zVXjRupthYfYGzc8ztbFbu6dUNOblBG7XLMR1kEhS/DNnfCZ2IhdDew==", "dev": true, "license": "MIT", "dependencies": { - "@vitest/pretty-format": "2.0.2", + "@vitest/pretty-format": "2.0.5", "magic-string": "^0.30.10", "pathe": "^1.1.2" }, @@ -3127,9 +3131,9 @@ } }, "node_modules/@vitest/spy": { - "version": "2.0.2", - "resolved": "https://registry.npmjs.org/@vitest/spy/-/spy-2.0.2.tgz", - "integrity": "sha512-MgwJ4AZtCgqyp2d7WcQVE8aNG5vQ9zu9qMPYQHjsld/QVsrvg78beNrXdO4HYkP0lDahCO3P4F27aagIag+SGQ==", + "version": "2.0.5", + "resolved": "https://registry.npmjs.org/@vitest/spy/-/spy-2.0.5.tgz", + "integrity": "sha512-c/jdthAhvJdpfVuaexSrnawxZz6pywlTPe84LUB2m/4t3rl2fTo9NFGBG4oWgaD+FTgDDV8hJ/nibT7IfH3JfA==", "dev": true, "license": "MIT", "dependencies": { @@ -3140,13 +3144,13 @@ } }, "node_modules/@vitest/utils": { - "version": "2.0.2", - "resolved": "https://registry.npmjs.org/@vitest/utils/-/utils-2.0.2.tgz", - "integrity": "sha512-pxCY1v7kmOCWYWjzc0zfjGTA3Wmn8PKnlPvSrsA643P1NHl1fOyXj2Q9SaNlrlFE+ivCsxM80Ov3AR82RmHCWQ==", + "version": "2.0.5", + "resolved": "https://registry.npmjs.org/@vitest/utils/-/utils-2.0.5.tgz", + "integrity": "sha512-d8HKbqIcya+GR67mkZbrzhS5kKhtp8dQLcmRZLGTscGVg7yImT82cIrhtn2L8+VujWcy6KZweApgNmPsTAO/UQ==", "dev": true, "license": "MIT", "dependencies": { - "@vitest/pretty-format": "2.0.2", + "@vitest/pretty-format": "2.0.5", "estree-walker": "^3.0.3", "loupe": "^3.1.1", "tinyrainbow": "^1.2.0" @@ -3281,9 +3285,9 @@ } }, "node_modules/apexcharts": { - "version": "3.50.0", - "resolved": "https://registry.npmjs.org/apexcharts/-/apexcharts-3.50.0.tgz", - "integrity": "sha512-LJT1PNAm+NoIU3aogL2P+ViC0y/Cjik54FdzzGV54UNnGQLBoLe5ok3fxsJDTgyez45BGYT8gqNpYKqhdfy5sg==", + "version": "3.52.0", + "resolved": "https://registry.npmjs.org/apexcharts/-/apexcharts-3.52.0.tgz", + "integrity": "sha512-7dg0ADKs8AA89iYMZMe2sFDG0XK5PfqllKV9N+i3hKHm3vEtdhwz8AlXGm+/b0nJ6jKiaXsqci5LfVxNhtB+dA==", "license": "MIT", "dependencies": { "@yr/monotone-cubic-spline": "^1.0.3", @@ -3353,9 +3357,9 @@ "integrity": "sha512-Oei9OH4tRh0YqU3GxhX79dM/mwVgvbZJaSNaRk+bshkj0S5cfHcgYakreBjrHwatXKbz+IoIdYLxrKim2MjW0Q==" }, "node_modules/autoprefixer": { - "version": "10.4.19", - "resolved": "https://registry.npmjs.org/autoprefixer/-/autoprefixer-10.4.19.tgz", - "integrity": "sha512-BaENR2+zBZ8xXhM4pUaKUxlVdxZ0EZhjvbopwnXmxRUfqDmwSpC2lAi/QXvx7NRdPCo1WKEcEF6mV64si1z4Ew==", + "version": "10.4.20", + "resolved": "https://registry.npmjs.org/autoprefixer/-/autoprefixer-10.4.20.tgz", + "integrity": "sha512-XY25y5xSv/wEoqzDyXXME4AFfkZI0P23z6Fs3YgymDnKJkCGOnkL0iTxCa85UTqaSgfcqyf3UA6+c7wUvx/16g==", "dev": true, "funding": [ { @@ -3371,12 +3375,13 @@ "url": "https://github.com/sponsors/ai" } ], + "license": "MIT", "dependencies": { - "browserslist": "^4.23.0", - "caniuse-lite": "^1.0.30001599", + "browserslist": "^4.23.3", + "caniuse-lite": "^1.0.30001646", "fraction.js": "^4.3.7", "normalize-range": "^0.1.2", - "picocolors": "^1.0.0", + "picocolors": "^1.0.1", "postcss-value-parser": "^4.2.0" }, "bin": { @@ -3390,9 +3395,10 @@ } }, "node_modules/axios": { - "version": "1.7.2", - "resolved": "https://registry.npmjs.org/axios/-/axios-1.7.2.tgz", - "integrity": "sha512-2A8QhOMrbomlDuiLeK9XibIBzuHeRcqqNOHp0Cyp5EoJ1IFDh+XZH3A6BkXtv0K4gFGCI0Y4BM7B1wOEi0Rmgw==", + "version": "1.7.3", + "resolved": "https://registry.npmjs.org/axios/-/axios-1.7.3.tgz", + "integrity": "sha512-Ar7ND9pU99eJ9GpoGQKhKf58GpUOgnzuaB7ueNQ5BMi0p+LZ5oaEnfF999fAArcTIBwXTCHAmGcHOZJaWPq9Nw==", + "license": "MIT", "dependencies": { "follow-redirects": "^1.15.6", "form-data": "^4.0.0", @@ -3454,9 +3460,9 @@ } }, "node_modules/browserslist": { - "version": "4.23.0", - "resolved": "https://registry.npmjs.org/browserslist/-/browserslist-4.23.0.tgz", - "integrity": "sha512-QW8HiM1shhT2GuzkvklfjcKDiWFXHOeFCIA/huJPwHsslwcydgk7X+z2zXpEijP98UCY7HbubZt5J2Zgvf0CaQ==", + "version": "4.23.3", + "resolved": "https://registry.npmjs.org/browserslist/-/browserslist-4.23.3.tgz", + "integrity": "sha512-btwCFJVjI4YWDNfau8RhZ+B1Q/VLoUITrm3RlP6y1tYGWIOa+InuYiRGXUBXo8nA1qKmHMyLB/iVQg5TT4eFoA==", "dev": true, "funding": [ { @@ -3472,11 +3478,12 @@ "url": "https://github.com/sponsors/ai" } ], + "license": "MIT", "dependencies": { - "caniuse-lite": "^1.0.30001587", - "electron-to-chromium": "^1.4.668", - "node-releases": "^2.0.14", - "update-browserslist-db": "^1.0.13" + "caniuse-lite": "^1.0.30001646", + "electron-to-chromium": "^1.5.4", + "node-releases": "^2.0.18", + "update-browserslist-db": "^1.1.0" }, "bin": { "browserslist": "cli.js" @@ -3529,9 +3536,9 @@ } }, "node_modules/caniuse-lite": { - "version": "1.0.30001599", - "resolved": "https://registry.npmjs.org/caniuse-lite/-/caniuse-lite-1.0.30001599.tgz", - "integrity": "sha512-LRAQHZ4yT1+f9LemSMeqdMpMxZcc4RMWdj4tiFe3G8tNkWK+E58g+/tzotb5cU6TbcVJLr4fySiAW7XmxQvZQA==", + "version": "1.0.30001651", + "resolved": "https://registry.npmjs.org/caniuse-lite/-/caniuse-lite-1.0.30001651.tgz", + "integrity": "sha512-9Cf+Xv1jJNe1xPZLGuUXLNkE1BoDkqRqYyFJ9TDYSqhduqA4hu4oR9HluGoWYQC/aj8WHjsGVV+bwkh0+tegRg==", "dev": true, "funding": [ { @@ -3546,7 +3553,8 @@ "type": "github", "url": "https://github.com/sponsors/ai" } - ] + ], + "license": "CC-BY-4.0" }, "node_modules/chai": { "version": "5.1.1", @@ -4073,10 +4081,11 @@ "license": "MIT" }, "node_modules/electron-to-chromium": { - "version": "1.4.692", - "resolved": "https://registry.npmjs.org/electron-to-chromium/-/electron-to-chromium-1.4.692.tgz", - "integrity": "sha512-d5rZRka9n2Y3MkWRN74IoAsxR0HK3yaAt7T50e3iT9VZmCCQDT3geXUO5ZRMhDToa1pkCeQXuNo+0g+NfDOVPA==", - "dev": true + "version": "1.5.5", + "resolved": "https://registry.npmjs.org/electron-to-chromium/-/electron-to-chromium-1.5.5.tgz", + "integrity": "sha512-QR7/A7ZkMS8tZuoftC/jfqNkZLQO779SSW3YuZHP4eXpj3EffGLFcB/Xu9AAZQzLccTiCV+EmUo3ha4mQ9wnlA==", + "dev": true, + "license": "ISC" }, "node_modules/emoji-regex": { "version": "8.0.0", @@ -4136,10 +4145,11 @@ } }, "node_modules/escalade": { - "version": "3.1.1", - "resolved": "https://registry.npmjs.org/escalade/-/escalade-3.1.1.tgz", - "integrity": "sha512-k0er2gUkLf8O0zKJiAhmkTnJlTvINGv7ygDNPbeIsX/TJjGJZHuh9B2UxbsaEkmlEo9MfhrSzmhIlhRlI2GXnw==", + "version": "3.1.2", + "resolved": "https://registry.npmjs.org/escalade/-/escalade-3.1.2.tgz", + "integrity": "sha512-ErCHMCae19vR8vQGe50xIsVomy19rg6gFu3+r3jkEO46suLMWBksvVyoGgQV+jOfl84ZSOSlmv6Gxa89PmTGmA==", "dev": true, + "license": "MIT", "engines": { "node": ">=6" } @@ -4859,9 +4869,9 @@ "dev": true }, "node_modules/hls.js": { - "version": "1.5.13", - "resolved": "https://registry.npmjs.org/hls.js/-/hls.js-1.5.13.tgz", - "integrity": "sha512-xRgKo84nsC7clEvSfIdgn/Tc0NOT+d7vdiL/wvkLO+0k0juc26NRBPPG1SfB8pd5bHXIjMW/F5VM8VYYkOYYdw==", + "version": "1.5.14", + "resolved": "https://registry.npmjs.org/hls.js/-/hls.js-1.5.14.tgz", + "integrity": "sha512-5wLiQ2kWJMui6oUslaq8PnPOv1vjuee5gTxjJD0DSsccY12OXtDT0h137UuqjczNeHzeEYR0ROZQibKNMr7Mzg==", "license": "Apache-2.0" }, "node_modules/html-encoding-sniffer": { @@ -4898,9 +4908,9 @@ } }, "node_modules/https-proxy-agent": { - "version": "7.0.4", - "resolved": "https://registry.npmjs.org/https-proxy-agent/-/https-proxy-agent-7.0.4.tgz", - "integrity": "sha512-wlwpilI7YdjSkWaQ/7omYBMTliDcmCN8OLihO6I9B86g06lMyAoqgoDpV0XqoaPOKj+0DIdAvnsWfyAAhmimcg==", + "version": "7.0.5", + "resolved": "https://registry.npmjs.org/https-proxy-agent/-/https-proxy-agent-7.0.5.tgz", + "integrity": "sha512-1e4Wqeblerz+tMKPIq2EMGiiWW1dIjZOksyHWSUm1rmuvw/how9hBHZ38lAGj5ID4Ik6EdkOw7NmWPy6LAwalw==", "dev": true, "license": "MIT", "dependencies": { @@ -5301,9 +5311,9 @@ } }, "node_modules/jsdom": { - "version": "24.1.0", - "resolved": "https://registry.npmjs.org/jsdom/-/jsdom-24.1.0.tgz", - "integrity": "sha512-6gpM7pRXCwIOKxX47cgOyvyQDN/Eh0f1MeKySBV2xGdKtqJBLj8P25eY3EVCWo2mglDDzozR2r2MW4T+JiNUZA==", + "version": "24.1.1", + "resolved": "https://registry.npmjs.org/jsdom/-/jsdom-24.1.1.tgz", + "integrity": "sha512-5O1wWV99Jhq4DV7rCLIoZ/UIhyQeDR7wHVyZAHAshbrvZsLs+Xzz7gtwnlJTJDjleiTKh54F4dXrX70vJQTyJQ==", "dev": true, "license": "MIT", "dependencies": { @@ -5313,11 +5323,11 @@ "form-data": "^4.0.0", "html-encoding-sniffer": "^4.0.0", "http-proxy-agent": "^7.0.2", - "https-proxy-agent": "^7.0.4", + "https-proxy-agent": "^7.0.5", "is-potential-custom-element-name": "^1.0.1", - "nwsapi": "^2.2.10", + "nwsapi": "^2.2.12", "parse5": "^7.1.2", - "rrweb-cssom": "^0.7.0", + "rrweb-cssom": "^0.7.1", "saxes": "^6.0.0", "symbol-tree": "^3.2.4", "tough-cookie": "^4.1.4", @@ -5326,7 +5336,7 @@ "whatwg-encoding": "^3.1.1", "whatwg-mimetype": "^4.0.0", "whatwg-url": "^14.0.0", - "ws": "^8.17.0", + "ws": "^8.18.0", "xml-name-validator": "^5.0.0" }, "engines": { @@ -5342,9 +5352,9 @@ } }, "node_modules/jsdom/node_modules/rrweb-cssom": { - "version": "0.7.0", - "resolved": "https://registry.npmjs.org/rrweb-cssom/-/rrweb-cssom-0.7.0.tgz", - "integrity": "sha512-KlSv0pm9kgQSRxXEMgtivPJ4h826YHsuob8pSHcfSZsSXGtvpEAie8S0AnXuObEJ7nhikOb4ahwxDm0H2yW17g==", + "version": "0.7.1", + "resolved": "https://registry.npmjs.org/rrweb-cssom/-/rrweb-cssom-0.7.1.tgz", + "integrity": "sha512-TrEMa7JGdVm0UThDJSx7ddw5nVm3UJS9o9CCIZ72B1vSyEZoziDqBYP3XIoi/12lKrJR8rE3jeFHMok2F/Mnsg==", "dev": true, "license": "MIT" }, @@ -5384,9 +5394,9 @@ } }, "node_modules/konva": { - "version": "9.3.13", - "resolved": "https://registry.npmjs.org/konva/-/konva-9.3.13.tgz", - "integrity": "sha512-hs0ysHnqjK9noZ/rkfDNJINfbNhkXMgjgkJ8uc6vU0amu05mSDtRlukz5kKHOaSnWHA6miXcHJydvPABh18Y8A==", + "version": "9.3.14", + "resolved": "https://registry.npmjs.org/konva/-/konva-9.3.14.tgz", + "integrity": "sha512-Gmm5lyikGYJyogKQA7Fy6dKkfNh350V6DwfZkid0RVrGYP2cfCsxuMxgF5etKeCv7NjXYpJxKqi1dYkIkX/dcA==", "funding": [ { "type": "patreon", @@ -5642,9 +5652,10 @@ "peer": true }, "node_modules/monaco-languageserver-types": { - "version": "0.3.2", - "resolved": "https://registry.npmjs.org/monaco-languageserver-types/-/monaco-languageserver-types-0.3.2.tgz", - "integrity": "sha512-KiGVYK/DiX1pnacnOjGNlM85bhV3ZTyFlM+ce7B8+KpWCbF1XJVovu51YyuGfm+K7+K54mIpT4DFX16xmi+tYA==", + "version": "0.4.0", + "resolved": "https://registry.npmjs.org/monaco-languageserver-types/-/monaco-languageserver-types-0.4.0.tgz", + "integrity": "sha512-QQ3BZiU5LYkJElGncSNb5AKoJ/LCs6YBMCJMAz9EA7v+JaOdn3kx2cXpPTcZfKA5AEsR0vc97sAw+5mdNhVBmw==", + "license": "MIT", "dependencies": { "monaco-types": "^0.1.0", "vscode-languageserver-protocol": "^3.0.0", @@ -5669,6 +5680,7 @@ "version": "0.1.0", "resolved": "https://registry.npmjs.org/monaco-types/-/monaco-types-0.1.0.tgz", "integrity": "sha512-aWK7SN9hAqNYi0WosPoMjenMeXJjwCxDibOqWffyQ/qXdzB/86xshGQobRferfmNz7BSNQ8GB0MD0oby9/5fTQ==", + "license": "MIT", "funding": { "url": "https://github.com/sponsors/remcohaszing" } @@ -5682,13 +5694,16 @@ } }, "node_modules/monaco-yaml": { - "version": "5.1.1", - "resolved": "https://registry.npmjs.org/monaco-yaml/-/monaco-yaml-5.1.1.tgz", - "integrity": "sha512-BuZ0/ZCGjrPNRzYMZ/MoxH8F/SdM+mATENXnpOhDYABi1Eh+QvxSszEct+ACSCarZiwLvy7m6yEF/pvW8XJkyQ==", + "version": "5.2.2", + "resolved": "https://registry.npmjs.org/monaco-yaml/-/monaco-yaml-5.2.2.tgz", + "integrity": "sha512-NWO/UhtJATlIsqwWPzK7YfbcIvPo3riFGsUkaGxNJoGiNPOvHD8vZ83ecqMQGkHPOpgHtSbe94uokE1AJvpbyQ==", + "license": "MIT", + "workspaces": [ + "examples/*" + ], "dependencies": { - "@types/json-schema": "^7.0.0", "jsonc-parser": "^3.0.0", - "monaco-languageserver-types": "^0.3.0", + "monaco-languageserver-types": "^0.4.0", "monaco-marker-data-provider": "^1.0.0", "monaco-types": "^0.1.0", "monaco-worker-manager": "^2.0.0", @@ -5727,17 +5742,17 @@ "dev": true }, "node_modules/msw": { - "version": "2.3.1", - "resolved": "https://registry.npmjs.org/msw/-/msw-2.3.1.tgz", - "integrity": "sha512-ocgvBCLn/5l3jpl1lssIb3cniuACJLoOfZu01e3n5dbJrpA5PeeWn28jCLgQDNt6d7QT8tF2fYRzm9JoEHtiig==", + "version": "2.3.5", + "resolved": "https://registry.npmjs.org/msw/-/msw-2.3.5.tgz", + "integrity": "sha512-+GUI4gX5YC5Bv33epBrD+BGdmDvBg2XGruiWnI3GbIbRmMMBeZ5gs3mJ51OWSGHgJKztZ8AtZeYMMNMVrje2/Q==", "dev": true, "hasInstallScript": true, "license": "MIT", "dependencies": { "@bundled-es-modules/cookie": "^2.0.0", "@bundled-es-modules/statuses": "^1.0.1", + "@bundled-es-modules/tough-cookie": "^0.1.6", "@inquirer/confirm": "^3.0.0", - "@mswjs/cookies": "^1.1.0", "@mswjs/interceptors": "^0.29.0", "@open-draft/until": "^2.1.0", "@types/cookie": "^0.6.0", @@ -5834,10 +5849,11 @@ } }, "node_modules/node-releases": { - "version": "2.0.14", - "resolved": "https://registry.npmjs.org/node-releases/-/node-releases-2.0.14.tgz", - "integrity": "sha512-y10wOWt8yZpqXmOgRo77WaHEmhYQYGNA6y421PKsKYWEK8aW+cqAphborZDhqfyKrbZEN92CN1X2KbafY2s7Yw==", - "dev": true + "version": "2.0.18", + "resolved": "https://registry.npmjs.org/node-releases/-/node-releases-2.0.18.tgz", + "integrity": "sha512-d9VeXT4SJ7ZeOqGX6R5EM022wpL+eWPooLI+5UpWn2jCT1aosUQEhQP214x33Wkwx3JQMvIm+tIoVOdodFS40g==", + "dev": true, + "license": "MIT" }, "node_modules/normalize-path": { "version": "3.0.0", @@ -5889,9 +5905,9 @@ } }, "node_modules/nwsapi": { - "version": "2.2.10", - "resolved": "https://registry.npmjs.org/nwsapi/-/nwsapi-2.2.10.tgz", - "integrity": "sha512-QK0sRs7MKv0tKe1+5uZIQk/C8XGza4DAnztJG8iD+TpJIORARrCxczA738awHrZoHeTjSSoHqao2teO0dC/gFQ==", + "version": "2.2.12", + "resolved": "https://registry.npmjs.org/nwsapi/-/nwsapi-2.2.12.tgz", + "integrity": "sha512-qXDmcVlZV4XRtKFzddidpfVP4oMSGhga+xdMc25mv8kaLUHtgzCDhUxkrN8exkGdTlLNaXj7CV3GtON7zuGZ+w==", "dev": true, "license": "MIT" }, @@ -6165,9 +6181,9 @@ } }, "node_modules/postcss": { - "version": "8.4.39", - "resolved": "https://registry.npmjs.org/postcss/-/postcss-8.4.39.tgz", - "integrity": "sha512-0vzE+lAiG7hZl1/9I8yzKLx3aR9Xbof3fBHKunvMfOCYAtMhrsnccJY2iTURb9EZd5+pLuiNV9/c/GZJOHsgIw==", + "version": "8.4.41", + "resolved": "https://registry.npmjs.org/postcss/-/postcss-8.4.41.tgz", + "integrity": "sha512-TesUflQ0WKZqAvg52PWL6kHgLKP6xB6heTOdoYM0Wt2UHyxNa4K25EZZMgKns3BH1RLVbZCREPpLY0rhnNoHVQ==", "funding": [ { "type": "opencollective", @@ -6741,12 +6757,12 @@ } }, "node_modules/react-router": { - "version": "6.24.1", - "resolved": "https://registry.npmjs.org/react-router/-/react-router-6.24.1.tgz", - "integrity": "sha512-PTXFXGK2pyXpHzVo3rR9H7ip4lSPZZc0bHG5CARmj65fTT6qG7sTngmb6lcYu1gf3y/8KxORoy9yn59pGpCnpg==", + "version": "6.26.0", + "resolved": "https://registry.npmjs.org/react-router/-/react-router-6.26.0.tgz", + "integrity": "sha512-wVQq0/iFYd3iZ9H2l3N3k4PL8EEHcb0XlU2Na8nEwmiXgIUElEH6gaJDtUQxJ+JFzmIXaQjfdpcGWaM6IoQGxg==", "license": "MIT", "dependencies": { - "@remix-run/router": "1.17.1" + "@remix-run/router": "1.19.0" }, "engines": { "node": ">=14.0.0" @@ -6756,13 +6772,13 @@ } }, "node_modules/react-router-dom": { - "version": "6.24.1", - "resolved": "https://registry.npmjs.org/react-router-dom/-/react-router-dom-6.24.1.tgz", - "integrity": "sha512-U19KtXqooqw967Vw0Qcn5cOvrX5Ejo9ORmOtJMzYWtCT4/WOfFLIZGGsVLxcd9UkBO0mSTZtXqhZBsWlHr7+Sg==", + "version": "6.26.0", + "resolved": "https://registry.npmjs.org/react-router-dom/-/react-router-dom-6.26.0.tgz", + "integrity": "sha512-RRGUIiDtLrkX3uYcFiCIxKFWMcWQGMojpYZfcstc63A1+sSnVgILGIm9gNUA6na3Fm1QuPGSBQH2EMbAZOnMsQ==", "license": "MIT", "dependencies": { - "@remix-run/router": "1.17.1", - "react-router": "6.24.1" + "@remix-run/router": "1.19.0", + "react-router": "6.26.0" }, "engines": { "node": ">=14.0.0" @@ -7246,7 +7262,8 @@ "version": "2.0.0", "resolved": "https://registry.npmjs.org/siginfo/-/siginfo-2.0.0.tgz", "integrity": "sha512-ybx0WO1/8bSBLEWXZvEd7gMW3Sn3JFlW3TvX1nREbDLRNQNaeNN8WK0meBwPdAaOI7TtRRRJn/Es1zhrrCHu7g==", - "dev": true + "dev": true, + "license": "ISC" }, "node_modules/signal-exit": { "version": "4.1.0", @@ -7300,7 +7317,8 @@ "version": "0.0.2", "resolved": "https://registry.npmjs.org/stackback/-/stackback-0.0.2.tgz", "integrity": "sha512-1XMJE5fQo1jGH6Y/7ebnwPOBEkIEnT4QF32d5R1+VXdXveM0IBMJt8zfaxX1P3QhVwrYe+576+jkANtSS2mBbw==", - "dev": true + "dev": true, + "license": "MIT" }, "node_modules/statuses": { "version": "2.0.1", @@ -7426,26 +7444,6 @@ "url": "https://github.com/sponsors/sindresorhus" } }, - "node_modules/strip-literal": { - "version": "2.1.0", - "resolved": "https://registry.npmjs.org/strip-literal/-/strip-literal-2.1.0.tgz", - "integrity": "sha512-Op+UycaUt/8FbN/Z2TWPBLge3jWrP3xj10f3fnYxf052bKuS3EKs1ZQcVGjnEMdsNVAM+plXRdmjrZ/KgG3Skw==", - "dev": true, - "license": "MIT", - "dependencies": { - "js-tokens": "^9.0.0" - }, - "funding": { - "url": "https://github.com/sponsors/antfu" - } - }, - "node_modules/strip-literal/node_modules/js-tokens": { - "version": "9.0.0", - "resolved": "https://registry.npmjs.org/js-tokens/-/js-tokens-9.0.0.tgz", - "integrity": "sha512-WriZw1luRMlmV3LGJaR6QOJjWwgLUTf89OwT2lUOyjX2dJGBwgmIkbcz+7WFZjrZM635JOIR517++e/67CP9dQ==", - "dev": true, - "license": "MIT" - }, "node_modules/sucrase": { "version": "3.34.0", "resolved": "https://registry.npmjs.org/sucrase/-/sucrase-3.34.0.tgz", @@ -7648,9 +7646,9 @@ } }, "node_modules/tailwindcss": { - "version": "3.4.4", - "resolved": "https://registry.npmjs.org/tailwindcss/-/tailwindcss-3.4.4.tgz", - "integrity": "sha512-ZoyXOdJjISB7/BcLTR6SEsLgKtDStYyYZVLsUtWChO4Ps20CBad7lfJKVDiejocV4ME1hLmyY0WJE3hSDcmQ2A==", + "version": "3.4.9", + "resolved": "https://registry.npmjs.org/tailwindcss/-/tailwindcss-3.4.9.tgz", + "integrity": "sha512-1SEOvRr6sSdV5IDf9iC+NU4dhwdqzF4zKKq3sAbasUWHEM6lsMhX+eNN5gkPx1BvLFEnZQEUFbXnGj8Qlp83Pg==", "license": "MIT", "dependencies": { "@alloc/quick-lru": "^5.2.0", @@ -7931,9 +7929,9 @@ } }, "node_modules/typescript": { - "version": "5.5.3", - "resolved": "https://registry.npmjs.org/typescript/-/typescript-5.5.3.tgz", - "integrity": "sha512-/hreyEujaB0w76zKo6717l3L0o/qEUtRgdvUBvlkhoWeOVMjMuHNHk0BRBzikzuGDqNmPQbg5ifMEqsHLiIUcQ==", + "version": "5.5.4", + "resolved": "https://registry.npmjs.org/typescript/-/typescript-5.5.4.tgz", + "integrity": "sha512-Mtq29sKDAEYP7aljRgtPOpTvOfbwRWlS6dPRzwjdE+C0R4brX/GUyhHSecbHMFLNBLcJIPt9nl9yG5TZ1weH+Q==", "dev": true, "license": "Apache-2.0", "bin": { @@ -7992,9 +7990,9 @@ } }, "node_modules/update-browserslist-db": { - "version": "1.0.13", - "resolved": "https://registry.npmjs.org/update-browserslist-db/-/update-browserslist-db-1.0.13.tgz", - "integrity": "sha512-xebP81SNcPuNpPP3uzeW1NYXxI3rxyJzF3pD6sH4jE7o/IX+WtSpwnVU+qIsDPyk0d3hmFQ7mjqc6AtV604hbg==", + "version": "1.1.0", + "resolved": "https://registry.npmjs.org/update-browserslist-db/-/update-browserslist-db-1.1.0.tgz", + "integrity": "sha512-EdRAaAyk2cUE1wOf2DkEhzxqOQvFOoRJFNS6NeyJ01Gp2beMRpBAINjM2iDXE3KCuKhwnvHIQCJm6ThL2Z+HzQ==", "dev": true, "funding": [ { @@ -8010,9 +8008,10 @@ "url": "https://github.com/sponsors/ai" } ], + "license": "MIT", "dependencies": { - "escalade": "^3.1.1", - "picocolors": "^1.0.0" + "escalade": "^3.1.2", + "picocolors": "^1.0.1" }, "bin": { "update-browserslist-db": "cli.js" @@ -8120,14 +8119,14 @@ } }, "node_modules/vite": { - "version": "5.3.3", - "resolved": "https://registry.npmjs.org/vite/-/vite-5.3.3.tgz", - "integrity": "sha512-NPQdeCU0Dv2z5fu+ULotpuq5yfCS1BzKUIPhNbP3YBfAMGJXbt2nS+sbTFu+qchaqWTD+H3JK++nRwr6XIcp6A==", + "version": "5.4.0", + "resolved": "https://registry.npmjs.org/vite/-/vite-5.4.0.tgz", + "integrity": "sha512-5xokfMX0PIiwCMCMb9ZJcMyh5wbBun0zUzKib+L65vAZ8GY9ePZMXxFrHbr/Kyll2+LSCY7xtERPpxkBDKngwg==", "dev": true, "license": "MIT", "dependencies": { "esbuild": "^0.21.3", - "postcss": "^8.4.39", + "postcss": "^8.4.40", "rollup": "^4.13.0" }, "bin": { @@ -8147,6 +8146,7 @@ "less": "*", "lightningcss": "^1.21.0", "sass": "*", + "sass-embedded": "*", "stylus": "*", "sugarss": "*", "terser": "^5.4.0" @@ -8164,6 +8164,9 @@ "sass": { "optional": true }, + "sass-embedded": { + "optional": true + }, "stylus": { "optional": true }, @@ -8176,9 +8179,9 @@ } }, "node_modules/vite-node": { - "version": "2.0.2", - "resolved": "https://registry.npmjs.org/vite-node/-/vite-node-2.0.2.tgz", - "integrity": "sha512-w4vkSz1Wo+NIQg8pjlEn0jQbcM/0D+xVaYjhw3cvarTanLLBh54oNiRbsT8PNK5GfuST0IlVXjsNRoNlqvY/fw==", + "version": "2.0.5", + "resolved": "https://registry.npmjs.org/vite-node/-/vite-node-2.0.5.tgz", + "integrity": "sha512-LdsW4pxj0Ot69FAoXZ1yTnA9bjGohr2yNBU7QKRxpz8ITSkhuDl6h3zS/tvgz4qrNjeRnvrWeXQ8ZF7Um4W00Q==", "dev": true, "license": "MIT", "dependencies": { @@ -8207,19 +8210,19 @@ } }, "node_modules/vitest": { - "version": "2.0.2", - "resolved": "https://registry.npmjs.org/vitest/-/vitest-2.0.2.tgz", - "integrity": "sha512-WlpZ9neRIjNBIOQwBYfBSr0+of5ZCbxT2TVGKW4Lv0c8+srCFIiRdsP7U009t8mMn821HQ4XKgkx5dVWpyoyLw==", + "version": "2.0.5", + "resolved": "https://registry.npmjs.org/vitest/-/vitest-2.0.5.tgz", + "integrity": "sha512-8GUxONfauuIdeSl5f9GTgVEpg5BTOlplET4WEDaeY2QBiN8wSm68vxN/tb5z405OwppfoCavnwXafiaYBC/xOA==", "dev": true, "license": "MIT", "dependencies": { "@ampproject/remapping": "^2.3.0", - "@vitest/expect": "2.0.2", - "@vitest/pretty-format": "^2.0.2", - "@vitest/runner": "2.0.2", - "@vitest/snapshot": "2.0.2", - "@vitest/spy": "2.0.2", - "@vitest/utils": "2.0.2", + "@vitest/expect": "2.0.5", + "@vitest/pretty-format": "^2.0.5", + "@vitest/runner": "2.0.5", + "@vitest/snapshot": "2.0.5", + "@vitest/spy": "2.0.5", + "@vitest/utils": "2.0.5", "chai": "^5.1.1", "debug": "^4.3.5", "execa": "^8.0.1", @@ -8230,8 +8233,8 @@ "tinypool": "^1.0.0", "tinyrainbow": "^1.2.0", "vite": "^5.0.0", - "vite-node": "2.0.2", - "why-is-node-running": "^2.2.2" + "vite-node": "2.0.5", + "why-is-node-running": "^2.3.0" }, "bin": { "vitest": "vitest.mjs" @@ -8245,8 +8248,8 @@ "peerDependencies": { "@edge-runtime/vm": "*", "@types/node": "^18.0.0 || >=20.0.0", - "@vitest/browser": "2.0.2", - "@vitest/ui": "2.0.2", + "@vitest/browser": "2.0.5", + "@vitest/ui": "2.0.5", "happy-dom": "*", "jsdom": "*" }, @@ -8275,6 +8278,7 @@ "version": "8.2.0", "resolved": "https://registry.npmjs.org/vscode-jsonrpc/-/vscode-jsonrpc-8.2.0.tgz", "integrity": "sha512-C+r0eKJUIfiDIfwJhria30+TYWPtuHJXHtI7J0YlOmKAo7ogxP20T0zxB7HZQIFhIyvoBPwWskjxrvAtfjyZfA==", + "license": "MIT", "engines": { "node": ">=14.0.0" } @@ -8283,6 +8287,7 @@ "version": "3.17.5", "resolved": "https://registry.npmjs.org/vscode-languageserver-protocol/-/vscode-languageserver-protocol-3.17.5.tgz", "integrity": "sha512-mb1bvRJN8SVznADSGWM9u/b07H7Ecg0I3OgXDuLdn307rl/J3A9YD6/eYOssqhecL27hK1IPZAsaqh00i/Jljg==", + "license": "MIT", "dependencies": { "vscode-jsonrpc": "8.2.0", "vscode-languageserver-types": "3.17.5" @@ -8296,12 +8301,14 @@ "node_modules/vscode-languageserver-types": { "version": "3.17.5", "resolved": "https://registry.npmjs.org/vscode-languageserver-types/-/vscode-languageserver-types-3.17.5.tgz", - "integrity": "sha512-Ld1VelNuX9pdF39h2Hgaeb5hEZM2Z3jUrrMgWQAu82jMtZp7p3vJT3BzToKtZI7NgQssZje5o0zryOrhQvzQAg==" + "integrity": "sha512-Ld1VelNuX9pdF39h2Hgaeb5hEZM2Z3jUrrMgWQAu82jMtZp7p3vJT3BzToKtZI7NgQssZje5o0zryOrhQvzQAg==", + "license": "MIT" }, "node_modules/vscode-uri": { "version": "3.0.8", "resolved": "https://registry.npmjs.org/vscode-uri/-/vscode-uri-3.0.8.tgz", - "integrity": "sha512-AyFQ0EVmsOZOlAnxoFOGOq1SQDWAB7C6aqMGS23svWAllfOaxbuFvcT8D1i8z3Gyn8fraVeZNNmN6e9bxxXkKw==" + "integrity": "sha512-AyFQ0EVmsOZOlAnxoFOGOq1SQDWAB7C6aqMGS23svWAllfOaxbuFvcT8D1i8z3Gyn8fraVeZNNmN6e9bxxXkKw==", + "license": "MIT" }, "node_modules/w3c-xmlserializer": { "version": "5.0.0", @@ -8386,10 +8393,11 @@ } }, "node_modules/why-is-node-running": { - "version": "2.2.2", - "resolved": "https://registry.npmjs.org/why-is-node-running/-/why-is-node-running-2.2.2.tgz", - "integrity": "sha512-6tSwToZxTOcotxHeA+qGCq1mVzKR3CwcJGmVcY+QE8SHy6TnpFnh8PAvPNHYr7EcuVeG0QSMxtYCuO1ta/G/oA==", + "version": "2.3.0", + "resolved": "https://registry.npmjs.org/why-is-node-running/-/why-is-node-running-2.3.0.tgz", + "integrity": "sha512-hUrmaWBdVDcxvYqnyh09zunKzROWjbZTiNy8dBEjkS7ehEDQibXJ7XvlmtbwuTclUiIyN+CyXQD4Vmko8fNm8w==", "dev": true, + "license": "MIT", "dependencies": { "siginfo": "^2.0.0", "stackback": "0.0.2" @@ -8440,9 +8448,9 @@ "integrity": "sha512-l4Sp/DRseor9wL6EvV2+TuQn63dMkPjZ/sp9XkghTEbV9KlPS1xUsZ3u7/IQO4wxtcFB4bgpQPRcR3QCvezPcQ==" }, "node_modules/ws": { - "version": "8.17.0", - "resolved": "https://registry.npmjs.org/ws/-/ws-8.17.0.tgz", - "integrity": "sha512-uJq6108EgZMAl20KagGkzCKfMEjxmKvZHG7Tlq0Z6nOky7YF7aq4mOx6xK8TJ/i1LeK4Qus7INktacctDgY8Ow==", + "version": "8.18.0", + "resolved": "https://registry.npmjs.org/ws/-/ws-8.18.0.tgz", + "integrity": "sha512-8VbfWfHLbbwu3+N6OKsOMpBdT4kXPDDB9cJk2bJ6mh9ucxdlnNvH1e+roYkKmN9Nxw2yjz7VzeO9oOz2zJ04Pw==", "dev": true, "license": "MIT", "engines": { diff --git a/web/package.json b/web/package.json index 328e995b7..54cb39ebd 100644 --- a/web/package.json +++ b/web/package.json @@ -14,7 +14,7 @@ "coverage": "vitest run --coverage" }, "dependencies": { - "@cycjimmy/jsmpeg-player": "^6.0.5", + "@cycjimmy/jsmpeg-player": "^6.1.1", "@hookform/resolvers": "^3.9.0", "@radix-ui/react-alert-dialog": "^1.1.1", "@radix-ui/react-aspect-ratio": "^1.1.0", @@ -36,19 +36,19 @@ "@radix-ui/react-toggle": "^1.1.0", "@radix-ui/react-toggle-group": "^1.1.0", "@radix-ui/react-tooltip": "^1.1.2", - "apexcharts": "^3.50.0", - "axios": "^1.7.2", + "apexcharts": "^3.52.0", + "axios": "^1.7.3", "class-variance-authority": "^0.7.0", "clsx": "^2.1.1", "copy-to-clipboard": "^3.3.3", "date-fns": "^3.6.0", - "hls.js": "^1.5.13", + "hls.js": "^1.5.14", "idb-keyval": "^6.2.1", "immer": "^10.1.1", - "konva": "^9.3.13", + "konva": "^9.3.14", "lodash": "^4.17.21", "lucide-react": "^0.407.0", - "monaco-yaml": "^5.1.1", + "monaco-yaml": "^5.2.2", "next-themes": "^0.3.0", "nosleep.js": "^0.12.0", "react": "^18.3.1", @@ -60,7 +60,7 @@ "react-hook-form": "^7.52.1", "react-icons": "^5.2.1", "react-konva": "^18.2.10", - "react-router-dom": "^6.24.1", + "react-router-dom": "^6.26.0", "react-swipeable": "^7.0.1", "react-tracked": "^2.0.0", "react-transition-group": "^4.4.5", @@ -82,7 +82,7 @@ "devDependencies": { "@tailwindcss/forms": "^0.5.7", "@testing-library/jest-dom": "^6.4.6", - "@types/lodash": "^4.17.6", + "@types/lodash": "^4.17.7", "@types/node": "^20.14.10", "@types/react": "^18.3.2", "@types/react-dom": "^18.3.0", @@ -93,8 +93,8 @@ "@typescript-eslint/eslint-plugin": "^7.5.0", "@typescript-eslint/parser": "^7.5.0", "@vitejs/plugin-react-swc": "^3.6.0", - "@vitest/coverage-v8": "^2.0.2", - "autoprefixer": "^10.4.19", + "@vitest/coverage-v8": "^2.0.5", + "autoprefixer": "^10.4.20", "eslint": "^8.57.0", "eslint-config-prettier": "^9.1.0", "eslint-plugin-jest": "^28.2.0", @@ -104,14 +104,14 @@ "eslint-plugin-vitest-globals": "^1.5.0", "fake-indexeddb": "^6.0.0", "jest-websocket-mock": "^2.5.0", - "jsdom": "^24.0.0", - "msw": "^2.3.0", + "jsdom": "^24.1.1", + "msw": "^2.3.5", "postcss": "^8.4.39", "prettier": "^3.3.2", "prettier-plugin-tailwindcss": "^0.6.5", - "tailwindcss": "^3.4.3", - "typescript": "^5.5.3", - "vite": "^5.3.3", - "vitest": "^2.0.2" + "tailwindcss": "^3.4.9", + "typescript": "^5.5.4", + "vite": "^5.4.0", + "vitest": "^2.0.5" } } From 940c12d9d80fe9861e33da84189e73976db5667a Mon Sep 17 00:00:00 2001 From: Nicolas Mowen Date: Fri, 9 Aug 2024 16:22:24 -0600 Subject: [PATCH 35/81] Remove user args from http jpeg (#12909) --- frigate/ffmpeg_presets.py | 3 +-- 1 file changed, 1 insertion(+), 2 deletions(-) diff --git a/frigate/ffmpeg_presets.py b/frigate/ffmpeg_presets.py index d07ae369f..402046bfe 100644 --- a/frigate/ffmpeg_presets.py +++ b/frigate/ffmpeg_presets.py @@ -214,8 +214,7 @@ def parse_preset_hardware_acceleration_encode( PRESETS_INPUT = { - "preset-http-jpeg-generic": _user_agent_args - + [ + "preset-http-jpeg-generic": [ "-r", "{}", "-stream_loop", From d3259c4782c22236810b40309513dec677b70596 Mon Sep 17 00:00:00 2001 From: Stavros Kois <47820033+stavros-k@users.noreply.github.com> Date: Sat, 10 Aug 2024 19:25:13 +0300 Subject: [PATCH 36/81] add shortcut and query for fullscreen in live view (#12924) * add shortcut and query for live view * Update web/src/views/live/LiveDashboardView.tsx * Update web/src/views/live/LiveDashboardView.tsx Co-authored-by: Nicolas Mowen * Apply suggestions from code review Co-authored-by: Nicolas Mowen * Update LiveDashboardView.tsx --------- Co-authored-by: Nicolas Mowen --- web/src/views/live/LiveDashboardView.tsx | 20 ++++++++++++++++++++ 1 file changed, 20 insertions(+) diff --git a/web/src/views/live/LiveDashboardView.tsx b/web/src/views/live/LiveDashboardView.tsx index 00c046224..51f46d2f2 100644 --- a/web/src/views/live/LiveDashboardView.tsx +++ b/web/src/views/live/LiveDashboardView.tsx @@ -31,6 +31,9 @@ import { cn } from "@/lib/utils"; import { LivePlayerError, LivePlayerMode } from "@/types/live"; import { FaCompress, FaExpand } from "react-icons/fa"; import { useResizeObserver } from "@/hooks/resize-observer"; +import useKeyboardListener, { + KeyModifiers, +} from "@/hooks/use-keyboard-listener"; type LiveDashboardViewProps = { cameras: CameraConfig[]; @@ -247,6 +250,23 @@ export default function LiveDashboardView({ [setPreferredLiveModes], ); + const onKeyboardShortcut = useCallback( + (key: string, modifiers: KeyModifiers) => { + if (!modifiers.down) { + return; + } + + switch (key) { + case "f": + toggleFullscreen(); + break; + } + }, + [toggleFullscreen], + ); + + useKeyboardListener(["f"], onKeyboardShortcut); + return (
Date: Sun, 11 Aug 2024 07:25:09 -0500 Subject: [PATCH 37/81] Add confirmation dialog before deleting review items (#12950) --- web/src/components/card/ReviewCard.tsx | 189 +++++++++++++----- .../components/filter/ReviewActionGroup.tsx | 130 ++++++++---- web/src/components/player/VideoControls.tsx | 2 +- web/src/hooks/use-keyboard-listener.tsx | 29 ++- web/src/views/live/LiveDashboardView.tsx | 29 +-- 5 files changed, 263 insertions(+), 116 deletions(-) diff --git a/web/src/components/card/ReviewCard.tsx b/web/src/components/card/ReviewCard.tsx index 64f52bd1b..33032d2b8 100644 --- a/web/src/components/card/ReviewCard.tsx +++ b/web/src/components/card/ReviewCard.tsx @@ -6,7 +6,7 @@ import { getIconForLabel } from "@/utils/iconUtil"; import { isDesktop, isIOS, isSafari } from "react-device-detect"; import useSWR from "swr"; import TimeAgo from "../dynamic/TimeAgo"; -import { useCallback, useMemo, useState } from "react"; +import { useCallback, useMemo, useRef, useState } from "react"; import useImageLoaded from "@/hooks/use-image-loaded"; import ImageLoadingIndicator from "../indicators/ImageLoadingIndicator"; import { FaCompactDisc } from "react-icons/fa"; @@ -18,9 +18,20 @@ import { ContextMenuItem, ContextMenuTrigger, } from "../ui/context-menu"; +import { + AlertDialog, + AlertDialogAction, + AlertDialogCancel, + AlertDialogContent, + AlertDialogDescription, + AlertDialogFooter, + AlertDialogHeader, + AlertDialogTitle, +} from "../ui/alert-dialog"; import { Drawer, DrawerContent } from "../ui/drawer"; import axios from "axios"; import { toast } from "sonner"; +import useKeyboardListener from "@/hooks/use-keyboard-listener"; type ReviewCardProps = { event: ReviewSegment; @@ -46,6 +57,8 @@ export default function ReviewCard({ ); const [optionsOpen, setOptionsOpen] = useState(false); + const [deleteDialogOpen, setDeleteDialogOpen] = useState(false); + const bypassDialogRef = useRef(false); const onMarkAsReviewed = useCallback(async () => { await axios.post(`reviews/viewed`, { ids: [event.id] }); @@ -92,6 +105,18 @@ export default function ReviewCard({ setOptionsOpen(false); }, [event]); + useKeyboardListener(["Shift"], (_, modifiers) => { + bypassDialogRef.current = modifiers.shift; + }); + + const handleDelete = useCallback(() => { + if (bypassDialogRef.current) { + onDelete(); + } else { + setDeleteDialogOpen(true); + } + }, [bypassDialogRef, onDelete]); + const content = (
- {content} - - -
- -
Export
-
-
- {!event.has_been_reviewed && ( + <> + setDeleteDialogOpen(!deleteDialogOpen)} + > + + + Confirm Delete + + + Are you sure you want to delete all recorded video associated with + this review item? +
+
+ Hold the Shift key to bypass this dialog in the future. +
+ + setOptionsOpen(false)}> + Cancel + + + Delete + + +
+
+ + {content} +
- -
Mark as reviewed
+ +
Export
- )} - -
- -
Delete
-
-
-
-
+ {!event.has_been_reviewed && ( + +
+ +
Mark as reviewed
+
+
+ )} + +
+ +
+ {bypassDialogRef.current ? "Delete Now" : "Delete"} +
+
+
+
+ + ); } return ( - - {content} - -
- -
Export
-
- {!event.has_been_reviewed && ( + <> + setDeleteDialogOpen(!deleteDialogOpen)} + > + + + Confirm Delete + + + Are you sure you want to delete all recorded video associated with + this review item? +
+
+ Hold the Shift key to bypass this dialog in the future. +
+ + setOptionsOpen(false)}> + Cancel + + + Delete + + +
+
+ + {content} +
- -
Mark as reviewed
+ +
Export
- )} -
- -
Delete
-
-
-
+ {!event.has_been_reviewed && ( +
+ +
Mark as reviewed
+
+ )} +
+ +
+ {bypassDialogRef.current ? "Delete Now" : "Delete"} +
+
+
+
+ ); } diff --git a/web/src/components/filter/ReviewActionGroup.tsx b/web/src/components/filter/ReviewActionGroup.tsx index 49e9f561a..c637b1e35 100644 --- a/web/src/components/filter/ReviewActionGroup.tsx +++ b/web/src/components/filter/ReviewActionGroup.tsx @@ -1,10 +1,21 @@ import { FaCircleCheck } from "react-icons/fa6"; -import { useCallback } from "react"; +import { useCallback, useState } from "react"; import axios from "axios"; import { Button } from "../ui/button"; import { isDesktop } from "react-device-detect"; import { FaCompactDisc } from "react-icons/fa"; import { HiTrash } from "react-icons/hi"; +import { + AlertDialog, + AlertDialogAction, + AlertDialogCancel, + AlertDialogContent, + AlertDialogDescription, + AlertDialogFooter, + AlertDialogHeader, + AlertDialogTitle, +} from "../ui/alert-dialog"; +import useKeyboardListener from "@/hooks/use-keyboard-listener"; type ReviewActionGroupProps = { selectedReviews: string[]; @@ -34,49 +45,94 @@ export default function ReviewActionGroup({ pullLatestData(); }, [selectedReviews, setSelectedReviews, pullLatestData]); + const [deleteDialogOpen, setDeleteDialogOpen] = useState(false); + const [bypassDialog, setBypassDialog] = useState(false); + + useKeyboardListener(["Shift"], (_, modifiers) => { + setBypassDialog(modifiers.shift); + }); + + const handleDelete = useCallback(() => { + if (bypassDialog) { + onDelete(); + } else { + setDeleteDialogOpen(true); + } + }, [bypassDialog, onDelete]); + return ( -
-
-
{`${selectedReviews.length} selected`}
-
{"|"}
-
- Unselect + <> + setDeleteDialogOpen(!deleteDialogOpen)} + > + + + Confirm Delete + + + Are you sure you want to delete all recorded video associated with + the selected review items? +
+
+ Hold the Shift key to bypass this dialog in the future. +
+ + Cancel + + Delete + + +
+
+ +
+
+
{`${selectedReviews.length} selected`}
+
{"|"}
+
+ Unselect +
-
-
- {selectedReviews.length == 1 && ( +
+ {selectedReviews.length == 1 && ( + + )} - )} - - + +
-
+ ); } diff --git a/web/src/components/player/VideoControls.tsx b/web/src/components/player/VideoControls.tsx index 70d9a4be8..50b2cc045 100644 --- a/web/src/components/player/VideoControls.tsx +++ b/web/src/components/player/VideoControls.tsx @@ -141,7 +141,7 @@ export default function VideoControls({ }, [volume, muted]); const onKeyboardShortcut = useCallback( - (key: string, modifiers: KeyModifiers) => { + (key: string | null, modifiers: KeyModifiers) => { if (!modifiers.down) { return; } diff --git a/web/src/hooks/use-keyboard-listener.tsx b/web/src/hooks/use-keyboard-listener.tsx index f127cf0d8..ad9462a05 100644 --- a/web/src/hooks/use-keyboard-listener.tsx +++ b/web/src/hooks/use-keyboard-listener.tsx @@ -4,11 +4,12 @@ export type KeyModifiers = { down: boolean; repeat: boolean; ctrl: boolean; + shift: boolean; }; export default function useKeyboardListener( keys: string[], - listener: (key: string, modifiers: KeyModifiers) => void, + listener: (key: string | null, modifiers: KeyModifiers) => void, ) { const keyDownListener = useCallback( (e: KeyboardEvent) => { @@ -16,13 +17,18 @@ export default function useKeyboardListener( return; } + const modifiers = { + down: true, + repeat: e.repeat, + ctrl: e.ctrlKey || e.metaKey, + shift: e.shiftKey, + }; + if (keys.includes(e.key)) { e.preventDefault(); - listener(e.key, { - down: true, - repeat: e.repeat, - ctrl: e.ctrlKey || e.metaKey, - }); + listener(e.key, modifiers); + } else if (e.key === "Shift" || e.key === "Control" || e.key === "Meta") { + listener(null, modifiers); } }, [keys, listener], @@ -34,9 +40,18 @@ export default function useKeyboardListener( return; } + const modifiers = { + down: false, + repeat: false, + ctrl: false, + shift: false, + }; + if (keys.includes(e.key)) { e.preventDefault(); - listener(e.key, { down: false, repeat: false, ctrl: false }); + listener(e.key, modifiers); + } else if (e.key === "Shift" || e.key === "Control" || e.key === "Meta") { + listener(null, modifiers); } }, [keys, listener], diff --git a/web/src/views/live/LiveDashboardView.tsx b/web/src/views/live/LiveDashboardView.tsx index 51f46d2f2..a91afb356 100644 --- a/web/src/views/live/LiveDashboardView.tsx +++ b/web/src/views/live/LiveDashboardView.tsx @@ -31,9 +31,7 @@ import { cn } from "@/lib/utils"; import { LivePlayerError, LivePlayerMode } from "@/types/live"; import { FaCompress, FaExpand } from "react-icons/fa"; import { useResizeObserver } from "@/hooks/resize-observer"; -import useKeyboardListener, { - KeyModifiers, -} from "@/hooks/use-keyboard-listener"; +import useKeyboardListener from "@/hooks/use-keyboard-listener"; type LiveDashboardViewProps = { cameras: CameraConfig[]; @@ -250,22 +248,17 @@ export default function LiveDashboardView({ [setPreferredLiveModes], ); - const onKeyboardShortcut = useCallback( - (key: string, modifiers: KeyModifiers) => { - if (!modifiers.down) { - return; - } + useKeyboardListener(["f"], (key, modifiers) => { + if (!modifiers.down) { + return; + } - switch (key) { - case "f": - toggleFullscreen(); - break; - } - }, - [toggleFullscreen], - ); - - useKeyboardListener(["f"], onKeyboardShortcut); + switch (key) { + case "f": + toggleFullscreen(); + break; + } + }); return (
Date: Sun, 11 Aug 2024 06:32:39 -0600 Subject: [PATCH 38/81] Catch case where user tries to end definite manual event (#12951) * Catch case where user tries to end definite manual event * Formatting --- frigate/review/maintainer.py | 11 +++++++++-- 1 file changed, 9 insertions(+), 2 deletions(-) diff --git a/frigate/review/maintainer.py b/frigate/review/maintainer.py index 8fb1df362..dfc7259b2 100644 --- a/frigate/review/maintainer.py +++ b/frigate/review/maintainer.py @@ -503,8 +503,15 @@ class ReviewSegmentMaintainer(threading.Thread): # temporarily make it so this event can not end current_segment.last_update = sys.maxsize elif manual_info["state"] == ManualEventState.end: - self.indefinite_events[camera].pop(manual_info["event_id"]) - current_segment.last_update = manual_info["end_time"] + event_id = manual_info["event_id"] + + if event_id in self.indefinite_events[camera]: + self.indefinite_events[camera].pop(event_id) + current_segment.last_update = manual_info["end_time"] + else: + logger.error( + f"Event with ID {event_id} has a set duration and can not be ended manually." + ) else: if topic == DetectionTypeEnum.video: self.check_if_new_segment( From 07ffd764376552c37a949256281553005a22383a Mon Sep 17 00:00:00 2001 From: Josh Hawkins <32435876+hawkeye217@users.noreply.github.com> Date: Sun, 11 Aug 2024 08:15:04 -0500 Subject: [PATCH 39/81] Add pan/pinch/zoom capability on plus snapshots (#12953) --- web/src/pages/SubmitPlus.tsx | 75 ++++++++++++++++++++++-------------- 1 file changed, 46 insertions(+), 29 deletions(-) diff --git a/web/src/pages/SubmitPlus.tsx b/web/src/pages/SubmitPlus.tsx index 57c0fdf37..1fcc6ef2a 100644 --- a/web/src/pages/SubmitPlus.tsx +++ b/web/src/pages/SubmitPlus.tsx @@ -47,6 +47,7 @@ import { LuFolderX } from "react-icons/lu"; import { PiSlidersHorizontalFill } from "react-icons/pi"; import useSWR from "swr"; import useSWRInfinite from "swr/infinite"; +import { TransformWrapper, TransformComponent } from "react-zoom-pan-pinch"; const API_LIMIT = 100; @@ -254,36 +255,52 @@ export default function SubmitPlus() { open={upload != undefined} onOpenChange={(open) => (!open ? setUpload(undefined) : null)} > - - - Submit To Frigate+ - - Objects in locations you want to avoid are not false - positives. Submitting them as false positives will confuse - the model. - - - {`${upload?.label}`} - - - - - + {upload?.id && ( + {`${upload?.label}`} + )} + + + + + + + From 338b59a32ec6b9f669f8c17b2a18ac3eb12e5ccc Mon Sep 17 00:00:00 2001 From: Nicolas Mowen Date: Sun, 11 Aug 2024 07:32:17 -0600 Subject: [PATCH 40/81] Catch case where recording starts right at end of request (#12956) --- frigate/api/media.py | 5 +++++ 1 file changed, 5 insertions(+) diff --git a/frigate/api/media.py b/frigate/api/media.py index 98bb2f952..911e13f7e 100644 --- a/frigate/api/media.py +++ b/frigate/api/media.py @@ -546,6 +546,11 @@ def vod_ts(camera_name, start_ts, end_ts): if recording.end_time > end_ts: duration -= int((recording.end_time - end_ts) * 1000) + if duration == 0: + # this means the segment starts right at the end of the requested time range + # and it does not need to be included + continue + if 0 < duration < max_duration_ms: clip["keyFrameDurations"] = [duration] clips.append(clip) From b2c23a367d1bc37ddaef06bca7f166eb562e43f2 Mon Sep 17 00:00:00 2001 From: Nicolas Mowen Date: Mon, 12 Aug 2024 07:21:21 -0600 Subject: [PATCH 41/81] Hide record switch when disabled (#12997) --- web/src/views/live/LiveCameraView.tsx | 17 ++++++++++++----- 1 file changed, 12 insertions(+), 5 deletions(-) diff --git a/web/src/views/live/LiveCameraView.tsx b/web/src/views/live/LiveCameraView.tsx index cd7190e86..db162198c 100644 --- a/web/src/views/live/LiveCameraView.tsx +++ b/web/src/views/live/LiveCameraView.tsx @@ -412,6 +412,7 @@ export default function LiveCameraView({ )} sendDetect(detectState == "ON" ? "OFF" : "ON")} /> - sendRecord(recordState == "ON" ? "OFF" : "ON")} - /> + {recordingEnabled && ( + + sendRecord(recordState == "ON" ? "OFF" : "ON") + } + /> + )} Date: Mon, 12 Aug 2024 14:30:16 -0600 Subject: [PATCH 42/81] Recordings Fixes (#13005) * If recordings don't exist mark as no recordings * Fix reloading recordings failing * Fix mark items not clearing selected * Cleanup * Default to last full hour when error occurs * Remove check * Cleanup * Handle empty recordings list case * Ensure that the start time is within the time range * Catch other reset cases --- .../player/dynamic/DynamicVideoPlayer.tsx | 6 +++- web/src/pages/Events.tsx | 4 +-- web/src/views/events/EventView.tsx | 4 +++ web/src/views/events/RecordingView.tsx | 29 ++++++++++++++++--- 4 files changed, 36 insertions(+), 7 deletions(-) diff --git a/web/src/components/player/dynamic/DynamicVideoPlayer.tsx b/web/src/components/player/dynamic/DynamicVideoPlayer.tsx index a40d521ea..caa709430 100644 --- a/web/src/components/player/dynamic/DynamicVideoPlayer.tsx +++ b/web/src/components/player/dynamic/DynamicVideoPlayer.tsx @@ -167,7 +167,11 @@ export default function DynamicVideoPlayer({ ); useEffect(() => { - if (!controller || !recordings) { + if (!controller || !recordings?.length) { + if (recordings?.length == 0) { + setNoRecording(true); + } + return; } diff --git a/web/src/pages/Events.tsx b/web/src/pages/Events.tsx index ce646f5bd..0326fe3dc 100644 --- a/web/src/pages/Events.tsx +++ b/web/src/pages/Events.tsx @@ -101,7 +101,7 @@ export default function Events() { // review paging - const [beforeTs, setBeforeTs] = useState(Date.now() / 1000); + const [beforeTs, setBeforeTs] = useState(Math.ceil(Date.now() / 1000)); const last24Hours = useMemo(() => { return { before: beforeTs, after: getHoursAgo(24) }; }, [beforeTs]); @@ -455,5 +455,5 @@ export default function Events() { function getHoursAgo(hours: number): number { const now = new Date(); now.setHours(now.getHours() - hours); - return now.getTime() / 1000; + return Math.ceil(now.getTime() / 1000); } diff --git a/web/src/views/events/EventView.tsx b/web/src/views/events/EventView.tsx index 8b4569be0..adfb08206 100644 --- a/web/src/views/events/EventView.tsx +++ b/web/src/views/events/EventView.tsx @@ -395,6 +395,7 @@ export default function EventView({ markAllItemsAsReviewed={markAllItemsAsReviewed} onSelectReview={onSelectReview} onSelectAllReviews={onSelectAllReviews} + setSelectedReviews={setSelectedReviews} pullLatestData={pullLatestData} /> )} @@ -437,6 +438,7 @@ type DetectionReviewProps = { markAllItemsAsReviewed: (currentItems: ReviewSegment[]) => void; onSelectReview: (review: ReviewSegment, ctrl: boolean) => void; onSelectAllReviews: () => void; + setSelectedReviews: (reviewIds: string[]) => void; pullLatestData: () => void; }; function DetectionReview({ @@ -455,6 +457,7 @@ function DetectionReview({ markAllItemsAsReviewed, onSelectReview, onSelectAllReviews, + setSelectedReviews, pullLatestData, }: DetectionReviewProps) { const reviewTimelineRef = useRef(null); @@ -692,6 +695,7 @@ function DetectionReview({ className="text-white" variant="select" onClick={() => { + setSelectedReviews([]); markAllItemsAsReviewed(currentItems ?? []); }} > diff --git a/web/src/views/events/RecordingView.tsx b/web/src/views/events/RecordingView.tsx index c1b30b98e..1bc82d9e9 100644 --- a/web/src/views/events/RecordingView.tsx +++ b/web/src/views/events/RecordingView.tsx @@ -84,7 +84,11 @@ export function RecordingView({ const previewRowRef = useRef(null); const previewRefs = useRef<{ [camera: string]: PreviewController }>({}); - const [playbackStart, setPlaybackStart] = useState(startTime); + const [playbackStart, setPlaybackStart] = useState( + startTime >= timeRange.after && startTime <= timeRange.before + ? startTime + : timeRange.before - 60, + ); const mainCameraReviewItems = useMemo( () => reviewItems?.filter((cam) => cam.camera == mainCamera) ?? [], @@ -107,8 +111,10 @@ export function RecordingView({ return chunk.after <= startTime && chunk.before >= startTime; }), ); - const currentTimeRange = useMemo( - () => chunkedTimeRange[selectedRangeIdx], + const currentTimeRange = useMemo( + () => + chunkedTimeRange[selectedRangeIdx] ?? + chunkedTimeRange[chunkedTimeRange.length - 1], [selectedRangeIdx, chunkedTimeRange], ); const reviewFilterList = useMemo(() => { @@ -198,6 +204,10 @@ export function RecordingView({ const manuallySetCurrentTime = useCallback( (time: number) => { + if (!currentTimeRange) { + return; + } + setCurrentTime(time); if (currentTimeRange.after <= time && currentTimeRange.before >= time) { @@ -420,7 +430,18 @@ export function RecordingView({ filterList={reviewFilterList} showReviewed setShowReviewed={() => {}} - onUpdateFilter={updateFilter} + onUpdateFilter={(newFilter) => { + // if we are resetting the date to last 24 hours + // then we need to reset the playbackStart time + if ( + filter?.before != undefined && + newFilter?.before == undefined + ) { + setPlaybackStart(Date.now() / 1000 - 360); + } + + updateFilter(newFilter); + }} setMotionOnly={() => {}} /> )} From 009900b29b6a270d7f3d0189282da45129646095 Mon Sep 17 00:00:00 2001 From: Nicolas Mowen Date: Mon, 12 Aug 2024 15:12:49 -0600 Subject: [PATCH 43/81] Reset recordings when changing the date (#13009) --- web/src/pages/Events.tsx | 1 + web/src/views/events/RecordingView.tsx | 13 +------------ 2 files changed, 2 insertions(+), 12 deletions(-) diff --git a/web/src/pages/Events.tsx b/web/src/pages/Events.tsx index 0326fe3dc..9ee81b1dc 100644 --- a/web/src/pages/Events.tsx +++ b/web/src/pages/Events.tsx @@ -416,6 +416,7 @@ export default function Events() { if (selectedReviewData) { return ( {}} - onUpdateFilter={(newFilter) => { - // if we are resetting the date to last 24 hours - // then we need to reset the playbackStart time - if ( - filter?.before != undefined && - newFilter?.before == undefined - ) { - setPlaybackStart(Date.now() / 1000 - 360); - } - - updateFilter(newFilter); - }} + onUpdateFilter={updateFilter} setMotionOnly={() => {}} /> )} From a75feb7f8fadab44625b3bec36e6e535dccae309 Mon Sep 17 00:00:00 2001 From: Nicolas Mowen Date: Tue, 13 Aug 2024 08:23:46 -0600 Subject: [PATCH 44/81] Fix last hour preview (#13027) --- web/src/pages/Events.tsx | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/web/src/pages/Events.tsx b/web/src/pages/Events.tsx index 9ee81b1dc..07675d87e 100644 --- a/web/src/pages/Events.tsx +++ b/web/src/pages/Events.tsx @@ -111,7 +111,7 @@ export default function Events() { } return { - before: Math.floor(reviewSearchParams["before"]), + before: Math.ceil(reviewSearchParams["before"]), after: Math.floor(reviewSearchParams["after"]), }; }, [last24Hours, reviewSearchParams]); From f9baa3bf209281ea2d215870b885dd35f84be13f Mon Sep 17 00:00:00 2001 From: Nicolas Mowen Date: Tue, 13 Aug 2024 09:12:06 -0600 Subject: [PATCH 45/81] UI fixes (#13030) * Fix difficulty overwriting export name * Fix NaN for score selector --- web/src/components/card/ExportCard.tsx | 14 +++++++---- web/src/pages/SubmitPlus.tsx | 32 ++++++++++++++++---------- 2 files changed, 29 insertions(+), 17 deletions(-) diff --git a/web/src/components/card/ExportCard.tsx b/web/src/components/card/ExportCard.tsx index d39cbbeda..1ad98be0d 100644 --- a/web/src/components/card/ExportCard.tsx +++ b/web/src/components/card/ExportCard.tsx @@ -44,7 +44,7 @@ export default function ExportCard({ const [editName, setEditName] = useState<{ original: string; - update: string; + update?: string; }>(); const submitRename = useCallback(() => { @@ -52,7 +52,7 @@ export default function ExportCard({ return; } - onRename(exportedRecording.id, editName.update); + onRename(exportedRecording.id, editName.update ?? ""); setEditName(undefined); }, [editName, exportedRecording, onRename, setEditName]); @@ -64,7 +64,7 @@ export default function ExportCard({ modifiers.down && !modifiers.repeat && editName && - editName.update.length > 0 + (editName.update?.length ?? 0) > 0 ) { submitRename(); } @@ -92,7 +92,11 @@ export default function ExportCard({ className="mt-3" type="search" placeholder={editName?.original} - value={editName?.update || editName?.original} + value={ + editName?.update == undefined + ? editName?.original + : editName?.update + } onChange={(e) => setEditName({ original: editName.original ?? "", @@ -159,7 +163,7 @@ export default function ExportCard({ onClick={() => setEditName({ original: exportedRecording.name, - update: "", + update: undefined, }) } > diff --git a/web/src/pages/SubmitPlus.tsx b/web/src/pages/SubmitPlus.tsx index 1fcc6ef2a..9d2b9ae5b 100644 --- a/web/src/pages/SubmitPlus.tsx +++ b/web/src/pages/SubmitPlus.tsx @@ -494,12 +494,16 @@ function PlusFilterGroup({ className="w-12" inputMode="numeric" value={Math.round((currentScoreRange?.at(0) ?? 0.5) * 100)} - onChange={(e) => - setCurrentScoreRange([ - parseInt(e.target.value) / 100.0, - currentScoreRange?.at(1) ?? 1.0, - ]) - } + onChange={(e) => { + const value = e.target.value; + + if (value) { + setCurrentScoreRange([ + parseInt(value) / 100.0, + currentScoreRange?.at(1) ?? 1.0, + ]); + } + }} /> - setCurrentScoreRange([ - currentScoreRange?.at(0) ?? 0.5, - parseInt(e.target.value) / 100.0, - ]) - } + onChange={(e) => { + const value = e.target.value; + + if (value) { + setCurrentScoreRange([ + currentScoreRange?.at(0) ?? 0.5, + parseInt(value) / 100.0, + ]); + } + }} />
From 6b9082bdd99bf8f1c1ee2e800a8fce9779026b99 Mon Sep 17 00:00:00 2001 From: Nicolas Mowen Date: Tue, 13 Aug 2024 13:26:01 -0600 Subject: [PATCH 46/81] Rename bug report (#13039) --- .github/DISCUSSION_TEMPLATE/{bug-report.yml => report-a-bug.yml} | 0 1 file changed, 0 insertions(+), 0 deletions(-) rename .github/DISCUSSION_TEMPLATE/{bug-report.yml => report-a-bug.yml} (100%) diff --git a/.github/DISCUSSION_TEMPLATE/bug-report.yml b/.github/DISCUSSION_TEMPLATE/report-a-bug.yml similarity index 100% rename from .github/DISCUSSION_TEMPLATE/bug-report.yml rename to .github/DISCUSSION_TEMPLATE/report-a-bug.yml From dbd042ca3e10a64c1f0a8ad1fffc6922fda932ff Mon Sep 17 00:00:00 2001 From: Nicolas Mowen Date: Wed, 14 Aug 2024 19:41:41 -0600 Subject: [PATCH 47/81] Catch case where github sends bad json data (#13077) --- frigate/stats/util.py | 3 ++- 1 file changed, 2 insertions(+), 1 deletion(-) diff --git a/frigate/stats/util.py b/frigate/stats/util.py index 09710b358..ac28e3d89 100644 --- a/frigate/stats/util.py +++ b/frigate/stats/util.py @@ -4,6 +4,7 @@ import asyncio import os import shutil import time +from json import JSONDecodeError from typing import Any, Optional import psutil @@ -35,7 +36,7 @@ def get_latest_version(config: FrigateConfig) -> str: "https://api.github.com/repos/blakeblackshear/frigate/releases/latest", timeout=10, ) - except RequestException: + except (RequestException, JSONDecodeError): return "unknown" response = request.json() From 3650000b315728ffbe33c2398faef4cc8b942db3 Mon Sep 17 00:00:00 2001 From: Josh Hawkins <32435876+hawkeye217@users.noreply.github.com> Date: Thu, 15 Aug 2024 09:51:44 -0500 Subject: [PATCH 48/81] Add shortcut key "r" to mark selected items as reviewed (#13087) * Add shortcut key "r" to mark selected items as reviewed * unselect after keypress --- web/src/views/events/EventView.tsx | 12 +++++++++++- 1 file changed, 11 insertions(+), 1 deletion(-) diff --git a/web/src/views/events/EventView.tsx b/web/src/views/events/EventView.tsx index adfb08206..f3b2395f8 100644 --- a/web/src/views/events/EventView.tsx +++ b/web/src/views/events/EventView.tsx @@ -606,7 +606,7 @@ function DetectionReview({ // keyboard - useKeyboardListener(["a"], (key, modifiers) => { + useKeyboardListener(["a", "r"], (key, modifiers) => { if (modifiers.repeat || !modifiers.down) { return; } @@ -614,6 +614,16 @@ function DetectionReview({ if (key == "a" && modifiers.ctrl) { onSelectAllReviews(); } + + if (key == "r" && selectedReviews.length > 0) { + currentItems?.forEach((item) => { + if (selectedReviews.includes(item.id)) { + item.has_been_reviewed = true; + markItemAsReviewed(item); + } + }); + setSelectedReviews([]); + } }); return ( From 758b0f9734e2435eedcf4e04ca1d10619f19f320 Mon Sep 17 00:00:00 2001 From: Josh Hawkins <32435876+hawkeye217@users.noreply.github.com> Date: Thu, 15 Aug 2024 16:13:11 -0500 Subject: [PATCH 49/81] Remove dashboard keyboard listener (#13102) --- web/src/views/live/LiveDashboardView.tsx | 13 ------------- 1 file changed, 13 deletions(-) diff --git a/web/src/views/live/LiveDashboardView.tsx b/web/src/views/live/LiveDashboardView.tsx index a91afb356..00c046224 100644 --- a/web/src/views/live/LiveDashboardView.tsx +++ b/web/src/views/live/LiveDashboardView.tsx @@ -31,7 +31,6 @@ import { cn } from "@/lib/utils"; import { LivePlayerError, LivePlayerMode } from "@/types/live"; import { FaCompress, FaExpand } from "react-icons/fa"; import { useResizeObserver } from "@/hooks/resize-observer"; -import useKeyboardListener from "@/hooks/use-keyboard-listener"; type LiveDashboardViewProps = { cameras: CameraConfig[]; @@ -248,18 +247,6 @@ export default function LiveDashboardView({ [setPreferredLiveModes], ); - useKeyboardListener(["f"], (key, modifiers) => { - if (!modifiers.down) { - return; - } - - switch (key) { - case "f": - toggleFullscreen(); - break; - } - }); - return (
Date: Sat, 17 Aug 2024 13:16:48 -0500 Subject: [PATCH 50/81] Live player fixes (#13143) * Jump to live when exceeding buffer time threshold in MSE player * clean up * Try adjusting playback rate instead of jumping to live * clean up * fallback to webrtc if enabled before jsmpeg * baseline * clean up * remove comments * adaptive playback rate and intelligent switching improvements * increase logging and reset live mode after camera is no longer active on dashboard only * jump to live on safari/iOS * clean up * clean up * refactor camera live mode hook * remove key listener * resolve conflicts --- web/src/components/player/LivePlayer.tsx | 14 +-- web/src/components/player/MsePlayer.tsx | 140 +++++++++++++++++++-- web/src/hooks/use-camera-live-mode.ts | 86 +++++++------ web/src/types/frigateConfig.ts | 7 +- web/src/views/live/DraggableGridLayout.tsx | 37 ++---- web/src/views/live/LiveCameraView.tsx | 29 +++-- web/src/views/live/LiveDashboardView.tsx | 35 +----- 7 files changed, 228 insertions(+), 120 deletions(-) diff --git a/web/src/components/player/LivePlayer.tsx b/web/src/components/player/LivePlayer.tsx index 67057a278..9a0b6f3db 100644 --- a/web/src/components/player/LivePlayer.tsx +++ b/web/src/components/player/LivePlayer.tsx @@ -13,7 +13,6 @@ import { LivePlayerMode, VideoResolutionType, } from "@/types/live"; -import useCameraLiveMode from "@/hooks/use-camera-live-mode"; import { getIconForLabel } from "@/utils/iconUtil"; import Chip from "../indicators/Chip"; import { capitalizeFirstLetter } from "@/utils/stringUtil"; @@ -25,7 +24,7 @@ type LivePlayerProps = { containerRef?: React.MutableRefObject; className?: string; cameraConfig: CameraConfig; - preferredLiveMode?: LivePlayerMode; + preferredLiveMode: LivePlayerMode; showStillWithoutActivity?: boolean; windowVisible?: boolean; playAudio?: boolean; @@ -36,6 +35,7 @@ type LivePlayerProps = { onClick?: () => void; setFullResolution?: React.Dispatch>; onError?: (error: LivePlayerError) => void; + onResetLiveMode?: () => void; }; export default function LivePlayer({ @@ -54,6 +54,7 @@ export default function LivePlayer({ onClick, setFullResolution, onError, + onResetLiveMode, }: LivePlayerProps) { const internalContainerRef = useRef(null); // camera activity @@ -70,8 +71,6 @@ export default function LivePlayer({ // camera live state - const liveMode = useCameraLiveMode(cameraConfig, preferredLiveMode); - const [liveReady, setLiveReady] = useState(false); const liveReadyRef = useRef(liveReady); @@ -91,6 +90,7 @@ export default function LivePlayer({ const timer = setTimeout(() => { if (liveReadyRef.current && !cameraActiveRef.current) { setLiveReady(false); + onResetLiveMode?.(); } }, 500); @@ -152,7 +152,7 @@ export default function LivePlayer({ let player; if (!autoLive) { player = null; - } else if (liveMode == "webrtc") { + } else if (preferredLiveMode == "webrtc") { player = ( ); - } else if (liveMode == "mse") { + } else if (preferredLiveMode == "mse") { if ("MediaSource" in window || "ManagedMediaSource" in window) { player = ( ); } - } else if (liveMode == "jsmpeg") { + } else if (preferredLiveMode == "jsmpeg") { if (cameraActive || !showStillWithoutActivity || liveReady) { player = ( ([]); + const bufferIndex = useRef(0); const [wsState, setWsState] = useState(WebSocket.CLOSED); const [connectTS, setConnectTS] = useState(0); @@ -133,6 +139,13 @@ function MSEPlayer({ } }, [bufferTimeout]); + const handlePause = useCallback(() => { + // don't let the user pause the live stream + if (isPlaying && playbackEnabled) { + videoRef.current?.play(); + } + }, [isPlaying, playbackEnabled]); + const onOpen = () => { setWsState(WebSocket.OPEN); @@ -193,6 +206,7 @@ function MSEPlayer({ const onMse = () => { if ("ManagedMediaSource" in window) { + // safari const MediaSource = window.ManagedMediaSource; msRef.current?.addEventListener( @@ -224,6 +238,7 @@ function MSEPlayer({ videoRef.current.srcObject = msRef.current; } } else { + // non safari msRef.current?.addEventListener( "sourceopen", () => { @@ -247,15 +262,35 @@ function MSEPlayer({ }, { once: true }, ); - videoRef.current!.src = URL.createObjectURL(msRef.current!); - videoRef.current!.srcObject = null; + if (videoRef.current && msRef.current) { + videoRef.current.src = URL.createObjectURL(msRef.current); + videoRef.current.srcObject = null; + } } play(); onmessageRef.current["mse"] = (msg) => { if (msg.type !== "mse") return; - const sb = msRef.current?.addSourceBuffer(msg.value); + let sb: SourceBuffer | undefined; + try { + sb = msRef.current?.addSourceBuffer(msg.value); + if (sb?.mode) { + sb.mode = "segments"; + } + } catch (e) { + // Safari sometimes throws this error + if (e instanceof DOMException && e.name === "InvalidStateError") { + if (wsRef.current) { + onDisconnect(); + } + onError?.("mse-decode"); + return; + } else { + throw e; // Re-throw if it's not the error we're handling + } + } + sb?.addEventListener("updateend", () => { if (sb.updating) return; @@ -302,6 +337,43 @@ function MSEPlayer({ return video.buffered.end(video.buffered.length - 1) - video.currentTime; }; + const jumpToLive = () => { + if (!videoRef.current) return; + + const buffered = videoRef.current.buffered; + if (buffered.length > 0) { + const liveEdge = buffered.end(buffered.length - 1); + // Jump to the live edge + videoRef.current.currentTime = liveEdge - 0.75; + lastJumpTimeRef.current = Date.now(); + } + }; + + const calculateAdaptiveBufferThreshold = () => { + const filledEntries = bufferTimes.current.length; + const sum = bufferTimes.current.reduce((a, b) => a + b, 0); + const averageBufferTime = filledEntries ? sum / filledEntries : 0; + return averageBufferTime * (isSafari || isIOS ? 3 : 1.5); + }; + + const calculateAdaptivePlaybackRate = ( + bufferTime: number, + bufferThreshold: number, + ) => { + const alpha = 0.2; // aggressiveness of playback rate increase + const beta = 0.5; // steepness of exponential growth + + // don't adjust playback rate if we're close enough to live + if ( + (bufferTime <= bufferThreshold && bufferThreshold < 3) || + bufferTime < 3 + ) { + return 1; + } + const rate = 1 + alpha * Math.exp(beta * bufferTime - bufferThreshold); + return Math.min(rate, 2); + }; + useEffect(() => { if (!playbackEnabled) { return; @@ -386,21 +458,71 @@ function MSEPlayer({ handleLoadedMetadata?.(); onPlaying?.(); setIsPlaying(true); + lastJumpTimeRef.current = Date.now(); }} muted={!audioEnabled} - onPause={() => videoRef.current?.play()} + onPause={handlePause} onProgress={() => { + const bufferTime = getBufferedTime(videoRef.current); + + if ( + videoRef.current && + (videoRef.current.playbackRate === 1 || bufferTime < 3) + ) { + if (bufferTimes.current.length < MAX_BUFFER_ENTRIES) { + bufferTimes.current.push(bufferTime); + } else { + bufferTimes.current[bufferIndex.current] = bufferTime; + bufferIndex.current = + (bufferIndex.current + 1) % MAX_BUFFER_ENTRIES; + } + } + + const bufferThreshold = calculateAdaptiveBufferThreshold(); + // if we have > 3 seconds of buffered data and we're still not playing, // something might be wrong - maybe codec issue, no audio, etc // so mark the player as playing so that error handlers will fire - if ( - !isPlaying && - playbackEnabled && - getBufferedTime(videoRef.current) > 3 - ) { + if (!isPlaying && playbackEnabled && bufferTime > 3) { setIsPlaying(true); + lastJumpTimeRef.current = Date.now(); onPlaying?.(); } + + // if we have more than 10 seconds of buffer, something's wrong so error out + if ( + isPlaying && + playbackEnabled && + (bufferThreshold > 10 || bufferTime > 10) + ) { + onDisconnect(); + onError?.("stalled"); + } + + const playbackRate = calculateAdaptivePlaybackRate( + bufferTime, + bufferThreshold, + ); + + // if we're above our rolling average threshold or have > 3 seconds of + // buffered data and we're playing, we may have drifted from actual live + // time, so increase playback rate to compensate - non safari/ios only + if ( + videoRef.current && + isPlaying && + playbackEnabled && + Date.now() - lastJumpTimeRef.current > BUFFERING_COOLDOWN_TIMEOUT + ) { + // Jump to live on Safari/iOS due to a change of playback rate causing re-buffering + if (isSafari || isIOS) { + if (bufferTime > 3) { + jumpToLive(); + } + } else { + videoRef.current.playbackRate = playbackRate; + } + } + if (onError != undefined) { if (videoRef.current?.paused) { return; diff --git a/web/src/hooks/use-camera-live-mode.ts b/web/src/hooks/use-camera-live-mode.ts index 2a1997045..edf165951 100644 --- a/web/src/hooks/use-camera-live-mode.ts +++ b/web/src/hooks/use-camera-live-mode.ts @@ -1,49 +1,65 @@ import { CameraConfig, FrigateConfig } from "@/types/frigateConfig"; -import { useMemo } from "react"; +import { useCallback, useEffect, useState } from "react"; import useSWR from "swr"; -import { usePersistence } from "./use-persistence"; import { LivePlayerMode } from "@/types/live"; export default function useCameraLiveMode( - cameraConfig: CameraConfig, - preferredMode?: LivePlayerMode, -): LivePlayerMode | undefined { + cameras: CameraConfig[], + windowVisible: boolean, +) { const { data: config } = useSWR("config"); + const [preferredLiveModes, setPreferredLiveModes] = useState<{ + [key: string]: LivePlayerMode; + }>({}); - const restreamEnabled = useMemo(() => { - if (!config) { - return false; - } + useEffect(() => { + if (!cameras) return; - return ( - cameraConfig && - Object.keys(config.go2rtc.streams || {}).includes( - cameraConfig.live.stream_name, - ) + const mseSupported = + "MediaSource" in window || "ManagedMediaSource" in window; + + const newPreferredLiveModes = cameras.reduce( + (acc, camera) => { + const isRestreamed = + config && + Object.keys(config.go2rtc.streams || {}).includes( + camera.live.stream_name, + ); + + if (!mseSupported) { + acc[camera.name] = isRestreamed ? "webrtc" : "jsmpeg"; + } else { + acc[camera.name] = isRestreamed ? "mse" : "jsmpeg"; + } + return acc; + }, + {} as { [key: string]: LivePlayerMode }, ); - }, [config, cameraConfig]); - const defaultLiveMode = useMemo(() => { - if (config) { - if (restreamEnabled) { - return preferredMode || "mse"; - } - return "jsmpeg"; - } + setPreferredLiveModes(newPreferredLiveModes); + }, [cameras, config, windowVisible]); - return undefined; - }, [config, preferredMode, restreamEnabled]); - const [viewSource] = usePersistence( - `${cameraConfig.name}-source`, - defaultLiveMode, + const resetPreferredLiveMode = useCallback( + (cameraName: string) => { + const mseSupported = + "MediaSource" in window || "ManagedMediaSource" in window; + const isRestreamed = + config && Object.keys(config.go2rtc.streams || {}).includes(cameraName); + + setPreferredLiveModes((prevModes) => { + const newModes = { ...prevModes }; + + if (!mseSupported) { + newModes[cameraName] = isRestreamed ? "webrtc" : "jsmpeg"; + } else { + newModes[cameraName] = isRestreamed ? "mse" : "jsmpeg"; + } + + return newModes; + }); + }, + [config], ); - if ( - restreamEnabled && - (preferredMode == "mse" || preferredMode == "webrtc") - ) { - return preferredMode; - } else { - return viewSource; - } + return { preferredLiveModes, setPreferredLiveModes, resetPreferredLiveMode }; } diff --git a/web/src/types/frigateConfig.ts b/web/src/types/frigateConfig.ts index 26228dbaa..a38cbd847 100644 --- a/web/src/types/frigateConfig.ts +++ b/web/src/types/frigateConfig.ts @@ -298,7 +298,12 @@ export interface FrigateConfig { retry_interval: number; }; - go2rtc: Record; + go2rtc: { + streams: string[]; + webrtc: { + candidates: string[]; + }; + }; camera_groups: { [groupName: string]: CameraGroupConfig }; diff --git a/web/src/views/live/DraggableGridLayout.tsx b/web/src/views/live/DraggableGridLayout.tsx index 165829719..fc2d9bb52 100644 --- a/web/src/views/live/DraggableGridLayout.tsx +++ b/web/src/views/live/DraggableGridLayout.tsx @@ -41,6 +41,7 @@ import { TooltipContent, } from "@/components/ui/tooltip"; import { Toaster } from "@/components/ui/sonner"; +import useCameraLiveMode from "@/hooks/use-camera-live-mode"; type DraggableGridLayoutProps = { cameras: CameraConfig[]; @@ -75,36 +76,8 @@ export default function DraggableGridLayout({ // preferred live modes per camera - const [preferredLiveModes, setPreferredLiveModes] = useState<{ - [key: string]: LivePlayerMode; - }>({}); - - useEffect(() => { - if (!cameras) return; - - const mseSupported = - "MediaSource" in window || "ManagedMediaSource" in window; - - const newPreferredLiveModes = cameras.reduce( - (acc, camera) => { - const isRestreamed = - config && - Object.keys(config.go2rtc.streams || {}).includes( - camera.live.stream_name, - ); - - if (!mseSupported) { - acc[camera.name] = isRestreamed ? "webrtc" : "jsmpeg"; - } else { - acc[camera.name] = isRestreamed ? "mse" : "jsmpeg"; - } - return acc; - }, - {} as { [key: string]: LivePlayerMode }, - ); - - setPreferredLiveModes(newPreferredLiveModes); - }, [cameras, config, windowVisible]); + const { preferredLiveModes, setPreferredLiveModes, resetPreferredLiveMode } = + useCameraLiveMode(cameras, windowVisible); const ResponsiveGridLayout = useMemo(() => WidthProvider(Responsive), []); @@ -477,6 +450,7 @@ export default function DraggableGridLayout({ return newModes; }); }} + onResetLiveMode={() => resetPreferredLiveMode(camera.name)} > {isEditMode && showCircles && } @@ -635,6 +609,7 @@ type LivePlayerGridItemProps = { preferredLiveMode: LivePlayerMode; onClick: () => void; onError: (e: LivePlayerError) => void; + onResetLiveMode: () => void; }; const LivePlayerGridItem = React.forwardRef< @@ -655,6 +630,7 @@ const LivePlayerGridItem = React.forwardRef< preferredLiveMode, onClick, onError, + onResetLiveMode, ...props }, ref, @@ -676,6 +652,7 @@ const LivePlayerGridItem = React.forwardRef< preferredLiveMode={preferredLiveMode} onClick={onClick} onError={onError} + onResetLiveMode={onResetLiveMode} containerRef={ref as React.RefObject} /> {children} diff --git a/web/src/views/live/LiveCameraView.tsx b/web/src/views/live/LiveCameraView.tsx index db162198c..9dab7d916 100644 --- a/web/src/views/live/LiveCameraView.tsx +++ b/web/src/views/live/LiveCameraView.tsx @@ -227,6 +227,10 @@ export default function LiveCameraView({ return "webrtc"; } + if (!isRestreamed) { + return "jsmpeg"; + } + return "mse"; }, [lowBandwidth, mic, webRTC, isRestreamed]); @@ -286,14 +290,23 @@ export default function LiveCameraView({ } }, [fullscreen, isPortrait, cameraAspectRatio, containerAspectRatio]); - const handleError = useCallback((e: LivePlayerError) => { - if (e == "mse-decode") { - setWebRTC(true); - } else { - setWebRTC(false); - setLowBandwidth(true); - } - }, []); + const handleError = useCallback( + (e: LivePlayerError) => { + if (e) { + if ( + !webRTC && + config && + config.go2rtc?.webrtc?.candidates?.length > 0 + ) { + setWebRTC(true); + } else { + setWebRTC(false); + setLowBandwidth(true); + } + } + }, + [config, webRTC], + ); return ( diff --git a/web/src/views/live/LiveDashboardView.tsx b/web/src/views/live/LiveDashboardView.tsx index 00c046224..cac604e26 100644 --- a/web/src/views/live/LiveDashboardView.tsx +++ b/web/src/views/live/LiveDashboardView.tsx @@ -28,8 +28,9 @@ import DraggableGridLayout from "./DraggableGridLayout"; import { IoClose } from "react-icons/io5"; import { LuLayoutDashboard } from "react-icons/lu"; import { cn } from "@/lib/utils"; -import { LivePlayerError, LivePlayerMode } from "@/types/live"; +import { LivePlayerError } from "@/types/live"; import { FaCompress, FaExpand } from "react-icons/fa"; +import useCameraLiveMode from "@/hooks/use-camera-live-mode"; import { useResizeObserver } from "@/hooks/resize-observer"; type LiveDashboardViewProps = { @@ -129,9 +130,6 @@ export default function LiveDashboardView({ // camera live views const [autoLiveView] = usePersistence("autoLiveView", true); - const [preferredLiveModes, setPreferredLiveModes] = useState<{ - [key: string]: LivePlayerMode; - }>({}); const [{ height: containerHeight }] = useResizeObserver(containerRef); @@ -186,32 +184,8 @@ export default function LiveDashboardView({ }; }, []); - useEffect(() => { - if (!cameras) return; - - const mseSupported = - "MediaSource" in window || "ManagedMediaSource" in window; - - const newPreferredLiveModes = cameras.reduce( - (acc, camera) => { - const isRestreamed = - config && - Object.keys(config.go2rtc.streams || {}).includes( - camera.live.stream_name, - ); - - if (!mseSupported) { - acc[camera.name] = isRestreamed ? "webrtc" : "jsmpeg"; - } else { - acc[camera.name] = isRestreamed ? "mse" : "jsmpeg"; - } - return acc; - }, - {} as { [key: string]: LivePlayerMode }, - ); - - setPreferredLiveModes(newPreferredLiveModes); - }, [cameras, config, windowVisible]); + const { preferredLiveModes, setPreferredLiveModes, resetPreferredLiveMode } = + useCameraLiveMode(cameras, windowVisible); const cameraRef = useCallback( (node: HTMLElement | null) => { @@ -381,6 +355,7 @@ export default function LiveDashboardView({ autoLive={autoLiveView} onClick={() => onSelectCamera(camera.name)} onError={(e) => handleError(camera.name, e)} + onResetLiveMode={() => resetPreferredLiveMode(camera.name)} /> ); })} From 58ca44bd151b6697cf009e171709d1a752502ea3 Mon Sep 17 00:00:00 2001 From: Nicolas Mowen Date: Sun, 18 Aug 2024 07:41:10 -0600 Subject: [PATCH 51/81] Fix plus view resetting (#13160) --- web/src/pages/SubmitPlus.tsx | 231 +++++++++++++++++++---------------- 1 file changed, 124 insertions(+), 107 deletions(-) diff --git a/web/src/pages/SubmitPlus.tsx b/web/src/pages/SubmitPlus.tsx index 9d2b9ae5b..9424a3350 100644 --- a/web/src/pages/SubmitPlus.tsx +++ b/web/src/pages/SubmitPlus.tsx @@ -242,119 +242,136 @@ export default function SubmitPlus() {
- {isValidating ? ( - - ) : events?.length === 0 ? ( -
- - No snapshots found -
+ {!events?.length ? ( + <> + {isValidating ? ( + + ) : ( +
+ + No snapshots found +
+ )} + ) : ( -
- (!open ? setUpload(undefined) : null)} - > - - - - Submit To Frigate+ - - Objects in locations you want to avoid are not false - positives. Submitting them as false positives will confuse - the model. - - - +
+ (!open ? setUpload(undefined) : null)} + > + + - {upload?.id && ( - {`${upload?.label}`} - )} - - - - - - - - - + {upload?.id && ( + {`${upload?.label}`} + )} + + + + + + + + +
- {events?.map((event) => { - if (event.data.type != "object" || event.plus_id) { - return; - } + {events?.map((event) => { + if (event.data.type != "object" || event.plus_id) { + return; + } - return ( -
setUpload(event)} - > -
- -
- -
- - {[event.label].map((object) => { - return getIconForLabel( - object, - "size-3 text-white", - ); - })} -
- {Math.round(event.data.score * 100)}% -
-
-
-
-
- - {[event.label] - .map((text) => capitalizeFirstLetter(text)) - .sort() - .join(", ") - .replaceAll("-verified", "")} - -
+ return ( +
setUpload(event)} + > +
+ +
+ +
+ + {[event.label].map((object) => { + return getIconForLabel( + object, + "size-3 text-white", + ); + })} +
+ {Math.round(event.data.score * 100)}% +
+
+
+
+
+ + {[event.label] + .map((text) => capitalizeFirstLetter(text)) + .sort() + .join(", ") + .replaceAll("-verified", "")} + +
+
+
- -
- ); - })} - {!isValidating && !isDone &&
} -
+ ); + })} +
+ {!isDone && isValidating ? ( +
+ +
+ ) : ( +
+ )} + )}
From 8b2adb55ed3023bb1cacc20d4fec5edc6e509e33 Mon Sep 17 00:00:00 2001 From: Josh Hawkins <32435876+hawkeye217@users.noreply.github.com> Date: Sun, 18 Aug 2024 13:13:21 -0500 Subject: [PATCH 52/81] Adjust MSE player playback rate logic (#13164) * Fix MSE playback rate logic * don't adjust playback rate if we just started streaming * memoize onprogress --- web/src/components/player/MsePlayer.tsx | 184 +++++++++++++----------- 1 file changed, 97 insertions(+), 87 deletions(-) diff --git a/web/src/components/player/MsePlayer.tsx b/web/src/components/player/MsePlayer.tsx index 187567be6..52cf8f99c 100644 --- a/web/src/components/player/MsePlayer.tsx +++ b/web/src/components/player/MsePlayer.tsx @@ -364,9 +364,11 @@ function MSEPlayer({ const beta = 0.5; // steepness of exponential growth // don't adjust playback rate if we're close enough to live + // or if we just started streaming if ( - (bufferTime <= bufferThreshold && bufferThreshold < 3) || - bufferTime < 3 + ((bufferTime <= bufferThreshold && bufferThreshold < 3) || + bufferTime < 3) && + bufferTimes.current.length <= MAX_BUFFER_ENTRIES ) { return 1; } @@ -374,6 +376,98 @@ function MSEPlayer({ return Math.min(rate, 2); }; + const onProgress = useCallback(() => { + const bufferTime = getBufferedTime(videoRef.current); + + if ( + videoRef.current && + (videoRef.current.playbackRate === 1 || bufferTime < 3) + ) { + if (bufferTimes.current.length < MAX_BUFFER_ENTRIES) { + bufferTimes.current.push(bufferTime); + } else { + bufferTimes.current[bufferIndex.current] = bufferTime; + bufferIndex.current = (bufferIndex.current + 1) % MAX_BUFFER_ENTRIES; + } + } + + const bufferThreshold = calculateAdaptiveBufferThreshold(); + + // if we have > 3 seconds of buffered data and we're still not playing, + // something might be wrong - maybe codec issue, no audio, etc + // so mark the player as playing so that error handlers will fire + if (!isPlaying && playbackEnabled && bufferTime > 3) { + setIsPlaying(true); + lastJumpTimeRef.current = Date.now(); + onPlaying?.(); + } + + // if we have more than 10 seconds of buffer, something's wrong so error out + if ( + isPlaying && + playbackEnabled && + (bufferThreshold > 10 || bufferTime > 10) + ) { + onDisconnect(); + onError?.("stalled"); + } + + const playbackRate = calculateAdaptivePlaybackRate( + bufferTime, + bufferThreshold, + ); + + // if we're above our rolling average threshold or have > 3 seconds of + // buffered data and we're playing, we may have drifted from actual live + // time + if (videoRef.current && isPlaying && playbackEnabled) { + if ( + (isSafari || isIOS) && + bufferTime > 3 && + Date.now() - lastJumpTimeRef.current > BUFFERING_COOLDOWN_TIMEOUT + ) { + // Jump to live on Safari/iOS due to a change of playback rate causing re-buffering + jumpToLive(); + } else { + // increase/decrease playback rate to compensate - non Safari/iOS only + if (videoRef.current.playbackRate !== playbackRate) { + videoRef.current.playbackRate = playbackRate; + } + } + } + + if (onError != undefined) { + if (videoRef.current?.paused) { + return; + } + + if (bufferTimeout) { + clearTimeout(bufferTimeout); + setBufferTimeout(undefined); + } + + setBufferTimeout( + setTimeout(() => { + if ( + document.visibilityState === "visible" && + wsRef.current != null && + videoRef.current + ) { + onDisconnect(); + onError("stalled"); + } + }, 3000), + ); + } + }, [ + bufferTimeout, + isPlaying, + onDisconnect, + onError, + onPlaying, + playbackEnabled, + ]); + useEffect(() => { if (!playbackEnabled) { return; @@ -462,91 +556,7 @@ function MSEPlayer({ }} muted={!audioEnabled} onPause={handlePause} - onProgress={() => { - const bufferTime = getBufferedTime(videoRef.current); - - if ( - videoRef.current && - (videoRef.current.playbackRate === 1 || bufferTime < 3) - ) { - if (bufferTimes.current.length < MAX_BUFFER_ENTRIES) { - bufferTimes.current.push(bufferTime); - } else { - bufferTimes.current[bufferIndex.current] = bufferTime; - bufferIndex.current = - (bufferIndex.current + 1) % MAX_BUFFER_ENTRIES; - } - } - - const bufferThreshold = calculateAdaptiveBufferThreshold(); - - // if we have > 3 seconds of buffered data and we're still not playing, - // something might be wrong - maybe codec issue, no audio, etc - // so mark the player as playing so that error handlers will fire - if (!isPlaying && playbackEnabled && bufferTime > 3) { - setIsPlaying(true); - lastJumpTimeRef.current = Date.now(); - onPlaying?.(); - } - - // if we have more than 10 seconds of buffer, something's wrong so error out - if ( - isPlaying && - playbackEnabled && - (bufferThreshold > 10 || bufferTime > 10) - ) { - onDisconnect(); - onError?.("stalled"); - } - - const playbackRate = calculateAdaptivePlaybackRate( - bufferTime, - bufferThreshold, - ); - - // if we're above our rolling average threshold or have > 3 seconds of - // buffered data and we're playing, we may have drifted from actual live - // time, so increase playback rate to compensate - non safari/ios only - if ( - videoRef.current && - isPlaying && - playbackEnabled && - Date.now() - lastJumpTimeRef.current > BUFFERING_COOLDOWN_TIMEOUT - ) { - // Jump to live on Safari/iOS due to a change of playback rate causing re-buffering - if (isSafari || isIOS) { - if (bufferTime > 3) { - jumpToLive(); - } - } else { - videoRef.current.playbackRate = playbackRate; - } - } - - if (onError != undefined) { - if (videoRef.current?.paused) { - return; - } - - if (bufferTimeout) { - clearTimeout(bufferTimeout); - setBufferTimeout(undefined); - } - - setBufferTimeout( - setTimeout(() => { - if ( - document.visibilityState === "visible" && - wsRef.current != null && - videoRef.current - ) { - onDisconnect(); - onError("stalled"); - } - }, 3000), - ); - } - }} + onProgress={onProgress} onError={(e) => { if ( // @ts-expect-error code does exist From 65ceadda2bcf73ec03a8d57828a4055da1f0b209 Mon Sep 17 00:00:00 2001 From: Nicolas Mowen Date: Mon, 19 Aug 2024 10:45:55 -0600 Subject: [PATCH 53/81] Preview fixes (#13193) * Handle case where preview was saved late * fix timing --- web/src/components/player/PreviewPlayer.tsx | 15 ++++++------- web/src/hooks/use-camera-previews.ts | 24 +++++++++++++++++++-- 2 files changed, 28 insertions(+), 11 deletions(-) diff --git a/web/src/components/player/PreviewPlayer.tsx b/web/src/components/player/PreviewPlayer.tsx index 3b314f9a6..8c0394abd 100644 --- a/web/src/components/player/PreviewPlayer.tsx +++ b/web/src/components/player/PreviewPlayer.tsx @@ -16,6 +16,7 @@ import { isAndroid, isChrome, isMobile } from "react-device-detect"; import { TimeRange } from "@/types/timeline"; import { Skeleton } from "../ui/skeleton"; import { cn } from "@/lib/utils"; +import { usePreviewForTimeRange } from "@/hooks/use-camera-previews"; type PreviewPlayerProps = { className?: string; @@ -39,15 +40,11 @@ export default function PreviewPlayer({ onClick, }: PreviewPlayerProps) { const [currentHourFrame, setCurrentHourFrame] = useState(); - - const currentPreview = useMemo(() => { - return cameraPreviews.find( - (preview) => - preview.camera == camera && - Math.round(preview.start) >= timeRange.after && - Math.floor(preview.end) <= timeRange.before, - ); - }, [cameraPreviews, camera, timeRange]); + const currentPreview = usePreviewForTimeRange( + cameraPreviews, + camera, + timeRange, + ); if (currentPreview) { return ( diff --git a/web/src/hooks/use-camera-previews.ts b/web/src/hooks/use-camera-previews.ts index 3bdbd7efb..040b1081b 100644 --- a/web/src/hooks/use-camera-previews.ts +++ b/web/src/hooks/use-camera-previews.ts @@ -1,6 +1,6 @@ import { Preview } from "@/types/preview"; import { TimeRange } from "@/types/timeline"; -import { useEffect, useState } from "react"; +import { useEffect, useMemo, useState } from "react"; import useSWR from "swr"; type OptionalCameraPreviewProps = { @@ -8,7 +8,6 @@ type OptionalCameraPreviewProps = { autoRefresh?: boolean; fetchPreviews?: boolean; }; - export function useCameraPreviews( initialTimeRange: TimeRange, { @@ -32,3 +31,24 @@ export function useCameraPreviews( return allPreviews; } + +// we need to add a buffer of 5 seconds to the end preview times +// this ensures that if preview generation is running slowly +// and the previews are generated 1-5 seconds late +// it is not falsely thrown out. +const PREVIEW_END_BUFFER = 5; // seconds + +export function usePreviewForTimeRange( + allPreviews: Preview[], + camera: string, + timeRange: TimeRange, +) { + return useMemo(() => { + return allPreviews.find( + (preview) => + preview.camera == camera && + Math.ceil(preview.start) >= timeRange.after && + Math.floor(preview.end) <= timeRange.before + PREVIEW_END_BUFFER, + ); + }, [allPreviews, camera, timeRange]); +} From 4974defe6faaaea535b2a541f468f64c1f73df30 Mon Sep 17 00:00:00 2001 From: Nicolas Mowen Date: Mon, 19 Aug 2024 15:01:21 -0600 Subject: [PATCH 54/81] Dynamically detect if full screen is supported (#13197) --- web/src/components/player/HlsVideoPlayer.tsx | 6 ++-- .../player/dynamic/DynamicVideoPlayer.tsx | 3 ++ web/src/hooks/use-fullscreen.ts | 30 +++++++++++++++++-- web/src/pages/Live.tsx | 5 +++- web/src/views/events/RecordingView.tsx | 4 ++- web/src/views/live/LiveBirdseyeView.tsx | 20 ++++++++----- web/src/views/live/LiveCameraView.tsx | 4 ++- 7 files changed, 57 insertions(+), 15 deletions(-) diff --git a/web/src/components/player/HlsVideoPlayer.tsx b/web/src/components/player/HlsVideoPlayer.tsx index 5f499ade7..7f49a6cad 100644 --- a/web/src/components/player/HlsVideoPlayer.tsx +++ b/web/src/components/player/HlsVideoPlayer.tsx @@ -6,7 +6,7 @@ import { useState, } from "react"; import Hls from "hls.js"; -import { isAndroid, isDesktop, isIOS, isMobile } from "react-device-detect"; +import { isAndroid, isDesktop, isMobile } from "react-device-detect"; import { TransformComponent, TransformWrapper } from "react-zoom-pan-pinch"; import VideoControls from "./VideoControls"; import { VideoResolutionType } from "@/types/live"; @@ -33,6 +33,7 @@ type HlsVideoPlayerProps = { visible: boolean; currentSource: string; hotKeys: boolean; + supportsFullscreen: boolean; fullscreen: boolean; onClipEnded?: () => void; onPlayerLoaded?: () => void; @@ -49,6 +50,7 @@ export default function HlsVideoPlayer({ visible, currentSource, hotKeys, + supportsFullscreen, fullscreen, onClipEnded, onPlayerLoaded, @@ -180,7 +182,7 @@ export default function HlsVideoPlayer({ seek: true, playbackRate: true, plusUpload: config?.plus?.enabled == true, - fullscreen: !isIOS, + fullscreen: supportsFullscreen, }} setControlsOpen={setControlsOpen} setMuted={(muted) => setMuted(muted, true)} diff --git a/web/src/components/player/dynamic/DynamicVideoPlayer.tsx b/web/src/components/player/dynamic/DynamicVideoPlayer.tsx index caa709430..6c4e28e27 100644 --- a/web/src/components/player/dynamic/DynamicVideoPlayer.tsx +++ b/web/src/components/player/dynamic/DynamicVideoPlayer.tsx @@ -24,6 +24,7 @@ type DynamicVideoPlayerProps = { startTimestamp?: number; isScrubbing: boolean; hotKeys: boolean; + supportsFullscreen: boolean; fullscreen: boolean; onControllerReady: (controller: DynamicVideoController) => void; onTimestampUpdate?: (timestamp: number) => void; @@ -40,6 +41,7 @@ export default function DynamicVideoPlayer({ startTimestamp, isScrubbing, hotKeys, + supportsFullscreen, fullscreen, onControllerReady, onTimestampUpdate, @@ -201,6 +203,7 @@ export default function DynamicVideoPlayer({ visible={!(isScrubbing || isLoading)} currentSource={source} hotKeys={hotKeys} + supportsFullscreen={supportsFullscreen} fullscreen={fullscreen} onTimeUpdate={onTimeUpdate} onPlayerLoaded={onPlayerLoaded} diff --git a/web/src/hooks/use-fullscreen.ts b/web/src/hooks/use-fullscreen.ts index a0f0a1e56..c7082ab5c 100644 --- a/web/src/hooks/use-fullscreen.ts +++ b/web/src/hooks/use-fullscreen.ts @@ -1,4 +1,4 @@ -import { RefObject, useCallback, useEffect, useState } from "react"; +import { RefObject, useCallback, useEffect, useMemo, useState } from "react"; import nosleep from "nosleep.js"; const NoSleep = new nosleep(); @@ -147,5 +147,31 @@ export function useFullscreen( } }, [elementRef, handleFullscreenChange, handleFullscreenError]); - return { fullscreen, toggleFullscreen, error, clearError }; + // compatibility + + const supportsFullScreen = useMemo(() => { + // @ts-expect-error we need to check that fullscreen exists + if (document.exitFullscreen) return true; + // eslint-disable-next-line @typescript-eslint/no-explicit-any + if ((document as any).msExitFullscreen) + // eslint-disable-next-line @typescript-eslint/no-explicit-any + return true; + // eslint-disable-next-line @typescript-eslint/no-explicit-any + if ((document as any).webkitExitFullscreen) + // eslint-disable-next-line @typescript-eslint/no-explicit-any + return true; + // eslint-disable-next-line @typescript-eslint/no-explicit-any + if ((document as any).mozCancelFullScreen) + // eslint-disable-next-line @typescript-eslint/no-explicit-any + return true; + return false; + }, []); + + return { + fullscreen, + toggleFullscreen, + supportsFullScreen, + error, + clearError, + }; } diff --git a/web/src/pages/Live.tsx b/web/src/pages/Live.tsx index cd8cc6b0a..c088a5b04 100644 --- a/web/src/pages/Live.tsx +++ b/web/src/pages/Live.tsx @@ -36,7 +36,8 @@ function Live() { const mainRef = useRef(null); - const { fullscreen, toggleFullscreen } = useFullscreen(mainRef); + const { fullscreen, toggleFullscreen, supportsFullScreen } = + useFullscreen(mainRef); // document title @@ -100,6 +101,7 @@ function Live() {
{selectedCameraName === "birdseye" ? ( @@ -107,6 +109,7 @@ function Live() { diff --git a/web/src/views/events/RecordingView.tsx b/web/src/views/events/RecordingView.tsx index 1de3a690f..f01da9578 100644 --- a/web/src/views/events/RecordingView.tsx +++ b/web/src/views/events/RecordingView.tsx @@ -257,7 +257,8 @@ export function RecordingView({ // fullscreen - const { fullscreen, toggleFullscreen } = useFullscreen(mainLayoutRef); + const { fullscreen, toggleFullscreen, supportsFullScreen } = + useFullscreen(mainLayoutRef); // layout @@ -549,6 +550,7 @@ export function RecordingView({ mainControllerRef.current = controller; }} isScrubbing={scrubbing || exportMode == "timeline"} + supportsFullscreen={supportsFullScreen} setFullResolution={setFullResolution} toggleFullscreen={toggleFullscreen} containerRef={mainLayoutRef} diff --git a/web/src/views/live/LiveBirdseyeView.tsx b/web/src/views/live/LiveBirdseyeView.tsx index 70e20e77a..be69b43c8 100644 --- a/web/src/views/live/LiveBirdseyeView.tsx +++ b/web/src/views/live/LiveBirdseyeView.tsx @@ -22,11 +22,13 @@ import { TransformWrapper, TransformComponent } from "react-zoom-pan-pinch"; import useSWR from "swr"; type LiveBirdseyeViewProps = { + supportsFullscreen: boolean; fullscreen: boolean; toggleFullscreen: () => void; }; export default function LiveBirdseyeView({ + supportsFullscreen, fullscreen, toggleFullscreen, }: LiveBirdseyeViewProps) { @@ -155,14 +157,16 @@ export default function LiveBirdseyeView({
- + {supportsFullscreen && ( + + )} {!isIOS && !isFirefox && config.birdseye.restream && ( void; }; export default function LiveCameraView({ config, camera, + supportsFullscreen, fullscreen, toggleFullscreen, }: LiveCameraViewProps) { @@ -376,7 +378,7 @@ export default function LiveCameraView({ )} )} - {!isIOS && ( + {supportsFullscreen && ( Date: Mon, 19 Aug 2024 15:01:48 -0600 Subject: [PATCH 55/81] Ensure only enabled birdseye cameras are considered active (#13194) * Ensure only enabled birdseye cameras are considered active * Cleanup --- frigate/output/birdseye.py | 3 ++- 1 file changed, 2 insertions(+), 1 deletion(-) diff --git a/frigate/output/birdseye.py b/frigate/output/birdseye.py index 2b17a4cf1..6e0e2bc22 100644 --- a/frigate/output/birdseye.py +++ b/frigate/output/birdseye.py @@ -395,7 +395,8 @@ class BirdsEyeFrameManager: [ cam for cam, cam_data in self.cameras.items() - if cam_data["last_active_frame"] > 0 + if self.config.cameras[cam].birdseye.enabled + and cam_data["last_active_frame"] > 0 and cam_data["current_frame"] - cam_data["last_active_frame"] < self.inactivity_threshold ] From 9b4602acb39682febbcda284c64a5730e83ac6c1 Mon Sep 17 00:00:00 2001 From: Nicolas Mowen Date: Wed, 21 Aug 2024 08:19:07 -0600 Subject: [PATCH 56/81] UI fixes (#13246) * Fix bad data in stats * Add support for changes dialog when leaving without saving config editor * Fix scrolling into view --- web/src/pages/ConfigEditor.tsx | 37 +++++++++++++++++++++++++ web/src/pages/Logs.tsx | 1 + web/src/views/system/GeneralMetrics.tsx | 16 +++++++---- 3 files changed, 48 insertions(+), 6 deletions(-) diff --git a/web/src/pages/ConfigEditor.tsx b/web/src/pages/ConfigEditor.tsx index 55df04e34..dc351e7f7 100644 --- a/web/src/pages/ConfigEditor.tsx +++ b/web/src/pages/ConfigEditor.tsx @@ -124,12 +124,49 @@ function ConfigEditor() { }; }); + // monitoring state + + const [hasChanges, setHasChanges] = useState(false); + + useEffect(() => { + if (!config || !modelRef.current) { + return; + } + + modelRef.current.onDidChangeContent(() => { + if (modelRef.current?.getValue() != config) { + setHasChanges(true); + } else { + setHasChanges(false); + } + }); + }, [config]); + useEffect(() => { if (config && modelRef.current) { modelRef.current.setValue(config); + setHasChanges(false); } }, [config]); + useEffect(() => { + let listener: ((e: BeforeUnloadEvent) => void) | undefined; + if (hasChanges) { + listener = (e) => { + e.preventDefault(); + e.returnValue = true; + return "Exit without saving?"; + }; + window.addEventListener("beforeunload", listener); + } + + return () => { + if (listener) { + window.removeEventListener("beforeunload", listener); + } + }; + }, [hasChanges]); + if (!config) { return ; } diff --git a/web/src/pages/Logs.tsx b/web/src/pages/Logs.tsx index 0691cb635..582190098 100644 --- a/web/src/pages/Logs.tsx +++ b/web/src/pages/Logs.tsx @@ -286,6 +286,7 @@ function Logs() { key={item} className={`flex items-center justify-between gap-2 ${logService == item ? "" : "text-muted-foreground"}`} value={item} + data-nav-item={item} aria-label={`Select ${item}`} >
{item}
diff --git a/web/src/views/system/GeneralMetrics.tsx b/web/src/views/system/GeneralMetrics.tsx index fa23d47b0..f617e9654 100644 --- a/web/src/views/system/GeneralMetrics.tsx +++ b/web/src/views/system/GeneralMetrics.tsx @@ -163,7 +163,7 @@ export default function GeneralMetrics({ series[key] = { name: key, data: [] }; } - const data = stats.cpu_usages[detStats.pid.toString()].cpu; + const data = stats.cpu_usages[detStats.pid.toString()]?.cpu; if (data != undefined) { series[key].data.push({ @@ -304,7 +304,7 @@ export default function GeneralMetrics({ series[key] = { name: key, data: [] }; } - const data = stats.cpu_usages[procStats.pid.toString()].cpu; + const data = stats.cpu_usages[procStats.pid.toString()]?.cpu; if (data != undefined) { series[key].data.push({ @@ -338,10 +338,14 @@ export default function GeneralMetrics({ series[key] = { name: key, data: [] }; } - series[key].data.push({ - x: statsIdx + 1, - y: stats.cpu_usages[procStats.pid.toString()].mem, - }); + const data = stats.cpu_usages[procStats.pid.toString()]?.mem; + + if (data) { + series[key].data.push({ + x: statsIdx + 1, + y: data, + }); + } } }); }); From 13bb9dd71555910e26b3b343fffff65019550957 Mon Sep 17 00:00:00 2001 From: Nicolas Mowen Date: Thu, 22 Aug 2024 07:06:26 -0600 Subject: [PATCH 57/81] Fix case where user's cgroup says it has 0 cpu cores (#13271) --- docker/main/rootfs/etc/s6-overlay/s6-rc.d/nginx/run | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/docker/main/rootfs/etc/s6-overlay/s6-rc.d/nginx/run b/docker/main/rootfs/etc/s6-overlay/s6-rc.d/nginx/run index a6436abd4..677126a6d 100755 --- a/docker/main/rootfs/etc/s6-overlay/s6-rc.d/nginx/run +++ b/docker/main/rootfs/etc/s6-overlay/s6-rc.d/nginx/run @@ -38,7 +38,7 @@ function get_cpus() { fi local cpus - if [ -n "${quota}" ] && [ -n "${period}" ]; then + if [ "${period}" != "0" ] && [ -n "${quota}" ] && [ -n "${period}" ]; then cpus=$((quota / period)) if [ "$cpus" -eq 0 ]; then cpus=1 From 19c253b4299adcea8b27d1c25893ea5e43b6f26d Mon Sep 17 00:00:00 2001 From: Josh Hawkins <32435876+hawkeye217@users.noreply.github.com> Date: Fri, 23 Aug 2024 07:44:31 -0500 Subject: [PATCH 58/81] Update discussion templates (#13291) * Revamp support discussion templates * move text to description * remove duplicate logs box * ffprobe on camera support * longer description on config support --- .../DISCUSSION_TEMPLATE/camera-support.yml | 41 +++++++++++-- .../DISCUSSION_TEMPLATE/config-support.yml | 18 +++++- .../DISCUSSION_TEMPLATE/detector-support.yml | 37 +++++++++-- .../DISCUSSION_TEMPLATE/general-support.yml | 39 ++++++++++-- .../hardware-acceleration-support.yml | 39 ++++++++++-- .github/DISCUSSION_TEMPLATE/question.yml | 14 ++++- .github/DISCUSSION_TEMPLATE/report-a-bug.yml | 61 +++++++++++++++++-- 7 files changed, 222 insertions(+), 27 deletions(-) diff --git a/.github/DISCUSSION_TEMPLATE/camera-support.yml b/.github/DISCUSSION_TEMPLATE/camera-support.yml index 6f99790df..ec6497309 100644 --- a/.github/DISCUSSION_TEMPLATE/camera-support.yml +++ b/.github/DISCUSSION_TEMPLATE/camera-support.yml @@ -1,6 +1,16 @@ title: "[Camera Support]: " labels: ["support", "triage"] body: + - type: markdown + attributes: + value: | + Use this form for support or questions for an issue with your cameras. + + Before submitting your support request, please [search the discussions][discussions], read the [official Frigate documentation][docs], and read the [Frigate FAQ][faq] pinned at the Discussion page to see if your question has already been answered by the community. + + [discussions]: https://www.github.com/blakeblackshear/frigate/discussions + [docs]: https://docs.frigate.video + [faq]: https://github.com/blakeblackshear/frigate/discussions/12724 - type: textarea id: description attributes: @@ -11,9 +21,15 @@ body: id: version attributes: label: Version - description: Visible on the System page in the Web UI + description: Visible on the System page in the Web UI. Please include the full version including the build identifier (eg. 0.14.0-ea36ds1) validations: required: true + - type: input + attributes: + label: What browser(s) are you using? + placeholder: Google Chrome 88.0.4324.150 + description: > + Provide the full name and don't forget to add the version! - type: textarea id: config attributes: @@ -23,10 +39,18 @@ body: validations: required: true - type: textarea - id: logs + id: frigatelogs attributes: - label: Relevant log output - description: Please copy and paste any relevant log output. This will be automatically formatted into code, so no need for backticks. + label: Relevant Frigate log output + description: Please copy and paste any relevant Frigate log output. Include logs before and after your exact error when possible. This will be automatically formatted into code, so no need for backticks. + render: shell + validations: + required: true + - type: textarea + id: go2rtclogs + attributes: + label: Relevant go2rtc log output + description: Please copy and paste any relevant go2rtc log output. Include logs before and after your exact error when possible. This will be automatically formatted into code, so no need for backticks. render: shell validations: required: true @@ -34,7 +58,7 @@ body: id: ffprobe attributes: label: FFprobe output from your camera - description: Run `ffprobe ` and provide output below + description: Run `ffprobe ` from within the Frigate container if possible, and provide output below render: shell validations: required: true @@ -78,7 +102,7 @@ body: - TensorRT - RKNN - Other - - CPU (no coral) + - CPU (no Coral) validations: required: true - type: dropdown @@ -98,6 +122,11 @@ body: description: Dahua, hikvision, amcrest, reolink, etc and model number validations: required: true + - type: textarea + id: screenshots + attributes: + label: Screenshots of the Frigate UI's System metrics pages and any other screenshots pertinent to your issue. + description: Drag and drop for images is possible in this field. - type: textarea id: other attributes: diff --git a/.github/DISCUSSION_TEMPLATE/config-support.yml b/.github/DISCUSSION_TEMPLATE/config-support.yml index 3a9265f44..72b11e634 100644 --- a/.github/DISCUSSION_TEMPLATE/config-support.yml +++ b/.github/DISCUSSION_TEMPLATE/config-support.yml @@ -1,6 +1,16 @@ title: "[Config Support]: " labels: ["support", "triage"] body: + - type: markdown + attributes: + value: | + Use this form for support or questions related to Frigate's configuration and config file. + + Before submitting your support request, please [search the discussions][discussions], read the [official Frigate documentation][docs], and read the [Frigate FAQ][faq] pinned at the Discussion page to see if your question has already been answered by the community. + + [discussions]: https://www.github.com/blakeblackshear/frigate/discussions + [docs]: https://docs.frigate.video + [faq]: https://github.com/blakeblackshear/frigate/discussions/12724 - type: textarea id: description attributes: @@ -11,7 +21,7 @@ body: id: version attributes: label: Version - description: Visible on the System page in the Web UI + description: Visible on the System page in the Web UI. Please include the full version including the build identifier (eg. 0.14.0-ea36ds1) validations: required: true - type: textarea @@ -26,7 +36,7 @@ body: id: logs attributes: label: Relevant log output - description: Please copy and paste any relevant log output. This will be automatically formatted into code, so no need for backticks. + description: Please copy and paste any relevant log output, including any go2rtc and Frigate logs. Include logs before and after your exact error when possible. This will be automatically formatted into code, so no need for backticks. render: shell validations: required: true @@ -73,6 +83,10 @@ body: - CPU (no coral) validations: required: true + - type: textarea + id: screenshots + attributes: + label: Screenshots of the Frigate UI's System metrics pages and any other screenshots pertinent to your issue. Drag and drop or simple cut/paste is possible in this field. - type: textarea id: other attributes: diff --git a/.github/DISCUSSION_TEMPLATE/detector-support.yml b/.github/DISCUSSION_TEMPLATE/detector-support.yml index c09aec2ec..8d1a57ff1 100644 --- a/.github/DISCUSSION_TEMPLATE/detector-support.yml +++ b/.github/DISCUSSION_TEMPLATE/detector-support.yml @@ -1,6 +1,16 @@ title: "[Detector Support]: " labels: ["support", "triage"] body: + - type: markdown + attributes: + value: | + Use this form for support or questions related to Frigate's object detectors. + + Before submitting your support request, please [search the discussions][discussions], read the [official Frigate documentation][docs], and read the [Frigate FAQ][faq] pinned at the Discussion page to see if your question has already been answered by the community. + + [discussions]: https://www.github.com/blakeblackshear/frigate/discussions + [docs]: https://docs.frigate.video + [faq]: https://github.com/blakeblackshear/frigate/discussions/12724 - type: textarea id: description attributes: @@ -11,9 +21,15 @@ body: id: version attributes: label: Version - description: Visible on the System page in the Web UI + description: Visible on the System page in the Web UI. Please include the full version including the build identifier (eg. 0.14.0-ea36ds1) validations: required: true + - type: input + attributes: + label: What browser(s) are you using? + placeholder: Google Chrome 88.0.4324.150 + description: > + Provide the full name and don't forget to add the version! - type: textarea id: config attributes: @@ -31,10 +47,18 @@ body: validations: required: true - type: textarea - id: logs + id: frigatelogs attributes: - label: Relevant log output - description: Please copy and paste any relevant log output. This will be automatically formatted into code, so no need for backticks. + label: Relevant Frigate log output + description: Please copy and paste any relevant Frigate log output. Include logs before and after your exact error when possible. This will be automatically formatted into code, so no need for backticks. + render: shell + validations: + required: true + - type: textarea + id: go2rtclogs + attributes: + label: Relevant go2rtc log output + description: Please copy and paste any relevant go2rtc log output. Include logs before and after your exact error when possible. This will be automatically formatted into code, so no need for backticks. render: shell validations: required: true @@ -75,6 +99,11 @@ body: - CPU (no coral) validations: required: true + - type: textarea + id: screenshots + attributes: + label: Screenshots of the Frigate UI's System metrics pages and any other screenshots pertinent to your issue. + description: Drag and drop for images is possible in this field. - type: textarea id: other attributes: diff --git a/.github/DISCUSSION_TEMPLATE/general-support.yml b/.github/DISCUSSION_TEMPLATE/general-support.yml index cb9bd1992..b3592797c 100644 --- a/.github/DISCUSSION_TEMPLATE/general-support.yml +++ b/.github/DISCUSSION_TEMPLATE/general-support.yml @@ -1,6 +1,16 @@ title: "[Support]: " labels: ["support", "triage"] body: + - type: markdown + attributes: + value: | + Use this form for support for issues that don't fall into any specific category. + + Before submitting your support request, please [search the discussions][discussions], read the [official Frigate documentation][docs], and read the [Frigate FAQ][faq] pinned at the Discussion page to see if your question has already been answered by the community. + + [discussions]: https://www.github.com/blakeblackshear/frigate/discussions + [docs]: https://docs.frigate.video + [faq]: https://github.com/blakeblackshear/frigate/discussions/12724 - type: textarea id: description attributes: @@ -11,9 +21,15 @@ body: id: version attributes: label: Version - description: Visible on the System page in the Web UI + description: Visible on the System page in the Web UI. Please include the full version including the build identifier (eg. 0.14.0-ea36ds1) validations: required: true + - type: input + attributes: + label: What browser(s) are you using? + placeholder: Google Chrome 88.0.4324.150 + description: > + Provide the full name and don't forget to add the version! - type: textarea id: config attributes: @@ -23,10 +39,18 @@ body: validations: required: true - type: textarea - id: logs + id: frigatelogs attributes: - label: Relevant log output - description: Please copy and paste any relevant log output. This will be automatically formatted into code, so no need for backticks. + label: Relevant Frigate log output + description: Please copy and paste any relevant Frigate log output. Include logs before and after your exact error when possible. This will be automatically formatted into code, so no need for backticks. + render: shell + validations: + required: true + - type: textarea + id: go2rtclogs + attributes: + label: Relevant go2rtc log output + description: Please copy and paste any relevant go2rtc log output. Include logs before and after your exact error when possible. This will be automatically formatted into code, so no need for backticks. render: shell validations: required: true @@ -34,7 +58,7 @@ body: id: ffprobe attributes: label: FFprobe output from your camera - description: Run `ffprobe ` and provide output below + description: Run `ffprobe ` from within the Frigate container if possible, and provide output below render: shell validations: required: true @@ -98,6 +122,11 @@ body: description: Dahua, hikvision, amcrest, reolink, etc and model number validations: required: true + - type: textarea + id: screenshots + attributes: + label: Screenshots of the Frigate UI's System metrics pages and any other screenshots pertinent to your issue. + description: Drag and drop for images is possible in this field. - type: textarea id: other attributes: diff --git a/.github/DISCUSSION_TEMPLATE/hardware-acceleration-support.yml b/.github/DISCUSSION_TEMPLATE/hardware-acceleration-support.yml index 43960c537..e50b21b9b 100644 --- a/.github/DISCUSSION_TEMPLATE/hardware-acceleration-support.yml +++ b/.github/DISCUSSION_TEMPLATE/hardware-acceleration-support.yml @@ -1,6 +1,16 @@ title: "[HW Accel Support]: " labels: ["support", "triage"] body: + - type: markdown + attributes: + value: | + Use this form to submit a support request for hardware acceleration issues. + + Before submitting your support request, please [search the discussions][discussions], read the [official Frigate documentation][docs], and read the [Frigate FAQ][faq] pinned at the Discussion page to see if your question has already been answered by the community. + + [discussions]: https://www.github.com/blakeblackshear/frigate/discussions + [docs]: https://docs.frigate.video + [faq]: https://github.com/blakeblackshear/frigate/discussions/12724 - type: textarea id: description attributes: @@ -11,9 +21,15 @@ body: id: version attributes: label: Version - description: Visible on the System page in the Web UI + description: Visible on the System page in the Web UI. Please include the full version including the build identifier (eg. 0.14.0-ea36ds1) validations: required: true + - type: input + attributes: + label: In which browser(s) are you experiencing the issue with? + placeholder: Google Chrome 88.0.4324.150 + description: > + Provide the full name and don't forget to add the version! - type: textarea id: config attributes: @@ -31,10 +47,18 @@ body: validations: required: true - type: textarea - id: logs + id: frigatelogs attributes: - label: Relevant log output - description: Please copy and paste any relevant log output. This will be automatically formatted into code, so no need for backticks. + label: Relevant Frigate log output + description: Please copy and paste any relevant Frigate log output. Include logs before and after your exact error when possible. This will be automatically formatted into code, so no need for backticks. + render: shell + validations: + required: true + - type: textarea + id: go2rtclogs + attributes: + label: Relevant go2rtc log output + description: Please copy and paste any relevant go2rtc log output. Include logs before and after your exact error when possible. This will be automatically formatted into code, so no need for backticks. render: shell validations: required: true @@ -42,7 +66,7 @@ body: id: ffprobe attributes: label: FFprobe output from your camera - description: Run `ffprobe ` and provide output below + description: Run `ffprobe ` from within the Frigate container if possible, and provide output below render: shell validations: required: true @@ -87,6 +111,11 @@ body: description: Dahua, hikvision, amcrest, reolink, etc and model number validations: required: true + - type: textarea + id: screenshots + attributes: + label: Screenshots of the Frigate UI's System metrics pages and any other screenshots pertinent to your issue. + description: Drag and drop for images is possible in this field. - type: textarea id: other attributes: diff --git a/.github/DISCUSSION_TEMPLATE/question.yml b/.github/DISCUSSION_TEMPLATE/question.yml index 41339e609..8043ab5a9 100644 --- a/.github/DISCUSSION_TEMPLATE/question.yml +++ b/.github/DISCUSSION_TEMPLATE/question.yml @@ -1,9 +1,21 @@ title: "[Question]: " labels: ["question"] body: + - type: markdown + attributes: + value: | + Use this form for questions you have about Frigate. + + Before submitting your question, please [search the discussions][discussions], read the [official Frigate documentation][docs], and read the [Frigate FAQ][faq] pinned at the Discussion page to see if your question has already been answered by the community. + + **If you are looking for support, start a new discussion and use a support category.** + + [discussions]: https://www.github.com/blakeblackshear/frigate/discussions + [docs]: https://docs.frigate.video + [faq]: https://github.com/blakeblackshear/frigate/discussions/12724 - type: textarea id: description attributes: - label: "What is your question:" + label: "What is your question?" validations: required: true diff --git a/.github/DISCUSSION_TEMPLATE/report-a-bug.yml b/.github/DISCUSSION_TEMPLATE/report-a-bug.yml index c1c34fb51..472dffb73 100644 --- a/.github/DISCUSSION_TEMPLATE/report-a-bug.yml +++ b/.github/DISCUSSION_TEMPLATE/report-a-bug.yml @@ -1,25 +1,65 @@ title: "[Bug]: " labels: ["bug", "triage"] body: + - type: markdown + attributes: + value: | + Use this form to submit a reproducible bug in Frigate or Frigate's UI. + + Before submitting your bug report, please [search the discussions][discussions], look at recent open and closed [pull requests][prs], read the [official Frigate documentation][docs], and read the [Frigate FAQ][faq] pinned at the Discussion page to see if your bug has already been fixed by the developers or reported by the community. + + **If you are unsure if your issue is actually a bug or not, please submit a support request first.** + + [discussions]: https://www.github.com/blakeblackshear/frigate/discussions + [prs]: https://www.github.com/blakeblackshear/frigate/pulls + [docs]: https://docs.frigate.video + [faq]: https://github.com/blakeblackshear/frigate/discussions/12724 + - type: checkboxes + attributes: + label: Checklist + description: Please verify that you've followed these steps + options: + - label: I have updated to the latest available Frigate version. + required: true + - label: I have cleared the cache of my browser. + required: true + - label: I have tried a different browser to see if it is related to my browser. + required: true + - label: I have tried reproducing the issue in [incognito mode](https://www.computerworld.com/article/1719851/how-to-go-incognito-in-chrome-firefox-safari-and-edge.html) to rule out problems with any third party extensions or plugins I have installed. - type: textarea id: description attributes: label: Describe the problem you are having + description: Provide a clear and concise description of what the bug is. validations: required: true - type: textarea id: steps attributes: label: Steps to reproduce + description: | + Please tell us exactly how to reproduce your issue. + Provide clear and concise step by step instructions and add code snippets if needed. + value: | + 1. + 2. + 3. + ... validations: required: true - type: input id: version attributes: label: Version - description: Visible on the System page in the Web UI + description: Visible on the System page in the Web UI. Please include the full version including the build identifier (eg. 0.14.0-ea36ds1) validations: required: true + - type: input + attributes: + label: In which browser(s) are you experiencing the issue with? + placeholder: Google Chrome 88.0.4324.150 + description: > + Provide the full name and don't forget to add the version! - type: textarea id: config attributes: @@ -29,10 +69,18 @@ body: validations: required: true - type: textarea - id: logs + id: frigatelogs attributes: - label: Relevant log output - description: Please copy and paste any relevant log output. This will be automatically formatted into code, so no need for backticks. + label: Relevant Frigate log output + description: Please copy and paste any relevant Frigate log output. Include logs before and after your exact error when possible. This will be automatically formatted into code, so no need for backticks. + render: shell + validations: + required: true + - type: textarea + id: go2rtclogs + attributes: + label: Relevant go2rtc log output + description: Please copy and paste any relevant go2rtc log output. Include logs before and after your exact error when possible. This will be automatically formatted into code, so no need for backticks. render: shell validations: required: true @@ -77,6 +125,11 @@ body: description: Dahua, hikvision, amcrest, reolink, etc and model number validations: required: true + - type: textarea + id: screenshots + attributes: + label: Screenshots of the Frigate UI's System metrics pages and any other screenshots pertinent to your issue. + description: Drag and drop for images is possible in this field. - type: textarea id: other attributes: From 1529ee59fed6852990ee352f92ed7655f52e1407 Mon Sep 17 00:00:00 2001 From: Josh Hawkins <32435876+hawkeye217@users.noreply.github.com> Date: Fri, 23 Aug 2024 07:58:39 -0500 Subject: [PATCH 59/81] Fix discussion templates (#13292) * Fix yaml spacing for discussion templates * Remove browser question from detectors --- .github/DISCUSSION_TEMPLATE/config-support.yml | 16 ++++++++++++---- .github/DISCUSSION_TEMPLATE/detector-support.yml | 8 +------- .github/DISCUSSION_TEMPLATE/question.yml | 2 +- 3 files changed, 14 insertions(+), 12 deletions(-) diff --git a/.github/DISCUSSION_TEMPLATE/config-support.yml b/.github/DISCUSSION_TEMPLATE/config-support.yml index 72b11e634..954b2d3e3 100644 --- a/.github/DISCUSSION_TEMPLATE/config-support.yml +++ b/.github/DISCUSSION_TEMPLATE/config-support.yml @@ -1,7 +1,7 @@ title: "[Config Support]: " labels: ["support", "triage"] body: - - type: markdown + - type: markdown attributes: value: | Use this form for support or questions related to Frigate's configuration and config file. @@ -33,10 +33,18 @@ body: validations: required: true - type: textarea - id: logs + id: frigatelogs attributes: - label: Relevant log output - description: Please copy and paste any relevant log output, including any go2rtc and Frigate logs. Include logs before and after your exact error when possible. This will be automatically formatted into code, so no need for backticks. + label: Relevant Frigate log output + description: Please copy and paste any relevant Frigate log output. Include logs before and after your exact error when possible. This will be automatically formatted into code, so no need for backticks. + render: shell + validations: + required: true + - type: textarea + id: go2rtclogs + attributes: + label: Relevant go2rtc log output + description: Please copy and paste any relevant go2rtc log output. Include logs before and after your exact error when possible. This will be automatically formatted into code, so no need for backticks. render: shell validations: required: true diff --git a/.github/DISCUSSION_TEMPLATE/detector-support.yml b/.github/DISCUSSION_TEMPLATE/detector-support.yml index 8d1a57ff1..18f24f4d2 100644 --- a/.github/DISCUSSION_TEMPLATE/detector-support.yml +++ b/.github/DISCUSSION_TEMPLATE/detector-support.yml @@ -1,7 +1,7 @@ title: "[Detector Support]: " labels: ["support", "triage"] body: - - type: markdown + - type: markdown attributes: value: | Use this form for support or questions related to Frigate's object detectors. @@ -24,12 +24,6 @@ body: description: Visible on the System page in the Web UI. Please include the full version including the build identifier (eg. 0.14.0-ea36ds1) validations: required: true - - type: input - attributes: - label: What browser(s) are you using? - placeholder: Google Chrome 88.0.4324.150 - description: > - Provide the full name and don't forget to add the version! - type: textarea id: config attributes: diff --git a/.github/DISCUSSION_TEMPLATE/question.yml b/.github/DISCUSSION_TEMPLATE/question.yml index 8043ab5a9..6a4789c9c 100644 --- a/.github/DISCUSSION_TEMPLATE/question.yml +++ b/.github/DISCUSSION_TEMPLATE/question.yml @@ -1,7 +1,7 @@ title: "[Question]: " labels: ["question"] body: - - type: markdown + - type: markdown attributes: value: | Use this form for questions you have about Frigate. From f8fd746678afa575921d8a68753fa38a9d1e6f2b Mon Sep 17 00:00:00 2001 From: Nicolas Mowen Date: Fri, 23 Aug 2024 08:51:59 -0600 Subject: [PATCH 60/81] Fix delayed preview not showing (#13295) --- web/src/components/player/PreviewPlayer.tsx | 12 +++++----- web/src/hooks/use-camera-previews.ts | 25 ++++++++++++++------- 2 files changed, 22 insertions(+), 15 deletions(-) diff --git a/web/src/components/player/PreviewPlayer.tsx b/web/src/components/player/PreviewPlayer.tsx index 8c0394abd..6ff5e1590 100644 --- a/web/src/components/player/PreviewPlayer.tsx +++ b/web/src/components/player/PreviewPlayer.tsx @@ -16,7 +16,10 @@ import { isAndroid, isChrome, isMobile } from "react-device-detect"; import { TimeRange } from "@/types/timeline"; import { Skeleton } from "../ui/skeleton"; import { cn } from "@/lib/utils"; -import { usePreviewForTimeRange } from "@/hooks/use-camera-previews"; +import { + getPreviewForTimeRange, + usePreviewForTimeRange, +} from "@/hooks/use-camera-previews"; type PreviewPlayerProps = { className?: string; @@ -243,12 +246,7 @@ function PreviewVideoPlayer({ return; } - const preview = cameraPreviews.find( - (preview) => - preview.camera == camera && - Math.round(preview.start) >= timeRange.after && - Math.floor(preview.end) <= timeRange.before, - ); + const preview = getPreviewForTimeRange(cameraPreviews, camera, timeRange); if (preview != currentPreview) { controller.newPreviewLoaded = false; diff --git a/web/src/hooks/use-camera-previews.ts b/web/src/hooks/use-camera-previews.ts index 040b1081b..921fecd70 100644 --- a/web/src/hooks/use-camera-previews.ts +++ b/web/src/hooks/use-camera-previews.ts @@ -38,17 +38,26 @@ export function useCameraPreviews( // it is not falsely thrown out. const PREVIEW_END_BUFFER = 5; // seconds +export function getPreviewForTimeRange( + allPreviews: Preview[], + camera: string, + timeRange: TimeRange, +) { + return allPreviews.find( + (preview) => + preview.camera == camera && + Math.ceil(preview.start) >= timeRange.after && + Math.floor(preview.end) <= timeRange.before + PREVIEW_END_BUFFER, + ); +} + export function usePreviewForTimeRange( allPreviews: Preview[], camera: string, timeRange: TimeRange, ) { - return useMemo(() => { - return allPreviews.find( - (preview) => - preview.camera == camera && - Math.ceil(preview.start) >= timeRange.after && - Math.floor(preview.end) <= timeRange.before + PREVIEW_END_BUFFER, - ); - }, [allPreviews, camera, timeRange]); + return useMemo( + () => getPreviewForTimeRange(allPreviews, camera, timeRange), + [allPreviews, camera, timeRange], + ); } From 4de088d7255569d35c41c4f3b7eaa29a40577a27 Mon Sep 17 00:00:00 2001 From: Josh Hawkins <32435876+hawkeye217@users.noreply.github.com> Date: Fri, 23 Aug 2024 18:05:14 -0500 Subject: [PATCH 61/81] Update discussion templates (#13303) * Update discussion templates * camera support go2rtc --- .github/DISCUSSION_TEMPLATE/camera-support.yml | 8 +++++--- .github/DISCUSSION_TEMPLATE/config-support.yml | 3 ++- .github/DISCUSSION_TEMPLATE/detector-support.yml | 8 +++++--- .github/DISCUSSION_TEMPLATE/general-support.yml | 6 +++--- .../hardware-acceleration-support.yml | 8 +++++--- .github/DISCUSSION_TEMPLATE/report-a-bug.yml | 16 +++++++++++++--- 6 files changed, 33 insertions(+), 16 deletions(-) diff --git a/.github/DISCUSSION_TEMPLATE/camera-support.yml b/.github/DISCUSSION_TEMPLATE/camera-support.yml index ec6497309..bbbffea50 100644 --- a/.github/DISCUSSION_TEMPLATE/camera-support.yml +++ b/.github/DISCUSSION_TEMPLATE/camera-support.yml @@ -50,7 +50,7 @@ body: id: go2rtclogs attributes: label: Relevant go2rtc log output - description: Please copy and paste any relevant go2rtc log output. Include logs before and after your exact error when possible. This will be automatically formatted into code, so no need for backticks. + description: Please copy and paste any relevant go2rtc log output. Include logs before and after your exact error when possible. Logs can be viewed via the Frigate UI, Docker, or the go2rtc dashboard. This will be automatically formatted into code, so no need for backticks. render: shell validations: required: true @@ -125,8 +125,10 @@ body: - type: textarea id: screenshots attributes: - label: Screenshots of the Frigate UI's System metrics pages and any other screenshots pertinent to your issue. - description: Drag and drop for images is possible in this field. + label: Screenshots of the Frigate UI's System metrics pages + description: Drag and drop for images is possible in this field. Please post screenshots of at least General and Cameras tabs. + validations: + required: true - type: textarea id: other attributes: diff --git a/.github/DISCUSSION_TEMPLATE/config-support.yml b/.github/DISCUSSION_TEMPLATE/config-support.yml index 954b2d3e3..5b70b1d91 100644 --- a/.github/DISCUSSION_TEMPLATE/config-support.yml +++ b/.github/DISCUSSION_TEMPLATE/config-support.yml @@ -94,7 +94,8 @@ body: - type: textarea id: screenshots attributes: - label: Screenshots of the Frigate UI's System metrics pages and any other screenshots pertinent to your issue. Drag and drop or simple cut/paste is possible in this field. + label: Screenshots of the Frigate UI's System metrics pages + description: Drag and drop or simple cut/paste is possible in this field - type: textarea id: other attributes: diff --git a/.github/DISCUSSION_TEMPLATE/detector-support.yml b/.github/DISCUSSION_TEMPLATE/detector-support.yml index 18f24f4d2..e4ae976a3 100644 --- a/.github/DISCUSSION_TEMPLATE/detector-support.yml +++ b/.github/DISCUSSION_TEMPLATE/detector-support.yml @@ -52,7 +52,7 @@ body: id: go2rtclogs attributes: label: Relevant go2rtc log output - description: Please copy and paste any relevant go2rtc log output. Include logs before and after your exact error when possible. This will be automatically formatted into code, so no need for backticks. + description: Please copy and paste any relevant go2rtc log output. Include logs before and after your exact error when possible. Logs can be viewed via the Frigate UI, Docker, or the go2rtc dashboard. This will be automatically formatted into code, so no need for backticks. render: shell validations: required: true @@ -96,8 +96,10 @@ body: - type: textarea id: screenshots attributes: - label: Screenshots of the Frigate UI's System metrics pages and any other screenshots pertinent to your issue. - description: Drag and drop for images is possible in this field. + label: Screenshots of the Frigate UI's System metrics pages + description: Drag and drop for images is possible in this field. Please post screenshots of at least General and Cameras tabs. + validations: + required: true - type: textarea id: other attributes: diff --git a/.github/DISCUSSION_TEMPLATE/general-support.yml b/.github/DISCUSSION_TEMPLATE/general-support.yml index b3592797c..0ae7d7083 100644 --- a/.github/DISCUSSION_TEMPLATE/general-support.yml +++ b/.github/DISCUSSION_TEMPLATE/general-support.yml @@ -50,7 +50,7 @@ body: id: go2rtclogs attributes: label: Relevant go2rtc log output - description: Please copy and paste any relevant go2rtc log output. Include logs before and after your exact error when possible. This will be automatically formatted into code, so no need for backticks. + description: Please copy and paste any relevant go2rtc log output. Include logs before and after your exact error when possible. Logs can be viewed via the Frigate UI, Docker, or the go2rtc dashboard. This will be automatically formatted into code, so no need for backticks. render: shell validations: required: true @@ -125,8 +125,8 @@ body: - type: textarea id: screenshots attributes: - label: Screenshots of the Frigate UI's System metrics pages and any other screenshots pertinent to your issue. - description: Drag and drop for images is possible in this field. + label: Screenshots of the Frigate UI's System metrics pages + description: Drag and drop for images is possible in this field - type: textarea id: other attributes: diff --git a/.github/DISCUSSION_TEMPLATE/hardware-acceleration-support.yml b/.github/DISCUSSION_TEMPLATE/hardware-acceleration-support.yml index e50b21b9b..1b7094fd7 100644 --- a/.github/DISCUSSION_TEMPLATE/hardware-acceleration-support.yml +++ b/.github/DISCUSSION_TEMPLATE/hardware-acceleration-support.yml @@ -58,7 +58,7 @@ body: id: go2rtclogs attributes: label: Relevant go2rtc log output - description: Please copy and paste any relevant go2rtc log output. Include logs before and after your exact error when possible. This will be automatically formatted into code, so no need for backticks. + description: Please copy and paste any relevant go2rtc log output. Include logs before and after your exact error when possible. Logs can be viewed via the Frigate UI, Docker, or the go2rtc dashboard. This will be automatically formatted into code, so no need for backticks. render: shell validations: required: true @@ -114,8 +114,10 @@ body: - type: textarea id: screenshots attributes: - label: Screenshots of the Frigate UI's System metrics pages and any other screenshots pertinent to your issue. - description: Drag and drop for images is possible in this field. + label: Screenshots of the Frigate UI's System metrics pages + description: Drag and drop for images is possible in this field. Please post screenshots of at least General and Cameras tabs. + validations: + required: true - type: textarea id: other attributes: diff --git a/.github/DISCUSSION_TEMPLATE/report-a-bug.yml b/.github/DISCUSSION_TEMPLATE/report-a-bug.yml index 472dffb73..dba6d695e 100644 --- a/.github/DISCUSSION_TEMPLATE/report-a-bug.yml +++ b/.github/DISCUSSION_TEMPLATE/report-a-bug.yml @@ -68,6 +68,14 @@ body: render: yaml validations: required: true + - type: textarea + id: docker + attributes: + label: docker-compose file or Docker CLI command + description: This will be automatically formatted into code, so no need for backticks. + render: yaml + validations: + required: true - type: textarea id: frigatelogs attributes: @@ -80,7 +88,7 @@ body: id: go2rtclogs attributes: label: Relevant go2rtc log output - description: Please copy and paste any relevant go2rtc log output. Include logs before and after your exact error when possible. This will be automatically formatted into code, so no need for backticks. + description: Please copy and paste any relevant go2rtc log output. Include logs before and after your exact error when possible. Logs can be viewed via the Frigate UI, Docker, or the go2rtc dashboard. This will be automatically formatted into code, so no need for backticks. render: shell validations: required: true @@ -128,8 +136,10 @@ body: - type: textarea id: screenshots attributes: - label: Screenshots of the Frigate UI's System metrics pages and any other screenshots pertinent to your issue. - description: Drag and drop for images is possible in this field. + label: Screenshots of the Frigate UI's System metrics pages + description: Drag and drop for images is possible in this field. Please post screenshots of all tabs. + validations: + required: true - type: textarea id: other attributes: From 617d279419db1c24f4a1673a09d0fa086728c07d Mon Sep 17 00:00:00 2001 From: Blake Blackshear Date: Sat, 24 Aug 2024 07:25:24 -0500 Subject: [PATCH 62/81] update actions for release (#13318) --- .github/actions/setup/action.yml | 2 +- .github/workflows/release.yml | 8 ++++---- .github/workflows/stale.yml | 26 +++++++++++++------------- 3 files changed, 18 insertions(+), 18 deletions(-) diff --git a/.github/actions/setup/action.yml b/.github/actions/setup/action.yml index 88ceab935..793ea7d42 100644 --- a/.github/actions/setup/action.yml +++ b/.github/actions/setup/action.yml @@ -5,7 +5,7 @@ inputs: required: true outputs: image-name: - value: ghcr.io/${{ steps.lowercaseRepo.outputs.lowercase }}:${{ github.ref_name }}-${{ steps.create-short-sha.outputs.SHORT_SHA }} + value: ghcr.io/${{ steps.lowercaseRepo.outputs.lowercase }}:${{ steps.create-short-sha.outputs.SHORT_SHA }} cache-name: value: ghcr.io/${{ steps.lowercaseRepo.outputs.lowercase }}:cache runs: diff --git a/.github/workflows/release.yml b/.github/workflows/release.yml index b51381956..97d22202e 100644 --- a/.github/workflows/release.yml +++ b/.github/workflows/release.yml @@ -23,10 +23,10 @@ jobs: password: ${{ secrets.GITHUB_TOKEN }} - name: Create tag variables run: | - BRANCH=dev - echo "BRANCH=${BRANCH}" >> $GITHUB_ENV + BUILD_TYPE=$([[ "${{ github.ref_name }}" =~ ^v[0-9]+\.[0-9]+\.[0-9]+$ ]] && echo "stable" || echo "beta") + echo "BUILD_TYPE=${BUILD_TYPE}" >> $GITHUB_ENV echo "BASE=ghcr.io/${{ steps.lowercaseRepo.outputs.lowercase }}" >> $GITHUB_ENV - echo "BUILD_TAG=${BRANCH}-${GITHUB_SHA::7}" >> $GITHUB_ENV + echo "BUILD_TAG=${GITHUB_SHA::7}" >> $GITHUB_ENV echo "CLEAN_VERSION=$(echo ${GITHUB_REF##*/} | tr '[:upper:]' '[:lower:]' | sed 's/^[v]//')" >> $GITHUB_ENV - name: Tag and push the main image run: | @@ -39,7 +39,7 @@ jobs: done # stable tag - if [[ "${BRANCH}" == "master" ]]; then + if [[ "${BUILD_TYPE}" == "stable" ]]; then docker run --rm -v $HOME/.docker/config.json:/config.json quay.io/skopeo/stable:latest copy --authfile /config.json --multi-arch all docker://${PULL_TAG} docker://${STABLE_TAG} for variant in standard-arm64 tensorrt tensorrt-jp4 tensorrt-jp5 rk; do docker run --rm -v $HOME/.docker/config.json:/config.json quay.io/skopeo/stable:latest copy --authfile /config.json --multi-arch all docker://${PULL_TAG}-${variant} docker://${STABLE_TAG}-${variant} diff --git a/.github/workflows/stale.yml b/.github/workflows/stale.yml index 172eaeca6..8e7e3223c 100644 --- a/.github/workflows/stale.yml +++ b/.github/workflows/stale.yml @@ -25,17 +25,17 @@ jobs: - name: Print outputs run: echo ${{ join(steps.stale.outputs.*, ',') }} - clean_ghcr: - name: Delete outdated dev container images - runs-on: ubuntu-latest - steps: - - name: Delete old images - uses: snok/container-retention-policy@v2 - with: - image-names: dev-* - cut-off: 60 days ago UTC - keep-at-least: 5 - account-type: personal - token: ${{ secrets.GITHUB_TOKEN }} - token-type: github-token + # clean_ghcr: + # name: Delete outdated dev container images + # runs-on: ubuntu-latest + # steps: + # - name: Delete old images + # uses: snok/container-retention-policy@v2 + # with: + # image-names: dev-* + # cut-off: 60 days ago UTC + # keep-at-least: 5 + # account-type: personal + # token: ${{ secrets.GITHUB_TOKEN }} + # token-type: github-token From 70aab068fd1d7a533c1481daa8747e9842663eaa Mon Sep 17 00:00:00 2001 From: Blake Blackshear Date: Sat, 24 Aug 2024 07:44:15 -0500 Subject: [PATCH 63/81] fix default build (#13321) --- .github/workflows/ci.yml | 6 +++--- 1 file changed, 3 insertions(+), 3 deletions(-) diff --git a/.github/workflows/ci.yml b/.github/workflows/ci.yml index 860c8b4e4..5e360b5ff 100644 --- a/.github/workflows/ci.yml +++ b/.github/workflows/ci.yml @@ -229,7 +229,7 @@ jobs: run: echo "SHORT_SHA=${GITHUB_SHA::7}" >> $GITHUB_ENV - uses: int128/docker-manifest-create-action@v2 with: - tags: ghcr.io/${{ steps.lowercaseRepo.outputs.lowercase }}:${{ github.ref_name }}-${{ env.SHORT_SHA }} + tags: ghcr.io/${{ steps.lowercaseRepo.outputs.lowercase }}:${{ env.SHORT_SHA }} sources: | - ghcr.io/${{ steps.lowercaseRepo.outputs.lowercase }}:${{ github.ref_name }}-${{ env.SHORT_SHA }}-amd64 - ghcr.io/${{ steps.lowercaseRepo.outputs.lowercase }}:${{ github.ref_name }}-${{ env.SHORT_SHA }}-rpi + ghcr.io/${{ steps.lowercaseRepo.outputs.lowercase }}:${{ env.SHORT_SHA }}-amd64 + ghcr.io/${{ steps.lowercaseRepo.outputs.lowercase }}:${{ env.SHORT_SHA }}-rpi From 190ce5ee31ec49cefed0b607864f5b25c802ce8f Mon Sep 17 00:00:00 2001 From: Nicolas Mowen Date: Sun, 25 Aug 2024 06:57:10 -0600 Subject: [PATCH 64/81] Add tooltip for icons in review event list (#13334) --- web/src/components/card/ReviewCard.tsx | 54 +++++++++++++++++++------- 1 file changed, 39 insertions(+), 15 deletions(-) diff --git a/web/src/components/card/ReviewCard.tsx b/web/src/components/card/ReviewCard.tsx index 33032d2b8..359dd6536 100644 --- a/web/src/components/card/ReviewCard.tsx +++ b/web/src/components/card/ReviewCard.tsx @@ -32,6 +32,8 @@ import { Drawer, DrawerContent } from "../ui/drawer"; import axios from "axios"; import { toast } from "sonner"; import useKeyboardListener from "@/hooks/use-keyboard-listener"; +import { Tooltip, TooltipContent, TooltipTrigger } from "../ui/tooltip"; +import { capitalizeFirstLetter } from "@/utils/stringUtil"; type ReviewCardProps = { event: ReviewSegment; @@ -153,21 +155,43 @@ export default function ReviewCard({ }} />
-
- {event.data.objects.map((object) => { - return getIconForLabel( - object, - "size-3 text-primary dark:text-white", - ); - })} - {event.data.audio.map((audio) => { - return getIconForLabel( - audio, - "size-3 text-primary dark:text-white", - ); - })} -
{formattedDate}
-
+ + +
+ <> + {event.data.objects.map((object) => { + return getIconForLabel( + object, + "size-3 text-primary dark:text-white", + ); + })} + {event.data.audio.map((audio) => { + return getIconForLabel( + audio, + "size-3 text-primary dark:text-white", + ); + })} + +
{formattedDate}
+
+
+ + {[ + ...new Set([ + ...(event.data.objects || []), + ...(event.data.sub_labels || []), + ...(event.data.audio || []), + ]), + ] + .filter( + (item) => item !== undefined && !item.includes("-verified"), + ) + .map((text) => capitalizeFirstLetter(text)) + .sort() + .join(", ") + .replaceAll("-verified", "")} + +
Date: Mon, 26 Aug 2024 23:17:24 +0200 Subject: [PATCH 65/81] update go2rtc version in reference config (#13367) --- docs/docs/configuration/reference.md | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/docs/docs/configuration/reference.md b/docs/docs/configuration/reference.md index 60f21abaa..76a9e6158 100644 --- a/docs/docs/configuration/reference.md +++ b/docs/docs/configuration/reference.md @@ -477,7 +477,7 @@ snapshots: quality: 70 # Optional: Restream configuration -# Uses https://github.com/AlexxIT/go2rtc (v1.8.3) +# Uses https://github.com/AlexxIT/go2rtc (v1.9.2) go2rtc: # Optional: jsmpeg stream configuration for WebUI From 3f996cd62c0334552b370e8584ffc8dcd76197c6 Mon Sep 17 00:00:00 2001 From: Josh Hawkins <32435876+hawkeye217@users.noreply.github.com> Date: Tue, 27 Aug 2024 20:14:22 -0500 Subject: [PATCH 66/81] Add portal the live player tooltip (#13389) --- web/src/components/player/LivePlayer.tsx | 31 +++++++++++++----------- 1 file changed, 17 insertions(+), 14 deletions(-) diff --git a/web/src/components/player/LivePlayer.tsx b/web/src/components/player/LivePlayer.tsx index 9a0b6f3db..79de3b5a2 100644 --- a/web/src/components/player/LivePlayer.tsx +++ b/web/src/components/player/LivePlayer.tsx @@ -18,6 +18,7 @@ import Chip from "../indicators/Chip"; import { capitalizeFirstLetter } from "@/utils/stringUtil"; import { cn } from "@/lib/utils"; import { TbExclamationCircle } from "react-icons/tb"; +import { TooltipPortal } from "@radix-ui/react-tooltip"; type LivePlayerProps = { cameraRef?: (ref: HTMLDivElement | null) => void; @@ -258,20 +259,22 @@ export default function LivePlayer({
- - {[ - ...new Set([ - ...(objects || []).map(({ label, sub_label }) => - label.endsWith("verified") ? sub_label : label, - ), - ]), - ] - .filter((label) => label?.includes("-verified") == false) - .map((label) => capitalizeFirstLetter(label)) - .sort() - .join(", ") - .replaceAll("-verified", "")} - + + + {[ + ...new Set([ + ...(objects || []).map(({ label, sub_label }) => + label.endsWith("verified") ? sub_label : label, + ), + ]), + ] + .filter((label) => label?.includes("-verified") == false) + .map((label) => capitalizeFirstLetter(label)) + .sort() + .join(", ") + .replaceAll("-verified", "")} + +
)} From 55e1f865d8481814ecae1e23654e8a9a7bb4fdf4 Mon Sep 17 00:00:00 2001 From: Josh Hawkins <32435876+hawkeye217@users.noreply.github.com> Date: Wed, 28 Aug 2024 07:26:50 -0500 Subject: [PATCH 67/81] Don't allow periods in zone or camera group names (#13400) --- web/src/components/filter/CameraGroupSelector.tsx | 8 ++++++++ web/src/components/settings/ZoneEditPane.tsx | 8 ++++++++ 2 files changed, 16 insertions(+) diff --git a/web/src/components/filter/CameraGroupSelector.tsx b/web/src/components/filter/CameraGroupSelector.tsx index f6a5c5fdf..6f14b8eb9 100644 --- a/web/src/components/filter/CameraGroupSelector.tsx +++ b/web/src/components/filter/CameraGroupSelector.tsx @@ -551,6 +551,14 @@ export function CameraGroupEdit({ message: "Camera group name already exists.", }, ) + .refine( + (value: string) => { + return !value.includes("."); + }, + { + message: "Camera group name must not contain a period.", + }, + ) .refine((value: string) => value.toLowerCase() !== "default", { message: "Invalid camera group name.", }), diff --git a/web/src/components/settings/ZoneEditPane.tsx b/web/src/components/settings/ZoneEditPane.tsx index b80fa933e..f1c23c705 100644 --- a/web/src/components/settings/ZoneEditPane.tsx +++ b/web/src/components/settings/ZoneEditPane.tsx @@ -106,6 +106,14 @@ export default function ZoneEditPane({ { message: "Zone name already exists on this camera.", }, + ) + .refine( + (value: string) => { + return !value.includes("."); + }, + { + message: "Zone name must not contain a period.", + }, ), inertia: z.coerce .number() From a82c1f303b5b372ef609aa83d497453a2de69a8d Mon Sep 17 00:00:00 2001 From: Josh Hawkins <32435876+hawkeye217@users.noreply.github.com> Date: Thu, 5 Sep 2024 20:59:47 -0500 Subject: [PATCH 68/81] Clarify decoding and the detect role (#13580) --- docs/docs/configuration/cameras.md | 10 +++++++++- docs/docs/guides/getting_started.md | 9 ++++++++- 2 files changed, 17 insertions(+), 2 deletions(-) diff --git a/docs/docs/configuration/cameras.md b/docs/docs/configuration/cameras.md index 49eae7a5b..a1ad1ad5a 100644 --- a/docs/docs/configuration/cameras.md +++ b/docs/docs/configuration/cameras.md @@ -46,6 +46,14 @@ cameras: side: ... ``` +:::note + +If you only define one stream in your `inputs` and do not assign a `detect` role to it, Frigate will automatically assign it the `detect` role. Frigate will always decode a stream to support motion detection, Birdseye, the API image endpoints, and other features, even if you have disabled object detection with `enabled: False` in your config's `detect` section. + +If you plan to use Frigate for recording only, it is still recommended to define a `detect` role for a low resolution stream to minimize resource usage from the required stream decoding. + +::: + For camera model specific settings check the [camera specific](camera_specific.md) infos. ## Setting up camera PTZ controls @@ -80,7 +88,7 @@ This list of working and non-working PTZ cameras is based on user feedback. | Brand or specific camera | PTZ Controls | Autotracking | Notes | | ------------------------ | :----------: | :----------: | ----------------------------------------------------------------------------------------------------------------------------------------------- | | Amcrest | ✅ | ✅ | ⛔️ Generally, Amcrest should work, but some older models (like the common IP2M-841) don't support autotracking | -| Amcrest ASH21 | ✅ | ❌ | ONVIF service port: 80 | +| Amcrest ASH21 | ✅ | ❌ | ONVIF service port: 80 | | Ctronics PTZ | ✅ | ❌ | | | Dahua | ✅ | ✅ | | | Dahua DH-SD2A500HB | ✅ | ❌ | | diff --git a/docs/docs/guides/getting_started.md b/docs/docs/guides/getting_started.md index b74cb9377..e2a4420a3 100644 --- a/docs/docs/guides/getting_started.md +++ b/docs/docs/guides/getting_started.md @@ -294,6 +294,14 @@ cameras: If you don't have separate streams for detect and record, you would just add the record role to the list on the first input. +:::note + +If you only define one stream in your `inputs` and do not assign a `detect` role to it, Frigate will automatically assign it the `detect` role. Frigate will always decode a stream to support motion detection, Birdseye, the API image endpoints, and other features, even if you have disabled object detection with `enabled: False` in your config's `detect` section. + +If you plan to use Frigate for recording only, it is still recommended to define a `detect` role for a low resolution stream to minimize resource usage from the required stream decoding. + +::: + By default, Frigate will retain video of all events for 10 days. The full set of options for recording can be found [here](../configuration/reference.md). ### Step 7: Complete config @@ -309,4 +317,3 @@ Now that you have a working install, you can use the following documentation for 3. [Review](../configuration/review.md) 4. [Masks](../configuration/masks.md) 5. [Home Assistant Integration](../integrations/home-assistant.md) - Integrate with Home Assistant - From 94de29187a77387b6a325ed81a9c28b232bbbacb Mon Sep 17 00:00:00 2001 From: OldTyT <42464565+OldTyT@users.noreply.github.com> Date: Fri, 6 Sep 2024 14:42:21 +0300 Subject: [PATCH 69/81] docs(third_party_extensions.md): added info about frigate telegram (#13584) --- docs/docs/integrations/third_party_extensions.md | 4 ++++ 1 file changed, 4 insertions(+) diff --git a/docs/docs/integrations/third_party_extensions.md b/docs/docs/integrations/third_party_extensions.md index ca15d5523..a9677e721 100644 --- a/docs/docs/integrations/third_party_extensions.md +++ b/docs/docs/integrations/third_party_extensions.md @@ -18,3 +18,7 @@ Please use your own knowledge to assess and vet them before you install anything [Double Take](https://github.com/skrashevich/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. This is a fork (with fixed errors and new features) of [original Double Take](https://github.com/jakowenko/double-take) project which, unfortunately, isn't being maintained by author. + +## [Frigate telegram](https://github.com/OldTyT/frigate-telegram) + +[Frigate telegram](https://github.com/OldTyT/frigate-telegram) makes it possible to send events from Frigate to Telegram. Events are sent as a message with a text description, video, and thumbnail. From e7dfbf76bb3ca2b68ad4a4b0cb3e26fede0f0011 Mon Sep 17 00:00:00 2001 From: Blake Blackshear Date: Sat, 7 Sep 2024 07:28:28 -0500 Subject: [PATCH 70/81] update plus docs for 0.14 (#13604) --- docs/docs/integrations/plus.md | 4 ++-- docs/static/img/plus/send-to-plus.jpg | Bin 58675 -> 63797 bytes docs/static/img/plus/submit-to-plus.jpg | Bin 64958 -> 49810 bytes 3 files changed, 2 insertions(+), 2 deletions(-) diff --git a/docs/docs/integrations/plus.md b/docs/docs/integrations/plus.md index afa7bc216..23a66cbde 100644 --- a/docs/docs/integrations/plus.md +++ b/docs/docs/integrations/plus.md @@ -19,7 +19,7 @@ Once logged in, you can generate an API key for Frigate in Settings. ### Set your API key -In Frigate, you can use an environment variable or a docker secret named `PLUS_API_KEY` to enable the `SEND TO FRIGATE+` buttons on the events page. Home Assistant Addon users can set it under Settings > Addons > Frigate NVR > Configuration > Options (be sure to toggle the "Show unused optional configuration options" switch). +In Frigate, you can use an environment variable or a docker secret named `PLUS_API_KEY` to enable the Frigate+ page. Home Assistant Addon users can set it under Settings > Addons > Frigate NVR > Configuration > Options (be sure to toggle the "Show unused optional configuration options" switch). :::warning @@ -29,7 +29,7 @@ You cannot use the `environment_vars` section of your configuration file to set ## Submit examples -Once your API key is configured, you can submit examples directly from the events page in Frigate using the `SEND TO FRIGATE+` button. +Once your API key is configured, you can submit examples directly from the Frigate+ page. :::note diff --git a/docs/static/img/plus/send-to-plus.jpg b/docs/static/img/plus/send-to-plus.jpg index 2029f36a04b32564b9f1e85e9422a53f1d803040..cffd7e5f64a0b9198d021df0d9d7986928b3362d 100644 GIT binary patch literal 63797 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

B6U66!D<)$i#uAe9xrazsl9 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 From 2fc58fea81d95f9ee02ce710b4b947d20a39b184 Mon Sep 17 00:00:00 2001 From: Nicolas Mowen Date: Sat, 7 Sep 2024 13:21:38 -0600 Subject: [PATCH 71/81] Add api docs for review api (#13613) --- docs/docs/integrations/api.md | 8 ++++++++ 1 file changed, 8 insertions(+) diff --git a/docs/docs/integrations/api.md b/docs/docs/integrations/api.md index a0208bc01..4c3095835 100644 --- a/docs/docs/integrations/api.md +++ b/docs/docs/integrations/api.md @@ -381,6 +381,14 @@ List of frames in the preview cache for the time range. Previews are only kept i Specific preview frame from preview cache. +### `GET /review//preview` + +Looping image made from preview video / frames during this review item. + +| param | Type | Description | +| --------- | ---- | -------------------------------- | +| `format` | str | Format of preview [`gif`, `mp4`] | + ### `GET //start//end//preview` Looping image made from preview video / frames during this time range. From 2f38d960d4cc068bab4f0e19d80a2f2647963923 Mon Sep 17 00:00:00 2001 From: mrmorganmurphy <22598031+mrmorganmurphy@users.noreply.github.com> Date: Thu, 12 Sep 2024 06:16:23 -0700 Subject: [PATCH 72/81] Update cameras.md (#13691) Amcrest IP5M-1190EW does not support autotracking. FOV relative movement not supported. --- docs/docs/configuration/cameras.md | 1 + 1 file changed, 1 insertion(+) diff --git a/docs/docs/configuration/cameras.md b/docs/docs/configuration/cameras.md index a1ad1ad5a..c516d3bc8 100644 --- a/docs/docs/configuration/cameras.md +++ b/docs/docs/configuration/cameras.md @@ -89,6 +89,7 @@ This list of working and non-working PTZ cameras is based on user feedback. | ------------------------ | :----------: | :----------: | ----------------------------------------------------------------------------------------------------------------------------------------------- | | Amcrest | ✅ | ✅ | ⛔️ Generally, Amcrest should work, but some older models (like the common IP2M-841) don't support autotracking | | Amcrest ASH21 | ✅ | ❌ | ONVIF service port: 80 | +| Amcrest IP5M-1190EW | ✅ | ❌ | ONVIF Port: 80. FOV relative movement not supported. | | Ctronics PTZ | ✅ | ❌ | | | Dahua | ✅ | ✅ | | | Dahua DH-SD2A500HB | ✅ | ❌ | | From 7ad30f15d5a2c173977a4cd086a57bd42ab7d808 Mon Sep 17 00:00:00 2001 From: Josh Hawkins <32435876+hawkeye217@users.noreply.github.com> Date: Fri, 13 Sep 2024 11:43:48 -0500 Subject: [PATCH 73/81] Add note about onvif cameras without auth (#13721) --- docs/docs/configuration/cameras.md | 6 ++++++ 1 file changed, 6 insertions(+) diff --git a/docs/docs/configuration/cameras.md b/docs/docs/configuration/cameras.md index c516d3bc8..7f89157c6 100644 --- a/docs/docs/configuration/cameras.md +++ b/docs/docs/configuration/cameras.md @@ -79,6 +79,12 @@ cameras: If the ONVIF connection is successful, PTZ controls will be available in the camera's WebUI. +:::tip + +If your ONVIF camera does not require authentication credentials, you may still need to specify an empty string for `user` and `password`, eg: `user: ""` and `password: ""`. + +::: + An ONVIF-capable camera that supports relative movement within the field of view (FOV) can also be configured to automatically track moving objects and keep them in the center of the frame. For autotracking setup, see the [autotracking](autotracking.md) docs. ## ONVIF PTZ camera recommendations From 3df33199bcf8f05380b13becba2c3c0364540a52 Mon Sep 17 00:00:00 2001 From: Nicolas Mowen Date: Sun, 15 Sep 2024 11:37:30 -0600 Subject: [PATCH 74/81] Add Arc a750 to hardware stats list (#13752) --- docs/docs/frigate/hardware.md | 1 + 1 file changed, 1 insertion(+) diff --git a/docs/docs/frigate/hardware.md b/docs/docs/frigate/hardware.md index 602cc906f..cc9515f67 100644 --- a/docs/docs/frigate/hardware.md +++ b/docs/docs/frigate/hardware.md @@ -69,6 +69,7 @@ Inference speeds vary greatly depending on the CPU, GPU, or VPU used, some known | Intel i5 7500 | ~ 15 ms | Inference speeds on CPU were ~ 260 ms | | Intel i5 1135G7 | 10 - 15 ms | | | Intel i5 12600K | ~ 15 ms | Inference speeds on CPU were ~ 35 ms | +| Intel Arc A750 | ~ 4 ms | | ### TensorRT - Nvidia GPU From bd906a7915de7d18cb0fb21a520d38488047c81d Mon Sep 17 00:00:00 2001 From: Josh Hawkins <32435876+hawkeye217@users.noreply.github.com> Date: Sun, 15 Sep 2024 12:43:11 -0500 Subject: [PATCH 75/81] Update docs for another supported autotracking cam (#13753) * update for Uniview IPC6612SR-X33-VG * wording --- docs/docs/configuration/cameras.md | 1 + 1 file changed, 1 insertion(+) diff --git a/docs/docs/configuration/cameras.md b/docs/docs/configuration/cameras.md index 7f89157c6..29c4ef7ca 100644 --- a/docs/docs/configuration/cameras.md +++ b/docs/docs/configuration/cameras.md @@ -109,6 +109,7 @@ This list of working and non-working PTZ cameras is based on user feedback. | Sunba 405-D20X | ✅ | ❌ | | | Tapo | ✅ | ❌ | Many models supported, ONVIF Service Port: 2020 | | Uniview IPC672LR-AX4DUPK | ✅ | ❌ | Firmware says FOV relative movement is supported, but camera doesn't actually move when sending ONVIF commands | +| Uniview IPC6612SR-X33-VG | ✅ | ✅ | Leave `calibrate_on_startup` as `False`. A user has reported that zooming with `absolute` is working. | | Vikylin PTZ-2804X-I2 | ❌ | ❌ | Incomplete ONVIF support | ## Setting up camera groups From c4e2f3bc70c3802f3ce7e7430c22f84b7d344b48 Mon Sep 17 00:00:00 2001 From: Darryl Sokoloski Date: Mon, 16 Sep 2024 18:17:22 -0400 Subject: [PATCH 76/81] Updated supported cameras: Speco O8P32X (#13698) Signed-off-by: Darryl Sokoloski --- docs/docs/configuration/cameras.md | 1 + 1 file changed, 1 insertion(+) diff --git a/docs/docs/configuration/cameras.md b/docs/docs/configuration/cameras.md index 29c4ef7ca..b2bfd055a 100644 --- a/docs/docs/configuration/cameras.md +++ b/docs/docs/configuration/cameras.md @@ -106,6 +106,7 @@ This list of working and non-working PTZ cameras is based on user feedback. | Reolink E1 Pro | ✅ | ❌ | | | Reolink E1 Zoom | ✅ | ❌ | | | Reolink RLC-823A 16x | ✅ | ❌ | | +| Speco O8P32X | ✅ | ❌ | | | Sunba 405-D20X | ✅ | ❌ | | | Tapo | ✅ | ❌ | Many models supported, ONVIF Service Port: 2020 | | Uniview IPC672LR-AX4DUPK | ✅ | ❌ | Firmware says FOV relative movement is supported, but camera doesn't actually move when sending ONVIF commands | From 811da2e1595b2eebbee5702f97739007f1861ca7 Mon Sep 17 00:00:00 2001 From: Josh Hawkins <32435876+hawkeye217@users.noreply.github.com> Date: Fri, 20 Sep 2024 07:27:15 -0500 Subject: [PATCH 77/81] Clarify live view docs (#13848) --- docs/docs/configuration/live.md | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/docs/docs/configuration/live.md b/docs/docs/configuration/live.md index e543e5ada..31e720031 100644 --- a/docs/docs/configuration/live.md +++ b/docs/docs/configuration/live.md @@ -21,8 +21,8 @@ The jsmpeg live view will use more browser and client GPU resources. Using go2rt If you are using go2rtc, you should adjust the following settings in your camera's firmware for the best experience with Live view: -- Video codec: H.264 - provides the most compatible video codec with all Live view technologies and browsers. -- Audio codec: AAC - provides the most compatible audio codec with all Live view technologies and browsers. +- Video codec: **H.264** - provides the most compatible video codec with all Live view technologies and browsers. Avoid any kind of "smart codec" or "+" codec like _H.264+_ or _H.265+_. as these non-standard codecs remove keyframes (see below). +- Audio codec: **AAC** - provides the most compatible audio codec with all Live view technologies and browsers that support audio. - I-frame interval (sometimes called the keyframe interval, the interframe space, or the GOP length): match your camera's frame rate, or choose "1x" (for interframe space on Reolink cameras). For example, if your stream outputs 20fps, your i-frame interval should be 20 (or 1x on Reolink). Values higher than the frame rate will cause the stream to take longer to begin playback. See [this page](https://gardinal.net/understanding-the-keyframe-interval/) for more on keyframes. The default video and audio codec on your camera may not always be compatible with your browser, which is why setting them to H.264 and AAC is recommended. See the [go2rtc docs](https://github.com/AlexxIT/go2rtc?tab=readme-ov-file#codecs-madness) for codec support information. From a3d3fe07ce97ea656ced87be4dafa385ee4762a2 Mon Sep 17 00:00:00 2001 From: Josh Hawkins <32435876+hawkeye217@users.noreply.github.com> Date: Tue, 24 Sep 2024 13:41:58 -0500 Subject: [PATCH 78/81] PTZ camera support docs update (#13941) * Add user reports for ptz cameras/autotracking * remove message --- docs/docs/configuration/cameras.md | 44 ++++++++++++++++-------------- 1 file changed, 23 insertions(+), 21 deletions(-) diff --git a/docs/docs/configuration/cameras.md b/docs/docs/configuration/cameras.md index b2bfd055a..1a3622449 100644 --- a/docs/docs/configuration/cameras.md +++ b/docs/docs/configuration/cameras.md @@ -91,27 +91,29 @@ An ONVIF-capable camera that supports relative movement within the field of view This list of working and non-working PTZ cameras is based on user feedback. -| Brand or specific camera | PTZ Controls | Autotracking | Notes | -| ------------------------ | :----------: | :----------: | ----------------------------------------------------------------------------------------------------------------------------------------------- | -| Amcrest | ✅ | ✅ | ⛔️ Generally, Amcrest should work, but some older models (like the common IP2M-841) don't support autotracking | -| Amcrest ASH21 | ✅ | ❌ | ONVIF service port: 80 | -| Amcrest IP5M-1190EW | ✅ | ❌ | ONVIF Port: 80. FOV relative movement not supported. | -| Ctronics PTZ | ✅ | ❌ | | -| Dahua | ✅ | ✅ | | -| Dahua DH-SD2A500HB | ✅ | ❌ | | -| Foscam R5 | ✅ | ❌ | | -| Hanwha XNP-6550RH | ✅ | ❌ | | -| Hikvision | ✅ | ❌ | Incomplete ONVIF support (MoveStatus won't update even on latest firmware) - reported with HWP-N4215IH-DE and DS-2DE3304W-DE, but likely others | -| Reolink 511WA | ✅ | ❌ | Zoom only | -| Reolink E1 Pro | ✅ | ❌ | | -| Reolink E1 Zoom | ✅ | ❌ | | -| Reolink RLC-823A 16x | ✅ | ❌ | | -| Speco O8P32X | ✅ | ❌ | | -| Sunba 405-D20X | ✅ | ❌ | | -| Tapo | ✅ | ❌ | Many models supported, ONVIF Service Port: 2020 | -| Uniview IPC672LR-AX4DUPK | ✅ | ❌ | Firmware says FOV relative movement is supported, but camera doesn't actually move when sending ONVIF commands | -| Uniview IPC6612SR-X33-VG | ✅ | ✅ | Leave `calibrate_on_startup` as `False`. A user has reported that zooming with `absolute` is working. | -| Vikylin PTZ-2804X-I2 | ❌ | ❌ | Incomplete ONVIF support | +| Brand or specific camera | PTZ Controls | Autotracking | Notes | +| ---------------------------- | :----------: | :----------: | ----------------------------------------------------------------------------------------------------------------------------------------------- | +| Amcrest | ✅ | ✅ | ⛔️ Generally, Amcrest should work, but some older models (like the common IP2M-841) don't support autotracking | +| Amcrest ASH21 | ✅ | ❌ | ONVIF service port: 80 | +| Amcrest IP4M-S2112EW-AI | ✅ | ❌ | FOV relative movement not supported. | +| Amcrest IP5M-1190EW | ✅ | ❌ | ONVIF Port: 80. FOV relative movement not supported. | +| Ctronics PTZ | ✅ | ❌ | | +| Dahua | ✅ | ✅ | | +| Dahua DH-SD2A500HB | ✅ | ❌ | | +| Foscam R5 | ✅ | ❌ | | +| Hanwha XNP-6550RH | ✅ | ❌ | | +| Hikvision | ✅ | ❌ | Incomplete ONVIF support (MoveStatus won't update even on latest firmware) - reported with HWP-N4215IH-DE and DS-2DE3304W-DE, but likely others | +| Hikvision DS-2DE3A404IWG-E/W | ✅ | ✅ | | +| Reolink 511WA | ✅ | ❌ | Zoom only | +| Reolink E1 Pro | ✅ | ❌ | | +| Reolink E1 Zoom | ✅ | ❌ | | +| Reolink RLC-823A 16x | ✅ | ❌ | | +| Speco O8P32X | ✅ | ❌ | | +| Sunba 405-D20X | ✅ | ❌ | | +| Tapo | ✅ | ❌ | Many models supported, ONVIF Service Port: 2020 | +| Uniview IPC672LR-AX4DUPK | ✅ | ❌ | Firmware says FOV relative movement is supported, but camera doesn't actually move when sending ONVIF commands | +| Uniview IPC6612SR-X33-VG | ✅ | ✅ | Leave `calibrate_on_startup` as `False`. A user has reported that zooming with `absolute` is working. | +| Vikylin PTZ-2804X-I2 | ❌ | ❌ | Incomplete ONVIF support | ## Setting up camera groups From 6381028fd6a3778919400eb78a40b8d71c54cd88 Mon Sep 17 00:00:00 2001 From: Josh Hawkins <32435876+hawkeye217@users.noreply.github.com> Date: Fri, 27 Sep 2024 07:52:42 -0500 Subject: [PATCH 79/81] Ensure config file naming is consistent (#14011) --- docs/docs/configuration/advanced.md | 2 +- docs/docs/configuration/hardware_acceleration.md | 2 +- 2 files changed, 2 insertions(+), 2 deletions(-) diff --git a/docs/docs/configuration/advanced.md b/docs/docs/configuration/advanced.md index b237d4b66..59786d212 100644 --- a/docs/docs/configuration/advanced.md +++ b/docs/docs/configuration/advanced.md @@ -183,7 +183,7 @@ To do this: 3. Give `go2rtc` execute permission. 4. Restart Frigate and the custom version will be used, you can verify by checking go2rtc logs. -## Validating your config.yaml file updates +## Validating your config.yml file updates When frigate starts up, it checks whether your config file is valid, and if it is not, the process exits. To minimize interruptions when updating your config, you have three options -- you can edit the config via the WebUI which has built in validation, use the config API, or you can validate on the command line using the frigate docker container. diff --git a/docs/docs/configuration/hardware_acceleration.md b/docs/docs/configuration/hardware_acceleration.md index 0b62a0f08..2bebd2875 100644 --- a/docs/docs/configuration/hardware_acceleration.md +++ b/docs/docs/configuration/hardware_acceleration.md @@ -370,7 +370,7 @@ Make sure to follow the [Rockchip specific installation instructions](/frigate/i ### Configuration -Add one of the following FFmpeg presets to your `config.yaml` to enable hardware video processing: +Add one of the following FFmpeg presets to your `config.yml` to enable hardware video processing: ```yaml # if you try to decode a h264 encoded stream From 077402406b3e577f4eae027c27915832e703535f Mon Sep 17 00:00:00 2001 From: Nicolas Mowen Date: Wed, 2 Oct 2024 20:52:58 -0600 Subject: [PATCH 80/81] Make env vars warning more clear (#14128) --- docs/docs/integrations/plus.md | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/docs/docs/integrations/plus.md b/docs/docs/integrations/plus.md index 23a66cbde..0ee056414 100644 --- a/docs/docs/integrations/plus.md +++ b/docs/docs/integrations/plus.md @@ -23,7 +23,7 @@ In Frigate, you can use an environment variable or a docker secret named `PLUS_A :::warning -You cannot use the `environment_vars` section of your configuration file to set this environment variable. +You cannot use the `environment_vars` section of your Frigate configuration file to set this environment variable. It must be defined as an environment variable in the docker config or HA addon config. ::: From 21c12d118b819f4428ce2120091f1cd1f4f84cbc Mon Sep 17 00:00:00 2001 From: Nicolas Mowen Date: Thu, 3 Oct 2024 07:40:49 -0600 Subject: [PATCH 81/81] Correct preview docs (#14136) --- docs/docs/integrations/api.md | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/docs/docs/integrations/api.md b/docs/docs/integrations/api.md index 4c3095835..0c48c0c82 100644 --- a/docs/docs/integrations/api.md +++ b/docs/docs/integrations/api.md @@ -373,7 +373,7 @@ Metadata about previews for this time range. Metadata about previews for this hour -### `GET /api/preview//start//end/` +### `GET /api/preview//start//end//frames` List of frames in the preview cache for the time range. Previews are only kept in the cache until they are combined into an mp4 at the end of the hour.

R?}7pase-LiB6W%%L@l8~)Dp0RcXtrOr8?5u zR)9OmZl|)bGo~|z9GwXvt?^~l@R|BdvzbkulktrfMU0xNj4@ZJa(IkWe{p5TY$!I4 z=auHS4C{{F>TUg``0yIH^k-r0D&sM08g+TA`sZgFpQC@Wi$DN-du=I7J-nJc2y1yeY5ShYR!6F}d=%b@UxR8vyp zji@9kbIq_cy{Or3{f$LJoEeR1MJtY{lJCnEA)xlTL=3LBl3jsFPfG%I67BFGUd_LE z7MwMG!$X0o&OK2{QA)}b&r&n8VgEc`(R8z1WbX!r2h0Sfn^I1GY7bH!DzJiJ3l=O~ z90yVbu+kLs5r8laZFJ08!GqC@BuY?gZ&N6_P3IbK`29~}PF8!fKIKT8ph-1cCEH2KyLXi2`%C%Go(<+Dps=FwR$M4#P?TeSe)w5ss&94XY?4d}dY==FCB{091rx70bY62WF`K{@iBouSF9JTnPC_N6V4t-Dij#LF-~bYhpZK%^jX! zZAcRpU>OQ@xmLzs6viX-nz1@YhPEMG`MkgHm_ZMnlOlt+c?uaQ>jHUN`|N(f0M6R& zHcL5+a!Waq6j1B2%IoTAtg2mhD8N#FIHC0Q3C_v)*41n&!Fi8C{DaO9Qu|6qn;e|tQ^t2Tg$?Q1&UO72wMpOd zi)!6HQBbm%yHW4=9Sig=6Nb$)@JQ<%(=&kS%3HhNAVdR*ce=!<1?{lznIHct1`h$v z>xL0tundU`qX!;EQ#n>-2t~4GWM;nXEs=doQ9U?Cq5rDpzsv>a()I0bPG!MBX%h1) zKDacdHVNv3voJlT%+*RFy@#&oIFANYb9ow5&6)36IuqrY3~^of!=Kon+HV;=bhFtk zkp}#TW5bJ+m}r&Zi*FBrZ;`U0m)*k|q0={NC`Z6d`HZ15-05a!^VoM7wPLK0!Hb5% z;pY6F0pnWW`^F3K@&MLl z%8ohPY#w&5O^4(HCM92Y>?hTs8>I>qh~5)eG$O~qC`wLBt>TRjqqKK!Ahi?viDMf6 zH@^WeX&*Ot`)S6(&fMJ&%z{d}kd8+=B(e0ZKcX?0-Q-$LZI*qXWUdRCV77N5uK<_l zp`TN=&0sX;z~C4?GOE|^JX-$gJ&$?*20l<-#EU;ZLe=r4nut)S_NSFrTb-5^2GqU- z2C@vv*{Lp;83aTO^_36}6}J+rf5`GTL$kKFgBe1x!l(bC4ud9uE9)Ap9k}-boX>?J z{+%^-k8=RZP`Z`1c&kLadx2=BLE55@dVgZr{W64PpM2`&$+iQbEOPMo9}r0kJG zSF2~Iq2Qy667+hMVq0bDko@**puEmtxvbdT(H%S5G5wki4MYbdo)fR~f(tjB^)EGo zyoE7FrKnhlm?~vHNEDX4mo}Qmb}J;Jvm^=VAMh|Cz*VrxTnU-I;4er=%(VnT|3mos zpMl8BSIBD9PY(Lk(T($6v@$IrXTo+u;^`#B zlw|Yy@9IUlh+?rzj*#N#RKak`h#7pnY}F+Tgf}sPJ=S)M$8>sBE69^BokbnJH9eQF zU6Z?EeLq+DbicM(LVsE@RkiXB6hr2T7$z#1*CWYCY7KUhU}WEW)6GM7qFSfkI7m z6WdQj9z9*o$c(O{35`WfO)wur87;|@`=cgrO@nz=go0f_L5m7oCL1%=6fcc96H4t0J>K zQ01>)jCoG}^;;o-&cgfck%YOvBs!K5G7#4PUxG+`h+a`+`lHrgLo3 z`xA$XpL-R2Py!X|0*=i^!}UO~GL6o`w-E$-K}XHBUnKF5-v+hokO~FgkZb0&*SnoWDpl>EjlL zH;{&xQH8I!gpW)_OQh~#$M$DT^$V-Z!a;-Z!K5>0?mC1}J*pDmmj3->mH%TS^D@^9 zDnb;@H@8VKSXf&81q1{G1E_o}-|V#DvK%>>$>p_CbVkJF)H%*@bHfUSs)0%wS!AC+ zS4bRhFI%Q#nORNofDQc?wZ}{>dRbTj@mp4CS;B81g}M3iC@ADnhjPdHy3xJzi9%Yo zoD9++oMp*%Cjo7^7k*XiyioYZ!UO_yv5+hM|%}&j6T=Xn0iq)+JhlyoXbw_Ixbr{PF9ct(XUn5z?h=~8a_5+pUv-9e=n!W$)uK}s$lnl%BY7zLv3DvP;wh@}wT zEQBpFd+hU7eSpZ2To+GTYfVFOU9_TB_AdSuQx_-{uc0PEz%hzVZV@;iiq`D*-=X=rd2@sn zr=rDgldCT?6G|WQc4_?rYtvTFn+uPxtE5W=>ZW4Vhn8`*X+GRq zj3SirG!7FBugeS{S}@}Esd8eGG@iDhDc`9+odvr6yQzZt)qVc9j}&IaMG@x_0{Yj6 zmNaWTxwV{_r5sV4BxuP6NnAEs6u>vX@LkB`N`RdXdU|?SnDdtUnf2W5lJBm(+ay4U z*;IC{)BTN)0dTW#+I*@Z%n?Oim)FQe%0-#m@YEWO`D`o!fm3L`abXIH#ZJQR>< zU)?fE;{}fSv!BS#ul?7i%PX-ZruKEN!v*!HflSJNU$ayuREHP=1=G{qop0Ew^Ri)x2bDCWKsBOYLuopGyUp=j5Nun`*0bf^lt}_f!`YoR%l& zhm+85tKhO4??k8vKAC+F8hKDE?f>`!R!EV#9}xuP$Y$C_p( z+6QW$Dk2`v2wK~vt05@gvO%f5e#>w$%|##V)#It$sxLwzXQjsz&mkt(Rr?K9Idq9M zPRoIy?*7J@#VHcElGIV+)W>CJLX*>16Y9EF84G7e?Pc+>+)k4gQWrs zhQAJv)(oPt=NVc1e|)_KR2)m&H44EUf;$9)y9Rf68Ny(}-Q9viaCf%>W^i}61c%@j zAh?GF0z}Sja=!Pw|IPcaJ8MnNbXRqCNq2R1J^R`FSy>`4-KFC}h80MSN=eY|Q^{>~ z&}~79xD+u1d64DN;d=gL(2SSAc)~<|Gqh~!uu2=f+t_;_q zUX?E9#q&unI%#|(8q+YdEZp#~FI=Mbh<^6N_OpLTpSaj*+mB^*uA9@P0${Jh1F)jg zpsM?d?vTOPMLo0ShPhYMQ5y#R5cL7ZbSI%$QZ&J1ZD}%r(}tKS5CiW*Bh$V9L=f^y zNB&7RTt~||2t*`5CUD@CmX`_1b(*)NRC&2%!LB4;bhq^g_;?-R`3w=zbgny{c#neA zFerHXg?ULz1uIb0@S(x!D>sT{YXW*7=NgX52D8R>r!+!^a_0`f@k&7Nnj_$JPh2uO*2E3Eb$xG_-f8OqH%FZ8m43OiefGxkP8HV9xvswkZ;-uIW}ig1e&`M) z%%rr;%o;)OYwLqipibX8Rf(tLcw}QOWgI*vf%?mf1GeG~2y|$j72Hk}aTJFM{l2cN zr(5sfV0OkZ$zLWG3pTJx|8}l6=_Ea2q>?!DW?-pt$F6k#3&M>g@+8eE^BRdatbxKH zNLhZnlm^%IOcKcmC1v*aM!(D%LC+!JY{jkqogxW7Q)>=oVk3dPh1qPHw9RP1)`;+} z8Y^=i)&*}t1Dmu$>ic^Il8pew50hnVldGi_f%(axm*il_WseLxCq#eyXIS9S_cwMB{dvY0i!r<94J`5L%PDjtnwN%-W zQ5Y}g;3%Rv-Ld#Ph^>1vnVEJV$myt4$#}y~edL`eYZv^NMAaQGOJRXF>CiL8dJ7GM zG{s!uWxZ9~a6Eep^8Duk_(u1K4qh%=UD$K)FkbA9RgUL`Z8#P?)>`PhpC1!x3o@O$ zH(Jd2JYS-n*dscH`%N}7s{wbVKiv6^^19(ysvvX%FSAc;nb5+0@uSFBHcJDN?o*g( zHj-Q$W(nQ?V+_76mSyaRX0~j3QEpGNN$+nRr z;)JLPfe?OoB8N2wr%17V9sCC!>%>bjL5;deNV->upd5XLH4!e-GResnGq@v(40MvzhnhfYbF5jT`S)?dgfuN{(eBDj6I-2GF_-@MsB>_>wfqxJd9v zY+I)P(z^T~?;af`d7g_FhrzPfcbO+Eo@hMJ;Kh_~v1c0xd-c?vHYre~xmJT>U)><& za2Fd3tJW|~YbMKd}m}FtAEy)U8b-4kBCnYC|mu<(hdIoUE6SU($K{ z0|bHI$><&K4*oimMr^;F8s(T==^$dYDVZ<1F96259C~lSokw}@V->x9#oHQZ2rRN< z#ueLs%h=IK%4DqP`F-?lykODbC-i75XhA=}gSq3+Y5g)4^Mqd-=&fU=jH1*$T%tN# zMIJic^_-_o2NntjL8FKiY|}}j+FEVdhlp7WfTwFLc5sUpwiwIInIu(H7`l>6M4NgW z4G{>>K`vcDUp=1{7AUzz)T1vk~gE)f!HzIK1?RxGL44cNTpEQ8PUa zZ{U{#PGhX=W=6FT9UP zD*xd_H|mKbt|ko~o$&vVF8m)iOc?Q&6^h+2h&XcZyXh|{S|6La1K3tD=oBeTykC90 zrB$CJQ->7T7uz85T(b8ipiTSE+2)TMYU3D$(VFjNFE@}o$8xFV)E68+w87GtU8nYp8ui3h)pRz!xgN(zXHS0Y%JS@S zQhfxu+?*B%&rFig%op|y3Ui(F?-siga&c}h_CH9tPf>E1HHiEJC(HJxZmb3ybB3qD zKO-41QlUngcY`>1-}R#t%;vEU`%9ZytWr!HG?n6}w?j;C8~LxEi#~%{$1FS~A)keG z$7TO4hqVa^?PQfdKqfhUc{s`F{5zoKA5z`xeGNU6#V)AgEpd*07oNP_aO`JM1w#py z)7)^+Hry*%gvJnZZ#cUa3s~?vHF-F4@Jgsn&|Q*$K*JlB&iG_q=iglycd?(j7*Y+?_! zDbShvH!n+wk=l}_0U_!K)`Tc4g_jVTg6@4J1;CMrnMActkP+1QOqr%#7ET1c%}F?2 z3q>qTOIz}q4cR$W?~G|gpYQUH#M^(2_;-oMCv@*?-+YvH{l1i;-<4Y$7!|SLB-J1X z5S@o6Zx;wZUyxnLsW=jDY5_$p3~P0;&b;CH6`3{GVKrM7zolFk%bN3fg0Ls_MXVkl z(Yd~(T(T8S)HfRZFsQfWYv$YwU1>et*0oJ(Cn*fPwtf6jRRjYp_))MUtxx_J80yx z5^=npRXCp`yqSM13l%e@rxV#m042Qx#q+Yo*dutOk z2paabYik{%JfuLmO4nleap8h_NUV#SDzJKOwe7xt@hy}42W};9)Q_k?m^d~Jo}f0M zGCIV6G1FsH+EqsO=6f7+2mxSb&~8Ft7PA*l9qnSN!$+>BbSu&y3SNZvZ=&}cR>gd= z*Z0y7r)C!_I2s)7hInnx>O+%K5@Zo2$EI9;@bvxK_ zYLHq}UU|_fQTLnQDE|6^3$f`pCz$_nNUKIVr#iZS{(Z_sbvAH`oz7^8oEKgOGv58Q zO!wY`Z0GwdDLm?n?^E&xnLiFIhbjAh#VQktt?+A=MDh>qmBeOYSsGkVv3|(8GN{fU z=a09;o*?A#mR}B2LQCt-hhV7~x9TO^vUqLaKlNk95;y)u(h~Z*%CE0FhcV^Es)LKd zA5VAT%N19Abk25DHpz#>{l`Y>J+#f-M-CNU!pO`sav#d^El+(cNOH{lFdG{uokG&d zWCuLMAi4V2%S)1n-Qh0z!l>kWl=j(W`4~;|&qJ^f9ugSV-+7h1!tyD?bzL>ktizgH z{jbHZ4&o-UrMW%@WS%3PRjs;`8Tguzgj_{@R$DaJn+_DmTGf%NRi4}~cZn^uj22np zrS!XpiN(i927#fryx)=?Gck3YFhDCL1$tkA5emrX0Eo9sL~gQI>zuFQ#qR1kheyoL zVU>0#Ewu{xrO7yt6K)%4p`0~q47*^zhL%mF#K6q93IKe)NVsj***1>ufL22taA#KNQ@uBR|6J8xwq+b-|z-ZXGmDlRUMRZ5rXZ z=+4CF=d!p5@51%0+Mp+r$@jtJL9!lOYaWr2AGMksw{`HHr38Ji)hfzEZ^SZi9Ug{o zAdRi6ltiRMDJ`WYUf4$MKL*OVtVmf(82bnkLrW5GqSfx=cdaYp!w)p=J)4KHD_osm z#fZOM%QEv-Y!+p1BTkq;_nw-G>G>+W-xOM;)(1l;f$nzxG=q=C8y9?9{5TYtY~@Ob z>)zXtf6w#lr-Z(S7b_VK4szKBda8kbS$fXSQs1hUhAfrD>5JaPl?eI@`XZ(Xi~uwF zM?*m|Rcd{W+?1{dRU+zqMh(owPH2er&s}j?PDG0ap4c^vwUodkiOIUL`*HkrjimcD z>8Y!cqc@rb7217T;)it{TXh)D25(ULN$HC*y8E-^U9z&LGW^LF}A z(gdrKI&HHAOQ}5t`|@(kgqbO|UUP<{l-e`axQ#1xl7P3nPvs~9zGd&8%7FqD)TZrq z^3#9Ai;R7>yNMfyA69<&Z<~4|`_`mm{`~^{{6OKK61D>g!9YTGAR*BYD?xONydc64 zs}3l?#sO`DwgZhw(gR0TkGM@oB8iR(%;#b6B1f#eUy8-;Ot1~KD|BYN`W0KSSf)kB zG}#L;{>ThtpBj!s&%xNwVo<*t3M1ik#=EeIX`+Op{E&6yRfFRCt)osv46QaNS#n5qO*ljEU~}TZEc{&{mRBCF^<3hS>v1u5 zZe68>A|?jGrXIOmXP1$2#w{m5V#v@yBE?J$Mb&p~)&_ANNUj}*u^kC{qj_E=i3uK( zvFcYIyDe*ZfMof#YeozIyyPI;ROTAhvn}-l@{naYmEqo5Pqlz%tOytd74s2h;H>Ql z4NLuXo1Q2MW1)WG+|GII-;}E315pLoJIf8uppzK&`S-P5}`C>r_B*(j~b&!1%}&v&|YZ$azqPJ!Pl2^9tE{9I!PgtydgoaBj(8+FW%C9 zJ5o?sIwo{DW#@JZk51MfS@fHyC#reZ<{!BDdfMuH@IP>$S>N0LCJMfvxMBG>I=22# zG|XQ)bC$~9PjX72p^|4}Vq;pJd{7A9?T#%fZ}=PuxklT?83Qg=m8|+X0fQePEWzgE zkyn8eME6F4LEbY93EX))y|lAP_>CylI~0GlT_#FAih|X{Ers>>jn?8p*AxO#dGU=A zRhZ(*1{&{s3Ng6^kLg@A33?{j%*4OtauEMhuKyJK-*VNz_)oE6e~KMe*hE^EtTVMR z{tR2X0$^6+wzI{eTSb`UAtgF&e+}x~)xOuKvJpy{`v(r+!MaC2msRXF6%jMFQ^jdF zSz$eL#n3i80b&QUymmWvm;Dsd6|VjGg`HvJ7|gAcR7`3xOqgkPfozh0 z)Z8~xomuxZH|r?5hO;12syp^vIH#p{k^8Sv@jQhY zS>z`2-D`kPfi*b&T1H}fY~?O8(b-yv&T~{ ztX4X$t?G<^wv&PpMO|oS4s)U|Af$Z}+G=~vxOn8o@e)1n8N4f{a<1b|GA!rtS&>6= zh;CP7Z*2QzAuQ3|B?_hla0rT zK5JB)onLOqCjac8z6EpYUNt%WlFyQ@qqUU1 zr=NK4)^%$0QFpm>Ucy#i{P>F~VPrZ;Jt2~pxHh+c@{>q`R=^RcyKDRtVnbi&8r9Gz z&CP9ioy28VEjyBfeyHh2=FoN3fF)L{Nmef$3ELdplLvz}IBmeUIfT|+pODZ4YY*?k z1;2T==W1YGtXk2+Wve<`I#p@m#=!2wYh@@|q{1XcGxi9h50j zg_mr?w@;g#P#jB(;Cf_blsVmqh?1?E3kogfMKjMx5O9m%esAWHDlYR#{!uX@1 z{>$bOFyW>#JWB`4%c1Hbezvl3+wQlbc*1@;`1~HzVFO&p4K*xewozkYpO-=GqD$`A zu3Wvpq%^|zaAhq6u_pvdL4*{o^{!?PGIE zSW3FO&zDg;>k~rTi2Y7fOLA_esSrz9~&W;Sma7gn?XcVtVp3D21;-N6JgBuFY!i(zIPWh zbBW5ubMjp_sDG5MIuYv`s#*JvoW&0uBx)|h`Ai;O9+U9$L{@3sv?E0X7a@_2dk>l(imb{bC_BDIz zvQd4QsRVdlK)s@yLtR@lPa7r-5HMTnyBYf8&?|1b#<-3LtCKV}&83aZrDfuURFL}1 znas)|!>mmp*N2c}8XUr?WepH@YGwl$-@C~LvTCMJZ*WsmTT@!wVmVR?m(ST;52^BZ z;g{P8ju=HQ1~^#*^p@@Y2|}zT8R`1HG?*+)#!Hlp#<@aP7$pRj^8C+upn_rQSN}>I zV6P4HBUHq>x9E;z6~gSM{ASd;pY3%qh-Jt_?krFgTf??Yh!vC&z0y;SQ~_~=g|Son zTstER%aCB6m3Dh*7<`z58o{1|xS7p4I=awjoQjx)9a8Mg&ol0&)rGM>A5n8?J>KPd zEvX??TBctVq?{G{;k5WmfWR0~duojL9QGq@ww3)2!7_b%OO~43sHHXU*1EdNyvlXX z73|TbX2wd$5!iHOz>Vr8$J@co-@Z{i7XH5Nhnc~L2%3_yzt@&xWr0;>(c0wZMa+ve zOsrR~3p`YE0Wt^2dGhD-nQC=#k)cxq4|{1`mMNK8p&vA2(H*fNjkCiY9pGgeW79E) zVl0XnAcRJo!t`8a!4N@Y{o4}dOUxb#}5NIII`mhx< zvLHg!Wa@W}Z!no$253vcFBoK?QSwZbP(=sdoY)sM69&yuowny&oZisPRsp3Qch-#c?a*2Is|1>2SVMBTfb>k5XsGi0HzTDnu7JTnuj*rVM6X zPV%@U#lJ6B&LnhvLO7|m{(T3l5x&?I;UJ?W?}8`Hyj_>t+1S_`AG}1 zZ5hQS=kP|aJ7_rHqB;(acJjb{7FKrM7n|~VWh{-&!!s=}KKI~mcah&xyD{2SmKQ88 zanH=?I%8vC)FyU?kwq6|i8#_WruS&qp=ZUp zV%r57IfA4rG}FcB7XxApYBHzRo(p-slb&AYm|_=*HbLRR+^v~TKl@Ef?ht zQ!J&bHS1!uyC0laiXVNq!E`19Enxskuo-WNCa11*0s#>J^K3~JVl(hl^)C5v->a3= z#6NB892x>{I|p^dx2j?S=X10)AJkf&l;cFhTdDYgUOURdGHmcO-a&03-ilal{ETdX z9nF>KIg#IQ7M3+LC_fg>hh;C_j{I-+DO2x1Gct*_qr)J#ynSHQna^Gcpjc%z>VQ{q zf~};^>aeg1WF5?_M$oLq5d&}O?^5jBxwhN$=D*ig8apnQzPJ*2<$o&KQZB4RuZl=k zUTNH-1>FBK0ACE^z-8uyLB*kE5k45Y@}TB-4ds*ljNR!hZa73+mgqzYdf$vXA;=k# zuD%Bp3jMl}HDTGEaEFwwzc#^wW?5Dnc+vB1*a6z1&4SSsSf>j}q4HGdGh@g` zO(jA4`6m2)keS;9svO#efdoCM3!k z+gYRK2%UCIyCzGiEepQ(Jd12kW(-XZd##MNU#i6-b0`92%-qGyrF7GGSrN>{CP7d> zV)p!_Dix8H*OUKbihE6cSSrs+IlTGp5=nl$0|l)MJ*|dBw>z%dWqv&Gk9Jzb?ThzP zg5B<1DwmrP_yO&7z>Y~kMa&-3W@JhxeiYf7}tX zFAC3s_qcsaLM6VS25Jsc39Jn7bwvDhL^W0TY-$9oo_#enk_{3-JV*TLBrH-SLHSIi z&}UpS>L}?cn0*Sm&P-+C0l%JPju&Et$#kLA@u6yAN)#F?X`>8M?qskSFTR40r+iCZ z1a;Ghv+=hPf-3O|*+5nE{!~>Z{x{uIRfB=5KDPj>+8_NTX@o(`Vzs8JF{w5^uJZi@ zh03N;Hc$SC?+gdX|8Xv58HZwzT!hP6EY|qPy7#Y&4A9`tqY$8Dt24ic@;P*i$)o2$ zKjo5AjC)0FEm&X?kwpCeo-!#1Gnmj1%gYm@{C#52-MH3I1YY1=<6NCLx1 zv64Y#gQskyK!+72bzN|KX5M1yxwaf>bd=GlV-zijA2(Zh4Efu}YeI0Gxu!710GB9| zq|vEB-)sKM)=L84;ZukGD2fErIb%&-J#qW&%QYGDvf5%6M?Gi@`veN6i8RT6Z6enX z>&e>xp|kpb%?9ZdCvn)M4lgpG}8AW@E3hWwl0XcQiwY35CpnzF>?;X08p|gJ;KjJ;bm``Ry=u2VU5)9 zAA&N7Xc0Ny(J?%uZ92mnDH);NDg0o#T0}`#8Y>W!muB7n2+dIv2`(6(xEAP~horx*C{DO0Ik;zF}B8Y`<6H(Ug-v9L!aKe_FMLg8U+*ZaR-^9o?bfyjZ+Y zp#!V@Li7JC1lgB{kt}D7KP#nsi&@x(SvzSHnbeZ6r*Yn5W6!~|vCQ0o%o2Gw0hrB$!9XX#g#bV=Hg)rib_<{E{D+rR(19gxz#ehha=I1)*}XK4n-3 zWJsv{1{t@y?4FOM>Nl1jh;az%z%v*%0fY;9VC$%{>uT?Z(`9dLsgzN#J|RkNY>mi9 z$-SjE!FnV7C+htltR70%TO}4Fe?jUE8qfZ|>QdwK8#SpP--~>{H7q^4l-iPVSPWOj zVg-2t&mvZC$!;iIjWqi7*=y&yP}y&ERjp}C=-DFn10>ct@slkzyohB{sRH}h^SY+@ zIxjp9Dw8Ffqd=gRv+xjC;Fca_?MLC2ekr6Iguw12&tKPrp6j=pQi?Vx-ZKW+TwMvByxrXTRk6$`P9 z!6`1nM|(=j;7V!$4&XQ*CB`TqauQ%W{UO~6j}?uG3&v5@h}lvUWY$g8=YNwXDyDr| zXDv-$U?Bp57UW<_(ZD{u;r-w?z230A)@p3c{{|rebEa+V4{@<~Nei1{7RnD9U)tWx zV}46jd$)-W?GCvD;+oM9Dd{h?n}((9v{s{_wQbdn{1DTR;^>)~#C!#O8gCWpW6D`z z#4XcwF)z8HPBG#|>}V+n|RKEw5x z;0e&LsePZiP}A?{z_|-U@ugi2*@mlrb&e4hl#{!=Ts(YSfE!eIp&v=1+88Ih8jEW! zqQiC6gWx&E@ekZ`BE@|@EcH1>0Y=PfggTwMCH66Tng>aNuC(n*;bFQUZ7dhDc*Z2= zW~7s9JWfbsf&IIm;uY3P==B3M<)I6f`*AfUF!VH5T4$qL8d&tr$nO&Xzx7bC)1iM= zhQGd6gdkvzj&prupN-hKBla1$v+Cj-yjIxIZs93q@a#a|N z-gFI!mrGtb&qJjUDWb0R)PhuMgc+A!HJt`>hFd%Sz+Yc>QcWf*Ai|xyhryj#FU3k_ zVOEK${uz!g)YgeO%K*C)@L;2TDb$JEHK8ZdYIf_HJ`esjld9e`(T=~H4JH%;*Q-~F zJ2(lP%GCvx7Y`wiVj^}}p}(r|(Y#Q{$IeU~WyDlqpO`sOL*>`f*^KP$s14WtP9#Xm z_x#}4PG1&o5P!eQ!q6!vJ!8>TRtXv>wM=yGOjyU1Pa*7Vi)OUX=zp6+|y%9m@ zyCF8}I!WKZK+E>xPCPUefxX|=3OE?0+vflK7F9*H9yO)rX%My+_rnaAZ`>9zOc*&S z879Sxw|2TbV_!1=*2)(}vlOR4@gvysJLV3fU-@x;?qtRLO+tWzvkRj+&~eF|rpspt z0(~nonGG4O%qb^$$XDiIbzgYSB6Vy(M%RX?ciAypqy6jW+kJ8$q_|)Ea6gK(=gu~3 zr<|?syC2gxLc+*i_Zf7oN`lhaW2KSX_jH|< zcF=)lXKoy0W+w)rYrB9M|Gb}Ki{O&Bd55Sb4}&fj9~Kd9+^%{>N=3k?C!01*Msrt% zt&_93Yw``PP!aQmo6f=vWwkEt6e0W;rZ%}8qvxMn{8}}M2{bMA;u5M7hFF*`I`3(P zDgkk=TvyG%t!EaqT`X)pz`jfWF9dos@8|Sd074~=8UNM0_JfMz)-0ds_*)M)pXUsA z;3CPrY8Ku!*t&B;D_A7xyCH7d{r=Ty>+v@E%963MwPCivP2b*VAvPAsIXmeOhrBf0 zyT~}cE=3$&DIOZOc9A4A&ve|Q5Htj$Gw#FX#WaMQMis7gWnSJ|VLc(@Tee60=3*`8 zJb{u{VrDBgb-<^axNAd)YUcvR+%*9>Cf^ZKv7Ff##cZt?5WKqJ7jb0%O4SPL^4@>J zM798H>@1{cIEk-;GCE5`k}aS7=ENVxuch-ZK)P$I)Bc2VB$OEBJadn~- z3$;%@%Pg43sv;nQo&L2Tx8O{V&}AV0@sW9b5I?eu@U7>!ktW8@c<&`G)`NfGwiJ0% zap$cC)rBu!A8*rtt0bpej`h&d5*hZTY5SGD(Ee)M?`y*Ao_2Jo(;YzAow}_%`VzlB}yoL_fYB|Jo#APql+S2HW10n_3^VT<`KN?ptBWxd$8V2WoCh>tfLf0zQXS+3;FahcE%IRzd#&b0( z8M^GVZwz~S&n|RUY*1v?=7hX!tC6g7G&LFuc|J}8Gcu(abAZm8)UnZj8<`7vp&ZIV ze<62l$&K&%vTan!9vSIse&(0@O-R&fv$OzKaoDbJ=oz51*D-I>Wqa zs&Q*SQ-)>P`vW+q|C!_rVVvgjHzd`9hSjw1(%4KxE!SiDFAdYX5iEv}g~Wxh1t6OLa0bPPgr^OPW`4mFX&85`eu zP5f`f+)vN{M95%=e>T5nGP@3)ExTr#8!&WXiyJ7BLPhj*G(qx1q$VZ#@^q}ij#lxz ze-j2;0>=hQkWz=rV8;Al`7F*t@q)Vz5{dkAcWy$p){V}*x+Y-tdJ7(9nHg|Q*nNa` zl`h-rah(s5e`~eG2z#h$TMt(^jwLE6uH@^ne~pN1F3e9#@Lxks^^L`1-6`WY`DUSK zXV0^=CmliDe5d8`bxrI2P)A|TT(s%_RkACbhRdzbJxJ4O>$!Z}13{M0pM$$HJenn(OC*vnX8|-bcpLpSC9E!cNe(0X?s&U(G%djKivniWhE41% z%f1cK*TgDWnX^#2sEPSWn^+RhLV(;{F|DfnI53F$-t%((&bK6T`eJ|^k z5W$y&KAi(g86eSh^{W~kp{c5bX#v_vZdXD2bb;cqDO4HXYMK`5yKVBpDknWjhIaqS zMxGZJFo9Os=_4$QSb4ADy^qb)l=lirAYSU~%pJ!^W^1pxZ+zA#)-maBHg@J8IHzqE zl^m~jZ!cn#cNL%nPD|A1V#h^ z%b;Z2ZFn$pNe{kUt1;P?SGrD%o5s@9S%DeRP9<@C4W!sb!km118w+6KX>aHFe0r0&{%O4EHN9)gCx>j~1{&eU?M6>roiKfeIC zH?zGbz?EfXWe;BfTj_11GP4hM1Mg*lZFi9iu}$QIh=Nf+{~jz$kJz@UZC!w_#3dgz z^xs(i)OOnr>uf3b1V}F4d0!>$MBO=DVZHO8*cg0sVf6#yEui1|L=F$Iod_naHMOt( zeFY~g4JuPRthq&hcZgT$7H8U{+p6QHy7-@MBXhB}FAC48PX4oP32Xy9*FWQ3&c>2N zf42qFmqg=u2LjB^-A0yT3qZsQDUNp(J&=a>&)<&s0nK|r&^ku9RYW)@^Ayl+K^-De zgmMHD`}Gsns8+lP4~ADwjDXBdOvFC@uAUt4emV4DoPD3;uFRuP{ zfuc5dcZH{1!o(f3J~`cJ+pFT`TlNo4#PfPG$;6%Qi|kWP%*^z^4Y6O}ax#WAP$7#I zzSm!L%YERHo=J?9Au@WuUh2fbZ zk0si`ozU%`Vp@dphP2zj*cqZAKm4lOz)PPGR}Be78q0Ghnt3e1GB$8*h**;p<#Iqy z=uN01Kw54mv9-XR|6AV#gJ$6UtDZ*q4z zG}@R7vN_c*mb-eE@7p3WM8|n!epn-iBSP8=~9u{0WcpPNk3}4nd@0cmB_1`;7*+>UghhbKFRepVZ9=e>FZr4KZDO za_2TSF{{M$`Hxs7y~ainzZ~s{T^8UQXI!`-Pxq?OI|cB{BN%CD<2Lj(hhbZ_wU&tM z!+Fq0_wZ%L@!5hzN@L|s(hZFsqUx4(g@p#-p8u}K;CyLe>7W=b4-U@dMupa`>TiyZ zFf0hH2QB?|?cI@#zrSAQ>l`=5K-*+sH5p{f4C7sC4bIdFVwf!n&UF9HFngc%V8^-V z_zzr6aAwnQ`lsCs|Nh)O{g{fx=b?>3tFd%mCh|BJ*^Gd6+!w`q;VN3qa&_+_qZ2Va zTawla2QBbO+=R1xvx?g>f9!e4Uqqo0c4i9GYEdRG;n$M~5S_e_tzh(OsS)o=qv2Js zfp4J|r`9uKqqvzgYE_A763`dL>eeMOBABrlx$qxjhBl(o1QdM`uHrhi@Ll~mumM6Q z)Ig)}NL#F8F?A>^E6#c6)K;SoNkoS3=F~gkLqacW+kQ;brw`zD@F)y~^A^HOQ+(x=KmPQ1|e~5o8Pe!tF-2QLZ$bYp3utJS}6tjCi zLZT>sVL<>&mR;GOO+S@yH|ToUB*UGdPA59{E}_dS4o3Tx{#EtFsP*H_}_m| zX0Jk&;MfN*%~@FFUoR=-R;YyzoBkoEwFXSomQ)7SYnhOL;KVd_g>{=jqOKh%y|tXq zL+w%#%d>Hdvxqo>?!r4EQAja?tjkBP>yH$&YV-VA>j41x$rAliZz$I z+P)Er!n1Fc`NKNSTRs|E%REu?_Be)^do^xd{-_{qu>BWV z$6E7YM+{OA7nv{&a49uRQcdUKmqSdgjYfwaLyB~7z6-V6NlT+4R>;2zDvsyC5~{;> zFm9;8oiAR^m$^JUE_c>Owj8Ib$O0>F-gtRcp-ma=q1s!~^JGSUDsjorBXB!u-Lew9 zT-z(+C7oa~*sU_vo~66%mxFC0c3#6O0y;zV%OpdaJR66$Svo^oQw>{*!T|6O$uP+e z{{9T{F6hRaojJVR*G|dzun$yrHorY?E&6M2sWtzB!|~yoNE!2r??%~be1hg7C}>ap zTPwB?Be+tAmBl*}r)JLY((%D10$9lLm*V&;C8nsOiV zK!0bhlwrccz(wRSxOCWjh>5N$)*xnsIUyHrx*U4#54H>>n5wcYwtdq8i?*svZn73i zq;$ZJMQwO+u#V8k7%X{bUhBk%r(=#;W|pXi$J}=yx|2~oNr74ql7^S1YYn6zHsfe;be1mX6n|mkb@?$G1SASrU zisFDVlHz5h&PP$=Hn)o9^h>l>-tC^ZkQEMNmsOSeg(1^2rllbD>Ij+zo{Sx#g* zVZ<6O-I+ulWhcOGoTm`vb8>EeGuv;qjD^J3@cs>f zdyk0G;hN!ck%oo}mh27RE1}VbmfuyK=3mf`U)m8jbO|M!S*o9@Q~9Ci3drCE)@@YT zo`54NAtavD&aL>!PO+E!82W7G9iKoN!V#MkCsm}6QeJq3_o?@RoGF0&^=6sVc zyu{tnzK{jzMX~lgsrk#q?l?YE{R17$8c5_y6nwg2`L~RyY^eEL$3(jTzJwz279@&Q zvs`t!j;1R&x`^4JNUtQ_J|>bELPkbCuz)j>l&Z6W1Jb;O2V%_aPW-}|_z(3b>ON{K zn4?3O9c+0=6tS06c z_Kz-5pe7$?;EX{IO3XhiKkne82Rg^vI9k>+*G~;xHhOi&8@F@>rq4%_#ji#AAV>IU zs9W+~z|CeshB=&g=C2A;HQ>Pt0}EhlI~A> z^4$kO3m;t{DEPutQ4A`eo=#EaiwnXGe13NZr7Dq3nF9eRPd z@R(xhm-`pX-}MI~Gj33M_J4MB-smXP#mtJq>nfiVsvK~fJ|IOl3GD|^jay9yZV=I` z4zrTt9)JC9H^!u|KSq@2Lp!mf7i=t$q$}YZ+4YNC^0(h}P>@+5*@sLrg_u>~J z_i1oPQ7^Bu!c1xI{qAAxI#(3;Ay0noFYDLUFkNXzs$lvMs$uRJ-RSkQG6m)Uc-I;L zj7>GO)=ZpSVIig!>$knIuf|rhunyJv2vR#5#icvUlZy`M_=#6M@;3Y939J4_$JjH^ za?#fDLE@-Qgxr%Pj-Y9+n6lPavXm{ARJAen6C8S`pvA$#u|Y11u1f6k!`nC-417tt zRQL;ZKgY!B<;bl@p)RHkS3+4Z*wuNT)?s?{2lH;*sP60|Y4^L2;ZAXGs~g-#+%+66 z*Kw}^YXQgWrw=%jo!bLZ?F6hs4JA6!;f|->hZ-DhT`sLiJxIB(6S!ybb*tg#TqOx< zc&OJ&W@fHGC@u+|$8y)JEoNK_k30(~VtzS_Aqerr_sVe+WNtJ@E{K+$r?P;m#azPe zKNNp3@SF%gm}>6$mU;2i{|SJ6#1&rQN-qKmvG*K?;k4D2Y66p)K0t z9|zb_^nnI^D&Q0B-y&l=AuJoeP#yBK!w`z?ccz=2T;X;SAIE&^B9v#F(WWEzQGN0o z%(l7r%P?jBN@gKMoi6fbYqxXb(YA7A2QGiDXt831p>T(y_Eam*czK>w<3FBYu^et6Ev^Nf_mPnQD!^G-l>cUNa~zREYg%)R%F*d2sT+8tMMfS16cZr=Mc> zS>h>|OQKEan^6+Mao&NFu0Z}9)ZM&Dw^Pt+`03@e{-}z^kS-s0PS;$le0-^g{2>N!MGkn#rPtVFwnQopL6ka zy%_&tVGdLn^=Rsv^}ZVM{?3>1Cn)TvhbTpVtm~UUHGTC|)B3%5^3ORtnp+$M-=h?C zsgB6u;6eIdylr>l!!HQ&GV8;mJ7S_%s4_-%x|wcE zT{UzH(u+85XyVpe;DMtc6w-)E&@H7!dZIBDhu|?wVtb}BR0EGcS~oR{iFe`jvy@Bt zV?)?hD>p`^>8uM&DeXG!x^fWb|7q(xz?y27hC_$YJBHpN^sW@?y+}v`3IXXIMY)5<1cmrHi5WA}AnDx(FhO_{aDC-tXS;-v2!F%-K0}b~C%#vuAeC>25L4zM4^f{nJ?Gr1);Zd|{hb|aVbX-G_iK=eha&_-tG|f)+%A0G$lVn$^7&~B}NsceKvPgN)ub#%GO0nUU($1)DQD< z)DBDdRp>M}&PdN{T-Kq_gjjC{?L1zP}bNH(Fec^F>`g3zd6P$<*Yres#+{ze9V zH-8?7)9?z}PDNtULGF56wSU}C#;@3lioT5_1G^8A_yJ2xOKELqv{d>_Lsdu`N z0EP{L^NT_!=sP z6Cv{tGt-7K)8*MK)i)^V!J=ha6hanvCtNq05*x{xM7?;52x~muUtRG*!W+ikEyFGNH4^x3O21JCxSW^kM_hET~ z0*w=fDg_H1YK_u3#nx9UG3hp06(VaB2Seynzw15%-csKN@8OqnJ~I4BF^gs(7naZ1 zZyC`~b*R0@cGgr->HI2SNBdfjbA%a-1tOHdtQ_r3Q?ONc&+BAC*EZgO>%KIVM?^Kh zj!YHDUce{K|FmPv@~fw)-?0qLx?pDHPl`&JubIgu-0P9PWt%;y@|)+U){aX8NRoQp-cU0f$KAyJlk`0syX5AolvG7!t5bKqtXN;Gdp&|4EfieY zKzcU$bElVNExDkuY3LyPApA$bKB{obeD-31bFAnvR=dCuRxVzw53nW}ui!gGW^paf#*FfK6u77?V%=QgJ<4?iw)7Z`wV4*X}ZiJA^ zyR*4oAw7vRUrfA4qG)sb_>$RFP{EkkZ?Z+;QNA*+I5!vdlxzyX&*D>{m#9ikZ0c%B zFphj?d&|>z$k!~XOSpdYu?Wv3%}my!3e^6-24rB%op431V(Pi|M-2kkS3!{llbavU zcJ6S_j&HzdvrIV0-$f+M@~|RfzL7A?p&q(OONGS^6^=7an0#68$vv?f9B_O-BT+ak z&Ou|=fvwnydZq$?RMG0|W}BQA4bfGdPf^g*v>!3@(#FU}?y}fy)$3|QpfDWYT%x0Y zbMpW$IX8H&nXL`y~w}Y*;e^UEuR~OSC&(SK1uA8Bt&Vq^(;pd4ZPLlsl4;kQN?x_ zro(3Q&(+!2;X2COuyGT&LAEu#>+yAQ!2~%2^I=(fr13;$#zX2wn51OySIVCy_z2hP zF>mNp#4&6n`nT)VFQg>HkK>y_?*|oAF*8$JIJTkh!n4zL8v!PsY$+f2UjU;YViDTQ zRF>bP%l$##7n@C;km%R%V9qH>r$wU|W%(j>1V|7j^cAoCsA>kG9G#?Ji6x*-yn*@1 z>)~z2ucRs&o0)O=ZOA`>7pmbO)T!)Gu zae5wd3A}F8e6)wK-h9$-j*Qopmc3>-yy#vjJzrd0a5ULI(c#nH>G-Tb_Y*hvnHb5U z_trAo{C=}nmVLHNp@y(xc)Q0u+Qq5KNkCk5W{Jx1P{icidEzfXqsO_5Ni+92%BIRKX z?Yi1YI`+zo2g-rMGXtC7bMTfU_IF(a15uFm359qS{CB*?+i+9=;;Pl!e6-2jpsm=2 z=`_=T206561*8}s3(^q6(VpySoc4t~QT+x$$`4~l3Rz&OgyCwLNQ$v>#RazGCrd$h zfWDvK2Y&}i@%nZ~KcRVLJSb3si3$;{S(whJ*uIx6a+j(7q$GZ*U?ZnmFKL)e4XUvR zm!j^_N#k#VtNSb+^o!Y@`I%<%<@S-@(531+=D=W{C@KLk2Q>vha@o zZ8JWQbK9`(*NYk|wQAl{t|n$lll}>X3maRwu5`9iy&O=AOtD{QVGTbyy5a1zEoMn( zX91^qU@D}yGq*(FsqTD&hq`5hQ@=}Fu1VLudX5!gC z#a7oH&wz|q=!XVZ@vOWz9ty1}Q_20r$LD4V)VnXc$wh)1+-RnGecxC`vv`-7mxOVc!PzO} zYKkJ2+)3C_JHRqaX?C6vLy|#i3tZQlBA}vf%9fRqei$pAWFOnP63H}5ov}NPy;1e+ z&nrMAN+E+_ivKZeQFK|AJ-4=7o2lC-ietSWm=q^#a86>jW%r#fnP8uCK9Wzhpvkqo`7wqD z9N4;7`~l!s{->Z^Lbxk)VsFDWaUQz91QY-@@dh1&OttTdt%{B_wk$JFVUvyk&wY|u{R#`v6%ofOQmBBcIh7b zTFN`o@n_;&7|PSC(v#RNg?u9ygii?dLxGBh8!b{Zx5Q27NnJ=Z@kYpa67%NMjADI;bl&`}s||I=JOWq1E$d|T9!ZH;>cn$LZ}NSM_pr_LgLx=b zZEMbNf!lY7Kf+KqxtE8)Y^aNBJ-=N2Kt=q;Y3xT(jE@S=aa${^)&%>sA z%1hu2NJ`43YJ1Aly{VBWaJ!J1#BBcBNxA(zEZ^hKujen@yul9!!@_`7OQlpUE7?)s z?)DT7oHg7t8;=L>bIr{&+G8D(3)2!4d^>|pQu{nN_}P~2=)$-+Q|Xe5<($SV?7iD> zZeYdLG7V;5>3M_8vaj{gpSVMh>}UYrpBBVf87e4pA_RGU0|YJx*rXDn>7#V<6l?=J zRQ9jN!d@Tba7>TVO+uAd0$3JqWKB)`nxs}YHIm)8E-!VSBrWum3;(1y+Qda7?#;8& zwy4!FT-M9x-}(%^JaNn=kmi(2N26W@lug2n^X_? z9V-Xw-ZjQUT7EV;iN`QhOQi{E&$5Zf$p=i(N5l~=1~eyMU-K25NWeB*oKx!`+noJz zPYC(e8n*un;zoMU4zN%Xs2@W;QzLM)33;+a5Ns=(i8^~_Q-QFu$)HN%32ak-Zpp3o z8!#_2oH}l>uDk3P#Ba96pj-8lBB_O6=K*=%$%u!nD@;rna z4GWfWZu|7l0oRU^M|_-MxPc)#Jsap5e0x~ zh?tGg#D#X+MP(05vNangyRH5ReMuqWu+1sxPzMux&p1IP$lwT!V2IHW3azr_$`?&| zg`3mLw#}dto7p)H6$+OQ_>)L*@>HowE~HY>X2DL2%50!2spCputvBi^vy380aOip} z5}Jinoka-&A;*HTxK)4B6=b4#K0>){dY89bxoT$x`xJsg_Ykhjh5iM8e`BU=opAJ! zOktc8*N=!NJ*z?j*m)4sJohwCzJfr2>9Dt=DeBkM(X_P>Pc5yMPjo#~zj9p3hY54u z6e$tM{L5c+$@jmVX)}G=I(TjT_+|T7iK~d+ex>uDn{x1^^MqJmPd<2s%pJ-i3wj`n zfXB&bFCoGK*maGT+j$QX(lkaK&U*bmx65ZvmXG|~X+oi_=?%=*>gGSesV@4gH-1@x zb5C*TP7o}(Mw?^u_Lr2+N6$iqs%{ON$Cx`((G@rtd1WPI&!`!>@a9x=Cl>X>x5|Rp zPs3H8_7$%Mm&vEj&O)e+wE>Th5X(y;GXF1|jAZDBjQ*vOO6s63R-LP%C0vA*6;p?-sOT zaIm$HwFN&K6(C*^#*!$(la#~d>K;hGz@bt0NF&xW6X`Uj+i@y zSHX3L*NEce^7d)>>!ORs7ww5J#*ey`vvrz*6})}UMl`XWVl%i6zcxgTRprdiZ@`CW z?_^sQaDWh4#f~{bE|Mruhcl$)+^Fy;ukVE&d$F<<&=U>2N0T&)UvRyfzjbd7jeDkZ)gq?OZ zR?KQM8)#5zC9X{cQ8eRj8Ru?!BrH4Q*<@W*%>P8;Sb@wa3+OkWo(1V%q!slLT|DQm zflLsF#6pJ2V&ixXbjDb`)=8O%K1j1!Fkyg@U@~{v!WyVi4j7hhGVQtozcLhqcBF{wM##!*WjmzCtYAFed5na5Xk~0j51#2QH`(HN_ zt~}{2Gtg!_;?pm3eRKc5-aaX`>4eRP^W?KxUJ~RolyrCV`^mj=7Zt>6FKx-20?;JX z*757jsORUMo$QU*%;s*qmcCH=(i78r3-*Kec33E>*FPCnqI)S?A5->phFD=kf=N&9 zI4=Ih+@vtiqivw}H7OBtAs{e3p_&`n-(D`l1+~ow4T|L)6s(NU`4?cMe_javZ-IM4 zaj?9o9^_>gACKsX_`ha(nGN4?-FW@0AMG>3(f7O&Ilsw!N2$FC9d)NY&U(W?ydo0`}X zbErLwQSq!=*=FPmu1Ox+hMrzb=4KKl#(9yzB8;}-U6hxJ=9ovisI;22+=lpraWU?N z+gC%me?j)uf-Ks|%?bcFYMfu2znV*Q{j?tv@AFS0=QuyCA!e7?XJqI^t8S z4LK$1Nn6pp$X=eR6ZWV{F)su4+qu}QzBjv}1>rR?#P-C_*Z0z;H;~TTFd&a#P zvMgMA%mn&~fht43;?@^*)evN~^RHROB>$zndHNgz5_}C5U*h~4w5XbHG4_P0p;@@z zM-%p5y6@&KTMB;`dC+F&O3|hCtIEZ&LSv*FFN^WU4PrkaV2ny>AgwKB#xcST)z!@z zR^>#pDd%}v<~oICBj?jZEQVPrzUUt@EwZ0|#qW{x&R3BM+^Ly6iLjGyiJO4CmWo~>^wH#Nm|6-I#+w#o8 zL2Yp0gM%8iZ)6A67U@OY4O;2i>Z`4mIUbfdS`yKTzzK>TX5NYG;K^!{Xp}yER*}XSg+>{YgOb360CPa>3tnU1 zV0D!jT?IoJH)09B0jW9&qvFuw1mMLwP1G%lmDP36sHYX0btSPv4DIEa%>9TO9n7k# zaO}E*bz~fnC5nqb7PB^K?c#6L$(p@priUUeW(v%qco(Va#=~Zn^Ux~$y6+1|H9YjUTV~EKyW!rD$mlp=;lv>UiUZOgo0<08!8Z_??vM^0_mzR7 zlIY%Ky&?}whwnlTBFgFVNw@Wz1z1d+B!M|BT21%JW{$#Y|rrOyA zSzYSdt*Rfjyjt_M0~}EN2E?nybF92py3&@TrNgwL(Ea|4ZNtdjGsNa^fO-Je)LiXy z1oYEEp&4?T-{_h1wOgKn%1$Sh9zHq~+~~Xdo@FA_((Kn7@A!8iW{JK&Htr{XGN+dR z7Cq+Eygap?eV6@JQ#+k3Lo0btucsBAuKM_E&ro!OTf-XS_v4!8WVBJxC z&zUm3xij@iHpZnQu{fgg`DP)ra$GOA4e?^24Mfzwt}5m{j&5wN+M zv>AKGo=hW1nu(brAOz^*wl%-Z5C(=IzL=6MmdX1S0$US@NtyXfg?R;m&Sm2VF&e9Q zeOcj;zDz(&PE1M!AR_pezRW0XK}^TPDD0 zjb4T4r0+zNA-=xCz9X4U5;}vUoVwk|i;wP?Low6%LNWU)P18Sa@bzT8G;sZve6tcZE&d}LY;U(>W|Ds~3b zN|kYhg1g$fQ6c`{FL_H|wygf2gcMc8rxdNm2-E0zjHza9iAgNPTAk=H710YRGk79= zoUir15#+XfI|zDi$7EVb8b0N|urhUS$(fCH8TQGoCz~Fuo*s1QNoy>j$zhNY<=4h43GQI2PXAkao%hV65(K7q~_GNXl zCdK2}Z%Us)wRzLaI>j(dpI>nV)ABZT5$1X*79G`*Ec{n4tCKTrpE%en_GCfJ&HHak zlAy!nUJ{n@ti)5z%mr;y*v`Zc?nj=DLjxPGErI`n+CbOZK<2PYg^r49 z7di2z(MKn9PRcA%n0qArdFR~7!JGSm-@ z#Z>I$SJ!D|ji{HE6R6lMVSmP?IG}=7`tF#l>6W>1-nG5$o$9)=$$+6d;pQ z95(cvRG7tZMwOhYEcf*p3)ITPVMiLd?(@VK^Hi!9B$GnJG0KFh25mhD)yU9~a*B{Y$HYI4|rfHLbdUZv$m1_#0S6$;o`sYz1-5*|EI{*(hE`8Oa9Qpp_q2zhWWT)sBAq{ zwgZE-ogS`&4d?YxWvOuv#Uy#G)$F)h-5>sg1LefpkvYpIxPvkm{Qd0_E~dHcY%_N} z5IjAMwqNgkB9>bI=}Ul=&jm?~7#wt?`taJO+=wV7u3KylHZ93i=jW zlvCylDb|hquk(Ge&*~B%h&FS|KK3&B;VG~1A$M2D@Z{9e=F|5^?XRk4`xTH#y}>VO zBhBgXkdHeb!YX|Zs$1G$-+!dfMjVT~AJU|b_PhEG`2V3&Q5XkD6?_;EG2l(BKz}j( z<2ZuAJq<5c7;s|g^g_vqy~_}&EBk^TAy$A*)pSYTX9{@dqHX-(3Y=;@VEyH8Nn>j! zU6HPa#!|j%(GhH$Zp)erx4X7dv+#`m5^a7wzzA0izUAnacWHfm3M{JZzlTm@K5c(;l&f_k| zG`b3nIPkN-inb{C1`7~%+D@_0K!yEk2Ooqp)99GBLXyyRZ9{r=-HC+7H6{#)^wct{(r|-&e~_iV1>U<&HSW()b5-a0 zBKz^8pivGq`CdyI#sbYMw1(#drXqDUb(p5%XpwB-R%*?`Omk3l98i&zU=AqF zOMtngBaZ{-3OM;%#J7GiDugD&L@c4cE+k)oLA+WitXrmOAkkM0dvB!YG%wX)V zw^>y}QfQo7E8*fJjns%Cy|4mmv~s*Se!RY^ZSp|#!pb*3%kBg!oSgio_7}fhrI+p4 z7xw+}byEj!)-Uce97jH(UU<37>=(>Ybd^?2I{2lLiSqd}kC70;qFN@u>=AkOZ00r2 z>^0aLmT-$S&TqpnVsJv;B|zWBxd?HciSlPHa#`_PdonV^#{@TG@_sJ$)Ac4>DnU}! zi-?(Slc2>-=%f-AjD2l ztK?*Zw3@YETaJSRjcDLzd)Ak6^7kxZd@v(wOXlb}1+&D|on9@KR&Z9RAhgdf4@0@$@|E=}jTHjl*d#&lJy}N%^wR`WX+BN-c{M%qckkZCzK2bLgF^veqG1C3A0OWu(Qt2HpF}@G zzlMu;9rqeK?zL~tXk=GPUPu3~?vLZf&FknG*KS>Pl3x4c_|b=U?K=96n;73F(Xi03 z9>hV%x$2t768+Z={|$vxZ6=KPZ*Kh8J?Nf14>Lvo#s4>y%xd-p*Y_I<2PdR?%pUwV z&wRhu<6&72MHd<|k2H|gzXsw6T?hC}c{_g;;Hv$L5vu}jBh7M8B>UTZ4BgoGt zdP_Hc#_ZfUQ*;9fi99qXFU%OVmW0@K1LHdRT83*cGp0rP>qP&PfV)7e#pvPR^@GGu zo4pasBFTT_9DiKsnqL%aV^`gKYYuArdblpo-_x}An54SbSdL6^dW!Kxb9EfLv3S9K ztmJy)xk{A(A8pZVIxwv2>_s80gg_8np zIXbG%*T#$2)m6tF;$wH&V^856?E=bm_G3NHaBh|VWYhIoUB=}yw6H$aQxh~a^t#sK zr+Q0h4DuKq2$Vu_;QcP(3o=Cuf2tP~cI>H$kN*h{4ed9buk5plP7BffkK+G@he>|; ze}9Vo3+6vj*KH|Ux8CsTwGzzFe@0{YP>TNvjiu>M^B3Ze|MV!C@rysY&^5rkmf)V= zKCu4FX0qA~YUdT<9G=lgNQPtPE#KFNLOfIc^Fc9iO$Hx2zN|!XVakGPH=o$a$a(RZtLA&*yU!p-o1|DKuIA7S;=TQK8yJdiF zz5mQm?x58-LJ{)T4YcE~GQCLV$DjNUm_IjDN|@rEUr@X9zy7)PKhx2xDExtLgRUvF z(Gi>Xjj~8J*{}?Inm9a86gxrr1ZRRJFFZ^+>2T#gL!n&@suttu`#x6N+=WGnk?Ebu zh}wPLLA{d7P#rDs;~>l%0dWW#km8BQY`yCRe-U!Yqw2rFMRO64kl#P>Ut1A4Uq2&i z#54lxqVTkLGi!MzmPT|GORROfOU&GDrFz4K1;*A|7)Iy!^rv6{M`RLtkJZ!XC#O(l z^OiA(`TMHBE5Bh^m_*vf+~L7mAt2AR74*UqH*TC@X}C{TsIxQ;eq-BSSdG*42>&pjuA$-1B(pf#n4@LClHHp|GW}q6*i-$=Mv!Zs|8WzU_r^~6@ zp2zPO^sYu-63r!eTsfM5IsHYxh* zZCS5uJVz}Fbla|Jj|u4mnVi=3(z|C`BfgB};I&&MkLhO2ez5Fb5gLv6{zPNlcDDD= z;6HkTSgbR;T(hTl%z{Ol8c!W4-?T%M(=&yC7*|%kJcxuQ<_~r`2iwnZ1Ba3{+A^kV(=_W`2KP1$ zb5x>M>g2Fyd%L?!ifDW{Sh@01@HEm&CAVn(ii&$6xAjqoE%`Sz;RpcqFt!$+^*o-# z`TVJdj-H-_RVM-i0*1zbNhQ9IPPc0OJI;eIR5w(K5Pgf5ZcIv^}TJ zSki&f{N+m_pZwj(iC~S6Juh>?SR2zBf^r9eaGv;Wz)QXat5K_{>C9QzLYLqfS0JSX&yWFpcxI@mA&l*b6Y5=V`Pjl(L{#bLK-SAO(M(JSX$VbZC_4-iFG#bhy6&oq8s4uxTJ8fhq28ClT1Q2H7VsDPhs_5r zn+pgqMh1}H+|&m7bh2LMVDB-4dAyaLP?ccA49RWy@DZxduHS3~$YjC#J4$==U(kML ze^_$NR~2p7RWQ&)*Wy_2VuFpWAXg`4?V~<|*F~3L!VS(_oNAMfHzj4>Hi)<7I)tZ% z$|?5A;wopE#;T7tCnc%@#cL1oB;%()lnIdA6s}aKXEdyzmz5M1=hQrw8;)5iGZl{*#P~@ z@S((_qylLx_5C^iv82JhMYBTFK9`E+hNz>CpB1|$$=3ZRznUBM5$(_M3C-*pe^BBB z!Be($O5%?O8O5ckRtMzVfLRy;quvie4b0mru99@tS>@+xJts$dZ1Q zKg=IBp8!tj(_n+SIYdq+HZ*jJ?Ol{Sg?#a4oa@sOFmYBp-LwQwEu;~uwxVby#c+E0*;0z^ydgkINiZ( z4S15xnIizA8-=WK87M0R`-iP@0UjlB&i;b&4-9A2)87XBAG-gc`Py&GYn{7rj)(^T zkt+-1QSGfP-?Aka7n&x+Ef=kHlzd7H7THBhIBZ4&yGtrOj?TvM~ z^J_wU=Y^N9C+mksxqSgg|5euhW7&K1OdIPr`ua#`WztJ$KBDBvncueA30!mTb%*#W z3|)kZvaR{S((3~NqQV9ZL>s@vciwt^#oRb;M)R@NvbGcySlo#xS~aJz|L}( z+rxJ2h&_~qzg8Pg@d(cv5N-A4kGft`xRe#nG9R)opI#+~JDFVBu$`(s@Dz=F=EuL0 z_oJz1{(zO=wR9sP!D-2(0sGGwbujC;g><5fiAASrx_z)Kja1_{UDY{`A^XI44nMIU ztM_O-^VA7d66Tpo^2&$6ZKVZkk1OrTR2Drd^o4dP2QSF!zHA|ZBYF=KqJwz zE`0iQD$bK)vyRep&eulEek$s~v9&!q2c4`Q)zLIyFy+exPv}#6@x$2vNnEP|2oy1l z2iEJ@3VRLh|2f?sDy`N}J;4f9R=$xxrzwHtcE}H(BnE2a=aC3;F++s8r(&+K7@o7y zLvH;NApWO>?>jN&)?`~6RPFKf%G(d7@d3+;HvVQ^9?D{yCM=4PI=xJ>{<`L^g8K)X z4)y`}nDa@5TEXy zd6tjajljpfW!irceq{`+9k}AwP}NRl4<)=S)4(A|l^L|zTLD@ev{ocP>S;d!-U7Ze zlcLi|BHQW9>M6*JN#wXyyY+jLETZYj7AhQ}R|OuP>&hs!?LE6nk_9LM1?fvGr*(UI zaR)^ky;D7F#|@a)F7ieXvpBZjm%ghZ9LlX)W)bfYk&piyES9A3D-IqXO#0WWC@eFVu7PjuYXqjDklC| zzrFyc>1vDKr7mVJBn#iVc@7+z1`Q83k{)&FofxfmJ>pW>^3mE528~SY_DNF>n)UJP z$$Zp^vbKIV#mbqxB}yyd`UmzkLz7vz)LGF)oR?RHD%LiEp%=eOMf)?hc!$bpd@u=l-Qt-de7XK9d!ke*U>9Ed-Bp9`i3onEF`sxVB zj+upfT6gq0_UX2{_rfP~yQ!azjsL}lU)Uc>O!oemU9g6tqtNkYrAlu{9sjZoF23N{ z?LC&|#u_h56SO*q2K}44(hy3D6Dme}Vi9^JfAkJ$SNH-m|cr(|fwPQ4lKTkI(xi8`HcV z3g&z=5IkfzEzgCDB~*e<$>+sQ35%>Pa|!zgi)YI=+vwO*a6}IP$C9F?`4*jyg?!ze zDLo^c8mnL?`U%gP{_V!#$D7S`n3E0cVe@o~J5^T<=*VTwqhH-1^ni*Z2q8e8TWsaB z)8p}j3LyNlxD8>z+A6Q`yWnTp9ZlPMj;r9ML&K?He~WlAzWT+;1BXp_y~V@c9y5|? z_L#%fGzxy3e^(v(DMfaeSz+V)*yGLTz{d?w$qXuktuGb{Xy#2!;8{(W1oJVJn-hhV zC;zrYhgYV$8f^uF-ka)TVc+w4H{OW|Vu7ZA`S_Rd{h951jx}(#h*;pM2kob}fC-}} zYO}_;z(j5P*}GvdqiW~G>gHf`Xn9XrJ%UToyPL4SFT|A}C~cbPAP^1vQr$jjiP1%C zta6BkW+epR6cYLw{8##JXv%Wd$z{rwI6ipF)yBjeFm2RAWA-r$tI}BWDJr`SIFY|I zJd?kJv?!f0>+A&uwPrbP5?i`Yl71Ah8Fcs4CJk&np9hDbZzm@X9EkTGxx_t1|7-y@)|^-8xutW4yz@F zUYTywIuxA>wrO*Mo&Zme--7n93g7gt=tDj??bNQVmRKB|n_2zi=v^)Bj(^IGk?2Fzpf0j*?0?O{Bl5v z-tVR_O1W+v|1j#%VOA$xaE}zaF2WAKiiyy5B897Z6SB1wCz!shxp~|*mP|2II#X&v z5lYz>iHg167Z3UPtC@exeJ@GPx_S@M4E?9<^q0qpW8T^Vo}t3Zb!o=Gv{O>PVurL?9Rb>_C8j4AXC6)ML#e*Al^Us``yRkK>yTieqJ42xn9N-;#o+ZclT=B82b zc5F3**ea&;{eZ(E0wpJURfS)RPTg%>uOi2PF%J0rr}T?;d|uy!ZG4|fwaHaPbegbP ztt2$`CrTKI4T6ufr3^Zi^3MWYVo0O<)P(o%!gm_DH#5!>XrW~^ZS}f4P*3Oe&+=u3 zQEab&QT=ZiZ2o%FAI0<(HN zcI3alE|+u_*CYHJ@SmG*<$0}~>KE7=(B6mJ{yDJDeR?7ommH6e6v8;7^{s<@pOpvN zYvvc|ZMjHS`37zJ>;r4h8wSZ_aqqnT8SHQA?{(APR?6(FG26~nN{j8dt*Jp{o%}&K zDQovsARlR0q=O;zs(z+U6g=WGHa~oxa9mn9(NfWzs46j$2++9EI~FY$PbXiL#UF!u z&X&=~nPZ&au{yY*boJkdUnQz^sNPAa#v0XHl%%Dv?XY2L&tE6T+cAA=xz*+sUrRZ? z09$Ttm-dy7m#>|4Krt^S4NcfA#t;8h-S7G8Z_#4==}!7`RmYCiS?zM>ymm~-mRwal zi3Di;scBCd=>$98&VfM$!O^UA3$IeOh&cAYnpX`_^kPUWm3FX zTSa%x?nJEYP~5L-9z9Gn-XDLA&NdYO7vV$*CEV zZ?ZUmV#NspCP=ql(T7wU*f{hiuWL;hl1zj+BI?|Wm7@k%(ge#!X}h3ZMSN3{^pG?l z>Z%SeyO-a6EPzk~tNe9jzk4INYw!@wTIHN^@ZJdeK%4upg)vsegt0?<49r6JE@O*H z6iKB7bD$Yle^K8y+P~i^|BnB!+zshUDO@k*IIy;BZuj1Wh{n{+tcE0))zWF%`<)ce z;QnxdoKBvsg8^e@cX7ZiNABd}b4fVN5nPzD45zB41ny_h@>$OP;}TTwa9F(3@DyCDT<|Kf(CwYxW=A#vSNI+CHhxq^ ze-`y8{e8Q?vNrrsK5l($L2i6QZa+L|tcll&MCItQ9`0y|GYh@d^5t?bH zvA==Ln(A%AB%0(GVb?+@^6N%>P6HOQ&NtVd`6>$$H3q9W9892 zEh-AJO;U^>G~2IEWVh|EHj_dQ@1gRia>Q|2^5hcM4D%=3sBEKt!2ci|)n+BCoM|DW zax!;?d$=|q0nOQwvi{|IPS!{!CrSO}@6~MX(O12`S99`whV&iE{dm$`e1H;;=8CO+ zoj|VnM(3w3&9OGVKCWJECo8`vKcIgh&{Lxe5WD=*C}`L1-deM72O^W}jx_`HG=SlfBy91$!Z)hT;e@+wGJ=@~>B5d%D-fpZ=mKQzU zgVqJ*E4l3-+xG~zG}qmYC;ssti~9lkVQhAT;*T?nN6jM!SM%4|cMF{}&5U&+v399I z@cr&~(^hNY$YY^HwQ<%9xSOq4bnMqTajcE2ikzoSu-NPggJPo_`?7zIkJ*qFG;;<7 zjeM<_4(6kjMy$BJ1JG>kg(m-E@lOnTYSfRi=2Z#aw5X=1h%|f(t`p(KJ#ppx8~CJ> zcWU|a_qlpT3#;huf~&BKTiPwcwqk(Gl^yd4WIpS#=+w%b=ueoxkT)aB`~PUT9+EP# z1*_RV80yo3?);(soJ-lr2?P~Zyi4F?n@Oi6c=6Tg4AhKq-f^MK$M6({!# z!bj9xL_EA=Dm0Iu(2A<^Ju{@E7ne}G`m*iz)z@&>Zlm#1Jhw(o)(UF*T7KEmr^st6 zb8#xVKQjOSH~sxc$f<6=uu+*#sS>yMS-#gv7Qx5(|7jMl!T6z9=S7fL^|L&piYhKf zwO1>-DwdUQp&vSmYBD@q1xmY$ion9`F1rqGfpAK1dI4vVlg9~6Q1|HD`6b;BL)Auc z7p-1);5_SsmP#}5S#Cg-}+kB#|IjPy@_J}Iwr02no0*v zbgw^Eqe>BO{#s8V1@|2Avm|`ZQA|e60OpR-Hcsu)j+~fEB-1+Gtab$khIrtF(Vni+ z5JeLk;=KSQ>C<_-!NRC^cdO9^CrVh6>AmpCCAxF4C{vVy)gptLRz3y03lwvb=WBF+ z(Rdq3^N{$kMBd?R4UQL)5%$8SShP8-Nm+K#7|5B-b$3t9`00ogC&}%=^#^xY#<7s5 zBKU2|=Rz{_UG(p7^YRMEHXEgJW_0G{@=d#PHWZgnyia47lKO@g(-XqAKaqt!!m(y) zh@!pSpTjUKbm(K{1j}M}bNF-yWh0}&G5BU=; z)(%~&_A}$n6skeonC}d%{qz^3gA}qe>+WV&UdY_N;${SaKM|kAXmzNLjw=qHPKp@YN{`$Q9zAcnzL`hU#leG*s!wT zEIevWVkWO&c{0Txr5wI*rl;L!h@~LcbBB(O#B{@YQ+y~xgfYF&&{lZ3-wDUcbhM!d z1Jr@P#B^daGa-VOA|Per8V-e=DZ=eVboqks`!-JK!NBLt(3?+O zn9@G=P~Ly>#o^Zb7$Me@`HawetS%PfO>&f!Dth3aGY*9IW|SaZnA=fPIYsf7ua%(T z)ctX%|A!gNvQ@vHqI-Wzg3rLg-1#dQ_ zyPE8BS!$2(Eh{7iGHEzW+}#?bqFEaa9P;cUW=k1QTpP$5-|efniE-V&`1 zEX6o1F?{wq-M;W^j!rFd6ZZTFJ-fELc`K=y^8bvaxOfd&efVm0XGIu!sk7l@(ml+( z31l@Ru=SAUa+Sn6mzVz{88L*{9fWWV_ATRQdnRu|D6N#nbG?)K8KOGf-IFEvgvV5U zMdZQrcO4eK3vveAK!RJxf}37v%qxDEcNSuxakg7t7Pu1+*ZA3rKKNTd?2tG2ZY1~n z-+tbO(1s7Ce?yC^|Av;dHwH2%wm%pxITy&pe>_=A8o=>+1qkSabLxApfhk@Oop$IE z3cPpIn%7X%(V5e~DIqp}y$k`seWy~{K|+bG)mNI>xY0k5!MgB4eV)$C`w@Ns4J&VU z%(D~)>T~S_(Vv&uye#*-Q?JcD(XR{4YOWm)})< zKIqk@_X_O9vO$|N%0}BKR=?!3BX7Vws@Nn|q^rMTs$*<%IJIo{ zl1S@l3Rguz#Z)X+_ysupg)g~Pe?h@h8)W6^X?n_V+_Ymj$sqU;gRjGVntoDs-Rynb zT{XzUGrSaCO!g-k4OH-3-ESC8MB2?gnmx?V%f{|mynTD_Z`tq+P9qV&LBbk`)5P8d zWxUpm~Gy?gR7f=4x;7e-xbg9S(PKg#~6J=Ex z1u6@gb~x{)pG95^=f%k)hu-FvXQZBd%QGq!vdXd}G-U^yy8CobmPNPt!xa4jN5hA{ zZ)om=7DpmTmC5NN$$MaCe|Gh?u=TAEXV&D-Fex+a{HqC9y+F{b=yTLEEE%AinV7)q zrJyM@}4@OPv$lY+1eKid^X4eVf+&|xr{PZQjS!MXV{^kRIh1A!mOFkOM$+|}2}suK0mxVE5Xn}^ zD>*b&G3u+B^5GNR4)B>6>+p*-e2aUnTsqHS)-vxHmy@F=FfbP_nDlZL!^K-)J`c|B z-@hY+1)W)@IpG`u|065#RCU{|+_a$>(^z|E~S(tT|Il*{Ba7J7YelDn+ZN6MvV zlFmOWh6mZkbJ`sis-K^a;HYk9%-<|EN~O8$iG`xkR2N` zn$$;P#yfXbY{qclQ9w{{S?|YqKqefzv+D_Q*+Wv~{))-xo9h+tw==o&HVTgtx z20saHTF2wJJnM?|sm5z`u?u%S=yNmN+P5j91bR$MSJ z6j;MArO5#}dx`ql(k6=?ll!kolpA$vqsy8|i-%GuQL;haW`T5JOghBFhr{`Rjirav z(xXT6uvPpW$B#@&WqDBwC0Y{?UJhF3ro@AxO<|Or@K7DR^!)aM2T_r^X4R!(OdWT% zN^ok1{mDZp9c?)?B-=1(cK_9v+T9Xnl_GD3M1CIr1%17b<0q)JzzH<-jna|<- z@!JQ6O^lD!u7FoAUO-A3S+9aVei}WizdNMadWs~nU;!DNN*;45!vXHn|MJ7)JI2b zuBc586A{A1G2=zhG6`yEVfItp>7>!@tZAKY>x{fKFSkixCYi(oCIYpvQ$?td$1=ry z+c&g`(ug8Sop?Gi4zbr-oW>CdwY@ywZI1NnZ3ivVob?v56_#&k3LsDli6Z^8`;zA| z&mR*Pe*82}tL)h^*^Pk8F@CYlP19Mm77v8nT+)*Fm-XgSz2ku#hAJE-n$fbt%ko&; zQ^ypaGB#RKH4>j!;;iC_?|m)k>7n8Bo+=$xcR?xT=J^HE3KBSN8S!L@<2f+a*-Or4 z(|46W)|@W4crhW#%T2V<*uw%n!Ryy#38F6r+Ii3$w~Ra+FipSo4AxU*|NR^)^hFS?5q;gQUznI z(NmBt-fpLPwuy3%6o>N7nC!~>EIqoog?ycdL7(y#`P2`18K^o=7V||gQr0#L=Myw* zJ`Sl798~QU*pL(%$Lacoz zboyPj#G(E|g=_{z&n(R5ZEmD*aX-mnIaXFa4>Sv2hcew54o?BS)l<*OFFyS|l_1A} z8k02OXiH(}%=sZ^9oDg}*qwRN!w zIdptDTe{vWcQtJ*?uUlAdf^~O8-qSSe^$(k<$pl3%&W)~DH-*twZMT&C_>I}q)C(S zmUwW2^x}AwA|Q|)u8>`ow%O@f8C#WWle6++WI77eh>DZKI#bYpTCo(%tlKK-Kt$0( z)okHZ!UfXqUk;I`taxlap!I>%Jfhb_7NQ`#WZ6YmS`p;KKr>-M^1e0d!e~4igI<}P zA|EtyWU{oU*`kn9m|D?T1IAF2w7t25`?)yqp1G)RWFbX3G&qegTBnuBbk2}REuJb; zxa;7+4@}t>f|Xrv=sSn#H0#OiRl-eBVg+CN5>>{neqG!USuU(kEGyvNA?V6Agz3=)bJiI&ifFi=Q?*{{15jO(^S)sVxO8MK6hLXNj1=^T~~if zcgK}GMx{&-l968%8yG2=Lg~X^MD)rpTiBnA4mWXmG1|7Y>@eD#-T{m28`?#gWFXI= zV%wH|c%qI9HN@BPT%hBz{9c3O*}nUBj>ZQZq5|`l`Og%6y2%^f!K}s*FmYpbGB4B*owJJo1!zDp;T~axaFAyue~V?tVFOPonKu*8 za)Ps_$!>NqtyNXMfZE%sO^K}FwSvdf#3AIVONloU^^c67MN%vPan~`kus^5Pn`TFW zy(!ve^5dpsYbHW7o0K(*%;Kh!JHFcD`!mEn@xN_XZA@3Xeh;B$6CwGWJ1euHcNx=b zZkDJ`8wp*tCC90GqrBo#87Lo?$ z^b2uELgzl6Cgl2IoECr`!cT_v)&#q0RjChOPEL0z zhcgZ^c_?OC;O{|3gkX`|BJ0Yl;H>>-^F6CagQd9=&&3$Z3V`)(oZiBPtNyg_>@dP^ z9Fw6yS)IZm{PeBdwhH13+QQ9JI)d_;N}95=L1rh~TO|r~OD<`7YNNH2^*2E=rCj@D z{)Y?(j2h$Yxh=85+KPJbk2CnqoetBrtps)BWZ&JyH2||F1Xi$-zi~gu$l{$Q@Az6K z`}!Pe8O)FO;W*n?097vyeYzFUp|)RT+X^XXLgWzOY7yA)RfV&qDe6TmPmDKk_92z? zL(?7>WoDYIGTqo$#*K|QQnar0{zN}l2pSG^s9m_lA8+33t1fvu!$3y$&{CzmYi~@+ zMqx}_lG7Q7l#9))iVExiwjq0UTsc3Joai`TMX8Zc8o!&1GeE+`$_wi+x+pYZYVJ(%Wnmr415bessW`{7gChAAA z73w8uU%oer83WZTmhIfSi~m>jc~Quq73qqr~ z`RZybr+RnGSi8ro>C1TFJ9}}o(rL|>PCVL`L*xD*MkTV0Sf3F?^sH7`(}5Erifk~m z0vEPiVzm}2Zs8@kE(zVUjaQSEE8289C6x@6an&uoRwZQUC1$tH>TGl}F~5Qwx+8I$ zYzh(XQEWSQlLxZ7ATlhQvNZ?YcQ7sSY{oAW-_UNUYR67R?Ywy_^Z@sBa_G$)E{rsv z#3=9F{-PrN_CGd$Y$W_3hs$tHALzs-0-R7feBI)s@J@gHPDmB!tNg0lBXJZX>xAn5ba_X1lcne)y~*CLikH!3FH zM9LxSUTa2;_G##Kc=%v$xKqkQHl8IfmfjRz8JXNz?hN$p-qaFa=%UF&criMZ(Rk8+ z5;NM&>g=3}&QG6h0BgmP6%A@BCyF@$+V4;3$^pCiNZigTdA_0Dn>%xl-;h3E{mrEe zenYc_zFCS*@3GgB(1ON9G9~d{JKD$W5y9yQ(nGiwNW3&3ku@NpOBfQ=rulAcGs>g7xI z{)4*FGgPb7u)P`RSq+n_9QSSp;NC$UsYmVEbDE^tj;D#)ial>1CD3-&tR@F)PeTWd zcsJ}mnN(oU{jV8?Aq^85qqnTbBiU&B@qTD6kZzkuZlu?_SE9;t?`He88ro`;ci$C@0dDH6Z zK|tW?XHUVyq!w{fxQMG$`@Tc6HP&;~f_*q|58$#bc(|wYNrC{Oc<@{BE8|+?#%&#!4}FSMk0hPeERdvuc+;6x z5L;l~5gglKz%OjC4izr*Wc}jbSLCqy*{&Z(2QupZk}g;{f0_wAwx1%8d;e}rh zX^Y$-v?@j^rw+m->pU!Dx=f1;NAZb8_9KIA93JnO(4B7DK{Pvy!`ao$449TowT52E zNjR~vsv7te?;}2J#OhhW0?s(PD+_?ezSrN3X2>RLsBAJ0nv2Vmq)ShhUY2vZQ67o+ zrc(01YQm*^d~otIo=urA+~ehi3;Fyq(N@j`rcWAOZ1GS+%z6>2ahEp>gb~I+^i53iq;ZE_UhTaPv)P=Y{Kr~-uNhc znzILq40$T80i9*GT$Es!c`n-=1G zsBpFZP5%fdFr%E1oO>xZU=OT{v^05dlock&JyN_;F8V>#kB*9||E+w=J&f-Q zZ&Sg-bV?xf%stG+9w>V!2laOE1S@;g%pBpz?oJ#=Zi)8WM5<3BEK~F8!5Aoh@IaP- zlXP;T{Y|T`xRi{;b5qaDgIhW6GUJ$}*JWtm%1q+HZYz2BH_%I7@QZ9p88`_XBUf3a z6G|xUo1tB}|7WDwtoF^y3Qe%X{_o*coW?B64X=S<5WB@OZC8U)N?9dPvQq0PQn`zP zDq?NYEunqIkXKbwSj>P>*8Y+y<6%IK^g^x*Q18(>Q06XM`pu|;w_R&*%$|tvM&3R* z7QYDOPI(x#c5_xso*rh*x|eMKzx-rd+B!mAMZ>ep?bUbMBrCCf`Jj+&bb=dU?&&PVC|vnlJH}OY+60uaJ28@J|QzR?P<$ye3tCdxm;NuKZpxIR0_&_WB9l^QU9u3C+nza(^ z2VYKbxiQ(csA1APdI(0zhSq2D(G+7XLe{;ecmwvbZIZGBO{W|l(Ii<@O$mq3Dif2* zW7k3aI0n3?9`XJix_p>o(7;&2K5@U9nT_H(SS;Znym|O6`8P*=s-rWkKpOTw>b(i? zn*Nd;&7-snDOs9h>YJI>H%Zn_0B_vDnTyl_dDUE%0&*k4zO0GtpknEA@Etp$0$4?s z3ms)crbu;nk1bWAxRm!L;#GTsW-J$^EuR-#x*^8^&%(}iI?`$1s)%m6s#}YZalCQ` z8lONKLbXDKNA%$|)Pf0B9Ee$DHa661)Nv?0s$ox(;qUjXlQ_jVaCn^2Gpb~4FVFeQ zxqVff)i*R{0jz0fyxwl?#GE>Cp@BV|hD`Pwa|3>o?7qKe zO*@vrL_V0pr>o$=tyQd)E@gIAiJTTKmA#iPq89pWQjxU^XQ;0LWS3mpcsq2);8D;Z zUF&BD`zPG}-Tpc+AB0oW#%?4lZ;b${!WP53eY2dua86~JOqdLfBO4-_*)hYBK_E8N zX9><(%42O#6)<&tNE` zk)@?$&i4mfg3Z_S5k_yXqmzvCc_=korE!FZh+lp-}NQR*@;HyTk52H`Jw#Mpq!i@T(%e)}yCx@XJv2DyYVrEhJFdsFV=CuSv0O5= zGGT`#Azh|9bhj?r{pc{fFs%ZQE00Ob875?ox%yj^t8q$#zoD&KH8`c4$cT8ofl;12 zHDzUeAdZiGVvhi873?h6U%CM)uWu0D%L>>ZJnobUD36DEi+J(-f4yPa{Arxtw4>BM zjk#*)4x8zWJ2?L&J71^GW6i-()iC%0y;VVCa>usH+2cHWnR}oWZ$hjNPUYJrzGx~+ zzSnJOYwO+?_oSAB>+JGt*X}eDJfTJ!jkSleN`Wscw>% zNtJ1JL8mTibV{-$R!mk=y>XRmw+-eS*E=cOSuNMrfblv1UDcfu%R4*eaQ3c?h_iq9sT0QB&Iu@FkR z^IQfdQy`a1{cR1HtB3&SXDkT@<)+WKUmDB2BO%Bw96>ub}(oz2>z z20wX>lVohSk&`^#TCr7{$a8HKEf1YbaWKJNQLyT}adHd4td5We$A}Fl>up6l?@!KO z0VbJi2qyKr5LHt%N$z{R(Qd}LXQZacm`)>`XBz{rs)EGo8BC3%u{4)L*)9&XmrcW3 zl3^{52B%|W3VC$Y>`v=)CXA++jK4=I;5qDt-GW^ITXhSyepC7xT4{~NIW zx5NNGwmSp5vOQR(sx}s$%iB8P{6_u_5Vq$Ett zi;COZ8}9cFWeD2|7jjr0@7|7~*<=`(Lq_D_3*fGq7%l8>ybV%vd{&+xq^DQsOjV(` zLGK=G{bcGJ+R^Zle@=ug`y#XJAg;K1zMwzOXBnT+BbJrIYOuXdG}8nk!Q&g+uJrE_ zs7~m~Cwgf!g?h|%M%bP!jKPXpveu}B@O#D!de3AjS+vl?Xa4fD19$QuLCpFf5_(z%}cY!jy&jrmQ6}XFT z7un=a{74R~sDmQNF~kfpGc%3HJa)`6 zGcz+|j4?AaGc&UtGutsUJ7$QPot^jn{$DGtR=RhkEA3vbnx?yFdioTcI`y2VP9bWa zy07$$G-?={1<$l|Fs>;#=zTXcY`H^V7cWw^!vypdO*Y|CmyMX;4VGp0wq0yH{Bg_H zLg8eSNK}{RcN&>rr|)p5?xAByv~?uLIA@zQgW5J(9bqaO=3(3YwWg789Wv$8j)_76 zIA*e1qoJATFCn7DTdXU1R9=lLPxC?2EQQflmMW{`PM}iI>W@;Q`dXfC(I^U6SzOQ3 z>}mU{+8O3;`@ew1V!yExn97ZzbRw6BDoxu3fpJ2ibYvkY5VYJ4S2?=ZEB6cj0<-YO z!?BQDycgD)8+oPiJTpmJBlVnC+CrEejKP5;6QWRjBe)0Cgi{(=ds&i!kN}1!Q0$A&k9z;N) z|Al+^q`WH05HvktC!`zIFABy;t=Ne;m+ao)Yt}Wg>!f09p^?K@!#CB{#pL2OCvV7BUf+m$KU&iOnZg@3a)GPz8ak4V(Rit6jSTe17GOF6Hf@{J=oQ(lNEQ+QZHC`hD9JLKg1_fqo%6g_84WwXkA+vm)*}6v`&Kh|slPGmoz13wFQm=#~^2X8%Gc zb5SL{nk{JjGLZ%i4&wfKUo`129@q+WA@E-aC*5@RT~iO(!m6f5^XsWkb@}m>_FA&o%r(Mie)gM8hROE~Q}L|d zmh0wVy}_eQ8uw$K#CjNa z7L;%89;bF}p1Mp{+od^e2|bhlK>^adlx1+FlngNt4stH?54;$(o=Jz0o*Ibg7Nhlylii5_uJd;V4x(zUoM>j8pQUu7Hhj=v6=!p~rpv`0YrctSBY% zZ_bgHq7a!V&RNQl{$5#W6C3Vk(y*|*F;M-WL=xF9dZV&b?FZ-u!r_blIE905svFe>_@yRkCBmJP%Y- zG|sQlo2X`0O{|1at%ImY^m@ZWLzgG-Z^}3Bal_y7mK$CW!Nba@qx~*o_Xh+YTO$8E zqkF*_-RhE+oG)15%6ip15&r9*zB84K)t>C6fkeEf{2@+n`kPiCw|&!KgJx3 zn@r?f1R67`qmyvtrF#>nFpxGKrMTj+@?5jw`ByFQ70HGg8njw201A}l`Y=JkJ>Qs+ zlFm9VtjNDT;%g&VP<6|%wm>Pi?!=d^=0ll!xd7Ouz6Bu4Z-VWTyN|Sc_N&YEACWWi zbQM;n0#U*dU+xp5G)#MP*~FyO9H5k}K^SGVdCltI&MRa>R>g{~n*qyT{z6PTZc*BE z?suJ7t?hShWiCxbr=D@pQHs~r->{e$x(c-TsrK}wz0OXdG+0tdw*5GTE@KHy7-QIN z7Y{;bjK)XL9~q4mDvU&~3(6@TDzhylf3$62N3d5&3c_AUleQv%ltSvGe2)^h+4T0e zFhQpD9g2J1UjngN_m9}8*YU2vWgIdH16X8n3yXRFkWBu1K#G9E_3KfG01ABAsZvv} z{Guaj$_YK#rPSmlZ8&vYIvNy{lNZBlzqMt$KE}GmSCrMm`3tcbSuHfX?|jCHqp2I- zXH>(Hsk!h)B{g=qsmO|Nd6pnF3b4!z=u%V?eFq=oMRES|v3?Dx}51ba${yA1k8P5^XqT_lvmS$o~nO$p$ zL4`rY(dRg4Ss0EyvPe`Iq?;)%9VHauZ7&v+&OQe7<}qt#(O%UT6F|v72|p^tJr5cf zm)12@Yvsj=uB4wnc~H9@T;*@DAkh}A*w&X3Op?Zy`AGQwSu!0CpPltqi?M$V0GM^0 z@vuxz^pb5!$aSKg9I#D?X~MVPAj?ZaCk~Q)_ry@JV*N(W8WfZ-<^0ZlP9OzvUYS1^ z^`oHp$gDdFRuS(W+waRX)#v*>L71xOp1om)7SkPCPfhOWaZ!~Y`OS)265;4ZCc(SY z(!wgji+L|B62ngVQ&b6W$~)udu_|e!t&dJ}Usavc=lW$K6h;I*LfA&rYS%@yGm9(2 zsG&J#T0@S3xRu7%xJXLU=o=xSHfw?(tvQ2~!(uOo^4X-nr+x0#HHPx@D9q;OAlWQA zdPnCjFBj|Yq?Mz~pH;7FrtC7dq>nR-#6MIi+3tUlfIb^0q7=wFUw#gpbHu&Eej6my zJx!$-b)q6bqacW=aeRg82q7?HH)R^LG(Bx1z|Lhv_{6wskye{lR#RPSA^=A7%M=^d zkEoiPVf!gw*K&Sm-BH0ob?px(od5K(`Ka{qVS6Y|!-~Er|LodmY8I&FGZ1}>TWkZVm&yj;g1+*2TaaR1ONB4u%Cyu68_22fApJc=8or?WV@PMOCL{cF{Ul+kUYn!f zRWcE`MD`5U?Hruz9e{?l|PWQv_bz9DBy|i+|QnLx+Ad8+M!y-JFu z8|^7P|G!$W_LB788NsE=Sj#MSVs{u6z&(a8|fS=nfMB!)(K6jdKCO$ zb}h8zH6$*2%S5YP{@e^{I^at$6 zv@0mo$Q$UcD5c~YMr~M}kgH{>M0NLb*4z_>iv(&jTr(GYD+a@o%M%hz$Qv4xfB>>4VXIjJ`i>l46s06ADzt^se6$(aKQmeyLYRCVvnm4-}583t39 zU#jo>33n;9rVcQJMBf4hUAl^S#o7h)VdHGe&C94|y8H1#J~5wLPMZ_bmCZ@KDm2R% zM=TFsOQ0DZr5&|x&C8jl*3d`yoJC0|le>!8%V8{(9aCgZHTGlL+02U6zBT!bY}6Va zE9*p^#hKJo4YphLM^q@L-PJ@#YA=%{CwXR@;{QHwPQ2Qhuqz;%ltD#rUIMx@V=Du= zEX_>PQt0HN6xkkXIJ;A2(gGpVHBCZH#QcfI5xEu~j|T{tLzl_8kBqAX(zK=Bdc>!> z*1#q|{8skRPc7muOO~Vt^H=2PUd9PSS|+a+9eee5JeSeTOJh1I+s~YuiWu4n#bi28 z_$epFHYEh>VM}4+o*6midbjMBp$dCbqnqOdk`er-dT2B=2z}K)#n8O_Zl;!CkiOA#9Y#{u-Uz%tQ>o z*DTWOFGRqa=v)e=l}h?m6_Il(xfsv+Ab`dXr%Fq^*P`hr@XYe^%*O#c#Wf)dS;YYZ zMX0_TADydpg&5p?YMnojE8;gPUdO*?BuHGHn~8Qye#+xG7AtgIsvbmXU-V8k-iA0U zqHIPtEa1V*DM1=6A#tt7y;jO3UrEDAEYBd5BnRNZm}?fMw0g2(h!(QP+%qM)oL;S$ z{#%{kOb13vI)X&MTS`qdI7*b!!bm^TqH8}Y&7-$+lJWBiDo)rEkLnZ?=Vc4!l-M_X zR&q~I?YgF5h_ZB%tOzttAvpG5j zuSsGxCD{FTlo)l~FKVYn{(u1k%V>-0Y{*@^dryt+Scw#}h%;M!y4YJ49nc80zm5<1 z?6HwLD|mSq_(SN+Xc;9#W+=;JrES_A>pQ6Q*Qn|Am1`0fN6q7QFK3rTvQ*N%TJD?~ zV~b^FGSh>JnH6JFc<53-mjn!ZMe{G2Bc$O`oU>ts%y~Qel9LOZ=WJrqGFh)4renlR zJHx)I=F3+7svGjQ;Py)Vt+xEFIW2A0$;SB4DY>;`kf1>I^PJJwWY7n^bETOYuk17} zdX>X|GRd3czxt%_Tp2q>o8h+Rl`Op64$T6VTBYTp|C}E@$-qC?R6=g1_ot=c9=7AyZFw;+*UID%ldhzolw?N~j=yJ6 zttwDHYDcXen!HB{i@6qSo?2_p&lxD3SC_<7L{!qUElzCzj5|S#+Y~I_H_S zxX8X#3Iax1k9@0Sv*r^sZYoQX)6$qd|6s*nQMzkMQuo88&}&BzF@R2 z9qOIro8UT=eAC;s=Aff0A9S_tNe~xpD&+Z5N@93>uV}_x^*cE`>)WD(VOB*5sTy#f z*$JmmGQoYtBz<11w_dDEb1q91sV2vJu76cVymW(y?0zh6f7)18bef9l-ZWYC{fqZ- zDQ_gfC*!LjAN1nxuLF|yZBlwIM$v6yye&l9a`CMh7VWB(T@k~suA43znvXr2v`YPU zDNNRlSaalC`V-->CY4C^qelmB%BQ7E;#D*0mX$Z0!?G6z1K(o)+|WcQeDymcQ(aWlI;A9bUyQG`fM7Og(ljO zba^_;Wt&<4;83P@9^MjArKAuQ~x?LUNDs|>6BZK zwUS0EY$hjp`e$YsN2F!LWc`6eyoe0FP-m(&v(s2ApE4C-vSQ^eo&x68Z6w(&^ZTePf}SD`FAr zC|YZRj-auGyu-%)0W_7qZZ1^GTFZDf?7ZsUoz^9f z_Z8>S+u^(GVq7usnM;}(9(_9g<3?xykZ0AKQ1xc_K{-;2Rl#6imyow2>}eOz3iUC5 zpCHST{FET~DN&AeNuJI6k+DR{bt9^N`~7r$&9S;>x*?q9t`*i*kv$~auBH0k&a$j% z%pzTOO38i5>arg*TsGLN?tK0uG}^M*x3Y{$l!{xiN{_Y+vD1(wl$xOsiHB;TY0L75i9yoo7(;%R(B?wZ%qz(pon_LE`5mqKe5IM4K7O=^bDMeJdJMj z!V8j)glAhvX(ezAG#O>j*44+@R|P(xg9i?TQ1S*}8oEy`OGG@IXmLy{6b~t5@;z2k z*@q&s@hFCs3OY;J=f(Qrj=A#MEJF@2dKqokK*(t|%`)@tXxe<2#>!J&b{ z#0wcPbpj{0Y3h4Yc$_i*qqvqj+pTGt#ucBLg)e0Nq+`BIJ%kxj zqpN_6(a|H`k=4@0zYzbBlm*9rN8-w^3}`b8#>(3JCM)R1($OUjz?@^Z#d1MmGW@W= zuriBWqFrJo4?!rS=JOye=p(9PxmX_(icjQ|^9o5*t;JM47_sBc^pWH8`t$(QNkIz$ zwzS1G_2Ek~1kZG{ZYzZapPa=^AB$Upl()ta<8F%!bM11~Kcakl(qEM)VDssP5@aHN zZMa%$813;tAfw7P#$van=@jUc-b-2MsJAC8|E$O%1IMZdUhnG>^X#}@g&6S<1l6k> ze_0tp>6h`i8nHyuT_s!kHDKG&+-8No%Qz_MjKzH6t7yq$_EVBbqiX>SNQr#7yMKPWH_TnehyvBAj@h zxUP%?t39nd3m&w7wI~%0nI7wxV8oYz(_9P3?ZGq9H%*M<@W(kcFpHmXcjpN8e|XV< zP7Z~mCB;(zP@wi5%%A~$$TQLgR@tEl3HN}RjgMo-Xg#tW9v3O$^beyRcPM?(bzjd3 zhysmATKoinb*{FdY%4QwIkZ2;8NNVvh!f0jy+unFddAZj!oWD{(%Hwp7y@QP34?ZX8qt z-E0Y0>Cz3(E|@vKrvNc1MYEE%w+GYC)#+IQI-|;!cDV@~{1M@u<`^<~_%fq^Ax8Ht zm{-9iYW*OLJZ?PlmgGN~`xk;H_1AnEXDBXpC>m#&BMJtg33ucxrbt*Q?jETu8a4P5 zIn0U_B&CV|eB+C!n+ywkm{KDn=QL4O=O|jtUkHBho$M{plh-_sMi!~8TZgk!TB=vn zI=|D=&oO#*ZR!SK!Rh zh6HVUqGs0wSCoc*6au^YSS?A5Y$H|CV`*W^P7$*S{%I=M<^<&@vu(SbA>^bY{FUp( z&xT^`{*=G7j8dfq5Oop?)udAfVkN>Q4MB}M>c@XkX}Oazo=_kznKCuNew^ zARxY*Hw+Ap$^*xGn*7G% zeJnQrTkRG(s!Dy+`Hj;io6p2sIsvSiX9&VBDaf)~%S#{A&cIu<3fz}XjGHXr9w1|4*DQKrHJjGD&c5E# zQoAn2m0Q%xYZ>w^(&&jC>ehcNXj&Z*SC~BAS?S!C6dF6jSWfUG`L|A^;e?J&`cq5n zxM);!tC0i1I8i2@jv37MxvVCi;^7>jJ{v{ganZB}cEfIt63xJalfbvmfQ=VF92;2X zvy>h8NW%W)fA0q#Lhu9KW0jh4qdA=nsq8O=Bo-Cvlz2;y?En|DJqCvo!bLa-T`_vu z08?=R)9kkL;dNg0o>I~`$47!i`lwsGV+-5irdg?kZkcSlFo!U*P(o|Z1UZ_6Y{!%Z zGi6kH*D)p3jpCF!4zR0z?|EJoW^>zcA50$p3(@+|JAp?7eV3`Dtw+v)g1?bgMgqux-nXu){vph$PwQm?b{# zuK>-W8il~p%m*s50t5WGtK1*_Z;(ejsDcpRpP*-c68l2j{(<`+U%ZBawB)a%#7IDE z=ByrNupGMnT?$w7EkTPP^*`;rJVL91vum^ha42&B@rbng(-ycwV=os zFa-62$M4G1U4!0Z)1!=WhuQwWy2IOf&h7m`O@DWbSk@;z0fFQ%t@v_G@($zy zkG*}jAlL5bjUR~|TDiYDX07~^=EcfClx0DSwO;uo5-Lf>%A&5VH%d3N#f6*7cxi9) zTU}n-6os#f?YaV?frp;IK#kJs1(sHKvP^p6Qd9;{0+Agw@8=%oS1a4X8OWTE8&n*h z=`Y*xlmw=|axuTfz;*Z>(xGOzfv)MX^dCOAv6r?i9aONJrPS(FEju}D_+K@~(Lkw` zJs?liU82)Fs)HUrC0X|7kgK(9a8Zv;7DUH@o|2WCE)kNNNIEs&xgM!MtqYV)>x+P8 z@l;tvg`T;4i190Ja()LDW`7Zn3z@!q!LnLKI1)1~fa#t`dGKlERjbBaI4GRX=vCdY zsMf-!MTHuGtGFvv`AW7iolx-(EHxW+G7q3CpWW7fXe6FkXHkM{DF41SEv8DE++oc` zFzOh_tq`OxXkS7Vwb0GizNaT%lr%B1b|efz(+N765uGqxT(Q}*C8V5D2=ds9t=RXX+Qvs#` zr(HDE+?V-7z-;E`Jlp0ew6XCczYa7abaEK}_b#X2(9;m1ymKeGyJw<7j%WX`K)7snb=I06P+mz)arrK$3{j8Q=9$Vl4eJ%%UpzQx6 z(7#gs-|XTge*iG=Ysb%EdH6@;k)#|GXE!~zzL||`wR~UjrEm0tX>p~ z5lt)r5+V`?O$-f1!PO15E$i_oDH=Qs6ePrtum6`1JU={Bfh(Npm5#s`an9~QnB2mh z=bYBBpe|FvA5kT?X}6i)dohFp3(z-g_*6NG=v}3#3y-c(zIy`m__d-|-RKo};+3=i zPd~n+N3y-EgVo3M_{JqNaLwdR&n24BE5jujpqE&5x8KRcD=&MNO;db7oiP7$B&lH5 z6JA|l-88RTlCjCLFnb12m#CUfER#zqammSD1gfm19hR`N)f1Xj8RCkg2mQaXL-Bkg z0X+VN0IMNyddZ7-XR+!!kM|<%pg(W|J27t0faI9)ZQZbo{-!D=1l3)^!>a<)Tyu-ZDL=&; zlO7qNF+iqM!W(+k{{bRtX<|Tl3oz8spBl|Y&{PdCOF%>MKeM7|gEoff)9-`kvMH=*jKc(%dwO;2S@mV^Q z_&d2q)Q+PnEVHiy6e<)P8>$)m_pp(h!0wpwCZvr|nu8pJ}C$9RSCSL7s{FHZR-Vba)$|R2J&4pu|7P z9NaLiGoJ&u%kg?WmiZ?3rRO}9aL~+7!+g(hgJ}ofr^;L~t1S77W!luapPhIy)OhD> zb4OGNVbtc+1pDhs$`d>^i3O`XzffBYwFBY|8boPp3sGQ>06H|vz$QU_D47IIuqqtY zkNuTab*~mWXKo#rjSBmhG zjzc}8CZ<@rlJ+aYGZm_?VtM5lt9=-s@!tldeUh|tka~TeU@PPZFET-eJ=4%30y`|s5WB8A?y|TC7LALQe4+G3Kzpy>eE-F zd<14D1HfjwVyglHZ9_N09P3_~)4+?=@Kn?+!vm&hGS=bkb`^^K>OJd$=yXWLz=b_l zZQ+RM&1&b_cOy~`6;PR^n02u@9qj2@5&wVRG5#~StGdFX@iaxpTUh}EKU7uU;QQNs z#%+FUX4~eS1XaHWGhVpPz$d0EFh{{BJLNk8NM-)}Ih_F?ozuzmTpWtvgN_)jQq(_aW@1}_wzG8&AyF2LaR zK9E%HSTR|2Yp=e^WZZglJBIz>4S@S%CZk*0%M;YP+wY@#NfA{W`eB^5Zyy97fF%El zef2Cii-J`#z-}OD@cx^}U5}>L3{+(_4yk`l_o=mxpzR3u^Lz|9iV<{^!~Qxtx1%aG zfXf3KQ=m-3omPHcLEAwm-HjCzlrY3;L$dKz@-r9S>&e0%%ix`_r7^*}2_7#~ep(Rv>-l4&(ukD|&4` z;gN>KH3X6_nn&olmyOO)X8_+}xFKJikQ@TdNyZ6#8K3g@t)P(JH)^wH5GJw+wNg&; zN{X$0DA;ZyelrmH3+>W>*TrCk1p=JIGCO)Vv8hdLqmJqIAST4}O4>nLS;ZkYHdhZ4SxCR`&wk_Vo{k#h>8rGGfh(=a3(?|72tFuZUrcv3Tr?{Df3t|Zv8oIaGV*@#bf278g6CR(~^83haL&v zR;I{pHbrSicHm)}n*A zQuaa{Ov%Dt2@7C>AtYQ5jH7SVujDnf1N0^dDUK1jlW)#ZNQCe!CN|}mY7a|U z{@FJRjjJb>)AKpp*%ZY^1+*=d=kP)peMTNBE;)(f$@_OnUSc-GR&mrVaglz&O@E+^ z6~HR~T!;0LM$+;aL5XV2I+z0OFhkKw?dm6VWmsOb{Gt<&6odgPMIx`cq^e}71C#Dw zh!6m;a5TwZ2(_R7yYnrb<{wmL!~Nzk^`9iKX(;~9adTw7(iht-r9HMk`@_5`638*L zd^=PU7y|5Z2)~CRO8FhAzr+bk+6g-!`i5Rnu%snxBon}nya9mnp7XC9>=Ey+kwiM? zJ{9PIo(VkvJ%2Cc8865F3sBeW}AUboEqE0@v@#)Lsk`@!xAoKD#@gx zU!5RcP?PfA)F6i8-UXt87UGB=y;rjj7_wU+k(eHd#1h>e+4HZMo z>PqCmfo&*pvy3jMdq%fZ$FbNj)8Nr&uG zfd2&%Shb;DzEGyt=6v^oP7}20^llr5P8-?ZCUIIe^LphwJR*IN8YLe30aAOVPe$zY zhDbdNmvp@-n@7s1hPevJ!z75~X5_8`JXd?%@|)aem{b+cxidir z1B7I+C|~bI`kroMP%J_EZ`HXU)E+aBSwQO8@=tqvI1fdS_Z~;0xpVIE7e1ZNsrU)w za)0Vx>2s>jRUDnuB6S}vw3eZVnhP_RikLH>k}23<=_QH|$OI@rFMi*%f9D~WRZ6EQ zuX#~3&V%Hn(EC`C__WS8Km)nZ{p8WQF{-gqj;ZbWOnl0hvEaNv8^zvfHAueJ!vobt z=lr4|K53qvUi3q?#~ld#!A-1GVtSwgJ5@C=JAgef3iir$L>!fx6;I}*(=3{| z#;#o@xJ~kBYM)I+9)+S(ty`i~pY|}bXjmuXrf~JC`q|;o^7U~H;#<~S{9sSD;gEC&UR$J$4>(0HJd-Sfk;pMT%1OB z*kpSW_J%XwVw=iG`@6XfUr|w>zI! zI5NbjHT1YTxHf=3&aIBxZ=uMw*J_OmQ#LC+C+-Wkm@HdDz3SR}FW!jjO|qTjQa?Ux9)ciPE;b(Rhko?E=f>P_7o~ z3j!W?mwzFGXpF|!7ffwW*<0@Mv^}G$yT#SE3K{GblUvLFr*BYrM)8Q_kyL;&1^g%g-bwAVrw3eZ!+7$ALQ$2!C)1V7Gzb*m5!?x*&<5k9Z(j(!NtKgC~ z13)cNrN`;_?f`&#+yi|JsY{{Nk?qN&3Or9`*a&*4BDf2=aGIi-fH}%j5@!YA|0(^{)9$ToHL12 zaYqriiq6nw{-hcQ`6S+Jde4pcE_{IPim-`Gx6UjEU%&XNM)f*@ygG*b3k4+0}ewa+(jb-Ye27bE9 zZRhdE>bKkFC)JXU>)a)Wxv^EYsM*Zp|H)(a7}vFONAY}-S5?*fUd7hbCY$Mm zg<{;{pKnwTKw5VgnjZjdj|U1{5Bmm+L_;2?H885^!~#LoK;7~#@cor@a6!{2hlYp! zh=XplDhFc9bh`ljZ}x9AF#?Ny)I{GFDh(p3w3fXv-K@u+MXcD_zSyyHFIcWacW~xu zdfvhX`P^x+N28S9#dT)Jz*v9P<07CgxSCraW&%%v=u@IvV@{V<^6lf-c5~GVC+_8% zuEOZGG_ofX-JU#;>EX_IeluiSsjOVB2L4b#>`v9v|JiE;rw6O0Gc}| zf09>hn+$Mp(dX)cDRxh4R1exsyWu+Uq=98ZFlas`PTP+BPD3@jgBT9vzktRMHd&J# z=Rm{OABIqOjWk5hKMNH>2cbXo>S;ac(I`kFhN~BsYseMvfgIvBe zi|~9km9!$L6B*eosYA}5VEnS`^&8<0Mk*%%0unyjp;az#uYLgV;2GE@?spGFU~tNbJdyk zYrx_0nUVXg z-#vRgYN;%F((1pcDvvQ1?+~nBV48iPw0iuR^uMOm{!iX$^Mo;)^pZdA0{>BeDbFVG z{u!^fc67?eSKPYt!7h*nRxgPAWd&A?sz8srSh#M07@)5opK*5s(903Gp8wAm&;C7g z3HBBLcZ>FwbWHyfI`b+TZ@OX<1gkU0RCePFt3=K2Op94>pdazYsjd*dOj31y_enbDM96N!rgk z|2%VE-k_x;l8!TtGywj8$@;G!>izL_1RAI6≠L@9R@s%pkx9*OvZ2YWAZ?u=0=w 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 literal 64958 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