Files
FabledCurator/backend/app/tasks/thumbnail.py
T
bvandeusen d3bb8c509a feat(fc2a): add Celery tasks — scan_directory, import_media_file, generate_thumbnail
scan_directory walks ImportSettings.import_scan_path, creates an
ImportBatch, enumerates supported files into ImportTasks, and enqueues
import_media_file per task. import_media_file moves the task through
its state machine (pending → queued → processing → complete/skipped/failed),
updates ImportBatch counters atomically (UPDATE ... SET col = col + 1),
enqueues a thumbnail task on success, and marks the batch complete when
the last task drains.

generate_thumbnail runs on its own queue (thumbnail) so big imports
don't starve thumbnail throughput; failure here is logged and does not
fail the import.

Co-Authored-By: Claude Opus 4.7 (1M context) <noreply@anthropic.com>
2026-05-14 12:08:37 -04:00

51 lines
1.8 KiB
Python

"""generate_thumbnail task: PIL/ffmpeg thumbnail generation on the thumbnail queue.
Lives separately from import_file because thumbnails can be regenerated en
masse (FC-2c adds the 'regenerate all' admin action) and they're CPU-bound
so they deserve their own queue lane.
"""
from pathlib import Path
from sqlalchemy import create_engine
from sqlalchemy.orm import sessionmaker
from ..celery_app import celery
from ..config import get_config
from ..models import ImageRecord
from ..services.importer import is_video
from ..services.thumbnailer import Thumbnailer
IMAGES_ROOT = Path("/images")
def _sync_session_factory():
cfg = get_config()
engine = create_engine(cfg.database_url_sync, future=True, pool_pre_ping=True)
return sessionmaker(engine, expire_on_commit=False)
@celery.task(name="backend.app.tasks.thumbnail.generate_thumbnail", bind=True)
def generate_thumbnail(self, image_id: int) -> dict:
SessionLocal = _sync_session_factory()
with SessionLocal() as session:
record = session.get(ImageRecord, image_id)
if record is None:
return {"status": "missing", "image_id": image_id}
thumbnailer = Thumbnailer(images_root=IMAGES_ROOT)
source = Path(record.path)
try:
if is_video(source):
result = thumbnailer.generate_video_thumbnail(source, record.sha256)
else:
result = thumbnailer.generate_image_thumbnail(source, record.sha256)
except Exception as exc: # pragma: no cover — thumbnail failure is non-fatal
return {"status": "failed", "image_id": image_id, "error": f"{type(exc).__name__}: {exc}"}
record.thumbnail_path = str(result.path)
session.add(record)
session.commit()
return {"status": "ok", "image_id": image_id, "path": str(result.path)}