| import subprocess |
| import os |
| import uvicorn |
| from fastapi import FastAPI |
| from fastapi.staticfiles import StaticFiles |
| from fastapi.middleware.cors import CORSMiddleware |
|
|
| app = FastAPI() |
|
|
| app.add_middleware( |
| CORSMiddleware, |
| allow_origins=["*"], |
| allow_methods=["*"], |
| allow_headers=["*"], |
| ) |
|
|
| DIRECTORIO_HLS = "/tmp/hls_vod" |
| os.makedirs(DIRECTORIO_HLS, exist_ok=True) |
|
|
| app.mount("/hls", StaticFiles(directory=DIRECTORIO_HLS), name="hls") |
|
|
| proceso_ffmpeg = None |
|
|
|
|
| @app.get("/iniciar-transmision") |
| def iniciar_transmision(url: str): |
| global proceso_ffmpeg |
|
|
| if proceso_ffmpeg and proceso_ffmpeg.poll() is None: |
| proceso_ffmpeg.terminate() |
| proceso_ffmpeg.wait() |
|
|
| os.system(f"rm -rf {DIRECTORIO_HLS}/*") |
|
|
| |
| probe = subprocess.run( |
| ["ffprobe", "-v", "error", "-select_streams", "a", |
| "-show_entries", "stream=index", "-of", "csv=p=0", url], |
| capture_output=True, text=True, timeout=15 |
| ) |
| pistas_audio = [l for l in probe.stdout.strip().splitlines() if l.strip()] |
| num_audios = len(pistas_audio) |
| if num_audios == 0: |
| num_audios = 1 |
|
|
| |
| mapeos = ["-map", "0:v:0"] |
| for i in range(num_audios): |
| mapeos += ["-map", f"0:a:{i}"] |
|
|
| |
| |
| nombres = ["audio1", "audio2", "audio3", "audio4"] |
| stream_map_parts = ["v:0,a:0,name:" + nombres[0]] |
| for i in range(1, num_audios): |
| stream_map_parts.append(f"a:{i},name:{nombres[i]}") |
| var_stream_map = " ".join(stream_map_parts) |
|
|
| comando = [ |
| "ffmpeg", "-y", |
| "-i", url, |
| *mapeos, |
| "-c:v", "libx264", "-preset", "veryfast", "-crf", "23", |
| "-profile:v", "baseline", "-level", "3.0", |
| "-g", "48", "-keyint_min", "48", "-sc_threshold", "0", |
| "-c:a", "aac", "-b:a", "128k", "-ac", "2", |
| "-f", "hls", |
| "-hls_time", "6", |
| "-hls_list_size", "0", |
| "-hls_playlist_type", "vod", |
| "-hls_flags", "independent_segments", |
| "-hls_segment_type", "mpegts", |
| "-hls_segment_filename", f"{DIRECTORIO_HLS}/seg_%v_%03d.ts", |
| "-master_pl_name", "master.m3u8", |
| "-var_stream_map", var_stream_map, |
| f"{DIRECTORIO_HLS}/playlist_%v.m3u8" |
| ] |
|
|
| proceso_ffmpeg = subprocess.Popen( |
| comando, |
| stdout=subprocess.DEVNULL, |
| stderr=open("/tmp/ffmpeg.log", "w") |
| ) |
|
|
| return { |
| "estado": "Procesando", |
| "audios_detectados": num_audios, |
| "url_master": "https://cinemax-mp4.hf.space/hls/master.m3u8", |
| "url_directa": "https://cinemax-mp4.hf.space/hls/playlist_0.m3u8", |
| } |
|
|
|
|
| @app.get("/listo") |
| def listo(): |
| """El PHP hace polling aquí hasta que FFmpeg termina.""" |
| global proceso_ffmpeg |
| if proceso_ffmpeg is None: |
| return {"listo": False, "razon": "Sin proceso"} |
|
|
| activo = proceso_ffmpeg.poll() is None |
| segmentos = len([f for f in os.listdir(DIRECTORIO_HLS) if f.endswith(".ts")]) |
|
|
| |
| vod_listo = False |
| playlist = f"{DIRECTORIO_HLS}/playlist_0.m3u8" |
| if not activo and os.path.exists(playlist): |
| with open(playlist) as f: |
| vod_listo = "#EXT-X-ENDLIST" in f.read() |
|
|
| return { |
| "listo": vod_listo, |
| "ffmpeg_activo": activo, |
| "segmentos": segmentos |
| } |
|
|
|
|
| @app.get("/detener") |
| def detener(): |
| global proceso_ffmpeg |
| if proceso_ffmpeg and proceso_ffmpeg.poll() is None: |
| proceso_ffmpeg.terminate() |
| return {"estado": "Detenido"} |
| return {"estado": "No había proceso activo"} |
|
|
|
|
| if __name__ == "__main__": |
| uvicorn.run(app, host="0.0.0.0", port=7860) |