From 9a0ac85f8caff2e36aa1ec60c6aef176fbe40ca2 Mon Sep 17 00:00:00 2001 From: Bryan Van Deusen Date: Sun, 19 Apr 2026 22:31:50 -0400 Subject: [PATCH] fix(ml): skip videos in tag_and_embed and backfill WD14 and SigLIP are image-only. The current backfill enqueues every ImageRecord row, and tag_and_embed retries 3 times (~3 min) on each video before giving up, blocking the serialized ml queue. Worse, videos never get a prediction row, so every subsequent backfill re-enqueues them. - tag_and_embed: early return 'unsupported_format' for video extensions, no retry. - backfill: filter the outerjoin query to exclude video extensions via VIDEO_EXTS from app.utils.image_importer. Co-Authored-By: Claude Opus 4.7 --- app/tasks/ml.py | 13 ++++++++++++- 1 file changed, 12 insertions(+), 1 deletion(-) diff --git a/app/tasks/ml.py b/app/tasks/ml.py index 91bb4d4..807052a 100644 --- a/app/tasks/ml.py +++ b/app/tasks/ml.py @@ -5,7 +5,7 @@ import os import time import numpy as np -from sqlalchemy import and_, func +from sqlalchemy import and_, func, or_ from sqlalchemy.dialects.postgresql import insert as pg_insert from app.celery_app import celery @@ -47,6 +47,11 @@ def tag_and_embed(self, image_id: int): log.warning(f"tag_and_embed: file missing at {image.filepath}") return {'status': 'file_missing'} + # WD14 + SigLIP are image-only; videos bypass inference entirely. + from app.utils.image_importer import VIDEO_EXTS + if image.filepath.lower().endswith(VIDEO_EXTS): + return {'status': 'unsupported_format'} + try: t0 = time.time() raw = wd14.infer_filtered(image.filepath, min_any=WD14_STORE_FLOOR) @@ -102,6 +107,11 @@ def backfill(self, batch_size: int = 50, pause_seconds: float = 0.5): """ from app.ml.wd14 import MODEL_VERSION as WD14_VER from app.ml.siglip import MODEL_VERSION as SIGLIP_VER + from app.utils.image_importer import VIDEO_EXTS + + video_filter = ~or_(*[ + func.lower(ImageRecord.filepath).like(f'%{ext}') for ext in VIDEO_EXTS + ]) enqueued_total = 0 last_id = 0 @@ -123,6 +133,7 @@ def backfill(self, batch_size: int = 50, pause_seconds: float = 0.5): ), ) .filter(ImageRecord.id > last_id) + .filter(video_filter) .filter( (ImageTagPrediction.image_id.is_(None)) | (ImageEmbedding.image_id.is_(None))