reelforge 0.4.0 → 0.4.2
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 +0 -6
- package/dist/commands/create.js +3 -19
- package/dist/commands/pipelines.js +1 -78
- package/dist/commands/workflows.js +1 -1
- package/dist/index.js +0 -2
- package/package.json +52 -52
- package/dist/commands/videos.js +0 -52
package/README.md
CHANGED
|
@@ -79,8 +79,6 @@ Run `rf <command> --help` for full details on any of these.
|
|
|
79
79
|
| `tts voices [--locale zh]` | List supported Edge TTS voices |
|
|
80
80
|
| `images generate -p <prompt> -w <workflow>` | Image generation via ComfyUI / RunningHub |
|
|
81
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 |
|
|
84
82
|
|
|
85
83
|
### Content generation
|
|
86
84
|
|
|
@@ -111,10 +109,6 @@ All `pipelines *` commands submit an **async task** and (by default) poll until
|
|
|
111
109
|
| command | what it does |
|
|
112
110
|
|---|---|
|
|
113
111
|
| `pipelines standard -t <topic\|script>` | Topic / script → narration → frames → final MP4 |
|
|
114
|
-
| `pipelines asset-based --intent ... --assets <file>` | User assets → AI-arranged scene video |
|
|
115
|
-
| `pipelines digital-human -w <wf> -i <portrait> -t <text>` | Digital human speaker |
|
|
116
|
-
| `pipelines i2v -w <wf> -i <image>` | Image-to-video |
|
|
117
|
-
| `pipelines action-transfer -w <wf> --reference-video <v> --character-image <img>` | Action transfer |
|
|
118
112
|
|
|
119
113
|
### Resources
|
|
120
114
|
|
package/dist/commands/create.js
CHANGED
|
@@ -15,12 +15,8 @@ function calcWorkflowUnits(wfKey) {
|
|
|
15
15
|
return 1;
|
|
16
16
|
if (base.startsWith("image_"))
|
|
17
17
|
return 3;
|
|
18
|
-
if (base.startsWith("video_") || base.startsWith("i2v_"))
|
|
19
|
-
return 15;
|
|
20
18
|
if (base.startsWith("analyse_") || base.startsWith("analyze_"))
|
|
21
19
|
return 2;
|
|
22
|
-
if (base.startsWith("af_") || base.startsWith("digital_"))
|
|
23
|
-
return 15;
|
|
24
20
|
return 3;
|
|
25
21
|
}
|
|
26
22
|
function estimateUnits(body) {
|
|
@@ -32,20 +28,13 @@ function estimateUnits(body) {
|
|
|
32
28
|
const tplBase = (tplKey.split("/").pop() || "").toLowerCase();
|
|
33
29
|
const tplType = tplBase.startsWith("static_")
|
|
34
30
|
? "static"
|
|
35
|
-
: tplBase.startsWith("
|
|
36
|
-
? "
|
|
37
|
-
:
|
|
38
|
-
? "video"
|
|
39
|
-
: tplBase.startsWith("asset_")
|
|
40
|
-
? "asset"
|
|
41
|
-
: "image";
|
|
31
|
+
: tplBase.startsWith("asset_")
|
|
32
|
+
? "asset"
|
|
33
|
+
: "image";
|
|
42
34
|
let mediaPerFrame = 0;
|
|
43
35
|
if (tplType === "image") {
|
|
44
36
|
mediaPerFrame = body.media_workflow ? calcWorkflowUnits(body.media_workflow) : 3;
|
|
45
37
|
}
|
|
46
|
-
else if (tplType === "video") {
|
|
47
|
-
mediaPerFrame = body.media_workflow ? calcWorkflowUnits(body.media_workflow) : 15;
|
|
48
|
-
}
|
|
49
38
|
const ttsMode = body.tts_inference_mode || (body.tts_workflow ? "comfyui" : "local");
|
|
50
39
|
const ttsPerFrame = ttsMode === "comfyui" ? 1 : 0;
|
|
51
40
|
const narrations = mode === "generate" ? 1 : 0;
|
|
@@ -402,11 +391,6 @@ export function registerCreate(program) {
|
|
|
402
391
|
" # Landscape (1920x1080)",
|
|
403
392
|
' rf create "..." --frame-template 1920x1080/image_default.html',
|
|
404
393
|
"",
|
|
405
|
-
" # AI-generated video background instead of still image",
|
|
406
|
-
' rf create "..." \\',
|
|
407
|
-
" --frame-template 1080x1920/video_default.html \\",
|
|
408
|
-
" --media-workflow runninghub/video_wan2.2.json",
|
|
409
|
-
"",
|
|
410
394
|
" # Add BGM",
|
|
411
395
|
' rf create "..." --bgm bgm/Echoes.mp3 --bgm-volume 0.3 --bgm-mode loop',
|
|
412
396
|
"",
|
|
@@ -1,5 +1,4 @@
|
|
|
1
1
|
import fs from "node:fs/promises";
|
|
2
|
-
import path from "node:path";
|
|
3
2
|
import { post } from "../client.js";
|
|
4
3
|
import { waitForTask } from "../utils/task-waiter.js";
|
|
5
4
|
import { downloadTo } from "../utils/download.js";
|
|
@@ -35,7 +34,7 @@ export function registerPipelines(program) {
|
|
|
35
34
|
const pl = program
|
|
36
35
|
.command("pipelines")
|
|
37
36
|
.alias("pipeline")
|
|
38
|
-
.description("End-to-end video pipelines (standard
|
|
37
|
+
.description("End-to-end video pipelines (standard)")
|
|
39
38
|
.helpOption("-h, --help", "show help");
|
|
40
39
|
// ---------- standard ----------
|
|
41
40
|
commonOptions(pl
|
|
@@ -79,80 +78,4 @@ export function registerPipelines(program) {
|
|
|
79
78
|
bgm_volume: opts.bgmVolume,
|
|
80
79
|
}, { wait: opts.wait, output: opts.output, pollMs: opts.pollMs, timeoutMs: opts.timeoutMs });
|
|
81
80
|
});
|
|
82
|
-
// ---------- asset-based ----------
|
|
83
|
-
commonOptions(pl
|
|
84
|
-
.command("asset-based")
|
|
85
|
-
.description("User-uploaded assets + intent → AI-arranged scene video")
|
|
86
|
-
.helpOption("-h, --help", "show help")
|
|
87
|
-
.requiredOption("--intent <text>", "video purpose / intent")
|
|
88
|
-
.requiredOption("--assets <file>", "file with one asset path per line")
|
|
89
|
-
.option("--title <text>", "video title")
|
|
90
|
-
.option("--duration <s>", "target duration", parseInt, 30)
|
|
91
|
-
.option("--source <src>", "analysis backend: runninghub | selfhost", "runninghub")
|
|
92
|
-
.option("--voice <id>", "Edge TTS voice", "zh-CN-YunjianNeural")).action(async (opts) => {
|
|
93
|
-
const assetsText = await fs.readFile(opts.assets, "utf-8");
|
|
94
|
-
const assets = assetsText.split(/\r?\n/).map((s) => s.trim()).filter(Boolean);
|
|
95
|
-
await submitAndMaybeWait("/api/v1/pipelines/asset-based", {
|
|
96
|
-
intent: opts.intent,
|
|
97
|
-
video_title: opts.title,
|
|
98
|
-
duration: opts.duration,
|
|
99
|
-
source: opts.source,
|
|
100
|
-
voice_id: opts.voice,
|
|
101
|
-
assets: assets.map((a) => path.resolve(a)),
|
|
102
|
-
}, { wait: opts.wait, output: opts.output, pollMs: opts.pollMs, timeoutMs: opts.timeoutMs });
|
|
103
|
-
});
|
|
104
|
-
// ---------- digital-human ----------
|
|
105
|
-
commonOptions(pl
|
|
106
|
-
.command("digital-human")
|
|
107
|
-
.description("Portrait image + (text or audio) → digital-human speaker video")
|
|
108
|
-
.helpOption("-h, --help", "show help")
|
|
109
|
-
.requiredOption("-w, --workflow <key>", "workflow, e.g. runninghub/digital_image.json")
|
|
110
|
-
.requiredOption("-i, --image <file>", "portrait image path/URL")
|
|
111
|
-
.option("-t, --text <text>", "speech text (TTS will run if --audio absent)")
|
|
112
|
-
.option("--audio <file>", "pre-existing audio file (skip TTS)")
|
|
113
|
-
.option("--voice <id>", "Edge TTS voice", "zh-CN-YunjianNeural")
|
|
114
|
-
.option("--speed <n>", "TTS speed", parseFloat, 1.0)).action(async (opts) => {
|
|
115
|
-
if (!opts.text && !opts.audio)
|
|
116
|
-
throw new Error("Either --text or --audio is required");
|
|
117
|
-
await submitAndMaybeWait("/api/v1/pipelines/digital-human", {
|
|
118
|
-
workflow: opts.workflow,
|
|
119
|
-
image: opts.image,
|
|
120
|
-
text: opts.text,
|
|
121
|
-
audio: opts.audio,
|
|
122
|
-
voice: opts.voice,
|
|
123
|
-
speed: opts.speed,
|
|
124
|
-
}, { wait: opts.wait, output: opts.output, pollMs: opts.pollMs, timeoutMs: opts.timeoutMs });
|
|
125
|
-
});
|
|
126
|
-
// ---------- i2v ----------
|
|
127
|
-
commonOptions(pl
|
|
128
|
-
.command("i2v")
|
|
129
|
-
.description("Static image → AI-generated dynamic video (image-to-video)")
|
|
130
|
-
.helpOption("-h, --help", "show help")
|
|
131
|
-
.requiredOption("-w, --workflow <key>", "workflow, e.g. runninghub/i2v_LTX2.json")
|
|
132
|
-
.requiredOption("-i, --image <file>", "image path/URL")
|
|
133
|
-
.option("-p, --prompt <text>", "motion description prompt")
|
|
134
|
-
.option("--duration <s>", "target duration", parseInt, 4)).action(async (opts) => {
|
|
135
|
-
await submitAndMaybeWait("/api/v1/pipelines/i2v", {
|
|
136
|
-
workflow: opts.workflow,
|
|
137
|
-
image: opts.image,
|
|
138
|
-
prompt: opts.prompt,
|
|
139
|
-
duration: opts.duration,
|
|
140
|
-
}, { wait: opts.wait, output: opts.output, pollMs: opts.pollMs, timeoutMs: opts.timeoutMs });
|
|
141
|
-
});
|
|
142
|
-
// ---------- action-transfer ----------
|
|
143
|
-
commonOptions(pl
|
|
144
|
-
.command("action-transfer")
|
|
145
|
-
.description("Reference action video + character image → action-mimicking video")
|
|
146
|
-
.helpOption("-h, --help", "show help")
|
|
147
|
-
.requiredOption("-w, --workflow <key>", "workflow, e.g. runninghub/af_scail.json")
|
|
148
|
-
.requiredOption("--reference-video <file>", "reference action video")
|
|
149
|
-
.requiredOption("--character-image <file>", "character image")
|
|
150
|
-
.option("-p, --prompt <text>", "extra prompt")).action(async (opts) => {
|
|
151
|
-
await submitAndMaybeWait("/api/v1/pipelines/action-transfer", {
|
|
152
|
-
workflow: opts.workflow,
|
|
153
|
-
reference_video: opts.referenceVideo,
|
|
154
|
-
character_image: opts.characterImage,
|
|
155
|
-
prompt: opts.prompt,
|
|
156
|
-
}, { wait: opts.wait, output: opts.output, pollMs: opts.pollMs, timeoutMs: opts.timeoutMs });
|
|
157
|
-
});
|
|
158
81
|
}
|
|
@@ -11,7 +11,7 @@ export function registerWorkflows(program) {
|
|
|
11
11
|
.description("List all available workflows")
|
|
12
12
|
.helpOption("-h, --help", "show help")
|
|
13
13
|
.option("--source <src>", "filter by source: selfhost | runninghub")
|
|
14
|
-
.option("--kind <kind>", "filter by kind: image |
|
|
14
|
+
.option("--kind <kind>", "filter by kind: image | tts | analyse")
|
|
15
15
|
.action(async (opts) => {
|
|
16
16
|
const qs = new URLSearchParams();
|
|
17
17
|
if (opts.source)
|
package/dist/index.js
CHANGED
|
@@ -17,7 +17,6 @@ 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";
|
|
@@ -80,7 +79,6 @@ registerCreate(program);
|
|
|
80
79
|
registerLlm(program);
|
|
81
80
|
registerTts(program);
|
|
82
81
|
registerImages(program);
|
|
83
|
-
registerVideos(program);
|
|
84
82
|
registerContent(program);
|
|
85
83
|
registerTemplates(program);
|
|
86
84
|
registerFrames(program);
|
package/package.json
CHANGED
|
@@ -1,52 +1,52 @@
|
|
|
1
|
-
{
|
|
2
|
-
"name": "reelforge",
|
|
3
|
-
"version": "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
|
-
"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
|
-
"comfyui",
|
|
39
|
-
"runninghub",
|
|
40
|
-
"tts",
|
|
41
|
-
"edge-tts",
|
|
42
|
-
"ffmpeg",
|
|
43
|
-
"playwright",
|
|
44
|
-
"cli"
|
|
45
|
-
],
|
|
46
|
-
"repository": {
|
|
47
|
-
"type": "git",
|
|
48
|
-
"url": "https://github.com/puke3615/ReelForge.git",
|
|
49
|
-
"directory": "cli"
|
|
50
|
-
},
|
|
51
|
-
"homepage": "https://github.com/puke3615/ReelForge"
|
|
52
|
-
}
|
|
1
|
+
{
|
|
2
|
+
"name": "reelforge",
|
|
3
|
+
"version": "0.4.2",
|
|
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
|
+
"comfyui",
|
|
39
|
+
"runninghub",
|
|
40
|
+
"tts",
|
|
41
|
+
"edge-tts",
|
|
42
|
+
"ffmpeg",
|
|
43
|
+
"playwright",
|
|
44
|
+
"cli"
|
|
45
|
+
],
|
|
46
|
+
"repository": {
|
|
47
|
+
"type": "git",
|
|
48
|
+
"url": "https://github.com/puke3615/ReelForge.git",
|
|
49
|
+
"directory": "cli"
|
|
50
|
+
},
|
|
51
|
+
"homepage": "https://github.com/puke3615/ReelForge"
|
|
52
|
+
}
|
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_* / i2v_* 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 (for i2v workflows)")
|
|
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
|
-
}
|