reelforge 1.20.2 → 1.21.0

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
@@ -113,6 +113,7 @@ export function registerAssets(program) {
113
113
  "spec 的结构与示例见 `rf compose --help`。",
114
114
  ].join("\n"))
115
115
  .action(() => {
116
+ warn("`rf assets workflow` 已更名 → 用 `rf vlog`(列赛道)/ `rf vlog pet`(宠物 SOP)。下方为旧版通用 SOP。");
116
117
  process.stdout.write(ASSETS_WORKFLOW_TEXT);
117
118
  if (!ASSETS_WORKFLOW_TEXT.endsWith("\n"))
118
119
  process.stdout.write("\n");
@@ -0,0 +1,83 @@
1
+ import fs from "node:fs";
2
+ import path from "node:path";
3
+ import { fileURLToPath } from "node:url";
4
+ const __filename = fileURLToPath(import.meta.url);
5
+ const __dirname = path.dirname(__filename);
6
+ const VERTICALS = [
7
+ {
8
+ id: "pet",
9
+ label: "宠物第一视角搞笑 vlog",
10
+ blurb: "用户拍的猫狗图 / 视频 + 一句话 → 宠物第一人称搞笑竖屏短视频",
11
+ },
12
+ ];
13
+ function loadPlaybook(id) {
14
+ const candidates = [
15
+ path.resolve(__dirname, "../data/vlog", `${id}.md`),
16
+ path.resolve(__dirname, "../../../docs/vlog", `${id}.md`),
17
+ ];
18
+ for (const p of candidates) {
19
+ try {
20
+ return fs.readFileSync(p, "utf8");
21
+ }
22
+ catch {
23
+ }
24
+ }
25
+ return null;
26
+ }
27
+ function indexText() {
28
+ const lines = [
29
+ "rf vlog — 用素材做视频的「赛道剧本」(给 agent 读的 use-case SOP)",
30
+ "",
31
+ "底层能力是 `rf assets describe`(看素材) + `rf compose`(渲染);本命名空间只给",
32
+ "「怎么把一条赛道落地」的引导。写旁白是 agent 的活,引擎不替写。",
33
+ "",
34
+ "可用赛道:",
35
+ ];
36
+ for (const v of VERTICALS) {
37
+ lines.push(` ${v.id.padEnd(8)} ${v.label}`);
38
+ lines.push(` ${" ".repeat(8)} ${v.blurb}`);
39
+ }
40
+ lines.push("");
41
+ lines.push("用法:");
42
+ lines.push(" rf vlog pet 打印「宠物赛道」完整剧本(SOP)");
43
+ lines.push(" rf vlog pet | less 分页浏览");
44
+ return lines.join("\n");
45
+ }
46
+ export function registerVlog(program) {
47
+ const cmd = program
48
+ .command("vlog")
49
+ .description("素材→视频 垂类剧本 (给 agent 读的 use-case SOP;底层用 rf assets describe + rf compose)")
50
+ .helpOption("-h, --help", "show help")
51
+ .action(() => {
52
+ process.stdout.write(indexText() + "\n");
53
+ });
54
+ for (const v of VERTICALS) {
55
+ cmd
56
+ .command(v.id)
57
+ .description(`打印「${v.label}」完整剧本 (SOP) 到 stdout`)
58
+ .helpOption("-h, --help", "show help")
59
+ .addHelpText("after", ["", `不带 -h 直接运行 \`rf vlog ${v.id}\` 打印完整剧本。`, "适合 agent 启动时一次性读入。"].join("\n"))
60
+ .action(() => {
61
+ const text = loadPlaybook(v.id);
62
+ if (text) {
63
+ process.stdout.write(text);
64
+ if (!text.endsWith("\n"))
65
+ process.stdout.write("\n");
66
+ return;
67
+ }
68
+ process.stdout.write([
69
+ `# rf vlog · ${v.label}`,
70
+ "",
71
+ "(本机未找到完整剧本文件;可能是旧版安装。)",
72
+ "核心流程:",
73
+ " 1. rf assets describe ./素材目录/ -o assets.json # 让 agent 看懂素材",
74
+ " 2. agent 基于真实画面写第一人称旁白(不要虚构画面没发生的动作)",
75
+ " 3. 写 compose.v3 spec,音色查 `rf tts voices --provider relayx --model vox/index-tts-2`",
76
+ " 4. rf compose spec.json -o ./final.mp4",
77
+ "",
78
+ "spec 字段见 `rf compose -h`,封面模板见 `rf cover templates`。",
79
+ "",
80
+ ].join("\n"));
81
+ });
82
+ }
83
+ }
@@ -0,0 +1,155 @@
1
+ # rf vlog · 宠物赛道剧本(pet)
2
+
3
+ > 给**上层 agent**(你,正在帮用户做视频的 Claude Code / Cursor 等)读的工作指南。
4
+ > 教你怎么把用户的宠物素材 + 一句话,做成一条第一视角搞笑竖屏 vlog。
5
+ >
6
+ > **角色边界(先记死)**
7
+ > - 引擎只给你两样**你做不到**的能力:`rf assets describe`(给视频「眼睛」,你看不到视频帧)、`rf compose`(渲染 + 配音 + 字幕对齐)。
8
+ > - **写旁白 / 脚本是你的活,不是引擎的。** 本剧本只给你「怎么写」的最佳实践,绝不替你写一个字。
9
+ > - 字段 / 参数的精确用法一律以 `rf compose -h`、`rf cover -h` 为准。
10
+
11
+ ---
12
+
13
+ ## Step 0 · 动手前先确认输入(别让用户从 0 摸索)
14
+
15
+ 宠物赛道的输入清单 + 默认值。**只问灵魂 2 问,其余用默认,别拿用户看不懂的选项去烦他**:
16
+
17
+ | 输入 | 默认 | 要问吗 |
18
+ |---|---|---|
19
+ | 素材目录 | — | 必给(让用户指一个文件夹) |
20
+ | **故事方向 / 今天发生了啥** | — | **灵魂问 1** |
21
+ | **音色** | 熊小二(推荐,非强制) | **灵魂问 2**:挑 2-3 个候选给用户选,别让他盲选 |
22
+ | 封面 | 自动取一张高光素材 | 用户想要精致封面时,把模板预览图发他挑 |
23
+ | 字幕风格 | stroke(描边,短视频爆款风) | 默认,不问 |
24
+ | BGM / 分辨率 / 画幅 | 默认 BGM / 1080×1920 / blur-bg | 默认,不问 |
25
+
26
+ ---
27
+
28
+ ## Step 1 · 看素材(必做,这是你「看到」素材的唯一方式)
29
+
30
+ ```bash
31
+ rf assets describe ./素材目录/ -o assets.json
32
+ ```
33
+
34
+ 返回每个素材的客观描述(图片 ~30 字 / 视频 ~80 字含动作与氛围)+ 视频时长。
35
+ **这是事实地基。后面写旁白,只能基于这里描述出来的真实动作。**
36
+
37
+ ## Step 2 · 你来写旁白 + 给用户过故事板
38
+
39
+ 引擎不替你写。按下面《写作最佳实践》自己写,然后用**人话故事板**(不是 JSON)给用户确认。
40
+
41
+ ## Step 3 · 渲染
42
+
43
+ ```bash
44
+ rf compose pet.compose.json -o ./final.mp4
45
+ ```
46
+
47
+ ---
48
+
49
+ ## ★ 写作最佳实践(宠物第一视角搞笑)
50
+
51
+ ### 人设 brief
52
+ - 第一人称,宠物自己开口。称主人「铲屎的」,自称「本喵 / 本汪」。
53
+ - 调性:**痞帅自信 → 怂的反差**。先吹牛,翻车了立刻甩锅装无辜。
54
+ - 多内心戏、多吐槽;句子口语、短、有梗。
55
+
56
+ ### 🚫 反虚构铁律(最重要,违反 = 废片)
57
+ **旁白只能解说画面里 `describe` 出来的真实动作。**
58
+ - ✅ 可以编:情绪、内心戏、动机、夸张比喻(画面在踱步 → 写「围着鱼缸转了八圈」可以)。
59
+ - ❌ 不准编:画面没发生的物理事件。
60
+ - 画面是「伸爪够、够不着」→ 不能写「一爪把鱼捞上来」「鱼归我了」。
61
+ - 画面是「趴着不动」→ 不能写「它一跃而起」。
62
+ - 不确定画面里到底发生没发生 → 当作没发生,别写。
63
+
64
+ ### 句子写法
65
+ - 写**完整句子、句末带标点**(。!?)。每句 10~30 字都行。
66
+ - **别为字幕节奏手动拆短**——字幕切分是引擎的活(compose 内部按真实配音时间对齐)。
67
+ - 每条旁白是一个独立句子,放进对应 scene 的 `narration` 数组里。
68
+
69
+ ### 范例(同一段「够鱼」画面)
70
+ - ✅ 「想吃,真想吃,可这缸里装的居然是水?本喵金贵爪子凭啥沾水。」
71
+ - ❌ 「本喵一爪下去,鱼就归我了。」(画面根本没捞到——虚构)
72
+
73
+ ---
74
+
75
+ ## 音色怎么选(不强制熊小二)
76
+
77
+ 熊小二只是**推荐默认**(痞帅搞笑,贴宠物号),用户想用别的随时换。
78
+
79
+ 查全部音色:
80
+
81
+ ```bash
82
+ rf tts voices --provider relayx --model vox/index-tts-2
83
+ ```
84
+
85
+ 按风格筛,例如想要解说腔:
86
+
87
+ ```bash
88
+ rf tts voices --provider relayx --model vox/index-tts-2 | grep 解说
89
+ ```
90
+
91
+ **别让用户从一长串里盲选**:你按他要的调性挑 2-3 个候选报给他,他点一个。
92
+ 选定后填进 spec 的 `audio.tts_voice`(就是音色名,如 `"熊小二"`)。
93
+
94
+ ---
95
+
96
+ ## 封面怎么选
97
+
98
+ compose 的 `cover.image` 只收**一张图**。两种做法:
99
+
100
+ **A. 省事**:直接挑一张高光素材当封面 → `"cover": { "image": "高光帧.jpg" }`。
101
+
102
+ **B. 套模板(精致)**:引擎内置多个封面模板。先看长啥样——每个模板都带一条预览图链接:
103
+
104
+ ```bash
105
+ rf cover templates # 人读表格 + 预览链接
106
+ rf cover templates --json # agent 消费,含每个模板的 preview_url
107
+ ```
108
+
109
+ **把这些预览裸链接发给用户,让他看图挑**(别凭模板名猜)。用户选定后渲封面:
110
+
111
+ ```bash
112
+ rf cover -i 高光帧.jpg -t "橘子大战鱼缸" --template <模板id> -o cover.png
113
+ ```
114
+
115
+ (`--subtitle` / `--badge` / `--param` 等精确用法见 `rf cover -h`。)
116
+ 然后填进 spec:`"cover": { "image": "cover.png" }`。
117
+
118
+ ---
119
+
120
+ ## compose.v3 spec 模板(pet 默认值已填好)
121
+
122
+ ```json
123
+ {
124
+ "experimental": "compose.v3",
125
+ "output": { "width": 1080, "height": 1920, "fps": 30, "layout": "blur-bg" },
126
+ "audio": { "tts_model": "vox/index-tts-2", "tts_voice": "熊小二", "bgm_volume": 0.12 },
127
+ "subtitle_style": "stroke",
128
+ "title": "可选,片头标题",
129
+ "cover": { "image": "封面.jpg" },
130
+ "scenes": [
131
+ { "video": "clip1.mp4", "muted": true, "narration": ["句1。", "句2。"] },
132
+ { "image": "photo1.jpg", "motion": "zoom-in", "narration": ["句1。"] }
133
+ ]
134
+ }
135
+ ```
136
+
137
+ - **横屏视频用 `layout: "blur-bg"`**:视频缩放居中 + 模糊垫底,绝不裁掉主角。
138
+ - spec 里**没有任何秒数**;时间由引擎量真实配音派生。
139
+ - 旁白尽量 ≤ 视频时长,省掉片头黑卡;真比视频长时引擎会自动前置一张标题卡。
140
+
141
+ ### ⚠️ 照片朝向
142
+ 手机竖拍照片常带旋转标记,直接喂可能是横躺的。喂前先摆正、抽帧确认方向:
143
+
144
+ ```bash
145
+ ffmpeg -noautorotate -i in.jpg -vf "transpose=1" up.jpg
146
+ ```
147
+
148
+ ---
149
+
150
+ ## 提交前自查
151
+ - [ ] 跑过 `rf assets describe`?
152
+ - [ ] 旁白每一句都对得上画面真实动作?(过一遍反虚构铁律)
153
+ - [ ] 给用户看的是**人话故事板**,不是裸 JSON?
154
+ - [ ] 照片朝向已摆正?
155
+ - [ ] output 1080×1920、横屏视频 blur-bg?
package/dist/index.js CHANGED
@@ -34,6 +34,7 @@ import { registerExtract } from "./commands/extract.js";
34
34
  import { registerMedia } from "./commands/media.js";
35
35
  import { registerScript } from "./commands/script.js";
36
36
  import { registerCompose } from "./commands/compose.js";
37
+ import { registerVlog } from "./commands/vlog.js";
37
38
  import { error as logError } from "./utils/output.js";
38
39
  import { ApiCallError } from "./client.js";
39
40
  const program = new Command();
@@ -111,8 +112,10 @@ program.addHelpText("after", [
111
112
  " rf history list 看之前生成的视频",
112
113
  " rf tasks list --status running 看进行中的任务",
113
114
  "",
114
- "Agent 工作流(use-case SOP,挂在用例命名空间下)",
115
- " rf assets workflow 用户素材→视频 SOP(给 agent 看的)",
115
+ " vlog?(用户拍的素材 → 视频,如宠物号)先看赛道剧本:",
116
+ " rf vlog 列出可用赛道(宠物等)",
117
+ " rf vlog pet 打印「宠物第一视角搞笑」完整 SOP",
118
+ " → 拿到 SOP 后照着走:rf assets describe(看素材) → 写旁白 → rf compose(渲染)",
116
119
  ].join("\n"));
117
120
  registerAuth(program);
118
121
  registerCreate(program);
@@ -142,6 +145,7 @@ registerExtract(program);
142
145
  registerMedia(program);
143
146
  registerScript(program);
144
147
  registerCompose(program);
148
+ registerVlog(program);
145
149
  async function main() {
146
150
  if (process.argv.length <= 2) {
147
151
  program.outputHelp();
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "reelforge",
3
- "version": "1.20.2",
3
+ "version": "1.21.0",
4
4
  "description": "AI 视频生成 CLI。一句话主题或自己的脚本 → 自动出抖音/TikTok/视频号竖屏 MP4。安装即用:`reelforge` 或短别名 `rf`。",
5
5
  "license": "Apache-2.0",
6
6
  "type": "module",