@houtini/gemini-mcp 1.4.5 → 2.2.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/README.md +314 -834
- package/claude_desktop_config_example.json +1 -0
- package/dist/config/index.d.ts.map +1 -1
- package/dist/config/index.js +8 -4
- package/dist/config/index.js.map +1 -1
- package/dist/config/types.d.ts +5 -0
- package/dist/config/types.d.ts.map +1 -1
- package/dist/image-viewer/image-viewer-app.html +180 -0
- package/dist/image-viewer/src/ui/image-viewer.html +324 -0
- package/dist/index-new.d.ts +3 -0
- package/dist/index-new.d.ts.map +1 -0
- package/dist/index-new.js +7 -0
- package/dist/index-new.js.map +1 -0
- package/dist/index.d.ts +3 -1
- package/dist/index.d.ts.map +1 -1
- package/dist/index.js +70 -172
- package/dist/index.js.map +1 -1
- package/dist/landing-page-viewer/src/ui/landing-page-viewer.html +330 -0
- package/dist/services/gemini/export.d.ts +5 -0
- package/dist/services/gemini/export.d.ts.map +1 -0
- package/dist/services/gemini/export.js +5 -0
- package/dist/services/gemini/export.js.map +1 -0
- package/dist/services/gemini/image-service.d.ts +45 -0
- package/dist/services/gemini/image-service.d.ts.map +1 -0
- package/dist/services/gemini/image-service.js +248 -0
- package/dist/services/gemini/image-service.js.map +1 -0
- package/dist/services/gemini/index.d.ts +7 -2
- package/dist/services/gemini/index.d.ts.map +1 -1
- package/dist/services/gemini/index.js +132 -56
- package/dist/services/gemini/index.js.map +1 -1
- package/dist/services/gemini/types.d.ts +32 -0
- package/dist/services/gemini/types.d.ts.map +1 -1
- package/dist/services/gemini/video-service.d.ts +58 -0
- package/dist/services/gemini/video-service.d.ts.map +1 -0
- package/dist/services/gemini/video-service.js +325 -0
- package/dist/services/gemini/video-service.js.map +1 -0
- package/dist/services/media-server.d.ts +28 -0
- package/dist/services/media-server.d.ts.map +1 -0
- package/dist/services/media-server.js +195 -0
- package/dist/services/media-server.js.map +1 -0
- package/dist/svg-viewer/src/ui/svg-viewer.html +325 -0
- package/dist/tools/gemini-chat.d.ts.map +1 -1
- package/dist/tools/gemini-chat.js +7 -1
- package/dist/tools/gemini-chat.js.map +1 -1
- package/dist/tools/gemini-deep-research.d.ts +1 -2
- package/dist/tools/gemini-deep-research.d.ts.map +1 -1
- package/dist/tools/gemini-deep-research.js +11 -51
- package/dist/tools/gemini-deep-research.js.map +1 -1
- package/dist/tools/gemini-help.d.ts +3 -0
- package/dist/tools/gemini-help.d.ts.map +1 -0
- package/dist/tools/gemini-help.js +534 -0
- package/dist/tools/gemini-help.js.map +1 -0
- package/dist/tools/gemini-prompt-assistant.d.ts +20 -0
- package/dist/tools/gemini-prompt-assistant.d.ts.map +1 -0
- package/dist/tools/gemini-prompt-assistant.js +129 -0
- package/dist/tools/gemini-prompt-assistant.js.map +1 -0
- package/dist/tools/generate-landing-page.d.ts +15 -0
- package/dist/tools/generate-landing-page.d.ts.map +1 -0
- package/dist/tools/generate-landing-page.js +66 -0
- package/dist/tools/generate-landing-page.js.map +1 -0
- package/dist/tools/generate-svg.d.ts +14 -0
- package/dist/tools/generate-svg.d.ts.map +1 -0
- package/dist/tools/generate-svg.js +106 -0
- package/dist/tools/generate-svg.js.map +1 -0
- package/dist/tools/generate-video.d.ts +24 -0
- package/dist/tools/generate-video.d.ts.map +1 -0
- package/dist/tools/generate-video.js +163 -0
- package/dist/tools/generate-video.js.map +1 -0
- package/dist/tools/image-prompt-assistant.d.ts +3 -0
- package/dist/tools/image-prompt-assistant.d.ts.map +1 -0
- package/dist/tools/image-prompt-assistant.js +790 -0
- package/dist/tools/image-prompt-assistant.js.map +1 -0
- package/dist/tools/load-image-from-path.d.ts +11 -0
- package/dist/tools/load-image-from-path.d.ts.map +1 -0
- package/dist/tools/load-image-from-path.js +100 -0
- package/dist/tools/load-image-from-path.js.map +1 -0
- package/dist/tools/prompt-library/charts.d.ts +325 -0
- package/dist/tools/prompt-library/charts.d.ts.map +1 -0
- package/dist/tools/prompt-library/charts.js +384 -0
- package/dist/tools/prompt-library/charts.js.map +1 -0
- package/dist/tools/prompt-library/index.d.ts +8 -0
- package/dist/tools/prompt-library/index.d.ts.map +1 -0
- package/dist/tools/prompt-library/index.js +10 -0
- package/dist/tools/prompt-library/index.js.map +1 -0
- package/dist/tools/register-analyze-image.d.ts +3 -0
- package/dist/tools/register-analyze-image.d.ts.map +1 -0
- package/dist/tools/register-analyze-image.js +67 -0
- package/dist/tools/register-analyze-image.js.map +1 -0
- package/dist/tools/register-chat.d.ts +3 -0
- package/dist/tools/register-chat.d.ts.map +1 -0
- package/dist/tools/register-chat.js +71 -0
- package/dist/tools/register-chat.js.map +1 -0
- package/dist/tools/register-deep-research.d.ts +3 -0
- package/dist/tools/register-deep-research.d.ts.map +1 -0
- package/dist/tools/register-deep-research.js +59 -0
- package/dist/tools/register-deep-research.js.map +1 -0
- package/dist/tools/register-describe-image.d.ts +3 -0
- package/dist/tools/register-describe-image.d.ts.map +1 -0
- package/dist/tools/register-describe-image.js +59 -0
- package/dist/tools/register-describe-image.js.map +1 -0
- package/dist/tools/register-image-gen.d.ts +3 -0
- package/dist/tools/register-image-gen.d.ts.map +1 -0
- package/dist/tools/register-image-gen.js +235 -0
- package/dist/tools/register-image-gen.js.map +1 -0
- package/dist/tools/register-landing-page.d.ts +3 -0
- package/dist/tools/register-landing-page.d.ts.map +1 -0
- package/dist/tools/register-landing-page.js +79 -0
- package/dist/tools/register-landing-page.js.map +1 -0
- package/dist/tools/register-list-models.d.ts +3 -0
- package/dist/tools/register-list-models.d.ts.map +1 -0
- package/dist/tools/register-list-models.js +33 -0
- package/dist/tools/register-list-models.js.map +1 -0
- package/dist/tools/register-load-image.d.ts +3 -0
- package/dist/tools/register-load-image.d.ts.map +1 -0
- package/dist/tools/register-load-image.js +66 -0
- package/dist/tools/register-load-image.js.map +1 -0
- package/dist/tools/register-svg.d.ts +3 -0
- package/dist/tools/register-svg.d.ts.map +1 -0
- package/dist/tools/register-svg.js +84 -0
- package/dist/tools/register-svg.js.map +1 -0
- package/dist/tools/register-video.d.ts +3 -0
- package/dist/tools/register-video.d.ts.map +1 -0
- package/dist/tools/register-video.js +118 -0
- package/dist/tools/register-video.js.map +1 -0
- package/dist/tools/register-viewers.d.ts +8 -0
- package/dist/tools/register-viewers.d.ts.map +1 -0
- package/dist/tools/register-viewers.js +89 -0
- package/dist/tools/register-viewers.js.map +1 -0
- package/dist/tools/schemas.d.ts +33 -0
- package/dist/tools/schemas.d.ts.map +1 -0
- package/dist/tools/schemas.js +39 -0
- package/dist/tools/schemas.js.map +1 -0
- package/dist/tools/types.d.ts +12 -0
- package/dist/tools/types.d.ts.map +1 -0
- package/dist/tools/types.js +2 -0
- package/dist/tools/types.js.map +1 -0
- package/dist/ui/image-viewer.d.ts +2 -0
- package/dist/ui/image-viewer.d.ts.map +1 -0
- package/dist/ui/image-viewer.js +42 -0
- package/dist/ui/image-viewer.js.map +1 -0
- package/dist/utils/chart-design-system.d.ts +92 -0
- package/dist/utils/chart-design-system.d.ts.map +1 -0
- package/dist/utils/chart-design-system.js +235 -0
- package/dist/utils/chart-design-system.js.map +1 -0
- package/dist/utils/image-compress.d.ts +9 -0
- package/dist/utils/image-compress.d.ts.map +1 -0
- package/dist/utils/image-compress.js +43 -0
- package/dist/utils/image-compress.js.map +1 -0
- package/dist/utils/image-utils.d.ts +9 -0
- package/dist/utils/image-utils.d.ts.map +1 -0
- package/dist/utils/image-utils.js +257 -0
- package/dist/utils/image-utils.js.map +1 -0
- package/dist/utils/resolve-images.d.ts +29 -0
- package/dist/utils/resolve-images.d.ts.map +1 -0
- package/dist/utils/resolve-images.js +56 -0
- package/dist/utils/resolve-images.js.map +1 -0
- package/dist/utils/tool-wrapper.d.ts +13 -0
- package/dist/utils/tool-wrapper.d.ts.map +1 -0
- package/dist/utils/tool-wrapper.js +22 -0
- package/dist/utils/tool-wrapper.js.map +1 -0
- package/dist/utils/video-utils.d.ts +16 -0
- package/dist/utils/video-utils.d.ts.map +1 -0
- package/dist/utils/video-utils.js +319 -0
- package/dist/utils/video-utils.js.map +1 -0
- package/dist/video-viewer/src/ui/video-viewer.html +310 -0
- package/package.json +21 -7
- package/server.json +30 -29
|
@@ -0,0 +1,325 @@
|
|
|
1
|
+
import { GeminiError } from '../../utils/error-handler.js';
|
|
2
|
+
import { BaseService } from '../base-service.js';
|
|
3
|
+
import fs from 'fs/promises';
|
|
4
|
+
import path from 'path';
|
|
5
|
+
const GEMINI_API_BASE = 'https://generativelanguage.googleapis.com/v1beta/models';
|
|
6
|
+
export const VIDEO_GENERATION_MODELS = [
|
|
7
|
+
'veo-3.1-generate-preview',
|
|
8
|
+
];
|
|
9
|
+
const DEFAULT_VIDEO_MODEL = 'veo-3.1-generate-preview';
|
|
10
|
+
// ─── Retry constants ─────────────────────────────────────────────────────────
|
|
11
|
+
const MAX_429_RETRIES = 3;
|
|
12
|
+
const INITIAL_BACKOFF_MS = 30_000; // 30 seconds
|
|
13
|
+
// ─── Service ─────────────────────────────────────────────────────────────────
|
|
14
|
+
export class GeminiVideoService extends BaseService {
|
|
15
|
+
apiKey;
|
|
16
|
+
defaultModel;
|
|
17
|
+
outputDir;
|
|
18
|
+
pollingIntervalMs;
|
|
19
|
+
maxPollingTimeMs;
|
|
20
|
+
constructor(config, outputDir) {
|
|
21
|
+
super();
|
|
22
|
+
if (!config.apiKey) {
|
|
23
|
+
throw new GeminiError('Missing API key for Gemini video service');
|
|
24
|
+
}
|
|
25
|
+
this.apiKey = config.apiKey;
|
|
26
|
+
this.defaultModel = DEFAULT_VIDEO_MODEL;
|
|
27
|
+
this.outputDir = outputDir || './output';
|
|
28
|
+
// Veo 3.1 video generation takes 2-5 minutes for 1080p 8-second videos
|
|
29
|
+
// Poll every 15 seconds to avoid rate limits
|
|
30
|
+
this.pollingIntervalMs = 15000;
|
|
31
|
+
// Allow up to 10 minutes for video generation
|
|
32
|
+
this.maxPollingTimeMs = 600000;
|
|
33
|
+
this.logInfo('Gemini video service initialised', {
|
|
34
|
+
outputDir: this.outputDir,
|
|
35
|
+
pollingInterval: `${this.pollingIntervalMs / 1000}s`,
|
|
36
|
+
maxPollingTime: `${this.maxPollingTimeMs / 1000}s`
|
|
37
|
+
});
|
|
38
|
+
}
|
|
39
|
+
/**
|
|
40
|
+
* Fetch with automatic retry on 429 (rate limit) responses.
|
|
41
|
+
* Uses exponential backoff starting at INITIAL_BACKOFF_MS.
|
|
42
|
+
*/
|
|
43
|
+
async fetchWithRetry(url, init, label = 'request') {
|
|
44
|
+
let lastError;
|
|
45
|
+
for (let attempt = 0; attempt <= MAX_429_RETRIES; attempt++) {
|
|
46
|
+
const response = await fetch(url, init);
|
|
47
|
+
if (response.status !== 429) {
|
|
48
|
+
return response;
|
|
49
|
+
}
|
|
50
|
+
// 429 rate limited
|
|
51
|
+
lastError = new GeminiError(`Video generation rate limited (429). The API has a quota limit. ` +
|
|
52
|
+
`Please wait 30-60 seconds before retrying.`);
|
|
53
|
+
if (attempt < MAX_429_RETRIES) {
|
|
54
|
+
const backoffMs = INITIAL_BACKOFF_MS * Math.pow(2, attempt);
|
|
55
|
+
this.logInfo(`Rate limited (429) on ${label}, retrying in ${backoffMs / 1000}s`, {
|
|
56
|
+
attempt: attempt + 1,
|
|
57
|
+
maxRetries: MAX_429_RETRIES,
|
|
58
|
+
});
|
|
59
|
+
await new Promise(resolve => setTimeout(resolve, backoffMs));
|
|
60
|
+
}
|
|
61
|
+
}
|
|
62
|
+
throw lastError;
|
|
63
|
+
}
|
|
64
|
+
validateModel(model) {
|
|
65
|
+
if (!VIDEO_GENERATION_MODELS.includes(model)) {
|
|
66
|
+
throw new GeminiError(`Invalid video model: ${model}. Allowed: ${VIDEO_GENERATION_MODELS.join(', ')}`);
|
|
67
|
+
}
|
|
68
|
+
}
|
|
69
|
+
buildVeoRequest(options) {
|
|
70
|
+
const instance = {
|
|
71
|
+
prompt: options.prompt
|
|
72
|
+
};
|
|
73
|
+
// Add first frame image if provided (image-to-video)
|
|
74
|
+
if (options.firstFrameImage) {
|
|
75
|
+
instance.image = {
|
|
76
|
+
bytesBase64Encoded: options.firstFrameImage.data,
|
|
77
|
+
mimeType: options.firstFrameImage.mimeType
|
|
78
|
+
};
|
|
79
|
+
}
|
|
80
|
+
// Add reference images if provided (up to 3 for character/style consistency)
|
|
81
|
+
if (options.referenceImages?.length) {
|
|
82
|
+
if (options.referenceImages.length > 3) {
|
|
83
|
+
throw new GeminiError('Maximum 3 reference images allowed');
|
|
84
|
+
}
|
|
85
|
+
instance.referenceImages = options.referenceImages.map(ref => ({
|
|
86
|
+
referenceType: ref.referenceType,
|
|
87
|
+
referenceImage: {
|
|
88
|
+
bytesBase64Encoded: ref.image.data,
|
|
89
|
+
mimeType: ref.image.mimeType
|
|
90
|
+
}
|
|
91
|
+
}));
|
|
92
|
+
}
|
|
93
|
+
const parameters = {
|
|
94
|
+
aspectRatio: options.aspectRatio || '16:9',
|
|
95
|
+
resolution: options.resolution || '1080p',
|
|
96
|
+
durationSeconds: options.durationSeconds || 8,
|
|
97
|
+
sampleCount: options.sampleCount || 1
|
|
98
|
+
};
|
|
99
|
+
if (options.seed !== undefined) {
|
|
100
|
+
parameters.seed = options.seed;
|
|
101
|
+
}
|
|
102
|
+
// Handle audio generation via prompt engineering
|
|
103
|
+
// Gemini API (generativelanguage.googleapis.com) doesn't accept generateAudio parameter
|
|
104
|
+
// Instead, audio is native and controlled via negative prompts
|
|
105
|
+
if (options.generateAudio === false) {
|
|
106
|
+
parameters.negativePrompt = 'audio, sound, noise, speech, dialogue, music, sound effects';
|
|
107
|
+
this.logInfo('Audio generation disabled via negativePrompt');
|
|
108
|
+
}
|
|
109
|
+
return {
|
|
110
|
+
instances: [instance],
|
|
111
|
+
parameters
|
|
112
|
+
};
|
|
113
|
+
}
|
|
114
|
+
async pollOperation(operationName) {
|
|
115
|
+
const startTime = Date.now();
|
|
116
|
+
let attempts = 0;
|
|
117
|
+
while (Date.now() - startTime < this.maxPollingTimeMs) {
|
|
118
|
+
attempts++;
|
|
119
|
+
// The operation name from API is like "models/veo-3.1-generate-preview/operations/12345"
|
|
120
|
+
// Since GEMINI_API_BASE already includes /models, we need to strip it to avoid duplication
|
|
121
|
+
let opPath = operationName;
|
|
122
|
+
if (opPath.startsWith('models/')) {
|
|
123
|
+
opPath = opPath.replace('models/', '');
|
|
124
|
+
}
|
|
125
|
+
const url = `${GEMINI_API_BASE}/${opPath}?key=${this.apiKey}`;
|
|
126
|
+
this.logInfo(`Polling video operation (attempt ${attempts})`, {
|
|
127
|
+
elapsed: `${Math.round((Date.now() - startTime) / 1000)}s`,
|
|
128
|
+
maxTime: `${this.maxPollingTimeMs / 1000}s`
|
|
129
|
+
});
|
|
130
|
+
const response = await this.fetchWithRetry(url, undefined, 'poll operation');
|
|
131
|
+
if (!response.ok) {
|
|
132
|
+
const text = await response.text();
|
|
133
|
+
throw new GeminiError(`Failed to poll operation: ${response.status} ${text}`);
|
|
134
|
+
}
|
|
135
|
+
const data = await response.json();
|
|
136
|
+
if (data.error) {
|
|
137
|
+
throw new GeminiError(`Video generation failed: ${data.error.message}`);
|
|
138
|
+
}
|
|
139
|
+
if (data.done) {
|
|
140
|
+
this.logInfo('Video generation completed', {
|
|
141
|
+
attempts,
|
|
142
|
+
totalTime: `${Math.round((Date.now() - startTime) / 1000)}s`
|
|
143
|
+
});
|
|
144
|
+
return data;
|
|
145
|
+
}
|
|
146
|
+
// Log progress if available
|
|
147
|
+
if (data.metadata?.progressPercent !== undefined) {
|
|
148
|
+
this.logInfo(`Video generation progress: ${data.metadata.progressPercent}%`);
|
|
149
|
+
}
|
|
150
|
+
// Wait before next poll
|
|
151
|
+
await new Promise(resolve => setTimeout(resolve, this.pollingIntervalMs));
|
|
152
|
+
}
|
|
153
|
+
throw new GeminiError(`Video generation timed out after ${this.maxPollingTimeMs / 1000}s. ` +
|
|
154
|
+
`Video generation can take 2-5 minutes for 1080p videos.`);
|
|
155
|
+
}
|
|
156
|
+
async downloadVideoFromUri(uri, mimeType, customPath) {
|
|
157
|
+
this.logInfo('Downloading video from URI', { uri: uri.slice(0, 80) });
|
|
158
|
+
// Download video file using authenticated request
|
|
159
|
+
const response = await this.fetchWithRetry(uri, {
|
|
160
|
+
headers: {
|
|
161
|
+
'x-goog-api-key': this.apiKey
|
|
162
|
+
}
|
|
163
|
+
}, 'download video');
|
|
164
|
+
if (!response.ok) {
|
|
165
|
+
const text = await response.text();
|
|
166
|
+
throw new GeminiError(`Failed to download video: ${response.status} ${text}`);
|
|
167
|
+
}
|
|
168
|
+
const arrayBuffer = await response.arrayBuffer();
|
|
169
|
+
const buffer = Buffer.from(arrayBuffer);
|
|
170
|
+
// Ensure output directory exists
|
|
171
|
+
await fs.mkdir(this.outputDir, { recursive: true });
|
|
172
|
+
// Determine file extension from mimeType
|
|
173
|
+
const ext = mimeType.includes('mp4') ? 'mp4' : 'webm';
|
|
174
|
+
// Use custom path if provided, otherwise generate timestamped filename
|
|
175
|
+
const filename = customPath
|
|
176
|
+
? path.basename(customPath)
|
|
177
|
+
: `veo-video-${Date.now()}.${ext}`;
|
|
178
|
+
const fullPath = customPath || path.join(this.outputDir, filename);
|
|
179
|
+
// Write buffer to file
|
|
180
|
+
await fs.writeFile(fullPath, buffer);
|
|
181
|
+
this.logInfo('Video file saved', {
|
|
182
|
+
path: fullPath,
|
|
183
|
+
size: `${Math.round(buffer.length / 1024 / 1024)}MB`
|
|
184
|
+
});
|
|
185
|
+
return path.resolve(fullPath);
|
|
186
|
+
}
|
|
187
|
+
async saveVideoFile(base64Data, mimeType, customPath) {
|
|
188
|
+
// Ensure output directory exists
|
|
189
|
+
await fs.mkdir(this.outputDir, { recursive: true });
|
|
190
|
+
// Determine file extension from mimeType
|
|
191
|
+
const ext = mimeType.includes('mp4') ? 'mp4' : 'webm';
|
|
192
|
+
// Use custom path if provided, otherwise generate timestamped filename
|
|
193
|
+
const filename = customPath
|
|
194
|
+
? path.basename(customPath)
|
|
195
|
+
: `veo-video-${Date.now()}.${ext}`;
|
|
196
|
+
const fullPath = customPath || path.join(this.outputDir, filename);
|
|
197
|
+
// Convert base64 to buffer and write to file
|
|
198
|
+
const buffer = Buffer.from(base64Data, 'base64');
|
|
199
|
+
await fs.writeFile(fullPath, buffer);
|
|
200
|
+
this.logInfo('Video file saved', {
|
|
201
|
+
path: fullPath,
|
|
202
|
+
size: `${Math.round(buffer.length / 1024 / 1024)}MB`
|
|
203
|
+
});
|
|
204
|
+
return path.resolve(fullPath);
|
|
205
|
+
}
|
|
206
|
+
async generateVideo(options) {
|
|
207
|
+
const model = options.model || this.defaultModel;
|
|
208
|
+
this.validateModel(model);
|
|
209
|
+
this.logInfo('Starting video generation', {
|
|
210
|
+
model,
|
|
211
|
+
prompt: options.prompt.slice(0, 80),
|
|
212
|
+
duration: options.durationSeconds || 8,
|
|
213
|
+
resolution: options.resolution || '1080p',
|
|
214
|
+
aspectRatio: options.aspectRatio || '16:9',
|
|
215
|
+
hasFirstFrame: !!options.firstFrameImage,
|
|
216
|
+
referenceImageCount: options.referenceImages?.length || 0
|
|
217
|
+
});
|
|
218
|
+
// Build request body using Vertex AI format
|
|
219
|
+
const requestBody = this.buildVeoRequest(options);
|
|
220
|
+
// Submit async job
|
|
221
|
+
const submitUrl = `${GEMINI_API_BASE}/${model}:predictLongRunning?key=${this.apiKey}`;
|
|
222
|
+
const submitResponse = await this.fetchWithRetry(submitUrl, {
|
|
223
|
+
method: 'POST',
|
|
224
|
+
headers: { 'Content-Type': 'application/json' },
|
|
225
|
+
body: JSON.stringify(requestBody)
|
|
226
|
+
}, 'submit video');
|
|
227
|
+
if (!submitResponse.ok) {
|
|
228
|
+
const text = await submitResponse.text();
|
|
229
|
+
throw new GeminiError(`Failed to submit video generation: ${submitResponse.status} ${text}`);
|
|
230
|
+
}
|
|
231
|
+
const submitData = await submitResponse.json();
|
|
232
|
+
if (!submitData.name) {
|
|
233
|
+
throw new GeminiError('No operation name returned from video generation submission');
|
|
234
|
+
}
|
|
235
|
+
this.logInfo('Video generation job submitted', {
|
|
236
|
+
operationName: submitData.name
|
|
237
|
+
});
|
|
238
|
+
// Poll for completion
|
|
239
|
+
const completedOperation = await this.pollOperation(submitData.name);
|
|
240
|
+
// Extract video data from response
|
|
241
|
+
// Gemini API returns: response.generateVideoResponse.generatedSamples[0].video.uri
|
|
242
|
+
// Vertex AI returns: response.predictions[0].bytesBase64Encoded
|
|
243
|
+
const response = completedOperation.response;
|
|
244
|
+
if (!response) {
|
|
245
|
+
throw new GeminiError('No response in completed operation');
|
|
246
|
+
}
|
|
247
|
+
// Debug: Log the actual response structure
|
|
248
|
+
this.logInfo('Completed operation response structure:', {
|
|
249
|
+
hasGenerateVideoResponse: !!response.generateVideoResponse,
|
|
250
|
+
hasPredictions: !!response.predictions,
|
|
251
|
+
hasVideos: !!response.videos,
|
|
252
|
+
hasGeneratedSamples: !!response.generatedSamples,
|
|
253
|
+
responseKeys: Object.keys(response),
|
|
254
|
+
fullResponse: JSON.stringify(response, null, 2).slice(0, 500)
|
|
255
|
+
});
|
|
256
|
+
// Try Gemini API format first (URI-based)
|
|
257
|
+
const geminiSample = response.generateVideoResponse?.generatedSamples?.[0];
|
|
258
|
+
const geminiVideo = geminiSample?.video;
|
|
259
|
+
// Try Vertex AI format (base64-based)
|
|
260
|
+
const vertexPrediction = response.predictions?.[0];
|
|
261
|
+
const vertexVideo = response.videos?.[0];
|
|
262
|
+
const altSample = response.generatedSamples?.[0];
|
|
263
|
+
// Handle both URI-based (Gemini API) and base64-based (Vertex AI) responses
|
|
264
|
+
let videoPath;
|
|
265
|
+
let mimeType = 'video/mp4'; // Default mimeType
|
|
266
|
+
if (geminiVideo?.uri) {
|
|
267
|
+
// Gemini API format - download from URI (mimeType often not included, default to mp4)
|
|
268
|
+
this.logInfo('Using Gemini API format (URI-based download)');
|
|
269
|
+
mimeType = geminiVideo.mimeType || 'video/mp4';
|
|
270
|
+
videoPath = await this.downloadVideoFromUri(geminiVideo.uri, mimeType, options.outputPath);
|
|
271
|
+
}
|
|
272
|
+
else if (vertexPrediction?.bytesBase64Encoded && vertexPrediction.mimeType) {
|
|
273
|
+
// Vertex AI predictions format
|
|
274
|
+
this.logInfo('Using Vertex AI predictions format (base64)');
|
|
275
|
+
mimeType = vertexPrediction.mimeType;
|
|
276
|
+
videoPath = await this.saveVideoFile(vertexPrediction.bytesBase64Encoded, mimeType, options.outputPath);
|
|
277
|
+
}
|
|
278
|
+
else if (vertexVideo?.bytesBase64Encoded && vertexVideo.mimeType) {
|
|
279
|
+
// Vertex AI videos format
|
|
280
|
+
this.logInfo('Using Vertex AI videos format (base64)');
|
|
281
|
+
mimeType = vertexVideo.mimeType;
|
|
282
|
+
videoPath = await this.saveVideoFile(vertexVideo.bytesBase64Encoded, mimeType, options.outputPath);
|
|
283
|
+
}
|
|
284
|
+
else if (altSample?.bytesBase64Encoded && altSample.mimeType) {
|
|
285
|
+
// Alternative generatedSamples format
|
|
286
|
+
this.logInfo('Using generatedSamples format (base64)');
|
|
287
|
+
mimeType = altSample.mimeType;
|
|
288
|
+
videoPath = await this.saveVideoFile(altSample.bytesBase64Encoded, altSample.mimeType, options.outputPath);
|
|
289
|
+
}
|
|
290
|
+
else {
|
|
291
|
+
// Provide detailed error with actual response structure for debugging
|
|
292
|
+
const debugInfo = {
|
|
293
|
+
hasGenerateVideoResponse: !!response.generateVideoResponse,
|
|
294
|
+
hasPredictions: !!response.predictions,
|
|
295
|
+
hasVideos: !!response.videos,
|
|
296
|
+
hasGeneratedSamples: !!response.generatedSamples,
|
|
297
|
+
responseKeys: Object.keys(response),
|
|
298
|
+
sampleStructure: JSON.stringify({
|
|
299
|
+
geminiSample,
|
|
300
|
+
geminiVideo,
|
|
301
|
+
vertexPrediction,
|
|
302
|
+
vertexVideo,
|
|
303
|
+
altSample
|
|
304
|
+
}, null, 2)
|
|
305
|
+
};
|
|
306
|
+
throw new GeminiError(`Video response contains neither URI nor base64 data in any recognized format.\n` +
|
|
307
|
+
`Debug info: ${JSON.stringify(debugInfo, null, 2)}\n` +
|
|
308
|
+
`Full response (first 1000 chars): ${JSON.stringify(response, null, 2).slice(0, 1000)}`);
|
|
309
|
+
}
|
|
310
|
+
return {
|
|
311
|
+
videoPath,
|
|
312
|
+
mimeType,
|
|
313
|
+
duration: options.durationSeconds || 8,
|
|
314
|
+
resolution: options.resolution || '1080p',
|
|
315
|
+
aspectRatio: options.aspectRatio || '16:9'
|
|
316
|
+
};
|
|
317
|
+
}
|
|
318
|
+
getDefaultModel() {
|
|
319
|
+
return this.defaultModel;
|
|
320
|
+
}
|
|
321
|
+
getAllowedModels() {
|
|
322
|
+
return VIDEO_GENERATION_MODELS;
|
|
323
|
+
}
|
|
324
|
+
}
|
|
325
|
+
//# sourceMappingURL=video-service.js.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"video-service.js","sourceRoot":"","sources":["../../../src/services/gemini/video-service.ts"],"names":[],"mappings":"AACA,OAAO,EAAE,WAAW,EAAE,MAAM,8BAA8B,CAAC;AAC3D,OAAO,EAAE,WAAW,EAAE,MAAM,oBAAoB,CAAC;AACjD,OAAO,EAAE,MAAM,aAAa,CAAC;AAC7B,OAAO,IAAI,MAAM,MAAM,CAAC;AAExB,MAAM,eAAe,GAAG,yDAAyD,CAAC;AAElF,MAAM,CAAC,MAAM,uBAAuB,GAAG;IACrC,0BAA0B;CAClB,CAAC;AAGX,MAAM,mBAAmB,GAAe,0BAA0B,CAAC;AA+GnE,gFAAgF;AAChF,MAAM,eAAe,GAAG,CAAC,CAAC;AAC1B,MAAM,kBAAkB,GAAG,MAAM,CAAC,CAAC,aAAa;AAEhD,gFAAgF;AAEhF,MAAM,OAAO,kBAAmB,SAAQ,WAAW;IACzC,MAAM,CAAS;IACf,YAAY,CAAS;IACrB,SAAS,CAAS;IAClB,iBAAiB,CAAS;IAC1B,gBAAgB,CAAS;IAEjC,YAAY,MAAoB,EAAE,SAAkB;QAClD,KAAK,EAAE,CAAC;QACR,IAAI,CAAC,MAAM,CAAC,MAAM,EAAE,CAAC;YACnB,MAAM,IAAI,WAAW,CAAC,0CAA0C,CAAC,CAAC;QACpE,CAAC;QACD,IAAI,CAAC,MAAM,GAAG,MAAM,CAAC,MAAM,CAAC;QAC5B,IAAI,CAAC,YAAY,GAAG,mBAAmB,CAAC;QACxC,IAAI,CAAC,SAAS,GAAG,SAAS,IAAI,UAAU,CAAC;QAEzC,uEAAuE;QACvE,6CAA6C;QAC7C,IAAI,CAAC,iBAAiB,GAAG,KAAK,CAAC;QAC/B,8CAA8C;QAC9C,IAAI,CAAC,gBAAgB,GAAG,MAAM,CAAC;QAE/B,IAAI,CAAC,OAAO,CAAC,kCAAkC,EAAE;YAC/C,SAAS,EAAE,IAAI,CAAC,SAAS;YACzB,eAAe,EAAE,GAAG,IAAI,CAAC,iBAAiB,GAAG,IAAI,GAAG;YACpD,cAAc,EAAE,GAAG,IAAI,CAAC,gBAAgB,GAAG,IAAI,GAAG;SACnD,CAAC,CAAC;IACL,CAAC;IAED;;;OAGG;IACK,KAAK,CAAC,cAAc,CAC1B,GAAW,EACX,IAAkB,EAClB,KAAK,GAAG,SAAS;QAEjB,IAAI,SAA4B,CAAC;QAEjC,KAAK,IAAI,OAAO,GAAG,CAAC,EAAE,OAAO,IAAI,eAAe,EAAE,OAAO,EAAE,EAAE,CAAC;YAC5D,MAAM,QAAQ,GAAG,MAAM,KAAK,CAAC,GAAG,EAAE,IAAI,CAAC,CAAC;YAExC,IAAI,QAAQ,CAAC,MAAM,KAAK,GAAG,EAAE,CAAC;gBAC5B,OAAO,QAAQ,CAAC;YAClB,CAAC;YAED,mBAAmB;YACnB,SAAS,GAAG,IAAI,WAAW,CACzB,kEAAkE;gBAClE,4CAA4C,CAC7C,CAAC;YAEF,IAAI,OAAO,GAAG,eAAe,EAAE,CAAC;gBAC9B,MAAM,SAAS,GAAG,kBAAkB,GAAG,IAAI,CAAC,GAAG,CAAC,CAAC,EAAE,OAAO,CAAC,CAAC;gBAC5D,IAAI,CAAC,OAAO,CAAC,yBAAyB,KAAK,iBAAiB,SAAS,GAAG,IAAI,GAAG,EAAE;oBAC/E,OAAO,EAAE,OAAO,GAAG,CAAC;oBACpB,UAAU,EAAE,eAAe;iBAC5B,CAAC,CAAC;gBACH,MAAM,IAAI,OAAO,CAAC,OAAO,CAAC,EAAE,CAAC,UAAU,CAAC,OAAO,EAAE,SAAS,CAAC,CAAC,CAAC;YAC/D,CAAC;QACH,CAAC;QAED,MAAM,SAAU,CAAC;IACnB,CAAC;IAEO,aAAa,CAAC,KAAa;QACjC,IAAI,CAAC,uBAAuB,CAAC,QAAQ,CAAC,KAAmB,CAAC,EAAE,CAAC;YAC3D,MAAM,IAAI,WAAW,CACnB,wBAAwB,KAAK,cAAc,uBAAuB,CAAC,IAAI,CAAC,IAAI,CAAC,EAAE,CAChF,CAAC;QACJ,CAAC;IACH,CAAC;IAEO,eAAe,CAAC,OAA6B;QACnD,MAAM,QAAQ,GAAgB;YAC5B,MAAM,EAAE,OAAO,CAAC,MAAM;SACvB,CAAC;QAEF,qDAAqD;QACrD,IAAI,OAAO,CAAC,eAAe,EAAE,CAAC;YAC5B,QAAQ,CAAC,KAAK,GAAG;gBACf,kBAAkB,EAAE,OAAO,CAAC,eAAe,CAAC,IAAI;gBAChD,QAAQ,EAAE,OAAO,CAAC,eAAe,CAAC,QAAQ;aAC3C,CAAC;QACJ,CAAC;QAED,6EAA6E;QAC7E,IAAI,OAAO,CAAC,eAAe,EAAE,MAAM,EAAE,CAAC;YACpC,IAAI,OAAO,CAAC,eAAe,CAAC,MAAM,GAAG,CAAC,EAAE,CAAC;gBACvC,MAAM,IAAI,WAAW,CAAC,oCAAoC,CAAC,CAAC;YAC9D,CAAC;YAED,QAAQ,CAAC,eAAe,GAAG,OAAO,CAAC,eAAe,CAAC,GAAG,CAAC,GAAG,CAAC,EAAE,CAAC,CAAC;gBAC7D,aAAa,EAAE,GAAG,CAAC,aAAa;gBAChC,cAAc,EAAE;oBACd,kBAAkB,EAAE,GAAG,CAAC,KAAK,CAAC,IAAI;oBAClC,QAAQ,EAAE,GAAG,CAAC,KAAK,CAAC,QAAQ;iBAC7B;aACF,CAAC,CAAC,CAAC;QACN,CAAC;QAED,MAAM,UAAU,GAAkB;YAChC,WAAW,EAAE,OAAO,CAAC,WAAW,IAAI,MAAM;YAC1C,UAAU,EAAE,OAAO,CAAC,UAAU,IAAI,OAAO;YACzC,eAAe,EAAE,OAAO,CAAC,eAAe,IAAI,CAAC;YAC7C,WAAW,EAAE,OAAO,CAAC,WAAW,IAAI,CAAC;SACtC,CAAC;QAEF,IAAI,OAAO,CAAC,IAAI,KAAK,SAAS,EAAE,CAAC;YAC/B,UAAU,CAAC,IAAI,GAAG,OAAO,CAAC,IAAI,CAAC;QACjC,CAAC;QAED,iDAAiD;QACjD,wFAAwF;QACxF,+DAA+D;QAC/D,IAAI,OAAO,CAAC,aAAa,KAAK,KAAK,EAAE,CAAC;YACpC,UAAU,CAAC,cAAc,GAAG,6DAA6D,CAAC;YAC1F,IAAI,CAAC,OAAO,CAAC,8CAA8C,CAAC,CAAC;QAC/D,CAAC;QAED,OAAO;YACL,SAAS,EAAE,CAAC,QAAQ,CAAC;YACrB,UAAU;SACX,CAAC;IACJ,CAAC;IAEO,KAAK,CAAC,aAAa,CAAC,aAAqB;QAC/C,MAAM,SAAS,GAAG,IAAI,CAAC,GAAG,EAAE,CAAC;QAC7B,IAAI,QAAQ,GAAG,CAAC,CAAC;QAEjB,OAAO,IAAI,CAAC,GAAG,EAAE,GAAG,SAAS,GAAG,IAAI,CAAC,gBAAgB,EAAE,CAAC;YACtD,QAAQ,EAAE,CAAC;YAEX,yFAAyF;YACzF,2FAA2F;YAC3F,IAAI,MAAM,GAAG,aAAa,CAAC;YAC3B,IAAI,MAAM,CAAC,UAAU,CAAC,SAAS,CAAC,EAAE,CAAC;gBACjC,MAAM,GAAG,MAAM,CAAC,OAAO,CAAC,SAAS,EAAE,EAAE,CAAC,CAAC;YACzC,CAAC;YAED,MAAM,GAAG,GAAG,GAAG,eAAe,IAAI,MAAM,QAAQ,IAAI,CAAC,MAAM,EAAE,CAAC;YAE9D,IAAI,CAAC,OAAO,CAAC,oCAAoC,QAAQ,GAAG,EAAE;gBAC5D,OAAO,EAAE,GAAG,IAAI,CAAC,KAAK,CAAC,CAAC,IAAI,CAAC,GAAG,EAAE,GAAG,SAAS,CAAC,GAAG,IAAI,CAAC,GAAG;gBAC1D,OAAO,EAAE,GAAG,IAAI,CAAC,gBAAgB,GAAG,IAAI,GAAG;aAC5C,CAAC,CAAC;YAEH,MAAM,QAAQ,GAAG,MAAM,IAAI,CAAC,cAAc,CAAC,GAAG,EAAE,SAAS,EAAE,gBAAgB,CAAC,CAAC;YAE7E,IAAI,CAAC,QAAQ,CAAC,EAAE,EAAE,CAAC;gBACjB,MAAM,IAAI,GAAG,MAAM,QAAQ,CAAC,IAAI,EAAE,CAAC;gBACnC,MAAM,IAAI,WAAW,CAAC,6BAA6B,QAAQ,CAAC,MAAM,IAAI,IAAI,EAAE,CAAC,CAAC;YAChF,CAAC;YAED,MAAM,IAAI,GAAG,MAAM,QAAQ,CAAC,IAAI,EAA0B,CAAC;YAE3D,IAAI,IAAI,CAAC,KAAK,EAAE,CAAC;gBACf,MAAM,IAAI,WAAW,CAAC,4BAA4B,IAAI,CAAC,KAAK,CAAC,OAAO,EAAE,CAAC,CAAC;YAC1E,CAAC;YAED,IAAI,IAAI,CAAC,IAAI,EAAE,CAAC;gBACd,IAAI,CAAC,OAAO,CAAC,4BAA4B,EAAE;oBACzC,QAAQ;oBACR,SAAS,EAAE,GAAG,IAAI,CAAC,KAAK,CAAC,CAAC,IAAI,CAAC,GAAG,EAAE,GAAG,SAAS,CAAC,GAAG,IAAI,CAAC,GAAG;iBAC7D,CAAC,CAAC;gBACH,OAAO,IAAI,CAAC;YACd,CAAC;YAED,4BAA4B;YAC5B,IAAI,IAAI,CAAC,QAAQ,EAAE,eAAe,KAAK,SAAS,EAAE,CAAC;gBACjD,IAAI,CAAC,OAAO,CAAC,8BAA8B,IAAI,CAAC,QAAQ,CAAC,eAAe,GAAG,CAAC,CAAC;YAC/E,CAAC;YAED,wBAAwB;YACxB,MAAM,IAAI,OAAO,CAAC,OAAO,CAAC,EAAE,CAAC,UAAU,CAAC,OAAO,EAAE,IAAI,CAAC,iBAAiB,CAAC,CAAC,CAAC;QAC5E,CAAC;QAED,MAAM,IAAI,WAAW,CACnB,oCAAoC,IAAI,CAAC,gBAAgB,GAAG,IAAI,KAAK;YACrE,yDAAyD,CAC1D,CAAC;IACJ,CAAC;IAEO,KAAK,CAAC,oBAAoB,CAAC,GAAW,EAAE,QAAgB,EAAE,UAAmB;QACnF,IAAI,CAAC,OAAO,CAAC,4BAA4B,EAAE,EAAE,GAAG,EAAE,GAAG,CAAC,KAAK,CAAC,CAAC,EAAE,EAAE,CAAC,EAAE,CAAC,CAAC;QAEtE,kDAAkD;QAClD,MAAM,QAAQ,GAAG,MAAM,IAAI,CAAC,cAAc,CAAC,GAAG,EAAE;YAC9C,OAAO,EAAE;gBACP,gBAAgB,EAAE,IAAI,CAAC,MAAM;aAC9B;SACF,EAAE,gBAAgB,CAAC,CAAC;QAErB,IAAI,CAAC,QAAQ,CAAC,EAAE,EAAE,CAAC;YACjB,MAAM,IAAI,GAAG,MAAM,QAAQ,CAAC,IAAI,EAAE,CAAC;YACnC,MAAM,IAAI,WAAW,CAAC,6BAA6B,QAAQ,CAAC,MAAM,IAAI,IAAI,EAAE,CAAC,CAAC;QAChF,CAAC;QAED,MAAM,WAAW,GAAG,MAAM,QAAQ,CAAC,WAAW,EAAE,CAAC;QACjD,MAAM,MAAM,GAAG,MAAM,CAAC,IAAI,CAAC,WAAW,CAAC,CAAC;QAExC,iCAAiC;QACjC,MAAM,EAAE,CAAC,KAAK,CAAC,IAAI,CAAC,SAAS,EAAE,EAAE,SAAS,EAAE,IAAI,EAAE,CAAC,CAAC;QAEpD,yCAAyC;QACzC,MAAM,GAAG,GAAG,QAAQ,CAAC,QAAQ,CAAC,KAAK,CAAC,CAAC,CAAC,CAAC,KAAK,CAAC,CAAC,CAAC,MAAM,CAAC;QAEtD,uEAAuE;QACvE,MAAM,QAAQ,GAAG,UAAU;YACzB,CAAC,CAAC,IAAI,CAAC,QAAQ,CAAC,UAAU,CAAC;YAC3B,CAAC,CAAC,aAAa,IAAI,CAAC,GAAG,EAAE,IAAI,GAAG,EAAE,CAAC;QAErC,MAAM,QAAQ,GAAG,UAAU,IAAI,IAAI,CAAC,IAAI,CAAC,IAAI,CAAC,SAAS,EAAE,QAAQ,CAAC,CAAC;QAEnE,uBAAuB;QACvB,MAAM,EAAE,CAAC,SAAS,CAAC,QAAQ,EAAE,MAAM,CAAC,CAAC;QAErC,IAAI,CAAC,OAAO,CAAC,kBAAkB,EAAE;YAC/B,IAAI,EAAE,QAAQ;YACd,IAAI,EAAE,GAAG,IAAI,CAAC,KAAK,CAAC,MAAM,CAAC,MAAM,GAAG,IAAI,GAAG,IAAI,CAAC,IAAI;SACrD,CAAC,CAAC;QAEH,OAAO,IAAI,CAAC,OAAO,CAAC,QAAQ,CAAC,CAAC;IAChC,CAAC;IAEO,KAAK,CAAC,aAAa,CACzB,UAAkB,EAClB,QAAgB,EAChB,UAAmB;QAEnB,iCAAiC;QACjC,MAAM,EAAE,CAAC,KAAK,CAAC,IAAI,CAAC,SAAS,EAAE,EAAE,SAAS,EAAE,IAAI,EAAE,CAAC,CAAC;QAEpD,yCAAyC;QACzC,MAAM,GAAG,GAAG,QAAQ,CAAC,QAAQ,CAAC,KAAK,CAAC,CAAC,CAAC,CAAC,KAAK,CAAC,CAAC,CAAC,MAAM,CAAC;QAEtD,uEAAuE;QACvE,MAAM,QAAQ,GAAG,UAAU;YACzB,CAAC,CAAC,IAAI,CAAC,QAAQ,CAAC,UAAU,CAAC;YAC3B,CAAC,CAAC,aAAa,IAAI,CAAC,GAAG,EAAE,IAAI,GAAG,EAAE,CAAC;QAErC,MAAM,QAAQ,GAAG,UAAU,IAAI,IAAI,CAAC,IAAI,CAAC,IAAI,CAAC,SAAS,EAAE,QAAQ,CAAC,CAAC;QAEnE,6CAA6C;QAC7C,MAAM,MAAM,GAAG,MAAM,CAAC,IAAI,CAAC,UAAU,EAAE,QAAQ,CAAC,CAAC;QACjD,MAAM,EAAE,CAAC,SAAS,CAAC,QAAQ,EAAE,MAAM,CAAC,CAAC;QAErC,IAAI,CAAC,OAAO,CAAC,kBAAkB,EAAE;YAC/B,IAAI,EAAE,QAAQ;YACd,IAAI,EAAE,GAAG,IAAI,CAAC,KAAK,CAAC,MAAM,CAAC,MAAM,GAAG,IAAI,GAAG,IAAI,CAAC,IAAI;SACrD,CAAC,CAAC;QAEH,OAAO,IAAI,CAAC,OAAO,CAAC,QAAQ,CAAC,CAAC;IAChC,CAAC;IAED,KAAK,CAAC,aAAa,CAAC,OAA6B;QAC/C,MAAM,KAAK,GAAG,OAAO,CAAC,KAAK,IAAI,IAAI,CAAC,YAAY,CAAC;QACjD,IAAI,CAAC,aAAa,CAAC,KAAK,CAAC,CAAC;QAE1B,IAAI,CAAC,OAAO,CAAC,2BAA2B,EAAE;YACxC,KAAK;YACL,MAAM,EAAE,OAAO,CAAC,MAAM,CAAC,KAAK,CAAC,CAAC,EAAE,EAAE,CAAC;YACnC,QAAQ,EAAE,OAAO,CAAC,eAAe,IAAI,CAAC;YACtC,UAAU,EAAE,OAAO,CAAC,UAAU,IAAI,OAAO;YACzC,WAAW,EAAE,OAAO,CAAC,WAAW,IAAI,MAAM;YAC1C,aAAa,EAAE,CAAC,CAAC,OAAO,CAAC,eAAe;YACxC,mBAAmB,EAAE,OAAO,CAAC,eAAe,EAAE,MAAM,IAAI,CAAC;SAC1D,CAAC,CAAC;QAEH,4CAA4C;QAC5C,MAAM,WAAW,GAAG,IAAI,CAAC,eAAe,CAAC,OAAO,CAAC,CAAC;QAElD,mBAAmB;QACnB,MAAM,SAAS,GAAG,GAAG,eAAe,IAAI,KAAK,2BAA2B,IAAI,CAAC,MAAM,EAAE,CAAC;QAEtF,MAAM,cAAc,GAAG,MAAM,IAAI,CAAC,cAAc,CAAC,SAAS,EAAE;YAC1D,MAAM,EAAE,MAAM;YACd,OAAO,EAAE,EAAE,cAAc,EAAE,kBAAkB,EAAE;YAC/C,IAAI,EAAE,IAAI,CAAC,SAAS,CAAC,WAAW,CAAC;SAClC,EAAE,cAAc,CAAC,CAAC;QAEnB,IAAI,CAAC,cAAc,CAAC,EAAE,EAAE,CAAC;YACvB,MAAM,IAAI,GAAG,MAAM,cAAc,CAAC,IAAI,EAAE,CAAC;YACzC,MAAM,IAAI,WAAW,CAAC,sCAAsC,cAAc,CAAC,MAAM,IAAI,IAAI,EAAE,CAAC,CAAC;QAC/F,CAAC;QAED,MAAM,UAAU,GAAG,MAAM,cAAc,CAAC,IAAI,EAA0B,CAAC;QAEvE,IAAI,CAAC,UAAU,CAAC,IAAI,EAAE,CAAC;YACrB,MAAM,IAAI,WAAW,CAAC,6DAA6D,CAAC,CAAC;QACvF,CAAC;QAED,IAAI,CAAC,OAAO,CAAC,gCAAgC,EAAE;YAC7C,aAAa,EAAE,UAAU,CAAC,IAAI;SAC/B,CAAC,CAAC;QAEH,sBAAsB;QACtB,MAAM,kBAAkB,GAAG,MAAM,IAAI,CAAC,aAAa,CAAC,UAAU,CAAC,IAAI,CAAC,CAAC;QAErE,mCAAmC;QACnC,mFAAmF;QACnF,gEAAgE;QAChE,MAAM,QAAQ,GAAG,kBAAkB,CAAC,QAAQ,CAAC;QAC7C,IAAI,CAAC,QAAQ,EAAE,CAAC;YACd,MAAM,IAAI,WAAW,CAAC,oCAAoC,CAAC,CAAC;QAC9D,CAAC;QAED,2CAA2C;QAC3C,IAAI,CAAC,OAAO,CAAC,yCAAyC,EAAE;YACtD,wBAAwB,EAAE,CAAC,CAAC,QAAQ,CAAC,qBAAqB;YAC1D,cAAc,EAAE,CAAC,CAAC,QAAQ,CAAC,WAAW;YACtC,SAAS,EAAE,CAAC,CAAC,QAAQ,CAAC,MAAM;YAC5B,mBAAmB,EAAE,CAAC,CAAC,QAAQ,CAAC,gBAAgB;YAChD,YAAY,EAAE,MAAM,CAAC,IAAI,CAAC,QAAQ,CAAC;YACnC,YAAY,EAAE,IAAI,CAAC,SAAS,CAAC,QAAQ,EAAE,IAAI,EAAE,CAAC,CAAC,CAAC,KAAK,CAAC,CAAC,EAAE,GAAG,CAAC;SAC9D,CAAC,CAAC;QAEH,0CAA0C;QAC1C,MAAM,YAAY,GAAG,QAAQ,CAAC,qBAAqB,EAAE,gBAAgB,EAAE,CAAC,CAAC,CAAC,CAAC;QAC3E,MAAM,WAAW,GAAG,YAAY,EAAE,KAAK,CAAC;QAExC,sCAAsC;QACtC,MAAM,gBAAgB,GAAG,QAAQ,CAAC,WAAW,EAAE,CAAC,CAAC,CAAC,CAAC;QACnD,MAAM,WAAW,GAAG,QAAQ,CAAC,MAAM,EAAE,CAAC,CAAC,CAAC,CAAC;QACzC,MAAM,SAAS,GAAG,QAAQ,CAAC,gBAAgB,EAAE,CAAC,CAAC,CAAC,CAAC;QAEjD,4EAA4E;QAC5E,IAAI,SAAiB,CAAC;QACtB,IAAI,QAAQ,GAAG,WAAW,CAAC,CAAC,mBAAmB;QAE/C,IAAI,WAAW,EAAE,GAAG,EAAE,CAAC;YACrB,sFAAsF;YACtF,IAAI,CAAC,OAAO,CAAC,8CAA8C,CAAC,CAAC;YAC7D,QAAQ,GAAG,WAAW,CAAC,QAAQ,IAAI,WAAW,CAAC;YAC/C,SAAS,GAAG,MAAM,IAAI,CAAC,oBAAoB,CAAC,WAAW,CAAC,GAAG,EAAE,QAAQ,EAAE,OAAO,CAAC,UAAU,CAAC,CAAC;QAC7F,CAAC;aAAM,IAAI,gBAAgB,EAAE,kBAAkB,IAAI,gBAAgB,CAAC,QAAQ,EAAE,CAAC;YAC7E,+BAA+B;YAC/B,IAAI,CAAC,OAAO,CAAC,6CAA6C,CAAC,CAAC;YAC5D,QAAQ,GAAG,gBAAgB,CAAC,QAAQ,CAAC;YACrC,SAAS,GAAG,MAAM,IAAI,CAAC,aAAa,CAAC,gBAAgB,CAAC,kBAAkB,EAAE,QAAQ,EAAE,OAAO,CAAC,UAAU,CAAC,CAAC;QAC1G,CAAC;aAAM,IAAI,WAAW,EAAE,kBAAkB,IAAI,WAAW,CAAC,QAAQ,EAAE,CAAC;YACnE,0BAA0B;YAC1B,IAAI,CAAC,OAAO,CAAC,wCAAwC,CAAC,CAAC;YACvD,QAAQ,GAAG,WAAW,CAAC,QAAQ,CAAC;YAChC,SAAS,GAAG,MAAM,IAAI,CAAC,aAAa,CAAC,WAAW,CAAC,kBAAkB,EAAE,QAAQ,EAAE,OAAO,CAAC,UAAU,CAAC,CAAC;QACrG,CAAC;aAAM,IAAI,SAAS,EAAE,kBAAkB,IAAI,SAAS,CAAC,QAAQ,EAAE,CAAC;YAC/D,sCAAsC;YACtC,IAAI,CAAC,OAAO,CAAC,wCAAwC,CAAC,CAAC;YACvD,QAAQ,GAAG,SAAS,CAAC,QAAQ,CAAC;YAC9B,SAAS,GAAG,MAAM,IAAI,CAAC,aAAa,CAAC,SAAS,CAAC,kBAAkB,EAAE,SAAS,CAAC,QAAQ,EAAE,OAAO,CAAC,UAAU,CAAC,CAAC;QAC7G,CAAC;aAAM,CAAC;YACN,sEAAsE;YACtE,MAAM,SAAS,GAAG;gBAChB,wBAAwB,EAAE,CAAC,CAAC,QAAQ,CAAC,qBAAqB;gBAC1D,cAAc,EAAE,CAAC,CAAC,QAAQ,CAAC,WAAW;gBACtC,SAAS,EAAE,CAAC,CAAC,QAAQ,CAAC,MAAM;gBAC5B,mBAAmB,EAAE,CAAC,CAAC,QAAQ,CAAC,gBAAgB;gBAChD,YAAY,EAAE,MAAM,CAAC,IAAI,CAAC,QAAQ,CAAC;gBACnC,eAAe,EAAE,IAAI,CAAC,SAAS,CAAC;oBAC9B,YAAY;oBACZ,WAAW;oBACX,gBAAgB;oBAChB,WAAW;oBACX,SAAS;iBACV,EAAE,IAAI,EAAE,CAAC,CAAC;aACZ,CAAC;YACF,MAAM,IAAI,WAAW,CACnB,iFAAiF;gBACjF,eAAe,IAAI,CAAC,SAAS,CAAC,SAAS,EAAE,IAAI,EAAE,CAAC,CAAC,IAAI;gBACrD,qCAAqC,IAAI,CAAC,SAAS,CAAC,QAAQ,EAAE,IAAI,EAAE,CAAC,CAAC,CAAC,KAAK,CAAC,CAAC,EAAE,IAAI,CAAC,EAAE,CACxF,CAAC;QACJ,CAAC;QAED,OAAO;YACL,SAAS;YACT,QAAQ;YACR,QAAQ,EAAE,OAAO,CAAC,eAAe,IAAI,CAAC;YACtC,UAAU,EAAE,OAAO,CAAC,UAAU,IAAI,OAAO;YACzC,WAAW,EAAE,OAAO,CAAC,WAAW,IAAI,MAAM;SAC3C,CAAC;IACJ,CAAC;IAED,eAAe;QACb,OAAO,IAAI,CAAC,YAAY,CAAC;IAC3B,CAAC;IAED,gBAAgB;QACd,OAAO,uBAAuB,CAAC;IACjC,CAAC;CACF"}
|
|
@@ -0,0 +1,28 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* Lightweight localhost-only HTTP server for serving generated media files
|
|
3
|
+
* to the MCP App viewer iframes (which cannot access file:// URIs).
|
|
4
|
+
*
|
|
5
|
+
* Security:
|
|
6
|
+
* - Binds to 127.0.0.1 only (no external access)
|
|
7
|
+
* - Random URL token required in path
|
|
8
|
+
* - Path traversal protection (all paths resolved against allowed directory)
|
|
9
|
+
* - Supports HTTP Range requests for video seeking
|
|
10
|
+
*/
|
|
11
|
+
export declare class MediaServer {
|
|
12
|
+
private server;
|
|
13
|
+
private port;
|
|
14
|
+
private token;
|
|
15
|
+
private allowedDir;
|
|
16
|
+
constructor(allowedDir: string);
|
|
17
|
+
start(): Promise<void>;
|
|
18
|
+
getPort(): number;
|
|
19
|
+
getBaseUrl(): string;
|
|
20
|
+
/**
|
|
21
|
+
* Convert an absolute file path to a media server URL.
|
|
22
|
+
* Returns null if the path is outside the allowed directory.
|
|
23
|
+
*/
|
|
24
|
+
getFileUrl(absPath: string): string | null;
|
|
25
|
+
stop(): void;
|
|
26
|
+
private handleRequest;
|
|
27
|
+
}
|
|
28
|
+
//# sourceMappingURL=media-server.d.ts.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"media-server.d.ts","sourceRoot":"","sources":["../../src/services/media-server.ts"],"names":[],"mappings":"AAqBA;;;;;;;;;GASG;AACH,qBAAa,WAAW;IACtB,OAAO,CAAC,MAAM,CAAuB;IACrC,OAAO,CAAC,IAAI,CAAK;IACjB,OAAO,CAAC,KAAK,CAAS;IACtB,OAAO,CAAC,UAAU,CAAS;gBAEf,UAAU,EAAE,MAAM;IAKxB,KAAK,IAAI,OAAO,CAAC,IAAI,CAAC;IAwB5B,OAAO,IAAI,MAAM;IAIjB,UAAU,IAAI,MAAM;IAIpB;;;OAGG;IACH,UAAU,CAAC,OAAO,EAAE,MAAM,GAAG,MAAM,GAAG,IAAI;IAsB1C,IAAI,IAAI,IAAI;IAQZ,OAAO,CAAC,aAAa;CAoHtB"}
|
|
@@ -0,0 +1,195 @@
|
|
|
1
|
+
import { createServer } from 'http';
|
|
2
|
+
import { stat, createReadStream } from 'fs';
|
|
3
|
+
import { resolve, normalize, extname } from 'path';
|
|
4
|
+
import { randomBytes } from 'crypto';
|
|
5
|
+
import logger from '../utils/logger.js';
|
|
6
|
+
const MIME_TYPES = {
|
|
7
|
+
'.mp4': 'video/mp4',
|
|
8
|
+
'.webm': 'video/webm',
|
|
9
|
+
'.mov': 'video/quicktime',
|
|
10
|
+
'.jpg': 'image/jpeg',
|
|
11
|
+
'.jpeg': 'image/jpeg',
|
|
12
|
+
'.png': 'image/png',
|
|
13
|
+
'.gif': 'image/gif',
|
|
14
|
+
'.webp': 'image/webp',
|
|
15
|
+
'.svg': 'image/svg+xml',
|
|
16
|
+
'.html': 'text/html',
|
|
17
|
+
'.css': 'text/css',
|
|
18
|
+
'.js': 'application/javascript',
|
|
19
|
+
};
|
|
20
|
+
/**
|
|
21
|
+
* Lightweight localhost-only HTTP server for serving generated media files
|
|
22
|
+
* to the MCP App viewer iframes (which cannot access file:// URIs).
|
|
23
|
+
*
|
|
24
|
+
* Security:
|
|
25
|
+
* - Binds to 127.0.0.1 only (no external access)
|
|
26
|
+
* - Random URL token required in path
|
|
27
|
+
* - Path traversal protection (all paths resolved against allowed directory)
|
|
28
|
+
* - Supports HTTP Range requests for video seeking
|
|
29
|
+
*/
|
|
30
|
+
export class MediaServer {
|
|
31
|
+
server = null;
|
|
32
|
+
port = 0;
|
|
33
|
+
token;
|
|
34
|
+
allowedDir;
|
|
35
|
+
constructor(allowedDir) {
|
|
36
|
+
this.allowedDir = resolve(allowedDir);
|
|
37
|
+
this.token = randomBytes(16).toString('hex');
|
|
38
|
+
}
|
|
39
|
+
async start() {
|
|
40
|
+
return new Promise((ok, fail) => {
|
|
41
|
+
this.server = createServer((req, res) => this.handleRequest(req, res));
|
|
42
|
+
this.server.on('error', (err) => {
|
|
43
|
+
logger.error('Media server error', { error: err });
|
|
44
|
+
fail(err);
|
|
45
|
+
});
|
|
46
|
+
// Bind to 127.0.0.1 with OS-assigned port
|
|
47
|
+
this.server.listen(0, '127.0.0.1', () => {
|
|
48
|
+
const addr = this.server.address();
|
|
49
|
+
if (addr && typeof addr === 'object') {
|
|
50
|
+
this.port = addr.port;
|
|
51
|
+
}
|
|
52
|
+
logger.info('Media server started', {
|
|
53
|
+
port: this.port,
|
|
54
|
+
baseUrl: this.getBaseUrl(),
|
|
55
|
+
});
|
|
56
|
+
ok();
|
|
57
|
+
});
|
|
58
|
+
});
|
|
59
|
+
}
|
|
60
|
+
getPort() {
|
|
61
|
+
return this.port;
|
|
62
|
+
}
|
|
63
|
+
getBaseUrl() {
|
|
64
|
+
return `http://127.0.0.1:${this.port}/${this.token}`;
|
|
65
|
+
}
|
|
66
|
+
/**
|
|
67
|
+
* Convert an absolute file path to a media server URL.
|
|
68
|
+
* Returns null if the path is outside the allowed directory.
|
|
69
|
+
*/
|
|
70
|
+
getFileUrl(absPath) {
|
|
71
|
+
const resolved = resolve(absPath);
|
|
72
|
+
const normalAllowed = normalize(this.allowedDir);
|
|
73
|
+
const normalResolved = normalize(resolved);
|
|
74
|
+
// Ensure the file is within the allowed directory
|
|
75
|
+
if (!normalResolved.startsWith(normalAllowed)) {
|
|
76
|
+
logger.warn('Media server: path outside allowed directory', {
|
|
77
|
+
path: absPath,
|
|
78
|
+
allowedDir: this.allowedDir,
|
|
79
|
+
});
|
|
80
|
+
return null;
|
|
81
|
+
}
|
|
82
|
+
// Build relative path portion (use forward slashes for URL)
|
|
83
|
+
const relative = normalResolved
|
|
84
|
+
.slice(normalAllowed.length)
|
|
85
|
+
.replace(/\\/g, '/');
|
|
86
|
+
return `${this.getBaseUrl()}${relative}`;
|
|
87
|
+
}
|
|
88
|
+
stop() {
|
|
89
|
+
if (this.server) {
|
|
90
|
+
this.server.close();
|
|
91
|
+
this.server = null;
|
|
92
|
+
logger.info('Media server stopped');
|
|
93
|
+
}
|
|
94
|
+
}
|
|
95
|
+
handleRequest(req, res) {
|
|
96
|
+
// CORS headers (safe since localhost-only)
|
|
97
|
+
res.setHeader('Access-Control-Allow-Origin', '*');
|
|
98
|
+
res.setHeader('Access-Control-Allow-Methods', 'GET, HEAD, OPTIONS');
|
|
99
|
+
res.setHeader('Access-Control-Allow-Headers', 'Range');
|
|
100
|
+
res.setHeader('Access-Control-Expose-Headers', 'Content-Range, Accept-Ranges, Content-Length');
|
|
101
|
+
if (req.method === 'OPTIONS') {
|
|
102
|
+
res.writeHead(204);
|
|
103
|
+
res.end();
|
|
104
|
+
return;
|
|
105
|
+
}
|
|
106
|
+
if (req.method !== 'GET' && req.method !== 'HEAD') {
|
|
107
|
+
res.writeHead(405, { 'Content-Type': 'text/plain' });
|
|
108
|
+
res.end('Method Not Allowed');
|
|
109
|
+
return;
|
|
110
|
+
}
|
|
111
|
+
const url = new URL(req.url || '/', `http://127.0.0.1:${this.port}`);
|
|
112
|
+
const pathname = decodeURIComponent(url.pathname);
|
|
113
|
+
// Validate token
|
|
114
|
+
const expectedPrefix = `/${this.token}`;
|
|
115
|
+
if (!pathname.startsWith(expectedPrefix)) {
|
|
116
|
+
res.writeHead(403, { 'Content-Type': 'text/plain' });
|
|
117
|
+
res.end('Forbidden');
|
|
118
|
+
return;
|
|
119
|
+
}
|
|
120
|
+
// Extract the file path after the token
|
|
121
|
+
const relativePath = pathname.slice(expectedPrefix.length) || '/';
|
|
122
|
+
if (relativePath === '/') {
|
|
123
|
+
res.writeHead(200, { 'Content-Type': 'text/plain' });
|
|
124
|
+
res.end('Gemini MCP Media Server');
|
|
125
|
+
return;
|
|
126
|
+
}
|
|
127
|
+
// Resolve to absolute path within allowed directory
|
|
128
|
+
const filePath = resolve(this.allowedDir, '.' + relativePath);
|
|
129
|
+
const normalAllowed = normalize(this.allowedDir);
|
|
130
|
+
const normalFile = normalize(filePath);
|
|
131
|
+
// Path traversal protection
|
|
132
|
+
if (!normalFile.startsWith(normalAllowed)) {
|
|
133
|
+
res.writeHead(403, { 'Content-Type': 'text/plain' });
|
|
134
|
+
res.end('Forbidden');
|
|
135
|
+
return;
|
|
136
|
+
}
|
|
137
|
+
// Get file stats and serve
|
|
138
|
+
stat(filePath, (err, stats) => {
|
|
139
|
+
if (err || !stats.isFile()) {
|
|
140
|
+
res.writeHead(404, { 'Content-Type': 'text/plain' });
|
|
141
|
+
res.end('Not Found');
|
|
142
|
+
return;
|
|
143
|
+
}
|
|
144
|
+
const ext = extname(filePath).toLowerCase();
|
|
145
|
+
const contentType = MIME_TYPES[ext] || 'application/octet-stream';
|
|
146
|
+
const fileSize = stats.size;
|
|
147
|
+
res.setHeader('Accept-Ranges', 'bytes');
|
|
148
|
+
// Handle Range requests (essential for video seeking)
|
|
149
|
+
const rangeHeader = req.headers.range;
|
|
150
|
+
if (rangeHeader) {
|
|
151
|
+
const match = /^bytes=(\d+)-(\d*)$/.exec(rangeHeader);
|
|
152
|
+
if (!match) {
|
|
153
|
+
res.writeHead(416, {
|
|
154
|
+
'Content-Range': `bytes */${fileSize}`,
|
|
155
|
+
'Content-Type': 'text/plain',
|
|
156
|
+
});
|
|
157
|
+
res.end('Range Not Satisfiable');
|
|
158
|
+
return;
|
|
159
|
+
}
|
|
160
|
+
const start = parseInt(match[1], 10);
|
|
161
|
+
const end = match[2] ? parseInt(match[2], 10) : fileSize - 1;
|
|
162
|
+
if (start >= fileSize || end >= fileSize || start > end) {
|
|
163
|
+
res.writeHead(416, {
|
|
164
|
+
'Content-Range': `bytes */${fileSize}`,
|
|
165
|
+
'Content-Type': 'text/plain',
|
|
166
|
+
});
|
|
167
|
+
res.end('Range Not Satisfiable');
|
|
168
|
+
return;
|
|
169
|
+
}
|
|
170
|
+
res.writeHead(206, {
|
|
171
|
+
'Content-Range': `bytes ${start}-${end}/${fileSize}`,
|
|
172
|
+
'Content-Length': end - start + 1,
|
|
173
|
+
'Content-Type': contentType,
|
|
174
|
+
});
|
|
175
|
+
if (req.method === 'HEAD') {
|
|
176
|
+
res.end();
|
|
177
|
+
return;
|
|
178
|
+
}
|
|
179
|
+
createReadStream(filePath, { start, end }).pipe(res);
|
|
180
|
+
}
|
|
181
|
+
else {
|
|
182
|
+
res.writeHead(200, {
|
|
183
|
+
'Content-Length': fileSize,
|
|
184
|
+
'Content-Type': contentType,
|
|
185
|
+
});
|
|
186
|
+
if (req.method === 'HEAD') {
|
|
187
|
+
res.end();
|
|
188
|
+
return;
|
|
189
|
+
}
|
|
190
|
+
createReadStream(filePath).pipe(res);
|
|
191
|
+
}
|
|
192
|
+
});
|
|
193
|
+
}
|
|
194
|
+
}
|
|
195
|
+
//# sourceMappingURL=media-server.js.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"media-server.js","sourceRoot":"","sources":["../../src/services/media-server.ts"],"names":[],"mappings":"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"}
|