""" Video Flow API - FastAPI Backend 前后端分离架构的后端服务 端口:8000(与 Streamlit 8503 共存;8502 为历史 runtime/素材目录标识) """ import os import sys import logging from pathlib import Path from contextlib import asynccontextmanager from fastapi import FastAPI from fastapi.middleware.cors import CORSMiddleware from fastapi.staticfiles import StaticFiles # 确保项目根目录在 path 中 PROJECT_ROOT = Path(__file__).parent.parent sys.path.insert(0, str(PROJECT_ROOT)) import config from api.routes import projects, editor, assets, compose # 配置日志 logging.basicConfig( level=logging.INFO, format='%(asctime)s - %(name)s - %(levelname)s - %(message)s' ) logger = logging.getLogger(__name__) @asynccontextmanager async def lifespan(app: FastAPI): """应用生命周期管理""" logger.info("Video Flow API 启动中...") logger.info(f"项目根目录: {PROJECT_ROOT}") logger.info(f"输出目录: {config.OUTPUT_DIR}") logger.info(f"临时目录: {config.TEMP_DIR}") yield logger.info("Video Flow API 关闭中...") # 创建 FastAPI 应用 app = FastAPI( title="Video Flow API", description="视频工作流后端 API - 支持项目管理、素材处理、视频编辑与合成", version="1.0.0", lifespan=lifespan, docs_url="/api/docs", redoc_url="/api/redoc", openapi_url="/api/openapi.json" ) # CORS 配置 - 允许 React 前端访问 app.add_middleware( CORSMiddleware, allow_origins=[ "http://localhost:3000", # React dev server "http://localhost:5173", # Vite dev server "http://127.0.0.1:3000", "http://127.0.0.1:5173", ], allow_credentials=True, allow_methods=["*"], allow_headers=["*"], ) # 挂载静态文件目录(用于访问生成的视频/图片) app.mount("/static/output", StaticFiles(directory=str(config.OUTPUT_DIR)), name="output") app.mount("/static/temp", StaticFiles(directory=str(config.TEMP_DIR)), name="temp") app.mount("/static/assets", StaticFiles(directory=str(config.ASSETS_DIR)), name="assets") # Legacy mounts(8502 runtime 产物,宿主机目录通过 docker-compose 挂载到容器内) try: app.mount("/static/legacy-temp", StaticFiles(directory="/legacy/temp"), name="legacy-temp") app.mount("/static/legacy-output", StaticFiles(directory="/legacy/output"), name="legacy-output") except Exception: # 本地非 docker 环境可能不存在这些目录 pass # 注册路由 app.include_router(projects.router, prefix="/api/projects", tags=["Projects"]) app.include_router(editor.router, prefix="/api/editor", tags=["Editor"]) app.include_router(assets.router, prefix="/api/assets", tags=["Assets"]) app.include_router(compose.router, prefix="/api/compose", tags=["Compose"]) @app.get("/api/health") async def health_check(): """健康检查""" return { "status": "ok", "service": "video-flow-api", "version": "1.0.0" } @app.get("/api/config") async def get_config(): """获取前端所需的配置信息""" return { "video_settings": config.VIDEO_SETTINGS, "available_voices": [ {"id": config.VOLC_TTS_DEFAULT_VOICE, "name": "三通永 (默认)"}, {"id": "zh_female_meilinvyou_saturn_bigtts", "name": "美丽女友"}, ], "available_bgm": _list_bgm_files(), } def _list_bgm_files(): """列出可用的 BGM 文件""" bgm_dir = config.ASSETS_DIR / "bgm" if not bgm_dir.exists(): return [] bgm_files = [] for f in bgm_dir.iterdir(): if f.suffix.lower() in ['.mp3', '.mp4', '.m4a', '.wav']: bgm_files.append({ "id": f.name, "name": f.stem, "path": f"/static/assets/bgm/{f.name}" }) return bgm_files if __name__ == "__main__": import uvicorn uvicorn.run( "api.main:app", host="0.0.0.0", port=8000, reload=True, log_level="info" )