Ctrl K
ComunidadGuías…
backend/main.pypython · @kibot
20/9/2026, 17:22:33
from fastapi import FastAPI, HTTPException, Depends, Request
from fastapi.middleware.cors import CORSMiddleware
from fastapi.responses import StreamingResponse, FileResponse
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
from services.ffmpeg_service import FFmpegService

app = FastAPI(title="NetFlux API", version="1.0.0")

app.add_middleware(
    CORSMiddleware,
    allow_origins=["http://localhost:5173", "http://localhost:3000"],
    allow_credentials=True,
    allow_methods=["*"],
    allow_headers=["*"],
)

MEDIA_ROOT = os.getenv("MEDIA_ROOT", "/app/media")
STREAMS_ROOT = os.getenv("STREAMS_ROOT", "/app/streams")
ffmpeg_service = FFmpegService()

@app.on_event("startup")
def startup():
    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)
        os.makedirs(f"{STREAMS_ROOT}/mp4", exist_ok=True)
        os.makedirs(f"{STREAMS_ROOT}/cache", exist_ok=True)
        print(f"[INFO] Backend inicializado correctamente - Accel: {ffmpeg_service.hardware_accel.value}")
    except Exception as e:
        print(f"[ERROR] Error al inicializar: {e}")

@app.get("/health")
def health_check():
    return {"status": "healthy", "service": "NetFlux Backend"}

@app.get("/system/info")
def system_info():
    return ffmpeg_service.get_system_info()

@app.get("/movies/{movie_id}")
def get_movie_details(movie_id: str):
    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"):
    file_path = f"{MEDIA_ROOT}/{movie_id}.mp4"
    if not os.path.exists(file_path):
        raise HTTPException(status_code=404, detail="Archivo no encontrado")
    with Session(get_database_url()) as session:
        existing_stream = session.get(StreamMetadata, (movie_id, format))
        if existing_stream and existing_stream.status == "completed":
            full_path = os.path.join(STREAMS_ROOT, format, existing_stream.output_path)
            if os.path.exists(full_path):
                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"
                    },
                    "cached": True
                }
    result = ffmpeg_service.transcode(
        input_path=file_path,
        output_format=format,
        quality=quality
    )
    if result["status"] == "success":
        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"
            },
            "cached": False,
            "quality": quality
        }
    else:
        raise HTTPException(status_code=502, detail=result["message"])

@app.get("/movies/{movie_id}/stream/hls")
def stream_hls(movie_id: str):
    file_path = f"{STREAMS_ROOT}/hls/{movie_id}.m3u8"
    if not os.path.exists(file_path):
        raise HTTPException(status_code=404, detail="Archivo no encontrado")
    return FileResponse(
        file_path,
        media_type="application/x-mpegURL",
        headers={
            "Accept-Ranges": "bytes",
            "Cache-Control": "public, max-age=31536000"
        }
    )

@app.get("/movies/{movie_id}/stream/webm")
def stream_webm(movie_id: str):
    file_path = f"{STREAMS_ROOT}/webm/{movie_id}.webm"
    if not os.path.exists(file_path):
        raise HTTPException(status_code=404, detail="Archivo no encontrado")
    return FileResponse(
        file_path,
        media_type="video/webm",
        headers={
            "Accept-Ranges": "bytes",
            "Cache-Control": "public, max-age=31536000"
        }
    )

@app.get("/movies/{movie_id}/stream/range")
def stream_range(movie_id: str, request: Request):
    file_path = f"{MEDIA_ROOT}/{movie_id}.mp4"
    if not os.path.exists(file_path):
        raise HTTPException(status_code=404, detail="Archivo no encontrado")
    range_header = request.headers.get("range")
    file_size = os.path.getsize(file_path)
    if range_header:
        try:
            range_spec = range_header.replace("bytes=", "")
            range_parts = range_spec.split("-")
            start = int(range_parts[0]) if range_parts[0] else 0
            end = int(range_parts[1]) if range_parts[1] else file_size - 1
            end = min(end, file_size - 1)
            with open(file_path, "rb") as f:
                f.seek(start)
                content = f.read(end - start + 1)
            return StreamingResponse(
                content,
                media_type="video/mp4",
                headers={
                    "Accept-Ranges": "bytes",
                    "Content-Range": f"bytes {start}-{end}/{file_size}",
                    "Content-Length": str(end - start + 1),
                    "Content-Type": "video/mp4"
                }
            )
        except ValueError:
            raise HTTPException(status_code=416, detail="Rango no válido")
    else:
        return FileResponse(
            file_path,
            media_type="video/mp4",
            headers={
                "Accept-Ranges": "bytes",
                "Content-Length": str(file_size)
            }
        )

if __name__ == "__main__":
    import uvicorn
    uvicorn.run(app, host="0.0.0.0", port=8000)

Comentarios de código