@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 +273 -6
- package/dist/index.d.cts +66 -3
- package/dist/index.d.ts +66 -3
- package/dist/index.js +273 -6
- package/package.json +1 -1
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]) => {
|