"""Background pre-transcode for video photos so /playback is a cache hit on first user click. Dispatched from scan_folder when a new video row is created, and from the backfill admin endpoint for the existing library.""" from __future__ import annotations import logging import os from celery import shared_task from app.services.video import ( PLAYBACK_OK_EXTS, PLAYBACK_OK_VCODECS, cache_path_for, ffprobe_video_codec, transcode_to_h264_mp4, ) logger = logging.getLogger(__name__) @shared_task( name='pretranscode_video', # Override the 10-minute global task_time_limit. Long videos (30+ min # raw clips, the occasional .3gp from the 2016 archive) can legitimately # take half an hour to transcode on this CPU-only box. time_limit=1800, soft_time_limit=1740, ) def pretranscode_video(photo_id: str, src_path: str): """Idempotent: skip if cache exists and is newer than source, skip if source is already a passthrough-safe codec/container. Reports the outcome as a status string so the admin backfill can summarise. The /playback endpoint also falls back to a sync transcode if the cache miss races a queued task.""" if not os.path.exists(src_path): logger.debug("pretranscode skip — source missing: %s", src_path) return {'status': 'missing', 'photo_id': photo_id} cache_path = cache_path_for(photo_id) if cache_path.exists(): try: if os.path.getmtime(src_path) <= os.path.getmtime(cache_path): return {'status': 'cached', 'photo_id': photo_id} cache_path.unlink() except OSError: pass ext = os.path.splitext(src_path)[1].lower() if ext in PLAYBACK_OK_EXTS: codec = ffprobe_video_codec(src_path) if codec in PLAYBACK_OK_VCODECS: return {'status': 'passthrough', 'photo_id': photo_id, 'codec': codec} ok = transcode_to_h264_mp4(src_path, str(cache_path)) return { 'status': 'transcoded' if ok else 'failed', 'photo_id': photo_id, }