- orchestrator._run_video: track.file_path 우선 사용 (audio_url 변환 불필요) - _local_path: /media/music/ → /app/data/ (마운트가 /app/data 직접이라 music 서브디렉토리 없음) - video.py/thumb.py: stderr truncation [-800:]/[-500:] — 진짜 에러 보이게
52 lines
1.9 KiB
Python
52 lines
1.9 KiB
Python
"""썸네일 생성 — 영상 5초 프레임 추출 + 텍스트 오버레이."""
|
|
import os
|
|
import subprocess
|
|
import logging
|
|
from PIL import Image, ImageDraw, ImageFont
|
|
|
|
from . import storage
|
|
|
|
logger = logging.getLogger("music-lab.thumb")
|
|
THUMB_TIMEOUT_S = 60
|
|
|
|
|
|
class ThumbGenerationError(Exception):
|
|
pass
|
|
|
|
|
|
def generate(*, pipeline_id: int, video_path: str,
|
|
track_title: str = "", overlay_text: bool = True) -> dict:
|
|
out_path = os.path.join(storage.pipeline_dir(pipeline_id), "thumbnail.jpg")
|
|
cmd = ["ffmpeg", "-y", "-i", video_path,
|
|
"-ss", "00:00:05", "-vframes", "1", "-q:v", "2", out_path]
|
|
result = subprocess.run(cmd, capture_output=True, text=True, timeout=THUMB_TIMEOUT_S)
|
|
if result.returncode != 0:
|
|
raise ThumbGenerationError(f"ffmpeg 썸네일 실패: {result.stderr[-500:]}")
|
|
|
|
if overlay_text and track_title:
|
|
_overlay_title(out_path, track_title)
|
|
|
|
return {"url": storage.media_url(pipeline_id, "thumbnail.jpg"), "used_fallback": False}
|
|
|
|
|
|
def _overlay_title(path: str, title: str) -> None:
|
|
try:
|
|
with Image.open(path) as src:
|
|
img = src.convert("RGB")
|
|
try:
|
|
font = ImageFont.truetype("/usr/share/fonts/truetype/dejavu/DejaVuSans-Bold.ttf", 80)
|
|
except OSError:
|
|
font = ImageFont.load_default()
|
|
draw = ImageDraw.Draw(img)
|
|
# 하단 30% 영역에 검정 반투명 박스 + 흰 글씨
|
|
w, h = img.size
|
|
box_h = int(h * 0.3)
|
|
with Image.new("RGBA", (w, box_h), (0, 0, 0, 160)) as overlay:
|
|
img.paste(overlay, (0, h - box_h), overlay)
|
|
bbox = draw.textbbox((0, 0), title, font=font)
|
|
tw = bbox[2] - bbox[0]
|
|
draw.text(((w - tw) // 2, h - box_h + 30), title, fill=(255, 255, 255), font=font)
|
|
img.save(path, "JPEG", quality=92)
|
|
except Exception as e:
|
|
logger.warning("썸네일 오버레이 실패: %s", e)
|