Studio 架构总览

本页讲 platform/studio 的整体设计思想,不逐条列接口/页面——接口清单见 API 参考,前端交互细节见 前端创作流程

定位

Studio 是车库内的 AI 短剧创作子系统:编排多家 AI 模型(图像/视频/语音/音乐),把一段剧本一步步产出配音配乐的成片视频。它只负责"生产",不负责"分发"——成片导出后,由运营在 admin 后台手动触发"导入",此后就是车库 catalog 的一条普通短剧内容,走标准审核上架流程。Studio 不是第二套内容中台。

命名债(读代码前先知道)

Studio 前身是一个独立产品,经历过多次改名,历史上代码里曾同时留着四个名字,容易让人以为是四个不同系统。已按本节建议统一为"Studio"(2026-09 完成 comic_gen 包目录/类名/API 标题重命名,见下表"现状"列):

名字曾经出现位置含义现状
comic_gensrc/apps/comic_gen/(目录名)最早期"AI 漫画生成器"定位的遗留命名✅ 已改名为 src/apps/studio/,类名 ComicGenPipeline 改为 StudioPipeline
AI Video Creation APIFastAPI title产品转向"视频生产"后起的 API 标题✅ 已改为 "Studio API"
LumenXdocs/upstream-AGENTS.md独立仓库时期的产品名仍在该份历史/上游文档里,作为背景参考保留,不影响当前代码
tron-comic~/.tron/comic/projects(结构化脚本编辑器的存储路径)上游 GitLab 仓库名残留未改动——这是运行时数据存储路径,不是单纯的命名问题,改动前需要先解决它"脱离 STUDIO_DATA_DIR"这个更大的技术债(见已知问题),否则单独改路径会让已有本地数据失联

两个 FastAPI app

实际启动入口是 python -m uvicorn src.apps.studio.api:app(见仓库 platform/studio/README.md)。src/apps/ 下只有两个 app:

  • studio:主业务域,145+ 路由几乎全部直接挂在同一个 FastAPI() 实例上(没有用 APIRouter 分组),靠注释块做视觉分区,是名副其实的"单体 handler 文件"(src/apps/studio/api.py)。
  • playground:独立的自由试验场子应用,用 APIRouter + include_router(prefix="/playground") 挂进主 app(src/apps/playground/api.py)。它脱离 Script/Series 的项目流程,是一次性生成沙盒,但复用同一套模型 provider 适配路由逻辑。

中间件挂载顺序是刻意设计的:先加鉴权中间件,再加 CORSapi.py)。Starlette 后加的中间件在最外层,这样 CORS 头能包住鉴权失败返回的 401,浏览器预检和错误响应才都带得上 CORS 头。

数据流全景:一个短剧项目的生命周期

原文本
  │  POST /projects(LLM 实体抽取)

Script { characters[], scenes[], props[] }
  │  资产生成:Wanx T2I 产参考图 → 可选生成运动参考视频

StoryboardFrame[](分镜切分,含景别/运镜/构图/台词等结构化字段)
  │  逐镜生成:T2I→I2V 或 Direct R2V(多候选"抽卡")

VideoTask[](每帧多个候选,用户选定 final_take)
  │  台词 TTS → 可选人声分离配音(原片环境音 + 新配音)
  │  BGM(Fun-Music)+ SFX(ElevenLabs)

POST /projects/{id}/merge(ffmpeg concat + 混音)

merged_video_url(成片)
  │  运营在 admin 手动触发「Studio 成片导入」

车库 catalog(drama + episode)→ 审核 → 上架 → BaaS → 消费端

存储形态

Studio 不用数据库,核心业务数据是三个扁平 JSON 文件:output/projects.json(Script)、output/series.json(Series)、output/library_assets.json(全局资产库),进程启动时整体读入内存 dict,每次写操作整体覆写(src/apps/studio/pipeline.py)。媒体文件落在 output/{uploads,video,assets,storyboard,audio,video_inputs,export,playground}/ 下,经 /files/* 静态路径对外提供。

STUDIO_DATA_DIR 环境变量决定这一切落在哪:进程启动时 chdir() 到这个目录(src/utils/__init__.py: enter_data_dir()),因为代码里约 140 处直接用相对路径 output/... 寻址,这是从桌面版遗留下来的"约定优于配置"设计。这意味着一个进程只能服务一个 STUDIO_DATA_DIR,是隐性的单实例假设,不能简单水平扩展成多进程共享同一份数据。

对象存储(生成的图片/视频)src/utils/oss_utils.py(2026-09 从阿里云 OSS 迁移为 S3 兼容协议,指向车库自建 RustFS,见 RustFS 对象存储)走"私有桶 + 动态签名"策略——上传只返回 Object Key,不存明文 URL;签名 URL 按用途分两档有效期:前端展示 2 小时、AI 厂商拉图 30 分钟。变量名仍叫 OSS_*/ALIBABA_CLOUD_*(历史命名,同时是设置页「存储」Tab 的字段名,未改名,见该文件顶部说明),实际值现在是 RustFS 的 S3 端点/桶/Key。这层跟车库主平台自己的对象存储(platform/server,同样已是 RustFS)是两套独立配置,Studio 建议用独立的 motorpool 桶,不与主平台的 motorpoo 混用。

双向鉴权机制

Studio 与车库主平台(platform/server,Go)之间是双向调用关系,两个方向用两套完全不同的凭证:

方向一:运营在 admin 登录 → 浏览器带 admin JWT 访问 Studio
  admin JWT ──▶ Studio 本地验签(ADMIN_SESSION_SECRET / ADMIN_JWT_SECRET)
             ──▶ 二次 introspection:GET {MOTORPOOL_API_URL}/api/v1/auth/me
             ──▶ 通过才放行(缓存 STUDIO_INTROSPECT_TTL_SECONDS 秒,默认 30s)

方向二:Go 后端反向调用 Studio 拉取成片
  STUDIO_SERVICE_TOKEN ──▶ Studio 跳过 introspection,直接信任(服务身份,不是账号)
  • 方向一为什么要二次 introspection:本地验签只能确认"这个 JWT 是车库签的",不能确认"这个账号现在还有效"——账号可能在签发后被禁用。Studio 因此在验签通过后再调一次 Go 的 /api/v1/auth/me 确认账号状态,Go 服务不可达时按未授权处理(fail closed,不会退化成只信任本地签名)。这个 JWT 从哪来对 Studio 后端是透明的:可以是浏览器读 admin 控制台的登录 cookie(旧路径),也可以是 Studio 自己的登录页直接打 Go 的 /api/v1/auth/login 拿到的(2026-09 新增,不再要求与 admin 同域名),两条路径签出的是同一种 admin JWT,后端校验逻辑不用区分来源;前端细节见 前端创作流程 鉴权一节。
  • 方向二为什么不用同一套机制:STUDIO_SERVICE_TOKEN 标识的是"车库服务本身"在替系统做一次编排调用,不对应一个会被封禁的用户账号,所以没有 introspection 的必要。
  • 两个方向共用一条约束:鉴权默认开启,STUDIO_AUTH_DISABLED=true 仅限本地调试;如果鉴权开着却一个凭证都没配,进程直接启动失败,不会裸奔。

与车库主平台的集成点

集成只有一处,方向是"Go 拉 Studio":

  • 单集:GET {STUDIO_API_URL}/projects/{id}POST /api/v1/admin/studio-imports
  • 整剧:GET /series/{id} + GET /series/{id}/episodes(含每集 merged_video_url)→ POST /api/v1/admin/studio-imports/series

导入路径与字段受车库主契约 docs/openapi/openapi.yaml 管辖;Studio 自身的其余路由不受这两份 YAML 管辖,详见 API 参考。导入永远由运营在后台手动触发,不做自动推送。魔改(从已上架剧派生)尚未做。

音频子系统

  • TTSsrc/audio/tts.py 维护一份手写语音注册表,按音色 family 分发到 CosyVoice v2/v3 或 Qwen3-TTS 两条不同的 DashScope SDK 调用路径。
  • 人声分离配音(Dub):先用 Demucs 把原视频背景音轨从人声里分离出来,再叠加新 TTS,保留原片环境音;Demucs 是可选重依赖(含 torch),未安装或分离失败时自动退化为直接叠加 TTS,会丢失原片环境音(退化逻辑在 pipeline.py: preview_dub)。
  • BGM:DashScope Fun-Music,需要单独的 DASHSCOPE_WORKSPACE_ID(按业务空间分域的端点,和其它 DashScope 调用不是一个体系)。
  • SFX:ElevenLabs text-to-sound-effect,独立账号凭证。当前 SFX 轨还没接入最终成片的混音输出——生成出来了但 merge_videos 从未使用,是一个做了一半的功能,详见 已知问题

相关页面