@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 +274 -16
- package/dist/index.d.cts +89 -11
- package/dist/index.d.ts +89 -11
- package/dist/index.js +274 -16
- package/package.json +10 -2
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
|
-
|
|
113
|
+
const r = await this.pollUntilComplete(
|
|
114
114
|
jobId,
|
|
115
115
|
pollIntervalMs
|
|
116
|
-
)
|
|
117
|
-
|
|
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
|
-
|
|
161
|
+
const r = await this.pollUntilComplete(
|
|
168
162
|
data.job_id,
|
|
169
163
|
pollIntervalMs
|
|
170
|
-
)
|
|
171
|
-
|
|
172
|
-
|
|
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
|
-
|
|
50
|
-
|
|
51
|
-
|
|
52
|
-
|
|
53
|
-
|
|
54
|
-
|
|
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
|
-
|
|
50
|
-
|
|
51
|
-
|
|
52
|
-
|
|
53
|
-
|
|
54
|
-
|
|
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
|
-
|
|
76
|
+
const r = await this.pollUntilComplete(
|
|
77
77
|
jobId,
|
|
78
78
|
pollIntervalMs
|
|
79
|
-
)
|
|
80
|
-
|
|
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
|
-
|
|
124
|
+
const r = await this.pollUntilComplete(
|
|
131
125
|
data.job_id,
|
|
132
126
|
pollIntervalMs
|
|
133
|
-
)
|
|
134
|
-
|
|
135
|
-
|
|
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.
|
|
3
|
+
"version": "0.1.10",
|
|
4
4
|
"type": "module",
|
|
5
|
-
"exports":
|
|
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/**/*"
|