@golpoai/sdk 0.1.8 → 0.1.10

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
@@ -110,13 +110,11 @@ var Golpo = class {
110
110
  async createPodcast(prompt, opts = {}) {
111
111
  const { pollIntervalMs = 2e3, ...jobOpts } = opts;
112
112
  const jobId = await this.createPodcastJob(prompt, jobOpts);
113
- return this.pollUntilComplete(
113
+ const r = await this.pollUntilComplete(
114
114
  jobId,
115
115
  pollIntervalMs
116
- ).then((r) => ({
117
- podcastUrl: r.podcast_url,
118
- podcastScript: r.podcast_script
119
- }));
116
+ );
117
+ return { url: r.podcast_url, script: r.podcast_script, videoId: jobId };
120
118
  }
121
119
  async createVideo(prompt, opts = {}) {
122
120
  const {
@@ -135,9 +133,7 @@ var Golpo = class {
135
133
  videoType = "long",
136
134
  includeWatermark = true,
137
135
  logo,
138
- timing = "1",
139
- useColor = false,
140
- newScript = null
136
+ timing = "1"
141
137
  } = opts;
142
138
  const fields = [
143
139
  ["prompt", prompt],
@@ -148,14 +144,12 @@ var Golpo = class {
148
144
  ["style", style],
149
145
  ["video_type", videoType],
150
146
  ["include_watermark", String(includeWatermark)],
151
- ["timing", timing],
152
- ["use_color", String(useColor)]
147
+ ["timing", timing]
153
148
  ];
154
149
  if (language) fields.push(["language", language]);
155
150
  if (voiceInstructions) fields.push(["voice_instructions", voiceInstructions]);
156
151
  if (personality1) fields.push(["personality_1", personality1]);
157
152
  if (bgMusic) fields.push(["bg_music", bgMusic]);
158
- if (newScript !== null) fields.push(["new_script", newScript]);
159
153
  if (logo) {
160
154
  fields.push(["logo_path", logo]);
161
155
  }
@@ -164,13 +158,231 @@ var Golpo = class {
164
158
  timeout: 24e4,
165
159
  headers: formData.getHeaders()
166
160
  });
167
- return this.pollUntilComplete(
161
+ const r = await this.pollUntilComplete(
168
162
  data.job_id,
169
163
  pollIntervalMs
170
- ).then((r) => ({
171
- videoUrl: r.podcast_url,
172
- videoScript: r.podcast_script
173
- }));
164
+ );
165
+ return { url: r.podcast_url, script: r.podcast_script, videoId: data.job_id };
166
+ }
167
+ /**
168
+ * Edit specific frames of a video and regenerate the video.
169
+ * This method polls until completion and returns the final edited video URL.
170
+ * If autoCombine is true, it will also combine all frames into a final video.
171
+ */
172
+ async editVideo(videoId, opts) {
173
+ const {
174
+ frameIds,
175
+ editPrompts,
176
+ referenceImages,
177
+ videoUrl,
178
+ userId,
179
+ pollIntervalMs = 2e3,
180
+ autoCombine = false
181
+ } = opts;
182
+ if (frameIds.length !== editPrompts.length) {
183
+ throw new Error("frameIds and editPrompts must have the same length");
184
+ }
185
+ if (!videoUrl) {
186
+ throw new Error("videoUrl is required");
187
+ }
188
+ const jobId = await this.editVideoJob(videoId, opts);
189
+ const editResult = await this.pollEditStatus(jobId, pollIntervalMs);
190
+ if (autoCombine) {
191
+ return this.combineEditedFrames(videoId, editResult, {
192
+ frameIds,
193
+ videoUrl,
194
+ pollIntervalMs
195
+ });
196
+ }
197
+ return {
198
+ videoUrl: editResult,
199
+ jobId
200
+ };
201
+ }
202
+ /**
203
+ * Combines edited frames into a final video with audio.
204
+ * This is a helper method used by editVideo when autoCombine is enabled.
205
+ */
206
+ async combineEditedFrames(videoId, editResult, opts) {
207
+ const { frameIds, videoUrl, pollIntervalMs } = opts;
208
+ const jobId = videoId;
209
+ try {
210
+ const frameVersions = await this.getFrameVersions(videoId);
211
+ let frameAnimations = frameVersions.frame_animations ? { ...frameVersions.frame_animations } : {};
212
+ for (const frameId of frameIds) {
213
+ await this.setFrameVersion(videoId, frameId, editResult);
214
+ const updatedVersions = await this.getFrameVersions(videoId);
215
+ frameAnimations = updatedVersions.frame_animations || {};
216
+ }
217
+ const mp4Urls = Object.entries(frameAnimations).sort(([a], [b]) => parseInt(a) - parseInt(b)).map(([_, url]) => url);
218
+ if (mp4Urls.length === 0) {
219
+ throw new Error("No frame animations found to combine");
220
+ }
221
+ const combineOptions = {
222
+ mp4Urls,
223
+ pollIntervalMs,
224
+ videoUrl
225
+ };
226
+ const combined = await this.combineVideos(combineOptions);
227
+ return { videoUrl: combined.url, jobId };
228
+ } catch (error) {
229
+ console.warn("Failed to auto-combine videos, returning edit result:", error);
230
+ return { videoUrl: editResult, jobId };
231
+ }
232
+ }
233
+ /**
234
+ * Create an edit job for a video without waiting for completion.
235
+ * Returns the job ID that can be used to check status later.
236
+ */
237
+ async editVideoJob(videoId, opts) {
238
+ const {
239
+ frameIds,
240
+ editPrompts,
241
+ referenceImages,
242
+ videoUrl,
243
+ userId
244
+ } = opts;
245
+ if (frameIds.length !== editPrompts.length) {
246
+ throw new Error("frameIds and editPrompts must have the same length");
247
+ }
248
+ const body = {
249
+ job_id: videoId,
250
+ frame_ids: frameIds,
251
+ edit_prompts: editPrompts,
252
+ return_single_frame: true
253
+ };
254
+ if (referenceImages) {
255
+ body.reference_images = referenceImages;
256
+ }
257
+ if (videoUrl) {
258
+ body.video_url = videoUrl;
259
+ }
260
+ if (userId) {
261
+ body.user_id = userId;
262
+ }
263
+ console.log("Body:", body);
264
+ const { data } = await this.http.post("/api/edit-and-reanimate-frames", body, {
265
+ headers: {
266
+ "Content-Type": "application/json"
267
+ },
268
+ timeout: 6e4
269
+ });
270
+ return data.job_id;
271
+ }
272
+ /**
273
+ * Check the status of an edit job.
274
+ * Returns the video URL if completed, or status information if still processing.
275
+ */
276
+ async getEditStatus(jobId) {
277
+ const { data } = await this.http.post(
278
+ "/api/edit-status",
279
+ { job_id: jobId },
280
+ {
281
+ headers: {
282
+ "Content-Type": "application/json"
283
+ }
284
+ }
285
+ );
286
+ return data;
287
+ }
288
+ /**
289
+ * Update video metadata (prompt, context, scenes, video_url, frame_animations).
290
+ * Note: This requires JWT authentication.
291
+ */
292
+ async updateVideo(videoId, opts) {
293
+ const { jwtToken, ...updateData } = opts;
294
+ if (!jwtToken) {
295
+ throw new Error("JWT token is required for updating video metadata");
296
+ }
297
+ const headers = {
298
+ "Content-Type": "application/json",
299
+ "Authorization": `Bearer ${jwtToken}`
300
+ };
301
+ const body = {};
302
+ if (updateData.prompt !== void 0) body.prompt = updateData.prompt;
303
+ if (updateData.context !== void 0) body.context = updateData.context;
304
+ if (updateData.scenes !== void 0) body.scenes = updateData.scenes;
305
+ if (updateData.videoUrl !== void 0) body.video_url = updateData.videoUrl;
306
+ if (updateData.frameAnimations !== void 0) body.frame_animations = updateData.frameAnimations;
307
+ const { data } = await this.http.put(
308
+ `/api/videos/${videoId}`,
309
+ body,
310
+ { headers }
311
+ );
312
+ return data;
313
+ }
314
+ /**
315
+ * Get frame animations for a video.
316
+ * Returns a dictionary mapping frame indices to animation URLs.
317
+ * This is a convenience method that extracts frame_animations from getFrameVersions.
318
+ */
319
+ async getFrameAnimations(videoId) {
320
+ const versions = await this.getFrameVersions(videoId);
321
+ return versions.frame_animations || {};
322
+ }
323
+ /**
324
+ * Get frame versions and animations for a video.
325
+ * Returns both the current frame_animations and available frame_animation_versions.
326
+ */
327
+ async getFrameVersions(videoId) {
328
+ const { data } = await this.http.get(`/api/frame-versions/${videoId}`, {
329
+ headers: {
330
+ "Content-Type": "application/json"
331
+ }
332
+ });
333
+ return data;
334
+ }
335
+ /**
336
+ * Set a specific frame animation version.
337
+ * Updates the frame_animations for a specific frame in the video.
338
+ */
339
+ async setFrameVersion(videoId, frameId, animationUrl) {
340
+ const { data } = await this.http.post(
341
+ "/api/set-frame-version",
342
+ {
343
+ job_id: videoId,
344
+ frame_id: frameId,
345
+ url: animationUrl
346
+ },
347
+ {
348
+ headers: {
349
+ "Content-Type": "application/json"
350
+ }
351
+ }
352
+ );
353
+ return data;
354
+ }
355
+ /**
356
+ * Combine multiple MP4 videos into a single video.
357
+ * This is used to combine frame animations into a final video.
358
+ */
359
+ async combineVideos(opts) {
360
+ const {
361
+ mp4Urls,
362
+ videoUrl,
363
+ pollIntervalMs = 2e3
364
+ } = opts;
365
+ if (!mp4Urls || mp4Urls.length === 0) {
366
+ throw new Error("At least one MP4 URL is required");
367
+ }
368
+ const body = {
369
+ mp4_urls: mp4Urls
370
+ };
371
+ if (videoUrl) {
372
+ body.video_url = videoUrl;
373
+ }
374
+ const { data } = await this.http.post("/combine-videos", body, {
375
+ headers: {
376
+ "Content-Type": "application/json"
377
+ },
378
+ timeout: 6e4
379
+ });
380
+ const combineJobId = data.job_id;
381
+ if (!combineJobId) {
382
+ throw new Error("No job ID received from combine-videos API");
383
+ }
384
+ const url = await this.pollEditStatus(combineJobId, pollIntervalMs);
385
+ return { url };
174
386
  }
175
387
  /* ------------------------------------------------------------ *
176
388
  * INTERNAL HELPERS
@@ -186,6 +398,52 @@ var Golpo = class {
186
398
  await new Promise((r) => setTimeout(r, interval));
187
399
  }
188
400
  }
401
+ async pollEditStatus(jobId, interval) {
402
+ const maxAttempts = 1200;
403
+ let attempts = 0;
404
+ let retryCount = 0;
405
+ const maxRetries = 3;
406
+ while (true) {
407
+ if (attempts >= maxAttempts) {
408
+ throw new Error("Edit job timed out");
409
+ }
410
+ try {
411
+ const { data } = await this.http.post(
412
+ "/api/edit-status",
413
+ { job_id: jobId },
414
+ {
415
+ headers: {
416
+ "Content-Type": "application/json"
417
+ }
418
+ }
419
+ );
420
+ if (data.status === "completed") {
421
+ return data.url;
422
+ } else if (data.status === "failed" || data.status === "not found") {
423
+ throw new Error(`Edit job failed: ${data.status}`);
424
+ } else {
425
+ console.log("Edit job status:", data.status);
426
+ }
427
+ retryCount = 0;
428
+ } catch (error) {
429
+ if (error.name === "TypeError" && error.message.includes("fetch failed") || error.code === "ECONNRESET" || error.name === "TimeoutError" || error.response?.status >= 500 && error.response?.status < 600) {
430
+ if (retryCount < maxRetries) {
431
+ retryCount++;
432
+ console.warn(`Retrying request (${retryCount}/${maxRetries}) after network error...`);
433
+ await new Promise((resolve2) => setTimeout(resolve2, Math.min(5e3 * retryCount, 15e3)));
434
+ continue;
435
+ } else {
436
+ console.warn(`Max retries (${maxRetries}) exceeded for network errors`);
437
+ retryCount = 0;
438
+ }
439
+ } else {
440
+ console.warn(`Error on attempt ${attempts + 1}, continuing polling:`, error instanceof Error ? error.message : error);
441
+ }
442
+ }
443
+ await new Promise((r) => setTimeout(r, interval));
444
+ attempts++;
445
+ }
446
+ }
189
447
  async createFormData(fields, uploads, concurrency, logo) {
190
448
  const formData = new import_form_data.default();
191
449
  fields.forEach(([key, value]) => {
package/dist/index.d.cts CHANGED
@@ -29,11 +29,47 @@ interface CreateVideoOptions {
29
29
  includeWatermark?: boolean;
30
30
  logo?: string;
31
31
  timing?: '1' | '2';
32
- useColor?: boolean;
33
- newScript?: string | null;
34
32
  pollIntervalMs?: number;
35
33
  concurrency?: number;
36
34
  }
35
+ interface EditVideoOptions {
36
+ frameIds: string[];
37
+ editPrompts: string[];
38
+ referenceImages?: string[];
39
+ videoUrl: string;
40
+ userId?: string;
41
+ pollIntervalMs?: number;
42
+ autoCombine?: boolean;
43
+ }
44
+ interface FrameAnimations {
45
+ [frameId: string]: string;
46
+ }
47
+ interface CombineVideosOptions {
48
+ mp4Urls: string[];
49
+ videoUrl?: string;
50
+ pollIntervalMs?: number;
51
+ }
52
+ /**
53
+ * Result of createPodcast or createVideo (generated content URL, script, and job ID).
54
+ */
55
+ interface GenerationResult {
56
+ url: string;
57
+ script: string;
58
+ videoId: string;
59
+ }
60
+ /**
61
+ * Result of editVideo (edited or combined video URL and job ID).
62
+ */
63
+ interface VideoEditResult {
64
+ videoUrl: string;
65
+ jobId: string;
66
+ }
67
+ /**
68
+ * Result of combineVideos (URL of the combined video).
69
+ */
70
+ interface CombinedVideoResult {
71
+ url: string;
72
+ }
37
73
  declare class Golpo {
38
74
  private readonly http;
39
75
  constructor(apiKey: string, baseUrl?: string);
@@ -45,16 +81,58 @@ declare class Golpo {
45
81
  podcastUrl: string;
46
82
  podcastScript: string;
47
83
  }>;
48
- createPodcast(prompt: string, opts?: CreatePodcastOptions): Promise<{
49
- podcastUrl: string;
50
- podcastScript: string;
51
- }>;
52
- createVideo(prompt: string, opts?: CreateVideoOptions): Promise<{
53
- videoUrl: string;
54
- videoScript: string;
55
- }>;
84
+ createPodcast(prompt: string, opts?: CreatePodcastOptions): Promise<GenerationResult>;
85
+ createVideo(prompt: string, opts?: CreateVideoOptions): Promise<GenerationResult>;
86
+ /**
87
+ * Edit specific frames of a video and regenerate the video.
88
+ * This method polls until completion and returns the final edited video URL.
89
+ * If autoCombine is true, it will also combine all frames into a final video.
90
+ */
91
+ editVideo(videoId: string, opts: EditVideoOptions): Promise<VideoEditResult>;
92
+ /**
93
+ * Combines edited frames into a final video with audio.
94
+ * This is a helper method used by editVideo when autoCombine is enabled.
95
+ */
96
+ private combineEditedFrames;
97
+ /**
98
+ * Create an edit job for a video without waiting for completion.
99
+ * Returns the job ID that can be used to check status later.
100
+ */
101
+ private editVideoJob;
102
+ /**
103
+ * Check the status of an edit job.
104
+ * Returns the video URL if completed, or status information if still processing.
105
+ */
106
+ private getEditStatus;
107
+ /**
108
+ * Update video metadata (prompt, context, scenes, video_url, frame_animations).
109
+ * Note: This requires JWT authentication.
110
+ */
111
+ private updateVideo;
112
+ /**
113
+ * Get frame animations for a video.
114
+ * Returns a dictionary mapping frame indices to animation URLs.
115
+ * This is a convenience method that extracts frame_animations from getFrameVersions.
116
+ */
117
+ getFrameAnimations(videoId: string): Promise<FrameAnimations>;
118
+ /**
119
+ * Get frame versions and animations for a video.
120
+ * Returns both the current frame_animations and available frame_animation_versions.
121
+ */
122
+ private getFrameVersions;
123
+ /**
124
+ * Set a specific frame animation version.
125
+ * Updates the frame_animations for a specific frame in the video.
126
+ */
127
+ private setFrameVersion;
128
+ /**
129
+ * Combine multiple MP4 videos into a single video.
130
+ * This is used to combine frame animations into a final video.
131
+ */
132
+ combineVideos(opts: CombineVideosOptions): Promise<CombinedVideoResult>;
56
133
  private pollUntilComplete;
134
+ private pollEditStatus;
57
135
  private createFormData;
58
136
  }
59
137
 
60
- export { type CreatePodcastOptions, type CreateVideoOptions, Golpo, Golpo as default };
138
+ export { type CombineVideosOptions, type CombinedVideoResult, type CreatePodcastOptions, type CreateVideoOptions, type EditVideoOptions, type GenerationResult, Golpo, type VideoEditResult, Golpo as default };
package/dist/index.d.ts CHANGED
@@ -29,11 +29,47 @@ interface CreateVideoOptions {
29
29
  includeWatermark?: boolean;
30
30
  logo?: string;
31
31
  timing?: '1' | '2';
32
- useColor?: boolean;
33
- newScript?: string | null;
34
32
  pollIntervalMs?: number;
35
33
  concurrency?: number;
36
34
  }
35
+ interface EditVideoOptions {
36
+ frameIds: string[];
37
+ editPrompts: string[];
38
+ referenceImages?: string[];
39
+ videoUrl: string;
40
+ userId?: string;
41
+ pollIntervalMs?: number;
42
+ autoCombine?: boolean;
43
+ }
44
+ interface FrameAnimations {
45
+ [frameId: string]: string;
46
+ }
47
+ interface CombineVideosOptions {
48
+ mp4Urls: string[];
49
+ videoUrl?: string;
50
+ pollIntervalMs?: number;
51
+ }
52
+ /**
53
+ * Result of createPodcast or createVideo (generated content URL, script, and job ID).
54
+ */
55
+ interface GenerationResult {
56
+ url: string;
57
+ script: string;
58
+ videoId: string;
59
+ }
60
+ /**
61
+ * Result of editVideo (edited or combined video URL and job ID).
62
+ */
63
+ interface VideoEditResult {
64
+ videoUrl: string;
65
+ jobId: string;
66
+ }
67
+ /**
68
+ * Result of combineVideos (URL of the combined video).
69
+ */
70
+ interface CombinedVideoResult {
71
+ url: string;
72
+ }
37
73
  declare class Golpo {
38
74
  private readonly http;
39
75
  constructor(apiKey: string, baseUrl?: string);
@@ -45,16 +81,58 @@ declare class Golpo {
45
81
  podcastUrl: string;
46
82
  podcastScript: string;
47
83
  }>;
48
- createPodcast(prompt: string, opts?: CreatePodcastOptions): Promise<{
49
- podcastUrl: string;
50
- podcastScript: string;
51
- }>;
52
- createVideo(prompt: string, opts?: CreateVideoOptions): Promise<{
53
- videoUrl: string;
54
- videoScript: string;
55
- }>;
84
+ createPodcast(prompt: string, opts?: CreatePodcastOptions): Promise<GenerationResult>;
85
+ createVideo(prompt: string, opts?: CreateVideoOptions): Promise<GenerationResult>;
86
+ /**
87
+ * Edit specific frames of a video and regenerate the video.
88
+ * This method polls until completion and returns the final edited video URL.
89
+ * If autoCombine is true, it will also combine all frames into a final video.
90
+ */
91
+ editVideo(videoId: string, opts: EditVideoOptions): Promise<VideoEditResult>;
92
+ /**
93
+ * Combines edited frames into a final video with audio.
94
+ * This is a helper method used by editVideo when autoCombine is enabled.
95
+ */
96
+ private combineEditedFrames;
97
+ /**
98
+ * Create an edit job for a video without waiting for completion.
99
+ * Returns the job ID that can be used to check status later.
100
+ */
101
+ private editVideoJob;
102
+ /**
103
+ * Check the status of an edit job.
104
+ * Returns the video URL if completed, or status information if still processing.
105
+ */
106
+ private getEditStatus;
107
+ /**
108
+ * Update video metadata (prompt, context, scenes, video_url, frame_animations).
109
+ * Note: This requires JWT authentication.
110
+ */
111
+ private updateVideo;
112
+ /**
113
+ * Get frame animations for a video.
114
+ * Returns a dictionary mapping frame indices to animation URLs.
115
+ * This is a convenience method that extracts frame_animations from getFrameVersions.
116
+ */
117
+ getFrameAnimations(videoId: string): Promise<FrameAnimations>;
118
+ /**
119
+ * Get frame versions and animations for a video.
120
+ * Returns both the current frame_animations and available frame_animation_versions.
121
+ */
122
+ private getFrameVersions;
123
+ /**
124
+ * Set a specific frame animation version.
125
+ * Updates the frame_animations for a specific frame in the video.
126
+ */
127
+ private setFrameVersion;
128
+ /**
129
+ * Combine multiple MP4 videos into a single video.
130
+ * This is used to combine frame animations into a final video.
131
+ */
132
+ combineVideos(opts: CombineVideosOptions): Promise<CombinedVideoResult>;
56
133
  private pollUntilComplete;
134
+ private pollEditStatus;
57
135
  private createFormData;
58
136
  }
59
137
 
60
- export { type CreatePodcastOptions, type CreateVideoOptions, Golpo, Golpo as default };
138
+ export { type CombineVideosOptions, type CombinedVideoResult, type CreatePodcastOptions, type CreateVideoOptions, type EditVideoOptions, type GenerationResult, Golpo, type VideoEditResult, Golpo as default };
package/dist/index.js CHANGED
@@ -73,13 +73,11 @@ var Golpo = class {
73
73
  async createPodcast(prompt, opts = {}) {
74
74
  const { pollIntervalMs = 2e3, ...jobOpts } = opts;
75
75
  const jobId = await this.createPodcastJob(prompt, jobOpts);
76
- return this.pollUntilComplete(
76
+ const r = await this.pollUntilComplete(
77
77
  jobId,
78
78
  pollIntervalMs
79
- ).then((r) => ({
80
- podcastUrl: r.podcast_url,
81
- podcastScript: r.podcast_script
82
- }));
79
+ );
80
+ return { url: r.podcast_url, script: r.podcast_script, videoId: jobId };
83
81
  }
84
82
  async createVideo(prompt, opts = {}) {
85
83
  const {
@@ -98,9 +96,7 @@ var Golpo = class {
98
96
  videoType = "long",
99
97
  includeWatermark = true,
100
98
  logo,
101
- timing = "1",
102
- useColor = false,
103
- newScript = null
99
+ timing = "1"
104
100
  } = opts;
105
101
  const fields = [
106
102
  ["prompt", prompt],
@@ -111,14 +107,12 @@ var Golpo = class {
111
107
  ["style", style],
112
108
  ["video_type", videoType],
113
109
  ["include_watermark", String(includeWatermark)],
114
- ["timing", timing],
115
- ["use_color", String(useColor)]
110
+ ["timing", timing]
116
111
  ];
117
112
  if (language) fields.push(["language", language]);
118
113
  if (voiceInstructions) fields.push(["voice_instructions", voiceInstructions]);
119
114
  if (personality1) fields.push(["personality_1", personality1]);
120
115
  if (bgMusic) fields.push(["bg_music", bgMusic]);
121
- if (newScript !== null) fields.push(["new_script", newScript]);
122
116
  if (logo) {
123
117
  fields.push(["logo_path", logo]);
124
118
  }
@@ -127,13 +121,231 @@ var Golpo = class {
127
121
  timeout: 24e4,
128
122
  headers: formData.getHeaders()
129
123
  });
130
- return this.pollUntilComplete(
124
+ const r = await this.pollUntilComplete(
131
125
  data.job_id,
132
126
  pollIntervalMs
133
- ).then((r) => ({
134
- videoUrl: r.podcast_url,
135
- videoScript: r.podcast_script
136
- }));
127
+ );
128
+ return { url: r.podcast_url, script: r.podcast_script, videoId: data.job_id };
129
+ }
130
+ /**
131
+ * Edit specific frames of a video and regenerate the video.
132
+ * This method polls until completion and returns the final edited video URL.
133
+ * If autoCombine is true, it will also combine all frames into a final video.
134
+ */
135
+ async editVideo(videoId, opts) {
136
+ const {
137
+ frameIds,
138
+ editPrompts,
139
+ referenceImages,
140
+ videoUrl,
141
+ userId,
142
+ pollIntervalMs = 2e3,
143
+ autoCombine = false
144
+ } = opts;
145
+ if (frameIds.length !== editPrompts.length) {
146
+ throw new Error("frameIds and editPrompts must have the same length");
147
+ }
148
+ if (!videoUrl) {
149
+ throw new Error("videoUrl is required");
150
+ }
151
+ const jobId = await this.editVideoJob(videoId, opts);
152
+ const editResult = await this.pollEditStatus(jobId, pollIntervalMs);
153
+ if (autoCombine) {
154
+ return this.combineEditedFrames(videoId, editResult, {
155
+ frameIds,
156
+ videoUrl,
157
+ pollIntervalMs
158
+ });
159
+ }
160
+ return {
161
+ videoUrl: editResult,
162
+ jobId
163
+ };
164
+ }
165
+ /**
166
+ * Combines edited frames into a final video with audio.
167
+ * This is a helper method used by editVideo when autoCombine is enabled.
168
+ */
169
+ async combineEditedFrames(videoId, editResult, opts) {
170
+ const { frameIds, videoUrl, pollIntervalMs } = opts;
171
+ const jobId = videoId;
172
+ try {
173
+ const frameVersions = await this.getFrameVersions(videoId);
174
+ let frameAnimations = frameVersions.frame_animations ? { ...frameVersions.frame_animations } : {};
175
+ for (const frameId of frameIds) {
176
+ await this.setFrameVersion(videoId, frameId, editResult);
177
+ const updatedVersions = await this.getFrameVersions(videoId);
178
+ frameAnimations = updatedVersions.frame_animations || {};
179
+ }
180
+ const mp4Urls = Object.entries(frameAnimations).sort(([a], [b]) => parseInt(a) - parseInt(b)).map(([_, url]) => url);
181
+ if (mp4Urls.length === 0) {
182
+ throw new Error("No frame animations found to combine");
183
+ }
184
+ const combineOptions = {
185
+ mp4Urls,
186
+ pollIntervalMs,
187
+ videoUrl
188
+ };
189
+ const combined = await this.combineVideos(combineOptions);
190
+ return { videoUrl: combined.url, jobId };
191
+ } catch (error) {
192
+ console.warn("Failed to auto-combine videos, returning edit result:", error);
193
+ return { videoUrl: editResult, jobId };
194
+ }
195
+ }
196
+ /**
197
+ * Create an edit job for a video without waiting for completion.
198
+ * Returns the job ID that can be used to check status later.
199
+ */
200
+ async editVideoJob(videoId, opts) {
201
+ const {
202
+ frameIds,
203
+ editPrompts,
204
+ referenceImages,
205
+ videoUrl,
206
+ userId
207
+ } = opts;
208
+ if (frameIds.length !== editPrompts.length) {
209
+ throw new Error("frameIds and editPrompts must have the same length");
210
+ }
211
+ const body = {
212
+ job_id: videoId,
213
+ frame_ids: frameIds,
214
+ edit_prompts: editPrompts,
215
+ return_single_frame: true
216
+ };
217
+ if (referenceImages) {
218
+ body.reference_images = referenceImages;
219
+ }
220
+ if (videoUrl) {
221
+ body.video_url = videoUrl;
222
+ }
223
+ if (userId) {
224
+ body.user_id = userId;
225
+ }
226
+ console.log("Body:", body);
227
+ const { data } = await this.http.post("/api/edit-and-reanimate-frames", body, {
228
+ headers: {
229
+ "Content-Type": "application/json"
230
+ },
231
+ timeout: 6e4
232
+ });
233
+ return data.job_id;
234
+ }
235
+ /**
236
+ * Check the status of an edit job.
237
+ * Returns the video URL if completed, or status information if still processing.
238
+ */
239
+ async getEditStatus(jobId) {
240
+ const { data } = await this.http.post(
241
+ "/api/edit-status",
242
+ { job_id: jobId },
243
+ {
244
+ headers: {
245
+ "Content-Type": "application/json"
246
+ }
247
+ }
248
+ );
249
+ return data;
250
+ }
251
+ /**
252
+ * Update video metadata (prompt, context, scenes, video_url, frame_animations).
253
+ * Note: This requires JWT authentication.
254
+ */
255
+ async updateVideo(videoId, opts) {
256
+ const { jwtToken, ...updateData } = opts;
257
+ if (!jwtToken) {
258
+ throw new Error("JWT token is required for updating video metadata");
259
+ }
260
+ const headers = {
261
+ "Content-Type": "application/json",
262
+ "Authorization": `Bearer ${jwtToken}`
263
+ };
264
+ const body = {};
265
+ if (updateData.prompt !== void 0) body.prompt = updateData.prompt;
266
+ if (updateData.context !== void 0) body.context = updateData.context;
267
+ if (updateData.scenes !== void 0) body.scenes = updateData.scenes;
268
+ if (updateData.videoUrl !== void 0) body.video_url = updateData.videoUrl;
269
+ if (updateData.frameAnimations !== void 0) body.frame_animations = updateData.frameAnimations;
270
+ const { data } = await this.http.put(
271
+ `/api/videos/${videoId}`,
272
+ body,
273
+ { headers }
274
+ );
275
+ return data;
276
+ }
277
+ /**
278
+ * Get frame animations for a video.
279
+ * Returns a dictionary mapping frame indices to animation URLs.
280
+ * This is a convenience method that extracts frame_animations from getFrameVersions.
281
+ */
282
+ async getFrameAnimations(videoId) {
283
+ const versions = await this.getFrameVersions(videoId);
284
+ return versions.frame_animations || {};
285
+ }
286
+ /**
287
+ * Get frame versions and animations for a video.
288
+ * Returns both the current frame_animations and available frame_animation_versions.
289
+ */
290
+ async getFrameVersions(videoId) {
291
+ const { data } = await this.http.get(`/api/frame-versions/${videoId}`, {
292
+ headers: {
293
+ "Content-Type": "application/json"
294
+ }
295
+ });
296
+ return data;
297
+ }
298
+ /**
299
+ * Set a specific frame animation version.
300
+ * Updates the frame_animations for a specific frame in the video.
301
+ */
302
+ async setFrameVersion(videoId, frameId, animationUrl) {
303
+ const { data } = await this.http.post(
304
+ "/api/set-frame-version",
305
+ {
306
+ job_id: videoId,
307
+ frame_id: frameId,
308
+ url: animationUrl
309
+ },
310
+ {
311
+ headers: {
312
+ "Content-Type": "application/json"
313
+ }
314
+ }
315
+ );
316
+ return data;
317
+ }
318
+ /**
319
+ * Combine multiple MP4 videos into a single video.
320
+ * This is used to combine frame animations into a final video.
321
+ */
322
+ async combineVideos(opts) {
323
+ const {
324
+ mp4Urls,
325
+ videoUrl,
326
+ pollIntervalMs = 2e3
327
+ } = opts;
328
+ if (!mp4Urls || mp4Urls.length === 0) {
329
+ throw new Error("At least one MP4 URL is required");
330
+ }
331
+ const body = {
332
+ mp4_urls: mp4Urls
333
+ };
334
+ if (videoUrl) {
335
+ body.video_url = videoUrl;
336
+ }
337
+ const { data } = await this.http.post("/combine-videos", body, {
338
+ headers: {
339
+ "Content-Type": "application/json"
340
+ },
341
+ timeout: 6e4
342
+ });
343
+ const combineJobId = data.job_id;
344
+ if (!combineJobId) {
345
+ throw new Error("No job ID received from combine-videos API");
346
+ }
347
+ const url = await this.pollEditStatus(combineJobId, pollIntervalMs);
348
+ return { url };
137
349
  }
138
350
  /* ------------------------------------------------------------ *
139
351
  * INTERNAL HELPERS
@@ -149,6 +361,52 @@ var Golpo = class {
149
361
  await new Promise((r) => setTimeout(r, interval));
150
362
  }
151
363
  }
364
+ async pollEditStatus(jobId, interval) {
365
+ const maxAttempts = 1200;
366
+ let attempts = 0;
367
+ let retryCount = 0;
368
+ const maxRetries = 3;
369
+ while (true) {
370
+ if (attempts >= maxAttempts) {
371
+ throw new Error("Edit job timed out");
372
+ }
373
+ try {
374
+ const { data } = await this.http.post(
375
+ "/api/edit-status",
376
+ { job_id: jobId },
377
+ {
378
+ headers: {
379
+ "Content-Type": "application/json"
380
+ }
381
+ }
382
+ );
383
+ if (data.status === "completed") {
384
+ return data.url;
385
+ } else if (data.status === "failed" || data.status === "not found") {
386
+ throw new Error(`Edit job failed: ${data.status}`);
387
+ } else {
388
+ console.log("Edit job status:", data.status);
389
+ }
390
+ retryCount = 0;
391
+ } catch (error) {
392
+ if (error.name === "TypeError" && error.message.includes("fetch failed") || error.code === "ECONNRESET" || error.name === "TimeoutError" || error.response?.status >= 500 && error.response?.status < 600) {
393
+ if (retryCount < maxRetries) {
394
+ retryCount++;
395
+ console.warn(`Retrying request (${retryCount}/${maxRetries}) after network error...`);
396
+ await new Promise((resolve2) => setTimeout(resolve2, Math.min(5e3 * retryCount, 15e3)));
397
+ continue;
398
+ } else {
399
+ console.warn(`Max retries (${maxRetries}) exceeded for network errors`);
400
+ retryCount = 0;
401
+ }
402
+ } else {
403
+ console.warn(`Error on attempt ${attempts + 1}, continuing polling:`, error instanceof Error ? error.message : error);
404
+ }
405
+ }
406
+ await new Promise((r) => setTimeout(r, interval));
407
+ attempts++;
408
+ }
409
+ }
152
410
  async createFormData(fields, uploads, concurrency, logo) {
153
411
  const formData = new FormData();
154
412
  fields.forEach(([key, value]) => {
package/package.json CHANGED
@@ -1,8 +1,16 @@
1
1
  {
2
2
  "name": "@golpoai/sdk",
3
- "version": "0.1.8",
3
+ "version": "0.1.10",
4
4
  "type": "module",
5
- "exports": "./dist/index.js",
5
+ "exports": {
6
+ ".": {
7
+ "types": "./dist/index.d.ts",
8
+ "import": "./dist/index.js",
9
+ "require": "./dist/index.cjs"
10
+ }
11
+ },
12
+ "main": "./dist/index.cjs",
13
+ "module": "./dist/index.js",
6
14
  "types": "./dist/index.d.ts",
7
15
  "files": [
8
16
  "dist/**/*"