reelforge 0.4.1 → 0.5.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 +10 -14
- package/dist/commands/config.js +5 -5
- package/dist/commands/create.js +16 -51
- package/dist/commands/images.js +8 -28
- package/dist/commands/llm.js +3 -3
- package/dist/commands/pipelines.js +3 -3
- package/dist/commands/tts.js +14 -15
- package/dist/index.js +1 -5
- package/package.json +2 -3
- package/dist/commands/videos.js +0 -52
- package/dist/commands/workflows.js +0 -29
package/README.md
CHANGED
|
@@ -44,7 +44,7 @@ That's the whole story — no server to run.
|
|
|
44
44
|
|
|
45
45
|
### Self-hosting
|
|
46
46
|
|
|
47
|
-
If you want to run your own ReelForge Studio (own
|
|
47
|
+
If you want to run your own ReelForge Studio (own RelayX key, your own pricing) clone the upstream repo, `pnpm dev`, then point the CLI at it:
|
|
48
48
|
|
|
49
49
|
```bash
|
|
50
50
|
rf --server http://localhost:8501 health
|
|
@@ -72,15 +72,12 @@ Run `rf <command> --help` for full details on any of these.
|
|
|
72
72
|
|
|
73
73
|
| command | what it does |
|
|
74
74
|
|---|---|
|
|
75
|
-
| `llm chat -p <text>` | Send one prompt to the configured LLM |
|
|
76
|
-
| `llm presets` | List built-in
|
|
77
|
-
| `tts edge -t <text> -o out.mp3` | Local Edge TTS synthesis |
|
|
78
|
-
| `tts
|
|
75
|
+
| `llm chat -p <text>` | Send one prompt to the configured LLM (RelayX gateway by default) |
|
|
76
|
+
| `llm presets` | List built-in RelayX model presets |
|
|
77
|
+
| `tts edge -t <text> -o out.mp3` | Local Edge TTS synthesis (free) |
|
|
78
|
+
| `tts relayx -t <text> -o out.mp3` | RelayX TTS (vox/index-tts-2, 149 built-in voices) |
|
|
79
79
|
| `tts voices [--locale zh]` | List supported Edge TTS voices |
|
|
80
|
-
| `images generate -p <prompt> -
|
|
81
|
-
| `images analyze -i <image>` | Reverse-describe an image |
|
|
82
|
-
| `videos generate -p <prompt> -w <workflow>` | Atomic video generation |
|
|
83
|
-
| `videos analyze -i <video>` | Reverse-describe a video |
|
|
80
|
+
| `images generate -p <prompt> -m rx-image-flux` | Image generation via RelayX (rx-image-z / rx-image-flux / rx-image-qwen) |
|
|
84
81
|
|
|
85
82
|
### Content generation
|
|
86
83
|
|
|
@@ -116,7 +113,6 @@ All `pipelines *` commands submit an **async task** and (by default) poll until
|
|
|
116
113
|
|
|
117
114
|
| command | what it does |
|
|
118
115
|
|---|---|
|
|
119
|
-
| `workflows list [--source runninghub] [--kind image]` | Browse ComfyUI workflows |
|
|
120
116
|
| `bgm list / upload <file> / delete <name>` | Manage background music |
|
|
121
117
|
| `files list / upload <file> / download <path> / delete <path>` | Manage user assets |
|
|
122
118
|
|
|
@@ -150,12 +146,12 @@ rf tasks list --limit 5
|
|
|
150
146
|
rf history get <task-id> --download recovered.mp4
|
|
151
147
|
|
|
152
148
|
# 4. JSON pipe for automation
|
|
153
|
-
rf
|
|
149
|
+
rf llm presets --json | jq '.[].defaultModel'
|
|
154
150
|
|
|
155
151
|
# 5. Configure & test LLM (self-hosted)
|
|
156
|
-
rf config set llm.api_key
|
|
157
|
-
rf config set llm.base_url https://
|
|
158
|
-
rf config set llm.model
|
|
152
|
+
rf config set llm.api_key rx-xxxxx # RelayX key (or your own provider key)
|
|
153
|
+
rf config set llm.base_url https://relayx.timor419.com/v1
|
|
154
|
+
rf config set llm.model anthropic/claude-4-7-sonnet
|
|
159
155
|
rf llm chat -p 'one-sentence summary of antifragile'
|
|
160
156
|
```
|
|
161
157
|
|
package/dist/commands/config.js
CHANGED
|
@@ -4,7 +4,7 @@ import { print } from "../utils/output.js";
|
|
|
4
4
|
export function registerConfig(program) {
|
|
5
5
|
const cfg = program
|
|
6
6
|
.command("config")
|
|
7
|
-
.description("Read or update the server config.yaml (LLM /
|
|
7
|
+
.description("Read or update the server config.yaml (LLM / RelayX keys)")
|
|
8
8
|
.helpOption("-h, --help", "show help");
|
|
9
9
|
cfg
|
|
10
10
|
.command("get")
|
|
@@ -22,10 +22,10 @@ export function registerConfig(program) {
|
|
|
22
22
|
"",
|
|
23
23
|
"Examples:",
|
|
24
24
|
" reelforge config set llm.api_key sk-xxxxxx",
|
|
25
|
-
" reelforge config set llm.base_url https://
|
|
26
|
-
" reelforge config set llm.model
|
|
27
|
-
" reelforge config set
|
|
28
|
-
" reelforge config set
|
|
25
|
+
" reelforge config set llm.base_url https://relayx.timor419.com/v1",
|
|
26
|
+
" reelforge config set llm.model openai/gpt-5-mini",
|
|
27
|
+
" reelforge config set relayx.api_key rx-xxxxxx",
|
|
28
|
+
" reelforge config set relayx.default_image_model rx-image-flux",
|
|
29
29
|
].join("\n"))
|
|
30
30
|
.action(async (key, value) => {
|
|
31
31
|
const parts = key.split(".");
|
package/dist/commands/create.js
CHANGED
|
@@ -7,20 +7,8 @@ import { downloadTo } from "../utils/download.js";
|
|
|
7
7
|
import { info, print, success, warn } from "../utils/output.js";
|
|
8
8
|
const LAST_CREATE_PATH = path.join(os.homedir(), ".reelforge", "last-create.json");
|
|
9
9
|
// ── Cost estimation (mirrors server src/lib/billing.ts) ──────────
|
|
10
|
-
|
|
11
|
-
|
|
12
|
-
return 3;
|
|
13
|
-
const base = (wfKey.split("/").pop() || wfKey).toLowerCase();
|
|
14
|
-
if (base.startsWith("tts_"))
|
|
15
|
-
return 1;
|
|
16
|
-
if (base.startsWith("image_"))
|
|
17
|
-
return 3;
|
|
18
|
-
if (base.startsWith("video_"))
|
|
19
|
-
return 15;
|
|
20
|
-
if (base.startsWith("analyse_") || base.startsWith("analyze_"))
|
|
21
|
-
return 2;
|
|
22
|
-
return 3;
|
|
23
|
-
}
|
|
10
|
+
const IMAGE_UNITS = 3; // matches ATOMIC_UNITS["images.generate"] in src/lib/billing.ts
|
|
11
|
+
const TTS_RELAYX_UNITS = 1; // matches ATOMIC_UNITS["tts.relayx"]
|
|
24
12
|
function estimateUnits(body) {
|
|
25
13
|
const mode = body.mode || "generate";
|
|
26
14
|
const titleExplicit = !!body.title;
|
|
@@ -30,22 +18,12 @@ function estimateUnits(body) {
|
|
|
30
18
|
const tplBase = (tplKey.split("/").pop() || "").toLowerCase();
|
|
31
19
|
const tplType = tplBase.startsWith("static_")
|
|
32
20
|
? "static"
|
|
33
|
-
: tplBase.startsWith("
|
|
34
|
-
? "
|
|
35
|
-
:
|
|
36
|
-
|
|
37
|
-
|
|
38
|
-
|
|
39
|
-
: "image";
|
|
40
|
-
let mediaPerFrame = 0;
|
|
41
|
-
if (tplType === "image") {
|
|
42
|
-
mediaPerFrame = body.media_workflow ? calcWorkflowUnits(body.media_workflow) : 3;
|
|
43
|
-
}
|
|
44
|
-
else if (tplType === "video") {
|
|
45
|
-
mediaPerFrame = body.media_workflow ? calcWorkflowUnits(body.media_workflow) : 15;
|
|
46
|
-
}
|
|
47
|
-
const ttsMode = body.tts_inference_mode || (body.tts_workflow ? "comfyui" : "local");
|
|
48
|
-
const ttsPerFrame = ttsMode === "comfyui" ? 1 : 0;
|
|
21
|
+
: tplBase.startsWith("asset_")
|
|
22
|
+
? "asset"
|
|
23
|
+
: "image";
|
|
24
|
+
const mediaPerFrame = tplType === "image" ? IMAGE_UNITS : 0;
|
|
25
|
+
const ttsMode = body.tts_inference_mode || "edge";
|
|
26
|
+
const ttsPerFrame = ttsMode === "relayx" ? TTS_RELAYX_UNITS : 0;
|
|
49
27
|
const narrations = mode === "generate" ? 1 : 0;
|
|
50
28
|
const title = titleExplicit ? 0 : 1;
|
|
51
29
|
const imagePrompts = tplType === "static" ? 0 : 1;
|
|
@@ -156,14 +134,10 @@ function optsToBody(opts) {
|
|
|
156
134
|
out.tts_voice = opts.ttsVoice;
|
|
157
135
|
if (opts.voiceId !== undefined)
|
|
158
136
|
out.voice_id = opts.voiceId;
|
|
159
|
-
if (opts.ttsWorkflow !== undefined)
|
|
160
|
-
out.tts_workflow = opts.ttsWorkflow;
|
|
161
137
|
if (opts.ttsSpeed !== undefined)
|
|
162
138
|
out.tts_speed = opts.ttsSpeed;
|
|
163
|
-
if (opts.
|
|
164
|
-
out.
|
|
165
|
-
if (opts.mediaWorkflow !== undefined)
|
|
166
|
-
out.media_workflow = opts.mediaWorkflow;
|
|
139
|
+
if (opts.imageModel !== undefined)
|
|
140
|
+
out.image_model = opts.imageModel;
|
|
167
141
|
if (opts.frameTemplate !== undefined)
|
|
168
142
|
out.frame_template = opts.frameTemplate;
|
|
169
143
|
if (opts.promptPrefix !== undefined)
|
|
@@ -322,16 +296,14 @@ export function registerCreate(program) {
|
|
|
322
296
|
.option("--max-image-prompt-words <N>", "image prompt max words", (v) => parseInt(v, 10))
|
|
323
297
|
// --- Visual ---
|
|
324
298
|
.option("--frame-template <key>", "HTML frame template, e.g. 1080x1920/image_default.html")
|
|
325
|
-
.option("--
|
|
299
|
+
.option("--image-model <id>", "RelayX image model (rx-image-z | rx-image-flux | rx-image-qwen)")
|
|
326
300
|
.option("--prompt-prefix <text>", "raw style prefix prepended to every image prompt (overrides --style)")
|
|
327
301
|
.option("--style <preset>", "image style preset — shortcut for --prompt-prefix; see 'Style presets' below for the full list")
|
|
328
302
|
// --- Audio (TTS) ---
|
|
329
|
-
.option("--tts-voice <id>", "
|
|
303
|
+
.option("--tts-voice <id>", "TTS voice id; for edge use e.g. zh-CN-YunjianNeural / en-US-AriaNeural; for relayx use vox voice ids (default: 专业解说)")
|
|
330
304
|
.option("--tts-speed <n>", "speech speed 0.5..2", parseFloat)
|
|
331
|
-
.option("--tts-inference-mode <mode>", "local |
|
|
332
|
-
.option("--tts-workflow <key>", "ComfyUI TTS workflow (forces inference-mode=comfyui)")
|
|
305
|
+
.option("--tts-inference-mode <mode>", "edge (default, local Microsoft Edge TTS) | relayx (vox/index-tts-2 via RelayX)")
|
|
333
306
|
.option("--voice-id <id>", "alias of --tts-voice (legacy compat)")
|
|
334
|
-
.option("--ref-audio <path>", "reference audio for voice-cloning TTS workflows")
|
|
335
307
|
// --- Audio (BGM) ---
|
|
336
308
|
.option("--bgm <path>", "background music file path (server-side relative to bgm/)")
|
|
337
309
|
.option("--bgm-volume <n>", "BGM volume 0..1", parseFloat)
|
|
@@ -363,8 +335,8 @@ export function registerCreate(program) {
|
|
|
363
335
|
"",
|
|
364
336
|
"Param groups:",
|
|
365
337
|
" Content : --mode --title -n --split-mode --min/max-narration-words --min/max-image-prompt-words",
|
|
366
|
-
" Visual : --frame-template --
|
|
367
|
-
" TTS : --tts-voice --tts-speed --tts-inference-mode --
|
|
338
|
+
" Visual : --frame-template --image-model --style --prompt-prefix",
|
|
339
|
+
" TTS : --tts-voice --tts-speed --tts-inference-mode --voice-id",
|
|
368
340
|
" BGM : --bgm --bgm-volume --bgm-mode",
|
|
369
341
|
" Output : --video-fps --template-params -o --no-download --no-wait --poll-ms --timeout-ms",
|
|
370
342
|
" Workflow: --recipe --redo --dry-run",
|
|
@@ -382,8 +354,6 @@ export function registerCreate(program) {
|
|
|
382
354
|
"",
|
|
383
355
|
"Explore available resources (separate commands):",
|
|
384
356
|
" reelforge templates list # all HTML templates",
|
|
385
|
-
" reelforge workflows list --kind image # all AI image workflows",
|
|
386
|
-
" reelforge workflows list --kind video # all AI video workflows",
|
|
387
357
|
" reelforge tts voices --locale zh # Edge TTS voice ids",
|
|
388
358
|
" reelforge bgm list # built-in BGM files",
|
|
389
359
|
"",
|
|
@@ -400,11 +370,6 @@ export function registerCreate(program) {
|
|
|
400
370
|
" # Landscape (1920x1080)",
|
|
401
371
|
' rf create "..." --frame-template 1920x1080/image_default.html',
|
|
402
372
|
"",
|
|
403
|
-
" # AI-generated video background instead of still image",
|
|
404
|
-
' rf create "..." \\',
|
|
405
|
-
" --frame-template 1080x1920/video_default.html \\",
|
|
406
|
-
" --media-workflow runninghub/video_wan2.2.json",
|
|
407
|
-
"",
|
|
408
373
|
" # Add BGM",
|
|
409
374
|
' rf create "..." --bgm bgm/Echoes.mp3 --bgm-volume 0.3 --bgm-mode loop',
|
|
410
375
|
"",
|
|
@@ -441,7 +406,7 @@ export function registerCreate(program) {
|
|
|
441
406
|
' "text": "为什么我们还没找到外星文明?",',
|
|
442
407
|
' "n_scenes": 7,',
|
|
443
408
|
' "frame_template": "1080x1920/image_default.html",',
|
|
444
|
-
' "
|
|
409
|
+
' "image_model": "rx-image-flux",',
|
|
445
410
|
' "prompt_prefix": "Minimalist matchstick figure style",',
|
|
446
411
|
' "tts_voice": "zh-CN-YunjianNeural",',
|
|
447
412
|
' "tts_speed": 1.2,',
|
package/dist/commands/images.js
CHANGED
|
@@ -4,42 +4,32 @@ import { print, success } from "../utils/output.js";
|
|
|
4
4
|
export function registerImages(program) {
|
|
5
5
|
const images = program
|
|
6
6
|
.command("images")
|
|
7
|
-
.description("Image generation
|
|
7
|
+
.description("Image generation via RelayX (rx-image-z / rx-image-flux / rx-image-qwen)")
|
|
8
8
|
.helpOption("-h, --help", "show help");
|
|
9
9
|
images
|
|
10
10
|
.command("generate")
|
|
11
|
-
.description("Generate an image via
|
|
11
|
+
.description("Generate an image via RelayX")
|
|
12
12
|
.helpOption("-h, --help", "show help")
|
|
13
13
|
.requiredOption("-p, --prompt <text>", "text prompt")
|
|
14
|
-
.
|
|
14
|
+
.option("-m, --model <id>", "RelayX image model id (rx-image-z | rx-image-flux | rx-image-qwen)")
|
|
15
15
|
.option("--width <n>", "image width", parseInt)
|
|
16
16
|
.option("--height <n>", "image height", parseInt)
|
|
17
|
-
.option("--steps <n>", "sampling steps", parseInt)
|
|
18
|
-
.option("--seed <n>", "random seed", parseInt)
|
|
19
|
-
.option("--cfg <n>", "CFG scale", parseFloat)
|
|
20
|
-
.option("--negative <text>", "negative prompt")
|
|
21
17
|
.option("-o, --output <file>", "download first image to this local path")
|
|
22
18
|
.option("--all-output <dir>", "download ALL generated images into this directory")
|
|
23
19
|
.addHelpText("after", [
|
|
24
20
|
"",
|
|
25
21
|
"Examples:",
|
|
26
|
-
" reelforge images generate -p 'a cat' -
|
|
27
|
-
" reelforge images generate -p 'cyberpunk city'
|
|
22
|
+
" reelforge images generate -p 'a cat' -m rx-image-flux --width 1024 --height 1024 -o cat.png",
|
|
23
|
+
" reelforge images generate -p 'cyberpunk city, neon, 9:16' --width 1080 --height 1920 -o city.png",
|
|
28
24
|
].join("\n"))
|
|
29
25
|
.action(async (opts) => {
|
|
30
|
-
const body = { prompt: opts.prompt
|
|
26
|
+
const body = { prompt: opts.prompt };
|
|
27
|
+
if (opts.model)
|
|
28
|
+
body.model = opts.model;
|
|
31
29
|
if (opts.width !== undefined)
|
|
32
30
|
body.width = opts.width;
|
|
33
31
|
if (opts.height !== undefined)
|
|
34
32
|
body.height = opts.height;
|
|
35
|
-
if (opts.steps !== undefined)
|
|
36
|
-
body.steps = opts.steps;
|
|
37
|
-
if (opts.seed !== undefined)
|
|
38
|
-
body.seed = opts.seed;
|
|
39
|
-
if (opts.cfg !== undefined)
|
|
40
|
-
body.cfg = opts.cfg;
|
|
41
|
-
if (opts.negative)
|
|
42
|
-
body.negative_prompt = opts.negative;
|
|
43
33
|
const r = await post("/api/v1/images/generate", body);
|
|
44
34
|
if (opts.output && r.images?.[0]) {
|
|
45
35
|
await downloadTo(r.images[0], opts.output);
|
|
@@ -54,14 +44,4 @@ export function registerImages(program) {
|
|
|
54
44
|
}
|
|
55
45
|
print(r);
|
|
56
46
|
});
|
|
57
|
-
images
|
|
58
|
-
.command("analyze")
|
|
59
|
-
.description("Reverse-describe an image using an analyse_image workflow")
|
|
60
|
-
.helpOption("-h, --help", "show help")
|
|
61
|
-
.requiredOption("-i, --image <pathOrUrl>", "image to analyze (local path or URL)")
|
|
62
|
-
.option("-w, --workflow <key>", "workflow key", "selfhost/analyse_image.json")
|
|
63
|
-
.action(async (opts) => {
|
|
64
|
-
const r = await post("/api/v1/images/analyze", { image: opts.image, workflow: opts.workflow });
|
|
65
|
-
print(r);
|
|
66
|
-
});
|
|
67
47
|
}
|
package/dist/commands/llm.js
CHANGED
|
@@ -4,7 +4,7 @@ import { print, table } from "../utils/output.js";
|
|
|
4
4
|
export function registerLlm(program) {
|
|
5
5
|
const llm = program
|
|
6
6
|
.command("llm")
|
|
7
|
-
.description("Large-language-model utilities (chat, list
|
|
7
|
+
.description("Large-language-model utilities (chat, list RelayX model presets)")
|
|
8
8
|
.helpOption("-h, --help", "show help");
|
|
9
9
|
llm
|
|
10
10
|
.command("chat")
|
|
@@ -21,7 +21,7 @@ export function registerLlm(program) {
|
|
|
21
21
|
"",
|
|
22
22
|
"Examples:",
|
|
23
23
|
" reelforge llm chat -p 'Hello'",
|
|
24
|
-
" reelforge llm chat -p @prompt.txt -m
|
|
24
|
+
" reelforge llm chat -p @prompt.txt -m anthropic/claude-4-7-sonnet -t 0.4",
|
|
25
25
|
" reelforge llm chat -p 'movie review of Inception' --schema review.json --json",
|
|
26
26
|
].join("\n"))
|
|
27
27
|
.action(async (opts) => {
|
|
@@ -47,7 +47,7 @@ export function registerLlm(program) {
|
|
|
47
47
|
});
|
|
48
48
|
llm
|
|
49
49
|
.command("presets")
|
|
50
|
-
.description("List built-in
|
|
50
|
+
.description("List built-in RelayX model presets (GPT-5 / Claude 4.7 / Gemini 3 / DeepSeek / Qwen / Kimi)")
|
|
51
51
|
.helpOption("-h, --help", "show help")
|
|
52
52
|
.action(async () => {
|
|
53
53
|
const r = await get("/api/v1/llm/presets");
|
|
@@ -47,7 +47,7 @@ export function registerPipelines(program) {
|
|
|
47
47
|
.option("-n, --n-scenes <n>", "number of scenes (mode=generate)", parseInt, 5)
|
|
48
48
|
.option("--split-mode <mode>", "paragraph | line | sentence (mode=fixed)", "paragraph")
|
|
49
49
|
.option("--frame-template <key>", "template, e.g. 1080x1920/static_default.html", "1080x1920/static_default.html")
|
|
50
|
-
.option("--
|
|
50
|
+
.option("--image-model <id>", "RelayX image model (rx-image-z | rx-image-flux | rx-image-qwen) — only when template requires AI images")
|
|
51
51
|
.option("--prompt-prefix <text>", "style prefix prepended to image prompts")
|
|
52
52
|
.option("--tts-voice <id>", "Edge TTS voice", "zh-CN-YunjianNeural")
|
|
53
53
|
.option("--tts-speed <n>", "speech speed (0.5..2)", parseFloat, 1.2)
|
|
@@ -58,7 +58,7 @@ export function registerPipelines(program) {
|
|
|
58
58
|
"Examples:",
|
|
59
59
|
" reelforge pipelines standard -t 'why we explore space' -n 5 -o space.mp4",
|
|
60
60
|
" reelforge pipelines standard -t @script.txt --mode fixed --split-mode paragraph --title 'My Show' -o out.mp4",
|
|
61
|
-
" reelforge pipelines standard -t '宠物' --frame-template 1080x1920/image_default.html --
|
|
61
|
+
" reelforge pipelines standard -t '宠物' --frame-template 1080x1920/image_default.html --image-model rx-image-flux --prompt-prefix 'cinematic'",
|
|
62
62
|
].join("\n"))).action(async (opts) => {
|
|
63
63
|
let text = opts.text;
|
|
64
64
|
if (text.startsWith("@"))
|
|
@@ -70,7 +70,7 @@ export function registerPipelines(program) {
|
|
|
70
70
|
n_scenes: opts.nScenes,
|
|
71
71
|
split_mode: opts.splitMode,
|
|
72
72
|
frame_template: opts.frameTemplate,
|
|
73
|
-
|
|
73
|
+
image_model: opts.imageModel,
|
|
74
74
|
prompt_prefix: opts.promptPrefix,
|
|
75
75
|
tts_voice: opts.ttsVoice,
|
|
76
76
|
tts_speed: opts.ttsSpeed,
|
package/dist/commands/tts.js
CHANGED
|
@@ -4,7 +4,7 @@ import { print, table, success } from "../utils/output.js";
|
|
|
4
4
|
export function registerTts(program) {
|
|
5
5
|
const tts = program
|
|
6
6
|
.command("tts")
|
|
7
|
-
.description("Text-to-speech: local Edge TTS or
|
|
7
|
+
.description("Text-to-speech: local Edge TTS (free) or RelayX vox/index-tts-2")
|
|
8
8
|
.helpOption("-h, --help", "show help");
|
|
9
9
|
tts
|
|
10
10
|
.command("edge")
|
|
@@ -37,32 +37,31 @@ export function registerTts(program) {
|
|
|
37
37
|
print({ ok: r.ok, voice: r.voice, rate: r.rate, size_bytes: r.size_bytes, file_path: r.file_path, url: r.url, downloaded_to: opts.output || null });
|
|
38
38
|
});
|
|
39
39
|
tts
|
|
40
|
-
.command("
|
|
41
|
-
.description("Synthesize speech via
|
|
40
|
+
.command("relayx")
|
|
41
|
+
.description("Synthesize speech via RelayX (vox/index-tts-2 by default; 149 built-in voices)")
|
|
42
42
|
.helpOption("-h, --help", "show help")
|
|
43
43
|
.requiredOption("-t, --text <text>", "text to synthesize")
|
|
44
|
-
.
|
|
45
|
-
.option("--voice <id>", "voice
|
|
46
|
-
.option("--speed <n>", "speech speed multiplier", parseFloat)
|
|
47
|
-
.option("--ref-audio <path>", "reference audio (URL or local path) for clone-style workflows")
|
|
44
|
+
.option("-m, --model <id>", "RelayX TTS model id (default: vox/index-tts-2)")
|
|
45
|
+
.option("--voice <id>", "voice id within the model (vox default: 专业解说)")
|
|
46
|
+
.option("--speed <n>", "speech speed multiplier (0.5..2)", parseFloat)
|
|
48
47
|
.option("-o, --output <file>", "download audio to this local path")
|
|
49
48
|
.addHelpText("after", [
|
|
50
49
|
"",
|
|
51
50
|
"Example:",
|
|
52
|
-
" reelforge tts
|
|
51
|
+
" reelforge tts relayx -t '你好世界' --voice '专业解说' -o hello.mp3",
|
|
53
52
|
].join("\n"))
|
|
54
53
|
.action(async (opts) => {
|
|
55
|
-
const body = { text: opts.text
|
|
54
|
+
const body = { text: opts.text };
|
|
55
|
+
if (opts.model)
|
|
56
|
+
body.model = opts.model;
|
|
56
57
|
if (opts.voice)
|
|
57
58
|
body.voice = opts.voice;
|
|
58
59
|
if (opts.speed !== undefined)
|
|
59
60
|
body.speed = opts.speed;
|
|
60
|
-
|
|
61
|
-
|
|
62
|
-
|
|
63
|
-
|
|
64
|
-
await downloadTo(r.audio_url, opts.output);
|
|
65
|
-
success(`Saved → ${opts.output}`);
|
|
61
|
+
const r = await post("/api/v1/tts/relayx", body);
|
|
62
|
+
if (opts.output) {
|
|
63
|
+
await downloadTo(r.url, opts.output);
|
|
64
|
+
success(`Saved → ${opts.output} (${r.size_bytes} bytes, model=${r.model}, voice=${r.voice})`);
|
|
66
65
|
}
|
|
67
66
|
print(r);
|
|
68
67
|
});
|
package/dist/index.js
CHANGED
|
@@ -17,13 +17,11 @@ import { registerCreate } from "./commands/create.js";
|
|
|
17
17
|
import { registerLlm } from "./commands/llm.js";
|
|
18
18
|
import { registerTts } from "./commands/tts.js";
|
|
19
19
|
import { registerImages } from "./commands/images.js";
|
|
20
|
-
import { registerVideos } from "./commands/videos.js";
|
|
21
20
|
import { registerContent } from "./commands/content.js";
|
|
22
21
|
import { registerTemplates } from "./commands/templates.js";
|
|
23
22
|
import { registerFrames } from "./commands/frames.js";
|
|
24
23
|
import { registerCompositions } from "./commands/compositions.js";
|
|
25
24
|
import { registerPipelines } from "./commands/pipelines.js";
|
|
26
|
-
import { registerWorkflows } from "./commands/workflows.js";
|
|
27
25
|
import { registerBgm } from "./commands/bgm.js";
|
|
28
26
|
import { registerFiles } from "./commands/files.js";
|
|
29
27
|
import { registerTasks } from "./commands/tasks.js";
|
|
@@ -70,7 +68,7 @@ program.addHelpText("afterAll", [
|
|
|
70
68
|
" rf create '...' -o ./videos/space.mp4 # pick the exact path",
|
|
71
69
|
" rf llm chat --prompt 'explain antifragile in 3 sentences'",
|
|
72
70
|
" rf tts edge --text 'hello world' --voice en-US-AriaNeural -o out.mp3",
|
|
73
|
-
" rf images generate --prompt 'a cat' --
|
|
71
|
+
" rf images generate --prompt 'a cat' --model rx-image-flux -o cat.png",
|
|
74
72
|
" rf pipelines standard --text 'why we explore space' --tts-voice zh-CN-YunjianNeural",
|
|
75
73
|
" rf tasks list --status running",
|
|
76
74
|
" rf config get",
|
|
@@ -80,13 +78,11 @@ registerCreate(program);
|
|
|
80
78
|
registerLlm(program);
|
|
81
79
|
registerTts(program);
|
|
82
80
|
registerImages(program);
|
|
83
|
-
registerVideos(program);
|
|
84
81
|
registerContent(program);
|
|
85
82
|
registerTemplates(program);
|
|
86
83
|
registerFrames(program);
|
|
87
84
|
registerCompositions(program);
|
|
88
85
|
registerPipelines(program);
|
|
89
|
-
registerWorkflows(program);
|
|
90
86
|
registerBgm(program);
|
|
91
87
|
registerFiles(program);
|
|
92
88
|
registerTasks(program);
|
package/package.json
CHANGED
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
{
|
|
2
2
|
"name": "reelforge",
|
|
3
|
-
"version": "0.
|
|
3
|
+
"version": "0.5.0",
|
|
4
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
5
|
"license": "Apache-2.0",
|
|
6
6
|
"type": "module",
|
|
@@ -35,8 +35,7 @@
|
|
|
35
35
|
"keywords": [
|
|
36
36
|
"reelforge",
|
|
37
37
|
"ai-video",
|
|
38
|
-
"
|
|
39
|
-
"runninghub",
|
|
38
|
+
"relayx",
|
|
40
39
|
"tts",
|
|
41
40
|
"edge-tts",
|
|
42
41
|
"ffmpeg",
|
package/dist/commands/videos.js
DELETED
|
@@ -1,52 +0,0 @@
|
|
|
1
|
-
import { post } from "../client.js";
|
|
2
|
-
import { downloadTo } from "../utils/download.js";
|
|
3
|
-
import { print, success } from "../utils/output.js";
|
|
4
|
-
export function registerVideos(program) {
|
|
5
|
-
const videos = program
|
|
6
|
-
.command("videos")
|
|
7
|
-
.description("Atomic video generation and analysis via ComfyUI / RunningHub workflows")
|
|
8
|
-
.helpOption("-h, --help", "show help");
|
|
9
|
-
videos
|
|
10
|
-
.command("generate")
|
|
11
|
-
.description("Generate a video via a video_* workflow")
|
|
12
|
-
.helpOption("-h, --help", "show help")
|
|
13
|
-
.requiredOption("-p, --prompt <text>", "text prompt")
|
|
14
|
-
.requiredOption("-w, --workflow <key>", "workflow key, e.g. runninghub/video_wan2.2.json")
|
|
15
|
-
.option("--width <n>", "video width", parseInt)
|
|
16
|
-
.option("--height <n>", "video height", parseInt)
|
|
17
|
-
.option("--duration <s>", "target duration in seconds", parseFloat)
|
|
18
|
-
.option("--image <pathOrUrl>", "reference image (when the workflow accepts one)")
|
|
19
|
-
.option("-o, --output <file>", "download first video to this local path")
|
|
20
|
-
.addHelpText("after", [
|
|
21
|
-
"",
|
|
22
|
-
"Example:",
|
|
23
|
-
" reelforge videos generate -p 'astronaut riding a horse' -w runninghub/video_wan2.2.json --duration 4 -o out.mp4",
|
|
24
|
-
].join("\n"))
|
|
25
|
-
.action(async (opts) => {
|
|
26
|
-
const body = { prompt: opts.prompt, workflow: opts.workflow };
|
|
27
|
-
if (opts.width !== undefined)
|
|
28
|
-
body.width = opts.width;
|
|
29
|
-
if (opts.height !== undefined)
|
|
30
|
-
body.height = opts.height;
|
|
31
|
-
if (opts.duration !== undefined)
|
|
32
|
-
body.duration = opts.duration;
|
|
33
|
-
if (opts.image)
|
|
34
|
-
body.image = opts.image;
|
|
35
|
-
const r = await post("/api/v1/videos/generate", body);
|
|
36
|
-
if (opts.output && r.videos?.[0]) {
|
|
37
|
-
await downloadTo(r.videos[0], opts.output);
|
|
38
|
-
success(`Saved → ${opts.output}`);
|
|
39
|
-
}
|
|
40
|
-
print(r);
|
|
41
|
-
});
|
|
42
|
-
videos
|
|
43
|
-
.command("analyze")
|
|
44
|
-
.description("Reverse-describe a video using an analyse_video / video_understanding workflow")
|
|
45
|
-
.helpOption("-h, --help", "show help")
|
|
46
|
-
.requiredOption("-i, --video <pathOrUrl>", "video to analyze (local path or URL)")
|
|
47
|
-
.option("-w, --workflow <key>", "workflow key", "selfhost/analyse_video.json")
|
|
48
|
-
.action(async (opts) => {
|
|
49
|
-
const r = await post("/api/v1/videos/analyze", { video: opts.video, workflow: opts.workflow });
|
|
50
|
-
print(r);
|
|
51
|
-
});
|
|
52
|
-
}
|
|
@@ -1,29 +0,0 @@
|
|
|
1
|
-
import { get } from "../client.js";
|
|
2
|
-
import { table } from "../utils/output.js";
|
|
3
|
-
export function registerWorkflows(program) {
|
|
4
|
-
const wf = program
|
|
5
|
-
.command("workflows")
|
|
6
|
-
.alias("workflow")
|
|
7
|
-
.description("Browse ComfyUI / RunningHub workflows under workflows/")
|
|
8
|
-
.helpOption("-h, --help", "show help");
|
|
9
|
-
wf
|
|
10
|
-
.command("list")
|
|
11
|
-
.description("List all available workflows")
|
|
12
|
-
.helpOption("-h, --help", "show help")
|
|
13
|
-
.option("--source <src>", "filter by source: selfhost | runninghub")
|
|
14
|
-
.option("--kind <kind>", "filter by kind: image | video | tts | analyse")
|
|
15
|
-
.action(async (opts) => {
|
|
16
|
-
const qs = new URLSearchParams();
|
|
17
|
-
if (opts.source)
|
|
18
|
-
qs.set("source", opts.source);
|
|
19
|
-
if (opts.kind)
|
|
20
|
-
qs.set("kind", opts.kind);
|
|
21
|
-
const r = await get(`/api/v1/workflows${qs.toString() ? `?${qs}` : ""}`);
|
|
22
|
-
table(r.workflows.map((w) => ({
|
|
23
|
-
key: w.key,
|
|
24
|
-
source: w.source,
|
|
25
|
-
kind: w.kind,
|
|
26
|
-
workflow_id: w.workflow_id || "-",
|
|
27
|
-
})));
|
|
28
|
-
});
|
|
29
|
-
}
|