CoolFace
Apppublic

wpms/w

sourceHugging Faceupdated 2mo agoView on Hugging Face
0likes
app.py199 linesDownload Raw Back to root
1import os2import uuid3import asyncio4import subprocess5import shutil6from fastapi import FastAPI, Query, HTTPException7from fastapi.responses import FileResponse8from fastapi.middleware.cors import CORSMiddleware9import logging10 11logging.basicConfig(level=logging.INFO)12logger = logging.getLogger(__name__)13 14app = FastAPI(title="M3U8 to MP4 Converter", version="1.0.0")15 16app.add_middleware(17    CORSMiddleware,18    allow_origins=["*"],19    allow_credentials=True,20    allow_methods=["*"],21    allow_headers=["*"],22)23 24TEMP_DIR = "/tmp/m3u8_conversions"25os.makedirs(TEMP_DIR, exist_ok=True)26 27active_conversions = {}28 29 30def cleanup_files(file_paths):31    for path in file_paths:32        try:33            if os.path.exists(path):34                if os.path.isdir(path):35                    shutil.rmtree(path)36                else:37                    os.remove(path)38                logger.info(f"Cleaned up: {path}")39        except Exception as e:40            logger.error(f"Cleanup error for {path}: {e}")41 42 43def get_ffmpeg_path():44    ffmpeg_path = shutil.which("ffmpeg")45    if ffmpeg_path:46        return ffmpeg_path47    for path in ["/usr/bin/ffmpeg", "/usr/local/bin/ffmpeg", "/app/ffmpeg", "./ffmpeg"]:48        if os.path.exists(path):49            return path50    return "ffmpeg"51 52 53async def convert_m3u8_to_mp4(m3u8_url: str, output_path: str, task_id: str):54    ffmpeg = get_ffmpeg_path()55    56    cmd = [57        ffmpeg,58        "-i", m3u8_url,59        "-c", "copy",60        "-bsf:a", "aac_adtstoasc",61        "-movflags", "+faststart",62        "-y",63        output_path64    ]65    66    cmd_fallback = [67        ffmpeg,68        "-i", m3u8_url,69        "-c:v", "libx264",70        "-preset", "fast",71        "-crf", "23",72        "-c:a", "aac",73        "-b:a", "128k",74        "-movflags", "+faststart",75        "-y",76        output_path77    ]78    79    logger.info(f"Starting conversion: {task_id}")80    logger.info(f"Command: {' '.join(cmd)}")81    82    active_conversions[task_id] = {"status": "converting", "progress": 0}83    84    try:85        process = await asyncio.create_subprocess_exec(86            *cmd,87            stdout=asyncio.subprocess.PIPE,88            stderr=asyncio.subprocess.PIPE89        )90        91        stdout, stderr = await process.communicate()92        93        if process.returncode != 0:94            logger.warning(f"Copy mode failed, trying re-encode mode: {stderr.decode()[:500]}")95            active_conversions[task_id]["status"] = "re-encoding"96            97            process = await asyncio.create_subprocess_exec(98                *cmd_fallback,99                stdout=asyncio.subprocess.PIPE,100                stderr=asyncio.subprocess.PIPE101            )102            stdout, stderr = await process.communicate()103            104            if process.returncode != 0:105                raise Exception(f"Conversion failed: {stderr.decode()[:1000]}")106        107        active_conversions[task_id]["status"] = "completed"108        logger.info(f"Conversion completed: {task_id}")109        110    except Exception as e:111        active_conversions[task_id]["status"] = "failed"112        active_conversions[task_id]["error"] = str(e)113        logger.error(f"Conversion error: {e}")114        raise115 116 117@app.get("/")118def root():119    return {120        "message": "M3U8 to MP4 Converter API",121        "usage": "/video?url=<m3u8-url>&format=mp4",122        "endpoints": {123            "convert": "/video?url=<m3u8-url>&format=mp4",124            "status": "/status/<task_id>"125        }126    }127 128 129@app.get("/video")130async def convert_video(131    url: str = Query(..., description="M3U8 URL to convert"),132    format: str = Query("mp4", description="Output format (mp4 only)")133):134    if not url:135        raise HTTPException(status_code=400, detail="URL parameter is required")136    137    if not url.startswith(("http://", "https://")):138        raise HTTPException(status_code=400, detail="Invalid URL format")139    140    task_id = str(uuid.uuid4())141    output_filename = f"video_{task_id}.mp4"142    output_path = os.path.join(TEMP_DIR, output_filename)143    144    try:145        await convert_m3u8_to_mp4(url, output_path, task_id)146        147        if not os.path.exists(output_path):148            raise HTTPException(status_code=500, detail="Conversion failed - output file not created")149        150        file_size = os.path.getsize(output_path)151        logger.info(f"File size: {file_size} bytes")152        153        response = FileResponse(154            path=output_path,155            media_type="video/mp4",156            filename=f"converted_video_{task_id}.mp4",157            headers={158                "Content-Disposition": f'attachment; filename="converted_video_{task_id}.mp4"'159            }160        )161        162        @response.background163        def cleanup():164            cleanup_files([output_path])165        166        return response167        168    except HTTPException:169        cleanup_files([output_path])170        raise171    except Exception as e:172        cleanup_files([output_path])173        logger.error(f"Error: {e}")174        raise HTTPException(status_code=500, detail=f"Conversion error: {str(e)}")175 176 177@app.get("/status/{task_id}")178def get_status(task_id: str):179    if task_id not in active_conversions:180        return {"status": "not_found"}181    return active_conversions[task_id]182 183 184@app.get("/health")185def health_check():186    ffmpeg = get_ffmpeg_path()187    ffmpeg_available = os.path.exists(ffmpeg) or shutil.which("ffmpeg") is not None188    return {189        "status": "healthy",190        "ffmpeg_available": ffmpeg_available,191        "ffmpeg_path": ffmpeg192    }193 194 195if __name__ == "__main__":196    import uvicorn197    port = int(os.environ.get("PORT", 7860))198    uvicorn.run(app, host="0.0.0.0", port=port)199