archive-backend/utils/update_thumbs.py
2026-11-07 11:40:36 +00:00

83 lines
2.6 KiB
Python
Raw Blame History

This file contains ambiguous Unicode characters

This file contains Unicode characters that might be confused with other characters. If you think that this is intentional, you can safely ignore this warning. Use the Escape button to reveal them.

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))