From e365a94dd1cdc74a38d35fd50bb629a3d4e6a685 Mon Sep 17 00:00:00 2001 From: Tony Zhang Date: Wed, 17 Dec 2025 10:35:14 +0800 Subject: [PATCH] =?UTF-8?q?fix(ports):=20docker=20=E9=BB=98=E8=AE=A4?= =?UTF-8?q?=E5=AF=B9=E5=A4=968502=E5=B9=B6=E4=BF=AE=E5=A4=8D=E7=A9=BADB?= =?UTF-8?q?=E8=BF=9E=E6=8E=A5=E4=B8=B2=E5=9B=9E=E9=80=80?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- Dockerfile | 37 ++++++++++ api/main.py | 138 +++++++++++++++++++++++++++++++++++++ config.py | 10 ++- docker-compose.yml | 149 ++++++++++++++++++++++++++++++++++++++++ scripts/start-docker.sh | 3 +- 5 files changed, 333 insertions(+), 4 deletions(-) create mode 100644 Dockerfile create mode 100644 api/main.py create mode 100644 docker-compose.yml diff --git a/Dockerfile b/Dockerfile new file mode 100644 index 0000000..f3288e3 --- /dev/null +++ b/Dockerfile @@ -0,0 +1,37 @@ +# Video Flow - Python 后端 Dockerfile +FROM python:3.11-slim + +# 安装系统依赖 +RUN apt-get update && apt-get install -y --no-install-recommends \ + ffmpeg \ + libsm6 \ + libxext6 \ + libgl1 \ + fonts-noto-cjk \ + fonts-wqy-zenhei \ + curl \ + && rm -rf /var/lib/apt/lists/* + +# 设置工作目录 +WORKDIR /app + +# 复制依赖文件 +COPY requirements.txt . + +# 安装 Python 依赖 +RUN pip install --no-cache-dir -r requirements.txt + +# 复制应用代码 +COPY . . + +# 创建必要的目录 +RUN mkdir -p /app/output /app/temp /app/assets + +# 暴露端口 +# - 8000: FastAPI +# - 8503: Streamlit(docker-compose.yml 中运行在 8503;8502 是历史 runtime 标识) +EXPOSE 8000 8503 + +# 默认命令 +CMD ["uvicorn", "api.main:app", "--host", "0.0.0.0", "--port", "8000"] + diff --git a/api/main.py b/api/main.py new file mode 100644 index 0000000..3b0beef --- /dev/null +++ b/api/main.py @@ -0,0 +1,138 @@ +""" +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" + ) + + + diff --git a/config.py b/config.py index cc371ce..377c0e2 100644 --- a/config.py +++ b/config.py @@ -15,9 +15,13 @@ load_dotenv() VOLC_API_KEY = os.getenv("VOLC_API_KEY", "05aed9c1-f5e6-487b-9273-fe7d6be51957") VOLC_BASE_URL = os.getenv("VOLC_BASE_URL", "https://ark.cn-beijing.volces.com/api/v3") +# OpenAI (for GPT models) +OPENAI_API_KEY = os.getenv("OPENAI_API_KEY", "") +OPENAI_BASE_URL = os.getenv("OPENAI_BASE_URL", "https://api.openai.com/v1") + # Models (Updated with User-Provided Endpoint IDs) # LLM: Doubao Pro 1.5 (Using provided brain/vision endpoint) -BRAIN_MODEL_ID = os.getenv("BRAIN_MODEL_ID", "ep-20251203231055-dpsp7") +BRAIN_MODEL_ID = os.getenv("BRAIN_MODEL_ID", "ep-20251203231055-dpsp7") # Vision: Doubao Vision Pro 1.5 VISION_MODEL_ID = os.getenv("VISION_MODEL_ID", "ep-20251203232121-xjt2s") # Image: Doubao Image (Updated to user provided model) @@ -115,8 +119,8 @@ FONTS_DIR.mkdir(exist_ok=True) # Database Configuration # ============================================================ # Format: postgresql://user:password@host:port/dbname -# Default to SQLite if not provided -DB_CONNECTION_STRING = os.getenv("DB_CONNECTION_STRING", f"sqlite:///{BASE_DIR}/video_flow.db") +# Default to SQLite if not provided OR provided as empty string +DB_CONNECTION_STRING = os.getenv("DB_CONNECTION_STRING") or f"sqlite:///{BASE_DIR}/video_flow.db" # ============================================================ # Font Settings (字体配置) diff --git a/docker-compose.yml b/docker-compose.yml new file mode 100644 index 0000000..e9a84aa --- /dev/null +++ b/docker-compose.yml @@ -0,0 +1,149 @@ +version: '3.8' + +# Video Flow - Docker Compose 配置 +# 支持水平扩展的视频处理服务 + +services: + # ============================================================ + # Redis - 消息队列和缓存 + # ============================================================ + redis: + image: redis:7-alpine + ports: + - "6379:6379" + volumes: + - redis_data:/data + command: redis-server --appendonly yes + healthcheck: + test: ["CMD", "redis-cli", "ping"] + interval: 10s + timeout: 5s + retries: 5 + restart: unless-stopped + + # ============================================================ + # FastAPI Backend - API 服务 + # ============================================================ + api: + build: + context: . + dockerfile: Dockerfile + ports: + - "8000:8000" + volumes: + - ./modules:/app/modules:ro + - ./api:/app/api:ro + - ./config.py:/app/config.py:ro + - ./assets:/app/assets + - ./output:/app/output + - ./temp:/app/temp + - ./video_flow.db:/app/video_flow.db + # 方案A:通过 Unix Socket 连接宿主机 Postgres(不暴露 5432) + - /var/run/postgresql:/var/run/postgresql + # 复用 8502(/root/video-flow)生成的历史素材 + - /root/video-flow/temp:/legacy/temp:ro + - /root/video-flow/output:/legacy/output:ro + environment: + - REDIS_URL=redis://redis:6379/0 + - PYTHONUNBUFFERED=1 + # 8503 读写 8502 的 Postgres(从 .env 注入,避免写死密码) + - DB_CONNECTION_STRING=${VIDEO_FLOW_DB_CONNECTION_STRING} + depends_on: + redis: + condition: service_healthy + command: uvicorn api.main:app --host 0.0.0.0 --port 8000 + healthcheck: + test: ["CMD", "curl", "-f", "http://localhost:8000/api/health"] + interval: 30s + timeout: 10s + retries: 3 + restart: unless-stopped + + # ============================================================ + # Celery Worker - 视频处理 Worker + # 可以通过 docker-compose scale worker=N 水平扩展 + # ============================================================ + worker: + build: + context: . + dockerfile: Dockerfile + volumes: + - ./modules:/app/modules:ro + - ./api:/app/api:ro + - ./config.py:/app/config.py:ro + - ./bin:/app/bin:ro + - ./assets:/app/assets + - ./output:/app/output + - ./temp:/app/temp + - ./video_flow.db:/app/video_flow.db + - /var/run/postgresql:/var/run/postgresql + - /root/video-flow/temp:/legacy/temp:ro + - /root/video-flow/output:/legacy/output:ro + environment: + - REDIS_URL=redis://redis:6379/0 + - PYTHONUNBUFFERED=1 + - C_FORCE_ROOT=1 + - DB_CONNECTION_STRING=${VIDEO_FLOW_DB_CONNECTION_STRING} + depends_on: + redis: + condition: service_healthy + command: celery -A api.celery_app worker --loglevel=info --concurrency=2 -Q default,video,audio + restart: unless-stopped + deploy: + # 生产环境可以增加副本数 + replicas: 1 + resources: + limits: + cpus: '2' + memory: 4G + + # ============================================================ + # React Frontend - 前端服务 (生产环境) + # ============================================================ + web: + build: + context: ./web + dockerfile: Dockerfile + ports: + - "3000:80" + depends_on: + - api + restart: unless-stopped + + # ============================================================ + # Streamlit - 原有调试界面 (保留在 8503) + # 连接到 8502 的数据目录,复用历史素材 + # ============================================================ + streamlit: + build: + context: . + dockerfile: Dockerfile + ports: + # 默认对外保持 8502(兼容历史入口);容器内仍运行在 8503 + - "${STREAMLIT_PORT:-8502}:8503" + volumes: + - ./app.py:/app/app.py:ro + - ./modules:/app/modules:ro + - ./config.py:/app/config.py:ro + - ./assets:/app/assets + - ./output:/app/output + # 挂载 8502 服务的 temp 目录,复用历史项目数据 + - /opt/gloda-factory/temp:/app/temp + - ./video_flow.db:/app/video_flow.db + - /var/run/postgresql:/var/run/postgresql + - /root/video-flow/temp:/legacy/temp:ro + - /root/video-flow/output:/legacy/output:ro + environment: + - PYTHONUNBUFFERED=1 + - DB_CONNECTION_STRING=${VIDEO_FLOW_DB_CONNECTION_STRING} + - WEB_BASE_URL=${WEB_BASE_URL} + command: streamlit run app.py --server.port 8503 --server.address 0.0.0.0 + restart: unless-stopped + +volumes: + redis_data: + +networks: + default: + name: video-flow-network + diff --git a/scripts/start-docker.sh b/scripts/start-docker.sh index 794ac07..d031477 100755 --- a/scripts/start-docker.sh +++ b/scripts/start-docker.sh @@ -21,7 +21,8 @@ echo "" echo "📌 访问地址:" echo " - 视频编辑器: http://localhost:3000" echo " - API 文档: http://localhost:8000/api/docs" -echo " - 工作流控制台: http://localhost:8503" +STREAMLIT_PORT="${STREAMLIT_PORT:-8502}" +echo " - 工作流控制台: http://localhost:${STREAMLIT_PORT}" echo "" echo "📊 查看日志: docker-compose logs -f" echo "🔧 扩展 Worker: docker-compose scale worker=3"