@golpoai/sdk 0.1.9 → 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 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 = "conversational",
62
+ style = "solo-female-3",
69
63
  bgMusic,
70
- outputVolume = 1,
71
- no_voice_chunking = false
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
- ["add_music", String(addMusic)],
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 (no_voice_chunking) fields.push(["no_voice_chunking", String(no_voice_chunking)]);
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,
@@ -110,63 +132,210 @@ var Golpo = class {
110
132
  async createPodcast(prompt, opts = {}) {
111
133
  const { pollIntervalMs = 2e3, ...jobOpts } = opts;
112
134
  const jobId = await this.createPodcastJob(prompt, jobOpts);
113
- return this.pollUntilComplete(
135
+ const r = await this.pollUntilComplete(
114
136
  jobId,
115
137
  pollIntervalMs
116
- ).then((r) => ({
117
- podcastUrl: r.podcast_url,
118
- podcastScript: r.podcast_script
119
- }));
138
+ );
139
+ return { url: r.podcast_url, script: r.podcast_script, videoId: jobId };
120
140
  }
121
141
  async createVideo(prompt, opts = {}) {
122
142
  const {
123
143
  uploads,
124
144
  pollIntervalMs = 2e3,
125
- concurrency = 8,
126
145
  voiceInstructions,
127
- personality1,
128
- doResearch = false,
129
- ttsModel = "accurate",
130
146
  language,
131
- style = "solo-female",
147
+ style = "solo-female-3",
132
148
  bgMusic = "engaging",
133
- bgVolume = 1.4,
134
- outputVolume = 1,
135
149
  videoType = "long",
136
- includeWatermark = true,
150
+ includeWatermark = false,
137
151
  logo,
138
- timing = "1"
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
139
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
+ };
140
214
  const fields = [
141
215
  ["prompt", prompt],
142
- ["do_research", String(doResearch)],
143
- ["tts_model", ttsModel],
144
- ["bg_volume", String(bgVolume)],
145
- ["output_volume", String(outputVolume)],
146
216
  ["style", style],
147
217
  ["video_type", videoType],
148
218
  ["include_watermark", String(includeWatermark)],
149
- ["timing", timing]
219
+ ["timing", String(timing)],
220
+ ["use_color", String(useColor)]
150
221
  ];
151
222
  if (language) fields.push(["language", language]);
152
223
  if (voiceInstructions) fields.push(["voice_instructions", voiceInstructions]);
153
- if (personality1) fields.push(["personality_1", personality1]);
154
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)]);
155
268
  if (logo) {
156
269
  fields.push(["logo_path", logo]);
157
270
  }
158
- const formData = await this.createFormData(fields, uploads, concurrency, logo);
271
+ const formData = await this.createFormData(
272
+ fields,
273
+ uploads,
274
+ concurrency,
275
+ logo,
276
+ audioClip,
277
+ userImages,
278
+ userVideos,
279
+ inputImages
280
+ );
159
281
  const { data } = await this.http.post("/generate", formData, {
160
282
  timeout: 24e4,
161
283
  headers: formData.getHeaders()
162
284
  });
163
- return this.pollUntilComplete(
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
+ }
293
+ const r = await this.pollUntilComplete(
164
294
  data.job_id,
165
295
  pollIntervalMs
166
- ).then((r) => ({
167
- videoUrl: r.podcast_url,
168
- videoScript: r.podcast_script
169
- }));
296
+ );
297
+ return { url: r.podcast_url, script: r.podcast_script, videoId: data.job_id };
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 };
170
339
  }
171
340
  /**
172
341
  * Edit specific frames of a video and regenerate the video.
@@ -213,11 +382,11 @@ var Golpo = class {
213
382
  try {
214
383
  const frameVersions = await this.getFrameVersions(videoId);
215
384
  let frameAnimations = frameVersions.frame_animations ? { ...frameVersions.frame_animations } : {};
216
- frameIds.forEach(async (frameId) => {
385
+ for (const frameId of frameIds) {
217
386
  await this.setFrameVersion(videoId, frameId, editResult);
218
387
  const updatedVersions = await this.getFrameVersions(videoId);
219
388
  frameAnimations = updatedVersions.frame_animations || {};
220
- });
389
+ }
221
390
  const mp4Urls = Object.entries(frameAnimations).sort(([a], [b]) => parseInt(a) - parseInt(b)).map(([_, url]) => url);
222
391
  if (mp4Urls.length === 0) {
223
392
  throw new Error("No frame animations found to combine");
@@ -227,17 +396,11 @@ var Golpo = class {
227
396
  pollIntervalMs,
228
397
  videoUrl
229
398
  };
230
- const combinedUrl = await this.combineVideos(combineOptions);
231
- return {
232
- videoUrl: combinedUrl,
233
- jobId
234
- };
399
+ const combined = await this.combineVideos(combineOptions);
400
+ return { videoUrl: combined.url, jobId };
235
401
  } catch (error) {
236
402
  console.warn("Failed to auto-combine videos, returning edit result:", error);
237
- return {
238
- videoUrl: editResult,
239
- jobId
240
- };
403
+ return { videoUrl: editResult, jobId };
241
404
  }
242
405
  }
243
406
  /**
@@ -391,19 +554,25 @@ var Golpo = class {
391
554
  if (!combineJobId) {
392
555
  throw new Error("No job ID received from combine-videos API");
393
556
  }
394
- return this.pollEditStatus(combineJobId, pollIntervalMs);
557
+ const url = await this.pollEditStatus(combineJobId, pollIntervalMs);
558
+ return { url };
395
559
  }
396
560
  /* ------------------------------------------------------------ *
397
561
  * INTERNAL HELPERS
398
562
  * ------------------------------------------------------------ */
399
- async pollUntilComplete(jobId, interval) {
563
+ async pollUntilComplete(jobId, interval, statusPath = "/status") {
400
564
  while (true) {
565
+ let data;
401
566
  try {
402
- const { data } = await this.http.get(`/status/${jobId}`);
403
- if (data.status === "completed") return data;
567
+ const res = await this.http.get(`${statusPath}/${jobId}`);
568
+ data = res.data;
404
569
  } catch (error) {
405
570
  console.warn(`Status check failed for job ${jobId}, retrying in ${interval}ms:`, error instanceof Error ? error.message : error);
406
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
+ }
407
576
  await new Promise((r) => setTimeout(r, interval));
408
577
  }
409
578
  }
@@ -453,7 +622,50 @@ var Golpo = class {
453
622
  attempts++;
454
623
  }
455
624
  }
456
- async createFormData(fields, uploads, concurrency, logo) {
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) {
457
669
  const formData = new import_form_data.default();
458
670
  fields.forEach(([key, value]) => {
459
671
  if (key !== "logo_path") {
@@ -462,16 +674,147 @@ var Golpo = class {
462
674
  });
463
675
  if (logo) {
464
676
  const logoPath = logo.trim().replace(/^<|>$/g, "");
677
+ let logoUrl;
465
678
  if (URL_RE.test(logoPath)) {
466
- throw new Error("URL uploads not supported for logo - only local file paths are accepted");
679
+ logoUrl = logoPath;
680
+ } else {
681
+ logoUrl = await this.uploadFileAndGetUrl(logoPath);
682
+ }
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
+ }
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));
467
794
  }
468
- const absLogoPath = (0, import_path.resolve)(logoPath);
469
- const logoFileName = (0, import_path.basename)(absLogoPath);
470
- const logoMimeType = (0, import_mime_types.lookup)(absLogoPath) || "application/octet-stream";
471
- formData.append("logo", (0, import_fs.createReadStream)(absLogoPath), {
472
- filename: logoFileName,
473
- contentType: logoMimeType
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
+ }
474
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
+ }
475
818
  }
476
819
  if (uploads) {
477
820
  const list = typeof uploads === "string" ? [uploads] : [...uploads];