import os import uuid import uvicorn from fastapi import FastAPI, HTTPException, BackgroundTasks from fastapi.responses import FileResponse from pydantic import BaseModel from yt_dlp import YoutubeDL app = FastAPI() # Input schema because we aren't savages class VideoRequest(BaseModel): url: str def cleanup(path: str): """Deletes the file after serving because I know you won't.""" try: os.remove(path) print(f"Deleted trash: {path}") except Exception as e: print(f"Failed to delete {path}: {e}") @app.post("/audio") async def download_audio(req: VideoRequest, background_tasks: BackgroundTasks): # Random ID so concurrent requests don't overwrite each other file_id = str(uuid.uuid4()) output_template = f"/tmp/{file_id}.%(ext)s" # yt-dlp config: best audio, force mp3 because whisper hates weird codecs ydl_opts = { 'format': 'bestaudio/best', 'outtmpl': output_template, 'postprocessors': [{ 'key': 'FFmpegExtractAudio', 'preferredcodec': 'mp3', 'preferredquality': '192', }], 'quiet': True, 'no_warnings': True, } try: with YoutubeDL(ydl_opts) as ydl: print(f"Yoinking audio from: {req.url}") info = ydl.extract_info(req.url, download=True) # yt-dlp might change extension post-processing, so we find the actual file # usually it's .mp3 if we forced it, but let's be safe filename = ydl.prepare_filename(info).rsplit('.', 1)[0] + '.mp3' if not os.path.exists(filename): raise HTTPException(status_code=500, detail="Download failed, file missing. Blame YouTube.") # queue the deletion so it happens AFTER the response is sent background_tasks.add_task(cleanup, filename) return FileResponse( path=filename, filename=f"{file_id}.mp3", media_type='audio/mpeg' ) except Exception as e: print(f"L: {str(e)}") raise HTTPException(status_code=400, detail=f"Failed to download: {str(e)}") if __name__ == "__main__": # Host on 0.0.0.0 so your docker container isn't useless uvicorn.run(app, host="0.0.0.0", port=8000)