diff --git a/frigate/review/maintainer.py b/frigate/review/maintainer.py
index 77d3d2a6b..4f419b36b 100644
--- a/frigate/review/maintainer.py
+++ b/frigate/review/maintainer.py
@@ -68,7 +68,8 @@ class PendingReviewSegment:
self.last_update = frame_time
# thumbnail
- self.frame = np.zeros((THUMB_HEIGHT * 3 // 2, THUMB_WIDTH), np.uint8)
+ self._frame = np.zeros((THUMB_HEIGHT * 3 // 2, THUMB_WIDTH), np.uint8)
+ self.has_frame = False
self.frame_active_count = 0
self.frame_path = os.path.join(
CLIPS_DIR, f"review/thumb-{self.camera}-{self.id}.webp"
@@ -101,25 +102,27 @@ class PendingReviewSegment:
color_frame = cv2.cvtColor(frame, cv2.COLOR_YUV2BGR_I420)
color_frame = color_frame[region[1] : region[3], region[0] : region[2]]
width = int(THUMB_HEIGHT * color_frame.shape[1] / color_frame.shape[0])
- self.frame = cv2.resize(
+ self._frame = cv2.resize(
color_frame, dsize=(width, THUMB_HEIGHT), interpolation=cv2.INTER_AREA
)
- if self.frame is not None:
+ if self._frame is not None:
+ self.has_frame = True
cv2.imwrite(
- self.frame_path, self.frame, [int(cv2.IMWRITE_WEBP_QUALITY), 60]
+ self.frame_path, self._frame, [int(cv2.IMWRITE_WEBP_QUALITY), 60]
)
def save_full_frame(self, camera_config: CameraConfig, frame):
color_frame = cv2.cvtColor(frame, cv2.COLOR_YUV2BGR_I420)
width = int(THUMB_HEIGHT * color_frame.shape[1] / color_frame.shape[0])
- self.frame = cv2.resize(
+ self._frame = cv2.resize(
color_frame, dsize=(width, THUMB_HEIGHT), interpolation=cv2.INTER_AREA
)
- if self.frame is not None:
+ if self._frame is not None:
+ self.has_frame = True
cv2.imwrite(
- self.frame_path, self.frame, [int(cv2.IMWRITE_WEBP_QUALITY), 60]
+ self.frame_path, self._frame, [int(cv2.IMWRITE_WEBP_QUALITY), 60]
)
def get_data(self, ended: bool) -> dict:
@@ -194,7 +197,10 @@ class ReviewSegmentMaintainer(threading.Thread):
) -> None:
"""Update segment."""
prev_data = segment.get_data(ended=False)
- segment.update_frame(camera_config, frame, objects)
+
+ if frame is not None:
+ segment.update_frame(camera_config, frame, objects)
+
new_data = segment.get_data(ended=False)
self.requestor.send_data(UPSERT_REVIEW_SEGMENT, new_data)
self.requestor.send_data(
@@ -282,33 +288,23 @@ class ReviewSegmentMaintainer(threading.Thread):
except FileNotFoundError:
return
else:
+ if not segment.has_frame:
+ try:
+ frame_id = f"{camera_config.name}{frame_time}"
+ yuv_frame = self.frame_manager.get(
+ frame_id, camera_config.frame_shape_yuv
+ )
+ segment.save_full_frame(camera_config, yuv_frame)
+ self.frame_manager.close(frame_id)
+ self.update_segment(segment, camera_config, None, [])
+ except FileNotFoundError:
+ return
+
if segment.severity == SeverityEnum.alert and frame_time > (
segment.last_update + THRESHOLD_ALERT_ACTIVITY
):
- if segment.frame is None:
- try:
- frame_id = f"{camera_config.name}{frame_time}"
- yuv_frame = self.frame_manager.get(
- frame_id, camera_config.frame_shape_yuv
- )
- segment.save_full_frame(camera_config, yuv_frame)
- self.frame_manager.close(frame_id)
- except FileNotFoundError:
- return
-
self.end_segment(segment)
elif frame_time > (segment.last_update + THRESHOLD_DETECTION_ACTIVITY):
- if segment.frame is None:
- try:
- frame_id = f"{camera_config.name}{frame_time}"
- yuv_frame = self.frame_manager.get(
- frame_id, camera_config.frame_shape_yuv
- )
- segment.save_full_frame(camera_config, yuv_frame)
- self.frame_manager.close(frame_id)
- except FileNotFoundError:
- return
-
self.end_segment(segment)
def check_if_new_segment(
diff --git a/frigate/util/config.py b/frigate/util/config.py
index e882f0bfd..f395ee125 100644
--- a/frigate/util/config.py
+++ b/frigate/util/config.py
@@ -157,26 +157,45 @@ def get_relative_coordinates(
points = m.split(",")
if any(x > "1.0" for x in points):
- relative_masks.append(
- ",".join(
- [
- f"{round(int(points[i]) / frame_shape[1], 3)},{round(int(points[i + 1]) / frame_shape[0], 3)}"
- for i in range(0, len(points), 2)
- ]
+ rel_points = []
+ for i in range(0, len(points), 2):
+ x = int(points[i])
+ y = int(points[i + 1])
+
+ if x > frame_shape[1] or y > frame_shape[0]:
+ logger.error(
+ f"Not applying mask due to invalid coordinates. {x},{y} is outside of the detection resolution {frame_shape[1]}x{frame_shape[0]}. Use the editor in the UI to correct the mask."
+ )
+ continue
+
+ rel_points.append(
+ f"{round(x / frame_shape[1], 3)},{round(y / frame_shape[0], 3)}"
)
- )
+
+ relative_masks.append(",".join(rel_points))
else:
relative_masks.append(m)
mask = relative_masks
elif isinstance(mask, str) and any(x > "1.0" for x in mask.split(",")):
points = mask.split(",")
- mask = ",".join(
- [
- f"{round(int(points[i]) / frame_shape[1], 3)},{round(int(points[i + 1]) / frame_shape[0], 3)}"
- for i in range(0, len(points), 2)
- ]
- )
+ rel_points = []
+
+ for i in range(0, len(points), 2):
+ x = int(points[i])
+ y = int(points[i + 1])
+
+ if x > frame_shape[1] or y > frame_shape[0]:
+ logger.error(
+ f"Not applying mask due to invalid coordinates. {x},{y} is outside of the detection resolution {frame_shape[1]}x{frame_shape[0]}. Use the editor in the UI to correct the mask."
+ )
+ return []
+
+ rel_points.append(
+ f"{round(x / frame_shape[1], 3)},{round(y / frame_shape[0], 3)}"
+ )
+
+ mask = ",".join(rel_points)
return mask
diff --git a/web/.eslintrc.cjs b/web/.eslintrc.cjs
index deba5f544..883537d0f 100644
--- a/web/.eslintrc.cjs
+++ b/web/.eslintrc.cjs
@@ -44,6 +44,12 @@ module.exports = {
{ argsIgnorePattern: "^_", varsIgnorePattern: "^_" },
],
"no-console": "error",
+ "prettier/prettier": [
+ "warn",
+ {
+ plugins: ["prettier-plugin-tailwindcss"],
+ },
+ ],
},
overrides: [
{
diff --git a/web/.prettierrc b/web/.prettierrc
new file mode 100644
index 000000000..b4bfed357
--- /dev/null
+++ b/web/.prettierrc
@@ -0,0 +1,3 @@
+{
+ "plugins": ["prettier-plugin-tailwindcss"]
+}
diff --git a/web/package-lock.json b/web/package-lock.json
index 1a680777a..77db8051e 100644
--- a/web/package-lock.json
+++ b/web/package-lock.json
@@ -96,9 +96,10 @@
"fake-indexeddb": "^5.0.2",
"jest-websocket-mock": "^2.5.0",
"jsdom": "^24.0.0",
- "msw": "^2.2.14",
+ "msw": "^2.3.0",
"postcss": "^8.4.38",
"prettier": "^3.2.5",
+ "prettier-plugin-tailwindcss": "^0.5.14",
"tailwindcss": "^3.4.3",
"typescript": "^5.4.5",
"vite": "^5.2.11",
@@ -848,9 +849,9 @@
}
},
"node_modules/@mswjs/interceptors": {
- "version": "0.26.15",
- "resolved": "https://registry.npmjs.org/@mswjs/interceptors/-/interceptors-0.26.15.tgz",
- "integrity": "sha512-HM47Lu1YFmnYHKMBynFfjCp0U/yRskHj/8QEJW0CBEPOlw8Gkmjfll+S9b8M7V5CNDw2/ciRxjjnWeaCiblSIQ==",
+ "version": "0.29.1",
+ "resolved": "https://registry.npmjs.org/@mswjs/interceptors/-/interceptors-0.29.1.tgz",
+ "integrity": "sha512-3rDakgJZ77+RiQUuSK69t1F0m8BQKA8Vh5DCS5V0DWvNY67zob2JhhQrhCO0AKLGINTRSFd1tBaHcJTkhefoSw==",
"dev": true,
"dependencies": {
"@open-draft/deferred-promise": "^2.2.0",
@@ -5595,9 +5596,9 @@
"dev": true
},
"node_modules/msw": {
- "version": "2.2.14",
- "resolved": "https://registry.npmjs.org/msw/-/msw-2.2.14.tgz",
- "integrity": "sha512-64i8rNCa1xzDK8ZYsTrVMli05D687jty8+Th+PU5VTbJ2/4P7fkQFVyDQ6ZFT5FrNR8z2BHhbY47fKNvfHrumA==",
+ "version": "2.3.0",
+ "resolved": "https://registry.npmjs.org/msw/-/msw-2.3.0.tgz",
+ "integrity": "sha512-cDr1q/QTMzaWhY8n9lpGhceY209k29UZtdTgJ3P8Bzne3TSMchX2EM/ldvn4ATLOktpCefCU2gcEgzHc31GTPw==",
"dev": true,
"hasInstallScript": true,
"dependencies": {
@@ -5605,7 +5606,7 @@
"@bundled-es-modules/statuses": "^1.0.1",
"@inquirer/confirm": "^3.0.0",
"@mswjs/cookies": "^1.1.0",
- "@mswjs/interceptors": "^0.26.14",
+ "@mswjs/interceptors": "^0.29.0",
"@open-draft/until": "^2.1.0",
"@types/cookie": "^0.6.0",
"@types/statuses": "^2.0.4",
@@ -6181,6 +6182,80 @@
"node": ">=6.0.0"
}
},
+ "node_modules/prettier-plugin-tailwindcss": {
+ "version": "0.5.14",
+ "resolved": "https://registry.npmjs.org/prettier-plugin-tailwindcss/-/prettier-plugin-tailwindcss-0.5.14.tgz",
+ "integrity": "sha512-Puaz+wPUAhFp8Lo9HuciYKM2Y2XExESjeT+9NQoVFXZsPPnc9VYss2SpxdQ6vbatmt8/4+SN0oe0I1cPDABg9Q==",
+ "dev": true,
+ "engines": {
+ "node": ">=14.21.3"
+ },
+ "peerDependencies": {
+ "@ianvs/prettier-plugin-sort-imports": "*",
+ "@prettier/plugin-pug": "*",
+ "@shopify/prettier-plugin-liquid": "*",
+ "@trivago/prettier-plugin-sort-imports": "*",
+ "@zackad/prettier-plugin-twig-melody": "*",
+ "prettier": "^3.0",
+ "prettier-plugin-astro": "*",
+ "prettier-plugin-css-order": "*",
+ "prettier-plugin-import-sort": "*",
+ "prettier-plugin-jsdoc": "*",
+ "prettier-plugin-marko": "*",
+ "prettier-plugin-organize-attributes": "*",
+ "prettier-plugin-organize-imports": "*",
+ "prettier-plugin-sort-imports": "*",
+ "prettier-plugin-style-order": "*",
+ "prettier-plugin-svelte": "*"
+ },
+ "peerDependenciesMeta": {
+ "@ianvs/prettier-plugin-sort-imports": {
+ "optional": true
+ },
+ "@prettier/plugin-pug": {
+ "optional": true
+ },
+ "@shopify/prettier-plugin-liquid": {
+ "optional": true
+ },
+ "@trivago/prettier-plugin-sort-imports": {
+ "optional": true
+ },
+ "@zackad/prettier-plugin-twig-melody": {
+ "optional": true
+ },
+ "prettier-plugin-astro": {
+ "optional": true
+ },
+ "prettier-plugin-css-order": {
+ "optional": true
+ },
+ "prettier-plugin-import-sort": {
+ "optional": true
+ },
+ "prettier-plugin-jsdoc": {
+ "optional": true
+ },
+ "prettier-plugin-marko": {
+ "optional": true
+ },
+ "prettier-plugin-organize-attributes": {
+ "optional": true
+ },
+ "prettier-plugin-organize-imports": {
+ "optional": true
+ },
+ "prettier-plugin-sort-imports": {
+ "optional": true
+ },
+ "prettier-plugin-style-order": {
+ "optional": true
+ },
+ "prettier-plugin-svelte": {
+ "optional": true
+ }
+ }
+ },
"node_modules/pretty-format": {
"version": "29.7.0",
"resolved": "https://registry.npmjs.org/pretty-format/-/pretty-format-29.7.0.tgz",
diff --git a/web/package.json b/web/package.json
index 4638572a6..d41c9ea75 100644
--- a/web/package.json
+++ b/web/package.json
@@ -104,6 +104,7 @@
"msw": "^2.3.0",
"postcss": "^8.4.38",
"prettier": "^3.2.5",
+ "prettier-plugin-tailwindcss": "^0.5.14",
"tailwindcss": "^3.4.3",
"typescript": "^5.4.5",
"vite": "^5.2.11",
diff --git a/web/src/App.tsx b/web/src/App.tsx
index a86e79491..eb81bc812 100644
--- a/web/src/App.tsx
+++ b/web/src/App.tsx
@@ -31,7 +31,7 @@ function App() {
{isMobile &&