@mrgoonie/multix 1.0.1 → 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 +14 -0
- package/README.md +6 -5
- package/dist/cli.js +867 -553
- 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";
|
|
@@ -4520,14 +4916,43 @@ var FAL_DEFAULTS = {
|
|
|
4520
4916
|
};
|
|
4521
4917
|
|
|
4522
4918
|
// src/providers/fal/run-helpers.ts
|
|
4523
|
-
|
|
4524
|
-
import
|
|
4919
|
+
init_errors();
|
|
4920
|
+
import fs19 from "fs";
|
|
4921
|
+
import path22 from "path";
|
|
4525
4922
|
init_redact();
|
|
4526
4923
|
|
|
4527
4924
|
// src/providers/fal/client.ts
|
|
4528
4925
|
init_errors();
|
|
4529
4926
|
init_errors();
|
|
4530
4927
|
init_redact();
|
|
4928
|
+
var FAL_QUEUE_HOST = "queue.fal.run";
|
|
4929
|
+
function parseUrl(url) {
|
|
4930
|
+
try {
|
|
4931
|
+
return new URL(url);
|
|
4932
|
+
} catch {
|
|
4933
|
+
return void 0;
|
|
4934
|
+
}
|
|
4935
|
+
}
|
|
4936
|
+
function normalizedHost(u) {
|
|
4937
|
+
return u.hostname.toLowerCase().replace(/\.$/, "");
|
|
4938
|
+
}
|
|
4939
|
+
function normalizedOrigin(u) {
|
|
4940
|
+
return `${u.protocol}//${normalizedHost(u)}:${u.port}`;
|
|
4941
|
+
}
|
|
4942
|
+
function assertTrustedFalUrl(url, baseUrl) {
|
|
4943
|
+
const target = parseUrl(url);
|
|
4944
|
+
if (!target) {
|
|
4945
|
+
throw new ProviderError("Refusing to send the fal key to a malformed URL", "fal");
|
|
4946
|
+
}
|
|
4947
|
+
const base = parseUrl(baseUrl);
|
|
4948
|
+
if (base && normalizedOrigin(target) === normalizedOrigin(base)) return;
|
|
4949
|
+
const host = normalizedHost(target);
|
|
4950
|
+
if (target.protocol === "https:" && host === FAL_QUEUE_HOST && target.port === "") return;
|
|
4951
|
+
throw new ProviderError(
|
|
4952
|
+
`Refusing to send the fal key to ${host || "this URL"}: not https://${FAL_QUEUE_HOST} or the FAL_BASE_URL origin`,
|
|
4953
|
+
"fal"
|
|
4954
|
+
);
|
|
4955
|
+
}
|
|
4531
4956
|
function requireFalKey() {
|
|
4532
4957
|
const key = resolveRoutedKey(FAL_ROUTING);
|
|
4533
4958
|
if (!key) {
|
|
@@ -4546,13 +4971,25 @@ var FalClient = class {
|
|
|
4546
4971
|
apiKey;
|
|
4547
4972
|
baseUrl;
|
|
4548
4973
|
/** `path` is appended verbatim to the base queue URL, e.g. "/fal-ai/flux/schnell". */
|
|
4549
|
-
async request(
|
|
4974
|
+
async request(path58, opts = {}) {
|
|
4550
4975
|
const base = this.baseUrl.replace(/\/$/, "");
|
|
4551
|
-
const suffix =
|
|
4552
|
-
return this.
|
|
4976
|
+
const suffix = path58.startsWith("/") ? path58 : `/${path58}`;
|
|
4977
|
+
return this.send(base + suffix, opts);
|
|
4553
4978
|
}
|
|
4554
|
-
/**
|
|
4979
|
+
/**
|
|
4980
|
+
* Same auth/headers as `request`, but `url` is used verbatim (e.g. a
|
|
4981
|
+
* status_url/response_url returned by submit). The URL must pass
|
|
4982
|
+
* {@link assertTrustedFalUrl} before the key is sent.
|
|
4983
|
+
*/
|
|
4555
4984
|
async requestAbsolute(url, opts = {}) {
|
|
4985
|
+
assertTrustedFalUrl(url, this.baseUrl);
|
|
4986
|
+
return this.send(url, opts);
|
|
4987
|
+
}
|
|
4988
|
+
/** Throws a ProviderError when `url` may not receive the key (see {@link assertTrustedFalUrl}). */
|
|
4989
|
+
assertTrustedUrl(url) {
|
|
4990
|
+
assertTrustedFalUrl(url, this.baseUrl);
|
|
4991
|
+
}
|
|
4992
|
+
async send(url, opts) {
|
|
4556
4993
|
opts.logger?.debug(`${opts.method ?? "GET"} ${redactUrl(url)}`);
|
|
4557
4994
|
return httpJson({
|
|
4558
4995
|
url,
|
|
@@ -4565,11 +5002,11 @@ var FalClient = class {
|
|
|
4565
5002
|
timeoutMs: opts.timeoutMs ?? 12e4
|
|
4566
5003
|
});
|
|
4567
5004
|
}
|
|
4568
|
-
get(
|
|
4569
|
-
return this.request(
|
|
5005
|
+
get(path58, logger) {
|
|
5006
|
+
return this.request(path58, { method: "GET", logger });
|
|
4570
5007
|
}
|
|
4571
|
-
post(
|
|
4572
|
-
return this.request(
|
|
5008
|
+
post(path58, body, logger) {
|
|
5009
|
+
return this.request(path58, { method: "POST", body, logger });
|
|
4573
5010
|
}
|
|
4574
5011
|
getAbsolute(url, logger) {
|
|
4575
5012
|
return this.requestAbsolute(url, { method: "GET", logger });
|
|
@@ -4620,6 +5057,13 @@ async function submitAndWait(opts) {
|
|
|
4620
5057
|
const submitRes = await client.post(submitPath(model), input, logger);
|
|
4621
5058
|
const requestId = submitRes.request_id;
|
|
4622
5059
|
logger.debug(`request_id: ${requestId}`);
|
|
5060
|
+
for (const field of ["status_url", "response_url"]) {
|
|
5061
|
+
const url = submitRes[field];
|
|
5062
|
+
if (typeof url !== "string" || url === "") {
|
|
5063
|
+
throw new ProviderError(`fal submit response is missing ${field}`, "fal");
|
|
5064
|
+
}
|
|
5065
|
+
client.assertTrustedUrl(url);
|
|
5066
|
+
}
|
|
4623
5067
|
logger.info("Polling for completion...");
|
|
4624
5068
|
const waitTimeoutMs = opts.waitTimeoutMs ?? 48e4;
|
|
4625
5069
|
const intervalMs = opts.pollIntervalMs ?? 3e3;
|
|
@@ -4711,7 +5155,7 @@ function extractMediaUrls(result) {
|
|
|
4711
5155
|
function extFromUrl(url, fallback = ".bin") {
|
|
4712
5156
|
try {
|
|
4713
5157
|
const u = new URL(url);
|
|
4714
|
-
const e =
|
|
5158
|
+
const e = path22.extname(u.pathname);
|
|
4715
5159
|
return e.length > 0 ? e : fallback;
|
|
4716
5160
|
} catch {
|
|
4717
5161
|
return fallback;
|
|
@@ -4724,13 +5168,13 @@ async function downloadResultMedia(urls, outDir, requestId, logger) {
|
|
|
4724
5168
|
if (!url) continue;
|
|
4725
5169
|
const ext = extFromUrl(url);
|
|
4726
5170
|
const filename = `fal-${requestId.slice(0, 8)}-${String(i + 1).padStart(2, "0")}${ext}`;
|
|
4727
|
-
const filepath =
|
|
5171
|
+
const filepath = path22.join(outDir, filename);
|
|
4728
5172
|
try {
|
|
4729
5173
|
await downloadFile(url, filepath);
|
|
4730
5174
|
saved.push(filepath);
|
|
4731
5175
|
logger.success(`Saved ${filepath}`);
|
|
4732
5176
|
} catch (e) {
|
|
4733
|
-
|
|
5177
|
+
fs19.rmSync(filepath, { force: true });
|
|
4734
5178
|
logger.warn(`Download failed for ${redactUrl(url)}: ${e instanceof Error ? e.message : e}`);
|
|
4735
5179
|
}
|
|
4736
5180
|
}
|
|
@@ -4775,9 +5219,9 @@ function registerFalImageCommand(parent) {
|
|
|
4775
5219
|
process.exit(1);
|
|
4776
5220
|
}
|
|
4777
5221
|
if (opts.output && saved[0]) {
|
|
4778
|
-
const dest =
|
|
4779
|
-
|
|
4780
|
-
|
|
5222
|
+
const dest = path23.resolve(opts.output);
|
|
5223
|
+
fs20.mkdirSync(path23.dirname(dest), { recursive: true });
|
|
5224
|
+
fs20.copyFileSync(saved[0], dest);
|
|
4781
5225
|
logger.success(`Copied to ${dest}`);
|
|
4782
5226
|
}
|
|
4783
5227
|
const finalImages = finalizeGeneratedImages(saved, { ...opts, logger });
|
|
@@ -4932,9 +5376,9 @@ function registerFalCommands(program2) {
|
|
|
4932
5376
|
|
|
4933
5377
|
// src/core/stdin-file.ts
|
|
4934
5378
|
init_errors();
|
|
4935
|
-
import
|
|
4936
|
-
import
|
|
4937
|
-
import
|
|
5379
|
+
import fs21 from "fs";
|
|
5380
|
+
import os3 from "os";
|
|
5381
|
+
import path24 from "path";
|
|
4938
5382
|
|
|
4939
5383
|
// src/core/limits.ts
|
|
4940
5384
|
var FILES_API_MAX_BYTES = 2e9;
|
|
@@ -5039,7 +5483,7 @@ function trackTempDir(dir) {
|
|
|
5039
5483
|
let done = false;
|
|
5040
5484
|
const remove = () => {
|
|
5041
5485
|
try {
|
|
5042
|
-
|
|
5486
|
+
fs21.rmSync(dir, { recursive: true, force: true, maxRetries: 3, retryDelay: 100 });
|
|
5043
5487
|
} catch {
|
|
5044
5488
|
}
|
|
5045
5489
|
};
|
|
@@ -5071,9 +5515,9 @@ async function stdinToTempFile(opts = {}) {
|
|
|
5071
5515
|
}
|
|
5072
5516
|
const forcedExt = opts.ext ? normalizeExt(opts.ext) : void 0;
|
|
5073
5517
|
const maxBytes = opts.maxBytes ?? MAX_STDIN_BYTES;
|
|
5074
|
-
const dir =
|
|
5518
|
+
const dir = fs21.mkdtempSync(path24.join(os3.tmpdir(), "multix-stdin-"));
|
|
5075
5519
|
const cleanup = trackTempDir(dir);
|
|
5076
|
-
const rawPath =
|
|
5520
|
+
const rawPath = path24.join(dir, "input");
|
|
5077
5521
|
const hint = setTimeout(() => {
|
|
5078
5522
|
process.stderr.write(`Waiting for data on stdin (\`${flag} -\`)... press Ctrl+C to cancel
|
|
5079
5523
|
`);
|
|
@@ -5082,7 +5526,7 @@ async function stdinToTempFile(opts = {}) {
|
|
|
5082
5526
|
let head = Buffer.alloc(0);
|
|
5083
5527
|
let size = 0;
|
|
5084
5528
|
try {
|
|
5085
|
-
const fd =
|
|
5529
|
+
const fd = fs21.openSync(rawPath, "w", 384);
|
|
5086
5530
|
try {
|
|
5087
5531
|
for await (const chunk of input) {
|
|
5088
5532
|
clearTimeout(hint);
|
|
@@ -5094,17 +5538,17 @@ async function stdinToTempFile(opts = {}) {
|
|
|
5094
5538
|
head = Buffer.concat([head, buf.subarray(0, HEAD_BYTES - head.length)]);
|
|
5095
5539
|
}
|
|
5096
5540
|
let offset = 0;
|
|
5097
|
-
while (offset < buf.length) offset +=
|
|
5541
|
+
while (offset < buf.length) offset += fs21.writeSync(fd, buf, offset);
|
|
5098
5542
|
size += buf.length;
|
|
5099
5543
|
}
|
|
5100
5544
|
} finally {
|
|
5101
5545
|
clearTimeout(hint);
|
|
5102
|
-
|
|
5546
|
+
fs21.closeSync(fd);
|
|
5103
5547
|
}
|
|
5104
5548
|
if (size === 0) throw new ValidationError(`\`${flag} -\` was given but stdin was empty`);
|
|
5105
5549
|
const detectedExt = forcedExt ?? detectExtension(head);
|
|
5106
|
-
const finalPath =
|
|
5107
|
-
|
|
5550
|
+
const finalPath = path24.join(dir, `stdin${detectedExt ?? ".bin"}`);
|
|
5551
|
+
fs21.renameSync(rawPath, finalPath);
|
|
5108
5552
|
return { path: finalPath, size, detected: detectedExt !== void 0, cleanup };
|
|
5109
5553
|
} catch (error) {
|
|
5110
5554
|
cleanup();
|
|
@@ -5122,7 +5566,7 @@ async function resolveFileInputs(files, opts = {}) {
|
|
|
5122
5566
|
throw new ValidationError(`--stdin-ext only applies when ${flag} includes \`-\``);
|
|
5123
5567
|
}
|
|
5124
5568
|
for (const f of files) {
|
|
5125
|
-
if (f !== STDIN_ARG && !
|
|
5569
|
+
if (f !== STDIN_ARG && !fs21.existsSync(f)) throw new ValidationError(`File not found: ${f}`);
|
|
5126
5570
|
}
|
|
5127
5571
|
if (stdinCount === 0) {
|
|
5128
5572
|
return { inputs: files.map((f) => ({ path: f, label: f })), cleanup: () => {
|
|
@@ -5410,12 +5854,12 @@ async function withGeminiKey(fn, opts = {}) {
|
|
|
5410
5854
|
}
|
|
5411
5855
|
|
|
5412
5856
|
// src/providers/gemini/local-files.ts
|
|
5413
|
-
import
|
|
5414
|
-
import
|
|
5857
|
+
import fs22 from "fs";
|
|
5858
|
+
import path26 from "path";
|
|
5415
5859
|
init_errors();
|
|
5416
5860
|
|
|
5417
5861
|
// src/providers/gemini/task-resolver.ts
|
|
5418
|
-
import
|
|
5862
|
+
import path25 from "path";
|
|
5419
5863
|
var MIME_MAP = {
|
|
5420
5864
|
// Audio
|
|
5421
5865
|
".mp3": "audio/mp3",
|
|
@@ -5456,7 +5900,7 @@ var MIME_MAP = {
|
|
|
5456
5900
|
".pptx": "application/vnd.openxmlformats-officedocument.presentationml.presentation"
|
|
5457
5901
|
};
|
|
5458
5902
|
function getMimeType(filePath) {
|
|
5459
|
-
const ext =
|
|
5903
|
+
const ext = path25.extname(filePath).toLowerCase();
|
|
5460
5904
|
return MIME_MAP[ext] ?? "application/octet-stream";
|
|
5461
5905
|
}
|
|
5462
5906
|
|
|
@@ -5476,10 +5920,10 @@ function minUploadBytesPerSec() {
|
|
|
5476
5920
|
}
|
|
5477
5921
|
async function uploadLocalFile(filePath, opts = {}) {
|
|
5478
5922
|
const bytesPerSec = minUploadBytesPerSec();
|
|
5479
|
-
const blob = await
|
|
5923
|
+
const blob = await fs22.openAsBlob(filePath);
|
|
5480
5924
|
const source = {
|
|
5481
5925
|
blob,
|
|
5482
|
-
displayName:
|
|
5926
|
+
displayName: path26.basename(filePath),
|
|
5483
5927
|
mimeType: getMimeType(filePath),
|
|
5484
5928
|
label: filePath
|
|
5485
5929
|
};
|
|
@@ -5487,12 +5931,12 @@ async function uploadLocalFile(filePath, opts = {}) {
|
|
|
5487
5931
|
return opts.apiKey ? upload(opts.apiKey) : withGeminiKey(upload, { logger: opts.logger });
|
|
5488
5932
|
}
|
|
5489
5933
|
function inlinePart(filePath) {
|
|
5490
|
-
const data =
|
|
5934
|
+
const data = fs22.readFileSync(filePath).toString("base64");
|
|
5491
5935
|
const mimeType = getMimeType(filePath);
|
|
5492
5936
|
return { inlineData: { mimeType, data } };
|
|
5493
5937
|
}
|
|
5494
5938
|
function shouldUseFileApi(filePath) {
|
|
5495
|
-
const stat =
|
|
5939
|
+
const stat = fs22.statSync(filePath);
|
|
5496
5940
|
return stat.size > INLINE_MAX_BYTES;
|
|
5497
5941
|
}
|
|
5498
5942
|
|
|
@@ -5543,13 +5987,13 @@ function getDefaultModel(task) {
|
|
|
5543
5987
|
}
|
|
5544
5988
|
|
|
5545
5989
|
// src/providers/gemini/result-formatter.ts
|
|
5546
|
-
import
|
|
5547
|
-
import
|
|
5990
|
+
import fs23 from "fs";
|
|
5991
|
+
import path27 from "path";
|
|
5548
5992
|
function formatResults(results, format, outputPath) {
|
|
5549
5993
|
const content = renderFormat(results, format);
|
|
5550
5994
|
if (outputPath) {
|
|
5551
|
-
|
|
5552
|
-
|
|
5995
|
+
fs23.mkdirSync(path27.dirname(path27.resolve(outputPath)), { recursive: true });
|
|
5996
|
+
fs23.writeFileSync(outputPath, content, "utf8");
|
|
5553
5997
|
} else {
|
|
5554
5998
|
process.stdout.write(content);
|
|
5555
5999
|
}
|
|
@@ -5733,8 +6177,8 @@ function registerExtractCommand(parent) {
|
|
|
5733
6177
|
init_errors();
|
|
5734
6178
|
|
|
5735
6179
|
// src/providers/gemini/generators/speech.ts
|
|
5736
|
-
import
|
|
5737
|
-
import
|
|
6180
|
+
import fs24 from "fs";
|
|
6181
|
+
import path28 from "path";
|
|
5738
6182
|
|
|
5739
6183
|
// src/providers/gemini/voices.ts
|
|
5740
6184
|
var GEMINI_TTS_VOICES = [
|
|
@@ -5906,12 +6350,12 @@ function extractWavData(wav) {
|
|
|
5906
6350
|
}
|
|
5907
6351
|
function saveSpeech(outBytes, outputFormat, output, logger) {
|
|
5908
6352
|
const outDir = getOutputDir();
|
|
5909
|
-
const dest =
|
|
5910
|
-
|
|
6353
|
+
const dest = path28.join(outDir, `gemini_speech_${Date.now()}.${outputFormat}`);
|
|
6354
|
+
fs24.writeFileSync(dest, outBytes);
|
|
5911
6355
|
logger?.success(`Saved: ${dest} (${(outBytes.length / 1024).toFixed(1)} KB)`);
|
|
5912
6356
|
if (output) {
|
|
5913
|
-
|
|
5914
|
-
|
|
6357
|
+
fs24.mkdirSync(path28.dirname(path28.resolve(output)), { recursive: true });
|
|
6358
|
+
fs24.copyFileSync(dest, output);
|
|
5915
6359
|
logger?.success(`Copied to: ${output}`);
|
|
5916
6360
|
}
|
|
5917
6361
|
return dest;
|
|
@@ -6024,24 +6468,24 @@ function collectSpeakers(value, prev) {
|
|
|
6024
6468
|
|
|
6025
6469
|
// src/providers/gemini/commands/generate-video.ts
|
|
6026
6470
|
init_errors();
|
|
6027
|
-
import
|
|
6028
|
-
import
|
|
6471
|
+
import fs26 from "fs";
|
|
6472
|
+
import path29 from "path";
|
|
6029
6473
|
|
|
6030
6474
|
// src/providers/gemini/veo.ts
|
|
6031
|
-
import
|
|
6475
|
+
import fs25 from "fs";
|
|
6032
6476
|
init_errors();
|
|
6033
6477
|
var API_HOST = "generativelanguage.googleapis.com";
|
|
6034
6478
|
var BASE2 = `https://${API_HOST}/v1beta`;
|
|
6035
6479
|
var VEO_IMAGE_TYPES = /* @__PURE__ */ new Set(["image/jpeg", "image/png", "image/webp"]);
|
|
6036
6480
|
var VEO_TIMEOUT_MS = 10 * 60 * 1e3;
|
|
6037
6481
|
function veoImageFromFile(imgPath) {
|
|
6038
|
-
if (!
|
|
6482
|
+
if (!fs25.existsSync(imgPath)) throw new ValidationError(`Image not found: ${imgPath}`);
|
|
6039
6483
|
const detected = getMimeType(imgPath);
|
|
6040
6484
|
const mimeType = detected === "application/octet-stream" ? "image/jpeg" : detected;
|
|
6041
6485
|
if (!VEO_IMAGE_TYPES.has(mimeType)) {
|
|
6042
6486
|
throw new ValidationError(`Veo accepts JPEG, PNG or WebP images: ${imgPath} (${mimeType})`);
|
|
6043
6487
|
}
|
|
6044
|
-
return { bytesBase64Encoded:
|
|
6488
|
+
return { bytesBase64Encoded: fs25.readFileSync(imgPath).toString("base64"), mimeType };
|
|
6045
6489
|
}
|
|
6046
6490
|
function veoFramesFromPaths(paths = []) {
|
|
6047
6491
|
if (paths.length > 2) {
|
|
@@ -6167,7 +6611,7 @@ function registerGenerateVideoCommand(parent) {
|
|
|
6167
6611
|
logger.debug(`Submitting video generation with ${model}...`);
|
|
6168
6612
|
logger.info("This may take 30s\u20136 minutes. Veo requires billing.");
|
|
6169
6613
|
const frames = veoFramesFromPaths(opts.referenceImages);
|
|
6170
|
-
const dest =
|
|
6614
|
+
const dest = path29.join(getOutputDir(), `veo_generated_${Date.now()}.mp4`);
|
|
6171
6615
|
const finalOp = await runVeo(
|
|
6172
6616
|
model,
|
|
6173
6617
|
{
|
|
@@ -6181,8 +6625,8 @@ function registerGenerateVideoCommand(parent) {
|
|
|
6181
6625
|
);
|
|
6182
6626
|
logger.success(`Saved video: ${dest}`);
|
|
6183
6627
|
if (opts.output) {
|
|
6184
|
-
|
|
6185
|
-
|
|
6628
|
+
fs26.mkdirSync(path29.dirname(path29.resolve(opts.output)), { recursive: true });
|
|
6629
|
+
fs26.copyFileSync(dest, opts.output);
|
|
6186
6630
|
logger.success(`Copied to: ${opts.output}`);
|
|
6187
6631
|
}
|
|
6188
6632
|
await maybeDownloadThumb(finalOp.response, dest, {
|
|
@@ -6198,8 +6642,8 @@ Generated video: ${dest}`);
|
|
|
6198
6642
|
|
|
6199
6643
|
// src/providers/gemini/commands/generate.ts
|
|
6200
6644
|
init_errors();
|
|
6201
|
-
import
|
|
6202
|
-
import
|
|
6645
|
+
import fs27 from "fs";
|
|
6646
|
+
import path30 from "path";
|
|
6203
6647
|
function registerGenerateCommand(parent) {
|
|
6204
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(
|
|
6205
6649
|
async (opts) => {
|
|
@@ -6247,8 +6691,8 @@ function registerGenerateCommand(parent) {
|
|
|
6247
6691
|
const outDir = getOutputDir();
|
|
6248
6692
|
const ext = img.mimeType.includes("png") ? "png" : "jpg";
|
|
6249
6693
|
const fname = `gemini_generated_${Date.now()}_${i}.${ext}`;
|
|
6250
|
-
const dest =
|
|
6251
|
-
|
|
6694
|
+
const dest = path30.join(outDir, fname);
|
|
6695
|
+
fs27.writeFileSync(dest, Buffer.from(img.data, "base64"));
|
|
6252
6696
|
savedFiles.push(dest);
|
|
6253
6697
|
logger.success(`Saved: ${dest}`);
|
|
6254
6698
|
}
|
|
@@ -6263,8 +6707,8 @@ function registerGenerateCommand(parent) {
|
|
|
6263
6707
|
process.exit(1);
|
|
6264
6708
|
}
|
|
6265
6709
|
if (opts.output && savedFiles[0]) {
|
|
6266
|
-
|
|
6267
|
-
|
|
6710
|
+
fs27.mkdirSync(path30.dirname(path30.resolve(opts.output)), { recursive: true });
|
|
6711
|
+
fs27.copyFileSync(savedFiles[0], opts.output);
|
|
6268
6712
|
logger.success(`Copied to: ${opts.output}`);
|
|
6269
6713
|
}
|
|
6270
6714
|
const finalImages = finalizeGeneratedImages(savedFiles, { ...opts, logger });
|
|
@@ -6276,8 +6720,8 @@ Generated ${finalImages.length} image(s):`);
|
|
|
6276
6720
|
}
|
|
6277
6721
|
|
|
6278
6722
|
// src/providers/gemini/commands/image-to-image.ts
|
|
6279
|
-
import
|
|
6280
|
-
import
|
|
6723
|
+
import fs28 from "fs";
|
|
6724
|
+
import path31 from "path";
|
|
6281
6725
|
var SOFT_LIMIT_BYTES = 5 * 1024 * 1024;
|
|
6282
6726
|
var HARD_LIMIT_BYTES = 7 * 1024 * 1024;
|
|
6283
6727
|
function registerGeminiImageToImageCommand(parent) {
|
|
@@ -6340,14 +6784,14 @@ function registerGeminiImageToImageCommand(parent) {
|
|
|
6340
6784
|
const img = images[i];
|
|
6341
6785
|
if (!img) continue;
|
|
6342
6786
|
const ext = img.mimeType.includes("png") ? "png" : "jpg";
|
|
6343
|
-
const dest =
|
|
6344
|
-
|
|
6787
|
+
const dest = path31.join(outDir, `gemini-i2i-${ts}-${i + 1}.${ext}`);
|
|
6788
|
+
fs28.writeFileSync(dest, Buffer.from(img.data, "base64"));
|
|
6345
6789
|
saved.push(dest);
|
|
6346
6790
|
logger.success(`Saved: ${dest}`);
|
|
6347
6791
|
}
|
|
6348
6792
|
if (opts.output && saved[0]) {
|
|
6349
|
-
|
|
6350
|
-
|
|
6793
|
+
fs28.mkdirSync(path31.dirname(path31.resolve(opts.output)), { recursive: true });
|
|
6794
|
+
fs28.copyFileSync(saved[0], opts.output);
|
|
6351
6795
|
logger.success(`Copied to: ${opts.output}`);
|
|
6352
6796
|
}
|
|
6353
6797
|
const finalImages = finalizeGeneratedImages(saved, { ...opts, logger });
|
|
@@ -6375,7 +6819,7 @@ function sniffImageMime(buf, url) {
|
|
|
6375
6819
|
if (buf.length >= 12 && buf.slice(0, 4).toString() === "RIFF" && buf.slice(8, 12).toString() === "WEBP")
|
|
6376
6820
|
return "image/webp";
|
|
6377
6821
|
}
|
|
6378
|
-
const ext =
|
|
6822
|
+
const ext = path31.extname(new URL(url).pathname).toLowerCase();
|
|
6379
6823
|
if (ext === ".png") return "image/png";
|
|
6380
6824
|
if (ext === ".webp") return "image/webp";
|
|
6381
6825
|
if (ext === ".gif") return "image/gif";
|
|
@@ -6384,8 +6828,8 @@ function sniffImageMime(buf, url) {
|
|
|
6384
6828
|
|
|
6385
6829
|
// src/providers/gemini/commands/image-to-video.ts
|
|
6386
6830
|
init_errors();
|
|
6387
|
-
import
|
|
6388
|
-
import
|
|
6831
|
+
import fs29 from "fs";
|
|
6832
|
+
import path32 from "path";
|
|
6389
6833
|
function registerGeminiImageToVideoCommand(parent) {
|
|
6390
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(
|
|
6391
6835
|
async (imagePath, opts) => {
|
|
@@ -6405,7 +6849,7 @@ function registerGeminiImageToVideoCommand(parent) {
|
|
|
6405
6849
|
logger.info(
|
|
6406
6850
|
"Submitting image-to-video. This may take 30s\u20136 minutes. Veo requires billing."
|
|
6407
6851
|
);
|
|
6408
|
-
const dest =
|
|
6852
|
+
const dest = path32.join(getOutputDir(), `veo_i2v_${Date.now()}.mp4`);
|
|
6409
6853
|
const finalOp = await runVeo(
|
|
6410
6854
|
model,
|
|
6411
6855
|
{
|
|
@@ -6420,8 +6864,8 @@ function registerGeminiImageToVideoCommand(parent) {
|
|
|
6420
6864
|
);
|
|
6421
6865
|
logger.success(`Saved video: ${dest}`);
|
|
6422
6866
|
if (opts.output) {
|
|
6423
|
-
|
|
6424
|
-
|
|
6867
|
+
fs29.mkdirSync(path32.dirname(path32.resolve(opts.output)), { recursive: true });
|
|
6868
|
+
fs29.copyFileSync(dest, opts.output);
|
|
6425
6869
|
logger.success(`Copied to: ${opts.output}`);
|
|
6426
6870
|
}
|
|
6427
6871
|
await maybeDownloadThumb(finalOp.response, dest, {
|
|
@@ -6626,8 +7070,8 @@ var HEYGEN_OPERATIONS = {
|
|
|
6626
7070
|
};
|
|
6627
7071
|
|
|
6628
7072
|
// src/providers/heygen/node-helpers.ts
|
|
6629
|
-
import
|
|
6630
|
-
import
|
|
7073
|
+
import fs30 from "fs";
|
|
7074
|
+
import path33 from "path";
|
|
6631
7075
|
init_errors();
|
|
6632
7076
|
init_redact();
|
|
6633
7077
|
|
|
@@ -6740,12 +7184,12 @@ function describePayload(data) {
|
|
|
6740
7184
|
);
|
|
6741
7185
|
return (text ?? String(data)).slice(0, 500);
|
|
6742
7186
|
}
|
|
6743
|
-
function requireJobId(data, field,
|
|
7187
|
+
function requireJobId(data, field, path58) {
|
|
6744
7188
|
const value = data?.[field];
|
|
6745
7189
|
const ok2 = Array.isArray(value) ? value.length > 0 && value.every((v) => typeof v === "string" && v.length > 0) : typeof value === "string" && value.length > 0;
|
|
6746
7190
|
if (!ok2) {
|
|
6747
7191
|
throw new ProviderError(
|
|
6748
|
-
`HeyGen ${
|
|
7192
|
+
`HeyGen ${path58} returned no ${field}; a job may still have been created, so check the HeyGen dashboard. Response: ${describePayload(data)}`,
|
|
6749
7193
|
"heygen"
|
|
6750
7194
|
);
|
|
6751
7195
|
}
|
|
@@ -6770,8 +7214,8 @@ var HeygenClient = class {
|
|
|
6770
7214
|
this.newIdempotencyKey = opts.newIdempotencyKey ?? (() => crypto.randomUUID());
|
|
6771
7215
|
}
|
|
6772
7216
|
/** Build an absolute URL for a v3 path plus query parameters. */
|
|
6773
|
-
url(
|
|
6774
|
-
const suffix =
|
|
7217
|
+
url(path58, query) {
|
|
7218
|
+
const suffix = path58.startsWith("/") ? path58 : `/${path58}`;
|
|
6775
7219
|
const params = new URLSearchParams();
|
|
6776
7220
|
for (const [k, v] of Object.entries(query ?? {})) {
|
|
6777
7221
|
if (v !== void 0 && v !== "") params.set(k, String(v));
|
|
@@ -6793,10 +7237,10 @@ var HeygenClient = class {
|
|
|
6793
7237
|
* `request_in_progress` are retried only when an Idempotency-Key makes the
|
|
6794
7238
|
* replay safe. GET requests are left to the caller's polling loop.
|
|
6795
7239
|
*/
|
|
6796
|
-
async send(
|
|
7240
|
+
async send(path58, opts = {}) {
|
|
6797
7241
|
const method = opts.method ?? "GET";
|
|
6798
7242
|
const key = typeof opts.idempotencyKey === "string" ? opts.idempotencyKey : opts.idempotencyKey ? this.newIdempotencyKey() : void 0;
|
|
6799
|
-
const url = this.url(
|
|
7243
|
+
const url = this.url(path58, opts.query);
|
|
6800
7244
|
for (let attempt = 1; ; attempt++) {
|
|
6801
7245
|
try {
|
|
6802
7246
|
return await fetchJson({
|
|
@@ -6830,18 +7274,18 @@ var HeygenClient = class {
|
|
|
6830
7274
|
return void 0;
|
|
6831
7275
|
}
|
|
6832
7276
|
/** Send and unwrap `{ data }`. A 2xx body carrying `error` is treated as a failure. */
|
|
6833
|
-
async request(
|
|
6834
|
-
const json = await this.send(
|
|
6835
|
-
return unwrapData(json,
|
|
7277
|
+
async request(path58, opts = {}) {
|
|
7278
|
+
const json = await this.send(path58, opts);
|
|
7279
|
+
return unwrapData(json, path58);
|
|
6836
7280
|
}
|
|
6837
|
-
async page(
|
|
6838
|
-
const json = await this.send(
|
|
7281
|
+
async page(path58, query) {
|
|
7282
|
+
const json = await this.send(path58, {
|
|
6839
7283
|
query
|
|
6840
7284
|
});
|
|
6841
|
-
const items = unwrapData(json,
|
|
7285
|
+
const items = unwrapData(json, path58);
|
|
6842
7286
|
if (!Array.isArray(items)) {
|
|
6843
7287
|
throw new ProviderError(
|
|
6844
|
-
`Unexpected HeyGen response from ${
|
|
7288
|
+
`Unexpected HeyGen response from ${path58}: data is not a list`,
|
|
6845
7289
|
"heygen"
|
|
6846
7290
|
);
|
|
6847
7291
|
}
|
|
@@ -6859,9 +7303,9 @@ var HeygenClient = class {
|
|
|
6859
7303
|
// ── Video Agent ───────────────────────────────────────────────────────
|
|
6860
7304
|
/** POST /v3/video-agents. No Idempotency-Key: the endpoint does not document one. */
|
|
6861
7305
|
async createVideoAgent(body) {
|
|
6862
|
-
const
|
|
6863
|
-
const data = await this.request(
|
|
6864
|
-
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);
|
|
6865
7309
|
}
|
|
6866
7310
|
async getVideoAgentSession(sessionId, opts = {}) {
|
|
6867
7311
|
return this.request(`/v3/video-agents/${seg(sessionId, "session id")}`, opts);
|
|
@@ -6871,36 +7315,36 @@ var HeygenClient = class {
|
|
|
6871
7315
|
}
|
|
6872
7316
|
// ── Videos ────────────────────────────────────────────────────────────
|
|
6873
7317
|
async createVideo(body, idempotencyKey = true) {
|
|
6874
|
-
const
|
|
6875
|
-
const data = await this.request(
|
|
7318
|
+
const path58 = "/v3/videos";
|
|
7319
|
+
const data = await this.request(path58, {
|
|
6876
7320
|
method: "POST",
|
|
6877
7321
|
body,
|
|
6878
7322
|
idempotencyKey
|
|
6879
7323
|
});
|
|
6880
|
-
return requireJobId(data, "video_id",
|
|
7324
|
+
return requireJobId(data, "video_id", path58);
|
|
6881
7325
|
}
|
|
6882
7326
|
async getVideo(videoId, opts = {}) {
|
|
6883
7327
|
return this.request(`/v3/videos/${seg(videoId, "video id")}`, opts);
|
|
6884
7328
|
}
|
|
6885
7329
|
/** POST /v3/templates/{id}. Returns the created video's `VideoDetail`. */
|
|
6886
7330
|
async generateFromTemplate(templateId, body, idempotencyKey = true) {
|
|
6887
|
-
const
|
|
6888
|
-
const data = await this.request(
|
|
7331
|
+
const path58 = `/v3/templates/${seg(templateId, "template id")}`;
|
|
7332
|
+
const data = await this.request(path58, {
|
|
6889
7333
|
method: "POST",
|
|
6890
7334
|
body,
|
|
6891
7335
|
idempotencyKey
|
|
6892
7336
|
});
|
|
6893
|
-
return requireJobId(data, "id",
|
|
7337
|
+
return requireJobId(data, "id", path58);
|
|
6894
7338
|
}
|
|
6895
7339
|
// ── HeyGen Video model ────────────────────────────────────────────────
|
|
6896
7340
|
async createModelVideo(body, idempotencyKey = true) {
|
|
6897
|
-
const
|
|
6898
|
-
const data = await this.request(
|
|
7341
|
+
const path58 = "/v3/models/videos";
|
|
7342
|
+
const data = await this.request(path58, {
|
|
6899
7343
|
method: "POST",
|
|
6900
7344
|
body,
|
|
6901
7345
|
idempotencyKey
|
|
6902
7346
|
});
|
|
6903
|
-
return requireJobId(data, "video_id",
|
|
7347
|
+
return requireJobId(data, "video_id", path58);
|
|
6904
7348
|
}
|
|
6905
7349
|
async getModelVideo(videoId, opts = {}) {
|
|
6906
7350
|
return this.request(`/v3/models/videos/${seg(videoId, "video id")}`, opts);
|
|
@@ -6922,13 +7366,13 @@ var HeygenClient = class {
|
|
|
6922
7366
|
}
|
|
6923
7367
|
// ── Translation & lipsync ─────────────────────────────────────────────
|
|
6924
7368
|
async createVideoTranslation(body, idempotencyKey = true) {
|
|
6925
|
-
const
|
|
6926
|
-
const data = await this.request(
|
|
7369
|
+
const path58 = "/v3/video-translations";
|
|
7370
|
+
const data = await this.request(path58, {
|
|
6927
7371
|
method: "POST",
|
|
6928
7372
|
body,
|
|
6929
7373
|
idempotencyKey
|
|
6930
7374
|
});
|
|
6931
|
-
return requireJobId(data, "video_translation_ids",
|
|
7375
|
+
return requireJobId(data, "video_translation_ids", path58);
|
|
6932
7376
|
}
|
|
6933
7377
|
async getVideoTranslation(id, opts = {}) {
|
|
6934
7378
|
return this.request(`/v3/video-translations/${seg(id, "video translation id")}`, opts);
|
|
@@ -6941,13 +7385,13 @@ var HeygenClient = class {
|
|
|
6941
7385
|
return data.languages.filter((l) => typeof l === "string");
|
|
6942
7386
|
}
|
|
6943
7387
|
async createLipsync(body, idempotencyKey = true) {
|
|
6944
|
-
const
|
|
6945
|
-
const data = await this.request(
|
|
7388
|
+
const path58 = "/v3/lipsyncs";
|
|
7389
|
+
const data = await this.request(path58, {
|
|
6946
7390
|
method: "POST",
|
|
6947
7391
|
body,
|
|
6948
7392
|
idempotencyKey
|
|
6949
7393
|
});
|
|
6950
|
-
return requireJobId(data, "lipsync_id",
|
|
7394
|
+
return requireJobId(data, "lipsync_id", path58);
|
|
6951
7395
|
}
|
|
6952
7396
|
async getLipsync(id, opts = {}) {
|
|
6953
7397
|
return this.request(`/v3/lipsyncs/${seg(id, "lipsync id")}`, opts);
|
|
@@ -7012,9 +7456,9 @@ var HeygenClient = class {
|
|
|
7012
7456
|
}
|
|
7013
7457
|
}
|
|
7014
7458
|
};
|
|
7015
|
-
function unwrapData(json,
|
|
7459
|
+
function unwrapData(json, path58) {
|
|
7016
7460
|
if (json === null || typeof json !== "object") {
|
|
7017
|
-
throw new ProviderError(`Unexpected HeyGen response from ${
|
|
7461
|
+
throw new ProviderError(`Unexpected HeyGen response from ${path58}: not a JSON object`, "heygen");
|
|
7018
7462
|
}
|
|
7019
7463
|
const record = json;
|
|
7020
7464
|
const error = record.error;
|
|
@@ -7026,7 +7470,7 @@ function unwrapData(json, path57) {
|
|
|
7026
7470
|
);
|
|
7027
7471
|
}
|
|
7028
7472
|
if (!("data" in record) || record.data === null || record.data === void 0) {
|
|
7029
|
-
throw new ProviderError(`Unexpected HeyGen response from ${
|
|
7473
|
+
throw new ProviderError(`Unexpected HeyGen response from ${path58}: missing data`, "heygen");
|
|
7030
7474
|
}
|
|
7031
7475
|
return record.data;
|
|
7032
7476
|
}
|
|
@@ -7239,7 +7683,6 @@ function resumeCommand2(kind, id, opts = {}) {
|
|
|
7239
7683
|
|
|
7240
7684
|
// src/providers/heygen/request-builders.ts
|
|
7241
7685
|
init_errors();
|
|
7242
|
-
init_redact();
|
|
7243
7686
|
function oneOf(value, allowed, flag) {
|
|
7244
7687
|
if (value === void 0) return void 0;
|
|
7245
7688
|
if (!allowed.includes(value)) {
|
|
@@ -7268,23 +7711,6 @@ function isHttpsUrl(url) {
|
|
|
7268
7711
|
return false;
|
|
7269
7712
|
}
|
|
7270
7713
|
}
|
|
7271
|
-
var LOOPBACK_HOSTS = /* @__PURE__ */ new Set(["localhost", "127.0.0.1", "[::1]"]);
|
|
7272
|
-
function checkBaseUrl(raw, variable) {
|
|
7273
|
-
const value = raw.trim();
|
|
7274
|
-
let url;
|
|
7275
|
-
try {
|
|
7276
|
-
url = new URL(value);
|
|
7277
|
-
} catch {
|
|
7278
|
-
throw new ConfigError(`${variable} must be an absolute https:// URL`);
|
|
7279
|
-
}
|
|
7280
|
-
const loopbackHttp = url.protocol === "http:" && LOOPBACK_HOSTS.has(url.hostname);
|
|
7281
|
-
if (url.protocol !== "https:" && !loopbackHttp) {
|
|
7282
|
-
throw new ConfigError(
|
|
7283
|
-
`${variable} must use https:// (got ${redactUrl(value)}); plain http:// is only allowed for localhost, 127.0.0.1, or [::1]`
|
|
7284
|
-
);
|
|
7285
|
-
}
|
|
7286
|
-
return value;
|
|
7287
|
-
}
|
|
7288
7714
|
function classifyMediaRef(value) {
|
|
7289
7715
|
const v = value.trim();
|
|
7290
7716
|
if (!v) throw new ValidationError("Media reference must not be empty");
|
|
@@ -7555,17 +7981,17 @@ function createHeygenClientFromEnv() {
|
|
|
7555
7981
|
return new HeygenClient({ apiKey, baseUrl: checkBaseUrl(baseUrl, "HEYGEN_BASE_URL") });
|
|
7556
7982
|
}
|
|
7557
7983
|
async function uploadLocalFile2(client, filePath, logger) {
|
|
7558
|
-
const abs =
|
|
7984
|
+
const abs = path33.resolve(filePath);
|
|
7559
7985
|
let stat;
|
|
7560
7986
|
try {
|
|
7561
|
-
stat =
|
|
7987
|
+
stat = fs30.statSync(abs);
|
|
7562
7988
|
} catch {
|
|
7563
7989
|
throw new ValidationError(
|
|
7564
7990
|
`File not found: ${filePath}. Use an https:// URL, asset:<asset_id>, or an existing file.`
|
|
7565
7991
|
);
|
|
7566
7992
|
}
|
|
7567
7993
|
if (!stat.isFile()) throw new ValidationError(`Not a file: ${filePath}`);
|
|
7568
|
-
const ext =
|
|
7994
|
+
const ext = path33.extname(abs).slice(1).toLowerCase();
|
|
7569
7995
|
const mimeType = ASSET_MIME_BY_EXT[ext];
|
|
7570
7996
|
if (!mimeType) {
|
|
7571
7997
|
throw new ValidationError(
|
|
@@ -7577,10 +8003,10 @@ async function uploadLocalFile2(client, filePath, logger) {
|
|
|
7577
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.`
|
|
7578
8004
|
);
|
|
7579
8005
|
}
|
|
7580
|
-
logger.info(`Uploading ${
|
|
8006
|
+
logger.info(`Uploading ${path33.basename(abs)} to HeyGen assets...`);
|
|
7581
8007
|
const res = await client.uploadAsset({
|
|
7582
|
-
data: new Uint8Array(
|
|
7583
|
-
filename:
|
|
8008
|
+
data: new Uint8Array(fs30.readFileSync(abs)),
|
|
8009
|
+
filename: path33.basename(abs),
|
|
7584
8010
|
mimeType
|
|
7585
8011
|
});
|
|
7586
8012
|
logger.debug(`Uploaded asset ${res.asset_id} (${res.mime_type}, ${res.size_bytes} bytes)`);
|
|
@@ -7624,7 +8050,7 @@ var MEDIA_EXTS = /* @__PURE__ */ new Set([...VIDEO_EXTS, ...AUDIO_EXTS]);
|
|
|
7624
8050
|
var OUTPUT_EXTS = /* @__PURE__ */ new Set([...MEDIA_EXTS, "srt", "vtt"]);
|
|
7625
8051
|
function inferExtension(url, fallback) {
|
|
7626
8052
|
try {
|
|
7627
|
-
const ext =
|
|
8053
|
+
const ext = path33.extname(new URL(url).pathname).slice(1).toLowerCase();
|
|
7628
8054
|
return OUTPUT_EXTS.has(ext) ? ext : fallback;
|
|
7629
8055
|
} catch {
|
|
7630
8056
|
return fallback;
|
|
@@ -7662,11 +8088,11 @@ async function downloadOutput(url, dest, resumeHint) {
|
|
|
7662
8088
|
}
|
|
7663
8089
|
function defaultOutputPath(state, ext) {
|
|
7664
8090
|
const lang = state.outputLanguage ? `-${safeSlug(state.outputLanguage)}` : "";
|
|
7665
|
-
return
|
|
8091
|
+
return path33.join(getOutputDir(), `heygen-${state.kind}${lang}-${safeSlug(state.id)}.${ext}`);
|
|
7666
8092
|
}
|
|
7667
8093
|
function statOrUndefined(p) {
|
|
7668
8094
|
try {
|
|
7669
|
-
return
|
|
8095
|
+
return fs30.statSync(p);
|
|
7670
8096
|
} catch {
|
|
7671
8097
|
return void 0;
|
|
7672
8098
|
}
|
|
@@ -7675,29 +8101,29 @@ function isDirectoryOutput2(output, target) {
|
|
|
7675
8101
|
if (/[\\/]$/.test(output)) return true;
|
|
7676
8102
|
const stat = statOrUndefined(target);
|
|
7677
8103
|
if (stat) return stat.isDirectory();
|
|
7678
|
-
return
|
|
8104
|
+
return path33.extname(target) === "";
|
|
7679
8105
|
}
|
|
7680
8106
|
function withMediaExt(file, ext) {
|
|
7681
|
-
const given =
|
|
8107
|
+
const given = path33.extname(file).slice(1).toLowerCase();
|
|
7682
8108
|
const mismatch = VIDEO_EXTS.has(given) && AUDIO_EXTS.has(ext) || AUDIO_EXTS.has(given) && VIDEO_EXTS.has(ext);
|
|
7683
8109
|
return mismatch ? `${file.slice(0, -(given.length + 1))}.${ext}` : file;
|
|
7684
8110
|
}
|
|
7685
8111
|
function outputPathFor(state, ext, output, count) {
|
|
7686
8112
|
if (!output) return defaultOutputPath(state, ext);
|
|
7687
|
-
const target =
|
|
7688
|
-
const givenExt =
|
|
8113
|
+
const target = path33.resolve(output);
|
|
8114
|
+
const givenExt = path33.extname(target);
|
|
7689
8115
|
const isTemplate = count > 1 && MEDIA_EXTS.has(givenExt.slice(1).toLowerCase());
|
|
7690
8116
|
if (isDirectoryOutput2(output, target) || count > 1 && !isTemplate) {
|
|
7691
|
-
|
|
7692
|
-
return
|
|
8117
|
+
fs30.mkdirSync(target, { recursive: true });
|
|
8118
|
+
return path33.join(target, path33.basename(defaultOutputPath(state, ext)));
|
|
7693
8119
|
}
|
|
7694
8120
|
if (!isTemplate) return withMediaExt(target, ext);
|
|
7695
|
-
const base =
|
|
8121
|
+
const base = path33.basename(target, givenExt);
|
|
7696
8122
|
const suffix = safeSlug(state.outputLanguage ?? state.id);
|
|
7697
|
-
return withMediaExt(
|
|
8123
|
+
return withMediaExt(path33.join(path33.dirname(target), `${base}-${suffix}${givenExt}`), ext);
|
|
7698
8124
|
}
|
|
7699
8125
|
function claimPath(dest, state, used) {
|
|
7700
|
-
const ext =
|
|
8126
|
+
const ext = path33.extname(dest);
|
|
7701
8127
|
const stem = dest.slice(0, dest.length - ext.length);
|
|
7702
8128
|
let candidate = dest;
|
|
7703
8129
|
for (let n = 1; used.has(candidate.toLowerCase()); n++) {
|
|
@@ -7708,7 +8134,7 @@ function claimPath(dest, state, used) {
|
|
|
7708
8134
|
return candidate;
|
|
7709
8135
|
}
|
|
7710
8136
|
function captionPathFor(dest, capExt) {
|
|
7711
|
-
const stem =
|
|
8137
|
+
const stem = path33.join(path33.dirname(dest), path33.basename(dest, path33.extname(dest)));
|
|
7712
8138
|
const capDest = `${stem}.${capExt}`;
|
|
7713
8139
|
return capDest.toLowerCase() === dest.toLowerCase() ? `${stem}.captions.${capExt}` : capDest;
|
|
7714
8140
|
}
|
|
@@ -8086,7 +8512,7 @@ function registerHeygenModelVideoCommand(parent) {
|
|
|
8086
8512
|
|
|
8087
8513
|
// src/providers/heygen/commands/run.ts
|
|
8088
8514
|
init_errors();
|
|
8089
|
-
import
|
|
8515
|
+
import path34 from "path";
|
|
8090
8516
|
function extractJobIds(operation, data) {
|
|
8091
8517
|
const op = HEYGEN_OPERATIONS[operation];
|
|
8092
8518
|
if (!op) return [];
|
|
@@ -8124,7 +8550,7 @@ function registerHeygenRunCommand(parent) {
|
|
|
8124
8550
|
return;
|
|
8125
8551
|
}
|
|
8126
8552
|
const ext = inferExtension(audioUrl, "mp3");
|
|
8127
|
-
const dest = opts.output ?
|
|
8553
|
+
const dest = opts.output ? path34.resolve(opts.output) : path34.join(getOutputDir(), `heygen-speech-${Date.now()}.${ext}`);
|
|
8128
8554
|
await downloadOutput(audioUrl, dest, "Re-run the command to generate a fresh URL.");
|
|
8129
8555
|
console.log(`Saved: ${dest}`);
|
|
8130
8556
|
return;
|
|
@@ -8254,7 +8680,7 @@ function registerHeygenTranslateCommand(parent) {
|
|
|
8254
8680
|
}
|
|
8255
8681
|
|
|
8256
8682
|
// src/providers/heygen/commands/tts.ts
|
|
8257
|
-
import
|
|
8683
|
+
import path35 from "path";
|
|
8258
8684
|
init_redact();
|
|
8259
8685
|
function registerHeygenTtsCommand(parent) {
|
|
8260
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) => {
|
|
@@ -8285,7 +8711,7 @@ function registerHeygenTtsCommand(parent) {
|
|
|
8285
8711
|
}
|
|
8286
8712
|
const ext = inferExtension(res.audio_url, "mp3");
|
|
8287
8713
|
const name = `heygen-tts-${res.request_id ?? Date.now()}.${ext}`.replace(/[^\w.-]+/g, "-");
|
|
8288
|
-
const dest = opts.output ?
|
|
8714
|
+
const dest = opts.output ? path35.resolve(opts.output) : path35.join(getOutputDir(), name);
|
|
8289
8715
|
await downloadOutput(res.audio_url, dest, "Re-run the command to generate a fresh URL.");
|
|
8290
8716
|
if (opts.json) console.log(JSON.stringify({ ...summary, file: dest }, null, 2));
|
|
8291
8717
|
else console.log(`Saved: ${dest} (${res.duration}s, ${res.engine})`);
|
|
@@ -8404,8 +8830,8 @@ function registerHeygenCommands(program2) {
|
|
|
8404
8830
|
}
|
|
8405
8831
|
|
|
8406
8832
|
// src/providers/leonardo/commands/generate.ts
|
|
8407
|
-
import
|
|
8408
|
-
import
|
|
8833
|
+
import fs31 from "fs";
|
|
8834
|
+
import path36 from "path";
|
|
8409
8835
|
|
|
8410
8836
|
// src/providers/leonardo/client.ts
|
|
8411
8837
|
init_errors();
|
|
@@ -8483,11 +8909,11 @@ var LeonardoClient = class {
|
|
|
8483
8909
|
}
|
|
8484
8910
|
apiKey;
|
|
8485
8911
|
baseUrl;
|
|
8486
|
-
async request(
|
|
8912
|
+
async request(path58, opts = {}) {
|
|
8487
8913
|
const base = this.baseUrl.replace(/\/$/, "");
|
|
8488
|
-
const v2Override =
|
|
8914
|
+
const v2Override = path58.startsWith("v2:");
|
|
8489
8915
|
const finalBase = v2Override ? base.replace(/\/v1$/, "/v2") : base;
|
|
8490
|
-
const finalPath = v2Override ?
|
|
8916
|
+
const finalPath = v2Override ? path58.slice(3) : path58;
|
|
8491
8917
|
const url = new URL(finalBase + finalPath);
|
|
8492
8918
|
if (opts.query) {
|
|
8493
8919
|
for (const [k, v] of Object.entries(opts.query)) {
|
|
@@ -8506,14 +8932,14 @@ var LeonardoClient = class {
|
|
|
8506
8932
|
timeoutMs: opts.timeoutMs ?? 12e4
|
|
8507
8933
|
});
|
|
8508
8934
|
}
|
|
8509
|
-
get(
|
|
8510
|
-
return this.request(
|
|
8935
|
+
get(path58, query, logger) {
|
|
8936
|
+
return this.request(path58, { method: "GET", query, logger });
|
|
8511
8937
|
}
|
|
8512
|
-
post(
|
|
8513
|
-
return this.request(
|
|
8938
|
+
post(path58, body, logger) {
|
|
8939
|
+
return this.request(path58, { method: "POST", body, logger });
|
|
8514
8940
|
}
|
|
8515
|
-
delete(
|
|
8516
|
-
return this.request(
|
|
8941
|
+
delete(path58, logger) {
|
|
8942
|
+
return this.request(path58, { method: "DELETE", logger });
|
|
8517
8943
|
}
|
|
8518
8944
|
};
|
|
8519
8945
|
function createLeonardoClient() {
|
|
@@ -8524,7 +8950,7 @@ function createLeonardoClient() {
|
|
|
8524
8950
|
function extFromUrl2(url, fallback = ".png") {
|
|
8525
8951
|
try {
|
|
8526
8952
|
const u = new URL(url);
|
|
8527
|
-
const e =
|
|
8953
|
+
const e = path36.extname(u.pathname);
|
|
8528
8954
|
return e.length > 0 ? e : fallback;
|
|
8529
8955
|
} catch {
|
|
8530
8956
|
return fallback;
|
|
@@ -8609,7 +9035,7 @@ function registerLeonardoGenerateCommand(parent) {
|
|
|
8609
9035
|
if (!img) continue;
|
|
8610
9036
|
const ext = extFromUrl2(img.url);
|
|
8611
9037
|
const filename = autoFilename(generationId, i + 1, ext);
|
|
8612
|
-
const filepath =
|
|
9038
|
+
const filepath = path36.join(outDir, filename);
|
|
8613
9039
|
try {
|
|
8614
9040
|
await downloadFile(img.url, filepath);
|
|
8615
9041
|
saved.push(filepath);
|
|
@@ -8619,9 +9045,9 @@ function registerLeonardoGenerateCommand(parent) {
|
|
|
8619
9045
|
}
|
|
8620
9046
|
}
|
|
8621
9047
|
if (opts.output && saved[0]) {
|
|
8622
|
-
const dest =
|
|
8623
|
-
|
|
8624
|
-
|
|
9048
|
+
const dest = path36.resolve(opts.output);
|
|
9049
|
+
fs31.mkdirSync(path36.dirname(dest), { recursive: true });
|
|
9050
|
+
fs31.copyFileSync(saved[0], dest);
|
|
8625
9051
|
logger.success(`Copied to ${dest}`);
|
|
8626
9052
|
}
|
|
8627
9053
|
const finalImages = finalizeGeneratedImages(saved, { ...opts, logger });
|
|
@@ -8633,11 +9059,11 @@ Generated ${finalImages.length} image(s):`);
|
|
|
8633
9059
|
}
|
|
8634
9060
|
|
|
8635
9061
|
// src/providers/leonardo/commands/image-to-image.ts
|
|
8636
|
-
import
|
|
8637
|
-
import
|
|
9062
|
+
import fs32 from "fs";
|
|
9063
|
+
import path37 from "path";
|
|
8638
9064
|
function extFromUrl3(url, fallback = ".png") {
|
|
8639
9065
|
try {
|
|
8640
|
-
return
|
|
9066
|
+
return path37.extname(new URL(url).pathname) || fallback;
|
|
8641
9067
|
} catch {
|
|
8642
9068
|
return fallback;
|
|
8643
9069
|
}
|
|
@@ -8699,7 +9125,7 @@ function registerLeonardoImageToImageCommand(parent) {
|
|
|
8699
9125
|
if (!img) continue;
|
|
8700
9126
|
const ext = extFromUrl3(img.url);
|
|
8701
9127
|
const filename = `leonardo-i2i-${generationId.slice(0, 8)}-${String(i + 1).padStart(2, "0")}${ext}`;
|
|
8702
|
-
const filepath =
|
|
9128
|
+
const filepath = path37.join(outDir, filename);
|
|
8703
9129
|
try {
|
|
8704
9130
|
await downloadFile(img.url, filepath);
|
|
8705
9131
|
saved.push(filepath);
|
|
@@ -8709,9 +9135,9 @@ function registerLeonardoImageToImageCommand(parent) {
|
|
|
8709
9135
|
}
|
|
8710
9136
|
}
|
|
8711
9137
|
if (opts.output && saved[0]) {
|
|
8712
|
-
const dest =
|
|
8713
|
-
|
|
8714
|
-
|
|
9138
|
+
const dest = path37.resolve(opts.output);
|
|
9139
|
+
fs32.mkdirSync(path37.dirname(dest), { recursive: true });
|
|
9140
|
+
fs32.copyFileSync(saved[0], dest);
|
|
8715
9141
|
logger.success(`Copied to ${dest}`);
|
|
8716
9142
|
}
|
|
8717
9143
|
const finalImages = finalizeGeneratedImages(saved, { ...opts, logger });
|
|
@@ -8723,7 +9149,7 @@ Generated ${finalImages.length} image(s):`);
|
|
|
8723
9149
|
}
|
|
8724
9150
|
|
|
8725
9151
|
// src/providers/leonardo/commands/video.ts
|
|
8726
|
-
import
|
|
9152
|
+
import path38 from "path";
|
|
8727
9153
|
function v2DimsFor(resolution) {
|
|
8728
9154
|
if (resolution === "RESOLUTION_1080") return { width: 1920, height: 1080 };
|
|
8729
9155
|
if (resolution === "RESOLUTION_480") return { width: 854, height: 480 };
|
|
@@ -8834,7 +9260,7 @@ async function pollAndDownload(client, jobId, opts) {
|
|
|
8834
9260
|
logger.error(`No video URL in completed generation: ${JSON.stringify(gen)}`);
|
|
8835
9261
|
process.exit(1);
|
|
8836
9262
|
}
|
|
8837
|
-
const outPath = opts.output ??
|
|
9263
|
+
const outPath = opts.output ?? path38.join(getOutputDir(), `${opts.basename}-${jobId.slice(0, 8)}.mp4`);
|
|
8838
9264
|
await downloadFile(videoUrl2, outPath);
|
|
8839
9265
|
logger.success(`Saved ${outPath}`);
|
|
8840
9266
|
await maybeDownloadThumb(gen, outPath, {
|
|
@@ -8940,7 +9366,7 @@ function registerLeonardoModelsCommand(parent) {
|
|
|
8940
9366
|
}
|
|
8941
9367
|
|
|
8942
9368
|
// src/providers/leonardo/commands/status.ts
|
|
8943
|
-
import
|
|
9369
|
+
import path39 from "path";
|
|
8944
9370
|
var VIDEO_KEYS = ["motionMP4URL", "url"];
|
|
8945
9371
|
var IMAGE_EXT_RE2 = /\.(jpe?g|png|webp|gif)(\?|$)/i;
|
|
8946
9372
|
var VIDEO_EXT_RE = /\.(mp4|webm|mov|m4v)(\?|$)/i;
|
|
@@ -9005,7 +9431,7 @@ function registerLeonardoStatusCommand(parent) {
|
|
|
9005
9431
|
const outDir = getOutputDir();
|
|
9006
9432
|
let idx = 0;
|
|
9007
9433
|
for (const url of videos) {
|
|
9008
|
-
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`);
|
|
9009
9435
|
await downloadFile(url, dest);
|
|
9010
9436
|
logger.success(`Saved ${dest}`);
|
|
9011
9437
|
await maybeDownloadThumb(gen, dest, {
|
|
@@ -9018,7 +9444,7 @@ function registerLeonardoStatusCommand(parent) {
|
|
|
9018
9444
|
for (const url of images) {
|
|
9019
9445
|
const m = url.match(IMAGE_EXT_RE2);
|
|
9020
9446
|
const ext = m?.[1] ? m[1].toLowerCase().replace("jpeg", "jpg") : "png";
|
|
9021
|
-
const dest =
|
|
9447
|
+
const dest = path39.join(outDir, `leonardo-${generationId.slice(0, 8)}-${idx}.${ext}`);
|
|
9022
9448
|
await downloadFile(url, dest);
|
|
9023
9449
|
logger.success(`Saved ${dest}`);
|
|
9024
9450
|
idx++;
|
|
@@ -9175,8 +9601,8 @@ function sleep3(ms) {
|
|
|
9175
9601
|
}
|
|
9176
9602
|
|
|
9177
9603
|
// src/providers/minimax/generators/music.ts
|
|
9178
|
-
import
|
|
9179
|
-
import
|
|
9604
|
+
import fs33 from "fs";
|
|
9605
|
+
import path40 from "path";
|
|
9180
9606
|
async function generateMinimaxMusic(opts) {
|
|
9181
9607
|
const {
|
|
9182
9608
|
apiKey,
|
|
@@ -9221,14 +9647,14 @@ async function generateMinimaxMusic(opts) {
|
|
|
9221
9647
|
}
|
|
9222
9648
|
const durationMs = resp.extra_info?.music_duration ?? 0;
|
|
9223
9649
|
const outDir = getOutputDir();
|
|
9224
|
-
const dest =
|
|
9650
|
+
const dest = path40.join(outDir, `minimax_music_${Date.now()}.${outputFormat}`);
|
|
9225
9651
|
try {
|
|
9226
9652
|
if (audioData.startsWith("http")) {
|
|
9227
9653
|
const bytes = await fetchBytes(audioData);
|
|
9228
|
-
|
|
9654
|
+
fs33.writeFileSync(dest, bytes);
|
|
9229
9655
|
} else {
|
|
9230
9656
|
const audioBytes = Buffer.from(audioData, "hex");
|
|
9231
|
-
|
|
9657
|
+
fs33.writeFileSync(dest, audioBytes);
|
|
9232
9658
|
}
|
|
9233
9659
|
} catch (e) {
|
|
9234
9660
|
return {
|
|
@@ -9239,8 +9665,8 @@ async function generateMinimaxMusic(opts) {
|
|
|
9239
9665
|
const durSec = durationMs / 1e3;
|
|
9240
9666
|
logger?.success(`Saved: ${dest} (${durSec.toFixed(1)}s)`);
|
|
9241
9667
|
if (output) {
|
|
9242
|
-
|
|
9243
|
-
|
|
9668
|
+
fs33.mkdirSync(path40.dirname(path40.resolve(output)), { recursive: true });
|
|
9669
|
+
fs33.copyFileSync(dest, output);
|
|
9244
9670
|
}
|
|
9245
9671
|
return { status: "success", generatedAudio: dest, durationMs, model };
|
|
9246
9672
|
}
|
|
@@ -9314,8 +9740,8 @@ Generated music: ${result.generatedAudio}`);
|
|
|
9314
9740
|
init_errors();
|
|
9315
9741
|
|
|
9316
9742
|
// src/providers/minimax/generators/speech.ts
|
|
9317
|
-
import
|
|
9318
|
-
import
|
|
9743
|
+
import fs34 from "fs";
|
|
9744
|
+
import path41 from "path";
|
|
9319
9745
|
async function generateMinimaxSpeech(opts) {
|
|
9320
9746
|
const {
|
|
9321
9747
|
apiKey,
|
|
@@ -9362,12 +9788,12 @@ async function generateMinimaxSpeech(opts) {
|
|
|
9362
9788
|
const audioBytes = Buffer.from(audioHex, "hex");
|
|
9363
9789
|
const ext = ["mp3", "wav", "flac"].includes(outputFormat) ? outputFormat : "mp3";
|
|
9364
9790
|
const outDir = getOutputDir();
|
|
9365
|
-
const dest =
|
|
9366
|
-
|
|
9791
|
+
const dest = path41.join(outDir, `minimax_speech_${Date.now()}.${ext}`);
|
|
9792
|
+
fs34.writeFileSync(dest, audioBytes);
|
|
9367
9793
|
logger?.success(`Saved: ${dest} (${(audioBytes.length / 1024).toFixed(1)} KB)`);
|
|
9368
9794
|
if (output) {
|
|
9369
|
-
|
|
9370
|
-
|
|
9795
|
+
fs34.mkdirSync(path41.dirname(path41.resolve(output)), { recursive: true });
|
|
9796
|
+
fs34.copyFileSync(dest, output);
|
|
9371
9797
|
}
|
|
9372
9798
|
return { status: "success", generatedAudio: dest, model };
|
|
9373
9799
|
}
|
|
@@ -9414,8 +9840,8 @@ Generated audio: ${result.generatedAudio}`);
|
|
|
9414
9840
|
init_errors();
|
|
9415
9841
|
|
|
9416
9842
|
// src/providers/minimax/generators/video.ts
|
|
9417
|
-
import
|
|
9418
|
-
import
|
|
9843
|
+
import fs35 from "fs";
|
|
9844
|
+
import path42 from "path";
|
|
9419
9845
|
async function generateMinimaxVideo(opts) {
|
|
9420
9846
|
const {
|
|
9421
9847
|
apiKey,
|
|
@@ -9461,18 +9887,18 @@ async function generateMinimaxVideo(opts) {
|
|
|
9461
9887
|
return { status: "error", error: `No file_id in poll result: ${JSON.stringify(pollResult)}` };
|
|
9462
9888
|
}
|
|
9463
9889
|
const outDir = getOutputDir();
|
|
9464
|
-
const dest =
|
|
9890
|
+
const dest = path42.join(outDir, `minimax_video_${Date.now()}.mp4`);
|
|
9465
9891
|
try {
|
|
9466
9892
|
await downloadMinimaxFile(fileId, apiKey, dest, logger);
|
|
9467
9893
|
} catch (e) {
|
|
9468
9894
|
return { status: "error", error: e instanceof Error ? e.message : String(e) };
|
|
9469
9895
|
}
|
|
9470
9896
|
const elapsed = (Date.now() - start) / 1e3;
|
|
9471
|
-
const stat =
|
|
9897
|
+
const stat = fs35.statSync(dest);
|
|
9472
9898
|
const fileSizeMb = stat.size / (1024 * 1024);
|
|
9473
9899
|
if (output) {
|
|
9474
|
-
|
|
9475
|
-
|
|
9900
|
+
fs35.mkdirSync(path42.dirname(path42.resolve(output)), { recursive: true });
|
|
9901
|
+
fs35.copyFileSync(dest, output);
|
|
9476
9902
|
}
|
|
9477
9903
|
await maybeDownloadThumb(pollResult, dest, {
|
|
9478
9904
|
skip: thumb === false,
|
|
@@ -9534,8 +9960,8 @@ Generated video: ${result.generatedVideo}`);
|
|
|
9534
9960
|
|
|
9535
9961
|
// src/providers/minimax/generators/image.ts
|
|
9536
9962
|
init_errors();
|
|
9537
|
-
import
|
|
9538
|
-
import
|
|
9963
|
+
import fs36 from "fs";
|
|
9964
|
+
import path43 from "path";
|
|
9539
9965
|
async function generateMinimaxImage(opts) {
|
|
9540
9966
|
const {
|
|
9541
9967
|
apiKey,
|
|
@@ -9573,8 +9999,8 @@ async function generateMinimaxImage(opts) {
|
|
|
9573
9999
|
try {
|
|
9574
10000
|
const bytes = await fetchBytes(url);
|
|
9575
10001
|
const fname = `minimax_image_${Date.now()}_${i}.png`;
|
|
9576
|
-
const dest =
|
|
9577
|
-
|
|
10002
|
+
const dest = path43.join(outDir, fname);
|
|
10003
|
+
fs36.writeFileSync(dest, bytes);
|
|
9578
10004
|
savedFiles.push(dest);
|
|
9579
10005
|
logger?.success(`Saved: ${dest}`);
|
|
9580
10006
|
} catch (e) {
|
|
@@ -9585,8 +10011,8 @@ async function generateMinimaxImage(opts) {
|
|
|
9585
10011
|
}
|
|
9586
10012
|
}
|
|
9587
10013
|
if (output && savedFiles[0]) {
|
|
9588
|
-
|
|
9589
|
-
|
|
10014
|
+
fs36.mkdirSync(path43.dirname(path43.resolve(output)), { recursive: true });
|
|
10015
|
+
fs36.copyFileSync(savedFiles[0], output);
|
|
9590
10016
|
}
|
|
9591
10017
|
return { status: "success", generatedImages: savedFiles, model };
|
|
9592
10018
|
}
|
|
@@ -9625,8 +10051,8 @@ Generated ${finalImages.length} image(s):`);
|
|
|
9625
10051
|
|
|
9626
10052
|
// src/providers/minimax/commands/image-to-image.ts
|
|
9627
10053
|
init_errors();
|
|
9628
|
-
import
|
|
9629
|
-
import
|
|
10054
|
+
import fs37 from "fs";
|
|
10055
|
+
import path44 from "path";
|
|
9630
10056
|
function registerMinimaxImageToImageCommand(parent) {
|
|
9631
10057
|
parent.command("image-to-image").alias("i2i").description(
|
|
9632
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."
|
|
@@ -9681,8 +10107,8 @@ function registerMinimaxImageToImageCommand(parent) {
|
|
|
9681
10107
|
if (!url) continue;
|
|
9682
10108
|
try {
|
|
9683
10109
|
const bytes = await fetchBytes(url);
|
|
9684
|
-
const dest =
|
|
9685
|
-
|
|
10110
|
+
const dest = path44.join(outDir, `minimax-i2i-${ts}-${i + 1}.png`);
|
|
10111
|
+
fs37.writeFileSync(dest, bytes);
|
|
9686
10112
|
saved.push(dest);
|
|
9687
10113
|
logger.success(`Saved: ${dest}`);
|
|
9688
10114
|
} catch (e) {
|
|
@@ -9693,8 +10119,8 @@ function registerMinimaxImageToImageCommand(parent) {
|
|
|
9693
10119
|
}
|
|
9694
10120
|
}
|
|
9695
10121
|
if (opts.output && saved[0]) {
|
|
9696
|
-
|
|
9697
|
-
|
|
10122
|
+
fs37.mkdirSync(path44.dirname(path44.resolve(opts.output)), { recursive: true });
|
|
10123
|
+
fs37.copyFileSync(saved[0], opts.output);
|
|
9698
10124
|
logger.success(`Copied to: ${opts.output}`);
|
|
9699
10125
|
}
|
|
9700
10126
|
const finalImages = finalizeGeneratedImages(saved, { ...opts, logger });
|
|
@@ -9828,12 +10254,12 @@ function validateKnownSpeakerPairing(names = [], refs = []) {
|
|
|
9828
10254
|
}
|
|
9829
10255
|
|
|
9830
10256
|
// src/providers/openai/openai-audio.ts
|
|
9831
|
-
import
|
|
9832
|
-
import
|
|
10257
|
+
import fs39 from "fs";
|
|
10258
|
+
import path46 from "path";
|
|
9833
10259
|
|
|
9834
10260
|
// src/providers/openai/client.ts
|
|
9835
|
-
import
|
|
9836
|
-
import
|
|
10261
|
+
import fs38 from "fs";
|
|
10262
|
+
import path45 from "path";
|
|
9837
10263
|
init_errors();
|
|
9838
10264
|
var DEFAULT_TIMEOUT_MS4 = 24e4;
|
|
9839
10265
|
function requireOpenAIKey() {
|
|
@@ -9901,10 +10327,10 @@ async function postOpenAIBinary(pathname, body, apiKey, timeoutMs = DEFAULT_TIME
|
|
|
9901
10327
|
return Buffer.from(await res.arrayBuffer());
|
|
9902
10328
|
}
|
|
9903
10329
|
function fileFromPath(filePath) {
|
|
9904
|
-
const abs =
|
|
9905
|
-
if (!
|
|
9906
|
-
const bytes =
|
|
9907
|
-
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));
|
|
9908
10334
|
}
|
|
9909
10335
|
|
|
9910
10336
|
// src/providers/openai/openai-audio.ts
|
|
@@ -9943,7 +10369,7 @@ async function resolveAudioReferenceDataUrl(ref) {
|
|
|
9943
10369
|
}
|
|
9944
10370
|
function audioMimeFromReference(ref) {
|
|
9945
10371
|
const pathname = new URL(ref).pathname;
|
|
9946
|
-
const ext =
|
|
10372
|
+
const ext = path46.extname(pathname).toLowerCase();
|
|
9947
10373
|
switch (ext) {
|
|
9948
10374
|
case ".mp3":
|
|
9949
10375
|
return "audio/mpeg";
|
|
@@ -9962,12 +10388,12 @@ function audioMimeFromReference(ref) {
|
|
|
9962
10388
|
async function generateOpenAISpeech(opts) {
|
|
9963
10389
|
const bytes = await postOpenAIBinary("/audio/speech", buildSpeechBody(opts), requireOpenAIKey());
|
|
9964
10390
|
const ext = extFromAudioFormat(opts.outputFormat);
|
|
9965
|
-
const dest =
|
|
9966
|
-
|
|
10391
|
+
const dest = path46.join(getOutputDir(), `openai-speech-${Date.now()}.${ext}`);
|
|
10392
|
+
fs39.writeFileSync(dest, bytes);
|
|
9967
10393
|
opts.logger?.success(`Saved: ${dest} (${(bytes.length / 1024).toFixed(1)} KB)`);
|
|
9968
10394
|
if (opts.output) {
|
|
9969
|
-
|
|
9970
|
-
|
|
10395
|
+
fs39.mkdirSync(path46.dirname(path46.resolve(opts.output)), { recursive: true });
|
|
10396
|
+
fs39.copyFileSync(dest, opts.output);
|
|
9971
10397
|
}
|
|
9972
10398
|
return dest;
|
|
9973
10399
|
}
|
|
@@ -9989,9 +10415,9 @@ async function transcribeOpenAIAudio(opts) {
|
|
|
9989
10415
|
);
|
|
9990
10416
|
const text = typeof data.text === "string" ? data.text : void 0;
|
|
9991
10417
|
const ext = opts.format === "text" ? "txt" : "json";
|
|
9992
|
-
const dest = opts.output ?
|
|
9993
|
-
|
|
9994
|
-
|
|
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));
|
|
9995
10421
|
opts.logger?.success(`Saved transcript: ${dest}`);
|
|
9996
10422
|
return { path: dest, text };
|
|
9997
10423
|
}
|
|
@@ -10018,9 +10444,9 @@ Generated audio: ${dest}`);
|
|
|
10018
10444
|
}
|
|
10019
10445
|
|
|
10020
10446
|
// src/providers/openai/codex-image-driver.ts
|
|
10021
|
-
import
|
|
10022
|
-
import
|
|
10023
|
-
import
|
|
10447
|
+
import fs40 from "fs";
|
|
10448
|
+
import os4 from "os";
|
|
10449
|
+
import path47 from "path";
|
|
10024
10450
|
import { execa } from "execa";
|
|
10025
10451
|
init_errors();
|
|
10026
10452
|
var defaultRunner = async (cmd, args, opts) => {
|
|
@@ -10069,12 +10495,12 @@ function sanitizeCodexEnv() {
|
|
|
10069
10495
|
async function runCodexImageGeneration(opts) {
|
|
10070
10496
|
const runner = opts.runner ?? defaultRunner;
|
|
10071
10497
|
const ext = extFromImageFormat(opts.outputFormat);
|
|
10072
|
-
const dest = opts.output ?
|
|
10073
|
-
|
|
10074
|
-
const tmp =
|
|
10075
|
-
const schema =
|
|
10076
|
-
const result =
|
|
10077
|
-
|
|
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(
|
|
10078
10504
|
schema,
|
|
10079
10505
|
JSON.stringify({
|
|
10080
10506
|
type: "object",
|
|
@@ -10104,7 +10530,7 @@ async function runCodexImageGeneration(opts) {
|
|
|
10104
10530
|
if (res.exitCode !== 0) {
|
|
10105
10531
|
throw new ProviderError(`Codex image driver failed: ${res.stderr || res.stdout}`, "openai");
|
|
10106
10532
|
}
|
|
10107
|
-
if (!
|
|
10533
|
+
if (!fs40.existsSync(dest) || fs40.statSync(dest).size === 0) {
|
|
10108
10534
|
throw new ProviderError(`Codex did not create a non-empty image at ${dest}`, "openai");
|
|
10109
10535
|
}
|
|
10110
10536
|
return { path: dest };
|
|
@@ -10121,8 +10547,8 @@ function buildCodexPrompt(userPrompt, outputPath) {
|
|
|
10121
10547
|
|
|
10122
10548
|
// src/providers/openai/openai-image.ts
|
|
10123
10549
|
init_errors();
|
|
10124
|
-
import
|
|
10125
|
-
import
|
|
10550
|
+
import fs41 from "fs";
|
|
10551
|
+
import path48 from "path";
|
|
10126
10552
|
function buildImageGenerationBody(opts) {
|
|
10127
10553
|
return {
|
|
10128
10554
|
model: opts.model ?? DEFAULT_OPENAI_IMAGE_MODEL,
|
|
@@ -10155,14 +10581,14 @@ function saveOpenAIImages(opts) {
|
|
|
10155
10581
|
for (let i = 0; i < items.length; i++) {
|
|
10156
10582
|
const b64 = items[i]?.b64_json;
|
|
10157
10583
|
if (!b64) throw new ProviderError("No b64_json in OpenAI image response", "openai");
|
|
10158
|
-
const dest =
|
|
10159
|
-
|
|
10584
|
+
const dest = path48.join(outDir, `openai-${opts.task}-${ts}-${i + 1}.${ext}`);
|
|
10585
|
+
fs41.writeFileSync(dest, Buffer.from(b64, "base64"));
|
|
10160
10586
|
saved.push(dest);
|
|
10161
10587
|
opts.logger?.success(`Saved: ${dest}`);
|
|
10162
10588
|
}
|
|
10163
10589
|
if (opts.output && saved[0]) {
|
|
10164
|
-
|
|
10165
|
-
|
|
10590
|
+
fs41.mkdirSync(path48.dirname(path48.resolve(opts.output)), { recursive: true });
|
|
10591
|
+
fs41.copyFileSync(saved[0], opts.output);
|
|
10166
10592
|
opts.logger?.success(`Copied to: ${opts.output}`);
|
|
10167
10593
|
}
|
|
10168
10594
|
return saved;
|
|
@@ -10171,7 +10597,7 @@ async function resolveImageRefBytes(ref) {
|
|
|
10171
10597
|
if (isHttpUrl(ref)) {
|
|
10172
10598
|
const bytes2 = await fetchBytes(ref);
|
|
10173
10599
|
const pathname = new URL(ref).pathname;
|
|
10174
|
-
const filename =
|
|
10600
|
+
const filename = path48.basename(pathname) || "reference.png";
|
|
10175
10601
|
return { filename, bytes: bytes2, mime: sniffImageMime2(bytes2, filename) };
|
|
10176
10602
|
}
|
|
10177
10603
|
const file = fileFromPath(ref);
|
|
@@ -10216,7 +10642,7 @@ function sniffImageMime2(buf, name) {
|
|
|
10216
10642
|
if (buf[0] === 137 && buf[1] === 80 && buf[2] === 78 && buf[3] === 71) return "image/png";
|
|
10217
10643
|
if (buf.slice(0, 4).toString() === "RIFF" && buf.slice(8, 12).toString() === "WEBP")
|
|
10218
10644
|
return "image/webp";
|
|
10219
|
-
const ext =
|
|
10645
|
+
const ext = path48.extname(name).toLowerCase();
|
|
10220
10646
|
if (ext === ".jpg" || ext === ".jpeg") return "image/jpeg";
|
|
10221
10647
|
if (ext === ".webp") return "image/webp";
|
|
10222
10648
|
return "image/png";
|
|
@@ -10360,8 +10786,8 @@ function registerOpenAICommands(program2) {
|
|
|
10360
10786
|
}
|
|
10361
10787
|
|
|
10362
10788
|
// src/providers/openrouter/client.ts
|
|
10363
|
-
import
|
|
10364
|
-
import
|
|
10789
|
+
import fs42 from "fs";
|
|
10790
|
+
import path49 from "path";
|
|
10365
10791
|
init_errors();
|
|
10366
10792
|
|
|
10367
10793
|
// src/providers/openrouter/payload.ts
|
|
@@ -10525,8 +10951,8 @@ async function generateOpenRouterImage(opts) {
|
|
|
10525
10951
|
if (!url) continue;
|
|
10526
10952
|
try {
|
|
10527
10953
|
const bytes = await fetchBytes(url);
|
|
10528
|
-
const dest =
|
|
10529
|
-
|
|
10954
|
+
const dest = path49.join(outDir, `openrouter_image_${Date.now()}_${i}.png`);
|
|
10955
|
+
fs42.writeFileSync(dest, bytes);
|
|
10530
10956
|
savedFiles.push(dest);
|
|
10531
10957
|
logger?.success(`Saved: ${dest}`);
|
|
10532
10958
|
} catch (e) {
|
|
@@ -10537,8 +10963,8 @@ async function generateOpenRouterImage(opts) {
|
|
|
10537
10963
|
}
|
|
10538
10964
|
}
|
|
10539
10965
|
if (output && savedFiles[0]) {
|
|
10540
|
-
|
|
10541
|
-
|
|
10966
|
+
fs42.mkdirSync(path49.dirname(path49.resolve(output)), { recursive: true });
|
|
10967
|
+
fs42.writeFileSync(output, fs42.readFileSync(savedFiles[0]));
|
|
10542
10968
|
}
|
|
10543
10969
|
return { status: "success", generatedImages: savedFiles, model: usedModel };
|
|
10544
10970
|
}
|
|
@@ -10579,8 +11005,8 @@ Generated ${finalImages.length} image(s):`);
|
|
|
10579
11005
|
}
|
|
10580
11006
|
|
|
10581
11007
|
// src/providers/openrouter/commands/image-to-image.ts
|
|
10582
|
-
import
|
|
10583
|
-
import
|
|
11008
|
+
import fs43 from "fs";
|
|
11009
|
+
import path50 from "path";
|
|
10584
11010
|
init_errors();
|
|
10585
11011
|
var DEFAULT_MODEL = "google/gemini-2.5-flash-image";
|
|
10586
11012
|
var SOFT_LIMIT_BYTES2 = 5 * 1024 * 1024;
|
|
@@ -10672,14 +11098,14 @@ async function runImageToImage(opts) {
|
|
|
10672
11098
|
const url = parsed.urls[i];
|
|
10673
11099
|
if (!url) continue;
|
|
10674
11100
|
const bytes = await fetchBytes(url);
|
|
10675
|
-
const dest =
|
|
10676
|
-
|
|
11101
|
+
const dest = path50.join(outDir, `openrouter-i2i-${ts}-${i + 1}.png`);
|
|
11102
|
+
fs43.writeFileSync(dest, bytes);
|
|
10677
11103
|
saved.push(dest);
|
|
10678
11104
|
opts.logger.success(`Saved: ${dest}`);
|
|
10679
11105
|
}
|
|
10680
11106
|
if (opts.output && saved[0]) {
|
|
10681
|
-
|
|
10682
|
-
|
|
11107
|
+
fs43.mkdirSync(path50.dirname(path50.resolve(opts.output)), { recursive: true });
|
|
11108
|
+
fs43.copyFileSync(saved[0], opts.output);
|
|
10683
11109
|
opts.logger.success(`Copied to: ${opts.output}`);
|
|
10684
11110
|
}
|
|
10685
11111
|
return saved;
|
|
@@ -10724,13 +11150,13 @@ async function listVideoModels(apiKey = requireOpenRouterKey()) {
|
|
|
10724
11150
|
}
|
|
10725
11151
|
|
|
10726
11152
|
// src/providers/openrouter/commands/video-status.ts
|
|
10727
|
-
import
|
|
10728
|
-
import
|
|
11153
|
+
import fs44 from "fs";
|
|
11154
|
+
import path51 from "path";
|
|
10729
11155
|
async function downloadOpenRouterVideo(opts) {
|
|
10730
11156
|
const { jobId, logger, source } = opts;
|
|
10731
11157
|
const apiKey = requireOpenRouterKey();
|
|
10732
11158
|
const url = videoContentUrl(jobId);
|
|
10733
|
-
const dest =
|
|
11159
|
+
const dest = path51.join(getOutputDir(), `openrouter_video_${jobId}.mp4`);
|
|
10734
11160
|
const { response: r } = await fetchWithValidatedRedirects(url, {
|
|
10735
11161
|
headers: { Authorization: `Bearer ${apiKey}` }
|
|
10736
11162
|
});
|
|
@@ -10742,14 +11168,14 @@ async function downloadOpenRouterVideo(opts) {
|
|
|
10742
11168
|
logger.error("Empty response body");
|
|
10743
11169
|
process.exit(1);
|
|
10744
11170
|
}
|
|
10745
|
-
|
|
10746
|
-
const ws =
|
|
11171
|
+
fs44.mkdirSync(path51.dirname(dest), { recursive: true });
|
|
11172
|
+
const ws = fs44.createWriteStream(dest);
|
|
10747
11173
|
const { pipeline: pipeline2 } = await import("stream/promises");
|
|
10748
11174
|
await pipeline2(r.body, ws);
|
|
10749
11175
|
logger.success(`Saved ${dest}`);
|
|
10750
11176
|
if (opts.output) {
|
|
10751
|
-
|
|
10752
|
-
|
|
11177
|
+
fs44.mkdirSync(path51.dirname(path51.resolve(opts.output)), { recursive: true });
|
|
11178
|
+
fs44.copyFileSync(dest, opts.output);
|
|
10753
11179
|
logger.success(`Copied to ${opts.output}`);
|
|
10754
11180
|
}
|
|
10755
11181
|
await maybeDownloadThumb(source, dest, {
|
|
@@ -10947,8 +11373,8 @@ function parseModelRef(ref, explicitVersion) {
|
|
|
10947
11373
|
}
|
|
10948
11374
|
return { kind: "version", version: trimmed };
|
|
10949
11375
|
}
|
|
10950
|
-
const [
|
|
10951
|
-
const [owner, name, ...extra] = (
|
|
11376
|
+
const [path58, inlineVersion, ...rest] = trimmed.split(":");
|
|
11377
|
+
const [owner, name, ...extra] = (path58 ?? "").split("/");
|
|
10952
11378
|
if (rest.length > 0 || extra.length > 0 || !owner || !name) {
|
|
10953
11379
|
throw new ValidationError(
|
|
10954
11380
|
`Invalid Replicate model "${ref}" \u2014 expected owner/name, owner/name:version, or a 64-char version id`
|
|
@@ -11108,9 +11534,9 @@ var ReplicateClient = class {
|
|
|
11108
11534
|
return { accept: "application/json", authorization: `Bearer ${this.apiKey}`, ...extra };
|
|
11109
11535
|
}
|
|
11110
11536
|
/** Raw request: throws HttpError so polling can classify transient failures. */
|
|
11111
|
-
raw(
|
|
11537
|
+
raw(path58, method, body, headers2, timeoutMs = REQUEST_TIMEOUT_MS) {
|
|
11112
11538
|
return fetchJson({
|
|
11113
|
-
url: `${this.baseUrl}${
|
|
11539
|
+
url: `${this.baseUrl}${path58}`,
|
|
11114
11540
|
method,
|
|
11115
11541
|
headers: this.headers(headers2),
|
|
11116
11542
|
body,
|
|
@@ -11122,7 +11548,7 @@ var ReplicateClient = class {
|
|
|
11122
11548
|
async createPrediction(opts) {
|
|
11123
11549
|
const { model, input } = opts;
|
|
11124
11550
|
const isOfficial = model.kind === "official";
|
|
11125
|
-
const
|
|
11551
|
+
const path58 = isOfficial ? `/models/${model.owner}/${model.name}/predictions` : "/predictions";
|
|
11126
11552
|
const body = isOfficial ? { input } : { version: model.version, input };
|
|
11127
11553
|
const subject = isOfficial ? `model ${model.owner}/${model.name}` : `model version ${model.version}`;
|
|
11128
11554
|
const headers2 = {};
|
|
@@ -11134,7 +11560,7 @@ var ReplicateClient = class {
|
|
|
11134
11560
|
}
|
|
11135
11561
|
for (let attempt = 0; ; attempt++) {
|
|
11136
11562
|
try {
|
|
11137
|
-
const res = await this.raw(
|
|
11563
|
+
const res = await this.raw(path58, "POST", body, headers2, timeoutMs);
|
|
11138
11564
|
return validatePrediction(res);
|
|
11139
11565
|
} catch (error) {
|
|
11140
11566
|
if (error instanceof HttpError && error.status === 429 && attempt < CREATE_MAX_RETRIES) {
|
|
@@ -11297,145 +11723,33 @@ import { InvalidArgumentError, Option as Option2 } from "commander";
|
|
|
11297
11723
|
|
|
11298
11724
|
// src/providers/replicate/node-outputs.ts
|
|
11299
11725
|
init_errors();
|
|
11300
|
-
import
|
|
11301
|
-
import
|
|
11726
|
+
import fs45 from "fs";
|
|
11727
|
+
import path52 from "path";
|
|
11302
11728
|
init_redact();
|
|
11303
11729
|
|
|
11304
11730
|
// src/providers/replicate/output.ts
|
|
11305
11731
|
init_redact();
|
|
11306
|
-
|
|
11307
|
-
// src/core/url-safety.ts
|
|
11308
|
-
init_errors();
|
|
11309
|
-
function ipv4Octets(host) {
|
|
11310
|
-
const parts = host.split(".");
|
|
11311
|
-
if (parts.length !== 4 || !parts.every((p) => /^\d{1,3}$/.test(p))) return void 0;
|
|
11312
|
-
const octets = parts.map(Number);
|
|
11313
|
-
return octets.every((o) => o <= 255) ? octets : void 0;
|
|
11314
|
-
}
|
|
11315
|
-
function isNonPublicIpv4([a = 0, b = 0, c2 = 0]) {
|
|
11316
|
-
return a === 0 || // "this network" / unspecified
|
|
11317
|
-
a === 10 || a === 127 || // loopback
|
|
11318
|
-
a === 100 && b >= 64 && b <= 127 || // carrier-grade NAT
|
|
11319
|
-
a === 169 && b === 254 || // link-local (cloud metadata)
|
|
11320
|
-
a === 172 && b >= 16 && b <= 31 || a === 192 && b === 0 && c2 === 0 || // IETF protocol assignments
|
|
11321
|
-
a === 192 && b === 0 && c2 === 2 || // TEST-NET-1
|
|
11322
|
-
a === 192 && b === 168 || a === 198 && (b === 18 || b === 19) || // benchmarking
|
|
11323
|
-
a === 198 && b === 51 && c2 === 100 || // TEST-NET-2
|
|
11324
|
-
a === 203 && b === 0 && c2 === 113 || // TEST-NET-3
|
|
11325
|
-
a >= 224;
|
|
11326
|
-
}
|
|
11327
|
-
function ipv6Groups(host) {
|
|
11328
|
-
if (!/^[0-9a-f:.]+$/i.test(host) || !host.includes(":")) return void 0;
|
|
11329
|
-
let text = host;
|
|
11330
|
-
const tail = /^(.*:)(\d+\.\d+\.\d+\.\d+)$/.exec(text);
|
|
11331
|
-
if (tail) {
|
|
11332
|
-
const octets = ipv4Octets(tail[2]);
|
|
11333
|
-
if (!octets) return void 0;
|
|
11334
|
-
const [a = 0, b = 0, c2 = 0, d = 0] = octets;
|
|
11335
|
-
text = `${tail[1]}${(a << 8 | b).toString(16)}:${(c2 << 8 | d).toString(16)}`;
|
|
11336
|
-
}
|
|
11337
|
-
const halves = text.split("::");
|
|
11338
|
-
if (halves.length > 2) return void 0;
|
|
11339
|
-
const parse = (s) => s === "" ? [] : s.split(":");
|
|
11340
|
-
const head = parse(halves[0]);
|
|
11341
|
-
const rest = halves.length === 2 ? parse(halves[1]) : [];
|
|
11342
|
-
const missing = 8 - head.length - rest.length;
|
|
11343
|
-
if (halves.length === 2 ? missing < 1 : missing !== 0) return void 0;
|
|
11344
|
-
const all = [...head, ...Array(halves.length === 2 ? missing : 0).fill("0"), ...rest];
|
|
11345
|
-
if (!all.every((g) => /^[0-9a-f]{1,4}$/i.test(g))) return void 0;
|
|
11346
|
-
return all.map((g) => Number.parseInt(g, 16));
|
|
11347
|
-
}
|
|
11348
|
-
function embeddedIpv4(hi, lo) {
|
|
11349
|
-
return [hi >> 8, hi & 255, lo >> 8, lo & 255];
|
|
11350
|
-
}
|
|
11351
|
-
function isNonPublicIpv6(groups) {
|
|
11352
|
-
const [g0 = 0, g1 = 0, g2 = 0, g3 = 0, g4 = 0, g5 = 0, g6 = 0, g7 = 0] = groups;
|
|
11353
|
-
const zeroUpTo = (n) => groups.slice(0, n).every((g) => g === 0);
|
|
11354
|
-
if (zeroUpTo(5) && g5 === 65535) return isNonPublicIpv4(embeddedIpv4(g6, g7));
|
|
11355
|
-
if (g0 === 100 && g1 === 65435 && g2 === 0 && g3 === 0 && g4 === 0 && g5 === 0) {
|
|
11356
|
-
return isNonPublicIpv4(embeddedIpv4(g6, g7));
|
|
11357
|
-
}
|
|
11358
|
-
if (g0 === 8194) return isNonPublicIpv4(embeddedIpv4(g1, g2));
|
|
11359
|
-
return g0 === 0 || // unspecified (::), loopback (::1), IPv4-compatible and other reserved
|
|
11360
|
-
g0 === 256 && g1 === 0 && g2 === 0 && g3 === 0 || // discard-only 100::/64
|
|
11361
|
-
(g0 & 65024) === 64512 || // unique local fc00::/7
|
|
11362
|
-
(g0 & 65472) === 65152 || // link-local fe80::/10
|
|
11363
|
-
(g0 & 65472) === 65216 || // deprecated site-local fec0::/10
|
|
11364
|
-
(g0 & 65280) === 65280;
|
|
11365
|
-
}
|
|
11366
|
-
function bareHost(hostname) {
|
|
11367
|
-
const host = hostname.toLowerCase().replace(/\.$/, "");
|
|
11368
|
-
return host.startsWith("[") && host.endsWith("]") ? host.slice(1, -1) : host;
|
|
11369
|
-
}
|
|
11370
|
-
function isLoopbackHost(hostname) {
|
|
11371
|
-
const host = bareHost(hostname);
|
|
11372
|
-
if (host === "localhost" || host.endsWith(".localhost")) return true;
|
|
11373
|
-
const octets = ipv4Octets(host);
|
|
11374
|
-
if (octets) return octets[0] === 127;
|
|
11375
|
-
const groups = ipv6Groups(host);
|
|
11376
|
-
if (!groups) return false;
|
|
11377
|
-
if (groups.slice(0, 5).every((g) => g === 0) && groups[5] === 65535) {
|
|
11378
|
-
return groups[6] !== void 0 && groups[6] >> 8 === 127;
|
|
11379
|
-
}
|
|
11380
|
-
return groups.slice(0, 7).every((g) => g === 0) && groups[7] === 1;
|
|
11381
|
-
}
|
|
11382
|
-
function isNonPublicHost(hostname) {
|
|
11383
|
-
const host = bareHost(hostname);
|
|
11384
|
-
if (host === "localhost" || host.endsWith(".localhost")) return true;
|
|
11385
|
-
const octets = ipv4Octets(host);
|
|
11386
|
-
if (octets) return isNonPublicIpv4(octets);
|
|
11387
|
-
const groups = ipv6Groups(host);
|
|
11388
|
-
return groups !== void 0 && isNonPublicIpv6(groups);
|
|
11389
|
-
}
|
|
11390
|
-
function checkUrlSafety(url, opts) {
|
|
11391
|
-
const action = opts.action ?? "download from";
|
|
11392
|
-
let parsed;
|
|
11393
|
-
try {
|
|
11394
|
-
parsed = new URL(url);
|
|
11395
|
-
} catch {
|
|
11396
|
-
throw new ProviderError(`Refusing to ${action} a malformed URL`, opts.provider);
|
|
11397
|
-
}
|
|
11398
|
-
const host = parsed.hostname.toLowerCase().replace(/\.$/, "");
|
|
11399
|
-
const localOk = opts.allowLocalhostHttp === true && isLoopbackHost(host);
|
|
11400
|
-
const httpOk = parsed.protocol === "https:" || localOk && parsed.protocol === "http:";
|
|
11401
|
-
if (!httpOk) {
|
|
11402
|
-
throw new ProviderError(
|
|
11403
|
-
`Refusing to ${action} ${host || "this URL"}: only https:// URLs are allowed`,
|
|
11404
|
-
opts.provider
|
|
11405
|
-
);
|
|
11406
|
-
}
|
|
11407
|
-
if (!localOk && isNonPublicHost(host)) {
|
|
11408
|
-
throw new ProviderError(
|
|
11409
|
-
`Refusing to ${action} ${host}: it points to a local or private address`,
|
|
11410
|
-
opts.provider
|
|
11411
|
-
);
|
|
11412
|
-
}
|
|
11413
|
-
const trusted = (opts.trustedHosts ?? []).some((t) => host === t || host.endsWith(`.${t}`));
|
|
11414
|
-
return { host, trusted };
|
|
11415
|
-
}
|
|
11416
|
-
|
|
11417
|
-
// src/providers/replicate/output.ts
|
|
11418
11732
|
var HTTP_RE = /^https?:\/\//i;
|
|
11419
11733
|
var DATA_URI_RE = /^data:[^,]*,/i;
|
|
11420
11734
|
function isMediaRef(value) {
|
|
11421
11735
|
return typeof value === "string" && (HTTP_RE.test(value) || DATA_URI_RE.test(value));
|
|
11422
11736
|
}
|
|
11423
11737
|
var REPLICATE_DELIVERY_HOST = "replicate.delivery";
|
|
11424
|
-
function
|
|
11738
|
+
function checkOutputUrl2(url) {
|
|
11425
11739
|
return checkUrlSafety(url, { provider: "replicate", trustedHosts: [REPLICATE_DELIVERY_HOST] });
|
|
11426
11740
|
}
|
|
11427
11741
|
function extractOutput(output) {
|
|
11428
11742
|
const result = { media: [], texts: [] };
|
|
11429
11743
|
const seen = /* @__PURE__ */ new Set();
|
|
11430
|
-
const visit = (value,
|
|
11744
|
+
const visit = (value, path58) => {
|
|
11431
11745
|
if (value === null || value === void 0) return;
|
|
11432
11746
|
if (typeof value === "string") {
|
|
11433
|
-
if (isMediaRef(value)) result.media.push({ url: value, path:
|
|
11434
|
-
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);
|
|
11435
11749
|
return;
|
|
11436
11750
|
}
|
|
11437
11751
|
if (typeof value !== "object") {
|
|
11438
|
-
result.texts.push(
|
|
11752
|
+
result.texts.push(path58 ? `${path58}: ${String(value)}` : String(value));
|
|
11439
11753
|
return;
|
|
11440
11754
|
}
|
|
11441
11755
|
if (seen.has(value)) return;
|
|
@@ -11443,14 +11757,14 @@ function extractOutput(output) {
|
|
|
11443
11757
|
if (Array.isArray(value)) {
|
|
11444
11758
|
if (value.length > 0 && value.every((v) => typeof v === "string" && !isMediaRef(v))) {
|
|
11445
11759
|
const joined = value.join("");
|
|
11446
|
-
if (joined.length > 0) result.texts.push(
|
|
11760
|
+
if (joined.length > 0) result.texts.push(path58 ? `${path58}: ${joined}` : joined);
|
|
11447
11761
|
return;
|
|
11448
11762
|
}
|
|
11449
|
-
value.forEach((item, i) => visit(item, `${
|
|
11763
|
+
value.forEach((item, i) => visit(item, `${path58}[${i}]`));
|
|
11450
11764
|
return;
|
|
11451
11765
|
}
|
|
11452
11766
|
for (const [key, val] of Object.entries(value)) {
|
|
11453
|
-
visit(val,
|
|
11767
|
+
visit(val, path58 ? `${path58}.${key}` : key);
|
|
11454
11768
|
}
|
|
11455
11769
|
};
|
|
11456
11770
|
visit(output, "");
|
|
@@ -11568,16 +11882,16 @@ var DOWNLOAD_TIMEOUT_MS = 3e5;
|
|
|
11568
11882
|
function isDirTarget(output) {
|
|
11569
11883
|
if (/[\\/]$/.test(output)) return true;
|
|
11570
11884
|
try {
|
|
11571
|
-
return
|
|
11885
|
+
return fs45.statSync(output).isDirectory();
|
|
11572
11886
|
} catch {
|
|
11573
11887
|
return false;
|
|
11574
11888
|
}
|
|
11575
11889
|
}
|
|
11576
11890
|
function resolveDestination(predictionId, index, count, ext, opts) {
|
|
11577
|
-
if (!opts.output) return
|
|
11578
|
-
const output =
|
|
11579
|
-
if (isDirTarget(opts.output)) return
|
|
11580
|
-
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);
|
|
11581
11895
|
if (count === 1) return givenExt ? output : `${output}${ext}`;
|
|
11582
11896
|
const base = givenExt ? output.slice(0, -givenExt.length) : output;
|
|
11583
11897
|
return `${base}-${String(index + 1).padStart(2, "0")}${ext}`;
|
|
@@ -11595,8 +11909,8 @@ async function saveOne(item, predictionId, index, count, opts) {
|
|
|
11595
11909
|
extFromUrl4(item.url) ?? ".bin",
|
|
11596
11910
|
opts
|
|
11597
11911
|
);
|
|
11598
|
-
|
|
11599
|
-
|
|
11912
|
+
fs45.mkdirSync(path52.dirname(dest), { recursive: true });
|
|
11913
|
+
fs45.writeFileSync(dest, bytes);
|
|
11600
11914
|
return dest;
|
|
11601
11915
|
}
|
|
11602
11916
|
const warned2 = opts.warnedHosts;
|
|
@@ -11610,7 +11924,7 @@ async function saveOne(item, predictionId, index, count, opts) {
|
|
|
11610
11924
|
maxBytes: opts.maxBytes,
|
|
11611
11925
|
fetch: opts.fetch,
|
|
11612
11926
|
validateUrl: (url) => {
|
|
11613
|
-
const { host, trusted } =
|
|
11927
|
+
const { host, trusted } = checkOutputUrl2(url);
|
|
11614
11928
|
if (!trusted && !warned2.has(host)) {
|
|
11615
11929
|
warned2.add(host);
|
|
11616
11930
|
opts.logger.warn(
|
|
@@ -11811,8 +12125,8 @@ function registerReplicateImageCommand(parent) {
|
|
|
11811
12125
|
|
|
11812
12126
|
// src/providers/replicate/node-input-files.ts
|
|
11813
12127
|
init_errors();
|
|
11814
|
-
import
|
|
11815
|
-
import
|
|
12128
|
+
import fs46 from "fs";
|
|
12129
|
+
import path53 from "path";
|
|
11816
12130
|
var DATA_URI_MAX_BYTES = 256 * 1024;
|
|
11817
12131
|
var UPLOAD_MAX_BYTES = 100 * 1024 * 1024;
|
|
11818
12132
|
function formatMb4(bytes) {
|
|
@@ -11820,10 +12134,10 @@ function formatMb4(bytes) {
|
|
|
11820
12134
|
}
|
|
11821
12135
|
async function resolveFileInput(value, client, logger) {
|
|
11822
12136
|
if (isMediaRef(value)) return value;
|
|
11823
|
-
const filePath =
|
|
12137
|
+
const filePath = path53.resolve(value);
|
|
11824
12138
|
let stat;
|
|
11825
12139
|
try {
|
|
11826
|
-
stat =
|
|
12140
|
+
stat = fs46.statSync(filePath);
|
|
11827
12141
|
} catch {
|
|
11828
12142
|
throw new ValidationError(`Input file not found: ${value} (pass a local path or an https URL)`);
|
|
11829
12143
|
}
|
|
@@ -11834,9 +12148,9 @@ async function resolveFileInput(value, client, logger) {
|
|
|
11834
12148
|
`Input file ${value} is ${formatMb4(stat.size)}; Replicate accepts uploads under 100MB. Host it and pass an https URL instead.`
|
|
11835
12149
|
);
|
|
11836
12150
|
}
|
|
11837
|
-
const filename =
|
|
12151
|
+
const filename = path53.basename(filePath);
|
|
11838
12152
|
const contentType = mimeFromFilename(filename);
|
|
11839
|
-
const bytes =
|
|
12153
|
+
const bytes = fs46.readFileSync(filePath);
|
|
11840
12154
|
if (stat.size <= DATA_URI_MAX_BYTES) {
|
|
11841
12155
|
logger.debug(`Inlining ${filename} (${stat.size} bytes) as a data URI`);
|
|
11842
12156
|
return `data:${contentType};base64,${bytes.toString("base64")}`;
|
|
@@ -11963,8 +12277,8 @@ function registerReplicateCommands(program2) {
|
|
|
11963
12277
|
}
|
|
11964
12278
|
|
|
11965
12279
|
// src/commands/check.ts
|
|
11966
|
-
import
|
|
11967
|
-
import
|
|
12280
|
+
import fs47 from "fs";
|
|
12281
|
+
import os5 from "os";
|
|
11968
12282
|
|
|
11969
12283
|
// src/commands/check-helpers/binary-check.ts
|
|
11970
12284
|
import { execa as execa2 } from "execa";
|
|
@@ -12212,8 +12526,8 @@ function registerCheckCommand(program2) {
|
|
|
12212
12526
|
logger.info(`Env files loaded: ${files.length > 0 ? files.join(", ") : "none"}`);
|
|
12213
12527
|
}
|
|
12214
12528
|
if (!legacyClaudeEnvEnabled()) {
|
|
12215
|
-
const home = process.env.MULTIX_DISABLE_HOME_ENV ? void 0 :
|
|
12216
|
-
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));
|
|
12217
12531
|
if (legacy.length > 0) {
|
|
12218
12532
|
logger.info(
|
|
12219
12533
|
`Found ${legacy.length} legacy .claude env file(s); set MULTIX_LEGACY_CLAUDE_ENV=1 to load them`
|
|
@@ -12330,8 +12644,8 @@ function registerCheckCommand(program2) {
|
|
|
12330
12644
|
}
|
|
12331
12645
|
|
|
12332
12646
|
// src/commands/doc/convert.ts
|
|
12333
|
-
import
|
|
12334
|
-
import
|
|
12647
|
+
import fs48 from "fs";
|
|
12648
|
+
import path54 from "path";
|
|
12335
12649
|
|
|
12336
12650
|
// src/commands/doc/default-prompt.ts
|
|
12337
12651
|
var DEFAULT_CONVERT_PROMPT = `Convert this document to clean, well-formatted Markdown.
|
|
@@ -12349,7 +12663,7 @@ Output only the markdown content without any preamble or explanation.`;
|
|
|
12349
12663
|
// src/commands/doc/convert.ts
|
|
12350
12664
|
function autoNameBase(input) {
|
|
12351
12665
|
if (input === STDIN_ARG) return "stdin";
|
|
12352
|
-
return
|
|
12666
|
+
return path54.basename(input, path54.extname(input));
|
|
12353
12667
|
}
|
|
12354
12668
|
async function convertDocuments(opts) {
|
|
12355
12669
|
const { output, autoName = false, verbose, logger } = opts;
|
|
@@ -12366,13 +12680,13 @@ async function convertDocuments(opts) {
|
|
|
12366
12680
|
try {
|
|
12367
12681
|
let outputPath;
|
|
12368
12682
|
if (output) {
|
|
12369
|
-
outputPath =
|
|
12683
|
+
outputPath = path54.resolve(output);
|
|
12370
12684
|
} else if (autoName && opts.inputs.length === 1 && opts.inputs[0]) {
|
|
12371
|
-
outputPath =
|
|
12685
|
+
outputPath = path54.join(getOutputDir(), `${autoNameBase(opts.inputs[0])}-extraction.md`);
|
|
12372
12686
|
} else {
|
|
12373
|
-
outputPath =
|
|
12687
|
+
outputPath = path54.join(getOutputDir(), "document-extraction.md");
|
|
12374
12688
|
}
|
|
12375
|
-
|
|
12689
|
+
fs48.mkdirSync(path54.dirname(outputPath), { recursive: true });
|
|
12376
12690
|
failed = await convertResolved(inputs, { outputPath, model, prompt, verbose, logger });
|
|
12377
12691
|
} finally {
|
|
12378
12692
|
cleanup();
|
|
@@ -12445,7 +12759,7 @@ async function convertResolved(inputs, ctx) {
|
|
|
12445
12759
|
`);
|
|
12446
12760
|
for (const r of results) {
|
|
12447
12761
|
lines.push(`
|
|
12448
|
-
## ${
|
|
12762
|
+
## ${path54.basename(r.file).replace(/[<>]/g, "\\$&")}
|
|
12449
12763
|
`);
|
|
12450
12764
|
if (r.status === "success" && r.markdown) {
|
|
12451
12765
|
lines.push(`${r.markdown}
|
|
@@ -12458,7 +12772,7 @@ async function convertResolved(inputs, ctx) {
|
|
|
12458
12772
|
}
|
|
12459
12773
|
lines.push("\n---\n");
|
|
12460
12774
|
}
|
|
12461
|
-
|
|
12775
|
+
fs48.writeFileSync(outputPath, lines.join("\n"), "utf8");
|
|
12462
12776
|
const succeeded = results.filter((r) => r.status === "success").length;
|
|
12463
12777
|
console.log(`
|
|
12464
12778
|
${"=".repeat(50)}`);
|
|
@@ -12512,12 +12826,12 @@ function registerDocCommands(program2) {
|
|
|
12512
12826
|
|
|
12513
12827
|
// src/commands/media/index.ts
|
|
12514
12828
|
init_errors();
|
|
12515
|
-
import
|
|
12516
|
-
import
|
|
12829
|
+
import fs51 from "fs";
|
|
12830
|
+
import path57 from "path";
|
|
12517
12831
|
|
|
12518
12832
|
// src/commands/media/batch.ts
|
|
12519
|
-
import
|
|
12520
|
-
import
|
|
12833
|
+
import fs49 from "fs";
|
|
12834
|
+
import path55 from "path";
|
|
12521
12835
|
|
|
12522
12836
|
// src/commands/media/detect-type.ts
|
|
12523
12837
|
var VIDEO_EXTS2 = /* @__PURE__ */ new Set([".mp4", ".mov", ".avi", ".mkv", ".webm", ".flv", ".mpg", ".mpeg"]);
|
|
@@ -12713,10 +13027,10 @@ async function batchOptimize(opts) {
|
|
|
12713
13027
|
verbose,
|
|
12714
13028
|
logger
|
|
12715
13029
|
} = opts;
|
|
12716
|
-
|
|
12717
|
-
const entries =
|
|
13030
|
+
fs49.mkdirSync(outputDir, { recursive: true });
|
|
13031
|
+
const entries = fs49.readdirSync(inputDir);
|
|
12718
13032
|
const mediaFiles = entries.filter((f) => {
|
|
12719
|
-
const ext =
|
|
13033
|
+
const ext = path55.extname(f).toLowerCase();
|
|
12720
13034
|
return ALL_EXTS.has(ext);
|
|
12721
13035
|
});
|
|
12722
13036
|
if (mediaFiles.length === 0) {
|
|
@@ -12727,9 +13041,9 @@ async function batchOptimize(opts) {
|
|
|
12727
13041
|
let succeeded = 0;
|
|
12728
13042
|
const errors = [];
|
|
12729
13043
|
for (const file of mediaFiles) {
|
|
12730
|
-
const inputPath =
|
|
12731
|
-
const outputPath =
|
|
12732
|
-
const ext =
|
|
13044
|
+
const inputPath = path55.join(inputDir, file);
|
|
13045
|
+
const outputPath = path55.join(outputDir, file);
|
|
13046
|
+
const ext = path55.extname(file).toLowerCase();
|
|
12733
13047
|
const kind = extToKind(ext);
|
|
12734
13048
|
logger?.debug(`Processing: ${file}`);
|
|
12735
13049
|
try {
|
|
@@ -12766,8 +13080,8 @@ async function batchOptimize(opts) {
|
|
|
12766
13080
|
|
|
12767
13081
|
// src/commands/media/split-video.ts
|
|
12768
13082
|
init_errors();
|
|
12769
|
-
import
|
|
12770
|
-
import
|
|
13083
|
+
import fs50 from "fs";
|
|
13084
|
+
import path56 from "path";
|
|
12771
13085
|
async function splitVideo(opts) {
|
|
12772
13086
|
await assertFfmpeg();
|
|
12773
13087
|
const { input, outputDir, chunkDuration = 3600, verbose, logger } = opts;
|
|
@@ -12778,10 +13092,10 @@ async function splitVideo(opts) {
|
|
|
12778
13092
|
logger?.info("Video is short enough \u2014 no splitting needed");
|
|
12779
13093
|
return [input];
|
|
12780
13094
|
}
|
|
12781
|
-
|
|
12782
|
-
const ext =
|
|
12783
|
-
const baseName =
|
|
12784
|
-
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}`);
|
|
12785
13099
|
logger?.debug(`Splitting into ~${numChunks} chunks of ${chunkDuration}s each`);
|
|
12786
13100
|
const args = [
|
|
12787
13101
|
"-i",
|
|
@@ -12800,8 +13114,8 @@ async function splitVideo(opts) {
|
|
|
12800
13114
|
await runFfmpeg(args, { verbose, logger });
|
|
12801
13115
|
const files = [];
|
|
12802
13116
|
for (let i = 0; i < numChunks + 1; i++) {
|
|
12803
|
-
const fname =
|
|
12804
|
-
if (
|
|
13117
|
+
const fname = path56.join(outputDir, `${baseName}_chunk_${String(i).padStart(3, "0")}${ext}`);
|
|
13118
|
+
if (fs50.existsSync(fname)) files.push(fname);
|
|
12805
13119
|
}
|
|
12806
13120
|
logger?.success(`Created ${files.length} chunks in ${outputDir}`);
|
|
12807
13121
|
return files;
|
|
@@ -12815,9 +13129,9 @@ function registerMediaCommands(program2) {
|
|
|
12815
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(
|
|
12816
13130
|
async (opts) => {
|
|
12817
13131
|
const logger = createLogger({ verbose: opts.verbose ?? false });
|
|
12818
|
-
if (!
|
|
12819
|
-
|
|
12820
|
-
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();
|
|
12821
13135
|
const kind = extToKind(ext);
|
|
12822
13136
|
if (!kind) throw new ValidationError(`Unsupported file type: ${ext}`);
|
|
12823
13137
|
const quality = Number.parseInt(opts.quality, 10);
|
|
@@ -12857,7 +13171,7 @@ function registerMediaCommands(program2) {
|
|
|
12857
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(
|
|
12858
13172
|
async (opts) => {
|
|
12859
13173
|
const logger = createLogger({ verbose: opts.verbose ?? false });
|
|
12860
|
-
if (!
|
|
13174
|
+
if (!fs51.existsSync(opts.input)) throw new ValidationError(`Input not found: ${opts.input}`);
|
|
12861
13175
|
const chunks = await splitVideo({
|
|
12862
13176
|
input: opts.input,
|
|
12863
13177
|
outputDir: opts.outputDir,
|
|
@@ -12873,7 +13187,7 @@ Created ${chunks.length} chunk(s) in ${opts.outputDir}`);
|
|
|
12873
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(
|
|
12874
13188
|
async (opts) => {
|
|
12875
13189
|
const logger = createLogger({ verbose: opts.verbose ?? false });
|
|
12876
|
-
if (!
|
|
13190
|
+
if (!fs51.existsSync(opts.inputDir))
|
|
12877
13191
|
throw new ValidationError(`Input dir not found: ${opts.inputDir}`);
|
|
12878
13192
|
const result = await batchOptimize({
|
|
12879
13193
|
inputDir: opts.inputDir,
|