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
|
@@ -36,56 +36,74 @@ export function registerPipelines(program) {
|
|
|
36
36
|
const pl = program
|
|
37
37
|
.command("pipelines")
|
|
38
38
|
.alias("pipeline")
|
|
39
|
-
.description("End-to-end video pipelines (standard)")
|
|
39
|
+
.description("End-to-end video pipelines (standard, audio-first)")
|
|
40
40
|
.helpOption("-h, --help", "show help");
|
|
41
41
|
// ---------- standard ----------
|
|
42
42
|
commonOptions(pl
|
|
43
43
|
.command("standard")
|
|
44
|
-
.description("
|
|
44
|
+
.description("Audio-first pipeline: topic|script → master TTS → ASR → scene/subtitle layers → final MP4")
|
|
45
45
|
.helpOption("-h, --help", "show help")
|
|
46
|
-
.
|
|
47
|
-
.option("--
|
|
48
|
-
.option("--
|
|
49
|
-
.option("-
|
|
50
|
-
.option("--
|
|
51
|
-
.option("--frame-template <keyOrPath>", "preset key (e.g. 1080x1920/static_default.html) OR path to a local .html file", "1080x1920/static_default.html")
|
|
46
|
+
.option("-t, --topic <text>", "video topic (mode=generate). Use @file to read from disk.")
|
|
47
|
+
.option("--script <text>", "your own master script text (mode=fixed). Use @file to read from disk.")
|
|
48
|
+
.option("-d, --duration <sec>", "target video duration in seconds (generate mode; default 45)", (v) => parseInt(v, 10))
|
|
49
|
+
.option("-p, --pace <pace>", "visual rhythm hint: slow | normal | fast (default normal)")
|
|
50
|
+
.option("--frame-template <keyOrPath>", "preset key (e.g. 1080x1920/image_default.html) OR path to a local .html file")
|
|
52
51
|
.option("--frame-template-size <wxh>", "size for inline HTML when the file lacks <meta template:width|height>")
|
|
53
|
-
.option("--frame-template-type <type>", "inline type: image (default) | static | asset
|
|
54
|
-
.option("--image-model <id>", "RelayX image model (rx-image-z | rx-image-flux | rx-image-qwen)
|
|
55
|
-
.option("--prompt-prefix <text>", "style prefix prepended to image
|
|
56
|
-
.option("--
|
|
57
|
-
.option("--tts-speed <n>", "speech speed (0.5..2)", parseFloat
|
|
58
|
-
.option("--
|
|
59
|
-
.option("--
|
|
52
|
+
.option("--frame-template-type <type>", "inline type: image (default) | static | asset")
|
|
53
|
+
.option("--image-model <id>", "RelayX image model (rx-image-z | rx-image-flux | rx-image-qwen)")
|
|
54
|
+
.option("--prompt-prefix <text>", "style prefix prepended to every image prompt")
|
|
55
|
+
.option("--voice-id <id>", "RelayX TTS voice id (default 专业解说); see `rf tts voices`")
|
|
56
|
+
.option("--tts-speed <n>", "speech speed (0.5..2; default 1.0)", parseFloat)
|
|
57
|
+
.option("--video-fps <n>", "output video fps (default 30)", (v) => parseInt(v, 10))
|
|
58
|
+
.option("--subtitle-min-chars <N>", "subtitle line min chars (default 10)", (v) => parseInt(v, 10))
|
|
59
|
+
.option("--subtitle-hard-max <N>", "subtitle line absolute max chars (default 24)", (v) => parseInt(v, 10))
|
|
60
60
|
.addHelpText("after", [
|
|
61
61
|
"",
|
|
62
|
-
"
|
|
63
|
-
"
|
|
64
|
-
"
|
|
65
|
-
"
|
|
62
|
+
"Two content modes (exactly one required):",
|
|
63
|
+
" generate AI writes the script. --topic / -t <text> + optional --duration -d",
|
|
64
|
+
" fixed You supply the script. --script <text-or-@file>",
|
|
65
|
+
"",
|
|
66
|
+
"Pace (LLM visual rhythm hint): slow | normal | fast",
|
|
66
67
|
"",
|
|
67
|
-
"
|
|
68
|
-
"
|
|
69
|
-
"
|
|
68
|
+
"Examples:",
|
|
69
|
+
" rf pipelines standard -t 'why we explore space' -d 60 -o space.mp4",
|
|
70
|
+
" rf pipelines standard --script @script.txt -p slow -o out.mp4",
|
|
71
|
+
" rf pipelines standard -t '宠物' --frame-template ./my-brand.html -o final.mp4",
|
|
70
72
|
].join("\n"))).action(async (opts) => {
|
|
71
|
-
|
|
72
|
-
|
|
73
|
-
|
|
74
|
-
|
|
73
|
+
const hasTopic = typeof opts.topic === "string" && opts.topic.length > 0;
|
|
74
|
+
const hasScript = typeof opts.script === "string" && opts.script.length > 0;
|
|
75
|
+
if (!hasTopic && !hasScript) {
|
|
76
|
+
throw new Error("either --topic / -t or --script is required");
|
|
77
|
+
}
|
|
78
|
+
if (hasTopic && hasScript) {
|
|
79
|
+
throw new Error("--topic and --script are mutually exclusive");
|
|
80
|
+
}
|
|
81
|
+
if (opts.pace && !["slow", "normal", "fast"].includes(opts.pace)) {
|
|
82
|
+
throw new Error(`--pace must be one of slow|normal|fast (got: ${opts.pace})`);
|
|
83
|
+
}
|
|
84
|
+
let topic = opts.topic;
|
|
85
|
+
let script = opts.script;
|
|
86
|
+
if (topic?.startsWith("@"))
|
|
87
|
+
topic = await fs.readFile(topic.slice(1), "utf-8");
|
|
88
|
+
if (script?.startsWith("@"))
|
|
89
|
+
script = await fs.readFile(script.slice(1), "utf-8");
|
|
90
|
+
const tpl = opts.frameTemplate
|
|
91
|
+
? resolveTemplateArg(opts.frameTemplate, opts.frameTemplateSize)
|
|
92
|
+
: {};
|
|
75
93
|
await submitAndMaybeWait("/api/v1/pipelines/standard", {
|
|
76
|
-
|
|
77
|
-
|
|
78
|
-
|
|
79
|
-
|
|
80
|
-
split_mode: opts.splitMode,
|
|
94
|
+
topic,
|
|
95
|
+
script,
|
|
96
|
+
duration: opts.duration,
|
|
97
|
+
pace: opts.pace,
|
|
81
98
|
...tpl,
|
|
82
99
|
frame_template_type: opts.frameTemplateType,
|
|
83
100
|
image_model: opts.imageModel,
|
|
84
101
|
prompt_prefix: opts.promptPrefix,
|
|
85
|
-
|
|
102
|
+
voice_id: opts.voiceId,
|
|
86
103
|
tts_speed: opts.ttsSpeed,
|
|
87
|
-
|
|
88
|
-
|
|
104
|
+
video_fps: opts.videoFps,
|
|
105
|
+
subtitle_min_chars: opts.subtitleMinChars,
|
|
106
|
+
subtitle_hard_max: opts.subtitleHardMax,
|
|
89
107
|
}, { wait: opts.wait, output: opts.output, pollMs: opts.pollMs, timeoutMs: opts.timeoutMs });
|
|
90
108
|
});
|
|
91
109
|
}
|
|
@@ -0,0 +1,40 @@
|
|
|
1
|
+
import fs from "node:fs/promises";
|
|
2
|
+
import { post } from "../client.js";
|
|
3
|
+
import { print } from "../utils/output.js";
|
|
4
|
+
export function registerSubtitles(program) {
|
|
5
|
+
const sub = program
|
|
6
|
+
.command("subtitles")
|
|
7
|
+
.alias("subtitle")
|
|
8
|
+
.description("Subtitle atomics — deterministic line splitter (no LLM, no billing)")
|
|
9
|
+
.helpOption("-h, --help", "show help");
|
|
10
|
+
sub
|
|
11
|
+
.command("split")
|
|
12
|
+
.description("Split a chunk of text into subtitle-sized lines using tiered punctuation priority")
|
|
13
|
+
.helpOption("-h, --help", "show help")
|
|
14
|
+
.requiredOption("-t, --text <text>", "text to split. Use @file to read from disk.")
|
|
15
|
+
.option("--min <N>", "minimum line length in chars (default 10)", (v) => parseInt(v, 10))
|
|
16
|
+
.option("--hard-max <N>", "absolute maximum line length in chars (default 24)", (v) => parseInt(v, 10))
|
|
17
|
+
.addHelpText("after", [
|
|
18
|
+
"",
|
|
19
|
+
"Rule:",
|
|
20
|
+
" Within [min, hard-max], pick the highest-tier punctuation; same tier → latest position.",
|
|
21
|
+
" Tier 1 (。!?) > Tier 2 (;:) > Tier 3 (,、)",
|
|
22
|
+
" No punctuation in window → force-cut at hard-max.",
|
|
23
|
+
"",
|
|
24
|
+
"Examples:",
|
|
25
|
+
" rf subtitles split -t '雨水缓缓滑落在玻璃窗上,像是无声的泪珠。'",
|
|
26
|
+
" rf subtitles split -t @./narration.txt --min 8 --hard-max 20",
|
|
27
|
+
].join("\n"))
|
|
28
|
+
.action(async (opts) => {
|
|
29
|
+
let text = opts.text;
|
|
30
|
+
if (text.startsWith("@"))
|
|
31
|
+
text = (await fs.readFile(text.slice(1), "utf-8")).trim();
|
|
32
|
+
const body = { text };
|
|
33
|
+
if (opts.min !== undefined)
|
|
34
|
+
body.min_chars = opts.min;
|
|
35
|
+
if (opts.hardMax !== undefined)
|
|
36
|
+
body.hard_max = opts.hardMax;
|
|
37
|
+
const r = await post("/api/v1/subtitles/split", body);
|
|
38
|
+
print({ count: r.count, lines: r.lines });
|
|
39
|
+
});
|
|
40
|
+
}
|
package/dist/index.js
CHANGED
|
@@ -19,6 +19,8 @@ import { registerModels } from "./commands/models.js";
|
|
|
19
19
|
import { registerTts } from "./commands/tts.js";
|
|
20
20
|
import { registerImages } from "./commands/images.js";
|
|
21
21
|
import { registerContent } from "./commands/content.js";
|
|
22
|
+
import { registerAudio } from "./commands/audio.js";
|
|
23
|
+
import { registerSubtitles } from "./commands/subtitles.js";
|
|
22
24
|
import { registerTemplates } from "./commands/templates.js";
|
|
23
25
|
import { registerFrames } from "./commands/frames.js";
|
|
24
26
|
import { registerCompositions } from "./commands/compositions.js";
|
|
@@ -70,7 +72,7 @@ program.addHelpText("afterAll", [
|
|
|
70
72
|
" rf llm chat --prompt 'explain antifragile in 3 sentences'",
|
|
71
73
|
" rf tts edge --text 'hello world' --voice en-US-AriaNeural -o out.mp3",
|
|
72
74
|
" rf images generate --prompt 'a cat' --model rx-image-flux -o cat.png",
|
|
73
|
-
" rf pipelines standard
|
|
75
|
+
" rf pipelines standard -t 'why we explore space' -d 60",
|
|
74
76
|
" rf tasks list --status running",
|
|
75
77
|
" rf config get",
|
|
76
78
|
].join("\n"));
|
|
@@ -81,6 +83,8 @@ registerModels(program);
|
|
|
81
83
|
registerTts(program);
|
|
82
84
|
registerImages(program);
|
|
83
85
|
registerContent(program);
|
|
86
|
+
registerAudio(program);
|
|
87
|
+
registerSubtitles(program);
|
|
84
88
|
registerTemplates(program);
|
|
85
89
|
registerFrames(program);
|
|
86
90
|
registerCompositions(program);
|
package/package.json
CHANGED
|
@@ -1,51 +1,51 @@
|
|
|
1
|
-
{
|
|
2
|
-
"name": "reelforge",
|
|
3
|
-
"version": "0.
|
|
4
|
-
"description": "CLI for ReelForge Studio — AI video engine. Installs as both `reelforge` and the short alias `rf`. Every REST API exposed as a command, with --help on every level.",
|
|
5
|
-
"license": "Apache-2.0",
|
|
6
|
-
"type": "module",
|
|
7
|
-
"bin": {
|
|
8
|
-
"reelforge": "./bin/reelforge.js",
|
|
9
|
-
"rf": "./bin/reelforge.js"
|
|
10
|
-
},
|
|
11
|
-
"files": [
|
|
12
|
-
"bin",
|
|
13
|
-
"dist",
|
|
14
|
-
"README.md"
|
|
15
|
-
],
|
|
16
|
-
"engines": {
|
|
17
|
-
"node": ">=18.17"
|
|
18
|
-
},
|
|
19
|
-
"scripts": {
|
|
20
|
-
"build": "tsc -p tsconfig.json",
|
|
21
|
-
"dev": "tsc -p tsconfig.json --watch",
|
|
22
|
-
"typecheck": "tsc -p tsconfig.json --noEmit",
|
|
23
|
-
"clean": "rimraf dist",
|
|
24
|
-
"prepublishOnly": "npm run clean && npm run build"
|
|
25
|
-
},
|
|
26
|
-
"dependencies": {
|
|
27
|
-
"commander": "^12.1.0",
|
|
28
|
-
"kleur": "^4.1.5"
|
|
29
|
-
},
|
|
30
|
-
"devDependencies": {
|
|
31
|
-
"@types/node": "^20.14.0",
|
|
32
|
-
"rimraf": "^6.0.1",
|
|
33
|
-
"typescript": "^5.5.0"
|
|
34
|
-
},
|
|
35
|
-
"keywords": [
|
|
36
|
-
"reelforge",
|
|
37
|
-
"ai-video",
|
|
38
|
-
"relayx",
|
|
39
|
-
"tts",
|
|
40
|
-
"edge-tts",
|
|
41
|
-
"ffmpeg",
|
|
42
|
-
"playwright",
|
|
43
|
-
"cli"
|
|
44
|
-
],
|
|
45
|
-
"repository": {
|
|
46
|
-
"type": "git",
|
|
47
|
-
"url": "https://github.com/puke3615/ReelForge.git",
|
|
48
|
-
"directory": "cli"
|
|
49
|
-
},
|
|
50
|
-
"homepage": "https://github.com/puke3615/ReelForge"
|
|
51
|
-
}
|
|
1
|
+
{
|
|
2
|
+
"name": "reelforge",
|
|
3
|
+
"version": "0.7.0",
|
|
4
|
+
"description": "CLI for ReelForge Studio — AI video engine. Installs as both `reelforge` and the short alias `rf`. Every REST API exposed as a command, with --help on every level.",
|
|
5
|
+
"license": "Apache-2.0",
|
|
6
|
+
"type": "module",
|
|
7
|
+
"bin": {
|
|
8
|
+
"reelforge": "./bin/reelforge.js",
|
|
9
|
+
"rf": "./bin/reelforge.js"
|
|
10
|
+
},
|
|
11
|
+
"files": [
|
|
12
|
+
"bin",
|
|
13
|
+
"dist",
|
|
14
|
+
"README.md"
|
|
15
|
+
],
|
|
16
|
+
"engines": {
|
|
17
|
+
"node": ">=18.17"
|
|
18
|
+
},
|
|
19
|
+
"scripts": {
|
|
20
|
+
"build": "tsc -p tsconfig.json",
|
|
21
|
+
"dev": "tsc -p tsconfig.json --watch",
|
|
22
|
+
"typecheck": "tsc -p tsconfig.json --noEmit",
|
|
23
|
+
"clean": "rimraf dist",
|
|
24
|
+
"prepublishOnly": "npm run clean && npm run build"
|
|
25
|
+
},
|
|
26
|
+
"dependencies": {
|
|
27
|
+
"commander": "^12.1.0",
|
|
28
|
+
"kleur": "^4.1.5"
|
|
29
|
+
},
|
|
30
|
+
"devDependencies": {
|
|
31
|
+
"@types/node": "^20.14.0",
|
|
32
|
+
"rimraf": "^6.0.1",
|
|
33
|
+
"typescript": "^5.5.0"
|
|
34
|
+
},
|
|
35
|
+
"keywords": [
|
|
36
|
+
"reelforge",
|
|
37
|
+
"ai-video",
|
|
38
|
+
"relayx",
|
|
39
|
+
"tts",
|
|
40
|
+
"edge-tts",
|
|
41
|
+
"ffmpeg",
|
|
42
|
+
"playwright",
|
|
43
|
+
"cli"
|
|
44
|
+
],
|
|
45
|
+
"repository": {
|
|
46
|
+
"type": "git",
|
|
47
|
+
"url": "https://github.com/puke3615/ReelForge.git",
|
|
48
|
+
"directory": "cli"
|
|
49
|
+
},
|
|
50
|
+
"homepage": "https://github.com/puke3615/ReelForge"
|
|
51
|
+
}
|