reelforge 0.5.5 → 0.7.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.
- package/README.md +240 -222
- package/dist/commands/audio.js +73 -0
- package/dist/commands/content.js +50 -96
- package/dist/commands/create.js +179 -213
- package/dist/commands/pipelines.js +52 -34
- package/dist/commands/subtitles.js +40 -0
- package/dist/index.js +5 -1
- package/package.json +51 -51
package/dist/commands/content.js
CHANGED
|
@@ -4,109 +4,63 @@ import { print } from "../utils/output.js";
|
|
|
4
4
|
export function registerContent(program) {
|
|
5
5
|
const content = program
|
|
6
6
|
.command("content")
|
|
7
|
-
.description("
|
|
7
|
+
.description("Content atomics — scene planning (master script + image prompts in one call)")
|
|
8
8
|
.helpOption("-h, --help", "show help");
|
|
9
9
|
content
|
|
10
|
-
.command("
|
|
11
|
-
.description("Generate
|
|
10
|
+
.command("scene-plan")
|
|
11
|
+
.description("Generate a master script + per-scene image prompts (replaces narration/image-prompts/title)")
|
|
12
12
|
.helpOption("-h, --help", "show help")
|
|
13
|
-
.
|
|
14
|
-
.option("
|
|
15
|
-
.option("
|
|
16
|
-
.option("
|
|
17
|
-
.
|
|
18
|
-
.action(async (opts) => {
|
|
19
|
-
const r = await post("/api/v1/content/narration", {
|
|
20
|
-
topic: opts.topic,
|
|
21
|
-
n_scenes: opts.nScenes,
|
|
22
|
-
min_words: opts.minWords,
|
|
23
|
-
max_words: opts.maxWords,
|
|
24
|
-
});
|
|
25
|
-
print(r);
|
|
26
|
-
});
|
|
27
|
-
content
|
|
28
|
-
.command("split")
|
|
29
|
-
.description("Split a fixed script into narrations (no LLM cost)")
|
|
30
|
-
.helpOption("-h, --help", "show help")
|
|
31
|
-
.requiredOption("-s, --script <text>", "raw script text (use @file for a file)")
|
|
32
|
-
.option("-m, --mode <mode>", "paragraph | line | sentence", "paragraph")
|
|
33
|
-
.addHelpText("after", "\nExample:\n reelforge content split -s @script.txt -m sentence")
|
|
34
|
-
.action(async (opts) => {
|
|
35
|
-
let script = opts.script;
|
|
36
|
-
if (script.startsWith("@"))
|
|
37
|
-
script = await fs.readFile(script.slice(1), "utf-8");
|
|
38
|
-
const r = await post("/api/v1/content/narration/split", { script, mode: opts.mode });
|
|
39
|
-
print(r);
|
|
40
|
-
});
|
|
41
|
-
content
|
|
42
|
-
.command("image-prompts")
|
|
43
|
-
.description("Generate English image-generation prompts from narrations")
|
|
44
|
-
.helpOption("-h, --help", "show help")
|
|
45
|
-
.requiredOption("-i, --narrations <file>", "file with one narration per line (or @file)")
|
|
46
|
-
.option("--prefix <text>", "style prefix prepended to each prompt")
|
|
47
|
-
.option("--min-words <n>", "minimum words per prompt", parseInt, 30)
|
|
48
|
-
.option("--max-words <n>", "maximum words per prompt", parseInt, 60)
|
|
49
|
-
.addHelpText("after", "\nExample:\n reelforge content image-prompts -i narrations.txt --prefix 'cinematic'")
|
|
50
|
-
.action(async (opts) => {
|
|
51
|
-
let src = opts.narrations;
|
|
52
|
-
if (src.startsWith("@"))
|
|
53
|
-
src = src.slice(1);
|
|
54
|
-
const text = await fs.readFile(src, "utf-8");
|
|
55
|
-
const narrations = text.split(/\r?\n/).map((s) => s.trim()).filter(Boolean);
|
|
56
|
-
const r = await post("/api/v1/content/image-prompts", {
|
|
57
|
-
narrations,
|
|
58
|
-
prompt_prefix: opts.prefix,
|
|
59
|
-
min_words: opts.minWords,
|
|
60
|
-
max_words: opts.maxWords,
|
|
61
|
-
});
|
|
62
|
-
print(r);
|
|
63
|
-
});
|
|
64
|
-
content
|
|
65
|
-
.command("title")
|
|
66
|
-
.description("Generate a short video title from content")
|
|
67
|
-
.helpOption("-h, --help", "show help")
|
|
68
|
-
.requiredOption("-c, --content <text>", "content to title (use @file)")
|
|
69
|
-
.option("--max-length <n>", "maximum characters", parseInt, 15)
|
|
70
|
-
.action(async (opts) => {
|
|
71
|
-
let body = opts.content;
|
|
72
|
-
if (body.startsWith("@"))
|
|
73
|
-
body = await fs.readFile(body.slice(1), "utf-8");
|
|
74
|
-
const r = await post("/api/v1/content/title", {
|
|
75
|
-
content: body,
|
|
76
|
-
max_length: opts.maxLength,
|
|
77
|
-
});
|
|
78
|
-
print(r);
|
|
79
|
-
});
|
|
80
|
-
content
|
|
81
|
-
.command("asset-script")
|
|
82
|
-
.description("Generate a scene script that assigns user-uploaded assets to scenes")
|
|
83
|
-
.helpOption("-h, --help", "show help")
|
|
84
|
-
.requiredOption("--intent <text>", "video intent / purpose")
|
|
85
|
-
.option("--title <text>", "optional video title")
|
|
86
|
-
.option("--duration <s>", "target duration in seconds", parseInt, 30)
|
|
87
|
-
.requiredOption("--assets <file>", "file with one asset per line, format: `path | description`")
|
|
13
|
+
.option("-t, --topic <text>", "video topic; AI writes the script (generate mode). Use @file for disk input.")
|
|
14
|
+
.option("--script <text>", "your own master script text (fixed mode). Use @file for disk input.")
|
|
15
|
+
.option("-d, --duration <sec>", "target video duration in seconds (generate mode; default 45)", (v) => parseInt(v, 10))
|
|
16
|
+
.option("-p, --pace <pace>", "visual rhythm hint: slow | normal | fast (default normal)")
|
|
17
|
+
.option("-m, --model <id>", "override LLM model")
|
|
88
18
|
.addHelpText("after", [
|
|
89
19
|
"",
|
|
90
|
-
"
|
|
91
|
-
"
|
|
92
|
-
"
|
|
20
|
+
"Two modes (exactly one required):",
|
|
21
|
+
" generate -t / --topic <text> LLM writes both script and image prompts",
|
|
22
|
+
" fixed --script @file or text LLM only segments + writes image prompts; text unchanged verbatim",
|
|
23
|
+
"",
|
|
24
|
+
"Examples:",
|
|
25
|
+
" rf content scene-plan -t '深夜便利店' -d 60 -p slow",
|
|
26
|
+
" rf content scene-plan --script @./my-script.txt -p fast",
|
|
27
|
+
" rf content scene-plan -t '雨天的玻璃窗' --json | jq .scenes",
|
|
93
28
|
].join("\n"))
|
|
94
29
|
.action(async (opts) => {
|
|
95
|
-
const
|
|
96
|
-
const
|
|
97
|
-
|
|
98
|
-
|
|
99
|
-
|
|
100
|
-
|
|
101
|
-
|
|
102
|
-
|
|
103
|
-
|
|
104
|
-
|
|
105
|
-
|
|
106
|
-
|
|
107
|
-
|
|
108
|
-
|
|
30
|
+
const hasTopic = typeof opts.topic === "string" && opts.topic.length > 0;
|
|
31
|
+
const hasScript = typeof opts.script === "string" && opts.script.length > 0;
|
|
32
|
+
if (!hasTopic && !hasScript) {
|
|
33
|
+
throw new Error("either --topic / -t or --script is required");
|
|
34
|
+
}
|
|
35
|
+
if (hasTopic && hasScript) {
|
|
36
|
+
throw new Error("--topic and --script are mutually exclusive");
|
|
37
|
+
}
|
|
38
|
+
if (opts.pace && !["slow", "normal", "fast"].includes(opts.pace)) {
|
|
39
|
+
throw new Error(`--pace must be one of slow|normal|fast (got: ${opts.pace})`);
|
|
40
|
+
}
|
|
41
|
+
let topic = opts.topic;
|
|
42
|
+
let script = opts.script;
|
|
43
|
+
if (topic?.startsWith("@"))
|
|
44
|
+
topic = (await fs.readFile(topic.slice(1), "utf-8")).trim();
|
|
45
|
+
if (script?.startsWith("@"))
|
|
46
|
+
script = (await fs.readFile(script.slice(1), "utf-8")).trim();
|
|
47
|
+
const body = {};
|
|
48
|
+
if (topic)
|
|
49
|
+
body.topic = topic;
|
|
50
|
+
if (script)
|
|
51
|
+
body.script = script;
|
|
52
|
+
if (opts.duration !== undefined)
|
|
53
|
+
body.duration = opts.duration;
|
|
54
|
+
if (opts.pace)
|
|
55
|
+
body.pace = opts.pace;
|
|
56
|
+
if (opts.model)
|
|
57
|
+
body.model = opts.model;
|
|
58
|
+
const r = await post("/api/v1/content/scene-plan", body);
|
|
59
|
+
print({
|
|
60
|
+
mode: r.mode,
|
|
61
|
+
title: r.title,
|
|
62
|
+
n_scenes: r.scenes.length,
|
|
63
|
+
scenes: r.scenes,
|
|
109
64
|
});
|
|
110
|
-
print(r);
|
|
111
65
|
});
|
|
112
66
|
}
|