@bonginkan/maria 4.3.16 → 4.3.17
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- package/README.md +4 -4
- package/dist/READY.manifest.json +1 -1
- package/dist/bin/maria.cjs +34 -10
- package/dist/bin/maria.cjs.map +1 -1
- package/dist/cli.cjs +34 -10
- package/dist/cli.cjs.map +1 -1
- package/dist/index.js +2 -2
- package/dist/index.js.map +1 -1
- package/dist/server/express-server.cjs +183 -36
- package/dist/server/express-server.js +183 -36
- package/dist/server-express.cjs +183 -36
- package/dist/server-express.cjs.map +1 -1
- package/package.json +2 -2
- package/src/slash-commands/READY.manifest.json +1 -1
|
@@ -6531,12 +6531,12 @@ __export(src_exports, {
|
|
|
6531
6531
|
Response: () => Response,
|
|
6532
6532
|
blobFrom: () => blobFrom,
|
|
6533
6533
|
blobFromSync: () => blobFromSync,
|
|
6534
|
-
default: () =>
|
|
6534
|
+
default: () => fetch2,
|
|
6535
6535
|
fileFrom: () => fileFrom,
|
|
6536
6536
|
fileFromSync: () => fileFromSync,
|
|
6537
6537
|
isRedirect: () => isRedirect
|
|
6538
6538
|
});
|
|
6539
|
-
async function
|
|
6539
|
+
async function fetch2(url, options_) {
|
|
6540
6540
|
return new Promise((resolve2, reject) => {
|
|
6541
6541
|
const request = new Request(url, options_);
|
|
6542
6542
|
const { parsedURL, options } = getNodeRequestOptions(request);
|
|
@@ -6668,7 +6668,7 @@ async function fetch(url, options_) {
|
|
|
6668
6668
|
if (responseReferrerPolicy) {
|
|
6669
6669
|
requestOptions.referrerPolicy = responseReferrerPolicy;
|
|
6670
6670
|
}
|
|
6671
|
-
resolve2(
|
|
6671
|
+
resolve2(fetch2(new Request(locationURL, requestOptions)));
|
|
6672
6672
|
finalize();
|
|
6673
6673
|
return;
|
|
6674
6674
|
}
|
|
@@ -6991,6 +6991,15 @@ async function hasCaseInsensitiveCollision(dirFull, targetFile) {
|
|
|
6991
6991
|
async function atomicRename(stage, dest) {
|
|
6992
6992
|
try {
|
|
6993
6993
|
await fsp__namespace.rename(stage, dest);
|
|
6994
|
+
try {
|
|
6995
|
+
const fd = await fsp__namespace.open(dest, "r");
|
|
6996
|
+
try {
|
|
6997
|
+
await fd.sync();
|
|
6998
|
+
} finally {
|
|
6999
|
+
await fd.close();
|
|
7000
|
+
}
|
|
7001
|
+
} catch {
|
|
7002
|
+
}
|
|
6994
7003
|
} catch (e2) {
|
|
6995
7004
|
if (e2 && e2.code === "EXDEV") {
|
|
6996
7005
|
await fsp__namespace.copyFile(stage, dest);
|
|
@@ -7045,7 +7054,8 @@ async function saveArtifacts(ctx, items, manifest) {
|
|
|
7045
7054
|
}
|
|
7046
7055
|
await fsp__namespace.writeFile(stg, it.bytes);
|
|
7047
7056
|
await atomicRename(stg, dest.full);
|
|
7048
|
-
|
|
7057
|
+
const relPosix = dest.rel.replace(/\\/g, "/");
|
|
7058
|
+
saved.push(relPosix);
|
|
7049
7059
|
}
|
|
7050
7060
|
if (!ctx.skipManifest) {
|
|
7051
7061
|
const manifestObj = {
|
|
@@ -7064,7 +7074,7 @@ async function saveArtifacts(ctx, items, manifest) {
|
|
|
7064
7074
|
}
|
|
7065
7075
|
await atomicRename(manifestStage, manifestFull);
|
|
7066
7076
|
await fsp__namespace.rm(stage, { recursive: true, force: true });
|
|
7067
|
-
return { files: saved, manifestPath: manifestPathRel };
|
|
7077
|
+
return { files: saved, manifestPath: manifestPathRel.replace(/\\/g, "/") };
|
|
7068
7078
|
} else {
|
|
7069
7079
|
await fsp__namespace.rm(stage, { recursive: true, force: true });
|
|
7070
7080
|
return { files: saved, manifestPath: "" };
|
|
@@ -7111,6 +7121,13 @@ var GeminiMediaProvider = class {
|
|
|
7111
7121
|
`GeminiMediaProvider.generateImage request failed: model=${modelName}; prompt="${promptPreview}"; error=${errMsg}`
|
|
7112
7122
|
);
|
|
7113
7123
|
}
|
|
7124
|
+
const feedback = resp?.response?.promptFeedback;
|
|
7125
|
+
const blockReason = feedback?.blockReason || feedback?.block_reason;
|
|
7126
|
+
if (blockReason) {
|
|
7127
|
+
const modelName2 = this.primaryModel;
|
|
7128
|
+
const reason = String(blockReason);
|
|
7129
|
+
throw new Error(`GeminiMediaProvider.policy_violation: model=${modelName2}; reason=${reason}`);
|
|
7130
|
+
}
|
|
7114
7131
|
const parts = resp?.response?.candidates?.[0]?.content?.parts || [];
|
|
7115
7132
|
for (const p of parts) {
|
|
7116
7133
|
const data = p?.inlineData?.data || p?.inline_data?.data;
|
|
@@ -7801,12 +7818,12 @@ var UnifiedBaseProvider = class {
|
|
|
7801
7818
|
}
|
|
7802
7819
|
// Helper method for HTTP requests
|
|
7803
7820
|
async makeRequest(url, options) {
|
|
7804
|
-
const
|
|
7821
|
+
const fetch3 = (await Promise.resolve().then(() => (init_src(), src_exports))).default;
|
|
7805
7822
|
const timeoutMs = options.timeout || 3e4;
|
|
7806
7823
|
const controller = new AbortController();
|
|
7807
7824
|
const timeoutId = setTimeout(() => controller.abort(), timeoutMs);
|
|
7808
7825
|
try {
|
|
7809
|
-
const response2 = await
|
|
7826
|
+
const response2 = await fetch3(url, {
|
|
7810
7827
|
method: "POST",
|
|
7811
7828
|
headers: {
|
|
7812
7829
|
"Content-Type": "application/json",
|
|
@@ -7830,12 +7847,12 @@ var UnifiedBaseProvider = class {
|
|
|
7830
7847
|
}
|
|
7831
7848
|
// Helper method for streaming requests
|
|
7832
7849
|
async makeStreamRequest(url, options) {
|
|
7833
|
-
const
|
|
7850
|
+
const fetch3 = (await Promise.resolve().then(() => (init_src(), src_exports))).default;
|
|
7834
7851
|
const timeoutMs = options.timeout || 3e4;
|
|
7835
7852
|
const controller = new AbortController();
|
|
7836
7853
|
const timeoutId = setTimeout(() => controller.abort(), timeoutMs);
|
|
7837
7854
|
try {
|
|
7838
|
-
const response2 = await
|
|
7855
|
+
const response2 = await fetch3(url, {
|
|
7839
7856
|
method: "POST",
|
|
7840
7857
|
headers: {
|
|
7841
7858
|
"Content-Type": "application/json",
|
|
@@ -8548,7 +8565,7 @@ app.use(express__default.default.json({ limit: "10mb" }));
|
|
|
8548
8565
|
app.use(express__default.default.urlencoded({ extended: true }));
|
|
8549
8566
|
try {
|
|
8550
8567
|
const artifactsDir = path__namespace.default.resolve(process.cwd(), "artifacts");
|
|
8551
|
-
app.use("/artifacts", express__default.default.static(artifactsDir, { fallthrough: true, extensions: ["json", "png", "jpg", "webp", "mp4"] }));
|
|
8568
|
+
app.use("/artifacts", express__default.default.static(artifactsDir, { fallthrough: true, extensions: ["json", "png", "jpg", "webp", "mp4", "webm", "mov"] }));
|
|
8552
8569
|
} catch {
|
|
8553
8570
|
}
|
|
8554
8571
|
app.use((req, res, next) => {
|
|
@@ -8572,7 +8589,7 @@ app.get("/api/status", (req, res) => {
|
|
|
8572
8589
|
app.get("/", (req, res) => {
|
|
8573
8590
|
res.json({
|
|
8574
8591
|
name: "MARIA CODE API",
|
|
8575
|
-
version: "4.3.
|
|
8592
|
+
version: "4.3.17",
|
|
8576
8593
|
status: "running",
|
|
8577
8594
|
environment: process.env.NODE_ENV || "development",
|
|
8578
8595
|
endpoints: {
|
|
@@ -8587,6 +8604,30 @@ app.get("/", (req, res) => {
|
|
|
8587
8604
|
timestamp: (/* @__PURE__ */ new Date()).toISOString()
|
|
8588
8605
|
});
|
|
8589
8606
|
});
|
|
8607
|
+
function classifyMediaError(err) {
|
|
8608
|
+
const raw = err;
|
|
8609
|
+
const msg = String(raw?.message || raw || "unknown error");
|
|
8610
|
+
const lower2 = msg.toLowerCase();
|
|
8611
|
+
if (lower2.includes("missing api key") || lower2.includes("api key") && lower2.includes("missing")) {
|
|
8612
|
+
return { status: 503, code: "provider_unavailable", message: "Provider API key is not configured", hint: "Set GOOGLE_API_KEY or GEMINI_API_KEY on the server" };
|
|
8613
|
+
}
|
|
8614
|
+
if (lower2.includes("invalid api key") || lower2.includes("permission denied") || lower2.includes("unauthorized")) {
|
|
8615
|
+
return { status: 502, code: "provider_auth_failed", message: "Provider authentication failed", hint: "Verify your Google AI Studio API key" };
|
|
8616
|
+
}
|
|
8617
|
+
if (lower2.includes("blockreason") || lower2.includes("safety") || lower2.includes("blocked") || lower2.includes("policy")) {
|
|
8618
|
+
return { status: 422, code: "policy_violation", message: "Request was blocked by provider policy", hint: "Modify the prompt to comply with safety policies" };
|
|
8619
|
+
}
|
|
8620
|
+
if (lower2.includes("no inline image returned") || lower2.includes("no video returned") || lower2.includes("refus")) {
|
|
8621
|
+
return { status: 422, code: "content_refused", message: "Model refused or returned no content", hint: "Try rephrasing the prompt or a different model" };
|
|
8622
|
+
}
|
|
8623
|
+
if (lower2.includes("timeout")) {
|
|
8624
|
+
return { status: 504, code: "timeout", message: "Generation timed out", hint: "Please retry later" };
|
|
8625
|
+
}
|
|
8626
|
+
if (lower2.includes("rate limit") || lower2.includes("429")) {
|
|
8627
|
+
return { status: 429, code: "rate_limited", message: "Rate limit exceeded", hint: "Slow down requests or try again shortly" };
|
|
8628
|
+
}
|
|
8629
|
+
return { status: 500, code: "internal_error", message: "Failed to generate media" };
|
|
8630
|
+
}
|
|
8590
8631
|
async function decodeFirebaseToken(token) {
|
|
8591
8632
|
try {
|
|
8592
8633
|
const admin = await import('firebase-admin').catch(() => null);
|
|
@@ -8716,9 +8757,10 @@ app.post("/api/v1/image", rateLimitMiddleware, async (req, res) => {
|
|
|
8716
8757
|
try {
|
|
8717
8758
|
await loadProviderKeys();
|
|
8718
8759
|
const auth = req.headers.authorization;
|
|
8719
|
-
if (!auth || !auth.startsWith("Bearer ")) return res.status(401).json({ error: "unauthorized" });
|
|
8760
|
+
if (!auth || !auth.startsWith("Bearer ")) return res.status(401).json({ error: "unauthorized", message: "Login required", hint: "Sign in and retry" });
|
|
8720
8761
|
const idToken = auth.substring("Bearer ".length).trim();
|
|
8721
8762
|
const decoded = await decodeFirebaseToken(idToken).catch(() => null);
|
|
8763
|
+
if (!decoded) return res.status(401).json({ error: "unauthorized", message: "Invalid login session", hint: "Re-login to continue" });
|
|
8722
8764
|
const uid = decoded?.uid || decoded?.sub || "current";
|
|
8723
8765
|
const { prompt, model, size = "1024x1024", format = "png", count = 1, seed } = req.body || {};
|
|
8724
8766
|
if (!prompt) return res.status(400).json({ error: "bad_request", message: "prompt required" });
|
|
@@ -8760,17 +8802,15 @@ app.post("/api/v1/image", rateLimitMiddleware, async (req, res) => {
|
|
|
8760
8802
|
return res.json({ success: true, data: { url: saved.manifestPath, files: saved.files, filesInline, jobId: manifest.trace } });
|
|
8761
8803
|
} catch (error) {
|
|
8762
8804
|
console.error("[Image API] Error:", error);
|
|
8763
|
-
|
|
8764
|
-
|
|
8765
|
-
message: "Failed to generate image"
|
|
8766
|
-
});
|
|
8805
|
+
const mapped = classifyMediaError(error);
|
|
8806
|
+
return res.status(mapped.status).json({ error: mapped.code, message: mapped.message, hint: mapped.hint });
|
|
8767
8807
|
}
|
|
8768
8808
|
});
|
|
8769
8809
|
app.post("/api/v1/video", rateLimitMiddleware, async (req, res) => {
|
|
8770
8810
|
try {
|
|
8771
8811
|
await loadProviderKeys();
|
|
8772
8812
|
const auth = req.headers.authorization;
|
|
8773
|
-
if (!auth || !auth.startsWith("Bearer ")) return res.status(401).json({ error: "unauthorized" });
|
|
8813
|
+
if (!auth || !auth.startsWith("Bearer ")) return res.status(401).json({ error: "unauthorized", message: "Login required", hint: "Sign in and retry" });
|
|
8774
8814
|
const { prompt, duration = 8, fps = 24, res: resStr = "1280x720", format = "mp4", model, seed } = req.body || {};
|
|
8775
8815
|
if (!prompt) return res.status(400).json({ error: "bad_request", message: "prompt required" });
|
|
8776
8816
|
const m2 = /^(\d{2,4})x(\d{2,4})$/.exec(String(resStr));
|
|
@@ -8778,7 +8818,7 @@ app.post("/api/v1/video", rateLimitMiddleware, async (req, res) => {
|
|
|
8778
8818
|
const w = +m2[1], h2 = +m2[2];
|
|
8779
8819
|
const { GoogleGenAI } = __require("@google/genai");
|
|
8780
8820
|
const apiKey = process.env.GOOGLE_API_KEY || process.env.GEMINI_API_KEY;
|
|
8781
|
-
if (!apiKey) return res.status(
|
|
8821
|
+
if (!apiKey) return res.status(503).json({ error: "provider_unavailable", message: "Provider API key is not configured", hint: "Set GOOGLE_API_KEY or GEMINI_API_KEY on the server" });
|
|
8782
8822
|
const ai = new GoogleGenAI({ apiKey });
|
|
8783
8823
|
const veoModel = model && String(model).trim() || process.env.MARIA_VIDEO_MODEL || "veo-3.0-generate-001";
|
|
8784
8824
|
const aspectRatio = w >= h2 ? "16:9" : "9:16";
|
|
@@ -8793,14 +8833,15 @@ app.post("/api/v1/video", rateLimitMiddleware, async (req, res) => {
|
|
|
8793
8833
|
// Pass duration/fps to provider to avoid default ~1s clips
|
|
8794
8834
|
config: { aspectRatio, durationSeconds: effectiveDuration, frameRate: effectiveFps }
|
|
8795
8835
|
});
|
|
8796
|
-
const deadline = Date.now() +
|
|
8836
|
+
const deadline = Date.now() + 72e4;
|
|
8797
8837
|
while (!operation?.done) {
|
|
8798
8838
|
if (Date.now() > deadline) {
|
|
8799
8839
|
return res.status(504).json({ error: "timeout", message: "video generation timed out" });
|
|
8800
8840
|
}
|
|
8801
|
-
await new Promise((r2) => setTimeout(r2,
|
|
8841
|
+
await new Promise((r2) => setTimeout(r2, 1e4));
|
|
8802
8842
|
operation = await ai.operations.getVideosOperation({ operation });
|
|
8803
8843
|
}
|
|
8844
|
+
await new Promise((r2) => setTimeout(r2, 2500));
|
|
8804
8845
|
const videoRef = operation?.response?.generatedVideos?.[0]?.video;
|
|
8805
8846
|
const videoMime = videoRef && (videoRef.mimeType || videoRef.mime_type) ? String(videoRef.mimeType || videoRef.mime_type) : void 0;
|
|
8806
8847
|
if (!videoRef) {
|
|
@@ -8820,36 +8861,143 @@ app.post("/api/v1/video", rateLimitMiddleware, async (req, res) => {
|
|
|
8820
8861
|
const looksHtml = headStr.includes("<!doctype") || headStr.includes("<html") || headStr.includes("<?xml");
|
|
8821
8862
|
return (hasFtyp2 || isWebm2) && !looksHtml;
|
|
8822
8863
|
};
|
|
8823
|
-
const
|
|
8864
|
+
const readUint32BE = (buf, off) => (buf[off] << 24 | buf[off + 1] << 16 | buf[off + 2] << 8 | buf[off + 3]) >>> 0;
|
|
8865
|
+
const readUint64BE = (buf, off) => {
|
|
8866
|
+
const hi = readUint32BE(buf, off);
|
|
8867
|
+
const lo = readUint32BE(buf, off + 4);
|
|
8868
|
+
return hi * 2 ** 32 + lo;
|
|
8869
|
+
};
|
|
8870
|
+
const tryParseMp4DurationSec = (buf) => {
|
|
8871
|
+
try {
|
|
8872
|
+
for (let i2 = 0; i2 + 8 < buf.length; ) {
|
|
8873
|
+
const size = readUint32BE(buf, i2);
|
|
8874
|
+
const type = buf.subarray(i2 + 4, i2 + 8).toString("latin1");
|
|
8875
|
+
if (!size || size < 8) break;
|
|
8876
|
+
if (type === "moov") {
|
|
8877
|
+
const end = Math.min(buf.length, i2 + size);
|
|
8878
|
+
let j = i2 + 8;
|
|
8879
|
+
while (j + 8 < end) {
|
|
8880
|
+
const sz = readUint32BE(buf, j);
|
|
8881
|
+
const tp = buf.subarray(j + 4, j + 8).toString("latin1");
|
|
8882
|
+
if (!sz || sz < 8) break;
|
|
8883
|
+
if (tp === "mvhd") {
|
|
8884
|
+
const ver = buf[j + 8];
|
|
8885
|
+
if (ver === 1) {
|
|
8886
|
+
const timescale = readUint32BE(buf, j + 28);
|
|
8887
|
+
const duration2 = readUint64BE(buf, j + 32);
|
|
8888
|
+
if (timescale > 0) return duration2 / timescale;
|
|
8889
|
+
} else {
|
|
8890
|
+
const timescale = readUint32BE(buf, j + 20);
|
|
8891
|
+
const duration2 = readUint32BE(buf, j + 24);
|
|
8892
|
+
if (timescale > 0) return duration2 / timescale;
|
|
8893
|
+
}
|
|
8894
|
+
break;
|
|
8895
|
+
}
|
|
8896
|
+
j += sz;
|
|
8897
|
+
}
|
|
8898
|
+
break;
|
|
8899
|
+
}
|
|
8900
|
+
i2 += size;
|
|
8901
|
+
}
|
|
8902
|
+
} catch {
|
|
8903
|
+
}
|
|
8904
|
+
return null;
|
|
8905
|
+
};
|
|
8906
|
+
const hasAtom = (buf, atom) => {
|
|
8907
|
+
const tgt = Buffer.from(atom, "latin1");
|
|
8908
|
+
return buf.indexOf(tgt) !== -1;
|
|
8909
|
+
};
|
|
8910
|
+
const validateVideoBytes = (buf) => {
|
|
8911
|
+
if (!buf || buf.length < 1024) return { kind: "unknown", ok: false, durationSec: null, reason: "too_small" };
|
|
8912
|
+
const header2 = buf.subarray(0, 256);
|
|
8913
|
+
const headerStr2 = header2.toString("latin1");
|
|
8914
|
+
const hasFtyp2 = headerStr2.indexOf("ftyp") >= 0 || header2.includes(Buffer.from("ftyp", "ascii"));
|
|
8915
|
+
const isWebm2 = header2[0] === 26 && header2[1] === 69 && header2[2] === 223 && header2[3] === 163;
|
|
8916
|
+
if (isWebm2) {
|
|
8917
|
+
return { kind: "webm", ok: buf.length > 2e5, durationSec: null };
|
|
8918
|
+
}
|
|
8919
|
+
if (hasFtyp2) {
|
|
8920
|
+
const majorBrand = header2.subarray(8, 12).toString("latin1").toLowerCase();
|
|
8921
|
+
const kind = majorBrand.startsWith("qt") ? "mov" : "mp4";
|
|
8922
|
+
const hasMoov = hasAtom(buf, "moov");
|
|
8923
|
+
const dur = tryParseMp4DurationSec(buf);
|
|
8924
|
+
const nearTarget = typeof dur === "number" ? dur + 0.75 >= effectiveDuration : false;
|
|
8925
|
+
const ok = hasMoov && nearTarget;
|
|
8926
|
+
return { kind, ok, durationSec: dur, reason: ok ? void 0 : !hasMoov ? "missing_moov" : "short_duration" };
|
|
8927
|
+
}
|
|
8928
|
+
return { kind: "unknown", ok: false, durationSec: null, reason: "unknown_header" };
|
|
8929
|
+
};
|
|
8930
|
+
const ensureFileMaterialized = async (p, waitMs = 45e3) => {
|
|
8824
8931
|
const deadline2 = Date.now() + waitMs;
|
|
8932
|
+
let lastSize = -1;
|
|
8933
|
+
let stableCount = 0;
|
|
8825
8934
|
while (true) {
|
|
8826
8935
|
try {
|
|
8827
8936
|
const st = await fsp__namespace.default.stat(p);
|
|
8828
|
-
if (st && st.size > 0)
|
|
8937
|
+
if (st && st.size > 0) {
|
|
8938
|
+
if (st.size === lastSize) {
|
|
8939
|
+
stableCount++;
|
|
8940
|
+
if (stableCount >= 5) return;
|
|
8941
|
+
} else {
|
|
8942
|
+
stableCount = 0;
|
|
8943
|
+
lastSize = st.size;
|
|
8944
|
+
}
|
|
8945
|
+
}
|
|
8829
8946
|
} catch {
|
|
8830
8947
|
}
|
|
8831
|
-
if (Date.now() > deadline2) throw new Error(`video file not
|
|
8948
|
+
if (Date.now() > deadline2) throw new Error(`video file not fully materialized: ${p}`);
|
|
8832
8949
|
await new Promise((r2) => setTimeout(r2, 200));
|
|
8833
8950
|
}
|
|
8834
8951
|
};
|
|
8835
8952
|
const trySdkDownloadOnce = async () => {
|
|
8836
8953
|
try {
|
|
8837
8954
|
await ai.files.download({ file: videoRef, downloadPath: tmpOut });
|
|
8838
|
-
await ensureFileMaterialized(tmpOut,
|
|
8955
|
+
await ensureFileMaterialized(tmpOut, 45e3);
|
|
8839
8956
|
const bytes = await fsp__namespace.default.readFile(tmpOut);
|
|
8840
8957
|
if (await isLikelyValidVideo(bytes)) return bytes;
|
|
8841
8958
|
} catch {
|
|
8842
8959
|
}
|
|
8843
8960
|
return null;
|
|
8844
8961
|
};
|
|
8962
|
+
const tryManualDownload = async () => {
|
|
8963
|
+
try {
|
|
8964
|
+
const fileObj = videoRef;
|
|
8965
|
+
const direct = fileObj?.uri || fileObj?.url || fileObj?.downloadUri || fileObj?.downloadUrl || "";
|
|
8966
|
+
if (!direct || typeof direct !== "string") return null;
|
|
8967
|
+
const res2 = await fetch(direct, {
|
|
8968
|
+
method: "GET",
|
|
8969
|
+
headers: dlApiKey ? { "x-goog-api-key": dlApiKey } : void 0,
|
|
8970
|
+
redirect: "follow"
|
|
8971
|
+
});
|
|
8972
|
+
if (!res2.ok) return null;
|
|
8973
|
+
const lenHeader = res2.headers?.get?.("content-length");
|
|
8974
|
+
const expectedLen = lenHeader ? Number(lenHeader) : void 0;
|
|
8975
|
+
const ab = await res2.arrayBuffer();
|
|
8976
|
+
const buf = Buffer.from(ab);
|
|
8977
|
+
if (typeof expectedLen === "number" && Number.isFinite(expectedLen) && expectedLen > 0 && buf.length !== expectedLen) {
|
|
8978
|
+
return null;
|
|
8979
|
+
}
|
|
8980
|
+
await fsp__namespace.default.writeFile(tmpOut, buf);
|
|
8981
|
+
if (await isLikelyValidVideo(buf)) return buf;
|
|
8982
|
+
} catch {
|
|
8983
|
+
}
|
|
8984
|
+
return null;
|
|
8985
|
+
};
|
|
8845
8986
|
let videoBytes = null;
|
|
8846
|
-
|
|
8847
|
-
|
|
8848
|
-
|
|
8849
|
-
|
|
8850
|
-
|
|
8851
|
-
|
|
8987
|
+
const maxAttempts = 10;
|
|
8988
|
+
for (let attempt = 1; attempt <= maxAttempts; attempt++) {
|
|
8989
|
+
let bytes = await trySdkDownloadOnce();
|
|
8990
|
+
if (!bytes) bytes = await tryManualDownload();
|
|
8991
|
+
if (bytes && await isLikelyValidVideo(bytes)) {
|
|
8992
|
+
const v = validateVideoBytes(bytes);
|
|
8993
|
+
if (v.ok) {
|
|
8994
|
+
videoBytes = bytes;
|
|
8995
|
+
break;
|
|
8996
|
+
}
|
|
8997
|
+
}
|
|
8998
|
+
await new Promise((r2) => setTimeout(r2, Math.min(8e3, 1500 * attempt)));
|
|
8852
8999
|
}
|
|
9000
|
+
if (!videoBytes) throw new Error("failed to obtain fully materialized video");
|
|
8853
9001
|
const header = videoBytes.subarray(0, 256);
|
|
8854
9002
|
const headerStr = header.toString("latin1");
|
|
8855
9003
|
let outExt = ".mp4";
|
|
@@ -8884,7 +9032,8 @@ app.post("/api/v1/video", rateLimitMiddleware, async (req, res) => {
|
|
|
8884
9032
|
metrics: { durationMs: Date.now() - startedMs, retries: 0, fallbacks: 0 },
|
|
8885
9033
|
trace: traceId
|
|
8886
9034
|
};
|
|
8887
|
-
const
|
|
9035
|
+
const baseDir = path__namespace.default.join("artifacts", "media", "videos", traceId);
|
|
9036
|
+
const saved = await saveArtifacts({ root: process.cwd(), kind: "video", baseDir, flat: true }, [{ bytes: videoBytes, ext: outExt }], manifest);
|
|
8888
9037
|
const idemKey = req.headers["idempotency-key"] || void 0;
|
|
8889
9038
|
const idToken = auth.substring("Bearer ".length).trim();
|
|
8890
9039
|
const decoded = await decodeFirebaseToken(idToken).catch(() => null);
|
|
@@ -8899,13 +9048,11 @@ app.post("/api/v1/video", rateLimitMiddleware, async (req, res) => {
|
|
|
8899
9048
|
updatedAt: (/* @__PURE__ */ new Date()).toISOString(),
|
|
8900
9049
|
uid
|
|
8901
9050
|
});
|
|
8902
|
-
return res.json({ success: true, data: { url: saved.files[0] ? `/${saved.files[0]}` : void 0, files: saved.files, jobId: manifest.trace, applied: { durationSeconds: effectiveDuration, frameRate: effectiveFps } } });
|
|
9051
|
+
return res.json({ success: true, data: { url: saved.manifestPath || (saved.files[0] ? `/${saved.files[0]}` : void 0), files: saved.files, jobId: manifest.trace, applied: { durationSeconds: effectiveDuration, frameRate: effectiveFps } } });
|
|
8903
9052
|
} catch (error) {
|
|
8904
9053
|
console.error("[Video API] Error:", error);
|
|
8905
|
-
|
|
8906
|
-
|
|
8907
|
-
message: "Failed to generate video"
|
|
8908
|
-
});
|
|
9054
|
+
const mapped = classifyMediaError(error);
|
|
9055
|
+
return res.status(mapped.status).json({ error: mapped.code, message: mapped.message, hint: mapped.hint });
|
|
8909
9056
|
}
|
|
8910
9057
|
});
|
|
8911
9058
|
app.get("/api/v1/jobs/:id", async (req, res) => {
|