vidspotai-shared 1.0.94 → 1.0.96
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/globals/aiModels/enums.d.ts +2 -0
- package/lib/globals/aiModels/enums.d.ts.map +1 -1
- package/lib/globals/aiModels/enums.js +5 -0
- package/lib/globals/aiModels/index.d.ts.map +1 -1
- package/lib/globals/aiModels/index.js +2 -0
- package/lib/globals/aiModels/providers/hostedAvatars.d.ts +22 -0
- package/lib/globals/aiModels/providers/hostedAvatars.d.ts.map +1 -0
- package/lib/globals/aiModels/providers/hostedAvatars.js +129 -0
- package/lib/globals/types.d.ts +58 -1
- package/lib/globals/types.d.ts.map +1 -1
- package/lib/globals/types.js +61 -1
- package/lib/models/demoVideoProject.model.d.ts +244 -0
- package/lib/models/demoVideoProject.model.d.ts.map +1 -0
- package/lib/models/demoVideoProject.model.js +2 -0
- package/lib/models/githubConnection.model.d.ts +35 -0
- package/lib/models/githubConnection.model.d.ts.map +1 -0
- package/lib/models/githubConnection.model.js +2 -0
- package/lib/models/index.d.ts +3 -0
- package/lib/models/index.d.ts.map +1 -1
- package/lib/models/index.js +3 -0
- package/lib/models/outreach.model.d.ts +175 -0
- package/lib/models/outreach.model.d.ts.map +1 -0
- package/lib/models/outreach.model.js +2 -0
- package/lib/models/video.model.d.ts +4 -0
- package/lib/models/video.model.d.ts.map +1 -1
- package/lib/schemas/brandKit.schema.d.ts +4 -0
- package/lib/schemas/brandKit.schema.d.ts.map +1 -1
- package/lib/schemas/brandKit.schema.js +10 -0
- package/lib/schemas/demoQuestion.schema.d.ts +286 -0
- package/lib/schemas/demoQuestion.schema.d.ts.map +1 -0
- package/lib/schemas/demoQuestion.schema.js +210 -0
- package/lib/schemas/demoSpec.schema.d.ts +109 -6
- package/lib/schemas/demoSpec.schema.d.ts.map +1 -1
- package/lib/schemas/demoSpec.schema.js +95 -7
- package/lib/schemas/index.d.ts +1 -0
- package/lib/schemas/index.d.ts.map +1 -1
- package/lib/schemas/index.js +1 -0
- 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/providerFallback/chains.d.ts.map +1 -1
- package/lib/services/agent/providerFallback/chains.js +9 -1
- package/lib/services/agent/taskPoller.d.ts.map +1 -1
- package/lib/services/agent/taskPoller.js +17 -6
- package/lib/services/agent/tools/generateAvatarVideo.tool.d.ts +21 -9
- package/lib/services/agent/tools/generateAvatarVideo.tool.d.ts.map +1 -1
- package/lib/services/agent/tools/generateAvatarVideo.tool.js +135 -39
- package/lib/services/agent/tools/planVideo.tool.d.ts +2 -2
- package/lib/services/aiGen/aiGenFactory.service.d.ts +1 -1
- package/lib/services/aiGen/aiGenFactory.service.d.ts.map +1 -1
- package/lib/services/aiGen/aiGenFactory.service.js +10 -2
- package/lib/services/aiGen/helpers.d.ts.map +1 -1
- package/lib/services/aiGen/helpers.js +12 -2
- 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/lib/services/avatarGen/IAvatarVideoProvider.d.ts +136 -0
- package/lib/services/avatarGen/IAvatarVideoProvider.d.ts.map +1 -0
- package/lib/services/avatarGen/IAvatarVideoProvider.js +42 -0
- package/lib/services/avatarGen/avatarProviderFactory.d.ts +18 -0
- package/lib/services/avatarGen/avatarProviderFactory.d.ts.map +1 -0
- package/lib/services/avatarGen/avatarProviderFactory.js +54 -0
- package/lib/services/avatarGen/index.d.ts +6 -0
- package/lib/services/avatarGen/index.d.ts.map +1 -0
- package/lib/services/avatarGen/index.js +21 -0
- package/lib/services/avatarGen/providers/heygenAvatarProvider.d.ts +45 -0
- package/lib/services/avatarGen/providers/heygenAvatarProvider.d.ts.map +1 -0
- package/lib/services/avatarGen/providers/heygenAvatarProvider.js +190 -0
- package/lib/services/avatarGen/providers/klingAvatarProvider.d.ts +32 -0
- package/lib/services/avatarGen/providers/klingAvatarProvider.d.ts.map +1 -0
- package/lib/services/avatarGen/providers/klingAvatarProvider.js +69 -0
- package/lib/services/avatarGen/providers/tavusAvatarProvider.d.ts +56 -0
- package/lib/services/avatarGen/providers/tavusAvatarProvider.d.ts.map +1 -0
- package/lib/services/avatarGen/providers/tavusAvatarProvider.js +183 -0
- package/lib/services/demo/demoAuthor.d.ts +45 -0
- package/lib/services/demo/demoAuthor.d.ts.map +1 -0
- package/lib/services/demo/demoAuthor.js +75 -0
- package/lib/services/demo/demoAuthorNormalize.d.ts +49 -0
- package/lib/services/demo/demoAuthorNormalize.d.ts.map +1 -0
- package/lib/services/demo/demoAuthorNormalize.js +327 -0
- package/lib/services/demo/demoAuthorPrompt.d.ts +102 -0
- package/lib/services/demo/demoAuthorPrompt.d.ts.map +1 -0
- package/lib/services/demo/demoAuthorPrompt.js +243 -0
- package/lib/services/demo/demoCredits.d.ts +20 -0
- package/lib/services/demo/demoCredits.d.ts.map +1 -0
- package/lib/services/demo/demoCredits.js +51 -0
- package/lib/services/demo/demoMediaGuard.d.ts +43 -0
- package/lib/services/demo/demoMediaGuard.d.ts.map +1 -0
- package/lib/services/demo/demoMediaGuard.js +207 -0
- package/lib/services/demo/demoMusic.d.ts +13 -0
- package/lib/services/demo/demoMusic.d.ts.map +1 -0
- package/lib/services/demo/demoMusic.js +93 -0
- package/lib/services/demo/demoPrefill.d.ts +76 -0
- package/lib/services/demo/demoPrefill.d.ts.map +1 -0
- package/lib/services/demo/demoPrefill.js +435 -0
- package/lib/services/demo/demoRevisePrompt.d.ts +17 -0
- package/lib/services/demo/demoRevisePrompt.d.ts.map +1 -0
- package/lib/services/demo/demoRevisePrompt.js +85 -0
- package/lib/services/demo/demoReviser.d.ts +63 -0
- package/lib/services/demo/demoReviser.d.ts.map +1 -0
- package/lib/services/demo/demoReviser.js +81 -0
- package/lib/services/demo/demoScriptPrompt.d.ts +63 -0
- package/lib/services/demo/demoScriptPrompt.d.ts.map +1 -0
- package/lib/services/demo/demoScriptPrompt.js +234 -0
- package/lib/services/demo/demoScriptWriter.d.ts +65 -0
- package/lib/services/demo/demoScriptWriter.d.ts.map +1 -0
- package/lib/services/demo/demoScriptWriter.js +211 -0
- package/lib/services/demo/demoTriage.d.ts +65 -0
- package/lib/services/demo/demoTriage.d.ts.map +1 -0
- package/lib/services/demo/demoTriage.js +130 -0
- package/lib/services/demo/githubOAuth.d.ts +78 -0
- package/lib/services/demo/githubOAuth.d.ts.map +1 -0
- package/lib/services/demo/githubOAuth.js +209 -0
- package/lib/services/demo/githubOAuthState.d.ts +10 -0
- package/lib/services/demo/githubOAuthState.d.ts.map +1 -0
- package/lib/services/demo/githubOAuthState.js +63 -0
- package/lib/services/demo/index.d.ts +24 -0
- package/lib/services/demo/index.d.ts.map +1 -0
- package/lib/services/demo/index.js +39 -0
- package/lib/services/firestore.service.d.ts +11 -0
- package/lib/services/firestore.service.d.ts.map +1 -1
- package/lib/services/firestore.service.js +24 -0
- package/lib/services/index.d.ts +2 -0
- package/lib/services/index.d.ts.map +1 -1
- package/lib/services/index.js +2 -0
- package/lib/services/tts/providers/elevenlabs.service.d.ts.map +1 -1
- package/lib/services/tts/providers/elevenlabs.service.js +16 -8
- package/lib/utils/index.d.ts +1 -0
- package/lib/utils/index.d.ts.map +1 -1
- package/lib/utils/index.js +1 -0
- package/lib/utils/outreachTemplate.d.ts +46 -0
- package/lib/utils/outreachTemplate.d.ts.map +1 -0
- package/lib/utils/outreachTemplate.js +174 -0
- package/package.json +1 -1
|
@@ -4,79 +4,177 @@ exports.generateAvatarVideoTool = void 0;
|
|
|
4
4
|
const zod_1 = require("zod");
|
|
5
5
|
const aiModels_1 = require("../../../globals/aiModels");
|
|
6
6
|
const firebase_1 = require("../../../libs/firebase");
|
|
7
|
-
const
|
|
7
|
+
const avatarProviderFactory_1 = require("../../avatarGen/avatarProviderFactory");
|
|
8
8
|
const types_1 = require("../../aiGen/types");
|
|
9
9
|
const rateLimiter_1 = require("../../rateLimiter");
|
|
10
10
|
const ttsFactory_service_1 = require("../../tts/ttsFactory.service");
|
|
11
11
|
const toolRegistry_1 = require("../toolRegistry");
|
|
12
12
|
/**
|
|
13
|
-
* generate_avatar_video —
|
|
14
|
-
*
|
|
15
|
-
*
|
|
16
|
-
*
|
|
17
|
-
* before handing off, since Kling avatar requires a public sound_file URL).
|
|
13
|
+
* generate_avatar_video — drives avatar generation through the dedicated avatar
|
|
14
|
+
* provider seam (getAvatarVideoProvider / IAvatarVideoProvider), so the planner's
|
|
15
|
+
* `talking-head-avatar` scene strategy is executable. Avatar gen is handled by
|
|
16
|
+
* the avatar provider — NOT the generic video-gen provider. Two engine families:
|
|
18
17
|
*
|
|
19
|
-
*
|
|
20
|
-
*
|
|
18
|
+
* - byo-media (default kling-avatar): caller supplies a presenter face image
|
|
19
|
+
* plus either a pre-uploaded audio URL or raw TTS text (the tool synthesizes
|
|
20
|
+
* via ElevenLabs + uploads to GCS first, since the engine needs a public
|
|
21
|
+
* audio URL).
|
|
22
|
+
* - hosted (heygen-avatar / tavus-avatar, via `model`): caller supplies a
|
|
23
|
+
* provider-hosted avatar id + a script; the engine runs its own TTS/lipsync,
|
|
24
|
+
* so the tool does NO TTS and passes no audio.
|
|
25
|
+
*
|
|
26
|
+
* Returns a provider task id; the executor's TaskPoller polls completion — it
|
|
27
|
+
* routes hosted models through the avatar seam and everything else through the
|
|
28
|
+
* generic video factory, so this works for both families.
|
|
21
29
|
*/
|
|
22
30
|
const InputSchema = zod_1.z
|
|
23
31
|
.object({
|
|
32
|
+
model: zod_1.z
|
|
33
|
+
.nativeEnum(aiModels_1.EVideoGenModels)
|
|
34
|
+
.optional()
|
|
35
|
+
.describe("Avatar engine. Default kling-avatar (byo-media: animate a face image). heygen-avatar / tavus-avatar are hosted (animate a provider-hosted avatar + native TTS)."),
|
|
24
36
|
inputImageUrl: zod_1.z
|
|
25
37
|
.string()
|
|
26
38
|
.url()
|
|
27
|
-
.
|
|
39
|
+
.optional()
|
|
40
|
+
.describe("byo-media: presenter face still the engine animates to lipsync the audio. Required for byo-media; omit for hosted."),
|
|
41
|
+
hostedAvatarId: zod_1.z
|
|
42
|
+
.string()
|
|
43
|
+
.optional()
|
|
44
|
+
.describe("hosted: provider-hosted avatar id (HeyGen) or replica_id (Tavus) to animate. Required for hosted engines."),
|
|
45
|
+
hostedVoiceId: zod_1.z
|
|
46
|
+
.string()
|
|
47
|
+
.optional()
|
|
48
|
+
.describe("hosted: provider-hosted voice id (HeyGen). Unused for Tavus (the replica owns its voice)."),
|
|
28
49
|
ttsText: zod_1.z
|
|
29
50
|
.string()
|
|
30
51
|
.min(1)
|
|
31
52
|
.optional()
|
|
32
|
-
.describe("VO line
|
|
53
|
+
.describe("VO line / script. byo-media: synthesized to audio (or use inputAudioUrl; if both set, inputAudioUrl wins). hosted: spoken natively by the engine (required)."),
|
|
33
54
|
inputAudioUrl: zod_1.z
|
|
34
55
|
.string()
|
|
35
56
|
.url()
|
|
36
57
|
.optional()
|
|
37
|
-
.describe("
|
|
58
|
+
.describe("byo-media only: pre-uploaded audio URL (mp3/wav). Skips internal TTS+upload."),
|
|
38
59
|
voiceId: zod_1.z.string().optional(),
|
|
39
60
|
languageCode: zod_1.z.string().optional(),
|
|
40
61
|
mode: zod_1.z.enum(["std", "pro"]).default("std"),
|
|
41
62
|
prompt: zod_1.z
|
|
42
63
|
.string()
|
|
43
64
|
.optional()
|
|
44
|
-
.describe("
|
|
65
|
+
.describe("byo-media only: optional motion/emotion hint passed through to the engine."),
|
|
45
66
|
})
|
|
46
67
|
.superRefine((v, ctx) => {
|
|
47
|
-
if (
|
|
48
|
-
|
|
49
|
-
|
|
50
|
-
|
|
51
|
-
|
|
68
|
+
if (v.model && (0, avatarProviderFactory_1.isHostedAvatarModel)(v.model)) {
|
|
69
|
+
if (!v.hostedAvatarId) {
|
|
70
|
+
ctx.addIssue({
|
|
71
|
+
code: zod_1.z.ZodIssueCode.custom,
|
|
72
|
+
message: "hostedAvatarId is required for hosted avatar engines.",
|
|
73
|
+
path: ["hostedAvatarId"],
|
|
74
|
+
});
|
|
75
|
+
}
|
|
76
|
+
if (!v.ttsText) {
|
|
77
|
+
ctx.addIssue({
|
|
78
|
+
code: zod_1.z.ZodIssueCode.custom,
|
|
79
|
+
message: "ttsText (the script) is required for hosted avatar engines.",
|
|
80
|
+
path: ["ttsText"],
|
|
81
|
+
});
|
|
82
|
+
}
|
|
83
|
+
}
|
|
84
|
+
else {
|
|
85
|
+
if (!v.inputImageUrl) {
|
|
86
|
+
ctx.addIssue({
|
|
87
|
+
code: zod_1.z.ZodIssueCode.custom,
|
|
88
|
+
message: "inputImageUrl is required for byo-media avatar engines.",
|
|
89
|
+
path: ["inputImageUrl"],
|
|
90
|
+
});
|
|
91
|
+
}
|
|
92
|
+
if (!v.ttsText && !v.inputAudioUrl) {
|
|
93
|
+
ctx.addIssue({
|
|
94
|
+
code: zod_1.z.ZodIssueCode.custom,
|
|
95
|
+
message: "Either ttsText or inputAudioUrl is required.",
|
|
96
|
+
});
|
|
97
|
+
}
|
|
52
98
|
}
|
|
53
99
|
});
|
|
54
100
|
const OutputSchema = zod_1.z.object({
|
|
55
101
|
status: zod_1.z.literal("triggered"),
|
|
56
102
|
taskId: zod_1.z.string(),
|
|
57
|
-
audioUrl: zod_1.z
|
|
103
|
+
audioUrl: zod_1.z
|
|
104
|
+
.string()
|
|
105
|
+
.url()
|
|
106
|
+
.optional()
|
|
107
|
+
.describe("byo-media: audio URL fed to the engine — host can reuse for caption sync. Absent for hosted engines (native TTS)."),
|
|
58
108
|
});
|
|
59
109
|
exports.generateAvatarVideoTool = {
|
|
60
110
|
name: "generate_avatar_video",
|
|
61
111
|
description: [
|
|
62
|
-
"Generate a talking-head avatar video
|
|
63
|
-
"
|
|
112
|
+
"Generate a talking-head avatar video that lipsyncs a voiceover. Routed",
|
|
113
|
+
"through the dedicated avatar provider seam. Two engine families: byo-media",
|
|
114
|
+
"(default kling-avatar) animates a presenter face still you supply; hosted",
|
|
115
|
+
"(heygen-avatar / tavus-avatar, via `model`) animates a provider-hosted",
|
|
116
|
+
"avatar and runs its own TTS from a script.",
|
|
64
117
|
"Use this for the `talking-head-avatar` scene strategy. Do NOT use it for",
|
|
65
118
|
"general motion shots — generate_video is cheaper and more flexible there.",
|
|
66
119
|
"Examples:",
|
|
67
120
|
' { inputImageUrl: "https://.../face.png", ttsText: "Hi, today we\'re testing the new serum." }',
|
|
68
121
|
' { inputImageUrl: "https://.../face.png", inputAudioUrl: "https://.../vo.mp3", mode: "pro" }',
|
|
122
|
+
' { model: "heygen-avatar", hostedAvatarId: "Daisy-inskirt-20220818", hostedVoiceId: "2d5b0e6cf36f460aa7fc47e3eee4ba54", ttsText: "Welcome back!" }',
|
|
69
123
|
].join(" "),
|
|
70
124
|
inputSchema: InputSchema,
|
|
71
125
|
outputSchema: OutputSchema,
|
|
72
126
|
idempotent: false,
|
|
73
127
|
estimateCost: async (input) => {
|
|
74
|
-
//
|
|
128
|
+
// Without a length we assume 6s (typical scene).
|
|
75
129
|
const approxSec = input.ttsText ? Math.max(2, Math.ceil(input.ttsText.length / 15)) : 6;
|
|
130
|
+
const modelKey = input.model ?? aiModels_1.EVideoGenModels.KLING_AVATAR;
|
|
131
|
+
if ((0, avatarProviderFactory_1.isHostedAvatarModel)(modelKey)) {
|
|
132
|
+
// Hosted engines bill per generated MINUTE (min 1 minute).
|
|
133
|
+
const perMinute = aiModels_1.aiModelConfigs[modelKey]?.cost?.perMinute ?? 0.5;
|
|
134
|
+
const billedMinutes = Math.max(1, Math.ceil(approxSec / 60));
|
|
135
|
+
return Math.ceil(perMinute * billedMinutes * 100); // rough credit conversion
|
|
136
|
+
}
|
|
137
|
+
// byo-media (Kling) bills per audio second.
|
|
76
138
|
const perSec = input.mode === "pro" ? 0.028 : 0.014;
|
|
77
|
-
return Math.ceil(approxSec * perSec * 100);
|
|
139
|
+
return Math.ceil(approxSec * perSec * 100);
|
|
78
140
|
},
|
|
79
141
|
handler: async (input, ctx) => {
|
|
142
|
+
const modelKey = input.model ?? aiModels_1.EVideoGenModels.KLING_AVATAR;
|
|
143
|
+
const renderMode = input.mode === "pro" ? types_1.EVideoMode.PROFESSIONAL : types_1.EVideoMode.STANDARD;
|
|
144
|
+
// ── Hosted engines (HeyGen/Tavus) ────────────────────────────────────────
|
|
145
|
+
// The engine does its own TTS + lipsync from the script, so there's no TTS
|
|
146
|
+
// synthesis / GCS upload here. Submit and hand the task id to the poller,
|
|
147
|
+
// which routes hosted models through the avatar seam.
|
|
148
|
+
if ((0, avatarProviderFactory_1.isHostedAvatarModel)(modelKey)) {
|
|
149
|
+
const limiter = (0, rateLimiter_1.getAiGenModelRateLimiter)(modelKey);
|
|
150
|
+
await limiter.waitUntilAvailable();
|
|
151
|
+
let heldSlot = true;
|
|
152
|
+
try {
|
|
153
|
+
const provider = (0, avatarProviderFactory_1.getAvatarVideoProvider)(modelKey);
|
|
154
|
+
const result = await provider.generate({
|
|
155
|
+
mode: "hosted",
|
|
156
|
+
hostedAvatarId: input.hostedAvatarId,
|
|
157
|
+
hostedVoiceId: input.hostedVoiceId ?? "",
|
|
158
|
+
script: input.ttsText,
|
|
159
|
+
locale: input.languageCode,
|
|
160
|
+
renderMode,
|
|
161
|
+
});
|
|
162
|
+
const taskId = result.providerTaskId?.taskId ??
|
|
163
|
+
result.providerTaskId?.id ??
|
|
164
|
+
(typeof result.providerTaskId === "string" ? result.providerTaskId : "unknown");
|
|
165
|
+
if (taskId === "unknown") {
|
|
166
|
+
limiter.releaseSlot();
|
|
167
|
+
heldSlot = false;
|
|
168
|
+
}
|
|
169
|
+
return { status: "triggered", taskId };
|
|
170
|
+
}
|
|
171
|
+
catch (err) {
|
|
172
|
+
if (heldSlot)
|
|
173
|
+
limiter.releaseSlot();
|
|
174
|
+
throw err;
|
|
175
|
+
}
|
|
176
|
+
}
|
|
177
|
+
// ── byo-media engines (Kling) ─────────────────────────────────────────────
|
|
80
178
|
let audioUrl = input.inputAudioUrl;
|
|
81
179
|
if (!audioUrl) {
|
|
82
180
|
const ttsLimiter = (0, rateLimiter_1.getTtsProviderRateLimiter)("elevenlabs");
|
|
@@ -100,29 +198,27 @@ exports.generateAvatarVideoTool = {
|
|
|
100
198
|
const [signedUrl] = await file.getSignedUrl({ action: "read", expires: "03-09-2491" });
|
|
101
199
|
audioUrl = signedUrl;
|
|
102
200
|
}
|
|
103
|
-
// Hold the
|
|
201
|
+
// Hold the byo-media model slot through return — host's TaskPoller releases
|
|
104
202
|
// when the task settles. Release here on submit error.
|
|
105
|
-
const limiter = (0, rateLimiter_1.getAiGenModelRateLimiter)(
|
|
203
|
+
const limiter = (0, rateLimiter_1.getAiGenModelRateLimiter)(modelKey);
|
|
106
204
|
await limiter.waitUntilAvailable();
|
|
107
205
|
let heldSlot = true;
|
|
108
206
|
try {
|
|
109
|
-
|
|
110
|
-
//
|
|
111
|
-
//
|
|
112
|
-
//
|
|
113
|
-
|
|
114
|
-
|
|
115
|
-
|
|
116
|
-
|
|
117
|
-
|
|
207
|
+
// byo-media avatar request: a face still + the audio we just resolved. The
|
|
208
|
+
// avatar provider owns endpoint/type dispatch internally (Kling routes on
|
|
209
|
+
// modelKey === "kling-avatar"), so no generic-video `type` is passed here.
|
|
210
|
+
// inputImageUrl presence is guaranteed by the byo-media superRefine branch.
|
|
211
|
+
const provider = (0, avatarProviderFactory_1.getAvatarVideoProvider)(modelKey);
|
|
212
|
+
const result = await provider.generate({
|
|
213
|
+
mode: "byo-media",
|
|
214
|
+
faceImageUrl: input.inputImageUrl,
|
|
215
|
+
audioUrl,
|
|
118
216
|
prompt: input.prompt,
|
|
119
|
-
|
|
120
|
-
inputAudioUrl: audioUrl,
|
|
121
|
-
mode: input.mode === "pro" ? types_1.EVideoMode.PROFESSIONAL : types_1.EVideoMode.STANDARD,
|
|
217
|
+
renderMode,
|
|
122
218
|
});
|
|
123
|
-
const taskId = result.
|
|
124
|
-
result.
|
|
125
|
-
(typeof result.
|
|
219
|
+
const taskId = result.providerTaskId?.taskId ??
|
|
220
|
+
result.providerTaskId?.id ??
|
|
221
|
+
(typeof result.providerTaskId === "string" ? result.providerTaskId : "unknown");
|
|
126
222
|
if (taskId === "unknown") {
|
|
127
223
|
limiter.releaseSlot();
|
|
128
224
|
heldSlot = false;
|
|
@@ -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,7 +1,7 @@
|
|
|
1
1
|
import { BaseAiGenProviderService } from "./providers/baseAiGenProvider.service";
|
|
2
2
|
import { EImageGenModelKey, EMusicGenModels, ESfxGenModels, ETextGenModels, EVideoGenModels, TAiGenModel } from "../../globals/aiModels";
|
|
3
3
|
type AiGenServiceConstructor = new () => BaseAiGenProviderService;
|
|
4
|
-
export declare const videoServicesMap: Record<EVideoGenModels, AiGenServiceConstructor
|
|
4
|
+
export declare const videoServicesMap: Partial<Record<EVideoGenModels, AiGenServiceConstructor>>;
|
|
5
5
|
/**
|
|
6
6
|
* Image-gen factory. Each provider service implements `generateImage()` (sync;
|
|
7
7
|
* returns a URL or base64 in a single API roundtrip — no BullMQ worker needed).
|
|
@@ -1 +1 @@
|
|
|
1
|
-
{"version":3,"file":"aiGenFactory.service.d.ts","sourceRoot":"","sources":["../../../src/services/aiGen/aiGenFactory.service.ts"],"names":[],"mappings":"AAAA,OAAO,EAAE,wBAAwB,EAAE,MAAM,uCAAuC,CAAC;AACjF,OAAO,EACL,iBAAiB,EAEjB,eAAe,EACf,aAAa,EACb,cAAc,EACd,eAAe,EACf,WAAW,EACZ,MAAM,wBAAwB,CAAC;AAchC,KAAK,uBAAuB,GAAG,UAAU,wBAAwB,CAAC;
|
|
1
|
+
{"version":3,"file":"aiGenFactory.service.d.ts","sourceRoot":"","sources":["../../../src/services/aiGen/aiGenFactory.service.ts"],"names":[],"mappings":"AAAA,OAAO,EAAE,wBAAwB,EAAE,MAAM,uCAAuC,CAAC;AACjF,OAAO,EACL,iBAAiB,EAEjB,eAAe,EACf,aAAa,EACb,cAAc,EACd,eAAe,EACf,WAAW,EACZ,MAAM,wBAAwB,CAAC;AAchC,KAAK,uBAAuB,GAAG,UAAU,wBAAwB,CAAC;AASlE,eAAO,MAAM,gBAAgB,EAAE,OAAO,CACpC,MAAM,CAAC,eAAe,EAAE,uBAAuB,CAAC,CA+DjD,CAAC;AAEF;;;;;;;;;;;GAWG;AACH,eAAO,MAAM,gBAAgB,EAAE,MAAM,CACnC,iBAAiB,EACjB,uBAAuB,CA2CxB,CAAC;AAEF;;;GAGG;AACH,eAAO,MAAM,gBAAgB,EAAE,MAAM,CACnC,eAAe,EACf,uBAAuB,CAMxB,CAAC;AAEF;;;GAGG;AACH,eAAO,MAAM,cAAc,EAAE,MAAM,CACjC,aAAa,EACb,uBAAuB,CAGxB,CAAC;AAEF,eAAO,MAAM,eAAe,EAAE,MAAM,CAAC,cAAc,EAAE,uBAAuB,CAsBzE,CAAC;AAEJ,wBAAgB,uBAAuB,CACrC,QAAQ,EAAE,WAAW,GACpB,wBAAwB,CAmB1B"}
|
|
@@ -14,6 +14,13 @@ const pixverse_service_1 = require("./providers/pixverse/pixverse.service");
|
|
|
14
14
|
const bytedance_service_1 = require("./providers/bytedance/bytedance.service");
|
|
15
15
|
const elevenlabs_service_1 = require("./providers/elevenlabs/elevenlabs.service");
|
|
16
16
|
const anthropic_service_1 = require("./providers/anthropic/anthropic.service");
|
|
17
|
+
// Partial, intentionally: hosted avatar engines (heygen-avatar, tavus-avatar)
|
|
18
|
+
// are NOT generic video models — they're handled by the dedicated avatar seam
|
|
19
|
+
// (getAvatarVideoProvider / IAvatarVideoProvider). getAiGenProviderService gates
|
|
20
|
+
// on `modelKey in videoServicesMap` and throws for anything absent, so these
|
|
21
|
+
// keys correctly resolve through the avatar factory instead. (kling-avatar stays
|
|
22
|
+
// here because KlingService still backs both its generic-credit path and the
|
|
23
|
+
// KlingAvatarProvider adapter.)
|
|
17
24
|
exports.videoServicesMap = {
|
|
18
25
|
// --- Pixverse ---
|
|
19
26
|
"pixverse-v6": pixverse_service_1.PixVerseService,
|
|
@@ -157,8 +164,9 @@ exports.textServicesMap = {
|
|
|
157
164
|
// "openai-gpt-3.5-turbo": OpenaiService,
|
|
158
165
|
};
|
|
159
166
|
function getAiGenProviderService(modelKey) {
|
|
160
|
-
|
|
161
|
-
|
|
167
|
+
const VideoService = exports.videoServicesMap[modelKey];
|
|
168
|
+
if (VideoService) {
|
|
169
|
+
return new VideoService();
|
|
162
170
|
}
|
|
163
171
|
if (modelKey in exports.imageServicesMap) {
|
|
164
172
|
return new exports.imageServicesMap[modelKey]();
|
|
@@ -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":"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
|