Files
transcript/app/main.py
Julien Calixte 57910462e4 feat: add YouTube cookies upload via web UI
Adds a Settings panel to upload a cookies.txt file directly from the
browser, persisted in a named Docker volume. yt-dlp uses the file
when present to bypass YouTube bot detection.
2026-03-23 19:32:51 +01:00

100 lines
2.6 KiB
Python

import os
from contextlib import asynccontextmanager
from pathlib import Path
from fastapi import FastAPI, HTTPException, BackgroundTasks, UploadFile
from fastapi.responses import FileResponse
from pydantic import BaseModel
from app import downloader
from app.config import settings
STATIC_DIR = Path(__file__).parent / "static"
AUDIO_TMP_DIR = Path("/tmp/apoena-audio")
@asynccontextmanager
async def lifespan(app: FastAPI):
AUDIO_TMP_DIR.mkdir(parents=True, exist_ok=True)
yield
app = FastAPI(title="apoena-transcript", lifespan=lifespan)
class ExtractAudioRequest(BaseModel):
url: str
@app.get("/health")
async def health():
return {"status": "ok"}
@app.get("/worker.js")
async def worker_js():
return FileResponse(STATIC_DIR / "worker.js", media_type="application/javascript")
@app.get("/sw.js")
async def sw_js():
return FileResponse(STATIC_DIR / "sw.js", media_type="application/javascript")
@app.get("/manifest.json")
async def manifest():
return FileResponse(STATIC_DIR / "manifest.json", media_type="application/manifest+json")
@app.get("/icon.svg")
async def icon():
return FileResponse(STATIC_DIR / "icon.svg", media_type="image/svg+xml")
@app.get("/icon-maskable.svg")
async def icon_maskable():
return FileResponse(STATIC_DIR / "icon-maskable.svg", media_type="image/svg+xml")
@app.post("/extract-audio")
async def extract_audio(body: ExtractAudioRequest, background_tasks: BackgroundTasks):
try:
audio_path = await downloader.extract_audio(body.url)
except RuntimeError as e:
raise HTTPException(status_code=422, detail=str(e))
background_tasks.add_task(_delete_file, audio_path)
return FileResponse(audio_path, media_type="audio/mpeg", filename="audio.mp3")
def _delete_file(path):
try:
os.unlink(path)
except OSError:
pass
@app.post("/admin/cookies")
async def upload_cookies(file: UploadFile):
if not settings.yt_dlp_cookies_file:
raise HTTPException(status_code=500, detail="YT_DLP_COOKIES_FILE not configured")
cookies_path = Path(settings.yt_dlp_cookies_file)
cookies_path.parent.mkdir(parents=True, exist_ok=True)
content = await file.read()
cookies_path.write_bytes(content)
return {"status": "ok", "path": str(cookies_path)}
@app.get("/admin/cookies/status")
async def cookies_status():
if not settings.yt_dlp_cookies_file:
return {"present": False}
path = Path(settings.yt_dlp_cookies_file)
return {"present": path.exists(), "size": path.stat().st_size if path.exists() else 0}
@app.get("/")
async def index():
return FileResponse(STATIC_DIR / "index.html")