diff --git a/app/discord.py b/app/discord.py index f21482d..dfb9ce2 100644 --- a/app/discord.py +++ b/app/discord.py @@ -104,7 +104,7 @@ async def send_to_discord(title: str, video_url: str, summary: str) -> None: payload = {"embeds": [embed]} - async with httpx.AsyncClient() as client: + async with httpx.AsyncClient(timeout=30.0) as client: resp = await client.post(settings.discord_webhook_url, json=payload) resp.raise_for_status() @@ -131,5 +131,5 @@ async def send_error_to_discord( payload = {"embeds": [embed]} - async with httpx.AsyncClient() as client: + async with httpx.AsyncClient(timeout=30.0) as client: await client.post(settings.discord_webhook_url, json=payload) diff --git a/app/main.py b/app/main.py index 135b121..2a237bd 100644 --- a/app/main.py +++ b/app/main.py @@ -4,7 +4,7 @@ from pydantic import BaseModel from app.config import settings from app.discord import send_error_to_discord, send_to_discord from app.summarizer import summarize -from app.transcript import extract_video_id, fetch_transcript +from app.transcript import SkipVideo, extract_video_id, fetch_transcript app = FastAPI(title="News Summary Bot") @@ -29,6 +29,8 @@ async def summarize_video( transcript = fetch_transcript(video_id) summary = summarize(transcript, title) await send_to_discord(title, req.video_url, summary) + except SkipVideo as e: + return {"status": "skipped", "title": title, "reason": str(e)} except Exception as e: await send_error_to_discord(title, req.video_url, e) raise HTTPException(status_code=500, detail=str(e)) diff --git a/app/transcript.py b/app/transcript.py index 292866b..9cc152f 100644 --- a/app/transcript.py +++ b/app/transcript.py @@ -8,8 +8,14 @@ import yt_dlp COOKIES_SRC = "/app/cookies.txt" +class SkipVideo(Exception): + """라이브/쇼츠 등 요약 대상이 아닌 영상.""" + + def extract_video_id(url: str) -> str: """YouTube URL에서 video ID 추출.""" + if "/shorts/" in url: + raise SkipVideo("쇼츠 영상은 요약 대상이 아닙니다") if "youtu.be/" in url: return url.split("youtu.be/")[1].split("?")[0] if "v=" in url: @@ -25,6 +31,7 @@ def fetch_transcript(video_id: str) -> str: "skip_download": True, "quiet": True, "no_warnings": True, + "socket_timeout": 30, } if os.path.isfile(COOKIES_SRC): @@ -39,6 +46,9 @@ def fetch_transcript(video_id: str) -> str: if "cookiefile" in ydl_opts: os.unlink(ydl_opts["cookiefile"]) + if info.get("is_live") or info.get("live_status") in ("is_live", "is_upcoming"): + raise SkipVideo("라이브/예정 영상은 요약 대상이 아닙니다") + subs = info.get("automatic_captions", {}) lang = "ko" if "ko" in subs else "en" if "en" in subs else None if not lang: @@ -53,7 +63,7 @@ def fetch_transcript(video_id: str) -> str: if not sub_url: raise ValueError(f"json3 자막 포맷을 찾을 수 없습니다: {video_id}") - resp = httpx.get(sub_url) + resp = httpx.get(sub_url, timeout=30.0) resp.raise_for_status() data = resp.json()