zerocut-cli 0.2.1 → 0.3.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 CHANGED
@@ -101,7 +101,7 @@ zerocut config --ott <token> --region <cn|us> # non-interactive
101
101
  - `image` — create a new image (default action; requires `--prompt`)
102
102
  - Options:
103
103
  - `--prompt <prompt>` (required)
104
- - `--model <model>` (seedream|seedream-pro|seedream-5l|banana|banana2|banana-pro|wan)
104
+ - `--model <model>` (seedream|seedream-pro|seedream-5l|banana|banana2|banana-pro|wan|wan-pro)
105
105
  - `--aspectRatio <ratio>` (1:1|3:4|4:3|16:9|9:16|2:3|3:2|21:9|1:4|4:1|1:8|8:1)
106
106
  - `--resolution <resolution>` (1K|2K|4K)
107
107
  - `--refs <img1,img2,...>` (comma-separated paths/URLs)
@@ -110,15 +110,18 @@ zerocut config --ott <token> --region <cn|us> # non-interactive
110
110
  - Options:
111
111
  - `--prompt <prompt>` (required)
112
112
  - `--duration <seconds>` (integer 1–16; when `--sourceVideo` is set, must be 3–10)
113
- - `--model <model>` (enum: `zerocut3.0|zerocut3.0-pro|zerocut3.0-pro-fast|seedance-1.5-pro|vidu|vidu-pro|viduq3|viduq3-turbo|kling|kling-v3|wan|wan-flash|sora2|sora2-pro|veo3.1|veo3.1-pro`; default `vidu`)
113
+ - `--model <model>` (enum: `zerocut3.0|zerocut3.0-pro|zerocut3.0-pro-fast|zerocut3.0-turbo|seedance-1.5-pro|seedance-2.0|seedance-2.0-fast|vidu|vidu-pro|viduq3|viduq3-turbo|kling|kling-v3|wan|wan-flash|sora2|sora2-pro|veo3.1|veo3.1-pro`; default `vidu`)
114
114
  - `--sourceVideo <video>` (base video path/url for edit mode)
115
115
  - `--seed <seed>`
116
116
  - `--firstFrame <image>`
117
117
  - `--lastFrame <image>`
118
+ - `--storyboard <image>`
119
+ - `--persons <persons>` (comma-separated person image paths/URLs)
118
120
  - `--refs <assets>`
119
121
  - `--resolution <resolution>`
120
122
  - `--aspectRatio <ratio>` (9:16|16:9|1:1)
121
123
  - `--withAudio`
124
+ - `--withBGM <withBGM>` (true|false, default true)
122
125
  - `--optimizeCameraMotion`
123
126
  - `--output <file>`
124
127
  - Notes:
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "zerocut-cli",
3
- "version": "0.2.1",
3
+ "version": "0.3.0",
4
4
  "description": "ZeroCut CLI: AI assistant CLI for creating and editing images/audio/video",
5
5
  "main": "dist/index.js",
6
6
  "types": "dist/index.d.ts",
@@ -38,7 +38,7 @@
38
38
  "typescript-eslint": "^8.54.0"
39
39
  },
40
40
  "dependencies": {
41
- "cerevox": "^4.29.3",
41
+ "cerevox": "^4.46.0",
42
42
  "commander": "^14.0.3",
43
43
  "inquirer": "^13.2.2"
44
44
  },
@@ -19,6 +19,7 @@ export function register(program: Command): void {
19
19
  "banana2",
20
20
  "banana-pro",
21
21
  "wan",
22
+ "wan-pro",
22
23
  ] as const;
23
24
  type AllowedModel = (typeof allowedModels)[number];
24
25
  const allowedAspectRatios = [
@@ -67,6 +68,7 @@ export function register(program: Command): void {
67
68
  aspect_ratio: aspectRatio,
68
69
  resolution,
69
70
  reference_images: referenceImages,
71
+ sequential_image_generation: "disabled",
70
72
  onProgress,
71
73
  };
72
74
  const res = await (session.ai.generateImage as (arg: Record<string, unknown>) => Promise<any>)(
@@ -5,16 +5,33 @@ import path from "node:path";
5
5
  export const name = "skill";
6
6
  export const description = "Print built-in SKILL.md content";
7
7
 
8
+ function printSkill(relativePath: string): void {
9
+ const filePath = path.resolve(__dirname, relativePath);
10
+ const content = fs.readFileSync(filePath, "utf8");
11
+ process.stdout.write(content);
12
+ if (!content.endsWith("\n")) {
13
+ process.stdout.write("\n");
14
+ }
15
+ }
16
+
8
17
  export function register(program: Command): void {
9
- program
10
- .command("skill")
11
- .description("Print built-in skill markdown")
18
+ const parent = program.command("skill").description("Print built-in skill markdown");
19
+
20
+ parent
21
+ .command("one-click-video")
22
+ .description("Print one-click-video skill markdown")
12
23
  .action(() => {
13
- const filePath = path.resolve(__dirname, "../skill/SKILL.md");
14
- const content = fs.readFileSync(filePath, "utf8");
15
- process.stdout.write(content);
16
- if (!content.endsWith("\n")) {
17
- process.stdout.write("\n");
18
- }
24
+ printSkill("../skill/one-click-video/SKILL.md");
19
25
  });
26
+
27
+ parent
28
+ .command("edit-video")
29
+ .description("Print edit-video skill markdown")
30
+ .action(() => {
31
+ printSkill("../skill/edit-video/SKILL.md");
32
+ });
33
+
34
+ parent.action(() => {
35
+ printSkill("../skill/SKILL.md");
36
+ });
20
37
  }
@@ -34,7 +34,10 @@ export function register(program: Command): void {
34
34
  "zerocut3.0",
35
35
  "zerocut3.0-pro",
36
36
  "zerocut3.0-pro-fast",
37
+ "zerocut3.0-turbo",
37
38
  "seedance-1.5-pro",
39
+ "seedance-2.0",
40
+ "seedance-2.0-fast",
38
41
  "vidu",
39
42
  "vidu-pro",
40
43
  "viduq3",
@@ -61,10 +64,13 @@ export function register(program: Command): void {
61
64
  seed?: string;
62
65
  firstFrame?: string;
63
66
  lastFrame?: string;
67
+ storyboard?: string;
68
+ persons?: string;
64
69
  refs?: string;
65
70
  resolution?: "720p" | "1080p";
66
71
  aspectRatio?: "9:16" | "16:9" | "1:1";
67
72
  withAudio?: boolean;
73
+ withBGM?: string;
68
74
  optimizeCameraMotion?: boolean;
69
75
  output?: string;
70
76
  }
@@ -122,8 +128,21 @@ export function register(program: Command): void {
122
128
  return;
123
129
  }
124
130
  const aspectRatio = ar as (typeof allowedAspectRatios)[number] | undefined;
131
+ let withBGM = true;
132
+ if (typeof opts.withBGM === "string") {
133
+ const withBGMRaw = opts.withBGM.trim().toLowerCase();
134
+ if (withBGMRaw === "true") {
135
+ withBGM = true;
136
+ } else if (withBGMRaw === "false") {
137
+ withBGM = false;
138
+ } else {
139
+ process.stderr.write("Invalid value for --withBGM: expected true|false\n");
140
+ process.exitCode = 1;
141
+ return;
142
+ }
143
+ }
125
144
  const images: {
126
- type: "first_frame" | "last_frame" | "reference" | "storyboard";
145
+ type: "first_frame" | "last_frame" | "reference" | "storyboard" | "person";
127
146
  url: string;
128
147
  name?: string;
129
148
  }[] = [];
@@ -139,6 +158,25 @@ export function register(program: Command): void {
139
158
  url: await getMaterialUri(session, opts.lastFrame),
140
159
  });
141
160
  }
161
+ if (opts.storyboard) {
162
+ images.push({
163
+ type: "storyboard",
164
+ url: await getMaterialUri(session, opts.storyboard),
165
+ });
166
+ }
167
+ const personList =
168
+ typeof opts.persons === "string" && opts.persons.length > 0
169
+ ? opts.persons
170
+ .split(",")
171
+ .map((s) => s.trim())
172
+ .filter((s) => s.length > 0)
173
+ : [];
174
+ for (const person of personList) {
175
+ images.push({
176
+ type: "person",
177
+ url: await getMaterialUri(session, person),
178
+ });
179
+ }
142
180
  const refsList =
143
181
  typeof opts.refs === "string" && opts.refs.length > 0
144
182
  ? opts.refs
@@ -152,13 +190,14 @@ export function register(program: Command): void {
152
190
  url: await getMaterialUri(session, ref),
153
191
  });
154
192
  }
155
- const res = await session.ai.generateVideo({
193
+ const request = {
156
194
  prompt,
157
195
  model: model as unknown as Parameters<typeof session.ai.generateVideo>[0]["model"],
158
196
  duration: duration || undefined,
159
197
  resolution: opts.resolution,
160
198
  aspect_ratio: aspectRatio,
161
199
  mute: !(opts.withAudio ?? true),
200
+ bgm: withBGM,
162
201
  optimize_camera: opts.optimizeCameraMotion,
163
202
  seed: opts.seed ? Number.parseInt(opts.seed, 10) : undefined,
164
203
  images: images.length > 0 ? images : undefined,
@@ -172,7 +211,8 @@ export function register(program: Command): void {
172
211
  : undefined,
173
212
  onProgress: createProgressSpinner("inferencing"),
174
213
  timeout: 7_200_000,
175
- });
214
+ } as unknown as Parameters<typeof session.ai.generateVideo>[0];
215
+ const res = await session.ai.generateVideo(request);
176
216
  const initialUrl = resolveResultUrl(res);
177
217
  try {
178
218
  if (initialUrl) {
@@ -219,10 +259,13 @@ export function register(program: Command): void {
219
259
  .option("--seed <seed>", "Random seed")
220
260
  .option("--firstFrame <image>", "First frame image path/url")
221
261
  .option("--lastFrame <image>", "Last frame image path/url")
262
+ .option("--storyboard <image>", "Storyboard image path/url")
263
+ .option("--persons <persons>", "Comma-separated person image paths/urls")
222
264
  .option("--refs <refs>", "Comma-separated reference image/video paths/urls")
223
265
  .option("--resolution <resolution>", "Resolution, e.g., 720p")
224
266
  .option("--aspectRatio <ratio>", "Aspect ratio: 9:16|16:9|1:1")
225
267
  .option("--withAudio", "Include audio track")
268
+ .option("--withBGM <withBGM>", "Include background music: true|false (default: true)")
226
269
  .option("--optimizeCameraMotion", "Optimize camera motion")
227
270
  .option("--output <file>", "Output file path")
228
271
  .action(videoCreateAction);
@@ -241,10 +284,13 @@ export function register(program: Command): void {
241
284
  .option("--seed <seed>", "Random seed")
242
285
  .option("--firstFrame <image>", "First frame image path/url")
243
286
  .option("--lastFrame <image>", "Last frame image path/url")
287
+ .option("--storyboard <image>", "Storyboard image path/url")
288
+ .option("--persons <persons>", "Comma-separated person image paths/urls")
244
289
  .option("--refs <refs>", "Comma-separated reference image/video paths/urls")
245
290
  .option("--resolution <resolution>", "Resolution, e.g., 720p")
246
291
  .option("--aspectRatio <ratio>", "Aspect ratio: 9:16|16:9|1:1")
247
292
  .option("--withAudio", "Include audio track")
293
+ .option("--withBGM <withBGM>", "Include background music: true|false (default: true)")
248
294
  .option("--optimizeCameraMotion", "Optimize camera motion")
249
295
  .option("--output <file>", "Output file path")
250
296
  .action(videoCreateAction);
@@ -204,7 +204,14 @@ export function applyConfigInterceptor(program: Command): void {
204
204
  const current = (actionCommand ?? thisCommand) as Command;
205
205
  const name = current?.name?.();
206
206
  const parentName = current?.parent?.name?.();
207
- if (name === "help" || name === "skill" || name === "config" || parentName === "config") return;
207
+ if (
208
+ name === "help" ||
209
+ name === "skill" ||
210
+ name === "config" ||
211
+ parentName === "config" ||
212
+ parentName === "skill"
213
+ )
214
+ return;
208
215
  const ok = await ensureConfig();
209
216
  if (!ok) {
210
217
  process.exit(1);
@@ -217,7 +224,8 @@ export function applyConfigInterceptor(program: Command): void {
217
224
  });
218
225
  program.hook("postAction", async (thisCommand, actionCommand) => {
219
226
  const name = actionCommand?.name?.() ?? thisCommand?.name?.();
220
- if (name === "help" || name === "skill") return;
227
+ const parentName = actionCommand?.parent?.name?.() ?? thisCommand?.parent?.name?.();
228
+ if (name === "help" || name === "skill" || parentName === "skill") return;
221
229
  try {
222
230
  const cmd = (actionCommand ?? thisCommand) as Command & {
223
231
  [SESSION_SYMBOL]?: import("cerevox").Session;
@@ -81,6 +81,21 @@ Notes:
81
81
 
82
82
  ## Command Reference
83
83
 
84
+ ### skill
85
+
86
+ Use `skill` command to fetch built-in skill markdown:
87
+
88
+ ```bash
89
+ npx zerocut-cli skill
90
+ npx zerocut-cli skill one-click-video
91
+ npx zerocut-cli skill edit-video
92
+ ```
93
+
94
+ Routing rules:
95
+
96
+ - When user requests one-click video creation, run `npx zerocut-cli skill one-click-video` to get the sub-skill and execute.
97
+ - When user requests video editing, run `npx zerocut-cli skill edit-video` to get the sub-skill and execute.
98
+
84
99
  ### image
85
100
 
86
101
  Default action: `create`
@@ -102,7 +117,7 @@ Options:
102
117
  Validation rules:
103
118
 
104
119
  - `--prompt` must be non-empty
105
- - `--model` allowed: `seedream|seedream-pro|seedream-5l|banana|banana2|banana-pro|wan`
120
+ - `--model` allowed: `seedream|seedream-pro|seedream-5l|banana|banana2|banana-pro|wan|wan-pro`
106
121
  - `--aspectRatio` allowed: `1:1|3:4|4:3|16:9|9:16|2:3|3:2|21:9|1:4|4:1|1:8|8:1`
107
122
  - unless user specifies aspect ratio, default to `16:9`
108
123
  - `--resolution` allowed: `1K|2K|4K`
@@ -127,17 +142,20 @@ Options:
127
142
  - `--seed <seed>`
128
143
  - `--firstFrame <image>`
129
144
  - `--lastFrame <image>`
145
+ - `--storyboard <image>`
146
+ - `--persons <persons>`
130
147
  - `--refs <assets>`
131
148
  - `--resolution <resolution>`
132
149
  - `--aspectRatio <ratio>`
133
150
  - `--withAudio`
151
+ - `--withBGM <withBGM>`
134
152
  - `--optimizeCameraMotion`
135
153
  - `--output <file>`
136
154
 
137
155
  Validation rules:
138
156
 
139
157
  - `--prompt` must be non-empty
140
- - `--model` allowed: `zerocut3.0|zerocut3.0-pro|zerocut3.0-pro-fast|seedance-1.5-pro|vidu|vidu-pro|viduq3|viduq3-turbo|kling|kling-v3|wan|wan-flash|sora2|sora2-pro|veo3.1|veo3.1-pro|zerocut-avatar-1.0|zerocut-avatar-1.5|zerocut-mv-1.0`
158
+ - `--model` allowed: `zerocut3.0|zerocut3.0-pro|zerocut3.0-pro-fast|zerocut3.0-turbo|seedance-1.5-pro|seedance-2.0|seedance-2.0-fast|vidu|vidu-pro|viduq3|viduq3-turbo|kling|kling-v3|wan|wan-flash|sora2|sora2-pro|veo3.1|veo3.1-pro|zerocut-avatar-1.0|zerocut-avatar-1.5|zerocut-mv-1.0`
141
159
  - `--duration` must follow model range:
142
160
  - default models: `1-16`
143
161
  - `zerocut-avatar-1.0` / `zerocut-avatar-1.5`: `5-240`
@@ -145,6 +163,7 @@ Validation rules:
145
163
  - `--aspectRatio` allowed: `9:16|16:9|1:1`
146
164
  - unless user specifies aspect ratio, default to `16:9`
147
165
  - unless user specifies resolution, default to `720p`
166
+ - `--withBGM` allowed: `true|false`, default to `true`
148
167
 
149
168
  Long video guidance:
150
169
 
@@ -0,0 +1,111 @@
1
+ ---
2
+ name: edit-video
3
+ description: Use this skill when the user wants to edit existing videos, replace visual elements, preserve camera language, or stitch multiple clips. Execute with zerocut-cli commands only.
4
+ homepage: "https://github.com/liubei-ai/zerocut-cli"
5
+ source: "https://github.com/liubei-ai/zerocut-cli"
6
+ requires_binaries:
7
+ - "zerocut-cli"
8
+ - "npx"
9
+ ---
10
+
11
+ # Edit Video
12
+
13
+ ## Runtime Requirements
14
+
15
+ - Use CLI commands only.
16
+ - Do not use MCP tool names in instructions.
17
+ - Ensure CLI is available:
18
+ - `pnpm dlx zerocut-cli help`
19
+ - `pnpm add -g zerocut-cli && zerocut-cli help`
20
+ - `npx zerocut-cli help`
21
+
22
+ ## Required Pre-Check
23
+
24
+ ```bash
25
+ npx zerocut-cli config list
26
+ ```
27
+
28
+ If `apiKey` is missing, stop and request OTT token:
29
+
30
+ ```bash
31
+ npx zerocut-cli config --ott <token> --region <cn|us>
32
+ ```
33
+
34
+ ## Video Edit Defaults
35
+
36
+ - Default model is `seedance-2.0` unless user explicitly specifies another legal video model.
37
+ - Unless user explicitly specifies, keep original visual specs by not forcing `--aspectRatio` and `--resolution`.
38
+ - Keep user-requested camera language and motion intent.
39
+
40
+ ## Legal Video Parameters
41
+
42
+ Only use legal `video` command options:
43
+
44
+ - `--prompt <prompt>` required
45
+ - `--duration <seconds>`
46
+ - `--model <model>`
47
+ - `--sourceVideo <video>`
48
+ - `--seed <seed>`
49
+ - `--firstFrame <image>`
50
+ - `--lastFrame <image>`
51
+ - `--storyboard <image>`
52
+ - `--persons <persons>`
53
+ - `--refs <assets>`
54
+ - `--resolution <resolution>`
55
+ - `--aspectRatio <ratio>`
56
+ - `--withAudio`
57
+ - `--withBGM <withBGM>`
58
+ - `--optimizeCameraMotion`
59
+ - `--output <file>`
60
+
61
+ ## Replace Elements In Existing Video
62
+
63
+ When user asks to replace objects/subjects in an existing clip, pass source video and references to `video` command:
64
+
65
+ ```bash
66
+ npx zerocut-cli video \
67
+ --prompt "Replace the perfume gift-box product with the cream from reference image, keep original camera movement." \
68
+ --model seedance-2.0 \
69
+ --sourceVideo input.mp4 \
70
+ --refs product_ref.png \
71
+ --withAudio \
72
+ --withBGM true \
73
+ --output edited.mp4
74
+ ```
75
+
76
+ ## Character-Aware Editing
77
+
78
+ If user provides character photos, pass them with `--persons` so they map to `type=person`:
79
+
80
+ ```bash
81
+ npx zerocut-cli video \
82
+ --prompt "Keep scene pacing, replace actor with provided character while preserving performance rhythm." \
83
+ --model seedance-2.0 \
84
+ --sourceVideo input.mp4 \
85
+ --persons actor_front.png,actor_side.png \
86
+ --withAudio \
87
+ --output edited_person.mp4
88
+ ```
89
+
90
+ ## Stitch Multiple Videos
91
+
92
+ Use ffmpeg command path for deterministic stitching.
93
+
94
+ Create concat list:
95
+
96
+ ```bash
97
+ printf "file 'clip1.mp4'\nfile 'clip2.mp4'\nfile 'clip3.mp4'\n" > concat.txt
98
+ ```
99
+
100
+ Concatenate:
101
+
102
+ ```bash
103
+ npx zerocut-cli ffmpeg --args -f concat -safe 0 -i concat.txt -c copy merged.mp4 --resources concat.txt clip1.mp4 clip2.mp4 clip3.mp4
104
+ ```
105
+
106
+ ## Hard Rules
107
+
108
+ - Never use non-CLI tool calls in this skill.
109
+ - Do not use unsupported video parameters.
110
+ - Keep edits faithful to user instructions and preserve continuity.
111
+ - If a command returns `Not enough credits`, stop immediately and ask user to recharge before continuing.
@@ -0,0 +1,140 @@
1
+ ---
2
+ name: one-click-video
3
+ description: Use this skill when the user wants to produce a complete short video from a topic with fast CLI-driven workflow: scene planning, storyboard creation, scene video generation, optional background music, and final ffmpeg composition.
4
+ homepage: "https://github.com/liubei-ai/zerocut-cli"
5
+ source: "https://github.com/liubei-ai/zerocut-cli"
6
+ requires_binaries:
7
+ - "zerocut-cli"
8
+ - "npx"
9
+ ---
10
+
11
+ # One-Click Video
12
+
13
+ ## Purpose
14
+
15
+ Create a deliverable final video by orchestrating `zerocut-cli` commands only.
16
+
17
+ ## Runtime Requirements
18
+
19
+ - Use CLI commands only, never MCP tool names.
20
+ - Ensure `zerocut-cli` is available:
21
+ - `pnpm dlx zerocut-cli help`
22
+ - `pnpm add -g zerocut-cli && zerocut-cli help`
23
+ - `npx zerocut-cli help`
24
+
25
+ ## Required Pre-Check
26
+
27
+ Run config check first:
28
+
29
+ ```bash
30
+ npx zerocut-cli config list
31
+ ```
32
+
33
+ If `apiKey` is missing, stop and request user OTT token:
34
+
35
+ ```bash
36
+ npx zerocut-cli config --ott <token> --region <cn|us>
37
+ ```
38
+
39
+ ## Video Parameter Contract
40
+
41
+ When generating scene videos, only use legal `video` command parameters:
42
+
43
+ - `--prompt <prompt>` required
44
+ - `--duration <seconds>`
45
+ - `--model <model>`
46
+ - `--sourceVideo <video>`
47
+ - `--seed <seed>`
48
+ - `--firstFrame <image>`
49
+ - `--lastFrame <image>`
50
+ - `--storyboard <image>`
51
+ - `--persons <persons>` comma-separated image paths/URLs, mapped to `type=person`
52
+ - `--refs <assets>`
53
+ - `--resolution <resolution>`
54
+ - `--aspectRatio <ratio>`
55
+ - `--withAudio`
56
+ - `--withBGM <withBGM>` `true|false`, default `true`
57
+ - `--optimizeCameraMotion`
58
+ - `--output <file>`
59
+
60
+ ## Output Naming Rules
61
+
62
+ - If user does not provide output path, generate meaningful names with 3-digit prefix:
63
+ - `001_storyboard_scene1.png`
64
+ - `002_scene1.mp4`
65
+ - `003_scene2.mp4`
66
+ - `004_bgm.mp3`
67
+ - `005_final.mp4`
68
+
69
+ ## Workflow
70
+
71
+ 1. Understand topic, goal, duration, platform orientation, and style.
72
+ 2. Split into 1-5 scenes with clear narrative progression.
73
+ 3. Create one storyboard image for each scene.
74
+ 4. Generate one video clip for each scene using only legal video parameters.
75
+ 5. Optionally generate one background music track with `music` command.
76
+ 6. Compose final video with `ffmpeg` command.
77
+
78
+ ## Scene Storyboard Step
79
+
80
+ Generate storyboard for each scene:
81
+
82
+ ```bash
83
+ npx zerocut-cli image --prompt "<scene storyboard prompt>" --model banana2 --aspectRatio 16:9 --resolution 1K --output 001_storyboard_scene1.png
84
+ ```
85
+
86
+ ## Scene Video Step
87
+
88
+ Use storyboard as `--storyboard`, optional character images via `--persons`, and optional extra references via `--refs`.
89
+
90
+ ```bash
91
+ npx zerocut-cli video \
92
+ --prompt "<scene video prompt>" \
93
+ --model seedance-2.0 \
94
+ --duration 12 \
95
+ --resolution 720p \
96
+ --aspectRatio 16:9 \
97
+ --storyboard 001_storyboard_scene1.png \
98
+ --persons actor_front.png,actor_side.png \
99
+ --refs prop_ref.png,env_ref.png \
100
+ --withAudio \
101
+ --withBGM true \
102
+ --output 002_scene1.mp4
103
+ ```
104
+
105
+ ## Background Music Step
106
+
107
+ Generate one BGM track when needed:
108
+
109
+ ```bash
110
+ npx zerocut-cli music --prompt "<bgm prompt>" --output 004_bgm.mp3
111
+ ```
112
+
113
+ ## Final Composition Step (ffmpeg only)
114
+
115
+ Create concat list:
116
+
117
+ ```bash
118
+ printf "file '002_scene1.mp4'\nfile '003_scene2.mp4'\n" > concat.txt
119
+ ```
120
+
121
+ Concatenate scene clips:
122
+
123
+ ```bash
124
+ npx zerocut-cli ffmpeg --args -f concat -safe 0 -i concat.txt -c copy 005_concat.mp4 --resources concat.txt 002_scene1.mp4 003_scene2.mp4
125
+ ```
126
+
127
+ Mix BGM with original video audio:
128
+
129
+ ```bash
130
+ npx zerocut-cli ffmpeg --args -i 005_concat.mp4 -i 004_bgm.mp3 -filter_complex "[1:a]volume=0.2[bgm];[0:a][bgm]amix=inputs=2:duration=first:dropout_transition=2[aout]" -map 0:v -map "[aout]" -c:v copy -c:a aac 005_final.mp4 --resources 005_concat.mp4 004_bgm.mp3
131
+ ```
132
+
133
+ ## Hard Rules
134
+
135
+ - Do not introduce non-CLI tool calls.
136
+ - Do not use parameters outside the legal `video` parameter contract.
137
+ - Keep single scene duration within model limits.
138
+ - Keep visual style consistent across all scenes.
139
+ - Keep role identity consistent when using `--persons`.
140
+ - Do not generate subtitles in this workflow.