We are currently solving the following issue within our repository. Here is the issue text:
--- BEGIN ISSUE ---
Catch time zone tzlocal error
### Describe the problem you are having

Im always seeing this message, unfortunately i did exactly what this message is tellimg me.
I'm currently using beta5, but also tested with stable.


The docker-host (debian 12) also uses this timezone.  
```
timedatectl | grep "Time zone"
Time zone: Europe/Vienna (CET, +0100)
```

I configured it in the docker-compose. I also tried a different City, with the same result. 
I also tried to remove this. What am i missing?
```
    environment:
      - TZ="Europe/Vienna"
#      - TZ="Europe/Berlin" 

```

### Version

beta5

### Frigate config file

```yaml
empty default config!
```


### Relevant log output

```shell
zoneinfo._common.ZoneInfoNotFoundError: 'tzlocal() does not support non-zoneinfo timezones like "Europe/Vienna". \nPlease use a timezone in the form of Continent/City'
```


### FFprobe output from your camera

```shell
-
```


### Frigate stats

```json
-
```


### Operating system

Debian

### Install method

Docker Compose

### Coral version

Other

### Network connection

Wired

### Camera make and model

-

### Any other information that may be helpful

-

--- END ISSUE ---

Below are some code segments, each from a relevant file. One or more of these files may contain bugs.
--- BEGIN FILES ---
Path: `frigate/util/builtin.py`
Content:
```
1 """Utilities for builtin types manipulation."""
2 
3 import copy
4 import datetime
5 import logging
6 import re
7 import shlex
8 import urllib.parse
9 from collections import Counter
10 from collections.abc import Mapping
11 from pathlib import Path
12 from typing import Any, Tuple
13 
14 import numpy as np
15 import pytz
16 import yaml
17 from ruamel.yaml import YAML
18 from tzlocal import get_localzone
19 
20 from frigate.const import REGEX_HTTP_CAMERA_USER_PASS, REGEX_RTSP_CAMERA_USER_PASS
21 
22 logger = logging.getLogger(__name__)
23 
24 
25 class EventsPerSecond:
26     def __init__(self, max_events=1000, last_n_seconds=10):
27         self._start = None
28         self._max_events = max_events
29         self._last_n_seconds = last_n_seconds
30         self._timestamps = []
31 
32     def start(self):
33         self._start = datetime.datetime.now().timestamp()
34 
35     def update(self):
36         now = datetime.datetime.now().timestamp()
37         if self._start is None:
38             self._start = now
39         self._timestamps.append(now)
40         # truncate the list when it goes 100 over the max_size
41         if len(self._timestamps) > self._max_events + 100:
42             self._timestamps = self._timestamps[(1 - self._max_events) :]
43         self.expire_timestamps(now)
44 
45     def eps(self):
46         now = datetime.datetime.now().timestamp()
47         if self._start is None:
48             self._start = now
49         # compute the (approximate) events in the last n seconds
50         self.expire_timestamps(now)
51         seconds = min(now - self._start, self._last_n_seconds)
52         # avoid divide by zero
53         if seconds == 0:
54             seconds = 1
55         return len(self._timestamps) / seconds
56 
57     # remove aged out timestamps
58     def expire_timestamps(self, now):
59         threshold = now - self._last_n_seconds
60         while self._timestamps and self._timestamps[0] < threshold:
61             del self._timestamps[0]
62 
63 
64 def deep_merge(dct1: dict, dct2: dict, override=False, merge_lists=False) -> dict:
65     """
66     :param dct1: First dict to merge
67     :param dct2: Second dict to merge
68     :param override: if same key exists in both dictionaries, should override? otherwise ignore. (default=True)
69     :return: The merge dictionary
70     """
71     merged = copy.deepcopy(dct1)
72     for k, v2 in dct2.items():
73         if k in merged:
74             v1 = merged[k]
75             if isinstance(v1, dict) and isinstance(v2, Mapping):
76                 merged[k] = deep_merge(v1, v2, override)
77             elif isinstance(v1, list) and isinstance(v2, list):
78                 if merge_lists:
79                     merged[k] = v1 + v2
80             else:
81                 if override:
82                     merged[k] = copy.deepcopy(v2)
83         else:
84             merged[k] = copy.deepcopy(v2)
85     return merged
86 
87 
88 def load_config_with_no_duplicates(raw_config) -> dict:
89     """Get config ensuring duplicate keys are not allowed."""
90 
91     # https://stackoverflow.com/a/71751051
92     # important to use SafeLoader here to avoid RCE
93     class PreserveDuplicatesLoader(yaml.loader.SafeLoader):
94         pass
95 
96     def map_constructor(loader, node, deep=False):
97         keys = [loader.construct_object(node, deep=deep) for node, _ in node.value]
98         vals = [loader.construct_object(node, deep=deep) for _, node in node.value]
99         key_count = Counter(keys)
100         data = {}
101         for key, val in zip(keys, vals):
102             if key_count[key] > 1:
103                 raise ValueError(
104                     f"Config input {key} is defined multiple times for the same field, this is not allowed."
105                 )
106             else:
107                 data[key] = val
108         return data
109 
110     PreserveDuplicatesLoader.add_constructor(
111         yaml.resolver.BaseResolver.DEFAULT_MAPPING_TAG, map_constructor
112     )
113     return yaml.load(raw_config, PreserveDuplicatesLoader)
114 
115 
116 def clean_camera_user_pass(line: str) -> str:
117     """Removes user and password from line."""
118     rtsp_cleaned = re.sub(REGEX_RTSP_CAMERA_USER_PASS, "://*:*@", line)
119     return re.sub(REGEX_HTTP_CAMERA_USER_PASS, "user=*&password=*", rtsp_cleaned)
120 
121 
122 def escape_special_characters(path: str) -> str:
123     """Cleans reserved characters to encodings for ffmpeg."""
124     try:
125         found = re.search(REGEX_RTSP_CAMERA_USER_PASS, path).group(0)[3:-1]
126         pw = found[(found.index(":") + 1) :]
127         return path.replace(pw, urllib.parse.quote_plus(pw))
128     except AttributeError:
129         # path does not have user:pass
130         return path
131 
132 
133 def get_ffmpeg_arg_list(arg: Any) -> list:
134     """Use arg if list or convert to list format."""
135     return arg if isinstance(arg, list) else shlex.split(arg)
136 
137 
138 def load_labels(path, encoding="utf-8", prefill=91):
139     """Loads labels from file (with or without index numbers).
140     Args:
141       path: path to label file.
142       encoding: label file encoding.
143     Returns:
144       Dictionary mapping indices to labels.
145     """
146     with open(path, "r", encoding=encoding) as f:
147         labels = {index: "unknown" for index in range(prefill)}
148         lines = f.readlines()
149         if not lines:
150             return {}
151 
152         if lines[0].split(" ", maxsplit=1)[0].isdigit():
153             pairs = [line.split(" ", maxsplit=1) for line in lines]
154             labels.update({int(index): label.strip() for index, label in pairs})
155         else:
156             labels.update({index: line.strip() for index, line in enumerate(lines)})
157         return labels
158 
159 
160 def get_tz_modifiers(tz_name: str) -> Tuple[str, str, int]:
161     seconds_offset = (
162         datetime.datetime.now(pytz.timezone(tz_name)).utcoffset().total_seconds()
163     )
164     hours_offset = int(seconds_offset / 60 / 60)
165     minutes_offset = int(seconds_offset / 60 - hours_offset * 60)
166     hour_modifier = f"{hours_offset} hour"
167     minute_modifier = f"{minutes_offset} minute"
168     return hour_modifier, minute_modifier, seconds_offset
169 
170 
171 def to_relative_box(
172     width: int, height: int, box: Tuple[int, int, int, int]
173 ) -> Tuple[int, int, int, int]:
174     return (
175         box[0] / width,  # x
176         box[1] / height,  # y
177         (box[2] - box[0]) / width,  # w
178         (box[3] - box[1]) / height,  # h
179     )
180 
181 
182 def create_mask(frame_shape, mask):
183     mask_img = np.zeros(frame_shape, np.uint8)
184     mask_img[:] = 255
185 
186 
187 def update_yaml_from_url(file_path, url):
188     parsed_url = urllib.parse.urlparse(url)
189     query_string = urllib.parse.parse_qs(parsed_url.query, keep_blank_values=True)
190 
191     for key_path_str, new_value_list in query_string.items():
192         key_path = key_path_str.split(".")
193         for i in range(len(key_path)):
194             try:
195                 index = int(key_path[i])
196                 key_path[i] = (key_path[i - 1], index)
197                 key_path.pop(i - 1)
198             except ValueError:
199                 pass
200         new_value = new_value_list[0]
201         update_yaml_file(file_path, key_path, new_value)
202 
203 
204 def update_yaml_file(file_path, key_path, new_value):
205     yaml = YAML()
206     with open(file_path, "r") as f:
207         data = yaml.load(f)
208 
209     data = update_yaml(data, key_path, new_value)
210 
211     with open(file_path, "w") as f:
212         yaml.dump(data, f)
213 
214 
215 def update_yaml(data, key_path, new_value):
216     temp = data
217     for key in key_path[:-1]:
218         if isinstance(key, tuple):
219             if key[0] not in temp:
220                 temp[key[0]] = [{}] * max(1, key[1] + 1)
221             elif len(temp[key[0]]) <= key[1]:
222                 temp[key[0]] += [{}] * (key[1] - len(temp[key[0]]) + 1)
223             temp = temp[key[0]][key[1]]
224         else:
225             if key not in temp:
226                 temp[key] = {}
227             temp = temp[key]
228 
229     last_key = key_path[-1]
230     if new_value == "":
231         if isinstance(last_key, tuple):
232             del temp[last_key[0]][last_key[1]]
233         else:
234             del temp[last_key]
235     else:
236         if isinstance(last_key, tuple):
237             if last_key[0] not in temp:
238                 temp[last_key[0]] = [{}] * max(1, last_key[1] + 1)
239             elif len(temp[last_key[0]]) <= last_key[1]:
240                 temp[last_key[0]] += [{}] * (last_key[1] - len(temp[last_key[0]]) + 1)
241             temp[last_key[0]][last_key[1]] = new_value
242         else:
243             if (
244                 last_key in temp
245                 and isinstance(temp[last_key], dict)
246                 and isinstance(new_value, dict)
247             ):
248                 temp[last_key].update(new_value)
249             else:
250                 temp[last_key] = new_value
251 
252     return data
253 
254 
255 def find_by_key(dictionary, target_key):
256     if target_key in dictionary:
257         return dictionary[target_key]
258     else:
259         for value in dictionary.values():
260             if isinstance(value, dict):
261                 result = find_by_key(value, target_key)
262                 if result is not None:
263                     return result
264     return None
265 
266 
267 def get_tomorrow_at_time(hour: int) -> datetime.datetime:
268     """Returns the datetime of the following day at 2am."""
269     tomorrow = datetime.datetime.now(get_localzone()) + datetime.timedelta(days=1)
270     return tomorrow.replace(hour=hour, minute=0, second=0).astimezone(
271         datetime.timezone.utc
272     )
273 
274 
275 def clear_and_unlink(file: Path, missing_ok: bool = True) -> None:
276     """clear file then unlink to avoid space retained by file descriptors."""
277     if not missing_ok and not file.exists():
278         raise FileNotFoundError()
279 
280     # empty contents of file before unlinking https://github.com/blakeblackshear/frigate/issues/4769
281     with open(file, "w"):
282         pass
283 
284     file.unlink(missing_ok=missing_ok)
285
```
--- END FILES ---

Please first localize the bug based on the issue statement, and then generate a patch according to the `git diff` format fenced by three backticks.

Here is an example:

```diff
diff --git a/examples/server_async.py b/examples/server_async.py
--- a/examples/server_async.py
+++ b/examples/server_async.py
@@ -313,4 +313,4 @@
 
 
 if __name__ == "__main__":
-    asyncio.run(run_async_server("."), debug=True)
+    asyncio.run(run_async_server(), debug=True)
diff --git a/examples/server_sync.py b/examples/server_sync.py
--- a/examples/server_sync.py
+++ b/examples/server_sync.py
@@ -313,5 +313,5 @@
 
 
 if __name__ == "__main__":
-    server = run_sync_server(".")
+    server = run_sync_server()
     server.shutdown()

```

diff --git a/frigate/util/builtin.py b/frigate/util/builtin.py
--- a/frigate/util/builtin.py
+++ b/frigate/util/builtin.py
@@ -16,6 +16,7 @@
 import yaml
 from ruamel.yaml import YAML
 from tzlocal import get_localzone
+from zoneinfo import ZoneInfoNotFoundError
 
 from frigate.const import REGEX_HTTP_CAMERA_USER_PASS, REGEX_RTSP_CAMERA_USER_PASS
 
@@ -266,7 +267,16 @@
 
 def get_tomorrow_at_time(hour: int) -> datetime.datetime:
     """Returns the datetime of the following day at 2am."""
-    tomorrow = datetime.datetime.now(get_localzone()) + datetime.timedelta(days=1)
+    try:
+        tomorrow = datetime.datetime.now(get_localzone()) + datetime.timedelta(days=1)
+    except ZoneInfoNotFoundError:
+        tomorrow = datetime.datetime.now(datetime.timezone.utc) + datetime.timedelta(
+            days=1
+        )
+        logger.warning(
+            "Using utc for maintenance due to missing or incorrect timezone set"
+        )
+
     return tomorrow.replace(hour=hour, minute=0, second=0).astimezone(
         datetime.timezone.utc
     )

{"golden_diff": "diff --git a/frigate/util/builtin.py b/frigate/util/builtin.py\n--- a/frigate/util/builtin.py\n+++ b/frigate/util/builtin.py\n@@ -16,6 +16,7 @@\n import yaml\n from ruamel.yaml import YAML\n from tzlocal import get_localzone\n+from zoneinfo import ZoneInfoNotFoundError\n \n from frigate.const import REGEX_HTTP_CAMERA_USER_PASS, REGEX_RTSP_CAMERA_USER_PASS\n \n@@ -266,7 +267,16 @@\n \n def get_tomorrow_at_time(hour: int) -> datetime.datetime:\n     \"\"\"Returns the datetime of the following day at 2am.\"\"\"\n-    tomorrow = datetime.datetime.now(get_localzone()) + datetime.timedelta(days=1)\n+    try:\n+        tomorrow = datetime.datetime.now(get_localzone()) + datetime.timedelta(days=1)\n+    except ZoneInfoNotFoundError:\n+        tomorrow = datetime.datetime.now(datetime.timezone.utc) + datetime.timedelta(\n+            days=1\n+        )\n+        logger.warning(\n+            \"Using utc for maintenance due to missing or incorrect timezone set\"\n+        )\n+\n     return tomorrow.replace(hour=hour, minute=0, second=0).astimezone(\n         datetime.timezone.utc\n     )\n", "issue": "Catch time zone tzlocal error\n### Describe the problem you are having\n\nIm always seeing this message, unfortunately i did exactly what this message is tellimg me.\r\nI'm currently using beta5, but also tested with stable.\r\n\r\n\r\nThe docker-host (debian 12) also uses this timezone.  \r\n```\r\ntimedatectl | grep \"Time zone\"\r\nTime zone: Europe/Vienna (CET, +0100)\r\n```\r\n\r\nI configured it in the docker-compose. I also tried a different City, with the same result. \r\nI also tried to remove this. What am i missing?\r\n```\r\n    environment:\r\n      - TZ=\"Europe/Vienna\"\r\n#      - TZ=\"Europe/Berlin\" \r\n\r\n```\n\n### Version\n\nbeta5\n\n### Frigate config file\n\n```yaml\nempty default config!\n```\n\n\n### Relevant log output\n\n```shell\nzoneinfo._common.ZoneInfoNotFoundError: 'tzlocal() does not support non-zoneinfo timezones like \"Europe/Vienna\". \\nPlease use a timezone in the form of Continent/City'\n```\n\n\n### FFprobe output from your camera\n\n```shell\n-\n```\n\n\n### Frigate stats\n\n```json\n-\n```\n\n\n### Operating system\n\nDebian\n\n### Install method\n\nDocker Compose\n\n### Coral version\n\nOther\n\n### Network connection\n\nWired\n\n### Camera make and model\n\n-\n\n### Any other information that may be helpful\n\n-\n", "before_files": [{"content": "\"\"\"Utilities for builtin types manipulation.\"\"\"\n\nimport copy\nimport datetime\nimport logging\nimport re\nimport shlex\nimport urllib.parse\nfrom collections import Counter\nfrom collections.abc import Mapping\nfrom pathlib import Path\nfrom typing import Any, Tuple\n\nimport numpy as np\nimport pytz\nimport yaml\nfrom ruamel.yaml import YAML\nfrom tzlocal import get_localzone\n\nfrom frigate.const import REGEX_HTTP_CAMERA_USER_PASS, REGEX_RTSP_CAMERA_USER_PASS\n\nlogger = logging.getLogger(__name__)\n\n\nclass EventsPerSecond:\n    def __init__(self, max_events=1000, last_n_seconds=10):\n        self._start = None\n        self._max_events = max_events\n        self._last_n_seconds = last_n_seconds\n        self._timestamps = []\n\n    def start(self):\n        self._start = datetime.datetime.now().timestamp()\n\n    def update(self):\n        now = datetime.datetime.now().timestamp()\n        if self._start is None:\n            self._start = now\n        self._timestamps.append(now)\n        # truncate the list when it goes 100 over the max_size\n        if len(self._timestamps) > self._max_events + 100:\n            self._timestamps = self._timestamps[(1 - self._max_events) :]\n        self.expire_timestamps(now)\n\n    def eps(self):\n        now = datetime.datetime.now().timestamp()\n        if self._start is None:\n            self._start = now\n        # compute the (approximate) events in the last n seconds\n        self.expire_timestamps(now)\n        seconds = min(now - self._start, self._last_n_seconds)\n        # avoid divide by zero\n        if seconds == 0:\n            seconds = 1\n        return len(self._timestamps) / seconds\n\n    # remove aged out timestamps\n    def expire_timestamps(self, now):\n        threshold = now - self._last_n_seconds\n        while self._timestamps and self._timestamps[0] < threshold:\n            del self._timestamps[0]\n\n\ndef deep_merge(dct1: dict, dct2: dict, override=False, merge_lists=False) -> dict:\n    \"\"\"\n    :param dct1: First dict to merge\n    :param dct2: Second dict to merge\n    :param override: if same key exists in both dictionaries, should override? otherwise ignore. (default=True)\n    :return: The merge dictionary\n    \"\"\"\n    merged = copy.deepcopy(dct1)\n    for k, v2 in dct2.items():\n        if k in merged:\n            v1 = merged[k]\n            if isinstance(v1, dict) and isinstance(v2, Mapping):\n                merged[k] = deep_merge(v1, v2, override)\n            elif isinstance(v1, list) and isinstance(v2, list):\n                if merge_lists:\n                    merged[k] = v1 + v2\n            else:\n                if override:\n                    merged[k] = copy.deepcopy(v2)\n        else:\n            merged[k] = copy.deepcopy(v2)\n    return merged\n\n\ndef load_config_with_no_duplicates(raw_config) -> dict:\n    \"\"\"Get config ensuring duplicate keys are not allowed.\"\"\"\n\n    # https://stackoverflow.com/a/71751051\n    # important to use SafeLoader here to avoid RCE\n    class PreserveDuplicatesLoader(yaml.loader.SafeLoader):\n        pass\n\n    def map_constructor(loader, node, deep=False):\n        keys = [loader.construct_object(node, deep=deep) for node, _ in node.value]\n        vals = [loader.construct_object(node, deep=deep) for _, node in node.value]\n        key_count = Counter(keys)\n        data = {}\n        for key, val in zip(keys, vals):\n            if key_count[key] > 1:\n                raise ValueError(\n                    f\"Config input {key} is defined multiple times for the same field, this is not allowed.\"\n                )\n            else:\n                data[key] = val\n        return data\n\n    PreserveDuplicatesLoader.add_constructor(\n        yaml.resolver.BaseResolver.DEFAULT_MAPPING_TAG, map_constructor\n    )\n    return yaml.load(raw_config, PreserveDuplicatesLoader)\n\n\ndef clean_camera_user_pass(line: str) -> str:\n    \"\"\"Removes user and password from line.\"\"\"\n    rtsp_cleaned = re.sub(REGEX_RTSP_CAMERA_USER_PASS, \"://*:*@\", line)\n    return re.sub(REGEX_HTTP_CAMERA_USER_PASS, \"user=*&password=*\", rtsp_cleaned)\n\n\ndef escape_special_characters(path: str) -> str:\n    \"\"\"Cleans reserved characters to encodings for ffmpeg.\"\"\"\n    try:\n        found = re.search(REGEX_RTSP_CAMERA_USER_PASS, path).group(0)[3:-1]\n        pw = found[(found.index(\":\") + 1) :]\n        return path.replace(pw, urllib.parse.quote_plus(pw))\n    except AttributeError:\n        # path does not have user:pass\n        return path\n\n\ndef get_ffmpeg_arg_list(arg: Any) -> list:\n    \"\"\"Use arg if list or convert to list format.\"\"\"\n    return arg if isinstance(arg, list) else shlex.split(arg)\n\n\ndef load_labels(path, encoding=\"utf-8\", prefill=91):\n    \"\"\"Loads labels from file (with or without index numbers).\n    Args:\n      path: path to label file.\n      encoding: label file encoding.\n    Returns:\n      Dictionary mapping indices to labels.\n    \"\"\"\n    with open(path, \"r\", encoding=encoding) as f:\n        labels = {index: \"unknown\" for index in range(prefill)}\n        lines = f.readlines()\n        if not lines:\n            return {}\n\n        if lines[0].split(\" \", maxsplit=1)[0].isdigit():\n            pairs = [line.split(\" \", maxsplit=1) for line in lines]\n            labels.update({int(index): label.strip() for index, label in pairs})\n        else:\n            labels.update({index: line.strip() for index, line in enumerate(lines)})\n        return labels\n\n\ndef get_tz_modifiers(tz_name: str) -> Tuple[str, str, int]:\n    seconds_offset = (\n        datetime.datetime.now(pytz.timezone(tz_name)).utcoffset().total_seconds()\n    )\n    hours_offset = int(seconds_offset / 60 / 60)\n    minutes_offset = int(seconds_offset / 60 - hours_offset * 60)\n    hour_modifier = f\"{hours_offset} hour\"\n    minute_modifier = f\"{minutes_offset} minute\"\n    return hour_modifier, minute_modifier, seconds_offset\n\n\ndef to_relative_box(\n    width: int, height: int, box: Tuple[int, int, int, int]\n) -> Tuple[int, int, int, int]:\n    return (\n        box[0] / width,  # x\n        box[1] / height,  # y\n        (box[2] - box[0]) / width,  # w\n        (box[3] - box[1]) / height,  # h\n    )\n\n\ndef create_mask(frame_shape, mask):\n    mask_img = np.zeros(frame_shape, np.uint8)\n    mask_img[:] = 255\n\n\ndef update_yaml_from_url(file_path, url):\n    parsed_url = urllib.parse.urlparse(url)\n    query_string = urllib.parse.parse_qs(parsed_url.query, keep_blank_values=True)\n\n    for key_path_str, new_value_list in query_string.items():\n        key_path = key_path_str.split(\".\")\n        for i in range(len(key_path)):\n            try:\n                index = int(key_path[i])\n                key_path[i] = (key_path[i - 1], index)\n                key_path.pop(i - 1)\n            except ValueError:\n                pass\n        new_value = new_value_list[0]\n        update_yaml_file(file_path, key_path, new_value)\n\n\ndef update_yaml_file(file_path, key_path, new_value):\n    yaml = YAML()\n    with open(file_path, \"r\") as f:\n        data = yaml.load(f)\n\n    data = update_yaml(data, key_path, new_value)\n\n    with open(file_path, \"w\") as f:\n        yaml.dump(data, f)\n\n\ndef update_yaml(data, key_path, new_value):\n    temp = data\n    for key in key_path[:-1]:\n        if isinstance(key, tuple):\n            if key[0] not in temp:\n                temp[key[0]] = [{}] * max(1, key[1] + 1)\n            elif len(temp[key[0]]) <= key[1]:\n                temp[key[0]] += [{}] * (key[1] - len(temp[key[0]]) + 1)\n            temp = temp[key[0]][key[1]]\n        else:\n            if key not in temp:\n                temp[key] = {}\n            temp = temp[key]\n\n    last_key = key_path[-1]\n    if new_value == \"\":\n        if isinstance(last_key, tuple):\n            del temp[last_key[0]][last_key[1]]\n        else:\n            del temp[last_key]\n    else:\n        if isinstance(last_key, tuple):\n            if last_key[0] not in temp:\n                temp[last_key[0]] = [{}] * max(1, last_key[1] + 1)\n            elif len(temp[last_key[0]]) <= last_key[1]:\n                temp[last_key[0]] += [{}] * (last_key[1] - len(temp[last_key[0]]) + 1)\n            temp[last_key[0]][last_key[1]] = new_value\n        else:\n            if (\n                last_key in temp\n                and isinstance(temp[last_key], dict)\n                and isinstance(new_value, dict)\n            ):\n                temp[last_key].update(new_value)\n            else:\n                temp[last_key] = new_value\n\n    return data\n\n\ndef find_by_key(dictionary, target_key):\n    if target_key in dictionary:\n        return dictionary[target_key]\n    else:\n        for value in dictionary.values():\n            if isinstance(value, dict):\n                result = find_by_key(value, target_key)\n                if result is not None:\n                    return result\n    return None\n\n\ndef get_tomorrow_at_time(hour: int) -> datetime.datetime:\n    \"\"\"Returns the datetime of the following day at 2am.\"\"\"\n    tomorrow = datetime.datetime.now(get_localzone()) + datetime.timedelta(days=1)\n    return tomorrow.replace(hour=hour, minute=0, second=0).astimezone(\n        datetime.timezone.utc\n    )\n\n\ndef clear_and_unlink(file: Path, missing_ok: bool = True) -> None:\n    \"\"\"clear file then unlink to avoid space retained by file descriptors.\"\"\"\n    if not missing_ok and not file.exists():\n        raise FileNotFoundError()\n\n    # empty contents of file before unlinking https://github.com/blakeblackshear/frigate/issues/4769\n    with open(file, \"w\"):\n        pass\n\n    file.unlink(missing_ok=missing_ok)\n", "path": "frigate/util/builtin.py"}], "after_files": [{"content": "\"\"\"Utilities for builtin types manipulation.\"\"\"\n\nimport copy\nimport datetime\nimport logging\nimport re\nimport shlex\nimport urllib.parse\nfrom collections import Counter\nfrom collections.abc import Mapping\nfrom pathlib import Path\nfrom typing import Any, Tuple\n\nimport numpy as np\nimport pytz\nimport yaml\nfrom ruamel.yaml import YAML\nfrom tzlocal import get_localzone\nfrom zoneinfo import ZoneInfoNotFoundError\n\nfrom frigate.const import REGEX_HTTP_CAMERA_USER_PASS, REGEX_RTSP_CAMERA_USER_PASS\n\nlogger = logging.getLogger(__name__)\n\n\nclass EventsPerSecond:\n    def __init__(self, max_events=1000, last_n_seconds=10):\n        self._start = None\n        self._max_events = max_events\n        self._last_n_seconds = last_n_seconds\n        self._timestamps = []\n\n    def start(self):\n        self._start = datetime.datetime.now().timestamp()\n\n    def update(self):\n        now = datetime.datetime.now().timestamp()\n        if self._start is None:\n            self._start = now\n        self._timestamps.append(now)\n        # truncate the list when it goes 100 over the max_size\n        if len(self._timestamps) > self._max_events + 100:\n            self._timestamps = self._timestamps[(1 - self._max_events) :]\n        self.expire_timestamps(now)\n\n    def eps(self):\n        now = datetime.datetime.now().timestamp()\n        if self._start is None:\n            self._start = now\n        # compute the (approximate) events in the last n seconds\n        self.expire_timestamps(now)\n        seconds = min(now - self._start, self._last_n_seconds)\n        # avoid divide by zero\n        if seconds == 0:\n            seconds = 1\n        return len(self._timestamps) / seconds\n\n    # remove aged out timestamps\n    def expire_timestamps(self, now):\n        threshold = now - self._last_n_seconds\n        while self._timestamps and self._timestamps[0] < threshold:\n            del self._timestamps[0]\n\n\ndef deep_merge(dct1: dict, dct2: dict, override=False, merge_lists=False) -> dict:\n    \"\"\"\n    :param dct1: First dict to merge\n    :param dct2: Second dict to merge\n    :param override: if same key exists in both dictionaries, should override? otherwise ignore. (default=True)\n    :return: The merge dictionary\n    \"\"\"\n    merged = copy.deepcopy(dct1)\n    for k, v2 in dct2.items():\n        if k in merged:\n            v1 = merged[k]\n            if isinstance(v1, dict) and isinstance(v2, Mapping):\n                merged[k] = deep_merge(v1, v2, override)\n            elif isinstance(v1, list) and isinstance(v2, list):\n                if merge_lists:\n                    merged[k] = v1 + v2\n            else:\n                if override:\n                    merged[k] = copy.deepcopy(v2)\n        else:\n            merged[k] = copy.deepcopy(v2)\n    return merged\n\n\ndef load_config_with_no_duplicates(raw_config) -> dict:\n    \"\"\"Get config ensuring duplicate keys are not allowed.\"\"\"\n\n    # https://stackoverflow.com/a/71751051\n    # important to use SafeLoader here to avoid RCE\n    class PreserveDuplicatesLoader(yaml.loader.SafeLoader):\n        pass\n\n    def map_constructor(loader, node, deep=False):\n        keys = [loader.construct_object(node, deep=deep) for node, _ in node.value]\n        vals = [loader.construct_object(node, deep=deep) for _, node in node.value]\n        key_count = Counter(keys)\n        data = {}\n        for key, val in zip(keys, vals):\n            if key_count[key] > 1:\n                raise ValueError(\n                    f\"Config input {key} is defined multiple times for the same field, this is not allowed.\"\n                )\n            else:\n                data[key] = val\n        return data\n\n    PreserveDuplicatesLoader.add_constructor(\n        yaml.resolver.BaseResolver.DEFAULT_MAPPING_TAG, map_constructor\n    )\n    return yaml.load(raw_config, PreserveDuplicatesLoader)\n\n\ndef clean_camera_user_pass(line: str) -> str:\n    \"\"\"Removes user and password from line.\"\"\"\n    rtsp_cleaned = re.sub(REGEX_RTSP_CAMERA_USER_PASS, \"://*:*@\", line)\n    return re.sub(REGEX_HTTP_CAMERA_USER_PASS, \"user=*&password=*\", rtsp_cleaned)\n\n\ndef escape_special_characters(path: str) -> str:\n    \"\"\"Cleans reserved characters to encodings for ffmpeg.\"\"\"\n    try:\n        found = re.search(REGEX_RTSP_CAMERA_USER_PASS, path).group(0)[3:-1]\n        pw = found[(found.index(\":\") + 1) :]\n        return path.replace(pw, urllib.parse.quote_plus(pw))\n    except AttributeError:\n        # path does not have user:pass\n        return path\n\n\ndef get_ffmpeg_arg_list(arg: Any) -> list:\n    \"\"\"Use arg if list or convert to list format.\"\"\"\n    return arg if isinstance(arg, list) else shlex.split(arg)\n\n\ndef load_labels(path, encoding=\"utf-8\", prefill=91):\n    \"\"\"Loads labels from file (with or without index numbers).\n    Args:\n      path: path to label file.\n      encoding: label file encoding.\n    Returns:\n      Dictionary mapping indices to labels.\n    \"\"\"\n    with open(path, \"r\", encoding=encoding) as f:\n        labels = {index: \"unknown\" for index in range(prefill)}\n        lines = f.readlines()\n        if not lines:\n            return {}\n\n        if lines[0].split(\" \", maxsplit=1)[0].isdigit():\n            pairs = [line.split(\" \", maxsplit=1) for line in lines]\n            labels.update({int(index): label.strip() for index, label in pairs})\n        else:\n            labels.update({index: line.strip() for index, line in enumerate(lines)})\n        return labels\n\n\ndef get_tz_modifiers(tz_name: str) -> Tuple[str, str, int]:\n    seconds_offset = (\n        datetime.datetime.now(pytz.timezone(tz_name)).utcoffset().total_seconds()\n    )\n    hours_offset = int(seconds_offset / 60 / 60)\n    minutes_offset = int(seconds_offset / 60 - hours_offset * 60)\n    hour_modifier = f\"{hours_offset} hour\"\n    minute_modifier = f\"{minutes_offset} minute\"\n    return hour_modifier, minute_modifier, seconds_offset\n\n\ndef to_relative_box(\n    width: int, height: int, box: Tuple[int, int, int, int]\n) -> Tuple[int, int, int, int]:\n    return (\n        box[0] / width,  # x\n        box[1] / height,  # y\n        (box[2] - box[0]) / width,  # w\n        (box[3] - box[1]) / height,  # h\n    )\n\n\ndef create_mask(frame_shape, mask):\n    mask_img = np.zeros(frame_shape, np.uint8)\n    mask_img[:] = 255\n\n\ndef update_yaml_from_url(file_path, url):\n    parsed_url = urllib.parse.urlparse(url)\n    query_string = urllib.parse.parse_qs(parsed_url.query, keep_blank_values=True)\n\n    for key_path_str, new_value_list in query_string.items():\n        key_path = key_path_str.split(\".\")\n        for i in range(len(key_path)):\n            try:\n                index = int(key_path[i])\n                key_path[i] = (key_path[i - 1], index)\n                key_path.pop(i - 1)\n            except ValueError:\n                pass\n        new_value = new_value_list[0]\n        update_yaml_file(file_path, key_path, new_value)\n\n\ndef update_yaml_file(file_path, key_path, new_value):\n    yaml = YAML()\n    with open(file_path, \"r\") as f:\n        data = yaml.load(f)\n\n    data = update_yaml(data, key_path, new_value)\n\n    with open(file_path, \"w\") as f:\n        yaml.dump(data, f)\n\n\ndef update_yaml(data, key_path, new_value):\n    temp = data\n    for key in key_path[:-1]:\n        if isinstance(key, tuple):\n            if key[0] not in temp:\n                temp[key[0]] = [{}] * max(1, key[1] + 1)\n            elif len(temp[key[0]]) <= key[1]:\n                temp[key[0]] += [{}] * (key[1] - len(temp[key[0]]) + 1)\n            temp = temp[key[0]][key[1]]\n        else:\n            if key not in temp:\n                temp[key] = {}\n            temp = temp[key]\n\n    last_key = key_path[-1]\n    if new_value == \"\":\n        if isinstance(last_key, tuple):\n            del temp[last_key[0]][last_key[1]]\n        else:\n            del temp[last_key]\n    else:\n        if isinstance(last_key, tuple):\n            if last_key[0] not in temp:\n                temp[last_key[0]] = [{}] * max(1, last_key[1] + 1)\n            elif len(temp[last_key[0]]) <= last_key[1]:\n                temp[last_key[0]] += [{}] * (last_key[1] - len(temp[last_key[0]]) + 1)\n            temp[last_key[0]][last_key[1]] = new_value\n        else:\n            if (\n                last_key in temp\n                and isinstance(temp[last_key], dict)\n                and isinstance(new_value, dict)\n            ):\n                temp[last_key].update(new_value)\n            else:\n                temp[last_key] = new_value\n\n    return data\n\n\ndef find_by_key(dictionary, target_key):\n    if target_key in dictionary:\n        return dictionary[target_key]\n    else:\n        for value in dictionary.values():\n            if isinstance(value, dict):\n                result = find_by_key(value, target_key)\n                if result is not None:\n                    return result\n    return None\n\n\ndef get_tomorrow_at_time(hour: int) -> datetime.datetime:\n    \"\"\"Returns the datetime of the following day at 2am.\"\"\"\n    try:\n        tomorrow = datetime.datetime.now(get_localzone()) + datetime.timedelta(days=1)\n    except ZoneInfoNotFoundError:\n        tomorrow = datetime.datetime.now(datetime.timezone.utc) + datetime.timedelta(\n            days=1\n        )\n        logger.warning(\n            \"Using utc for maintenance due to missing or incorrect timezone set\"\n        )\n\n    return tomorrow.replace(hour=hour, minute=0, second=0).astimezone(\n        datetime.timezone.utc\n    )\n\n\ndef clear_and_unlink(file: Path, missing_ok: bool = True) -> None:\n    \"\"\"clear file then unlink to avoid space retained by file descriptors.\"\"\"\n    if not missing_ok and not file.exists():\n        raise FileNotFoundError()\n\n    # empty contents of file before unlinking https://github.com/blakeblackshear/frigate/issues/4769\n    with open(file, \"w\"):\n        pass\n\n    file.unlink(missing_ok=missing_ok)\n", "path": "frigate/util/builtin.py"}]}