azer / app.py
gooookim's picture
Update app.py
5c08511 verified
Raw
History Blame Contribute Delete
3.39 kB
import hashlib
import re
from pathlib import Path
from fastapi import FastAPI
from fastapi.responses import FileResponse, JSONResponse, PlainTextResponse
from gtts import gTTS
ROOT = Path(__file__).resolve().parent
STATIC_DIR = ROOT / "static"
SLIDES_DIR = ROOT / "slides"
AUDIO_DIR = ROOT / "audio"
SCRIPT_FILE = ROOT / "scripts.txt"
AUDIO_DIR.mkdir(exist_ok=True)
app = FastAPI()
def read_slide_data():
"""
scripts.txtμ—μ„œ ## 제λͺ©κ³Ό γ€Œμ›κ³ γ€λ₯Ό μΆ”μΆœν•©λ‹ˆλ‹€.
slides/0.png ~ slides/19.png와 index 번호둜 μ—°κ²°λ©λ‹ˆλ‹€.
"""
if not SCRIPT_FILE.exists():
return []
text = SCRIPT_FILE.read_text(encoding="utf-8")
titles = re.findall(r"##\s*(.+)", text)
speeches = re.findall(r"γ€Œ([\s\S]*?)」", text)
slides = []
for i, speech in enumerate(speeches):
title = titles[i].strip() if i < len(titles) else f"Slide {i}"
image_name = f"{i}.png"
image_path = SLIDES_DIR / image_name
slides.append({
"index": i,
"title": title,
"speech": speech.strip(),
"image": f"/slides/{image_name}",
"image_exists": image_path.exists(),
"audio": f"/api/audio/{i}"
})
return slides
@app.get("/")
def index():
return FileResponse(STATIC_DIR / "index.html")
@app.get("/style.css")
def css():
return FileResponse(STATIC_DIR / "style.css", media_type="text/css")
@app.get("/app.js")
def js():
return FileResponse(STATIC_DIR / "app.js", media_type="application/javascript")
@app.get("/api/slides")
def api_slides():
return JSONResponse(read_slide_data())
@app.get("/slides/{image_name}")
def slide_image(image_name: str):
"""
slides/0.png ~ slides/19.png 제곡.
ν—ˆμš©λœ 파일λͺ… νŒ¨ν„΄λ§Œ μ œκ³΅ν•©λ‹ˆλ‹€.
"""
if not re.fullmatch(r"\d+\.png", image_name):
return PlainTextResponse("Invalid image name", status_code=400)
image_path = SLIDES_DIR / image_name
if not image_path.exists():
return PlainTextResponse(f"Image not found: {image_name}", status_code=404)
return FileResponse(image_path, media_type="image/png")
@app.get("/api/audio/{slide_index}")
def api_audio(slide_index: int):
"""
ν˜„μž¬ νŽ˜μ΄μ§€ 원고λ₯Ό gTTS둜 mp3 생성 ν›„ λ°˜ν™˜ν•©λ‹ˆλ‹€.
ν•œ 번 μƒμ„±λœ mp3λŠ” audio/에 μΊμ‹œλ©λ‹ˆλ‹€.
"""
slides = read_slide_data()
if slide_index < 0 or slide_index >= len(slides):
return PlainTextResponse("Invalid slide index", status_code=404)
text = slides[slide_index]["speech"]
if not text:
return PlainTextResponse("Empty script", status_code=400)
key = hashlib.md5(f"{slide_index}-{text}".encode("utf-8")).hexdigest()
mp3_path = AUDIO_DIR / f"slide_{slide_index:02d}_{key}.mp3"
if not mp3_path.exists():
try:
tts = gTTS(text=text, lang="en", slow=False)
tts.save(str(mp3_path))
except Exception as e:
return PlainTextResponse(f"TTS generation failed: {e}", status_code=500)
return FileResponse(mp3_path, media_type="audio/mpeg")
@app.get("/health")
def health():
return JSONResponse({
"status": "ok",
"scripts_txt": SCRIPT_FILE.exists(),
"slides_dir": SLIDES_DIR.exists(),
"static_dir": STATIC_DIR.exists(),
"slide_count": len(read_slide_data())
})