diff --git a/frigate/api/media.py b/frigate/api/media.py index 6d010efd14..95696cf94e 100644 --- a/frigate/api/media.py +++ b/frigate/api/media.py @@ -1060,7 +1060,7 @@ async def event_thumbnail( except DoesNotExist: thumbnail_bytes = None - if thumbnail_bytes is None: + if not thumbnail_bytes: # see if the object is currently being tracked try: camera_states = request.app.detected_frames_processor.get_camera_states() @@ -1076,7 +1076,7 @@ async def event_thumbnail( status_code=404, ) - if thumbnail_bytes is None: + if not thumbnail_bytes: return JSONResponse( content={"success": False, "message": "Event not found"}, status_code=404, @@ -1085,6 +1085,13 @@ async def event_thumbnail( img_as_np = np.frombuffer(thumbnail_bytes, dtype=np.uint8) img = cv2.imdecode(img_as_np, flags=1) + if img is None: + # thumbnail on disk is truncated or corrupt + return JSONResponse( + content={"success": False, "message": "Event not found"}, + status_code=404, + ) + # android notifications prefer a 2:1 ratio if format == "android": img = cv2.copyMakeBorder( diff --git a/frigate/test/test_file.py b/frigate/test/test_file.py index 6bbe2b6a87..0a17181a3a 100644 --- a/frigate/test/test_file.py +++ b/frigate/test/test_file.py @@ -70,3 +70,19 @@ class TestFileUtils(TestCase): assert rendered_image is not None assert rendered_image.shape[0] == 40 assert rendered_image.max() > 0 + + def test_get_event_thumbnail_bytes_ignores_empty_file(self): + """Verify empty thumbnail files are treated as missing.""" + event = SimpleNamespace(id="empty-thumb", camera="front_door", thumbnail=None) + + with ( + tempfile.TemporaryDirectory() as thumb_dir, + patch.object(file_util, "THUMB_DIR", thumb_dir), + ): + camera_dir = os.path.join(thumb_dir, event.camera) + os.makedirs(camera_dir) + + with open(os.path.join(camera_dir, f"{event.id}.webp"), "wb"): + pass + + assert file_util.get_event_thumbnail_bytes(event) is None diff --git a/frigate/util/file.py b/frigate/util/file.py index e259d13456..97fa96bc63 100644 --- a/frigate/util/file.py +++ b/frigate/util/file.py @@ -20,14 +20,15 @@ logger = logging.getLogger(__name__) def get_event_thumbnail_bytes(event: Event) -> bytes | None: + # callers treat empty bytes as a valid image, so normalize them to None if event.thumbnail: - return base64.b64decode(event.thumbnail) + return base64.b64decode(event.thumbnail) or None else: try: with open( os.path.join(THUMB_DIR, event.camera, f"{event.id}.webp"), "rb" ) as f: - return f.read() + return f.read() or None except Exception: return None