blitzreels 0.1.1 → 0.1.3
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 +69 -1
- package/dist/index.js +2027 -38
- package/dist/index.js.map +1 -1
- package/package.json +2 -2
package/dist/index.js
CHANGED
|
@@ -1,21 +1,473 @@
|
|
|
1
1
|
#!/usr/bin/env node
|
|
2
2
|
import { spawn } from "node:child_process";
|
|
3
|
-
import { mkdir, readFile, writeFile } from "node:fs/promises";
|
|
3
|
+
import { mkdir, readFile, stat, writeFile } from "node:fs/promises";
|
|
4
4
|
import { homedir } from "node:os";
|
|
5
|
-
import { dirname, join, resolve } from "node:path";
|
|
5
|
+
import { basename, dirname, extname, join, resolve } from "node:path";
|
|
6
6
|
import { BlitzReelsClient, BlitzReelsError, } from "@blitzreels/sdk";
|
|
7
7
|
const CONFIG_PATH = join(homedir(), ".blitzreels", "config.json");
|
|
8
8
|
const KEYCHAIN_SERVICE = "BlitzReels CLI";
|
|
9
9
|
const KEYCHAIN_ACCOUNT = "api-key";
|
|
10
10
|
const DEFAULT_WAIT_INTERVAL_MS = 3000;
|
|
11
11
|
const DEFAULT_WAIT_TIMEOUT_MS = 20 * 60 * 1000;
|
|
12
|
+
const GLOBAL_FLAGS = ["api-key", "base-url", "json", "help"];
|
|
13
|
+
const TEXT_STYLE_FLAGS = [
|
|
14
|
+
"spec-json",
|
|
15
|
+
"font-family",
|
|
16
|
+
"font-size",
|
|
17
|
+
"font-weight",
|
|
18
|
+
"color",
|
|
19
|
+
"background-color",
|
|
20
|
+
"padding-x",
|
|
21
|
+
"padding-y",
|
|
22
|
+
"border-radius",
|
|
23
|
+
"top",
|
|
24
|
+
"left",
|
|
25
|
+
"text-align",
|
|
26
|
+
"text-shadow",
|
|
27
|
+
"no-background",
|
|
28
|
+
];
|
|
29
|
+
const CAPTION_WORD_SELECTOR_FLAGS = [
|
|
30
|
+
"word-id",
|
|
31
|
+
"word-ids",
|
|
32
|
+
"timeline-item-id",
|
|
33
|
+
"match-text",
|
|
34
|
+
"match-pattern",
|
|
35
|
+
];
|
|
36
|
+
const CAPTION_WORD_STYLE_FLAGS = [
|
|
37
|
+
"style-json",
|
|
38
|
+
"font-family",
|
|
39
|
+
"font-weight",
|
|
40
|
+
"font-style",
|
|
41
|
+
"color",
|
|
42
|
+
"active-color",
|
|
43
|
+
"background-color",
|
|
44
|
+
"active-animation",
|
|
45
|
+
"text-transform",
|
|
46
|
+
"text-shadow",
|
|
47
|
+
"glow-color",
|
|
48
|
+
"glow-blur-px",
|
|
49
|
+
"scale",
|
|
50
|
+
"offset-x",
|
|
51
|
+
"offset-y",
|
|
52
|
+
"rotation-deg",
|
|
53
|
+
"letter-spacing",
|
|
54
|
+
"opacity",
|
|
55
|
+
"clear-existing",
|
|
56
|
+
];
|
|
57
|
+
function commandSpec({ command, usage, flags = [], }) {
|
|
58
|
+
return { command, usage, flags };
|
|
59
|
+
}
|
|
60
|
+
const COMMAND_SPECS = [
|
|
61
|
+
commandSpec({
|
|
62
|
+
command: ["auth", "login"],
|
|
63
|
+
usage: "blitzreels auth login [--no-browser] [--timeout-ms 120000]",
|
|
64
|
+
flags: ["no-browser", "timeout-ms"],
|
|
65
|
+
}),
|
|
66
|
+
commandSpec({
|
|
67
|
+
command: ["auth", "set-key"],
|
|
68
|
+
usage: "blitzreels auth set-key --api-key KEY [--base-url URL]",
|
|
69
|
+
}),
|
|
70
|
+
commandSpec({
|
|
71
|
+
command: ["auth", "whoami"],
|
|
72
|
+
usage: "blitzreels auth whoami [--json]",
|
|
73
|
+
}),
|
|
74
|
+
commandSpec({
|
|
75
|
+
command: ["workspace", "settings", "get"],
|
|
76
|
+
usage: "blitzreels workspace settings get [--json]",
|
|
77
|
+
}),
|
|
78
|
+
commandSpec({
|
|
79
|
+
command: ["workspace", "settings", "update"],
|
|
80
|
+
usage: "blitzreels workspace settings update [--name NAME] [--description TEXT|null] [--protected-words foo,bar] [--icon-storage-key KEY|null] [--aspect-ratio 9:16] [--orientation portrait] [--resolution 1080p] [--frame-rate 30] [--auto-manage-broll true] [--caption-theme-id ID|null]",
|
|
81
|
+
flags: [
|
|
82
|
+
"name",
|
|
83
|
+
"description",
|
|
84
|
+
"protected-words",
|
|
85
|
+
"icon-storage-key",
|
|
86
|
+
"aspect-ratio",
|
|
87
|
+
"orientation",
|
|
88
|
+
"resolution",
|
|
89
|
+
"frame-rate",
|
|
90
|
+
"auto-manage-broll",
|
|
91
|
+
"caption-theme-id",
|
|
92
|
+
],
|
|
93
|
+
}),
|
|
94
|
+
commandSpec({
|
|
95
|
+
command: ["projects", "list"],
|
|
96
|
+
usage: "blitzreels projects list [--search QUERY] [--status active|archived|all] [--limit 50] [--offset 0] [--cursor CURSOR] [--json]",
|
|
97
|
+
flags: ["search", "status", "limit", "offset", "cursor"],
|
|
98
|
+
}),
|
|
99
|
+
commandSpec({
|
|
100
|
+
command: ["projects", "create"],
|
|
101
|
+
usage: "blitzreels projects create --name NAME [--description TEXT] [--aspect-ratio 9:16] [--project-type video|carousel] [--carousel-settings-json JSON]",
|
|
102
|
+
flags: [
|
|
103
|
+
"name",
|
|
104
|
+
"description",
|
|
105
|
+
"aspect-ratio",
|
|
106
|
+
"project-type",
|
|
107
|
+
"carousel-settings-json",
|
|
108
|
+
],
|
|
109
|
+
}),
|
|
110
|
+
commandSpec({
|
|
111
|
+
command: ["projects", "get"],
|
|
112
|
+
usage: "blitzreels projects get --project-id ID [--json]",
|
|
113
|
+
flags: ["project-id"],
|
|
114
|
+
}),
|
|
115
|
+
commandSpec({
|
|
116
|
+
command: ["projects", "rename"],
|
|
117
|
+
usage: "blitzreels projects rename --project-id ID --name NAME",
|
|
118
|
+
flags: ["project-id", "name"],
|
|
119
|
+
}),
|
|
120
|
+
commandSpec({
|
|
121
|
+
command: ["projects", "update"],
|
|
122
|
+
usage: "blitzreels projects update --project-id ID [--name NAME] [--description TEXT|null]",
|
|
123
|
+
flags: ["project-id", "name", "description"],
|
|
124
|
+
}),
|
|
125
|
+
commandSpec({
|
|
126
|
+
command: ["projects", "delete"],
|
|
127
|
+
usage: "blitzreels projects delete --project-id ID --confirm-delete",
|
|
128
|
+
flags: ["project-id", "confirm-delete"],
|
|
129
|
+
}),
|
|
130
|
+
commandSpec({
|
|
131
|
+
command: ["media", "list"],
|
|
132
|
+
usage: "blitzreels media list [--folder-id ID] [--asset-type video|audio|image|document] [--search QUERY] [--limit 100] [--offset 0] [--json]",
|
|
133
|
+
flags: ["folder-id", "asset-type", "search", "limit", "offset"],
|
|
134
|
+
}),
|
|
135
|
+
commandSpec({
|
|
136
|
+
command: ["media", "get"],
|
|
137
|
+
usage: "blitzreels media get --asset-id ID [--json]",
|
|
138
|
+
flags: ["asset-id"],
|
|
139
|
+
}),
|
|
140
|
+
commandSpec({
|
|
141
|
+
command: ["media", "delete"],
|
|
142
|
+
usage: "blitzreels media delete --asset-id ID --confirm-delete",
|
|
143
|
+
flags: ["asset-id", "confirm-delete"],
|
|
144
|
+
}),
|
|
145
|
+
commandSpec({
|
|
146
|
+
command: ["media", "upload"],
|
|
147
|
+
usage: "blitzreels media upload --file ./video.mp4 [--name NAME] [--content-type video/mp4] [--auto-process true] [--auto-transcribe true] [--auto-describe true] [--auto-suggest-shorts true]",
|
|
148
|
+
flags: [
|
|
149
|
+
"file",
|
|
150
|
+
"name",
|
|
151
|
+
"content-type",
|
|
152
|
+
"auto-process",
|
|
153
|
+
"auto-transcribe",
|
|
154
|
+
"auto-describe",
|
|
155
|
+
"auto-suggest-shorts",
|
|
156
|
+
],
|
|
157
|
+
}),
|
|
158
|
+
commandSpec({
|
|
159
|
+
command: ["media", "import-youtube"],
|
|
160
|
+
usage: "blitzreels media import-youtube --url URL [--folder-id ID|root] [--quality 1080p] [--fps 30] [--audio-language fr|null]",
|
|
161
|
+
flags: ["url", "folder-id", "quality", "fps", "audio-language"],
|
|
162
|
+
}),
|
|
163
|
+
commandSpec({
|
|
164
|
+
command: ["media", "rename"],
|
|
165
|
+
usage: "blitzreels media rename --asset-id ID --name NAME",
|
|
166
|
+
flags: ["asset-id", "name"],
|
|
167
|
+
}),
|
|
168
|
+
commandSpec({
|
|
169
|
+
command: ["media", "update"],
|
|
170
|
+
usage: "blitzreels media update --asset-id ID [--name NAME] [--description TEXT|null] [--folder-id ID|root] [--allow-broll true]",
|
|
171
|
+
flags: ["asset-id", "name", "description", "folder-id", "allow-broll"],
|
|
172
|
+
}),
|
|
173
|
+
commandSpec({
|
|
174
|
+
command: ["media", "move"],
|
|
175
|
+
usage: "blitzreels media move --asset-id ID|--asset-ids ID1,ID2 --target-folder-id ID|root",
|
|
176
|
+
flags: ["asset-id", "asset-ids", "target-folder-id"],
|
|
177
|
+
}),
|
|
178
|
+
commandSpec({
|
|
179
|
+
command: ["media", "attach"],
|
|
180
|
+
usage: "blitzreels media attach --project-id ID --asset-id ID [--at 0] [--duration 4] [--layer-index 2] [--allow-duplicate true] [--position-preset center] [--position-x 0] [--position-y 0] [--width-px 320] [--height-px 180] [--scale 1.2] [--opacity 0.9] [--animation-preset popIn]",
|
|
181
|
+
flags: [
|
|
182
|
+
"project-id",
|
|
183
|
+
"asset-id",
|
|
184
|
+
"at",
|
|
185
|
+
"duration",
|
|
186
|
+
"layer-index",
|
|
187
|
+
"allow-duplicate",
|
|
188
|
+
"position-preset",
|
|
189
|
+
"position-x",
|
|
190
|
+
"position-y",
|
|
191
|
+
"width-px",
|
|
192
|
+
"height-px",
|
|
193
|
+
"scale",
|
|
194
|
+
"opacity",
|
|
195
|
+
"animation-preset",
|
|
196
|
+
],
|
|
197
|
+
}),
|
|
198
|
+
commandSpec({
|
|
199
|
+
command: ["media", "folders", "list"],
|
|
200
|
+
usage: "blitzreels media folders list [--parent-folder-id ID] [--json]",
|
|
201
|
+
flags: ["parent-folder-id"],
|
|
202
|
+
}),
|
|
203
|
+
commandSpec({
|
|
204
|
+
command: ["media", "folders", "create"],
|
|
205
|
+
usage: "blitzreels media folders create --name NAME [--parent-folder-id ID] [--description TEXT|null] [--icon-type video]",
|
|
206
|
+
flags: ["name", "parent-folder-id", "description", "icon-type"],
|
|
207
|
+
}),
|
|
208
|
+
commandSpec({
|
|
209
|
+
command: ["media", "folders", "rename"],
|
|
210
|
+
usage: "blitzreels media folders rename --folder-id ID --name NAME",
|
|
211
|
+
flags: ["folder-id", "name"],
|
|
212
|
+
}),
|
|
213
|
+
commandSpec({
|
|
214
|
+
command: ["media", "folders", "update"],
|
|
215
|
+
usage: "blitzreels media folders update --folder-id ID [--name NAME] [--parent-folder-id ID|root] [--description TEXT|null] [--icon-type video]",
|
|
216
|
+
flags: [
|
|
217
|
+
"folder-id",
|
|
218
|
+
"name",
|
|
219
|
+
"parent-folder-id",
|
|
220
|
+
"description",
|
|
221
|
+
"icon-type",
|
|
222
|
+
],
|
|
223
|
+
}),
|
|
224
|
+
commandSpec({
|
|
225
|
+
command: ["media", "folders", "delete"],
|
|
226
|
+
usage: "blitzreels media folders delete --folder-id ID --confirm-delete [--cascade true]",
|
|
227
|
+
flags: ["folder-id", "confirm-delete", "cascade"],
|
|
228
|
+
}),
|
|
229
|
+
commandSpec({
|
|
230
|
+
command: ["project", "inspect"],
|
|
231
|
+
usage: "blitzreels project inspect --project-id ID [--mode full] [--json]",
|
|
232
|
+
flags: ["project-id", "mode"],
|
|
233
|
+
}),
|
|
234
|
+
commandSpec({
|
|
235
|
+
command: ["project", "timeline"],
|
|
236
|
+
usage: "blitzreels project timeline --project-id ID [--json]",
|
|
237
|
+
flags: ["project-id"],
|
|
238
|
+
}),
|
|
239
|
+
commandSpec({
|
|
240
|
+
command: ["project", "snapshot"],
|
|
241
|
+
usage: "blitzreels project snapshot --project-id ID --at 00:03 [--target-width 608] [--image-format jpeg|png|webp] [--json]",
|
|
242
|
+
flags: ["project-id", "at", "target-width", "image-format"],
|
|
243
|
+
}),
|
|
244
|
+
commandSpec({
|
|
245
|
+
command: ["project", "snapshots"],
|
|
246
|
+
usage: "blitzreels project snapshots --project-id ID --at 00:01,00:03 [--target-width 608] [--image-format jpeg|png|webp] [--json]",
|
|
247
|
+
flags: ["project-id", "at", "target-width", "image-format"],
|
|
248
|
+
}),
|
|
249
|
+
commandSpec({
|
|
250
|
+
command: ["timeline", "transform"],
|
|
251
|
+
usage: "blitzreels timeline transform --project-id ID --timeline-item-id ID [--position-x 0] [--position-y 0] [--width-px 320|null] [--height-px 180|null] [--keep-aspect-ratio true]",
|
|
252
|
+
flags: [
|
|
253
|
+
"project-id",
|
|
254
|
+
"timeline-item-id",
|
|
255
|
+
"position-x",
|
|
256
|
+
"position-y",
|
|
257
|
+
"width-px",
|
|
258
|
+
"height-px",
|
|
259
|
+
"keep-aspect-ratio",
|
|
260
|
+
],
|
|
261
|
+
}),
|
|
262
|
+
commandSpec({
|
|
263
|
+
command: ["timeline", "move"],
|
|
264
|
+
usage: "blitzreels timeline move --project-id ID --timeline-item-id ID --at 00:03 [--layer-index 2]",
|
|
265
|
+
flags: ["project-id", "timeline-item-id", "at", "layer-index"],
|
|
266
|
+
}),
|
|
267
|
+
commandSpec({
|
|
268
|
+
command: ["timeline", "delete"],
|
|
269
|
+
usage: "blitzreels timeline delete --project-id ID --timeline-item-id ID|--timeline-item-ids ID1,ID2 [--auto-pack true]",
|
|
270
|
+
flags: ["project-id", "timeline-item-id", "timeline-item-ids", "auto-pack"],
|
|
271
|
+
}),
|
|
272
|
+
commandSpec({
|
|
273
|
+
command: ["text", "add"],
|
|
274
|
+
usage: "blitzreels text add --project-id ID --text \"Hook\" [--name NAME] [--at 0] [--duration 4] [--layer-index 2] [--spec-json JSON] [--font-family Inter] [--font-size 64] [--font-weight 700] [--color '#fff'] [--background-color '#000'] [--padding-x 32] [--padding-y 16] [--border-radius 24] [--top '18%'] [--left '50%'] [--text-align center] [--text-shadow '0 6px 18px #000'] [--no-background]",
|
|
275
|
+
flags: [
|
|
276
|
+
"project-id",
|
|
277
|
+
"text",
|
|
278
|
+
"name",
|
|
279
|
+
"at",
|
|
280
|
+
"duration",
|
|
281
|
+
"layer-index",
|
|
282
|
+
...TEXT_STYLE_FLAGS,
|
|
283
|
+
],
|
|
284
|
+
}),
|
|
285
|
+
commandSpec({
|
|
286
|
+
command: ["text", "list"],
|
|
287
|
+
usage: "blitzreels text list --project-id ID [--json]",
|
|
288
|
+
flags: ["project-id"],
|
|
289
|
+
}),
|
|
290
|
+
commandSpec({
|
|
291
|
+
command: ["text", "update"],
|
|
292
|
+
usage: "blitzreels text update --project-id ID --content-item-id ID [--text \"New hook\"] [--name NAME] [--spec-json JSON] [--font-family Pacifico] [--font-size 82] [--font-weight 700] [--color '#17FFA6'] [--background-color '#051914'] [--padding-x 32] [--padding-y 16] [--border-radius 24] [--top '18%'] [--left '50%'] [--text-align center] [--text-shadow '0 6px 18px #000'] [--no-background]",
|
|
293
|
+
flags: [
|
|
294
|
+
"project-id",
|
|
295
|
+
"content-item-id",
|
|
296
|
+
"text",
|
|
297
|
+
"name",
|
|
298
|
+
...TEXT_STYLE_FLAGS,
|
|
299
|
+
],
|
|
300
|
+
}),
|
|
301
|
+
commandSpec({
|
|
302
|
+
command: ["text", "remove"],
|
|
303
|
+
usage: "blitzreels text remove --project-id ID --content-item-id ID",
|
|
304
|
+
flags: ["project-id", "content-item-id"],
|
|
305
|
+
}),
|
|
306
|
+
commandSpec({
|
|
307
|
+
command: ["audio", "add"],
|
|
308
|
+
usage: "blitzreels audio add --project-id ID --asset-id ID [--at 0] [--duration 30] [--volume 0.6] [--fade-in 0.5] [--fade-out 0.5] [--loop true]",
|
|
309
|
+
flags: [
|
|
310
|
+
"project-id",
|
|
311
|
+
"asset-id",
|
|
312
|
+
"at",
|
|
313
|
+
"duration",
|
|
314
|
+
"volume",
|
|
315
|
+
"fade-in",
|
|
316
|
+
"fade-out",
|
|
317
|
+
"loop",
|
|
318
|
+
],
|
|
319
|
+
}),
|
|
320
|
+
commandSpec({
|
|
321
|
+
command: ["silence", "plan"],
|
|
322
|
+
usage: "blitzreels silence plan --project-id ID --timeline-item-id ID [--padding-ms 120] [--silence-threshold-db -35] [--min-silence-duration 0.25] [--remove-blips-max-duration 0.12] [--use-caption-validation true] [--json]",
|
|
323
|
+
flags: [
|
|
324
|
+
"project-id",
|
|
325
|
+
"timeline-item-id",
|
|
326
|
+
"padding-ms",
|
|
327
|
+
"silence-threshold-db",
|
|
328
|
+
"min-silence-duration",
|
|
329
|
+
"remove-blips-max-duration",
|
|
330
|
+
"use-caption-validation",
|
|
331
|
+
],
|
|
332
|
+
}),
|
|
333
|
+
commandSpec({
|
|
334
|
+
command: ["silence", "apply"],
|
|
335
|
+
usage: "blitzreels silence apply --project-id ID --timeline-item-id ID --confirm-preview [--padding-ms 120] [--silence-threshold-db -35] [--min-silence-duration 0.25] [--remove-blips-max-duration 0.12] [--use-caption-validation true] [--delete-silent-clips true] [--manual-segment-overrides-json JSON]",
|
|
336
|
+
flags: [
|
|
337
|
+
"project-id",
|
|
338
|
+
"timeline-item-id",
|
|
339
|
+
"confirm-preview",
|
|
340
|
+
"padding-ms",
|
|
341
|
+
"silence-threshold-db",
|
|
342
|
+
"min-silence-duration",
|
|
343
|
+
"remove-blips-max-duration",
|
|
344
|
+
"use-caption-validation",
|
|
345
|
+
"delete-silent-clips",
|
|
346
|
+
"manual-segment-overrides-json",
|
|
347
|
+
],
|
|
348
|
+
}),
|
|
349
|
+
commandSpec({
|
|
350
|
+
command: ["mistakes", "plan"],
|
|
351
|
+
usage: "blitzreels mistakes plan --project-id ID --timeline-item-id ID [--json]",
|
|
352
|
+
flags: ["project-id", "timeline-item-id"],
|
|
353
|
+
}),
|
|
354
|
+
commandSpec({
|
|
355
|
+
command: ["mistakes", "apply"],
|
|
356
|
+
usage: "blitzreels mistakes apply --project-id ID --timeline-item-id ID --confirm-preview [--plan-json JSON] [--manual-verdicts-json JSON] [--delete-mistakes true]",
|
|
357
|
+
flags: [
|
|
358
|
+
"project-id",
|
|
359
|
+
"timeline-item-id",
|
|
360
|
+
"confirm-preview",
|
|
361
|
+
"plan-json",
|
|
362
|
+
"manual-verdicts-json",
|
|
363
|
+
"delete-mistakes",
|
|
364
|
+
],
|
|
365
|
+
}),
|
|
366
|
+
commandSpec({
|
|
367
|
+
command: ["captions", "words", "list"],
|
|
368
|
+
usage: "blitzreels captions words list --project-id ID [--timeline-item-id ID] [--match-text TEXT] [--limit 100] [--offset 0]",
|
|
369
|
+
flags: ["project-id", "timeline-item-id", "match-text", "limit", "offset"],
|
|
370
|
+
}),
|
|
371
|
+
commandSpec({
|
|
372
|
+
command: ["captions", "words", "text"],
|
|
373
|
+
usage: 'blitzreels captions words text --project-id ID --word-id ID --text "Fix"|--new-text "Fix"',
|
|
374
|
+
flags: ["project-id", "word-id", "text", "new-text"],
|
|
375
|
+
}),
|
|
376
|
+
commandSpec({
|
|
377
|
+
command: ["captions", "words", "batch-text"],
|
|
378
|
+
usage: 'blitzreels captions words batch-text --project-id ID --updates-json \'[{"word_id":"...","new_text":"..."}]\'',
|
|
379
|
+
flags: ["project-id", "updates-json"],
|
|
380
|
+
}),
|
|
381
|
+
commandSpec({
|
|
382
|
+
command: ["captions", "words", "style"],
|
|
383
|
+
usage: "blitzreels captions words style --project-id ID [--word-id ID|--word-ids IDs|--timeline-item-id ID|--match-text TEXT|--match-pattern numbers|caps|exclamations] [--style-json JSON|null] [--font-family Pacifico] [--font-weight 700] [--font-style italic] [--color '#17FFA6'] [--active-color '#fff'] [--background-color '#051914'] [--active-animation pop] [--text-transform uppercase] [--text-shadow '0 6px 18px #000'] [--glow-color '#17FFA6'] [--glow-blur-px 18] [--scale 1.45] [--offset-x 0] [--offset-y -8] [--rotation-deg -2] [--letter-spacing 1] [--opacity 1] [--clear-existing true]",
|
|
384
|
+
flags: [
|
|
385
|
+
"project-id",
|
|
386
|
+
...CAPTION_WORD_SELECTOR_FLAGS,
|
|
387
|
+
...CAPTION_WORD_STYLE_FLAGS,
|
|
388
|
+
],
|
|
389
|
+
}),
|
|
390
|
+
commandSpec({
|
|
391
|
+
command: ["captions", "words", "emphasis"],
|
|
392
|
+
usage: "blitzreels captions words emphasis --project-id ID [--word-id ID|--word-ids IDs|--timeline-item-id ID|--match-text TEXT|--match-pattern numbers|caps|exclamations] [--emphasis true]",
|
|
393
|
+
flags: ["project-id", ...CAPTION_WORD_SELECTOR_FLAGS, "emphasis"],
|
|
394
|
+
}),
|
|
395
|
+
commandSpec({
|
|
396
|
+
command: ["captions", "words", "delete"],
|
|
397
|
+
usage: "blitzreels captions words delete --project-id ID --word-id ID|--word-ids ID1,ID2",
|
|
398
|
+
flags: ["project-id", "word-id", "word-ids"],
|
|
399
|
+
}),
|
|
400
|
+
commandSpec({
|
|
401
|
+
command: ["captions", "words", "merge"],
|
|
402
|
+
usage: 'blitzreels captions words merge --project-id ID --word-ids ID1,ID2 [--text "Merged"]',
|
|
403
|
+
flags: ["project-id", "word-ids", "text"],
|
|
404
|
+
}),
|
|
405
|
+
commandSpec({
|
|
406
|
+
command: ["captions", "words", "split"],
|
|
407
|
+
usage: 'blitzreels captions words split --project-id ID --word-id ID --words "foo,bar"',
|
|
408
|
+
flags: ["project-id", "word-id", "words"],
|
|
409
|
+
}),
|
|
410
|
+
commandSpec({
|
|
411
|
+
command: ["captions", "themes", "list"],
|
|
412
|
+
usage: "blitzreels captions themes list [--json]",
|
|
413
|
+
}),
|
|
414
|
+
commandSpec({
|
|
415
|
+
command: ["captions", "theme", "set"],
|
|
416
|
+
usage: "blitzreels captions theme set --project-id ID --theme-id ID",
|
|
417
|
+
flags: ["project-id", "theme-id"],
|
|
418
|
+
}),
|
|
419
|
+
commandSpec({
|
|
420
|
+
command: ["captions", "theme", "set-default"],
|
|
421
|
+
usage: "blitzreels captions theme set-default --theme-id ID",
|
|
422
|
+
flags: ["theme-id"],
|
|
423
|
+
}),
|
|
424
|
+
commandSpec({
|
|
425
|
+
command: ["exports", "start"],
|
|
426
|
+
usage: "blitzreels exports start --project-id ID [--resolution 1080p] [--format mp4] [--include-captions true]",
|
|
427
|
+
flags: ["project-id", "resolution", "format", "include-captions"],
|
|
428
|
+
}),
|
|
429
|
+
commandSpec({
|
|
430
|
+
command: ["exports", "wait"],
|
|
431
|
+
usage: "blitzreels exports wait --export-id ID [--interval-ms 3000] [--timeout-ms 1200000] [--json]",
|
|
432
|
+
flags: ["export-id", "interval-ms", "timeout-ms"],
|
|
433
|
+
}),
|
|
434
|
+
commandSpec({
|
|
435
|
+
command: ["exports", "download"],
|
|
436
|
+
usage: "blitzreels exports download --export-id ID --output ./short.mp4",
|
|
437
|
+
flags: ["export-id", "output"],
|
|
438
|
+
}),
|
|
439
|
+
commandSpec({
|
|
440
|
+
command: ["api", "docs"],
|
|
441
|
+
usage: "blitzreels api docs",
|
|
442
|
+
}),
|
|
443
|
+
commandSpec({
|
|
444
|
+
command: ["api", "spec"],
|
|
445
|
+
usage: "blitzreels api spec",
|
|
446
|
+
}),
|
|
447
|
+
];
|
|
448
|
+
class CliUsageError extends Error {
|
|
449
|
+
code;
|
|
450
|
+
suggestions;
|
|
451
|
+
usage;
|
|
452
|
+
constructor({ code, message, suggestions = [], usage, }) {
|
|
453
|
+
super(message);
|
|
454
|
+
this.name = "CliUsageError";
|
|
455
|
+
this.code = code;
|
|
456
|
+
this.suggestions = suggestions;
|
|
457
|
+
this.usage = usage;
|
|
458
|
+
}
|
|
459
|
+
}
|
|
12
460
|
async function main() {
|
|
13
461
|
const parsed = parseArgs({ argv: process.argv.slice(2) });
|
|
14
|
-
if (parsed.command.length === 0
|
|
15
|
-
getBooleanFlag({ flags: parsed.flags, name: "help" })) {
|
|
462
|
+
if (parsed.command.length === 0) {
|
|
16
463
|
printHelp();
|
|
17
464
|
return;
|
|
18
465
|
}
|
|
466
|
+
if (getBooleanFlag({ flags: parsed.flags, name: "help" })) {
|
|
467
|
+
printCommandHelpOrThrow({ command: parsed.command });
|
|
468
|
+
return;
|
|
469
|
+
}
|
|
470
|
+
validateParsedArgs({ parsed });
|
|
19
471
|
const config = await loadConfig();
|
|
20
472
|
const apiKey = getStringFlag({ flags: parsed.flags, name: "api-key" }) ??
|
|
21
473
|
process.env.BLITZREELS_API_KEY ??
|
|
@@ -83,6 +535,484 @@ async function runCommand({ context }) {
|
|
|
83
535
|
return;
|
|
84
536
|
}
|
|
85
537
|
requireApiKey({ apiKey: context.config.apiKey });
|
|
538
|
+
if (group === "workspace" && action === "settings" && subaction === "get") {
|
|
539
|
+
const result = await context.client.workspace.getSettings();
|
|
540
|
+
printResult({
|
|
541
|
+
json: context.json,
|
|
542
|
+
value: result,
|
|
543
|
+
human: [
|
|
544
|
+
`Workspace: ${result.settings.name}`,
|
|
545
|
+
`Description: ${result.settings.description ?? ""}`,
|
|
546
|
+
`Protected words: ${result.settings.protected_words.join(", ") || "-"}`,
|
|
547
|
+
`Defaults: ${JSON.stringify(result.settings.defaults)}`,
|
|
548
|
+
],
|
|
549
|
+
});
|
|
550
|
+
return;
|
|
551
|
+
}
|
|
552
|
+
if (group === "workspace" &&
|
|
553
|
+
action === "settings" &&
|
|
554
|
+
subaction === "update") {
|
|
555
|
+
const result = await context.client.workspace.updateSettings({
|
|
556
|
+
name: getStringFlag({ flags: context.args.flags, name: "name" }),
|
|
557
|
+
description: parseNullableStringFlag({
|
|
558
|
+
flags: context.args.flags,
|
|
559
|
+
name: "description",
|
|
560
|
+
}),
|
|
561
|
+
protectedWords: parseCsvFlag({
|
|
562
|
+
flags: context.args.flags,
|
|
563
|
+
name: "protected-words",
|
|
564
|
+
}),
|
|
565
|
+
iconStorageKey: parseNullableStringFlag({
|
|
566
|
+
flags: context.args.flags,
|
|
567
|
+
name: "icon-storage-key",
|
|
568
|
+
}),
|
|
569
|
+
defaults: buildWorkspaceDefaults({ flags: context.args.flags }),
|
|
570
|
+
});
|
|
571
|
+
printResult({
|
|
572
|
+
json: context.json,
|
|
573
|
+
value: result,
|
|
574
|
+
human: [`Workspace settings updated: ${result.settings.name}`],
|
|
575
|
+
});
|
|
576
|
+
return;
|
|
577
|
+
}
|
|
578
|
+
if (group === "projects" && action === "list") {
|
|
579
|
+
const result = await context.client.projects.list({
|
|
580
|
+
limit: getNumberFlag({ flags: context.args.flags, name: "limit" }),
|
|
581
|
+
cursor: getStringFlag({ flags: context.args.flags, name: "cursor" }),
|
|
582
|
+
offset: getNumberFlag({ flags: context.args.flags, name: "offset" }),
|
|
583
|
+
search: getStringFlag({ flags: context.args.flags, name: "search" }),
|
|
584
|
+
status: parseProjectStatus({
|
|
585
|
+
value: getStringFlag({ flags: context.args.flags, name: "status" }),
|
|
586
|
+
}),
|
|
587
|
+
});
|
|
588
|
+
printResult({
|
|
589
|
+
json: context.json,
|
|
590
|
+
value: result,
|
|
591
|
+
human: result.projects.map((project) => `${project.id} ${project.status} ${project.name} (${project.aspect_ratio ?? "-"})`),
|
|
592
|
+
});
|
|
593
|
+
return;
|
|
594
|
+
}
|
|
595
|
+
if (group === "projects" && action === "create") {
|
|
596
|
+
const result = await context.client.projects.create({
|
|
597
|
+
name: requireStringFlag({ flags: context.args.flags, name: "name" }),
|
|
598
|
+
description: getStringFlag({
|
|
599
|
+
flags: context.args.flags,
|
|
600
|
+
name: "description",
|
|
601
|
+
}),
|
|
602
|
+
aspectRatio: parseAspectRatio({
|
|
603
|
+
value: getStringFlag({
|
|
604
|
+
flags: context.args.flags,
|
|
605
|
+
name: "aspect-ratio",
|
|
606
|
+
}),
|
|
607
|
+
}),
|
|
608
|
+
projectType: parseProjectType({
|
|
609
|
+
value: getStringFlag({
|
|
610
|
+
flags: context.args.flags,
|
|
611
|
+
name: "project-type",
|
|
612
|
+
}),
|
|
613
|
+
}),
|
|
614
|
+
carouselSettings: parseJsonObjectFlag({
|
|
615
|
+
flags: context.args.flags,
|
|
616
|
+
name: "carousel-settings-json",
|
|
617
|
+
}),
|
|
618
|
+
});
|
|
619
|
+
printResult({
|
|
620
|
+
json: context.json,
|
|
621
|
+
value: result,
|
|
622
|
+
human: [`Project created: ${result.id} ${result.name}`],
|
|
623
|
+
});
|
|
624
|
+
return;
|
|
625
|
+
}
|
|
626
|
+
if (group === "projects" && action === "get") {
|
|
627
|
+
const result = await context.client.projects.get({
|
|
628
|
+
projectId: requireStringFlag({
|
|
629
|
+
flags: context.args.flags,
|
|
630
|
+
name: "project-id",
|
|
631
|
+
}),
|
|
632
|
+
});
|
|
633
|
+
printResult({
|
|
634
|
+
json: context.json,
|
|
635
|
+
value: result,
|
|
636
|
+
human: [
|
|
637
|
+
`${result.id} ${result.status} ${result.name}`,
|
|
638
|
+
`Description: ${result.description ?? ""}`,
|
|
639
|
+
],
|
|
640
|
+
});
|
|
641
|
+
return;
|
|
642
|
+
}
|
|
643
|
+
if (group === "projects" && action === "rename") {
|
|
644
|
+
const result = await context.client.projects.update({
|
|
645
|
+
projectId: requireStringFlag({
|
|
646
|
+
flags: context.args.flags,
|
|
647
|
+
name: "project-id",
|
|
648
|
+
}),
|
|
649
|
+
name: requireStringFlag({ flags: context.args.flags, name: "name" }),
|
|
650
|
+
description: undefined,
|
|
651
|
+
});
|
|
652
|
+
printResult({
|
|
653
|
+
json: context.json,
|
|
654
|
+
value: result,
|
|
655
|
+
human: [`Project renamed: ${result.id} ${result.name}`],
|
|
656
|
+
});
|
|
657
|
+
return;
|
|
658
|
+
}
|
|
659
|
+
if (group === "projects" && action === "update") {
|
|
660
|
+
const result = await context.client.projects.update({
|
|
661
|
+
projectId: requireStringFlag({
|
|
662
|
+
flags: context.args.flags,
|
|
663
|
+
name: "project-id",
|
|
664
|
+
}),
|
|
665
|
+
name: getStringFlag({ flags: context.args.flags, name: "name" }),
|
|
666
|
+
description: parseNullableStringFlag({
|
|
667
|
+
flags: context.args.flags,
|
|
668
|
+
name: "description",
|
|
669
|
+
}),
|
|
670
|
+
});
|
|
671
|
+
printResult({
|
|
672
|
+
json: context.json,
|
|
673
|
+
value: result,
|
|
674
|
+
human: [`Project updated: ${result.id} ${result.name}`],
|
|
675
|
+
});
|
|
676
|
+
return;
|
|
677
|
+
}
|
|
678
|
+
if (group === "projects" && action === "delete") {
|
|
679
|
+
requireConfirmDelete({ flags: context.args.flags });
|
|
680
|
+
const result = await context.client.projects.delete({
|
|
681
|
+
projectId: requireStringFlag({
|
|
682
|
+
flags: context.args.flags,
|
|
683
|
+
name: "project-id",
|
|
684
|
+
}),
|
|
685
|
+
});
|
|
686
|
+
printResult({
|
|
687
|
+
json: context.json,
|
|
688
|
+
value: result,
|
|
689
|
+
human: ["Project deleted."],
|
|
690
|
+
});
|
|
691
|
+
return;
|
|
692
|
+
}
|
|
693
|
+
if (group === "media" && action === "list") {
|
|
694
|
+
const result = await context.client.media.list({
|
|
695
|
+
folderId: getStringFlag({ flags: context.args.flags, name: "folder-id" }),
|
|
696
|
+
assetType: parseAssetType({
|
|
697
|
+
value: getStringFlag({ flags: context.args.flags, name: "asset-type" }),
|
|
698
|
+
}),
|
|
699
|
+
search: getStringFlag({ flags: context.args.flags, name: "search" }),
|
|
700
|
+
limit: getNumberFlag({ flags: context.args.flags, name: "limit" }),
|
|
701
|
+
offset: getNumberFlag({ flags: context.args.flags, name: "offset" }),
|
|
702
|
+
});
|
|
703
|
+
printResult({
|
|
704
|
+
json: context.json,
|
|
705
|
+
value: result,
|
|
706
|
+
human: result.assets.map((asset) => `${asset.id} ${asset.assetType} ${asset.name ?? ""} ${asset.durationSeconds ?? "-"}s`),
|
|
707
|
+
});
|
|
708
|
+
return;
|
|
709
|
+
}
|
|
710
|
+
if (group === "media" && action === "get") {
|
|
711
|
+
const result = await context.client.media.get({
|
|
712
|
+
assetId: requireStringFlag({
|
|
713
|
+
flags: context.args.flags,
|
|
714
|
+
name: "asset-id",
|
|
715
|
+
}),
|
|
716
|
+
});
|
|
717
|
+
printResult({
|
|
718
|
+
json: context.json,
|
|
719
|
+
value: result,
|
|
720
|
+
human: [
|
|
721
|
+
`${result.asset.id} ${result.asset.assetType} ${result.asset.name}`,
|
|
722
|
+
`URL: ${result.asset.fileUrl ?? ""}`,
|
|
723
|
+
],
|
|
724
|
+
});
|
|
725
|
+
return;
|
|
726
|
+
}
|
|
727
|
+
if (group === "media" && action === "delete") {
|
|
728
|
+
requireConfirmDelete({ flags: context.args.flags });
|
|
729
|
+
const result = await context.client.media.delete({
|
|
730
|
+
assetId: requireStringFlag({
|
|
731
|
+
flags: context.args.flags,
|
|
732
|
+
name: "asset-id",
|
|
733
|
+
}),
|
|
734
|
+
});
|
|
735
|
+
printResult({
|
|
736
|
+
json: context.json,
|
|
737
|
+
value: result,
|
|
738
|
+
human: ["Media asset deleted."],
|
|
739
|
+
});
|
|
740
|
+
return;
|
|
741
|
+
}
|
|
742
|
+
if (group === "media" && action === "upload") {
|
|
743
|
+
const result = await uploadMediaFile({ context });
|
|
744
|
+
printResult({
|
|
745
|
+
json: context.json,
|
|
746
|
+
value: result,
|
|
747
|
+
human: [
|
|
748
|
+
`Media uploaded: ${result.id} ${result.name} (${result.asset_type}, ${result.processing_status})`,
|
|
749
|
+
],
|
|
750
|
+
});
|
|
751
|
+
return;
|
|
752
|
+
}
|
|
753
|
+
if (group === "media" && action === "import-youtube") {
|
|
754
|
+
const result = await context.client.media.importYoutube({
|
|
755
|
+
url: requireStringFlag({ flags: context.args.flags, name: "url" }),
|
|
756
|
+
folderId: parseFolderIdFlag({
|
|
757
|
+
flags: context.args.flags,
|
|
758
|
+
name: "folder-id",
|
|
759
|
+
}),
|
|
760
|
+
quality: parseYoutubeQuality({
|
|
761
|
+
value: getStringFlag({ flags: context.args.flags, name: "quality" }),
|
|
762
|
+
}),
|
|
763
|
+
fps: getNumberFlag({ flags: context.args.flags, name: "fps" }),
|
|
764
|
+
audioLanguage: parseNullableStringFlag({
|
|
765
|
+
flags: context.args.flags,
|
|
766
|
+
name: "audio-language",
|
|
767
|
+
}),
|
|
768
|
+
});
|
|
769
|
+
printResult({
|
|
770
|
+
json: context.json,
|
|
771
|
+
value: result,
|
|
772
|
+
human: [`YouTube import queued: ${result.asset_id} ${result.status}`],
|
|
773
|
+
});
|
|
774
|
+
return;
|
|
775
|
+
}
|
|
776
|
+
if (group === "media" && action === "update") {
|
|
777
|
+
const result = await context.client.media.update({
|
|
778
|
+
assetId: requireStringFlag({
|
|
779
|
+
flags: context.args.flags,
|
|
780
|
+
name: "asset-id",
|
|
781
|
+
}),
|
|
782
|
+
name: getStringFlag({ flags: context.args.flags, name: "name" }),
|
|
783
|
+
description: parseNullableStringFlag({
|
|
784
|
+
flags: context.args.flags,
|
|
785
|
+
name: "description",
|
|
786
|
+
}),
|
|
787
|
+
folderId: parseFolderIdFlag({
|
|
788
|
+
flags: context.args.flags,
|
|
789
|
+
name: "folder-id",
|
|
790
|
+
}),
|
|
791
|
+
allowUsingAsBroll: getOptionalBooleanFlag({
|
|
792
|
+
flags: context.args.flags,
|
|
793
|
+
name: "allow-broll",
|
|
794
|
+
}),
|
|
795
|
+
});
|
|
796
|
+
printResult({
|
|
797
|
+
json: context.json,
|
|
798
|
+
value: result,
|
|
799
|
+
human: [`Media updated: ${result.asset.id} ${result.asset.name}`],
|
|
800
|
+
});
|
|
801
|
+
return;
|
|
802
|
+
}
|
|
803
|
+
if (group === "media" && action === "rename") {
|
|
804
|
+
const result = await context.client.media.update({
|
|
805
|
+
assetId: requireStringFlag({
|
|
806
|
+
flags: context.args.flags,
|
|
807
|
+
name: "asset-id",
|
|
808
|
+
}),
|
|
809
|
+
name: requireStringFlag({ flags: context.args.flags, name: "name" }),
|
|
810
|
+
description: undefined,
|
|
811
|
+
folderId: undefined,
|
|
812
|
+
allowUsingAsBroll: undefined,
|
|
813
|
+
});
|
|
814
|
+
printResult({
|
|
815
|
+
json: context.json,
|
|
816
|
+
value: result,
|
|
817
|
+
human: [`Media renamed: ${result.asset.id} ${result.asset.name}`],
|
|
818
|
+
});
|
|
819
|
+
return;
|
|
820
|
+
}
|
|
821
|
+
if (group === "media" && action === "move") {
|
|
822
|
+
const result = await context.client.media.move({
|
|
823
|
+
assetIds: parseAssetIds({ flags: context.args.flags }),
|
|
824
|
+
targetFolderId: requireFolderIdFlag({
|
|
825
|
+
flags: context.args.flags,
|
|
826
|
+
name: "target-folder-id",
|
|
827
|
+
}),
|
|
828
|
+
});
|
|
829
|
+
printResult({
|
|
830
|
+
json: context.json,
|
|
831
|
+
value: result,
|
|
832
|
+
human: [`Moved ${result.moved_count} media asset(s).`],
|
|
833
|
+
});
|
|
834
|
+
return;
|
|
835
|
+
}
|
|
836
|
+
if (group === "media" && action === "attach") {
|
|
837
|
+
const result = await context.client.media.attachToProject({
|
|
838
|
+
projectId: requireStringFlag({
|
|
839
|
+
flags: context.args.flags,
|
|
840
|
+
name: "project-id",
|
|
841
|
+
}),
|
|
842
|
+
assetId: requireStringFlag({
|
|
843
|
+
flags: context.args.flags,
|
|
844
|
+
name: "asset-id",
|
|
845
|
+
}),
|
|
846
|
+
startSeconds: getStringFlag({ flags: context.args.flags, name: "at" }) === undefined
|
|
847
|
+
? undefined
|
|
848
|
+
: parseTimecode({
|
|
849
|
+
value: requireStringFlag({
|
|
850
|
+
flags: context.args.flags,
|
|
851
|
+
name: "at",
|
|
852
|
+
}),
|
|
853
|
+
}),
|
|
854
|
+
durationSeconds: getNumberFlag({ flags: context.args.flags, name: "duration" }) ?? null,
|
|
855
|
+
layerIndex: getNumberFlag({
|
|
856
|
+
flags: context.args.flags,
|
|
857
|
+
name: "layer-index",
|
|
858
|
+
}),
|
|
859
|
+
allowDuplicate: getOptionalBooleanFlag({
|
|
860
|
+
flags: context.args.flags,
|
|
861
|
+
name: "allow-duplicate",
|
|
862
|
+
}),
|
|
863
|
+
positionPreset: parseMediaPositionPreset({
|
|
864
|
+
value: getStringFlag({
|
|
865
|
+
flags: context.args.flags,
|
|
866
|
+
name: "position-preset",
|
|
867
|
+
}),
|
|
868
|
+
}),
|
|
869
|
+
positionX: getNumberFlag({
|
|
870
|
+
flags: context.args.flags,
|
|
871
|
+
name: "position-x",
|
|
872
|
+
}),
|
|
873
|
+
positionY: getNumberFlag({
|
|
874
|
+
flags: context.args.flags,
|
|
875
|
+
name: "position-y",
|
|
876
|
+
}),
|
|
877
|
+
widthPx: getNumberFlag({
|
|
878
|
+
flags: context.args.flags,
|
|
879
|
+
name: "width-px",
|
|
880
|
+
}),
|
|
881
|
+
heightPx: getNumberFlag({
|
|
882
|
+
flags: context.args.flags,
|
|
883
|
+
name: "height-px",
|
|
884
|
+
}),
|
|
885
|
+
scale: getNumberFlag({ flags: context.args.flags, name: "scale" }),
|
|
886
|
+
opacity: getNumberFlag({ flags: context.args.flags, name: "opacity" }),
|
|
887
|
+
animationPreset: parseMediaAnimationPreset({
|
|
888
|
+
value: getStringFlag({
|
|
889
|
+
flags: context.args.flags,
|
|
890
|
+
name: "animation-preset",
|
|
891
|
+
}),
|
|
892
|
+
}),
|
|
893
|
+
});
|
|
894
|
+
const requestedLayer = getNumberFlag({
|
|
895
|
+
flags: context.args.flags,
|
|
896
|
+
name: "layer-index",
|
|
897
|
+
});
|
|
898
|
+
if (requestedLayer !== undefined) {
|
|
899
|
+
await Promise.all(result.inserted
|
|
900
|
+
.map((item) => item.timeline_item_id)
|
|
901
|
+
.filter((item) => typeof item === "string")
|
|
902
|
+
.map((timelineItemId) => context.client.timeline.move({
|
|
903
|
+
projectId: requireStringFlag({
|
|
904
|
+
flags: context.args.flags,
|
|
905
|
+
name: "project-id",
|
|
906
|
+
}),
|
|
907
|
+
timelineItemId,
|
|
908
|
+
newStartSeconds: result.inserted.find((item) => item.timeline_item_id === timelineItemId)?.start_seconds ?? 0,
|
|
909
|
+
newLayerIndex: requestedLayer,
|
|
910
|
+
})));
|
|
911
|
+
}
|
|
912
|
+
printResult({
|
|
913
|
+
json: context.json,
|
|
914
|
+
value: result,
|
|
915
|
+
human: [
|
|
916
|
+
`Media attached: ${result.inserted[0]?.timeline_item_id ?? result.message}`,
|
|
917
|
+
],
|
|
918
|
+
});
|
|
919
|
+
return;
|
|
920
|
+
}
|
|
921
|
+
if (group === "media" && action === "folders" && subaction === "list") {
|
|
922
|
+
const result = await context.client.media.folders.list({
|
|
923
|
+
parentFolderId: getStringFlag({
|
|
924
|
+
flags: context.args.flags,
|
|
925
|
+
name: "parent-folder-id",
|
|
926
|
+
}),
|
|
927
|
+
});
|
|
928
|
+
printResult({
|
|
929
|
+
json: context.json,
|
|
930
|
+
value: result,
|
|
931
|
+
human: result.folders.map((folder) => `${folder.id} ${folder.name} (${folder.assetCount ?? 0} assets)`),
|
|
932
|
+
});
|
|
933
|
+
return;
|
|
934
|
+
}
|
|
935
|
+
if (group === "media" && action === "folders" && subaction === "create") {
|
|
936
|
+
const result = await context.client.media.folders.create({
|
|
937
|
+
name: requireStringFlag({ flags: context.args.flags, name: "name" }),
|
|
938
|
+
parentFolderId: parseFolderIdFlag({
|
|
939
|
+
flags: context.args.flags,
|
|
940
|
+
name: "parent-folder-id",
|
|
941
|
+
}),
|
|
942
|
+
description: parseNullableStringFlag({
|
|
943
|
+
flags: context.args.flags,
|
|
944
|
+
name: "description",
|
|
945
|
+
}),
|
|
946
|
+
iconType: getStringFlag({ flags: context.args.flags, name: "icon-type" }),
|
|
947
|
+
});
|
|
948
|
+
printResult({
|
|
949
|
+
json: context.json,
|
|
950
|
+
value: result,
|
|
951
|
+
human: [`Folder created: ${result.folder.id} ${result.folder.name}`],
|
|
952
|
+
});
|
|
953
|
+
return;
|
|
954
|
+
}
|
|
955
|
+
if (group === "media" && action === "folders" && subaction === "rename") {
|
|
956
|
+
const result = await context.client.media.folders.update({
|
|
957
|
+
folderId: requireStringFlag({
|
|
958
|
+
flags: context.args.flags,
|
|
959
|
+
name: "folder-id",
|
|
960
|
+
}),
|
|
961
|
+
name: requireStringFlag({ flags: context.args.flags, name: "name" }),
|
|
962
|
+
parentFolderId: undefined,
|
|
963
|
+
description: undefined,
|
|
964
|
+
iconType: undefined,
|
|
965
|
+
});
|
|
966
|
+
printResult({
|
|
967
|
+
json: context.json,
|
|
968
|
+
value: result,
|
|
969
|
+
human: [`Folder renamed: ${result.folder.id} ${result.folder.name}`],
|
|
970
|
+
});
|
|
971
|
+
return;
|
|
972
|
+
}
|
|
973
|
+
if (group === "media" && action === "folders" && subaction === "update") {
|
|
974
|
+
const result = await context.client.media.folders.update({
|
|
975
|
+
folderId: requireStringFlag({
|
|
976
|
+
flags: context.args.flags,
|
|
977
|
+
name: "folder-id",
|
|
978
|
+
}),
|
|
979
|
+
name: getStringFlag({ flags: context.args.flags, name: "name" }),
|
|
980
|
+
parentFolderId: parseFolderIdFlag({
|
|
981
|
+
flags: context.args.flags,
|
|
982
|
+
name: "parent-folder-id",
|
|
983
|
+
}),
|
|
984
|
+
description: parseNullableStringFlag({
|
|
985
|
+
flags: context.args.flags,
|
|
986
|
+
name: "description",
|
|
987
|
+
}),
|
|
988
|
+
iconType: getStringFlag({ flags: context.args.flags, name: "icon-type" }),
|
|
989
|
+
});
|
|
990
|
+
printResult({
|
|
991
|
+
json: context.json,
|
|
992
|
+
value: result,
|
|
993
|
+
human: [`Folder updated: ${result.folder.id} ${result.folder.name}`],
|
|
994
|
+
});
|
|
995
|
+
return;
|
|
996
|
+
}
|
|
997
|
+
if (group === "media" && action === "folders" && subaction === "delete") {
|
|
998
|
+
requireConfirmDelete({ flags: context.args.flags });
|
|
999
|
+
const result = await context.client.media.folders.delete({
|
|
1000
|
+
folderId: requireStringFlag({
|
|
1001
|
+
flags: context.args.flags,
|
|
1002
|
+
name: "folder-id",
|
|
1003
|
+
}),
|
|
1004
|
+
cascade: getOptionalBooleanFlag({
|
|
1005
|
+
flags: context.args.flags,
|
|
1006
|
+
name: "cascade",
|
|
1007
|
+
}),
|
|
1008
|
+
});
|
|
1009
|
+
printResult({
|
|
1010
|
+
json: context.json,
|
|
1011
|
+
value: result,
|
|
1012
|
+
human: ["Folder deleted."],
|
|
1013
|
+
});
|
|
1014
|
+
return;
|
|
1015
|
+
}
|
|
86
1016
|
if (group === "project" && action === "inspect") {
|
|
87
1017
|
const projectId = requireStringFlag({
|
|
88
1018
|
flags: context.args.flags,
|
|
@@ -170,21 +1100,33 @@ async function runCommand({ context }) {
|
|
|
170
1100
|
return;
|
|
171
1101
|
}
|
|
172
1102
|
if (group === "text" && action === "add") {
|
|
1103
|
+
const text = requireStringFlag({ flags: context.args.flags, name: "text" });
|
|
1104
|
+
const durationSeconds = getNumberFlag({ flags: context.args.flags, name: "duration" }) ?? 4;
|
|
1105
|
+
const spec = parseJsonObjectFlag({ flags: context.args.flags, name: "spec-json" }) ??
|
|
1106
|
+
(hasTextStyleFlags({ flags: context.args.flags })
|
|
1107
|
+
? buildTextOverlaySpec({
|
|
1108
|
+
text,
|
|
1109
|
+
name: getStringFlag({ flags: context.args.flags, name: "name" }),
|
|
1110
|
+
durationSeconds,
|
|
1111
|
+
flags: context.args.flags,
|
|
1112
|
+
})
|
|
1113
|
+
: undefined);
|
|
173
1114
|
const result = await context.client.text.add({
|
|
174
1115
|
projectId: requireStringFlag({
|
|
175
1116
|
flags: context.args.flags,
|
|
176
1117
|
name: "project-id",
|
|
177
1118
|
}),
|
|
178
|
-
text
|
|
1119
|
+
text,
|
|
179
1120
|
name: getStringFlag({ flags: context.args.flags, name: "name" }),
|
|
180
1121
|
startSeconds: parseTimecode({
|
|
181
1122
|
value: getStringFlag({ flags: context.args.flags, name: "at" }) ?? "0",
|
|
182
1123
|
}),
|
|
183
|
-
durationSeconds
|
|
1124
|
+
durationSeconds,
|
|
184
1125
|
layerIndex: getNumberFlag({
|
|
185
1126
|
flags: context.args.flags,
|
|
186
1127
|
name: "layer-index",
|
|
187
1128
|
}),
|
|
1129
|
+
spec,
|
|
188
1130
|
});
|
|
189
1131
|
printResult({
|
|
190
1132
|
json: context.json,
|
|
@@ -226,6 +1168,29 @@ async function runCommand({ context }) {
|
|
|
226
1168
|
return;
|
|
227
1169
|
}
|
|
228
1170
|
if (group === "text" && action === "update") {
|
|
1171
|
+
const explicitSpec = parseJsonObjectFlag({
|
|
1172
|
+
flags: context.args.flags,
|
|
1173
|
+
name: "spec-json",
|
|
1174
|
+
});
|
|
1175
|
+
const spec = explicitSpec ??
|
|
1176
|
+
(hasTextStyleFlags({ flags: context.args.flags })
|
|
1177
|
+
? await buildUpdatedTextSpec({
|
|
1178
|
+
context,
|
|
1179
|
+
projectId: requireStringFlag({
|
|
1180
|
+
flags: context.args.flags,
|
|
1181
|
+
name: "project-id",
|
|
1182
|
+
}),
|
|
1183
|
+
contentItemId: requireStringFlag({
|
|
1184
|
+
flags: context.args.flags,
|
|
1185
|
+
name: "content-item-id",
|
|
1186
|
+
}),
|
|
1187
|
+
nextText: getStringFlag({
|
|
1188
|
+
flags: context.args.flags,
|
|
1189
|
+
name: "text",
|
|
1190
|
+
}),
|
|
1191
|
+
flags: context.args.flags,
|
|
1192
|
+
})
|
|
1193
|
+
: undefined);
|
|
229
1194
|
const result = await context.client.text.update({
|
|
230
1195
|
projectId: requireStringFlag({
|
|
231
1196
|
flags: context.args.flags,
|
|
@@ -237,15 +1202,94 @@ async function runCommand({ context }) {
|
|
|
237
1202
|
}),
|
|
238
1203
|
text: getStringFlag({ flags: context.args.flags, name: "text" }),
|
|
239
1204
|
name: getStringFlag({ flags: context.args.flags, name: "name" }),
|
|
240
|
-
spec
|
|
1205
|
+
spec,
|
|
1206
|
+
});
|
|
1207
|
+
printResult({
|
|
1208
|
+
json: context.json,
|
|
1209
|
+
value: result,
|
|
1210
|
+
human: ["Text updated."],
|
|
1211
|
+
});
|
|
1212
|
+
return;
|
|
1213
|
+
}
|
|
1214
|
+
if (group === "timeline" && action === "transform") {
|
|
1215
|
+
const result = await context.client.timeline.transform({
|
|
1216
|
+
projectId: requireStringFlag({
|
|
241
1217
|
flags: context.args.flags,
|
|
242
|
-
name: "
|
|
1218
|
+
name: "project-id",
|
|
1219
|
+
}),
|
|
1220
|
+
timelineItemId: requireStringFlag({
|
|
1221
|
+
flags: context.args.flags,
|
|
1222
|
+
name: "timeline-item-id",
|
|
1223
|
+
}),
|
|
1224
|
+
positionX: getNumberFlag({
|
|
1225
|
+
flags: context.args.flags,
|
|
1226
|
+
name: "position-x",
|
|
1227
|
+
}),
|
|
1228
|
+
positionY: getNumberFlag({
|
|
1229
|
+
flags: context.args.flags,
|
|
1230
|
+
name: "position-y",
|
|
1231
|
+
}),
|
|
1232
|
+
widthPx: parseNullableNumberFlag({
|
|
1233
|
+
flags: context.args.flags,
|
|
1234
|
+
name: "width-px",
|
|
1235
|
+
}),
|
|
1236
|
+
heightPx: parseNullableNumberFlag({
|
|
1237
|
+
flags: context.args.flags,
|
|
1238
|
+
name: "height-px",
|
|
1239
|
+
}),
|
|
1240
|
+
keepAspectRatio: getOptionalBooleanFlag({
|
|
1241
|
+
flags: context.args.flags,
|
|
1242
|
+
name: "keep-aspect-ratio",
|
|
243
1243
|
}),
|
|
244
1244
|
});
|
|
245
1245
|
printResult({
|
|
246
1246
|
json: context.json,
|
|
247
1247
|
value: result,
|
|
248
|
-
human: [
|
|
1248
|
+
human: [JSON.stringify(result, null, 2)],
|
|
1249
|
+
});
|
|
1250
|
+
return;
|
|
1251
|
+
}
|
|
1252
|
+
if (group === "timeline" && action === "move") {
|
|
1253
|
+
const result = await context.client.timeline.move({
|
|
1254
|
+
projectId: requireStringFlag({
|
|
1255
|
+
flags: context.args.flags,
|
|
1256
|
+
name: "project-id",
|
|
1257
|
+
}),
|
|
1258
|
+
timelineItemId: requireStringFlag({
|
|
1259
|
+
flags: context.args.flags,
|
|
1260
|
+
name: "timeline-item-id",
|
|
1261
|
+
}),
|
|
1262
|
+
newStartSeconds: parseTimecode({
|
|
1263
|
+
value: requireStringFlag({ flags: context.args.flags, name: "at" }),
|
|
1264
|
+
}),
|
|
1265
|
+
newLayerIndex: getNumberFlag({
|
|
1266
|
+
flags: context.args.flags,
|
|
1267
|
+
name: "layer-index",
|
|
1268
|
+
}),
|
|
1269
|
+
});
|
|
1270
|
+
printResult({
|
|
1271
|
+
json: context.json,
|
|
1272
|
+
value: result,
|
|
1273
|
+
human: [JSON.stringify(result, null, 2)],
|
|
1274
|
+
});
|
|
1275
|
+
return;
|
|
1276
|
+
}
|
|
1277
|
+
if (group === "timeline" && action === "delete") {
|
|
1278
|
+
const result = await context.client.timeline.delete({
|
|
1279
|
+
projectId: requireStringFlag({
|
|
1280
|
+
flags: context.args.flags,
|
|
1281
|
+
name: "project-id",
|
|
1282
|
+
}),
|
|
1283
|
+
timelineItemIds: parseTimelineItemIds({ flags: context.args.flags }),
|
|
1284
|
+
autoPack: getOptionalBooleanFlag({
|
|
1285
|
+
flags: context.args.flags,
|
|
1286
|
+
name: "auto-pack",
|
|
1287
|
+
}),
|
|
1288
|
+
});
|
|
1289
|
+
printResult({
|
|
1290
|
+
json: context.json,
|
|
1291
|
+
value: result,
|
|
1292
|
+
human: [JSON.stringify(result, null, 2)],
|
|
249
1293
|
});
|
|
250
1294
|
return;
|
|
251
1295
|
}
|
|
@@ -420,6 +1464,154 @@ async function runCommand({ context }) {
|
|
|
420
1464
|
});
|
|
421
1465
|
return;
|
|
422
1466
|
}
|
|
1467
|
+
if (group === "captions" && action === "words" && subaction === "list") {
|
|
1468
|
+
const result = await context.client.captions.listWords({
|
|
1469
|
+
projectId: requireStringFlag({
|
|
1470
|
+
flags: context.args.flags,
|
|
1471
|
+
name: "project-id",
|
|
1472
|
+
}),
|
|
1473
|
+
timelineItemId: getStringFlag({
|
|
1474
|
+
flags: context.args.flags,
|
|
1475
|
+
name: "timeline-item-id",
|
|
1476
|
+
}),
|
|
1477
|
+
matchText: getStringFlag({
|
|
1478
|
+
flags: context.args.flags,
|
|
1479
|
+
name: "match-text",
|
|
1480
|
+
}),
|
|
1481
|
+
limit: getNumberFlag({ flags: context.args.flags, name: "limit" }),
|
|
1482
|
+
offset: getNumberFlag({ flags: context.args.flags, name: "offset" }),
|
|
1483
|
+
});
|
|
1484
|
+
printResult({
|
|
1485
|
+
json: context.json,
|
|
1486
|
+
value: result,
|
|
1487
|
+
human: result.words.map((word) => JSON.stringify(word)),
|
|
1488
|
+
});
|
|
1489
|
+
return;
|
|
1490
|
+
}
|
|
1491
|
+
if (group === "captions" && action === "words" && subaction === "text") {
|
|
1492
|
+
const result = await context.client.captions.updateWordText({
|
|
1493
|
+
projectId: requireStringFlag({
|
|
1494
|
+
flags: context.args.flags,
|
|
1495
|
+
name: "project-id",
|
|
1496
|
+
}),
|
|
1497
|
+
wordId: requireStringFlag({ flags: context.args.flags, name: "word-id" }),
|
|
1498
|
+
newText: getStringFlag({ flags: context.args.flags, name: "new-text" }) ??
|
|
1499
|
+
requireStringFlag({ flags: context.args.flags, name: "text" }),
|
|
1500
|
+
});
|
|
1501
|
+
printResult({
|
|
1502
|
+
json: context.json,
|
|
1503
|
+
value: result,
|
|
1504
|
+
human: [
|
|
1505
|
+
`Updated ${result.word_id}: ${result.old_text} -> ${result.new_text}`,
|
|
1506
|
+
],
|
|
1507
|
+
});
|
|
1508
|
+
return;
|
|
1509
|
+
}
|
|
1510
|
+
if (group === "captions" &&
|
|
1511
|
+
action === "words" &&
|
|
1512
|
+
subaction === "batch-text") {
|
|
1513
|
+
const result = await context.client.captions.batchUpdateWordText({
|
|
1514
|
+
projectId: requireStringFlag({
|
|
1515
|
+
flags: context.args.flags,
|
|
1516
|
+
name: "project-id",
|
|
1517
|
+
}),
|
|
1518
|
+
updates: parseWordTextUpdatesFlag({
|
|
1519
|
+
flags: context.args.flags,
|
|
1520
|
+
name: "updates-json",
|
|
1521
|
+
}),
|
|
1522
|
+
});
|
|
1523
|
+
printResult({
|
|
1524
|
+
json: context.json,
|
|
1525
|
+
value: result,
|
|
1526
|
+
human: [result.message],
|
|
1527
|
+
});
|
|
1528
|
+
return;
|
|
1529
|
+
}
|
|
1530
|
+
if (group === "captions" && action === "words" && subaction === "style") {
|
|
1531
|
+
const styleJson = parseNullableJsonObjectFlag({
|
|
1532
|
+
flags: context.args.flags,
|
|
1533
|
+
name: "style-json",
|
|
1534
|
+
});
|
|
1535
|
+
const style = styleJson === undefined
|
|
1536
|
+
? buildCaptionWordStyleFromFlags({ flags: context.args.flags })
|
|
1537
|
+
: styleJson;
|
|
1538
|
+
const result = await context.client.captions.setWordStyle({
|
|
1539
|
+
...buildCaptionWordSelector({ flags: context.args.flags }),
|
|
1540
|
+
style,
|
|
1541
|
+
clearExisting: getOptionalBooleanFlag({
|
|
1542
|
+
flags: context.args.flags,
|
|
1543
|
+
name: "clear-existing",
|
|
1544
|
+
}),
|
|
1545
|
+
});
|
|
1546
|
+
printResult({
|
|
1547
|
+
json: context.json,
|
|
1548
|
+
value: result,
|
|
1549
|
+
human: [`Updated ${result.updated_count} caption word(s).`],
|
|
1550
|
+
});
|
|
1551
|
+
return;
|
|
1552
|
+
}
|
|
1553
|
+
if (group === "captions" && action === "words" && subaction === "emphasis") {
|
|
1554
|
+
const result = await context.client.captions.setWordEmphasis({
|
|
1555
|
+
...buildCaptionWordSelector({ flags: context.args.flags }),
|
|
1556
|
+
emphasis: getOptionalBooleanFlag({
|
|
1557
|
+
flags: context.args.flags,
|
|
1558
|
+
name: "emphasis",
|
|
1559
|
+
}) ?? true,
|
|
1560
|
+
});
|
|
1561
|
+
printResult({
|
|
1562
|
+
json: context.json,
|
|
1563
|
+
value: result,
|
|
1564
|
+
human: [`Updated ${result.updated_count} caption word(s).`],
|
|
1565
|
+
});
|
|
1566
|
+
return;
|
|
1567
|
+
}
|
|
1568
|
+
if (group === "captions" && action === "words" && subaction === "delete") {
|
|
1569
|
+
const result = await context.client.captions.deleteWords({
|
|
1570
|
+
projectId: requireStringFlag({
|
|
1571
|
+
flags: context.args.flags,
|
|
1572
|
+
name: "project-id",
|
|
1573
|
+
}),
|
|
1574
|
+
wordIds: parseWordIds({ flags: context.args.flags }),
|
|
1575
|
+
});
|
|
1576
|
+
printResult({
|
|
1577
|
+
json: context.json,
|
|
1578
|
+
value: result,
|
|
1579
|
+
human: [result.message],
|
|
1580
|
+
});
|
|
1581
|
+
return;
|
|
1582
|
+
}
|
|
1583
|
+
if (group === "captions" && action === "words" && subaction === "merge") {
|
|
1584
|
+
const result = await context.client.captions.mergeWords({
|
|
1585
|
+
projectId: requireStringFlag({
|
|
1586
|
+
flags: context.args.flags,
|
|
1587
|
+
name: "project-id",
|
|
1588
|
+
}),
|
|
1589
|
+
wordIds: parseWordIds({ flags: context.args.flags }),
|
|
1590
|
+
text: getStringFlag({ flags: context.args.flags, name: "text" }),
|
|
1591
|
+
});
|
|
1592
|
+
printResult({
|
|
1593
|
+
json: context.json,
|
|
1594
|
+
value: result,
|
|
1595
|
+
human: [result.message],
|
|
1596
|
+
});
|
|
1597
|
+
return;
|
|
1598
|
+
}
|
|
1599
|
+
if (group === "captions" && action === "words" && subaction === "split") {
|
|
1600
|
+
const result = await context.client.captions.splitWord({
|
|
1601
|
+
projectId: requireStringFlag({
|
|
1602
|
+
flags: context.args.flags,
|
|
1603
|
+
name: "project-id",
|
|
1604
|
+
}),
|
|
1605
|
+
wordId: requireStringFlag({ flags: context.args.flags, name: "word-id" }),
|
|
1606
|
+
words: parseCsvRequiredFlag({ flags: context.args.flags, name: "words" }),
|
|
1607
|
+
});
|
|
1608
|
+
printResult({
|
|
1609
|
+
json: context.json,
|
|
1610
|
+
value: result,
|
|
1611
|
+
human: [result.message],
|
|
1612
|
+
});
|
|
1613
|
+
return;
|
|
1614
|
+
}
|
|
423
1615
|
if (group === "captions" && action === "themes" && subaction === "list") {
|
|
424
1616
|
const result = await context.client.captions.listThemes();
|
|
425
1617
|
printResult({
|
|
@@ -525,7 +1717,7 @@ async function runCommand({ context }) {
|
|
|
525
1717
|
});
|
|
526
1718
|
return;
|
|
527
1719
|
}
|
|
528
|
-
throw
|
|
1720
|
+
throw buildUnknownCommandError({ command: context.args.command });
|
|
529
1721
|
}
|
|
530
1722
|
async function setApiKey({ context }) {
|
|
531
1723
|
const apiKey = getStringFlag({ flags: context.args.flags, name: "api-key" }) ??
|
|
@@ -632,6 +1824,52 @@ async function loginWithBrowser({ context, }) {
|
|
|
632
1824
|
}
|
|
633
1825
|
throw new Error("Timed out waiting for browser approval.");
|
|
634
1826
|
}
|
|
1827
|
+
async function uploadMediaFile({ context }) {
|
|
1828
|
+
const filePath = resolve(requireStringFlag({ flags: context.args.flags, name: "file" }));
|
|
1829
|
+
const fileStats = await stat(filePath);
|
|
1830
|
+
if (!fileStats.isFile()) {
|
|
1831
|
+
throw new Error(`Not a file: ${filePath}`);
|
|
1832
|
+
}
|
|
1833
|
+
const fileName = getStringFlag({ flags: context.args.flags, name: "name" }) ??
|
|
1834
|
+
basename(filePath);
|
|
1835
|
+
const contentType = getStringFlag({ flags: context.args.flags, name: "content-type" }) ??
|
|
1836
|
+
inferContentType({ filePath });
|
|
1837
|
+
const init = await context.client.media.initUpload({
|
|
1838
|
+
fileName,
|
|
1839
|
+
contentType,
|
|
1840
|
+
});
|
|
1841
|
+
const bytes = await readFile(filePath);
|
|
1842
|
+
const uploadResponse = await fetch(init.upload_url, {
|
|
1843
|
+
method: "PUT",
|
|
1844
|
+
headers: { "Content-Type": contentType },
|
|
1845
|
+
body: new Blob([new Uint8Array(bytes)], { type: contentType }),
|
|
1846
|
+
});
|
|
1847
|
+
if (!uploadResponse.ok) {
|
|
1848
|
+
throw new Error(`Upload failed with ${uploadResponse.status}: ${await uploadResponse.text()}`);
|
|
1849
|
+
}
|
|
1850
|
+
return context.client.media.finalizeUpload({
|
|
1851
|
+
storageKey: init.storage_key,
|
|
1852
|
+
fileName,
|
|
1853
|
+
contentType,
|
|
1854
|
+
fileSizeBytes: fileStats.size,
|
|
1855
|
+
autoProcessEnabled: getOptionalBooleanFlag({
|
|
1856
|
+
flags: context.args.flags,
|
|
1857
|
+
name: "auto-process",
|
|
1858
|
+
}),
|
|
1859
|
+
autoTranscribe: getOptionalBooleanFlag({
|
|
1860
|
+
flags: context.args.flags,
|
|
1861
|
+
name: "auto-transcribe",
|
|
1862
|
+
}),
|
|
1863
|
+
autoDescribeAfterTranscribe: getOptionalBooleanFlag({
|
|
1864
|
+
flags: context.args.flags,
|
|
1865
|
+
name: "auto-describe",
|
|
1866
|
+
}),
|
|
1867
|
+
autoSuggestShorts: getOptionalBooleanFlag({
|
|
1868
|
+
flags: context.args.flags,
|
|
1869
|
+
name: "auto-suggest-shorts",
|
|
1870
|
+
}),
|
|
1871
|
+
});
|
|
1872
|
+
}
|
|
635
1873
|
async function readJsonResponse({ response, url, action, }) {
|
|
636
1874
|
const contentType = response.headers.get("content-type") ?? "";
|
|
637
1875
|
const text = await response.text();
|
|
@@ -752,6 +1990,118 @@ function parseArgs({ argv }) {
|
|
|
752
1990
|
}
|
|
753
1991
|
return { command, flags };
|
|
754
1992
|
}
|
|
1993
|
+
function validateParsedArgs({ parsed }) {
|
|
1994
|
+
const spec = findCommandSpec({ command: parsed.command });
|
|
1995
|
+
if (!spec) {
|
|
1996
|
+
throw buildUnknownCommandError({ command: parsed.command });
|
|
1997
|
+
}
|
|
1998
|
+
const allowedFlags = new Set([...GLOBAL_FLAGS, ...spec.flags]);
|
|
1999
|
+
for (const flag of Object.keys(parsed.flags)) {
|
|
2000
|
+
if (!allowedFlags.has(flag)) {
|
|
2001
|
+
throw buildUnknownFlagError({ spec, flag });
|
|
2002
|
+
}
|
|
2003
|
+
}
|
|
2004
|
+
}
|
|
2005
|
+
function findCommandSpec({ command, }) {
|
|
2006
|
+
return COMMAND_SPECS.find((spec) => commandsEqual(spec.command, command));
|
|
2007
|
+
}
|
|
2008
|
+
function commandsEqual(left, right) {
|
|
2009
|
+
return (left.length === right.length &&
|
|
2010
|
+
left.every((part, index) => part === right[index]));
|
|
2011
|
+
}
|
|
2012
|
+
function commandLabel({ command }) {
|
|
2013
|
+
return command.join(" ");
|
|
2014
|
+
}
|
|
2015
|
+
function printCommandHelpOrThrow({ command }) {
|
|
2016
|
+
const spec = findCommandSpec({ command });
|
|
2017
|
+
if (!spec) {
|
|
2018
|
+
throw buildUnknownCommandError({ command });
|
|
2019
|
+
}
|
|
2020
|
+
printCommandHelp({ spec });
|
|
2021
|
+
}
|
|
2022
|
+
function printCommandHelp({ spec }) {
|
|
2023
|
+
const flags = [...new Set([...spec.flags, ...GLOBAL_FLAGS])].sort();
|
|
2024
|
+
console.log(`Usage:
|
|
2025
|
+
${spec.usage}
|
|
2026
|
+
|
|
2027
|
+
Flags:
|
|
2028
|
+
${flags.map((flag) => ` --${flag}`).join("\n")}
|
|
2029
|
+
`);
|
|
2030
|
+
}
|
|
2031
|
+
function buildUnknownCommandError({ command, }) {
|
|
2032
|
+
const suggestions = suggestCommands({ command }).map((spec) => spec.usage);
|
|
2033
|
+
return new CliUsageError({
|
|
2034
|
+
code: "unknown_command",
|
|
2035
|
+
message: `Unknown command: ${commandLabel({ command })}`,
|
|
2036
|
+
suggestions,
|
|
2037
|
+
usage: suggestions[0],
|
|
2038
|
+
});
|
|
2039
|
+
}
|
|
2040
|
+
function buildUnknownFlagError({ spec, flag, }) {
|
|
2041
|
+
const flags = [...new Set([...spec.flags, ...GLOBAL_FLAGS])];
|
|
2042
|
+
const suggestions = suggestValues({ value: flag, candidates: flags }).map((candidate) => `--${candidate}`);
|
|
2043
|
+
return new CliUsageError({
|
|
2044
|
+
code: "unknown_flag",
|
|
2045
|
+
message: `Unknown flag --${flag} for \`${commandLabel({ command: spec.command })}\`.`,
|
|
2046
|
+
suggestions,
|
|
2047
|
+
usage: spec.usage,
|
|
2048
|
+
});
|
|
2049
|
+
}
|
|
2050
|
+
function suggestCommands({ command }) {
|
|
2051
|
+
const joined = commandLabel({ command });
|
|
2052
|
+
const prefixMatches = COMMAND_SPECS.filter((spec) => commandsEqual(spec.command.slice(0, command.length), command));
|
|
2053
|
+
if (prefixMatches.length > 0) {
|
|
2054
|
+
return prefixMatches.slice(0, 3);
|
|
2055
|
+
}
|
|
2056
|
+
return COMMAND_SPECS.map((spec) => ({
|
|
2057
|
+
spec,
|
|
2058
|
+
score: commandSimilarity({
|
|
2059
|
+
left: joined,
|
|
2060
|
+
right: commandLabel({ command: spec.command }),
|
|
2061
|
+
}),
|
|
2062
|
+
}))
|
|
2063
|
+
.filter((item) => item.score >= 0.45)
|
|
2064
|
+
.sort((left, right) => right.score - left.score)
|
|
2065
|
+
.slice(0, 3)
|
|
2066
|
+
.map((item) => item.spec);
|
|
2067
|
+
}
|
|
2068
|
+
function suggestValues({ value, candidates, }) {
|
|
2069
|
+
return candidates
|
|
2070
|
+
.map((candidate) => ({
|
|
2071
|
+
candidate,
|
|
2072
|
+
score: commandSimilarity({ left: value, right: candidate }),
|
|
2073
|
+
}))
|
|
2074
|
+
.filter((item) => item.score >= 0.45)
|
|
2075
|
+
.sort((left, right) => right.score - left.score)
|
|
2076
|
+
.slice(0, 3)
|
|
2077
|
+
.map((item) => item.candidate);
|
|
2078
|
+
}
|
|
2079
|
+
function commandSimilarity({ left, right, }) {
|
|
2080
|
+
const maxLength = Math.max(left.length, right.length);
|
|
2081
|
+
if (maxLength === 0) {
|
|
2082
|
+
return 1;
|
|
2083
|
+
}
|
|
2084
|
+
return 1 - levenshteinDistance({ left, right }) / maxLength;
|
|
2085
|
+
}
|
|
2086
|
+
function levenshteinDistance({ left, right, }) {
|
|
2087
|
+
const previous = Array.from({ length: right.length + 1 }, (_, index) => index);
|
|
2088
|
+
const current = Array.from({ length: right.length + 1 }, () => 0);
|
|
2089
|
+
for (let leftIndex = 1; leftIndex <= left.length; leftIndex++) {
|
|
2090
|
+
current[0] = leftIndex;
|
|
2091
|
+
for (let rightIndex = 1; rightIndex <= right.length; rightIndex++) {
|
|
2092
|
+
const substitutionCost = left[leftIndex - 1] === right[rightIndex - 1] ? 0 : 1;
|
|
2093
|
+
current[rightIndex] = Math.min((current[rightIndex - 1] ?? 0) + 1, (previous[rightIndex] ?? 0) + 1, (previous[rightIndex - 1] ?? 0) + substitutionCost);
|
|
2094
|
+
}
|
|
2095
|
+
for (let index = 0; index < previous.length; index++) {
|
|
2096
|
+
previous[index] = current[index] ?? 0;
|
|
2097
|
+
}
|
|
2098
|
+
}
|
|
2099
|
+
return previous[right.length] ?? 0;
|
|
2100
|
+
}
|
|
2101
|
+
function usageForCurrentCommand() {
|
|
2102
|
+
const parsed = parseArgs({ argv: process.argv.slice(2) });
|
|
2103
|
+
return findCommandSpec({ command: parsed.command })?.usage;
|
|
2104
|
+
}
|
|
755
2105
|
function getStringFlag({ flags, name, }) {
|
|
756
2106
|
const value = flags[name];
|
|
757
2107
|
return typeof value === "string" ? value : undefined;
|
|
@@ -783,6 +2133,20 @@ function getNumberFlag({ flags, name, }) {
|
|
|
783
2133
|
}
|
|
784
2134
|
return parsed;
|
|
785
2135
|
}
|
|
2136
|
+
function parseNullableNumberFlag({ flags, name, }) {
|
|
2137
|
+
const value = getStringFlag({ flags, name });
|
|
2138
|
+
if (value === undefined) {
|
|
2139
|
+
return undefined;
|
|
2140
|
+
}
|
|
2141
|
+
if (value === "null") {
|
|
2142
|
+
return null;
|
|
2143
|
+
}
|
|
2144
|
+
const parsed = Number(value);
|
|
2145
|
+
if (!Number.isFinite(parsed)) {
|
|
2146
|
+
throw new Error(`--${name} must be a number or null.`);
|
|
2147
|
+
}
|
|
2148
|
+
return parsed;
|
|
2149
|
+
}
|
|
786
2150
|
function parseJsonObjectFlag({ flags, name, }) {
|
|
787
2151
|
const value = getStringFlag({ flags, name });
|
|
788
2152
|
if (value === undefined) {
|
|
@@ -794,6 +2158,58 @@ function parseJsonObjectFlag({ flags, name, }) {
|
|
|
794
2158
|
}
|
|
795
2159
|
return parsed;
|
|
796
2160
|
}
|
|
2161
|
+
function parseNullableJsonObjectFlag({ flags, name, }) {
|
|
2162
|
+
const value = getStringFlag({ flags, name });
|
|
2163
|
+
if (value === undefined) {
|
|
2164
|
+
return undefined;
|
|
2165
|
+
}
|
|
2166
|
+
if (value === "null") {
|
|
2167
|
+
return null;
|
|
2168
|
+
}
|
|
2169
|
+
const parsed = JSON.parse(value);
|
|
2170
|
+
if (!parsed || typeof parsed !== "object" || Array.isArray(parsed)) {
|
|
2171
|
+
throw new Error(`--${name} must be a JSON object or null.`);
|
|
2172
|
+
}
|
|
2173
|
+
return parsed;
|
|
2174
|
+
}
|
|
2175
|
+
function parseJsonArrayFlag({ flags, name, }) {
|
|
2176
|
+
const value = getStringFlag({ flags, name });
|
|
2177
|
+
if (value === undefined) {
|
|
2178
|
+
return undefined;
|
|
2179
|
+
}
|
|
2180
|
+
const parsed = JSON.parse(value);
|
|
2181
|
+
if (!Array.isArray(parsed)) {
|
|
2182
|
+
throw new Error(`--${name} must be a JSON array.`);
|
|
2183
|
+
}
|
|
2184
|
+
return parsed;
|
|
2185
|
+
}
|
|
2186
|
+
function parseCsvFlag({ flags, name, }) {
|
|
2187
|
+
const value = getStringFlag({ flags, name });
|
|
2188
|
+
if (value === undefined) {
|
|
2189
|
+
return undefined;
|
|
2190
|
+
}
|
|
2191
|
+
return value
|
|
2192
|
+
.split(",")
|
|
2193
|
+
.map((item) => item.trim())
|
|
2194
|
+
.filter((item) => item.length > 0);
|
|
2195
|
+
}
|
|
2196
|
+
function parseCsvRequiredFlag({ flags, name, }) {
|
|
2197
|
+
const parsed = parseCsvFlag({ flags, name });
|
|
2198
|
+
if (parsed === undefined || parsed.length === 0) {
|
|
2199
|
+
throw new Error(`Missing required --${name}.`);
|
|
2200
|
+
}
|
|
2201
|
+
return parsed;
|
|
2202
|
+
}
|
|
2203
|
+
function parseNullableStringFlag({ flags, name, }) {
|
|
2204
|
+
const value = getStringFlag({ flags, name });
|
|
2205
|
+
if (value === undefined) {
|
|
2206
|
+
return undefined;
|
|
2207
|
+
}
|
|
2208
|
+
if (value === "null") {
|
|
2209
|
+
return null;
|
|
2210
|
+
}
|
|
2211
|
+
return value;
|
|
2212
|
+
}
|
|
797
2213
|
function parseStringRecordFlag({ flags, name, }) {
|
|
798
2214
|
const value = parseJsonObjectFlag({ flags, name });
|
|
799
2215
|
if (value === undefined) {
|
|
@@ -812,6 +2228,11 @@ function requireConfirmPreview({ flags }) {
|
|
|
812
2228
|
}
|
|
813
2229
|
return true;
|
|
814
2230
|
}
|
|
2231
|
+
function requireConfirmDelete({ flags }) {
|
|
2232
|
+
if (!getBooleanFlag({ flags, name: "confirm-delete" })) {
|
|
2233
|
+
throw new Error("Delete commands require --confirm-delete.");
|
|
2234
|
+
}
|
|
2235
|
+
}
|
|
815
2236
|
function parseTimecode({ value }) {
|
|
816
2237
|
const trimmed = value.trim();
|
|
817
2238
|
if (/^\d+(\.\d+)?$/.test(trimmed)) {
|
|
@@ -924,6 +2345,542 @@ function parseImageFormat({ value, }) {
|
|
|
924
2345
|
}
|
|
925
2346
|
throw new Error("--image-format must be jpeg or png.");
|
|
926
2347
|
}
|
|
2348
|
+
function parseProjectStatus({ value, }) {
|
|
2349
|
+
if (value === undefined) {
|
|
2350
|
+
return undefined;
|
|
2351
|
+
}
|
|
2352
|
+
if (value === "active" || value === "archived" || value === "all") {
|
|
2353
|
+
return value;
|
|
2354
|
+
}
|
|
2355
|
+
throw new Error("--status must be active, archived, or all.");
|
|
2356
|
+
}
|
|
2357
|
+
function parseAspectRatio({ value, }) {
|
|
2358
|
+
if (value === undefined) {
|
|
2359
|
+
return undefined;
|
|
2360
|
+
}
|
|
2361
|
+
if (value === "9:16" ||
|
|
2362
|
+
value === "16:9" ||
|
|
2363
|
+
value === "1:1" ||
|
|
2364
|
+
value === "4:5") {
|
|
2365
|
+
return value;
|
|
2366
|
+
}
|
|
2367
|
+
throw new Error("--aspect-ratio must be 9:16, 16:9, 1:1, or 4:5.");
|
|
2368
|
+
}
|
|
2369
|
+
function parseProjectType({ value, }) {
|
|
2370
|
+
if (value === undefined) {
|
|
2371
|
+
return undefined;
|
|
2372
|
+
}
|
|
2373
|
+
if (value === "video" || value === "carousel") {
|
|
2374
|
+
return value;
|
|
2375
|
+
}
|
|
2376
|
+
throw new Error("--project-type must be video or carousel.");
|
|
2377
|
+
}
|
|
2378
|
+
function parseAssetType({ value, }) {
|
|
2379
|
+
if (value === undefined) {
|
|
2380
|
+
return undefined;
|
|
2381
|
+
}
|
|
2382
|
+
if (value === "video" ||
|
|
2383
|
+
value === "audio" ||
|
|
2384
|
+
value === "image" ||
|
|
2385
|
+
value === "document" ||
|
|
2386
|
+
value === "all") {
|
|
2387
|
+
return value;
|
|
2388
|
+
}
|
|
2389
|
+
throw new Error("--asset-type must be video, audio, image, document, or all.");
|
|
2390
|
+
}
|
|
2391
|
+
function parseYoutubeQuality({ value, }) {
|
|
2392
|
+
if (value === undefined) {
|
|
2393
|
+
return undefined;
|
|
2394
|
+
}
|
|
2395
|
+
if (value === "720p" ||
|
|
2396
|
+
value === "1080p" ||
|
|
2397
|
+
value === "1440p" ||
|
|
2398
|
+
value === "2160p") {
|
|
2399
|
+
return value;
|
|
2400
|
+
}
|
|
2401
|
+
throw new Error("--quality must be 720p, 1080p, 1440p, or 2160p.");
|
|
2402
|
+
}
|
|
2403
|
+
function parseMediaPositionPreset({ value, }) {
|
|
2404
|
+
if (value === undefined) {
|
|
2405
|
+
return undefined;
|
|
2406
|
+
}
|
|
2407
|
+
if (value === "center" ||
|
|
2408
|
+
value === "top-left" ||
|
|
2409
|
+
value === "top-right" ||
|
|
2410
|
+
value === "bottom-left" ||
|
|
2411
|
+
value === "bottom-right" ||
|
|
2412
|
+
value === "full-screen") {
|
|
2413
|
+
return value;
|
|
2414
|
+
}
|
|
2415
|
+
throw new Error("--position-preset must be center, top-left, top-right, bottom-left, bottom-right, or full-screen.");
|
|
2416
|
+
}
|
|
2417
|
+
function parseMediaAnimationPreset({ value, }) {
|
|
2418
|
+
if (value === undefined) {
|
|
2419
|
+
return undefined;
|
|
2420
|
+
}
|
|
2421
|
+
if (value === "none" ||
|
|
2422
|
+
value === "fadeIn" ||
|
|
2423
|
+
value === "fadeOut" ||
|
|
2424
|
+
value === "zoomIn" ||
|
|
2425
|
+
value === "slideIn" ||
|
|
2426
|
+
value === "popIn" ||
|
|
2427
|
+
value === "bounce" ||
|
|
2428
|
+
value === "spin") {
|
|
2429
|
+
return value;
|
|
2430
|
+
}
|
|
2431
|
+
throw new Error("--animation-preset must be none, fadeIn, fadeOut, zoomIn, slideIn, popIn, bounce, or spin.");
|
|
2432
|
+
}
|
|
2433
|
+
function parseCaptionMatchPattern({ value, }) {
|
|
2434
|
+
if (value === undefined) {
|
|
2435
|
+
return undefined;
|
|
2436
|
+
}
|
|
2437
|
+
if (value === "numbers" || value === "caps" || value === "exclamations") {
|
|
2438
|
+
return value;
|
|
2439
|
+
}
|
|
2440
|
+
throw new Error("--match-pattern must be numbers, caps, or exclamations.");
|
|
2441
|
+
}
|
|
2442
|
+
function parseFolderIdFlag({ flags, name, }) {
|
|
2443
|
+
const value = getStringFlag({ flags, name });
|
|
2444
|
+
if (value === undefined) {
|
|
2445
|
+
return undefined;
|
|
2446
|
+
}
|
|
2447
|
+
if (value === "root" || value === "null") {
|
|
2448
|
+
return null;
|
|
2449
|
+
}
|
|
2450
|
+
return value;
|
|
2451
|
+
}
|
|
2452
|
+
function requireFolderIdFlag({ flags, name, }) {
|
|
2453
|
+
const value = parseFolderIdFlag({ flags, name });
|
|
2454
|
+
if (value === undefined) {
|
|
2455
|
+
throw new Error(`Missing required --${name}. Use "root" for no folder.`);
|
|
2456
|
+
}
|
|
2457
|
+
return value;
|
|
2458
|
+
}
|
|
2459
|
+
function parseAssetIds({ flags }) {
|
|
2460
|
+
const csv = getStringFlag({ flags, name: "asset-ids" }) ??
|
|
2461
|
+
getStringFlag({ flags, name: "asset-id" });
|
|
2462
|
+
if (!csv) {
|
|
2463
|
+
throw new Error("Missing required --asset-id or --asset-ids.");
|
|
2464
|
+
}
|
|
2465
|
+
return csv
|
|
2466
|
+
.split(",")
|
|
2467
|
+
.map((item) => item.trim())
|
|
2468
|
+
.filter((item) => item.length > 0);
|
|
2469
|
+
}
|
|
2470
|
+
function parseTimelineItemIds({ flags }) {
|
|
2471
|
+
const csv = getStringFlag({ flags, name: "timeline-item-ids" }) ??
|
|
2472
|
+
getStringFlag({ flags, name: "timeline-item-id" });
|
|
2473
|
+
if (!csv) {
|
|
2474
|
+
throw new Error("Missing required --timeline-item-id or --timeline-item-ids.");
|
|
2475
|
+
}
|
|
2476
|
+
return csv
|
|
2477
|
+
.split(",")
|
|
2478
|
+
.map((item) => item.trim())
|
|
2479
|
+
.filter((item) => item.length > 0);
|
|
2480
|
+
}
|
|
2481
|
+
function parseWordIds({ flags }) {
|
|
2482
|
+
const csv = getStringFlag({ flags, name: "word-ids" }) ??
|
|
2483
|
+
getStringFlag({ flags, name: "word-id" });
|
|
2484
|
+
if (!csv) {
|
|
2485
|
+
throw new Error("Missing required --word-id or --word-ids.");
|
|
2486
|
+
}
|
|
2487
|
+
return csv
|
|
2488
|
+
.split(",")
|
|
2489
|
+
.map((item) => item.trim())
|
|
2490
|
+
.filter((item) => item.length > 0);
|
|
2491
|
+
}
|
|
2492
|
+
function parseWordTextUpdatesFlag({ flags, name, }) {
|
|
2493
|
+
const parsed = parseJsonArrayFlag({ flags, name });
|
|
2494
|
+
if (parsed === undefined) {
|
|
2495
|
+
throw new Error(`Missing required --${name}.`);
|
|
2496
|
+
}
|
|
2497
|
+
return parsed.map((item, index) => {
|
|
2498
|
+
if (!item || typeof item !== "object" || Array.isArray(item)) {
|
|
2499
|
+
throw new Error(`--${name}[${index}] must be an object.`);
|
|
2500
|
+
}
|
|
2501
|
+
const record = item;
|
|
2502
|
+
if (typeof record.word_id !== "string" ||
|
|
2503
|
+
typeof record.new_text !== "string") {
|
|
2504
|
+
throw new Error(`--${name}[${index}] must include word_id and new_text.`);
|
|
2505
|
+
}
|
|
2506
|
+
return { word_id: record.word_id, new_text: record.new_text };
|
|
2507
|
+
});
|
|
2508
|
+
}
|
|
2509
|
+
function inferContentType({ filePath }) {
|
|
2510
|
+
const extension = extname(filePath).toLowerCase();
|
|
2511
|
+
switch (extension) {
|
|
2512
|
+
case ".mp4":
|
|
2513
|
+
return "video/mp4";
|
|
2514
|
+
case ".mov":
|
|
2515
|
+
return "video/quicktime";
|
|
2516
|
+
case ".webm":
|
|
2517
|
+
return "video/webm";
|
|
2518
|
+
case ".mp3":
|
|
2519
|
+
return "audio/mpeg";
|
|
2520
|
+
case ".wav":
|
|
2521
|
+
return "audio/wav";
|
|
2522
|
+
case ".m4a":
|
|
2523
|
+
return "audio/mp4";
|
|
2524
|
+
case ".jpg":
|
|
2525
|
+
case ".jpeg":
|
|
2526
|
+
return "image/jpeg";
|
|
2527
|
+
case ".png":
|
|
2528
|
+
return "image/png";
|
|
2529
|
+
case ".webp":
|
|
2530
|
+
return "image/webp";
|
|
2531
|
+
default:
|
|
2532
|
+
return "application/octet-stream";
|
|
2533
|
+
}
|
|
2534
|
+
}
|
|
2535
|
+
function parseOrientation({ value, }) {
|
|
2536
|
+
if (value === undefined) {
|
|
2537
|
+
return undefined;
|
|
2538
|
+
}
|
|
2539
|
+
if (value === "null") {
|
|
2540
|
+
return null;
|
|
2541
|
+
}
|
|
2542
|
+
if (value === "landscape" || value === "portrait" || value === "square") {
|
|
2543
|
+
return value;
|
|
2544
|
+
}
|
|
2545
|
+
throw new Error("--orientation must be landscape, portrait, square, or null.");
|
|
2546
|
+
}
|
|
2547
|
+
function parseResolution({ value, }) {
|
|
2548
|
+
if (value === undefined) {
|
|
2549
|
+
return undefined;
|
|
2550
|
+
}
|
|
2551
|
+
if (value === "null") {
|
|
2552
|
+
return null;
|
|
2553
|
+
}
|
|
2554
|
+
if (value === "0.25" ||
|
|
2555
|
+
value === "0.5" ||
|
|
2556
|
+
value === "0.75" ||
|
|
2557
|
+
value === "1" ||
|
|
2558
|
+
value === "1.5" ||
|
|
2559
|
+
value === "2") {
|
|
2560
|
+
return value;
|
|
2561
|
+
}
|
|
2562
|
+
throw new Error("--resolution must be 0.25, 0.5, 0.75, 1, 1.5, 2, or null.");
|
|
2563
|
+
}
|
|
2564
|
+
function parseFrameRate({ value, }) {
|
|
2565
|
+
if (value === undefined) {
|
|
2566
|
+
return undefined;
|
|
2567
|
+
}
|
|
2568
|
+
if (value === "null") {
|
|
2569
|
+
return null;
|
|
2570
|
+
}
|
|
2571
|
+
if (value === "24" ||
|
|
2572
|
+
value === "25" ||
|
|
2573
|
+
value === "30" ||
|
|
2574
|
+
value === "50" ||
|
|
2575
|
+
value === "60") {
|
|
2576
|
+
return value;
|
|
2577
|
+
}
|
|
2578
|
+
throw new Error("--frame-rate must be 24, 25, 30, 50, 60, or null.");
|
|
2579
|
+
}
|
|
2580
|
+
function parseNullableAspectRatio({ value, }) {
|
|
2581
|
+
if (value === "null") {
|
|
2582
|
+
return null;
|
|
2583
|
+
}
|
|
2584
|
+
return parseAspectRatio({ value });
|
|
2585
|
+
}
|
|
2586
|
+
function buildWorkspaceDefaults({ flags }) {
|
|
2587
|
+
const defaults = {
|
|
2588
|
+
aspectRatio: parseNullableAspectRatio({
|
|
2589
|
+
value: getStringFlag({ flags, name: "aspect-ratio" }),
|
|
2590
|
+
}),
|
|
2591
|
+
orientation: parseOrientation({
|
|
2592
|
+
value: getStringFlag({ flags, name: "orientation" }),
|
|
2593
|
+
}),
|
|
2594
|
+
resolution: parseResolution({
|
|
2595
|
+
value: getStringFlag({ flags, name: "resolution" }),
|
|
2596
|
+
}),
|
|
2597
|
+
frameRate: parseFrameRate({
|
|
2598
|
+
value: getStringFlag({ flags, name: "frame-rate" }),
|
|
2599
|
+
}),
|
|
2600
|
+
autoManageBroll: getOptionalBooleanFlag({
|
|
2601
|
+
flags,
|
|
2602
|
+
name: "auto-manage-broll",
|
|
2603
|
+
}),
|
|
2604
|
+
captionThemeId: parseNullableStringFlag({
|
|
2605
|
+
flags,
|
|
2606
|
+
name: "caption-theme-id",
|
|
2607
|
+
}),
|
|
2608
|
+
};
|
|
2609
|
+
if (defaults.aspectRatio === undefined &&
|
|
2610
|
+
defaults.orientation === undefined &&
|
|
2611
|
+
defaults.resolution === undefined &&
|
|
2612
|
+
defaults.frameRate === undefined &&
|
|
2613
|
+
defaults.autoManageBroll === undefined &&
|
|
2614
|
+
defaults.captionThemeId === undefined) {
|
|
2615
|
+
return undefined;
|
|
2616
|
+
}
|
|
2617
|
+
return defaults;
|
|
2618
|
+
}
|
|
2619
|
+
const TEXT_STYLE_FLAG_NAMES = [
|
|
2620
|
+
"font-family",
|
|
2621
|
+
"font-size",
|
|
2622
|
+
"font-weight",
|
|
2623
|
+
"color",
|
|
2624
|
+
"background-color",
|
|
2625
|
+
"padding-x",
|
|
2626
|
+
"padding-y",
|
|
2627
|
+
"border-radius",
|
|
2628
|
+
"top",
|
|
2629
|
+
"left",
|
|
2630
|
+
"text-align",
|
|
2631
|
+
"text-shadow",
|
|
2632
|
+
"no-background",
|
|
2633
|
+
];
|
|
2634
|
+
function hasTextStyleFlags({ flags }) {
|
|
2635
|
+
return TEXT_STYLE_FLAG_NAMES.some((name) => name in flags);
|
|
2636
|
+
}
|
|
2637
|
+
function buildTextOverlaySpec({ text, name, durationSeconds, flags, }) {
|
|
2638
|
+
const durationInFrames = Math.max(1, Math.round(durationSeconds * 30));
|
|
2639
|
+
const top = getStringFlag({ flags, name: "top" }) ?? "18%";
|
|
2640
|
+
const left = getStringFlag({ flags, name: "left" }) ?? "50%";
|
|
2641
|
+
const textStyle = buildTextStyle({ flags });
|
|
2642
|
+
return {
|
|
2643
|
+
id: `cli-${Date.now()}`,
|
|
2644
|
+
fps: 30,
|
|
2645
|
+
mode: "elements",
|
|
2646
|
+
name: name ?? text,
|
|
2647
|
+
fonts: [String(textStyle.fontFamily ?? "Inter")],
|
|
2648
|
+
width: 1080,
|
|
2649
|
+
height: 1920,
|
|
2650
|
+
defaults: {
|
|
2651
|
+
color: textStyle.color,
|
|
2652
|
+
fontSize: textStyle.fontSize,
|
|
2653
|
+
fontFamily: textStyle.fontFamily,
|
|
2654
|
+
},
|
|
2655
|
+
elements: [
|
|
2656
|
+
{
|
|
2657
|
+
id: `group-${Date.now()}`,
|
|
2658
|
+
type: "group",
|
|
2659
|
+
style: {
|
|
2660
|
+
top,
|
|
2661
|
+
left,
|
|
2662
|
+
display: "inline-flex",
|
|
2663
|
+
opacity: {
|
|
2664
|
+
keyframes: [
|
|
2665
|
+
{ frame: 0, value: 0 },
|
|
2666
|
+
{ frame: 10, value: 1 },
|
|
2667
|
+
{ frame: Math.max(10, durationInFrames - 10), value: 1 },
|
|
2668
|
+
{ frame: durationInFrames, value: 0 },
|
|
2669
|
+
],
|
|
2670
|
+
},
|
|
2671
|
+
position: "absolute",
|
|
2672
|
+
transform: "translate(-50%, -50%)",
|
|
2673
|
+
alignItems: "center",
|
|
2674
|
+
flexDirection: "column",
|
|
2675
|
+
},
|
|
2676
|
+
children: [
|
|
2677
|
+
{
|
|
2678
|
+
id: `text-${Date.now()}`,
|
|
2679
|
+
type: "text",
|
|
2680
|
+
style: textStyle,
|
|
2681
|
+
layout: "none",
|
|
2682
|
+
content: text,
|
|
2683
|
+
},
|
|
2684
|
+
],
|
|
2685
|
+
},
|
|
2686
|
+
],
|
|
2687
|
+
metadata: {
|
|
2688
|
+
kind: "overlay",
|
|
2689
|
+
createdBy: "blitzreels-cli",
|
|
2690
|
+
},
|
|
2691
|
+
durationInFrames,
|
|
2692
|
+
};
|
|
2693
|
+
}
|
|
2694
|
+
function buildTextStyle({ flags }) {
|
|
2695
|
+
const noBackground = getBooleanFlag({ flags, name: "no-background" });
|
|
2696
|
+
const backgroundColor = noBackground
|
|
2697
|
+
? "transparent"
|
|
2698
|
+
: (getStringFlag({ flags, name: "background-color" }) ??
|
|
2699
|
+
"rgba(17, 24, 39, 0.86)");
|
|
2700
|
+
const style = {
|
|
2701
|
+
color: getStringFlag({ flags, name: "color" }) ?? "#FFFFFF",
|
|
2702
|
+
padding: `${getNumberFlag({ flags, name: "padding-y" }) ?? 12}px ${getNumberFlag({ flags, name: "padding-x" }) ?? 24}px`,
|
|
2703
|
+
fontSize: getNumberFlag({ flags, name: "font-size" }) ?? 48,
|
|
2704
|
+
textAlign: parseTextAlign({
|
|
2705
|
+
value: getStringFlag({ flags, name: "text-align" }) ?? "center",
|
|
2706
|
+
}),
|
|
2707
|
+
fontFamily: getStringFlag({ flags, name: "font-family" }) ?? "Inter",
|
|
2708
|
+
fontWeight: getStringFlag({ flags, name: "font-weight" }) ?? "700",
|
|
2709
|
+
lineHeight: 1.12,
|
|
2710
|
+
textShadow: getStringFlag({ flags, name: "text-shadow" }) ??
|
|
2711
|
+
"0 8px 22px rgba(0, 0, 0, 0.52)",
|
|
2712
|
+
borderRadius: getNumberFlag({ flags, name: "border-radius" }) ?? 18,
|
|
2713
|
+
letterSpacing: 0,
|
|
2714
|
+
backgroundColor,
|
|
2715
|
+
bubbleJoinStyle: "reverse-radius",
|
|
2716
|
+
boxDecorationBreak: "clone",
|
|
2717
|
+
};
|
|
2718
|
+
return style;
|
|
2719
|
+
}
|
|
2720
|
+
async function buildUpdatedTextSpec({ context, projectId, contentItemId, nextText, flags, }) {
|
|
2721
|
+
const items = await context.client.text.list({ projectId });
|
|
2722
|
+
const item = items.data.find((candidate) => candidate.content_item_id === contentItemId);
|
|
2723
|
+
if (!item?.spec) {
|
|
2724
|
+
throw new Error(`Content item ${contentItemId} has no editable text spec.`);
|
|
2725
|
+
}
|
|
2726
|
+
return applyTextStyleToSpec({
|
|
2727
|
+
spec: item.spec,
|
|
2728
|
+
text: nextText,
|
|
2729
|
+
flags,
|
|
2730
|
+
});
|
|
2731
|
+
}
|
|
2732
|
+
function applyTextStyleToSpec({ spec, text, flags, }) {
|
|
2733
|
+
const cloned = JSON.parse(JSON.stringify(spec));
|
|
2734
|
+
const elements = Array.isArray(cloned.elements) ? cloned.elements : [];
|
|
2735
|
+
const firstGroup = elements.find((element) => Boolean(element) &&
|
|
2736
|
+
typeof element === "object" &&
|
|
2737
|
+
element.type === "group");
|
|
2738
|
+
const children = Array.isArray(firstGroup?.children)
|
|
2739
|
+
? firstGroup.children
|
|
2740
|
+
: [];
|
|
2741
|
+
const firstText = children.find((child) => Boolean(child) &&
|
|
2742
|
+
typeof child === "object" &&
|
|
2743
|
+
child.type === "text");
|
|
2744
|
+
if (!firstText) {
|
|
2745
|
+
throw new Error("Text overlay spec has no editable text element.");
|
|
2746
|
+
}
|
|
2747
|
+
firstText.style = {
|
|
2748
|
+
...(firstText.style ?? {}),
|
|
2749
|
+
...buildTextStyle({ flags }),
|
|
2750
|
+
};
|
|
2751
|
+
if (text !== undefined) {
|
|
2752
|
+
firstText.content = text;
|
|
2753
|
+
}
|
|
2754
|
+
if (firstGroup) {
|
|
2755
|
+
const style = (firstGroup.style ??
|
|
2756
|
+
{});
|
|
2757
|
+
firstGroup.style = {
|
|
2758
|
+
...style,
|
|
2759
|
+
top: getStringFlag({ flags, name: "top" }) ?? style.top,
|
|
2760
|
+
left: getStringFlag({ flags, name: "left" }) ?? style.left,
|
|
2761
|
+
};
|
|
2762
|
+
}
|
|
2763
|
+
return cloned;
|
|
2764
|
+
}
|
|
2765
|
+
function parseTextAlign({ value, }) {
|
|
2766
|
+
if (value === "left" ||
|
|
2767
|
+
value === "center" ||
|
|
2768
|
+
value === "right" ||
|
|
2769
|
+
value === "justify") {
|
|
2770
|
+
return value;
|
|
2771
|
+
}
|
|
2772
|
+
throw new Error("--text-align must be left, center, right, or justify.");
|
|
2773
|
+
}
|
|
2774
|
+
function buildCaptionWordSelector({ flags }) {
|
|
2775
|
+
const wordIds = parseCsvFlag({ flags, name: "word-ids" }) ??
|
|
2776
|
+
parseCsvFlag({ flags, name: "word-id" });
|
|
2777
|
+
return {
|
|
2778
|
+
projectId: requireStringFlag({ flags, name: "project-id" }),
|
|
2779
|
+
wordIds,
|
|
2780
|
+
matchText: getStringFlag({ flags, name: "match-text" }),
|
|
2781
|
+
matchPattern: parseCaptionMatchPattern({
|
|
2782
|
+
value: getStringFlag({ flags, name: "match-pattern" }),
|
|
2783
|
+
}),
|
|
2784
|
+
timelineItemId: getStringFlag({ flags, name: "timeline-item-id" }),
|
|
2785
|
+
};
|
|
2786
|
+
}
|
|
2787
|
+
function buildCaptionWordStyleFromFlags({ flags, }) {
|
|
2788
|
+
const style = {};
|
|
2789
|
+
setIfDefined({
|
|
2790
|
+
target: style,
|
|
2791
|
+
key: "fontFamily",
|
|
2792
|
+
value: getStringFlag({ flags, name: "font-family" }),
|
|
2793
|
+
});
|
|
2794
|
+
setIfDefined({
|
|
2795
|
+
target: style,
|
|
2796
|
+
key: "fontWeight",
|
|
2797
|
+
value: getStringFlag({ flags, name: "font-weight" }),
|
|
2798
|
+
});
|
|
2799
|
+
setIfDefined({
|
|
2800
|
+
target: style,
|
|
2801
|
+
key: "color",
|
|
2802
|
+
value: getStringFlag({ flags, name: "color" }),
|
|
2803
|
+
});
|
|
2804
|
+
setIfDefined({
|
|
2805
|
+
target: style,
|
|
2806
|
+
key: "activeColor",
|
|
2807
|
+
value: getStringFlag({ flags, name: "active-color" }),
|
|
2808
|
+
});
|
|
2809
|
+
setIfDefined({
|
|
2810
|
+
target: style,
|
|
2811
|
+
key: "backgroundColor",
|
|
2812
|
+
value: getStringFlag({ flags, name: "background-color" }),
|
|
2813
|
+
});
|
|
2814
|
+
setIfDefined({
|
|
2815
|
+
target: style,
|
|
2816
|
+
key: "fontStyle",
|
|
2817
|
+
value: getStringFlag({ flags, name: "font-style" }),
|
|
2818
|
+
});
|
|
2819
|
+
setIfDefined({
|
|
2820
|
+
target: style,
|
|
2821
|
+
key: "activeAnimation",
|
|
2822
|
+
value: getStringFlag({ flags, name: "active-animation" }),
|
|
2823
|
+
});
|
|
2824
|
+
setIfDefined({
|
|
2825
|
+
target: style,
|
|
2826
|
+
key: "textTransform",
|
|
2827
|
+
value: getStringFlag({ flags, name: "text-transform" }),
|
|
2828
|
+
});
|
|
2829
|
+
setIfDefined({
|
|
2830
|
+
target: style,
|
|
2831
|
+
key: "textShadow",
|
|
2832
|
+
value: getStringFlag({ flags, name: "text-shadow" }),
|
|
2833
|
+
});
|
|
2834
|
+
setIfDefined({
|
|
2835
|
+
target: style,
|
|
2836
|
+
key: "glowColor",
|
|
2837
|
+
value: getStringFlag({ flags, name: "glow-color" }),
|
|
2838
|
+
});
|
|
2839
|
+
setIfDefined({
|
|
2840
|
+
target: style,
|
|
2841
|
+
key: "scale",
|
|
2842
|
+
value: getNumberFlag({ flags, name: "scale" }),
|
|
2843
|
+
});
|
|
2844
|
+
setIfDefined({
|
|
2845
|
+
target: style,
|
|
2846
|
+
key: "offsetX",
|
|
2847
|
+
value: getNumberFlag({ flags, name: "offset-x" }),
|
|
2848
|
+
});
|
|
2849
|
+
setIfDefined({
|
|
2850
|
+
target: style,
|
|
2851
|
+
key: "offsetY",
|
|
2852
|
+
value: getNumberFlag({ flags, name: "offset-y" }),
|
|
2853
|
+
});
|
|
2854
|
+
setIfDefined({
|
|
2855
|
+
target: style,
|
|
2856
|
+
key: "rotationDeg",
|
|
2857
|
+
value: getNumberFlag({ flags, name: "rotation-deg" }),
|
|
2858
|
+
});
|
|
2859
|
+
setIfDefined({
|
|
2860
|
+
target: style,
|
|
2861
|
+
key: "letterSpacing",
|
|
2862
|
+
value: getNumberFlag({ flags, name: "letter-spacing" }),
|
|
2863
|
+
});
|
|
2864
|
+
setIfDefined({
|
|
2865
|
+
target: style,
|
|
2866
|
+
key: "opacity",
|
|
2867
|
+
value: getNumberFlag({ flags, name: "opacity" }),
|
|
2868
|
+
});
|
|
2869
|
+
setIfDefined({
|
|
2870
|
+
target: style,
|
|
2871
|
+
key: "glowBlurPx",
|
|
2872
|
+
value: getNumberFlag({ flags, name: "glow-blur-px" }),
|
|
2873
|
+
});
|
|
2874
|
+
if (Object.keys(style).length === 0) {
|
|
2875
|
+
throw new Error("Provide --style-json or at least one caption word style flag.");
|
|
2876
|
+
}
|
|
2877
|
+
return style;
|
|
2878
|
+
}
|
|
2879
|
+
function setIfDefined({ target, key, value, }) {
|
|
2880
|
+
if (value !== undefined) {
|
|
2881
|
+
target[key] = value;
|
|
2882
|
+
}
|
|
2883
|
+
}
|
|
927
2884
|
function parseContextMode({ value, }) {
|
|
928
2885
|
if (value === "summary" ||
|
|
929
2886
|
value === "assets" ||
|
|
@@ -950,30 +2907,7 @@ function printHelp() {
|
|
|
950
2907
|
console.log(`blitzreels
|
|
951
2908
|
|
|
952
2909
|
Usage:
|
|
953
|
-
|
|
954
|
-
blitzreels auth set-key --api-key KEY [--base-url URL]
|
|
955
|
-
blitzreels auth whoami [--json]
|
|
956
|
-
blitzreels project inspect --project-id ID [--mode full] [--json]
|
|
957
|
-
blitzreels project timeline --project-id ID [--json]
|
|
958
|
-
blitzreels project snapshot --project-id ID --at 00:03 [--json]
|
|
959
|
-
blitzreels project snapshots --project-id ID --at 00:01,00:03 [--json]
|
|
960
|
-
blitzreels text add --project-id ID --text "Hook" [--at 0] [--duration 4]
|
|
961
|
-
blitzreels text list --project-id ID [--json]
|
|
962
|
-
blitzreels text update --project-id ID --content-item-id ID [--text "New hook"]
|
|
963
|
-
blitzreels text remove --project-id ID --content-item-id ID
|
|
964
|
-
blitzreels audio add --project-id ID --asset-id ID [--at 0] [--volume 0.6]
|
|
965
|
-
blitzreels silence plan --project-id ID --timeline-item-id ID [--json]
|
|
966
|
-
blitzreels silence apply --project-id ID --timeline-item-id ID --confirm-preview
|
|
967
|
-
blitzreels mistakes plan --project-id ID --timeline-item-id ID [--json]
|
|
968
|
-
blitzreels mistakes apply --project-id ID --timeline-item-id ID --confirm-preview
|
|
969
|
-
blitzreels captions themes list [--json]
|
|
970
|
-
blitzreels captions theme set --project-id ID --theme-id ID
|
|
971
|
-
blitzreels captions theme set-default --theme-id ID
|
|
972
|
-
blitzreels exports start --project-id ID [--resolution 1080p] [--format mp4]
|
|
973
|
-
blitzreels exports wait --export-id ID [--json]
|
|
974
|
-
blitzreels exports download --export-id ID --output ./short.mp4
|
|
975
|
-
blitzreels api docs
|
|
976
|
-
blitzreels api spec
|
|
2910
|
+
${COMMAND_SPECS.map((spec) => ` ${spec.usage}`).join("\n")}
|
|
977
2911
|
|
|
978
2912
|
Global flags:
|
|
979
2913
|
--api-key KEY
|
|
@@ -982,14 +2916,69 @@ Global flags:
|
|
|
982
2916
|
`);
|
|
983
2917
|
}
|
|
984
2918
|
main().catch((error) => {
|
|
2919
|
+
const json = isJsonOutputRequested();
|
|
985
2920
|
if (error instanceof BlitzReelsError) {
|
|
986
|
-
|
|
987
|
-
|
|
2921
|
+
printCliError({
|
|
2922
|
+
json,
|
|
2923
|
+
code: "api_error",
|
|
2924
|
+
message: `BlitzReels API error ${error.status}: ${error.message}`,
|
|
2925
|
+
status: error.status,
|
|
2926
|
+
requestId: error.requestId ?? undefined,
|
|
2927
|
+
usage: usageForCurrentCommand(),
|
|
2928
|
+
});
|
|
988
2929
|
process.exitCode = 1;
|
|
989
2930
|
return;
|
|
990
2931
|
}
|
|
991
|
-
|
|
992
|
-
|
|
2932
|
+
if (error instanceof CliUsageError) {
|
|
2933
|
+
printCliError({
|
|
2934
|
+
json,
|
|
2935
|
+
code: error.code,
|
|
2936
|
+
message: error.message,
|
|
2937
|
+
suggestions: error.suggestions,
|
|
2938
|
+
usage: error.usage,
|
|
2939
|
+
});
|
|
2940
|
+
process.exitCode = 1;
|
|
2941
|
+
return;
|
|
2942
|
+
}
|
|
2943
|
+
const usage = usageForCurrentCommand();
|
|
2944
|
+
printCliError({
|
|
2945
|
+
json,
|
|
2946
|
+
code: "cli_error",
|
|
2947
|
+
message: error instanceof Error ? error.message : String(error),
|
|
2948
|
+
usage,
|
|
2949
|
+
});
|
|
993
2950
|
process.exitCode = 1;
|
|
994
2951
|
});
|
|
2952
|
+
function isJsonOutputRequested() {
|
|
2953
|
+
return process.argv.slice(2).some((value) => value === "--json");
|
|
2954
|
+
}
|
|
2955
|
+
function printCliError({ json, code, message, suggestions = [], usage, status, requestId, }) {
|
|
2956
|
+
if (json) {
|
|
2957
|
+
console.error(JSON.stringify({
|
|
2958
|
+
error: {
|
|
2959
|
+
code,
|
|
2960
|
+
message,
|
|
2961
|
+
suggestions,
|
|
2962
|
+
usage,
|
|
2963
|
+
status,
|
|
2964
|
+
request_id: requestId,
|
|
2965
|
+
},
|
|
2966
|
+
}, null, 2));
|
|
2967
|
+
return;
|
|
2968
|
+
}
|
|
2969
|
+
console.error(message);
|
|
2970
|
+
if (suggestions.length > 0) {
|
|
2971
|
+
console.error("Did you mean?");
|
|
2972
|
+
for (const suggestion of suggestions) {
|
|
2973
|
+
console.error(` ${suggestion.startsWith("--") ? suggestion : suggestion}`);
|
|
2974
|
+
}
|
|
2975
|
+
}
|
|
2976
|
+
if (usage) {
|
|
2977
|
+
console.error("Usage:");
|
|
2978
|
+
console.error(` ${usage}`);
|
|
2979
|
+
}
|
|
2980
|
+
if (requestId) {
|
|
2981
|
+
console.error(`Request ID: ${requestId}`);
|
|
2982
|
+
}
|
|
2983
|
+
}
|
|
995
2984
|
//# sourceMappingURL=index.js.map
|