opencode-nanobanana 0.1.0
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- package/.ralph-events.json +151 -0
- package/.ralph-last-branch +1 -0
- package/.ralph-monitor-state.json +7 -0
- package/.ralph-monitor.pid +1 -0
- package/.ralph-timing.json +26 -0
- package/README.md +708 -0
- package/dist/index.d.ts +18 -0
- package/dist/index.d.ts.map +1 -0
- package/dist/index.js +21 -0
- package/dist/index.js.map +1 -0
- package/dist/platforms/android.d.ts +94 -0
- package/dist/platforms/android.d.ts.map +1 -0
- package/dist/platforms/android.js +123 -0
- package/dist/platforms/android.js.map +1 -0
- package/dist/platforms/ios.d.ts +51 -0
- package/dist/platforms/ios.d.ts.map +1 -0
- package/dist/platforms/ios.js +149 -0
- package/dist/platforms/ios.js.map +1 -0
- package/dist/platforms/macos.d.ts +33 -0
- package/dist/platforms/macos.d.ts.map +1 -0
- package/dist/platforms/macos.js +50 -0
- package/dist/platforms/macos.js.map +1 -0
- package/dist/platforms/watchos.d.ts +36 -0
- package/dist/platforms/watchos.d.ts.map +1 -0
- package/dist/platforms/watchos.js +113 -0
- package/dist/platforms/watchos.js.map +1 -0
- package/dist/platforms/web.d.ts +64 -0
- package/dist/platforms/web.d.ts.map +1 -0
- package/dist/platforms/web.js +96 -0
- package/dist/platforms/web.js.map +1 -0
- package/dist/providers/gemini.d.ts +41 -0
- package/dist/providers/gemini.d.ts.map +1 -0
- package/dist/providers/gemini.js +177 -0
- package/dist/providers/gemini.js.map +1 -0
- package/dist/tools/analyze/compare.d.ts +12 -0
- package/dist/tools/analyze/compare.d.ts.map +1 -0
- package/dist/tools/analyze/compare.js +83 -0
- package/dist/tools/analyze/compare.js.map +1 -0
- package/dist/tools/analyze/mockup.d.ts +12 -0
- package/dist/tools/analyze/mockup.d.ts.map +1 -0
- package/dist/tools/analyze/mockup.js +88 -0
- package/dist/tools/analyze/mockup.js.map +1 -0
- package/dist/tools/analyze/screenshot.d.ts +12 -0
- package/dist/tools/analyze/screenshot.d.ts.map +1 -0
- package/dist/tools/analyze/screenshot.js +61 -0
- package/dist/tools/analyze/screenshot.js.map +1 -0
- package/dist/tools/app-assets/app-icon.d.ts +9 -0
- package/dist/tools/app-assets/app-icon.d.ts.map +1 -0
- package/dist/tools/app-assets/app-icon.js +133 -0
- package/dist/tools/app-assets/app-icon.js.map +1 -0
- package/dist/tools/app-assets/device-mockup.d.ts +9 -0
- package/dist/tools/app-assets/device-mockup.d.ts.map +1 -0
- package/dist/tools/app-assets/device-mockup.js +139 -0
- package/dist/tools/app-assets/device-mockup.js.map +1 -0
- package/dist/tools/app-assets/launch-images.d.ts +3 -0
- package/dist/tools/app-assets/launch-images.d.ts.map +1 -0
- package/dist/tools/app-assets/launch-images.js +171 -0
- package/dist/tools/app-assets/launch-images.js.map +1 -0
- package/dist/tools/app-assets/resize-devices.d.ts +14 -0
- package/dist/tools/app-assets/resize-devices.d.ts.map +1 -0
- package/dist/tools/app-assets/resize-devices.js +296 -0
- package/dist/tools/app-assets/resize-devices.js.map +1 -0
- package/dist/tools/app-assets/screenshots.d.ts +14 -0
- package/dist/tools/app-assets/screenshots.d.ts.map +1 -0
- package/dist/tools/app-assets/screenshots.js +186 -0
- package/dist/tools/app-assets/screenshots.js.map +1 -0
- package/dist/tools/core/edit-image.d.ts +12 -0
- package/dist/tools/core/edit-image.d.ts.map +1 -0
- package/dist/tools/core/edit-image.js +102 -0
- package/dist/tools/core/edit-image.js.map +1 -0
- package/dist/tools/core/generate-image.d.ts +12 -0
- package/dist/tools/core/generate-image.d.ts.map +1 -0
- package/dist/tools/core/generate-image.js +96 -0
- package/dist/tools/core/generate-image.js.map +1 -0
- package/dist/tools/core/restore-image.d.ts +12 -0
- package/dist/tools/core/restore-image.d.ts.map +1 -0
- package/dist/tools/core/restore-image.js +104 -0
- package/dist/tools/core/restore-image.js.map +1 -0
- package/dist/tools/design/mockup-to-code.d.ts +3 -0
- package/dist/tools/design/mockup-to-code.d.ts.map +1 -0
- package/dist/tools/design/mockup-to-code.js +311 -0
- package/dist/tools/design/mockup-to-code.js.map +1 -0
- package/dist/tools/design/sketch-to-code.d.ts +3 -0
- package/dist/tools/design/sketch-to-code.d.ts.map +1 -0
- package/dist/tools/design/sketch-to-code.js +325 -0
- package/dist/tools/design/sketch-to-code.js.map +1 -0
- package/dist/tools/docs/architecture-diagram.d.ts +12 -0
- package/dist/tools/docs/architecture-diagram.d.ts.map +1 -0
- package/dist/tools/docs/architecture-diagram.js +179 -0
- package/dist/tools/docs/architecture-diagram.js.map +1 -0
- package/dist/tools/docs/readme-banner.d.ts +6 -0
- package/dist/tools/docs/readme-banner.d.ts.map +1 -0
- package/dist/tools/docs/readme-banner.js +108 -0
- package/dist/tools/docs/readme-banner.js.map +1 -0
- package/dist/tools/docs/sequence-diagram.d.ts +12 -0
- package/dist/tools/docs/sequence-diagram.d.ts.map +1 -0
- package/dist/tools/docs/sequence-diagram.js +161 -0
- package/dist/tools/docs/sequence-diagram.js.map +1 -0
- package/dist/tools/docs/social-preview.d.ts +11 -0
- package/dist/tools/docs/social-preview.d.ts.map +1 -0
- package/dist/tools/docs/social-preview.js +111 -0
- package/dist/tools/docs/social-preview.js.map +1 -0
- package/dist/tools/video/extend-video.d.ts +14 -0
- package/dist/tools/video/extend-video.d.ts.map +1 -0
- package/dist/tools/video/extend-video.js +39 -0
- package/dist/tools/video/extend-video.js.map +1 -0
- package/dist/tools/video/generate-video.d.ts +14 -0
- package/dist/tools/video/generate-video.d.ts.map +1 -0
- package/dist/tools/video/generate-video.js +39 -0
- package/dist/tools/video/generate-video.js.map +1 -0
- package/dist/tools/video/image-to-video.d.ts +15 -0
- package/dist/tools/video/image-to-video.d.ts.map +1 -0
- package/dist/tools/video/image-to-video.js +42 -0
- package/dist/tools/video/image-to-video.js.map +1 -0
- package/dist/tools/video/storyboard-video.d.ts +91 -0
- package/dist/tools/video/storyboard-video.d.ts.map +1 -0
- package/dist/tools/video/storyboard-video.js +230 -0
- package/dist/tools/video/storyboard-video.js.map +1 -0
- package/dist/utils/ffmpeg.d.ts +30 -0
- package/dist/utils/ffmpeg.d.ts.map +1 -0
- package/dist/utils/ffmpeg.js +205 -0
- package/dist/utils/ffmpeg.js.map +1 -0
- package/dist/utils/file-handler.d.ts +7 -0
- package/dist/utils/file-handler.d.ts.map +1 -0
- package/dist/utils/file-handler.js +10 -0
- package/dist/utils/file-handler.js.map +1 -0
- package/dist/utils/image-processing.d.ts +7 -0
- package/dist/utils/image-processing.d.ts.map +1 -0
- package/dist/utils/image-processing.js +10 -0
- package/dist/utils/image-processing.js.map +1 -0
- package/docs/PLUGIN-VERIFICATION.md +182 -0
- package/logs/notifications.jsonl +46 -0
- package/package.json +61 -0
- package/prd.json +216 -0
- package/progress.txt +145 -0
- package/ralph-report.html +297 -0
- package/src/index.ts +23 -0
- package/src/platforms/android/.gitkeep +0 -0
- package/src/platforms/ios/.gitkeep +0 -0
- package/src/platforms/web/.gitkeep +0 -0
- package/src/providers/.gitkeep +0 -0
- package/src/providers/gemini.ts +288 -0
- package/src/tools/core/.gitkeep +0 -0
- package/src/tools/platform/.gitkeep +0 -0
- package/src/tools/video/extend-video.ts +71 -0
- package/src/tools/video/generate-video.ts +70 -0
- package/src/tools/video/image-to-video.ts +76 -0
- package/src/tools/video/storyboard-video.ts +325 -0
- package/src/utils/.gitkeep +0 -0
- package/src/utils/ffmpeg.ts +266 -0
- package/src/utils/file-handler.ts +10 -0
- package/src/utils/image-processing.ts +10 -0
- package/templates/.gitkeep +0 -0
- package/test-analyze-screenshot.ts +50 -0
- package/test-app-icons.ts +55 -0
- package/test-cat-sunset.ts +30 -0
- package/test-full-plugin.ts +88 -0
- package/test-icon-gen.ts +30 -0
- package/test-output/test-edit.png +0 -0
- package/test-output/test-generate.png +0 -0
- package/test-output/test-video.mp4 +0 -0
- package/test-plugin-load.js +45 -0
- package/test-princess-emma-continue.ts +35 -0
- package/test-princess-emma-full.ts +38 -0
- package/test-princess-emma-short.ts +32 -0
- package/test-princess-emma-with-reference.ts +34 -0
- package/test-princess-emma.ts +38 -0
- package/test-product-ad.ts +66 -0
- package/test-ralph-droid.ts +30 -0
- package/test-social-preview.ts +61 -0
- package/test-veo31-live.ts +187 -0
- package/test-video-gen.ts +40 -0
- package/test-video-veo.ts +73 -0
- package/test-zurich-video.ts +64 -0
- package/tests/.gitkeep +0 -0
- package/tests/providers/gemini.test.ts +388 -0
- package/tests/utils/ffmpeg.test.ts +328 -0
- package/tests/video/storyboard.test.ts +469 -0
- package/tsconfig.json +25 -0
package/src/index.ts
ADDED
|
@@ -0,0 +1,23 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* OpenCode Visual Toolkit
|
|
3
|
+
*
|
|
4
|
+
* A comprehensive plugin providing visual capabilities:
|
|
5
|
+
* - Image generation (Nano Banana)
|
|
6
|
+
* - Visual analysis
|
|
7
|
+
* - Design-to-code
|
|
8
|
+
* - App asset pipelines (iOS, Android, macOS, Web)
|
|
9
|
+
*/
|
|
10
|
+
|
|
11
|
+
// Re-export providers
|
|
12
|
+
export * from './providers/gemini.js';
|
|
13
|
+
|
|
14
|
+
// Re-export utilities
|
|
15
|
+
export * from './utils/file-handler.js';
|
|
16
|
+
export * from './utils/image-processing.js';
|
|
17
|
+
export * from './utils/ffmpeg.js';
|
|
18
|
+
|
|
19
|
+
// Re-export tools
|
|
20
|
+
export * from './tools/video/generate-video.js';
|
|
21
|
+
export * from './tools/video/image-to-video.js';
|
|
22
|
+
export * from './tools/video/storyboard-video.js';
|
|
23
|
+
export * from './tools/video/extend-video.js';
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
@@ -0,0 +1,288 @@
|
|
|
1
|
+
import { GoogleGenAI } from '@google/genai';
|
|
2
|
+
|
|
3
|
+
export interface VideoGenerationOptions {
|
|
4
|
+
aspectRatio?: '16:9' | '9:16';
|
|
5
|
+
resolution?: '720p' | '1080p';
|
|
6
|
+
duration?: 4 | 6 | 8;
|
|
7
|
+
numberOfVideos?: number;
|
|
8
|
+
negativePrompt?: string;
|
|
9
|
+
}
|
|
10
|
+
|
|
11
|
+
export interface ImageAnimationOptions {
|
|
12
|
+
aspectRatio?: '16:9' | '9:16';
|
|
13
|
+
resolution?: '720p' | '1080p';
|
|
14
|
+
duration?: 4 | 6 | 8;
|
|
15
|
+
}
|
|
16
|
+
|
|
17
|
+
export interface VideoExtensionOptions {
|
|
18
|
+
aspectRatio?: '16:9' | '9:16';
|
|
19
|
+
resolution?: '720p' | '1080p';
|
|
20
|
+
}
|
|
21
|
+
|
|
22
|
+
export interface ReferenceImage {
|
|
23
|
+
buffer: Buffer;
|
|
24
|
+
description: string;
|
|
25
|
+
}
|
|
26
|
+
|
|
27
|
+
export interface VideoGenerationResult {
|
|
28
|
+
buffer: Buffer;
|
|
29
|
+
url?: string;
|
|
30
|
+
generationTime: number;
|
|
31
|
+
}
|
|
32
|
+
|
|
33
|
+
export class GeminiProvider {
|
|
34
|
+
private ai: GoogleGenAI;
|
|
35
|
+
private apiKey: string;
|
|
36
|
+
private readonly VEO_MODEL = 'veo-3.0-generate-001';
|
|
37
|
+
private readonly VEO_PREVIEW_MODEL = 'veo-3.1-generate-preview';
|
|
38
|
+
private readonly POLL_INTERVAL_MS = 10000;
|
|
39
|
+
|
|
40
|
+
constructor(apiKey: string) {
|
|
41
|
+
this.apiKey = apiKey;
|
|
42
|
+
this.ai = new GoogleGenAI({ apiKey });
|
|
43
|
+
}
|
|
44
|
+
|
|
45
|
+
async generateVideo(
|
|
46
|
+
prompt: string,
|
|
47
|
+
options: VideoGenerationOptions = {}
|
|
48
|
+
): Promise<VideoGenerationResult> {
|
|
49
|
+
const startTime = Date.now();
|
|
50
|
+
|
|
51
|
+
const {
|
|
52
|
+
aspectRatio = '16:9',
|
|
53
|
+
resolution = '720p',
|
|
54
|
+
duration = 8,
|
|
55
|
+
numberOfVideos = 1,
|
|
56
|
+
negativePrompt,
|
|
57
|
+
} = options;
|
|
58
|
+
|
|
59
|
+
let operation = await this.ai.models.generateVideos({
|
|
60
|
+
model: this.VEO_MODEL,
|
|
61
|
+
prompt,
|
|
62
|
+
config: {
|
|
63
|
+
numberOfVideos,
|
|
64
|
+
aspectRatio,
|
|
65
|
+
resolution,
|
|
66
|
+
durationSeconds: duration,
|
|
67
|
+
...(negativePrompt && { negativePrompt }),
|
|
68
|
+
},
|
|
69
|
+
});
|
|
70
|
+
|
|
71
|
+
operation = await this.pollOperation(operation);
|
|
72
|
+
|
|
73
|
+
if (!operation.response?.generatedVideos?.[0]) {
|
|
74
|
+
throw new Error('No video was generated');
|
|
75
|
+
}
|
|
76
|
+
|
|
77
|
+
const video = operation.response.generatedVideos[0];
|
|
78
|
+
const videoUrl = video.video?.uri;
|
|
79
|
+
|
|
80
|
+
if (!videoUrl) {
|
|
81
|
+
throw new Error('Video URL not available');
|
|
82
|
+
}
|
|
83
|
+
|
|
84
|
+
const buffer = await this.downloadVideo(videoUrl);
|
|
85
|
+
const generationTime = Date.now() - startTime;
|
|
86
|
+
|
|
87
|
+
return { buffer, url: videoUrl, generationTime };
|
|
88
|
+
}
|
|
89
|
+
|
|
90
|
+
async animateImage(
|
|
91
|
+
imageBuffer: Buffer,
|
|
92
|
+
prompt: string,
|
|
93
|
+
options: ImageAnimationOptions = {}
|
|
94
|
+
): Promise<VideoGenerationResult> {
|
|
95
|
+
const startTime = Date.now();
|
|
96
|
+
|
|
97
|
+
const {
|
|
98
|
+
aspectRatio = '16:9',
|
|
99
|
+
resolution = '720p',
|
|
100
|
+
duration = 8,
|
|
101
|
+
} = options;
|
|
102
|
+
|
|
103
|
+
const imageBase64 = imageBuffer.toString('base64');
|
|
104
|
+
const mimeType = this.detectImageMimeType(imageBuffer);
|
|
105
|
+
|
|
106
|
+
let operation = await this.ai.models.generateVideos({
|
|
107
|
+
model: this.VEO_MODEL,
|
|
108
|
+
prompt,
|
|
109
|
+
image: {
|
|
110
|
+
imageBytes: imageBase64,
|
|
111
|
+
mimeType,
|
|
112
|
+
},
|
|
113
|
+
config: {
|
|
114
|
+
numberOfVideos: 1,
|
|
115
|
+
aspectRatio,
|
|
116
|
+
resolution,
|
|
117
|
+
durationSeconds: duration,
|
|
118
|
+
},
|
|
119
|
+
});
|
|
120
|
+
|
|
121
|
+
operation = await this.pollOperation(operation);
|
|
122
|
+
|
|
123
|
+
if (!operation.response?.generatedVideos?.[0]) {
|
|
124
|
+
throw new Error('No video was generated from image');
|
|
125
|
+
}
|
|
126
|
+
|
|
127
|
+
const video = operation.response.generatedVideos[0];
|
|
128
|
+
const videoUrl = video.video?.uri;
|
|
129
|
+
|
|
130
|
+
if (!videoUrl) {
|
|
131
|
+
throw new Error('Video URL not available');
|
|
132
|
+
}
|
|
133
|
+
|
|
134
|
+
const buffer = await this.downloadVideo(videoUrl);
|
|
135
|
+
const generationTime = Date.now() - startTime;
|
|
136
|
+
|
|
137
|
+
return { buffer, url: videoUrl, generationTime };
|
|
138
|
+
}
|
|
139
|
+
|
|
140
|
+
async extendVideo(
|
|
141
|
+
videoBuffer: Buffer,
|
|
142
|
+
prompt: string,
|
|
143
|
+
options: VideoExtensionOptions = {}
|
|
144
|
+
): Promise<VideoGenerationResult> {
|
|
145
|
+
const startTime = Date.now();
|
|
146
|
+
|
|
147
|
+
const {
|
|
148
|
+
aspectRatio = '16:9',
|
|
149
|
+
resolution = '720p',
|
|
150
|
+
} = options;
|
|
151
|
+
|
|
152
|
+
const videoBase64 = videoBuffer.toString('base64');
|
|
153
|
+
|
|
154
|
+
let operation = await this.ai.models.generateVideos({
|
|
155
|
+
model: this.VEO_MODEL,
|
|
156
|
+
prompt,
|
|
157
|
+
video: {
|
|
158
|
+
videoBytes: videoBase64,
|
|
159
|
+
mimeType: 'video/mp4',
|
|
160
|
+
},
|
|
161
|
+
config: {
|
|
162
|
+
numberOfVideos: 1,
|
|
163
|
+
aspectRatio,
|
|
164
|
+
resolution,
|
|
165
|
+
},
|
|
166
|
+
});
|
|
167
|
+
|
|
168
|
+
operation = await this.pollOperation(operation);
|
|
169
|
+
|
|
170
|
+
if (!operation.response?.generatedVideos?.[0]) {
|
|
171
|
+
throw new Error('Video extension failed');
|
|
172
|
+
}
|
|
173
|
+
|
|
174
|
+
const video = operation.response.generatedVideos[0];
|
|
175
|
+
const videoUrl = video.video?.uri;
|
|
176
|
+
|
|
177
|
+
if (!videoUrl) {
|
|
178
|
+
throw new Error('Video URL not available');
|
|
179
|
+
}
|
|
180
|
+
|
|
181
|
+
const buffer = await this.downloadVideo(videoUrl);
|
|
182
|
+
const generationTime = Date.now() - startTime;
|
|
183
|
+
|
|
184
|
+
return { buffer, url: videoUrl, generationTime };
|
|
185
|
+
}
|
|
186
|
+
|
|
187
|
+
async generateVideoWithReferences(
|
|
188
|
+
prompt: string,
|
|
189
|
+
referenceImages: ReferenceImage[],
|
|
190
|
+
options: VideoGenerationOptions = {}
|
|
191
|
+
): Promise<VideoGenerationResult> {
|
|
192
|
+
if (referenceImages.length === 0 || referenceImages.length > 3) {
|
|
193
|
+
throw new Error('Reference images must be between 1 and 3');
|
|
194
|
+
}
|
|
195
|
+
|
|
196
|
+
const startTime = Date.now();
|
|
197
|
+
|
|
198
|
+
const {
|
|
199
|
+
aspectRatio = '16:9',
|
|
200
|
+
resolution = '720p',
|
|
201
|
+
duration = 8,
|
|
202
|
+
numberOfVideos = 1,
|
|
203
|
+
} = options;
|
|
204
|
+
|
|
205
|
+
const references = referenceImages.map((ref) => ({
|
|
206
|
+
image: {
|
|
207
|
+
imageBytes: ref.buffer.toString('base64'),
|
|
208
|
+
mimeType: this.detectImageMimeType(ref.buffer),
|
|
209
|
+
},
|
|
210
|
+
}));
|
|
211
|
+
|
|
212
|
+
let operation = await this.ai.models.generateVideos({
|
|
213
|
+
model: this.VEO_PREVIEW_MODEL,
|
|
214
|
+
prompt,
|
|
215
|
+
config: {
|
|
216
|
+
numberOfVideos,
|
|
217
|
+
aspectRatio,
|
|
218
|
+
resolution,
|
|
219
|
+
durationSeconds: duration,
|
|
220
|
+
referenceImages: references,
|
|
221
|
+
},
|
|
222
|
+
});
|
|
223
|
+
|
|
224
|
+
operation = await this.pollOperation(operation);
|
|
225
|
+
|
|
226
|
+
if (!operation.response?.generatedVideos?.[0]) {
|
|
227
|
+
console.error('Operation response:', JSON.stringify(operation, null, 2));
|
|
228
|
+
throw new Error('No video was generated with references');
|
|
229
|
+
}
|
|
230
|
+
|
|
231
|
+
const video = operation.response.generatedVideos[0];
|
|
232
|
+
const videoUrl = video.video?.uri;
|
|
233
|
+
|
|
234
|
+
if (!videoUrl) {
|
|
235
|
+
throw new Error('Video URL not available');
|
|
236
|
+
}
|
|
237
|
+
|
|
238
|
+
const buffer = await this.downloadVideo(videoUrl);
|
|
239
|
+
const generationTime = Date.now() - startTime;
|
|
240
|
+
|
|
241
|
+
return { buffer, url: videoUrl, generationTime };
|
|
242
|
+
}
|
|
243
|
+
|
|
244
|
+
private async pollOperation(operation: any): Promise<any> {
|
|
245
|
+
let currentOperation = operation;
|
|
246
|
+
|
|
247
|
+
while (!currentOperation.done) {
|
|
248
|
+
await new Promise((resolve) => setTimeout(resolve, this.POLL_INTERVAL_MS));
|
|
249
|
+
currentOperation = await this.ai.operations.getVideosOperation({
|
|
250
|
+
operation: currentOperation,
|
|
251
|
+
});
|
|
252
|
+
}
|
|
253
|
+
|
|
254
|
+
return currentOperation;
|
|
255
|
+
}
|
|
256
|
+
|
|
257
|
+
private async downloadVideo(url: string): Promise<Buffer> {
|
|
258
|
+
const response = await fetch(url, {
|
|
259
|
+
headers: {
|
|
260
|
+
'x-goog-api-key': this.apiKey,
|
|
261
|
+
},
|
|
262
|
+
redirect: 'follow',
|
|
263
|
+
});
|
|
264
|
+
|
|
265
|
+
if (!response.ok) {
|
|
266
|
+
throw new Error(`Failed to download video: ${response.statusText}`);
|
|
267
|
+
}
|
|
268
|
+
|
|
269
|
+
const arrayBuffer = await response.arrayBuffer();
|
|
270
|
+
return Buffer.from(arrayBuffer);
|
|
271
|
+
}
|
|
272
|
+
|
|
273
|
+
private detectImageMimeType(buffer: Buffer): string {
|
|
274
|
+
if (buffer[0] === 0xff && buffer[1] === 0xd8) {
|
|
275
|
+
return 'image/jpeg';
|
|
276
|
+
}
|
|
277
|
+
if (buffer[0] === 0x89 && buffer[1] === 0x50 && buffer[2] === 0x4e && buffer[3] === 0x47) {
|
|
278
|
+
return 'image/png';
|
|
279
|
+
}
|
|
280
|
+
if (buffer[0] === 0x47 && buffer[1] === 0x49 && buffer[2] === 0x46) {
|
|
281
|
+
return 'image/gif';
|
|
282
|
+
}
|
|
283
|
+
if (buffer[0] === 0x52 && buffer[1] === 0x49 && buffer[2] === 0x46 && buffer[3] === 0x46) {
|
|
284
|
+
return 'image/webp';
|
|
285
|
+
}
|
|
286
|
+
return 'image/png';
|
|
287
|
+
}
|
|
288
|
+
}
|
|
File without changes
|
|
File without changes
|
|
@@ -0,0 +1,71 @@
|
|
|
1
|
+
import { GeminiProvider } from '../../providers/gemini.js';
|
|
2
|
+
import { readFile, writeFile } from 'fs/promises';
|
|
3
|
+
import { join } from 'path';
|
|
4
|
+
import { tmpdir } from 'os';
|
|
5
|
+
|
|
6
|
+
export interface ExtendVideoOptions {
|
|
7
|
+
videoPath: string;
|
|
8
|
+
prompt: string;
|
|
9
|
+
aspectRatio?: '16:9' | '9:16';
|
|
10
|
+
resolution?: '720p' | '1080p';
|
|
11
|
+
outputPath?: string;
|
|
12
|
+
apiKey: string;
|
|
13
|
+
}
|
|
14
|
+
|
|
15
|
+
export interface ExtendVideoResult {
|
|
16
|
+
videoPath: string;
|
|
17
|
+
generationTime: number;
|
|
18
|
+
}
|
|
19
|
+
|
|
20
|
+
export async function extendVideo(
|
|
21
|
+
options: ExtendVideoOptions
|
|
22
|
+
): Promise<ExtendVideoResult> {
|
|
23
|
+
const startTime = Date.now();
|
|
24
|
+
|
|
25
|
+
const {
|
|
26
|
+
videoPath,
|
|
27
|
+
prompt,
|
|
28
|
+
aspectRatio = '16:9',
|
|
29
|
+
resolution = '720p',
|
|
30
|
+
outputPath,
|
|
31
|
+
apiKey,
|
|
32
|
+
} = options;
|
|
33
|
+
|
|
34
|
+
console.log('🎬 Extending video...');
|
|
35
|
+
console.log(` Video: ${videoPath}`);
|
|
36
|
+
console.log(` Prompt: ${prompt}`);
|
|
37
|
+
console.log(` Resolution: ${resolution}`);
|
|
38
|
+
console.log(` Aspect Ratio: ${aspectRatio}`);
|
|
39
|
+
|
|
40
|
+
const provider = new GeminiProvider(apiKey);
|
|
41
|
+
|
|
42
|
+
try {
|
|
43
|
+
console.log(' Loading video...');
|
|
44
|
+
const videoBuffer = await readFile(videoPath);
|
|
45
|
+
|
|
46
|
+
console.log(' Generating extension...');
|
|
47
|
+
const result = await provider.extendVideo(videoBuffer, prompt, {
|
|
48
|
+
aspectRatio,
|
|
49
|
+
resolution,
|
|
50
|
+
});
|
|
51
|
+
|
|
52
|
+
const finalOutputPath = outputPath || join(tmpdir(), `extended-${Date.now()}.mp4`);
|
|
53
|
+
|
|
54
|
+
console.log(' Saving extended video...');
|
|
55
|
+
await writeFile(finalOutputPath, result.buffer);
|
|
56
|
+
|
|
57
|
+
const totalTime = Date.now() - startTime;
|
|
58
|
+
|
|
59
|
+
console.log(`✨ Video extension complete!`);
|
|
60
|
+
console.log(` Output: ${finalOutputPath}`);
|
|
61
|
+
console.log(` Generation time: ${(totalTime / 1000).toFixed(2)}s`);
|
|
62
|
+
|
|
63
|
+
return {
|
|
64
|
+
videoPath: finalOutputPath,
|
|
65
|
+
generationTime: totalTime,
|
|
66
|
+
};
|
|
67
|
+
} catch (error) {
|
|
68
|
+
console.error('❌ Video extension failed:', error);
|
|
69
|
+
throw error;
|
|
70
|
+
}
|
|
71
|
+
}
|
|
@@ -0,0 +1,70 @@
|
|
|
1
|
+
import { GeminiProvider } from '../../providers/gemini.js';
|
|
2
|
+
import { writeFile } from 'fs/promises';
|
|
3
|
+
import { join } from 'path';
|
|
4
|
+
import { tmpdir } from 'os';
|
|
5
|
+
|
|
6
|
+
export interface GenerateVideoOptions {
|
|
7
|
+
prompt: string;
|
|
8
|
+
aspectRatio?: '16:9' | '9:16';
|
|
9
|
+
resolution?: '720p' | '1080p';
|
|
10
|
+
duration?: 4 | 6 | 8;
|
|
11
|
+
outputPath?: string;
|
|
12
|
+
apiKey: string;
|
|
13
|
+
}
|
|
14
|
+
|
|
15
|
+
export interface GenerateVideoResult {
|
|
16
|
+
videoPath: string;
|
|
17
|
+
generationTime: number;
|
|
18
|
+
}
|
|
19
|
+
|
|
20
|
+
export async function generateVideo(
|
|
21
|
+
options: GenerateVideoOptions
|
|
22
|
+
): Promise<GenerateVideoResult> {
|
|
23
|
+
const startTime = Date.now();
|
|
24
|
+
|
|
25
|
+
const {
|
|
26
|
+
prompt,
|
|
27
|
+
aspectRatio = '16:9',
|
|
28
|
+
resolution = '720p',
|
|
29
|
+
duration = 8,
|
|
30
|
+
outputPath,
|
|
31
|
+
apiKey,
|
|
32
|
+
} = options;
|
|
33
|
+
|
|
34
|
+
console.log('🎬 Generating video...');
|
|
35
|
+
console.log(` Prompt: ${prompt}`);
|
|
36
|
+
console.log(` Resolution: ${resolution}`);
|
|
37
|
+
console.log(` Duration: ${duration}s`);
|
|
38
|
+
console.log(` Aspect Ratio: ${aspectRatio}`);
|
|
39
|
+
console.log(` Audio: native (Veo 3.0)`);
|
|
40
|
+
|
|
41
|
+
const provider = new GeminiProvider(apiKey);
|
|
42
|
+
|
|
43
|
+
try {
|
|
44
|
+
console.log(' Generating with Veo 3.0...');
|
|
45
|
+
const result = await provider.generateVideo(prompt, {
|
|
46
|
+
aspectRatio,
|
|
47
|
+
resolution,
|
|
48
|
+
duration,
|
|
49
|
+
});
|
|
50
|
+
|
|
51
|
+
const finalOutputPath = outputPath || join(tmpdir(), `video-${Date.now()}.mp4`);
|
|
52
|
+
|
|
53
|
+
console.log(' Saving video...');
|
|
54
|
+
await writeFile(finalOutputPath, result.buffer);
|
|
55
|
+
|
|
56
|
+
const totalTime = Date.now() - startTime;
|
|
57
|
+
|
|
58
|
+
console.log(`✨ Video generation complete!`);
|
|
59
|
+
console.log(` Output: ${finalOutputPath}`);
|
|
60
|
+
console.log(` Generation time: ${(totalTime / 1000).toFixed(2)}s`);
|
|
61
|
+
|
|
62
|
+
return {
|
|
63
|
+
videoPath: finalOutputPath,
|
|
64
|
+
generationTime: totalTime,
|
|
65
|
+
};
|
|
66
|
+
} catch (error) {
|
|
67
|
+
console.error('❌ Video generation failed:', error);
|
|
68
|
+
throw error;
|
|
69
|
+
}
|
|
70
|
+
}
|
|
@@ -0,0 +1,76 @@
|
|
|
1
|
+
import { GeminiProvider } from '../../providers/gemini.js';
|
|
2
|
+
import { readFile, writeFile } from 'fs/promises';
|
|
3
|
+
import { join } from 'path';
|
|
4
|
+
import { tmpdir } from 'os';
|
|
5
|
+
|
|
6
|
+
export interface ImageToVideoOptions {
|
|
7
|
+
imagePath: string;
|
|
8
|
+
prompt: string;
|
|
9
|
+
aspectRatio?: '16:9' | '9:16';
|
|
10
|
+
resolution?: '720p' | '1080p';
|
|
11
|
+
duration?: 4 | 6 | 8;
|
|
12
|
+
outputPath?: string;
|
|
13
|
+
apiKey: string;
|
|
14
|
+
}
|
|
15
|
+
|
|
16
|
+
export interface ImageToVideoResult {
|
|
17
|
+
videoPath: string;
|
|
18
|
+
generationTime: number;
|
|
19
|
+
}
|
|
20
|
+
|
|
21
|
+
export async function imageToVideo(
|
|
22
|
+
options: ImageToVideoOptions
|
|
23
|
+
): Promise<ImageToVideoResult> {
|
|
24
|
+
const startTime = Date.now();
|
|
25
|
+
|
|
26
|
+
const {
|
|
27
|
+
imagePath,
|
|
28
|
+
prompt,
|
|
29
|
+
aspectRatio = '16:9',
|
|
30
|
+
resolution = '720p',
|
|
31
|
+
duration = 8,
|
|
32
|
+
outputPath,
|
|
33
|
+
apiKey,
|
|
34
|
+
} = options;
|
|
35
|
+
|
|
36
|
+
console.log('🎬 Animating image to video...');
|
|
37
|
+
console.log(` Image: ${imagePath}`);
|
|
38
|
+
console.log(` Prompt: ${prompt}`);
|
|
39
|
+
console.log(` Resolution: ${resolution}`);
|
|
40
|
+
console.log(` Duration: ${duration}s`);
|
|
41
|
+
console.log(` Aspect Ratio: ${aspectRatio}`);
|
|
42
|
+
console.log(` Audio: native (Veo 3.0)`);
|
|
43
|
+
|
|
44
|
+
const provider = new GeminiProvider(apiKey);
|
|
45
|
+
|
|
46
|
+
try {
|
|
47
|
+
console.log(' Loading image...');
|
|
48
|
+
const imageBuffer = await readFile(imagePath);
|
|
49
|
+
|
|
50
|
+
console.log(' Animating with Veo 3.0...');
|
|
51
|
+
const result = await provider.animateImage(imageBuffer, prompt, {
|
|
52
|
+
aspectRatio,
|
|
53
|
+
resolution,
|
|
54
|
+
duration,
|
|
55
|
+
});
|
|
56
|
+
|
|
57
|
+
const finalOutputPath = outputPath || join(tmpdir(), `animated-${Date.now()}.mp4`);
|
|
58
|
+
|
|
59
|
+
console.log(' Saving video...');
|
|
60
|
+
await writeFile(finalOutputPath, result.buffer);
|
|
61
|
+
|
|
62
|
+
const totalTime = Date.now() - startTime;
|
|
63
|
+
|
|
64
|
+
console.log(`✨ Image animation complete!`);
|
|
65
|
+
console.log(` Output: ${finalOutputPath}`);
|
|
66
|
+
console.log(` Generation time: ${(totalTime / 1000).toFixed(2)}s`);
|
|
67
|
+
|
|
68
|
+
return {
|
|
69
|
+
videoPath: finalOutputPath,
|
|
70
|
+
generationTime: totalTime,
|
|
71
|
+
};
|
|
72
|
+
} catch (error) {
|
|
73
|
+
console.error('❌ Image animation failed:', error);
|
|
74
|
+
throw error;
|
|
75
|
+
}
|
|
76
|
+
}
|