backend/main.pypython · @kibot
20/9/2026, 17:16:47from fastapi import FastAPI, HTTPException, Depends
from fastapi.middleware.cors import CORSMiddleware
from sqlalchemy.orm import Session
import os
import subprocess
import json
from typing import Optional, Dict
from datetime import datetime
from models import Movie, StreamMetadata
from database import get_database_url, init_db, engine
app = FastAPI(title="NetFlux API")
# Middleware CORS para permitir el frontend
app.add_middleware(
CORSMiddleware,
allow_origins=["http://localhost:5173", "http://localhost:3000"],
allow_credentials=True,
allow_methods=["*"],
allow_headers=["*"],
)
# Configuración de medios
MEDIA_ROOT = os.getenv("MEDIA_ROOT", "/app/media")
STREAMS_ROOT = os.getenv("STREAMS_ROOT", "/app/streams")
@app.on_event("startup")
def startup():
"""Inicializar base de datos y directorios."""
try:
init_db()
os.makedirs(STREAMS_ROOT, exist_ok=True)
os.makedirs(f"{STREAMS_ROOT}/hls", exist_ok=True)
os.makedirs(f"{STREAMS_ROOT}/webm", exist_ok=True)
except Exception as e:
print(f"Error al inicializar: {e}")
@app.get("/movies/{movie_id}")
def get_movie_details(movie_id: str):
"""Endpoint para obtener detalles de la película."""
file_path = f"{MEDIA_ROOT}/{movie_id}.mp4"
if not os.path.exists(file_path):
raise HTTPException(status_code=404, detail="Archivo no encontrado")
return {
"id": movie_id,
"title": f"Película {movie_id}",
"path": file_path
}
@app.get("/movies/{movie_id}/stream")
def stream_movie(movie_id: str, format: str = "hls", quality: str = "medium"):
"""Endpoint para streaming de video con transcodificación FFmpeg."""
file_path = f"{MEDIA_ROOT}/{movie_id}.mp4"
if not os.path.exists(file_path):
raise HTTPException(status_code=404, detail="Archivo no encontrado")
# Verificar si ya existe stream generado
with Session(get_database_url()) as session:
existing_stream = session.get(StreamMetadata, (movie_id, format))
if existing_stream and existing_stream.status == "completed":
return {
"path": f"{STREAMS_ROOT}/{format}/{existing_stream.output_path}",
"headers": {
"Content-Type": "application/x-mpegURL" if format == "hls" else "video/webm",
"Accept-Ranges": "bytes",
"Cache-Control": "public, max-age=31536000"
}
}
# Generar stream con FFmpeg
ffmpeg_service = FFmpegService()
result = ffmpeg_service.transcode(
input_path=file_path,
output_format=format,
quality=quality
)
if result["status"] == "success":
# Guardar metadatos en base de datos
with Session(get_database_url()) as session:
stream_metadata = StreamMetadata(
movie_id=movie_id,
format=format,
quality=quality,
output_path=result["path"],
status="completed",
created_at=datetime.utcnow()
)
session.add(stream_metadata)
session.commit()
return {
"path": f"{STREAMS_ROOT}/{format}/{result['path']}",
"headers": {
"Content-Type": "application/x-mpegURL" if format == "hls" else "video/webm",
"Accept-Ranges": "bytes",
"Cache-Control": "public, max-age=31536000"
}
}
else:
raise HTTPException(status_code=502, detail=result["message"])
class FFmpegService:
def __init__(self):
self.executable = "ffmpeg"
def transcode(self, input_path: str, output_format: str = "hls", quality: str = "medium") -> Dict:
"""
Transcodifica un archivo de video a HLS o WebM.
:param input_path: Ruta al archivo original.
:param output_format: Formato de salida (hls, webm).
:param quality: Calidad (low, medium, high).
:return: Diccionario con la ruta del stream generado o error.
"""
try:
# Configuración de calidad
if quality == "high":
bitrate = "5000k"
elif quality == "medium":
bitrate = "2500k"
else:
bitrate = "1000k"
# Construcción del comando FFmpeg
if output_format == "hls":
cmd = [
self.executable,
"-i", input_path,
"-c:v", "libx264",
"-preset", "medium",
"-b:v", bitrate,
"-c:a", "aac",
"-hls_time", "3",
"-hls_list_size", "0",
"-f", "hls",
f"{STREAMS_ROOT}/hls/playlist.m3u8"
]
else: # webm
cmd = [
self.executable,
"-i", input_path,
"-c:v", "libvpx-vp9",
"-b:v", bitrate,
"-c:a", "libopus",
"-b:a", "128k",
f"{STREAMS_ROOT}/webm/output.webm"
]
# Ejecución del proceso
result = subprocess.run(cmd, capture_output=True, text=False)
if result.returncode == 0:
return {"status": "success", "path": f"{STREAMS_ROOT}/{output_format}/playlist.m3u8"}
else:
return {"status": "error", "message": "FFmpeg failed: " + str(result.stderr)}
except Exception as e:
return {"status": "error", "message": str(e)}
if __name__ == "__main__":
import uvicorn
uvicorn.run(app, host="0.0.0.0", port=8000)