import argparse import asyncio from pathlib import Path from ..core.config import THUMBS_DIR, YT_DIR async def run (cmd: list[str]) -> tuple[int, bytes]: proc = await asyncio.create_subprocess_exec( *cmd, stdout=asyncio.subprocess.DEVNULL, stderr=asyncio.subprocess.PIPE, ) _, err = await proc.communicate() return proc.returncode, err async def extract_cover(video: Path, out: Path, sem: asyncio.Semaphore) -> bool: if ".Trash" in str(video) or any(part.startswith('.') for part in video.parts): return False # 1. Пропускаем временные файлы и аудио-дорожки yt-dlp if ".temp." in video.name or ".f" in video.name and video.suffix == ".webm": return False async with sem: out.parent.mkdir(parents=True, exist_ok=True) if out.exists() and out.stat().st_size > 0: return True # 2. Пытаемся взять ВТОРУЮ дорожку (с вопросительным знаком, чтобы не падало) code, err = await run([ "ffmpeg", "-y", "-loglevel", "error", "-i", str(video), "-map", "0:v:1?", "-c", "copy", str(out), ]) if code != 0: # 3. Если второй нет, пытаемся взять ПЕРВУЮ (тоже с вопросительным знаком) code, err = await run([ "ffmpeg", "-y", "-loglevel", "error", "-i", str(video), "-map", "0:v:0?", "-c", "copy", str(out), ]) if code != 0: print(f"FAIL {video.name}: {err.decode()[:300]}") return False return True async def main(workers: int): SEM = asyncio.Semaphore(workers) videos = [p for p in YT_DIR.rglob("*") if p.is_file() and p.suffix.lower() in {".mp4", ".mkv", ".webm"}] print(f"videos: {len(videos)}") tasks = [] for video in videos: rel = video.relative_to(YT_DIR) out = THUMBS_DIR / rel.with_suffix(".jpg") tasks.append(extract_cover(video, out, SEM)) results = await asyncio.gather(*tasks) ok = sum(1 for r in results if r) print(f"done: {ok}/{len(results)}") def parse_args(): parser = argparse.ArgumentParser(description="Extract video previews") parser.add_argument( "-w", "--workers", type=int, default=8, help="number of parallel ffmpeg processes (default: 8)", ) return parser.parse_args() if __name__ == "__main__": args = parse_args() asyncio.run(main(args.workers))