83 lines
2.6 KiB
Python
83 lines
2.6 KiB
Python
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)) |