reelforge 0.3.2 → 0.4.1

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 CHANGED
@@ -111,10 +111,6 @@ All `pipelines *` commands submit an **async task** and (by default) poll until
111
111
  | command | what it does |
112
112
  |---|---|
113
113
  | `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
114
 
119
115
  ### Resources
120
116
 
@@ -15,12 +15,10 @@ 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_"))
18
+ if (base.startsWith("video_"))
19
19
  return 15;
20
20
  if (base.startsWith("analyse_") || base.startsWith("analyze_"))
21
21
  return 2;
22
- if (base.startsWith("af_") || base.startsWith("digital_"))
23
- return 15;
24
22
  return 3;
25
23
  }
26
24
  function estimateUnits(body) {
@@ -197,6 +195,115 @@ const DEFAULTS = {
197
195
  tts_voice: "zh-CN-YunjianNeural",
198
196
  tts_speed: 1.2,
199
197
  };
198
+ const STYLE_PRESETS = {
199
+ matchstick: {
200
+ prefix: "Minimalist black-and-white matchstick figure style illustration, clean lines, simple sketch style",
201
+ label: "极简黑白火柴人",
202
+ scene: "知识科普 / 哲思 / 段子",
203
+ },
204
+ cinematic: {
205
+ prefix: "cinematic photography, soft natural lighting, shallow depth of field, 35mm film, color grading",
206
+ label: "电影感",
207
+ scene: "故事 / 旅行 / 通用百搭",
208
+ },
209
+ photorealistic: {
210
+ prefix: "photorealistic, professional studio lighting, sharp focus, high detail, 85mm portrait lens",
211
+ label: "写实棚拍",
212
+ scene: "产品 / 人像 / 美食",
213
+ },
214
+ documentary: {
215
+ prefix: "documentary photography, candid moment, natural available light, photojournalism, realistic colors",
216
+ label: "纪实新闻",
217
+ scene: "历史 / 人物 / 纪录",
218
+ },
219
+ flat: {
220
+ prefix: "flat vector illustration, modern editorial style, geometric shapes, limited color palette, clean composition",
221
+ label: "扁平商业插画",
222
+ scene: "财经 / 商业 / 数据科普",
223
+ },
224
+ anime: {
225
+ prefix: "anime illustration, cel-shaded, vibrant colors, detailed background, expressive characters, Makoto Shinkai aesthetic",
226
+ label: "日漫",
227
+ scene: "故事 / 二次元 / 年轻向",
228
+ },
229
+ comic: {
230
+ prefix: "comic book illustration, bold ink outlines, halftone shading, dynamic poses, Marvel style",
231
+ label: "美漫",
232
+ scene: "动作 / 英雄向 / 段子",
233
+ },
234
+ watercolor: {
235
+ prefix: "soft watercolor painting, gentle brush strokes, pastel color palette, dreamy atmosphere, hand-painted texture",
236
+ label: "水彩",
237
+ scene: "治愈 / 情感 / 小红书风",
238
+ },
239
+ "oil-painting": {
240
+ prefix: "oil painting, visible brushstrokes, rich saturated colors, classical composition, canvas texture",
241
+ label: "油画",
242
+ scene: "文艺 / 古典 / 艺术史",
243
+ },
244
+ ink: {
245
+ prefix: "traditional Chinese ink painting, sumi-e style, flowing brushwork, monochrome wash, minimalist composition with negative space",
246
+ label: "中国水墨",
247
+ scene: "中式叙事 / 古诗词 / 国风",
248
+ },
249
+ "3d-render": {
250
+ prefix: "3D render, octane render, smooth surfaces, soft global illumination, polished and clean",
251
+ label: "三维渲染",
252
+ scene: "科技 / 产品 / 未来 / 概念图",
253
+ },
254
+ claymation: {
255
+ prefix: "claymation style, handmade clay textures, soft diffused studio lighting, matte surfaces, pastel colors",
256
+ label: "黏土定格",
257
+ scene: "儿童 / 萌系 / 治愈段子",
258
+ },
259
+ "low-poly": {
260
+ prefix: "low poly 3D art, triangular faceted surfaces, gradient coloring, minimalist geometric, clean style",
261
+ label: "低多边形",
262
+ scene: "科技 / 概念 / 设计感",
263
+ },
264
+ pixel: {
265
+ prefix: "16-bit pixel art, retro game aesthetic, limited color palette, blocky shading, SNES style",
266
+ label: "像素",
267
+ scene: "游戏 / 怀旧 / 段子",
268
+ },
269
+ cyberpunk: {
270
+ prefix: "cyberpunk, neon-lit dystopian cityscape, rain-slicked streets, holographic signs, Blade Runner aesthetic",
271
+ label: "赛博朋克",
272
+ scene: "科技未来 / 都市夜景",
273
+ },
274
+ vaporwave: {
275
+ prefix: "vaporwave aesthetic, pink and purple gradient, retro 90s elements, glitch effects, dreamlike surreal",
276
+ label: "蒸汽波",
277
+ scene: "网络梗 / 怀旧 / 抽象审美",
278
+ },
279
+ "art-deco": {
280
+ prefix: "Art Deco style, geometric symmetric patterns, gold and black palette, 1920s glamour, luxury aesthetic",
281
+ label: "装饰艺术",
282
+ scene: "奢华品牌 / 复古优雅",
283
+ },
284
+ };
285
+ // CJK chars take 2 display columns in monospace terminals; pad accordingly.
286
+ function displayWidth(s) {
287
+ let w = 0;
288
+ for (const c of s)
289
+ w += c.charCodeAt(0) > 0x7f ? 2 : 1;
290
+ return w;
291
+ }
292
+ function padDisplay(s, width) {
293
+ const pad = Math.max(0, width - displayWidth(s));
294
+ return s + " ".repeat(pad);
295
+ }
296
+ function formatStylePresetsList() {
297
+ const keys = Object.keys(STYLE_PRESETS);
298
+ const keyW = Math.max(...keys.map((k) => k.length));
299
+ const labelW = Math.max(...keys.map((k) => displayWidth(STYLE_PRESETS[k].label)));
300
+ return keys
301
+ .map((k) => {
302
+ const p = STYLE_PRESETS[k];
303
+ return ` ${padDisplay(k, keyW)} ${padDisplay(p.label, labelW)} ${p.scene}`;
304
+ })
305
+ .join("\n");
306
+ }
200
307
  // ── Command registration ────────────────────────────────────────
201
308
  export function registerCreate(program) {
202
309
  program
@@ -216,7 +323,8 @@ export function registerCreate(program) {
216
323
  // --- Visual ---
217
324
  .option("--frame-template <key>", "HTML frame template, e.g. 1080x1920/image_default.html")
218
325
  .option("--media-workflow <key>", "AI image/video workflow, e.g. runninghub/image_flux.json")
219
- .option("--prompt-prefix <text>", "style prefix prepended to every image prompt")
326
+ .option("--prompt-prefix <text>", "raw style prefix prepended to every image prompt (overrides --style)")
327
+ .option("--style <preset>", "image style preset — shortcut for --prompt-prefix; see 'Style presets' below for the full list")
220
328
  // --- Audio (TTS) ---
221
329
  .option("--tts-voice <id>", "Edge TTS voice id, e.g. zh-CN-YunjianNeural / en-US-AriaNeural")
222
330
  .option("--tts-speed <n>", "speech speed 0.5..2", parseFloat)
@@ -255,12 +363,17 @@ export function registerCreate(program) {
255
363
  "",
256
364
  "Param groups:",
257
365
  " Content : --mode --title -n --split-mode --min/max-narration-words --min/max-image-prompt-words",
258
- " Visual : --frame-template --media-workflow --prompt-prefix",
366
+ " Visual : --frame-template --media-workflow --style --prompt-prefix",
259
367
  " TTS : --tts-voice --tts-speed --tts-inference-mode --tts-workflow --voice-id --ref-audio",
260
368
  " BGM : --bgm --bgm-volume --bgm-mode",
261
369
  " Output : --video-fps --template-params -o --no-download --no-wait --poll-ms --timeout-ms",
262
370
  " Workflow: --recipe --redo --dry-run",
263
371
  "",
372
+ "Style presets (--style <preset>) — quick shortcut for --prompt-prefix:",
373
+ formatStylePresetsList(),
374
+ " · Pass --prompt-prefix to override (raw string always wins).",
375
+ " · Omit both to use the server's configured default style.",
376
+ "",
264
377
  "Output behavior:",
265
378
  " No flag → saves to ./<title>-<task_id>.mp4 in current directory, prints the path",
266
379
  " -o <path> → saves to that exact path (must include filename, not just a directory)",
@@ -298,6 +411,13 @@ export function registerCreate(program) {
298
411
  " # Change voice + speed",
299
412
  ' rf create "..." --tts-voice zh-CN-XiaoxiaoNeural --tts-speed 1.0',
300
413
  "",
414
+ " # Pick a built-in style preset",
415
+ ' rf create "..." --style cinematic',
416
+ ' rf create "美食教程" --style photorealistic',
417
+ "",
418
+ " # Free-form style — write your own prefix from scratch",
419
+ ' rf create "..." --prompt-prefix "Studio Ghibli, pastel, dreamy"',
420
+ "",
301
421
  " # Full recipe in one file",
302
422
  " rf create --recipe ./space.recipe.json",
303
423
  "",
@@ -334,6 +454,17 @@ export function registerCreate(program) {
334
454
  if (opts.output) {
335
455
  await validateOutputPath(opts.output);
336
456
  }
457
+ // Expand --style preset to --prompt-prefix unless an explicit
458
+ // --prompt-prefix is also given (the raw string always wins).
459
+ if (opts.style) {
460
+ const preset = STYLE_PRESETS[opts.style];
461
+ if (!preset) {
462
+ throw new Error(`Unknown --style: ${opts.style}\nAvailable presets:\n${formatStylePresetsList()}`);
463
+ }
464
+ if (opts.promptPrefix === undefined) {
465
+ opts.promptPrefix = preset.prefix;
466
+ }
467
+ }
337
468
  // 1. Layer defaults: --redo → --recipe → CLI opts → positional topic
338
469
  let body = {};
339
470
  if (opts.redo) {
@@ -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, asset-based, digital-human, i2v, action-transfer)")
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
  }
@@ -8,14 +8,14 @@ export function registerVideos(program) {
8
8
  .helpOption("-h, --help", "show help");
9
9
  videos
10
10
  .command("generate")
11
- .description("Generate a video via a video_* / i2v_* workflow")
11
+ .description("Generate a video via a video_* workflow")
12
12
  .helpOption("-h, --help", "show help")
13
13
  .requiredOption("-p, --prompt <text>", "text prompt")
14
14
  .requiredOption("-w, --workflow <key>", "workflow key, e.g. runninghub/video_wan2.2.json")
15
15
  .option("--width <n>", "video width", parseInt)
16
16
  .option("--height <n>", "video height", parseInt)
17
17
  .option("--duration <s>", "target duration in seconds", parseFloat)
18
- .option("--image <pathOrUrl>", "reference image (for i2v workflows)")
18
+ .option("--image <pathOrUrl>", "reference image (when the workflow accepts one)")
19
19
  .option("-o, --output <file>", "download first video to this local path")
20
20
  .addHelpText("after", [
21
21
  "",
@@ -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 | video | tts | analyse | digital | i2v | af")
14
+ .option("--kind <kind>", "filter by kind: image | video | tts | analyse")
15
15
  .action(async (opts) => {
16
16
  const qs = new URLSearchParams();
17
17
  if (opts.source)
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "reelforge",
3
- "version": "0.3.2",
3
+ "version": "0.4.1",
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",