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.
@@ -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("Topic / script → narrationframes → final MP4")
44
+ .description("Audio-first pipeline: topic|script → master TTS ASRscene/subtitle layers → final MP4")
45
45
  .helpOption("-h, --help", "show help")
46
- .requiredOption("-t, --text <text>", "topic OR fixed script (use @file)")
47
- .option("--mode <mode>", "generate | fixed", "generate")
48
- .option("--title <text>", "explicit video title (skip LLM title gen)")
49
- .option("-n, --n-scenes <n>", "number of scenes (mode=generate)", parseInt, 5)
50
- .option("--split-mode <mode>", "paragraph | line | sentence (mode=fixed)", "paragraph")
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. Or set <meta name=\"template:type\"> in the HTML.")
54
- .option("--image-model <id>", "RelayX image model (rx-image-z | rx-image-flux | rx-image-qwen) — only when template requires AI images")
55
- .option("--prompt-prefix <text>", "style prefix prepended to image prompts")
56
- .option("--tts-voice <id>", "Edge TTS voice", "zh-CN-YunjianNeural")
57
- .option("--tts-speed <n>", "speech speed (0.5..2)", parseFloat, 1.2)
58
- .option("--bgm <path>", "BGM file path")
59
- .option("--bgm-volume <n>", "BGM volume", parseFloat, 0.2)
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
- "Examples:",
63
- " reelforge pipelines standard -t 'why we explore space' -n 5 -o space.mp4",
64
- " reelforge pipelines standard -t @script.txt --mode fixed --split-mode paragraph --title 'My Show' -o out.mp4",
65
- " reelforge pipelines standard -t '宠物' --frame-template 1080x1920/image_default.html --image-model rx-image-flux --prompt-prefix 'cinematic'",
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
- " Custom HTML template (sent inline; no upload needed):",
68
- " reelforge pipelines standard -t '宠物' --frame-template ./my-brand.html -o final.mp4",
69
- " (declare size via <meta name=\"template:width|height\"> or pass --frame-template-size 1080x1920)",
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
- let text = opts.text;
72
- if (text.startsWith("@"))
73
- text = await fs.readFile(text.slice(1), "utf-8");
74
- const tpl = resolveTemplateArg(opts.frameTemplate, opts.frameTemplateSize);
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
- text,
77
- mode: opts.mode,
78
- title: opts.title,
79
- n_scenes: opts.nScenes,
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
- tts_voice: opts.ttsVoice,
102
+ voice_id: opts.voiceId,
86
103
  tts_speed: opts.ttsSpeed,
87
- bgm_path: opts.bgm,
88
- bgm_volume: opts.bgmVolume,
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 --text 'why we explore space' --tts-voice zh-CN-YunjianNeural",
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.5.5",
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
+ }