import os, hashlib, subprocess from concurrent.futures import ThreadPoolExecutor # pull constants from app.py from app import THUMB_DIR, THUMB_WIDTH, FF_QUALITY def _hashed_thumb_path(video_id: str): h = hashlib.md5(video_id.encode()).hexdigest() sub1, sub2 = h[:2], h[2:4] path = os.path.join(THUMB_DIR, sub1, sub2) os.makedirs(path, exist_ok=True) return os.path.join(path, f"{video_id}.webp") def _gen_thumb_cmd(src: str, dest: str): return [ "ffmpeg", "-y", "-loglevel", "error", "-ss", "0", "-i", src, "-vframes", "1", "-vf", f"thumbnail,scale={THUMB_WIDTH}:-1", "-q:v", FF_QUALITY, dest ] def generate_thumbnails_for_videos(videos): tasks = [] for v in videos: video_id = v.get("video_id") filepath = v.get("filepath") thumb_path = _hashed_thumb_path(video_id) if not filepath: print(f"⚠️ Skipping {video_id}: missing filepath") continue if not os.path.exists(filepath): print(f"⚠️ Skipping {video_id}: file not found → {filepath}") continue if not os.path.exists(thumb_path): tasks.append((filepath, thumb_path)) v["thumbnail"] = thumb_path if tasks: with ThreadPoolExecutor(max_workers=os.cpu_count() * 2) as exe: list(exe.map( lambda t: subprocess.run( _gen_thumb_cmd(*t), stdout=subprocess.DEVNULL, stderr=subprocess.DEVNULL ), tasks ))