@mrgoonie/multix 0.0.5 → 0.0.6
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/CHANGELOG.md +7 -0
- package/README.md +19 -1
- package/dist/cli.js +405 -266
- package/package.json +8 -2
package/dist/cli.js
CHANGED
|
@@ -67,8 +67,8 @@ __export(http_client_exports, {
|
|
|
67
67
|
fetchBytes: () => fetchBytes,
|
|
68
68
|
httpJson: () => httpJson
|
|
69
69
|
});
|
|
70
|
-
import
|
|
71
|
-
import
|
|
70
|
+
import fs4 from "fs";
|
|
71
|
+
import path4 from "path";
|
|
72
72
|
import { pipeline } from "stream/promises";
|
|
73
73
|
async function httpJson(opts) {
|
|
74
74
|
const { url, method = "GET", headers = {}, body, timeoutMs = DEFAULT_TIMEOUT_MS } = opts;
|
|
@@ -103,7 +103,7 @@ async function httpJson(opts) {
|
|
|
103
103
|
return response.json();
|
|
104
104
|
}
|
|
105
105
|
async function downloadFile(url, dest, timeoutMs = DEFAULT_TIMEOUT_MS) {
|
|
106
|
-
|
|
106
|
+
fs4.mkdirSync(path4.dirname(dest), { recursive: true });
|
|
107
107
|
let response;
|
|
108
108
|
try {
|
|
109
109
|
response = await globalThis.fetch(url, { signal: AbortSignal.timeout(timeoutMs) });
|
|
@@ -117,7 +117,7 @@ async function downloadFile(url, dest, timeoutMs = DEFAULT_TIMEOUT_MS) {
|
|
|
117
117
|
if (!response.body) {
|
|
118
118
|
throw new HttpError(0, "Response body is null", url);
|
|
119
119
|
}
|
|
120
|
-
const fileStream =
|
|
120
|
+
const fileStream = fs4.createWriteStream(dest);
|
|
121
121
|
await pipeline(response.body, fileStream);
|
|
122
122
|
}
|
|
123
123
|
async function fetchBytes(urlOrDataUri) {
|
|
@@ -155,6 +155,90 @@ var init_http_client = __esm({
|
|
|
155
155
|
import { createRequire } from "module";
|
|
156
156
|
import { Command } from "commander";
|
|
157
157
|
|
|
158
|
+
// src/providers/byteplus/commands/generate-3d.ts
|
|
159
|
+
import path5 from "path";
|
|
160
|
+
|
|
161
|
+
// src/core/image-input.ts
|
|
162
|
+
import fs from "fs/promises";
|
|
163
|
+
import path from "path";
|
|
164
|
+
var IMAGE_MIME_BY_EXT = {
|
|
165
|
+
".jpg": "image/jpeg",
|
|
166
|
+
".jpeg": "image/jpeg",
|
|
167
|
+
".png": "image/png",
|
|
168
|
+
".webp": "image/webp",
|
|
169
|
+
".gif": "image/gif"
|
|
170
|
+
};
|
|
171
|
+
var VIDEO_MIME_BY_EXT = {
|
|
172
|
+
".mp4": "video/mp4",
|
|
173
|
+
".webm": "video/webm",
|
|
174
|
+
".mov": "video/quicktime",
|
|
175
|
+
".m4v": "video/x-m4v"
|
|
176
|
+
};
|
|
177
|
+
var AUDIO_MIME_BY_EXT = {
|
|
178
|
+
".mp3": "audio/mpeg",
|
|
179
|
+
".wav": "audio/wav",
|
|
180
|
+
".m4a": "audio/mp4",
|
|
181
|
+
".flac": "audio/flac",
|
|
182
|
+
".ogg": "audio/ogg"
|
|
183
|
+
};
|
|
184
|
+
var DEFAULT_SOFT_LIMIT_BYTES = 10 * 1024 * 1024;
|
|
185
|
+
var DEFAULT_HARD_LIMIT_BYTES = 25 * 1024 * 1024;
|
|
186
|
+
function isHttpUrl(input) {
|
|
187
|
+
return /^https?:\/\//i.test(input);
|
|
188
|
+
}
|
|
189
|
+
async function readAsDataUrl(filePath, mimeTable, kindLabel, opts = {}) {
|
|
190
|
+
const ext = path.extname(filePath).toLowerCase();
|
|
191
|
+
const mime = mimeTable[ext];
|
|
192
|
+
if (!mime) {
|
|
193
|
+
throw new Error(
|
|
194
|
+
`Unsupported ${kindLabel} extension '${ext}'. Allowed: ${Object.keys(mimeTable).join(", ")}`
|
|
195
|
+
);
|
|
196
|
+
}
|
|
197
|
+
const hard = opts.hardLimitBytes ?? DEFAULT_HARD_LIMIT_BYTES;
|
|
198
|
+
const soft = opts.softLimitBytes ?? DEFAULT_SOFT_LIMIT_BYTES;
|
|
199
|
+
const buf = await fs.readFile(filePath);
|
|
200
|
+
if (buf.byteLength > hard) {
|
|
201
|
+
throw new Error(
|
|
202
|
+
`${kindLabel} file ${filePath} is ${(buf.byteLength / 1024 / 1024).toFixed(1)}MB \u2014 exceeds ${(hard / 1024 / 1024).toFixed(0)}MB limit. Upload to a URL instead.`
|
|
203
|
+
);
|
|
204
|
+
}
|
|
205
|
+
if (buf.byteLength > soft) {
|
|
206
|
+
opts.logger?.warn(
|
|
207
|
+
`${kindLabel} payload ${(buf.byteLength / 1024 / 1024).toFixed(1)}MB is large; consider URL input.`
|
|
208
|
+
);
|
|
209
|
+
}
|
|
210
|
+
const dataUrl = `data:${mime};base64,${buf.toString("base64")}`;
|
|
211
|
+
return { dataUrl, mime, bytes: buf.byteLength };
|
|
212
|
+
}
|
|
213
|
+
async function resolveImageInput(input, opts = {}) {
|
|
214
|
+
const o = normalizeOpts(opts);
|
|
215
|
+
if (isHttpUrl(input)) return { kind: "url", url: input };
|
|
216
|
+
const { dataUrl, mime, bytes } = await readAsDataUrl(input, IMAGE_MIME_BY_EXT, "image", o);
|
|
217
|
+
return { kind: "data", dataUrl, mime, bytes };
|
|
218
|
+
}
|
|
219
|
+
async function resolveVideoInput(input, opts = {}) {
|
|
220
|
+
const o = normalizeOpts(opts);
|
|
221
|
+
if (isHttpUrl(input)) return { kind: "url", url: input };
|
|
222
|
+
const { dataUrl, mime, bytes } = await readAsDataUrl(input, VIDEO_MIME_BY_EXT, "video", o);
|
|
223
|
+
return { kind: "data", dataUrl, mime, bytes };
|
|
224
|
+
}
|
|
225
|
+
async function resolveAudioInput(input, opts = {}) {
|
|
226
|
+
const o = normalizeOpts(opts);
|
|
227
|
+
if (isHttpUrl(input)) return { kind: "url", url: input };
|
|
228
|
+
const { dataUrl, mime, bytes } = await readAsDataUrl(input, AUDIO_MIME_BY_EXT, "audio", o);
|
|
229
|
+
return { kind: "data", dataUrl, mime, bytes };
|
|
230
|
+
}
|
|
231
|
+
function refUrl(r) {
|
|
232
|
+
return r.kind === "url" ? r.url : r.dataUrl;
|
|
233
|
+
}
|
|
234
|
+
function normalizeOpts(opts) {
|
|
235
|
+
if (!opts) return {};
|
|
236
|
+
if (typeof opts.info === "function" && typeof opts.warn === "function") {
|
|
237
|
+
return { logger: opts };
|
|
238
|
+
}
|
|
239
|
+
return opts;
|
|
240
|
+
}
|
|
241
|
+
|
|
158
242
|
// src/core/logger.ts
|
|
159
243
|
var ANSI = {
|
|
160
244
|
reset: "\x1B[0m",
|
|
@@ -200,22 +284,69 @@ ${line}
|
|
|
200
284
|
}
|
|
201
285
|
var defaultLogger = createLogger({ verbose: false });
|
|
202
286
|
|
|
287
|
+
// src/core/output-dir.ts
|
|
288
|
+
import fs2 from "fs";
|
|
289
|
+
import path2 from "path";
|
|
290
|
+
var cachedOutputDir;
|
|
291
|
+
function getOutputDir() {
|
|
292
|
+
if (cachedOutputDir) return cachedOutputDir;
|
|
293
|
+
const dir = process.env.MULTIX_OUTPUT_DIR ? path2.resolve(process.env.MULTIX_OUTPUT_DIR) : path2.resolve(process.cwd(), "multix-output");
|
|
294
|
+
fs2.mkdirSync(dir, { recursive: true });
|
|
295
|
+
cachedOutputDir = dir;
|
|
296
|
+
return dir;
|
|
297
|
+
}
|
|
298
|
+
|
|
299
|
+
// src/providers/leonardo/poll.ts
|
|
300
|
+
var PollTimeoutError = class extends Error {
|
|
301
|
+
constructor() {
|
|
302
|
+
super("Polling timed out before the operation completed.");
|
|
303
|
+
this.name = "PollTimeoutError";
|
|
304
|
+
}
|
|
305
|
+
};
|
|
306
|
+
var PollFailedError = class extends Error {
|
|
307
|
+
constructor(value) {
|
|
308
|
+
super("Operation failed.");
|
|
309
|
+
this.value = value;
|
|
310
|
+
this.name = "PollFailedError";
|
|
311
|
+
}
|
|
312
|
+
value;
|
|
313
|
+
};
|
|
314
|
+
async function poll(opts) {
|
|
315
|
+
const interval = opts.intervalMs ?? 4e3;
|
|
316
|
+
const max = opts.maxAttempts ?? 120;
|
|
317
|
+
for (let attempt = 1; attempt <= max; attempt++) {
|
|
318
|
+
if (opts.signal?.aborted) throw new Error("Aborted");
|
|
319
|
+
const value = await opts.fetch();
|
|
320
|
+
opts.onTick?.(attempt, value);
|
|
321
|
+
if (opts.failed?.(value)) throw new PollFailedError(value);
|
|
322
|
+
if (opts.done(value)) return value;
|
|
323
|
+
await new Promise((resolve, reject) => {
|
|
324
|
+
const t = setTimeout(resolve, interval);
|
|
325
|
+
opts.signal?.addEventListener("abort", () => {
|
|
326
|
+
clearTimeout(t);
|
|
327
|
+
reject(new Error("Aborted"));
|
|
328
|
+
});
|
|
329
|
+
});
|
|
330
|
+
}
|
|
331
|
+
throw new PollTimeoutError();
|
|
332
|
+
}
|
|
333
|
+
|
|
203
334
|
// src/core/env-loader.ts
|
|
204
|
-
import
|
|
335
|
+
import fs3 from "fs";
|
|
205
336
|
import os from "os";
|
|
206
|
-
import
|
|
337
|
+
import path3 from "path";
|
|
207
338
|
import { config as dotenvConfig } from "dotenv";
|
|
208
339
|
var loaded = false;
|
|
209
340
|
function loadEnv() {
|
|
210
341
|
if (loaded) return;
|
|
211
342
|
loaded = true;
|
|
212
|
-
const cwdEnv =
|
|
213
|
-
if (
|
|
343
|
+
const cwdEnv = path3.resolve(process.cwd(), ".env");
|
|
344
|
+
if (fs3.existsSync(cwdEnv)) {
|
|
214
345
|
dotenvConfig({ path: cwdEnv, override: false });
|
|
215
346
|
}
|
|
216
347
|
if (!process.env.MULTIX_DISABLE_HOME_ENV) {
|
|
217
|
-
const homeEnv =
|
|
218
|
-
if (
|
|
348
|
+
const homeEnv = path3.join(os.homedir(), ".multix", ".env");
|
|
349
|
+
if (fs3.existsSync(homeEnv)) {
|
|
219
350
|
dotenvConfig({ path: homeEnv, override: false });
|
|
220
351
|
}
|
|
221
352
|
}
|
|
@@ -236,9 +367,15 @@ init_http_client();
|
|
|
236
367
|
var DEFAULT_BYTEPLUS_BASE_URL = "https://ark.ap-southeast.bytepluses.com/api/v3";
|
|
237
368
|
var DEFAULT_BYTEPLUS_IMAGE_MODEL = "seedream-4-0-250828";
|
|
238
369
|
var DEFAULT_BYTEPLUS_VIDEO_MODEL = "seedance-2.0";
|
|
370
|
+
var DEFAULT_BYTEPLUS_3D_MODEL = "hyper3d-gen2-260112";
|
|
371
|
+
var BYTEPLUS_3D_MODELS = [
|
|
372
|
+
{ id: "hyper3d-gen2-260112", name: "Hyper3D Gen2", family: "hyper3d" },
|
|
373
|
+
{ id: "hitem3d-2-0-251223", name: "Hitem3d 2.0", family: "hitem3d" }
|
|
374
|
+
];
|
|
239
375
|
var BYTEPLUS_DEFAULTS = {
|
|
240
376
|
imageModel: process.env.BYTEPLUS_IMAGE_MODEL ?? DEFAULT_BYTEPLUS_IMAGE_MODEL,
|
|
241
377
|
videoModel: process.env.BYTEPLUS_VIDEO_MODEL ?? DEFAULT_BYTEPLUS_VIDEO_MODEL,
|
|
378
|
+
threeDModel: process.env.BYTEPLUS_3D_MODEL ?? DEFAULT_BYTEPLUS_3D_MODEL,
|
|
242
379
|
videoResolution: "1080p",
|
|
243
380
|
videoDuration: 8,
|
|
244
381
|
videoAspectRatio: "16:9",
|
|
@@ -269,9 +406,9 @@ var BytePlusClient = class {
|
|
|
269
406
|
}
|
|
270
407
|
apiKey;
|
|
271
408
|
baseUrl;
|
|
272
|
-
async request(
|
|
409
|
+
async request(path36, opts = {}) {
|
|
273
410
|
const base = this.baseUrl.replace(/\/$/, "");
|
|
274
|
-
const url = new URL(base +
|
|
411
|
+
const url = new URL(base + path36);
|
|
275
412
|
if (opts.query) {
|
|
276
413
|
for (const [k, v] of Object.entries(opts.query)) {
|
|
277
414
|
if (v !== void 0 && v !== null) url.searchParams.set(k, String(v));
|
|
@@ -289,119 +426,145 @@ var BytePlusClient = class {
|
|
|
289
426
|
timeoutMs: opts.timeoutMs ?? 12e4
|
|
290
427
|
});
|
|
291
428
|
}
|
|
292
|
-
get(
|
|
293
|
-
return this.request(
|
|
429
|
+
get(path36, query, logger) {
|
|
430
|
+
return this.request(path36, { method: "GET", query, logger });
|
|
294
431
|
}
|
|
295
|
-
post(
|
|
296
|
-
return this.request(
|
|
432
|
+
post(path36, body, logger) {
|
|
433
|
+
return this.request(path36, { method: "POST", body, logger });
|
|
297
434
|
}
|
|
298
|
-
delete(
|
|
299
|
-
return this.request(
|
|
435
|
+
delete(path36, logger) {
|
|
436
|
+
return this.request(path36, { method: "DELETE", logger });
|
|
300
437
|
}
|
|
301
438
|
};
|
|
302
439
|
function createBytePlusClient() {
|
|
303
440
|
return new BytePlusClient(requireBytePlusKey(), bytePlusBaseUrl());
|
|
304
441
|
}
|
|
305
442
|
|
|
306
|
-
// src/providers/byteplus/generators/
|
|
443
|
+
// src/providers/byteplus/generators/three-d.ts
|
|
307
444
|
init_http_client();
|
|
308
|
-
|
|
309
|
-
|
|
310
|
-
|
|
311
|
-
|
|
312
|
-
|
|
313
|
-
|
|
314
|
-
|
|
315
|
-
|
|
316
|
-
".jpeg": "image/jpeg",
|
|
317
|
-
".png": "image/png",
|
|
318
|
-
".webp": "image/webp",
|
|
319
|
-
".gif": "image/gif"
|
|
320
|
-
};
|
|
321
|
-
var VIDEO_MIME_BY_EXT = {
|
|
322
|
-
".mp4": "video/mp4",
|
|
323
|
-
".webm": "video/webm",
|
|
324
|
-
".mov": "video/quicktime",
|
|
325
|
-
".m4v": "video/x-m4v"
|
|
326
|
-
};
|
|
327
|
-
var AUDIO_MIME_BY_EXT = {
|
|
328
|
-
".mp3": "audio/mpeg",
|
|
329
|
-
".wav": "audio/wav",
|
|
330
|
-
".m4a": "audio/mp4",
|
|
331
|
-
".flac": "audio/flac",
|
|
332
|
-
".ogg": "audio/ogg"
|
|
333
|
-
};
|
|
334
|
-
var DEFAULT_SOFT_LIMIT_BYTES = 10 * 1024 * 1024;
|
|
335
|
-
var DEFAULT_HARD_LIMIT_BYTES = 25 * 1024 * 1024;
|
|
336
|
-
function isHttpUrl(input) {
|
|
337
|
-
return /^https?:\/\//i.test(input);
|
|
338
|
-
}
|
|
339
|
-
async function readAsDataUrl(filePath, mimeTable, kindLabel, opts = {}) {
|
|
340
|
-
const ext = path3.extname(filePath).toLowerCase();
|
|
341
|
-
const mime = mimeTable[ext];
|
|
342
|
-
if (!mime) {
|
|
343
|
-
throw new Error(
|
|
344
|
-
`Unsupported ${kindLabel} extension '${ext}'. Allowed: ${Object.keys(mimeTable).join(", ")}`
|
|
345
|
-
);
|
|
346
|
-
}
|
|
347
|
-
const hard = opts.hardLimitBytes ?? DEFAULT_HARD_LIMIT_BYTES;
|
|
348
|
-
const soft = opts.softLimitBytes ?? DEFAULT_SOFT_LIMIT_BYTES;
|
|
349
|
-
const buf = await fs3.readFile(filePath);
|
|
350
|
-
if (buf.byteLength > hard) {
|
|
351
|
-
throw new Error(
|
|
352
|
-
`${kindLabel} file ${filePath} is ${(buf.byteLength / 1024 / 1024).toFixed(1)}MB \u2014 exceeds ${(hard / 1024 / 1024).toFixed(0)}MB limit. Upload to a URL instead.`
|
|
353
|
-
);
|
|
445
|
+
function buildThreeDTaskBody(opts) {
|
|
446
|
+
const promptText = (opts.prompt ?? "").trim();
|
|
447
|
+
const flagPart = opts.flags?.trim() ?? "";
|
|
448
|
+
const text = [promptText, flagPart].filter((s) => s.length > 0).join(" ");
|
|
449
|
+
const content = [];
|
|
450
|
+
if (text.length > 0) content.push({ type: "text", text });
|
|
451
|
+
for (const img of opts.imageInputs ?? []) {
|
|
452
|
+
content.push({ type: "image_url", image_url: { url: refUrl(img) } });
|
|
354
453
|
}
|
|
355
|
-
if (
|
|
356
|
-
|
|
357
|
-
`${kindLabel} payload ${(buf.byteLength / 1024 / 1024).toFixed(1)}MB is large; consider URL input.`
|
|
358
|
-
);
|
|
454
|
+
if (content.length === 0) {
|
|
455
|
+
throw new Error("3D task requires either a prompt or at least one image input.");
|
|
359
456
|
}
|
|
360
|
-
const
|
|
361
|
-
|
|
457
|
+
const body = { model: opts.model, content };
|
|
458
|
+
if (opts.seed !== void 0) body.seed = opts.seed;
|
|
459
|
+
return body;
|
|
362
460
|
}
|
|
363
|
-
async function
|
|
364
|
-
|
|
365
|
-
if (isHttpUrl(input)) return { kind: "url", url: input };
|
|
366
|
-
const { dataUrl, mime, bytes } = await readAsDataUrl(input, IMAGE_MIME_BY_EXT, "image", o);
|
|
367
|
-
return { kind: "data", dataUrl, mime, bytes };
|
|
461
|
+
async function submitThreeDTask(client, body, logger) {
|
|
462
|
+
return client.post("/contents/generations/tasks", body, logger);
|
|
368
463
|
}
|
|
369
|
-
async function
|
|
370
|
-
|
|
371
|
-
if (isHttpUrl(input)) return { kind: "url", url: input };
|
|
372
|
-
const { dataUrl, mime, bytes } = await readAsDataUrl(input, VIDEO_MIME_BY_EXT, "video", o);
|
|
373
|
-
return { kind: "data", dataUrl, mime, bytes };
|
|
464
|
+
async function getThreeDTaskStatus(client, id, logger) {
|
|
465
|
+
return client.get(`/contents/generations/tasks/${id}`, void 0, logger);
|
|
374
466
|
}
|
|
375
|
-
async function
|
|
376
|
-
const
|
|
377
|
-
|
|
378
|
-
|
|
379
|
-
|
|
467
|
+
async function waitForThreeDTask(client, id, opts = {}) {
|
|
468
|
+
const intervalMs = opts.intervalMs ?? 4e3;
|
|
469
|
+
const maxAttempts = opts.maxAttempts ?? (opts.waitTimeoutMs ? Math.max(1, Math.ceil(opts.waitTimeoutMs / intervalMs)) : 180);
|
|
470
|
+
return poll({
|
|
471
|
+
fetch: () => getThreeDTaskStatus(client, id, opts.logger),
|
|
472
|
+
done: (v) => v.status === "succeeded",
|
|
473
|
+
failed: (v) => v.status === "failed" || v.status === "cancelled",
|
|
474
|
+
intervalMs,
|
|
475
|
+
maxAttempts,
|
|
476
|
+
onTick: (attempt, v) => opts.logger?.debug(`attempt ${attempt} \u2014 status: ${v.status}`)
|
|
477
|
+
});
|
|
380
478
|
}
|
|
381
|
-
function
|
|
382
|
-
|
|
479
|
+
function inferModelFileExt(url) {
|
|
480
|
+
const m = url.match(/\.(glb|gltf|obj|fbx|usdz|stl|ply|zip)(?:\?|#|$)/i);
|
|
481
|
+
return m?.[1] ? m[1].toLowerCase() : "glb";
|
|
383
482
|
}
|
|
384
|
-
function
|
|
385
|
-
|
|
386
|
-
|
|
387
|
-
return { logger: opts };
|
|
388
|
-
}
|
|
389
|
-
return opts;
|
|
483
|
+
async function downloadModelFile(url, outputPath, logger) {
|
|
484
|
+
await downloadFile(url, outputPath);
|
|
485
|
+
logger?.success(`Saved ${outputPath}`);
|
|
390
486
|
}
|
|
391
487
|
|
|
392
|
-
// src/
|
|
393
|
-
|
|
394
|
-
|
|
395
|
-
|
|
396
|
-
|
|
397
|
-
|
|
398
|
-
|
|
399
|
-
|
|
400
|
-
|
|
401
|
-
|
|
488
|
+
// src/providers/byteplus/commands/generate-3d.ts
|
|
489
|
+
function registerBytePlusGenerate3DCommand(parent) {
|
|
490
|
+
parent.command("generate-3d").alias("3d").description(
|
|
491
|
+
`Generate a 3D model via BytePlus (Hyper3D / Hitem3d). Models: ${BYTEPLUS_3D_MODELS.map((m) => m.id).join(", ")}`
|
|
492
|
+
).option("--prompt <text>", "Text prompt (text-to-3D or to guide image-to-3D)").option(
|
|
493
|
+
"-m, --model <id>",
|
|
494
|
+
`Model id (default ${BYTEPLUS_DEFAULTS.threeDModel})`,
|
|
495
|
+
BYTEPLUS_DEFAULTS.threeDModel
|
|
496
|
+
).option("--input-image <path|url...>", "1\u20135 reference images for image-to-3D (path or URL)").option(
|
|
497
|
+
"--flags <raw>",
|
|
498
|
+
"Raw flags appended to the prompt (e.g. '--mesh_mode Raw --hd_texture true' for Hyper3D, '--ff 2 --resolution 1536pro' for Hitem3d)"
|
|
499
|
+
).option("--seed <n>", "Fixed seed").option("--async", "Submit task and exit; print taskId only").option("--wait-timeout <ms>", "Poll timeout in ms", "900000").option("--output <path>", "Output file path (extension auto-detected from URL if omitted)").option("-v, --verbose", "Verbose logging").action(
|
|
500
|
+
async (opts) => {
|
|
501
|
+
const logger = createLogger({ verbose: opts.verbose ?? false });
|
|
502
|
+
const client = createBytePlusClient();
|
|
503
|
+
const images = opts.inputImage ?? [];
|
|
504
|
+
if (images.length > 5) {
|
|
505
|
+
logger.error("BytePlus 3D supports at most 5 reference images.");
|
|
506
|
+
process.exit(1);
|
|
507
|
+
}
|
|
508
|
+
const imageInputs = await Promise.all(images.map((src) => resolveImageInput(src, logger)));
|
|
509
|
+
if (!opts.prompt && imageInputs.length === 0) {
|
|
510
|
+
logger.error("Provide --prompt and/or --input-image to describe the 3D content.");
|
|
511
|
+
process.exit(1);
|
|
512
|
+
}
|
|
513
|
+
const body = buildThreeDTaskBody({
|
|
514
|
+
model: opts.model,
|
|
515
|
+
prompt: opts.prompt,
|
|
516
|
+
flags: opts.flags,
|
|
517
|
+
seed: opts.seed ? Number.parseInt(opts.seed, 10) : void 0,
|
|
518
|
+
imageInputs
|
|
519
|
+
});
|
|
520
|
+
logger.info(
|
|
521
|
+
`Submitting 3D task (${opts.model}${imageInputs.length > 0 ? `, ${imageInputs.length} image ref${imageInputs.length === 1 ? "" : "s"}` : ""})`
|
|
522
|
+
);
|
|
523
|
+
const submitted = await submitThreeDTask(client, body, logger);
|
|
524
|
+
const taskId = submitted.id;
|
|
525
|
+
logger.debug(`taskId: ${taskId}`);
|
|
526
|
+
if (opts.async) {
|
|
527
|
+
console.log(taskId);
|
|
528
|
+
console.error(`Poll with: multix byteplus status ${taskId} --wait --download`);
|
|
529
|
+
return;
|
|
530
|
+
}
|
|
531
|
+
try {
|
|
532
|
+
const final = await waitForThreeDTask(client, taskId, {
|
|
533
|
+
waitTimeoutMs: Number.parseInt(opts.waitTimeout, 10) || 9e5,
|
|
534
|
+
logger
|
|
535
|
+
});
|
|
536
|
+
const url = final.content?.file_url;
|
|
537
|
+
if (!url) {
|
|
538
|
+
logger.error(`Task succeeded but no file_url in response: ${JSON.stringify(final)}`);
|
|
539
|
+
process.exit(1);
|
|
540
|
+
}
|
|
541
|
+
const ext = inferModelFileExt(url);
|
|
542
|
+
const outPath = opts.output ?? path5.join(getOutputDir(), `byteplus-3d-${taskId.slice(0, 8)}-${Date.now()}.${ext}`);
|
|
543
|
+
await downloadModelFile(url, outPath, logger);
|
|
544
|
+
console.log(`
|
|
545
|
+
Task: ${taskId}`);
|
|
546
|
+
console.log(`Saved: ${outPath}`);
|
|
547
|
+
} catch (e) {
|
|
548
|
+
if (e instanceof PollTimeoutError) {
|
|
549
|
+
logger.error(
|
|
550
|
+
`Polling timed out \u2014 keep the task and check later: multix byteplus status ${taskId} --wait --download`
|
|
551
|
+
);
|
|
552
|
+
process.exit(1);
|
|
553
|
+
}
|
|
554
|
+
if (e instanceof PollFailedError) {
|
|
555
|
+
logger.error(`Task failed: ${JSON.stringify(e.value)}`);
|
|
556
|
+
process.exit(1);
|
|
557
|
+
}
|
|
558
|
+
throw e;
|
|
559
|
+
}
|
|
560
|
+
}
|
|
561
|
+
);
|
|
402
562
|
}
|
|
403
563
|
|
|
404
564
|
// src/providers/byteplus/generators/image.ts
|
|
565
|
+
init_http_client();
|
|
566
|
+
import fs5 from "fs";
|
|
567
|
+
import path6 from "path";
|
|
405
568
|
var ASPECT_TO_SIZE = {
|
|
406
569
|
"1:1": "2048x2048",
|
|
407
570
|
"16:9": "2560x1440",
|
|
@@ -465,14 +628,14 @@ async function generateSeedream(client, opts) {
|
|
|
465
628
|
}
|
|
466
629
|
const ext = (() => {
|
|
467
630
|
try {
|
|
468
|
-
const e =
|
|
631
|
+
const e = path6.extname(new URL(url).pathname);
|
|
469
632
|
return e || ".jpg";
|
|
470
633
|
} catch {
|
|
471
634
|
return ".jpg";
|
|
472
635
|
}
|
|
473
636
|
})();
|
|
474
|
-
const filename = opts.output && items.length === 1 ?
|
|
475
|
-
fs5.mkdirSync(
|
|
637
|
+
const filename = opts.output && items.length === 1 ? path6.resolve(opts.output) : path6.join(outDir, `byteplus-image-${ts}-${i + 1}${ext}`);
|
|
638
|
+
fs5.mkdirSync(path6.dirname(filename), { recursive: true });
|
|
476
639
|
await downloadFile(url, filename);
|
|
477
640
|
saved.push({ path: filename, url, seed: item.seed });
|
|
478
641
|
logger?.success(`Saved ${filename}`);
|
|
@@ -552,12 +715,12 @@ function collect2(value, prev) {
|
|
|
552
715
|
}
|
|
553
716
|
|
|
554
717
|
// src/providers/byteplus/commands/image-to-video.ts
|
|
555
|
-
import
|
|
718
|
+
import path8 from "path";
|
|
556
719
|
|
|
557
720
|
// src/core/video-thumb.ts
|
|
558
721
|
init_http_client();
|
|
559
722
|
import fs6 from "fs";
|
|
560
|
-
import
|
|
723
|
+
import path7 from "path";
|
|
561
724
|
var THUMB_KEYS = /* @__PURE__ */ new Set([
|
|
562
725
|
"cover_image_url",
|
|
563
726
|
"coverImageUrl",
|
|
@@ -607,9 +770,9 @@ function detectThumbUrl(obj) {
|
|
|
607
770
|
async function downloadThumbBeside(thumbUrl, videoPath, logger) {
|
|
608
771
|
const match = thumbUrl.match(IMAGE_EXT_RE);
|
|
609
772
|
const ext = match?.[1] ? match[1].toLowerCase().replace("jpeg", "jpg") : "jpg";
|
|
610
|
-
const dir =
|
|
611
|
-
const base =
|
|
612
|
-
const dest =
|
|
773
|
+
const dir = path7.dirname(videoPath);
|
|
774
|
+
const base = path7.basename(videoPath, path7.extname(videoPath));
|
|
775
|
+
const dest = path7.join(dir, `${base}_thumb.${ext}`);
|
|
613
776
|
try {
|
|
614
777
|
await downloadFile(thumbUrl, dest);
|
|
615
778
|
logger?.success(`Saved thumb ${dest}`);
|
|
@@ -628,10 +791,10 @@ async function maybeDownloadThumb(source, videoPath, opts = {}) {
|
|
|
628
791
|
}
|
|
629
792
|
const saved = await downloadThumbBeside(url, videoPath, opts.logger);
|
|
630
793
|
if (saved && opts.copyTo) {
|
|
631
|
-
const copyExt =
|
|
632
|
-
const copyBase =
|
|
633
|
-
const copyDir =
|
|
634
|
-
const copyDest =
|
|
794
|
+
const copyExt = path7.extname(saved);
|
|
795
|
+
const copyBase = path7.basename(opts.copyTo, path7.extname(opts.copyTo));
|
|
796
|
+
const copyDir = path7.dirname(path7.resolve(opts.copyTo));
|
|
797
|
+
const copyDest = path7.join(copyDir, `${copyBase}_thumb${copyExt}`);
|
|
635
798
|
fs6.mkdirSync(copyDir, { recursive: true });
|
|
636
799
|
fs6.copyFileSync(saved, copyDest);
|
|
637
800
|
opts.logger?.success(`Copied thumb to ${copyDest}`);
|
|
@@ -639,41 +802,6 @@ async function maybeDownloadThumb(source, videoPath, opts = {}) {
|
|
|
639
802
|
return saved;
|
|
640
803
|
}
|
|
641
804
|
|
|
642
|
-
// src/providers/leonardo/poll.ts
|
|
643
|
-
var PollTimeoutError = class extends Error {
|
|
644
|
-
constructor() {
|
|
645
|
-
super("Polling timed out before the operation completed.");
|
|
646
|
-
this.name = "PollTimeoutError";
|
|
647
|
-
}
|
|
648
|
-
};
|
|
649
|
-
var PollFailedError = class extends Error {
|
|
650
|
-
constructor(value) {
|
|
651
|
-
super("Operation failed.");
|
|
652
|
-
this.value = value;
|
|
653
|
-
this.name = "PollFailedError";
|
|
654
|
-
}
|
|
655
|
-
value;
|
|
656
|
-
};
|
|
657
|
-
async function poll(opts) {
|
|
658
|
-
const interval = opts.intervalMs ?? 4e3;
|
|
659
|
-
const max = opts.maxAttempts ?? 120;
|
|
660
|
-
for (let attempt = 1; attempt <= max; attempt++) {
|
|
661
|
-
if (opts.signal?.aborted) throw new Error("Aborted");
|
|
662
|
-
const value = await opts.fetch();
|
|
663
|
-
opts.onTick?.(attempt, value);
|
|
664
|
-
if (opts.failed?.(value)) throw new PollFailedError(value);
|
|
665
|
-
if (opts.done(value)) return value;
|
|
666
|
-
await new Promise((resolve, reject) => {
|
|
667
|
-
const t = setTimeout(resolve, interval);
|
|
668
|
-
opts.signal?.addEventListener("abort", () => {
|
|
669
|
-
clearTimeout(t);
|
|
670
|
-
reject(new Error("Aborted"));
|
|
671
|
-
});
|
|
672
|
-
});
|
|
673
|
-
}
|
|
674
|
-
throw new PollTimeoutError();
|
|
675
|
-
}
|
|
676
|
-
|
|
677
805
|
// src/providers/byteplus/generators/video.ts
|
|
678
806
|
init_http_client();
|
|
679
807
|
function buildFlagSuffix(opts) {
|
|
@@ -796,7 +924,7 @@ function registerBytePlusImageToVideoCommand(parent) {
|
|
|
796
924
|
logger.error(`Task succeeded but no video_url: ${JSON.stringify(final)}`);
|
|
797
925
|
process.exit(1);
|
|
798
926
|
}
|
|
799
|
-
const outPath = opts.output ??
|
|
927
|
+
const outPath = opts.output ?? path8.join(getOutputDir(), `byteplus-i2v-${taskId.slice(0, 8)}-${Date.now()}.mp4`);
|
|
800
928
|
await downloadVideo(url, outPath, logger);
|
|
801
929
|
await maybeDownloadThumb(final, outPath, {
|
|
802
930
|
skip: opts.thumb === false,
|
|
@@ -822,7 +950,7 @@ Task: ${taskId}`);
|
|
|
822
950
|
}
|
|
823
951
|
|
|
824
952
|
// src/providers/byteplus/commands/reference-to-video.ts
|
|
825
|
-
import
|
|
953
|
+
import path9 from "path";
|
|
826
954
|
|
|
827
955
|
// src/providers/byteplus/reference-input.ts
|
|
828
956
|
var MAX_TOTAL_REFS = 12;
|
|
@@ -854,10 +982,10 @@ function toRef(kind, resolved, role) {
|
|
|
854
982
|
return { kind, url: resolved.dataUrl, mime: resolved.mime, role };
|
|
855
983
|
}
|
|
856
984
|
async function resolveRefSpec(kind, spec, logger) {
|
|
857
|
-
const { path:
|
|
858
|
-
if (kind === "image") return toRef(kind, await resolveImageInput(
|
|
859
|
-
if (kind === "video") return toRef(kind, await resolveVideoInput(
|
|
860
|
-
return toRef(kind, await resolveAudioInput(
|
|
985
|
+
const { path: path36, role } = parseRefSpec(spec);
|
|
986
|
+
if (kind === "image") return toRef(kind, await resolveImageInput(path36, logger), role);
|
|
987
|
+
if (kind === "video") return toRef(kind, await resolveVideoInput(path36, logger), role);
|
|
988
|
+
return toRef(kind, await resolveAudioInput(path36, logger), role);
|
|
861
989
|
}
|
|
862
990
|
async function resolveRefSpecs(specs, logger) {
|
|
863
991
|
const out = [];
|
|
@@ -954,7 +1082,7 @@ function registerBytePlusReferenceToVideoCommand(parent) {
|
|
|
954
1082
|
logger.error(`Task succeeded but no video_url: ${JSON.stringify(final)}`);
|
|
955
1083
|
process.exit(1);
|
|
956
1084
|
}
|
|
957
|
-
const outPath = opts.output ??
|
|
1085
|
+
const outPath = opts.output ?? path9.join(getOutputDir(), `byteplus-r2v-${taskId.slice(0, 8)}-${Date.now()}.mp4`);
|
|
958
1086
|
await downloadVideo(url, outPath, logger);
|
|
959
1087
|
await maybeDownloadThumb(final, outPath, {
|
|
960
1088
|
skip: opts.thumb === false,
|
|
@@ -980,7 +1108,7 @@ Task: ${taskId}`);
|
|
|
980
1108
|
}
|
|
981
1109
|
|
|
982
1110
|
// src/providers/byteplus/commands/status.ts
|
|
983
|
-
import
|
|
1111
|
+
import path10 from "path";
|
|
984
1112
|
function registerBytePlusStatusCommand(parent) {
|
|
985
1113
|
parent.command("status <taskId>").description("Get a Seedance task's status; --wait + --download to fetch the result MP4").option("--wait", "Poll until terminal status").option("--wait-timeout <ms>", "Poll timeout in ms (with --wait)", "600000").option("--download", "Download MP4 on success (implies --wait)").option("--output <path>", "Output MP4 path (with --download)").option("--no-thumb", "Skip downloading the thumbnail if the API returns one").option("-v, --verbose", "Verbose logging").action(
|
|
986
1114
|
async (taskId, opts) => {
|
|
@@ -994,19 +1122,27 @@ function registerBytePlusStatusCommand(parent) {
|
|
|
994
1122
|
}) : await getVideoTaskStatus(client, taskId, logger);
|
|
995
1123
|
console.log(JSON.stringify(status, null, 2));
|
|
996
1124
|
if (opts.download && status.status === "succeeded") {
|
|
997
|
-
const
|
|
998
|
-
|
|
999
|
-
|
|
1125
|
+
const content = status.content;
|
|
1126
|
+
const videoUrl = content?.video_url;
|
|
1127
|
+
const fileUrl = content?.file_url;
|
|
1128
|
+
if (videoUrl) {
|
|
1129
|
+
const outPath = opts.output ?? path10.join(getOutputDir(), `byteplus-video-${taskId.slice(0, 8)}.mp4`);
|
|
1130
|
+
await downloadVideo(videoUrl, outPath, logger);
|
|
1131
|
+
await maybeDownloadThumb(status, outPath, {
|
|
1132
|
+
skip: opts.thumb === false,
|
|
1133
|
+
copyTo: opts.output,
|
|
1134
|
+
logger
|
|
1135
|
+
});
|
|
1136
|
+
console.log(`Saved: ${outPath}`);
|
|
1137
|
+
} else if (fileUrl) {
|
|
1138
|
+
const ext = inferModelFileExt(fileUrl);
|
|
1139
|
+
const outPath = opts.output ?? path10.join(getOutputDir(), `byteplus-3d-${taskId.slice(0, 8)}.${ext}`);
|
|
1140
|
+
await downloadModelFile(fileUrl, outPath, logger);
|
|
1141
|
+
console.log(`Saved: ${outPath}`);
|
|
1142
|
+
} else {
|
|
1143
|
+
logger.error("Task succeeded but no video_url or file_url present");
|
|
1000
1144
|
process.exit(1);
|
|
1001
1145
|
}
|
|
1002
|
-
const outPath = opts.output ?? path9.join(getOutputDir(), `byteplus-video-${taskId.slice(0, 8)}.mp4`);
|
|
1003
|
-
await downloadVideo(url, outPath, logger);
|
|
1004
|
-
await maybeDownloadThumb(status, outPath, {
|
|
1005
|
-
skip: opts.thumb === false,
|
|
1006
|
-
copyTo: opts.output,
|
|
1007
|
-
logger
|
|
1008
|
-
});
|
|
1009
|
-
console.log(`Saved: ${outPath}`);
|
|
1010
1146
|
}
|
|
1011
1147
|
} catch (e) {
|
|
1012
1148
|
if (e instanceof PollTimeoutError) {
|
|
@@ -1024,7 +1160,7 @@ function registerBytePlusStatusCommand(parent) {
|
|
|
1024
1160
|
}
|
|
1025
1161
|
|
|
1026
1162
|
// src/providers/byteplus/commands/video.ts
|
|
1027
|
-
import
|
|
1163
|
+
import path11 from "path";
|
|
1028
1164
|
function registerBytePlusVideoCommand(parent) {
|
|
1029
1165
|
parent.command("video").description("Generate a video from a text prompt via BytePlus Seedance 2.0").requiredOption("--prompt <text>", "Text prompt").option("-m, --model <id>", "Model id", BYTEPLUS_DEFAULTS.videoModel).option("--resolution <r>", "480p|720p|1080p|2k", BYTEPLUS_DEFAULTS.videoResolution).option("--duration <n>", "Duration seconds (4-15)", String(BYTEPLUS_DEFAULTS.videoDuration)).option(
|
|
1030
1166
|
"--aspect-ratio <r>",
|
|
@@ -1066,7 +1202,7 @@ function registerBytePlusVideoCommand(parent) {
|
|
|
1066
1202
|
logger.error(`Task succeeded but no video_url in response: ${JSON.stringify(final)}`);
|
|
1067
1203
|
process.exit(1);
|
|
1068
1204
|
}
|
|
1069
|
-
const outPath = opts.output ??
|
|
1205
|
+
const outPath = opts.output ?? path11.join(getOutputDir(), `byteplus-video-${taskId.slice(0, 8)}-${Date.now()}.mp4`);
|
|
1070
1206
|
await downloadVideo(url, outPath, logger);
|
|
1071
1207
|
await maybeDownloadThumb(final, outPath, {
|
|
1072
1208
|
skip: opts.thumb === false,
|
|
@@ -1095,12 +1231,15 @@ Task: ${taskId}`);
|
|
|
1095
1231
|
|
|
1096
1232
|
// src/providers/byteplus/commands/index.ts
|
|
1097
1233
|
function registerBytePlusCommands(program2) {
|
|
1098
|
-
const byteplus = program2.command("byteplus").description(
|
|
1234
|
+
const byteplus = program2.command("byteplus").description(
|
|
1235
|
+
"BytePlus ModelArk: Seedream image, Seedance video, Hyper3D / Hitem3d 3D generation"
|
|
1236
|
+
);
|
|
1099
1237
|
registerBytePlusGenerateCommand(byteplus);
|
|
1100
1238
|
registerBytePlusImageToImageCommand(byteplus);
|
|
1101
1239
|
registerBytePlusVideoCommand(byteplus);
|
|
1102
1240
|
registerBytePlusImageToVideoCommand(byteplus);
|
|
1103
1241
|
registerBytePlusReferenceToVideoCommand(byteplus);
|
|
1242
|
+
registerBytePlusGenerate3DCommand(byteplus);
|
|
1104
1243
|
registerBytePlusStatusCommand(byteplus);
|
|
1105
1244
|
}
|
|
1106
1245
|
|
|
@@ -1110,12 +1249,12 @@ import fs9 from "fs";
|
|
|
1110
1249
|
|
|
1111
1250
|
// src/providers/gemini/client.ts
|
|
1112
1251
|
import fs7 from "fs";
|
|
1113
|
-
import
|
|
1252
|
+
import path13 from "path";
|
|
1114
1253
|
init_errors();
|
|
1115
1254
|
init_http_client();
|
|
1116
1255
|
|
|
1117
1256
|
// src/providers/gemini/task-resolver.ts
|
|
1118
|
-
import
|
|
1257
|
+
import path12 from "path";
|
|
1119
1258
|
var MIME_MAP = {
|
|
1120
1259
|
// Audio
|
|
1121
1260
|
".mp3": "audio/mp3",
|
|
@@ -1156,7 +1295,7 @@ var MIME_MAP = {
|
|
|
1156
1295
|
".pptx": "application/vnd.openxmlformats-officedocument.presentationml.presentation"
|
|
1157
1296
|
};
|
|
1158
1297
|
function getMimeType(filePath) {
|
|
1159
|
-
const ext =
|
|
1298
|
+
const ext = path12.extname(filePath).toLowerCase();
|
|
1160
1299
|
return MIME_MAP[ext] ?? "application/octet-stream";
|
|
1161
1300
|
}
|
|
1162
1301
|
function requiresProcessingWait(mimeType) {
|
|
@@ -1183,7 +1322,7 @@ async function uploadFile(filePath, opts = {}) {
|
|
|
1183
1322
|
const { timeoutMs = 3e5, logger } = opts;
|
|
1184
1323
|
const mimeType = getMimeType(filePath);
|
|
1185
1324
|
const fileBytes = fs7.readFileSync(filePath);
|
|
1186
|
-
const displayName =
|
|
1325
|
+
const displayName = path13.basename(filePath);
|
|
1187
1326
|
const boundary = `multix_${Date.now()}`;
|
|
1188
1327
|
const metaJson = JSON.stringify({ file: { displayName, mimeType } });
|
|
1189
1328
|
const body = Buffer.concat([
|
|
@@ -1331,11 +1470,11 @@ function getDefaultModel(task) {
|
|
|
1331
1470
|
|
|
1332
1471
|
// src/providers/gemini/result-formatter.ts
|
|
1333
1472
|
import fs8 from "fs";
|
|
1334
|
-
import
|
|
1473
|
+
import path14 from "path";
|
|
1335
1474
|
function formatResults(results, format, outputPath) {
|
|
1336
1475
|
const content = renderFormat(results, format);
|
|
1337
1476
|
if (outputPath) {
|
|
1338
|
-
fs8.mkdirSync(
|
|
1477
|
+
fs8.mkdirSync(path14.dirname(path14.resolve(outputPath)), { recursive: true });
|
|
1339
1478
|
fs8.writeFileSync(outputPath, content, "utf8");
|
|
1340
1479
|
} else {
|
|
1341
1480
|
process.stdout.write(content);
|
|
@@ -1499,7 +1638,7 @@ init_errors();
|
|
|
1499
1638
|
|
|
1500
1639
|
// src/providers/gemini/generators/speech.ts
|
|
1501
1640
|
import fs11 from "fs";
|
|
1502
|
-
import
|
|
1641
|
+
import path15 from "path";
|
|
1503
1642
|
|
|
1504
1643
|
// src/providers/gemini/voices.ts
|
|
1505
1644
|
var GEMINI_TTS_VOICES = [
|
|
@@ -1582,11 +1721,11 @@ async function generateGeminiSpeech(opts) {
|
|
|
1582
1721
|
const sampleRate = parseSampleRate(audio.mimeType) ?? TTS_PCM_SAMPLE_RATE;
|
|
1583
1722
|
const outBytes = outputFormat === "wav" ? wrapPcmInWav(pcmBytes, sampleRate, TTS_PCM_CHANNELS, TTS_PCM_BITS_PER_SAMPLE) : pcmBytes;
|
|
1584
1723
|
const outDir = getOutputDir();
|
|
1585
|
-
const dest =
|
|
1724
|
+
const dest = path15.join(outDir, `gemini_speech_${Date.now()}.${outputFormat}`);
|
|
1586
1725
|
fs11.writeFileSync(dest, outBytes);
|
|
1587
1726
|
logger?.success(`Saved: ${dest} (${(outBytes.length / 1024).toFixed(1)} KB)`);
|
|
1588
1727
|
if (output) {
|
|
1589
|
-
fs11.mkdirSync(
|
|
1728
|
+
fs11.mkdirSync(path15.dirname(path15.resolve(output)), { recursive: true });
|
|
1590
1729
|
fs11.copyFileSync(dest, output);
|
|
1591
1730
|
logger?.success(`Copied to: ${output}`);
|
|
1592
1731
|
}
|
|
@@ -1687,7 +1826,7 @@ function collectSpeakers(value, prev) {
|
|
|
1687
1826
|
|
|
1688
1827
|
// src/providers/gemini/commands/generate-video.ts
|
|
1689
1828
|
import fs12 from "fs";
|
|
1690
|
-
import
|
|
1829
|
+
import path16 from "path";
|
|
1691
1830
|
init_errors();
|
|
1692
1831
|
init_http_client();
|
|
1693
1832
|
var BASE2 = "https://generativelanguage.googleapis.com";
|
|
@@ -1724,7 +1863,7 @@ function registerGenerateVideoCommand(parent) {
|
|
|
1724
1863
|
if (!fs12.existsSync(imgPath))
|
|
1725
1864
|
throw new ValidationError(`Reference image not found: ${imgPath}`);
|
|
1726
1865
|
const data = fs12.readFileSync(imgPath).toString("base64");
|
|
1727
|
-
const ext =
|
|
1866
|
+
const ext = path16.extname(imgPath).toLowerCase();
|
|
1728
1867
|
const mimeType = ext === ".png" ? "image/png" : "image/jpeg";
|
|
1729
1868
|
contentParts.push({ inlineData: { mimeType, data } });
|
|
1730
1869
|
}
|
|
@@ -1773,12 +1912,12 @@ function registerGenerateVideoCommand(parent) {
|
|
|
1773
1912
|
);
|
|
1774
1913
|
}
|
|
1775
1914
|
const outDir = getOutputDir();
|
|
1776
|
-
const dest =
|
|
1915
|
+
const dest = path16.join(outDir, `veo_generated_${Date.now()}.mp4`);
|
|
1777
1916
|
const { downloadFile: downloadFile2 } = await Promise.resolve().then(() => (init_http_client(), http_client_exports));
|
|
1778
1917
|
await downloadFile2(videoUri, dest);
|
|
1779
1918
|
logger.success(`Saved video: ${dest}`);
|
|
1780
1919
|
if (opts.output) {
|
|
1781
|
-
fs12.mkdirSync(
|
|
1920
|
+
fs12.mkdirSync(path16.dirname(path16.resolve(opts.output)), { recursive: true });
|
|
1782
1921
|
fs12.copyFileSync(dest, opts.output);
|
|
1783
1922
|
logger.success(`Copied to: ${opts.output}`);
|
|
1784
1923
|
}
|
|
@@ -1799,7 +1938,7 @@ function sleep2(ms) {
|
|
|
1799
1938
|
// src/providers/gemini/commands/generate.ts
|
|
1800
1939
|
init_errors();
|
|
1801
1940
|
import fs13 from "fs";
|
|
1802
|
-
import
|
|
1941
|
+
import path17 from "path";
|
|
1803
1942
|
function registerGenerateCommand(parent) {
|
|
1804
1943
|
parent.command("generate").description("Generate images with Gemini (Nano Banana / Imagen 4)").requiredOption("--prompt <text>", "Image generation prompt").option("--model <id>", "Gemini image model to use").option("--aspect-ratio <ratio>", `Aspect ratio (${ASPECT_RATIOS.join("|")})`, "1:1").option("--num-images <n>", "Number of images to generate (1-4)", "1").option("--size <sz>", `Image size (${IMAGE_SIZES.join("|")})`).option("--output <path>", "Copy first generated image to this path").option("-v, --verbose", "Verbose logging").action(
|
|
1805
1944
|
async (opts) => {
|
|
@@ -1841,7 +1980,7 @@ function registerGenerateCommand(parent) {
|
|
|
1841
1980
|
const outDir = getOutputDir();
|
|
1842
1981
|
const ext = img.mimeType.includes("png") ? "png" : "jpg";
|
|
1843
1982
|
const fname = `gemini_generated_${Date.now()}_${i}.${ext}`;
|
|
1844
|
-
const dest =
|
|
1983
|
+
const dest = path17.join(outDir, fname);
|
|
1845
1984
|
fs13.writeFileSync(dest, Buffer.from(img.data, "base64"));
|
|
1846
1985
|
savedFiles.push(dest);
|
|
1847
1986
|
logger.success(`Saved: ${dest}`);
|
|
@@ -1857,7 +1996,7 @@ function registerGenerateCommand(parent) {
|
|
|
1857
1996
|
process.exit(1);
|
|
1858
1997
|
}
|
|
1859
1998
|
if (opts.output && savedFiles[0]) {
|
|
1860
|
-
fs13.mkdirSync(
|
|
1999
|
+
fs13.mkdirSync(path17.dirname(path17.resolve(opts.output)), { recursive: true });
|
|
1861
2000
|
fs13.copyFileSync(savedFiles[0], opts.output);
|
|
1862
2001
|
logger.success(`Copied to: ${opts.output}`);
|
|
1863
2002
|
}
|
|
@@ -1870,7 +2009,7 @@ Generated ${savedFiles.length} image(s):`);
|
|
|
1870
2009
|
|
|
1871
2010
|
// src/providers/gemini/commands/image-to-image.ts
|
|
1872
2011
|
import fs14 from "fs";
|
|
1873
|
-
import
|
|
2012
|
+
import path18 from "path";
|
|
1874
2013
|
var SOFT_LIMIT_BYTES = 5 * 1024 * 1024;
|
|
1875
2014
|
var HARD_LIMIT_BYTES = 7 * 1024 * 1024;
|
|
1876
2015
|
function registerGeminiImageToImageCommand(parent) {
|
|
@@ -1931,13 +2070,13 @@ function registerGeminiImageToImageCommand(parent) {
|
|
|
1931
2070
|
const img = images[i];
|
|
1932
2071
|
if (!img) continue;
|
|
1933
2072
|
const ext = img.mimeType.includes("png") ? "png" : "jpg";
|
|
1934
|
-
const dest =
|
|
2073
|
+
const dest = path18.join(outDir, `gemini-i2i-${ts}-${i + 1}.${ext}`);
|
|
1935
2074
|
fs14.writeFileSync(dest, Buffer.from(img.data, "base64"));
|
|
1936
2075
|
saved.push(dest);
|
|
1937
2076
|
logger.success(`Saved: ${dest}`);
|
|
1938
2077
|
}
|
|
1939
2078
|
if (opts.output && saved[0]) {
|
|
1940
|
-
fs14.mkdirSync(
|
|
2079
|
+
fs14.mkdirSync(path18.dirname(path18.resolve(opts.output)), { recursive: true });
|
|
1941
2080
|
fs14.copyFileSync(saved[0], opts.output);
|
|
1942
2081
|
logger.success(`Copied to: ${opts.output}`);
|
|
1943
2082
|
}
|
|
@@ -1965,7 +2104,7 @@ function sniffImageMime(buf, url) {
|
|
|
1965
2104
|
if (buf.length >= 12 && buf.slice(0, 4).toString() === "RIFF" && buf.slice(8, 12).toString() === "WEBP")
|
|
1966
2105
|
return "image/webp";
|
|
1967
2106
|
}
|
|
1968
|
-
const ext =
|
|
2107
|
+
const ext = path18.extname(new URL(url).pathname).toLowerCase();
|
|
1969
2108
|
if (ext === ".png") return "image/png";
|
|
1970
2109
|
if (ext === ".webp") return "image/webp";
|
|
1971
2110
|
if (ext === ".gif") return "image/gif";
|
|
@@ -1974,7 +2113,7 @@ function sniffImageMime(buf, url) {
|
|
|
1974
2113
|
|
|
1975
2114
|
// src/providers/gemini/commands/image-to-video.ts
|
|
1976
2115
|
import fs15 from "fs";
|
|
1977
|
-
import
|
|
2116
|
+
import path19 from "path";
|
|
1978
2117
|
init_errors();
|
|
1979
2118
|
init_http_client();
|
|
1980
2119
|
var BASE3 = "https://generativelanguage.googleapis.com";
|
|
@@ -1983,7 +2122,7 @@ function imagePart(imgPath) {
|
|
|
1983
2122
|
throw new ValidationError(`Image not found: ${imgPath}`);
|
|
1984
2123
|
}
|
|
1985
2124
|
const data = fs15.readFileSync(imgPath).toString("base64");
|
|
1986
|
-
const ext =
|
|
2125
|
+
const ext = path19.extname(imgPath).toLowerCase();
|
|
1987
2126
|
const mimeType = ext === ".png" ? "image/png" : "image/jpeg";
|
|
1988
2127
|
return { inlineData: { mimeType, data } };
|
|
1989
2128
|
}
|
|
@@ -2056,11 +2195,11 @@ function registerGeminiImageToVideoCommand(parent) {
|
|
|
2056
2195
|
"gemini"
|
|
2057
2196
|
);
|
|
2058
2197
|
}
|
|
2059
|
-
const dest =
|
|
2198
|
+
const dest = path19.join(getOutputDir(), `veo_i2v_${Date.now()}.mp4`);
|
|
2060
2199
|
await downloadFile(videoUri, dest);
|
|
2061
2200
|
logger.success(`Saved video: ${dest}`);
|
|
2062
2201
|
if (opts.output) {
|
|
2063
|
-
fs15.mkdirSync(
|
|
2202
|
+
fs15.mkdirSync(path19.dirname(path19.resolve(opts.output)), { recursive: true });
|
|
2064
2203
|
fs15.copyFileSync(dest, opts.output);
|
|
2065
2204
|
logger.success(`Copied to: ${opts.output}`);
|
|
2066
2205
|
}
|
|
@@ -2143,7 +2282,7 @@ function registerGeminiCommands(program2) {
|
|
|
2143
2282
|
// src/providers/leonardo/commands/generate.ts
|
|
2144
2283
|
init_http_client();
|
|
2145
2284
|
import fs17 from "fs";
|
|
2146
|
-
import
|
|
2285
|
+
import path20 from "path";
|
|
2147
2286
|
|
|
2148
2287
|
// src/providers/leonardo/client.ts
|
|
2149
2288
|
init_errors();
|
|
@@ -2217,11 +2356,11 @@ var LeonardoClient = class {
|
|
|
2217
2356
|
}
|
|
2218
2357
|
apiKey;
|
|
2219
2358
|
baseUrl;
|
|
2220
|
-
async request(
|
|
2359
|
+
async request(path36, opts = {}) {
|
|
2221
2360
|
const base = this.baseUrl.replace(/\/$/, "");
|
|
2222
|
-
const v2Override =
|
|
2361
|
+
const v2Override = path36.startsWith("v2:");
|
|
2223
2362
|
const finalBase = v2Override ? base.replace(/\/v1$/, "/v2") : base;
|
|
2224
|
-
const finalPath = v2Override ?
|
|
2363
|
+
const finalPath = v2Override ? path36.slice(3) : path36;
|
|
2225
2364
|
const url = new URL(finalBase + finalPath);
|
|
2226
2365
|
if (opts.query) {
|
|
2227
2366
|
for (const [k, v] of Object.entries(opts.query)) {
|
|
@@ -2240,14 +2379,14 @@ var LeonardoClient = class {
|
|
|
2240
2379
|
timeoutMs: opts.timeoutMs ?? 12e4
|
|
2241
2380
|
});
|
|
2242
2381
|
}
|
|
2243
|
-
get(
|
|
2244
|
-
return this.request(
|
|
2382
|
+
get(path36, query, logger) {
|
|
2383
|
+
return this.request(path36, { method: "GET", query, logger });
|
|
2245
2384
|
}
|
|
2246
|
-
post(
|
|
2247
|
-
return this.request(
|
|
2385
|
+
post(path36, body, logger) {
|
|
2386
|
+
return this.request(path36, { method: "POST", body, logger });
|
|
2248
2387
|
}
|
|
2249
|
-
delete(
|
|
2250
|
-
return this.request(
|
|
2388
|
+
delete(path36, logger) {
|
|
2389
|
+
return this.request(path36, { method: "DELETE", logger });
|
|
2251
2390
|
}
|
|
2252
2391
|
};
|
|
2253
2392
|
function createLeonardoClient() {
|
|
@@ -2258,7 +2397,7 @@ function createLeonardoClient() {
|
|
|
2258
2397
|
function extFromUrl(url, fallback = ".png") {
|
|
2259
2398
|
try {
|
|
2260
2399
|
const u = new URL(url);
|
|
2261
|
-
const e =
|
|
2400
|
+
const e = path20.extname(u.pathname);
|
|
2262
2401
|
return e.length > 0 ? e : fallback;
|
|
2263
2402
|
} catch {
|
|
2264
2403
|
return fallback;
|
|
@@ -2343,7 +2482,7 @@ function registerLeonardoGenerateCommand(parent) {
|
|
|
2343
2482
|
if (!img) continue;
|
|
2344
2483
|
const ext = extFromUrl(img.url);
|
|
2345
2484
|
const filename = autoFilename(generationId, i + 1, ext);
|
|
2346
|
-
const filepath =
|
|
2485
|
+
const filepath = path20.join(outDir, filename);
|
|
2347
2486
|
try {
|
|
2348
2487
|
await downloadFile(img.url, filepath);
|
|
2349
2488
|
saved.push(filepath);
|
|
@@ -2353,8 +2492,8 @@ function registerLeonardoGenerateCommand(parent) {
|
|
|
2353
2492
|
}
|
|
2354
2493
|
}
|
|
2355
2494
|
if (opts.output && saved[0]) {
|
|
2356
|
-
const dest =
|
|
2357
|
-
fs17.mkdirSync(
|
|
2495
|
+
const dest = path20.resolve(opts.output);
|
|
2496
|
+
fs17.mkdirSync(path20.dirname(dest), { recursive: true });
|
|
2358
2497
|
fs17.copyFileSync(saved[0], dest);
|
|
2359
2498
|
logger.success(`Copied to ${dest}`);
|
|
2360
2499
|
}
|
|
@@ -2368,10 +2507,10 @@ Generated ${saved.length} image(s):`);
|
|
|
2368
2507
|
// src/providers/leonardo/commands/image-to-image.ts
|
|
2369
2508
|
init_http_client();
|
|
2370
2509
|
import fs18 from "fs";
|
|
2371
|
-
import
|
|
2510
|
+
import path21 from "path";
|
|
2372
2511
|
function extFromUrl2(url, fallback = ".png") {
|
|
2373
2512
|
try {
|
|
2374
|
-
return
|
|
2513
|
+
return path21.extname(new URL(url).pathname) || fallback;
|
|
2375
2514
|
} catch {
|
|
2376
2515
|
return fallback;
|
|
2377
2516
|
}
|
|
@@ -2433,7 +2572,7 @@ function registerLeonardoImageToImageCommand(parent) {
|
|
|
2433
2572
|
if (!img) continue;
|
|
2434
2573
|
const ext = extFromUrl2(img.url);
|
|
2435
2574
|
const filename = `leonardo-i2i-${generationId.slice(0, 8)}-${String(i + 1).padStart(2, "0")}${ext}`;
|
|
2436
|
-
const filepath =
|
|
2575
|
+
const filepath = path21.join(outDir, filename);
|
|
2437
2576
|
try {
|
|
2438
2577
|
await downloadFile(img.url, filepath);
|
|
2439
2578
|
saved.push(filepath);
|
|
@@ -2443,8 +2582,8 @@ function registerLeonardoImageToImageCommand(parent) {
|
|
|
2443
2582
|
}
|
|
2444
2583
|
}
|
|
2445
2584
|
if (opts.output && saved[0]) {
|
|
2446
|
-
const dest =
|
|
2447
|
-
fs18.mkdirSync(
|
|
2585
|
+
const dest = path21.resolve(opts.output);
|
|
2586
|
+
fs18.mkdirSync(path21.dirname(dest), { recursive: true });
|
|
2448
2587
|
fs18.copyFileSync(saved[0], dest);
|
|
2449
2588
|
logger.success(`Copied to ${dest}`);
|
|
2450
2589
|
}
|
|
@@ -2457,7 +2596,7 @@ Generated ${saved.length} image(s):`);
|
|
|
2457
2596
|
|
|
2458
2597
|
// src/providers/leonardo/commands/video.ts
|
|
2459
2598
|
init_http_client();
|
|
2460
|
-
import
|
|
2599
|
+
import path22 from "path";
|
|
2461
2600
|
function v2DimsFor(resolution) {
|
|
2462
2601
|
if (resolution === "RESOLUTION_1080") return { width: 1920, height: 1080 };
|
|
2463
2602
|
if (resolution === "RESOLUTION_480") return { width: 854, height: 480 };
|
|
@@ -2568,7 +2707,7 @@ async function pollAndDownload(client, jobId, opts) {
|
|
|
2568
2707
|
logger.error(`No video URL in completed generation: ${JSON.stringify(gen)}`);
|
|
2569
2708
|
process.exit(1);
|
|
2570
2709
|
}
|
|
2571
|
-
const outPath = opts.output ??
|
|
2710
|
+
const outPath = opts.output ?? path22.join(getOutputDir(), `${opts.basename}-${jobId.slice(0, 8)}.mp4`);
|
|
2572
2711
|
await downloadFile(videoUrl, outPath);
|
|
2573
2712
|
logger.success(`Saved ${outPath}`);
|
|
2574
2713
|
await maybeDownloadThumb(gen, outPath, {
|
|
@@ -2675,7 +2814,7 @@ function registerLeonardoModelsCommand(parent) {
|
|
|
2675
2814
|
|
|
2676
2815
|
// src/providers/leonardo/commands/status.ts
|
|
2677
2816
|
init_http_client();
|
|
2678
|
-
import
|
|
2817
|
+
import path23 from "path";
|
|
2679
2818
|
var VIDEO_KEYS = ["motionMP4URL", "url"];
|
|
2680
2819
|
var IMAGE_EXT_RE2 = /\.(jpe?g|png|webp|gif)(\?|$)/i;
|
|
2681
2820
|
var VIDEO_EXT_RE = /\.(mp4|webm|mov|m4v)(\?|$)/i;
|
|
@@ -2740,7 +2879,7 @@ function registerLeonardoStatusCommand(parent) {
|
|
|
2740
2879
|
const outDir = getOutputDir();
|
|
2741
2880
|
let idx = 0;
|
|
2742
2881
|
for (const url of videos) {
|
|
2743
|
-
const dest = opts.output && videos.length === 1 && images.length === 0 ? opts.output :
|
|
2882
|
+
const dest = opts.output && videos.length === 1 && images.length === 0 ? opts.output : path23.join(outDir, `leonardo-${generationId.slice(0, 8)}-${idx}.mp4`);
|
|
2744
2883
|
await downloadFile(url, dest);
|
|
2745
2884
|
logger.success(`Saved ${dest}`);
|
|
2746
2885
|
await maybeDownloadThumb(gen, dest, {
|
|
@@ -2753,7 +2892,7 @@ function registerLeonardoStatusCommand(parent) {
|
|
|
2753
2892
|
for (const url of images) {
|
|
2754
2893
|
const m = url.match(IMAGE_EXT_RE2);
|
|
2755
2894
|
const ext = m?.[1] ? m[1].toLowerCase().replace("jpeg", "jpg") : "png";
|
|
2756
|
-
const dest =
|
|
2895
|
+
const dest = path23.join(outDir, `leonardo-${generationId.slice(0, 8)}-${idx}.${ext}`);
|
|
2757
2896
|
await downloadFile(url, dest);
|
|
2758
2897
|
logger.success(`Saved ${dest}`);
|
|
2759
2898
|
idx++;
|
|
@@ -2911,7 +3050,7 @@ function sleep4(ms) {
|
|
|
2911
3050
|
// src/providers/minimax/generators/music.ts
|
|
2912
3051
|
init_http_client();
|
|
2913
3052
|
import fs19 from "fs";
|
|
2914
|
-
import
|
|
3053
|
+
import path24 from "path";
|
|
2915
3054
|
async function generateMinimaxMusic(opts) {
|
|
2916
3055
|
const {
|
|
2917
3056
|
apiKey,
|
|
@@ -2956,7 +3095,7 @@ async function generateMinimaxMusic(opts) {
|
|
|
2956
3095
|
}
|
|
2957
3096
|
const durationMs = resp.extra_info?.music_duration ?? 0;
|
|
2958
3097
|
const outDir = getOutputDir();
|
|
2959
|
-
const dest =
|
|
3098
|
+
const dest = path24.join(outDir, `minimax_music_${Date.now()}.${outputFormat}`);
|
|
2960
3099
|
try {
|
|
2961
3100
|
if (audioData.startsWith("http")) {
|
|
2962
3101
|
const bytes = await fetchBytes(audioData);
|
|
@@ -2974,7 +3113,7 @@ async function generateMinimaxMusic(opts) {
|
|
|
2974
3113
|
const durSec = durationMs / 1e3;
|
|
2975
3114
|
logger?.success(`Saved: ${dest} (${durSec.toFixed(1)}s)`);
|
|
2976
3115
|
if (output) {
|
|
2977
|
-
fs19.mkdirSync(
|
|
3116
|
+
fs19.mkdirSync(path24.dirname(path24.resolve(output)), { recursive: true });
|
|
2978
3117
|
fs19.copyFileSync(dest, output);
|
|
2979
3118
|
}
|
|
2980
3119
|
return { status: "success", generatedAudio: dest, durationMs, model };
|
|
@@ -3050,7 +3189,7 @@ init_errors();
|
|
|
3050
3189
|
|
|
3051
3190
|
// src/providers/minimax/generators/speech.ts
|
|
3052
3191
|
import fs20 from "fs";
|
|
3053
|
-
import
|
|
3192
|
+
import path25 from "path";
|
|
3054
3193
|
async function generateMinimaxSpeech(opts) {
|
|
3055
3194
|
const {
|
|
3056
3195
|
apiKey,
|
|
@@ -3097,11 +3236,11 @@ async function generateMinimaxSpeech(opts) {
|
|
|
3097
3236
|
const audioBytes = Buffer.from(audioHex, "hex");
|
|
3098
3237
|
const ext = ["mp3", "wav", "flac"].includes(outputFormat) ? outputFormat : "mp3";
|
|
3099
3238
|
const outDir = getOutputDir();
|
|
3100
|
-
const dest =
|
|
3239
|
+
const dest = path25.join(outDir, `minimax_speech_${Date.now()}.${ext}`);
|
|
3101
3240
|
fs20.writeFileSync(dest, audioBytes);
|
|
3102
3241
|
logger?.success(`Saved: ${dest} (${(audioBytes.length / 1024).toFixed(1)} KB)`);
|
|
3103
3242
|
if (output) {
|
|
3104
|
-
fs20.mkdirSync(
|
|
3243
|
+
fs20.mkdirSync(path25.dirname(path25.resolve(output)), { recursive: true });
|
|
3105
3244
|
fs20.copyFileSync(dest, output);
|
|
3106
3245
|
}
|
|
3107
3246
|
return { status: "success", generatedAudio: dest, model };
|
|
@@ -3150,7 +3289,7 @@ init_errors();
|
|
|
3150
3289
|
|
|
3151
3290
|
// src/providers/minimax/generators/video.ts
|
|
3152
3291
|
import fs21 from "fs";
|
|
3153
|
-
import
|
|
3292
|
+
import path26 from "path";
|
|
3154
3293
|
async function generateMinimaxVideo(opts) {
|
|
3155
3294
|
const {
|
|
3156
3295
|
apiKey,
|
|
@@ -3196,7 +3335,7 @@ async function generateMinimaxVideo(opts) {
|
|
|
3196
3335
|
return { status: "error", error: `No file_id in poll result: ${JSON.stringify(pollResult)}` };
|
|
3197
3336
|
}
|
|
3198
3337
|
const outDir = getOutputDir();
|
|
3199
|
-
const dest =
|
|
3338
|
+
const dest = path26.join(outDir, `minimax_video_${Date.now()}.mp4`);
|
|
3200
3339
|
try {
|
|
3201
3340
|
await downloadMinimaxFile(fileId, apiKey, dest, logger);
|
|
3202
3341
|
} catch (e) {
|
|
@@ -3206,7 +3345,7 @@ async function generateMinimaxVideo(opts) {
|
|
|
3206
3345
|
const stat = fs21.statSync(dest);
|
|
3207
3346
|
const fileSizeMb = stat.size / (1024 * 1024);
|
|
3208
3347
|
if (output) {
|
|
3209
|
-
fs21.mkdirSync(
|
|
3348
|
+
fs21.mkdirSync(path26.dirname(path26.resolve(output)), { recursive: true });
|
|
3210
3349
|
fs21.copyFileSync(dest, output);
|
|
3211
3350
|
}
|
|
3212
3351
|
await maybeDownloadThumb(pollResult, dest, {
|
|
@@ -3271,7 +3410,7 @@ Generated video: ${result.generatedVideo}`);
|
|
|
3271
3410
|
init_errors();
|
|
3272
3411
|
init_http_client();
|
|
3273
3412
|
import fs22 from "fs";
|
|
3274
|
-
import
|
|
3413
|
+
import path27 from "path";
|
|
3275
3414
|
async function generateMinimaxImage(opts) {
|
|
3276
3415
|
const {
|
|
3277
3416
|
apiKey,
|
|
@@ -3309,7 +3448,7 @@ async function generateMinimaxImage(opts) {
|
|
|
3309
3448
|
try {
|
|
3310
3449
|
const bytes = await fetchBytes(url);
|
|
3311
3450
|
const fname = `minimax_image_${Date.now()}_${i}.png`;
|
|
3312
|
-
const dest =
|
|
3451
|
+
const dest = path27.join(outDir, fname);
|
|
3313
3452
|
fs22.writeFileSync(dest, bytes);
|
|
3314
3453
|
savedFiles.push(dest);
|
|
3315
3454
|
logger?.success(`Saved: ${dest}`);
|
|
@@ -3321,7 +3460,7 @@ async function generateMinimaxImage(opts) {
|
|
|
3321
3460
|
}
|
|
3322
3461
|
}
|
|
3323
3462
|
if (output && savedFiles[0]) {
|
|
3324
|
-
fs22.mkdirSync(
|
|
3463
|
+
fs22.mkdirSync(path27.dirname(path27.resolve(output)), { recursive: true });
|
|
3325
3464
|
fs22.copyFileSync(savedFiles[0], output);
|
|
3326
3465
|
}
|
|
3327
3466
|
return { status: "success", generatedImages: savedFiles, model };
|
|
@@ -3359,7 +3498,7 @@ Generated ${result.generatedImages?.length ?? 0} image(s):`);
|
|
|
3359
3498
|
init_errors();
|
|
3360
3499
|
init_http_client();
|
|
3361
3500
|
import fs23 from "fs";
|
|
3362
|
-
import
|
|
3501
|
+
import path28 from "path";
|
|
3363
3502
|
function registerMinimaxImageToImageCommand(parent) {
|
|
3364
3503
|
parent.command("image-to-image").alias("i2i").description(
|
|
3365
3504
|
"MiniMax character/subject preservation. CAVEAT: NOT free-form image editing \u2014 the --ref image is used only as a character/face reference for a NEW scene described by --prompt."
|
|
@@ -3414,7 +3553,7 @@ function registerMinimaxImageToImageCommand(parent) {
|
|
|
3414
3553
|
if (!url) continue;
|
|
3415
3554
|
try {
|
|
3416
3555
|
const bytes = await fetchBytes(url);
|
|
3417
|
-
const dest =
|
|
3556
|
+
const dest = path28.join(outDir, `minimax-i2i-${ts}-${i + 1}.png`);
|
|
3418
3557
|
fs23.writeFileSync(dest, bytes);
|
|
3419
3558
|
saved.push(dest);
|
|
3420
3559
|
logger.success(`Saved: ${dest}`);
|
|
@@ -3426,7 +3565,7 @@ function registerMinimaxImageToImageCommand(parent) {
|
|
|
3426
3565
|
}
|
|
3427
3566
|
}
|
|
3428
3567
|
if (opts.output && saved[0]) {
|
|
3429
|
-
fs23.mkdirSync(
|
|
3568
|
+
fs23.mkdirSync(path28.dirname(path28.resolve(opts.output)), { recursive: true });
|
|
3430
3569
|
fs23.copyFileSync(saved[0], opts.output);
|
|
3431
3570
|
logger.success(`Copied to: ${opts.output}`);
|
|
3432
3571
|
}
|
|
@@ -3449,7 +3588,7 @@ function registerMinimaxCommands(program2) {
|
|
|
3449
3588
|
|
|
3450
3589
|
// src/providers/openrouter/client.ts
|
|
3451
3590
|
import fs24 from "fs";
|
|
3452
|
-
import
|
|
3591
|
+
import path29 from "path";
|
|
3453
3592
|
init_errors();
|
|
3454
3593
|
init_http_client();
|
|
3455
3594
|
var OPENROUTER_API_URL = "https://openrouter.ai/api/v1/chat/completions";
|
|
@@ -3538,7 +3677,7 @@ async function generateOpenRouterImage(opts) {
|
|
|
3538
3677
|
if (!url) continue;
|
|
3539
3678
|
try {
|
|
3540
3679
|
const bytes = await fetchBytes(url);
|
|
3541
|
-
const dest =
|
|
3680
|
+
const dest = path29.join(outDir, `openrouter_image_${Date.now()}_${i}.png`);
|
|
3542
3681
|
fs24.writeFileSync(dest, bytes);
|
|
3543
3682
|
savedFiles.push(dest);
|
|
3544
3683
|
logger?.success(`Saved: ${dest}`);
|
|
@@ -3550,7 +3689,7 @@ async function generateOpenRouterImage(opts) {
|
|
|
3550
3689
|
}
|
|
3551
3690
|
}
|
|
3552
3691
|
if (output && savedFiles[0]) {
|
|
3553
|
-
fs24.mkdirSync(
|
|
3692
|
+
fs24.mkdirSync(path29.dirname(path29.resolve(output)), { recursive: true });
|
|
3554
3693
|
fs24.writeFileSync(output, fs24.readFileSync(savedFiles[0]));
|
|
3555
3694
|
}
|
|
3556
3695
|
return { status: "success", generatedImages: savedFiles, model: usedModel };
|
|
@@ -3587,7 +3726,7 @@ Generated ${result.generatedImages?.length ?? 0} image(s):`);
|
|
|
3587
3726
|
|
|
3588
3727
|
// src/providers/openrouter/commands/image-to-image.ts
|
|
3589
3728
|
import fs25 from "fs";
|
|
3590
|
-
import
|
|
3729
|
+
import path30 from "path";
|
|
3591
3730
|
init_errors();
|
|
3592
3731
|
init_http_client();
|
|
3593
3732
|
var DEFAULT_MODEL = "google/gemini-2.5-flash-image";
|
|
@@ -3674,13 +3813,13 @@ async function runImageToImage(opts) {
|
|
|
3674
3813
|
const url = messageImages[i]?.image_url?.url;
|
|
3675
3814
|
if (!url) continue;
|
|
3676
3815
|
const bytes = await fetchBytes(url);
|
|
3677
|
-
const dest =
|
|
3816
|
+
const dest = path30.join(outDir, `openrouter-i2i-${ts}-${i + 1}.png`);
|
|
3678
3817
|
fs25.writeFileSync(dest, bytes);
|
|
3679
3818
|
saved.push(dest);
|
|
3680
3819
|
opts.logger.success(`Saved: ${dest}`);
|
|
3681
3820
|
}
|
|
3682
3821
|
if (opts.output && saved[0]) {
|
|
3683
|
-
fs25.mkdirSync(
|
|
3822
|
+
fs25.mkdirSync(path30.dirname(path30.resolve(opts.output)), { recursive: true });
|
|
3684
3823
|
fs25.copyFileSync(saved[0], opts.output);
|
|
3685
3824
|
opts.logger.success(`Copied to: ${opts.output}`);
|
|
3686
3825
|
}
|
|
@@ -3728,12 +3867,12 @@ async function listVideoModels(apiKey = requireOpenRouterKey()) {
|
|
|
3728
3867
|
|
|
3729
3868
|
// src/providers/openrouter/commands/video-status.ts
|
|
3730
3869
|
import fs26 from "fs";
|
|
3731
|
-
import
|
|
3870
|
+
import path31 from "path";
|
|
3732
3871
|
async function downloadOpenRouterVideo(opts) {
|
|
3733
3872
|
const { jobId, logger, source } = opts;
|
|
3734
3873
|
const apiKey = requireOpenRouterKey();
|
|
3735
3874
|
const url = videoContentUrl(jobId);
|
|
3736
|
-
const dest =
|
|
3875
|
+
const dest = path31.join(getOutputDir(), `openrouter_video_${jobId}.mp4`);
|
|
3737
3876
|
const r = await globalThis.fetch(url, { headers: { Authorization: `Bearer ${apiKey}` } });
|
|
3738
3877
|
if (!r.ok) {
|
|
3739
3878
|
logger.error(`Download HTTP ${r.status}`);
|
|
@@ -3743,13 +3882,13 @@ async function downloadOpenRouterVideo(opts) {
|
|
|
3743
3882
|
logger.error("Empty response body");
|
|
3744
3883
|
process.exit(1);
|
|
3745
3884
|
}
|
|
3746
|
-
fs26.mkdirSync(
|
|
3885
|
+
fs26.mkdirSync(path31.dirname(dest), { recursive: true });
|
|
3747
3886
|
const ws = fs26.createWriteStream(dest);
|
|
3748
3887
|
const { pipeline: pipeline2 } = await import("stream/promises");
|
|
3749
3888
|
await pipeline2(r.body, ws);
|
|
3750
3889
|
logger.success(`Saved ${dest}`);
|
|
3751
3890
|
if (opts.output) {
|
|
3752
|
-
fs26.mkdirSync(
|
|
3891
|
+
fs26.mkdirSync(path31.dirname(path31.resolve(opts.output)), { recursive: true });
|
|
3753
3892
|
fs26.copyFileSync(dest, opts.output);
|
|
3754
3893
|
logger.success(`Copied to ${opts.output}`);
|
|
3755
3894
|
}
|
|
@@ -4099,7 +4238,7 @@ function registerCheckCommand(program2) {
|
|
|
4099
4238
|
// src/commands/doc/convert.ts
|
|
4100
4239
|
init_errors();
|
|
4101
4240
|
import fs27 from "fs";
|
|
4102
|
-
import
|
|
4241
|
+
import path32 from "path";
|
|
4103
4242
|
|
|
4104
4243
|
// src/commands/doc/default-prompt.ts
|
|
4105
4244
|
var DEFAULT_CONVERT_PROMPT = `Convert this document to clean, well-formatted Markdown.
|
|
@@ -4124,14 +4263,14 @@ async function convertDocuments(opts) {
|
|
|
4124
4263
|
}
|
|
4125
4264
|
let outputPath;
|
|
4126
4265
|
if (output) {
|
|
4127
|
-
outputPath =
|
|
4266
|
+
outputPath = path32.resolve(output);
|
|
4128
4267
|
} else if (autoName && inputs.length === 1 && inputs[0]) {
|
|
4129
|
-
const base =
|
|
4130
|
-
outputPath =
|
|
4268
|
+
const base = path32.basename(inputs[0], path32.extname(inputs[0]));
|
|
4269
|
+
outputPath = path32.join(getOutputDir(), `${base}-extraction.md`);
|
|
4131
4270
|
} else {
|
|
4132
|
-
outputPath =
|
|
4271
|
+
outputPath = path32.join(getOutputDir(), "document-extraction.md");
|
|
4133
4272
|
}
|
|
4134
|
-
fs27.mkdirSync(
|
|
4273
|
+
fs27.mkdirSync(path32.dirname(outputPath), { recursive: true });
|
|
4135
4274
|
const results = [];
|
|
4136
4275
|
for (let i = 0; i < inputs.length; i++) {
|
|
4137
4276
|
const filePath = inputs[i];
|
|
@@ -4182,7 +4321,7 @@ async function convertDocuments(opts) {
|
|
|
4182
4321
|
`);
|
|
4183
4322
|
for (const r of results) {
|
|
4184
4323
|
lines.push(`
|
|
4185
|
-
## ${
|
|
4324
|
+
## ${path32.basename(r.file)}
|
|
4186
4325
|
`);
|
|
4187
4326
|
if (r.status === "success" && r.markdown) {
|
|
4188
4327
|
lines.push(`${r.markdown}
|
|
@@ -4242,11 +4381,11 @@ function registerDocCommands(program2) {
|
|
|
4242
4381
|
// src/commands/media/index.ts
|
|
4243
4382
|
init_errors();
|
|
4244
4383
|
import fs30 from "fs";
|
|
4245
|
-
import
|
|
4384
|
+
import path35 from "path";
|
|
4246
4385
|
|
|
4247
4386
|
// src/commands/media/batch.ts
|
|
4248
4387
|
import fs28 from "fs";
|
|
4249
|
-
import
|
|
4388
|
+
import path33 from "path";
|
|
4250
4389
|
|
|
4251
4390
|
// src/commands/media/detect-type.ts
|
|
4252
4391
|
var VIDEO_EXTS = /* @__PURE__ */ new Set([".mp4", ".mov", ".avi", ".mkv", ".webm", ".flv", ".mpg", ".mpeg"]);
|
|
@@ -4445,7 +4584,7 @@ async function batchOptimize(opts) {
|
|
|
4445
4584
|
fs28.mkdirSync(outputDir, { recursive: true });
|
|
4446
4585
|
const entries = fs28.readdirSync(inputDir);
|
|
4447
4586
|
const mediaFiles = entries.filter((f) => {
|
|
4448
|
-
const ext =
|
|
4587
|
+
const ext = path33.extname(f).toLowerCase();
|
|
4449
4588
|
return ALL_EXTS.has(ext);
|
|
4450
4589
|
});
|
|
4451
4590
|
if (mediaFiles.length === 0) {
|
|
@@ -4456,9 +4595,9 @@ async function batchOptimize(opts) {
|
|
|
4456
4595
|
let succeeded = 0;
|
|
4457
4596
|
const errors = [];
|
|
4458
4597
|
for (const file of mediaFiles) {
|
|
4459
|
-
const inputPath =
|
|
4460
|
-
const outputPath =
|
|
4461
|
-
const ext =
|
|
4598
|
+
const inputPath = path33.join(inputDir, file);
|
|
4599
|
+
const outputPath = path33.join(outputDir, file);
|
|
4600
|
+
const ext = path33.extname(file).toLowerCase();
|
|
4462
4601
|
const kind = extToKind(ext);
|
|
4463
4602
|
logger?.debug(`Processing: ${file}`);
|
|
4464
4603
|
try {
|
|
@@ -4496,7 +4635,7 @@ async function batchOptimize(opts) {
|
|
|
4496
4635
|
// src/commands/media/split-video.ts
|
|
4497
4636
|
init_errors();
|
|
4498
4637
|
import fs29 from "fs";
|
|
4499
|
-
import
|
|
4638
|
+
import path34 from "path";
|
|
4500
4639
|
async function splitVideo(opts) {
|
|
4501
4640
|
await assertFfmpeg();
|
|
4502
4641
|
const { input, outputDir, chunkDuration = 3600, verbose, logger } = opts;
|
|
@@ -4508,9 +4647,9 @@ async function splitVideo(opts) {
|
|
|
4508
4647
|
return [input];
|
|
4509
4648
|
}
|
|
4510
4649
|
fs29.mkdirSync(outputDir, { recursive: true });
|
|
4511
|
-
const ext =
|
|
4512
|
-
const baseName =
|
|
4513
|
-
const outputPattern =
|
|
4650
|
+
const ext = path34.extname(input) || ".mp4";
|
|
4651
|
+
const baseName = path34.basename(input, ext);
|
|
4652
|
+
const outputPattern = path34.join(outputDir, `${baseName}_chunk_%03d${ext}`);
|
|
4514
4653
|
logger?.debug(`Splitting into ~${numChunks} chunks of ${chunkDuration}s each`);
|
|
4515
4654
|
const args = [
|
|
4516
4655
|
"-i",
|
|
@@ -4529,7 +4668,7 @@ async function splitVideo(opts) {
|
|
|
4529
4668
|
await runFfmpeg(args, { verbose, logger });
|
|
4530
4669
|
const files = [];
|
|
4531
4670
|
for (let i = 0; i < numChunks + 1; i++) {
|
|
4532
|
-
const fname =
|
|
4671
|
+
const fname = path34.join(outputDir, `${baseName}_chunk_${String(i).padStart(3, "0")}${ext}`);
|
|
4533
4672
|
if (fs29.existsSync(fname)) files.push(fname);
|
|
4534
4673
|
}
|
|
4535
4674
|
logger?.success(`Created ${files.length} chunks in ${outputDir}`);
|
|
@@ -4545,8 +4684,8 @@ function registerMediaCommands(program2) {
|
|
|
4545
4684
|
async (opts) => {
|
|
4546
4685
|
const logger = createLogger({ verbose: opts.verbose ?? false });
|
|
4547
4686
|
if (!fs30.existsSync(opts.input)) throw new ValidationError(`Input not found: ${opts.input}`);
|
|
4548
|
-
fs30.mkdirSync(
|
|
4549
|
-
const ext =
|
|
4687
|
+
fs30.mkdirSync(path35.dirname(path35.resolve(opts.output)), { recursive: true });
|
|
4688
|
+
const ext = path35.extname(opts.input).toLowerCase();
|
|
4550
4689
|
const kind = extToKind(ext);
|
|
4551
4690
|
if (!kind) throw new ValidationError(`Unsupported file type: ${ext}`);
|
|
4552
4691
|
const quality = Number.parseInt(opts.quality, 10);
|