@bonginkan/maria 4.3.15 → 4.3.16
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 +5 -5
- package/dist/READY.manifest.json +1 -1
- package/dist/bin/maria.cjs +122 -60
- package/dist/bin/maria.cjs.map +1 -1
- package/dist/cli.cjs +122 -60
- 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 +85 -36
- package/dist/server/express-server.js +85 -36
- package/dist/server-express.cjs +85 -36
- package/dist/server-express.cjs.map +1 -1
- package/package.json +2 -2
- package/src/slash-commands/READY.manifest.json +1 -1
|
@@ -7008,7 +7008,7 @@ async function atomicRename(stage, dest) {
|
|
|
7008
7008
|
}
|
|
7009
7009
|
async function saveArtifacts(ctx, items, manifest) {
|
|
7010
7010
|
const root = ctx.root;
|
|
7011
|
-
const base = ctx.baseDir
|
|
7011
|
+
const base = typeof ctx.baseDir === "string" ? ctx.baseDir : "";
|
|
7012
7012
|
const trace = ctx.trace || Math.random().toString(36).slice(2, 10).toUpperCase();
|
|
7013
7013
|
const stage = stageDir(root, trace);
|
|
7014
7014
|
ensureDirSync(stage);
|
|
@@ -7016,7 +7016,7 @@ async function saveArtifacts(ctx, items, manifest) {
|
|
|
7016
7016
|
const dateSeg = datePath(/* @__PURE__ */ new Date());
|
|
7017
7017
|
const reqHash = manifest.request && manifest.request.promptHash || "sha256:unknown";
|
|
7018
7018
|
const slug = hashPrefix(reqHash, 6);
|
|
7019
|
-
const outDirSeg = `${base}
|
|
7019
|
+
const outDirSeg = ctx.flat ? base || "" : `${base ? base + "/" : ""}${dateSeg}/${slug}`;
|
|
7020
7020
|
const outDir = safeJoin(root, outDirSeg).full;
|
|
7021
7021
|
ensureDirSync(outDir);
|
|
7022
7022
|
const saved = [];
|
|
@@ -7026,7 +7026,8 @@ async function saveArtifacts(ctx, items, manifest) {
|
|
|
7026
7026
|
const ext = it.ext.startsWith(".") ? it.ext : `.${it.ext}`;
|
|
7027
7027
|
const baseName = it.logicalName ? `${it.logicalName}` : `${contentHash}`;
|
|
7028
7028
|
const fname = `${baseName}${ext}`;
|
|
7029
|
-
const
|
|
7029
|
+
const relPath = outDirSeg ? `${outDirSeg}/${fname}` : `${fname}`;
|
|
7030
|
+
const dest = safeJoin(root, relPath);
|
|
7030
7031
|
validateWinPathEdge(dest.full);
|
|
7031
7032
|
if (await hasCaseInsensitiveCollision(path__namespace.dirname(dest.full), path__namespace.basename(dest.full))) {
|
|
7032
7033
|
throw new Error("case-insensitive filename collision");
|
|
@@ -7046,23 +7047,28 @@ async function saveArtifacts(ctx, items, manifest) {
|
|
|
7046
7047
|
await atomicRename(stg, dest.full);
|
|
7047
7048
|
saved.push(dest.rel);
|
|
7048
7049
|
}
|
|
7049
|
-
|
|
7050
|
-
|
|
7051
|
-
|
|
7052
|
-
|
|
7053
|
-
|
|
7054
|
-
|
|
7055
|
-
|
|
7056
|
-
|
|
7057
|
-
|
|
7058
|
-
|
|
7059
|
-
|
|
7060
|
-
|
|
7061
|
-
|
|
7050
|
+
if (!ctx.skipManifest) {
|
|
7051
|
+
const manifestObj = {
|
|
7052
|
+
manifestVersion: 1,
|
|
7053
|
+
...manifest,
|
|
7054
|
+
createdAt: manifest.createdAt || (/* @__PURE__ */ new Date()).toISOString(),
|
|
7055
|
+
artifacts: manifest.artifacts && manifest.artifacts.length > 0 ? manifest.artifacts : saved.map((file) => ({ file, hash: `sha256:${path__namespace.basename(file).split(".")[0]}` }))
|
|
7056
|
+
};
|
|
7057
|
+
const manifestPathRel = `${outDirSeg ? outDirSeg + "/" : ""}manifest.json`;
|
|
7058
|
+
const manifestStage = path__namespace.join(stage, "manifest.json.part");
|
|
7059
|
+
const manifestFull = safeJoin(root, manifestPathRel).full;
|
|
7060
|
+
await fsp__namespace.writeFile(manifestStage, JSON.stringify(manifestObj, null, 2), "utf8");
|
|
7061
|
+
try {
|
|
7062
|
+
console.log(`[store] manifest -> ${manifestPathRel}`);
|
|
7063
|
+
} catch {
|
|
7064
|
+
}
|
|
7065
|
+
await atomicRename(manifestStage, manifestFull);
|
|
7066
|
+
await fsp__namespace.rm(stage, { recursive: true, force: true });
|
|
7067
|
+
return { files: saved, manifestPath: manifestPathRel };
|
|
7068
|
+
} else {
|
|
7069
|
+
await fsp__namespace.rm(stage, { recursive: true, force: true });
|
|
7070
|
+
return { files: saved, manifestPath: "" };
|
|
7062
7071
|
}
|
|
7063
|
-
await atomicRename(manifestStage, manifestFull);
|
|
7064
|
-
await fsp__namespace.rm(stage, { recursive: true, force: true });
|
|
7065
|
-
return { files: saved, manifestPath: manifestPathRel };
|
|
7066
7072
|
} catch (e2) {
|
|
7067
7073
|
await fsp__namespace.rm(stage, { recursive: true, force: true });
|
|
7068
7074
|
throw e2;
|
|
@@ -8532,7 +8538,12 @@ var app = express__default.default();
|
|
|
8532
8538
|
var port = process.env.PORT || 8080;
|
|
8533
8539
|
app.use(helmet__default.default());
|
|
8534
8540
|
app.use(cors__default.default());
|
|
8535
|
-
app.use(compression__default.default(
|
|
8541
|
+
app.use(compression__default.default({
|
|
8542
|
+
filter: (req, res) => {
|
|
8543
|
+
if (/\.(mp4|webm|mov)$/i.test(req.url)) return false;
|
|
8544
|
+
return compression.filter(req, res);
|
|
8545
|
+
}
|
|
8546
|
+
}));
|
|
8536
8547
|
app.use(express__default.default.json({ limit: "10mb" }));
|
|
8537
8548
|
app.use(express__default.default.urlencoded({ extended: true }));
|
|
8538
8549
|
try {
|
|
@@ -8760,7 +8771,7 @@ app.post("/api/v1/video", rateLimitMiddleware, async (req, res) => {
|
|
|
8760
8771
|
await loadProviderKeys();
|
|
8761
8772
|
const auth = req.headers.authorization;
|
|
8762
8773
|
if (!auth || !auth.startsWith("Bearer ")) return res.status(401).json({ error: "unauthorized" });
|
|
8763
|
-
const { prompt, duration =
|
|
8774
|
+
const { prompt, duration = 8, fps = 24, res: resStr = "1280x720", format = "mp4", model, seed } = req.body || {};
|
|
8764
8775
|
if (!prompt) return res.status(400).json({ error: "bad_request", message: "prompt required" });
|
|
8765
8776
|
const m2 = /^(\d{2,4})x(\d{2,4})$/.exec(String(resStr));
|
|
8766
8777
|
if (!m2) return res.status(400).json({ error: "bad_request", message: "res must be WxH" });
|
|
@@ -8772,10 +8783,15 @@ app.post("/api/v1/video", rateLimitMiddleware, async (req, res) => {
|
|
|
8772
8783
|
const veoModel = model && String(model).trim() || process.env.MARIA_VIDEO_MODEL || "veo-3.0-generate-001";
|
|
8773
8784
|
const aspectRatio = w >= h2 ? "16:9" : "9:16";
|
|
8774
8785
|
const startedMs = Date.now();
|
|
8786
|
+
const requestedDuration = Number(duration);
|
|
8787
|
+
const effectiveDuration = Number.isFinite(requestedDuration) && requestedDuration === 8 ? 8 : 8;
|
|
8788
|
+
const requestedFps = Number(fps);
|
|
8789
|
+
const effectiveFps = Number.isFinite(requestedFps) ? Math.min(60, Math.max(1, Math.floor(requestedFps))) : 24;
|
|
8775
8790
|
let operation = await ai.models.generateVideos({
|
|
8776
8791
|
model: veoModel,
|
|
8777
8792
|
prompt: String(prompt),
|
|
8778
|
-
|
|
8793
|
+
// Pass duration/fps to provider to avoid default ~1s clips
|
|
8794
|
+
config: { aspectRatio, durationSeconds: effectiveDuration, frameRate: effectiveFps }
|
|
8779
8795
|
});
|
|
8780
8796
|
const deadline = Date.now() + 6e5;
|
|
8781
8797
|
while (!operation?.done) {
|
|
@@ -8794,22 +8810,46 @@ app.post("/api/v1/video", rateLimitMiddleware, async (req, res) => {
|
|
|
8794
8810
|
const tmpDir = path__namespace.default.join(process.cwd(), ".stage", traceId);
|
|
8795
8811
|
await fsp__namespace.default.mkdir(tmpDir, { recursive: true });
|
|
8796
8812
|
const tmpOut = path__namespace.default.join(tmpDir, "video.bin");
|
|
8797
|
-
|
|
8798
|
-
{
|
|
8799
|
-
|
|
8813
|
+
const dlApiKey = process.env.GOOGLE_API_KEY || process.env.GEMINI_API_KEY || "";
|
|
8814
|
+
const isLikelyValidVideo = async (buf) => {
|
|
8815
|
+
if (!buf || buf.length < 16) return false;
|
|
8816
|
+
const head = buf.subarray(0, Math.min(256, buf.length));
|
|
8817
|
+
const headStr = head.toString("latin1");
|
|
8818
|
+
const hasFtyp2 = headStr.indexOf("ftyp") >= 0 || head.includes(Buffer.from("ftyp", "ascii"));
|
|
8819
|
+
const isWebm2 = head[0] === 26 && head[1] === 69 && head[2] === 223 && head[3] === 163;
|
|
8820
|
+
const looksHtml = headStr.includes("<!doctype") || headStr.includes("<html") || headStr.includes("<?xml");
|
|
8821
|
+
return (hasFtyp2 || isWebm2) && !looksHtml;
|
|
8822
|
+
};
|
|
8823
|
+
const ensureFileMaterialized = async (p, waitMs = 1e4) => {
|
|
8824
|
+
const deadline2 = Date.now() + waitMs;
|
|
8800
8825
|
while (true) {
|
|
8801
8826
|
try {
|
|
8802
|
-
const st = await fsp__namespace.default.stat(
|
|
8803
|
-
if (st && st.size > 0)
|
|
8827
|
+
const st = await fsp__namespace.default.stat(p);
|
|
8828
|
+
if (st && st.size > 0) return;
|
|
8804
8829
|
} catch {
|
|
8805
8830
|
}
|
|
8806
|
-
if (Date.now() > deadline2) {
|
|
8807
|
-
throw new Error(`video file not found after download: ${tmpOut}`);
|
|
8808
|
-
}
|
|
8831
|
+
if (Date.now() > deadline2) throw new Error(`video file not found after download: ${p}`);
|
|
8809
8832
|
await new Promise((r2) => setTimeout(r2, 200));
|
|
8810
8833
|
}
|
|
8834
|
+
};
|
|
8835
|
+
const trySdkDownloadOnce = async () => {
|
|
8836
|
+
try {
|
|
8837
|
+
await ai.files.download({ file: videoRef, downloadPath: tmpOut });
|
|
8838
|
+
await ensureFileMaterialized(tmpOut, 15e3);
|
|
8839
|
+
const bytes = await fsp__namespace.default.readFile(tmpOut);
|
|
8840
|
+
if (await isLikelyValidVideo(bytes)) return bytes;
|
|
8841
|
+
} catch {
|
|
8842
|
+
}
|
|
8843
|
+
return null;
|
|
8844
|
+
};
|
|
8845
|
+
let videoBytes = null;
|
|
8846
|
+
for (let attempt = 1; attempt <= 3 && !videoBytes; attempt++) {
|
|
8847
|
+
videoBytes = await trySdkDownloadOnce();
|
|
8848
|
+
if (!videoBytes) await new Promise((r2) => setTimeout(r2, 600 * attempt));
|
|
8849
|
+
}
|
|
8850
|
+
if (!videoBytes) {
|
|
8851
|
+
throw new Error("failed to download valid video bytes");
|
|
8811
8852
|
}
|
|
8812
|
-
const videoBytes = await fsp__namespace.default.readFile(tmpOut);
|
|
8813
8853
|
const header = videoBytes.subarray(0, 256);
|
|
8814
8854
|
const headerStr = header.toString("latin1");
|
|
8815
8855
|
let outExt = ".mp4";
|
|
@@ -8819,23 +8859,32 @@ app.post("/api/v1/video", rateLimitMiddleware, async (req, res) => {
|
|
|
8819
8859
|
if (videoMime && /webm/i.test(videoMime)) {
|
|
8820
8860
|
outExt = ".webm";
|
|
8821
8861
|
outMime = "video/webm";
|
|
8862
|
+
} else if (videoMime && /quicktime/i.test(videoMime)) {
|
|
8863
|
+
outExt = ".mov";
|
|
8864
|
+
outMime = "video/quicktime";
|
|
8822
8865
|
} else if (isWebm) {
|
|
8823
8866
|
outExt = ".webm";
|
|
8824
8867
|
outMime = "video/webm";
|
|
8825
8868
|
} else if (hasFtyp) {
|
|
8826
|
-
|
|
8827
|
-
|
|
8869
|
+
const majorBrand = header.subarray(8, 12).toString("latin1");
|
|
8870
|
+
if ((majorBrand || "").toLowerCase().startsWith("qt")) {
|
|
8871
|
+
outExt = ".mov";
|
|
8872
|
+
outMime = "video/quicktime";
|
|
8873
|
+
} else {
|
|
8874
|
+
outExt = ".mp4";
|
|
8875
|
+
outMime = "video/mp4";
|
|
8876
|
+
}
|
|
8828
8877
|
}
|
|
8829
8878
|
await fsp__namespace.default.rm(tmpDir, { recursive: true, force: true });
|
|
8830
8879
|
const promptHash = hashPrompt(prompt);
|
|
8831
8880
|
const manifest = {
|
|
8832
8881
|
kind: "video",
|
|
8833
|
-
request: { promptHash, seed, params: { size: [w, h2], fps, duration, format: outExt.replace(".", "") }, model: veoModel, provider: "google" },
|
|
8882
|
+
request: { promptHash, seed, params: { size: [w, h2], fps: effectiveFps, duration: effectiveDuration, format: outExt.replace(".", "") }, model: veoModel, provider: "google" },
|
|
8834
8883
|
artifacts: [],
|
|
8835
8884
|
metrics: { durationMs: Date.now() - startedMs, retries: 0, fallbacks: 0 },
|
|
8836
8885
|
trace: traceId
|
|
8837
8886
|
};
|
|
8838
|
-
const saved = await saveArtifacts({ root: process.cwd(), kind: "video" }, [{ bytes: videoBytes, ext: outExt }], manifest);
|
|
8887
|
+
const saved = await saveArtifacts({ root: process.cwd(), kind: "video", baseDir: "artifacts/media/videos", flat: false, skipManifest: true }, [{ bytes: videoBytes, ext: outExt }], manifest);
|
|
8839
8888
|
const idemKey = req.headers["idempotency-key"] || void 0;
|
|
8840
8889
|
const idToken = auth.substring("Bearer ".length).trim();
|
|
8841
8890
|
const decoded = await decodeFirebaseToken(idToken).catch(() => null);
|
|
@@ -8850,7 +8899,7 @@ app.post("/api/v1/video", rateLimitMiddleware, async (req, res) => {
|
|
|
8850
8899
|
updatedAt: (/* @__PURE__ */ new Date()).toISOString(),
|
|
8851
8900
|
uid
|
|
8852
8901
|
});
|
|
8853
|
-
return res.json({ success: true, data: { url: saved.
|
|
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 } } });
|
|
8854
8903
|
} catch (error) {
|
|
8855
8904
|
console.error("[Video API] Error:", error);
|
|
8856
8905
|
return res.status(500).json({
|
|
@@ -7008,7 +7008,7 @@ async function atomicRename(stage, dest) {
|
|
|
7008
7008
|
}
|
|
7009
7009
|
async function saveArtifacts(ctx, items, manifest) {
|
|
7010
7010
|
const root = ctx.root;
|
|
7011
|
-
const base = ctx.baseDir
|
|
7011
|
+
const base = typeof ctx.baseDir === "string" ? ctx.baseDir : "";
|
|
7012
7012
|
const trace = ctx.trace || Math.random().toString(36).slice(2, 10).toUpperCase();
|
|
7013
7013
|
const stage = stageDir(root, trace);
|
|
7014
7014
|
ensureDirSync(stage);
|
|
@@ -7016,7 +7016,7 @@ async function saveArtifacts(ctx, items, manifest) {
|
|
|
7016
7016
|
const dateSeg = datePath(/* @__PURE__ */ new Date());
|
|
7017
7017
|
const reqHash = manifest.request && manifest.request.promptHash || "sha256:unknown";
|
|
7018
7018
|
const slug = hashPrefix(reqHash, 6);
|
|
7019
|
-
const outDirSeg = `${base}
|
|
7019
|
+
const outDirSeg = ctx.flat ? base || "" : `${base ? base + "/" : ""}${dateSeg}/${slug}`;
|
|
7020
7020
|
const outDir = safeJoin(root, outDirSeg).full;
|
|
7021
7021
|
ensureDirSync(outDir);
|
|
7022
7022
|
const saved = [];
|
|
@@ -7026,7 +7026,8 @@ async function saveArtifacts(ctx, items, manifest) {
|
|
|
7026
7026
|
const ext = it.ext.startsWith(".") ? it.ext : `.${it.ext}`;
|
|
7027
7027
|
const baseName = it.logicalName ? `${it.logicalName}` : `${contentHash}`;
|
|
7028
7028
|
const fname = `${baseName}${ext}`;
|
|
7029
|
-
const
|
|
7029
|
+
const relPath = outDirSeg ? `${outDirSeg}/${fname}` : `${fname}`;
|
|
7030
|
+
const dest = safeJoin(root, relPath);
|
|
7030
7031
|
validateWinPathEdge(dest.full);
|
|
7031
7032
|
if (await hasCaseInsensitiveCollision(path__namespace.dirname(dest.full), path__namespace.basename(dest.full))) {
|
|
7032
7033
|
throw new Error("case-insensitive filename collision");
|
|
@@ -7046,23 +7047,28 @@ async function saveArtifacts(ctx, items, manifest) {
|
|
|
7046
7047
|
await atomicRename(stg, dest.full);
|
|
7047
7048
|
saved.push(dest.rel);
|
|
7048
7049
|
}
|
|
7049
|
-
|
|
7050
|
-
|
|
7051
|
-
|
|
7052
|
-
|
|
7053
|
-
|
|
7054
|
-
|
|
7055
|
-
|
|
7056
|
-
|
|
7057
|
-
|
|
7058
|
-
|
|
7059
|
-
|
|
7060
|
-
|
|
7061
|
-
|
|
7050
|
+
if (!ctx.skipManifest) {
|
|
7051
|
+
const manifestObj = {
|
|
7052
|
+
manifestVersion: 1,
|
|
7053
|
+
...manifest,
|
|
7054
|
+
createdAt: manifest.createdAt || (/* @__PURE__ */ new Date()).toISOString(),
|
|
7055
|
+
artifacts: manifest.artifacts && manifest.artifacts.length > 0 ? manifest.artifacts : saved.map((file) => ({ file, hash: `sha256:${path__namespace.basename(file).split(".")[0]}` }))
|
|
7056
|
+
};
|
|
7057
|
+
const manifestPathRel = `${outDirSeg ? outDirSeg + "/" : ""}manifest.json`;
|
|
7058
|
+
const manifestStage = path__namespace.join(stage, "manifest.json.part");
|
|
7059
|
+
const manifestFull = safeJoin(root, manifestPathRel).full;
|
|
7060
|
+
await fsp__namespace.writeFile(manifestStage, JSON.stringify(manifestObj, null, 2), "utf8");
|
|
7061
|
+
try {
|
|
7062
|
+
console.log(`[store] manifest -> ${manifestPathRel}`);
|
|
7063
|
+
} catch {
|
|
7064
|
+
}
|
|
7065
|
+
await atomicRename(manifestStage, manifestFull);
|
|
7066
|
+
await fsp__namespace.rm(stage, { recursive: true, force: true });
|
|
7067
|
+
return { files: saved, manifestPath: manifestPathRel };
|
|
7068
|
+
} else {
|
|
7069
|
+
await fsp__namespace.rm(stage, { recursive: true, force: true });
|
|
7070
|
+
return { files: saved, manifestPath: "" };
|
|
7062
7071
|
}
|
|
7063
|
-
await atomicRename(manifestStage, manifestFull);
|
|
7064
|
-
await fsp__namespace.rm(stage, { recursive: true, force: true });
|
|
7065
|
-
return { files: saved, manifestPath: manifestPathRel };
|
|
7066
7072
|
} catch (e2) {
|
|
7067
7073
|
await fsp__namespace.rm(stage, { recursive: true, force: true });
|
|
7068
7074
|
throw e2;
|
|
@@ -8532,7 +8538,12 @@ var app = express__default.default();
|
|
|
8532
8538
|
var port = process.env.PORT || 8080;
|
|
8533
8539
|
app.use(helmet__default.default());
|
|
8534
8540
|
app.use(cors__default.default());
|
|
8535
|
-
app.use(compression__default.default(
|
|
8541
|
+
app.use(compression__default.default({
|
|
8542
|
+
filter: (req, res) => {
|
|
8543
|
+
if (/\.(mp4|webm|mov)$/i.test(req.url)) return false;
|
|
8544
|
+
return compression.filter(req, res);
|
|
8545
|
+
}
|
|
8546
|
+
}));
|
|
8536
8547
|
app.use(express__default.default.json({ limit: "10mb" }));
|
|
8537
8548
|
app.use(express__default.default.urlencoded({ extended: true }));
|
|
8538
8549
|
try {
|
|
@@ -8760,7 +8771,7 @@ app.post("/api/v1/video", rateLimitMiddleware, async (req, res) => {
|
|
|
8760
8771
|
await loadProviderKeys();
|
|
8761
8772
|
const auth = req.headers.authorization;
|
|
8762
8773
|
if (!auth || !auth.startsWith("Bearer ")) return res.status(401).json({ error: "unauthorized" });
|
|
8763
|
-
const { prompt, duration =
|
|
8774
|
+
const { prompt, duration = 8, fps = 24, res: resStr = "1280x720", format = "mp4", model, seed } = req.body || {};
|
|
8764
8775
|
if (!prompt) return res.status(400).json({ error: "bad_request", message: "prompt required" });
|
|
8765
8776
|
const m2 = /^(\d{2,4})x(\d{2,4})$/.exec(String(resStr));
|
|
8766
8777
|
if (!m2) return res.status(400).json({ error: "bad_request", message: "res must be WxH" });
|
|
@@ -8772,10 +8783,15 @@ app.post("/api/v1/video", rateLimitMiddleware, async (req, res) => {
|
|
|
8772
8783
|
const veoModel = model && String(model).trim() || process.env.MARIA_VIDEO_MODEL || "veo-3.0-generate-001";
|
|
8773
8784
|
const aspectRatio = w >= h2 ? "16:9" : "9:16";
|
|
8774
8785
|
const startedMs = Date.now();
|
|
8786
|
+
const requestedDuration = Number(duration);
|
|
8787
|
+
const effectiveDuration = Number.isFinite(requestedDuration) && requestedDuration === 8 ? 8 : 8;
|
|
8788
|
+
const requestedFps = Number(fps);
|
|
8789
|
+
const effectiveFps = Number.isFinite(requestedFps) ? Math.min(60, Math.max(1, Math.floor(requestedFps))) : 24;
|
|
8775
8790
|
let operation = await ai.models.generateVideos({
|
|
8776
8791
|
model: veoModel,
|
|
8777
8792
|
prompt: String(prompt),
|
|
8778
|
-
|
|
8793
|
+
// Pass duration/fps to provider to avoid default ~1s clips
|
|
8794
|
+
config: { aspectRatio, durationSeconds: effectiveDuration, frameRate: effectiveFps }
|
|
8779
8795
|
});
|
|
8780
8796
|
const deadline = Date.now() + 6e5;
|
|
8781
8797
|
while (!operation?.done) {
|
|
@@ -8794,22 +8810,46 @@ app.post("/api/v1/video", rateLimitMiddleware, async (req, res) => {
|
|
|
8794
8810
|
const tmpDir = path__namespace.default.join(process.cwd(), ".stage", traceId);
|
|
8795
8811
|
await fsp__namespace.default.mkdir(tmpDir, { recursive: true });
|
|
8796
8812
|
const tmpOut = path__namespace.default.join(tmpDir, "video.bin");
|
|
8797
|
-
|
|
8798
|
-
{
|
|
8799
|
-
|
|
8813
|
+
const dlApiKey = process.env.GOOGLE_API_KEY || process.env.GEMINI_API_KEY || "";
|
|
8814
|
+
const isLikelyValidVideo = async (buf) => {
|
|
8815
|
+
if (!buf || buf.length < 16) return false;
|
|
8816
|
+
const head = buf.subarray(0, Math.min(256, buf.length));
|
|
8817
|
+
const headStr = head.toString("latin1");
|
|
8818
|
+
const hasFtyp2 = headStr.indexOf("ftyp") >= 0 || head.includes(Buffer.from("ftyp", "ascii"));
|
|
8819
|
+
const isWebm2 = head[0] === 26 && head[1] === 69 && head[2] === 223 && head[3] === 163;
|
|
8820
|
+
const looksHtml = headStr.includes("<!doctype") || headStr.includes("<html") || headStr.includes("<?xml");
|
|
8821
|
+
return (hasFtyp2 || isWebm2) && !looksHtml;
|
|
8822
|
+
};
|
|
8823
|
+
const ensureFileMaterialized = async (p, waitMs = 1e4) => {
|
|
8824
|
+
const deadline2 = Date.now() + waitMs;
|
|
8800
8825
|
while (true) {
|
|
8801
8826
|
try {
|
|
8802
|
-
const st = await fsp__namespace.default.stat(
|
|
8803
|
-
if (st && st.size > 0)
|
|
8827
|
+
const st = await fsp__namespace.default.stat(p);
|
|
8828
|
+
if (st && st.size > 0) return;
|
|
8804
8829
|
} catch {
|
|
8805
8830
|
}
|
|
8806
|
-
if (Date.now() > deadline2) {
|
|
8807
|
-
throw new Error(`video file not found after download: ${tmpOut}`);
|
|
8808
|
-
}
|
|
8831
|
+
if (Date.now() > deadline2) throw new Error(`video file not found after download: ${p}`);
|
|
8809
8832
|
await new Promise((r2) => setTimeout(r2, 200));
|
|
8810
8833
|
}
|
|
8834
|
+
};
|
|
8835
|
+
const trySdkDownloadOnce = async () => {
|
|
8836
|
+
try {
|
|
8837
|
+
await ai.files.download({ file: videoRef, downloadPath: tmpOut });
|
|
8838
|
+
await ensureFileMaterialized(tmpOut, 15e3);
|
|
8839
|
+
const bytes = await fsp__namespace.default.readFile(tmpOut);
|
|
8840
|
+
if (await isLikelyValidVideo(bytes)) return bytes;
|
|
8841
|
+
} catch {
|
|
8842
|
+
}
|
|
8843
|
+
return null;
|
|
8844
|
+
};
|
|
8845
|
+
let videoBytes = null;
|
|
8846
|
+
for (let attempt = 1; attempt <= 3 && !videoBytes; attempt++) {
|
|
8847
|
+
videoBytes = await trySdkDownloadOnce();
|
|
8848
|
+
if (!videoBytes) await new Promise((r2) => setTimeout(r2, 600 * attempt));
|
|
8849
|
+
}
|
|
8850
|
+
if (!videoBytes) {
|
|
8851
|
+
throw new Error("failed to download valid video bytes");
|
|
8811
8852
|
}
|
|
8812
|
-
const videoBytes = await fsp__namespace.default.readFile(tmpOut);
|
|
8813
8853
|
const header = videoBytes.subarray(0, 256);
|
|
8814
8854
|
const headerStr = header.toString("latin1");
|
|
8815
8855
|
let outExt = ".mp4";
|
|
@@ -8819,23 +8859,32 @@ app.post("/api/v1/video", rateLimitMiddleware, async (req, res) => {
|
|
|
8819
8859
|
if (videoMime && /webm/i.test(videoMime)) {
|
|
8820
8860
|
outExt = ".webm";
|
|
8821
8861
|
outMime = "video/webm";
|
|
8862
|
+
} else if (videoMime && /quicktime/i.test(videoMime)) {
|
|
8863
|
+
outExt = ".mov";
|
|
8864
|
+
outMime = "video/quicktime";
|
|
8822
8865
|
} else if (isWebm) {
|
|
8823
8866
|
outExt = ".webm";
|
|
8824
8867
|
outMime = "video/webm";
|
|
8825
8868
|
} else if (hasFtyp) {
|
|
8826
|
-
|
|
8827
|
-
|
|
8869
|
+
const majorBrand = header.subarray(8, 12).toString("latin1");
|
|
8870
|
+
if ((majorBrand || "").toLowerCase().startsWith("qt")) {
|
|
8871
|
+
outExt = ".mov";
|
|
8872
|
+
outMime = "video/quicktime";
|
|
8873
|
+
} else {
|
|
8874
|
+
outExt = ".mp4";
|
|
8875
|
+
outMime = "video/mp4";
|
|
8876
|
+
}
|
|
8828
8877
|
}
|
|
8829
8878
|
await fsp__namespace.default.rm(tmpDir, { recursive: true, force: true });
|
|
8830
8879
|
const promptHash = hashPrompt(prompt);
|
|
8831
8880
|
const manifest = {
|
|
8832
8881
|
kind: "video",
|
|
8833
|
-
request: { promptHash, seed, params: { size: [w, h2], fps, duration, format: outExt.replace(".", "") }, model: veoModel, provider: "google" },
|
|
8882
|
+
request: { promptHash, seed, params: { size: [w, h2], fps: effectiveFps, duration: effectiveDuration, format: outExt.replace(".", "") }, model: veoModel, provider: "google" },
|
|
8834
8883
|
artifacts: [],
|
|
8835
8884
|
metrics: { durationMs: Date.now() - startedMs, retries: 0, fallbacks: 0 },
|
|
8836
8885
|
trace: traceId
|
|
8837
8886
|
};
|
|
8838
|
-
const saved = await saveArtifacts({ root: process.cwd(), kind: "video" }, [{ bytes: videoBytes, ext: outExt }], manifest);
|
|
8887
|
+
const saved = await saveArtifacts({ root: process.cwd(), kind: "video", baseDir: "artifacts/media/videos", flat: false, skipManifest: true }, [{ bytes: videoBytes, ext: outExt }], manifest);
|
|
8839
8888
|
const idemKey = req.headers["idempotency-key"] || void 0;
|
|
8840
8889
|
const idToken = auth.substring("Bearer ".length).trim();
|
|
8841
8890
|
const decoded = await decodeFirebaseToken(idToken).catch(() => null);
|
|
@@ -8850,7 +8899,7 @@ app.post("/api/v1/video", rateLimitMiddleware, async (req, res) => {
|
|
|
8850
8899
|
updatedAt: (/* @__PURE__ */ new Date()).toISOString(),
|
|
8851
8900
|
uid
|
|
8852
8901
|
});
|
|
8853
|
-
return res.json({ success: true, data: { url: saved.
|
|
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 } } });
|
|
8854
8903
|
} catch (error) {
|
|
8855
8904
|
console.error("[Video API] Error:", error);
|
|
8856
8905
|
return res.status(500).json({
|
package/dist/server-express.cjs
CHANGED
|
@@ -7008,7 +7008,7 @@ async function atomicRename(stage, dest) {
|
|
|
7008
7008
|
}
|
|
7009
7009
|
async function saveArtifacts(ctx, items, manifest) {
|
|
7010
7010
|
const root = ctx.root;
|
|
7011
|
-
const base = ctx.baseDir
|
|
7011
|
+
const base = typeof ctx.baseDir === "string" ? ctx.baseDir : "";
|
|
7012
7012
|
const trace = ctx.trace || Math.random().toString(36).slice(2, 10).toUpperCase();
|
|
7013
7013
|
const stage = stageDir(root, trace);
|
|
7014
7014
|
ensureDirSync(stage);
|
|
@@ -7016,7 +7016,7 @@ async function saveArtifacts(ctx, items, manifest) {
|
|
|
7016
7016
|
const dateSeg = datePath(/* @__PURE__ */ new Date());
|
|
7017
7017
|
const reqHash = manifest.request && manifest.request.promptHash || "sha256:unknown";
|
|
7018
7018
|
const slug = hashPrefix(reqHash, 6);
|
|
7019
|
-
const outDirSeg = `${base}
|
|
7019
|
+
const outDirSeg = ctx.flat ? base || "" : `${base ? base + "/" : ""}${dateSeg}/${slug}`;
|
|
7020
7020
|
const outDir = safeJoin(root, outDirSeg).full;
|
|
7021
7021
|
ensureDirSync(outDir);
|
|
7022
7022
|
const saved = [];
|
|
@@ -7026,7 +7026,8 @@ async function saveArtifacts(ctx, items, manifest) {
|
|
|
7026
7026
|
const ext = it.ext.startsWith(".") ? it.ext : `.${it.ext}`;
|
|
7027
7027
|
const baseName = it.logicalName ? `${it.logicalName}` : `${contentHash}`;
|
|
7028
7028
|
const fname = `${baseName}${ext}`;
|
|
7029
|
-
const
|
|
7029
|
+
const relPath = outDirSeg ? `${outDirSeg}/${fname}` : `${fname}`;
|
|
7030
|
+
const dest = safeJoin(root, relPath);
|
|
7030
7031
|
validateWinPathEdge(dest.full);
|
|
7031
7032
|
if (await hasCaseInsensitiveCollision(path__namespace.dirname(dest.full), path__namespace.basename(dest.full))) {
|
|
7032
7033
|
throw new Error("case-insensitive filename collision");
|
|
@@ -7046,23 +7047,28 @@ async function saveArtifacts(ctx, items, manifest) {
|
|
|
7046
7047
|
await atomicRename(stg, dest.full);
|
|
7047
7048
|
saved.push(dest.rel);
|
|
7048
7049
|
}
|
|
7049
|
-
|
|
7050
|
-
|
|
7051
|
-
|
|
7052
|
-
|
|
7053
|
-
|
|
7054
|
-
|
|
7055
|
-
|
|
7056
|
-
|
|
7057
|
-
|
|
7058
|
-
|
|
7059
|
-
|
|
7060
|
-
|
|
7061
|
-
|
|
7050
|
+
if (!ctx.skipManifest) {
|
|
7051
|
+
const manifestObj = {
|
|
7052
|
+
manifestVersion: 1,
|
|
7053
|
+
...manifest,
|
|
7054
|
+
createdAt: manifest.createdAt || (/* @__PURE__ */ new Date()).toISOString(),
|
|
7055
|
+
artifacts: manifest.artifacts && manifest.artifacts.length > 0 ? manifest.artifacts : saved.map((file) => ({ file, hash: `sha256:${path__namespace.basename(file).split(".")[0]}` }))
|
|
7056
|
+
};
|
|
7057
|
+
const manifestPathRel = `${outDirSeg ? outDirSeg + "/" : ""}manifest.json`;
|
|
7058
|
+
const manifestStage = path__namespace.join(stage, "manifest.json.part");
|
|
7059
|
+
const manifestFull = safeJoin(root, manifestPathRel).full;
|
|
7060
|
+
await fsp__namespace.writeFile(manifestStage, JSON.stringify(manifestObj, null, 2), "utf8");
|
|
7061
|
+
try {
|
|
7062
|
+
console.log(`[store] manifest -> ${manifestPathRel}`);
|
|
7063
|
+
} catch {
|
|
7064
|
+
}
|
|
7065
|
+
await atomicRename(manifestStage, manifestFull);
|
|
7066
|
+
await fsp__namespace.rm(stage, { recursive: true, force: true });
|
|
7067
|
+
return { files: saved, manifestPath: manifestPathRel };
|
|
7068
|
+
} else {
|
|
7069
|
+
await fsp__namespace.rm(stage, { recursive: true, force: true });
|
|
7070
|
+
return { files: saved, manifestPath: "" };
|
|
7062
7071
|
}
|
|
7063
|
-
await atomicRename(manifestStage, manifestFull);
|
|
7064
|
-
await fsp__namespace.rm(stage, { recursive: true, force: true });
|
|
7065
|
-
return { files: saved, manifestPath: manifestPathRel };
|
|
7066
7072
|
} catch (e2) {
|
|
7067
7073
|
await fsp__namespace.rm(stage, { recursive: true, force: true });
|
|
7068
7074
|
throw e2;
|
|
@@ -8532,7 +8538,12 @@ var app = express__default.default();
|
|
|
8532
8538
|
var port = process.env.PORT || 8080;
|
|
8533
8539
|
app.use(helmet__default.default());
|
|
8534
8540
|
app.use(cors__default.default());
|
|
8535
|
-
app.use(compression__default.default(
|
|
8541
|
+
app.use(compression__default.default({
|
|
8542
|
+
filter: (req, res) => {
|
|
8543
|
+
if (/\.(mp4|webm|mov)$/i.test(req.url)) return false;
|
|
8544
|
+
return compression.filter(req, res);
|
|
8545
|
+
}
|
|
8546
|
+
}));
|
|
8536
8547
|
app.use(express__default.default.json({ limit: "10mb" }));
|
|
8537
8548
|
app.use(express__default.default.urlencoded({ extended: true }));
|
|
8538
8549
|
try {
|
|
@@ -8760,7 +8771,7 @@ app.post("/api/v1/video", rateLimitMiddleware, async (req, res) => {
|
|
|
8760
8771
|
await loadProviderKeys();
|
|
8761
8772
|
const auth = req.headers.authorization;
|
|
8762
8773
|
if (!auth || !auth.startsWith("Bearer ")) return res.status(401).json({ error: "unauthorized" });
|
|
8763
|
-
const { prompt, duration =
|
|
8774
|
+
const { prompt, duration = 8, fps = 24, res: resStr = "1280x720", format = "mp4", model, seed } = req.body || {};
|
|
8764
8775
|
if (!prompt) return res.status(400).json({ error: "bad_request", message: "prompt required" });
|
|
8765
8776
|
const m2 = /^(\d{2,4})x(\d{2,4})$/.exec(String(resStr));
|
|
8766
8777
|
if (!m2) return res.status(400).json({ error: "bad_request", message: "res must be WxH" });
|
|
@@ -8772,10 +8783,15 @@ app.post("/api/v1/video", rateLimitMiddleware, async (req, res) => {
|
|
|
8772
8783
|
const veoModel = model && String(model).trim() || process.env.MARIA_VIDEO_MODEL || "veo-3.0-generate-001";
|
|
8773
8784
|
const aspectRatio = w >= h2 ? "16:9" : "9:16";
|
|
8774
8785
|
const startedMs = Date.now();
|
|
8786
|
+
const requestedDuration = Number(duration);
|
|
8787
|
+
const effectiveDuration = Number.isFinite(requestedDuration) && requestedDuration === 8 ? 8 : 8;
|
|
8788
|
+
const requestedFps = Number(fps);
|
|
8789
|
+
const effectiveFps = Number.isFinite(requestedFps) ? Math.min(60, Math.max(1, Math.floor(requestedFps))) : 24;
|
|
8775
8790
|
let operation = await ai.models.generateVideos({
|
|
8776
8791
|
model: veoModel,
|
|
8777
8792
|
prompt: String(prompt),
|
|
8778
|
-
|
|
8793
|
+
// Pass duration/fps to provider to avoid default ~1s clips
|
|
8794
|
+
config: { aspectRatio, durationSeconds: effectiveDuration, frameRate: effectiveFps }
|
|
8779
8795
|
});
|
|
8780
8796
|
const deadline = Date.now() + 6e5;
|
|
8781
8797
|
while (!operation?.done) {
|
|
@@ -8794,22 +8810,46 @@ app.post("/api/v1/video", rateLimitMiddleware, async (req, res) => {
|
|
|
8794
8810
|
const tmpDir = path__namespace.default.join(process.cwd(), ".stage", traceId);
|
|
8795
8811
|
await fsp__namespace.default.mkdir(tmpDir, { recursive: true });
|
|
8796
8812
|
const tmpOut = path__namespace.default.join(tmpDir, "video.bin");
|
|
8797
|
-
|
|
8798
|
-
{
|
|
8799
|
-
|
|
8813
|
+
const dlApiKey = process.env.GOOGLE_API_KEY || process.env.GEMINI_API_KEY || "";
|
|
8814
|
+
const isLikelyValidVideo = async (buf) => {
|
|
8815
|
+
if (!buf || buf.length < 16) return false;
|
|
8816
|
+
const head = buf.subarray(0, Math.min(256, buf.length));
|
|
8817
|
+
const headStr = head.toString("latin1");
|
|
8818
|
+
const hasFtyp2 = headStr.indexOf("ftyp") >= 0 || head.includes(Buffer.from("ftyp", "ascii"));
|
|
8819
|
+
const isWebm2 = head[0] === 26 && head[1] === 69 && head[2] === 223 && head[3] === 163;
|
|
8820
|
+
const looksHtml = headStr.includes("<!doctype") || headStr.includes("<html") || headStr.includes("<?xml");
|
|
8821
|
+
return (hasFtyp2 || isWebm2) && !looksHtml;
|
|
8822
|
+
};
|
|
8823
|
+
const ensureFileMaterialized = async (p, waitMs = 1e4) => {
|
|
8824
|
+
const deadline2 = Date.now() + waitMs;
|
|
8800
8825
|
while (true) {
|
|
8801
8826
|
try {
|
|
8802
|
-
const st = await fsp__namespace.default.stat(
|
|
8803
|
-
if (st && st.size > 0)
|
|
8827
|
+
const st = await fsp__namespace.default.stat(p);
|
|
8828
|
+
if (st && st.size > 0) return;
|
|
8804
8829
|
} catch {
|
|
8805
8830
|
}
|
|
8806
|
-
if (Date.now() > deadline2) {
|
|
8807
|
-
throw new Error(`video file not found after download: ${tmpOut}`);
|
|
8808
|
-
}
|
|
8831
|
+
if (Date.now() > deadline2) throw new Error(`video file not found after download: ${p}`);
|
|
8809
8832
|
await new Promise((r2) => setTimeout(r2, 200));
|
|
8810
8833
|
}
|
|
8834
|
+
};
|
|
8835
|
+
const trySdkDownloadOnce = async () => {
|
|
8836
|
+
try {
|
|
8837
|
+
await ai.files.download({ file: videoRef, downloadPath: tmpOut });
|
|
8838
|
+
await ensureFileMaterialized(tmpOut, 15e3);
|
|
8839
|
+
const bytes = await fsp__namespace.default.readFile(tmpOut);
|
|
8840
|
+
if (await isLikelyValidVideo(bytes)) return bytes;
|
|
8841
|
+
} catch {
|
|
8842
|
+
}
|
|
8843
|
+
return null;
|
|
8844
|
+
};
|
|
8845
|
+
let videoBytes = null;
|
|
8846
|
+
for (let attempt = 1; attempt <= 3 && !videoBytes; attempt++) {
|
|
8847
|
+
videoBytes = await trySdkDownloadOnce();
|
|
8848
|
+
if (!videoBytes) await new Promise((r2) => setTimeout(r2, 600 * attempt));
|
|
8849
|
+
}
|
|
8850
|
+
if (!videoBytes) {
|
|
8851
|
+
throw new Error("failed to download valid video bytes");
|
|
8811
8852
|
}
|
|
8812
|
-
const videoBytes = await fsp__namespace.default.readFile(tmpOut);
|
|
8813
8853
|
const header = videoBytes.subarray(0, 256);
|
|
8814
8854
|
const headerStr = header.toString("latin1");
|
|
8815
8855
|
let outExt = ".mp4";
|
|
@@ -8819,23 +8859,32 @@ app.post("/api/v1/video", rateLimitMiddleware, async (req, res) => {
|
|
|
8819
8859
|
if (videoMime && /webm/i.test(videoMime)) {
|
|
8820
8860
|
outExt = ".webm";
|
|
8821
8861
|
outMime = "video/webm";
|
|
8862
|
+
} else if (videoMime && /quicktime/i.test(videoMime)) {
|
|
8863
|
+
outExt = ".mov";
|
|
8864
|
+
outMime = "video/quicktime";
|
|
8822
8865
|
} else if (isWebm) {
|
|
8823
8866
|
outExt = ".webm";
|
|
8824
8867
|
outMime = "video/webm";
|
|
8825
8868
|
} else if (hasFtyp) {
|
|
8826
|
-
|
|
8827
|
-
|
|
8869
|
+
const majorBrand = header.subarray(8, 12).toString("latin1");
|
|
8870
|
+
if ((majorBrand || "").toLowerCase().startsWith("qt")) {
|
|
8871
|
+
outExt = ".mov";
|
|
8872
|
+
outMime = "video/quicktime";
|
|
8873
|
+
} else {
|
|
8874
|
+
outExt = ".mp4";
|
|
8875
|
+
outMime = "video/mp4";
|
|
8876
|
+
}
|
|
8828
8877
|
}
|
|
8829
8878
|
await fsp__namespace.default.rm(tmpDir, { recursive: true, force: true });
|
|
8830
8879
|
const promptHash = hashPrompt(prompt);
|
|
8831
8880
|
const manifest = {
|
|
8832
8881
|
kind: "video",
|
|
8833
|
-
request: { promptHash, seed, params: { size: [w, h2], fps, duration, format: outExt.replace(".", "") }, model: veoModel, provider: "google" },
|
|
8882
|
+
request: { promptHash, seed, params: { size: [w, h2], fps: effectiveFps, duration: effectiveDuration, format: outExt.replace(".", "") }, model: veoModel, provider: "google" },
|
|
8834
8883
|
artifacts: [],
|
|
8835
8884
|
metrics: { durationMs: Date.now() - startedMs, retries: 0, fallbacks: 0 },
|
|
8836
8885
|
trace: traceId
|
|
8837
8886
|
};
|
|
8838
|
-
const saved = await saveArtifacts({ root: process.cwd(), kind: "video" }, [{ bytes: videoBytes, ext: outExt }], manifest);
|
|
8887
|
+
const saved = await saveArtifacts({ root: process.cwd(), kind: "video", baseDir: "artifacts/media/videos", flat: false, skipManifest: true }, [{ bytes: videoBytes, ext: outExt }], manifest);
|
|
8839
8888
|
const idemKey = req.headers["idempotency-key"] || void 0;
|
|
8840
8889
|
const idToken = auth.substring("Bearer ".length).trim();
|
|
8841
8890
|
const decoded = await decodeFirebaseToken(idToken).catch(() => null);
|
|
@@ -8850,7 +8899,7 @@ app.post("/api/v1/video", rateLimitMiddleware, async (req, res) => {
|
|
|
8850
8899
|
updatedAt: (/* @__PURE__ */ new Date()).toISOString(),
|
|
8851
8900
|
uid
|
|
8852
8901
|
});
|
|
8853
|
-
return res.json({ success: true, data: { url: saved.
|
|
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 } } });
|
|
8854
8903
|
} catch (error) {
|
|
8855
8904
|
console.error("[Video API] Error:", error);
|
|
8856
8905
|
return res.status(500).json({
|