File size: 6,922 Bytes
4aed5f4
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
29e37f2
4aed5f4
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
29e37f2
4aed5f4
29e37f2
 
 
4aed5f4
29e37f2
4aed5f4
29e37f2
 
 
 
 
 
 
 
 
 
 
 
 
4aed5f4
 
 
 
 
 
 
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
96
97
98
99
100
101
102
103
104
105
106
107
108
109
110
111
112
113
114
115
116
117
118
119
120
121
122
123
124
125
126
127
128
129
130
131
132
133
134
135
136
137
138
139
140
141
142
143
144
145
146
147
148
149
"""
WHAT: The main entry point for the Multiverse AI Studio FastAPI application.
WHY: This file bootstraps the entire web server. It configures Cross-Origin Resource Sharing (CORS), 
     mounts physical filesystem directories to virtual web URLs, registers the API routes, 
     and handles server lifecycle hooks (such as creating storage folders on startup).
HOW: It instantiates a `FastAPI` application, applies standard middlewares (CORSMiddleware), 
     mounts `StaticFiles`, and registers the router imported from `api/routes.py`.
"""

import os
from dotenv import load_dotenv

# WHY: Load .env variables *before* importing config.py. That way,
# os.getenv("HF_TOKEN") calls in config.py will find the loaded token.
load_dotenv()

from contextlib import asynccontextmanager
from fastapi import FastAPI
from fastapi.middleware.cors import CORSMiddleware
from fastapi.staticfiles import StaticFiles
from fastapi.responses import FileResponse

# Import configurations and routers (relative to the backend package)
from .config import OUTPUT_DIR
from .api.routes import router as api_router

@asynccontextmanager
async def lifespan(app: FastAPI):
    """
    WHAT: A lifecyle manager handling startup and shutdown operations for the application.
    WHY: Before the server begins accepting client requests, we must guarantee that the local 
         filesystem is ready to receive generated assets. If we don't create the OUTPUT_DIR, 
         subsequent file write operations will throw directory-not-found errors.
    HOW: Uses Python's `contextlib.asynccontextmanager` decorator. Everything before the `yield` 
         runs on startup; everything after runs on shutdown.
    """
    # STARTUP: Create the asset output folder if it doesn't exist yet
    if not os.path.exists(OUTPUT_DIR):
        print(f"[Startup] Creating directory for generated assets: {OUTPUT_DIR}")
        os.makedirs(OUTPUT_DIR, exist_ok=True)
    else:
        print(f"[Startup] Output directory already exists: {OUTPUT_DIR}")
        
    yield  # The application runs and processes requests here
    
    # SHUTDOWN: Any cleanup logic (e.g., closing DB pools or freeing remaining models) goes here.
    print("[Shutdown] Cleaning up API resources.")


# Initialize the FastAPI application
# Using the lifespan parameter to handle our startup directory creation logic cleanly
app = FastAPI(
    title="Multiverse AI Studio API",
    description=(
        "A production-quality generative AI pipeline backend that chains five Hugging Face models: "
        "Prompt Expansion (LLM), Image Generation (SDXL), Depth Estimation (Depth-Anything), "
        "Audio Generation (MusicGen), and Video Generation (i2vgen-xl)."
    ),
    version="1.0.0",
    lifespan=lifespan
)


# ==========================================
# CORS CONFIGURATION (Cross-Origin Resource Sharing)
# ==========================================
# WHAT: Specifies which web domains are permitted to communicate with this backend API.
# WHY: By default, web browsers block frontend code (e.g., running on localhost:3000) from 
#      making requests to a different domain/port (e.g., running on localhost:8000). 
#      Configuring CORS allows our Next.js/React frontend to talk to our FastAPI backend safely.
# HOW: We use FastAPI's built-in CORSMiddleware and supply an explicit list of allowed origins.
origins = [
    "http://localhost:3000",      # Standard React / Next.js development server
    "http://127.0.0.1:3000",    # Alternative local address
]

app.add_middleware(
    CORSMiddleware,
    allow_origins=origins,
    allow_credentials=True,
    allow_methods=["*"],          # Allows all HTTP methods (GET, POST, OPTIONS, etc.)
    allow_headers=["*"],          # Allows all HTTP headers (Content-Type, Authorization, etc.)
)


# ==========================================
# STATIC FILES MOUNTING
# ==========================================
# WHAT: Maps a directory on the server's hard drive to a virtual web route.
# WHY: When we save a generated video to `generated_assets/job_123/video.mp4`, we need a way 
#      for the frontend to download and display it. Instead of writing custom download endpoints, 
#      FastAPI can serve everything in this folder statically.
# HOW: We mount the `OUTPUT_DIR` to the path `/assets`. 
#      Example: Opening `http://localhost:8000/assets/123/image.png` will load the file 
#               stored at `generated_assets/123/image.png` on disk.
# WHY ORDER MATTERS: FastAPI's StaticFiles will crash immediately if the directory doesn't exist 
# at mount time (which happens *before* the lifespan function runs). So we create the directory first.
if not os.path.exists(OUTPUT_DIR):
    os.makedirs(OUTPUT_DIR, exist_ok=True)
app.mount(
    "/assets", 
    StaticFiles(directory=OUTPUT_DIR), 
    name="assets"
)


# ==========================================
# API ROUTE REGISTRATION
# ==========================================
# WHAT: Registers our logical endpoints under a standard url path.
# WHY: Keeps code modular by keeping route definitions inside `api/routes.py` rather than 
#      cluttering `main.py`.
# HOW: We prefix all routes with `/api` so that endpoints become `/api/generate`, `/api/status`, etc.
app.include_router(
    api_router, 
    prefix="/api"
)

# ==========================================
# FRONTEND STATIC FILES MOUNTING (FOR MONOLITHIC PRODUCTION DEPLOYMENT)
# ==========================================
# WHAT: Serves the built React frontend bundle.
# WHY: In platforms like Hugging Face Spaces, the container can only expose a single port (7860).
#      FastAPI serves the static HTML/JS/CSS React bundle and handles API requests on "/api".
# HOW: We mount the bundled assets under "/static" and serve "index.html" for both "/" and any
#      unknown client-side route (SPA fallback), so deep links like /studio/<jobId> survive a refresh.
FRONTEND_DIST_DIR = os.path.join(os.path.dirname(os.path.dirname(__file__)), "frontend", "dist")
FRONTEND_INDEX = os.path.join(FRONTEND_DIST_DIR, "index.html")
if os.path.exists(FRONTEND_DIST_DIR):
    print(f"[Startup] Serving frontend static files from: {FRONTEND_DIST_DIR}")
    frontend_static_dir = os.path.join(FRONTEND_DIST_DIR, "static")
    if os.path.isdir(frontend_static_dir):
        app.mount(
            "/static",
            StaticFiles(directory=frontend_static_dir),
            name="frontend_static",
        )

    @app.get("/{full_path:path}")
    async def serve_spa(full_path: str):
        # Unknown routes fall back to index.html so client-side (React Router) routing works.
        return FileResponse(FRONTEND_INDEX)
else:
    print("[Startup Warning] Frontend build directory not found. Server running API-only mode.")


# Standard run instruction comment
# To run this server, execute the following command in your terminal from the backend directory:
# `uvicorn main:app --host 0.0.0.0 --port 8000 --reload`