vidspotai-shared 1.0.93 → 1.0.95
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/lib/schemas/videoPlan.schema.d.ts +2 -2
- package/lib/schemas/videoPlan.schema.d.ts.map +1 -1
- package/lib/schemas/videoPlan.schema.js +11 -1
- package/lib/services/agent/tools/planVideo.tool.d.ts +2 -2
- package/lib/services/aiGen/helpers.d.ts.map +1 -1
- package/lib/services/aiGen/helpers.js +12 -2
- package/lib/services/aiGen/providers/google/google.service.d.ts.map +1 -1
- package/lib/services/aiGen/providers/google/google.service.js +92 -8
- package/lib/services/aiGen/providers/kling/kling.service.d.ts.map +1 -1
- package/lib/services/aiGen/providers/kling/kling.service.js +47 -8
- package/lib/services/aiGen/providers/pixverse/pixverse.service.d.ts +8 -0
- package/lib/services/aiGen/providers/pixverse/pixverse.service.d.ts.map +1 -1
- package/lib/services/aiGen/providers/pixverse/pixverse.service.js +55 -2
- package/package.json +1 -1
|
@@ -676,12 +676,12 @@ export declare const VideoPlanSchema: z.ZodObject<{
|
|
|
676
676
|
estimatedDurationMs: z.ZodOptional<z.ZodNumber>;
|
|
677
677
|
sceneIndices: z.ZodDefault<z.ZodArray<z.ZodNumber>>;
|
|
678
678
|
}, z.core.$strip>>>;
|
|
679
|
-
script: z.ZodOptional<z.ZodObject<{
|
|
679
|
+
script: z.ZodOptional<z.ZodPipe<z.ZodTransform<unknown, unknown>, z.ZodObject<{
|
|
680
680
|
fullText: z.ZodString;
|
|
681
681
|
wordCount: z.ZodOptional<z.ZodNumber>;
|
|
682
682
|
estimatedDurationMs: z.ZodOptional<z.ZodNumber>;
|
|
683
683
|
wpm: z.ZodOptional<z.ZodNumber>;
|
|
684
|
-
}, z.core.$strip
|
|
684
|
+
}, z.core.$strip>>>;
|
|
685
685
|
music: z.ZodOptional<z.ZodObject<{
|
|
686
686
|
strategy: z.ZodEnum<{
|
|
687
687
|
none: "none";
|
|
@@ -1 +1 @@
|
|
|
1
|
-
{"version":3,"file":"videoPlan.schema.d.ts","sourceRoot":"","sources":["../../src/schemas/videoPlan.schema.ts"],"names":[],"mappings":"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
|
|
1
|
+
{"version":3,"file":"videoPlan.schema.d.ts","sourceRoot":"","sources":["../../src/schemas/videoPlan.schema.ts"],"names":[],"mappings":"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"}
|
|
@@ -489,8 +489,18 @@ exports.VideoPlanSchema = zod_1.z.object({
|
|
|
489
489
|
* Stream L (L-1) — full narration script for L1+ videos. Generated before
|
|
490
490
|
* scenes in script-first authoring (L-3); scenes' voiceoverLines are slices
|
|
491
491
|
* of `script.fullText`. Absent on scene-first (S1/S2/M1) plans.
|
|
492
|
+
*
|
|
493
|
+
* Coercion: the planner LLM repeatedly emits `script` as a bare narration
|
|
494
|
+
* string instead of `{ fullText }` (observed in prod 2026-06-30 — failed all
|
|
495
|
+
* 3 retries with "expected object, received string", killing the whole
|
|
496
|
+
* project). A string is unambiguous (it IS the full text), so we normalize it
|
|
497
|
+
* to `{ fullText }` at the schema boundary rather than burning retries on a
|
|
498
|
+
* shape the model won't reliably fix. `wordCount`/`estimatedDurationMs` stay
|
|
499
|
+
* optional and get backfilled downstream.
|
|
492
500
|
*/
|
|
493
|
-
script:
|
|
501
|
+
script: zod_1.z
|
|
502
|
+
.preprocess((v) => (typeof v === "string" ? { fullText: v } : v), exports.ScriptSchema)
|
|
503
|
+
.optional(),
|
|
494
504
|
/** Music recommendation for the whole video. */
|
|
495
505
|
music: zod_1.z
|
|
496
506
|
.object({
|
|
@@ -332,12 +332,12 @@ declare const OutputSchema: z.ZodObject<{
|
|
|
332
332
|
estimatedDurationMs: z.ZodOptional<z.ZodNumber>;
|
|
333
333
|
sceneIndices: z.ZodDefault<z.ZodArray<z.ZodNumber>>;
|
|
334
334
|
}, z.core.$strip>>>;
|
|
335
|
-
script: z.ZodOptional<z.ZodObject<{
|
|
335
|
+
script: z.ZodOptional<z.ZodPipe<z.ZodTransform<unknown, unknown>, z.ZodObject<{
|
|
336
336
|
fullText: z.ZodString;
|
|
337
337
|
wordCount: z.ZodOptional<z.ZodNumber>;
|
|
338
338
|
estimatedDurationMs: z.ZodOptional<z.ZodNumber>;
|
|
339
339
|
wpm: z.ZodOptional<z.ZodNumber>;
|
|
340
|
-
}, z.core.$strip
|
|
340
|
+
}, z.core.$strip>>>;
|
|
341
341
|
music: z.ZodOptional<z.ZodObject<{
|
|
342
342
|
strategy: z.ZodEnum<{
|
|
343
343
|
none: "none";
|
|
@@ -1 +1 @@
|
|
|
1
|
-
{"version":3,"file":"helpers.d.ts","sourceRoot":"","sources":["../../../src/services/aiGen/helpers.ts"],"names":[],"mappings":"
|
|
1
|
+
{"version":3,"file":"helpers.d.ts","sourceRoot":"","sources":["../../../src/services/aiGen/helpers.ts"],"names":[],"mappings":"AAEA,OAAO,EAAE,qBAAqB,EAAE,MAAM,mBAAmB,CAAC;AAE1D;;;;;;;GAOG;AACH,wBAAgB,sBAAsB,CAAC,QAAQ,EAAE,MAAM,GAAG,OAAO,CAGhE;AAED;;;;GAIG;AACH,wBAAgB,4BAA4B,IAAI,MAAM,EAAE,CAEvD;AAED;;;;;;GAMG;AACH,wBAAgB,kBAAkB,CAAC,QAAQ,EAAE,MAAM,EAAE,KAAK,EAAE,MAAM,GAAG,OAAO,CAG3E;AAED,wBAAgB,cAAc,CAAC,MAAM,EAAE,qBAAqB,GAAG,OAAO,CAiKrE"}
|
|
@@ -5,6 +5,7 @@ exports.getModelsSupportingLastFrame = getModelsSupportingLastFrame;
|
|
|
5
5
|
exports.modelDeclaresField = modelDeclaresField;
|
|
6
6
|
exports.validateParams = validateParams;
|
|
7
7
|
const aiModels_1 = require("../../globals/aiModels");
|
|
8
|
+
const errors_1 = require("../../utils/errors");
|
|
8
9
|
/**
|
|
9
10
|
* Whether a model supports first-last-frame (FLF) interpolation, i.e. accepts a
|
|
10
11
|
* `lastFrameImageUrl`. The source of truth is the model config: a model declares
|
|
@@ -125,9 +126,18 @@ function validateParams(params) {
|
|
|
125
126
|
}
|
|
126
127
|
}
|
|
127
128
|
}
|
|
128
|
-
// 4. Fail fast if validation fails
|
|
129
|
+
// 4. Fail fast if validation fails.
|
|
130
|
+
// These are DETERMINISTIC input/capability mismatches (a required input the
|
|
131
|
+
// model needs is absent, an unsupported value/combo, etc.) — an expected
|
|
132
|
+
// user-input outcome, NOT a server fault. Throw a typed UserFacingError with
|
|
133
|
+
// CAPABILITY_MISMATCH so it (a) short-circuits the scene retry loop, (b) stays
|
|
134
|
+
// OFF the pager (errorPaging non-paging set) instead of alerting on-call with
|
|
135
|
+
// a raw "INTERNAL Validation failed" — e.g. a kling-motion-control job
|
|
136
|
+
// submitted without its required inputVideoUrl (2026-07-09). The job surfaces
|
|
137
|
+
// the friendly CAPABILITY_MISMATCH message; the technical detail stays on the
|
|
138
|
+
// scene for debugging.
|
|
129
139
|
if (errors.length) {
|
|
130
|
-
throw new
|
|
140
|
+
throw new errors_1.UserFacingError(`Validation failed: ${errors.join("; ")}`, errors_1.USER_FACING_ERROR_CODES.CAPABILITY_MISMATCH);
|
|
131
141
|
}
|
|
132
142
|
return true;
|
|
133
143
|
}
|
|
@@ -1 +1 @@
|
|
|
1
|
-
{"version":3,"file":"google.service.d.ts","sourceRoot":"","sources":["../../../../../src/services/aiGen/providers/google/google.service.ts"],"names":[],"mappings":"AAiBA,OAAO,EAAE,wBAAwB,EAAE,MAAM,8BAA8B,CAAC;AACxE,OAAO,EACL,iBAAiB,EACjB,qBAAqB,EACrB,qBAAqB,EACrB,qBAAqB,EACrB,qBAAqB,EACrB,oBAAoB,EACpB,oBAAoB,EACpB,qBAAqB,EACrB,qBAAqB,EACrB,iBAAiB,EACjB,iBAAiB,EAClB,MAAM,UAAU,CAAC;
|
|
1
|
+
{"version":3,"file":"google.service.d.ts","sourceRoot":"","sources":["../../../../../src/services/aiGen/providers/google/google.service.ts"],"names":[],"mappings":"AAiBA,OAAO,EAAE,wBAAwB,EAAE,MAAM,8BAA8B,CAAC;AACxE,OAAO,EACL,iBAAiB,EACjB,qBAAqB,EACrB,qBAAqB,EACrB,qBAAqB,EACrB,qBAAqB,EACrB,oBAAoB,EACpB,oBAAoB,EACpB,qBAAqB,EACrB,qBAAqB,EACrB,iBAAiB,EACjB,iBAAiB,EAClB,MAAM,UAAU,CAAC;AAkElB,qBAAa,aAAc,SAAQ,wBAAwB;IAKzD,OAAO,CAAC,EAAE,CAAc;IACxB,OAAO,CAAC,OAAO,CAAgB;IAC/B,OAAO,CAAC,MAAM,CAAC,QAAQ,CAAC,kBAAkB,CAAK;;IAQ/C;;;;;;;;;OASG;IACG,YAAY,CAChB,MAAM,EAAE,oBAAoB,GAC3B,OAAO,CAAC,oBAAoB,CAAC;IAoChC;;;;;OAKG;IACH,OAAO,CAAC,eAAe;IAoCvB;;;;OAIG;YACW,kBAAkB;IAsC1B,aAAa,CACjB,MAAM,EAAE,qBAAqB,GAC5B,OAAO,CAAC,qBAAqB,CAAC;IA0O3B,gBAAgB,CAAC,EACrB,IAAI,EACJ,cAAc,EACd,cAAyB,GAC1B,EAAE,iBAAiB,GAAG,OAAO,CAAC,iBAAiB,CAAC;IA8J3C,aAAa,CACjB,MAAM,EAAE,qBAAqB,GAC5B,OAAO,CAAC,qBAAqB,CAAC;YAiBnB,cAAc;IAwG5B;;;;;;OAMG;IACH;;;;OAIG;IACG,aAAa,CACjB,MAAM,EAAE,qBAAqB,GAC5B,OAAO,CAAC,qBAAqB,CAAC;IAIjC,aAAa,CAAC,EAAE,QAAQ,EAAE,QAAY,EAAE,UAAmB,EAAE,SAAiB,EAAE,SAAa,EAAE,SAAS,EAAE,WAAW,EAAE,EAAE,iBAAiB,GAAG,MAAM;CA8BpJ"}
|
|
@@ -22,6 +22,52 @@ const googleFetch_1 = require("./googleFetch");
|
|
|
22
22
|
const googleMusic_1 = require("./googleMusic");
|
|
23
23
|
const googleApiKeys_1 = require("./googleApiKeys");
|
|
24
24
|
const googleKeyPool_1 = require("./googleKeyPool");
|
|
25
|
+
/**
|
|
26
|
+
* Wait until a file exists AND its on-disk size has stopped growing, returning
|
|
27
|
+
* the final stable byte size. The Veo Files API downloader can resolve / create
|
|
28
|
+
* the file before all bytes are flushed; reading it immediately then streams a
|
|
29
|
+
* partial file — truncated at a chunk boundary (commonly exactly 4 MiB) — which
|
|
30
|
+
* drops the trailing `moov` atom and yields a corrupt MP4. Requiring the size to
|
|
31
|
+
* hold steady across consecutive polls closes that flush race.
|
|
32
|
+
*/
|
|
33
|
+
async function waitForStableFile(path, timeoutMs = 30000, intervalMs = 500) {
|
|
34
|
+
const start = Date.now();
|
|
35
|
+
let lastSize = -1;
|
|
36
|
+
let stablePolls = 0;
|
|
37
|
+
while (Date.now() - start < timeoutMs) {
|
|
38
|
+
let size = -1;
|
|
39
|
+
try {
|
|
40
|
+
size = (await promises_1.default.stat(path)).size;
|
|
41
|
+
}
|
|
42
|
+
catch {
|
|
43
|
+
// not created yet — keep polling
|
|
44
|
+
}
|
|
45
|
+
if (size > 0 && size === lastSize) {
|
|
46
|
+
// Two consecutive equal, non-zero reads ⇒ the writer has finished.
|
|
47
|
+
if (++stablePolls >= 2)
|
|
48
|
+
return size;
|
|
49
|
+
}
|
|
50
|
+
else {
|
|
51
|
+
stablePolls = 0;
|
|
52
|
+
}
|
|
53
|
+
lastSize = size;
|
|
54
|
+
await new Promise((r) => setTimeout(r, intervalMs));
|
|
55
|
+
}
|
|
56
|
+
throw new Error(`Timeout waiting for a stable (fully-written) file: ${path}`);
|
|
57
|
+
}
|
|
58
|
+
/**
|
|
59
|
+
* Cheap structural check that a local MP4 is COMPLETE: a well-formed Veo clip
|
|
60
|
+
* carries both an `ftyp` box (header) and a `moov` box (movie metadata, written
|
|
61
|
+
* at the END for non-faststart output). A truncated download is missing `moov`
|
|
62
|
+
* — exactly what ffprobe reports downstream as "moov atom not found". Scans the
|
|
63
|
+
* file bytes (clips are a few MB) so we never need an ffprobe binary in this
|
|
64
|
+
* shared module.
|
|
65
|
+
*/
|
|
66
|
+
async function mp4IsComplete(path) {
|
|
67
|
+
const buf = await promises_1.default.readFile(path);
|
|
68
|
+
return (buf.includes(Buffer.from("ftyp", "ascii")) &&
|
|
69
|
+
buf.includes(Buffer.from("moov", "ascii")));
|
|
70
|
+
}
|
|
25
71
|
const VEO_3_1_MODELS = new Set([
|
|
26
72
|
aiModels_1.EVideoGenModels.GOOGLE_VEO_3_1,
|
|
27
73
|
aiModels_1.EVideoGenModels.GOOGLE_VEO_3_1_FAST,
|
|
@@ -402,14 +448,52 @@ class GoogleService extends baseAiGenProvider_service_1.BaseAiGenProviderService
|
|
|
402
448
|
// separate project, so downloading its videos via `this.ai` would 403/404.
|
|
403
449
|
// Use the re-selected `client` (falls back to `this.ai` for un-tagged
|
|
404
450
|
// legacy tasks, which were submitted on GOOGLE_API_KEY anyway).
|
|
405
|
-
|
|
406
|
-
|
|
407
|
-
|
|
408
|
-
|
|
409
|
-
|
|
410
|
-
|
|
411
|
-
|
|
412
|
-
|
|
451
|
+
// Download → validate-complete → upload, retried as a unit. The Files API
|
|
452
|
+
// downloader intermittently resolves before the clip is fully flushed to
|
|
453
|
+
// disk; uploading then truncates the scene at a chunk boundary (commonly
|
|
454
|
+
// exactly 4 MiB), dropping the trailing `moov` atom and yielding a corrupt
|
|
455
|
+
// MP4 that ffprobe rejects downstream (poster/watermark "moov atom not
|
|
456
|
+
// found", and a broken deliverable). Guard with (1) a stable-size wait so
|
|
457
|
+
// we only read a fully-written file, (2) a structural ftyp+moov check, and
|
|
458
|
+
// (3) a stored-vs-local size match after upload. Any failure re-runs the
|
|
459
|
+
// whole download instead of shipping corruption.
|
|
460
|
+
const MAX_FETCH_ATTEMPTS = 3;
|
|
461
|
+
let uploaded = false;
|
|
462
|
+
for (let attempt = 1; attempt <= MAX_FETCH_ATTEMPTS && !uploaded; attempt++) {
|
|
463
|
+
await this.withTransientRetry("files.download", () => client.files.download({ file: video, downloadPath: localPath }));
|
|
464
|
+
const localSize = await waitForStableFile(localPath, 30000, 500);
|
|
465
|
+
if (await mp4IsComplete(localPath)) {
|
|
466
|
+
await (0, promises_2.pipeline)((0, fs_1.createReadStream)(localPath), file.createWriteStream({ contentType: "video/mp4" }));
|
|
467
|
+
const [meta] = await file.getMetadata();
|
|
468
|
+
const storedSize = Number(meta.size);
|
|
469
|
+
if (Number.isFinite(storedSize) && storedSize === localSize) {
|
|
470
|
+
uploaded = true;
|
|
471
|
+
break; // faithful, complete upload
|
|
472
|
+
}
|
|
473
|
+
logger_1.logger.warn("google: scene upload size mismatch — retrying", {
|
|
474
|
+
filePath,
|
|
475
|
+
attempt,
|
|
476
|
+
localSize,
|
|
477
|
+
storedSize,
|
|
478
|
+
});
|
|
479
|
+
}
|
|
480
|
+
else {
|
|
481
|
+
logger_1.logger.warn("google: downloaded scene missing moov atom — retrying", {
|
|
482
|
+
filePath,
|
|
483
|
+
attempt,
|
|
484
|
+
localSize,
|
|
485
|
+
});
|
|
486
|
+
}
|
|
487
|
+
// Failed validation — discard the partial file before the next attempt.
|
|
488
|
+
await promises_1.default.unlink(localPath).catch(() => { });
|
|
489
|
+
}
|
|
490
|
+
if (!uploaded) {
|
|
491
|
+
logger_1.logger.error("google: scene download/upload failed integrity validation — giving up", { filePath });
|
|
492
|
+
return {
|
|
493
|
+
status: types_1.EVideoSceneStatus.FAILED,
|
|
494
|
+
errorMessage: "Generated video failed integrity validation (incomplete/truncated download).",
|
|
495
|
+
};
|
|
496
|
+
}
|
|
413
497
|
const [signedUrl] = await file.getSignedUrl({
|
|
414
498
|
action: "read",
|
|
415
499
|
expires: "03-09-2491",
|
|
@@ -1 +1 @@
|
|
|
1
|
-
{"version":3,"file":"kling.service.d.ts","sourceRoot":"","sources":["../../../../../src/services/aiGen/providers/kling/kling.service.ts"],"names":[],"mappings":"AAUA,OAAO,EAAE,wBAAwB,EAAE,MAAM,8BAA8B,CAAC;AACxE,OAAO,EACL,iBAAiB,EACjB,qBAAqB,EACrB,qBAAqB,EACrB,iBAAiB,EACjB,iBAAiB,EAClB,MAAM,UAAU,CAAC;AAsBlB;;;;;GAKG;AACH,qBAAa,YAAa,SAAQ,wBAAwB;IACxD,OAAO,CAAC,QAAQ,CAAC,OAAO,CAAuC;IAC/D,OAAO,CAAC,QAAQ,CAAC,eAAe,CAAW;IAC3C,OAAO,CAAC,QAAQ,CAAC,aAAa,CAAU;IASxC,OAAO,CAAC,QAAQ,CAAC,WAAW,CAAkD;IAC9E,OAAO,CAAC,QAAQ,CAAC,YAAY,CAAmD;;YAWlE,OAAO;IAgCf,aAAa,CACjB,MAAM,EAAE,qBAAqB,GAC5B,OAAO,CAAC,qBAAqB,CAAC;IAoDjC;;;;OAIG;YACW,WAAW;YA6BX,iBAAiB;YA6BjB,qBAAqB;YAgCrB,eAAe;YAkDf,mBAAmB;YAgCnB,uBAAuB;
|
|
1
|
+
{"version":3,"file":"kling.service.d.ts","sourceRoot":"","sources":["../../../../../src/services/aiGen/providers/kling/kling.service.ts"],"names":[],"mappings":"AAUA,OAAO,EAAE,wBAAwB,EAAE,MAAM,8BAA8B,CAAC;AACxE,OAAO,EACL,iBAAiB,EACjB,qBAAqB,EACrB,qBAAqB,EACrB,iBAAiB,EACjB,iBAAiB,EAClB,MAAM,UAAU,CAAC;AAsBlB;;;;;GAKG;AACH,qBAAa,YAAa,SAAQ,wBAAwB;IACxD,OAAO,CAAC,QAAQ,CAAC,OAAO,CAAuC;IAC/D,OAAO,CAAC,QAAQ,CAAC,eAAe,CAAW;IAC3C,OAAO,CAAC,QAAQ,CAAC,aAAa,CAAU;IASxC,OAAO,CAAC,QAAQ,CAAC,WAAW,CAAkD;IAC9E,OAAO,CAAC,QAAQ,CAAC,YAAY,CAAmD;;YAWlE,OAAO;IAgCf,aAAa,CACjB,MAAM,EAAE,qBAAqB,GAC5B,OAAO,CAAC,qBAAqB,CAAC;IAoDjC;;;;OAIG;YACW,WAAW;YA6BX,iBAAiB;YA6BjB,qBAAqB;YAgCrB,eAAe;YAkDf,mBAAmB;YAgCnB,uBAAuB;IAqI/B,gBAAgB,CAAC,MAAM,EAAE,iBAAiB,GAAG,OAAO,CAAC,iBAAiB,CAAC;IAW7E,cAAc,IAAI,MAAM;IAexB,aAAa,CAAC,MAAM,EAAE,iBAAiB,GAAG,MAAM;CAGjD"}
|
|
@@ -219,25 +219,64 @@ class KlingService extends baseAiGenProvider_service_1.BaseAiGenProviderService
|
|
|
219
219
|
// True rate-limit (HTTP 429 without the balance code) — worth retrying with backoff.
|
|
220
220
|
const isRateLimit = httpStatus === 429 && !isInsufficientBalance;
|
|
221
221
|
const isRetryable = (isTimeout || isNetworkError || isRateLimit) && !isInsufficientBalance;
|
|
222
|
-
// Insufficient balance is a
|
|
222
|
+
// Insufficient balance is a billing/account fault an operator must act on
|
|
223
|
+
// (top up Kling). Classify as ACCOUNT_QUOTA_EXCEEDED so the shared paging
|
|
224
|
+
// policy (errorPaging.ts) pages ONCE at the scene boundary — consistent
|
|
225
|
+
// with the Veo billing-depletion path. The top-up detail is logged at
|
|
226
|
+
// warn (Loki only, no duplicate Slack page) so it stays searchable
|
|
227
|
+
// without the previous double-page (raw throw here + generic scene page).
|
|
223
228
|
if (isInsufficientBalance) {
|
|
224
|
-
logger_1.logger.
|
|
229
|
+
logger_1.logger.warn("Kling account balance too low — top up required to continue video generation", {
|
|
225
230
|
endpoint,
|
|
226
231
|
klingCode,
|
|
227
232
|
klingMessage: err.response?.data?.message,
|
|
228
233
|
httpStatus,
|
|
229
234
|
});
|
|
230
|
-
throw
|
|
235
|
+
throw new errors_1.UserFacingError("This video provider is temporarily unavailable. Please try a different model or retry shortly.", errors_1.USER_FACING_ERROR_CODES.ACCOUNT_QUOTA_EXCEEDED);
|
|
231
236
|
}
|
|
232
|
-
//
|
|
237
|
+
// Kling code 1201 is a broad "input rejected" family — a user-fixable
|
|
238
|
+
// input constraint, not a system fault. It fires for AUDIO (avatar /
|
|
239
|
+
// lip-sync `sound_file`) AND for IMAGE inputs (e.g. an oversized
|
|
240
|
+
// end-frame `image_tail` on a plain image2video — see the kling-v3.0
|
|
241
|
+
// end-frame case, 2026-07-09). So only blame audio when audio was
|
|
242
|
+
// actually part of the request; otherwise the message misleads the
|
|
243
|
+
// user (and whoever debugs the log) into hunting a non-existent audio
|
|
244
|
+
// clip. CAPABILITY_MISMATCH keeps it off the pager (errorPaging set).
|
|
233
245
|
if (isAudioError) {
|
|
234
246
|
const klingMsg = (err.response?.data?.message ?? "").toLowerCase();
|
|
235
|
-
const
|
|
236
|
-
|
|
237
|
-
|
|
238
|
-
|
|
247
|
+
const isSize = klingMsg.includes("size");
|
|
248
|
+
const hasAudioInput = endpoint.includes("avatar") ||
|
|
249
|
+
endpoint.includes("lip") ||
|
|
250
|
+
!!(body && typeof body === "object" && body.sound_file);
|
|
251
|
+
let userMessage;
|
|
252
|
+
if (hasAudioInput) {
|
|
253
|
+
userMessage = isSize
|
|
254
|
+
? "Your audio clip is too large for this video. Please use a shorter audio clip and try again."
|
|
255
|
+
: "Your audio format is not supported. Please upload an MP3 or WAV file and try again.";
|
|
256
|
+
}
|
|
257
|
+
else {
|
|
258
|
+
userMessage = isSize
|
|
259
|
+
? "One of your input images is too large for this model. Please use a smaller image (or remove the end-frame) and try again."
|
|
260
|
+
: "One of your inputs was rejected by this model. Please adjust your image or settings and try again.";
|
|
261
|
+
}
|
|
262
|
+
throw new errors_1.UserFacingError(userMessage, errors_1.USER_FACING_ERROR_CODES.CAPABILITY_MISMATCH);
|
|
239
263
|
}
|
|
240
264
|
if (!isRetryable || attempt === maxAttempts) {
|
|
265
|
+
// A rate-limit that exhausted its retries is an expected, un-actionable
|
|
266
|
+
// condition (Kling throughput saturation) — surface it as a non-paging
|
|
267
|
+
// UserFacingError rather than a raw axios 429, so it doesn't page
|
|
268
|
+
// on-call. (Genuine 1102 balance depletion is handled above and DOES
|
|
269
|
+
// page; ambiguous bare 429s land here and read as rate-limit, which is
|
|
270
|
+
// the safe default — rate limits shouldn't page.)
|
|
271
|
+
if (isRateLimit) {
|
|
272
|
+
logger_1.logger.warn("Kling rate-limited — retries exhausted", {
|
|
273
|
+
endpoint,
|
|
274
|
+
attempt,
|
|
275
|
+
maxAttempts,
|
|
276
|
+
httpStatus,
|
|
277
|
+
});
|
|
278
|
+
throw new errors_1.UserFacingError("This video provider is busy right now. Please retry in a little while.", errors_1.USER_FACING_ERROR_CODES.VIDEO_PROVIDER_RATE_LIMITED);
|
|
279
|
+
}
|
|
241
280
|
logger_1.logger.error("Kling API request failed (non-retryable or max attempts reached)", {
|
|
242
281
|
endpoint,
|
|
243
282
|
attempt,
|
|
@@ -2,6 +2,14 @@ import { BaseAiGenProviderService } from "../baseAiGenProvider.service";
|
|
|
2
2
|
import { CreditUsageParams, VideoGenerationParams, VideoGenerationResult, VideoStatusParams, VideoStatusResult } from "../types";
|
|
3
3
|
export declare class PixVerseService extends BaseAiGenProviderService {
|
|
4
4
|
private readonly baseUrl;
|
|
5
|
+
/**
|
|
6
|
+
* Uploads a source image to PixVerse and returns the integer `img_id` that
|
|
7
|
+
* /img/generate requires. The Upload-Image endpoint accepts either a binary
|
|
8
|
+
* `image` file or an `image_url` string (multipart/form-data) — we pass the
|
|
9
|
+
* URL we already have. Note: NO Content-Type header here; fetch sets the
|
|
10
|
+
* multipart boundary itself when the body is a FormData.
|
|
11
|
+
*/
|
|
12
|
+
private uploadImage;
|
|
5
13
|
generateVideo(params: VideoGenerationParams): Promise<VideoGenerationResult>;
|
|
6
14
|
checkVideoStatus({ task, outputFilename, outputFilePath, }: VideoStatusParams): Promise<VideoStatusResult>;
|
|
7
15
|
getCreditUsed(params: CreditUsageParams): number;
|
|
@@ -1 +1 @@
|
|
|
1
|
-
{"version":3,"file":"pixverse.service.d.ts","sourceRoot":"","sources":["../../../../../src/services/aiGen/providers/pixverse/pixverse.service.ts"],"names":[],"mappings":"AAEA,OAAO,EAAE,wBAAwB,EAAE,MAAM,8BAA8B,CAAC;AACxE,OAAO,EACL,iBAAiB,EACjB,qBAAqB,EACrB,qBAAqB,EACrB,iBAAiB,EACjB,iBAAiB,EAClB,MAAM,UAAU,CAAC;AA0FlB,qBAAa,eAAgB,SAAQ,wBAAwB;IAC3D,OAAO,CAAC,QAAQ,CAAC,OAAO,CAAkD;
|
|
1
|
+
{"version":3,"file":"pixverse.service.d.ts","sourceRoot":"","sources":["../../../../../src/services/aiGen/providers/pixverse/pixverse.service.ts"],"names":[],"mappings":"AAEA,OAAO,EAAE,wBAAwB,EAAE,MAAM,8BAA8B,CAAC;AACxE,OAAO,EACL,iBAAiB,EACjB,qBAAqB,EACrB,qBAAqB,EACrB,iBAAiB,EACjB,iBAAiB,EAClB,MAAM,UAAU,CAAC;AA0FlB,qBAAa,eAAgB,SAAQ,wBAAwB;IAC3D,OAAO,CAAC,QAAQ,CAAC,OAAO,CAAkD;IAK1E;;;;;;OAMG;YACW,WAAW;IA6CnB,aAAa,CACjB,MAAM,EAAE,qBAAqB,GAC5B,OAAO,CAAC,qBAAqB,CAAC;IAgL3B,gBAAgB,CAAC,EACrB,IAAI,EACJ,cAAc,EACd,cAAyB,GAC1B,EAAE,iBAAiB,GAAG,OAAO,CAAC,iBAAiB,CAAC;IA0FjD,aAAa,CAAC,MAAM,EAAE,iBAAiB,GAAG,MAAM;CAkDjD"}
|
|
@@ -82,6 +82,52 @@ class PixVerseService extends baseAiGenProvider_service_1.BaseAiGenProviderServi
|
|
|
82
82
|
this.baseUrl = "https://app-api.pixverse.ai/openapi/v2/video";
|
|
83
83
|
}
|
|
84
84
|
// =========================================================
|
|
85
|
+
// UPLOAD IMAGE (image-to-video prerequisite)
|
|
86
|
+
// =========================================================
|
|
87
|
+
/**
|
|
88
|
+
* Uploads a source image to PixVerse and returns the integer `img_id` that
|
|
89
|
+
* /img/generate requires. The Upload-Image endpoint accepts either a binary
|
|
90
|
+
* `image` file or an `image_url` string (multipart/form-data) — we pass the
|
|
91
|
+
* URL we already have. Note: NO Content-Type header here; fetch sets the
|
|
92
|
+
* multipart boundary itself when the body is a FormData.
|
|
93
|
+
*/
|
|
94
|
+
async uploadImage(imageUrl, traceId) {
|
|
95
|
+
const form = new FormData();
|
|
96
|
+
form.append("image_url", imageUrl);
|
|
97
|
+
return (0, transientRetry_1.withTransientRetry)("pixverse:uploadImage", async () => {
|
|
98
|
+
const resp = await fetch(`https://app-api.pixverse.ai/openapi/v2/image/upload`, {
|
|
99
|
+
method: "POST",
|
|
100
|
+
headers: {
|
|
101
|
+
"API-KEY": process.env.PIXVERSE_API_KEY,
|
|
102
|
+
"Ai-trace-id": traceId,
|
|
103
|
+
},
|
|
104
|
+
body: form,
|
|
105
|
+
});
|
|
106
|
+
if (!resp.ok) {
|
|
107
|
+
const errText = await resp.text();
|
|
108
|
+
throw classifyPixVerseHttpError(resp.status, errText, "uploadImage");
|
|
109
|
+
}
|
|
110
|
+
const data = await resp.json();
|
|
111
|
+
if (data.ErrCode !== undefined && data.ErrCode !== 0) {
|
|
112
|
+
throw classifyPixVerseApiError(data.ErrCode, data.ErrMsg ?? "", "uploadImage");
|
|
113
|
+
}
|
|
114
|
+
const imgId = data?.Resp?.img_id;
|
|
115
|
+
if (imgId === undefined || imgId === null) {
|
|
116
|
+
throw new Error("PixVerse image upload did not return img_id");
|
|
117
|
+
}
|
|
118
|
+
return Number(imgId);
|
|
119
|
+
}, {
|
|
120
|
+
onRetry: ({ attempt, maxAttempts, backoffMs, err }) => {
|
|
121
|
+
logger_1.logger.warn(`PixVerse uploadImage transient error — retrying`, {
|
|
122
|
+
attempt,
|
|
123
|
+
maxAttempts,
|
|
124
|
+
backoffMs,
|
|
125
|
+
message: err.message,
|
|
126
|
+
});
|
|
127
|
+
},
|
|
128
|
+
});
|
|
129
|
+
}
|
|
130
|
+
// =========================================================
|
|
85
131
|
// GENERATE VIDEO
|
|
86
132
|
// =========================================================
|
|
87
133
|
async generateVideo(params) {
|
|
@@ -206,10 +252,17 @@ class PixVerseService extends baseAiGenProvider_service_1.BaseAiGenProviderServi
|
|
|
206
252
|
if (params.lipSync.ttsSpeakerId)
|
|
207
253
|
body.lip_sync_tts_speaker_id = params.lipSync.ttsSpeakerId;
|
|
208
254
|
}
|
|
209
|
-
// I2V switches endpoint and adds the image reference.
|
|
255
|
+
// I2V switches endpoint and adds the image reference. The /img/generate
|
|
256
|
+
// endpoint does NOT accept a raw image_url — it requires an integer
|
|
257
|
+
// `img_id` from the Upload-Image API (verified 2026-06-30 against
|
|
258
|
+
// https://docs.platform.pixverse.ai/image-to-video-generation-13016633e0).
|
|
259
|
+
// Sending image_url instead surfaced in prod as ErrCode 400017 "Either
|
|
260
|
+
// img_id or api_gen_img_ids must be provided." So we upload first, then
|
|
261
|
+
// pass the returned img_id. (Distinct from the /template/generate "effect"
|
|
262
|
+
// path above, which DOES take a customer_img_url directly.)
|
|
210
263
|
const isImageToVideo = !!params.inputImageUrl;
|
|
211
264
|
if (isImageToVideo) {
|
|
212
|
-
body.
|
|
265
|
+
body.img_id = await this.uploadImage(params.inputImageUrl, traceId);
|
|
213
266
|
}
|
|
214
267
|
// I2V path is `/img/generate` — NOT `/image/generate`, which returns a bare
|
|
215
268
|
// "404 page not found" (verified 2026-06-15 via PixVerse OpenAPI smoke test
|