@golpoai/sdk 0.1.10 → 1.0.0
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/dist/index.cjs +396 -44
- package/dist/index.d.cts +52 -20
- package/dist/index.d.ts +52 -20
- package/dist/index.js +397 -45
- package/package.json +1 -1
package/dist/index.cjs
CHANGED
|
@@ -57,33 +57,55 @@ var Golpo = class {
|
|
|
57
57
|
async createPodcastJob(prompt, opts = {}) {
|
|
58
58
|
const {
|
|
59
59
|
uploads,
|
|
60
|
-
concurrency = 8,
|
|
61
|
-
addMusic = false,
|
|
62
60
|
voiceInstructions,
|
|
63
|
-
personality1,
|
|
64
|
-
personality2,
|
|
65
|
-
doResearch = false,
|
|
66
|
-
ttsModel = "accurate",
|
|
67
61
|
language,
|
|
68
|
-
style = "
|
|
62
|
+
style = "solo-female-3",
|
|
69
63
|
bgMusic,
|
|
70
|
-
|
|
71
|
-
|
|
64
|
+
// Extended params
|
|
65
|
+
newScript,
|
|
66
|
+
timing = 1
|
|
67
|
+
// timing in minutes – default 1 if not provided
|
|
72
68
|
} = opts;
|
|
69
|
+
const concurrency = 8;
|
|
73
70
|
const fields = [
|
|
74
71
|
["prompt", prompt],
|
|
75
|
-
["
|
|
76
|
-
["do_research", String(doResearch)],
|
|
77
|
-
["tts_model", ttsModel],
|
|
78
|
-
["style", style],
|
|
79
|
-
["output_volume", String(outputVolume)]
|
|
72
|
+
["style", style]
|
|
80
73
|
];
|
|
81
74
|
if (voiceInstructions) fields.push(["voice_instructions", voiceInstructions]);
|
|
82
|
-
if (personality1) fields.push(["personality_1", personality1]);
|
|
83
|
-
if (personality2) fields.push(["personality_2", personality2]);
|
|
84
75
|
if (language) fields.push(["language", language]);
|
|
85
76
|
if (bgMusic) fields.push(["bg_music", bgMusic]);
|
|
86
|
-
if (
|
|
77
|
+
if (newScript != null) fields.push(["new_script", newScript]);
|
|
78
|
+
fields.push(["timing", String(timing)]);
|
|
79
|
+
fields.push(["video_type", "null"]);
|
|
80
|
+
const hasUploads = (() => {
|
|
81
|
+
if (!uploads) return false;
|
|
82
|
+
if (typeof uploads === "string") return uploads.trim().length > 0;
|
|
83
|
+
const iterator = uploads[Symbol.iterator]();
|
|
84
|
+
const first = iterator.next();
|
|
85
|
+
return !first.done;
|
|
86
|
+
})();
|
|
87
|
+
const request_payload = {
|
|
88
|
+
prompt,
|
|
89
|
+
video_voice: style,
|
|
90
|
+
video_duration: String(timing),
|
|
91
|
+
video_type: "null",
|
|
92
|
+
language,
|
|
93
|
+
voice_instructions: voiceInstructions,
|
|
94
|
+
new_script: newScript,
|
|
95
|
+
bg_music: bgMusic,
|
|
96
|
+
attached_documents: uploads,
|
|
97
|
+
source: "node-sdk"
|
|
98
|
+
};
|
|
99
|
+
try {
|
|
100
|
+
const result = await this.createVideoRecord({
|
|
101
|
+
topic: prompt,
|
|
102
|
+
requestPayload: request_payload
|
|
103
|
+
});
|
|
104
|
+
if (result && result.videoId) {
|
|
105
|
+
fields.push(["video_id", result.videoId]);
|
|
106
|
+
}
|
|
107
|
+
} catch (error) {
|
|
108
|
+
}
|
|
87
109
|
const formData = await this.createFormData(fields, uploads, concurrency);
|
|
88
110
|
const { data } = await this.http.post("/generate", formData, {
|
|
89
111
|
timeout: 24e4,
|
|
@@ -120,50 +142,201 @@ var Golpo = class {
|
|
|
120
142
|
const {
|
|
121
143
|
uploads,
|
|
122
144
|
pollIntervalMs = 2e3,
|
|
123
|
-
concurrency = 8,
|
|
124
145
|
voiceInstructions,
|
|
125
|
-
personality1,
|
|
126
|
-
doResearch = false,
|
|
127
|
-
ttsModel = "accurate",
|
|
128
146
|
language,
|
|
129
|
-
style = "solo-female",
|
|
147
|
+
style = "solo-female-3",
|
|
130
148
|
bgMusic = "engaging",
|
|
131
|
-
bgVolume = 1.4,
|
|
132
|
-
outputVolume = 1,
|
|
133
149
|
videoType = "long",
|
|
134
|
-
includeWatermark =
|
|
150
|
+
includeWatermark = false,
|
|
135
151
|
logo,
|
|
136
|
-
timing =
|
|
152
|
+
timing = 1,
|
|
153
|
+
useColor = true,
|
|
154
|
+
// newly added backend parameters
|
|
155
|
+
newScript,
|
|
156
|
+
logoPlacement,
|
|
157
|
+
videoInstructions,
|
|
158
|
+
useLineart2Style,
|
|
159
|
+
audioClip,
|
|
160
|
+
isPublic,
|
|
161
|
+
useAsIs,
|
|
162
|
+
skipAnimation,
|
|
163
|
+
userImages,
|
|
164
|
+
userImagesDescriptions,
|
|
165
|
+
userVideos,
|
|
166
|
+
userVideosDescription,
|
|
167
|
+
userAudioInVideo,
|
|
168
|
+
use2Style,
|
|
169
|
+
// specificAnimation, // not yet supported by the Golpo 2.0 pipeline
|
|
170
|
+
imageStyle,
|
|
171
|
+
inputImages,
|
|
172
|
+
penStyle,
|
|
173
|
+
showPencilCursor,
|
|
174
|
+
pacing,
|
|
175
|
+
justReturnScript
|
|
137
176
|
} = opts;
|
|
177
|
+
const concurrency = 8;
|
|
178
|
+
const request_payload = {
|
|
179
|
+
prompt,
|
|
180
|
+
video_voice: style,
|
|
181
|
+
video_duration: String(timing),
|
|
182
|
+
video_type: videoType,
|
|
183
|
+
language,
|
|
184
|
+
voice_instructions: voiceInstructions,
|
|
185
|
+
bg_music: bgMusic,
|
|
186
|
+
use_color: useColor,
|
|
187
|
+
direct_script: newScript,
|
|
188
|
+
script_mode: newScript ? true : false,
|
|
189
|
+
logo_placement: logoPlacement,
|
|
190
|
+
video_instructions: videoInstructions,
|
|
191
|
+
use_lineart_2_style: useLineart2Style,
|
|
192
|
+
audio_clip: audioClip,
|
|
193
|
+
is_public: isPublic,
|
|
194
|
+
use_as_is: useAsIs,
|
|
195
|
+
skip_animation: skipAnimation,
|
|
196
|
+
user_images: userImages,
|
|
197
|
+
user_images_descriptions: userImagesDescriptions,
|
|
198
|
+
user_videos: userVideos,
|
|
199
|
+
user_videos_description: userVideosDescription,
|
|
200
|
+
user_audio_in_video: userAudioInVideo,
|
|
201
|
+
use_2_0_style: use2Style,
|
|
202
|
+
// specific_animation: specificAnimation, // not yet supported by the pipeline
|
|
203
|
+
image_style: imageStyle,
|
|
204
|
+
input_images: inputImages,
|
|
205
|
+
pen_style: penStyle,
|
|
206
|
+
show_pencil_cursor: penStyle ? true : showPencilCursor ?? false,
|
|
207
|
+
pacing,
|
|
208
|
+
own_narration_video_mode: null,
|
|
209
|
+
own_narration_pip_position: null,
|
|
210
|
+
just_return_script: justReturnScript,
|
|
211
|
+
attached_documents: uploads,
|
|
212
|
+
source: "node-sdk"
|
|
213
|
+
};
|
|
138
214
|
const fields = [
|
|
139
215
|
["prompt", prompt],
|
|
140
|
-
["do_research", String(doResearch)],
|
|
141
|
-
["tts_model", ttsModel],
|
|
142
|
-
["bg_volume", String(bgVolume)],
|
|
143
|
-
["output_volume", String(outputVolume)],
|
|
144
216
|
["style", style],
|
|
145
217
|
["video_type", videoType],
|
|
146
218
|
["include_watermark", String(includeWatermark)],
|
|
147
|
-
["timing", timing]
|
|
219
|
+
["timing", String(timing)],
|
|
220
|
+
["use_color", String(useColor)]
|
|
148
221
|
];
|
|
149
222
|
if (language) fields.push(["language", language]);
|
|
150
223
|
if (voiceInstructions) fields.push(["voice_instructions", voiceInstructions]);
|
|
151
|
-
if (personality1) fields.push(["personality_1", personality1]);
|
|
152
224
|
if (bgMusic) fields.push(["bg_music", bgMusic]);
|
|
225
|
+
try {
|
|
226
|
+
const result = await this.createVideoRecord({
|
|
227
|
+
topic: prompt,
|
|
228
|
+
requestPayload: request_payload
|
|
229
|
+
});
|
|
230
|
+
if (result && result.videoId) {
|
|
231
|
+
fields.push(["video_id", result.videoId]);
|
|
232
|
+
}
|
|
233
|
+
} catch (error) {
|
|
234
|
+
}
|
|
235
|
+
if (newScript) fields.push(["new_script", newScript]);
|
|
236
|
+
if (logoPlacement) fields.push(["logo_placement", logoPlacement]);
|
|
237
|
+
if (videoInstructions) fields.push(["video_instructions", videoInstructions]);
|
|
238
|
+
if (useLineart2Style) fields.push(["use_lineart_2_style", useLineart2Style]);
|
|
239
|
+
if (isPublic != null) fields.push(["is_public", String(isPublic)]);
|
|
240
|
+
if (useAsIs) {
|
|
241
|
+
const value = Array.isArray(useAsIs) ? JSON.stringify(useAsIs) : useAsIs;
|
|
242
|
+
fields.push(["use_as_is", value]);
|
|
243
|
+
}
|
|
244
|
+
if (skipAnimation) {
|
|
245
|
+
const value = Array.isArray(skipAnimation) ? JSON.stringify(skipAnimation) : skipAnimation;
|
|
246
|
+
fields.push(["skip_animation", value]);
|
|
247
|
+
}
|
|
248
|
+
if (userImagesDescriptions) {
|
|
249
|
+
const value = Array.isArray(userImagesDescriptions) ? JSON.stringify(userImagesDescriptions) : userImagesDescriptions;
|
|
250
|
+
fields.push(["user_images_descriptions", value]);
|
|
251
|
+
}
|
|
252
|
+
if (userVideosDescription) {
|
|
253
|
+
const value = Array.isArray(userVideosDescription) ? JSON.stringify(userVideosDescription) : userVideosDescription;
|
|
254
|
+
fields.push(["user_videos_description", value]);
|
|
255
|
+
}
|
|
256
|
+
if (userAudioInVideo) {
|
|
257
|
+
fields.push(["user_audio_in_video", userAudioInVideo]);
|
|
258
|
+
} else if (userVideos) {
|
|
259
|
+
fields.push(["user_audio_in_video", "[]"]);
|
|
260
|
+
}
|
|
261
|
+
if (use2Style != null) fields.push(["use_2_0_style", String(use2Style)]);
|
|
262
|
+
if (imageStyle) fields.push(["image_style", imageStyle]);
|
|
263
|
+
if (penStyle) fields.push(["pen_style", penStyle]);
|
|
264
|
+
const effectiveShowPencilCursor = penStyle ? true : showPencilCursor ?? false;
|
|
265
|
+
fields.push(["show_pencil_cursor", String(effectiveShowPencilCursor)]);
|
|
266
|
+
if (pacing) fields.push(["pacing", pacing]);
|
|
267
|
+
if (justReturnScript != null) fields.push(["just_return_script", String(justReturnScript)]);
|
|
153
268
|
if (logo) {
|
|
154
269
|
fields.push(["logo_path", logo]);
|
|
155
270
|
}
|
|
156
|
-
const formData = await this.createFormData(
|
|
271
|
+
const formData = await this.createFormData(
|
|
272
|
+
fields,
|
|
273
|
+
uploads,
|
|
274
|
+
concurrency,
|
|
275
|
+
logo,
|
|
276
|
+
audioClip,
|
|
277
|
+
userImages,
|
|
278
|
+
userVideos,
|
|
279
|
+
inputImages
|
|
280
|
+
);
|
|
157
281
|
const { data } = await this.http.post("/generate", formData, {
|
|
158
282
|
timeout: 24e4,
|
|
159
283
|
headers: formData.getHeaders()
|
|
160
284
|
});
|
|
285
|
+
if (justReturnScript) {
|
|
286
|
+
const r2 = await this.pollUntilComplete(
|
|
287
|
+
data.job_id,
|
|
288
|
+
pollIntervalMs,
|
|
289
|
+
"/script-status"
|
|
290
|
+
);
|
|
291
|
+
return { url: "", script: r2.podcast_script, videoId: data.job_id };
|
|
292
|
+
}
|
|
161
293
|
const r = await this.pollUntilComplete(
|
|
162
294
|
data.job_id,
|
|
163
295
|
pollIntervalMs
|
|
164
296
|
);
|
|
165
297
|
return { url: r.podcast_url, script: r.podcast_script, videoId: data.job_id };
|
|
166
298
|
}
|
|
299
|
+
/**
|
|
300
|
+
* Private helper: Get user email from API key
|
|
301
|
+
* Backend resolves: api_key -> user_id -> user_credits.email
|
|
302
|
+
*/
|
|
303
|
+
async getUserEmailFromApiKey() {
|
|
304
|
+
try {
|
|
305
|
+
const { data } = await this.http.get("/api/resolve-email-from-api-key");
|
|
306
|
+
return data.email || null;
|
|
307
|
+
} catch (error) {
|
|
308
|
+
return null;
|
|
309
|
+
}
|
|
310
|
+
}
|
|
311
|
+
async createVideoRecord(opts) {
|
|
312
|
+
const {
|
|
313
|
+
topic,
|
|
314
|
+
context = "",
|
|
315
|
+
scenes = 6,
|
|
316
|
+
pipeline = "color",
|
|
317
|
+
shareDocuments = false,
|
|
318
|
+
logoPlacement = null,
|
|
319
|
+
audioClipUrl = null,
|
|
320
|
+
requestPayload
|
|
321
|
+
} = opts;
|
|
322
|
+
const emailFromApiKey = await this.getUserEmailFromApiKey();
|
|
323
|
+
if (!emailFromApiKey) {
|
|
324
|
+
throw new Error("Email not found");
|
|
325
|
+
}
|
|
326
|
+
const body = {
|
|
327
|
+
topic,
|
|
328
|
+
context,
|
|
329
|
+
scenes,
|
|
330
|
+
pipeline,
|
|
331
|
+
user_email: emailFromApiKey,
|
|
332
|
+
share_documents: shareDocuments,
|
|
333
|
+
logo_placement: logoPlacement,
|
|
334
|
+
audio_clip_url: audioClipUrl,
|
|
335
|
+
request_payload: requestPayload ? JSON.stringify(requestPayload) : null
|
|
336
|
+
};
|
|
337
|
+
const { data } = await this.http.post("/api/create-video-record", body);
|
|
338
|
+
return { videoId: data.video_id };
|
|
339
|
+
}
|
|
167
340
|
/**
|
|
168
341
|
* Edit specific frames of a video and regenerate the video.
|
|
169
342
|
* This method polls until completion and returns the final edited video URL.
|
|
@@ -387,14 +560,19 @@ var Golpo = class {
|
|
|
387
560
|
/* ------------------------------------------------------------ *
|
|
388
561
|
* INTERNAL HELPERS
|
|
389
562
|
* ------------------------------------------------------------ */
|
|
390
|
-
async pollUntilComplete(jobId, interval) {
|
|
563
|
+
async pollUntilComplete(jobId, interval, statusPath = "/status") {
|
|
391
564
|
while (true) {
|
|
565
|
+
let data;
|
|
392
566
|
try {
|
|
393
|
-
const
|
|
394
|
-
|
|
567
|
+
const res = await this.http.get(`${statusPath}/${jobId}`);
|
|
568
|
+
data = res.data;
|
|
395
569
|
} catch (error) {
|
|
396
570
|
console.warn(`Status check failed for job ${jobId}, retrying in ${interval}ms:`, error instanceof Error ? error.message : error);
|
|
397
571
|
}
|
|
572
|
+
if (data?.status === "completed") return data;
|
|
573
|
+
if (data?.status === "failed") {
|
|
574
|
+
throw new Error(`Job ${jobId} failed: ${data.error || data.message || "unknown error"}`);
|
|
575
|
+
}
|
|
398
576
|
await new Promise((r) => setTimeout(r, interval));
|
|
399
577
|
}
|
|
400
578
|
}
|
|
@@ -444,7 +622,50 @@ var Golpo = class {
|
|
|
444
622
|
attempts++;
|
|
445
623
|
}
|
|
446
624
|
}
|
|
447
|
-
|
|
625
|
+
/**
|
|
626
|
+
* Upload a file using /upload-url endpoint and return the final URL.
|
|
627
|
+
* Replicates the _presign_and_upload_one logic from the backend.
|
|
628
|
+
*/
|
|
629
|
+
async uploadFileAndGetUrl(filePath) {
|
|
630
|
+
const absPath = (0, import_path.resolve)(filePath.trim().replace(/^<|>$/g, ""));
|
|
631
|
+
try {
|
|
632
|
+
await import_fs.promises.access(absPath);
|
|
633
|
+
} catch (error) {
|
|
634
|
+
throw new Error(`File not found: ${absPath}. Original path: ${filePath}`);
|
|
635
|
+
}
|
|
636
|
+
const fileName = (0, import_path.basename)(absPath);
|
|
637
|
+
const fileData = await import_fs.promises.readFile(absPath);
|
|
638
|
+
const mimeType = (0, import_mime_types.lookup)(absPath) || "application/octet-stream";
|
|
639
|
+
const { data: presignInfo } = await this.http.post(
|
|
640
|
+
"/upload-url",
|
|
641
|
+
{ filename: fileName },
|
|
642
|
+
{ headers: { "Content-Type": "application/x-www-form-urlencoded" } }
|
|
643
|
+
);
|
|
644
|
+
let finalUrl;
|
|
645
|
+
if (presignInfo.url && presignInfo.fields) {
|
|
646
|
+
const uploadFormData = new import_form_data.default();
|
|
647
|
+
Object.entries(presignInfo.fields).forEach(([key, value]) => {
|
|
648
|
+
uploadFormData.append(key, value);
|
|
649
|
+
});
|
|
650
|
+
uploadFormData.append("file", fileData, { filename: fileName });
|
|
651
|
+
await import_axios.default.post(presignInfo.url, uploadFormData, {
|
|
652
|
+
headers: uploadFormData.getHeaders(),
|
|
653
|
+
timeout: 3e4
|
|
654
|
+
});
|
|
655
|
+
finalUrl = presignInfo.final_url || presignInfo.url.split("?")[0];
|
|
656
|
+
} else if (presignInfo.upload_url || presignInfo.url) {
|
|
657
|
+
const uploadUrl = presignInfo.upload_url || presignInfo.url;
|
|
658
|
+
await import_axios.default.put(uploadUrl, fileData, {
|
|
659
|
+
headers: { "Content-Type": mimeType },
|
|
660
|
+
timeout: 3e4
|
|
661
|
+
});
|
|
662
|
+
finalUrl = presignInfo.final_url || uploadUrl.split("?")[0];
|
|
663
|
+
} else {
|
|
664
|
+
throw new Error(`Unrecognized presign response: ${JSON.stringify(presignInfo)}`);
|
|
665
|
+
}
|
|
666
|
+
return finalUrl;
|
|
667
|
+
}
|
|
668
|
+
async createFormData(fields, uploads, concurrency, logo, audioClip, userImages, userVideos, inputImages) {
|
|
448
669
|
const formData = new import_form_data.default();
|
|
449
670
|
fields.forEach(([key, value]) => {
|
|
450
671
|
if (key !== "logo_path") {
|
|
@@ -453,16 +674,147 @@ var Golpo = class {
|
|
|
453
674
|
});
|
|
454
675
|
if (logo) {
|
|
455
676
|
const logoPath = logo.trim().replace(/^<|>$/g, "");
|
|
677
|
+
let logoUrl;
|
|
456
678
|
if (URL_RE.test(logoPath)) {
|
|
457
|
-
|
|
679
|
+
logoUrl = logoPath;
|
|
680
|
+
} else {
|
|
681
|
+
logoUrl = await this.uploadFileAndGetUrl(logoPath);
|
|
458
682
|
}
|
|
459
|
-
|
|
460
|
-
|
|
461
|
-
|
|
462
|
-
|
|
463
|
-
|
|
464
|
-
|
|
683
|
+
formData.append("logo", logoUrl);
|
|
684
|
+
}
|
|
685
|
+
if (audioClip) {
|
|
686
|
+
const audioList = Array.isArray(audioClip) ? audioClip : [audioClip];
|
|
687
|
+
const audioLocalFiles = [];
|
|
688
|
+
const audioUrls = [];
|
|
689
|
+
audioList.forEach((item) => {
|
|
690
|
+
const path = String(item).trim().replace(/^<|>$/g, "");
|
|
691
|
+
if (URL_RE.test(path)) {
|
|
692
|
+
audioUrls.push(path);
|
|
693
|
+
} else {
|
|
694
|
+
audioLocalFiles.push(path);
|
|
695
|
+
}
|
|
465
696
|
});
|
|
697
|
+
if (audioLocalFiles.length > 0) {
|
|
698
|
+
const limit = (0, import_p_limit.default)(concurrency);
|
|
699
|
+
const uploadedUrls = await Promise.all(
|
|
700
|
+
audioLocalFiles.map(
|
|
701
|
+
(filePath) => limit(() => this.uploadFileAndGetUrl(filePath))
|
|
702
|
+
)
|
|
703
|
+
);
|
|
704
|
+
audioUrls.push(...uploadedUrls);
|
|
705
|
+
}
|
|
706
|
+
if (audioUrls.length > 0) {
|
|
707
|
+
formData.append("audio_clip", audioUrls[0]);
|
|
708
|
+
}
|
|
709
|
+
}
|
|
710
|
+
if (userImages) {
|
|
711
|
+
const imagesList = Array.isArray(userImages) ? userImages : [userImages];
|
|
712
|
+
const imageLocalFiles = [];
|
|
713
|
+
const imageUrls = [];
|
|
714
|
+
console.log("[SDK] Processing userImages:", imagesList);
|
|
715
|
+
imagesList.forEach((item) => {
|
|
716
|
+
const path = String(item).trim().replace(/^<|>$/g, "");
|
|
717
|
+
if (URL_RE.test(path)) {
|
|
718
|
+
console.log("[SDK] userImages item is URL:", path);
|
|
719
|
+
imageUrls.push(path);
|
|
720
|
+
} else {
|
|
721
|
+
console.log("[SDK] userImages item is local file:", path);
|
|
722
|
+
imageLocalFiles.push(path);
|
|
723
|
+
}
|
|
724
|
+
});
|
|
725
|
+
if (imageLocalFiles.length > 0) {
|
|
726
|
+
console.log("[SDK] Uploading", imageLocalFiles.length, "image file(s)");
|
|
727
|
+
const limit = (0, import_p_limit.default)(concurrency);
|
|
728
|
+
try {
|
|
729
|
+
const uploadedUrls = await Promise.all(
|
|
730
|
+
imageLocalFiles.map(
|
|
731
|
+
(filePath) => limit(async () => {
|
|
732
|
+
try {
|
|
733
|
+
console.log("[SDK] Uploading image file:", filePath);
|
|
734
|
+
const url = await this.uploadFileAndGetUrl(filePath);
|
|
735
|
+
console.log("[SDK] Image uploaded, got URL:", url);
|
|
736
|
+
return url;
|
|
737
|
+
} catch (error) {
|
|
738
|
+
console.error(`[SDK] Failed to upload image file ${filePath}:`, error);
|
|
739
|
+
throw error;
|
|
740
|
+
}
|
|
741
|
+
})
|
|
742
|
+
)
|
|
743
|
+
);
|
|
744
|
+
imageUrls.push(...uploadedUrls);
|
|
745
|
+
console.log("[SDK] All images uploaded, total URLs:", imageUrls.length);
|
|
746
|
+
} catch (error) {
|
|
747
|
+
console.error("[SDK] Error uploading image files:", error);
|
|
748
|
+
throw error;
|
|
749
|
+
}
|
|
750
|
+
}
|
|
751
|
+
if (imageUrls.length > 0) {
|
|
752
|
+
const jsonArray = JSON.stringify(imageUrls);
|
|
753
|
+
console.log("[SDK] Sending user_images as JSON:", jsonArray);
|
|
754
|
+
formData.append("user_images", jsonArray);
|
|
755
|
+
} else {
|
|
756
|
+
console.log("[SDK] No image URLs to send");
|
|
757
|
+
}
|
|
758
|
+
}
|
|
759
|
+
if (userVideos) {
|
|
760
|
+
const videosList = Array.isArray(userVideos) ? userVideos : [userVideos];
|
|
761
|
+
const videoLocalFiles = [];
|
|
762
|
+
const videoUrls = [];
|
|
763
|
+
videosList.forEach((item) => {
|
|
764
|
+
const path = String(item).trim().replace(/^<|>$/g, "");
|
|
765
|
+
if (URL_RE.test(path)) {
|
|
766
|
+
videoUrls.push(path);
|
|
767
|
+
} else {
|
|
768
|
+
videoLocalFiles.push(path);
|
|
769
|
+
}
|
|
770
|
+
});
|
|
771
|
+
if (videoLocalFiles.length > 0) {
|
|
772
|
+
const limit = (0, import_p_limit.default)(concurrency);
|
|
773
|
+
try {
|
|
774
|
+
const uploadedUrls = await Promise.all(
|
|
775
|
+
videoLocalFiles.map(
|
|
776
|
+
(filePath) => limit(async () => {
|
|
777
|
+
try {
|
|
778
|
+
return await this.uploadFileAndGetUrl(filePath);
|
|
779
|
+
} catch (error) {
|
|
780
|
+
console.error(`Failed to upload video file ${filePath}:`, error);
|
|
781
|
+
throw error;
|
|
782
|
+
}
|
|
783
|
+
})
|
|
784
|
+
)
|
|
785
|
+
);
|
|
786
|
+
videoUrls.push(...uploadedUrls);
|
|
787
|
+
} catch (error) {
|
|
788
|
+
console.error("Error uploading video files:", error);
|
|
789
|
+
throw error;
|
|
790
|
+
}
|
|
791
|
+
}
|
|
792
|
+
if (videoUrls.length > 0) {
|
|
793
|
+
formData.append("user_videos", JSON.stringify(videoUrls));
|
|
794
|
+
}
|
|
795
|
+
}
|
|
796
|
+
if (inputImages) {
|
|
797
|
+
const imagesList = Array.isArray(inputImages) ? inputImages : [inputImages];
|
|
798
|
+
const localFiles = [];
|
|
799
|
+
const imageUrls = [];
|
|
800
|
+
imagesList.forEach((item) => {
|
|
801
|
+
const path = String(item).trim().replace(/^<|>$/g, "");
|
|
802
|
+
if (URL_RE.test(path)) {
|
|
803
|
+
imageUrls.push(path);
|
|
804
|
+
} else {
|
|
805
|
+
localFiles.push(path);
|
|
806
|
+
}
|
|
807
|
+
});
|
|
808
|
+
if (localFiles.length > 0) {
|
|
809
|
+
const limit = (0, import_p_limit.default)(concurrency);
|
|
810
|
+
const uploadedUrls = await Promise.all(
|
|
811
|
+
localFiles.map((filePath) => limit(() => this.uploadFileAndGetUrl(filePath)))
|
|
812
|
+
);
|
|
813
|
+
imageUrls.push(...uploadedUrls);
|
|
814
|
+
}
|
|
815
|
+
if (imageUrls.length > 0) {
|
|
816
|
+
formData.append("input_images", JSON.stringify(imageUrls));
|
|
817
|
+
}
|
|
466
818
|
}
|
|
467
819
|
if (uploads) {
|
|
468
820
|
const list = typeof uploads === "string" ? [uploads] : [...uploads];
|
package/dist/index.d.cts
CHANGED
|
@@ -1,36 +1,57 @@
|
|
|
1
|
+
interface CreateVideoRecordOptions {
|
|
2
|
+
topic: string;
|
|
3
|
+
userEmail?: string;
|
|
4
|
+
context?: string;
|
|
5
|
+
scenes?: number;
|
|
6
|
+
pipeline?: string;
|
|
7
|
+
shareDocuments?: boolean;
|
|
8
|
+
logoPlacement?: string;
|
|
9
|
+
audioClipUrl?: string;
|
|
10
|
+
requestPayload?: any;
|
|
11
|
+
videoId?: string;
|
|
12
|
+
}
|
|
1
13
|
interface CreatePodcastOptions {
|
|
2
14
|
uploads?: string | Iterable<string>;
|
|
3
|
-
addMusic?: boolean;
|
|
4
15
|
voiceInstructions?: string;
|
|
5
|
-
personality1?: string;
|
|
6
|
-
personality2?: string;
|
|
7
|
-
doResearch?: boolean;
|
|
8
|
-
ttsModel?: 'accurate' | 'fast';
|
|
9
16
|
language?: string;
|
|
10
|
-
style?: '
|
|
11
|
-
bgMusic?: 'jazz' | 'lofi' | 'dramatic' | null;
|
|
12
|
-
outputVolume?: number;
|
|
17
|
+
style?: 'solo-male-4' | 'solo-female-4' | 'solo-male-3' | 'solo-female-3';
|
|
18
|
+
bgMusic?: 'jazz' | 'lofi' | 'dramatic' | 'whimsical' | 'engaging' | 'hyper' | 'inspirational' | 'documentary' | null;
|
|
13
19
|
pollIntervalMs?: number;
|
|
14
|
-
|
|
15
|
-
|
|
20
|
+
newScript?: string;
|
|
21
|
+
timing?: 0.25 | 0.5 | 1 | 2 | 4 | 8 | 10;
|
|
16
22
|
}
|
|
17
23
|
interface CreateVideoOptions {
|
|
18
24
|
uploads?: string | Iterable<string>;
|
|
19
25
|
voiceInstructions?: string;
|
|
20
|
-
personality1?: string;
|
|
21
|
-
doResearch?: boolean;
|
|
22
|
-
ttsModel?: 'accurate' | 'fast';
|
|
23
26
|
language?: string;
|
|
24
|
-
style?: 'solo-male' | 'solo-female';
|
|
25
|
-
bgMusic?: '
|
|
26
|
-
bgVolume?: number;
|
|
27
|
-
outputVolume?: number;
|
|
27
|
+
style?: 'solo-male-3' | 'solo-female-3' | 'solo-male-4' | 'solo-female-4';
|
|
28
|
+
bgMusic?: 'jazz' | 'lofi' | 'dramatic' | 'whimsical' | 'engaging' | 'hyper' | 'inspirational' | 'documentary' | null;
|
|
28
29
|
videoType?: 'short' | 'long';
|
|
29
30
|
includeWatermark?: boolean;
|
|
30
31
|
logo?: string;
|
|
31
|
-
timing?:
|
|
32
|
+
timing?: 0.25 | 0.5 | 1 | 2 | 4 | 8 | 10;
|
|
32
33
|
pollIntervalMs?: number;
|
|
33
|
-
|
|
34
|
+
useColor?: boolean;
|
|
35
|
+
newScript?: string;
|
|
36
|
+
logoPlacement?: string;
|
|
37
|
+
videoInstructions?: string;
|
|
38
|
+
useLineart2Style?: string;
|
|
39
|
+
audioClip?: string | string[];
|
|
40
|
+
isPublic?: boolean;
|
|
41
|
+
useAsIs?: string | boolean[];
|
|
42
|
+
skipAnimation?: string | boolean[];
|
|
43
|
+
userImages?: string | string[];
|
|
44
|
+
userImagesDescriptions?: string | string[];
|
|
45
|
+
userVideos?: string | string[];
|
|
46
|
+
userVideosDescription?: string | string[];
|
|
47
|
+
userAudioInVideo?: string;
|
|
48
|
+
use2Style?: boolean;
|
|
49
|
+
imageStyle?: 'neon' | 'whiteboard' | 'modern_minimal' | 'playful' | 'technical' | 'editorial';
|
|
50
|
+
inputImages?: string | string[];
|
|
51
|
+
penStyle?: 'stylus' | 'marker' | 'pen';
|
|
52
|
+
showPencilCursor?: boolean;
|
|
53
|
+
pacing?: 'normal' | 'fast';
|
|
54
|
+
justReturnScript?: boolean;
|
|
34
55
|
}
|
|
35
56
|
interface EditVideoOptions {
|
|
36
57
|
frameIds: string[];
|
|
@@ -83,6 +104,12 @@ declare class Golpo {
|
|
|
83
104
|
}>;
|
|
84
105
|
createPodcast(prompt: string, opts?: CreatePodcastOptions): Promise<GenerationResult>;
|
|
85
106
|
createVideo(prompt: string, opts?: CreateVideoOptions): Promise<GenerationResult>;
|
|
107
|
+
/**
|
|
108
|
+
* Private helper: Get user email from API key
|
|
109
|
+
* Backend resolves: api_key -> user_id -> user_credits.email
|
|
110
|
+
*/
|
|
111
|
+
private getUserEmailFromApiKey;
|
|
112
|
+
private createVideoRecord;
|
|
86
113
|
/**
|
|
87
114
|
* Edit specific frames of a video and regenerate the video.
|
|
88
115
|
* This method polls until completion and returns the final edited video URL.
|
|
@@ -132,7 +159,12 @@ declare class Golpo {
|
|
|
132
159
|
combineVideos(opts: CombineVideosOptions): Promise<CombinedVideoResult>;
|
|
133
160
|
private pollUntilComplete;
|
|
134
161
|
private pollEditStatus;
|
|
162
|
+
/**
|
|
163
|
+
* Upload a file using /upload-url endpoint and return the final URL.
|
|
164
|
+
* Replicates the _presign_and_upload_one logic from the backend.
|
|
165
|
+
*/
|
|
166
|
+
private uploadFileAndGetUrl;
|
|
135
167
|
private createFormData;
|
|
136
168
|
}
|
|
137
169
|
|
|
138
|
-
export { type CombineVideosOptions, type CombinedVideoResult, type CreatePodcastOptions, type CreateVideoOptions, type EditVideoOptions, type GenerationResult, Golpo, type VideoEditResult, Golpo as default };
|
|
170
|
+
export { type CombineVideosOptions, type CombinedVideoResult, type CreatePodcastOptions, type CreateVideoOptions, type CreateVideoRecordOptions, type EditVideoOptions, type GenerationResult, Golpo, type VideoEditResult, Golpo as default };
|
package/dist/index.d.ts
CHANGED
|
@@ -1,36 +1,57 @@
|
|
|
1
|
+
interface CreateVideoRecordOptions {
|
|
2
|
+
topic: string;
|
|
3
|
+
userEmail?: string;
|
|
4
|
+
context?: string;
|
|
5
|
+
scenes?: number;
|
|
6
|
+
pipeline?: string;
|
|
7
|
+
shareDocuments?: boolean;
|
|
8
|
+
logoPlacement?: string;
|
|
9
|
+
audioClipUrl?: string;
|
|
10
|
+
requestPayload?: any;
|
|
11
|
+
videoId?: string;
|
|
12
|
+
}
|
|
1
13
|
interface CreatePodcastOptions {
|
|
2
14
|
uploads?: string | Iterable<string>;
|
|
3
|
-
addMusic?: boolean;
|
|
4
15
|
voiceInstructions?: string;
|
|
5
|
-
personality1?: string;
|
|
6
|
-
personality2?: string;
|
|
7
|
-
doResearch?: boolean;
|
|
8
|
-
ttsModel?: 'accurate' | 'fast';
|
|
9
16
|
language?: string;
|
|
10
|
-
style?: '
|
|
11
|
-
bgMusic?: 'jazz' | 'lofi' | 'dramatic' | null;
|
|
12
|
-
outputVolume?: number;
|
|
17
|
+
style?: 'solo-male-4' | 'solo-female-4' | 'solo-male-3' | 'solo-female-3';
|
|
18
|
+
bgMusic?: 'jazz' | 'lofi' | 'dramatic' | 'whimsical' | 'engaging' | 'hyper' | 'inspirational' | 'documentary' | null;
|
|
13
19
|
pollIntervalMs?: number;
|
|
14
|
-
|
|
15
|
-
|
|
20
|
+
newScript?: string;
|
|
21
|
+
timing?: 0.25 | 0.5 | 1 | 2 | 4 | 8 | 10;
|
|
16
22
|
}
|
|
17
23
|
interface CreateVideoOptions {
|
|
18
24
|
uploads?: string | Iterable<string>;
|
|
19
25
|
voiceInstructions?: string;
|
|
20
|
-
personality1?: string;
|
|
21
|
-
doResearch?: boolean;
|
|
22
|
-
ttsModel?: 'accurate' | 'fast';
|
|
23
26
|
language?: string;
|
|
24
|
-
style?: 'solo-male' | 'solo-female';
|
|
25
|
-
bgMusic?: '
|
|
26
|
-
bgVolume?: number;
|
|
27
|
-
outputVolume?: number;
|
|
27
|
+
style?: 'solo-male-3' | 'solo-female-3' | 'solo-male-4' | 'solo-female-4';
|
|
28
|
+
bgMusic?: 'jazz' | 'lofi' | 'dramatic' | 'whimsical' | 'engaging' | 'hyper' | 'inspirational' | 'documentary' | null;
|
|
28
29
|
videoType?: 'short' | 'long';
|
|
29
30
|
includeWatermark?: boolean;
|
|
30
31
|
logo?: string;
|
|
31
|
-
timing?:
|
|
32
|
+
timing?: 0.25 | 0.5 | 1 | 2 | 4 | 8 | 10;
|
|
32
33
|
pollIntervalMs?: number;
|
|
33
|
-
|
|
34
|
+
useColor?: boolean;
|
|
35
|
+
newScript?: string;
|
|
36
|
+
logoPlacement?: string;
|
|
37
|
+
videoInstructions?: string;
|
|
38
|
+
useLineart2Style?: string;
|
|
39
|
+
audioClip?: string | string[];
|
|
40
|
+
isPublic?: boolean;
|
|
41
|
+
useAsIs?: string | boolean[];
|
|
42
|
+
skipAnimation?: string | boolean[];
|
|
43
|
+
userImages?: string | string[];
|
|
44
|
+
userImagesDescriptions?: string | string[];
|
|
45
|
+
userVideos?: string | string[];
|
|
46
|
+
userVideosDescription?: string | string[];
|
|
47
|
+
userAudioInVideo?: string;
|
|
48
|
+
use2Style?: boolean;
|
|
49
|
+
imageStyle?: 'neon' | 'whiteboard' | 'modern_minimal' | 'playful' | 'technical' | 'editorial';
|
|
50
|
+
inputImages?: string | string[];
|
|
51
|
+
penStyle?: 'stylus' | 'marker' | 'pen';
|
|
52
|
+
showPencilCursor?: boolean;
|
|
53
|
+
pacing?: 'normal' | 'fast';
|
|
54
|
+
justReturnScript?: boolean;
|
|
34
55
|
}
|
|
35
56
|
interface EditVideoOptions {
|
|
36
57
|
frameIds: string[];
|
|
@@ -83,6 +104,12 @@ declare class Golpo {
|
|
|
83
104
|
}>;
|
|
84
105
|
createPodcast(prompt: string, opts?: CreatePodcastOptions): Promise<GenerationResult>;
|
|
85
106
|
createVideo(prompt: string, opts?: CreateVideoOptions): Promise<GenerationResult>;
|
|
107
|
+
/**
|
|
108
|
+
* Private helper: Get user email from API key
|
|
109
|
+
* Backend resolves: api_key -> user_id -> user_credits.email
|
|
110
|
+
*/
|
|
111
|
+
private getUserEmailFromApiKey;
|
|
112
|
+
private createVideoRecord;
|
|
86
113
|
/**
|
|
87
114
|
* Edit specific frames of a video and regenerate the video.
|
|
88
115
|
* This method polls until completion and returns the final edited video URL.
|
|
@@ -132,7 +159,12 @@ declare class Golpo {
|
|
|
132
159
|
combineVideos(opts: CombineVideosOptions): Promise<CombinedVideoResult>;
|
|
133
160
|
private pollUntilComplete;
|
|
134
161
|
private pollEditStatus;
|
|
162
|
+
/**
|
|
163
|
+
* Upload a file using /upload-url endpoint and return the final URL.
|
|
164
|
+
* Replicates the _presign_and_upload_one logic from the backend.
|
|
165
|
+
*/
|
|
166
|
+
private uploadFileAndGetUrl;
|
|
135
167
|
private createFormData;
|
|
136
168
|
}
|
|
137
169
|
|
|
138
|
-
export { type CombineVideosOptions, type CombinedVideoResult, type CreatePodcastOptions, type CreateVideoOptions, type EditVideoOptions, type GenerationResult, Golpo, type VideoEditResult, Golpo as default };
|
|
170
|
+
export { type CombineVideosOptions, type CombinedVideoResult, type CreatePodcastOptions, type CreateVideoOptions, type CreateVideoRecordOptions, type EditVideoOptions, type GenerationResult, Golpo, type VideoEditResult, Golpo as default };
|
package/dist/index.js
CHANGED
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
// src/Golpo.ts
|
|
2
2
|
import axios from "axios";
|
|
3
|
-
import { createReadStream } from "fs";
|
|
3
|
+
import { createReadStream, promises as fs } from "fs";
|
|
4
4
|
import { basename, resolve } from "path";
|
|
5
5
|
import { lookup as mimeLookup } from "mime-types";
|
|
6
6
|
import pLimit from "p-limit";
|
|
@@ -20,33 +20,55 @@ var Golpo = class {
|
|
|
20
20
|
async createPodcastJob(prompt, opts = {}) {
|
|
21
21
|
const {
|
|
22
22
|
uploads,
|
|
23
|
-
concurrency = 8,
|
|
24
|
-
addMusic = false,
|
|
25
23
|
voiceInstructions,
|
|
26
|
-
personality1,
|
|
27
|
-
personality2,
|
|
28
|
-
doResearch = false,
|
|
29
|
-
ttsModel = "accurate",
|
|
30
24
|
language,
|
|
31
|
-
style = "
|
|
25
|
+
style = "solo-female-3",
|
|
32
26
|
bgMusic,
|
|
33
|
-
|
|
34
|
-
|
|
27
|
+
// Extended params
|
|
28
|
+
newScript,
|
|
29
|
+
timing = 1
|
|
30
|
+
// timing in minutes – default 1 if not provided
|
|
35
31
|
} = opts;
|
|
32
|
+
const concurrency = 8;
|
|
36
33
|
const fields = [
|
|
37
34
|
["prompt", prompt],
|
|
38
|
-
["
|
|
39
|
-
["do_research", String(doResearch)],
|
|
40
|
-
["tts_model", ttsModel],
|
|
41
|
-
["style", style],
|
|
42
|
-
["output_volume", String(outputVolume)]
|
|
35
|
+
["style", style]
|
|
43
36
|
];
|
|
44
37
|
if (voiceInstructions) fields.push(["voice_instructions", voiceInstructions]);
|
|
45
|
-
if (personality1) fields.push(["personality_1", personality1]);
|
|
46
|
-
if (personality2) fields.push(["personality_2", personality2]);
|
|
47
38
|
if (language) fields.push(["language", language]);
|
|
48
39
|
if (bgMusic) fields.push(["bg_music", bgMusic]);
|
|
49
|
-
if (
|
|
40
|
+
if (newScript != null) fields.push(["new_script", newScript]);
|
|
41
|
+
fields.push(["timing", String(timing)]);
|
|
42
|
+
fields.push(["video_type", "null"]);
|
|
43
|
+
const hasUploads = (() => {
|
|
44
|
+
if (!uploads) return false;
|
|
45
|
+
if (typeof uploads === "string") return uploads.trim().length > 0;
|
|
46
|
+
const iterator = uploads[Symbol.iterator]();
|
|
47
|
+
const first = iterator.next();
|
|
48
|
+
return !first.done;
|
|
49
|
+
})();
|
|
50
|
+
const request_payload = {
|
|
51
|
+
prompt,
|
|
52
|
+
video_voice: style,
|
|
53
|
+
video_duration: String(timing),
|
|
54
|
+
video_type: "null",
|
|
55
|
+
language,
|
|
56
|
+
voice_instructions: voiceInstructions,
|
|
57
|
+
new_script: newScript,
|
|
58
|
+
bg_music: bgMusic,
|
|
59
|
+
attached_documents: uploads,
|
|
60
|
+
source: "node-sdk"
|
|
61
|
+
};
|
|
62
|
+
try {
|
|
63
|
+
const result = await this.createVideoRecord({
|
|
64
|
+
topic: prompt,
|
|
65
|
+
requestPayload: request_payload
|
|
66
|
+
});
|
|
67
|
+
if (result && result.videoId) {
|
|
68
|
+
fields.push(["video_id", result.videoId]);
|
|
69
|
+
}
|
|
70
|
+
} catch (error) {
|
|
71
|
+
}
|
|
50
72
|
const formData = await this.createFormData(fields, uploads, concurrency);
|
|
51
73
|
const { data } = await this.http.post("/generate", formData, {
|
|
52
74
|
timeout: 24e4,
|
|
@@ -83,50 +105,201 @@ var Golpo = class {
|
|
|
83
105
|
const {
|
|
84
106
|
uploads,
|
|
85
107
|
pollIntervalMs = 2e3,
|
|
86
|
-
concurrency = 8,
|
|
87
108
|
voiceInstructions,
|
|
88
|
-
personality1,
|
|
89
|
-
doResearch = false,
|
|
90
|
-
ttsModel = "accurate",
|
|
91
109
|
language,
|
|
92
|
-
style = "solo-female",
|
|
110
|
+
style = "solo-female-3",
|
|
93
111
|
bgMusic = "engaging",
|
|
94
|
-
bgVolume = 1.4,
|
|
95
|
-
outputVolume = 1,
|
|
96
112
|
videoType = "long",
|
|
97
|
-
includeWatermark =
|
|
113
|
+
includeWatermark = false,
|
|
98
114
|
logo,
|
|
99
|
-
timing =
|
|
115
|
+
timing = 1,
|
|
116
|
+
useColor = true,
|
|
117
|
+
// newly added backend parameters
|
|
118
|
+
newScript,
|
|
119
|
+
logoPlacement,
|
|
120
|
+
videoInstructions,
|
|
121
|
+
useLineart2Style,
|
|
122
|
+
audioClip,
|
|
123
|
+
isPublic,
|
|
124
|
+
useAsIs,
|
|
125
|
+
skipAnimation,
|
|
126
|
+
userImages,
|
|
127
|
+
userImagesDescriptions,
|
|
128
|
+
userVideos,
|
|
129
|
+
userVideosDescription,
|
|
130
|
+
userAudioInVideo,
|
|
131
|
+
use2Style,
|
|
132
|
+
// specificAnimation, // not yet supported by the Golpo 2.0 pipeline
|
|
133
|
+
imageStyle,
|
|
134
|
+
inputImages,
|
|
135
|
+
penStyle,
|
|
136
|
+
showPencilCursor,
|
|
137
|
+
pacing,
|
|
138
|
+
justReturnScript
|
|
100
139
|
} = opts;
|
|
140
|
+
const concurrency = 8;
|
|
141
|
+
const request_payload = {
|
|
142
|
+
prompt,
|
|
143
|
+
video_voice: style,
|
|
144
|
+
video_duration: String(timing),
|
|
145
|
+
video_type: videoType,
|
|
146
|
+
language,
|
|
147
|
+
voice_instructions: voiceInstructions,
|
|
148
|
+
bg_music: bgMusic,
|
|
149
|
+
use_color: useColor,
|
|
150
|
+
direct_script: newScript,
|
|
151
|
+
script_mode: newScript ? true : false,
|
|
152
|
+
logo_placement: logoPlacement,
|
|
153
|
+
video_instructions: videoInstructions,
|
|
154
|
+
use_lineart_2_style: useLineart2Style,
|
|
155
|
+
audio_clip: audioClip,
|
|
156
|
+
is_public: isPublic,
|
|
157
|
+
use_as_is: useAsIs,
|
|
158
|
+
skip_animation: skipAnimation,
|
|
159
|
+
user_images: userImages,
|
|
160
|
+
user_images_descriptions: userImagesDescriptions,
|
|
161
|
+
user_videos: userVideos,
|
|
162
|
+
user_videos_description: userVideosDescription,
|
|
163
|
+
user_audio_in_video: userAudioInVideo,
|
|
164
|
+
use_2_0_style: use2Style,
|
|
165
|
+
// specific_animation: specificAnimation, // not yet supported by the pipeline
|
|
166
|
+
image_style: imageStyle,
|
|
167
|
+
input_images: inputImages,
|
|
168
|
+
pen_style: penStyle,
|
|
169
|
+
show_pencil_cursor: penStyle ? true : showPencilCursor ?? false,
|
|
170
|
+
pacing,
|
|
171
|
+
own_narration_video_mode: null,
|
|
172
|
+
own_narration_pip_position: null,
|
|
173
|
+
just_return_script: justReturnScript,
|
|
174
|
+
attached_documents: uploads,
|
|
175
|
+
source: "node-sdk"
|
|
176
|
+
};
|
|
101
177
|
const fields = [
|
|
102
178
|
["prompt", prompt],
|
|
103
|
-
["do_research", String(doResearch)],
|
|
104
|
-
["tts_model", ttsModel],
|
|
105
|
-
["bg_volume", String(bgVolume)],
|
|
106
|
-
["output_volume", String(outputVolume)],
|
|
107
179
|
["style", style],
|
|
108
180
|
["video_type", videoType],
|
|
109
181
|
["include_watermark", String(includeWatermark)],
|
|
110
|
-
["timing", timing]
|
|
182
|
+
["timing", String(timing)],
|
|
183
|
+
["use_color", String(useColor)]
|
|
111
184
|
];
|
|
112
185
|
if (language) fields.push(["language", language]);
|
|
113
186
|
if (voiceInstructions) fields.push(["voice_instructions", voiceInstructions]);
|
|
114
|
-
if (personality1) fields.push(["personality_1", personality1]);
|
|
115
187
|
if (bgMusic) fields.push(["bg_music", bgMusic]);
|
|
188
|
+
try {
|
|
189
|
+
const result = await this.createVideoRecord({
|
|
190
|
+
topic: prompt,
|
|
191
|
+
requestPayload: request_payload
|
|
192
|
+
});
|
|
193
|
+
if (result && result.videoId) {
|
|
194
|
+
fields.push(["video_id", result.videoId]);
|
|
195
|
+
}
|
|
196
|
+
} catch (error) {
|
|
197
|
+
}
|
|
198
|
+
if (newScript) fields.push(["new_script", newScript]);
|
|
199
|
+
if (logoPlacement) fields.push(["logo_placement", logoPlacement]);
|
|
200
|
+
if (videoInstructions) fields.push(["video_instructions", videoInstructions]);
|
|
201
|
+
if (useLineart2Style) fields.push(["use_lineart_2_style", useLineart2Style]);
|
|
202
|
+
if (isPublic != null) fields.push(["is_public", String(isPublic)]);
|
|
203
|
+
if (useAsIs) {
|
|
204
|
+
const value = Array.isArray(useAsIs) ? JSON.stringify(useAsIs) : useAsIs;
|
|
205
|
+
fields.push(["use_as_is", value]);
|
|
206
|
+
}
|
|
207
|
+
if (skipAnimation) {
|
|
208
|
+
const value = Array.isArray(skipAnimation) ? JSON.stringify(skipAnimation) : skipAnimation;
|
|
209
|
+
fields.push(["skip_animation", value]);
|
|
210
|
+
}
|
|
211
|
+
if (userImagesDescriptions) {
|
|
212
|
+
const value = Array.isArray(userImagesDescriptions) ? JSON.stringify(userImagesDescriptions) : userImagesDescriptions;
|
|
213
|
+
fields.push(["user_images_descriptions", value]);
|
|
214
|
+
}
|
|
215
|
+
if (userVideosDescription) {
|
|
216
|
+
const value = Array.isArray(userVideosDescription) ? JSON.stringify(userVideosDescription) : userVideosDescription;
|
|
217
|
+
fields.push(["user_videos_description", value]);
|
|
218
|
+
}
|
|
219
|
+
if (userAudioInVideo) {
|
|
220
|
+
fields.push(["user_audio_in_video", userAudioInVideo]);
|
|
221
|
+
} else if (userVideos) {
|
|
222
|
+
fields.push(["user_audio_in_video", "[]"]);
|
|
223
|
+
}
|
|
224
|
+
if (use2Style != null) fields.push(["use_2_0_style", String(use2Style)]);
|
|
225
|
+
if (imageStyle) fields.push(["image_style", imageStyle]);
|
|
226
|
+
if (penStyle) fields.push(["pen_style", penStyle]);
|
|
227
|
+
const effectiveShowPencilCursor = penStyle ? true : showPencilCursor ?? false;
|
|
228
|
+
fields.push(["show_pencil_cursor", String(effectiveShowPencilCursor)]);
|
|
229
|
+
if (pacing) fields.push(["pacing", pacing]);
|
|
230
|
+
if (justReturnScript != null) fields.push(["just_return_script", String(justReturnScript)]);
|
|
116
231
|
if (logo) {
|
|
117
232
|
fields.push(["logo_path", logo]);
|
|
118
233
|
}
|
|
119
|
-
const formData = await this.createFormData(
|
|
234
|
+
const formData = await this.createFormData(
|
|
235
|
+
fields,
|
|
236
|
+
uploads,
|
|
237
|
+
concurrency,
|
|
238
|
+
logo,
|
|
239
|
+
audioClip,
|
|
240
|
+
userImages,
|
|
241
|
+
userVideos,
|
|
242
|
+
inputImages
|
|
243
|
+
);
|
|
120
244
|
const { data } = await this.http.post("/generate", formData, {
|
|
121
245
|
timeout: 24e4,
|
|
122
246
|
headers: formData.getHeaders()
|
|
123
247
|
});
|
|
248
|
+
if (justReturnScript) {
|
|
249
|
+
const r2 = await this.pollUntilComplete(
|
|
250
|
+
data.job_id,
|
|
251
|
+
pollIntervalMs,
|
|
252
|
+
"/script-status"
|
|
253
|
+
);
|
|
254
|
+
return { url: "", script: r2.podcast_script, videoId: data.job_id };
|
|
255
|
+
}
|
|
124
256
|
const r = await this.pollUntilComplete(
|
|
125
257
|
data.job_id,
|
|
126
258
|
pollIntervalMs
|
|
127
259
|
);
|
|
128
260
|
return { url: r.podcast_url, script: r.podcast_script, videoId: data.job_id };
|
|
129
261
|
}
|
|
262
|
+
/**
|
|
263
|
+
* Private helper: Get user email from API key
|
|
264
|
+
* Backend resolves: api_key -> user_id -> user_credits.email
|
|
265
|
+
*/
|
|
266
|
+
async getUserEmailFromApiKey() {
|
|
267
|
+
try {
|
|
268
|
+
const { data } = await this.http.get("/api/resolve-email-from-api-key");
|
|
269
|
+
return data.email || null;
|
|
270
|
+
} catch (error) {
|
|
271
|
+
return null;
|
|
272
|
+
}
|
|
273
|
+
}
|
|
274
|
+
async createVideoRecord(opts) {
|
|
275
|
+
const {
|
|
276
|
+
topic,
|
|
277
|
+
context = "",
|
|
278
|
+
scenes = 6,
|
|
279
|
+
pipeline = "color",
|
|
280
|
+
shareDocuments = false,
|
|
281
|
+
logoPlacement = null,
|
|
282
|
+
audioClipUrl = null,
|
|
283
|
+
requestPayload
|
|
284
|
+
} = opts;
|
|
285
|
+
const emailFromApiKey = await this.getUserEmailFromApiKey();
|
|
286
|
+
if (!emailFromApiKey) {
|
|
287
|
+
throw new Error("Email not found");
|
|
288
|
+
}
|
|
289
|
+
const body = {
|
|
290
|
+
topic,
|
|
291
|
+
context,
|
|
292
|
+
scenes,
|
|
293
|
+
pipeline,
|
|
294
|
+
user_email: emailFromApiKey,
|
|
295
|
+
share_documents: shareDocuments,
|
|
296
|
+
logo_placement: logoPlacement,
|
|
297
|
+
audio_clip_url: audioClipUrl,
|
|
298
|
+
request_payload: requestPayload ? JSON.stringify(requestPayload) : null
|
|
299
|
+
};
|
|
300
|
+
const { data } = await this.http.post("/api/create-video-record", body);
|
|
301
|
+
return { videoId: data.video_id };
|
|
302
|
+
}
|
|
130
303
|
/**
|
|
131
304
|
* Edit specific frames of a video and regenerate the video.
|
|
132
305
|
* This method polls until completion and returns the final edited video URL.
|
|
@@ -350,14 +523,19 @@ var Golpo = class {
|
|
|
350
523
|
/* ------------------------------------------------------------ *
|
|
351
524
|
* INTERNAL HELPERS
|
|
352
525
|
* ------------------------------------------------------------ */
|
|
353
|
-
async pollUntilComplete(jobId, interval) {
|
|
526
|
+
async pollUntilComplete(jobId, interval, statusPath = "/status") {
|
|
354
527
|
while (true) {
|
|
528
|
+
let data;
|
|
355
529
|
try {
|
|
356
|
-
const
|
|
357
|
-
|
|
530
|
+
const res = await this.http.get(`${statusPath}/${jobId}`);
|
|
531
|
+
data = res.data;
|
|
358
532
|
} catch (error) {
|
|
359
533
|
console.warn(`Status check failed for job ${jobId}, retrying in ${interval}ms:`, error instanceof Error ? error.message : error);
|
|
360
534
|
}
|
|
535
|
+
if (data?.status === "completed") return data;
|
|
536
|
+
if (data?.status === "failed") {
|
|
537
|
+
throw new Error(`Job ${jobId} failed: ${data.error || data.message || "unknown error"}`);
|
|
538
|
+
}
|
|
361
539
|
await new Promise((r) => setTimeout(r, interval));
|
|
362
540
|
}
|
|
363
541
|
}
|
|
@@ -407,7 +585,50 @@ var Golpo = class {
|
|
|
407
585
|
attempts++;
|
|
408
586
|
}
|
|
409
587
|
}
|
|
410
|
-
|
|
588
|
+
/**
|
|
589
|
+
* Upload a file using /upload-url endpoint and return the final URL.
|
|
590
|
+
* Replicates the _presign_and_upload_one logic from the backend.
|
|
591
|
+
*/
|
|
592
|
+
async uploadFileAndGetUrl(filePath) {
|
|
593
|
+
const absPath = resolve(filePath.trim().replace(/^<|>$/g, ""));
|
|
594
|
+
try {
|
|
595
|
+
await fs.access(absPath);
|
|
596
|
+
} catch (error) {
|
|
597
|
+
throw new Error(`File not found: ${absPath}. Original path: ${filePath}`);
|
|
598
|
+
}
|
|
599
|
+
const fileName = basename(absPath);
|
|
600
|
+
const fileData = await fs.readFile(absPath);
|
|
601
|
+
const mimeType = mimeLookup(absPath) || "application/octet-stream";
|
|
602
|
+
const { data: presignInfo } = await this.http.post(
|
|
603
|
+
"/upload-url",
|
|
604
|
+
{ filename: fileName },
|
|
605
|
+
{ headers: { "Content-Type": "application/x-www-form-urlencoded" } }
|
|
606
|
+
);
|
|
607
|
+
let finalUrl;
|
|
608
|
+
if (presignInfo.url && presignInfo.fields) {
|
|
609
|
+
const uploadFormData = new FormData();
|
|
610
|
+
Object.entries(presignInfo.fields).forEach(([key, value]) => {
|
|
611
|
+
uploadFormData.append(key, value);
|
|
612
|
+
});
|
|
613
|
+
uploadFormData.append("file", fileData, { filename: fileName });
|
|
614
|
+
await axios.post(presignInfo.url, uploadFormData, {
|
|
615
|
+
headers: uploadFormData.getHeaders(),
|
|
616
|
+
timeout: 3e4
|
|
617
|
+
});
|
|
618
|
+
finalUrl = presignInfo.final_url || presignInfo.url.split("?")[0];
|
|
619
|
+
} else if (presignInfo.upload_url || presignInfo.url) {
|
|
620
|
+
const uploadUrl = presignInfo.upload_url || presignInfo.url;
|
|
621
|
+
await axios.put(uploadUrl, fileData, {
|
|
622
|
+
headers: { "Content-Type": mimeType },
|
|
623
|
+
timeout: 3e4
|
|
624
|
+
});
|
|
625
|
+
finalUrl = presignInfo.final_url || uploadUrl.split("?")[0];
|
|
626
|
+
} else {
|
|
627
|
+
throw new Error(`Unrecognized presign response: ${JSON.stringify(presignInfo)}`);
|
|
628
|
+
}
|
|
629
|
+
return finalUrl;
|
|
630
|
+
}
|
|
631
|
+
async createFormData(fields, uploads, concurrency, logo, audioClip, userImages, userVideos, inputImages) {
|
|
411
632
|
const formData = new FormData();
|
|
412
633
|
fields.forEach(([key, value]) => {
|
|
413
634
|
if (key !== "logo_path") {
|
|
@@ -416,16 +637,147 @@ var Golpo = class {
|
|
|
416
637
|
});
|
|
417
638
|
if (logo) {
|
|
418
639
|
const logoPath = logo.trim().replace(/^<|>$/g, "");
|
|
640
|
+
let logoUrl;
|
|
419
641
|
if (URL_RE.test(logoPath)) {
|
|
420
|
-
|
|
642
|
+
logoUrl = logoPath;
|
|
643
|
+
} else {
|
|
644
|
+
logoUrl = await this.uploadFileAndGetUrl(logoPath);
|
|
421
645
|
}
|
|
422
|
-
|
|
423
|
-
|
|
424
|
-
|
|
425
|
-
|
|
426
|
-
|
|
427
|
-
|
|
646
|
+
formData.append("logo", logoUrl);
|
|
647
|
+
}
|
|
648
|
+
if (audioClip) {
|
|
649
|
+
const audioList = Array.isArray(audioClip) ? audioClip : [audioClip];
|
|
650
|
+
const audioLocalFiles = [];
|
|
651
|
+
const audioUrls = [];
|
|
652
|
+
audioList.forEach((item) => {
|
|
653
|
+
const path = String(item).trim().replace(/^<|>$/g, "");
|
|
654
|
+
if (URL_RE.test(path)) {
|
|
655
|
+
audioUrls.push(path);
|
|
656
|
+
} else {
|
|
657
|
+
audioLocalFiles.push(path);
|
|
658
|
+
}
|
|
428
659
|
});
|
|
660
|
+
if (audioLocalFiles.length > 0) {
|
|
661
|
+
const limit = pLimit(concurrency);
|
|
662
|
+
const uploadedUrls = await Promise.all(
|
|
663
|
+
audioLocalFiles.map(
|
|
664
|
+
(filePath) => limit(() => this.uploadFileAndGetUrl(filePath))
|
|
665
|
+
)
|
|
666
|
+
);
|
|
667
|
+
audioUrls.push(...uploadedUrls);
|
|
668
|
+
}
|
|
669
|
+
if (audioUrls.length > 0) {
|
|
670
|
+
formData.append("audio_clip", audioUrls[0]);
|
|
671
|
+
}
|
|
672
|
+
}
|
|
673
|
+
if (userImages) {
|
|
674
|
+
const imagesList = Array.isArray(userImages) ? userImages : [userImages];
|
|
675
|
+
const imageLocalFiles = [];
|
|
676
|
+
const imageUrls = [];
|
|
677
|
+
console.log("[SDK] Processing userImages:", imagesList);
|
|
678
|
+
imagesList.forEach((item) => {
|
|
679
|
+
const path = String(item).trim().replace(/^<|>$/g, "");
|
|
680
|
+
if (URL_RE.test(path)) {
|
|
681
|
+
console.log("[SDK] userImages item is URL:", path);
|
|
682
|
+
imageUrls.push(path);
|
|
683
|
+
} else {
|
|
684
|
+
console.log("[SDK] userImages item is local file:", path);
|
|
685
|
+
imageLocalFiles.push(path);
|
|
686
|
+
}
|
|
687
|
+
});
|
|
688
|
+
if (imageLocalFiles.length > 0) {
|
|
689
|
+
console.log("[SDK] Uploading", imageLocalFiles.length, "image file(s)");
|
|
690
|
+
const limit = pLimit(concurrency);
|
|
691
|
+
try {
|
|
692
|
+
const uploadedUrls = await Promise.all(
|
|
693
|
+
imageLocalFiles.map(
|
|
694
|
+
(filePath) => limit(async () => {
|
|
695
|
+
try {
|
|
696
|
+
console.log("[SDK] Uploading image file:", filePath);
|
|
697
|
+
const url = await this.uploadFileAndGetUrl(filePath);
|
|
698
|
+
console.log("[SDK] Image uploaded, got URL:", url);
|
|
699
|
+
return url;
|
|
700
|
+
} catch (error) {
|
|
701
|
+
console.error(`[SDK] Failed to upload image file ${filePath}:`, error);
|
|
702
|
+
throw error;
|
|
703
|
+
}
|
|
704
|
+
})
|
|
705
|
+
)
|
|
706
|
+
);
|
|
707
|
+
imageUrls.push(...uploadedUrls);
|
|
708
|
+
console.log("[SDK] All images uploaded, total URLs:", imageUrls.length);
|
|
709
|
+
} catch (error) {
|
|
710
|
+
console.error("[SDK] Error uploading image files:", error);
|
|
711
|
+
throw error;
|
|
712
|
+
}
|
|
713
|
+
}
|
|
714
|
+
if (imageUrls.length > 0) {
|
|
715
|
+
const jsonArray = JSON.stringify(imageUrls);
|
|
716
|
+
console.log("[SDK] Sending user_images as JSON:", jsonArray);
|
|
717
|
+
formData.append("user_images", jsonArray);
|
|
718
|
+
} else {
|
|
719
|
+
console.log("[SDK] No image URLs to send");
|
|
720
|
+
}
|
|
721
|
+
}
|
|
722
|
+
if (userVideos) {
|
|
723
|
+
const videosList = Array.isArray(userVideos) ? userVideos : [userVideos];
|
|
724
|
+
const videoLocalFiles = [];
|
|
725
|
+
const videoUrls = [];
|
|
726
|
+
videosList.forEach((item) => {
|
|
727
|
+
const path = String(item).trim().replace(/^<|>$/g, "");
|
|
728
|
+
if (URL_RE.test(path)) {
|
|
729
|
+
videoUrls.push(path);
|
|
730
|
+
} else {
|
|
731
|
+
videoLocalFiles.push(path);
|
|
732
|
+
}
|
|
733
|
+
});
|
|
734
|
+
if (videoLocalFiles.length > 0) {
|
|
735
|
+
const limit = pLimit(concurrency);
|
|
736
|
+
try {
|
|
737
|
+
const uploadedUrls = await Promise.all(
|
|
738
|
+
videoLocalFiles.map(
|
|
739
|
+
(filePath) => limit(async () => {
|
|
740
|
+
try {
|
|
741
|
+
return await this.uploadFileAndGetUrl(filePath);
|
|
742
|
+
} catch (error) {
|
|
743
|
+
console.error(`Failed to upload video file ${filePath}:`, error);
|
|
744
|
+
throw error;
|
|
745
|
+
}
|
|
746
|
+
})
|
|
747
|
+
)
|
|
748
|
+
);
|
|
749
|
+
videoUrls.push(...uploadedUrls);
|
|
750
|
+
} catch (error) {
|
|
751
|
+
console.error("Error uploading video files:", error);
|
|
752
|
+
throw error;
|
|
753
|
+
}
|
|
754
|
+
}
|
|
755
|
+
if (videoUrls.length > 0) {
|
|
756
|
+
formData.append("user_videos", JSON.stringify(videoUrls));
|
|
757
|
+
}
|
|
758
|
+
}
|
|
759
|
+
if (inputImages) {
|
|
760
|
+
const imagesList = Array.isArray(inputImages) ? inputImages : [inputImages];
|
|
761
|
+
const localFiles = [];
|
|
762
|
+
const imageUrls = [];
|
|
763
|
+
imagesList.forEach((item) => {
|
|
764
|
+
const path = String(item).trim().replace(/^<|>$/g, "");
|
|
765
|
+
if (URL_RE.test(path)) {
|
|
766
|
+
imageUrls.push(path);
|
|
767
|
+
} else {
|
|
768
|
+
localFiles.push(path);
|
|
769
|
+
}
|
|
770
|
+
});
|
|
771
|
+
if (localFiles.length > 0) {
|
|
772
|
+
const limit = pLimit(concurrency);
|
|
773
|
+
const uploadedUrls = await Promise.all(
|
|
774
|
+
localFiles.map((filePath) => limit(() => this.uploadFileAndGetUrl(filePath)))
|
|
775
|
+
);
|
|
776
|
+
imageUrls.push(...uploadedUrls);
|
|
777
|
+
}
|
|
778
|
+
if (imageUrls.length > 0) {
|
|
779
|
+
formData.append("input_images", JSON.stringify(imageUrls));
|
|
780
|
+
}
|
|
429
781
|
}
|
|
430
782
|
if (uploads) {
|
|
431
783
|
const list = typeof uploads === "string" ? [uploads] : [...uploads];
|