Studio 架构总览
本页讲 platform/studio 的整体设计思想,不逐条列接口/页面——接口清单见 API 参考,前端交互细节见 前端创作流程。
定位
Studio 是车库内的 AI 短剧创作子系统:编排多家 AI 模型(图像/视频/语音/音乐),把一段剧本一步步产出配音配乐的成片视频。它只负责"生产",不负责"分发"——成片导出后,由运营在 admin 后台手动触发"导入",此后就是车库 catalog 的一条普通短剧内容,走标准审核上架流程。Studio 不是第二套内容中台。
命名债(读代码前先知道)
Studio 前身是一个独立产品,经历过多次改名,历史上代码里曾同时留着四个名字,容易让人以为是四个不同系统。已按本节建议统一为"Studio"(2026-09 完成 comic_gen 包目录/类名/API 标题重命名,见下表"现状"列):
两个 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 适配路由逻辑。
中间件挂载顺序是刻意设计的:先加鉴权中间件,再加 CORS(api.py)。Starlette 后加的中间件在最外层,这样 CORS 头能包住鉴权失败返回的 401,浏览器预检和错误响应才都带得上 CORS 头。
数据流全景:一个短剧项目的生命周期
存储形态
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)之间是双向调用关系,两个方向用两套完全不同的凭证:
- 方向一为什么要二次 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 参考。导入永远由运营在后台手动触发,不做自动推送。魔改(从已上架剧派生)尚未做。
音频子系统
- TTS:
src/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从未使用,是一个做了一半的功能,详见 已知问题。