| """ |
| Product Showcase Studio — FastAPI backend. |
| Video pipeline aligned with python-backend (KIE Veo, image hosting, merge). |
| """ |
|
|
| from contextlib import asynccontextmanager |
| from pathlib import Path |
| import os |
| import shutil |
|
|
| from fastapi import FastAPI |
| from fastapi.staticfiles import StaticFiles |
| from fastapi.middleware.cors import CORSMiddleware |
|
|
| from api.video_generation import router as video_router |
| from api.seedance_generation import router as seedance_router |
| from api.image_service import router as image_router |
| from api.video_export import router as export_router |
| from api.showcase_prompts import router as showcase_router |
| from api.scraper_routes import router as scraper_router |
| from api.gpt_image_frames import router as gpt_image_router |
| from utils.env_load import load_app_env |
| from utils.public_url import get_public_base_url, get_server_port_str |
| from utils.storage import cleanup_old_files |
|
|
| load_app_env() |
|
|
|
|
| @asynccontextmanager |
| async def lifespan(app: FastAPI): |
| print("Starting Product Showcase API…") |
| os.makedirs("storage/images", exist_ok=True) |
| os.makedirs("storage/videos", exist_ok=True) |
| public = get_public_base_url() |
| print(f"Public URL for callbacks/hosted images: {public}") |
| if not os.getenv("KIE_API_KEY"): |
| if os.getenv("REPLICATE_API_TOKEN") or os.getenv("REPLICATE_API_KEY"): |
| print("Info: KIE_API_KEY not set — Seedance can use Replicate fallback when token is set.") |
| else: |
| print("Warning: KIE_API_KEY not set — KIE video generation will fail (set REPLICATE_API_TOKEN for Seedance fallback).") |
| if not os.getenv("OPENAI_API_KEY"): |
| print("Info: OPENAI_API_KEY not set — shot plan and GPT Image frames are unavailable.") |
| else: |
| print(f"GPT Image model for first frames: {os.getenv('GPT_IMAGE_MODEL', 'gpt-image-1.5')}") |
| yield |
| cleanup_old_files() |
|
|
|
|
| app = FastAPI( |
| title="Product Showcase API", |
| description="Cinematic product videos: shot planning + KIE Veo / Seedance 2 + export", |
| version="1.0.0", |
| lifespan=lifespan, |
| ) |
|
|
| origins = [ |
| "http://localhost:5173", |
| "http://127.0.0.1:5173", |
| "http://localhost:3000", |
| "http://127.0.0.1:3000", |
| ] |
| extra = (os.getenv("CORS_ALLOWED_ORIGINS") or "").strip() |
| if extra: |
| origins = [o.strip().rstrip("/") for o in extra.split(",") if o.strip()] |
|
|
| app.add_middleware( |
| CORSMiddleware, |
| allow_origins=origins, |
| allow_credentials=True, |
| allow_methods=["*"], |
| allow_headers=["*"], |
| ) |
|
|
| app.include_router(showcase_router, prefix="/api") |
| app.include_router(scraper_router, prefix="/api") |
| app.include_router(gpt_image_router, prefix="/api") |
| app.include_router(video_router, prefix="/api") |
| app.include_router(seedance_router, prefix="/api") |
| app.include_router(image_router, prefix="/api") |
| app.include_router(export_router, prefix="/api") |
|
|
|
|
| _REPO_ROOT = Path(__file__).resolve().parent.parent |
| _FRONTEND_DIST = _REPO_ROOT / "frontend" / "dist" |
|
|
|
|
| @app.get("/health") |
| def health(): |
| return { |
| "status": "healthy", |
| "service": "product-showcase", |
| "kie_configured": bool(os.getenv("KIE_API_KEY")), |
| "replicate_configured": bool( |
| (os.getenv("REPLICATE_API_TOKEN") or os.getenv("REPLICATE_API_KEY") or "").strip() |
| ), |
| "openai_configured": bool(os.getenv("OPENAI_API_KEY")), |
| "gpt_image_model": os.getenv("GPT_IMAGE_MODEL", "gpt-image-1.5"), |
| "ffmpeg_available": bool(shutil.which("ffmpeg")), |
| "ffprobe_available": bool(shutil.which("ffprobe")), |
| "public_base_url": get_public_base_url(), |
| "server_port": int(get_server_port_str()), |
| } |
|
|
|
|
| if _FRONTEND_DIST.is_dir(): |
| app.mount( |
| "/", |
| StaticFiles(directory=str(_FRONTEND_DIST), html=True), |
| name="frontend", |
| ) |
|
|
|
|
| if __name__ == "__main__": |
| import uvicorn |
|
|
| port = int(get_server_port_str()) |
| uvicorn.run("main:app", host="0.0.0.0", port=port, reload=True) |
|
|