mp4 / app.py
CineMax's picture
Update app.py
9ceb596 verified
Raw
History Blame Contribute Delete
3.91 kB
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}/*")
# Detectar TODAS las pistas de audio
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 # fallback
# Construir -map dinámicamente para todas las pistas
mapeos = ["-map", "0:v:0"]
for i in range(num_audios):
mapeos += ["-map", f"0:a:{i}"]
# var_stream_map: video va con audio 0, resto son pistas independientes
# Nombres de idioma genéricos si no sabemos cuáles son
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", # VOD real: escribe #EXT-X-ENDLIST al terminar
"-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 está listo cuando FFmpeg terminó Y el m3u8 tiene #EXT-X-ENDLIST
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)