@mrgoonie/multix 0.5.1 → 0.6.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/CHANGELOG.md +14 -0
- package/README.md +14 -0
- package/dist/cli.js +541 -315
- package/package.json +1 -1
- package/skill/SKILL.md +14 -1
package/dist/cli.js
CHANGED
|
@@ -414,9 +414,9 @@ var BytePlusClient = class {
|
|
|
414
414
|
}
|
|
415
415
|
apiKey;
|
|
416
416
|
baseUrl;
|
|
417
|
-
async request(
|
|
417
|
+
async request(path47, opts = {}) {
|
|
418
418
|
const base = this.baseUrl.replace(/\/$/, "");
|
|
419
|
-
const url = new URL(base +
|
|
419
|
+
const url = new URL(base + path47);
|
|
420
420
|
if (opts.query) {
|
|
421
421
|
for (const [k, v] of Object.entries(opts.query)) {
|
|
422
422
|
if (v !== void 0 && v !== null) url.searchParams.set(k, String(v));
|
|
@@ -434,14 +434,14 @@ var BytePlusClient = class {
|
|
|
434
434
|
timeoutMs: opts.timeoutMs ?? 12e4
|
|
435
435
|
});
|
|
436
436
|
}
|
|
437
|
-
get(
|
|
438
|
-
return this.request(
|
|
437
|
+
get(path47, query, logger) {
|
|
438
|
+
return this.request(path47, { method: "GET", query, logger });
|
|
439
439
|
}
|
|
440
|
-
post(
|
|
441
|
-
return this.request(
|
|
440
|
+
post(path47, body, logger) {
|
|
441
|
+
return this.request(path47, { method: "POST", body, logger });
|
|
442
442
|
}
|
|
443
|
-
delete(
|
|
444
|
-
return this.request(
|
|
443
|
+
delete(path47, logger) {
|
|
444
|
+
return this.request(path47, { method: "DELETE", logger });
|
|
445
445
|
}
|
|
446
446
|
};
|
|
447
447
|
function createBytePlusClient() {
|
|
@@ -569,10 +569,197 @@ Task: ${taskId}`);
|
|
|
569
569
|
);
|
|
570
570
|
}
|
|
571
571
|
|
|
572
|
+
// src/core/image-webp-finalize.ts
|
|
573
|
+
init_errors();
|
|
574
|
+
import { spawnSync } from "child_process";
|
|
575
|
+
import fs6 from "fs";
|
|
576
|
+
import path6 from "path";
|
|
577
|
+
import { Option } from "commander";
|
|
578
|
+
|
|
579
|
+
// src/core/image-header.ts
|
|
580
|
+
import fs5 from "fs";
|
|
581
|
+
function sniffImageKind(buf) {
|
|
582
|
+
if (buf.length >= 8 && buf.readUInt32BE(0) === 2303741511) return "png";
|
|
583
|
+
if (buf.length >= 3 && buf[0] === 255 && buf[1] === 216 && buf[2] === 255) return "jpeg";
|
|
584
|
+
if (buf.length >= 12 && buf.toString("ascii", 0, 4) === "RIFF" && buf.toString("ascii", 8, 12) === "WEBP")
|
|
585
|
+
return "webp";
|
|
586
|
+
return void 0;
|
|
587
|
+
}
|
|
588
|
+
function readPng(buf) {
|
|
589
|
+
if (buf.length < 33) return void 0;
|
|
590
|
+
const width = buf.readUInt32BE(16);
|
|
591
|
+
const height = buf.readUInt32BE(20);
|
|
592
|
+
const colorType = buf[25];
|
|
593
|
+
let hasAlpha = colorType === 4 || colorType === 6;
|
|
594
|
+
let offset = 8;
|
|
595
|
+
while (!hasAlpha && offset + 8 <= buf.length) {
|
|
596
|
+
const len = buf.readUInt32BE(offset);
|
|
597
|
+
const type = buf.toString("ascii", offset + 4, offset + 8);
|
|
598
|
+
if (type === "tRNS") hasAlpha = true;
|
|
599
|
+
if (type === "IDAT" || type === "IEND") break;
|
|
600
|
+
offset += 12 + len;
|
|
601
|
+
}
|
|
602
|
+
return { kind: "png", width, height, hasAlpha };
|
|
603
|
+
}
|
|
604
|
+
function readJpeg(buf) {
|
|
605
|
+
let offset = 2;
|
|
606
|
+
while (offset + 9 < buf.length) {
|
|
607
|
+
if (buf[offset] !== 255) {
|
|
608
|
+
offset++;
|
|
609
|
+
continue;
|
|
610
|
+
}
|
|
611
|
+
const marker = buf[offset + 1] ?? 0;
|
|
612
|
+
if (marker >= 192 && marker <= 207 && ![196, 200, 204].includes(marker)) {
|
|
613
|
+
return {
|
|
614
|
+
kind: "jpeg",
|
|
615
|
+
height: buf.readUInt16BE(offset + 5),
|
|
616
|
+
width: buf.readUInt16BE(offset + 7),
|
|
617
|
+
hasAlpha: false
|
|
618
|
+
};
|
|
619
|
+
}
|
|
620
|
+
if (marker === 216 || marker === 1 || marker >= 208 && marker <= 215) {
|
|
621
|
+
offset += 2;
|
|
622
|
+
continue;
|
|
623
|
+
}
|
|
624
|
+
offset += 2 + buf.readUInt16BE(offset + 2);
|
|
625
|
+
}
|
|
626
|
+
return void 0;
|
|
627
|
+
}
|
|
628
|
+
function readWebp(buf) {
|
|
629
|
+
if (buf.length < 30) return void 0;
|
|
630
|
+
const chunk = buf.toString("ascii", 12, 16);
|
|
631
|
+
if (chunk === "VP8X") {
|
|
632
|
+
const flags = buf[20] ?? 0;
|
|
633
|
+
return {
|
|
634
|
+
kind: "webp",
|
|
635
|
+
width: 1 + buf.readUIntLE(24, 3),
|
|
636
|
+
height: 1 + buf.readUIntLE(27, 3),
|
|
637
|
+
hasAlpha: (flags & 16) !== 0
|
|
638
|
+
};
|
|
639
|
+
}
|
|
640
|
+
if (chunk === "VP8L") {
|
|
641
|
+
const bits = buf.readUInt32LE(21);
|
|
642
|
+
return {
|
|
643
|
+
kind: "webp",
|
|
644
|
+
width: 1 + (bits & 16383),
|
|
645
|
+
height: 1 + (bits >>> 14 & 16383),
|
|
646
|
+
hasAlpha: (bits >>> 28 & 1) === 1
|
|
647
|
+
};
|
|
648
|
+
}
|
|
649
|
+
if (chunk === "VP8 ") {
|
|
650
|
+
return {
|
|
651
|
+
kind: "webp",
|
|
652
|
+
width: buf.readUInt16LE(26) & 16383,
|
|
653
|
+
height: buf.readUInt16LE(28) & 16383,
|
|
654
|
+
hasAlpha: false
|
|
655
|
+
};
|
|
656
|
+
}
|
|
657
|
+
return void 0;
|
|
658
|
+
}
|
|
659
|
+
function readImageHeader(buf) {
|
|
660
|
+
const kind = sniffImageKind(buf);
|
|
661
|
+
if (kind === "png") return readPng(buf);
|
|
662
|
+
if (kind === "jpeg") return readJpeg(buf);
|
|
663
|
+
if (kind === "webp") return readWebp(buf);
|
|
664
|
+
return void 0;
|
|
665
|
+
}
|
|
666
|
+
function readImageHeaderFromFile(file) {
|
|
667
|
+
return readImageHeader(fs5.readFileSync(file));
|
|
668
|
+
}
|
|
669
|
+
|
|
670
|
+
// src/core/image-webp-finalize.ts
|
|
671
|
+
var CWEBP_ARGS = ["-q", "85", "-m", "6", "-metadata", "none"];
|
|
672
|
+
var IMAGE_EXTS = /* @__PURE__ */ new Set([".png", ".jpg", ".jpeg", ".webp", ".gif", ".bmp", ".tif", ".tiff"]);
|
|
673
|
+
function imageFormatOption() {
|
|
674
|
+
return new Option(
|
|
675
|
+
"--image-format <mode>",
|
|
676
|
+
"Final image format: webp (default, cwebp -q 85) | original (keep provider format/quality)"
|
|
677
|
+
);
|
|
678
|
+
}
|
|
679
|
+
function noWebpOption() {
|
|
680
|
+
return new Option("--no-webp", "Keep the provider's original image format and quality");
|
|
681
|
+
}
|
|
682
|
+
function resolveImageFormatMode(opts, explicitProviderFormat = false) {
|
|
683
|
+
if (opts.webp === false && opts.imageFormat === void 0) return "original";
|
|
684
|
+
const raw = opts.imageFormat ?? process.env.MULTIX_IMAGE_FORMAT;
|
|
685
|
+
if (raw !== void 0 && raw !== "") {
|
|
686
|
+
const mode = raw.toLowerCase();
|
|
687
|
+
if (mode !== "webp" && mode !== "original") {
|
|
688
|
+
throw new ValidationError(`Invalid --image-format: ${raw}. Valid: webp, original`);
|
|
689
|
+
}
|
|
690
|
+
return mode;
|
|
691
|
+
}
|
|
692
|
+
if (explicitProviderFormat) return "original";
|
|
693
|
+
const ext = opts.output ? path6.extname(opts.output).toLowerCase() : "";
|
|
694
|
+
if (ext && ext !== ".webp" && IMAGE_EXTS.has(ext)) return "original";
|
|
695
|
+
return "webp";
|
|
696
|
+
}
|
|
697
|
+
function hasCwebp() {
|
|
698
|
+
const res = spawnSync("cwebp", ["-version"], { stdio: "ignore" });
|
|
699
|
+
return !res.error && res.status === 0;
|
|
700
|
+
}
|
|
701
|
+
function webpTarget(file) {
|
|
702
|
+
const ext = path6.extname(file);
|
|
703
|
+
return ext.toLowerCase() === ".webp" ? file : `${file.slice(0, -ext.length || void 0)}.webp`;
|
|
704
|
+
}
|
|
705
|
+
function convertToWebp(file, logger, keepPath = false) {
|
|
706
|
+
const src = readImageHeaderFromFile(file);
|
|
707
|
+
if (!src) {
|
|
708
|
+
logger?.warn(`Skipping WebP conversion (unrecognized image): ${file}`);
|
|
709
|
+
return file;
|
|
710
|
+
}
|
|
711
|
+
if (src.kind === "webp") {
|
|
712
|
+
const target2 = keepPath ? file : webpTarget(file);
|
|
713
|
+
if (target2 !== file) fs6.renameSync(file, target2);
|
|
714
|
+
return target2;
|
|
715
|
+
}
|
|
716
|
+
const target = keepPath ? file : webpTarget(file);
|
|
717
|
+
const tmp = `${target}.tmp-${process.pid}.webp`;
|
|
718
|
+
const res = spawnSync("cwebp", [...CWEBP_ARGS, file, "-o", tmp], { encoding: "utf8" });
|
|
719
|
+
if (res.error || res.status !== 0) {
|
|
720
|
+
fs6.rmSync(tmp, { force: true });
|
|
721
|
+
logger?.warn(`cwebp failed, keeping original ${file}: ${res.stderr || res.error?.message}`);
|
|
722
|
+
return file;
|
|
723
|
+
}
|
|
724
|
+
const out = fs6.existsSync(tmp) ? readImageHeaderFromFile(tmp) : void 0;
|
|
725
|
+
const problem = !out ? "output is not a valid WebP" : out.width !== src.width || out.height !== src.height ? `size changed ${src.width}x${src.height} -> ${out.width}x${out.height}` : src.hasAlpha && !out.hasAlpha ? "transparency lost" : void 0;
|
|
726
|
+
if (problem) {
|
|
727
|
+
fs6.rmSync(tmp, { force: true });
|
|
728
|
+
logger?.warn(`WebP verification failed (${problem}), keeping original ${file}`);
|
|
729
|
+
return file;
|
|
730
|
+
}
|
|
731
|
+
if (target !== file) fs6.rmSync(file, { force: true });
|
|
732
|
+
fs6.renameSync(tmp, target);
|
|
733
|
+
logger?.debug(`WebP verified ${out?.width}x${out?.height} alpha=${out?.hasAlpha}: ${target}`);
|
|
734
|
+
return target;
|
|
735
|
+
}
|
|
736
|
+
function finalizeGeneratedImages(files, opts) {
|
|
737
|
+
const mode = resolveImageFormatMode(opts, opts.explicitProviderFormat);
|
|
738
|
+
if (mode === "original" || files.length === 0) return files;
|
|
739
|
+
if (!hasCwebp()) {
|
|
740
|
+
opts.logger?.warn(
|
|
741
|
+
"cwebp not found on PATH; keeping original image format (install libwebp, or pass --image-format original)"
|
|
742
|
+
);
|
|
743
|
+
return files;
|
|
744
|
+
}
|
|
745
|
+
const outputAbs = opts.output ? path6.resolve(opts.output) : void 0;
|
|
746
|
+
const wroteOutputDirectly = outputAbs !== void 0 && files.some((f) => path6.resolve(f) === outputAbs);
|
|
747
|
+
const finals = files.map(
|
|
748
|
+
(f) => convertToWebp(f, opts.logger, outputAbs !== void 0 && path6.resolve(f) === outputAbs)
|
|
749
|
+
);
|
|
750
|
+
if (opts.output && outputAbs && !wroteOutputDirectly && finals[0] && fs6.existsSync(outputAbs)) {
|
|
751
|
+
const outExt = path6.extname(outputAbs).toLowerCase();
|
|
752
|
+
if ((outExt === ".webp" || outExt === "") && path6.extname(finals[0]).toLowerCase() === ".webp") {
|
|
753
|
+
fs6.copyFileSync(finals[0], outputAbs);
|
|
754
|
+
}
|
|
755
|
+
}
|
|
756
|
+
return finals;
|
|
757
|
+
}
|
|
758
|
+
|
|
572
759
|
// src/providers/byteplus/generators/image.ts
|
|
573
760
|
init_http_client();
|
|
574
|
-
import
|
|
575
|
-
import
|
|
761
|
+
import fs7 from "fs";
|
|
762
|
+
import path7 from "path";
|
|
576
763
|
var ASPECT_TO_SIZE = {
|
|
577
764
|
"1:1": "2048x2048",
|
|
578
765
|
"16:9": "2560x1440",
|
|
@@ -636,14 +823,14 @@ async function generateSeedream(client, opts) {
|
|
|
636
823
|
}
|
|
637
824
|
const ext = (() => {
|
|
638
825
|
try {
|
|
639
|
-
const e =
|
|
826
|
+
const e = path7.extname(new URL(url).pathname);
|
|
640
827
|
return e || ".jpg";
|
|
641
828
|
} catch {
|
|
642
829
|
return ".jpg";
|
|
643
830
|
}
|
|
644
831
|
})();
|
|
645
|
-
const filename = opts.output && items.length === 1 ?
|
|
646
|
-
|
|
832
|
+
const filename = opts.output && items.length === 1 ? path7.resolve(opts.output) : path7.join(outDir, `byteplus-image-${ts}-${i + 1}${ext}`);
|
|
833
|
+
fs7.mkdirSync(path7.dirname(filename), { recursive: true });
|
|
647
834
|
await downloadFile(url, filename);
|
|
648
835
|
saved.push({ path: filename, url, seed: item.seed });
|
|
649
836
|
logger?.success(`Saved ${filename}`);
|
|
@@ -658,7 +845,7 @@ function registerBytePlusGenerateCommand(parent) {
|
|
|
658
845
|
"Reference image (URL or local path); repeatable",
|
|
659
846
|
collect,
|
|
660
847
|
[]
|
|
661
|
-
).option("--output <path>", "Save single image to this path").option("-v, --verbose", "Verbose logging").action(
|
|
848
|
+
).option("--output <path>", "Save single image to this path").addOption(imageFormatOption()).addOption(noWebpOption()).option("-v, --verbose", "Verbose logging").action(
|
|
662
849
|
async (opts) => {
|
|
663
850
|
const logger = createLogger({ verbose: opts.verbose ?? false });
|
|
664
851
|
const client = createBytePlusClient();
|
|
@@ -674,9 +861,13 @@ function registerBytePlusGenerateCommand(parent) {
|
|
|
674
861
|
output: opts.output,
|
|
675
862
|
logger
|
|
676
863
|
});
|
|
864
|
+
const finalImages = finalizeGeneratedImages(
|
|
865
|
+
saved.map((f) => f.path),
|
|
866
|
+
{ ...opts, logger }
|
|
867
|
+
);
|
|
677
868
|
console.log(`
|
|
678
|
-
Generated ${
|
|
679
|
-
for (const f of
|
|
869
|
+
Generated ${finalImages.length} image(s):`);
|
|
870
|
+
for (const f of finalImages) console.log(` ${f}`);
|
|
680
871
|
}
|
|
681
872
|
);
|
|
682
873
|
}
|
|
@@ -693,7 +884,7 @@ function registerBytePlusImageToImageCommand(parent) {
|
|
|
693
884
|
"Reference image (URL or local path); repeatable for multi-ref",
|
|
694
885
|
collect2,
|
|
695
886
|
[]
|
|
696
|
-
).option("-m, --model <id>", "Model id (default seedream-4-0-250828)").option("--size <size>", "Image size (e.g. 1024x1024, 1K, 2K, 4K)").option("--aspect-ratio <ratio>", "Aspect ratio (e.g. 1:1, 16:9, 9:16); maps to size").option("-n, --num-images <n>", "Number of images", "1").option("--seed <n>", "Fixed seed").option("--no-watermark", "Disable watermark").option("--output <path>", "Save single image to this path").option("-v, --verbose", "Verbose logging").action(
|
|
887
|
+
).option("-m, --model <id>", "Model id (default seedream-4-0-250828)").option("--size <size>", "Image size (e.g. 1024x1024, 1K, 2K, 4K)").option("--aspect-ratio <ratio>", "Aspect ratio (e.g. 1:1, 16:9, 9:16); maps to size").option("-n, --num-images <n>", "Number of images", "1").option("--seed <n>", "Fixed seed").option("--no-watermark", "Disable watermark").option("--output <path>", "Save single image to this path").addOption(imageFormatOption()).addOption(noWebpOption()).option("-v, --verbose", "Verbose logging").action(
|
|
697
888
|
async (opts) => {
|
|
698
889
|
if (!opts.ref || opts.ref.length === 0) {
|
|
699
890
|
throw new Error("At least one --ref is required for image-to-image.");
|
|
@@ -712,9 +903,13 @@ function registerBytePlusImageToImageCommand(parent) {
|
|
|
712
903
|
output: opts.output,
|
|
713
904
|
logger
|
|
714
905
|
});
|
|
906
|
+
const finalImages = finalizeGeneratedImages(
|
|
907
|
+
saved.map((f) => f.path),
|
|
908
|
+
{ ...opts, logger }
|
|
909
|
+
);
|
|
715
910
|
console.log(`
|
|
716
|
-
Generated ${
|
|
717
|
-
for (const f of
|
|
911
|
+
Generated ${finalImages.length} image(s):`);
|
|
912
|
+
for (const f of finalImages) console.log(` ${f}`);
|
|
718
913
|
}
|
|
719
914
|
);
|
|
720
915
|
}
|
|
@@ -723,12 +918,12 @@ function collect2(value, prev) {
|
|
|
723
918
|
}
|
|
724
919
|
|
|
725
920
|
// src/providers/byteplus/commands/image-to-video.ts
|
|
726
|
-
import
|
|
921
|
+
import path9 from "path";
|
|
727
922
|
|
|
728
923
|
// src/core/video-thumb.ts
|
|
729
924
|
init_http_client();
|
|
730
|
-
import
|
|
731
|
-
import
|
|
925
|
+
import fs8 from "fs";
|
|
926
|
+
import path8 from "path";
|
|
732
927
|
var THUMB_KEYS = /* @__PURE__ */ new Set([
|
|
733
928
|
"cover_image_url",
|
|
734
929
|
"coverImageUrl",
|
|
@@ -778,9 +973,9 @@ function detectThumbUrl(obj) {
|
|
|
778
973
|
async function downloadThumbBeside(thumbUrl, videoPath, logger) {
|
|
779
974
|
const match = thumbUrl.match(IMAGE_EXT_RE);
|
|
780
975
|
const ext = match?.[1] ? match[1].toLowerCase().replace("jpeg", "jpg") : "jpg";
|
|
781
|
-
const dir =
|
|
782
|
-
const base =
|
|
783
|
-
const dest =
|
|
976
|
+
const dir = path8.dirname(videoPath);
|
|
977
|
+
const base = path8.basename(videoPath, path8.extname(videoPath));
|
|
978
|
+
const dest = path8.join(dir, `${base}_thumb.${ext}`);
|
|
784
979
|
try {
|
|
785
980
|
await downloadFile(thumbUrl, dest);
|
|
786
981
|
logger?.success(`Saved thumb ${dest}`);
|
|
@@ -799,12 +994,12 @@ async function maybeDownloadThumb(source, videoPath, opts = {}) {
|
|
|
799
994
|
}
|
|
800
995
|
const saved = await downloadThumbBeside(url, videoPath, opts.logger);
|
|
801
996
|
if (saved && opts.copyTo) {
|
|
802
|
-
const copyExt =
|
|
803
|
-
const copyBase =
|
|
804
|
-
const copyDir =
|
|
805
|
-
const copyDest =
|
|
806
|
-
|
|
807
|
-
|
|
997
|
+
const copyExt = path8.extname(saved);
|
|
998
|
+
const copyBase = path8.basename(opts.copyTo, path8.extname(opts.copyTo));
|
|
999
|
+
const copyDir = path8.dirname(path8.resolve(opts.copyTo));
|
|
1000
|
+
const copyDest = path8.join(copyDir, `${copyBase}_thumb${copyExt}`);
|
|
1001
|
+
fs8.mkdirSync(copyDir, { recursive: true });
|
|
1002
|
+
fs8.copyFileSync(saved, copyDest);
|
|
808
1003
|
opts.logger?.success(`Copied thumb to ${copyDest}`);
|
|
809
1004
|
}
|
|
810
1005
|
return saved;
|
|
@@ -932,7 +1127,7 @@ function registerBytePlusImageToVideoCommand(parent) {
|
|
|
932
1127
|
logger.error(`Task succeeded but no video_url: ${JSON.stringify(final)}`);
|
|
933
1128
|
process.exit(1);
|
|
934
1129
|
}
|
|
935
|
-
const outPath = opts.output ??
|
|
1130
|
+
const outPath = opts.output ?? path9.join(getOutputDir(), `byteplus-i2v-${taskId.slice(0, 8)}-${Date.now()}.mp4`);
|
|
936
1131
|
await downloadVideo(url, outPath, logger);
|
|
937
1132
|
await maybeDownloadThumb(final, outPath, {
|
|
938
1133
|
skip: opts.thumb === false,
|
|
@@ -958,7 +1153,7 @@ Task: ${taskId}`);
|
|
|
958
1153
|
}
|
|
959
1154
|
|
|
960
1155
|
// src/providers/byteplus/commands/reference-to-video.ts
|
|
961
|
-
import
|
|
1156
|
+
import path10 from "path";
|
|
962
1157
|
|
|
963
1158
|
// src/providers/byteplus/reference-input.ts
|
|
964
1159
|
var MAX_TOTAL_REFS = 12;
|
|
@@ -990,10 +1185,10 @@ function toRef(kind, resolved, role) {
|
|
|
990
1185
|
return { kind, url: resolved.dataUrl, mime: resolved.mime, role };
|
|
991
1186
|
}
|
|
992
1187
|
async function resolveRefSpec(kind, spec, logger) {
|
|
993
|
-
const { path:
|
|
994
|
-
if (kind === "image") return toRef(kind, await resolveImageInput(
|
|
995
|
-
if (kind === "video") return toRef(kind, await resolveVideoInput(
|
|
996
|
-
return toRef(kind, await resolveAudioInput(
|
|
1188
|
+
const { path: path47, role } = parseRefSpec(spec);
|
|
1189
|
+
if (kind === "image") return toRef(kind, await resolveImageInput(path47, logger), role);
|
|
1190
|
+
if (kind === "video") return toRef(kind, await resolveVideoInput(path47, logger), role);
|
|
1191
|
+
return toRef(kind, await resolveAudioInput(path47, logger), role);
|
|
997
1192
|
}
|
|
998
1193
|
async function resolveRefSpecs(specs, logger) {
|
|
999
1194
|
const out = [];
|
|
@@ -1090,7 +1285,7 @@ function registerBytePlusReferenceToVideoCommand(parent) {
|
|
|
1090
1285
|
logger.error(`Task succeeded but no video_url: ${JSON.stringify(final)}`);
|
|
1091
1286
|
process.exit(1);
|
|
1092
1287
|
}
|
|
1093
|
-
const outPath = opts.output ??
|
|
1288
|
+
const outPath = opts.output ?? path10.join(getOutputDir(), `byteplus-r2v-${taskId.slice(0, 8)}-${Date.now()}.mp4`);
|
|
1094
1289
|
await downloadVideo(url, outPath, logger);
|
|
1095
1290
|
await maybeDownloadThumb(final, outPath, {
|
|
1096
1291
|
skip: opts.thumb === false,
|
|
@@ -1116,7 +1311,7 @@ Task: ${taskId}`);
|
|
|
1116
1311
|
}
|
|
1117
1312
|
|
|
1118
1313
|
// src/providers/byteplus/commands/status.ts
|
|
1119
|
-
import
|
|
1314
|
+
import path11 from "path";
|
|
1120
1315
|
function registerBytePlusStatusCommand(parent) {
|
|
1121
1316
|
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(
|
|
1122
1317
|
async (taskId, opts) => {
|
|
@@ -1134,7 +1329,7 @@ function registerBytePlusStatusCommand(parent) {
|
|
|
1134
1329
|
const videoUrl2 = content?.video_url;
|
|
1135
1330
|
const fileUrl = content?.file_url;
|
|
1136
1331
|
if (videoUrl2) {
|
|
1137
|
-
const outPath = opts.output ??
|
|
1332
|
+
const outPath = opts.output ?? path11.join(getOutputDir(), `byteplus-video-${taskId.slice(0, 8)}.mp4`);
|
|
1138
1333
|
await downloadVideo(videoUrl2, outPath, logger);
|
|
1139
1334
|
await maybeDownloadThumb(status, outPath, {
|
|
1140
1335
|
skip: opts.thumb === false,
|
|
@@ -1144,7 +1339,7 @@ function registerBytePlusStatusCommand(parent) {
|
|
|
1144
1339
|
console.log(`Saved: ${outPath}`);
|
|
1145
1340
|
} else if (fileUrl) {
|
|
1146
1341
|
const ext = inferModelFileExt(fileUrl);
|
|
1147
|
-
const outPath = opts.output ??
|
|
1342
|
+
const outPath = opts.output ?? path11.join(getOutputDir(), `byteplus-3d-${taskId.slice(0, 8)}.${ext}`);
|
|
1148
1343
|
await downloadModelFile(fileUrl, outPath, logger);
|
|
1149
1344
|
console.log(`Saved: ${outPath}`);
|
|
1150
1345
|
} else {
|
|
@@ -1168,7 +1363,7 @@ function registerBytePlusStatusCommand(parent) {
|
|
|
1168
1363
|
}
|
|
1169
1364
|
|
|
1170
1365
|
// src/providers/byteplus/commands/video.ts
|
|
1171
|
-
import
|
|
1366
|
+
import path12 from "path";
|
|
1172
1367
|
function registerBytePlusVideoCommand(parent) {
|
|
1173
1368
|
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(
|
|
1174
1369
|
"--aspect-ratio <r>",
|
|
@@ -1210,7 +1405,7 @@ function registerBytePlusVideoCommand(parent) {
|
|
|
1210
1405
|
logger.error(`Task succeeded but no video_url in response: ${JSON.stringify(final)}`);
|
|
1211
1406
|
process.exit(1);
|
|
1212
1407
|
}
|
|
1213
|
-
const outPath = opts.output ??
|
|
1408
|
+
const outPath = opts.output ?? path12.join(getOutputDir(), `byteplus-video-${taskId.slice(0, 8)}-${Date.now()}.mp4`);
|
|
1214
1409
|
await downloadVideo(url, outPath, logger);
|
|
1215
1410
|
await maybeDownloadThumb(final, outPath, {
|
|
1216
1411
|
skip: opts.thumb === false,
|
|
@@ -1428,15 +1623,15 @@ async function downloadVideo2(url) {
|
|
|
1428
1623
|
|
|
1429
1624
|
// src/providers/cloudflare/media-output.ts
|
|
1430
1625
|
init_errors();
|
|
1431
|
-
import
|
|
1432
|
-
import
|
|
1626
|
+
import fs9 from "fs";
|
|
1627
|
+
import path13 from "path";
|
|
1433
1628
|
function destination(filename, output) {
|
|
1434
|
-
return output ?
|
|
1629
|
+
return output ? path13.resolve(output) : path13.join(getOutputDir(), filename);
|
|
1435
1630
|
}
|
|
1436
1631
|
function saveBytes(bytes, filename, output) {
|
|
1437
1632
|
const dest = destination(filename, output);
|
|
1438
|
-
|
|
1439
|
-
|
|
1633
|
+
fs9.mkdirSync(path13.dirname(dest), { recursive: true });
|
|
1634
|
+
fs9.writeFileSync(dest, bytes);
|
|
1440
1635
|
return dest;
|
|
1441
1636
|
}
|
|
1442
1637
|
function saveBase64Image(image, output) {
|
|
@@ -1553,7 +1748,7 @@ function registerCloudflareVideoCommand(parent) {
|
|
|
1553
1748
|
// src/providers/cloudflare/commands/generate.ts
|
|
1554
1749
|
init_errors();
|
|
1555
1750
|
function registerCloudflareGenerateCommand(parent) {
|
|
1556
|
-
parent.command("generate").description("Generate an image with Cloudflare Workers AI FLUX.1 Schnell").requiredOption("--prompt <text>", "Image prompt").option("--model <id>", "Cloudflare image model").option("--steps <n>", "Inference steps (1-8)", "4").option("--seed <n>", "Optional non-negative integer seed").option("--output <path>", "Save image at this path").option("-v, --verbose", "Verbose logging").action(
|
|
1751
|
+
parent.command("generate").description("Generate an image with Cloudflare Workers AI FLUX.1 Schnell").requiredOption("--prompt <text>", "Image prompt").option("--model <id>", "Cloudflare image model").option("--steps <n>", "Inference steps (1-8)", "4").option("--seed <n>", "Optional non-negative integer seed").option("--output <path>", "Save image at this path").addOption(imageFormatOption()).addOption(noWebpOption()).option("-v, --verbose", "Verbose logging").action(
|
|
1557
1752
|
async (opts) => {
|
|
1558
1753
|
if (!opts.prompt.trim()) throw new ValidationError("--prompt must not be empty");
|
|
1559
1754
|
const logger = createLogger({ verbose: opts.verbose ?? false });
|
|
@@ -1575,8 +1770,9 @@ function registerCloudflareGenerateCommand(parent) {
|
|
|
1575
1770
|
if (typeof result.image !== "string") {
|
|
1576
1771
|
throw new ProviderError("Cloudflare returned no image result", "Cloudflare");
|
|
1577
1772
|
}
|
|
1578
|
-
const
|
|
1579
|
-
logger.success(`Saved ${
|
|
1773
|
+
const saved = saveBase64Image(result.image, opts.output);
|
|
1774
|
+
logger.success(`Saved ${saved}`);
|
|
1775
|
+
const [destination2] = finalizeGeneratedImages([saved], { ...opts, logger });
|
|
1580
1776
|
console.log(destination2);
|
|
1581
1777
|
}
|
|
1582
1778
|
);
|
|
@@ -1618,8 +1814,8 @@ function registerCloudflareCommands(program2) {
|
|
|
1618
1814
|
}
|
|
1619
1815
|
|
|
1620
1816
|
// src/providers/elevenlabs/client.ts
|
|
1621
|
-
import
|
|
1622
|
-
import
|
|
1817
|
+
import fs10 from "fs";
|
|
1818
|
+
import path14 from "path";
|
|
1623
1819
|
init_errors();
|
|
1624
1820
|
init_http_client();
|
|
1625
1821
|
|
|
@@ -1781,10 +1977,10 @@ async function apiPostMultipart(endpoint2, form, apiKey, opts = {}) {
|
|
|
1781
1977
|
return res.json();
|
|
1782
1978
|
}
|
|
1783
1979
|
function readFileAsBlob(filePath, fieldFilename) {
|
|
1784
|
-
const abs =
|
|
1785
|
-
if (!
|
|
1786
|
-
const buf =
|
|
1787
|
-
const name = fieldFilename ??
|
|
1980
|
+
const abs = path14.resolve(filePath);
|
|
1981
|
+
if (!fs10.existsSync(abs)) throw new ValidationError(`File not found: ${abs}`);
|
|
1982
|
+
const buf = fs10.readFileSync(abs);
|
|
1983
|
+
const name = fieldFilename ?? path14.basename(abs);
|
|
1788
1984
|
return new File([new Uint8Array(buf)], name);
|
|
1789
1985
|
}
|
|
1790
1986
|
async function pollUntil(fetchStatus, isDone, isFailed, opts = {}) {
|
|
@@ -1808,12 +2004,12 @@ async function pollUntil(fetchStatus, isDone, isFailed, opts = {}) {
|
|
|
1808
2004
|
function saveBytes2(opts) {
|
|
1809
2005
|
const { bytes, task, ext, outputCopy, logger } = opts;
|
|
1810
2006
|
const dir = getOutputDir();
|
|
1811
|
-
const dest =
|
|
1812
|
-
|
|
2007
|
+
const dest = path14.join(dir, `elevenlabs_${task}_${Date.now()}.${ext}`);
|
|
2008
|
+
fs10.writeFileSync(dest, bytes);
|
|
1813
2009
|
logger?.success(`Saved: ${dest} (${(bytes.length / 1024).toFixed(1)} KB)`);
|
|
1814
2010
|
if (outputCopy) {
|
|
1815
|
-
|
|
1816
|
-
|
|
2011
|
+
fs10.mkdirSync(path14.dirname(path14.resolve(outputCopy)), { recursive: true });
|
|
2012
|
+
fs10.copyFileSync(dest, outputCopy);
|
|
1817
2013
|
}
|
|
1818
2014
|
return dest;
|
|
1819
2015
|
}
|
|
@@ -1852,14 +2048,14 @@ ${models.length} models`);
|
|
|
1852
2048
|
}
|
|
1853
2049
|
|
|
1854
2050
|
// src/providers/elevenlabs/commands/align.ts
|
|
1855
|
-
import
|
|
1856
|
-
import
|
|
2051
|
+
import fs11 from "fs";
|
|
2052
|
+
import path15 from "path";
|
|
1857
2053
|
function registerAlignCommand(parent) {
|
|
1858
2054
|
parent.command("align").description("Forced alignment of audio to a transcript (returns word/char timestamps)").requiredOption("--input <path>", "Source audio file").requiredOption("--text <str>", "Transcript text (or pass --text-file)").option("--text-file <path>", "Read transcript from file").option("--output <path>", "Save JSON to this path (default: multix-output)").option("-v, --verbose", "Verbose logging").action(async (opts) => {
|
|
1859
2055
|
const logger = createLogger({ verbose: opts.verbose ?? false });
|
|
1860
2056
|
const apiKey = requireElevenLabsKey();
|
|
1861
2057
|
let text = opts.text;
|
|
1862
|
-
if (opts.textFile) text =
|
|
2058
|
+
if (opts.textFile) text = fs11.readFileSync(path15.resolve(opts.textFile), "utf8");
|
|
1863
2059
|
const form = new FormData();
|
|
1864
2060
|
form.append("file", readFileAsBlob(opts.input));
|
|
1865
2061
|
form.append("text", text);
|
|
@@ -1867,9 +2063,9 @@ function registerAlignCommand(parent) {
|
|
|
1867
2063
|
const data = await apiPostMultipart("forced-alignment", form, apiKey, {
|
|
1868
2064
|
timeoutMs: 6e5
|
|
1869
2065
|
});
|
|
1870
|
-
const dest = opts.output ?
|
|
1871
|
-
|
|
1872
|
-
|
|
2066
|
+
const dest = opts.output ? path15.resolve(opts.output) : path15.join(getOutputDir(), `elevenlabs_align_${Date.now()}.json`);
|
|
2067
|
+
fs11.mkdirSync(path15.dirname(dest), { recursive: true });
|
|
2068
|
+
fs11.writeFileSync(dest, JSON.stringify(data, null, 2));
|
|
1873
2069
|
logger.success(`Saved alignment: ${dest}`);
|
|
1874
2070
|
if (data.words) console.log(`Aligned ${data.words.length} words (loss=${data.loss ?? "?"})`);
|
|
1875
2071
|
});
|
|
@@ -1912,8 +2108,8 @@ function registerCloneCommand(parent) {
|
|
|
1912
2108
|
// src/providers/elevenlabs/commands/dub.ts
|
|
1913
2109
|
init_errors();
|
|
1914
2110
|
init_http_client();
|
|
1915
|
-
import
|
|
1916
|
-
import
|
|
2111
|
+
import fs12 from "fs";
|
|
2112
|
+
import path16 from "path";
|
|
1917
2113
|
function registerDubCommand(parent) {
|
|
1918
2114
|
parent.command("dub").description("Dub a video/audio file into target languages (async)").option("--input <path>", "Source media file (use --input or --source-url)").option("--source-url <url>", "Public URL of source media").requiredOption("--target-lang <code>", "Target language ISO code (e.g. es, fr, de)").option("--source-lang <code>", "Source language (auto if omitted)").option("--num-speakers <n>", "Speaker count (auto if omitted)").option("--watermark", "Add ElevenLabs watermark", false).option("--start-time <s>", "Start time in seconds").option("--end-time <s>", "End time in seconds").option("--highest-resolution", "Use highest resolution video", false).option("--name <n>", "Project display name").option("--async", "Return job id immediately, do not wait", false).option("--wait", "Wait until status=dubbed (implied by --download)", false).option("--download", "Download dubbed media when ready", false).option("--poll-interval <s>", "Poll interval seconds", "10").option("--wait-timeout <ms>", "Poll timeout", "1800000").option("--output <path>", "Output path for downloaded dub").option("-v, --verbose", "Verbose logging").action(async (opts) => {
|
|
1919
2115
|
const logger = createLogger({ verbose: opts.verbose ?? false });
|
|
@@ -1956,7 +2152,7 @@ function registerDubCommand(parent) {
|
|
|
1956
2152
|
);
|
|
1957
2153
|
logger.success(`Dub complete: ${start.dubbing_id}`);
|
|
1958
2154
|
if (opts.download) {
|
|
1959
|
-
const dest = opts.output ?
|
|
2155
|
+
const dest = opts.output ? path16.resolve(opts.output) : path16.join(getOutputDir(), `elevenlabs_dub_${start.dubbing_id}_${opts.targetLang}.mp4`);
|
|
1960
2156
|
const url = `${ELEVENLABS_BASE_URL}/dubbing/${start.dubbing_id}/audio/${opts.targetLang}`;
|
|
1961
2157
|
await downloadDub(url, apiKey, dest);
|
|
1962
2158
|
logger.success(`Saved: ${dest}`);
|
|
@@ -1971,7 +2167,7 @@ Dubbed file: ${dest}`);
|
|
|
1971
2167
|
const status = await apiGet(`dubbing/${dubbingId}`, apiKey);
|
|
1972
2168
|
console.log(JSON.stringify(status, null, 2));
|
|
1973
2169
|
if (opts.download) {
|
|
1974
|
-
const dest = opts.output ?
|
|
2170
|
+
const dest = opts.output ? path16.resolve(opts.output) : path16.join(getOutputDir(), `elevenlabs_dub_${dubbingId}_${opts.download}.mp4`);
|
|
1975
2171
|
const url = `${ELEVENLABS_BASE_URL}/dubbing/${dubbingId}/audio/${opts.download}`;
|
|
1976
2172
|
await downloadDub(url, apiKey, dest);
|
|
1977
2173
|
console.log(`Saved: ${dest}`);
|
|
@@ -1979,7 +2175,7 @@ Dubbed file: ${dest}`);
|
|
|
1979
2175
|
});
|
|
1980
2176
|
}
|
|
1981
2177
|
async function downloadDub(url, apiKey, dest) {
|
|
1982
|
-
|
|
2178
|
+
fs12.mkdirSync(path16.dirname(dest), { recursive: true });
|
|
1983
2179
|
const res = await globalThis.fetch(url, {
|
|
1984
2180
|
headers: { "xi-api-key": apiKey },
|
|
1985
2181
|
signal: AbortSignal.timeout(6e5)
|
|
@@ -1988,7 +2184,7 @@ async function downloadDub(url, apiKey, dest) {
|
|
|
1988
2184
|
throw new Error(`Failed to download dub: HTTP ${res.status}`);
|
|
1989
2185
|
}
|
|
1990
2186
|
const arr = new Uint8Array(await res.arrayBuffer());
|
|
1991
|
-
|
|
2187
|
+
fs12.writeFileSync(dest, arr);
|
|
1992
2188
|
void downloadFile;
|
|
1993
2189
|
}
|
|
1994
2190
|
|
|
@@ -2017,8 +2213,8 @@ Isolated audio: ${dest}`);
|
|
|
2017
2213
|
|
|
2018
2214
|
// src/providers/elevenlabs/commands/music.ts
|
|
2019
2215
|
init_errors();
|
|
2020
|
-
import
|
|
2021
|
-
import
|
|
2216
|
+
import fs13 from "fs";
|
|
2217
|
+
import path17 from "path";
|
|
2022
2218
|
function registerMusicCommand(parent) {
|
|
2023
2219
|
parent.command("music").description("Generate music from a text prompt or composition plan").option("--prompt <str>", "Music prompt (mutually exclusive with --plan)").option("--plan <path>", "Path to composition plan JSON file").option("--music-length-ms <n>", "Total length in milliseconds (default 30000)").option("--model <id>", "Music model", "music_v1").option(
|
|
2024
2220
|
"--format <fmt>",
|
|
@@ -2033,9 +2229,9 @@ function registerMusicCommand(parent) {
|
|
|
2033
2229
|
const payload = { model_id: opts.model };
|
|
2034
2230
|
if (opts.prompt) payload.prompt = opts.prompt;
|
|
2035
2231
|
if (opts.plan) {
|
|
2036
|
-
const planPath =
|
|
2037
|
-
if (!
|
|
2038
|
-
const planJson = JSON.parse(
|
|
2232
|
+
const planPath = path17.resolve(opts.plan);
|
|
2233
|
+
if (!fs13.existsSync(planPath)) throw new ValidationError(`Plan file not found: ${planPath}`);
|
|
2234
|
+
const planJson = JSON.parse(fs13.readFileSync(planPath, "utf8"));
|
|
2039
2235
|
payload.composition_plan = planJson;
|
|
2040
2236
|
}
|
|
2041
2237
|
if (opts.musicLengthMs) payload.music_length_ms = Number.parseInt(opts.musicLengthMs, 10);
|
|
@@ -2094,8 +2290,8 @@ Generated SFX: ${dest}`);
|
|
|
2094
2290
|
}
|
|
2095
2291
|
|
|
2096
2292
|
// src/providers/elevenlabs/commands/transcribe.ts
|
|
2097
|
-
import
|
|
2098
|
-
import
|
|
2293
|
+
import fs14 from "fs";
|
|
2294
|
+
import path18 from "path";
|
|
2099
2295
|
function registerTranscribeCommand(parent) {
|
|
2100
2296
|
parent.command("transcribe").description("Speech-to-text transcription with Scribe").requiredOption("--input <path>", "Audio or video file to transcribe").option(
|
|
2101
2297
|
"--model <id>",
|
|
@@ -2118,7 +2314,7 @@ function registerTranscribeCommand(parent) {
|
|
|
2118
2314
|
});
|
|
2119
2315
|
const ext = opts.format === "json" ? "json" : opts.format === "srt" ? "srt" : opts.format === "vtt" ? "vtt" : "txt";
|
|
2120
2316
|
const dir = getOutputDir();
|
|
2121
|
-
const dest = opts.output ?
|
|
2317
|
+
const dest = opts.output ? path18.resolve(opts.output) : path18.join(dir, `elevenlabs_transcribe_${Date.now()}.${ext}`);
|
|
2122
2318
|
let body;
|
|
2123
2319
|
switch (opts.format) {
|
|
2124
2320
|
case "json":
|
|
@@ -2133,8 +2329,8 @@ function registerTranscribeCommand(parent) {
|
|
|
2133
2329
|
default:
|
|
2134
2330
|
body = data.text ?? "";
|
|
2135
2331
|
}
|
|
2136
|
-
|
|
2137
|
-
|
|
2332
|
+
fs14.mkdirSync(path18.dirname(dest), { recursive: true });
|
|
2333
|
+
fs14.writeFileSync(dest, body);
|
|
2138
2334
|
logger.success(`Saved transcript: ${dest}`);
|
|
2139
2335
|
if (data.language_code) {
|
|
2140
2336
|
logger.info(
|
|
@@ -2381,16 +2577,16 @@ function registerElevenLabsCommands(program2) {
|
|
|
2381
2577
|
|
|
2382
2578
|
// src/providers/gemini/commands/analyze.ts
|
|
2383
2579
|
init_errors();
|
|
2384
|
-
import
|
|
2580
|
+
import fs17 from "fs";
|
|
2385
2581
|
|
|
2386
2582
|
// src/providers/gemini/client.ts
|
|
2387
|
-
import
|
|
2388
|
-
import
|
|
2583
|
+
import fs15 from "fs";
|
|
2584
|
+
import path20 from "path";
|
|
2389
2585
|
init_errors();
|
|
2390
2586
|
init_http_client();
|
|
2391
2587
|
|
|
2392
2588
|
// src/providers/gemini/task-resolver.ts
|
|
2393
|
-
import
|
|
2589
|
+
import path19 from "path";
|
|
2394
2590
|
var MIME_MAP = {
|
|
2395
2591
|
// Audio
|
|
2396
2592
|
".mp3": "audio/mp3",
|
|
@@ -2431,7 +2627,7 @@ var MIME_MAP = {
|
|
|
2431
2627
|
".pptx": "application/vnd.openxmlformats-officedocument.presentationml.presentation"
|
|
2432
2628
|
};
|
|
2433
2629
|
function getMimeType(filePath) {
|
|
2434
|
-
const ext =
|
|
2630
|
+
const ext = path19.extname(filePath).toLowerCase();
|
|
2435
2631
|
return MIME_MAP[ext] ?? "application/octet-stream";
|
|
2436
2632
|
}
|
|
2437
2633
|
function requiresProcessingWait(mimeType) {
|
|
@@ -2457,8 +2653,8 @@ async function uploadFile(filePath, opts = {}) {
|
|
|
2457
2653
|
const apiKey = requireApiKey();
|
|
2458
2654
|
const { timeoutMs = 3e5, logger } = opts;
|
|
2459
2655
|
const mimeType = getMimeType(filePath);
|
|
2460
|
-
const fileBytes =
|
|
2461
|
-
const displayName =
|
|
2656
|
+
const fileBytes = fs15.readFileSync(filePath);
|
|
2657
|
+
const displayName = path20.basename(filePath);
|
|
2462
2658
|
const boundary = `multix_${Date.now()}`;
|
|
2463
2659
|
const metaJson = JSON.stringify({ file: { displayName, mimeType } });
|
|
2464
2660
|
const body = Buffer.concat([
|
|
@@ -2547,12 +2743,12 @@ function extractImages(resp) {
|
|
|
2547
2743
|
).map((p) => ({ mimeType: p.inlineData.mimeType, data: p.inlineData.data }));
|
|
2548
2744
|
}
|
|
2549
2745
|
function inlinePart(filePath) {
|
|
2550
|
-
const data =
|
|
2746
|
+
const data = fs15.readFileSync(filePath).toString("base64");
|
|
2551
2747
|
const mimeType = getMimeType(filePath);
|
|
2552
2748
|
return { inlineData: { mimeType, data } };
|
|
2553
2749
|
}
|
|
2554
2750
|
function shouldUseFileApi(filePath) {
|
|
2555
|
-
const stat =
|
|
2751
|
+
const stat = fs15.statSync(filePath);
|
|
2556
2752
|
return stat.size > 20 * 1024 * 1024;
|
|
2557
2753
|
}
|
|
2558
2754
|
function sleep(ms) {
|
|
@@ -2606,13 +2802,13 @@ function getDefaultModel(task) {
|
|
|
2606
2802
|
}
|
|
2607
2803
|
|
|
2608
2804
|
// src/providers/gemini/result-formatter.ts
|
|
2609
|
-
import
|
|
2610
|
-
import
|
|
2805
|
+
import fs16 from "fs";
|
|
2806
|
+
import path21 from "path";
|
|
2611
2807
|
function formatResults(results, format, outputPath) {
|
|
2612
2808
|
const content = renderFormat(results, format);
|
|
2613
2809
|
if (outputPath) {
|
|
2614
|
-
|
|
2615
|
-
|
|
2810
|
+
fs16.mkdirSync(path21.dirname(path21.resolve(outputPath)), { recursive: true });
|
|
2811
|
+
fs16.writeFileSync(outputPath, content, "utf8");
|
|
2616
2812
|
} else {
|
|
2617
2813
|
process.stdout.write(content);
|
|
2618
2814
|
}
|
|
@@ -2679,7 +2875,7 @@ function registerAnalyzeCommand(parent) {
|
|
|
2679
2875
|
const logger = createLogger({ verbose: opts.verbose ?? false });
|
|
2680
2876
|
const model = opts.model ?? getDefaultModel("analyze");
|
|
2681
2877
|
for (const f of opts.files) {
|
|
2682
|
-
if (!
|
|
2878
|
+
if (!fs17.existsSync(f)) throw new ValidationError(`File not found: ${f}`);
|
|
2683
2879
|
}
|
|
2684
2880
|
const results = [];
|
|
2685
2881
|
for (let i = 0; i < opts.files.length; i++) {
|
|
@@ -2725,14 +2921,14 @@ function registerAnalyzeCommand(parent) {
|
|
|
2725
2921
|
|
|
2726
2922
|
// src/providers/gemini/commands/extract.ts
|
|
2727
2923
|
init_errors();
|
|
2728
|
-
import
|
|
2924
|
+
import fs18 from "fs";
|
|
2729
2925
|
function registerExtractCommand(parent) {
|
|
2730
2926
|
parent.command("extract").description("Extract structured information from files with Gemini").requiredOption("--files <paths...>", "Input file paths").requiredOption("--prompt <text>", "Extraction prompt (required)").option("--model <id>", "Gemini model to use").option("--format <fmt>", "Output format: text|json|csv|markdown", "text").option("--output <path>", "Write results to file").option("-v, --verbose", "Verbose logging").action(
|
|
2731
2927
|
async (opts) => {
|
|
2732
2928
|
const logger = createLogger({ verbose: opts.verbose ?? false });
|
|
2733
2929
|
const model = opts.model ?? getDefaultModel("extract");
|
|
2734
2930
|
for (const f of opts.files) {
|
|
2735
|
-
if (!
|
|
2931
|
+
if (!fs18.existsSync(f)) throw new ValidationError(`File not found: ${f}`);
|
|
2736
2932
|
}
|
|
2737
2933
|
const generationConfig = opts.format === "json" ? { responseMimeType: "application/json" } : void 0;
|
|
2738
2934
|
const results = [];
|
|
@@ -2774,8 +2970,8 @@ function registerExtractCommand(parent) {
|
|
|
2774
2970
|
init_errors();
|
|
2775
2971
|
|
|
2776
2972
|
// src/providers/gemini/generators/speech.ts
|
|
2777
|
-
import
|
|
2778
|
-
import
|
|
2973
|
+
import fs19 from "fs";
|
|
2974
|
+
import path22 from "path";
|
|
2779
2975
|
|
|
2780
2976
|
// src/providers/gemini/voices.ts
|
|
2781
2977
|
var GEMINI_TTS_VOICES = [
|
|
@@ -2858,12 +3054,12 @@ async function generateGeminiSpeech(opts) {
|
|
|
2858
3054
|
const sampleRate = parseSampleRate(audio.mimeType) ?? TTS_PCM_SAMPLE_RATE;
|
|
2859
3055
|
const outBytes = outputFormat === "wav" ? wrapPcmInWav(pcmBytes, sampleRate, TTS_PCM_CHANNELS, TTS_PCM_BITS_PER_SAMPLE) : pcmBytes;
|
|
2860
3056
|
const outDir = getOutputDir();
|
|
2861
|
-
const dest =
|
|
2862
|
-
|
|
3057
|
+
const dest = path22.join(outDir, `gemini_speech_${Date.now()}.${outputFormat}`);
|
|
3058
|
+
fs19.writeFileSync(dest, outBytes);
|
|
2863
3059
|
logger?.success(`Saved: ${dest} (${(outBytes.length / 1024).toFixed(1)} KB)`);
|
|
2864
3060
|
if (output) {
|
|
2865
|
-
|
|
2866
|
-
|
|
3061
|
+
fs19.mkdirSync(path22.dirname(path22.resolve(output)), { recursive: true });
|
|
3062
|
+
fs19.copyFileSync(dest, output);
|
|
2867
3063
|
logger?.success(`Copied to: ${output}`);
|
|
2868
3064
|
}
|
|
2869
3065
|
return { status: "success", generatedAudio: dest, model, mimeType: audio.mimeType };
|
|
@@ -2962,8 +3158,8 @@ function collectSpeakers(value, prev) {
|
|
|
2962
3158
|
}
|
|
2963
3159
|
|
|
2964
3160
|
// src/providers/gemini/commands/generate-video.ts
|
|
2965
|
-
import
|
|
2966
|
-
import
|
|
3161
|
+
import fs20 from "fs";
|
|
3162
|
+
import path23 from "path";
|
|
2967
3163
|
init_errors();
|
|
2968
3164
|
init_http_client();
|
|
2969
3165
|
var BASE2 = "https://generativelanguage.googleapis.com";
|
|
@@ -2997,10 +3193,10 @@ function registerGenerateVideoCommand(parent) {
|
|
|
2997
3193
|
const contentParts = [{ text: opts.prompt }];
|
|
2998
3194
|
if (opts.referenceImages?.length) {
|
|
2999
3195
|
for (const imgPath of opts.referenceImages.slice(0, 2)) {
|
|
3000
|
-
if (!
|
|
3196
|
+
if (!fs20.existsSync(imgPath))
|
|
3001
3197
|
throw new ValidationError(`Reference image not found: ${imgPath}`);
|
|
3002
|
-
const data =
|
|
3003
|
-
const ext =
|
|
3198
|
+
const data = fs20.readFileSync(imgPath).toString("base64");
|
|
3199
|
+
const ext = path23.extname(imgPath).toLowerCase();
|
|
3004
3200
|
const mimeType = ext === ".png" ? "image/png" : "image/jpeg";
|
|
3005
3201
|
contentParts.push({ inlineData: { mimeType, data } });
|
|
3006
3202
|
}
|
|
@@ -3049,13 +3245,13 @@ function registerGenerateVideoCommand(parent) {
|
|
|
3049
3245
|
);
|
|
3050
3246
|
}
|
|
3051
3247
|
const outDir = getOutputDir();
|
|
3052
|
-
const dest =
|
|
3248
|
+
const dest = path23.join(outDir, `veo_generated_${Date.now()}.mp4`);
|
|
3053
3249
|
const { downloadFile: downloadFile2 } = await Promise.resolve().then(() => (init_http_client(), http_client_exports));
|
|
3054
3250
|
await downloadFile2(videoUri, dest);
|
|
3055
3251
|
logger.success(`Saved video: ${dest}`);
|
|
3056
3252
|
if (opts.output) {
|
|
3057
|
-
|
|
3058
|
-
|
|
3253
|
+
fs20.mkdirSync(path23.dirname(path23.resolve(opts.output)), { recursive: true });
|
|
3254
|
+
fs20.copyFileSync(dest, opts.output);
|
|
3059
3255
|
logger.success(`Copied to: ${opts.output}`);
|
|
3060
3256
|
}
|
|
3061
3257
|
await maybeDownloadThumb(finalOp.response, dest, {
|
|
@@ -3074,10 +3270,10 @@ function sleep2(ms) {
|
|
|
3074
3270
|
|
|
3075
3271
|
// src/providers/gemini/commands/generate.ts
|
|
3076
3272
|
init_errors();
|
|
3077
|
-
import
|
|
3078
|
-
import
|
|
3273
|
+
import fs21 from "fs";
|
|
3274
|
+
import path24 from "path";
|
|
3079
3275
|
function registerGenerateCommand(parent) {
|
|
3080
|
-
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(
|
|
3276
|
+
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").addOption(imageFormatOption()).addOption(noWebpOption()).option("-v, --verbose", "Verbose logging").action(
|
|
3081
3277
|
async (opts) => {
|
|
3082
3278
|
const logger = createLogger({ verbose: opts.verbose ?? false });
|
|
3083
3279
|
const model = opts.model ?? getDefaultModel("generate");
|
|
@@ -3117,8 +3313,8 @@ function registerGenerateCommand(parent) {
|
|
|
3117
3313
|
const outDir = getOutputDir();
|
|
3118
3314
|
const ext = img.mimeType.includes("png") ? "png" : "jpg";
|
|
3119
3315
|
const fname = `gemini_generated_${Date.now()}_${i}.${ext}`;
|
|
3120
|
-
const dest =
|
|
3121
|
-
|
|
3316
|
+
const dest = path24.join(outDir, fname);
|
|
3317
|
+
fs21.writeFileSync(dest, Buffer.from(img.data, "base64"));
|
|
3122
3318
|
savedFiles.push(dest);
|
|
3123
3319
|
logger.success(`Saved: ${dest}`);
|
|
3124
3320
|
}
|
|
@@ -3133,20 +3329,21 @@ function registerGenerateCommand(parent) {
|
|
|
3133
3329
|
process.exit(1);
|
|
3134
3330
|
}
|
|
3135
3331
|
if (opts.output && savedFiles[0]) {
|
|
3136
|
-
|
|
3137
|
-
|
|
3332
|
+
fs21.mkdirSync(path24.dirname(path24.resolve(opts.output)), { recursive: true });
|
|
3333
|
+
fs21.copyFileSync(savedFiles[0], opts.output);
|
|
3138
3334
|
logger.success(`Copied to: ${opts.output}`);
|
|
3139
3335
|
}
|
|
3336
|
+
const finalImages = finalizeGeneratedImages(savedFiles, { ...opts, logger });
|
|
3140
3337
|
console.log(`
|
|
3141
|
-
Generated ${
|
|
3142
|
-
for (const f of
|
|
3338
|
+
Generated ${finalImages.length} image(s):`);
|
|
3339
|
+
for (const f of finalImages) console.log(` ${f}`);
|
|
3143
3340
|
}
|
|
3144
3341
|
);
|
|
3145
3342
|
}
|
|
3146
3343
|
|
|
3147
3344
|
// src/providers/gemini/commands/image-to-image.ts
|
|
3148
|
-
import
|
|
3149
|
-
import
|
|
3345
|
+
import fs22 from "fs";
|
|
3346
|
+
import path25 from "path";
|
|
3150
3347
|
var SOFT_LIMIT_BYTES = 5 * 1024 * 1024;
|
|
3151
3348
|
var HARD_LIMIT_BYTES = 7 * 1024 * 1024;
|
|
3152
3349
|
function registerGeminiImageToImageCommand(parent) {
|
|
@@ -3157,7 +3354,7 @@ function registerGeminiImageToImageCommand(parent) {
|
|
|
3157
3354
|
"Reference image (URL or local path); repeatable for multi-ref",
|
|
3158
3355
|
collect4,
|
|
3159
3356
|
[]
|
|
3160
|
-
).option("-m, --model <id>", `Gemini image model (default ${IMAGE_MODEL_FALLBACK})`).option("--output <path>", "Save first generated image to this path").option("-v, --verbose", "Verbose logging").action(
|
|
3357
|
+
).option("-m, --model <id>", `Gemini image model (default ${IMAGE_MODEL_FALLBACK})`).option("--output <path>", "Save first generated image to this path").addOption(imageFormatOption()).addOption(noWebpOption()).option("-v, --verbose", "Verbose logging").action(
|
|
3161
3358
|
async (opts) => {
|
|
3162
3359
|
if (!opts.ref || opts.ref.length === 0) {
|
|
3163
3360
|
throw new Error("At least one --ref is required for image-to-image.");
|
|
@@ -3207,19 +3404,20 @@ function registerGeminiImageToImageCommand(parent) {
|
|
|
3207
3404
|
const img = images[i];
|
|
3208
3405
|
if (!img) continue;
|
|
3209
3406
|
const ext = img.mimeType.includes("png") ? "png" : "jpg";
|
|
3210
|
-
const dest =
|
|
3211
|
-
|
|
3407
|
+
const dest = path25.join(outDir, `gemini-i2i-${ts}-${i + 1}.${ext}`);
|
|
3408
|
+
fs22.writeFileSync(dest, Buffer.from(img.data, "base64"));
|
|
3212
3409
|
saved.push(dest);
|
|
3213
3410
|
logger.success(`Saved: ${dest}`);
|
|
3214
3411
|
}
|
|
3215
3412
|
if (opts.output && saved[0]) {
|
|
3216
|
-
|
|
3217
|
-
|
|
3413
|
+
fs22.mkdirSync(path25.dirname(path25.resolve(opts.output)), { recursive: true });
|
|
3414
|
+
fs22.copyFileSync(saved[0], opts.output);
|
|
3218
3415
|
logger.success(`Copied to: ${opts.output}`);
|
|
3219
3416
|
}
|
|
3417
|
+
const finalImages = finalizeGeneratedImages(saved, { ...opts, logger });
|
|
3220
3418
|
console.log(`
|
|
3221
|
-
Generated ${
|
|
3222
|
-
for (const f of
|
|
3419
|
+
Generated ${finalImages.length} image(s):`);
|
|
3420
|
+
for (const f of finalImages) console.log(` ${f}`);
|
|
3223
3421
|
}
|
|
3224
3422
|
);
|
|
3225
3423
|
}
|
|
@@ -3241,7 +3439,7 @@ function sniffImageMime(buf, url) {
|
|
|
3241
3439
|
if (buf.length >= 12 && buf.slice(0, 4).toString() === "RIFF" && buf.slice(8, 12).toString() === "WEBP")
|
|
3242
3440
|
return "image/webp";
|
|
3243
3441
|
}
|
|
3244
|
-
const ext =
|
|
3442
|
+
const ext = path25.extname(new URL(url).pathname).toLowerCase();
|
|
3245
3443
|
if (ext === ".png") return "image/png";
|
|
3246
3444
|
if (ext === ".webp") return "image/webp";
|
|
3247
3445
|
if (ext === ".gif") return "image/gif";
|
|
@@ -3249,17 +3447,17 @@ function sniffImageMime(buf, url) {
|
|
|
3249
3447
|
}
|
|
3250
3448
|
|
|
3251
3449
|
// src/providers/gemini/commands/image-to-video.ts
|
|
3252
|
-
import
|
|
3253
|
-
import
|
|
3450
|
+
import fs23 from "fs";
|
|
3451
|
+
import path26 from "path";
|
|
3254
3452
|
init_errors();
|
|
3255
3453
|
init_http_client();
|
|
3256
3454
|
var BASE3 = "https://generativelanguage.googleapis.com";
|
|
3257
3455
|
function imagePart(imgPath) {
|
|
3258
|
-
if (!
|
|
3456
|
+
if (!fs23.existsSync(imgPath)) {
|
|
3259
3457
|
throw new ValidationError(`Image not found: ${imgPath}`);
|
|
3260
3458
|
}
|
|
3261
|
-
const data =
|
|
3262
|
-
const ext =
|
|
3459
|
+
const data = fs23.readFileSync(imgPath).toString("base64");
|
|
3460
|
+
const ext = path26.extname(imgPath).toLowerCase();
|
|
3263
3461
|
const mimeType = ext === ".png" ? "image/png" : "image/jpeg";
|
|
3264
3462
|
return { inlineData: { mimeType, data } };
|
|
3265
3463
|
}
|
|
@@ -3332,12 +3530,12 @@ function registerGeminiImageToVideoCommand(parent) {
|
|
|
3332
3530
|
"gemini"
|
|
3333
3531
|
);
|
|
3334
3532
|
}
|
|
3335
|
-
const dest =
|
|
3533
|
+
const dest = path26.join(getOutputDir(), `veo_i2v_${Date.now()}.mp4`);
|
|
3336
3534
|
await downloadFile(videoUri, dest);
|
|
3337
3535
|
logger.success(`Saved video: ${dest}`);
|
|
3338
3536
|
if (opts.output) {
|
|
3339
|
-
|
|
3340
|
-
|
|
3537
|
+
fs23.mkdirSync(path26.dirname(path26.resolve(opts.output)), { recursive: true });
|
|
3538
|
+
fs23.copyFileSync(dest, opts.output);
|
|
3341
3539
|
logger.success(`Copied to: ${opts.output}`);
|
|
3342
3540
|
}
|
|
3343
3541
|
await maybeDownloadThumb(finalOp.response, dest, {
|
|
@@ -3353,14 +3551,14 @@ Generated video: ${dest}`);
|
|
|
3353
3551
|
|
|
3354
3552
|
// src/providers/gemini/commands/transcribe.ts
|
|
3355
3553
|
init_errors();
|
|
3356
|
-
import
|
|
3554
|
+
import fs24 from "fs";
|
|
3357
3555
|
function registerTranscribeCommand2(parent) {
|
|
3358
3556
|
parent.command("transcribe").description("Transcribe audio or video files with Gemini").requiredOption("--files <paths...>", "Audio/video file paths to transcribe").option("--prompt <text>", "Transcription prompt", "Generate a transcript with timestamps").option("--model <id>", "Gemini model to use").option("--format <fmt>", "Output format: text|json|csv|markdown", "text").option("--output <path>", "Write results to file").option("-v, --verbose", "Verbose logging").action(
|
|
3359
3557
|
async (opts) => {
|
|
3360
3558
|
const logger = createLogger({ verbose: opts.verbose ?? false });
|
|
3361
3559
|
const model = opts.model ?? getDefaultModel("transcribe");
|
|
3362
3560
|
for (const f of opts.files) {
|
|
3363
|
-
if (!
|
|
3561
|
+
if (!fs24.existsSync(f)) throw new ValidationError(`File not found: ${f}`);
|
|
3364
3562
|
}
|
|
3365
3563
|
const results = [];
|
|
3366
3564
|
for (let i = 0; i < opts.files.length; i++) {
|
|
@@ -3418,8 +3616,8 @@ function registerGeminiCommands(program2) {
|
|
|
3418
3616
|
|
|
3419
3617
|
// src/providers/leonardo/commands/generate.ts
|
|
3420
3618
|
init_http_client();
|
|
3421
|
-
import
|
|
3422
|
-
import
|
|
3619
|
+
import fs25 from "fs";
|
|
3620
|
+
import path27 from "path";
|
|
3423
3621
|
|
|
3424
3622
|
// src/providers/leonardo/client.ts
|
|
3425
3623
|
init_errors();
|
|
@@ -3493,11 +3691,11 @@ var LeonardoClient = class {
|
|
|
3493
3691
|
}
|
|
3494
3692
|
apiKey;
|
|
3495
3693
|
baseUrl;
|
|
3496
|
-
async request(
|
|
3694
|
+
async request(path47, opts = {}) {
|
|
3497
3695
|
const base = this.baseUrl.replace(/\/$/, "");
|
|
3498
|
-
const v2Override =
|
|
3696
|
+
const v2Override = path47.startsWith("v2:");
|
|
3499
3697
|
const finalBase = v2Override ? base.replace(/\/v1$/, "/v2") : base;
|
|
3500
|
-
const finalPath = v2Override ?
|
|
3698
|
+
const finalPath = v2Override ? path47.slice(3) : path47;
|
|
3501
3699
|
const url = new URL(finalBase + finalPath);
|
|
3502
3700
|
if (opts.query) {
|
|
3503
3701
|
for (const [k, v] of Object.entries(opts.query)) {
|
|
@@ -3516,14 +3714,14 @@ var LeonardoClient = class {
|
|
|
3516
3714
|
timeoutMs: opts.timeoutMs ?? 12e4
|
|
3517
3715
|
});
|
|
3518
3716
|
}
|
|
3519
|
-
get(
|
|
3520
|
-
return this.request(
|
|
3717
|
+
get(path47, query, logger) {
|
|
3718
|
+
return this.request(path47, { method: "GET", query, logger });
|
|
3521
3719
|
}
|
|
3522
|
-
post(
|
|
3523
|
-
return this.request(
|
|
3720
|
+
post(path47, body, logger) {
|
|
3721
|
+
return this.request(path47, { method: "POST", body, logger });
|
|
3524
3722
|
}
|
|
3525
|
-
delete(
|
|
3526
|
-
return this.request(
|
|
3723
|
+
delete(path47, logger) {
|
|
3724
|
+
return this.request(path47, { method: "DELETE", logger });
|
|
3527
3725
|
}
|
|
3528
3726
|
};
|
|
3529
3727
|
function createLeonardoClient() {
|
|
@@ -3534,7 +3732,7 @@ function createLeonardoClient() {
|
|
|
3534
3732
|
function extFromUrl(url, fallback = ".png") {
|
|
3535
3733
|
try {
|
|
3536
3734
|
const u = new URL(url);
|
|
3537
|
-
const e =
|
|
3735
|
+
const e = path27.extname(u.pathname);
|
|
3538
3736
|
return e.length > 0 ? e : fallback;
|
|
3539
3737
|
} catch {
|
|
3540
3738
|
return fallback;
|
|
@@ -3545,7 +3743,7 @@ function autoFilename(generationId, index, ext) {
|
|
|
3545
3743
|
return `leonardo-${generationId.slice(0, 8)}-${String(index).padStart(2, "0")}${e}`;
|
|
3546
3744
|
}
|
|
3547
3745
|
function registerLeonardoGenerateCommand(parent) {
|
|
3548
|
-
parent.command("generate <prompt>").alias("gen").description("Generate images from a text prompt via Leonardo").option("-m, --model <id>", "Model UUID or GPT-image id").option("-w, --width <n>", "Image width").option("-h, --height <n>", "Image height").option("-n, --num <n>", "Number of images").option("--alchemy", "Enable Alchemy").option("--ultra", "Enable Ultra mode").option("--contrast <n>", "Contrast").option("--style <uuid>", "Style UUID").option("--seed <n>", "Seed").option("--negative <text>", "Negative prompt").option("--enhance", "Enhance prompt").option("--quality <level>", "GPT Image quality: LOW|MEDIUM|HIGH", "HIGH").option("--output <path>", "Copy first image to this path (single-image only)").option("--no-download", "Skip downloading; print URLs only").option("--wait-timeout <ms>", "Polling timeout in milliseconds", "480000").option("-v, --verbose", "Verbose logging").action(
|
|
3746
|
+
parent.command("generate <prompt>").alias("gen").description("Generate images from a text prompt via Leonardo").option("-m, --model <id>", "Model UUID or GPT-image id").option("-w, --width <n>", "Image width").option("-h, --height <n>", "Image height").option("-n, --num <n>", "Number of images").option("--alchemy", "Enable Alchemy").option("--ultra", "Enable Ultra mode").option("--contrast <n>", "Contrast").option("--style <uuid>", "Style UUID").option("--seed <n>", "Seed").option("--negative <text>", "Negative prompt").option("--enhance", "Enhance prompt").option("--quality <level>", "GPT Image quality: LOW|MEDIUM|HIGH", "HIGH").option("--output <path>", "Copy first image to this path (single-image only)").addOption(imageFormatOption()).addOption(noWebpOption()).option("--no-download", "Skip downloading; print URLs only").option("--wait-timeout <ms>", "Polling timeout in milliseconds", "480000").option("-v, --verbose", "Verbose logging").action(
|
|
3549
3747
|
async (prompt, opts) => {
|
|
3550
3748
|
const logger = createLogger({ verbose: opts.verbose ?? false });
|
|
3551
3749
|
const client = createLeonardoClient();
|
|
@@ -3619,7 +3817,7 @@ function registerLeonardoGenerateCommand(parent) {
|
|
|
3619
3817
|
if (!img) continue;
|
|
3620
3818
|
const ext = extFromUrl(img.url);
|
|
3621
3819
|
const filename = autoFilename(generationId, i + 1, ext);
|
|
3622
|
-
const filepath =
|
|
3820
|
+
const filepath = path27.join(outDir, filename);
|
|
3623
3821
|
try {
|
|
3624
3822
|
await downloadFile(img.url, filepath);
|
|
3625
3823
|
saved.push(filepath);
|
|
@@ -3629,31 +3827,32 @@ function registerLeonardoGenerateCommand(parent) {
|
|
|
3629
3827
|
}
|
|
3630
3828
|
}
|
|
3631
3829
|
if (opts.output && saved[0]) {
|
|
3632
|
-
const dest =
|
|
3633
|
-
|
|
3634
|
-
|
|
3830
|
+
const dest = path27.resolve(opts.output);
|
|
3831
|
+
fs25.mkdirSync(path27.dirname(dest), { recursive: true });
|
|
3832
|
+
fs25.copyFileSync(saved[0], dest);
|
|
3635
3833
|
logger.success(`Copied to ${dest}`);
|
|
3636
3834
|
}
|
|
3835
|
+
const finalImages = finalizeGeneratedImages(saved, { ...opts, logger });
|
|
3637
3836
|
console.log(`
|
|
3638
|
-
Generated ${
|
|
3639
|
-
for (const f of
|
|
3837
|
+
Generated ${finalImages.length} image(s):`);
|
|
3838
|
+
for (const f of finalImages) console.log(` ${f}`);
|
|
3640
3839
|
}
|
|
3641
3840
|
);
|
|
3642
3841
|
}
|
|
3643
3842
|
|
|
3644
3843
|
// src/providers/leonardo/commands/image-to-image.ts
|
|
3645
3844
|
init_http_client();
|
|
3646
|
-
import
|
|
3647
|
-
import
|
|
3845
|
+
import fs26 from "fs";
|
|
3846
|
+
import path28 from "path";
|
|
3648
3847
|
function extFromUrl2(url, fallback = ".png") {
|
|
3649
3848
|
try {
|
|
3650
|
-
return
|
|
3849
|
+
return path28.extname(new URL(url).pathname) || fallback;
|
|
3651
3850
|
} catch {
|
|
3652
3851
|
return fallback;
|
|
3653
3852
|
}
|
|
3654
3853
|
}
|
|
3655
3854
|
function registerLeonardoImageToImageCommand(parent) {
|
|
3656
|
-
parent.command("image-to-image").alias("i2i").description("Generate an image conditioned on an existing Leonardo image id (init image)").requiredOption("--prompt <text>", "Text prompt describing the desired output").requiredOption("--ref <imageId>", "Existing Leonardo image id to use as init image").option("--image-type <type>", "GENERATED | UPLOADED", "GENERATED").option("--init-strength <n>", "Init image strength (0.0-0.9)", "0.5").option("-m, --model <id>", "Model UUID").option("-w, --width <n>", "Image width").option("-h, --height <n>", "Image height").option("-n, --num <n>", "Number of images").option("--seed <n>", "Seed").option("--negative <text>", "Negative prompt").option("--output <path>", "Copy first image to this path (single-image only)").option("--no-download", "Skip downloading; print URLs only").option("--wait-timeout <ms>", "Polling timeout in milliseconds", "480000").option("-v, --verbose", "Verbose logging").action(
|
|
3855
|
+
parent.command("image-to-image").alias("i2i").description("Generate an image conditioned on an existing Leonardo image id (init image)").requiredOption("--prompt <text>", "Text prompt describing the desired output").requiredOption("--ref <imageId>", "Existing Leonardo image id to use as init image").option("--image-type <type>", "GENERATED | UPLOADED", "GENERATED").option("--init-strength <n>", "Init image strength (0.0-0.9)", "0.5").option("-m, --model <id>", "Model UUID").option("-w, --width <n>", "Image width").option("-h, --height <n>", "Image height").option("-n, --num <n>", "Number of images").option("--seed <n>", "Seed").option("--negative <text>", "Negative prompt").option("--output <path>", "Copy first image to this path (single-image only)").addOption(imageFormatOption()).addOption(noWebpOption()).option("--no-download", "Skip downloading; print URLs only").option("--wait-timeout <ms>", "Polling timeout in milliseconds", "480000").option("-v, --verbose", "Verbose logging").action(
|
|
3657
3856
|
async (opts) => {
|
|
3658
3857
|
const logger = createLogger({ verbose: opts.verbose ?? false });
|
|
3659
3858
|
const imageType = opts.imageType.toUpperCase();
|
|
@@ -3709,7 +3908,7 @@ function registerLeonardoImageToImageCommand(parent) {
|
|
|
3709
3908
|
if (!img) continue;
|
|
3710
3909
|
const ext = extFromUrl2(img.url);
|
|
3711
3910
|
const filename = `leonardo-i2i-${generationId.slice(0, 8)}-${String(i + 1).padStart(2, "0")}${ext}`;
|
|
3712
|
-
const filepath =
|
|
3911
|
+
const filepath = path28.join(outDir, filename);
|
|
3713
3912
|
try {
|
|
3714
3913
|
await downloadFile(img.url, filepath);
|
|
3715
3914
|
saved.push(filepath);
|
|
@@ -3719,21 +3918,22 @@ function registerLeonardoImageToImageCommand(parent) {
|
|
|
3719
3918
|
}
|
|
3720
3919
|
}
|
|
3721
3920
|
if (opts.output && saved[0]) {
|
|
3722
|
-
const dest =
|
|
3723
|
-
|
|
3724
|
-
|
|
3921
|
+
const dest = path28.resolve(opts.output);
|
|
3922
|
+
fs26.mkdirSync(path28.dirname(dest), { recursive: true });
|
|
3923
|
+
fs26.copyFileSync(saved[0], dest);
|
|
3725
3924
|
logger.success(`Copied to ${dest}`);
|
|
3726
3925
|
}
|
|
3926
|
+
const finalImages = finalizeGeneratedImages(saved, { ...opts, logger });
|
|
3727
3927
|
console.log(`
|
|
3728
|
-
Generated ${
|
|
3729
|
-
for (const f of
|
|
3928
|
+
Generated ${finalImages.length} image(s):`);
|
|
3929
|
+
for (const f of finalImages) console.log(` ${f}`);
|
|
3730
3930
|
}
|
|
3731
3931
|
);
|
|
3732
3932
|
}
|
|
3733
3933
|
|
|
3734
3934
|
// src/providers/leonardo/commands/video.ts
|
|
3735
3935
|
init_http_client();
|
|
3736
|
-
import
|
|
3936
|
+
import path29 from "path";
|
|
3737
3937
|
function v2DimsFor(resolution) {
|
|
3738
3938
|
if (resolution === "RESOLUTION_1080") return { width: 1920, height: 1080 };
|
|
3739
3939
|
if (resolution === "RESOLUTION_480") return { width: 854, height: 480 };
|
|
@@ -3844,7 +4044,7 @@ async function pollAndDownload(client, jobId, opts) {
|
|
|
3844
4044
|
logger.error(`No video URL in completed generation: ${JSON.stringify(gen)}`);
|
|
3845
4045
|
process.exit(1);
|
|
3846
4046
|
}
|
|
3847
|
-
const outPath = opts.output ??
|
|
4047
|
+
const outPath = opts.output ?? path29.join(getOutputDir(), `${opts.basename}-${jobId.slice(0, 8)}.mp4`);
|
|
3848
4048
|
await downloadFile(videoUrl2, outPath);
|
|
3849
4049
|
logger.success(`Saved ${outPath}`);
|
|
3850
4050
|
await maybeDownloadThumb(gen, outPath, {
|
|
@@ -3951,7 +4151,7 @@ function registerLeonardoModelsCommand(parent) {
|
|
|
3951
4151
|
|
|
3952
4152
|
// src/providers/leonardo/commands/status.ts
|
|
3953
4153
|
init_http_client();
|
|
3954
|
-
import
|
|
4154
|
+
import path30 from "path";
|
|
3955
4155
|
var VIDEO_KEYS = ["motionMP4URL", "url"];
|
|
3956
4156
|
var IMAGE_EXT_RE2 = /\.(jpe?g|png|webp|gif)(\?|$)/i;
|
|
3957
4157
|
var VIDEO_EXT_RE = /\.(mp4|webm|mov|m4v)(\?|$)/i;
|
|
@@ -4016,7 +4216,7 @@ function registerLeonardoStatusCommand(parent) {
|
|
|
4016
4216
|
const outDir = getOutputDir();
|
|
4017
4217
|
let idx = 0;
|
|
4018
4218
|
for (const url of videos) {
|
|
4019
|
-
const dest = opts.output && videos.length === 1 && images.length === 0 ? opts.output :
|
|
4219
|
+
const dest = opts.output && videos.length === 1 && images.length === 0 ? opts.output : path30.join(outDir, `leonardo-${generationId.slice(0, 8)}-${idx}.mp4`);
|
|
4020
4220
|
await downloadFile(url, dest);
|
|
4021
4221
|
logger.success(`Saved ${dest}`);
|
|
4022
4222
|
await maybeDownloadThumb(gen, dest, {
|
|
@@ -4029,7 +4229,7 @@ function registerLeonardoStatusCommand(parent) {
|
|
|
4029
4229
|
for (const url of images) {
|
|
4030
4230
|
const m = url.match(IMAGE_EXT_RE2);
|
|
4031
4231
|
const ext = m?.[1] ? m[1].toLowerCase().replace("jpeg", "jpg") : "png";
|
|
4032
|
-
const dest =
|
|
4232
|
+
const dest = path30.join(outDir, `leonardo-${generationId.slice(0, 8)}-${idx}.${ext}`);
|
|
4033
4233
|
await downloadFile(url, dest);
|
|
4034
4234
|
logger.success(`Saved ${dest}`);
|
|
4035
4235
|
idx++;
|
|
@@ -4188,8 +4388,8 @@ function sleep4(ms) {
|
|
|
4188
4388
|
|
|
4189
4389
|
// src/providers/minimax/generators/music.ts
|
|
4190
4390
|
init_http_client();
|
|
4191
|
-
import
|
|
4192
|
-
import
|
|
4391
|
+
import fs27 from "fs";
|
|
4392
|
+
import path31 from "path";
|
|
4193
4393
|
async function generateMinimaxMusic(opts) {
|
|
4194
4394
|
const {
|
|
4195
4395
|
apiKey,
|
|
@@ -4234,14 +4434,14 @@ async function generateMinimaxMusic(opts) {
|
|
|
4234
4434
|
}
|
|
4235
4435
|
const durationMs = resp.extra_info?.music_duration ?? 0;
|
|
4236
4436
|
const outDir = getOutputDir();
|
|
4237
|
-
const dest =
|
|
4437
|
+
const dest = path31.join(outDir, `minimax_music_${Date.now()}.${outputFormat}`);
|
|
4238
4438
|
try {
|
|
4239
4439
|
if (audioData.startsWith("http")) {
|
|
4240
4440
|
const bytes = await fetchBytes(audioData);
|
|
4241
|
-
|
|
4441
|
+
fs27.writeFileSync(dest, bytes);
|
|
4242
4442
|
} else {
|
|
4243
4443
|
const audioBytes = Buffer.from(audioData, "hex");
|
|
4244
|
-
|
|
4444
|
+
fs27.writeFileSync(dest, audioBytes);
|
|
4245
4445
|
}
|
|
4246
4446
|
} catch (e) {
|
|
4247
4447
|
return {
|
|
@@ -4252,8 +4452,8 @@ async function generateMinimaxMusic(opts) {
|
|
|
4252
4452
|
const durSec = durationMs / 1e3;
|
|
4253
4453
|
logger?.success(`Saved: ${dest} (${durSec.toFixed(1)}s)`);
|
|
4254
4454
|
if (output) {
|
|
4255
|
-
|
|
4256
|
-
|
|
4455
|
+
fs27.mkdirSync(path31.dirname(path31.resolve(output)), { recursive: true });
|
|
4456
|
+
fs27.copyFileSync(dest, output);
|
|
4257
4457
|
}
|
|
4258
4458
|
return { status: "success", generatedAudio: dest, durationMs, model };
|
|
4259
4459
|
}
|
|
@@ -4327,8 +4527,8 @@ Generated music: ${result.generatedAudio}`);
|
|
|
4327
4527
|
init_errors();
|
|
4328
4528
|
|
|
4329
4529
|
// src/providers/minimax/generators/speech.ts
|
|
4330
|
-
import
|
|
4331
|
-
import
|
|
4530
|
+
import fs28 from "fs";
|
|
4531
|
+
import path32 from "path";
|
|
4332
4532
|
async function generateMinimaxSpeech(opts) {
|
|
4333
4533
|
const {
|
|
4334
4534
|
apiKey,
|
|
@@ -4375,12 +4575,12 @@ async function generateMinimaxSpeech(opts) {
|
|
|
4375
4575
|
const audioBytes = Buffer.from(audioHex, "hex");
|
|
4376
4576
|
const ext = ["mp3", "wav", "flac"].includes(outputFormat) ? outputFormat : "mp3";
|
|
4377
4577
|
const outDir = getOutputDir();
|
|
4378
|
-
const dest =
|
|
4379
|
-
|
|
4578
|
+
const dest = path32.join(outDir, `minimax_speech_${Date.now()}.${ext}`);
|
|
4579
|
+
fs28.writeFileSync(dest, audioBytes);
|
|
4380
4580
|
logger?.success(`Saved: ${dest} (${(audioBytes.length / 1024).toFixed(1)} KB)`);
|
|
4381
4581
|
if (output) {
|
|
4382
|
-
|
|
4383
|
-
|
|
4582
|
+
fs28.mkdirSync(path32.dirname(path32.resolve(output)), { recursive: true });
|
|
4583
|
+
fs28.copyFileSync(dest, output);
|
|
4384
4584
|
}
|
|
4385
4585
|
return { status: "success", generatedAudio: dest, model };
|
|
4386
4586
|
}
|
|
@@ -4427,8 +4627,8 @@ Generated audio: ${result.generatedAudio}`);
|
|
|
4427
4627
|
init_errors();
|
|
4428
4628
|
|
|
4429
4629
|
// src/providers/minimax/generators/video.ts
|
|
4430
|
-
import
|
|
4431
|
-
import
|
|
4630
|
+
import fs29 from "fs";
|
|
4631
|
+
import path33 from "path";
|
|
4432
4632
|
async function generateMinimaxVideo(opts) {
|
|
4433
4633
|
const {
|
|
4434
4634
|
apiKey,
|
|
@@ -4474,18 +4674,18 @@ async function generateMinimaxVideo(opts) {
|
|
|
4474
4674
|
return { status: "error", error: `No file_id in poll result: ${JSON.stringify(pollResult)}` };
|
|
4475
4675
|
}
|
|
4476
4676
|
const outDir = getOutputDir();
|
|
4477
|
-
const dest =
|
|
4677
|
+
const dest = path33.join(outDir, `minimax_video_${Date.now()}.mp4`);
|
|
4478
4678
|
try {
|
|
4479
4679
|
await downloadMinimaxFile(fileId, apiKey, dest, logger);
|
|
4480
4680
|
} catch (e) {
|
|
4481
4681
|
return { status: "error", error: e instanceof Error ? e.message : String(e) };
|
|
4482
4682
|
}
|
|
4483
4683
|
const elapsed = (Date.now() - start) / 1e3;
|
|
4484
|
-
const stat =
|
|
4684
|
+
const stat = fs29.statSync(dest);
|
|
4485
4685
|
const fileSizeMb = stat.size / (1024 * 1024);
|
|
4486
4686
|
if (output) {
|
|
4487
|
-
|
|
4488
|
-
|
|
4687
|
+
fs29.mkdirSync(path33.dirname(path33.resolve(output)), { recursive: true });
|
|
4688
|
+
fs29.copyFileSync(dest, output);
|
|
4489
4689
|
}
|
|
4490
4690
|
await maybeDownloadThumb(pollResult, dest, {
|
|
4491
4691
|
skip: thumb === false,
|
|
@@ -4548,8 +4748,8 @@ Generated video: ${result.generatedVideo}`);
|
|
|
4548
4748
|
// src/providers/minimax/generators/image.ts
|
|
4549
4749
|
init_errors();
|
|
4550
4750
|
init_http_client();
|
|
4551
|
-
import
|
|
4552
|
-
import
|
|
4751
|
+
import fs30 from "fs";
|
|
4752
|
+
import path34 from "path";
|
|
4553
4753
|
async function generateMinimaxImage(opts) {
|
|
4554
4754
|
const {
|
|
4555
4755
|
apiKey,
|
|
@@ -4587,8 +4787,8 @@ async function generateMinimaxImage(opts) {
|
|
|
4587
4787
|
try {
|
|
4588
4788
|
const bytes = await fetchBytes(url);
|
|
4589
4789
|
const fname = `minimax_image_${Date.now()}_${i}.png`;
|
|
4590
|
-
const dest =
|
|
4591
|
-
|
|
4790
|
+
const dest = path34.join(outDir, fname);
|
|
4791
|
+
fs30.writeFileSync(dest, bytes);
|
|
4592
4792
|
savedFiles.push(dest);
|
|
4593
4793
|
logger?.success(`Saved: ${dest}`);
|
|
4594
4794
|
} catch (e) {
|
|
@@ -4599,15 +4799,15 @@ async function generateMinimaxImage(opts) {
|
|
|
4599
4799
|
}
|
|
4600
4800
|
}
|
|
4601
4801
|
if (output && savedFiles[0]) {
|
|
4602
|
-
|
|
4603
|
-
|
|
4802
|
+
fs30.mkdirSync(path34.dirname(path34.resolve(output)), { recursive: true });
|
|
4803
|
+
fs30.copyFileSync(savedFiles[0], output);
|
|
4604
4804
|
}
|
|
4605
4805
|
return { status: "success", generatedImages: savedFiles, model };
|
|
4606
4806
|
}
|
|
4607
4807
|
|
|
4608
4808
|
// src/providers/minimax/commands/generate.ts
|
|
4609
4809
|
function registerMinimaxGenerateCommand(parent) {
|
|
4610
|
-
parent.command("generate").description("Generate images with MiniMax").requiredOption("--prompt <text>", "Image generation prompt").option("--model <id>", `Model (${[...MINIMAX_IMAGE_MODELS].join("|")})`).option("--aspect-ratio <ratio>", "Aspect ratio (e.g. 1:1, 16:9)", "1:1").option("--num-images <n>", "Number of images (1-9)", "1").option("--output <path>", "Copy first image to this path").option("-v, --verbose", "Verbose logging").action(
|
|
4810
|
+
parent.command("generate").description("Generate images with MiniMax").requiredOption("--prompt <text>", "Image generation prompt").option("--model <id>", `Model (${[...MINIMAX_IMAGE_MODELS].join("|")})`).option("--aspect-ratio <ratio>", "Aspect ratio (e.g. 1:1, 16:9)", "1:1").option("--num-images <n>", "Number of images (1-9)", "1").option("--output <path>", "Copy first image to this path").addOption(imageFormatOption()).addOption(noWebpOption()).option("-v, --verbose", "Verbose logging").action(
|
|
4611
4811
|
async (opts) => {
|
|
4612
4812
|
const logger = createLogger({ verbose: opts.verbose ?? false });
|
|
4613
4813
|
const apiKey = requireMinimaxKey();
|
|
@@ -4626,9 +4826,13 @@ function registerMinimaxGenerateCommand(parent) {
|
|
|
4626
4826
|
logger.error(result.error ?? "Unknown error");
|
|
4627
4827
|
process.exit(1);
|
|
4628
4828
|
}
|
|
4829
|
+
const finalImages = finalizeGeneratedImages(result.generatedImages ?? [], {
|
|
4830
|
+
...opts,
|
|
4831
|
+
logger
|
|
4832
|
+
});
|
|
4629
4833
|
console.log(`
|
|
4630
|
-
Generated ${
|
|
4631
|
-
for (const f of
|
|
4834
|
+
Generated ${finalImages.length} image(s):`);
|
|
4835
|
+
for (const f of finalImages) console.log(` ${f}`);
|
|
4632
4836
|
}
|
|
4633
4837
|
);
|
|
4634
4838
|
}
|
|
@@ -4636,15 +4840,15 @@ Generated ${result.generatedImages?.length ?? 0} image(s):`);
|
|
|
4636
4840
|
// src/providers/minimax/commands/image-to-image.ts
|
|
4637
4841
|
init_errors();
|
|
4638
4842
|
init_http_client();
|
|
4639
|
-
import
|
|
4640
|
-
import
|
|
4843
|
+
import fs31 from "fs";
|
|
4844
|
+
import path35 from "path";
|
|
4641
4845
|
function registerMinimaxImageToImageCommand(parent) {
|
|
4642
4846
|
parent.command("image-to-image").alias("i2i").description(
|
|
4643
4847
|
"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."
|
|
4644
4848
|
).requiredOption("--prompt <text>", "Prompt describing the desired NEW scene/composition").requiredOption(
|
|
4645
4849
|
"--ref <path>",
|
|
4646
4850
|
"Subject reference image (URL or local path) \u2014 character/face reference only, single image"
|
|
4647
|
-
).option("--model <id>", "Model id (default image-01)").option("--aspect-ratio <ratio>", "Aspect ratio (e.g. 1:1, 16:9)", "1:1").option("--num-images <n>", "Number of images (1-9)", "1").option("--output <path>", "Copy first image to this path").option("-v, --verbose", "Verbose logging").action(
|
|
4851
|
+
).option("--model <id>", "Model id (default image-01)").option("--aspect-ratio <ratio>", "Aspect ratio (e.g. 1:1, 16:9)", "1:1").option("--num-images <n>", "Number of images (1-9)", "1").option("--output <path>", "Copy first image to this path").addOption(imageFormatOption()).addOption(noWebpOption()).option("-v, --verbose", "Verbose logging").action(
|
|
4648
4852
|
async (opts) => {
|
|
4649
4853
|
const logger = createLogger({ verbose: opts.verbose ?? false });
|
|
4650
4854
|
const apiKey = requireMinimaxKey();
|
|
@@ -4692,8 +4896,8 @@ function registerMinimaxImageToImageCommand(parent) {
|
|
|
4692
4896
|
if (!url) continue;
|
|
4693
4897
|
try {
|
|
4694
4898
|
const bytes = await fetchBytes(url);
|
|
4695
|
-
const dest =
|
|
4696
|
-
|
|
4899
|
+
const dest = path35.join(outDir, `minimax-i2i-${ts}-${i + 1}.png`);
|
|
4900
|
+
fs31.writeFileSync(dest, bytes);
|
|
4697
4901
|
saved.push(dest);
|
|
4698
4902
|
logger.success(`Saved: ${dest}`);
|
|
4699
4903
|
} catch (e) {
|
|
@@ -4704,13 +4908,14 @@ function registerMinimaxImageToImageCommand(parent) {
|
|
|
4704
4908
|
}
|
|
4705
4909
|
}
|
|
4706
4910
|
if (opts.output && saved[0]) {
|
|
4707
|
-
|
|
4708
|
-
|
|
4911
|
+
fs31.mkdirSync(path35.dirname(path35.resolve(opts.output)), { recursive: true });
|
|
4912
|
+
fs31.copyFileSync(saved[0], opts.output);
|
|
4709
4913
|
logger.success(`Copied to: ${opts.output}`);
|
|
4710
4914
|
}
|
|
4915
|
+
const finalImages = finalizeGeneratedImages(saved, { ...opts, logger });
|
|
4711
4916
|
console.log(`
|
|
4712
|
-
Generated ${
|
|
4713
|
-
for (const f of
|
|
4917
|
+
Generated ${finalImages.length} image(s):`);
|
|
4918
|
+
for (const f of finalImages) console.log(` ${f}`);
|
|
4714
4919
|
}
|
|
4715
4920
|
);
|
|
4716
4921
|
}
|
|
@@ -4839,12 +5044,12 @@ function validateKnownSpeakerPairing(names = [], refs = []) {
|
|
|
4839
5044
|
|
|
4840
5045
|
// src/providers/openai/openai-audio.ts
|
|
4841
5046
|
init_http_client();
|
|
4842
|
-
import
|
|
4843
|
-
import
|
|
5047
|
+
import fs33 from "fs";
|
|
5048
|
+
import path37 from "path";
|
|
4844
5049
|
|
|
4845
5050
|
// src/providers/openai/client.ts
|
|
4846
|
-
import
|
|
4847
|
-
import
|
|
5051
|
+
import fs32 from "fs";
|
|
5052
|
+
import path36 from "path";
|
|
4848
5053
|
init_errors();
|
|
4849
5054
|
init_http_client();
|
|
4850
5055
|
var DEFAULT_TIMEOUT_MS3 = 24e4;
|
|
@@ -4911,10 +5116,10 @@ async function postOpenAIBinary(pathname, body, apiKey, timeoutMs = DEFAULT_TIME
|
|
|
4911
5116
|
return Buffer.from(await res.arrayBuffer());
|
|
4912
5117
|
}
|
|
4913
5118
|
function fileFromPath(filePath) {
|
|
4914
|
-
const abs =
|
|
4915
|
-
if (!
|
|
4916
|
-
const bytes =
|
|
4917
|
-
return new File([new Uint8Array(bytes)],
|
|
5119
|
+
const abs = path36.resolve(filePath);
|
|
5120
|
+
if (!fs32.existsSync(abs)) throw new ValidationError(`File not found: ${abs}`);
|
|
5121
|
+
const bytes = fs32.readFileSync(abs);
|
|
5122
|
+
return new File([new Uint8Array(bytes)], path36.basename(abs));
|
|
4918
5123
|
}
|
|
4919
5124
|
|
|
4920
5125
|
// src/providers/openai/openai-audio.ts
|
|
@@ -4953,7 +5158,7 @@ async function resolveAudioReferenceDataUrl(ref) {
|
|
|
4953
5158
|
}
|
|
4954
5159
|
function audioMimeFromReference(ref) {
|
|
4955
5160
|
const pathname = new URL(ref).pathname;
|
|
4956
|
-
const ext =
|
|
5161
|
+
const ext = path37.extname(pathname).toLowerCase();
|
|
4957
5162
|
switch (ext) {
|
|
4958
5163
|
case ".mp3":
|
|
4959
5164
|
return "audio/mpeg";
|
|
@@ -4972,12 +5177,12 @@ function audioMimeFromReference(ref) {
|
|
|
4972
5177
|
async function generateOpenAISpeech(opts) {
|
|
4973
5178
|
const bytes = await postOpenAIBinary("/audio/speech", buildSpeechBody(opts), requireOpenAIKey());
|
|
4974
5179
|
const ext = extFromAudioFormat(opts.outputFormat);
|
|
4975
|
-
const dest =
|
|
4976
|
-
|
|
5180
|
+
const dest = path37.join(getOutputDir(), `openai-speech-${Date.now()}.${ext}`);
|
|
5181
|
+
fs33.writeFileSync(dest, bytes);
|
|
4977
5182
|
opts.logger?.success(`Saved: ${dest} (${(bytes.length / 1024).toFixed(1)} KB)`);
|
|
4978
5183
|
if (opts.output) {
|
|
4979
|
-
|
|
4980
|
-
|
|
5184
|
+
fs33.mkdirSync(path37.dirname(path37.resolve(opts.output)), { recursive: true });
|
|
5185
|
+
fs33.copyFileSync(dest, opts.output);
|
|
4981
5186
|
}
|
|
4982
5187
|
return dest;
|
|
4983
5188
|
}
|
|
@@ -4999,9 +5204,9 @@ async function transcribeOpenAIAudio(opts) {
|
|
|
4999
5204
|
);
|
|
5000
5205
|
const text = typeof data.text === "string" ? data.text : void 0;
|
|
5001
5206
|
const ext = opts.format === "text" ? "txt" : "json";
|
|
5002
|
-
const dest = opts.output ?
|
|
5003
|
-
|
|
5004
|
-
|
|
5207
|
+
const dest = opts.output ? path37.resolve(opts.output) : path37.join(getOutputDir(), `openai-transcribe-${Date.now()}.${ext}`);
|
|
5208
|
+
fs33.mkdirSync(path37.dirname(dest), { recursive: true });
|
|
5209
|
+
fs33.writeFileSync(dest, opts.format === "text" ? text ?? "" : JSON.stringify(data, null, 2));
|
|
5005
5210
|
opts.logger?.success(`Saved transcript: ${dest}`);
|
|
5006
5211
|
return { path: dest, text };
|
|
5007
5212
|
}
|
|
@@ -5028,9 +5233,9 @@ Generated audio: ${dest}`);
|
|
|
5028
5233
|
}
|
|
5029
5234
|
|
|
5030
5235
|
// src/providers/openai/codex-image-driver.ts
|
|
5031
|
-
import
|
|
5236
|
+
import fs34 from "fs";
|
|
5032
5237
|
import os2 from "os";
|
|
5033
|
-
import
|
|
5238
|
+
import path38 from "path";
|
|
5034
5239
|
import { execa } from "execa";
|
|
5035
5240
|
init_errors();
|
|
5036
5241
|
var defaultRunner = async (cmd, args, opts) => {
|
|
@@ -5079,12 +5284,12 @@ function sanitizeCodexEnv() {
|
|
|
5079
5284
|
async function runCodexImageGeneration(opts) {
|
|
5080
5285
|
const runner = opts.runner ?? defaultRunner;
|
|
5081
5286
|
const ext = extFromImageFormat(opts.outputFormat);
|
|
5082
|
-
const dest = opts.output ?
|
|
5083
|
-
|
|
5084
|
-
const tmp =
|
|
5085
|
-
const schema =
|
|
5086
|
-
const result =
|
|
5087
|
-
|
|
5287
|
+
const dest = opts.output ? path38.resolve(opts.output) : path38.join(getOutputDir(), `codex-image-${Date.now()}.${ext}`);
|
|
5288
|
+
fs34.mkdirSync(path38.dirname(dest), { recursive: true });
|
|
5289
|
+
const tmp = fs34.mkdtempSync(path38.join(os2.tmpdir(), "multix-codex-image-"));
|
|
5290
|
+
const schema = path38.join(tmp, "schema.json");
|
|
5291
|
+
const result = path38.join(tmp, "result.json");
|
|
5292
|
+
fs34.writeFileSync(
|
|
5088
5293
|
schema,
|
|
5089
5294
|
JSON.stringify({
|
|
5090
5295
|
type: "object",
|
|
@@ -5114,7 +5319,7 @@ async function runCodexImageGeneration(opts) {
|
|
|
5114
5319
|
if (res.exitCode !== 0) {
|
|
5115
5320
|
throw new ProviderError(`Codex image driver failed: ${res.stderr || res.stdout}`, "openai");
|
|
5116
5321
|
}
|
|
5117
|
-
if (!
|
|
5322
|
+
if (!fs34.existsSync(dest) || fs34.statSync(dest).size === 0) {
|
|
5118
5323
|
throw new ProviderError(`Codex did not create a non-empty image at ${dest}`, "openai");
|
|
5119
5324
|
}
|
|
5120
5325
|
return { path: dest };
|
|
@@ -5132,8 +5337,8 @@ function buildCodexPrompt(userPrompt, outputPath) {
|
|
|
5132
5337
|
// src/providers/openai/openai-image.ts
|
|
5133
5338
|
init_errors();
|
|
5134
5339
|
init_http_client();
|
|
5135
|
-
import
|
|
5136
|
-
import
|
|
5340
|
+
import fs35 from "fs";
|
|
5341
|
+
import path39 from "path";
|
|
5137
5342
|
function buildImageGenerationBody(opts) {
|
|
5138
5343
|
return {
|
|
5139
5344
|
model: opts.model ?? DEFAULT_OPENAI_IMAGE_MODEL,
|
|
@@ -5166,14 +5371,14 @@ function saveOpenAIImages(opts) {
|
|
|
5166
5371
|
for (let i = 0; i < items.length; i++) {
|
|
5167
5372
|
const b64 = items[i]?.b64_json;
|
|
5168
5373
|
if (!b64) throw new ProviderError("No b64_json in OpenAI image response", "openai");
|
|
5169
|
-
const dest =
|
|
5170
|
-
|
|
5374
|
+
const dest = path39.join(outDir, `openai-${opts.task}-${ts}-${i + 1}.${ext}`);
|
|
5375
|
+
fs35.writeFileSync(dest, Buffer.from(b64, "base64"));
|
|
5171
5376
|
saved.push(dest);
|
|
5172
5377
|
opts.logger?.success(`Saved: ${dest}`);
|
|
5173
5378
|
}
|
|
5174
5379
|
if (opts.output && saved[0]) {
|
|
5175
|
-
|
|
5176
|
-
|
|
5380
|
+
fs35.mkdirSync(path39.dirname(path39.resolve(opts.output)), { recursive: true });
|
|
5381
|
+
fs35.copyFileSync(saved[0], opts.output);
|
|
5177
5382
|
opts.logger?.success(`Copied to: ${opts.output}`);
|
|
5178
5383
|
}
|
|
5179
5384
|
return saved;
|
|
@@ -5182,7 +5387,7 @@ async function resolveImageRefBytes(ref) {
|
|
|
5182
5387
|
if (isHttpUrl(ref)) {
|
|
5183
5388
|
const bytes2 = await fetchBytes(ref);
|
|
5184
5389
|
const pathname = new URL(ref).pathname;
|
|
5185
|
-
const filename =
|
|
5390
|
+
const filename = path39.basename(pathname) || "reference.png";
|
|
5186
5391
|
return { filename, bytes: bytes2, mime: sniffImageMime2(bytes2, filename) };
|
|
5187
5392
|
}
|
|
5188
5393
|
const file = fileFromPath(ref);
|
|
@@ -5227,7 +5432,7 @@ function sniffImageMime2(buf, name) {
|
|
|
5227
5432
|
if (buf[0] === 137 && buf[1] === 80 && buf[2] === 78 && buf[3] === 71) return "image/png";
|
|
5228
5433
|
if (buf.slice(0, 4).toString() === "RIFF" && buf.slice(8, 12).toString() === "WEBP")
|
|
5229
5434
|
return "image/webp";
|
|
5230
|
-
const ext =
|
|
5435
|
+
const ext = path39.extname(name).toLowerCase();
|
|
5231
5436
|
if (ext === ".jpg" || ext === ".jpeg") return "image/jpeg";
|
|
5232
5437
|
if (ext === ".webp") return "image/webp";
|
|
5233
5438
|
return "image/png";
|
|
@@ -5239,9 +5444,12 @@ function registerOpenAIGenerateCommand(parent) {
|
|
|
5239
5444
|
"-m, --model <id>",
|
|
5240
5445
|
"Image model",
|
|
5241
5446
|
resolveKey("OPENAI_IMAGE_MODEL") ?? DEFAULT_OPENAI_IMAGE_MODEL
|
|
5242
|
-
).option("--size <size>", "1024x1024|1024x1536|1536x1024|auto", "1024x1024").option("--quality <q>", "low|medium|high", "medium").option(
|
|
5447
|
+
).option("--size <size>", "1024x1024|1024x1536|1536x1024|auto", "1024x1024").option("--quality <q>", "low|medium|high", "medium").option(
|
|
5448
|
+
"--format <fmt>",
|
|
5449
|
+
"Provider output format png|jpeg|webp (explicit value keeps it; default png then WebP)"
|
|
5450
|
+
).option("-n, --num-images <n>", "Number of images for API driver", "1").option("--output <path>", "Save first image to this path").addOption(imageFormatOption()).addOption(noWebpOption()).option("-v, --verbose", "Verbose logging").action(async (opts) => {
|
|
5243
5451
|
const logger = createLogger({ verbose: opts.verbose ?? false });
|
|
5244
|
-
const outputFormat = parseImageFormat(opts.format);
|
|
5452
|
+
const outputFormat = parseImageFormat(opts.format ?? "png");
|
|
5245
5453
|
const driver = await resolveImageDriver({ requested: parseDriver(opts.driver) });
|
|
5246
5454
|
const saved = driver === "codex" ? [
|
|
5247
5455
|
(await runCodexImageGeneration({
|
|
@@ -5259,9 +5467,14 @@ function registerOpenAIGenerateCommand(parent) {
|
|
|
5259
5467
|
output: opts.output,
|
|
5260
5468
|
logger
|
|
5261
5469
|
});
|
|
5470
|
+
const finalImages = finalizeGeneratedImages(saved, {
|
|
5471
|
+
...opts,
|
|
5472
|
+
logger,
|
|
5473
|
+
explicitProviderFormat: opts.format !== void 0
|
|
5474
|
+
});
|
|
5262
5475
|
console.log(`
|
|
5263
|
-
Generated ${
|
|
5264
|
-
for (const
|
|
5476
|
+
Generated ${finalImages.length} image(s):`);
|
|
5477
|
+
for (const f of finalImages) console.log(` ${f}`);
|
|
5265
5478
|
});
|
|
5266
5479
|
}
|
|
5267
5480
|
|
|
@@ -5277,12 +5490,15 @@ function registerOpenAIImageToImageCommand(parent) {
|
|
|
5277
5490
|
"-m, --model <id>",
|
|
5278
5491
|
"Image model",
|
|
5279
5492
|
resolveKey("OPENAI_IMAGE_MODEL") ?? DEFAULT_OPENAI_IMAGE_MODEL
|
|
5280
|
-
).option("--size <size>", "1024x1024|1024x1536|1536x1024|auto", "1024x1024").option("--quality <q>", "low|medium|high", "medium").option(
|
|
5493
|
+
).option("--size <size>", "1024x1024|1024x1536|1536x1024|auto", "1024x1024").option("--quality <q>", "low|medium|high", "medium").option(
|
|
5494
|
+
"--format <fmt>",
|
|
5495
|
+
"Provider output format png|jpeg|webp (explicit value keeps it; default png then WebP)"
|
|
5496
|
+
).option("--output <path>", "Save first image to this path").addOption(imageFormatOption()).addOption(noWebpOption()).option("-v, --verbose", "Verbose logging").action(async (opts) => {
|
|
5281
5497
|
if (!opts.ref || opts.ref.length === 0) {
|
|
5282
5498
|
throw new ValidationError("At least one --ref is required for image-to-image.");
|
|
5283
5499
|
}
|
|
5284
5500
|
const logger = createLogger({ verbose: opts.verbose ?? false });
|
|
5285
|
-
const outputFormat = parseImageFormat(opts.format);
|
|
5501
|
+
const outputFormat = parseImageFormat(opts.format ?? "png");
|
|
5286
5502
|
const driver = await resolveImageDriver({ requested: parseDriver(opts.driver) });
|
|
5287
5503
|
const saved = driver === "codex" ? [
|
|
5288
5504
|
(await runCodexImageGeneration({
|
|
@@ -5301,9 +5517,14 @@ function registerOpenAIImageToImageCommand(parent) {
|
|
|
5301
5517
|
output: opts.output,
|
|
5302
5518
|
logger
|
|
5303
5519
|
});
|
|
5520
|
+
const finalImages = finalizeGeneratedImages(saved, {
|
|
5521
|
+
...opts,
|
|
5522
|
+
logger,
|
|
5523
|
+
explicitProviderFormat: opts.format !== void 0
|
|
5524
|
+
});
|
|
5304
5525
|
console.log(`
|
|
5305
|
-
Generated ${
|
|
5306
|
-
for (const
|
|
5526
|
+
Generated ${finalImages.length} image(s):`);
|
|
5527
|
+
for (const f of finalImages) console.log(` ${f}`);
|
|
5307
5528
|
});
|
|
5308
5529
|
}
|
|
5309
5530
|
function collect5(value, prev) {
|
|
@@ -5355,8 +5576,8 @@ function registerOpenAICommands(program2) {
|
|
|
5355
5576
|
}
|
|
5356
5577
|
|
|
5357
5578
|
// src/providers/openrouter/client.ts
|
|
5358
|
-
import
|
|
5359
|
-
import
|
|
5579
|
+
import fs36 from "fs";
|
|
5580
|
+
import path40 from "path";
|
|
5360
5581
|
init_errors();
|
|
5361
5582
|
init_http_client();
|
|
5362
5583
|
|
|
@@ -5521,8 +5742,8 @@ async function generateOpenRouterImage(opts) {
|
|
|
5521
5742
|
if (!url) continue;
|
|
5522
5743
|
try {
|
|
5523
5744
|
const bytes = await fetchBytes(url);
|
|
5524
|
-
const dest =
|
|
5525
|
-
|
|
5745
|
+
const dest = path40.join(outDir, `openrouter_image_${Date.now()}_${i}.png`);
|
|
5746
|
+
fs36.writeFileSync(dest, bytes);
|
|
5526
5747
|
savedFiles.push(dest);
|
|
5527
5748
|
logger?.success(`Saved: ${dest}`);
|
|
5528
5749
|
} catch (e) {
|
|
@@ -5533,8 +5754,8 @@ async function generateOpenRouterImage(opts) {
|
|
|
5533
5754
|
}
|
|
5534
5755
|
}
|
|
5535
5756
|
if (output && savedFiles[0]) {
|
|
5536
|
-
|
|
5537
|
-
|
|
5757
|
+
fs36.mkdirSync(path40.dirname(path40.resolve(output)), { recursive: true });
|
|
5758
|
+
fs36.writeFileSync(output, fs36.readFileSync(savedFiles[0]));
|
|
5538
5759
|
}
|
|
5539
5760
|
return { status: "success", generatedImages: savedFiles, model: usedModel };
|
|
5540
5761
|
}
|
|
@@ -5543,7 +5764,7 @@ async function generateOpenRouterImage(opts) {
|
|
|
5543
5764
|
function registerOpenRouterGenerateCommand(parent) {
|
|
5544
5765
|
parent.command("generate").description(
|
|
5545
5766
|
"Generate images via OpenRouter (text-to-image); models: Gemini, GPT-Image, Flux, Recraft, Seedream"
|
|
5546
|
-
).requiredOption("--prompt <text>", "Image generation prompt").option("--model <id>", "OpenRouter model id (e.g. google/gemini-3.1-flash-image-preview)").option("--aspect-ratio <ratio>", "Aspect ratio (e.g. 1:1, 16:9)", "1:1").option("--image-size <size>", "Image size hint (e.g. 1K, 2K)").option("--num-images <n>", "Number of images to generate", "1").option("--output <path>", "Copy first image to this path").option("-v, --verbose", "Verbose logging").action(
|
|
5767
|
+
).requiredOption("--prompt <text>", "Image generation prompt").option("--model <id>", "OpenRouter model id (e.g. google/gemini-3.1-flash-image-preview)").option("--aspect-ratio <ratio>", "Aspect ratio (e.g. 1:1, 16:9)", "1:1").option("--image-size <size>", "Image size hint (e.g. 1K, 2K)").option("--num-images <n>", "Number of images to generate", "1").option("--output <path>", "Copy first image to this path").addOption(imageFormatOption()).addOption(noWebpOption()).option("-v, --verbose", "Verbose logging").action(
|
|
5547
5768
|
async (opts) => {
|
|
5548
5769
|
const logger = createLogger({ verbose: opts.verbose ?? false });
|
|
5549
5770
|
const model = opts.model ?? resolveKey("OPENROUTER_IMAGE_MODEL") ?? DEFAULT_OPENROUTER_MODEL;
|
|
@@ -5562,17 +5783,21 @@ function registerOpenRouterGenerateCommand(parent) {
|
|
|
5562
5783
|
logger.error(result.error ?? "Unknown error");
|
|
5563
5784
|
process.exit(1);
|
|
5564
5785
|
}
|
|
5786
|
+
const finalImages = finalizeGeneratedImages(result.generatedImages ?? [], {
|
|
5787
|
+
...opts,
|
|
5788
|
+
logger
|
|
5789
|
+
});
|
|
5565
5790
|
console.log(`
|
|
5566
|
-
Generated ${
|
|
5567
|
-
for (const f of
|
|
5791
|
+
Generated ${finalImages.length} image(s):`);
|
|
5792
|
+
for (const f of finalImages) console.log(` ${f}`);
|
|
5568
5793
|
if (result.model) console.log(`Model used: ${result.model}`);
|
|
5569
5794
|
}
|
|
5570
5795
|
);
|
|
5571
5796
|
}
|
|
5572
5797
|
|
|
5573
5798
|
// src/providers/openrouter/commands/image-to-image.ts
|
|
5574
|
-
import
|
|
5575
|
-
import
|
|
5799
|
+
import fs37 from "fs";
|
|
5800
|
+
import path41 from "path";
|
|
5576
5801
|
init_errors();
|
|
5577
5802
|
init_http_client();
|
|
5578
5803
|
var DEFAULT_MODEL = "google/gemini-2.5-flash-image";
|
|
@@ -5586,7 +5811,7 @@ function registerOpenRouterImageToImageCommand(parent) {
|
|
|
5586
5811
|
"Reference image (URL or local path); repeatable (most models accept 1)",
|
|
5587
5812
|
collect7,
|
|
5588
5813
|
[]
|
|
5589
|
-
).option("-m, --model <id>", `OpenRouter model id (default ${DEFAULT_MODEL})`).option("--strength <n>", "Recraft init-image strength 0..1 (Recraft models only)").option("--output <path>", "Save first generated image to this path").option("-v, --verbose", "Verbose logging").action(
|
|
5814
|
+
).option("-m, --model <id>", `OpenRouter model id (default ${DEFAULT_MODEL})`).option("--strength <n>", "Recraft init-image strength 0..1 (Recraft models only)").option("--output <path>", "Save first generated image to this path").addOption(imageFormatOption()).addOption(noWebpOption()).option("-v, --verbose", "Verbose logging").action(
|
|
5590
5815
|
async (opts) => {
|
|
5591
5816
|
if (!opts.ref || opts.ref.length === 0) {
|
|
5592
5817
|
throw new ValidationError("At least one --ref is required for image-to-image.");
|
|
@@ -5618,9 +5843,10 @@ function registerOpenRouterImageToImageCommand(parent) {
|
|
|
5618
5843
|
output: opts.output,
|
|
5619
5844
|
logger
|
|
5620
5845
|
});
|
|
5846
|
+
const finalImages = finalizeGeneratedImages(saved, { ...opts, logger });
|
|
5621
5847
|
console.log(`
|
|
5622
|
-
Generated ${
|
|
5623
|
-
for (const f of
|
|
5848
|
+
Generated ${finalImages.length} image(s):`);
|
|
5849
|
+
for (const f of finalImages) console.log(` ${f}`);
|
|
5624
5850
|
console.log(`Model used: ${model}`);
|
|
5625
5851
|
}
|
|
5626
5852
|
);
|
|
@@ -5664,14 +5890,14 @@ async function runImageToImage(opts) {
|
|
|
5664
5890
|
const url = parsed.urls[i];
|
|
5665
5891
|
if (!url) continue;
|
|
5666
5892
|
const bytes = await fetchBytes(url);
|
|
5667
|
-
const dest =
|
|
5668
|
-
|
|
5893
|
+
const dest = path41.join(outDir, `openrouter-i2i-${ts}-${i + 1}.png`);
|
|
5894
|
+
fs37.writeFileSync(dest, bytes);
|
|
5669
5895
|
saved.push(dest);
|
|
5670
5896
|
opts.logger.success(`Saved: ${dest}`);
|
|
5671
5897
|
}
|
|
5672
5898
|
if (opts.output && saved[0]) {
|
|
5673
|
-
|
|
5674
|
-
|
|
5899
|
+
fs37.mkdirSync(path41.dirname(path41.resolve(opts.output)), { recursive: true });
|
|
5900
|
+
fs37.copyFileSync(saved[0], opts.output);
|
|
5675
5901
|
opts.logger.success(`Copied to: ${opts.output}`);
|
|
5676
5902
|
}
|
|
5677
5903
|
return saved;
|
|
@@ -5717,13 +5943,13 @@ async function listVideoModels(apiKey = requireOpenRouterKey()) {
|
|
|
5717
5943
|
}
|
|
5718
5944
|
|
|
5719
5945
|
// src/providers/openrouter/commands/video-status.ts
|
|
5720
|
-
import
|
|
5721
|
-
import
|
|
5946
|
+
import fs38 from "fs";
|
|
5947
|
+
import path42 from "path";
|
|
5722
5948
|
async function downloadOpenRouterVideo(opts) {
|
|
5723
5949
|
const { jobId, logger, source } = opts;
|
|
5724
5950
|
const apiKey = requireOpenRouterKey();
|
|
5725
5951
|
const url = videoContentUrl(jobId);
|
|
5726
|
-
const dest =
|
|
5952
|
+
const dest = path42.join(getOutputDir(), `openrouter_video_${jobId}.mp4`);
|
|
5727
5953
|
const r = await globalThis.fetch(url, { headers: { Authorization: `Bearer ${apiKey}` } });
|
|
5728
5954
|
if (!r.ok) {
|
|
5729
5955
|
logger.error(`Download HTTP ${r.status}`);
|
|
@@ -5733,14 +5959,14 @@ async function downloadOpenRouterVideo(opts) {
|
|
|
5733
5959
|
logger.error("Empty response body");
|
|
5734
5960
|
process.exit(1);
|
|
5735
5961
|
}
|
|
5736
|
-
|
|
5737
|
-
const ws =
|
|
5962
|
+
fs38.mkdirSync(path42.dirname(dest), { recursive: true });
|
|
5963
|
+
const ws = fs38.createWriteStream(dest);
|
|
5738
5964
|
const { pipeline: pipeline2 } = await import("stream/promises");
|
|
5739
5965
|
await pipeline2(r.body, ws);
|
|
5740
5966
|
logger.success(`Saved ${dest}`);
|
|
5741
5967
|
if (opts.output) {
|
|
5742
|
-
|
|
5743
|
-
|
|
5968
|
+
fs38.mkdirSync(path42.dirname(path42.resolve(opts.output)), { recursive: true });
|
|
5969
|
+
fs38.copyFileSync(dest, opts.output);
|
|
5744
5970
|
logger.success(`Copied to ${opts.output}`);
|
|
5745
5971
|
}
|
|
5746
5972
|
await maybeDownloadThumb(source, dest, {
|
|
@@ -6151,8 +6377,8 @@ function registerCheckCommand(program2) {
|
|
|
6151
6377
|
|
|
6152
6378
|
// src/commands/doc/convert.ts
|
|
6153
6379
|
init_errors();
|
|
6154
|
-
import
|
|
6155
|
-
import
|
|
6380
|
+
import fs39 from "fs";
|
|
6381
|
+
import path43 from "path";
|
|
6156
6382
|
|
|
6157
6383
|
// src/commands/doc/default-prompt.ts
|
|
6158
6384
|
var DEFAULT_CONVERT_PROMPT = `Convert this document to clean, well-formatted Markdown.
|
|
@@ -6173,18 +6399,18 @@ async function convertDocuments(opts) {
|
|
|
6173
6399
|
const model = opts.model ?? DOC_MODEL_DEFAULT;
|
|
6174
6400
|
const prompt = opts.prompt ?? DEFAULT_CONVERT_PROMPT;
|
|
6175
6401
|
for (const f of inputs) {
|
|
6176
|
-
if (!
|
|
6402
|
+
if (!fs39.existsSync(f)) throw new ValidationError(`Input file not found: ${f}`);
|
|
6177
6403
|
}
|
|
6178
6404
|
let outputPath;
|
|
6179
6405
|
if (output) {
|
|
6180
|
-
outputPath =
|
|
6406
|
+
outputPath = path43.resolve(output);
|
|
6181
6407
|
} else if (autoName && inputs.length === 1 && inputs[0]) {
|
|
6182
|
-
const base =
|
|
6183
|
-
outputPath =
|
|
6408
|
+
const base = path43.basename(inputs[0], path43.extname(inputs[0]));
|
|
6409
|
+
outputPath = path43.join(getOutputDir(), `${base}-extraction.md`);
|
|
6184
6410
|
} else {
|
|
6185
|
-
outputPath =
|
|
6411
|
+
outputPath = path43.join(getOutputDir(), "document-extraction.md");
|
|
6186
6412
|
}
|
|
6187
|
-
|
|
6413
|
+
fs39.mkdirSync(path43.dirname(outputPath), { recursive: true });
|
|
6188
6414
|
const results = [];
|
|
6189
6415
|
for (let i = 0; i < inputs.length; i++) {
|
|
6190
6416
|
const filePath = inputs[i];
|
|
@@ -6235,7 +6461,7 @@ async function convertDocuments(opts) {
|
|
|
6235
6461
|
`);
|
|
6236
6462
|
for (const r of results) {
|
|
6237
6463
|
lines.push(`
|
|
6238
|
-
## ${
|
|
6464
|
+
## ${path43.basename(r.file)}
|
|
6239
6465
|
`);
|
|
6240
6466
|
if (r.status === "success" && r.markdown) {
|
|
6241
6467
|
lines.push(`${r.markdown}
|
|
@@ -6248,7 +6474,7 @@ async function convertDocuments(opts) {
|
|
|
6248
6474
|
}
|
|
6249
6475
|
lines.push("\n---\n");
|
|
6250
6476
|
}
|
|
6251
|
-
|
|
6477
|
+
fs39.writeFileSync(outputPath, lines.join("\n"), "utf8");
|
|
6252
6478
|
const succeeded = results.filter((r) => r.status === "success").length;
|
|
6253
6479
|
console.log(`
|
|
6254
6480
|
${"=".repeat(50)}`);
|
|
@@ -6296,22 +6522,22 @@ function registerDocCommands(program2) {
|
|
|
6296
6522
|
|
|
6297
6523
|
// src/commands/media/index.ts
|
|
6298
6524
|
init_errors();
|
|
6299
|
-
import
|
|
6300
|
-
import
|
|
6525
|
+
import fs42 from "fs";
|
|
6526
|
+
import path46 from "path";
|
|
6301
6527
|
|
|
6302
6528
|
// src/commands/media/batch.ts
|
|
6303
|
-
import
|
|
6304
|
-
import
|
|
6529
|
+
import fs40 from "fs";
|
|
6530
|
+
import path44 from "path";
|
|
6305
6531
|
|
|
6306
6532
|
// src/commands/media/detect-type.ts
|
|
6307
6533
|
var VIDEO_EXTS = /* @__PURE__ */ new Set([".mp4", ".mov", ".avi", ".mkv", ".webm", ".flv", ".mpg", ".mpeg"]);
|
|
6308
6534
|
var AUDIO_EXTS = /* @__PURE__ */ new Set([".mp3", ".wav", ".m4a", ".flac", ".aac", ".ogg", ".aiff"]);
|
|
6309
|
-
var
|
|
6535
|
+
var IMAGE_EXTS2 = /* @__PURE__ */ new Set([".jpg", ".jpeg", ".png", ".webp", ".heic", ".heif", ".gif", ".bmp"]);
|
|
6310
6536
|
function extToKind(ext) {
|
|
6311
6537
|
const lower = ext.toLowerCase();
|
|
6312
6538
|
if (VIDEO_EXTS.has(lower)) return "video";
|
|
6313
6539
|
if (AUDIO_EXTS.has(lower)) return "audio";
|
|
6314
|
-
if (
|
|
6540
|
+
if (IMAGE_EXTS2.has(lower)) return "image";
|
|
6315
6541
|
return void 0;
|
|
6316
6542
|
}
|
|
6317
6543
|
|
|
@@ -6486,7 +6712,7 @@ async function optimizeVideo(opts) {
|
|
|
6486
6712
|
}
|
|
6487
6713
|
|
|
6488
6714
|
// src/commands/media/batch.ts
|
|
6489
|
-
var ALL_EXTS = /* @__PURE__ */ new Set([...VIDEO_EXTS, ...AUDIO_EXTS, ...
|
|
6715
|
+
var ALL_EXTS = /* @__PURE__ */ new Set([...VIDEO_EXTS, ...AUDIO_EXTS, ...IMAGE_EXTS2]);
|
|
6490
6716
|
async function batchOptimize(opts) {
|
|
6491
6717
|
const {
|
|
6492
6718
|
inputDir,
|
|
@@ -6497,10 +6723,10 @@ async function batchOptimize(opts) {
|
|
|
6497
6723
|
verbose,
|
|
6498
6724
|
logger
|
|
6499
6725
|
} = opts;
|
|
6500
|
-
|
|
6501
|
-
const entries =
|
|
6726
|
+
fs40.mkdirSync(outputDir, { recursive: true });
|
|
6727
|
+
const entries = fs40.readdirSync(inputDir);
|
|
6502
6728
|
const mediaFiles = entries.filter((f) => {
|
|
6503
|
-
const ext =
|
|
6729
|
+
const ext = path44.extname(f).toLowerCase();
|
|
6504
6730
|
return ALL_EXTS.has(ext);
|
|
6505
6731
|
});
|
|
6506
6732
|
if (mediaFiles.length === 0) {
|
|
@@ -6511,9 +6737,9 @@ async function batchOptimize(opts) {
|
|
|
6511
6737
|
let succeeded = 0;
|
|
6512
6738
|
const errors = [];
|
|
6513
6739
|
for (const file of mediaFiles) {
|
|
6514
|
-
const inputPath =
|
|
6515
|
-
const outputPath =
|
|
6516
|
-
const ext =
|
|
6740
|
+
const inputPath = path44.join(inputDir, file);
|
|
6741
|
+
const outputPath = path44.join(outputDir, file);
|
|
6742
|
+
const ext = path44.extname(file).toLowerCase();
|
|
6517
6743
|
const kind = extToKind(ext);
|
|
6518
6744
|
logger?.debug(`Processing: ${file}`);
|
|
6519
6745
|
try {
|
|
@@ -6550,8 +6776,8 @@ async function batchOptimize(opts) {
|
|
|
6550
6776
|
|
|
6551
6777
|
// src/commands/media/split-video.ts
|
|
6552
6778
|
init_errors();
|
|
6553
|
-
import
|
|
6554
|
-
import
|
|
6779
|
+
import fs41 from "fs";
|
|
6780
|
+
import path45 from "path";
|
|
6555
6781
|
async function splitVideo(opts) {
|
|
6556
6782
|
await assertFfmpeg();
|
|
6557
6783
|
const { input, outputDir, chunkDuration = 3600, verbose, logger } = opts;
|
|
@@ -6562,10 +6788,10 @@ async function splitVideo(opts) {
|
|
|
6562
6788
|
logger?.info("Video is short enough \u2014 no splitting needed");
|
|
6563
6789
|
return [input];
|
|
6564
6790
|
}
|
|
6565
|
-
|
|
6566
|
-
const ext =
|
|
6567
|
-
const baseName =
|
|
6568
|
-
const outputPattern =
|
|
6791
|
+
fs41.mkdirSync(outputDir, { recursive: true });
|
|
6792
|
+
const ext = path45.extname(input) || ".mp4";
|
|
6793
|
+
const baseName = path45.basename(input, ext);
|
|
6794
|
+
const outputPattern = path45.join(outputDir, `${baseName}_chunk_%03d${ext}`);
|
|
6569
6795
|
logger?.debug(`Splitting into ~${numChunks} chunks of ${chunkDuration}s each`);
|
|
6570
6796
|
const args = [
|
|
6571
6797
|
"-i",
|
|
@@ -6584,8 +6810,8 @@ async function splitVideo(opts) {
|
|
|
6584
6810
|
await runFfmpeg(args, { verbose, logger });
|
|
6585
6811
|
const files = [];
|
|
6586
6812
|
for (let i = 0; i < numChunks + 1; i++) {
|
|
6587
|
-
const fname =
|
|
6588
|
-
if (
|
|
6813
|
+
const fname = path45.join(outputDir, `${baseName}_chunk_${String(i).padStart(3, "0")}${ext}`);
|
|
6814
|
+
if (fs41.existsSync(fname)) files.push(fname);
|
|
6589
6815
|
}
|
|
6590
6816
|
logger?.success(`Created ${files.length} chunks in ${outputDir}`);
|
|
6591
6817
|
return files;
|
|
@@ -6599,9 +6825,9 @@ function registerMediaCommands(program2) {
|
|
|
6599
6825
|
media.command("optimize").description("Optimize a single media file (video, audio, or image)").requiredOption("--input <file>", "Input media file").requiredOption("--output <file>", "Output file path").option("--target-size <MB>", "Target file size in MB").option("--quality <n>", "Quality: video CRF (0-51) or image (1-100)", "85").option("--max-width <px>", "Max image/video width in pixels", "1920").option("--bitrate <rate>", "Audio bitrate (e.g. 64k)", "64k").option("--resolution <WxH>", "Force video resolution (e.g. 1920x1080)").option("-v, --verbose", "Verbose logging").action(
|
|
6600
6826
|
async (opts) => {
|
|
6601
6827
|
const logger = createLogger({ verbose: opts.verbose ?? false });
|
|
6602
|
-
if (!
|
|
6603
|
-
|
|
6604
|
-
const ext =
|
|
6828
|
+
if (!fs42.existsSync(opts.input)) throw new ValidationError(`Input not found: ${opts.input}`);
|
|
6829
|
+
fs42.mkdirSync(path46.dirname(path46.resolve(opts.output)), { recursive: true });
|
|
6830
|
+
const ext = path46.extname(opts.input).toLowerCase();
|
|
6605
6831
|
const kind = extToKind(ext);
|
|
6606
6832
|
if (!kind) throw new ValidationError(`Unsupported file type: ${ext}`);
|
|
6607
6833
|
const quality = Number.parseInt(opts.quality, 10);
|
|
@@ -6641,7 +6867,7 @@ function registerMediaCommands(program2) {
|
|
|
6641
6867
|
media.command("split").description("Split a long video into fixed-duration chunks").requiredOption("--input <file>", "Input video file").option("--output-dir <dir>", "Output directory for chunks", "./chunks").option("--chunk-duration <sec>", "Chunk duration in seconds", "3600").option("-v, --verbose", "Verbose logging").action(
|
|
6642
6868
|
async (opts) => {
|
|
6643
6869
|
const logger = createLogger({ verbose: opts.verbose ?? false });
|
|
6644
|
-
if (!
|
|
6870
|
+
if (!fs42.existsSync(opts.input)) throw new ValidationError(`Input not found: ${opts.input}`);
|
|
6645
6871
|
const chunks = await splitVideo({
|
|
6646
6872
|
input: opts.input,
|
|
6647
6873
|
outputDir: opts.outputDir,
|
|
@@ -6657,7 +6883,7 @@ Created ${chunks.length} chunk(s) in ${opts.outputDir}`);
|
|
|
6657
6883
|
media.command("batch").description("Batch optimize all media files in a directory").requiredOption("--input-dir <dir>", "Input directory").requiredOption("--output-dir <dir>", "Output directory").option("--quality <n>", "Quality setting", "85").option("--max-width <px>", "Max image/video width", "1920").option("--bitrate <rate>", "Audio bitrate", "64k").option("-v, --verbose", "Verbose logging").action(
|
|
6658
6884
|
async (opts) => {
|
|
6659
6885
|
const logger = createLogger({ verbose: opts.verbose ?? false });
|
|
6660
|
-
if (!
|
|
6886
|
+
if (!fs42.existsSync(opts.inputDir))
|
|
6661
6887
|
throw new ValidationError(`Input dir not found: ${opts.inputDir}`);
|
|
6662
6888
|
const result = await batchOptimize({
|
|
6663
6889
|
inputDir: opts.inputDir,
|