hyper-animator-codex 0.5.0 → 0.7.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 +37 -3
- package/package.json +1 -1
- package/skills/hyper-animator-codex/SKILL.md +23 -18
- package/skills/hyper-animator-codex/references/minimax-music-workflow.md +11 -0
- package/skills/hyper-animator-codex/references/narration-audio-workflow.md +110 -0
- package/skills/hyper-animator-codex/scripts/detect_project_files.mjs +178 -0
- package/skills/hyper-animator-codex/scripts/generate_minimax_music.mjs +279 -82
- package/skills/hyper-animator-codex/scripts/generate_minimax_tts.mjs +556 -0
- package/skills/hyper-animator-codex/scripts/generate_subtitles.mjs +181 -0
|
@@ -1,5 +1,5 @@
|
|
|
1
1
|
#!/usr/bin/env node
|
|
2
|
-
import { mkdir, writeFile } from "node:fs/promises";
|
|
2
|
+
import { mkdir, readFile, writeFile } from "node:fs/promises";
|
|
3
3
|
import { dirname, join } from "node:path";
|
|
4
4
|
import { fileURLToPath } from "node:url";
|
|
5
5
|
|
|
@@ -10,7 +10,7 @@ import {
|
|
|
10
10
|
validateMinimaxConfig,
|
|
11
11
|
} from "./minimax_runtime_config.mjs";
|
|
12
12
|
|
|
13
|
-
const
|
|
13
|
+
const DEFAULT_ENDPOINT = "https://api.minimaxi.com/v1/music_generation";
|
|
14
14
|
const scriptDir = dirname(fileURLToPath(import.meta.url));
|
|
15
15
|
const skillRoot = dirname(scriptDir);
|
|
16
16
|
|
|
@@ -28,15 +28,16 @@ function parseArgs(args) {
|
|
|
28
28
|
const parsed = {
|
|
29
29
|
prompt: undefined,
|
|
30
30
|
lyrics: undefined,
|
|
31
|
-
lyrics_optimizer:
|
|
32
|
-
is_instrumental:
|
|
33
|
-
output_dir:
|
|
34
|
-
output_format:
|
|
35
|
-
audio_format:
|
|
36
|
-
sample_rate:
|
|
37
|
-
bitrate:
|
|
31
|
+
lyrics_optimizer: undefined,
|
|
32
|
+
is_instrumental: undefined,
|
|
33
|
+
output_dir: undefined,
|
|
34
|
+
output_format: undefined,
|
|
35
|
+
audio_format: undefined,
|
|
36
|
+
sample_rate: undefined,
|
|
37
|
+
bitrate: undefined,
|
|
38
38
|
model: undefined,
|
|
39
39
|
config_path: undefined,
|
|
40
|
+
request_json: undefined,
|
|
40
41
|
dry_run: false,
|
|
41
42
|
};
|
|
42
43
|
|
|
@@ -76,6 +77,9 @@ function parseArgs(args) {
|
|
|
76
77
|
} else if (arg === "--config") {
|
|
77
78
|
parsed.config_path = requireValue(args, index, arg);
|
|
78
79
|
index += 1;
|
|
80
|
+
} else if (arg === "--request-json") {
|
|
81
|
+
parsed.request_json = requireValue(args, index, arg);
|
|
82
|
+
index += 1;
|
|
79
83
|
} else if (arg === "--dry-run") {
|
|
80
84
|
parsed.dry_run = true;
|
|
81
85
|
} else if (arg === "--help" || arg === "-h") {
|
|
@@ -105,41 +109,108 @@ Options:
|
|
|
105
109
|
--bitrate <bits> 32000, 64000, 128000, or 256000; default 256000
|
|
106
110
|
--model <model> music-2.6 or music-2.6-free
|
|
107
111
|
--config <file> Explicit MiniMax config JSON
|
|
112
|
+
--request-json <file> Read request options from a JSON file
|
|
108
113
|
--dry-run Print redacted request without contacting MiniMax
|
|
109
114
|
`);
|
|
110
115
|
}
|
|
111
116
|
|
|
117
|
+
async function readRequestJson(path) {
|
|
118
|
+
if (!path) {
|
|
119
|
+
return {};
|
|
120
|
+
}
|
|
121
|
+
|
|
122
|
+
const parsed = JSON.parse(await readFile(path, "utf8"));
|
|
123
|
+
|
|
124
|
+
if (!parsed || typeof parsed !== "object" || Array.isArray(parsed)) {
|
|
125
|
+
throw Object.assign(new Error(`--request-json must contain a JSON object: ${path}`), {
|
|
126
|
+
kind: "validation",
|
|
127
|
+
});
|
|
128
|
+
}
|
|
129
|
+
|
|
130
|
+
return parsed;
|
|
131
|
+
}
|
|
132
|
+
|
|
133
|
+
function mergeOptions(jsonOptions, cliOptions) {
|
|
134
|
+
return {
|
|
135
|
+
...cliOptions,
|
|
136
|
+
prompt: cliOptions.prompt ?? jsonOptions.prompt,
|
|
137
|
+
lyrics: cliOptions.lyrics ?? jsonOptions.lyrics,
|
|
138
|
+
lyrics_optimizer: cliOptions.lyrics_optimizer ?? Boolean(jsonOptions.lyrics_optimizer),
|
|
139
|
+
is_instrumental: cliOptions.is_instrumental ?? jsonOptions.is_instrumental ?? true,
|
|
140
|
+
output_dir: cliOptions.output_dir ?? jsonOptions.output_dir ?? join(process.cwd(), "hyper-animator-output", "music"),
|
|
141
|
+
output_format: cliOptions.output_format ?? jsonOptions.output_format ?? "hex",
|
|
142
|
+
audio_format: cliOptions.audio_format ?? jsonOptions.audio_format ?? "mp3",
|
|
143
|
+
sample_rate: cliOptions.sample_rate ?? jsonOptions.sample_rate ?? 44100,
|
|
144
|
+
bitrate: cliOptions.bitrate ?? jsonOptions.bitrate ?? 256000,
|
|
145
|
+
model: cliOptions.model ?? jsonOptions.model,
|
|
146
|
+
};
|
|
147
|
+
}
|
|
148
|
+
|
|
149
|
+
function endpointFromEnv(env = process.env) {
|
|
150
|
+
return env.MINIMAX_MUSIC_ENDPOINT || DEFAULT_ENDPOINT;
|
|
151
|
+
}
|
|
152
|
+
|
|
153
|
+
function timeoutFromEnv(env = process.env) {
|
|
154
|
+
const value = Number.parseInt(env.MINIMAX_REQUEST_TIMEOUT_MS || "60000", 10);
|
|
155
|
+
return Number.isFinite(value) && value > 0 ? value : 60000;
|
|
156
|
+
}
|
|
157
|
+
|
|
158
|
+
function makeErrorEnvelope(error, attempts = 0) {
|
|
159
|
+
return {
|
|
160
|
+
ok: false,
|
|
161
|
+
provider: "minimax",
|
|
162
|
+
error: {
|
|
163
|
+
kind: error.kind || "validation",
|
|
164
|
+
message: error.message,
|
|
165
|
+
retryable: Boolean(error.retryable),
|
|
166
|
+
http_status: error.http_status ?? null,
|
|
167
|
+
status_code: error.status_code ?? null,
|
|
168
|
+
},
|
|
169
|
+
attempts,
|
|
170
|
+
};
|
|
171
|
+
}
|
|
172
|
+
|
|
173
|
+
function minimaxError(message, fields = {}) {
|
|
174
|
+
const error = new Error(message);
|
|
175
|
+
Object.assign(error, fields);
|
|
176
|
+
return error;
|
|
177
|
+
}
|
|
178
|
+
|
|
179
|
+
function printJson(payload) {
|
|
180
|
+
console.log(JSON.stringify(payload, null, 2));
|
|
181
|
+
}
|
|
182
|
+
|
|
112
183
|
function validateOptions(options) {
|
|
113
184
|
if (!options.prompt || options.prompt.trim().length === 0) {
|
|
114
|
-
throw new Error("--prompt is required");
|
|
185
|
+
throw Object.assign(new Error("--prompt is required"), { kind: "validation" });
|
|
115
186
|
}
|
|
116
187
|
|
|
117
188
|
if (options.prompt.length > 2000) {
|
|
118
|
-
throw new Error("--prompt must be 2000 characters or fewer");
|
|
189
|
+
throw Object.assign(new Error("--prompt must be 2000 characters or fewer"), { kind: "validation" });
|
|
119
190
|
}
|
|
120
191
|
|
|
121
192
|
if (!["hex", "url"].includes(options.output_format)) {
|
|
122
|
-
throw new Error("--output-format must be hex or url");
|
|
193
|
+
throw Object.assign(new Error("--output-format must be hex or url"), { kind: "validation" });
|
|
123
194
|
}
|
|
124
195
|
|
|
125
196
|
if (!["mp3", "wav", "pcm"].includes(options.audio_format)) {
|
|
126
|
-
throw new Error("--format must be mp3, wav, or pcm");
|
|
197
|
+
throw Object.assign(new Error("--format must be mp3, wav, or pcm"), { kind: "validation" });
|
|
127
198
|
}
|
|
128
199
|
|
|
129
200
|
if (![16000, 24000, 32000, 44100].includes(options.sample_rate)) {
|
|
130
|
-
throw new Error("--sample-rate must be 16000, 24000, 32000, or 44100");
|
|
201
|
+
throw Object.assign(new Error("--sample-rate must be 16000, 24000, 32000, or 44100"), { kind: "validation" });
|
|
131
202
|
}
|
|
132
203
|
|
|
133
204
|
if (![32000, 64000, 128000, 256000].includes(options.bitrate)) {
|
|
134
|
-
throw new Error("--bitrate must be 32000, 64000, 128000, or 256000");
|
|
205
|
+
throw Object.assign(new Error("--bitrate must be 32000, 64000, 128000, or 256000"), { kind: "validation" });
|
|
135
206
|
}
|
|
136
207
|
|
|
137
208
|
if (!options.is_instrumental && !options.lyrics && !options.lyrics_optimizer) {
|
|
138
|
-
throw new Error("Vocal MiniMax generation requires --lyrics or --lyrics-optimizer");
|
|
209
|
+
throw Object.assign(new Error("Vocal MiniMax generation requires --lyrics or --lyrics-optimizer"), { kind: "validation" });
|
|
139
210
|
}
|
|
140
211
|
|
|
141
212
|
if (options.lyrics && options.lyrics.length > 3500) {
|
|
142
|
-
throw new Error("--lyrics must be 3500 characters or fewer");
|
|
213
|
+
throw Object.assign(new Error("--lyrics must be 3500 characters or fewer"), { kind: "validation" });
|
|
143
214
|
}
|
|
144
215
|
}
|
|
145
216
|
|
|
@@ -202,48 +273,148 @@ function decodeHexAudio(value) {
|
|
|
202
273
|
return Buffer.from(value, "hex");
|
|
203
274
|
}
|
|
204
275
|
|
|
205
|
-
|
|
206
|
-
|
|
276
|
+
function classifyHttpStatus(status) {
|
|
277
|
+
if (status === 401 || status === 403) {
|
|
278
|
+
return { kind: "auth", retryable: false };
|
|
279
|
+
}
|
|
280
|
+
|
|
281
|
+
if (status === 429) {
|
|
282
|
+
return { kind: "rate_limit", retryable: true };
|
|
283
|
+
}
|
|
284
|
+
|
|
285
|
+
if (status >= 500) {
|
|
286
|
+
return { kind: "server", retryable: true };
|
|
287
|
+
}
|
|
288
|
+
|
|
289
|
+
return { kind: "response", retryable: false };
|
|
290
|
+
}
|
|
291
|
+
|
|
292
|
+
async function fetchWithTimeout(url, options, timeoutMs) {
|
|
293
|
+
if (!URL.canParse(url)) {
|
|
294
|
+
throw minimaxError(`MiniMax endpoint is not a valid URL: ${url}`, {
|
|
295
|
+
kind: "validation",
|
|
296
|
+
retryable: false,
|
|
297
|
+
});
|
|
298
|
+
}
|
|
299
|
+
|
|
300
|
+
const controller = new AbortController();
|
|
301
|
+
const timeout = setTimeout(() => controller.abort(), timeoutMs);
|
|
302
|
+
|
|
303
|
+
try {
|
|
304
|
+
return await fetch(url, { ...options, signal: controller.signal });
|
|
305
|
+
} catch (error) {
|
|
306
|
+
if (error?.name === "AbortError") {
|
|
307
|
+
throw minimaxError("MiniMax request timed out", { kind: "timeout", retryable: true });
|
|
308
|
+
}
|
|
309
|
+
|
|
310
|
+
if (typeof error?.cause?.code === "string") {
|
|
311
|
+
throw minimaxError(`MiniMax network error: ${error.message}`, { kind: "network", retryable: true });
|
|
312
|
+
}
|
|
313
|
+
|
|
314
|
+
throw minimaxError(`MiniMax request setup failed: ${error.message}`, {
|
|
315
|
+
kind: "response",
|
|
316
|
+
retryable: false,
|
|
317
|
+
});
|
|
318
|
+
} finally {
|
|
319
|
+
clearTimeout(timeout);
|
|
320
|
+
}
|
|
321
|
+
}
|
|
322
|
+
|
|
323
|
+
async function callMiniMax(config, request, { endpoint, timeoutMs }) {
|
|
324
|
+
const response = await fetchWithTimeout(endpoint, {
|
|
207
325
|
method: "POST",
|
|
208
326
|
headers: {
|
|
209
327
|
"Content-Type": "application/json",
|
|
210
328
|
Authorization: `Bearer ${config.api_key}`,
|
|
211
329
|
},
|
|
212
330
|
body: JSON.stringify(request),
|
|
213
|
-
});
|
|
331
|
+
}, timeoutMs);
|
|
214
332
|
|
|
215
333
|
const text = await response.text();
|
|
216
|
-
let json;
|
|
334
|
+
let json = null;
|
|
335
|
+
|
|
336
|
+
if (!response.ok) {
|
|
337
|
+
if (text) {
|
|
338
|
+
try {
|
|
339
|
+
json = JSON.parse(text);
|
|
340
|
+
} catch {
|
|
341
|
+
json = null;
|
|
342
|
+
}
|
|
343
|
+
}
|
|
344
|
+
|
|
345
|
+
const message = json?.base_resp?.status_msg || `HTTP ${response.status}`;
|
|
346
|
+
throw minimaxError(`MiniMax request failed: ${message}`, {
|
|
347
|
+
...classifyHttpStatus(response.status),
|
|
348
|
+
http_status: response.status,
|
|
349
|
+
status_code: json?.base_resp?.status_code ?? null,
|
|
350
|
+
});
|
|
351
|
+
}
|
|
217
352
|
|
|
218
353
|
try {
|
|
219
354
|
json = JSON.parse(text);
|
|
220
355
|
} catch (error) {
|
|
221
|
-
throw
|
|
222
|
-
|
|
223
|
-
|
|
224
|
-
|
|
225
|
-
|
|
226
|
-
throw new Error(`MiniMax request failed: ${message}`);
|
|
356
|
+
throw minimaxError(`MiniMax returned non-JSON response with HTTP ${response.status}`, {
|
|
357
|
+
kind: "response",
|
|
358
|
+
retryable: false,
|
|
359
|
+
http_status: response.status,
|
|
360
|
+
});
|
|
227
361
|
}
|
|
228
362
|
|
|
229
363
|
const statusCode = json.base_resp?.status_code;
|
|
230
364
|
if (statusCode !== 0) {
|
|
231
365
|
const message = json.base_resp?.status_msg || "unknown MiniMax error";
|
|
232
|
-
throw
|
|
366
|
+
throw minimaxError(`MiniMax request failed with status_code ${statusCode}: ${message}`, {
|
|
367
|
+
kind: "provider",
|
|
368
|
+
retryable: false,
|
|
369
|
+
http_status: response.status,
|
|
370
|
+
status_code: statusCode,
|
|
371
|
+
});
|
|
233
372
|
}
|
|
234
373
|
|
|
235
374
|
if (json.data?.status !== 2) {
|
|
236
|
-
throw
|
|
375
|
+
throw minimaxError(`MiniMax generation is not complete; data.status is ${json.data?.status}`, {
|
|
376
|
+
kind: "provider",
|
|
377
|
+
retryable: false,
|
|
378
|
+
http_status: response.status,
|
|
379
|
+
status_code: statusCode,
|
|
380
|
+
});
|
|
237
381
|
}
|
|
238
382
|
|
|
239
383
|
if (!json.data?.audio) {
|
|
240
|
-
throw
|
|
384
|
+
throw minimaxError("MiniMax response did not include data.audio", {
|
|
385
|
+
kind: "provider",
|
|
386
|
+
retryable: false,
|
|
387
|
+
http_status: response.status,
|
|
388
|
+
status_code: statusCode,
|
|
389
|
+
});
|
|
241
390
|
}
|
|
242
391
|
|
|
243
392
|
return json;
|
|
244
393
|
}
|
|
245
394
|
|
|
246
|
-
async function
|
|
395
|
+
async function callMiniMaxWithRetries(config, request, { endpoint, timeoutMs, maxRetries = 2 }) {
|
|
396
|
+
let attempts = 0;
|
|
397
|
+
|
|
398
|
+
while (attempts <= maxRetries) {
|
|
399
|
+
attempts += 1;
|
|
400
|
+
|
|
401
|
+
try {
|
|
402
|
+
return {
|
|
403
|
+
responseJson: await callMiniMax(config, request, { endpoint, timeoutMs }),
|
|
404
|
+
attempts,
|
|
405
|
+
};
|
|
406
|
+
} catch (error) {
|
|
407
|
+
if (!error.retryable || attempts > maxRetries) {
|
|
408
|
+
error.attempts = attempts;
|
|
409
|
+
throw error;
|
|
410
|
+
}
|
|
411
|
+
}
|
|
412
|
+
}
|
|
413
|
+
|
|
414
|
+
throw minimaxError("MiniMax request failed", { kind: "network", retryable: true, attempts });
|
|
415
|
+
}
|
|
416
|
+
|
|
417
|
+
async function writeAudioOutput({ responseJson, request, outputDir, prompt, endpoint }) {
|
|
247
418
|
await mkdir(outputDir, { recursive: true });
|
|
248
419
|
|
|
249
420
|
const timestamp = new Date().toISOString().replace(/[:.]/g, "-");
|
|
@@ -261,7 +432,7 @@ async function writeAudioOutput({ responseJson, request, outputDir, prompt }) {
|
|
|
261
432
|
metadataPath,
|
|
262
433
|
`${JSON.stringify({
|
|
263
434
|
provider: "minimax",
|
|
264
|
-
endpoint
|
|
435
|
+
endpoint,
|
|
265
436
|
request,
|
|
266
437
|
response: {
|
|
267
438
|
base_resp: responseJson.base_resp,
|
|
@@ -283,64 +454,90 @@ async function writeAudioOutput({ responseJson, request, outputDir, prompt }) {
|
|
|
283
454
|
}
|
|
284
455
|
|
|
285
456
|
async function main() {
|
|
286
|
-
|
|
457
|
+
try {
|
|
458
|
+
const cliOptions = parseArgs(process.argv.slice(2));
|
|
459
|
+
const endpoint = endpointFromEnv();
|
|
460
|
+
const timeoutMs = timeoutFromEnv();
|
|
287
461
|
|
|
288
|
-
|
|
289
|
-
|
|
290
|
-
|
|
291
|
-
|
|
462
|
+
if (cliOptions.help) {
|
|
463
|
+
printHelp();
|
|
464
|
+
return;
|
|
465
|
+
}
|
|
292
466
|
|
|
293
|
-
|
|
467
|
+
const requestJson = await readRequestJson(cliOptions.request_json);
|
|
468
|
+
const options = mergeOptions(requestJson, cliOptions);
|
|
294
469
|
|
|
295
|
-
|
|
296
|
-
skillRoot,
|
|
297
|
-
env: process.env,
|
|
298
|
-
configPath: options.config_path,
|
|
299
|
-
});
|
|
470
|
+
validateOptions(options);
|
|
300
471
|
|
|
301
|
-
|
|
302
|
-
|
|
303
|
-
|
|
472
|
+
let runtime;
|
|
473
|
+
try {
|
|
474
|
+
runtime = await readMinimaxRuntimeConfig({
|
|
475
|
+
skillRoot,
|
|
476
|
+
env: process.env,
|
|
477
|
+
configPath: options.config_path,
|
|
478
|
+
});
|
|
479
|
+
} catch (error) {
|
|
480
|
+
throw Object.assign(error, { kind: "config", retryable: false });
|
|
481
|
+
}
|
|
482
|
+
|
|
483
|
+
if (!runtime) {
|
|
484
|
+
throw Object.assign(new Error("MiniMax config not found. Run hyper-animator-codex install with --minimax-api-key and --minimax-group-id, or set MINIMAX_API_KEY and MINIMAX_GROUP_ID."), {
|
|
485
|
+
kind: "config",
|
|
486
|
+
retryable: false,
|
|
487
|
+
});
|
|
488
|
+
}
|
|
489
|
+
|
|
490
|
+
let request;
|
|
491
|
+
try {
|
|
492
|
+
request = buildRequest(options, runtime.config);
|
|
493
|
+
} catch (error) {
|
|
494
|
+
throw Object.assign(error, { kind: "config", retryable: false });
|
|
495
|
+
}
|
|
496
|
+
const redactedConfig = redactMinimaxConfig(runtime.config);
|
|
497
|
+
|
|
498
|
+
if (options.dry_run) {
|
|
499
|
+
printJson({
|
|
500
|
+
ok: true,
|
|
501
|
+
dry_run: true,
|
|
502
|
+
provider: "minimax",
|
|
503
|
+
endpoint,
|
|
504
|
+
config: {
|
|
505
|
+
source: runtime.source,
|
|
506
|
+
path: runtime.configPath,
|
|
507
|
+
redacted: redactedConfig,
|
|
508
|
+
},
|
|
509
|
+
request_path: cliOptions.request_json,
|
|
510
|
+
request,
|
|
511
|
+
output_dir: options.output_dir,
|
|
512
|
+
});
|
|
513
|
+
return;
|
|
514
|
+
}
|
|
304
515
|
|
|
305
|
-
|
|
306
|
-
|
|
516
|
+
const { responseJson, attempts } = await callMiniMaxWithRetries(runtime.config, request, {
|
|
517
|
+
endpoint,
|
|
518
|
+
timeoutMs,
|
|
519
|
+
});
|
|
520
|
+
const output = await writeAudioOutput({
|
|
521
|
+
responseJson,
|
|
522
|
+
request,
|
|
523
|
+
outputDir: options.output_dir,
|
|
524
|
+
prompt: options.prompt,
|
|
525
|
+
endpoint,
|
|
526
|
+
});
|
|
307
527
|
|
|
308
|
-
|
|
309
|
-
console.log(JSON.stringify({
|
|
528
|
+
printJson({
|
|
310
529
|
ok: true,
|
|
311
|
-
dry_run: true,
|
|
312
530
|
provider: "minimax",
|
|
313
|
-
|
|
314
|
-
|
|
315
|
-
|
|
316
|
-
|
|
317
|
-
|
|
318
|
-
|
|
319
|
-
|
|
320
|
-
|
|
321
|
-
|
|
322
|
-
return;
|
|
531
|
+
model: request.model,
|
|
532
|
+
attempts,
|
|
533
|
+
output_path: output.audioPath,
|
|
534
|
+
metadata_path: output.metadataPath,
|
|
535
|
+
beat_analysis_command: `python3 scripts/analyze_music_beats.py ${output.audioPath} -o ${join(options.output_dir, "beat-map.json")} --fps 60 --pretty`,
|
|
536
|
+
});
|
|
537
|
+
} catch (error) {
|
|
538
|
+
printJson(makeErrorEnvelope(error, error.attempts ?? 0));
|
|
539
|
+
process.exitCode = 1;
|
|
323
540
|
}
|
|
324
|
-
|
|
325
|
-
const responseJson = await callMiniMax(runtime.config, request);
|
|
326
|
-
const output = await writeAudioOutput({
|
|
327
|
-
responseJson,
|
|
328
|
-
request,
|
|
329
|
-
outputDir: options.output_dir,
|
|
330
|
-
prompt: options.prompt,
|
|
331
|
-
});
|
|
332
|
-
|
|
333
|
-
console.log(JSON.stringify({
|
|
334
|
-
ok: true,
|
|
335
|
-
provider: "minimax",
|
|
336
|
-
model: request.model,
|
|
337
|
-
output_path: output.audioPath,
|
|
338
|
-
metadata_path: output.metadataPath,
|
|
339
|
-
beat_analysis_command: `python3 scripts/analyze_music_beats.py ${output.audioPath} -o ${join(options.output_dir, "beat-map.json")} --fps 60 --pretty`,
|
|
340
|
-
}, null, 2));
|
|
341
541
|
}
|
|
342
542
|
|
|
343
|
-
main()
|
|
344
|
-
console.error(`Error: ${error.message}`);
|
|
345
|
-
process.exitCode = 1;
|
|
346
|
-
});
|
|
543
|
+
main();
|