# shotforge **Repository Path**: yushui_wang/shotforge ## Basic Information - **Project Name**: shotforge - **Description**: No description available - **Primary Language**: Unknown - **License**: Apache-2.0 - **Default Branch**: main - **Homepage**: None - **GVP Project**: No ## Statistics - **Stars**: 0 - **Forks**: 0 - **Created**: 2026-09-30 - **Last Updated**: 2026-10-04 ## Categories & Tags **Categories**: Uncategorized **Tags**: None ## README # ShotForge Studio > 分镜驱动的 AI 视频生成工作台。把「写提示词 → 分镜 → 排队 → 生成 → 合并 → 配音上字幕」这条链路,收进一个网页界面里。 [![License](https://img.shields.io/badge/license-Apache--2.0-blue.svg)](LICENSE) ![Node](https://img.shields.io/badge/node-%3E%3D18-brightgreen.svg) ![Dependencies](https://img.shields.io/badge/npm%20dependencies-0-success.svg) **零 npm 依赖、无构建步骤。** 后端是单个 Node 原生 `http` 服务,前端是一张不打包的 HTML。 克隆下来配好引擎地址就能跑。 --- ## 它解决什么问题 直接调文生视频接口做长视频,会撞上三堵墙: 1. **时长上限** —— 单次生成只能出几秒。想要 30 秒的片子,得自己拆分镜、对齐场景连贯性、再拼起来。 2. **没有队列** —— 一次只能跑一个任务,人得盯着。中断了不知道跑到哪,引擎重启任务就丢。 3. **成片不等于可用** —— 生成的只是「素材」,配音、字幕、BGM、分辨率都还要再过一遍。 ShotForge Studio 把这三件事一起做了。 --- ## 特性 **分镜与生成** - 单镜头任务:直接指定时长入队 - **画面来源可选**:纯文本,或带关键帧图片 —— 首帧 / 尾帧 / 首尾帧。 图片在服务端按魔数校验格式与尺寸,不合规当场退回并说明原因; 提交时以 multipart 文件 part 原样转发给引擎 - **三段式提示词**:画面描述拆成「动作 / 场景 / 镜头」分别填写,也可让 LLM 一句话拆三段、 或只重写其中一段 - 输出画幅 6 档(21:9 / 16:9 / 4:3 / 1:1 / 3:4 / 9:16),可选指定像素分辨率, 另有负向提示词与随机种子 - 分镜项目:任意总时长,超限自动分镜。可用 LLM 智能分镜(保障相邻镜头场景连贯、动作衔接),也可退化为规则拆分 - **逐镜改稿重跑**:某一镜的构图不满意、或画出了乱码,可单独改提示词 / 负向提示词 / 时长 / 随机种子后重跑, 其余镜头原样复用;该镜跑完自动重新合并、自动重做后期。项目跑完之后仍可改 - 自动合并:逐镜生成完成后由 ffmpeg 无损拼接成粗剪 **队列与进度** - 本地任务队列,并发度可配(默认 1) - 四阶段进度追踪 + 按预估耗时的进度条 + 项目级汇总进度 - 记录持久化,服务重启后自动续接:排队中的保留、有远端 ID 的继续轮询、已在生成的自动恢复 - **任务列表分页**:按状态筛选(全部 / 进行中 / 已完成 / 失败)+ 每页条数可调(5 / 10 / 20 / 50)。分页在服务端完成,只把当前页下发给前端,历史任务多时轮询负载不随记录数增长 **引擎容错**(这类工具最容易忽略的部分) - 提交失败指数退避重试;参数类错误立即失败,不做无意义重试 - 轮询期间容忍网络抖动(默认可连续失败 90 次再判定丢失) - 引擎重启清空队列时,靠 404 语义自动重新提交 - 成片下载失败重试 - **连通性自检**:设置里一键测试引擎与 LLM,把「没连上」和「连上了但路径 / 模型不对」分开 **LLM 助手** - 提示词优化:把口语描述改写成结构化的画面/声音描述(主体 + 外观 + 动作 + 环境 + 运镜 + 光影 + 风格) - 三段拆分 / 单段重写:一句话拆成「动作 / 场景 / 镜头」,或只把其中一段改写得更具体 - 复杂输入拆解:产品简介、剧本片段直接粘进来,自动提炼出最值得呈现的镜头 - 整段旁白撰写:按分镜逐镜写口播文案,篇幅与每镜时长成正比 **后期处理** - AI 超分 2x(Real-ESRGAN,可选) - TTS 配音(edge-tts,可选,5 种中文音色) - 字幕烧录:长句按标点自动切分为短语并重排时间轴,避免整屏堆字 - BGM 混音:循环垫底 + 音量压制,环境音保留策略可选 --- ## 界面预览 ![界面](docs/images/ui.png) *(截图为演示数据:项目与提示词均为虚构示例,视频是抽象渐变占位片,不含任何真实内容)* 页面分两栏:左侧是生成表单,按「画面来源 → 提示词 → 输出 → 高级参数」自上而下排; 右侧是任务队列与记录(支持按状态筛选、展开日志、在线预览与下载)。 右上角是引擎 / LLM 两个状态灯(可点击直达设置)与**设置**入口。 > 「画面来源」里带关键帧的模式是否可选,取决于当前引擎预置是否支持(见 > [切换引擎预置](#切换引擎预置))。后端不支持时该选项会置灰并给出原因, > 而不是等到提交后才由引擎报错。**注意**:MiniMax-H3 的**蒸馏加速版**只接受纯文本 > (`t2va`),要用关键帧得切到全量版预置或另一个支持 `fl2va` 的后端。 --- ## 快速开始 ### 前置条件 | 依赖 | 必需性 | 说明 | |---|---|---| | Node.js ≥ 18 | 必需 | 只需运行时,无需 `npm install` | | 视频生成引擎 | 必需 | 需自备,见 [docs/ENGINE-API.md](docs/ENGINE-API.md) | | ffmpeg | 必需 | 合并与后期。macOS `brew install ffmpeg`,Debian `apt install ffmpeg` | | LLM 服务 | 可选 | 提供提示词优化 / 智能分镜 / 旁白 | | edge-tts | 可选 | 配音。`pip install edge-tts`(GPL-3.0,详见 [NOTICE](NOTICE)) | | Real-ESRGAN | 可选 | AI 超分。运行 `scripts/fetch-realesrgan.sh` 获取 | > **本项目不包含任何模型权重,也不实现推理。** 你需要自备符合契约的推理服务。 ### 1. 准备推理服务 在别的机器或本机拉起一个视频生成服务,让它满足三个接口:提交、查询、下载。 完整契约见 **[docs/ENGINE-API.md](docs/ENGINE-API.md)**。 ### 2. 配置并启动 ```bash git clone <仓库地址> shotforge cd shotforge cp .env.example .env # 编辑 .env,至少填对 FASTH3_BASE / QWEN_BASE ./start.sh ``` 或者直接用环境变量: ```bash FASTH3_BASE=http://127.0.0.1:8239 PORT=8300 node server.js ``` ### 3. 打开 浏览器访问 。 页面右上角有两个状态灯(视频引擎 / LLM),变绿说明链路通了。 若引擎不通,任务会一直卡在提交重试,先看日志再排查。 引擎地址与模型随时能在右上角**设置**里改,不必重启。 --- ## 配置 全部配置集中在 `config.js`,来源优先级为 **环境变量 > `.env` > 内置默认值**。 常用项(完整清单见 [.env.example](.env.example)): | 变量 | 默认值 | 说明 | |---|---|---| | `HOST` | `127.0.0.1` | 监听地址。改成 `0.0.0.0` 才会对外,**当前版本无内置鉴权** | | `PORT` | `8300` | 监听端口 | | `DATA_DIR` | `<仓库>/data` | 数据目录。生产环境建议指到仓库外 | | `ENGINE_PRESET` | `h3-fast` | 初始引擎预置:`h3-fast` / `h3-full` / `openai-video` | | `FASTH3_BASE` | `http://127.0.0.1:8239` | `h3-fast` 预置的引擎地址 | | `H3_FAST_MODEL` | `minimax-fast-h3` | `h3-fast` 预置的模型 ID | | `H3_FULL_BASE` | `http://127.0.0.1:8238` | `h3-full` 预置的引擎地址 | | `H3_FULL_MODEL` | `MiniMax-H3` | `h3-full` 预置的模型 ID | | `VIDEO_ENGINE_BASE` | `http://127.0.0.1:8000` | `openai-video` 预置的引擎地址 | | `OPENAI_VIDEO_MODEL` | `video-1` | `openai-video` 预置的模型 ID | | `VIDEO_MODEL` | 无 | **已过时**,仅作为初始预置的模型 ID 后备 | | `QWEN_BASE` | `http://127.0.0.1:18080` | LLM 地址 | | `LLM_ENABLED` | `true` | 关掉后 AI 功能禁用,分镜回退规则拆分 | | `QUEUE_CONCURRENCY` | `1` | 同时提交给引擎的镜头数 | | `SINGLE_SHOT_MAX` | `14` | 单镜头时长上限(秒) | | `SHOT_MAX` | `10` | 分镜单片时长上限(秒) | | `PROJECT_MAX` | `300` | 分镜项目总时长上限(秒) | | `ESTIMATE_TABLE` | 内置表 | 进度条耗时预估,**换硬件务必按实测调整** | ### 关于 `SHOT_MAX` 与 `SINGLE_SHOT_MAX` 单镜时长越长,生成时的显存峰值越高。默认把分镜单片压到 10 秒,是刻意的保守取值—— 在显存紧张的机器上,长镜头容易把引擎直接打到 OOM。 机器宽裕可以调大,但建议先小步试。 ### 关于 `ESTIMATE_TABLE` 它只影响界面上的进度条,不影响实际生成。 默认表来自一台双卡统一内存工作站的实测值,**换成其他硬件后进度条会明显不准**。 调法:跑几个不同时长的任务记下实际耗时,或简单粗暴地只设 `ESTIMATE_PER_SECOND` 用统一估算。 --- ## 界面里改设置 不是所有配置都要改 `.env` 再重启。点右上角**设置**,可以当场改模型、地址与各类上限, 保存后立刻生效(正在跑的任务不受影响)。 设置里能改的:引擎预置与地址、模型 ID、LLM 开关与地址、时长上限、扩散步数、并发度、 TTS 音色与字体、ffmpeg 路径等。**监听地址、端口、数据目录**这三项只能改 `.env` 后重启, 界面上以只读形式显示。 生效优先级: ``` 环境变量 / .env > 内置默认值 ← 底子,config.js 产出 ↑ data/settings.json 里的覆盖 ← 你在界面上保存的,压在最上面 ``` 覆盖值存在 `data/settings.json`。**只有你真正改动过的项才会写进去**—— 打开设置什么都不改、直接保存,不会产生任何覆盖。所以 `.env` 里的值仍然随改随生效, 不会被界面悄悄固化。想全部还原,点设置里的**恢复默认**。 ### 切换引擎预置 预置决定「跟哪个视频服务说话」,每个预置自带一套默认地址与模型 ID。 选另一个预置时,界面会立刻把地址与模型显示成该预置实际会用的值,保存后生效。 引擎地址与模型 ID 是**预置专属**的:换预置会连带重置这两项,避免出现 「换了预置但模型还是上一个预置的」这种半切换状态。如果换完预置后你又手动改了地址或模型, 那就以你填的为准。 每个预置还声明两项**能力**,界面据此决定哪些选项可用: - `tasks` —— 这个后端接受哪些任务类型。内置三种:`t2va`(纯文本)、`fl2va`(首帧/首尾帧)、 `ref2va`(参考图)。**MiniMax-H3 的蒸馏加速版只声明 `t2va`**, 所以带关键帧的模式在 `h3-fast` 预置下会置灰;要用关键帧请切到 `h3-full` 或其他支持 `fl2va` 的后端。 - `locked` —— 被后端钉死的采样值。蒸馏版要求扩散步数恒为 4、画面引导 12、音频引导 3, 填别的值会被引擎拒绝。工具会在提交前把值纠正过来并写进任务日志, 而不是把请求发出去等引擎报错。 这两项是**后端的事实**而非本工具的偏好,所以放在预置里声明。换后端时如果它其实支持带图, 也可以在设置里手动打开「允许关键帧图片」来覆盖(前提是它确实接受)。 ### 关于画幅与分辨率 引擎对画幅的处理有两条不直观的规则,工具已按引擎的语义实现: - 纯文本(`t2va`)**必须**显式给一个画幅,否则引擎直接报错,所以画幅这项在纯文本模式下是必填的。 - 带关键帧(`fl2va`)时引擎**忽略**画幅,改用参考图自身的宽高比 —— 界面上会提示这一点。 - 指定了「输出分辨率」(像素尺寸)时,它**优先于**画幅生效;留空才由引擎按画幅算画布。 ### 测试连接 「服务地址」下面有个**测试连接**按钮,视频引擎和大模型各一个。 它测的是**你当前填在框里的值,不需要先保存** —— 地址填错了不用先落盘再回滚。 自检结果不止是「通 / 不通」,还会说明是哪一步没通: - **引擎**:分别探 `/v1/models`(能读到模型列表最好)和预置里配的提交接口。 提交接口回 404/405 这类状态会被正确解读 —— `405` 是「路径存在,只是不接受 GET」, 属于**正常**;两个都 `404` 则提示多半是地址指向了另一台服务、或路径前缀写多了。 - **大模型**:探 `/v1/models` 之后再**真发一次最小推理请求**。 「模型列表读得到、但推理起不来」是很常见的失败形态,只探列表会漏掉。 - **模型名校验**:会告诉你配置的模型 ID 在不在服务端的可用列表里; 不在的话实际会回退用哪个。模型 ID 留空即自动取第一个可用模型。 - **失败原因**分成「连接被拒绝 / 域名解析失败 / 连接超时 / 网络不可达 / 连接被重置」 等具体类别,并各给一句排查方向 —— 「端口没起」和「接口路径不对」的现象很像,需要分开。 自检**不读取也不修改**已保存的配置,纯只读探测。 ### 添加一个引擎后端 要让预置认得一个全新的推理服务,在 `config.js` 的 `PRESETS` 里加一项即可, `server.js` 不需要改: ```js 'my-engine': { label: '我的推理服务', baseEnv: 'MY_ENGINE_BASE', // 该预置专属的环境变量名 defaultBase: 'http://127.0.0.1:9000', modelEnv: 'MY_ENGINE_MODEL', defaultModel: 'my-model', paths: { // 三个接口的路径,改这里适配非 /v1/videos 的后端 submit: '/v1/videos', status: id => `/v1/videos/${id}`, content: id => `/v1/videos/${id}/content`, }, extraParams: () => ({}), // 每次提交附带的自定义字段 singleShotMax: 14, // 该后端的单镜时长能力上限 tasks: ['t2va', 'fl2va'], // 接受哪些任务类型;不带图的后端只写 ['t2va'] locked: null, // 被后端钉死的采样值,如 { numInferenceSteps: 4, flowShift: 12 } } ``` 关键帧的字段名按引擎接口约定:**1 张图用单数 `input_reference`,2 张图用复数 `input_references`**。这不是风格选择 —— 复数通道只有在后端声明支持混合输入时才会把上传物 当图片解析,否则一律按视频校验;单数通道两种后端都收。帧的语义(首帧/尾帧)通过 `extra_params.frame_indices` 传递,取值 `[0]` / `[-1]` / `[0,-1]`。 --- ## 外部依赖与授权 本项目自身是 **Apache-2.0**。它通过**外部可执行文件 / 子进程**方式调用下列组件, 这些组件**不包含在仓库中**,也不随本项目分发: | 组件 | 许可证 | 调用方式 | |---|---|---| | ffmpeg | LGPL / GPL(取决于构建) | 独立可执行文件 | | edge-tts | GPL-3.0 | `python3 -m edge_tts` | | Real-ESRGAN | BSD-3-Clause | 独立可执行文件 | 以子进程方式调用通常被视为**聚合**而非衍生,不会传染本项目的许可。 但如果你的使用场景对许可证敏感,请自行确认。 完整声明见 **[NOTICE](NOTICE)**。 > **商标**:项目名中的 "MiniMax"、"Qwen" 等名称是各自权利人的商标。 > 本项目为独立实现,与上述厂商无从属或背书关系。 --- ## 架构 ``` 浏览器(原生 HTML/JS,无构建) │ JSON + HTTP Range ▼ server.js(Node 原生 http,零依赖) ├── 任务队列 waiting → submitting → generating → downloading → completed ├── 分镜规划 规则拆分 / LLM 智能分镜 ├── 引擎适配 config.js PRESETS ├── 运行时配置 settings.js(默认值 + 界面覆盖,就地生效无需重启) ├── 记录持久化 data/jobs.json + data/projects.json └── 后期处理 ffmpeg / edge-tts / Real-ESRGAN │ ▼ 视频引擎(/v1/videos) LLM(/v1/chat/completions) ``` 代码结构: ``` config.js 静态配置 + 引擎预置注册表(默认值来源) settings.js 运行时可调配置:三层合并、校验、持久化 compose.js 后期合成命令拼装(纯函数,便于断言参数矩阵) server.js 全部后端逻辑(队列、分镜、容错、后期、关键帧、HTTP 路由) public/index.html 单文件前端(含设置抽屉与逐镜改稿弹窗) test/settings-api.test.js 设置接口 + 任务列表分页端到端测试(自包含) test/frames-api.test.js 关键帧 / 三段式提示词 / 出站报文端到端测试(自包含) test/compose-args.test.js 合成参数矩阵与滤波标签不变量(不需要 ffmpeg) test/shots-api.test.js 逐镜改稿重跑端到端测试(含出站报文抓包核对) docs/ENGINE-API.md 引擎接口契约 scripts/fetch-realesrgan.sh 可选的超分组件下载 data/ 运行时数据(已 gitignore) ``` 配置分成两层是有意的:**改哪个模型、哪个地址**属于日常运行会变的东西,放在 `settings.js` 管、界面里随时改;**监听哪个网卡、数据放哪**属于部署形态,放在 `config.js` 管、只认环境变量。 新增一个引擎后端:在 `config.js` 的 `PRESETS` 里加一项,声明 `baseEnv` / `paths` / `extraParams` 即可,界面会自动出现这个预置,`server.js` 不需要改。 ### 任务列表接口 `GET /api/state` ``` /api/state?page=1&size=10&filter=all ``` 返回 `{ items, page, size, pages, total, counts, feed }`: | 字段 | 说明 | | --- | --- | | `items` | **当前页**的完整条目(项目 + 独立镜头按创建时间倒序混排),含 log / shots / URL | | `counts` | 各筛选下的条数,**始终按全集统计**,不随当前筛选变化 | | `feed` | **全部**条目的精简状态摘要,只带 `id / type / status` 等提醒文案需要的字段 | 两个刻意的设计: - **筛选规则只在后端实现一份**(`matchStatus`)。前端只负责渲染,避免两边各写一套后慢慢对不上。 - **`feed` 覆盖全部条目而不是当前页**。否则翻到第 2 页时,第 1 页上的任务完成了不会弹提醒 —— 而「切走页面」恰恰是用户最需要提醒的时候。同时它刻意不带 log / shots / URL, 这样每 3 秒的轮询负载不随历史记录数增长。 页码越界会自动收敛到最后一页,所以删空当前页后不会停在空白页上。 ### 关键帧接口 | 接口 | 说明 | | --- | --- | | `POST /api/frames?name=xxx.png` | 上传关键帧,请求体是原始字节。服务端按魔数识别真实格式(PNG / JPG / WebP / HEIC),读出宽高并校验,返回 `{ id, ext, w, h, bytes, url }` | | `GET /api/frames/:id.:ext` | 预览原图(扩展名走白名单,路径拼接前先校验,不可穿越目录) | | `DELETE /api/frames/:id.:ext` | 手动删除一张关键帧 | 图片的校验规则写在 `server.js` 的 `FRAME_RULES`:短边 ≥ 256、长边 ≤ 5760、 宽高比 0.4~2.5、单张 ≤ 30 MiB —— 这些是引擎的实际约束,不是本工具定的, 所以**被拒说明引擎也会拒**,早点拦下来比发出去等报错省事。 关键帧的文件名就是 `.`,随任务记录留档。删除任务时按**引用计数**清理: 同一张图被两条任务共用时不会删,等最后一个引用者被删掉才真正清文件 —— 否则另一条任务重试时会找不到图片。 ### 逐镜改稿重跑 `POST /api/projects/:id/shots/:index` ```jsonc { "prompt": "...", "negative_prompt": "...", "seconds": 6, "seed": 12345 } ``` 四个字段都可选,只传要改的。`seed` 传 `null` 表示交回引擎随机(不是 0 —— 0 是合法种子)。 成功返回该项目的最新视图;**被拒绝时不改动任何状态**,连任务 id 都不变。 拒绝的情况:项目正在运行、该镜还在生成中、镜号或项目不存在、提示词为空、 时长越界、种子不是 `0 ~ 4294967295` 的整数、负向提示词超过 2000 字。 三个刻意的设计: - **负向提示词必须在逐镜编辑里**。它在项目里是创建时一次性下发到每一镜的,事后没有别的入口能改; 而「某一镜画出了乱码文字」恰恰只能靠它救。 - **改稿即清空粗剪与成片的引用**。否则界面上会同时挂着旧片和新镜头, 而旧片已经和新画面不一致了 —— 宁可先没有,也不给人看错的。 - **不做逐镜预览视频**。改完就重跑,该镜跑完由既有的收敛流程自动重新合并与后期, 不需要为「只跑一镜」单独造一条链路。 --- ## 常见问题 **任务一直卡在「提交中」** 引擎不可达。**先点设置里的「测试连接」**,它会直接告诉你卡在哪一步: 端口没服务、域名解析不了、路径写多了、还是服务自身 5xx。 命令行等价写法:`curl -s /v1/videos`。 注意区分「端口没起」和「接口路径不对」——两者现象很像,自检里的状态码能分开。 **生成完成但合并失败** 多数是没装 ffmpeg,或 ffmpeg 缺 `libx264`。 用 `ffmpeg -encoders | grep libx264` 确认。 **超分报「未找到超分组件」** 运行 `./scripts/fetch-realesrgan.sh`,或用 `UPSCALE_BIN` 指定路径。 **配音报错** 需要 `pip install edge-tts`,且 `TTS_BIN` 指向的 python 里装了这个包。 **进度条和实际耗时差很多** 正常。调 `ESTIMATE_TABLE` / `ESTIMATE_PER_SECOND`,见上文。 **`jobs.json` 损坏了怎么办** 服务启动时若解析失败,会把原文件改名为 `jobs.json.corrupt-<时间戳>` 留档后从空记录启动, 不会静默丢数据。 **「首帧图 / 尾帧图 / 首尾帧」是灰的,点不了** 当前引擎预置不支持带图。MiniMax-H3 的**蒸馏加速版只接受纯文本**, 在设置里把预置切到 `h3-full`(或另一个声明了 `fl2va` 的后端)即可。 置灰旁边会给出去不去的具体原因。 **上传图片被拒,报「短边不足 / 长边超限 / 宽高比超出范围」** 这些阈值来自引擎自身的校验,不是本工具加的限制,因此这张图发给引擎也会被拒。 按提示调整尺寸即可;分辨率为 768p 量级的常见尺寸(如 `1344x768`、`1280x720`)都在范围内。 **带图时填的画幅好像没生效** 不是没生效,是引擎的语义:带关键帧(`fl2va`)时引擎**忽略**画幅参数, 改用参考图自身的宽高比。界面上会相应地提示。纯文本(`t2va`)则相反 —— **必须**显式给一个画幅,否则引擎直接报错。 **我填的引导强度 / 步数被改了** 该后端把这两个值钉死了(蒸馏版要求步数 4、引导 12、音频引导 3), 填别的会被引擎拒绝。工具在提交前纠正并写进任务日志,日志里能看到「改用 X:当前引擎为蒸馏版」。 **生成的视频里为什么没有我产品的界面 / 截图 / 文字** 视频模型画不出可读的文字和真实的界面,硬要求它画,得到的是**形似字母的笔画** (比如把品牌名渲染成一串近似的拉丁字母,把数字渲染成扭曲的金色光条)。 所以「画面里要有产品截图」这件事不靠提示词解决,有两条路: 1. **把截图作为首帧 / 尾帧喂给引擎**(`h3-full` 预置,见上文置灰那一条)。 适合「从一个确定的画面开始动起来」,但引擎会让画面继续演化,界面会被渐渐改写掉。 2. **后期叠加真截图**(推荐,成品最干净)。生成时让画面**留白**, 再把原生截图用 ffmpeg 叠上去 —— 截图里的字是像素级的,永远清晰。 本工具的 `post.narration` 之外没有内置这条路径,需要自己拼一条 ffmpeg 命令, 例如「第 6~11 秒在底部居中浮出一块带白边的画中画、前后各 0.5 秒淡入淡出」: ```bash ffmpeg -i rough.mp4 -loop 1 -t 21 -i product.png -filter_complex " [1:v]scale=780:-1,pad=iw+16:ih+16:8:8:color=white,format=rgba, fade=t=in:st=6:d=0.5:alpha=1,fade=t=out:st=10.5:d=0.5:alpha=1[pip]; [0:v][pip]overlay=x=(W-w)/2:y=H-h-56:enable='between(t,6,11)'" \ -c:v libx264 -crf 20 -pix_fmt yuv420p -c:a copy -movflags +faststart out.mp4 ``` 要「整屏展示几秒」就把缩放换成 `scale=1344:768:force_original_aspect_ratio=decrease,pad=1344:768:(ow-iw)/2:(oh-ih)/2`, overlay 用 `0:0`。`fade` 的 `st` 是绝对秒数,所以输入图要用 `-loop 1 -t <片长>` 铺满时间轴。 **某一镜画坏了,能只重跑这一镜吗** 可以,任务卡片上的「改稿重跑」。改完不用管别的镜头,也不用重跑整个项目 —— 该镜跑完会自动重新合并和重新做后期。见上文「逐镜改稿重跑」。 --- ## 路线图 - [x] **v0.1** 配置外置、引擎预置抽象、界面设置面板、连通性自检、任务列表分页、脱敏后可发布 - [x] **v0.1.1** 画面来源与关键帧上传、三段式提示词、画幅 / 分辨率 / 负向提示词 / 种子、 预置能力声明(可用任务类型与钉死采样值)、逐镜改稿重跑、合成参数矩阵抽取与测试、界面截图 - [ ] **v0.2** 可选鉴权与 API Token、Dockerfile + compose - [ ] **v0.3** 纯函数单元测试与 CI 矩阵、结构化日志 - [ ] **v0.4** 批量生成、多版本对比、项目导入导出、完成通知 - [ ] **v0.5** 后期叠加图片 / 截图(画中画与整屏插图)、英文界面、暗色模式 已明确**不做**的事:内置模型权重、做推理优化、提供公开托管服务。 --- ## 开发 ```bash npm run check # 源文件与测试文件的语法检查 npm test # 依次跑下面两套端到端测试 npm run test:settings # 设置接口 + 任务列表分页 npm run test:frames # 关键帧 / 三段式提示词 / 出站报文 ``` 两套测试都是自包含的:在随机空闲端口拉起一个 `server.js` 子进程、把 `DATA_DIR` 指到临时目录, 跑完断言后自动清理,因此可以随时重复执行,也不会影响你正在用的实例。 `test/frames-api.test.js` 还会**在测试进程内起一个假视频引擎**,把收到的 multipart 报文 原样抓下来。它断言的不是接口返回值,而是**真正发出去的字节** —— part 名、文件名、 Content-Type、`extra_params` 的内容、图片是否被原样转发(而不是被 base64 或 JSON 化)。 这些东西读代码猜不出来,只能抓包看;`input_reference` 单数/复数的区别就是这么定下来的。 --- ## 贡献 欢迎 issue 和 PR。提交前请阅读 [CONTRIBUTING.md](CONTRIBUTING.md), 并特别注意:**不要提交 `data/` 或 `bin/` 下的任何内容**。 PR 请提到 `dev` 分支(`main` 是受保护分支,只接受管理员合并)。 --- ## 授权 [Apache License 2.0](LICENSE) © 2026 wangys --- ## English **ShotForge Studio** is a storyboard-driven web workbench for AI video generation. It handles the parts that a raw text-to-video API does not: splitting long videos into coherent shots, queuing jobs, polling with retry and restart recovery, concatenating shots with ffmpeg, and optionally adding upscaling, TTS dubbing, subtitles and BGM. - **Zero npm dependencies, no build step** — a single Node `http` server plus one HTML file. - **Brings no models.** You supply an inference service implementing the contract in [docs/ENGINE-API.md](docs/ENGINE-API.md). - **Engine presets** declare only the interface shape, so supporting a new backend means adding one config entry. Model, endpoint and limits can also be changed from the in-app settings panel and take effect immediately, without editing `.env` or restarting. - **Connection self-check** in the same panel tests whatever address is currently typed in (no need to save first), and tells apart "cannot reach it" from "reached it, but the path or model is wrong". - Licensed under Apache-2.0. Third-party tools (ffmpeg, edge-tts, Real-ESRGAN) are invoked as external processes and are not bundled — see [NOTICE](NOTICE).