CoolFace
Apppublic

Igbalode/mp4-mp3

sourceHugging Faceupdated 1y agoView on Hugging Face
0likes
app.py192 linesDownload Raw Back to root
1"""2FastAPI service that:31. Accepts any public video URL (POST /convert)42. Converts it to MP3 using yt-dlp + FFmpeg53. Returns a permanent MP3 URL64. Provides a tiny HTML page (GET /download)75. Generates a customizable QR code with a ✔️ icon embedded8"""9 10import logging11import os12import uuid13import tempfile14from io import BytesIO15from pathlib import Path16from urllib.parse import quote_plus17from typing import Optional18 19import httpx20import qrcode21from qrcode.image.styledpil import StyledPilImage22from qrcode.image.styles.moduledrawers import RoundedModuleDrawer23from qrcode.image.styles.colormasks import SolidFillColorMask24from PIL import Image, ImageDraw25from fastapi import FastAPI, HTTPException, Query26from fastapi.responses import HTMLResponse, RedirectResponse, StreamingResponse27from fastapi.staticfiles import StaticFiles28from pydantic import BaseModel, HttpUrl29 30logging.basicConfig(level=logging.INFO)31logger = logging.getLogger("uvicorn")32 33STATIC_DIR = Path(__file__).with_suffix("").parent / "static"34STATIC_DIR.mkdir(exist_ok=True)35 36servers = [{"url": "https://igbalode-mp4-mp3.hf.space"}]37 38app = FastAPI(39    title="Video-to-MP3 API",40    description="Convert any public video to MP3 and grab it via a download page.",41    version="1.5.0",42    servers=servers,43)44 45app.mount("/static", StaticFiles(directory=STATIC_DIR), name="static")46 47 48class ConvertRequest(BaseModel):49    url: HttpUrl50 51 52class ConvertResponse(BaseModel):53    mp3_url: str54    filename: str55 56 57# ---------- Downloader ----------58def _download_audio(url: str) -> bytes:59    """Use yt-dlp to pull the best audio stream."""60    ydl_opts = {61        "format": "bestaudio/best",62        "outtmpl": "-",63        "quiet": True,64        "noplaylist": True,65    }66    with yt_dlp.YoutubeDL(ydl_opts) as ydl:67        info = ydl.extract_info(url, download=False)68        audio_url = info["url"]69        r = httpx.get(audio_url, timeout=120, follow_redirects=True)70        r.raise_for_status()71        if len(r.content) > 2_000_000_000:72            raise HTTPException(413, "Audio stream too large (>2 GB)")73        return r.content74 75 76# ---------- Converter ----------77def _convert_to_mp3(audio_bytes: bytes) -> bytes:78    import subprocess79    with tempfile.NamedTemporaryFile(delete=False, suffix=".webm") as tmp_in, \80         tempfile.NamedTemporaryFile(delete=False, suffix=".mp3") as tmp_out:81        tmp_in.write(audio_bytes)82        tmp_in.flush()83        subprocess.run(84            ["ffmpeg", "-i", tmp_in.name, "-vn", "-acodec", "libmp3lame", "-q:a", "2", tmp_out.name],85            check=True,86            stdout=subprocess.PIPE,87            stderr=subprocess.PIPE,88        )89        mp3_bytes = Path(tmp_out.name).read_bytes()90    Path(tmp_in.name).unlink(missing_ok=True)91    Path(tmp_out.name).unlink(missing_ok=True)92    return mp3_bytes93 94 95# ---------- Main endpoint ----------96@app.post("/convert", response_model=ConvertResponse)97def convert_video(req: ConvertRequest):98    logger.info("Convert request: %s", str(req.url))99    try:100        audio_bytes = _download_audio(str(req.url))101    except Exception as e:102        logger.exception("Download error")103        raise HTTPException(400, f"Download error: {e}")104 105    try:106        mp3_bytes = _convert_to_mp3(audio_bytes)107    except Exception as e:108        logger.exception("Conversion error")109        raise HTTPException(500, f"Conversion failed: {e}")110 111    filename = f"{uuid.uuid4().hex}.mp3"112    (STATIC_DIR / filename).write_bytes(mp3_bytes)113 114    mp3_url = f"https://igbalode-mp4-mp3.hf.space/static/{filename}"115    return ConvertResponse(mp3_url=mp3_url, filename=filename)116 117 118# ---------- Download page ----------119@app.get("/download", response_class=HTMLResponse)120def get_download_page(url: str = Query(..., description="Public MP3 URL")):121    safe_url = quote_plus(url, safe="/:")122    html = f"""123    <!DOCTYPE html>124    <html lang="en">125    <head>126        <meta charset="UTF-8"/>127        <title>Download MP3</title>128        <style>129            body{{font-family:system-ui,sans-serif;background:#f5f5f5;text-align:center;padding-top:10vh}}130            h1{{margin-bottom:1rem}}131            audio{{width:90%;max-width:400px}}132            .btn{{display:inline-block;margin-top:1rem;padding:.75rem 1.5rem;background:#0d6efd;color:white;border-radius:6px;text-decoration:none;font-weight:bold}}133            .btn:hover{{background:#0b5ed7}}134        </style>135    </head>136    <body>137        <h1>Your MP3 is ready</h1>138        <audio controls>139            <source src="{safe_url}" type="audio/mpeg"/>140        </audio><br/>141        <a class="btn" href="{safe_url}" download>Download MP3</a>142    </body>143    </html>144    """145    return html146 147 148# ---------- QR code with embedded ✔️ icon ----------149@app.get("/qr")150def qr_code(151    url: str = Query(..., description="Public MP3 URL"),152    size: int = Query(10, ge=4, le=40, description="Module pixel size"),153    border: int = Query(4, ge=0, le=20, description="Border thickness"),154    fg: str = Query("black", description="Foreground color"),155    bg: str = Query("white", description="Background color"),156):157    """PNG QR code with rounded modules and a ✔️ icon in center."""158    qr = qrcode.QRCode(159        error_correction=qrcode.constants.ERROR_CORRECT_H,160        box_size=size,161        border=border,162    )163    qr.add_data(url)164    qr.make(fit=True)165 166    # Base QR167    img = qr.make_image(168        image_factory=StyledPilImage,169        module_drawer=RoundedModuleDrawer(),170        color_mask=SolidFillColorMask(front_color=fg, back_color=bg),171    )172 173    # Add tiny ✔️ icon in center174    icon_size = int(img.size[0] * 0.18)175    icon = Image.new("RGBA", (icon_size, icon_size), (0, 0, 0, 0))176    draw = ImageDraw.Draw(icon)177    # Simple green tick178    draw.line([(0, icon_size // 2), (icon_size // 3, icon_size)], fill="green", width=3)179    draw.line([(icon_size // 3, icon_size), (icon_size, 0)], fill="green", width=3)180 181    pos = ((img.size[0] - icon_size) // 2, (img.size[1] - icon_size) // 2)182    img.paste(icon, pos, icon)183 184    buf = BytesIO()185    img.save(buf, format="PNG")186    buf.seek(0)187    return StreamingResponse(buf, media_type="image/png")188 189 190@app.get("/", include_in_schema=False)191def root():192    return RedirectResponse("/docs")