@golpoai/sdk 0.1.8 → 0.1.9

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