@mrgoonie/multix 1.0.2 → 1.0.3
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- package/CHANGELOG.md +7 -0
- package/README.md +6 -5
- package/dist/cli.js +817 -551
- package/package.json +1 -1
- package/skill/SKILL.md +4 -4
package/dist/cli.js
CHANGED
|
@@ -307,6 +307,7 @@ function validateRawInput(spec, body) {
|
|
|
307
307
|
}
|
|
308
308
|
|
|
309
309
|
// src/providers/atlascloud/models.ts
|
|
310
|
+
var ATLAS_PROVIDER = "atlascloud";
|
|
310
311
|
var DEFAULT_ATLAS_BASE_URL = "https://api.atlascloud.ai/api/v1";
|
|
311
312
|
var ATLAS_ROUTING = {
|
|
312
313
|
name: "ATLASCLOUD_BASE_URL",
|
|
@@ -626,6 +627,134 @@ var defaultLogger = createLogger({ verbose: false });
|
|
|
626
627
|
// src/providers/atlascloud/commands/env.ts
|
|
627
628
|
init_redact();
|
|
628
629
|
|
|
630
|
+
// src/core/url-safety.ts
|
|
631
|
+
init_errors();
|
|
632
|
+
init_redact();
|
|
633
|
+
function ipv4Octets(host) {
|
|
634
|
+
const parts = host.split(".");
|
|
635
|
+
if (parts.length !== 4 || !parts.every((p) => /^\d{1,3}$/.test(p))) return void 0;
|
|
636
|
+
const octets = parts.map(Number);
|
|
637
|
+
return octets.every((o) => o <= 255) ? octets : void 0;
|
|
638
|
+
}
|
|
639
|
+
function isNonPublicIpv4([a = 0, b = 0, c2 = 0]) {
|
|
640
|
+
return a === 0 || // "this network" / unspecified
|
|
641
|
+
a === 10 || a === 127 || // loopback
|
|
642
|
+
a === 100 && b >= 64 && b <= 127 || // carrier-grade NAT
|
|
643
|
+
a === 169 && b === 254 || // link-local (cloud metadata)
|
|
644
|
+
a === 172 && b >= 16 && b <= 31 || a === 192 && b === 0 && c2 === 0 || // IETF protocol assignments
|
|
645
|
+
a === 192 && b === 0 && c2 === 2 || // TEST-NET-1
|
|
646
|
+
a === 192 && b === 168 || a === 198 && (b === 18 || b === 19) || // benchmarking
|
|
647
|
+
a === 198 && b === 51 && c2 === 100 || // TEST-NET-2
|
|
648
|
+
a === 203 && b === 0 && c2 === 113 || // TEST-NET-3
|
|
649
|
+
a >= 224;
|
|
650
|
+
}
|
|
651
|
+
function ipv6Groups(host) {
|
|
652
|
+
if (!/^[0-9a-f:.]+$/i.test(host) || !host.includes(":")) return void 0;
|
|
653
|
+
let text = host;
|
|
654
|
+
const tail = /^(.*:)(\d+\.\d+\.\d+\.\d+)$/.exec(text);
|
|
655
|
+
if (tail) {
|
|
656
|
+
const octets = ipv4Octets(tail[2]);
|
|
657
|
+
if (!octets) return void 0;
|
|
658
|
+
const [a = 0, b = 0, c2 = 0, d = 0] = octets;
|
|
659
|
+
text = `${tail[1]}${(a << 8 | b).toString(16)}:${(c2 << 8 | d).toString(16)}`;
|
|
660
|
+
}
|
|
661
|
+
const halves = text.split("::");
|
|
662
|
+
if (halves.length > 2) return void 0;
|
|
663
|
+
const parse = (s) => s === "" ? [] : s.split(":");
|
|
664
|
+
const head = parse(halves[0]);
|
|
665
|
+
const rest = halves.length === 2 ? parse(halves[1]) : [];
|
|
666
|
+
const missing = 8 - head.length - rest.length;
|
|
667
|
+
if (halves.length === 2 ? missing < 1 : missing !== 0) return void 0;
|
|
668
|
+
const all = [...head, ...Array(halves.length === 2 ? missing : 0).fill("0"), ...rest];
|
|
669
|
+
if (!all.every((g) => /^[0-9a-f]{1,4}$/i.test(g))) return void 0;
|
|
670
|
+
return all.map((g) => Number.parseInt(g, 16));
|
|
671
|
+
}
|
|
672
|
+
function embeddedIpv4(hi, lo) {
|
|
673
|
+
return [hi >> 8, hi & 255, lo >> 8, lo & 255];
|
|
674
|
+
}
|
|
675
|
+
function isNonPublicIpv6(groups) {
|
|
676
|
+
const [g0 = 0, g1 = 0, g2 = 0, g3 = 0, g4 = 0, g5 = 0, g6 = 0, g7 = 0] = groups;
|
|
677
|
+
const zeroUpTo = (n) => groups.slice(0, n).every((g) => g === 0);
|
|
678
|
+
if (zeroUpTo(5) && g5 === 65535) return isNonPublicIpv4(embeddedIpv4(g6, g7));
|
|
679
|
+
if (g0 === 100 && g1 === 65435 && g2 === 0 && g3 === 0 && g4 === 0 && g5 === 0) {
|
|
680
|
+
return isNonPublicIpv4(embeddedIpv4(g6, g7));
|
|
681
|
+
}
|
|
682
|
+
if (g0 === 8194) return isNonPublicIpv4(embeddedIpv4(g1, g2));
|
|
683
|
+
return g0 === 0 || // unspecified (::), loopback (::1), IPv4-compatible and other reserved
|
|
684
|
+
g0 === 256 && g1 === 0 && g2 === 0 && g3 === 0 || // discard-only 100::/64
|
|
685
|
+
(g0 & 65024) === 64512 || // unique local fc00::/7
|
|
686
|
+
(g0 & 65472) === 65152 || // link-local fe80::/10
|
|
687
|
+
(g0 & 65472) === 65216 || // deprecated site-local fec0::/10
|
|
688
|
+
(g0 & 65280) === 65280;
|
|
689
|
+
}
|
|
690
|
+
function bareHost(hostname) {
|
|
691
|
+
const host = hostname.toLowerCase().replace(/\.$/, "");
|
|
692
|
+
return host.startsWith("[") && host.endsWith("]") ? host.slice(1, -1) : host;
|
|
693
|
+
}
|
|
694
|
+
function isLoopbackHost(hostname) {
|
|
695
|
+
const host = bareHost(hostname);
|
|
696
|
+
if (host === "localhost" || host.endsWith(".localhost")) return true;
|
|
697
|
+
const octets = ipv4Octets(host);
|
|
698
|
+
if (octets) return octets[0] === 127;
|
|
699
|
+
const groups = ipv6Groups(host);
|
|
700
|
+
if (!groups) return false;
|
|
701
|
+
if (groups.slice(0, 5).every((g) => g === 0) && groups[5] === 65535) {
|
|
702
|
+
return groups[6] !== void 0 && groups[6] >> 8 === 127;
|
|
703
|
+
}
|
|
704
|
+
return groups.slice(0, 7).every((g) => g === 0) && groups[7] === 1;
|
|
705
|
+
}
|
|
706
|
+
function isNonPublicHost(hostname) {
|
|
707
|
+
const host = bareHost(hostname);
|
|
708
|
+
if (host === "localhost" || host.endsWith(".localhost")) return true;
|
|
709
|
+
const octets = ipv4Octets(host);
|
|
710
|
+
if (octets) return isNonPublicIpv4(octets);
|
|
711
|
+
const groups = ipv6Groups(host);
|
|
712
|
+
return groups !== void 0 && isNonPublicIpv6(groups);
|
|
713
|
+
}
|
|
714
|
+
var LOOPBACK_BASE_HOSTS = /* @__PURE__ */ new Set(["localhost", "127.0.0.1", "[::1]"]);
|
|
715
|
+
function checkBaseUrl(raw, variable) {
|
|
716
|
+
const value = raw.trim();
|
|
717
|
+
let url;
|
|
718
|
+
try {
|
|
719
|
+
url = new URL(value);
|
|
720
|
+
} catch {
|
|
721
|
+
throw new ConfigError(`${variable} must be an absolute https:// URL`);
|
|
722
|
+
}
|
|
723
|
+
const loopbackHttp = url.protocol === "http:" && LOOPBACK_BASE_HOSTS.has(url.hostname);
|
|
724
|
+
if (url.protocol !== "https:" && !loopbackHttp) {
|
|
725
|
+
throw new ConfigError(
|
|
726
|
+
`${variable} must use https:// (got ${redactUrl(value)}); plain http:// is only allowed for localhost, 127.0.0.1, or [::1]`
|
|
727
|
+
);
|
|
728
|
+
}
|
|
729
|
+
return value;
|
|
730
|
+
}
|
|
731
|
+
function checkUrlSafety(url, opts) {
|
|
732
|
+
const action = opts.action ?? "download from";
|
|
733
|
+
let parsed;
|
|
734
|
+
try {
|
|
735
|
+
parsed = new URL(url);
|
|
736
|
+
} catch {
|
|
737
|
+
throw new ProviderError(`Refusing to ${action} a malformed URL`, opts.provider);
|
|
738
|
+
}
|
|
739
|
+
const host = parsed.hostname.toLowerCase().replace(/\.$/, "");
|
|
740
|
+
const localOk = opts.allowLocalhostHttp === true && isLoopbackHost(host);
|
|
741
|
+
const httpOk = parsed.protocol === "https:" || localOk && parsed.protocol === "http:";
|
|
742
|
+
if (!httpOk) {
|
|
743
|
+
throw new ProviderError(
|
|
744
|
+
`Refusing to ${action} ${host || "this URL"}: only https:// URLs are allowed`,
|
|
745
|
+
opts.provider
|
|
746
|
+
);
|
|
747
|
+
}
|
|
748
|
+
if (!localOk && isNonPublicHost(host)) {
|
|
749
|
+
throw new ProviderError(
|
|
750
|
+
`Refusing to ${action} ${host}: it points to a local or private address`,
|
|
751
|
+
opts.provider
|
|
752
|
+
);
|
|
753
|
+
}
|
|
754
|
+
const trusted = (opts.trustedHosts ?? []).some((t) => host === t || host.endsWith(`.${t}`));
|
|
755
|
+
return { host, trusted };
|
|
756
|
+
}
|
|
757
|
+
|
|
629
758
|
// src/core/async-poll.ts
|
|
630
759
|
init_errors();
|
|
631
760
|
var PollTimeoutError = class extends MultixError {
|
|
@@ -868,6 +997,7 @@ async function fetchJson(opts) {
|
|
|
868
997
|
}
|
|
869
998
|
|
|
870
999
|
// src/providers/atlascloud/prediction-output.ts
|
|
1000
|
+
init_errors();
|
|
871
1001
|
var SUCCESS_STATUSES = /* @__PURE__ */ new Set(["completed", "succeeded", "success"]);
|
|
872
1002
|
var FAILURE_STATUSES = /* @__PURE__ */ new Set(["failed", "error", "canceled", "cancelled", "timeout"]);
|
|
873
1003
|
function norm(status) {
|
|
@@ -917,6 +1047,9 @@ function collectOutputs(prediction) {
|
|
|
917
1047
|
}
|
|
918
1048
|
return out;
|
|
919
1049
|
}
|
|
1050
|
+
function checkOutputUrl(url) {
|
|
1051
|
+
checkUrlSafety(url, { provider: ATLAS_PROVIDER });
|
|
1052
|
+
}
|
|
920
1053
|
function isMediaOutput(value) {
|
|
921
1054
|
const s = value.trim();
|
|
922
1055
|
return /^https?:\/\//i.test(s) || /^data:[^,]*,/i.test(s);
|
|
@@ -941,6 +1074,7 @@ var EXT_CATEGORY = {
|
|
|
941
1074
|
flac: "audio",
|
|
942
1075
|
ogg: "audio",
|
|
943
1076
|
opus: "audio",
|
|
1077
|
+
weba: "audio",
|
|
944
1078
|
pcm: "audio",
|
|
945
1079
|
glb: "3d",
|
|
946
1080
|
gltf: "3d",
|
|
@@ -958,8 +1092,14 @@ var MIME_EXT = {
|
|
|
958
1092
|
"image/jpeg": "jpg",
|
|
959
1093
|
"image/webp": "webp",
|
|
960
1094
|
"image/gif": "gif",
|
|
1095
|
+
"image/avif": "avif",
|
|
1096
|
+
"image/bmp": "bmp",
|
|
1097
|
+
"image/svg+xml": "svg",
|
|
961
1098
|
"video/mp4": "mp4",
|
|
962
1099
|
"video/webm": "webm",
|
|
1100
|
+
"video/x-matroska": "mkv",
|
|
1101
|
+
"video/matroska": "mkv",
|
|
1102
|
+
"audio/webm": "weba",
|
|
963
1103
|
"audio/mpeg": "mp3",
|
|
964
1104
|
"audio/mp3": "mp3",
|
|
965
1105
|
"audio/wav": "wav",
|
|
@@ -976,6 +1116,7 @@ function extensionForContentType(contentType) {
|
|
|
976
1116
|
const mime = contentType?.split(";")[0]?.trim().toLowerCase();
|
|
977
1117
|
return mime ? MIME_EXT[mime] : void 0;
|
|
978
1118
|
}
|
|
1119
|
+
var KNOWN_EXTENSIONS = /* @__PURE__ */ new Set([...Object.keys(EXT_CATEGORY), "svg"]);
|
|
979
1120
|
function outputExtension(value) {
|
|
980
1121
|
const s = value.trim();
|
|
981
1122
|
const dataMatch = /^data:([^;,]+)/i.exec(s);
|
|
@@ -989,6 +1130,7 @@ function outputExtension(value) {
|
|
|
989
1130
|
const m = /\.([a-z0-9]{2,5})$/i.exec(pathname);
|
|
990
1131
|
if (!m?.[1]) return void 0;
|
|
991
1132
|
const ext = m[1].toLowerCase();
|
|
1133
|
+
if (!KNOWN_EXTENSIONS.has(ext)) return void 0;
|
|
992
1134
|
return ext === "jpeg" ? "jpg" : ext;
|
|
993
1135
|
}
|
|
994
1136
|
function outputCategory(value) {
|
|
@@ -1022,13 +1164,32 @@ function classifyOutputs(prediction) {
|
|
|
1022
1164
|
function hasNsfwFlag(prediction) {
|
|
1023
1165
|
return Array.isArray(prediction.has_nsfw_contents) && prediction.has_nsfw_contents.some(Boolean);
|
|
1024
1166
|
}
|
|
1167
|
+
function thumbnailSource(prediction) {
|
|
1168
|
+
return prediction.outputs ?? prediction.output ?? null;
|
|
1169
|
+
}
|
|
1170
|
+
var MAX_PREDICTION_ID_LENGTH = 128;
|
|
1171
|
+
var NAME_ID_LENGTH = 64;
|
|
1172
|
+
function fnv1a(value) {
|
|
1173
|
+
let hash = 2166136261;
|
|
1174
|
+
for (let i = 0; i < value.length; i++) {
|
|
1175
|
+
hash ^= value.charCodeAt(i);
|
|
1176
|
+
hash = Math.imul(hash, 16777619) >>> 0;
|
|
1177
|
+
}
|
|
1178
|
+
return hash.toString(16).padStart(8, "0");
|
|
1179
|
+
}
|
|
1025
1180
|
function outputFileName(predictionId, index, ext) {
|
|
1026
|
-
|
|
1027
|
-
|
|
1181
|
+
if (predictionId.length === 0 || predictionId.length > MAX_PREDICTION_ID_LENGTH) {
|
|
1182
|
+
throw new ProviderError(
|
|
1183
|
+
`Atlas Cloud returned an unusable prediction id (${predictionId.length === 0 ? "empty" : `${predictionId.length} characters`}).`,
|
|
1184
|
+
ATLAS_PROVIDER
|
|
1185
|
+
);
|
|
1186
|
+
}
|
|
1187
|
+
const short = predictionId.replace(/[^a-zA-Z0-9_-]/g, "").slice(0, NAME_ID_LENGTH);
|
|
1188
|
+
const id = short === predictionId ? short : short ? `${short}-${fnv1a(predictionId)}` : fnv1a(predictionId);
|
|
1189
|
+
return `atlas-${id}-${String(index + 1).padStart(2, "0")}.${ext}`;
|
|
1028
1190
|
}
|
|
1029
1191
|
|
|
1030
1192
|
// src/providers/atlascloud/client.ts
|
|
1031
|
-
var ATLAS_PROVIDER = "atlascloud";
|
|
1032
1193
|
var DEFAULT_UPLOAD_TIMEOUT_MS = 3e5;
|
|
1033
1194
|
var CATALOG_TIMEOUT_MS = 15e3;
|
|
1034
1195
|
var PREDICTION_ID_RE = /^[A-Za-z0-9_-]{1,128}$/;
|
|
@@ -1122,13 +1283,13 @@ var AtlasCloudClient = class {
|
|
|
1122
1283
|
}
|
|
1123
1284
|
/** Submit a generation job. `body` holds `model` plus the model's own fields, flattened. */
|
|
1124
1285
|
async submit(kind, body) {
|
|
1125
|
-
const
|
|
1126
|
-
if (!
|
|
1286
|
+
const path58 = SUBMIT_PATHS[kind];
|
|
1287
|
+
if (!path58) throw new ValidationError(`Unknown Atlas Cloud media type: ${String(kind)}`);
|
|
1127
1288
|
if (typeof body.model !== "string" || !body.model.trim()) {
|
|
1128
1289
|
throw new ValidationError("Atlas Cloud request body needs a non-empty `model`");
|
|
1129
1290
|
}
|
|
1130
1291
|
const env = await fetchJson({
|
|
1131
|
-
url: `${this.baseUrl}${
|
|
1292
|
+
url: `${this.baseUrl}${path58}`,
|
|
1132
1293
|
method: "POST",
|
|
1133
1294
|
headers: this.authHeaders(),
|
|
1134
1295
|
body,
|
|
@@ -1316,7 +1477,7 @@ function requireAtlasKey() {
|
|
|
1316
1477
|
}
|
|
1317
1478
|
function createAtlasClientFromEnv() {
|
|
1318
1479
|
const apiKey = requireAtlasKey();
|
|
1319
|
-
const baseUrl = resolveProviderBaseUrl(ATLAS_ROUTING);
|
|
1480
|
+
const baseUrl = checkBaseUrl(resolveProviderBaseUrl(ATLAS_ROUTING), "ATLASCLOUD_BASE_URL");
|
|
1320
1481
|
return new AtlasCloudClient({ apiKey, baseUrl });
|
|
1321
1482
|
}
|
|
1322
1483
|
async function resolveMediaRef(client, ref, logger) {
|
|
@@ -1386,8 +1547,8 @@ async function resolveMediaRefs(client, refs, logger) {
|
|
|
1386
1547
|
|
|
1387
1548
|
// src/providers/atlascloud/commands/prediction-runner.ts
|
|
1388
1549
|
init_errors();
|
|
1389
|
-
import
|
|
1390
|
-
import
|
|
1550
|
+
import fs9 from "fs";
|
|
1551
|
+
import path8 from "path";
|
|
1391
1552
|
|
|
1392
1553
|
// src/core/http-client.ts
|
|
1393
1554
|
import fs3 from "fs";
|
|
@@ -1787,14 +1948,18 @@ function detectThumbUrl(obj) {
|
|
|
1787
1948
|
}
|
|
1788
1949
|
return null;
|
|
1789
1950
|
}
|
|
1951
|
+
function resultDest(result) {
|
|
1952
|
+
const dest = result?.dest;
|
|
1953
|
+
return typeof dest === "string" ? dest : void 0;
|
|
1954
|
+
}
|
|
1790
1955
|
async function downloadThumbBeside(thumbUrl, videoPath, logger, download = downloadFile) {
|
|
1791
1956
|
const match = thumbUrl.match(IMAGE_EXT_RE);
|
|
1792
1957
|
const ext = match?.[1] ? match[1].toLowerCase().replace("jpeg", "jpg") : "jpg";
|
|
1793
1958
|
const dir = path6.dirname(videoPath);
|
|
1794
1959
|
const base = path6.basename(videoPath, path6.extname(videoPath));
|
|
1795
|
-
const
|
|
1960
|
+
const planned = path6.join(dir, `${base}_thumb.${ext}`);
|
|
1796
1961
|
try {
|
|
1797
|
-
await download(thumbUrl,
|
|
1962
|
+
const dest = resultDest(await download(thumbUrl, planned)) ?? planned;
|
|
1798
1963
|
logger?.success(`Saved thumb ${dest}`);
|
|
1799
1964
|
return dest;
|
|
1800
1965
|
} catch (e) {
|
|
@@ -1822,6 +1987,189 @@ async function maybeDownloadThumb(source, videoPath, opts = {}) {
|
|
|
1822
1987
|
return saved;
|
|
1823
1988
|
}
|
|
1824
1989
|
|
|
1990
|
+
// src/providers/atlascloud/commands/output-claims.ts
|
|
1991
|
+
init_errors();
|
|
1992
|
+
import fs8 from "fs";
|
|
1993
|
+
import os2 from "os";
|
|
1994
|
+
import path7 from "path";
|
|
1995
|
+
var PART = ".part";
|
|
1996
|
+
var PART_SUFFIX = /(?:\.\d+-\d+-[a-z0-9]+)?\.part$/i;
|
|
1997
|
+
var TEMP_TAIL = /^\.(\d+)-\d+-[a-z0-9]+\.part$/i;
|
|
1998
|
+
var MAX_UNIQUE_ATTEMPTS = 1e3;
|
|
1999
|
+
var UNREADABLE_LOCK_GRACE_MS = 1e4;
|
|
2000
|
+
var foldCase = process.platform === "win32" || process.platform === "darwin";
|
|
2001
|
+
var fold = (s) => foldCase ? s.toLowerCase() : s;
|
|
2002
|
+
function stemOf(name) {
|
|
2003
|
+
const base = name.replace(PART_SUFFIX, "");
|
|
2004
|
+
return fold(base.slice(0, base.length - path7.extname(base).length));
|
|
2005
|
+
}
|
|
2006
|
+
function listDir(dir) {
|
|
2007
|
+
try {
|
|
2008
|
+
return fs8.readdirSync(dir);
|
|
2009
|
+
} catch {
|
|
2010
|
+
return [];
|
|
2011
|
+
}
|
|
2012
|
+
}
|
|
2013
|
+
function removeQuietly2(file) {
|
|
2014
|
+
try {
|
|
2015
|
+
fs8.rmSync(file, { force: true });
|
|
2016
|
+
} catch {
|
|
2017
|
+
}
|
|
2018
|
+
}
|
|
2019
|
+
function savedOutput(file) {
|
|
2020
|
+
const dir = path7.dirname(file);
|
|
2021
|
+
const stem = stemOf(path7.basename(file));
|
|
2022
|
+
const matches = listDir(dir).filter((n) => {
|
|
2023
|
+
if (PART_SUFFIX.test(n) || stemOf(n) !== stem) return false;
|
|
2024
|
+
const st = fs8.statSync(path7.join(dir, n), { throwIfNoEntry: false });
|
|
2025
|
+
return st?.isFile() === true && st.size > 0;
|
|
2026
|
+
});
|
|
2027
|
+
const exact = matches.find((n) => n === path7.basename(file));
|
|
2028
|
+
const found = exact ?? matches.sort()[0];
|
|
2029
|
+
return found === void 0 ? void 0 : path7.join(dir, found);
|
|
2030
|
+
}
|
|
2031
|
+
function* candidateNames(file) {
|
|
2032
|
+
const dir = path7.dirname(file);
|
|
2033
|
+
const ext = path7.extname(file);
|
|
2034
|
+
const stem = path7.basename(file, ext);
|
|
2035
|
+
const taken = new Set(listDir(dir).map(stemOf));
|
|
2036
|
+
for (let n = 0; n < MAX_UNIQUE_ATTEMPTS; n++) {
|
|
2037
|
+
const name = n === 0 ? stem : `${stem}-${n}`;
|
|
2038
|
+
if (!taken.has(fold(name))) yield path7.join(dir, `${name}${ext}`);
|
|
2039
|
+
}
|
|
2040
|
+
}
|
|
2041
|
+
function readLock(lock) {
|
|
2042
|
+
try {
|
|
2043
|
+
const v = JSON.parse(fs8.readFileSync(lock, "utf8"));
|
|
2044
|
+
if (Number.isInteger(v.pid) && v.pid > 0 && typeof v.host === "string") {
|
|
2045
|
+
return v;
|
|
2046
|
+
}
|
|
2047
|
+
} catch {
|
|
2048
|
+
}
|
|
2049
|
+
return void 0;
|
|
2050
|
+
}
|
|
2051
|
+
function isProcessAlive(pid) {
|
|
2052
|
+
if (pid === process.pid) return true;
|
|
2053
|
+
try {
|
|
2054
|
+
process.kill(pid, 0);
|
|
2055
|
+
return true;
|
|
2056
|
+
} catch (e) {
|
|
2057
|
+
return e.code !== "ESRCH";
|
|
2058
|
+
}
|
|
2059
|
+
}
|
|
2060
|
+
function isStaleLock(lock) {
|
|
2061
|
+
const info = readLock(lock);
|
|
2062
|
+
if (!info) {
|
|
2063
|
+
const st = fs8.statSync(lock, { throwIfNoEntry: false });
|
|
2064
|
+
return st === void 0 || Date.now() - st.mtimeMs > UNREADABLE_LOCK_GRACE_MS;
|
|
2065
|
+
}
|
|
2066
|
+
if (info.host !== os2.hostname()) return false;
|
|
2067
|
+
return !isProcessAlive(info.pid);
|
|
2068
|
+
}
|
|
2069
|
+
var activeClaims = /* @__PURE__ */ new Set();
|
|
2070
|
+
function tryLock(dest) {
|
|
2071
|
+
const lock = `${dest}${PART}`;
|
|
2072
|
+
const content = JSON.stringify({
|
|
2073
|
+
pid: process.pid,
|
|
2074
|
+
host: os2.hostname(),
|
|
2075
|
+
startedAt: (/* @__PURE__ */ new Date()).toISOString()
|
|
2076
|
+
});
|
|
2077
|
+
for (let attempt = 0; attempt < 2; attempt++) {
|
|
2078
|
+
try {
|
|
2079
|
+
fs8.writeFileSync(lock, content, { flag: "wx" });
|
|
2080
|
+
activeClaims.add(dest);
|
|
2081
|
+
installInterruptCleanup();
|
|
2082
|
+
return true;
|
|
2083
|
+
} catch (e) {
|
|
2084
|
+
if (e.code !== "EEXIST") throw e;
|
|
2085
|
+
if (attempt > 0 || !isStaleLock(lock)) return false;
|
|
2086
|
+
removeQuietly2(lock);
|
|
2087
|
+
}
|
|
2088
|
+
}
|
|
2089
|
+
return false;
|
|
2090
|
+
}
|
|
2091
|
+
function removeOrphanedTemps(dest) {
|
|
2092
|
+
const base = path7.basename(dest);
|
|
2093
|
+
const dir = path7.dirname(dest);
|
|
2094
|
+
for (const n of listDir(dir)) {
|
|
2095
|
+
if (!n.startsWith(base)) continue;
|
|
2096
|
+
const m = TEMP_TAIL.exec(n.slice(base.length));
|
|
2097
|
+
if (m?.[1] && !isProcessAlive(Number(m[1]))) removeQuietly2(path7.join(dir, n));
|
|
2098
|
+
}
|
|
2099
|
+
}
|
|
2100
|
+
function releaseClaim(dest) {
|
|
2101
|
+
if (!activeClaims.delete(dest)) return;
|
|
2102
|
+
removeQuietly2(`${dest}${PART}`);
|
|
2103
|
+
}
|
|
2104
|
+
function cleanupActiveClaims() {
|
|
2105
|
+
for (const dest of [...activeClaims]) {
|
|
2106
|
+
const base = path7.basename(dest);
|
|
2107
|
+
const dir = path7.dirname(dest);
|
|
2108
|
+
for (const n of listDir(dir)) {
|
|
2109
|
+
if (!n.startsWith(base)) continue;
|
|
2110
|
+
const m = TEMP_TAIL.exec(n.slice(base.length));
|
|
2111
|
+
if (m?.[1] && Number(m[1]) === process.pid) removeQuietly2(path7.join(dir, n));
|
|
2112
|
+
}
|
|
2113
|
+
releaseClaim(dest);
|
|
2114
|
+
}
|
|
2115
|
+
}
|
|
2116
|
+
var INTERRUPT_EXIT_CODES = { SIGINT: 130, SIGTERM: 143 };
|
|
2117
|
+
function handleInterrupt(signal, exit = (code) => process.exit(code)) {
|
|
2118
|
+
cleanupActiveClaims();
|
|
2119
|
+
exit(INTERRUPT_EXIT_CODES[signal]);
|
|
2120
|
+
}
|
|
2121
|
+
var interruptCleanupInstalled = false;
|
|
2122
|
+
function installInterruptCleanup() {
|
|
2123
|
+
if (interruptCleanupInstalled) return;
|
|
2124
|
+
interruptCleanupInstalled = true;
|
|
2125
|
+
process.once("SIGINT", () => handleInterrupt("SIGINT"));
|
|
2126
|
+
process.once("SIGTERM", () => handleInterrupt("SIGTERM"));
|
|
2127
|
+
}
|
|
2128
|
+
function heldMessage(dest) {
|
|
2129
|
+
const info = readLock(`${dest}${PART}`);
|
|
2130
|
+
const holder = info ? ` (pid ${info.pid} on ${info.host})` : "";
|
|
2131
|
+
const advice = "Wait for it to finish, then retry. A lock left by a run that has stopped is replaced automatically.";
|
|
2132
|
+
return `${dest}${PART} is held by another multix run${holder} downloading this output. ${advice}`;
|
|
2133
|
+
}
|
|
2134
|
+
function claimOutputPath(file, policy) {
|
|
2135
|
+
if (policy === "overwrite") return file;
|
|
2136
|
+
fs8.mkdirSync(path7.dirname(file), { recursive: true });
|
|
2137
|
+
if (policy === "reuse") {
|
|
2138
|
+
if (!tryLock(file)) throw new ProviderError(heldMessage(file), ATLAS_PROVIDER);
|
|
2139
|
+
removeOrphanedTemps(file);
|
|
2140
|
+
return file;
|
|
2141
|
+
}
|
|
2142
|
+
for (const candidate of candidateNames(file)) {
|
|
2143
|
+
if (!tryLock(candidate)) continue;
|
|
2144
|
+
if (fs8.existsSync(candidate)) {
|
|
2145
|
+
releaseClaim(candidate);
|
|
2146
|
+
continue;
|
|
2147
|
+
}
|
|
2148
|
+
removeOrphanedTemps(candidate);
|
|
2149
|
+
return candidate;
|
|
2150
|
+
}
|
|
2151
|
+
throw new ProviderError(
|
|
2152
|
+
`No free file name for ${file} after ${MAX_UNIQUE_ATTEMPTS} attempts; choose another --output.`,
|
|
2153
|
+
ATLAS_PROVIDER
|
|
2154
|
+
);
|
|
2155
|
+
}
|
|
2156
|
+
async function downloadClaimed(url, choose, policy, opts) {
|
|
2157
|
+
let claimed;
|
|
2158
|
+
try {
|
|
2159
|
+
const { dest } = await downloadToFile(url, {
|
|
2160
|
+
...opts,
|
|
2161
|
+
dest: (response) => {
|
|
2162
|
+
const dest2 = claimOutputPath(choose(response), policy);
|
|
2163
|
+
if (policy !== "overwrite") claimed = dest2;
|
|
2164
|
+
return dest2;
|
|
2165
|
+
}
|
|
2166
|
+
});
|
|
2167
|
+
return dest;
|
|
2168
|
+
} finally {
|
|
2169
|
+
if (claimed) releaseClaim(claimed);
|
|
2170
|
+
}
|
|
2171
|
+
}
|
|
2172
|
+
|
|
1825
2173
|
// src/providers/atlascloud/commands/prediction-runner.ts
|
|
1826
2174
|
function addRunOptions(cmd, cfg) {
|
|
1827
2175
|
cmd.option("--async", "Submit and exit; print the prediction id (resume with `atlas status`)").option("--wait-timeout <ms>", "Max time to wait for completion", String(cfg.waitTimeoutMs)).option("--output <path>", "Output file (single output) or directory (path without extension)").option("--no-download", "Do not download media; print output URLs instead").option("--no-validate", "Skip client-side validation against the model's published schema");
|
|
@@ -1882,8 +2230,13 @@ function assertEndpoint(spec, expected, model) {
|
|
|
1882
2230
|
function capitalize(s) {
|
|
1883
2231
|
return s.charAt(0).toUpperCase() + s.slice(1);
|
|
1884
2232
|
}
|
|
1885
|
-
function resumeCommand(id) {
|
|
1886
|
-
|
|
2233
|
+
function resumeCommand(id, output) {
|
|
2234
|
+
const base = `multix atlas status ${id} --wait --download`;
|
|
2235
|
+
return output ? `${base} --output ${shellQuote(output)}` : base;
|
|
2236
|
+
}
|
|
2237
|
+
function shellQuote(value) {
|
|
2238
|
+
if (/^[\w@%+=:,./\\-]+$/.test(value)) return value;
|
|
2239
|
+
return `"${value.replace(/(["\\$`])/g, "\\$1")}"`;
|
|
1887
2240
|
}
|
|
1888
2241
|
async function executePrediction(o) {
|
|
1889
2242
|
const { client, logger, opts } = o;
|
|
@@ -1893,22 +2246,27 @@ async function executePrediction(o) {
|
|
|
1893
2246
|
const id = submitted.id;
|
|
1894
2247
|
if (opts.async) {
|
|
1895
2248
|
console.log(id);
|
|
1896
|
-
console.error(`Prediction submitted. Resume with: ${resumeCommand(id)}`);
|
|
2249
|
+
console.error(`Prediction submitted. Resume with: ${resumeCommand(id, opts.output)}`);
|
|
1897
2250
|
return { predictionId: id, files: [], texts: [] };
|
|
1898
2251
|
}
|
|
1899
2252
|
logger.info(`Prediction ${id} \u2014 waiting up to ${Math.round(timeoutMs / 1e3)}s...`);
|
|
1900
2253
|
const final = isTerminalStatus(submitted.status) ? submitted : await client.waitForPrediction(id, {
|
|
1901
2254
|
timeoutMs,
|
|
1902
2255
|
intervalMs: o.pollIntervalMs,
|
|
1903
|
-
resumeHint: `Resume with: ${resumeCommand(id)}`,
|
|
2256
|
+
resumeHint: `Resume with: ${resumeCommand(id, opts.output)}`,
|
|
1904
2257
|
onPoll: (p, n) => logger.debug(`poll ${n}: ${p.status}`)
|
|
1905
2258
|
});
|
|
1906
2259
|
assertPredictionSucceeded(final);
|
|
1907
|
-
const delivered = await deliverPrediction(final, {
|
|
2260
|
+
const delivered = await deliverPrediction(final, {
|
|
2261
|
+
logger,
|
|
2262
|
+
opts,
|
|
2263
|
+
hint: o.hint,
|
|
2264
|
+
predictionId: id
|
|
2265
|
+
});
|
|
1908
2266
|
return { predictionId: id, prediction: final, ...delivered };
|
|
1909
2267
|
}
|
|
1910
2268
|
function isDirectoryOutput(output) {
|
|
1911
|
-
return /[\\/]$/.test(output) ||
|
|
2269
|
+
return /[\\/]$/.test(output) || path8.extname(output) === "" || fs9.existsSync(output) && fs9.statSync(output).isDirectory();
|
|
1912
2270
|
}
|
|
1913
2271
|
function planOutputPaths(urls, cfg) {
|
|
1914
2272
|
const exts = urls.map((u) => {
|
|
@@ -1917,32 +2275,34 @@ function planOutputPaths(urls, cfg) {
|
|
|
1917
2275
|
});
|
|
1918
2276
|
const output = cfg.output;
|
|
1919
2277
|
if (!output || isDirectoryOutput(output)) {
|
|
1920
|
-
const dir = output ?
|
|
1921
|
-
return exts.map((ext, i) =>
|
|
2278
|
+
const dir = output ? path8.resolve(output) : cfg.outDir();
|
|
2279
|
+
return exts.map((ext, i) => path8.join(dir, outputFileName(cfg.predictionId, i, ext)));
|
|
1922
2280
|
}
|
|
1923
|
-
const abs =
|
|
2281
|
+
const abs = path8.resolve(output);
|
|
1924
2282
|
if (urls.length === 1) return [abs];
|
|
1925
|
-
const base = abs.slice(0, -
|
|
2283
|
+
const base = abs.slice(0, -path8.extname(abs).length);
|
|
1926
2284
|
return exts.map((ext, i) => `${base}-${String(i + 1).padStart(2, "0")}.${ext}`);
|
|
1927
2285
|
}
|
|
1928
2286
|
async function deliverPrediction(prediction, cfg) {
|
|
1929
2287
|
const { logger, opts } = cfg;
|
|
2288
|
+
const id = cfg.predictionId ?? prediction.id;
|
|
2289
|
+
const retry = resumeCommand(id, opts.output);
|
|
1930
2290
|
if (prediction.price !== void 0 && prediction.price !== "") {
|
|
1931
2291
|
logger.info(`Cost: $${prediction.price}`);
|
|
1932
2292
|
}
|
|
1933
2293
|
const classified = classifyOutputs(prediction);
|
|
1934
2294
|
if (classified.mode === "empty") {
|
|
1935
2295
|
throw new ProviderError(
|
|
1936
|
-
hasNsfwFlag(prediction) ? `Prediction ${
|
|
2296
|
+
hasNsfwFlag(prediction) ? `Prediction ${id} returned no output: blocked by content safety (has_nsfw_contents). Try a different prompt or input.` : `Prediction ${id} completed but returned no outputs.`,
|
|
1937
2297
|
ATLAS_PROVIDER
|
|
1938
2298
|
);
|
|
1939
2299
|
}
|
|
1940
2300
|
for (const text of classified.texts) console.log(text);
|
|
1941
2301
|
if (classified.mode === "text") {
|
|
1942
2302
|
if (opts.output) {
|
|
1943
|
-
const dest = isDirectoryOutput(opts.output) ?
|
|
1944
|
-
|
|
1945
|
-
|
|
2303
|
+
const dest = isDirectoryOutput(opts.output) ? path8.resolve(opts.output, outputFileName(id, 0, "txt")) : path8.resolve(opts.output);
|
|
2304
|
+
fs9.mkdirSync(path8.dirname(dest), { recursive: true });
|
|
2305
|
+
fs9.writeFileSync(dest, `${classified.texts.join("\n\n")}
|
|
1946
2306
|
`, "utf8");
|
|
1947
2307
|
logger.success(`Saved text output to ${dest}`);
|
|
1948
2308
|
return { files: [dest], texts: classified.texts };
|
|
@@ -1954,26 +2314,39 @@ async function deliverPrediction(prediction, cfg) {
|
|
|
1954
2314
|
return { files: [], texts: classified.texts };
|
|
1955
2315
|
}
|
|
1956
2316
|
const dests = planOutputPaths(classified.urls, {
|
|
1957
|
-
predictionId:
|
|
2317
|
+
predictionId: id,
|
|
1958
2318
|
outDir: getOutputDir,
|
|
1959
2319
|
output: opts.output,
|
|
1960
2320
|
hint: cfg.hint
|
|
1961
2321
|
});
|
|
1962
|
-
const
|
|
2322
|
+
const policy = namingPolicy(opts.output, classified.urls.length);
|
|
2323
|
+
const maxBytes = maxDownloadBytes();
|
|
1963
2324
|
const saved = [];
|
|
2325
|
+
const downloaded = [];
|
|
2326
|
+
const reused = /* @__PURE__ */ new Set();
|
|
1964
2327
|
let failed = 0;
|
|
1965
2328
|
for (let i = 0; i < classified.urls.length; i++) {
|
|
1966
2329
|
const url = classified.urls[i];
|
|
1967
2330
|
const planned = dests[i];
|
|
1968
|
-
const part = `${planned}.part`;
|
|
1969
2331
|
try {
|
|
1970
|
-
const
|
|
1971
|
-
|
|
1972
|
-
|
|
2332
|
+
const existing = policy === "reuse" ? savedOutput(planned) : void 0;
|
|
2333
|
+
if (existing) {
|
|
2334
|
+
saved.push(existing);
|
|
2335
|
+
reused.add(existing);
|
|
2336
|
+
logger.info(`Already saved: ${existing}`);
|
|
2337
|
+
continue;
|
|
2338
|
+
}
|
|
2339
|
+
const dest = await downloadClaimed(
|
|
2340
|
+
url,
|
|
2341
|
+
(response) => policy === "overwrite" || outputExtension(url) ? planned : withContentTypeExtension(planned, response.headers.get("content-type")),
|
|
2342
|
+
policy,
|
|
2343
|
+
// Inline data: outputs are decoded by fetch, never sent over the network.
|
|
2344
|
+
{ maxBytes, validateUrl: isDataUrl(url) ? void 0 : checkOutputUrl }
|
|
2345
|
+
);
|
|
1973
2346
|
saved.push(dest);
|
|
2347
|
+
downloaded.push(dest);
|
|
1974
2348
|
logger.debug(`Downloaded ${redactUrl(url)} -> ${dest}`);
|
|
1975
2349
|
} catch (e) {
|
|
1976
|
-
fs8.rmSync(part, { force: true });
|
|
1977
2350
|
failed++;
|
|
1978
2351
|
logger.warn(
|
|
1979
2352
|
`Download failed (${redactUrl(url)}): ${e instanceof Error ? e.message : String(e)}`
|
|
@@ -1982,11 +2355,11 @@ async function deliverPrediction(prediction, cfg) {
|
|
|
1982
2355
|
}
|
|
1983
2356
|
if (saved.length === 0) {
|
|
1984
2357
|
throw new ProviderError(
|
|
1985
|
-
`Prediction ${
|
|
2358
|
+
`Prediction ${id} produced ${classified.urls.length} output(s) but none downloaded. Retry with: ${retry}`,
|
|
1986
2359
|
ATLAS_PROVIDER
|
|
1987
2360
|
);
|
|
1988
2361
|
}
|
|
1989
|
-
const images =
|
|
2362
|
+
const images = downloaded.filter((f) => outputCategory(f) === "image");
|
|
1990
2363
|
const fileOutput = opts.output && !isDirectoryOutput(opts.output) ? opts.output : void 0;
|
|
1991
2364
|
const finalImages = finalizeGeneratedImages(images, {
|
|
1992
2365
|
imageFormat: opts.imageFormat,
|
|
@@ -1997,23 +2370,42 @@ async function deliverPrediction(prediction, cfg) {
|
|
|
1997
2370
|
const files = saved.map((f) => finalImages[images.indexOf(f)] ?? f);
|
|
1998
2371
|
const firstVideo = files.find((f) => outputCategory(f) === "video");
|
|
1999
2372
|
if (firstVideo) {
|
|
2000
|
-
await maybeDownloadThumb(prediction, firstVideo, {
|
|
2373
|
+
await maybeDownloadThumb(thumbnailSource(prediction), firstVideo, {
|
|
2374
|
+
skip: opts.thumb === false,
|
|
2375
|
+
logger,
|
|
2376
|
+
download: async (thumbUrl, planned) => {
|
|
2377
|
+
const existing = policy === "reuse" ? savedOutput(planned) : void 0;
|
|
2378
|
+
if (existing) return { dest: existing };
|
|
2379
|
+
const dest = await downloadClaimed(thumbUrl, () => planned, policy, {
|
|
2380
|
+
maxBytes,
|
|
2381
|
+
validateUrl: checkOutputUrl
|
|
2382
|
+
});
|
|
2383
|
+
return { dest };
|
|
2384
|
+
}
|
|
2385
|
+
});
|
|
2001
2386
|
}
|
|
2002
2387
|
console.log(`
|
|
2003
|
-
Prediction ${
|
|
2004
|
-
for (const f of files) console.log(` ${f}`);
|
|
2388
|
+
Prediction ${id}: saved ${files.length} file(s)`);
|
|
2389
|
+
for (const f of files) console.log(` ${f}${reused.has(f) ? " (already saved)" : ""}`);
|
|
2005
2390
|
if (failed > 0) {
|
|
2006
2391
|
throw new ProviderError(
|
|
2007
|
-
`Prediction ${
|
|
2392
|
+
`Prediction ${id}: downloaded ${saved.length} of ${classified.urls.length} output(s); ${failed} failed. Saved: ${files.join(", ")}. Retry with: ${retry}`,
|
|
2008
2393
|
ATLAS_PROVIDER
|
|
2009
2394
|
);
|
|
2010
2395
|
}
|
|
2011
2396
|
return { files, texts: classified.texts };
|
|
2012
2397
|
}
|
|
2398
|
+
function isDataUrl(url) {
|
|
2399
|
+
return /^data:/i.test(url.trim());
|
|
2400
|
+
}
|
|
2401
|
+
function namingPolicy(output, outputCount) {
|
|
2402
|
+
if (!output || isDirectoryOutput(output)) return "reuse";
|
|
2403
|
+
return outputCount === 1 ? "overwrite" : "unique";
|
|
2404
|
+
}
|
|
2013
2405
|
function withContentTypeExtension(planned, contentType) {
|
|
2014
2406
|
const ext = extensionForContentType(contentType);
|
|
2015
2407
|
if (!ext) return planned;
|
|
2016
|
-
const current =
|
|
2408
|
+
const current = path8.extname(planned);
|
|
2017
2409
|
return `${current ? planned.slice(0, -current.length) : planned}.${ext}`;
|
|
2018
2410
|
}
|
|
2019
2411
|
|
|
@@ -2148,12 +2540,12 @@ init_errors();
|
|
|
2148
2540
|
|
|
2149
2541
|
// src/core/json-input.ts
|
|
2150
2542
|
init_errors();
|
|
2151
|
-
import
|
|
2543
|
+
import fs10 from "fs";
|
|
2152
2544
|
function parseJsonInput(raw, flag = "--input") {
|
|
2153
2545
|
let text;
|
|
2154
2546
|
if (raw.startsWith("@")) {
|
|
2155
2547
|
try {
|
|
2156
|
-
text =
|
|
2548
|
+
text = fs10.readFileSync(raw.slice(1), "utf8");
|
|
2157
2549
|
} catch (e) {
|
|
2158
2550
|
const msg = e instanceof Error ? e.message : String(e);
|
|
2159
2551
|
throw new ValidationError(`Cannot read ${flag} file: ${msg}`);
|
|
@@ -2238,7 +2630,7 @@ function registerAtlasStatusCommand(parent) {
|
|
|
2238
2630
|
const wait = Boolean(opts.wait || opts.download);
|
|
2239
2631
|
const prediction = wait ? await client.waitForPrediction(predictionId, {
|
|
2240
2632
|
timeoutMs: parseTimeoutMs(opts.waitTimeout, STATUS_WAIT_MS),
|
|
2241
|
-
resumeHint: `Resume with: ${resumeCommand(predictionId)}`,
|
|
2633
|
+
resumeHint: `Resume with: ${resumeCommand(predictionId, opts.output)}`,
|
|
2242
2634
|
onPoll: (p, n) => logger.debug(`poll ${n}: ${p.status}`)
|
|
2243
2635
|
}) : await client.getPrediction(predictionId);
|
|
2244
2636
|
if (!opts.download) {
|
|
@@ -2247,7 +2639,11 @@ function registerAtlasStatusCommand(parent) {
|
|
|
2247
2639
|
return;
|
|
2248
2640
|
}
|
|
2249
2641
|
assertPredictionSucceeded(prediction);
|
|
2250
|
-
await deliverPrediction(prediction, {
|
|
2642
|
+
await deliverPrediction(prediction, {
|
|
2643
|
+
logger,
|
|
2644
|
+
opts: { ...opts, download: true },
|
|
2645
|
+
predictionId
|
|
2646
|
+
});
|
|
2251
2647
|
});
|
|
2252
2648
|
}
|
|
2253
2649
|
|
|
@@ -2377,11 +2773,11 @@ function registerAtlasCloudCommands(program2) {
|
|
|
2377
2773
|
}
|
|
2378
2774
|
|
|
2379
2775
|
// src/providers/byteplus/commands/generate-3d.ts
|
|
2380
|
-
import
|
|
2776
|
+
import path10 from "path";
|
|
2381
2777
|
|
|
2382
2778
|
// src/core/image-input.ts
|
|
2383
|
-
import
|
|
2384
|
-
import
|
|
2779
|
+
import fs11 from "fs/promises";
|
|
2780
|
+
import path9 from "path";
|
|
2385
2781
|
var IMAGE_MIME_BY_EXT = {
|
|
2386
2782
|
".jpg": "image/jpeg",
|
|
2387
2783
|
".jpeg": "image/jpeg",
|
|
@@ -2408,7 +2804,7 @@ function isHttpUrl(input) {
|
|
|
2408
2804
|
return /^https?:\/\//i.test(input);
|
|
2409
2805
|
}
|
|
2410
2806
|
async function readAsDataUrl(filePath, mimeTable, kindLabel, opts = {}) {
|
|
2411
|
-
const ext =
|
|
2807
|
+
const ext = path9.extname(filePath).toLowerCase();
|
|
2412
2808
|
const mime = mimeTable[ext];
|
|
2413
2809
|
if (!mime) {
|
|
2414
2810
|
throw new Error(
|
|
@@ -2417,7 +2813,7 @@ async function readAsDataUrl(filePath, mimeTable, kindLabel, opts = {}) {
|
|
|
2417
2813
|
}
|
|
2418
2814
|
const hard = opts.hardLimitBytes ?? DEFAULT_HARD_LIMIT_BYTES;
|
|
2419
2815
|
const soft = opts.softLimitBytes ?? DEFAULT_SOFT_LIMIT_BYTES;
|
|
2420
|
-
const buf = await
|
|
2816
|
+
const buf = await fs11.readFile(filePath);
|
|
2421
2817
|
if (buf.byteLength > hard) {
|
|
2422
2818
|
throw new Error(
|
|
2423
2819
|
`${kindLabel} file ${filePath} is ${(buf.byteLength / 1024 / 1024).toFixed(1)}MB \u2014 exceeds ${(hard / 1024 / 1024).toFixed(0)}MB limit. Upload to a URL instead.`
|
|
@@ -2546,9 +2942,9 @@ var BytePlusClient = class {
|
|
|
2546
2942
|
}
|
|
2547
2943
|
apiKey;
|
|
2548
2944
|
baseUrl;
|
|
2549
|
-
async request(
|
|
2945
|
+
async request(path58, opts = {}) {
|
|
2550
2946
|
const base = this.baseUrl.replace(/\/$/, "");
|
|
2551
|
-
const url = new URL(base +
|
|
2947
|
+
const url = new URL(base + path58);
|
|
2552
2948
|
if (opts.query) {
|
|
2553
2949
|
for (const [k, v] of Object.entries(opts.query)) {
|
|
2554
2950
|
if (v !== void 0 && v !== null) url.searchParams.set(k, String(v));
|
|
@@ -2566,14 +2962,14 @@ var BytePlusClient = class {
|
|
|
2566
2962
|
timeoutMs: opts.timeoutMs ?? 12e4
|
|
2567
2963
|
});
|
|
2568
2964
|
}
|
|
2569
|
-
get(
|
|
2570
|
-
return this.request(
|
|
2965
|
+
get(path58, query, logger) {
|
|
2966
|
+
return this.request(path58, { method: "GET", query, logger });
|
|
2571
2967
|
}
|
|
2572
|
-
post(
|
|
2573
|
-
return this.request(
|
|
2968
|
+
post(path58, body, logger) {
|
|
2969
|
+
return this.request(path58, { method: "POST", body, logger });
|
|
2574
2970
|
}
|
|
2575
|
-
delete(
|
|
2576
|
-
return this.request(
|
|
2971
|
+
delete(path58, logger) {
|
|
2972
|
+
return this.request(path58, { method: "DELETE", logger });
|
|
2577
2973
|
}
|
|
2578
2974
|
};
|
|
2579
2975
|
function createBytePlusClient() {
|
|
@@ -2678,7 +3074,7 @@ function registerBytePlusGenerate3DCommand(parent) {
|
|
|
2678
3074
|
process.exit(1);
|
|
2679
3075
|
}
|
|
2680
3076
|
const ext = inferModelFileExt(url);
|
|
2681
|
-
const outPath = opts.output ??
|
|
3077
|
+
const outPath = opts.output ?? path10.join(getOutputDir(), `byteplus-3d-${taskId.slice(0, 8)}-${Date.now()}.${ext}`);
|
|
2682
3078
|
await downloadModelFile(url, outPath, logger);
|
|
2683
3079
|
console.log(`
|
|
2684
3080
|
Task: ${taskId}`);
|
|
@@ -2701,8 +3097,8 @@ Task: ${taskId}`);
|
|
|
2701
3097
|
}
|
|
2702
3098
|
|
|
2703
3099
|
// src/providers/byteplus/generators/image.ts
|
|
2704
|
-
import
|
|
2705
|
-
import
|
|
3100
|
+
import fs12 from "fs";
|
|
3101
|
+
import path11 from "path";
|
|
2706
3102
|
var ASPECT_TO_SIZE = {
|
|
2707
3103
|
"1:1": "2048x2048",
|
|
2708
3104
|
"16:9": "2560x1440",
|
|
@@ -2766,14 +3162,14 @@ async function generateSeedream(client, opts) {
|
|
|
2766
3162
|
}
|
|
2767
3163
|
const ext = (() => {
|
|
2768
3164
|
try {
|
|
2769
|
-
const e =
|
|
3165
|
+
const e = path11.extname(new URL(url).pathname);
|
|
2770
3166
|
return e || ".jpg";
|
|
2771
3167
|
} catch {
|
|
2772
3168
|
return ".jpg";
|
|
2773
3169
|
}
|
|
2774
3170
|
})();
|
|
2775
|
-
const filename = opts.output && items.length === 1 ?
|
|
2776
|
-
|
|
3171
|
+
const filename = opts.output && items.length === 1 ? path11.resolve(opts.output) : path11.join(outDir, `byteplus-image-${ts}-${i + 1}${ext}`);
|
|
3172
|
+
fs12.mkdirSync(path11.dirname(filename), { recursive: true });
|
|
2777
3173
|
await downloadFile(url, filename);
|
|
2778
3174
|
saved.push({ path: filename, url, seed: item.seed });
|
|
2779
3175
|
logger?.success(`Saved ${filename}`);
|
|
@@ -2861,7 +3257,7 @@ function collect2(value, prev) {
|
|
|
2861
3257
|
}
|
|
2862
3258
|
|
|
2863
3259
|
// src/providers/byteplus/commands/image-to-video.ts
|
|
2864
|
-
import
|
|
3260
|
+
import path12 from "path";
|
|
2865
3261
|
|
|
2866
3262
|
// src/providers/byteplus/generators/video.ts
|
|
2867
3263
|
function buildFlagSuffix(opts) {
|
|
@@ -2984,7 +3380,7 @@ function registerBytePlusImageToVideoCommand(parent) {
|
|
|
2984
3380
|
logger.error(`Task succeeded but no video_url: ${JSON.stringify(final)}`);
|
|
2985
3381
|
process.exit(1);
|
|
2986
3382
|
}
|
|
2987
|
-
const outPath = opts.output ??
|
|
3383
|
+
const outPath = opts.output ?? path12.join(getOutputDir(), `byteplus-i2v-${taskId.slice(0, 8)}-${Date.now()}.mp4`);
|
|
2988
3384
|
await downloadVideo(url, outPath, logger);
|
|
2989
3385
|
await maybeDownloadThumb(final, outPath, {
|
|
2990
3386
|
skip: opts.thumb === false,
|
|
@@ -3010,7 +3406,7 @@ Task: ${taskId}`);
|
|
|
3010
3406
|
}
|
|
3011
3407
|
|
|
3012
3408
|
// src/providers/byteplus/commands/reference-to-video.ts
|
|
3013
|
-
import
|
|
3409
|
+
import path13 from "path";
|
|
3014
3410
|
|
|
3015
3411
|
// src/providers/byteplus/reference-input.ts
|
|
3016
3412
|
var MAX_TOTAL_REFS = 12;
|
|
@@ -3042,10 +3438,10 @@ function toRef(kind, resolved, role) {
|
|
|
3042
3438
|
return { kind, url: resolved.dataUrl, mime: resolved.mime, role };
|
|
3043
3439
|
}
|
|
3044
3440
|
async function resolveRefSpec(kind, spec, logger) {
|
|
3045
|
-
const { path:
|
|
3046
|
-
if (kind === "image") return toRef(kind, await resolveImageInput(
|
|
3047
|
-
if (kind === "video") return toRef(kind, await resolveVideoInput(
|
|
3048
|
-
return toRef(kind, await resolveAudioInput(
|
|
3441
|
+
const { path: path58, role } = parseRefSpec(spec);
|
|
3442
|
+
if (kind === "image") return toRef(kind, await resolveImageInput(path58, logger), role);
|
|
3443
|
+
if (kind === "video") return toRef(kind, await resolveVideoInput(path58, logger), role);
|
|
3444
|
+
return toRef(kind, await resolveAudioInput(path58, logger), role);
|
|
3049
3445
|
}
|
|
3050
3446
|
async function resolveRefSpecs(specs, logger) {
|
|
3051
3447
|
const out = [];
|
|
@@ -3142,7 +3538,7 @@ function registerBytePlusReferenceToVideoCommand(parent) {
|
|
|
3142
3538
|
logger.error(`Task succeeded but no video_url: ${JSON.stringify(final)}`);
|
|
3143
3539
|
process.exit(1);
|
|
3144
3540
|
}
|
|
3145
|
-
const outPath = opts.output ??
|
|
3541
|
+
const outPath = opts.output ?? path13.join(getOutputDir(), `byteplus-r2v-${taskId.slice(0, 8)}-${Date.now()}.mp4`);
|
|
3146
3542
|
await downloadVideo(url, outPath, logger);
|
|
3147
3543
|
await maybeDownloadThumb(final, outPath, {
|
|
3148
3544
|
skip: opts.thumb === false,
|
|
@@ -3168,7 +3564,7 @@ Task: ${taskId}`);
|
|
|
3168
3564
|
}
|
|
3169
3565
|
|
|
3170
3566
|
// src/providers/byteplus/commands/status.ts
|
|
3171
|
-
import
|
|
3567
|
+
import path14 from "path";
|
|
3172
3568
|
function registerBytePlusStatusCommand(parent) {
|
|
3173
3569
|
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(
|
|
3174
3570
|
async (taskId, opts) => {
|
|
@@ -3186,7 +3582,7 @@ function registerBytePlusStatusCommand(parent) {
|
|
|
3186
3582
|
const videoUrl2 = content?.video_url;
|
|
3187
3583
|
const fileUrl = content?.file_url;
|
|
3188
3584
|
if (videoUrl2) {
|
|
3189
|
-
const outPath = opts.output ??
|
|
3585
|
+
const outPath = opts.output ?? path14.join(getOutputDir(), `byteplus-video-${taskId.slice(0, 8)}.mp4`);
|
|
3190
3586
|
await downloadVideo(videoUrl2, outPath, logger);
|
|
3191
3587
|
await maybeDownloadThumb(status, outPath, {
|
|
3192
3588
|
skip: opts.thumb === false,
|
|
@@ -3196,7 +3592,7 @@ function registerBytePlusStatusCommand(parent) {
|
|
|
3196
3592
|
console.log(`Saved: ${outPath}`);
|
|
3197
3593
|
} else if (fileUrl) {
|
|
3198
3594
|
const ext = inferModelFileExt(fileUrl);
|
|
3199
|
-
const outPath = opts.output ??
|
|
3595
|
+
const outPath = opts.output ?? path14.join(getOutputDir(), `byteplus-3d-${taskId.slice(0, 8)}.${ext}`);
|
|
3200
3596
|
await downloadModelFile(fileUrl, outPath, logger);
|
|
3201
3597
|
console.log(`Saved: ${outPath}`);
|
|
3202
3598
|
} else {
|
|
@@ -3220,7 +3616,7 @@ function registerBytePlusStatusCommand(parent) {
|
|
|
3220
3616
|
}
|
|
3221
3617
|
|
|
3222
3618
|
// src/providers/byteplus/commands/video.ts
|
|
3223
|
-
import
|
|
3619
|
+
import path15 from "path";
|
|
3224
3620
|
function registerBytePlusVideoCommand(parent) {
|
|
3225
3621
|
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(
|
|
3226
3622
|
"--aspect-ratio <r>",
|
|
@@ -3262,7 +3658,7 @@ function registerBytePlusVideoCommand(parent) {
|
|
|
3262
3658
|
logger.error(`Task succeeded but no video_url in response: ${JSON.stringify(final)}`);
|
|
3263
3659
|
process.exit(1);
|
|
3264
3660
|
}
|
|
3265
|
-
const outPath = opts.output ??
|
|
3661
|
+
const outPath = opts.output ?? path15.join(getOutputDir(), `byteplus-video-${taskId.slice(0, 8)}-${Date.now()}.mp4`);
|
|
3266
3662
|
await downloadVideo(url, outPath, logger);
|
|
3267
3663
|
await maybeDownloadThumb(final, outPath, {
|
|
3268
3664
|
skip: opts.thumb === false,
|
|
@@ -3548,15 +3944,15 @@ async function downloadVideo2(url) {
|
|
|
3548
3944
|
|
|
3549
3945
|
// src/providers/cloudflare/media-output.ts
|
|
3550
3946
|
init_errors();
|
|
3551
|
-
import
|
|
3552
|
-
import
|
|
3947
|
+
import fs13 from "fs";
|
|
3948
|
+
import path16 from "path";
|
|
3553
3949
|
function destination(filename, output) {
|
|
3554
|
-
return output ?
|
|
3950
|
+
return output ? path16.resolve(output) : path16.join(getOutputDir(), filename);
|
|
3555
3951
|
}
|
|
3556
3952
|
function saveBytes(bytes, filename, output) {
|
|
3557
3953
|
const dest = destination(filename, output);
|
|
3558
|
-
|
|
3559
|
-
|
|
3954
|
+
fs13.mkdirSync(path16.dirname(dest), { recursive: true });
|
|
3955
|
+
fs13.writeFileSync(dest, bytes);
|
|
3560
3956
|
return dest;
|
|
3561
3957
|
}
|
|
3562
3958
|
function saveBase64Image(image, output) {
|
|
@@ -3739,8 +4135,8 @@ function registerCloudflareCommands(program2) {
|
|
|
3739
4135
|
}
|
|
3740
4136
|
|
|
3741
4137
|
// src/providers/elevenlabs/client.ts
|
|
3742
|
-
import
|
|
3743
|
-
import
|
|
4138
|
+
import fs14 from "fs";
|
|
4139
|
+
import path17 from "path";
|
|
3744
4140
|
init_errors();
|
|
3745
4141
|
|
|
3746
4142
|
// src/providers/elevenlabs/models.ts
|
|
@@ -3903,10 +4299,10 @@ async function apiPostMultipart(endpoint2, form, apiKey, opts = {}) {
|
|
|
3903
4299
|
return res.json();
|
|
3904
4300
|
}
|
|
3905
4301
|
function readFileAsBlob(filePath, fieldFilename) {
|
|
3906
|
-
const abs =
|
|
3907
|
-
if (!
|
|
3908
|
-
const buf =
|
|
3909
|
-
const name = fieldFilename ??
|
|
4302
|
+
const abs = path17.resolve(filePath);
|
|
4303
|
+
if (!fs14.existsSync(abs)) throw new ValidationError(`File not found: ${abs}`);
|
|
4304
|
+
const buf = fs14.readFileSync(abs);
|
|
4305
|
+
const name = fieldFilename ?? path17.basename(abs);
|
|
3910
4306
|
return new File([new Uint8Array(buf)], name);
|
|
3911
4307
|
}
|
|
3912
4308
|
async function pollUntil2(fetchStatus, isDone, isFailed, opts = {}) {
|
|
@@ -3930,12 +4326,12 @@ async function pollUntil2(fetchStatus, isDone, isFailed, opts = {}) {
|
|
|
3930
4326
|
function saveBytes2(opts) {
|
|
3931
4327
|
const { bytes, task, ext, outputCopy, logger } = opts;
|
|
3932
4328
|
const dir = getOutputDir();
|
|
3933
|
-
const dest =
|
|
3934
|
-
|
|
4329
|
+
const dest = path17.join(dir, `elevenlabs_${task}_${Date.now()}.${ext}`);
|
|
4330
|
+
fs14.writeFileSync(dest, bytes);
|
|
3935
4331
|
logger?.success(`Saved: ${dest} (${(bytes.length / 1024).toFixed(1)} KB)`);
|
|
3936
4332
|
if (outputCopy) {
|
|
3937
|
-
|
|
3938
|
-
|
|
4333
|
+
fs14.mkdirSync(path17.dirname(path17.resolve(outputCopy)), { recursive: true });
|
|
4334
|
+
fs14.copyFileSync(dest, outputCopy);
|
|
3939
4335
|
}
|
|
3940
4336
|
return dest;
|
|
3941
4337
|
}
|
|
@@ -3974,14 +4370,14 @@ ${models.length} models`);
|
|
|
3974
4370
|
}
|
|
3975
4371
|
|
|
3976
4372
|
// src/providers/elevenlabs/commands/align.ts
|
|
3977
|
-
import
|
|
3978
|
-
import
|
|
4373
|
+
import fs15 from "fs";
|
|
4374
|
+
import path18 from "path";
|
|
3979
4375
|
function registerAlignCommand(parent) {
|
|
3980
4376
|
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) => {
|
|
3981
4377
|
const logger = createLogger({ verbose: opts.verbose ?? false });
|
|
3982
4378
|
const apiKey = requireElevenLabsKey();
|
|
3983
4379
|
let text = opts.text;
|
|
3984
|
-
if (opts.textFile) text =
|
|
4380
|
+
if (opts.textFile) text = fs15.readFileSync(path18.resolve(opts.textFile), "utf8");
|
|
3985
4381
|
const form = new FormData();
|
|
3986
4382
|
form.append("file", readFileAsBlob(opts.input));
|
|
3987
4383
|
form.append("text", text);
|
|
@@ -3989,9 +4385,9 @@ function registerAlignCommand(parent) {
|
|
|
3989
4385
|
const data = await apiPostMultipart("forced-alignment", form, apiKey, {
|
|
3990
4386
|
timeoutMs: 6e5
|
|
3991
4387
|
});
|
|
3992
|
-
const dest = opts.output ?
|
|
3993
|
-
|
|
3994
|
-
|
|
4388
|
+
const dest = opts.output ? path18.resolve(opts.output) : path18.join(getOutputDir(), `elevenlabs_align_${Date.now()}.json`);
|
|
4389
|
+
fs15.mkdirSync(path18.dirname(dest), { recursive: true });
|
|
4390
|
+
fs15.writeFileSync(dest, JSON.stringify(data, null, 2));
|
|
3995
4391
|
logger.success(`Saved alignment: ${dest}`);
|
|
3996
4392
|
if (data.words) console.log(`Aligned ${data.words.length} words (loss=${data.loss ?? "?"})`);
|
|
3997
4393
|
});
|
|
@@ -4033,8 +4429,8 @@ function registerCloneCommand(parent) {
|
|
|
4033
4429
|
|
|
4034
4430
|
// src/providers/elevenlabs/commands/dub.ts
|
|
4035
4431
|
init_errors();
|
|
4036
|
-
import
|
|
4037
|
-
import
|
|
4432
|
+
import fs16 from "fs";
|
|
4433
|
+
import path19 from "path";
|
|
4038
4434
|
function registerDubCommand(parent) {
|
|
4039
4435
|
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) => {
|
|
4040
4436
|
const logger = createLogger({ verbose: opts.verbose ?? false });
|
|
@@ -4077,7 +4473,7 @@ function registerDubCommand(parent) {
|
|
|
4077
4473
|
);
|
|
4078
4474
|
logger.success(`Dub complete: ${start.dubbing_id}`);
|
|
4079
4475
|
if (opts.download) {
|
|
4080
|
-
const dest = opts.output ?
|
|
4476
|
+
const dest = opts.output ? path19.resolve(opts.output) : path19.join(getOutputDir(), `elevenlabs_dub_${start.dubbing_id}_${opts.targetLang}.mp4`);
|
|
4081
4477
|
const url = `${ELEVENLABS_BASE_URL}/dubbing/${start.dubbing_id}/audio/${opts.targetLang}`;
|
|
4082
4478
|
await downloadDub(url, apiKey, dest);
|
|
4083
4479
|
logger.success(`Saved: ${dest}`);
|
|
@@ -4092,7 +4488,7 @@ Dubbed file: ${dest}`);
|
|
|
4092
4488
|
const status = await apiGet(`dubbing/${dubbingId}`, apiKey);
|
|
4093
4489
|
console.log(JSON.stringify(status, null, 2));
|
|
4094
4490
|
if (opts.download) {
|
|
4095
|
-
const dest = opts.output ?
|
|
4491
|
+
const dest = opts.output ? path19.resolve(opts.output) : path19.join(getOutputDir(), `elevenlabs_dub_${dubbingId}_${opts.download}.mp4`);
|
|
4096
4492
|
const url = `${ELEVENLABS_BASE_URL}/dubbing/${dubbingId}/audio/${opts.download}`;
|
|
4097
4493
|
await downloadDub(url, apiKey, dest);
|
|
4098
4494
|
console.log(`Saved: ${dest}`);
|
|
@@ -4100,7 +4496,7 @@ Dubbed file: ${dest}`);
|
|
|
4100
4496
|
});
|
|
4101
4497
|
}
|
|
4102
4498
|
async function downloadDub(url, apiKey, dest) {
|
|
4103
|
-
|
|
4499
|
+
fs16.mkdirSync(path19.dirname(dest), { recursive: true });
|
|
4104
4500
|
const { response: res } = await fetchWithValidatedRedirects(url, {
|
|
4105
4501
|
headers: { "xi-api-key": apiKey },
|
|
4106
4502
|
signal: AbortSignal.timeout(6e5)
|
|
@@ -4109,7 +4505,7 @@ async function downloadDub(url, apiKey, dest) {
|
|
|
4109
4505
|
throw new Error(`Failed to download dub: HTTP ${res.status}`);
|
|
4110
4506
|
}
|
|
4111
4507
|
const arr = new Uint8Array(await res.arrayBuffer());
|
|
4112
|
-
|
|
4508
|
+
fs16.writeFileSync(dest, arr);
|
|
4113
4509
|
}
|
|
4114
4510
|
|
|
4115
4511
|
// src/providers/elevenlabs/commands/isolate.ts
|
|
@@ -4137,8 +4533,8 @@ Isolated audio: ${dest}`);
|
|
|
4137
4533
|
|
|
4138
4534
|
// src/providers/elevenlabs/commands/music.ts
|
|
4139
4535
|
init_errors();
|
|
4140
|
-
import
|
|
4141
|
-
import
|
|
4536
|
+
import fs17 from "fs";
|
|
4537
|
+
import path20 from "path";
|
|
4142
4538
|
function registerMusicCommand(parent) {
|
|
4143
4539
|
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(
|
|
4144
4540
|
"--format <fmt>",
|
|
@@ -4153,9 +4549,9 @@ function registerMusicCommand(parent) {
|
|
|
4153
4549
|
const payload = { model_id: opts.model };
|
|
4154
4550
|
if (opts.prompt) payload.prompt = opts.prompt;
|
|
4155
4551
|
if (opts.plan) {
|
|
4156
|
-
const planPath =
|
|
4157
|
-
if (!
|
|
4158
|
-
const planJson = JSON.parse(
|
|
4552
|
+
const planPath = path20.resolve(opts.plan);
|
|
4553
|
+
if (!fs17.existsSync(planPath)) throw new ValidationError(`Plan file not found: ${planPath}`);
|
|
4554
|
+
const planJson = JSON.parse(fs17.readFileSync(planPath, "utf8"));
|
|
4159
4555
|
payload.composition_plan = planJson;
|
|
4160
4556
|
}
|
|
4161
4557
|
if (opts.musicLengthMs) payload.music_length_ms = Number.parseInt(opts.musicLengthMs, 10);
|
|
@@ -4214,8 +4610,8 @@ Generated SFX: ${dest}`);
|
|
|
4214
4610
|
}
|
|
4215
4611
|
|
|
4216
4612
|
// src/providers/elevenlabs/commands/transcribe.ts
|
|
4217
|
-
import
|
|
4218
|
-
import
|
|
4613
|
+
import fs18 from "fs";
|
|
4614
|
+
import path21 from "path";
|
|
4219
4615
|
function registerTranscribeCommand(parent) {
|
|
4220
4616
|
parent.command("transcribe").description("Speech-to-text transcription with Scribe").requiredOption("--input <path>", "Audio or video file to transcribe").option(
|
|
4221
4617
|
"--model <id>",
|
|
@@ -4238,7 +4634,7 @@ function registerTranscribeCommand(parent) {
|
|
|
4238
4634
|
});
|
|
4239
4635
|
const ext = opts.format === "json" ? "json" : opts.format === "srt" ? "srt" : opts.format === "vtt" ? "vtt" : "txt";
|
|
4240
4636
|
const dir = getOutputDir();
|
|
4241
|
-
const dest = opts.output ?
|
|
4637
|
+
const dest = opts.output ? path21.resolve(opts.output) : path21.join(dir, `elevenlabs_transcribe_${Date.now()}.${ext}`);
|
|
4242
4638
|
let body;
|
|
4243
4639
|
switch (opts.format) {
|
|
4244
4640
|
case "json":
|
|
@@ -4253,8 +4649,8 @@ function registerTranscribeCommand(parent) {
|
|
|
4253
4649
|
default:
|
|
4254
4650
|
body = data.text ?? "";
|
|
4255
4651
|
}
|
|
4256
|
-
|
|
4257
|
-
|
|
4652
|
+
fs18.mkdirSync(path21.dirname(dest), { recursive: true });
|
|
4653
|
+
fs18.writeFileSync(dest, body);
|
|
4258
4654
|
logger.success(`Saved transcript: ${dest}`);
|
|
4259
4655
|
if (data.language_code) {
|
|
4260
4656
|
logger.info(
|
|
@@ -4500,8 +4896,8 @@ function registerElevenLabsCommands(program2) {
|
|
|
4500
4896
|
}
|
|
4501
4897
|
|
|
4502
4898
|
// src/providers/fal/commands/image.ts
|
|
4503
|
-
import
|
|
4504
|
-
import
|
|
4899
|
+
import fs20 from "fs";
|
|
4900
|
+
import path23 from "path";
|
|
4505
4901
|
|
|
4506
4902
|
// src/providers/fal/models.ts
|
|
4507
4903
|
var DEFAULT_FAL_BASE_URL = "https://queue.fal.run";
|
|
@@ -4521,8 +4917,8 @@ var FAL_DEFAULTS = {
|
|
|
4521
4917
|
|
|
4522
4918
|
// src/providers/fal/run-helpers.ts
|
|
4523
4919
|
init_errors();
|
|
4524
|
-
import
|
|
4525
|
-
import
|
|
4920
|
+
import fs19 from "fs";
|
|
4921
|
+
import path22 from "path";
|
|
4526
4922
|
init_redact();
|
|
4527
4923
|
|
|
4528
4924
|
// src/providers/fal/client.ts
|
|
@@ -4575,9 +4971,9 @@ var FalClient = class {
|
|
|
4575
4971
|
apiKey;
|
|
4576
4972
|
baseUrl;
|
|
4577
4973
|
/** `path` is appended verbatim to the base queue URL, e.g. "/fal-ai/flux/schnell". */
|
|
4578
|
-
async request(
|
|
4974
|
+
async request(path58, opts = {}) {
|
|
4579
4975
|
const base = this.baseUrl.replace(/\/$/, "");
|
|
4580
|
-
const suffix =
|
|
4976
|
+
const suffix = path58.startsWith("/") ? path58 : `/${path58}`;
|
|
4581
4977
|
return this.send(base + suffix, opts);
|
|
4582
4978
|
}
|
|
4583
4979
|
/**
|
|
@@ -4606,11 +5002,11 @@ var FalClient = class {
|
|
|
4606
5002
|
timeoutMs: opts.timeoutMs ?? 12e4
|
|
4607
5003
|
});
|
|
4608
5004
|
}
|
|
4609
|
-
get(
|
|
4610
|
-
return this.request(
|
|
5005
|
+
get(path58, logger) {
|
|
5006
|
+
return this.request(path58, { method: "GET", logger });
|
|
4611
5007
|
}
|
|
4612
|
-
post(
|
|
4613
|
-
return this.request(
|
|
5008
|
+
post(path58, body, logger) {
|
|
5009
|
+
return this.request(path58, { method: "POST", body, logger });
|
|
4614
5010
|
}
|
|
4615
5011
|
getAbsolute(url, logger) {
|
|
4616
5012
|
return this.requestAbsolute(url, { method: "GET", logger });
|
|
@@ -4759,7 +5155,7 @@ function extractMediaUrls(result) {
|
|
|
4759
5155
|
function extFromUrl(url, fallback = ".bin") {
|
|
4760
5156
|
try {
|
|
4761
5157
|
const u = new URL(url);
|
|
4762
|
-
const e =
|
|
5158
|
+
const e = path22.extname(u.pathname);
|
|
4763
5159
|
return e.length > 0 ? e : fallback;
|
|
4764
5160
|
} catch {
|
|
4765
5161
|
return fallback;
|
|
@@ -4772,13 +5168,13 @@ async function downloadResultMedia(urls, outDir, requestId, logger) {
|
|
|
4772
5168
|
if (!url) continue;
|
|
4773
5169
|
const ext = extFromUrl(url);
|
|
4774
5170
|
const filename = `fal-${requestId.slice(0, 8)}-${String(i + 1).padStart(2, "0")}${ext}`;
|
|
4775
|
-
const filepath =
|
|
5171
|
+
const filepath = path22.join(outDir, filename);
|
|
4776
5172
|
try {
|
|
4777
5173
|
await downloadFile(url, filepath);
|
|
4778
5174
|
saved.push(filepath);
|
|
4779
5175
|
logger.success(`Saved ${filepath}`);
|
|
4780
5176
|
} catch (e) {
|
|
4781
|
-
|
|
5177
|
+
fs19.rmSync(filepath, { force: true });
|
|
4782
5178
|
logger.warn(`Download failed for ${redactUrl(url)}: ${e instanceof Error ? e.message : e}`);
|
|
4783
5179
|
}
|
|
4784
5180
|
}
|
|
@@ -4823,9 +5219,9 @@ function registerFalImageCommand(parent) {
|
|
|
4823
5219
|
process.exit(1);
|
|
4824
5220
|
}
|
|
4825
5221
|
if (opts.output && saved[0]) {
|
|
4826
|
-
const dest =
|
|
4827
|
-
|
|
4828
|
-
|
|
5222
|
+
const dest = path23.resolve(opts.output);
|
|
5223
|
+
fs20.mkdirSync(path23.dirname(dest), { recursive: true });
|
|
5224
|
+
fs20.copyFileSync(saved[0], dest);
|
|
4829
5225
|
logger.success(`Copied to ${dest}`);
|
|
4830
5226
|
}
|
|
4831
5227
|
const finalImages = finalizeGeneratedImages(saved, { ...opts, logger });
|
|
@@ -4980,9 +5376,9 @@ function registerFalCommands(program2) {
|
|
|
4980
5376
|
|
|
4981
5377
|
// src/core/stdin-file.ts
|
|
4982
5378
|
init_errors();
|
|
4983
|
-
import
|
|
4984
|
-
import
|
|
4985
|
-
import
|
|
5379
|
+
import fs21 from "fs";
|
|
5380
|
+
import os3 from "os";
|
|
5381
|
+
import path24 from "path";
|
|
4986
5382
|
|
|
4987
5383
|
// src/core/limits.ts
|
|
4988
5384
|
var FILES_API_MAX_BYTES = 2e9;
|
|
@@ -5087,7 +5483,7 @@ function trackTempDir(dir) {
|
|
|
5087
5483
|
let done = false;
|
|
5088
5484
|
const remove = () => {
|
|
5089
5485
|
try {
|
|
5090
|
-
|
|
5486
|
+
fs21.rmSync(dir, { recursive: true, force: true, maxRetries: 3, retryDelay: 100 });
|
|
5091
5487
|
} catch {
|
|
5092
5488
|
}
|
|
5093
5489
|
};
|
|
@@ -5119,9 +5515,9 @@ async function stdinToTempFile(opts = {}) {
|
|
|
5119
5515
|
}
|
|
5120
5516
|
const forcedExt = opts.ext ? normalizeExt(opts.ext) : void 0;
|
|
5121
5517
|
const maxBytes = opts.maxBytes ?? MAX_STDIN_BYTES;
|
|
5122
|
-
const dir =
|
|
5518
|
+
const dir = fs21.mkdtempSync(path24.join(os3.tmpdir(), "multix-stdin-"));
|
|
5123
5519
|
const cleanup = trackTempDir(dir);
|
|
5124
|
-
const rawPath =
|
|
5520
|
+
const rawPath = path24.join(dir, "input");
|
|
5125
5521
|
const hint = setTimeout(() => {
|
|
5126
5522
|
process.stderr.write(`Waiting for data on stdin (\`${flag} -\`)... press Ctrl+C to cancel
|
|
5127
5523
|
`);
|
|
@@ -5130,7 +5526,7 @@ async function stdinToTempFile(opts = {}) {
|
|
|
5130
5526
|
let head = Buffer.alloc(0);
|
|
5131
5527
|
let size = 0;
|
|
5132
5528
|
try {
|
|
5133
|
-
const fd =
|
|
5529
|
+
const fd = fs21.openSync(rawPath, "w", 384);
|
|
5134
5530
|
try {
|
|
5135
5531
|
for await (const chunk of input) {
|
|
5136
5532
|
clearTimeout(hint);
|
|
@@ -5142,17 +5538,17 @@ async function stdinToTempFile(opts = {}) {
|
|
|
5142
5538
|
head = Buffer.concat([head, buf.subarray(0, HEAD_BYTES - head.length)]);
|
|
5143
5539
|
}
|
|
5144
5540
|
let offset = 0;
|
|
5145
|
-
while (offset < buf.length) offset +=
|
|
5541
|
+
while (offset < buf.length) offset += fs21.writeSync(fd, buf, offset);
|
|
5146
5542
|
size += buf.length;
|
|
5147
5543
|
}
|
|
5148
5544
|
} finally {
|
|
5149
5545
|
clearTimeout(hint);
|
|
5150
|
-
|
|
5546
|
+
fs21.closeSync(fd);
|
|
5151
5547
|
}
|
|
5152
5548
|
if (size === 0) throw new ValidationError(`\`${flag} -\` was given but stdin was empty`);
|
|
5153
5549
|
const detectedExt = forcedExt ?? detectExtension(head);
|
|
5154
|
-
const finalPath =
|
|
5155
|
-
|
|
5550
|
+
const finalPath = path24.join(dir, `stdin${detectedExt ?? ".bin"}`);
|
|
5551
|
+
fs21.renameSync(rawPath, finalPath);
|
|
5156
5552
|
return { path: finalPath, size, detected: detectedExt !== void 0, cleanup };
|
|
5157
5553
|
} catch (error) {
|
|
5158
5554
|
cleanup();
|
|
@@ -5170,7 +5566,7 @@ async function resolveFileInputs(files, opts = {}) {
|
|
|
5170
5566
|
throw new ValidationError(`--stdin-ext only applies when ${flag} includes \`-\``);
|
|
5171
5567
|
}
|
|
5172
5568
|
for (const f of files) {
|
|
5173
|
-
if (f !== STDIN_ARG && !
|
|
5569
|
+
if (f !== STDIN_ARG && !fs21.existsSync(f)) throw new ValidationError(`File not found: ${f}`);
|
|
5174
5570
|
}
|
|
5175
5571
|
if (stdinCount === 0) {
|
|
5176
5572
|
return { inputs: files.map((f) => ({ path: f, label: f })), cleanup: () => {
|
|
@@ -5458,12 +5854,12 @@ async function withGeminiKey(fn, opts = {}) {
|
|
|
5458
5854
|
}
|
|
5459
5855
|
|
|
5460
5856
|
// src/providers/gemini/local-files.ts
|
|
5461
|
-
import
|
|
5462
|
-
import
|
|
5857
|
+
import fs22 from "fs";
|
|
5858
|
+
import path26 from "path";
|
|
5463
5859
|
init_errors();
|
|
5464
5860
|
|
|
5465
5861
|
// src/providers/gemini/task-resolver.ts
|
|
5466
|
-
import
|
|
5862
|
+
import path25 from "path";
|
|
5467
5863
|
var MIME_MAP = {
|
|
5468
5864
|
// Audio
|
|
5469
5865
|
".mp3": "audio/mp3",
|
|
@@ -5504,7 +5900,7 @@ var MIME_MAP = {
|
|
|
5504
5900
|
".pptx": "application/vnd.openxmlformats-officedocument.presentationml.presentation"
|
|
5505
5901
|
};
|
|
5506
5902
|
function getMimeType(filePath) {
|
|
5507
|
-
const ext =
|
|
5903
|
+
const ext = path25.extname(filePath).toLowerCase();
|
|
5508
5904
|
return MIME_MAP[ext] ?? "application/octet-stream";
|
|
5509
5905
|
}
|
|
5510
5906
|
|
|
@@ -5524,10 +5920,10 @@ function minUploadBytesPerSec() {
|
|
|
5524
5920
|
}
|
|
5525
5921
|
async function uploadLocalFile(filePath, opts = {}) {
|
|
5526
5922
|
const bytesPerSec = minUploadBytesPerSec();
|
|
5527
|
-
const blob = await
|
|
5923
|
+
const blob = await fs22.openAsBlob(filePath);
|
|
5528
5924
|
const source = {
|
|
5529
5925
|
blob,
|
|
5530
|
-
displayName:
|
|
5926
|
+
displayName: path26.basename(filePath),
|
|
5531
5927
|
mimeType: getMimeType(filePath),
|
|
5532
5928
|
label: filePath
|
|
5533
5929
|
};
|
|
@@ -5535,12 +5931,12 @@ async function uploadLocalFile(filePath, opts = {}) {
|
|
|
5535
5931
|
return opts.apiKey ? upload(opts.apiKey) : withGeminiKey(upload, { logger: opts.logger });
|
|
5536
5932
|
}
|
|
5537
5933
|
function inlinePart(filePath) {
|
|
5538
|
-
const data =
|
|
5934
|
+
const data = fs22.readFileSync(filePath).toString("base64");
|
|
5539
5935
|
const mimeType = getMimeType(filePath);
|
|
5540
5936
|
return { inlineData: { mimeType, data } };
|
|
5541
5937
|
}
|
|
5542
5938
|
function shouldUseFileApi(filePath) {
|
|
5543
|
-
const stat =
|
|
5939
|
+
const stat = fs22.statSync(filePath);
|
|
5544
5940
|
return stat.size > INLINE_MAX_BYTES;
|
|
5545
5941
|
}
|
|
5546
5942
|
|
|
@@ -5591,13 +5987,13 @@ function getDefaultModel(task) {
|
|
|
5591
5987
|
}
|
|
5592
5988
|
|
|
5593
5989
|
// src/providers/gemini/result-formatter.ts
|
|
5594
|
-
import
|
|
5595
|
-
import
|
|
5990
|
+
import fs23 from "fs";
|
|
5991
|
+
import path27 from "path";
|
|
5596
5992
|
function formatResults(results, format, outputPath) {
|
|
5597
5993
|
const content = renderFormat(results, format);
|
|
5598
5994
|
if (outputPath) {
|
|
5599
|
-
|
|
5600
|
-
|
|
5995
|
+
fs23.mkdirSync(path27.dirname(path27.resolve(outputPath)), { recursive: true });
|
|
5996
|
+
fs23.writeFileSync(outputPath, content, "utf8");
|
|
5601
5997
|
} else {
|
|
5602
5998
|
process.stdout.write(content);
|
|
5603
5999
|
}
|
|
@@ -5781,8 +6177,8 @@ function registerExtractCommand(parent) {
|
|
|
5781
6177
|
init_errors();
|
|
5782
6178
|
|
|
5783
6179
|
// src/providers/gemini/generators/speech.ts
|
|
5784
|
-
import
|
|
5785
|
-
import
|
|
6180
|
+
import fs24 from "fs";
|
|
6181
|
+
import path28 from "path";
|
|
5786
6182
|
|
|
5787
6183
|
// src/providers/gemini/voices.ts
|
|
5788
6184
|
var GEMINI_TTS_VOICES = [
|
|
@@ -5954,12 +6350,12 @@ function extractWavData(wav) {
|
|
|
5954
6350
|
}
|
|
5955
6351
|
function saveSpeech(outBytes, outputFormat, output, logger) {
|
|
5956
6352
|
const outDir = getOutputDir();
|
|
5957
|
-
const dest =
|
|
5958
|
-
|
|
6353
|
+
const dest = path28.join(outDir, `gemini_speech_${Date.now()}.${outputFormat}`);
|
|
6354
|
+
fs24.writeFileSync(dest, outBytes);
|
|
5959
6355
|
logger?.success(`Saved: ${dest} (${(outBytes.length / 1024).toFixed(1)} KB)`);
|
|
5960
6356
|
if (output) {
|
|
5961
|
-
|
|
5962
|
-
|
|
6357
|
+
fs24.mkdirSync(path28.dirname(path28.resolve(output)), { recursive: true });
|
|
6358
|
+
fs24.copyFileSync(dest, output);
|
|
5963
6359
|
logger?.success(`Copied to: ${output}`);
|
|
5964
6360
|
}
|
|
5965
6361
|
return dest;
|
|
@@ -6072,24 +6468,24 @@ function collectSpeakers(value, prev) {
|
|
|
6072
6468
|
|
|
6073
6469
|
// src/providers/gemini/commands/generate-video.ts
|
|
6074
6470
|
init_errors();
|
|
6075
|
-
import
|
|
6076
|
-
import
|
|
6471
|
+
import fs26 from "fs";
|
|
6472
|
+
import path29 from "path";
|
|
6077
6473
|
|
|
6078
6474
|
// src/providers/gemini/veo.ts
|
|
6079
|
-
import
|
|
6475
|
+
import fs25 from "fs";
|
|
6080
6476
|
init_errors();
|
|
6081
6477
|
var API_HOST = "generativelanguage.googleapis.com";
|
|
6082
6478
|
var BASE2 = `https://${API_HOST}/v1beta`;
|
|
6083
6479
|
var VEO_IMAGE_TYPES = /* @__PURE__ */ new Set(["image/jpeg", "image/png", "image/webp"]);
|
|
6084
6480
|
var VEO_TIMEOUT_MS = 10 * 60 * 1e3;
|
|
6085
6481
|
function veoImageFromFile(imgPath) {
|
|
6086
|
-
if (!
|
|
6482
|
+
if (!fs25.existsSync(imgPath)) throw new ValidationError(`Image not found: ${imgPath}`);
|
|
6087
6483
|
const detected = getMimeType(imgPath);
|
|
6088
6484
|
const mimeType = detected === "application/octet-stream" ? "image/jpeg" : detected;
|
|
6089
6485
|
if (!VEO_IMAGE_TYPES.has(mimeType)) {
|
|
6090
6486
|
throw new ValidationError(`Veo accepts JPEG, PNG or WebP images: ${imgPath} (${mimeType})`);
|
|
6091
6487
|
}
|
|
6092
|
-
return { bytesBase64Encoded:
|
|
6488
|
+
return { bytesBase64Encoded: fs25.readFileSync(imgPath).toString("base64"), mimeType };
|
|
6093
6489
|
}
|
|
6094
6490
|
function veoFramesFromPaths(paths = []) {
|
|
6095
6491
|
if (paths.length > 2) {
|
|
@@ -6215,7 +6611,7 @@ function registerGenerateVideoCommand(parent) {
|
|
|
6215
6611
|
logger.debug(`Submitting video generation with ${model}...`);
|
|
6216
6612
|
logger.info("This may take 30s\u20136 minutes. Veo requires billing.");
|
|
6217
6613
|
const frames = veoFramesFromPaths(opts.referenceImages);
|
|
6218
|
-
const dest =
|
|
6614
|
+
const dest = path29.join(getOutputDir(), `veo_generated_${Date.now()}.mp4`);
|
|
6219
6615
|
const finalOp = await runVeo(
|
|
6220
6616
|
model,
|
|
6221
6617
|
{
|
|
@@ -6229,8 +6625,8 @@ function registerGenerateVideoCommand(parent) {
|
|
|
6229
6625
|
);
|
|
6230
6626
|
logger.success(`Saved video: ${dest}`);
|
|
6231
6627
|
if (opts.output) {
|
|
6232
|
-
|
|
6233
|
-
|
|
6628
|
+
fs26.mkdirSync(path29.dirname(path29.resolve(opts.output)), { recursive: true });
|
|
6629
|
+
fs26.copyFileSync(dest, opts.output);
|
|
6234
6630
|
logger.success(`Copied to: ${opts.output}`);
|
|
6235
6631
|
}
|
|
6236
6632
|
await maybeDownloadThumb(finalOp.response, dest, {
|
|
@@ -6246,8 +6642,8 @@ Generated video: ${dest}`);
|
|
|
6246
6642
|
|
|
6247
6643
|
// src/providers/gemini/commands/generate.ts
|
|
6248
6644
|
init_errors();
|
|
6249
|
-
import
|
|
6250
|
-
import
|
|
6645
|
+
import fs27 from "fs";
|
|
6646
|
+
import path30 from "path";
|
|
6251
6647
|
function registerGenerateCommand(parent) {
|
|
6252
6648
|
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(
|
|
6253
6649
|
async (opts) => {
|
|
@@ -6295,8 +6691,8 @@ function registerGenerateCommand(parent) {
|
|
|
6295
6691
|
const outDir = getOutputDir();
|
|
6296
6692
|
const ext = img.mimeType.includes("png") ? "png" : "jpg";
|
|
6297
6693
|
const fname = `gemini_generated_${Date.now()}_${i}.${ext}`;
|
|
6298
|
-
const dest =
|
|
6299
|
-
|
|
6694
|
+
const dest = path30.join(outDir, fname);
|
|
6695
|
+
fs27.writeFileSync(dest, Buffer.from(img.data, "base64"));
|
|
6300
6696
|
savedFiles.push(dest);
|
|
6301
6697
|
logger.success(`Saved: ${dest}`);
|
|
6302
6698
|
}
|
|
@@ -6311,8 +6707,8 @@ function registerGenerateCommand(parent) {
|
|
|
6311
6707
|
process.exit(1);
|
|
6312
6708
|
}
|
|
6313
6709
|
if (opts.output && savedFiles[0]) {
|
|
6314
|
-
|
|
6315
|
-
|
|
6710
|
+
fs27.mkdirSync(path30.dirname(path30.resolve(opts.output)), { recursive: true });
|
|
6711
|
+
fs27.copyFileSync(savedFiles[0], opts.output);
|
|
6316
6712
|
logger.success(`Copied to: ${opts.output}`);
|
|
6317
6713
|
}
|
|
6318
6714
|
const finalImages = finalizeGeneratedImages(savedFiles, { ...opts, logger });
|
|
@@ -6324,8 +6720,8 @@ Generated ${finalImages.length} image(s):`);
|
|
|
6324
6720
|
}
|
|
6325
6721
|
|
|
6326
6722
|
// src/providers/gemini/commands/image-to-image.ts
|
|
6327
|
-
import
|
|
6328
|
-
import
|
|
6723
|
+
import fs28 from "fs";
|
|
6724
|
+
import path31 from "path";
|
|
6329
6725
|
var SOFT_LIMIT_BYTES = 5 * 1024 * 1024;
|
|
6330
6726
|
var HARD_LIMIT_BYTES = 7 * 1024 * 1024;
|
|
6331
6727
|
function registerGeminiImageToImageCommand(parent) {
|
|
@@ -6388,14 +6784,14 @@ function registerGeminiImageToImageCommand(parent) {
|
|
|
6388
6784
|
const img = images[i];
|
|
6389
6785
|
if (!img) continue;
|
|
6390
6786
|
const ext = img.mimeType.includes("png") ? "png" : "jpg";
|
|
6391
|
-
const dest =
|
|
6392
|
-
|
|
6787
|
+
const dest = path31.join(outDir, `gemini-i2i-${ts}-${i + 1}.${ext}`);
|
|
6788
|
+
fs28.writeFileSync(dest, Buffer.from(img.data, "base64"));
|
|
6393
6789
|
saved.push(dest);
|
|
6394
6790
|
logger.success(`Saved: ${dest}`);
|
|
6395
6791
|
}
|
|
6396
6792
|
if (opts.output && saved[0]) {
|
|
6397
|
-
|
|
6398
|
-
|
|
6793
|
+
fs28.mkdirSync(path31.dirname(path31.resolve(opts.output)), { recursive: true });
|
|
6794
|
+
fs28.copyFileSync(saved[0], opts.output);
|
|
6399
6795
|
logger.success(`Copied to: ${opts.output}`);
|
|
6400
6796
|
}
|
|
6401
6797
|
const finalImages = finalizeGeneratedImages(saved, { ...opts, logger });
|
|
@@ -6423,7 +6819,7 @@ function sniffImageMime(buf, url) {
|
|
|
6423
6819
|
if (buf.length >= 12 && buf.slice(0, 4).toString() === "RIFF" && buf.slice(8, 12).toString() === "WEBP")
|
|
6424
6820
|
return "image/webp";
|
|
6425
6821
|
}
|
|
6426
|
-
const ext =
|
|
6822
|
+
const ext = path31.extname(new URL(url).pathname).toLowerCase();
|
|
6427
6823
|
if (ext === ".png") return "image/png";
|
|
6428
6824
|
if (ext === ".webp") return "image/webp";
|
|
6429
6825
|
if (ext === ".gif") return "image/gif";
|
|
@@ -6432,8 +6828,8 @@ function sniffImageMime(buf, url) {
|
|
|
6432
6828
|
|
|
6433
6829
|
// src/providers/gemini/commands/image-to-video.ts
|
|
6434
6830
|
init_errors();
|
|
6435
|
-
import
|
|
6436
|
-
import
|
|
6831
|
+
import fs29 from "fs";
|
|
6832
|
+
import path32 from "path";
|
|
6437
6833
|
function registerGeminiImageToVideoCommand(parent) {
|
|
6438
6834
|
parent.command("image-to-video <imagePath>").alias("i2v").description("Generate video from a starting image with Veo (image-to-video) [EXPERIMENTAL]").requiredOption("--prompt <text>", "Motion/scene prompt").option("--last-frame <path>", "Optional closing frame image (Veo 3.1)").option("--model <id>", "Veo model id").option("--resolution <res>", "720p|1080p", "1080p").option("--aspect-ratio <ratio>", "Aspect ratio", "16:9").option("--output <path>", "Copy generated video to this path").option("--no-thumb", "Skip downloading the thumbnail if the API returns one").option("-v, --verbose", "Verbose logging").action(
|
|
6439
6835
|
async (imagePath, opts) => {
|
|
@@ -6453,7 +6849,7 @@ function registerGeminiImageToVideoCommand(parent) {
|
|
|
6453
6849
|
logger.info(
|
|
6454
6850
|
"Submitting image-to-video. This may take 30s\u20136 minutes. Veo requires billing."
|
|
6455
6851
|
);
|
|
6456
|
-
const dest =
|
|
6852
|
+
const dest = path32.join(getOutputDir(), `veo_i2v_${Date.now()}.mp4`);
|
|
6457
6853
|
const finalOp = await runVeo(
|
|
6458
6854
|
model,
|
|
6459
6855
|
{
|
|
@@ -6468,8 +6864,8 @@ function registerGeminiImageToVideoCommand(parent) {
|
|
|
6468
6864
|
);
|
|
6469
6865
|
logger.success(`Saved video: ${dest}`);
|
|
6470
6866
|
if (opts.output) {
|
|
6471
|
-
|
|
6472
|
-
|
|
6867
|
+
fs29.mkdirSync(path32.dirname(path32.resolve(opts.output)), { recursive: true });
|
|
6868
|
+
fs29.copyFileSync(dest, opts.output);
|
|
6473
6869
|
logger.success(`Copied to: ${opts.output}`);
|
|
6474
6870
|
}
|
|
6475
6871
|
await maybeDownloadThumb(finalOp.response, dest, {
|
|
@@ -6674,8 +7070,8 @@ var HEYGEN_OPERATIONS = {
|
|
|
6674
7070
|
};
|
|
6675
7071
|
|
|
6676
7072
|
// src/providers/heygen/node-helpers.ts
|
|
6677
|
-
import
|
|
6678
|
-
import
|
|
7073
|
+
import fs30 from "fs";
|
|
7074
|
+
import path33 from "path";
|
|
6679
7075
|
init_errors();
|
|
6680
7076
|
init_redact();
|
|
6681
7077
|
|
|
@@ -6788,12 +7184,12 @@ function describePayload(data) {
|
|
|
6788
7184
|
);
|
|
6789
7185
|
return (text ?? String(data)).slice(0, 500);
|
|
6790
7186
|
}
|
|
6791
|
-
function requireJobId(data, field,
|
|
7187
|
+
function requireJobId(data, field, path58) {
|
|
6792
7188
|
const value = data?.[field];
|
|
6793
7189
|
const ok2 = Array.isArray(value) ? value.length > 0 && value.every((v) => typeof v === "string" && v.length > 0) : typeof value === "string" && value.length > 0;
|
|
6794
7190
|
if (!ok2) {
|
|
6795
7191
|
throw new ProviderError(
|
|
6796
|
-
`HeyGen ${
|
|
7192
|
+
`HeyGen ${path58} returned no ${field}; a job may still have been created, so check the HeyGen dashboard. Response: ${describePayload(data)}`,
|
|
6797
7193
|
"heygen"
|
|
6798
7194
|
);
|
|
6799
7195
|
}
|
|
@@ -6818,8 +7214,8 @@ var HeygenClient = class {
|
|
|
6818
7214
|
this.newIdempotencyKey = opts.newIdempotencyKey ?? (() => crypto.randomUUID());
|
|
6819
7215
|
}
|
|
6820
7216
|
/** Build an absolute URL for a v3 path plus query parameters. */
|
|
6821
|
-
url(
|
|
6822
|
-
const suffix =
|
|
7217
|
+
url(path58, query) {
|
|
7218
|
+
const suffix = path58.startsWith("/") ? path58 : `/${path58}`;
|
|
6823
7219
|
const params = new URLSearchParams();
|
|
6824
7220
|
for (const [k, v] of Object.entries(query ?? {})) {
|
|
6825
7221
|
if (v !== void 0 && v !== "") params.set(k, String(v));
|
|
@@ -6841,10 +7237,10 @@ var HeygenClient = class {
|
|
|
6841
7237
|
* `request_in_progress` are retried only when an Idempotency-Key makes the
|
|
6842
7238
|
* replay safe. GET requests are left to the caller's polling loop.
|
|
6843
7239
|
*/
|
|
6844
|
-
async send(
|
|
7240
|
+
async send(path58, opts = {}) {
|
|
6845
7241
|
const method = opts.method ?? "GET";
|
|
6846
7242
|
const key = typeof opts.idempotencyKey === "string" ? opts.idempotencyKey : opts.idempotencyKey ? this.newIdempotencyKey() : void 0;
|
|
6847
|
-
const url = this.url(
|
|
7243
|
+
const url = this.url(path58, opts.query);
|
|
6848
7244
|
for (let attempt = 1; ; attempt++) {
|
|
6849
7245
|
try {
|
|
6850
7246
|
return await fetchJson({
|
|
@@ -6878,18 +7274,18 @@ var HeygenClient = class {
|
|
|
6878
7274
|
return void 0;
|
|
6879
7275
|
}
|
|
6880
7276
|
/** Send and unwrap `{ data }`. A 2xx body carrying `error` is treated as a failure. */
|
|
6881
|
-
async request(
|
|
6882
|
-
const json = await this.send(
|
|
6883
|
-
return unwrapData(json,
|
|
7277
|
+
async request(path58, opts = {}) {
|
|
7278
|
+
const json = await this.send(path58, opts);
|
|
7279
|
+
return unwrapData(json, path58);
|
|
6884
7280
|
}
|
|
6885
|
-
async page(
|
|
6886
|
-
const json = await this.send(
|
|
7281
|
+
async page(path58, query) {
|
|
7282
|
+
const json = await this.send(path58, {
|
|
6887
7283
|
query
|
|
6888
7284
|
});
|
|
6889
|
-
const items = unwrapData(json,
|
|
7285
|
+
const items = unwrapData(json, path58);
|
|
6890
7286
|
if (!Array.isArray(items)) {
|
|
6891
7287
|
throw new ProviderError(
|
|
6892
|
-
`Unexpected HeyGen response from ${
|
|
7288
|
+
`Unexpected HeyGen response from ${path58}: data is not a list`,
|
|
6893
7289
|
"heygen"
|
|
6894
7290
|
);
|
|
6895
7291
|
}
|
|
@@ -6907,9 +7303,9 @@ var HeygenClient = class {
|
|
|
6907
7303
|
// ── Video Agent ───────────────────────────────────────────────────────
|
|
6908
7304
|
/** POST /v3/video-agents. No Idempotency-Key: the endpoint does not document one. */
|
|
6909
7305
|
async createVideoAgent(body) {
|
|
6910
|
-
const
|
|
6911
|
-
const data = await this.request(
|
|
6912
|
-
return requireJobId(data, "session_id",
|
|
7306
|
+
const path58 = "/v3/video-agents";
|
|
7307
|
+
const data = await this.request(path58, { method: "POST", body });
|
|
7308
|
+
return requireJobId(data, "session_id", path58);
|
|
6913
7309
|
}
|
|
6914
7310
|
async getVideoAgentSession(sessionId, opts = {}) {
|
|
6915
7311
|
return this.request(`/v3/video-agents/${seg(sessionId, "session id")}`, opts);
|
|
@@ -6919,36 +7315,36 @@ var HeygenClient = class {
|
|
|
6919
7315
|
}
|
|
6920
7316
|
// ── Videos ────────────────────────────────────────────────────────────
|
|
6921
7317
|
async createVideo(body, idempotencyKey = true) {
|
|
6922
|
-
const
|
|
6923
|
-
const data = await this.request(
|
|
7318
|
+
const path58 = "/v3/videos";
|
|
7319
|
+
const data = await this.request(path58, {
|
|
6924
7320
|
method: "POST",
|
|
6925
7321
|
body,
|
|
6926
7322
|
idempotencyKey
|
|
6927
7323
|
});
|
|
6928
|
-
return requireJobId(data, "video_id",
|
|
7324
|
+
return requireJobId(data, "video_id", path58);
|
|
6929
7325
|
}
|
|
6930
7326
|
async getVideo(videoId, opts = {}) {
|
|
6931
7327
|
return this.request(`/v3/videos/${seg(videoId, "video id")}`, opts);
|
|
6932
7328
|
}
|
|
6933
7329
|
/** POST /v3/templates/{id}. Returns the created video's `VideoDetail`. */
|
|
6934
7330
|
async generateFromTemplate(templateId, body, idempotencyKey = true) {
|
|
6935
|
-
const
|
|
6936
|
-
const data = await this.request(
|
|
7331
|
+
const path58 = `/v3/templates/${seg(templateId, "template id")}`;
|
|
7332
|
+
const data = await this.request(path58, {
|
|
6937
7333
|
method: "POST",
|
|
6938
7334
|
body,
|
|
6939
7335
|
idempotencyKey
|
|
6940
7336
|
});
|
|
6941
|
-
return requireJobId(data, "id",
|
|
7337
|
+
return requireJobId(data, "id", path58);
|
|
6942
7338
|
}
|
|
6943
7339
|
// ── HeyGen Video model ────────────────────────────────────────────────
|
|
6944
7340
|
async createModelVideo(body, idempotencyKey = true) {
|
|
6945
|
-
const
|
|
6946
|
-
const data = await this.request(
|
|
7341
|
+
const path58 = "/v3/models/videos";
|
|
7342
|
+
const data = await this.request(path58, {
|
|
6947
7343
|
method: "POST",
|
|
6948
7344
|
body,
|
|
6949
7345
|
idempotencyKey
|
|
6950
7346
|
});
|
|
6951
|
-
return requireJobId(data, "video_id",
|
|
7347
|
+
return requireJobId(data, "video_id", path58);
|
|
6952
7348
|
}
|
|
6953
7349
|
async getModelVideo(videoId, opts = {}) {
|
|
6954
7350
|
return this.request(`/v3/models/videos/${seg(videoId, "video id")}`, opts);
|
|
@@ -6970,13 +7366,13 @@ var HeygenClient = class {
|
|
|
6970
7366
|
}
|
|
6971
7367
|
// ── Translation & lipsync ─────────────────────────────────────────────
|
|
6972
7368
|
async createVideoTranslation(body, idempotencyKey = true) {
|
|
6973
|
-
const
|
|
6974
|
-
const data = await this.request(
|
|
7369
|
+
const path58 = "/v3/video-translations";
|
|
7370
|
+
const data = await this.request(path58, {
|
|
6975
7371
|
method: "POST",
|
|
6976
7372
|
body,
|
|
6977
7373
|
idempotencyKey
|
|
6978
7374
|
});
|
|
6979
|
-
return requireJobId(data, "video_translation_ids",
|
|
7375
|
+
return requireJobId(data, "video_translation_ids", path58);
|
|
6980
7376
|
}
|
|
6981
7377
|
async getVideoTranslation(id, opts = {}) {
|
|
6982
7378
|
return this.request(`/v3/video-translations/${seg(id, "video translation id")}`, opts);
|
|
@@ -6989,13 +7385,13 @@ var HeygenClient = class {
|
|
|
6989
7385
|
return data.languages.filter((l) => typeof l === "string");
|
|
6990
7386
|
}
|
|
6991
7387
|
async createLipsync(body, idempotencyKey = true) {
|
|
6992
|
-
const
|
|
6993
|
-
const data = await this.request(
|
|
7388
|
+
const path58 = "/v3/lipsyncs";
|
|
7389
|
+
const data = await this.request(path58, {
|
|
6994
7390
|
method: "POST",
|
|
6995
7391
|
body,
|
|
6996
7392
|
idempotencyKey
|
|
6997
7393
|
});
|
|
6998
|
-
return requireJobId(data, "lipsync_id",
|
|
7394
|
+
return requireJobId(data, "lipsync_id", path58);
|
|
6999
7395
|
}
|
|
7000
7396
|
async getLipsync(id, opts = {}) {
|
|
7001
7397
|
return this.request(`/v3/lipsyncs/${seg(id, "lipsync id")}`, opts);
|
|
@@ -7060,9 +7456,9 @@ var HeygenClient = class {
|
|
|
7060
7456
|
}
|
|
7061
7457
|
}
|
|
7062
7458
|
};
|
|
7063
|
-
function unwrapData(json,
|
|
7459
|
+
function unwrapData(json, path58) {
|
|
7064
7460
|
if (json === null || typeof json !== "object") {
|
|
7065
|
-
throw new ProviderError(`Unexpected HeyGen response from ${
|
|
7461
|
+
throw new ProviderError(`Unexpected HeyGen response from ${path58}: not a JSON object`, "heygen");
|
|
7066
7462
|
}
|
|
7067
7463
|
const record = json;
|
|
7068
7464
|
const error = record.error;
|
|
@@ -7074,7 +7470,7 @@ function unwrapData(json, path57) {
|
|
|
7074
7470
|
);
|
|
7075
7471
|
}
|
|
7076
7472
|
if (!("data" in record) || record.data === null || record.data === void 0) {
|
|
7077
|
-
throw new ProviderError(`Unexpected HeyGen response from ${
|
|
7473
|
+
throw new ProviderError(`Unexpected HeyGen response from ${path58}: missing data`, "heygen");
|
|
7078
7474
|
}
|
|
7079
7475
|
return record.data;
|
|
7080
7476
|
}
|
|
@@ -7287,7 +7683,6 @@ function resumeCommand2(kind, id, opts = {}) {
|
|
|
7287
7683
|
|
|
7288
7684
|
// src/providers/heygen/request-builders.ts
|
|
7289
7685
|
init_errors();
|
|
7290
|
-
init_redact();
|
|
7291
7686
|
function oneOf(value, allowed, flag) {
|
|
7292
7687
|
if (value === void 0) return void 0;
|
|
7293
7688
|
if (!allowed.includes(value)) {
|
|
@@ -7316,23 +7711,6 @@ function isHttpsUrl(url) {
|
|
|
7316
7711
|
return false;
|
|
7317
7712
|
}
|
|
7318
7713
|
}
|
|
7319
|
-
var LOOPBACK_HOSTS = /* @__PURE__ */ new Set(["localhost", "127.0.0.1", "[::1]"]);
|
|
7320
|
-
function checkBaseUrl(raw, variable) {
|
|
7321
|
-
const value = raw.trim();
|
|
7322
|
-
let url;
|
|
7323
|
-
try {
|
|
7324
|
-
url = new URL(value);
|
|
7325
|
-
} catch {
|
|
7326
|
-
throw new ConfigError(`${variable} must be an absolute https:// URL`);
|
|
7327
|
-
}
|
|
7328
|
-
const loopbackHttp = url.protocol === "http:" && LOOPBACK_HOSTS.has(url.hostname);
|
|
7329
|
-
if (url.protocol !== "https:" && !loopbackHttp) {
|
|
7330
|
-
throw new ConfigError(
|
|
7331
|
-
`${variable} must use https:// (got ${redactUrl(value)}); plain http:// is only allowed for localhost, 127.0.0.1, or [::1]`
|
|
7332
|
-
);
|
|
7333
|
-
}
|
|
7334
|
-
return value;
|
|
7335
|
-
}
|
|
7336
7714
|
function classifyMediaRef(value) {
|
|
7337
7715
|
const v = value.trim();
|
|
7338
7716
|
if (!v) throw new ValidationError("Media reference must not be empty");
|
|
@@ -7603,17 +7981,17 @@ function createHeygenClientFromEnv() {
|
|
|
7603
7981
|
return new HeygenClient({ apiKey, baseUrl: checkBaseUrl(baseUrl, "HEYGEN_BASE_URL") });
|
|
7604
7982
|
}
|
|
7605
7983
|
async function uploadLocalFile2(client, filePath, logger) {
|
|
7606
|
-
const abs =
|
|
7984
|
+
const abs = path33.resolve(filePath);
|
|
7607
7985
|
let stat;
|
|
7608
7986
|
try {
|
|
7609
|
-
stat =
|
|
7987
|
+
stat = fs30.statSync(abs);
|
|
7610
7988
|
} catch {
|
|
7611
7989
|
throw new ValidationError(
|
|
7612
7990
|
`File not found: ${filePath}. Use an https:// URL, asset:<asset_id>, or an existing file.`
|
|
7613
7991
|
);
|
|
7614
7992
|
}
|
|
7615
7993
|
if (!stat.isFile()) throw new ValidationError(`Not a file: ${filePath}`);
|
|
7616
|
-
const ext =
|
|
7994
|
+
const ext = path33.extname(abs).slice(1).toLowerCase();
|
|
7617
7995
|
const mimeType = ASSET_MIME_BY_EXT[ext];
|
|
7618
7996
|
if (!mimeType) {
|
|
7619
7997
|
throw new ValidationError(
|
|
@@ -7625,10 +8003,10 @@ async function uploadLocalFile2(client, filePath, logger) {
|
|
|
7625
8003
|
`${filePath} is ${(stat.size / 1024 / 1024).toFixed(1)} MB; HeyGen asset upload accepts at most 32 MB. Host it at a public https:// URL instead.`
|
|
7626
8004
|
);
|
|
7627
8005
|
}
|
|
7628
|
-
logger.info(`Uploading ${
|
|
8006
|
+
logger.info(`Uploading ${path33.basename(abs)} to HeyGen assets...`);
|
|
7629
8007
|
const res = await client.uploadAsset({
|
|
7630
|
-
data: new Uint8Array(
|
|
7631
|
-
filename:
|
|
8008
|
+
data: new Uint8Array(fs30.readFileSync(abs)),
|
|
8009
|
+
filename: path33.basename(abs),
|
|
7632
8010
|
mimeType
|
|
7633
8011
|
});
|
|
7634
8012
|
logger.debug(`Uploaded asset ${res.asset_id} (${res.mime_type}, ${res.size_bytes} bytes)`);
|
|
@@ -7672,7 +8050,7 @@ var MEDIA_EXTS = /* @__PURE__ */ new Set([...VIDEO_EXTS, ...AUDIO_EXTS]);
|
|
|
7672
8050
|
var OUTPUT_EXTS = /* @__PURE__ */ new Set([...MEDIA_EXTS, "srt", "vtt"]);
|
|
7673
8051
|
function inferExtension(url, fallback) {
|
|
7674
8052
|
try {
|
|
7675
|
-
const ext =
|
|
8053
|
+
const ext = path33.extname(new URL(url).pathname).slice(1).toLowerCase();
|
|
7676
8054
|
return OUTPUT_EXTS.has(ext) ? ext : fallback;
|
|
7677
8055
|
} catch {
|
|
7678
8056
|
return fallback;
|
|
@@ -7710,11 +8088,11 @@ async function downloadOutput(url, dest, resumeHint) {
|
|
|
7710
8088
|
}
|
|
7711
8089
|
function defaultOutputPath(state, ext) {
|
|
7712
8090
|
const lang = state.outputLanguage ? `-${safeSlug(state.outputLanguage)}` : "";
|
|
7713
|
-
return
|
|
8091
|
+
return path33.join(getOutputDir(), `heygen-${state.kind}${lang}-${safeSlug(state.id)}.${ext}`);
|
|
7714
8092
|
}
|
|
7715
8093
|
function statOrUndefined(p) {
|
|
7716
8094
|
try {
|
|
7717
|
-
return
|
|
8095
|
+
return fs30.statSync(p);
|
|
7718
8096
|
} catch {
|
|
7719
8097
|
return void 0;
|
|
7720
8098
|
}
|
|
@@ -7723,29 +8101,29 @@ function isDirectoryOutput2(output, target) {
|
|
|
7723
8101
|
if (/[\\/]$/.test(output)) return true;
|
|
7724
8102
|
const stat = statOrUndefined(target);
|
|
7725
8103
|
if (stat) return stat.isDirectory();
|
|
7726
|
-
return
|
|
8104
|
+
return path33.extname(target) === "";
|
|
7727
8105
|
}
|
|
7728
8106
|
function withMediaExt(file, ext) {
|
|
7729
|
-
const given =
|
|
8107
|
+
const given = path33.extname(file).slice(1).toLowerCase();
|
|
7730
8108
|
const mismatch = VIDEO_EXTS.has(given) && AUDIO_EXTS.has(ext) || AUDIO_EXTS.has(given) && VIDEO_EXTS.has(ext);
|
|
7731
8109
|
return mismatch ? `${file.slice(0, -(given.length + 1))}.${ext}` : file;
|
|
7732
8110
|
}
|
|
7733
8111
|
function outputPathFor(state, ext, output, count) {
|
|
7734
8112
|
if (!output) return defaultOutputPath(state, ext);
|
|
7735
|
-
const target =
|
|
7736
|
-
const givenExt =
|
|
8113
|
+
const target = path33.resolve(output);
|
|
8114
|
+
const givenExt = path33.extname(target);
|
|
7737
8115
|
const isTemplate = count > 1 && MEDIA_EXTS.has(givenExt.slice(1).toLowerCase());
|
|
7738
8116
|
if (isDirectoryOutput2(output, target) || count > 1 && !isTemplate) {
|
|
7739
|
-
|
|
7740
|
-
return
|
|
8117
|
+
fs30.mkdirSync(target, { recursive: true });
|
|
8118
|
+
return path33.join(target, path33.basename(defaultOutputPath(state, ext)));
|
|
7741
8119
|
}
|
|
7742
8120
|
if (!isTemplate) return withMediaExt(target, ext);
|
|
7743
|
-
const base =
|
|
8121
|
+
const base = path33.basename(target, givenExt);
|
|
7744
8122
|
const suffix = safeSlug(state.outputLanguage ?? state.id);
|
|
7745
|
-
return withMediaExt(
|
|
8123
|
+
return withMediaExt(path33.join(path33.dirname(target), `${base}-${suffix}${givenExt}`), ext);
|
|
7746
8124
|
}
|
|
7747
8125
|
function claimPath(dest, state, used) {
|
|
7748
|
-
const ext =
|
|
8126
|
+
const ext = path33.extname(dest);
|
|
7749
8127
|
const stem = dest.slice(0, dest.length - ext.length);
|
|
7750
8128
|
let candidate = dest;
|
|
7751
8129
|
for (let n = 1; used.has(candidate.toLowerCase()); n++) {
|
|
@@ -7756,7 +8134,7 @@ function claimPath(dest, state, used) {
|
|
|
7756
8134
|
return candidate;
|
|
7757
8135
|
}
|
|
7758
8136
|
function captionPathFor(dest, capExt) {
|
|
7759
|
-
const stem =
|
|
8137
|
+
const stem = path33.join(path33.dirname(dest), path33.basename(dest, path33.extname(dest)));
|
|
7760
8138
|
const capDest = `${stem}.${capExt}`;
|
|
7761
8139
|
return capDest.toLowerCase() === dest.toLowerCase() ? `${stem}.captions.${capExt}` : capDest;
|
|
7762
8140
|
}
|
|
@@ -8134,7 +8512,7 @@ function registerHeygenModelVideoCommand(parent) {
|
|
|
8134
8512
|
|
|
8135
8513
|
// src/providers/heygen/commands/run.ts
|
|
8136
8514
|
init_errors();
|
|
8137
|
-
import
|
|
8515
|
+
import path34 from "path";
|
|
8138
8516
|
function extractJobIds(operation, data) {
|
|
8139
8517
|
const op = HEYGEN_OPERATIONS[operation];
|
|
8140
8518
|
if (!op) return [];
|
|
@@ -8172,7 +8550,7 @@ function registerHeygenRunCommand(parent) {
|
|
|
8172
8550
|
return;
|
|
8173
8551
|
}
|
|
8174
8552
|
const ext = inferExtension(audioUrl, "mp3");
|
|
8175
|
-
const dest = opts.output ?
|
|
8553
|
+
const dest = opts.output ? path34.resolve(opts.output) : path34.join(getOutputDir(), `heygen-speech-${Date.now()}.${ext}`);
|
|
8176
8554
|
await downloadOutput(audioUrl, dest, "Re-run the command to generate a fresh URL.");
|
|
8177
8555
|
console.log(`Saved: ${dest}`);
|
|
8178
8556
|
return;
|
|
@@ -8302,7 +8680,7 @@ function registerHeygenTranslateCommand(parent) {
|
|
|
8302
8680
|
}
|
|
8303
8681
|
|
|
8304
8682
|
// src/providers/heygen/commands/tts.ts
|
|
8305
|
-
import
|
|
8683
|
+
import path35 from "path";
|
|
8306
8684
|
init_redact();
|
|
8307
8685
|
function registerHeygenTtsCommand(parent) {
|
|
8308
8686
|
parent.command("tts <text>").description("Text-to-speech with a HeyGen catalog voice (1-5000 characters)").requiredOption("--voice <voice-id>", "Voice ID from `heygen voices`").option("--ssml", "Treat the text as SSML").option("--speed <n>", "Speed multiplier 0.5-2.0").option("--language <code>", "Base language code, e.g. en (auto-detected when omitted)").option("--locale <tag>", "Locale tag for Starfish voices, e.g. zh-HK").option("--engine <e>", "starfish | orca | elevenlabs (defaults to the voice's engine)").option("--output <path>", "Output audio path").option("--no-download", "Print the audio URL instead of downloading").option("--json", "Print a JSON summary").option("-v, --verbose", "Verbose logging").action(async (text, opts) => {
|
|
@@ -8333,7 +8711,7 @@ function registerHeygenTtsCommand(parent) {
|
|
|
8333
8711
|
}
|
|
8334
8712
|
const ext = inferExtension(res.audio_url, "mp3");
|
|
8335
8713
|
const name = `heygen-tts-${res.request_id ?? Date.now()}.${ext}`.replace(/[^\w.-]+/g, "-");
|
|
8336
|
-
const dest = opts.output ?
|
|
8714
|
+
const dest = opts.output ? path35.resolve(opts.output) : path35.join(getOutputDir(), name);
|
|
8337
8715
|
await downloadOutput(res.audio_url, dest, "Re-run the command to generate a fresh URL.");
|
|
8338
8716
|
if (opts.json) console.log(JSON.stringify({ ...summary, file: dest }, null, 2));
|
|
8339
8717
|
else console.log(`Saved: ${dest} (${res.duration}s, ${res.engine})`);
|
|
@@ -8452,8 +8830,8 @@ function registerHeygenCommands(program2) {
|
|
|
8452
8830
|
}
|
|
8453
8831
|
|
|
8454
8832
|
// src/providers/leonardo/commands/generate.ts
|
|
8455
|
-
import
|
|
8456
|
-
import
|
|
8833
|
+
import fs31 from "fs";
|
|
8834
|
+
import path36 from "path";
|
|
8457
8835
|
|
|
8458
8836
|
// src/providers/leonardo/client.ts
|
|
8459
8837
|
init_errors();
|
|
@@ -8531,11 +8909,11 @@ var LeonardoClient = class {
|
|
|
8531
8909
|
}
|
|
8532
8910
|
apiKey;
|
|
8533
8911
|
baseUrl;
|
|
8534
|
-
async request(
|
|
8912
|
+
async request(path58, opts = {}) {
|
|
8535
8913
|
const base = this.baseUrl.replace(/\/$/, "");
|
|
8536
|
-
const v2Override =
|
|
8914
|
+
const v2Override = path58.startsWith("v2:");
|
|
8537
8915
|
const finalBase = v2Override ? base.replace(/\/v1$/, "/v2") : base;
|
|
8538
|
-
const finalPath = v2Override ?
|
|
8916
|
+
const finalPath = v2Override ? path58.slice(3) : path58;
|
|
8539
8917
|
const url = new URL(finalBase + finalPath);
|
|
8540
8918
|
if (opts.query) {
|
|
8541
8919
|
for (const [k, v] of Object.entries(opts.query)) {
|
|
@@ -8554,14 +8932,14 @@ var LeonardoClient = class {
|
|
|
8554
8932
|
timeoutMs: opts.timeoutMs ?? 12e4
|
|
8555
8933
|
});
|
|
8556
8934
|
}
|
|
8557
|
-
get(
|
|
8558
|
-
return this.request(
|
|
8935
|
+
get(path58, query, logger) {
|
|
8936
|
+
return this.request(path58, { method: "GET", query, logger });
|
|
8559
8937
|
}
|
|
8560
|
-
post(
|
|
8561
|
-
return this.request(
|
|
8938
|
+
post(path58, body, logger) {
|
|
8939
|
+
return this.request(path58, { method: "POST", body, logger });
|
|
8562
8940
|
}
|
|
8563
|
-
delete(
|
|
8564
|
-
return this.request(
|
|
8941
|
+
delete(path58, logger) {
|
|
8942
|
+
return this.request(path58, { method: "DELETE", logger });
|
|
8565
8943
|
}
|
|
8566
8944
|
};
|
|
8567
8945
|
function createLeonardoClient() {
|
|
@@ -8572,7 +8950,7 @@ function createLeonardoClient() {
|
|
|
8572
8950
|
function extFromUrl2(url, fallback = ".png") {
|
|
8573
8951
|
try {
|
|
8574
8952
|
const u = new URL(url);
|
|
8575
|
-
const e =
|
|
8953
|
+
const e = path36.extname(u.pathname);
|
|
8576
8954
|
return e.length > 0 ? e : fallback;
|
|
8577
8955
|
} catch {
|
|
8578
8956
|
return fallback;
|
|
@@ -8657,7 +9035,7 @@ function registerLeonardoGenerateCommand(parent) {
|
|
|
8657
9035
|
if (!img) continue;
|
|
8658
9036
|
const ext = extFromUrl2(img.url);
|
|
8659
9037
|
const filename = autoFilename(generationId, i + 1, ext);
|
|
8660
|
-
const filepath =
|
|
9038
|
+
const filepath = path36.join(outDir, filename);
|
|
8661
9039
|
try {
|
|
8662
9040
|
await downloadFile(img.url, filepath);
|
|
8663
9041
|
saved.push(filepath);
|
|
@@ -8667,9 +9045,9 @@ function registerLeonardoGenerateCommand(parent) {
|
|
|
8667
9045
|
}
|
|
8668
9046
|
}
|
|
8669
9047
|
if (opts.output && saved[0]) {
|
|
8670
|
-
const dest =
|
|
8671
|
-
|
|
8672
|
-
|
|
9048
|
+
const dest = path36.resolve(opts.output);
|
|
9049
|
+
fs31.mkdirSync(path36.dirname(dest), { recursive: true });
|
|
9050
|
+
fs31.copyFileSync(saved[0], dest);
|
|
8673
9051
|
logger.success(`Copied to ${dest}`);
|
|
8674
9052
|
}
|
|
8675
9053
|
const finalImages = finalizeGeneratedImages(saved, { ...opts, logger });
|
|
@@ -8681,11 +9059,11 @@ Generated ${finalImages.length} image(s):`);
|
|
|
8681
9059
|
}
|
|
8682
9060
|
|
|
8683
9061
|
// src/providers/leonardo/commands/image-to-image.ts
|
|
8684
|
-
import
|
|
8685
|
-
import
|
|
9062
|
+
import fs32 from "fs";
|
|
9063
|
+
import path37 from "path";
|
|
8686
9064
|
function extFromUrl3(url, fallback = ".png") {
|
|
8687
9065
|
try {
|
|
8688
|
-
return
|
|
9066
|
+
return path37.extname(new URL(url).pathname) || fallback;
|
|
8689
9067
|
} catch {
|
|
8690
9068
|
return fallback;
|
|
8691
9069
|
}
|
|
@@ -8747,7 +9125,7 @@ function registerLeonardoImageToImageCommand(parent) {
|
|
|
8747
9125
|
if (!img) continue;
|
|
8748
9126
|
const ext = extFromUrl3(img.url);
|
|
8749
9127
|
const filename = `leonardo-i2i-${generationId.slice(0, 8)}-${String(i + 1).padStart(2, "0")}${ext}`;
|
|
8750
|
-
const filepath =
|
|
9128
|
+
const filepath = path37.join(outDir, filename);
|
|
8751
9129
|
try {
|
|
8752
9130
|
await downloadFile(img.url, filepath);
|
|
8753
9131
|
saved.push(filepath);
|
|
@@ -8757,9 +9135,9 @@ function registerLeonardoImageToImageCommand(parent) {
|
|
|
8757
9135
|
}
|
|
8758
9136
|
}
|
|
8759
9137
|
if (opts.output && saved[0]) {
|
|
8760
|
-
const dest =
|
|
8761
|
-
|
|
8762
|
-
|
|
9138
|
+
const dest = path37.resolve(opts.output);
|
|
9139
|
+
fs32.mkdirSync(path37.dirname(dest), { recursive: true });
|
|
9140
|
+
fs32.copyFileSync(saved[0], dest);
|
|
8763
9141
|
logger.success(`Copied to ${dest}`);
|
|
8764
9142
|
}
|
|
8765
9143
|
const finalImages = finalizeGeneratedImages(saved, { ...opts, logger });
|
|
@@ -8771,7 +9149,7 @@ Generated ${finalImages.length} image(s):`);
|
|
|
8771
9149
|
}
|
|
8772
9150
|
|
|
8773
9151
|
// src/providers/leonardo/commands/video.ts
|
|
8774
|
-
import
|
|
9152
|
+
import path38 from "path";
|
|
8775
9153
|
function v2DimsFor(resolution) {
|
|
8776
9154
|
if (resolution === "RESOLUTION_1080") return { width: 1920, height: 1080 };
|
|
8777
9155
|
if (resolution === "RESOLUTION_480") return { width: 854, height: 480 };
|
|
@@ -8882,7 +9260,7 @@ async function pollAndDownload(client, jobId, opts) {
|
|
|
8882
9260
|
logger.error(`No video URL in completed generation: ${JSON.stringify(gen)}`);
|
|
8883
9261
|
process.exit(1);
|
|
8884
9262
|
}
|
|
8885
|
-
const outPath = opts.output ??
|
|
9263
|
+
const outPath = opts.output ?? path38.join(getOutputDir(), `${opts.basename}-${jobId.slice(0, 8)}.mp4`);
|
|
8886
9264
|
await downloadFile(videoUrl2, outPath);
|
|
8887
9265
|
logger.success(`Saved ${outPath}`);
|
|
8888
9266
|
await maybeDownloadThumb(gen, outPath, {
|
|
@@ -8988,7 +9366,7 @@ function registerLeonardoModelsCommand(parent) {
|
|
|
8988
9366
|
}
|
|
8989
9367
|
|
|
8990
9368
|
// src/providers/leonardo/commands/status.ts
|
|
8991
|
-
import
|
|
9369
|
+
import path39 from "path";
|
|
8992
9370
|
var VIDEO_KEYS = ["motionMP4URL", "url"];
|
|
8993
9371
|
var IMAGE_EXT_RE2 = /\.(jpe?g|png|webp|gif)(\?|$)/i;
|
|
8994
9372
|
var VIDEO_EXT_RE = /\.(mp4|webm|mov|m4v)(\?|$)/i;
|
|
@@ -9053,7 +9431,7 @@ function registerLeonardoStatusCommand(parent) {
|
|
|
9053
9431
|
const outDir = getOutputDir();
|
|
9054
9432
|
let idx = 0;
|
|
9055
9433
|
for (const url of videos) {
|
|
9056
|
-
const dest = opts.output && videos.length === 1 && images.length === 0 ? opts.output :
|
|
9434
|
+
const dest = opts.output && videos.length === 1 && images.length === 0 ? opts.output : path39.join(outDir, `leonardo-${generationId.slice(0, 8)}-${idx}.mp4`);
|
|
9057
9435
|
await downloadFile(url, dest);
|
|
9058
9436
|
logger.success(`Saved ${dest}`);
|
|
9059
9437
|
await maybeDownloadThumb(gen, dest, {
|
|
@@ -9066,7 +9444,7 @@ function registerLeonardoStatusCommand(parent) {
|
|
|
9066
9444
|
for (const url of images) {
|
|
9067
9445
|
const m = url.match(IMAGE_EXT_RE2);
|
|
9068
9446
|
const ext = m?.[1] ? m[1].toLowerCase().replace("jpeg", "jpg") : "png";
|
|
9069
|
-
const dest =
|
|
9447
|
+
const dest = path39.join(outDir, `leonardo-${generationId.slice(0, 8)}-${idx}.${ext}`);
|
|
9070
9448
|
await downloadFile(url, dest);
|
|
9071
9449
|
logger.success(`Saved ${dest}`);
|
|
9072
9450
|
idx++;
|
|
@@ -9223,8 +9601,8 @@ function sleep3(ms) {
|
|
|
9223
9601
|
}
|
|
9224
9602
|
|
|
9225
9603
|
// src/providers/minimax/generators/music.ts
|
|
9226
|
-
import
|
|
9227
|
-
import
|
|
9604
|
+
import fs33 from "fs";
|
|
9605
|
+
import path40 from "path";
|
|
9228
9606
|
async function generateMinimaxMusic(opts) {
|
|
9229
9607
|
const {
|
|
9230
9608
|
apiKey,
|
|
@@ -9269,14 +9647,14 @@ async function generateMinimaxMusic(opts) {
|
|
|
9269
9647
|
}
|
|
9270
9648
|
const durationMs = resp.extra_info?.music_duration ?? 0;
|
|
9271
9649
|
const outDir = getOutputDir();
|
|
9272
|
-
const dest =
|
|
9650
|
+
const dest = path40.join(outDir, `minimax_music_${Date.now()}.${outputFormat}`);
|
|
9273
9651
|
try {
|
|
9274
9652
|
if (audioData.startsWith("http")) {
|
|
9275
9653
|
const bytes = await fetchBytes(audioData);
|
|
9276
|
-
|
|
9654
|
+
fs33.writeFileSync(dest, bytes);
|
|
9277
9655
|
} else {
|
|
9278
9656
|
const audioBytes = Buffer.from(audioData, "hex");
|
|
9279
|
-
|
|
9657
|
+
fs33.writeFileSync(dest, audioBytes);
|
|
9280
9658
|
}
|
|
9281
9659
|
} catch (e) {
|
|
9282
9660
|
return {
|
|
@@ -9287,8 +9665,8 @@ async function generateMinimaxMusic(opts) {
|
|
|
9287
9665
|
const durSec = durationMs / 1e3;
|
|
9288
9666
|
logger?.success(`Saved: ${dest} (${durSec.toFixed(1)}s)`);
|
|
9289
9667
|
if (output) {
|
|
9290
|
-
|
|
9291
|
-
|
|
9668
|
+
fs33.mkdirSync(path40.dirname(path40.resolve(output)), { recursive: true });
|
|
9669
|
+
fs33.copyFileSync(dest, output);
|
|
9292
9670
|
}
|
|
9293
9671
|
return { status: "success", generatedAudio: dest, durationMs, model };
|
|
9294
9672
|
}
|
|
@@ -9362,8 +9740,8 @@ Generated music: ${result.generatedAudio}`);
|
|
|
9362
9740
|
init_errors();
|
|
9363
9741
|
|
|
9364
9742
|
// src/providers/minimax/generators/speech.ts
|
|
9365
|
-
import
|
|
9366
|
-
import
|
|
9743
|
+
import fs34 from "fs";
|
|
9744
|
+
import path41 from "path";
|
|
9367
9745
|
async function generateMinimaxSpeech(opts) {
|
|
9368
9746
|
const {
|
|
9369
9747
|
apiKey,
|
|
@@ -9410,12 +9788,12 @@ async function generateMinimaxSpeech(opts) {
|
|
|
9410
9788
|
const audioBytes = Buffer.from(audioHex, "hex");
|
|
9411
9789
|
const ext = ["mp3", "wav", "flac"].includes(outputFormat) ? outputFormat : "mp3";
|
|
9412
9790
|
const outDir = getOutputDir();
|
|
9413
|
-
const dest =
|
|
9414
|
-
|
|
9791
|
+
const dest = path41.join(outDir, `minimax_speech_${Date.now()}.${ext}`);
|
|
9792
|
+
fs34.writeFileSync(dest, audioBytes);
|
|
9415
9793
|
logger?.success(`Saved: ${dest} (${(audioBytes.length / 1024).toFixed(1)} KB)`);
|
|
9416
9794
|
if (output) {
|
|
9417
|
-
|
|
9418
|
-
|
|
9795
|
+
fs34.mkdirSync(path41.dirname(path41.resolve(output)), { recursive: true });
|
|
9796
|
+
fs34.copyFileSync(dest, output);
|
|
9419
9797
|
}
|
|
9420
9798
|
return { status: "success", generatedAudio: dest, model };
|
|
9421
9799
|
}
|
|
@@ -9462,8 +9840,8 @@ Generated audio: ${result.generatedAudio}`);
|
|
|
9462
9840
|
init_errors();
|
|
9463
9841
|
|
|
9464
9842
|
// src/providers/minimax/generators/video.ts
|
|
9465
|
-
import
|
|
9466
|
-
import
|
|
9843
|
+
import fs35 from "fs";
|
|
9844
|
+
import path42 from "path";
|
|
9467
9845
|
async function generateMinimaxVideo(opts) {
|
|
9468
9846
|
const {
|
|
9469
9847
|
apiKey,
|
|
@@ -9509,18 +9887,18 @@ async function generateMinimaxVideo(opts) {
|
|
|
9509
9887
|
return { status: "error", error: `No file_id in poll result: ${JSON.stringify(pollResult)}` };
|
|
9510
9888
|
}
|
|
9511
9889
|
const outDir = getOutputDir();
|
|
9512
|
-
const dest =
|
|
9890
|
+
const dest = path42.join(outDir, `minimax_video_${Date.now()}.mp4`);
|
|
9513
9891
|
try {
|
|
9514
9892
|
await downloadMinimaxFile(fileId, apiKey, dest, logger);
|
|
9515
9893
|
} catch (e) {
|
|
9516
9894
|
return { status: "error", error: e instanceof Error ? e.message : String(e) };
|
|
9517
9895
|
}
|
|
9518
9896
|
const elapsed = (Date.now() - start) / 1e3;
|
|
9519
|
-
const stat =
|
|
9897
|
+
const stat = fs35.statSync(dest);
|
|
9520
9898
|
const fileSizeMb = stat.size / (1024 * 1024);
|
|
9521
9899
|
if (output) {
|
|
9522
|
-
|
|
9523
|
-
|
|
9900
|
+
fs35.mkdirSync(path42.dirname(path42.resolve(output)), { recursive: true });
|
|
9901
|
+
fs35.copyFileSync(dest, output);
|
|
9524
9902
|
}
|
|
9525
9903
|
await maybeDownloadThumb(pollResult, dest, {
|
|
9526
9904
|
skip: thumb === false,
|
|
@@ -9582,8 +9960,8 @@ Generated video: ${result.generatedVideo}`);
|
|
|
9582
9960
|
|
|
9583
9961
|
// src/providers/minimax/generators/image.ts
|
|
9584
9962
|
init_errors();
|
|
9585
|
-
import
|
|
9586
|
-
import
|
|
9963
|
+
import fs36 from "fs";
|
|
9964
|
+
import path43 from "path";
|
|
9587
9965
|
async function generateMinimaxImage(opts) {
|
|
9588
9966
|
const {
|
|
9589
9967
|
apiKey,
|
|
@@ -9621,8 +9999,8 @@ async function generateMinimaxImage(opts) {
|
|
|
9621
9999
|
try {
|
|
9622
10000
|
const bytes = await fetchBytes(url);
|
|
9623
10001
|
const fname = `minimax_image_${Date.now()}_${i}.png`;
|
|
9624
|
-
const dest =
|
|
9625
|
-
|
|
10002
|
+
const dest = path43.join(outDir, fname);
|
|
10003
|
+
fs36.writeFileSync(dest, bytes);
|
|
9626
10004
|
savedFiles.push(dest);
|
|
9627
10005
|
logger?.success(`Saved: ${dest}`);
|
|
9628
10006
|
} catch (e) {
|
|
@@ -9633,8 +10011,8 @@ async function generateMinimaxImage(opts) {
|
|
|
9633
10011
|
}
|
|
9634
10012
|
}
|
|
9635
10013
|
if (output && savedFiles[0]) {
|
|
9636
|
-
|
|
9637
|
-
|
|
10014
|
+
fs36.mkdirSync(path43.dirname(path43.resolve(output)), { recursive: true });
|
|
10015
|
+
fs36.copyFileSync(savedFiles[0], output);
|
|
9638
10016
|
}
|
|
9639
10017
|
return { status: "success", generatedImages: savedFiles, model };
|
|
9640
10018
|
}
|
|
@@ -9673,8 +10051,8 @@ Generated ${finalImages.length} image(s):`);
|
|
|
9673
10051
|
|
|
9674
10052
|
// src/providers/minimax/commands/image-to-image.ts
|
|
9675
10053
|
init_errors();
|
|
9676
|
-
import
|
|
9677
|
-
import
|
|
10054
|
+
import fs37 from "fs";
|
|
10055
|
+
import path44 from "path";
|
|
9678
10056
|
function registerMinimaxImageToImageCommand(parent) {
|
|
9679
10057
|
parent.command("image-to-image").alias("i2i").description(
|
|
9680
10058
|
"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."
|
|
@@ -9729,8 +10107,8 @@ function registerMinimaxImageToImageCommand(parent) {
|
|
|
9729
10107
|
if (!url) continue;
|
|
9730
10108
|
try {
|
|
9731
10109
|
const bytes = await fetchBytes(url);
|
|
9732
|
-
const dest =
|
|
9733
|
-
|
|
10110
|
+
const dest = path44.join(outDir, `minimax-i2i-${ts}-${i + 1}.png`);
|
|
10111
|
+
fs37.writeFileSync(dest, bytes);
|
|
9734
10112
|
saved.push(dest);
|
|
9735
10113
|
logger.success(`Saved: ${dest}`);
|
|
9736
10114
|
} catch (e) {
|
|
@@ -9741,8 +10119,8 @@ function registerMinimaxImageToImageCommand(parent) {
|
|
|
9741
10119
|
}
|
|
9742
10120
|
}
|
|
9743
10121
|
if (opts.output && saved[0]) {
|
|
9744
|
-
|
|
9745
|
-
|
|
10122
|
+
fs37.mkdirSync(path44.dirname(path44.resolve(opts.output)), { recursive: true });
|
|
10123
|
+
fs37.copyFileSync(saved[0], opts.output);
|
|
9746
10124
|
logger.success(`Copied to: ${opts.output}`);
|
|
9747
10125
|
}
|
|
9748
10126
|
const finalImages = finalizeGeneratedImages(saved, { ...opts, logger });
|
|
@@ -9876,12 +10254,12 @@ function validateKnownSpeakerPairing(names = [], refs = []) {
|
|
|
9876
10254
|
}
|
|
9877
10255
|
|
|
9878
10256
|
// src/providers/openai/openai-audio.ts
|
|
9879
|
-
import
|
|
9880
|
-
import
|
|
10257
|
+
import fs39 from "fs";
|
|
10258
|
+
import path46 from "path";
|
|
9881
10259
|
|
|
9882
10260
|
// src/providers/openai/client.ts
|
|
9883
|
-
import
|
|
9884
|
-
import
|
|
10261
|
+
import fs38 from "fs";
|
|
10262
|
+
import path45 from "path";
|
|
9885
10263
|
init_errors();
|
|
9886
10264
|
var DEFAULT_TIMEOUT_MS4 = 24e4;
|
|
9887
10265
|
function requireOpenAIKey() {
|
|
@@ -9949,10 +10327,10 @@ async function postOpenAIBinary(pathname, body, apiKey, timeoutMs = DEFAULT_TIME
|
|
|
9949
10327
|
return Buffer.from(await res.arrayBuffer());
|
|
9950
10328
|
}
|
|
9951
10329
|
function fileFromPath(filePath) {
|
|
9952
|
-
const abs =
|
|
9953
|
-
if (!
|
|
9954
|
-
const bytes =
|
|
9955
|
-
return new File([new Uint8Array(bytes)],
|
|
10330
|
+
const abs = path45.resolve(filePath);
|
|
10331
|
+
if (!fs38.existsSync(abs)) throw new ValidationError(`File not found: ${abs}`);
|
|
10332
|
+
const bytes = fs38.readFileSync(abs);
|
|
10333
|
+
return new File([new Uint8Array(bytes)], path45.basename(abs));
|
|
9956
10334
|
}
|
|
9957
10335
|
|
|
9958
10336
|
// src/providers/openai/openai-audio.ts
|
|
@@ -9991,7 +10369,7 @@ async function resolveAudioReferenceDataUrl(ref) {
|
|
|
9991
10369
|
}
|
|
9992
10370
|
function audioMimeFromReference(ref) {
|
|
9993
10371
|
const pathname = new URL(ref).pathname;
|
|
9994
|
-
const ext =
|
|
10372
|
+
const ext = path46.extname(pathname).toLowerCase();
|
|
9995
10373
|
switch (ext) {
|
|
9996
10374
|
case ".mp3":
|
|
9997
10375
|
return "audio/mpeg";
|
|
@@ -10010,12 +10388,12 @@ function audioMimeFromReference(ref) {
|
|
|
10010
10388
|
async function generateOpenAISpeech(opts) {
|
|
10011
10389
|
const bytes = await postOpenAIBinary("/audio/speech", buildSpeechBody(opts), requireOpenAIKey());
|
|
10012
10390
|
const ext = extFromAudioFormat(opts.outputFormat);
|
|
10013
|
-
const dest =
|
|
10014
|
-
|
|
10391
|
+
const dest = path46.join(getOutputDir(), `openai-speech-${Date.now()}.${ext}`);
|
|
10392
|
+
fs39.writeFileSync(dest, bytes);
|
|
10015
10393
|
opts.logger?.success(`Saved: ${dest} (${(bytes.length / 1024).toFixed(1)} KB)`);
|
|
10016
10394
|
if (opts.output) {
|
|
10017
|
-
|
|
10018
|
-
|
|
10395
|
+
fs39.mkdirSync(path46.dirname(path46.resolve(opts.output)), { recursive: true });
|
|
10396
|
+
fs39.copyFileSync(dest, opts.output);
|
|
10019
10397
|
}
|
|
10020
10398
|
return dest;
|
|
10021
10399
|
}
|
|
@@ -10037,9 +10415,9 @@ async function transcribeOpenAIAudio(opts) {
|
|
|
10037
10415
|
);
|
|
10038
10416
|
const text = typeof data.text === "string" ? data.text : void 0;
|
|
10039
10417
|
const ext = opts.format === "text" ? "txt" : "json";
|
|
10040
|
-
const dest = opts.output ?
|
|
10041
|
-
|
|
10042
|
-
|
|
10418
|
+
const dest = opts.output ? path46.resolve(opts.output) : path46.join(getOutputDir(), `openai-transcribe-${Date.now()}.${ext}`);
|
|
10419
|
+
fs39.mkdirSync(path46.dirname(dest), { recursive: true });
|
|
10420
|
+
fs39.writeFileSync(dest, opts.format === "text" ? text ?? "" : JSON.stringify(data, null, 2));
|
|
10043
10421
|
opts.logger?.success(`Saved transcript: ${dest}`);
|
|
10044
10422
|
return { path: dest, text };
|
|
10045
10423
|
}
|
|
@@ -10066,9 +10444,9 @@ Generated audio: ${dest}`);
|
|
|
10066
10444
|
}
|
|
10067
10445
|
|
|
10068
10446
|
// src/providers/openai/codex-image-driver.ts
|
|
10069
|
-
import
|
|
10070
|
-
import
|
|
10071
|
-
import
|
|
10447
|
+
import fs40 from "fs";
|
|
10448
|
+
import os4 from "os";
|
|
10449
|
+
import path47 from "path";
|
|
10072
10450
|
import { execa } from "execa";
|
|
10073
10451
|
init_errors();
|
|
10074
10452
|
var defaultRunner = async (cmd, args, opts) => {
|
|
@@ -10117,12 +10495,12 @@ function sanitizeCodexEnv() {
|
|
|
10117
10495
|
async function runCodexImageGeneration(opts) {
|
|
10118
10496
|
const runner = opts.runner ?? defaultRunner;
|
|
10119
10497
|
const ext = extFromImageFormat(opts.outputFormat);
|
|
10120
|
-
const dest = opts.output ?
|
|
10121
|
-
|
|
10122
|
-
const tmp =
|
|
10123
|
-
const schema =
|
|
10124
|
-
const result =
|
|
10125
|
-
|
|
10498
|
+
const dest = opts.output ? path47.resolve(opts.output) : path47.join(getOutputDir(), `codex-image-${Date.now()}.${ext}`);
|
|
10499
|
+
fs40.mkdirSync(path47.dirname(dest), { recursive: true });
|
|
10500
|
+
const tmp = fs40.mkdtempSync(path47.join(os4.tmpdir(), "multix-codex-image-"));
|
|
10501
|
+
const schema = path47.join(tmp, "schema.json");
|
|
10502
|
+
const result = path47.join(tmp, "result.json");
|
|
10503
|
+
fs40.writeFileSync(
|
|
10126
10504
|
schema,
|
|
10127
10505
|
JSON.stringify({
|
|
10128
10506
|
type: "object",
|
|
@@ -10152,7 +10530,7 @@ async function runCodexImageGeneration(opts) {
|
|
|
10152
10530
|
if (res.exitCode !== 0) {
|
|
10153
10531
|
throw new ProviderError(`Codex image driver failed: ${res.stderr || res.stdout}`, "openai");
|
|
10154
10532
|
}
|
|
10155
|
-
if (!
|
|
10533
|
+
if (!fs40.existsSync(dest) || fs40.statSync(dest).size === 0) {
|
|
10156
10534
|
throw new ProviderError(`Codex did not create a non-empty image at ${dest}`, "openai");
|
|
10157
10535
|
}
|
|
10158
10536
|
return { path: dest };
|
|
@@ -10169,8 +10547,8 @@ function buildCodexPrompt(userPrompt, outputPath) {
|
|
|
10169
10547
|
|
|
10170
10548
|
// src/providers/openai/openai-image.ts
|
|
10171
10549
|
init_errors();
|
|
10172
|
-
import
|
|
10173
|
-
import
|
|
10550
|
+
import fs41 from "fs";
|
|
10551
|
+
import path48 from "path";
|
|
10174
10552
|
function buildImageGenerationBody(opts) {
|
|
10175
10553
|
return {
|
|
10176
10554
|
model: opts.model ?? DEFAULT_OPENAI_IMAGE_MODEL,
|
|
@@ -10203,14 +10581,14 @@ function saveOpenAIImages(opts) {
|
|
|
10203
10581
|
for (let i = 0; i < items.length; i++) {
|
|
10204
10582
|
const b64 = items[i]?.b64_json;
|
|
10205
10583
|
if (!b64) throw new ProviderError("No b64_json in OpenAI image response", "openai");
|
|
10206
|
-
const dest =
|
|
10207
|
-
|
|
10584
|
+
const dest = path48.join(outDir, `openai-${opts.task}-${ts}-${i + 1}.${ext}`);
|
|
10585
|
+
fs41.writeFileSync(dest, Buffer.from(b64, "base64"));
|
|
10208
10586
|
saved.push(dest);
|
|
10209
10587
|
opts.logger?.success(`Saved: ${dest}`);
|
|
10210
10588
|
}
|
|
10211
10589
|
if (opts.output && saved[0]) {
|
|
10212
|
-
|
|
10213
|
-
|
|
10590
|
+
fs41.mkdirSync(path48.dirname(path48.resolve(opts.output)), { recursive: true });
|
|
10591
|
+
fs41.copyFileSync(saved[0], opts.output);
|
|
10214
10592
|
opts.logger?.success(`Copied to: ${opts.output}`);
|
|
10215
10593
|
}
|
|
10216
10594
|
return saved;
|
|
@@ -10219,7 +10597,7 @@ async function resolveImageRefBytes(ref) {
|
|
|
10219
10597
|
if (isHttpUrl(ref)) {
|
|
10220
10598
|
const bytes2 = await fetchBytes(ref);
|
|
10221
10599
|
const pathname = new URL(ref).pathname;
|
|
10222
|
-
const filename =
|
|
10600
|
+
const filename = path48.basename(pathname) || "reference.png";
|
|
10223
10601
|
return { filename, bytes: bytes2, mime: sniffImageMime2(bytes2, filename) };
|
|
10224
10602
|
}
|
|
10225
10603
|
const file = fileFromPath(ref);
|
|
@@ -10264,7 +10642,7 @@ function sniffImageMime2(buf, name) {
|
|
|
10264
10642
|
if (buf[0] === 137 && buf[1] === 80 && buf[2] === 78 && buf[3] === 71) return "image/png";
|
|
10265
10643
|
if (buf.slice(0, 4).toString() === "RIFF" && buf.slice(8, 12).toString() === "WEBP")
|
|
10266
10644
|
return "image/webp";
|
|
10267
|
-
const ext =
|
|
10645
|
+
const ext = path48.extname(name).toLowerCase();
|
|
10268
10646
|
if (ext === ".jpg" || ext === ".jpeg") return "image/jpeg";
|
|
10269
10647
|
if (ext === ".webp") return "image/webp";
|
|
10270
10648
|
return "image/png";
|
|
@@ -10408,8 +10786,8 @@ function registerOpenAICommands(program2) {
|
|
|
10408
10786
|
}
|
|
10409
10787
|
|
|
10410
10788
|
// src/providers/openrouter/client.ts
|
|
10411
|
-
import
|
|
10412
|
-
import
|
|
10789
|
+
import fs42 from "fs";
|
|
10790
|
+
import path49 from "path";
|
|
10413
10791
|
init_errors();
|
|
10414
10792
|
|
|
10415
10793
|
// src/providers/openrouter/payload.ts
|
|
@@ -10573,8 +10951,8 @@ async function generateOpenRouterImage(opts) {
|
|
|
10573
10951
|
if (!url) continue;
|
|
10574
10952
|
try {
|
|
10575
10953
|
const bytes = await fetchBytes(url);
|
|
10576
|
-
const dest =
|
|
10577
|
-
|
|
10954
|
+
const dest = path49.join(outDir, `openrouter_image_${Date.now()}_${i}.png`);
|
|
10955
|
+
fs42.writeFileSync(dest, bytes);
|
|
10578
10956
|
savedFiles.push(dest);
|
|
10579
10957
|
logger?.success(`Saved: ${dest}`);
|
|
10580
10958
|
} catch (e) {
|
|
@@ -10585,8 +10963,8 @@ async function generateOpenRouterImage(opts) {
|
|
|
10585
10963
|
}
|
|
10586
10964
|
}
|
|
10587
10965
|
if (output && savedFiles[0]) {
|
|
10588
|
-
|
|
10589
|
-
|
|
10966
|
+
fs42.mkdirSync(path49.dirname(path49.resolve(output)), { recursive: true });
|
|
10967
|
+
fs42.writeFileSync(output, fs42.readFileSync(savedFiles[0]));
|
|
10590
10968
|
}
|
|
10591
10969
|
return { status: "success", generatedImages: savedFiles, model: usedModel };
|
|
10592
10970
|
}
|
|
@@ -10627,8 +11005,8 @@ Generated ${finalImages.length} image(s):`);
|
|
|
10627
11005
|
}
|
|
10628
11006
|
|
|
10629
11007
|
// src/providers/openrouter/commands/image-to-image.ts
|
|
10630
|
-
import
|
|
10631
|
-
import
|
|
11008
|
+
import fs43 from "fs";
|
|
11009
|
+
import path50 from "path";
|
|
10632
11010
|
init_errors();
|
|
10633
11011
|
var DEFAULT_MODEL = "google/gemini-2.5-flash-image";
|
|
10634
11012
|
var SOFT_LIMIT_BYTES2 = 5 * 1024 * 1024;
|
|
@@ -10720,14 +11098,14 @@ async function runImageToImage(opts) {
|
|
|
10720
11098
|
const url = parsed.urls[i];
|
|
10721
11099
|
if (!url) continue;
|
|
10722
11100
|
const bytes = await fetchBytes(url);
|
|
10723
|
-
const dest =
|
|
10724
|
-
|
|
11101
|
+
const dest = path50.join(outDir, `openrouter-i2i-${ts}-${i + 1}.png`);
|
|
11102
|
+
fs43.writeFileSync(dest, bytes);
|
|
10725
11103
|
saved.push(dest);
|
|
10726
11104
|
opts.logger.success(`Saved: ${dest}`);
|
|
10727
11105
|
}
|
|
10728
11106
|
if (opts.output && saved[0]) {
|
|
10729
|
-
|
|
10730
|
-
|
|
11107
|
+
fs43.mkdirSync(path50.dirname(path50.resolve(opts.output)), { recursive: true });
|
|
11108
|
+
fs43.copyFileSync(saved[0], opts.output);
|
|
10731
11109
|
opts.logger.success(`Copied to: ${opts.output}`);
|
|
10732
11110
|
}
|
|
10733
11111
|
return saved;
|
|
@@ -10772,13 +11150,13 @@ async function listVideoModels(apiKey = requireOpenRouterKey()) {
|
|
|
10772
11150
|
}
|
|
10773
11151
|
|
|
10774
11152
|
// src/providers/openrouter/commands/video-status.ts
|
|
10775
|
-
import
|
|
10776
|
-
import
|
|
11153
|
+
import fs44 from "fs";
|
|
11154
|
+
import path51 from "path";
|
|
10777
11155
|
async function downloadOpenRouterVideo(opts) {
|
|
10778
11156
|
const { jobId, logger, source } = opts;
|
|
10779
11157
|
const apiKey = requireOpenRouterKey();
|
|
10780
11158
|
const url = videoContentUrl(jobId);
|
|
10781
|
-
const dest =
|
|
11159
|
+
const dest = path51.join(getOutputDir(), `openrouter_video_${jobId}.mp4`);
|
|
10782
11160
|
const { response: r } = await fetchWithValidatedRedirects(url, {
|
|
10783
11161
|
headers: { Authorization: `Bearer ${apiKey}` }
|
|
10784
11162
|
});
|
|
@@ -10790,14 +11168,14 @@ async function downloadOpenRouterVideo(opts) {
|
|
|
10790
11168
|
logger.error("Empty response body");
|
|
10791
11169
|
process.exit(1);
|
|
10792
11170
|
}
|
|
10793
|
-
|
|
10794
|
-
const ws =
|
|
11171
|
+
fs44.mkdirSync(path51.dirname(dest), { recursive: true });
|
|
11172
|
+
const ws = fs44.createWriteStream(dest);
|
|
10795
11173
|
const { pipeline: pipeline2 } = await import("stream/promises");
|
|
10796
11174
|
await pipeline2(r.body, ws);
|
|
10797
11175
|
logger.success(`Saved ${dest}`);
|
|
10798
11176
|
if (opts.output) {
|
|
10799
|
-
|
|
10800
|
-
|
|
11177
|
+
fs44.mkdirSync(path51.dirname(path51.resolve(opts.output)), { recursive: true });
|
|
11178
|
+
fs44.copyFileSync(dest, opts.output);
|
|
10801
11179
|
logger.success(`Copied to ${opts.output}`);
|
|
10802
11180
|
}
|
|
10803
11181
|
await maybeDownloadThumb(source, dest, {
|
|
@@ -10995,8 +11373,8 @@ function parseModelRef(ref, explicitVersion) {
|
|
|
10995
11373
|
}
|
|
10996
11374
|
return { kind: "version", version: trimmed };
|
|
10997
11375
|
}
|
|
10998
|
-
const [
|
|
10999
|
-
const [owner, name, ...extra] = (
|
|
11376
|
+
const [path58, inlineVersion, ...rest] = trimmed.split(":");
|
|
11377
|
+
const [owner, name, ...extra] = (path58 ?? "").split("/");
|
|
11000
11378
|
if (rest.length > 0 || extra.length > 0 || !owner || !name) {
|
|
11001
11379
|
throw new ValidationError(
|
|
11002
11380
|
`Invalid Replicate model "${ref}" \u2014 expected owner/name, owner/name:version, or a 64-char version id`
|
|
@@ -11156,9 +11534,9 @@ var ReplicateClient = class {
|
|
|
11156
11534
|
return { accept: "application/json", authorization: `Bearer ${this.apiKey}`, ...extra };
|
|
11157
11535
|
}
|
|
11158
11536
|
/** Raw request: throws HttpError so polling can classify transient failures. */
|
|
11159
|
-
raw(
|
|
11537
|
+
raw(path58, method, body, headers2, timeoutMs = REQUEST_TIMEOUT_MS) {
|
|
11160
11538
|
return fetchJson({
|
|
11161
|
-
url: `${this.baseUrl}${
|
|
11539
|
+
url: `${this.baseUrl}${path58}`,
|
|
11162
11540
|
method,
|
|
11163
11541
|
headers: this.headers(headers2),
|
|
11164
11542
|
body,
|
|
@@ -11170,7 +11548,7 @@ var ReplicateClient = class {
|
|
|
11170
11548
|
async createPrediction(opts) {
|
|
11171
11549
|
const { model, input } = opts;
|
|
11172
11550
|
const isOfficial = model.kind === "official";
|
|
11173
|
-
const
|
|
11551
|
+
const path58 = isOfficial ? `/models/${model.owner}/${model.name}/predictions` : "/predictions";
|
|
11174
11552
|
const body = isOfficial ? { input } : { version: model.version, input };
|
|
11175
11553
|
const subject = isOfficial ? `model ${model.owner}/${model.name}` : `model version ${model.version}`;
|
|
11176
11554
|
const headers2 = {};
|
|
@@ -11182,7 +11560,7 @@ var ReplicateClient = class {
|
|
|
11182
11560
|
}
|
|
11183
11561
|
for (let attempt = 0; ; attempt++) {
|
|
11184
11562
|
try {
|
|
11185
|
-
const res = await this.raw(
|
|
11563
|
+
const res = await this.raw(path58, "POST", body, headers2, timeoutMs);
|
|
11186
11564
|
return validatePrediction(res);
|
|
11187
11565
|
} catch (error) {
|
|
11188
11566
|
if (error instanceof HttpError && error.status === 429 && attempt < CREATE_MAX_RETRIES) {
|
|
@@ -11345,145 +11723,33 @@ import { InvalidArgumentError, Option as Option2 } from "commander";
|
|
|
11345
11723
|
|
|
11346
11724
|
// src/providers/replicate/node-outputs.ts
|
|
11347
11725
|
init_errors();
|
|
11348
|
-
import
|
|
11349
|
-
import
|
|
11726
|
+
import fs45 from "fs";
|
|
11727
|
+
import path52 from "path";
|
|
11350
11728
|
init_redact();
|
|
11351
11729
|
|
|
11352
11730
|
// src/providers/replicate/output.ts
|
|
11353
11731
|
init_redact();
|
|
11354
|
-
|
|
11355
|
-
// src/core/url-safety.ts
|
|
11356
|
-
init_errors();
|
|
11357
|
-
function ipv4Octets(host) {
|
|
11358
|
-
const parts = host.split(".");
|
|
11359
|
-
if (parts.length !== 4 || !parts.every((p) => /^\d{1,3}$/.test(p))) return void 0;
|
|
11360
|
-
const octets = parts.map(Number);
|
|
11361
|
-
return octets.every((o) => o <= 255) ? octets : void 0;
|
|
11362
|
-
}
|
|
11363
|
-
function isNonPublicIpv4([a = 0, b = 0, c2 = 0]) {
|
|
11364
|
-
return a === 0 || // "this network" / unspecified
|
|
11365
|
-
a === 10 || a === 127 || // loopback
|
|
11366
|
-
a === 100 && b >= 64 && b <= 127 || // carrier-grade NAT
|
|
11367
|
-
a === 169 && b === 254 || // link-local (cloud metadata)
|
|
11368
|
-
a === 172 && b >= 16 && b <= 31 || a === 192 && b === 0 && c2 === 0 || // IETF protocol assignments
|
|
11369
|
-
a === 192 && b === 0 && c2 === 2 || // TEST-NET-1
|
|
11370
|
-
a === 192 && b === 168 || a === 198 && (b === 18 || b === 19) || // benchmarking
|
|
11371
|
-
a === 198 && b === 51 && c2 === 100 || // TEST-NET-2
|
|
11372
|
-
a === 203 && b === 0 && c2 === 113 || // TEST-NET-3
|
|
11373
|
-
a >= 224;
|
|
11374
|
-
}
|
|
11375
|
-
function ipv6Groups(host) {
|
|
11376
|
-
if (!/^[0-9a-f:.]+$/i.test(host) || !host.includes(":")) return void 0;
|
|
11377
|
-
let text = host;
|
|
11378
|
-
const tail = /^(.*:)(\d+\.\d+\.\d+\.\d+)$/.exec(text);
|
|
11379
|
-
if (tail) {
|
|
11380
|
-
const octets = ipv4Octets(tail[2]);
|
|
11381
|
-
if (!octets) return void 0;
|
|
11382
|
-
const [a = 0, b = 0, c2 = 0, d = 0] = octets;
|
|
11383
|
-
text = `${tail[1]}${(a << 8 | b).toString(16)}:${(c2 << 8 | d).toString(16)}`;
|
|
11384
|
-
}
|
|
11385
|
-
const halves = text.split("::");
|
|
11386
|
-
if (halves.length > 2) return void 0;
|
|
11387
|
-
const parse = (s) => s === "" ? [] : s.split(":");
|
|
11388
|
-
const head = parse(halves[0]);
|
|
11389
|
-
const rest = halves.length === 2 ? parse(halves[1]) : [];
|
|
11390
|
-
const missing = 8 - head.length - rest.length;
|
|
11391
|
-
if (halves.length === 2 ? missing < 1 : missing !== 0) return void 0;
|
|
11392
|
-
const all = [...head, ...Array(halves.length === 2 ? missing : 0).fill("0"), ...rest];
|
|
11393
|
-
if (!all.every((g) => /^[0-9a-f]{1,4}$/i.test(g))) return void 0;
|
|
11394
|
-
return all.map((g) => Number.parseInt(g, 16));
|
|
11395
|
-
}
|
|
11396
|
-
function embeddedIpv4(hi, lo) {
|
|
11397
|
-
return [hi >> 8, hi & 255, lo >> 8, lo & 255];
|
|
11398
|
-
}
|
|
11399
|
-
function isNonPublicIpv6(groups) {
|
|
11400
|
-
const [g0 = 0, g1 = 0, g2 = 0, g3 = 0, g4 = 0, g5 = 0, g6 = 0, g7 = 0] = groups;
|
|
11401
|
-
const zeroUpTo = (n) => groups.slice(0, n).every((g) => g === 0);
|
|
11402
|
-
if (zeroUpTo(5) && g5 === 65535) return isNonPublicIpv4(embeddedIpv4(g6, g7));
|
|
11403
|
-
if (g0 === 100 && g1 === 65435 && g2 === 0 && g3 === 0 && g4 === 0 && g5 === 0) {
|
|
11404
|
-
return isNonPublicIpv4(embeddedIpv4(g6, g7));
|
|
11405
|
-
}
|
|
11406
|
-
if (g0 === 8194) return isNonPublicIpv4(embeddedIpv4(g1, g2));
|
|
11407
|
-
return g0 === 0 || // unspecified (::), loopback (::1), IPv4-compatible and other reserved
|
|
11408
|
-
g0 === 256 && g1 === 0 && g2 === 0 && g3 === 0 || // discard-only 100::/64
|
|
11409
|
-
(g0 & 65024) === 64512 || // unique local fc00::/7
|
|
11410
|
-
(g0 & 65472) === 65152 || // link-local fe80::/10
|
|
11411
|
-
(g0 & 65472) === 65216 || // deprecated site-local fec0::/10
|
|
11412
|
-
(g0 & 65280) === 65280;
|
|
11413
|
-
}
|
|
11414
|
-
function bareHost(hostname) {
|
|
11415
|
-
const host = hostname.toLowerCase().replace(/\.$/, "");
|
|
11416
|
-
return host.startsWith("[") && host.endsWith("]") ? host.slice(1, -1) : host;
|
|
11417
|
-
}
|
|
11418
|
-
function isLoopbackHost(hostname) {
|
|
11419
|
-
const host = bareHost(hostname);
|
|
11420
|
-
if (host === "localhost" || host.endsWith(".localhost")) return true;
|
|
11421
|
-
const octets = ipv4Octets(host);
|
|
11422
|
-
if (octets) return octets[0] === 127;
|
|
11423
|
-
const groups = ipv6Groups(host);
|
|
11424
|
-
if (!groups) return false;
|
|
11425
|
-
if (groups.slice(0, 5).every((g) => g === 0) && groups[5] === 65535) {
|
|
11426
|
-
return groups[6] !== void 0 && groups[6] >> 8 === 127;
|
|
11427
|
-
}
|
|
11428
|
-
return groups.slice(0, 7).every((g) => g === 0) && groups[7] === 1;
|
|
11429
|
-
}
|
|
11430
|
-
function isNonPublicHost(hostname) {
|
|
11431
|
-
const host = bareHost(hostname);
|
|
11432
|
-
if (host === "localhost" || host.endsWith(".localhost")) return true;
|
|
11433
|
-
const octets = ipv4Octets(host);
|
|
11434
|
-
if (octets) return isNonPublicIpv4(octets);
|
|
11435
|
-
const groups = ipv6Groups(host);
|
|
11436
|
-
return groups !== void 0 && isNonPublicIpv6(groups);
|
|
11437
|
-
}
|
|
11438
|
-
function checkUrlSafety(url, opts) {
|
|
11439
|
-
const action = opts.action ?? "download from";
|
|
11440
|
-
let parsed;
|
|
11441
|
-
try {
|
|
11442
|
-
parsed = new URL(url);
|
|
11443
|
-
} catch {
|
|
11444
|
-
throw new ProviderError(`Refusing to ${action} a malformed URL`, opts.provider);
|
|
11445
|
-
}
|
|
11446
|
-
const host = parsed.hostname.toLowerCase().replace(/\.$/, "");
|
|
11447
|
-
const localOk = opts.allowLocalhostHttp === true && isLoopbackHost(host);
|
|
11448
|
-
const httpOk = parsed.protocol === "https:" || localOk && parsed.protocol === "http:";
|
|
11449
|
-
if (!httpOk) {
|
|
11450
|
-
throw new ProviderError(
|
|
11451
|
-
`Refusing to ${action} ${host || "this URL"}: only https:// URLs are allowed`,
|
|
11452
|
-
opts.provider
|
|
11453
|
-
);
|
|
11454
|
-
}
|
|
11455
|
-
if (!localOk && isNonPublicHost(host)) {
|
|
11456
|
-
throw new ProviderError(
|
|
11457
|
-
`Refusing to ${action} ${host}: it points to a local or private address`,
|
|
11458
|
-
opts.provider
|
|
11459
|
-
);
|
|
11460
|
-
}
|
|
11461
|
-
const trusted = (opts.trustedHosts ?? []).some((t) => host === t || host.endsWith(`.${t}`));
|
|
11462
|
-
return { host, trusted };
|
|
11463
|
-
}
|
|
11464
|
-
|
|
11465
|
-
// src/providers/replicate/output.ts
|
|
11466
11732
|
var HTTP_RE = /^https?:\/\//i;
|
|
11467
11733
|
var DATA_URI_RE = /^data:[^,]*,/i;
|
|
11468
11734
|
function isMediaRef(value) {
|
|
11469
11735
|
return typeof value === "string" && (HTTP_RE.test(value) || DATA_URI_RE.test(value));
|
|
11470
11736
|
}
|
|
11471
11737
|
var REPLICATE_DELIVERY_HOST = "replicate.delivery";
|
|
11472
|
-
function
|
|
11738
|
+
function checkOutputUrl2(url) {
|
|
11473
11739
|
return checkUrlSafety(url, { provider: "replicate", trustedHosts: [REPLICATE_DELIVERY_HOST] });
|
|
11474
11740
|
}
|
|
11475
11741
|
function extractOutput(output) {
|
|
11476
11742
|
const result = { media: [], texts: [] };
|
|
11477
11743
|
const seen = /* @__PURE__ */ new Set();
|
|
11478
|
-
const visit = (value,
|
|
11744
|
+
const visit = (value, path58) => {
|
|
11479
11745
|
if (value === null || value === void 0) return;
|
|
11480
11746
|
if (typeof value === "string") {
|
|
11481
|
-
if (isMediaRef(value)) result.media.push({ url: value, path:
|
|
11482
|
-
else if (value.length > 0) result.texts.push(
|
|
11747
|
+
if (isMediaRef(value)) result.media.push({ url: value, path: path58 || "output" });
|
|
11748
|
+
else if (value.length > 0) result.texts.push(path58 ? `${path58}: ${value}` : value);
|
|
11483
11749
|
return;
|
|
11484
11750
|
}
|
|
11485
11751
|
if (typeof value !== "object") {
|
|
11486
|
-
result.texts.push(
|
|
11752
|
+
result.texts.push(path58 ? `${path58}: ${String(value)}` : String(value));
|
|
11487
11753
|
return;
|
|
11488
11754
|
}
|
|
11489
11755
|
if (seen.has(value)) return;
|
|
@@ -11491,14 +11757,14 @@ function extractOutput(output) {
|
|
|
11491
11757
|
if (Array.isArray(value)) {
|
|
11492
11758
|
if (value.length > 0 && value.every((v) => typeof v === "string" && !isMediaRef(v))) {
|
|
11493
11759
|
const joined = value.join("");
|
|
11494
|
-
if (joined.length > 0) result.texts.push(
|
|
11760
|
+
if (joined.length > 0) result.texts.push(path58 ? `${path58}: ${joined}` : joined);
|
|
11495
11761
|
return;
|
|
11496
11762
|
}
|
|
11497
|
-
value.forEach((item, i) => visit(item, `${
|
|
11763
|
+
value.forEach((item, i) => visit(item, `${path58}[${i}]`));
|
|
11498
11764
|
return;
|
|
11499
11765
|
}
|
|
11500
11766
|
for (const [key, val] of Object.entries(value)) {
|
|
11501
|
-
visit(val,
|
|
11767
|
+
visit(val, path58 ? `${path58}.${key}` : key);
|
|
11502
11768
|
}
|
|
11503
11769
|
};
|
|
11504
11770
|
visit(output, "");
|
|
@@ -11616,16 +11882,16 @@ var DOWNLOAD_TIMEOUT_MS = 3e5;
|
|
|
11616
11882
|
function isDirTarget(output) {
|
|
11617
11883
|
if (/[\\/]$/.test(output)) return true;
|
|
11618
11884
|
try {
|
|
11619
|
-
return
|
|
11885
|
+
return fs45.statSync(output).isDirectory();
|
|
11620
11886
|
} catch {
|
|
11621
11887
|
return false;
|
|
11622
11888
|
}
|
|
11623
11889
|
}
|
|
11624
11890
|
function resolveDestination(predictionId, index, count, ext, opts) {
|
|
11625
|
-
if (!opts.output) return
|
|
11626
|
-
const output =
|
|
11627
|
-
if (isDirTarget(opts.output)) return
|
|
11628
|
-
const givenExt =
|
|
11891
|
+
if (!opts.output) return path52.join(opts.outDir, outputFilename(predictionId, index, ext));
|
|
11892
|
+
const output = path52.resolve(opts.output);
|
|
11893
|
+
if (isDirTarget(opts.output)) return path52.join(output, outputFilename(predictionId, index, ext));
|
|
11894
|
+
const givenExt = path52.extname(output);
|
|
11629
11895
|
if (count === 1) return givenExt ? output : `${output}${ext}`;
|
|
11630
11896
|
const base = givenExt ? output.slice(0, -givenExt.length) : output;
|
|
11631
11897
|
return `${base}-${String(index + 1).padStart(2, "0")}${ext}`;
|
|
@@ -11643,8 +11909,8 @@ async function saveOne(item, predictionId, index, count, opts) {
|
|
|
11643
11909
|
extFromUrl4(item.url) ?? ".bin",
|
|
11644
11910
|
opts
|
|
11645
11911
|
);
|
|
11646
|
-
|
|
11647
|
-
|
|
11912
|
+
fs45.mkdirSync(path52.dirname(dest), { recursive: true });
|
|
11913
|
+
fs45.writeFileSync(dest, bytes);
|
|
11648
11914
|
return dest;
|
|
11649
11915
|
}
|
|
11650
11916
|
const warned2 = opts.warnedHosts;
|
|
@@ -11658,7 +11924,7 @@ async function saveOne(item, predictionId, index, count, opts) {
|
|
|
11658
11924
|
maxBytes: opts.maxBytes,
|
|
11659
11925
|
fetch: opts.fetch,
|
|
11660
11926
|
validateUrl: (url) => {
|
|
11661
|
-
const { host, trusted } =
|
|
11927
|
+
const { host, trusted } = checkOutputUrl2(url);
|
|
11662
11928
|
if (!trusted && !warned2.has(host)) {
|
|
11663
11929
|
warned2.add(host);
|
|
11664
11930
|
opts.logger.warn(
|
|
@@ -11859,8 +12125,8 @@ function registerReplicateImageCommand(parent) {
|
|
|
11859
12125
|
|
|
11860
12126
|
// src/providers/replicate/node-input-files.ts
|
|
11861
12127
|
init_errors();
|
|
11862
|
-
import
|
|
11863
|
-
import
|
|
12128
|
+
import fs46 from "fs";
|
|
12129
|
+
import path53 from "path";
|
|
11864
12130
|
var DATA_URI_MAX_BYTES = 256 * 1024;
|
|
11865
12131
|
var UPLOAD_MAX_BYTES = 100 * 1024 * 1024;
|
|
11866
12132
|
function formatMb4(bytes) {
|
|
@@ -11868,10 +12134,10 @@ function formatMb4(bytes) {
|
|
|
11868
12134
|
}
|
|
11869
12135
|
async function resolveFileInput(value, client, logger) {
|
|
11870
12136
|
if (isMediaRef(value)) return value;
|
|
11871
|
-
const filePath =
|
|
12137
|
+
const filePath = path53.resolve(value);
|
|
11872
12138
|
let stat;
|
|
11873
12139
|
try {
|
|
11874
|
-
stat =
|
|
12140
|
+
stat = fs46.statSync(filePath);
|
|
11875
12141
|
} catch {
|
|
11876
12142
|
throw new ValidationError(`Input file not found: ${value} (pass a local path or an https URL)`);
|
|
11877
12143
|
}
|
|
@@ -11882,9 +12148,9 @@ async function resolveFileInput(value, client, logger) {
|
|
|
11882
12148
|
`Input file ${value} is ${formatMb4(stat.size)}; Replicate accepts uploads under 100MB. Host it and pass an https URL instead.`
|
|
11883
12149
|
);
|
|
11884
12150
|
}
|
|
11885
|
-
const filename =
|
|
12151
|
+
const filename = path53.basename(filePath);
|
|
11886
12152
|
const contentType = mimeFromFilename(filename);
|
|
11887
|
-
const bytes =
|
|
12153
|
+
const bytes = fs46.readFileSync(filePath);
|
|
11888
12154
|
if (stat.size <= DATA_URI_MAX_BYTES) {
|
|
11889
12155
|
logger.debug(`Inlining ${filename} (${stat.size} bytes) as a data URI`);
|
|
11890
12156
|
return `data:${contentType};base64,${bytes.toString("base64")}`;
|
|
@@ -12011,8 +12277,8 @@ function registerReplicateCommands(program2) {
|
|
|
12011
12277
|
}
|
|
12012
12278
|
|
|
12013
12279
|
// src/commands/check.ts
|
|
12014
|
-
import
|
|
12015
|
-
import
|
|
12280
|
+
import fs47 from "fs";
|
|
12281
|
+
import os5 from "os";
|
|
12016
12282
|
|
|
12017
12283
|
// src/commands/check-helpers/binary-check.ts
|
|
12018
12284
|
import { execa as execa2 } from "execa";
|
|
@@ -12260,8 +12526,8 @@ function registerCheckCommand(program2) {
|
|
|
12260
12526
|
logger.info(`Env files loaded: ${files.length > 0 ? files.join(", ") : "none"}`);
|
|
12261
12527
|
}
|
|
12262
12528
|
if (!legacyClaudeEnvEnabled()) {
|
|
12263
|
-
const home = process.env.MULTIX_DISABLE_HOME_ENV ? void 0 :
|
|
12264
|
-
const legacy = legacyClaudeEnvFiles(process.cwd(), home).filter((f) =>
|
|
12529
|
+
const home = process.env.MULTIX_DISABLE_HOME_ENV ? void 0 : os5.homedir();
|
|
12530
|
+
const legacy = legacyClaudeEnvFiles(process.cwd(), home).filter((f) => fs47.existsSync(f));
|
|
12265
12531
|
if (legacy.length > 0) {
|
|
12266
12532
|
logger.info(
|
|
12267
12533
|
`Found ${legacy.length} legacy .claude env file(s); set MULTIX_LEGACY_CLAUDE_ENV=1 to load them`
|
|
@@ -12378,8 +12644,8 @@ function registerCheckCommand(program2) {
|
|
|
12378
12644
|
}
|
|
12379
12645
|
|
|
12380
12646
|
// src/commands/doc/convert.ts
|
|
12381
|
-
import
|
|
12382
|
-
import
|
|
12647
|
+
import fs48 from "fs";
|
|
12648
|
+
import path54 from "path";
|
|
12383
12649
|
|
|
12384
12650
|
// src/commands/doc/default-prompt.ts
|
|
12385
12651
|
var DEFAULT_CONVERT_PROMPT = `Convert this document to clean, well-formatted Markdown.
|
|
@@ -12397,7 +12663,7 @@ Output only the markdown content without any preamble or explanation.`;
|
|
|
12397
12663
|
// src/commands/doc/convert.ts
|
|
12398
12664
|
function autoNameBase(input) {
|
|
12399
12665
|
if (input === STDIN_ARG) return "stdin";
|
|
12400
|
-
return
|
|
12666
|
+
return path54.basename(input, path54.extname(input));
|
|
12401
12667
|
}
|
|
12402
12668
|
async function convertDocuments(opts) {
|
|
12403
12669
|
const { output, autoName = false, verbose, logger } = opts;
|
|
@@ -12414,13 +12680,13 @@ async function convertDocuments(opts) {
|
|
|
12414
12680
|
try {
|
|
12415
12681
|
let outputPath;
|
|
12416
12682
|
if (output) {
|
|
12417
|
-
outputPath =
|
|
12683
|
+
outputPath = path54.resolve(output);
|
|
12418
12684
|
} else if (autoName && opts.inputs.length === 1 && opts.inputs[0]) {
|
|
12419
|
-
outputPath =
|
|
12685
|
+
outputPath = path54.join(getOutputDir(), `${autoNameBase(opts.inputs[0])}-extraction.md`);
|
|
12420
12686
|
} else {
|
|
12421
|
-
outputPath =
|
|
12687
|
+
outputPath = path54.join(getOutputDir(), "document-extraction.md");
|
|
12422
12688
|
}
|
|
12423
|
-
|
|
12689
|
+
fs48.mkdirSync(path54.dirname(outputPath), { recursive: true });
|
|
12424
12690
|
failed = await convertResolved(inputs, { outputPath, model, prompt, verbose, logger });
|
|
12425
12691
|
} finally {
|
|
12426
12692
|
cleanup();
|
|
@@ -12493,7 +12759,7 @@ async function convertResolved(inputs, ctx) {
|
|
|
12493
12759
|
`);
|
|
12494
12760
|
for (const r of results) {
|
|
12495
12761
|
lines.push(`
|
|
12496
|
-
## ${
|
|
12762
|
+
## ${path54.basename(r.file).replace(/[<>]/g, "\\$&")}
|
|
12497
12763
|
`);
|
|
12498
12764
|
if (r.status === "success" && r.markdown) {
|
|
12499
12765
|
lines.push(`${r.markdown}
|
|
@@ -12506,7 +12772,7 @@ async function convertResolved(inputs, ctx) {
|
|
|
12506
12772
|
}
|
|
12507
12773
|
lines.push("\n---\n");
|
|
12508
12774
|
}
|
|
12509
|
-
|
|
12775
|
+
fs48.writeFileSync(outputPath, lines.join("\n"), "utf8");
|
|
12510
12776
|
const succeeded = results.filter((r) => r.status === "success").length;
|
|
12511
12777
|
console.log(`
|
|
12512
12778
|
${"=".repeat(50)}`);
|
|
@@ -12560,12 +12826,12 @@ function registerDocCommands(program2) {
|
|
|
12560
12826
|
|
|
12561
12827
|
// src/commands/media/index.ts
|
|
12562
12828
|
init_errors();
|
|
12563
|
-
import
|
|
12564
|
-
import
|
|
12829
|
+
import fs51 from "fs";
|
|
12830
|
+
import path57 from "path";
|
|
12565
12831
|
|
|
12566
12832
|
// src/commands/media/batch.ts
|
|
12567
|
-
import
|
|
12568
|
-
import
|
|
12833
|
+
import fs49 from "fs";
|
|
12834
|
+
import path55 from "path";
|
|
12569
12835
|
|
|
12570
12836
|
// src/commands/media/detect-type.ts
|
|
12571
12837
|
var VIDEO_EXTS2 = /* @__PURE__ */ new Set([".mp4", ".mov", ".avi", ".mkv", ".webm", ".flv", ".mpg", ".mpeg"]);
|
|
@@ -12761,10 +13027,10 @@ async function batchOptimize(opts) {
|
|
|
12761
13027
|
verbose,
|
|
12762
13028
|
logger
|
|
12763
13029
|
} = opts;
|
|
12764
|
-
|
|
12765
|
-
const entries =
|
|
13030
|
+
fs49.mkdirSync(outputDir, { recursive: true });
|
|
13031
|
+
const entries = fs49.readdirSync(inputDir);
|
|
12766
13032
|
const mediaFiles = entries.filter((f) => {
|
|
12767
|
-
const ext =
|
|
13033
|
+
const ext = path55.extname(f).toLowerCase();
|
|
12768
13034
|
return ALL_EXTS.has(ext);
|
|
12769
13035
|
});
|
|
12770
13036
|
if (mediaFiles.length === 0) {
|
|
@@ -12775,9 +13041,9 @@ async function batchOptimize(opts) {
|
|
|
12775
13041
|
let succeeded = 0;
|
|
12776
13042
|
const errors = [];
|
|
12777
13043
|
for (const file of mediaFiles) {
|
|
12778
|
-
const inputPath =
|
|
12779
|
-
const outputPath =
|
|
12780
|
-
const ext =
|
|
13044
|
+
const inputPath = path55.join(inputDir, file);
|
|
13045
|
+
const outputPath = path55.join(outputDir, file);
|
|
13046
|
+
const ext = path55.extname(file).toLowerCase();
|
|
12781
13047
|
const kind = extToKind(ext);
|
|
12782
13048
|
logger?.debug(`Processing: ${file}`);
|
|
12783
13049
|
try {
|
|
@@ -12814,8 +13080,8 @@ async function batchOptimize(opts) {
|
|
|
12814
13080
|
|
|
12815
13081
|
// src/commands/media/split-video.ts
|
|
12816
13082
|
init_errors();
|
|
12817
|
-
import
|
|
12818
|
-
import
|
|
13083
|
+
import fs50 from "fs";
|
|
13084
|
+
import path56 from "path";
|
|
12819
13085
|
async function splitVideo(opts) {
|
|
12820
13086
|
await assertFfmpeg();
|
|
12821
13087
|
const { input, outputDir, chunkDuration = 3600, verbose, logger } = opts;
|
|
@@ -12826,10 +13092,10 @@ async function splitVideo(opts) {
|
|
|
12826
13092
|
logger?.info("Video is short enough \u2014 no splitting needed");
|
|
12827
13093
|
return [input];
|
|
12828
13094
|
}
|
|
12829
|
-
|
|
12830
|
-
const ext =
|
|
12831
|
-
const baseName =
|
|
12832
|
-
const outputPattern =
|
|
13095
|
+
fs50.mkdirSync(outputDir, { recursive: true });
|
|
13096
|
+
const ext = path56.extname(input) || ".mp4";
|
|
13097
|
+
const baseName = path56.basename(input, ext);
|
|
13098
|
+
const outputPattern = path56.join(outputDir, `${baseName}_chunk_%03d${ext}`);
|
|
12833
13099
|
logger?.debug(`Splitting into ~${numChunks} chunks of ${chunkDuration}s each`);
|
|
12834
13100
|
const args = [
|
|
12835
13101
|
"-i",
|
|
@@ -12848,8 +13114,8 @@ async function splitVideo(opts) {
|
|
|
12848
13114
|
await runFfmpeg(args, { verbose, logger });
|
|
12849
13115
|
const files = [];
|
|
12850
13116
|
for (let i = 0; i < numChunks + 1; i++) {
|
|
12851
|
-
const fname =
|
|
12852
|
-
if (
|
|
13117
|
+
const fname = path56.join(outputDir, `${baseName}_chunk_${String(i).padStart(3, "0")}${ext}`);
|
|
13118
|
+
if (fs50.existsSync(fname)) files.push(fname);
|
|
12853
13119
|
}
|
|
12854
13120
|
logger?.success(`Created ${files.length} chunks in ${outputDir}`);
|
|
12855
13121
|
return files;
|
|
@@ -12863,9 +13129,9 @@ function registerMediaCommands(program2) {
|
|
|
12863
13129
|
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(
|
|
12864
13130
|
async (opts) => {
|
|
12865
13131
|
const logger = createLogger({ verbose: opts.verbose ?? false });
|
|
12866
|
-
if (!
|
|
12867
|
-
|
|
12868
|
-
const ext =
|
|
13132
|
+
if (!fs51.existsSync(opts.input)) throw new ValidationError(`Input not found: ${opts.input}`);
|
|
13133
|
+
fs51.mkdirSync(path57.dirname(path57.resolve(opts.output)), { recursive: true });
|
|
13134
|
+
const ext = path57.extname(opts.input).toLowerCase();
|
|
12869
13135
|
const kind = extToKind(ext);
|
|
12870
13136
|
if (!kind) throw new ValidationError(`Unsupported file type: ${ext}`);
|
|
12871
13137
|
const quality = Number.parseInt(opts.quality, 10);
|
|
@@ -12905,7 +13171,7 @@ function registerMediaCommands(program2) {
|
|
|
12905
13171
|
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(
|
|
12906
13172
|
async (opts) => {
|
|
12907
13173
|
const logger = createLogger({ verbose: opts.verbose ?? false });
|
|
12908
|
-
if (!
|
|
13174
|
+
if (!fs51.existsSync(opts.input)) throw new ValidationError(`Input not found: ${opts.input}`);
|
|
12909
13175
|
const chunks = await splitVideo({
|
|
12910
13176
|
input: opts.input,
|
|
12911
13177
|
outputDir: opts.outputDir,
|
|
@@ -12921,7 +13187,7 @@ Created ${chunks.length} chunk(s) in ${opts.outputDir}`);
|
|
|
12921
13187
|
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(
|
|
12922
13188
|
async (opts) => {
|
|
12923
13189
|
const logger = createLogger({ verbose: opts.verbose ?? false });
|
|
12924
|
-
if (!
|
|
13190
|
+
if (!fs51.existsSync(opts.inputDir))
|
|
12925
13191
|
throw new ValidationError(`Input dir not found: ${opts.inputDir}`);
|
|
12926
13192
|
const result = await batchOptimize({
|
|
12927
13193
|
inputDir: opts.inputDir,
|