文本转视频生成流水线的部署与运行说明。项目以容器化、服务化为重心:Docker Compose 一键起服务,CLI / HTTP / WebUI 共享同一套配置与统一输出目录。
┌──────────────────────────────┐
浏览器 (WebUI) ──────► │ Next.js Web 服务 (端口 3000) │
HTTP /api/render ────► │ apps/web │
└───────────────┬──────────────┘
│ spawn 子进程
▼
┌──────────────────────────────┐
│ CLI apps/cli/dist/index.js │
│ runPipeline (7 阶段) │
│ parse → tts → assets → │
│ compose → render → export │
│ → publish(可选) │
└───────────────┬──────────────┘
│
┌────────────────────────┼────────────────────────┐
▼ ▼ ▼
统一输出目录 阿里云 OSS 飞书 Webhook
{output}/{模板}/{日期}/{文件} (上传视频) (推送结果/失败)
spawn 调用 CLI 子进程执行 runPipeline。apps/cli)是流水线唯一入口;WebUI 与 HTTP 都复用它,因此 OSS 上传 / 飞书推送只在 CLI 进程内发生一次。{模板}/{ISO日期}/{文件名}.mp4 自动建子目录。packages/shared|core|tts|templates|collect,apps/cli|web)。Docker 方式(推荐)
本地开发方式
@remotion/renderer),或设置 PUPPETEER_EXECUTABLE_PATH 指向系统 Chrome。# 1. 准备配置(密钥 / 端点)
cp .env.example .env
# 编辑 .env,至少填入 LLM、TTS 的 key;按需开启 OSS / 飞书
# 2. 构建并启动
docker compose up --build -d
# 3. 访问
# WebUI: http://localhost:13000
# (PORT 可在 .env 中覆盖,默认 13000 → 容器 3000)
docker compose up 会自动读取仓库根目录的 .env,把其中的变量替换进容器的 environment:(见 docker-compose.yml)。因此密钥放在宿主机 .env,不需要把 .env 打进镜像。
停止 / 查看日志 / 重建:
docker compose logs -f pipeline # 跟随日志
docker compose down # 停止
docker compose up --build -d # 改完代码后重新构建
容器内:
/app/apps/web,监听 3000。/app/output 挂载到宿主机 ./output(视频落盘可见、可清理)。jobs.json)保存在 docker volume pipeline-jobs(/tmp/pipeline-jobs)。配置分两层:.env(密钥 / 端点) + config/default.yaml(业务参数)。CLI 读取顺序:--config 参数 → pipeline.config.yaml → config/default.yaml;密钥统一走环境变量。
.env)| 变量 | 必填 | 说明 |
|---|---|---|
OPENAI_API_KEY / OPENAI_BASE_URL |
是* | LLM(OpenAI 兼容端点:DeepSeek / Qwen / GLM 等)。*用结构化 JSON + --skip-llm 时可省 |
OPENAI_TTS_API_KEY / OPENAI_TTS_BASE_URL |
是* | 默认 TTS provider。*用 --no-tts 时可省 |
OPENAI_TTS_MODEL |
否 | TTS 模型,默认 seed-tts-1.1 |
TTS_MAX_RETRIES |
否 | TTS 请求失败重试次数(网络错误 / 429 / 5xx,指数退避),默认 3;0 关闭 |
FISH_AUDIO_API_KEY |
否 | Fish Audio provider |
MINIMAX_API_KEY / MINIMAX_GROUP_ID |
否 | MiniMax provider |
ELEVENLABS_API_KEY |
否 | ElevenLabs provider |
OUTPUT_DIR |
否 | 统一输出目录;空则用 config.output.dir(默认 ./output)。Docker 内固定 /app/output |
OUTPUT_RETENTION_DAYS |
否 | 缓存保留天数,默认 30;0 关闭自动清理 |
OSS_REGION OSS_BUCKET OSS_ACCESS_KEY_ID OSS_ACCESS_KEY_SECRET |
否 | 阿里云 OSS;四项齐全才启用上传 |
OSS_ENDPOINT OSS_PATH OSS_PUBLIC_DOMAIN OSS_SECURE |
否 | OSS 自定义端点 / key 前缀 / 资源链接域名(如 CDN) / 是否 HTTPS |
FEISHU_WEBHOOK_URL FEISHU_WEBHOOK_SECRET |
否 | 飞书自定义机器人 webhook 及签名密钥 |
FEISHU_AT_OPEN_IDS |
否 | 推送消息时 @ 的成员 open_id(逗号分隔多个)。自定义机器人只能按 open_id @人,且需在机器人租户/群内可解析 |
LOG_LEVEL |
否 | 日志级别 debug\|info\|warn\|error,默认 info |
PORT |
否 | 宿主机映射端口,默认 13000 |
SCHEDULES |
否 | 定时任务(JSON 数组),整体覆盖 config/default.yaml 的 schedules,便于部署期改而无需重建镜像 |
SCHEDULER_ENABLED |
否 | 调度器总开关,false 关闭(默认开启) |
也可在 WebUI Settings 页面填写并保存到
.env(敏感值自动脱敏显示)。
config/default.yamlTTS provider 选择、模型、对齐(whisper)、模板配色、采集源(collect)、OSS/飞书的非敏感默认值(region/bucket/path 等)在此配置。详见文件内注释。
schedules 段定义定时任务,Next.js 服务启动时由进程内调度器(instrumentation 钩子 + node-cron)注册,到点 spawn CLI 渲染——复用 WebUI/HTTP 同一链路,任务进 jobs 列表、走 OSS/飞书发布。cron 按容器时区(TZ,默认 Asia/Shanghai=北京时间)解释。
schedules:
- cron: "50 7 * * *" # 北京时间每天 07:50
template: "github-trending"
platform: "bilibili"
source: "github-trending" # 先采集当日 trending,再解析→渲染→发布
publish: true
enabled: true
docker compose up -d)生效;或用 SCHEDULES 环境变量(JSON 数组,同结构)整体覆盖,部署期改无需重建镜像。SCHEDULER_ENABLED=false 关闭整个调度器。{OUTPUT_DIR}/{模板名}/{YYYY-MM-DD(UTC)}/{模板}-{平台}-{jobId前8位}.mp4
output/github-trending/2026-07-16/github-trending-bilibili-701832d4.mp4apps/web 落到同一物理目录;容器内为 /app/output。OUTPUT_RETENTION_DAYS(默认 30)扫描,删除 mtime 超过阈值的文件及随之变空的日期目录(best-effort,不影响渲染)。设 0 关闭。渲染完成后自动执行(可被 --no-publish / config.skipPublish 跳过):
| 场景 | 行为 |
|---|---|
| 上传 OSS 成功 | 推送飞书:✅ 视频生成成功 + OSS 资源链接 |
| 渲染成功但上传失败 | 推送飞书:⚠️ 已生成但上传失败 + 本地路径 + 错误(本地视频仍保留) |
| 渲染本身失败 | 推送飞书:❌ 生成失败 + 错误信息 |
ali-oss 分片上传;资源链接优先用 OSS_PUBLIC_DOMAIN(CDN),否则用 bucket 默认域名。假定对象为公开读;若为私读需另行接签名 URL。FEISHU_WEBHOOK_SECRET),算法为 base64(HMAC-SHA256(key=timestamp+"\n"+secret, message=""))。FEISHU_AT_OPEN_IDS(逗号分隔的 open_id)后,每条推送都会 @ 这些成员:消息正文追加 <at user_id="open_id"> 标签,并在 payload 顶层声明 at.open_ids(二者齐备才会真正触发 @ 通知)。注意自定义机器人只能按 open_id @人(无通讯录权限,不支持 user_id / 邮箱),且 open_id 须在该机器人租户内可解析(通常为目标群成员)。oss: <url>;WebUI 任务详情页展示「OSS Links」)。排查问题看三处:
docker compose logs -f pipeline。CLI 子进程的每阶段进度、TTS 重试、publish(OSS 上传 / 飞书发送)日志都会镜像到这里,每行带 [job <id前8位>] 前缀与时间戳,便于区分并发任务。error / publishError:任务卡片与详情页直接显示。日志格式:<ISO 时间> [LEVEL] [scope] message,scope 含 pipeline / tts / publish。用 LOG_LEVEL(debug|info|warn|error,默认 info)调节详细程度。
说明:渲染由 Web 服务 spawn CLI 子进程执行;CLI 的 stdout/stderr 默认被管道捕获。现在这些输出既会镜像到容器 stdout/stderr(供
docker compose logs),也会存入任务(供 UI)。TTS 的瞬时 502/网络重试会以[WARN] [tts] HTTP 502, retrying …形式出现;OSS/飞书失败会以[ERROR] [publish] …出现,不再被静默吞掉。
pnpm install
pnpm build # 构建所有包(含 apps/cli/dist、apps/web/.next)
# CLI 直接渲染
node apps/cli/dist/index.js render test/fixtures/sample-knowledge.json \
-t knowledge -p bilibili --skip-llm --no-tts --no-publish
# WebUI 开发服务器(热更新)
cd apps/web && pnpm dev # http://localhost:3000
常用命令:
pnpm typecheck # 全包类型检查
pnpm lint # 全包 lint
pnpm build # 全量构建
node apps/cli/dist/index.js render <input> -t <template> [options]
<input>:JSON / Markdown / 纯文本文件路径;或用 --source <name> 从采集源取数(如 github-trending、github-daily)。-t/--template:news|knowledge|opinion|marketing|github-trending(必填)。-p/--platform:bilibili|douyin-long|douyin-short|universal-16x9|universal-9x16,逗号分隔多平台。-o/--output:输出目录(默认走 config.output.dir / ./output)。--no-tts:跳过 TTS,生成静音视频(调试布局)。--skip-llm:跳过 LLM,要求输入是合法 VideoInputSchema JSON。--no-publish:跳过 OSS 上传与飞书推送。--config:指定配置文件路径。其它子命令:collect、preview、templates、voices、init(--help 查看)。
POST /api/render
Content-Type: application/json
{
"input": {
"template": "knowledge",
"platforms": ["bilibili"],
"ttsProvider": "openai-tts",
"text": "<VideoInput JSON / Markdown / 纯文本>"
},
"options": { "noTts": false }
}
input.template(必填)、input.ttsProvider(必填)。input.text 与 input.source 二选一;platforms 缺省为 ["bilibili"]。input.source + input.sourceArgs(如 { "owner": "x", "repo": "y" })。响应:{ "jobId": "<uuid>", "status": "started" }。
GET /api/jobs/<jobId> # 返回任务状态、outputFiles、ossUrls、publishError
GET /api/jobs # 任务列表
GET /api/download?file=<绝对路径>&download=1 # 下载视频(仅允许统一输出目录或任务目录内文件)
任务状态:pending | running | completed | failed。completed 后 outputFiles 给出本地路径,ossUrls 给出 OSS 链接(若已上传)。
curl -X POST http://localhost:13000/api/render \
-H 'Content-Type: application/json' \
-d '{"input":{"template":"knowledge","platforms":["bilibili"],"ttsProvider":"openai-tts","text":"<JSON 字符串>"}}'
# 轮询
curl http://localhost:13000/api/jobs/<jobId>
| 挂载 | 容器路径 | 用途 |
|---|---|---|
./output |
/app/output |
视频产物 + 渲染临时目录(按模板/日期归档,自动清理) |
docker volume pipeline-jobs |
/tmp/pipeline-jobs |
任务元数据 jobs.json(重启保留) |
注意:任务元数据存在内存卷中,删除 volume 会丢失任务历史(但不影响已生成的视频文件)。
| 现象 | 排查 |
|---|---|
渲染卡在 [5/7] Rendering |
headless Chrome 未就绪。本地确认 Chrome / Chromium 可用,或设 PUPPETEER_EXECUTABLE_PATH;Docker 镜像已内置所需系统库 |
| TTS 报 API_KEY 错误 | 检查 .env 的 TTS key;或临时用 --no-tts 调布局 |
| TTS 报 502 / 503 / 网络错误 | TTS 网关上游瞬时故障(如 DNS、滚动重启);已内置重试(TTS_MAX_RETRIES 默认 3)。若持续失败,检查 OPENAI_TTS_BASE_URL 网关健康度或临时换 provider |
日志里出现 Note: … @remotion/media-parser … license |
这是 Remotion 的许可证提醒(parseMedia() 探测音频时长时打印),不是错误、不影响渲染。若要消除,需在 packages/tts/src/duration.ts 的 parseMedia() 传入 acknowledgeRemotionLicense: true——注意这是对许可证的法律确认,公司用途请先确认是否需要 Remotion 许可 |
| OSS 上传失败 | 检查 OSS_REGION/BUCKET/ACCESS_KEY_*;任务详情会展示 Publish warning,本地视频仍生成 |
| 飞书收不到消息 | 确认 FEISHU_WEBHOOK_URL;若启用了签名校验需同时填 FEISHU_WEBHOOK_SECRET |
| 容器内找不到输出 | 确认挂载到 /app/output(OUTPUT_DIR),docker compose 已默认如此 |
改了 .env 不生效 |
重启服务:docker compose up -d(环境变量在容器启动时注入) |
apps/cli/ CLI 入口(Commander)
apps/web/ Next.js WebUI + API 路由 + 进程内定时调度器(instrumentation/node-cron)
packages/shared/ Zod schema、类型、常量、LLM 客户端、路径/发布配置解析
packages/core/ 流水线编排、7 阶段、cleanup、publish(OSS+飞书)
packages/tts/ TTS provider 注册表
packages/templates/ Remotion 场景组件
packages/collect/ 数据采集器(github-trending 等)
config/default.yaml 业务配置(provider / 模板配色 / 采集源 / OSS·飞书默认值)
.env(.example) 密钥与端点
docker-compose.yml 容器编排(Docker Compose 单机)
Dockerfile 多阶段构建(非 root、内置 HEALTHCHECK)
deploy/k8s/ Kubernetes 清单 + 部署说明
镜像本身已为 k8s 做好兼容:内置 /api/health/live(liveness)与 /api/health/ready(readiness)探针端点、以非 root(node,uid 1000)运行、输出与任务目录可挂载 PVC、进程重启后自动清理中断的孤儿任务。清单见 deploy/k8s/(含单副本 Deployment、Service、Ingress、ConfigMap、PVC 及部署 README)。
# 1. 设定你的 Harbor 地址与项目(替换为实际值)
HARBOR=harbor.example.com # Harbor 注册地址
PROJECT=library # Harbor 上已创建的项目名
IMAGE=$HARBOR/$PROJECT/pipeline
TAG=$(git rev-parse --short HEAD) # 也可用日期或 latest
# 2. 登录 Harbor
docker login $HARBOR -u <用户名> # 回车后输入密码 / Harbor CLI secret
# 3. 用 Harbor 地址构建并打 tag(Dockerfile 在仓库根目录,多阶段构建)
docker build -t $IMAGE:$TAG -t $IMAGE:latest .
# 4. 推送
docker push $IMAGE:$TAG
docker push $IMAGE:latest
# 5. 把 deploy/k8s/03-deployment.yaml 里的 image: 改成 $IMAGE:$TAG(或 :latest)
要点:
/etc/docker/daemon.json 加入 "insecure-registries": ["harbor.example.com"] 后重启 dockerd;或在 k8s 各节点同理配置 containerd。deploy/k8s/README.md。完整步骤(生成 Secret、应用清单、验证、扩缩容与持久化说明)见 deploy/k8s/README.md。最简流程:
# 用根目录 .env 生成密钥 Secret
kubectl create secret generic pipeline-env-secret --from-env-file=.env -n pipeline || \
kubectl create namespace pipeline && \
kubectl create secret generic pipeline-env-secret --from-env-file=.env -n pipeline
# 替换 03-deployment.yaml 中的镜像地址后:
kubectl apply -f deploy/k8s/
kubectl -n pipeline get pods -w
单副本:应用以本地
jobs.json+ spawn CLI 渲染,无共享状态/分布式锁,因此 Deployment 固定replicas: 1(PVCReadWriteOnce+strategy: Recreate)。水平扩展需先外置任务存储,详见 k8s README。