Download app.py from wpms/Discord: direct link, hf CLI and curl.
- Browser
- Download file 5.7 kB
-
https://huggingface.co/spaces/wpms/Discord/resolve/main/app.py
- Command line
-
hf download hf://spaces/wpms/Discord/app.py
-
curl -L -o app.py https://huggingface.co/spaces/wpms/Discord/resolve/main/app.py
5.7 kB
| import os | |
| import uuid | |
| import asyncio | |
| import subprocess | |
| import shutil | |
| from fastapi import FastAPI, Query, HTTPException | |
| from fastapi.responses import FileResponse | |
| from fastapi.middleware.cors import CORSMiddleware | |
| import logging | |
| logging.basicConfig(level=logging.INFO) | |
| logger = logging.getLogger(__name__) | |
| app = FastAPI(title="M3U8 to MP4 Converter", version="1.0.0") | |
| app.add_middleware( | |
| CORSMiddleware, | |
| allow_origins=["*"], | |
| allow_credentials=True, | |
| allow_methods=["*"], | |
| allow_headers=["*"], | |
| ) | |
| TEMP_DIR = "/tmp/m3u8_conversions" | |
| os.makedirs(TEMP_DIR, exist_ok=True) | |
| active_conversions = {} | |
| def cleanup_files(file_paths): | |
| for path in file_paths: | |
| try: | |
| if os.path.exists(path): | |
| if os.path.isdir(path): | |
| shutil.rmtree(path) | |
| else: | |
| os.remove(path) | |
| logger.info(f"Cleaned up: {path}") | |
| except Exception as e: | |
| logger.error(f"Cleanup error for {path}: {e}") | |
| def get_ffmpeg_path(): | |
| ffmpeg_path = shutil.which("ffmpeg") | |
| if ffmpeg_path: | |
| return ffmpeg_path | |
| for path in ["/usr/bin/ffmpeg", "/usr/local/bin/ffmpeg", "/app/ffmpeg", "./ffmpeg"]: | |
| if os.path.exists(path): | |
| return path | |
| return "ffmpeg" | |
| async def convert_m3u8_to_mp4(m3u8_url: str, output_path: str, task_id: str): | |
| ffmpeg = get_ffmpeg_path() | |
| cmd = [ | |
| ffmpeg, | |
| "-i", m3u8_url, | |
| "-c", "copy", | |
| "-bsf:a", "aac_adtstoasc", | |
| "-movflags", "+faststart", | |
| "-y", | |
| output_path | |
| ] | |
| cmd_fallback = [ | |
| ffmpeg, | |
| "-i", m3u8_url, | |
| "-c:v", "libx264", | |
| "-preset", "fast", | |
| "-crf", "23", | |
| "-c:a", "aac", | |
| "-b:a", "128k", | |
| "-movflags", "+faststart", | |
| "-y", | |
| output_path | |
| ] | |
| logger.info(f"Starting conversion: {task_id}") | |
| logger.info(f"Command: {' '.join(cmd)}") | |
| active_conversions[task_id] = {"status": "converting", "progress": 0} | |
| try: | |
| process = await asyncio.create_subprocess_exec( | |
| *cmd, | |
| stdout=asyncio.subprocess.PIPE, | |
| stderr=asyncio.subprocess.PIPE | |
| ) | |
| stdout, stderr = await process.communicate() | |
| if process.returncode != 0: | |
| logger.warning(f"Copy mode failed, trying re-encode mode: {stderr.decode()[:500]}") | |
| active_conversions[task_id]["status"] = "re-encoding" | |
| process = await asyncio.create_subprocess_exec( | |
| *cmd_fallback, | |
| stdout=asyncio.subprocess.PIPE, | |
| stderr=asyncio.subprocess.PIPE | |
| ) | |
| stdout, stderr = await process.communicate() | |
| if process.returncode != 0: | |
| raise Exception(f"Conversion failed: {stderr.decode()[:1000]}") | |
| active_conversions[task_id]["status"] = "completed" | |
| logger.info(f"Conversion completed: {task_id}") | |
| except Exception as e: | |
| active_conversions[task_id]["status"] = "failed" | |
| active_conversions[task_id]["error"] = str(e) | |
| logger.error(f"Conversion error: {e}") | |
| raise | |
| def root(): | |
| return { | |
| "message": "M3U8 to MP4 Converter API", | |
| "usage": "/video?url=<m3u8-url>&format=mp4", | |
| "endpoints": { | |
| "convert": "/video?url=<m3u8-url>&format=mp4", | |
| "status": "/status/<task_id>" | |
| } | |
| } | |
| async def convert_video( | |
| url: str = Query(..., description="M3U8 URL to convert"), | |
| format: str = Query("mp4", description="Output format (mp4 only)") | |
| ): | |
| if not url: | |
| raise HTTPException(status_code=400, detail="URL parameter is required") | |
| if not url.startswith(("http://", "https://")): | |
| raise HTTPException(status_code=400, detail="Invalid URL format") | |
| task_id = str(uuid.uuid4()) | |
| output_filename = f"video_{task_id}.mp4" | |
| output_path = os.path.join(TEMP_DIR, output_filename) | |
| try: | |
| await convert_m3u8_to_mp4(url, output_path, task_id) | |
| if not os.path.exists(output_path): | |
| raise HTTPException(status_code=500, detail="Conversion failed - output file not created") | |
| file_size = os.path.getsize(output_path) | |
| logger.info(f"File size: {file_size} bytes") | |
| response = FileResponse( | |
| path=output_path, | |
| media_type="video/mp4", | |
| filename=f"converted_video_{task_id}.mp4", | |
| headers={ | |
| "Content-Disposition": f'attachment; filename="converted_video_{task_id}.mp4"' | |
| } | |
| ) | |
| def cleanup(): | |
| cleanup_files([output_path]) | |
| return response | |
| except HTTPException: | |
| cleanup_files([output_path]) | |
| raise | |
| except Exception as e: | |
| cleanup_files([output_path]) | |
| logger.error(f"Error: {e}") | |
| raise HTTPException(status_code=500, detail=f"Conversion error: {str(e)}") | |
| def get_status(task_id: str): | |
| if task_id not in active_conversions: | |
| return {"status": "not_found"} | |
| return active_conversions[task_id] | |
| def health_check(): | |
| ffmpeg = get_ffmpeg_path() | |
| ffmpeg_available = os.path.exists(ffmpeg) or shutil.which("ffmpeg") is not None | |
| return { | |
| "status": "healthy", | |
| "ffmpeg_available": ffmpeg_available, | |
| "ffmpeg_path": ffmpeg | |
| } | |
| if __name__ == "__main__": | |
| import uvicorn | |
| port = int(os.environ.get("PORT", 7860)) | |
| uvicorn.run(app, host="0.0.0.0", port=port) | |