@mixio-pro/kalaasetu-mcp 1.0.15 → 1.0.16
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/package.json +1 -1
- package/src/storage/gcs.ts +0 -3
- package/src/tools/image-to-video.sdk-backup.ts +100 -43
- package/src/utils/fal.utils.ts +15 -7
package/package.json
CHANGED
package/src/storage/gcs.ts
CHANGED
|
@@ -18,9 +18,6 @@ export class GCSStorageProvider implements StorageProvider {
|
|
|
18
18
|
}
|
|
19
19
|
|
|
20
20
|
async init(): Promise<void> {
|
|
21
|
-
console.log(
|
|
22
|
-
`Initializing GCS Storage Provider with bucket: ${this.bucket}`
|
|
23
|
-
);
|
|
24
21
|
// Verify we can get credentials
|
|
25
22
|
try {
|
|
26
23
|
await this.auth.getClient();
|
|
@@ -15,27 +15,67 @@ function fileToBase64(filePath: string): { data: string; mimeType: string } {
|
|
|
15
15
|
const data = Buffer.from(buf).toString("base64");
|
|
16
16
|
// Detect mime type from extension
|
|
17
17
|
const ext = path.extname(filePath).toLowerCase();
|
|
18
|
-
const mimeType =
|
|
19
|
-
|
|
20
|
-
|
|
18
|
+
const mimeType =
|
|
19
|
+
ext === ".jpg" || ext === ".jpeg"
|
|
20
|
+
? "image/jpeg"
|
|
21
|
+
: ext === ".png"
|
|
22
|
+
? "image/png"
|
|
23
|
+
: ext === ".webp"
|
|
24
|
+
? "image/webp"
|
|
25
|
+
: "image/png";
|
|
21
26
|
return { data, mimeType };
|
|
22
27
|
}
|
|
23
28
|
|
|
24
29
|
export const imageToVideo = {
|
|
25
30
|
name: "imageToVideo",
|
|
26
|
-
description:
|
|
31
|
+
description:
|
|
32
|
+
"Generate videos from an image as starting first frame using Vertex Veo models (predictLongRunning + fetchPredictOperation).",
|
|
27
33
|
parameters: z.object({
|
|
28
34
|
prompt: z.string().describe("Text description for the video"),
|
|
29
|
-
image_path: z
|
|
30
|
-
|
|
31
|
-
|
|
32
|
-
|
|
33
|
-
|
|
34
|
-
|
|
35
|
-
|
|
36
|
-
|
|
37
|
-
|
|
38
|
-
|
|
35
|
+
image_path: z
|
|
36
|
+
.string()
|
|
37
|
+
.optional()
|
|
38
|
+
.describe("Path to source image for image-to-video generation"),
|
|
39
|
+
aspect_ratio: z
|
|
40
|
+
.string()
|
|
41
|
+
.optional()
|
|
42
|
+
.describe("Video aspect ratio: '16:9' or '9:16' (default: '9:16')"),
|
|
43
|
+
duration_seconds: z
|
|
44
|
+
.number()
|
|
45
|
+
.optional()
|
|
46
|
+
.describe("Video duration in seconds: 4, 6, or 8 (default: 6)"),
|
|
47
|
+
resolution: z
|
|
48
|
+
.string()
|
|
49
|
+
.optional()
|
|
50
|
+
.describe("Video resolution: '720p' or '1080p' (default: '720p')"),
|
|
51
|
+
negative_prompt: z
|
|
52
|
+
.string()
|
|
53
|
+
.optional()
|
|
54
|
+
.describe("Text describing what not to include in the video"),
|
|
55
|
+
person_generation: z
|
|
56
|
+
.string()
|
|
57
|
+
.optional()
|
|
58
|
+
.describe(
|
|
59
|
+
"Controls generation of people: 'allow_adult' (default for image-to-video) or 'allow_all'"
|
|
60
|
+
),
|
|
61
|
+
reference_images: z
|
|
62
|
+
.array(z.string())
|
|
63
|
+
.optional()
|
|
64
|
+
.describe("Additional image paths for reference (max 3)"),
|
|
65
|
+
output_path: z
|
|
66
|
+
.string()
|
|
67
|
+
.optional()
|
|
68
|
+
.describe(
|
|
69
|
+
"Output MP4 file path (if multiple predictions, index suffix is added)"
|
|
70
|
+
),
|
|
71
|
+
gemini_api_key: z
|
|
72
|
+
.string()
|
|
73
|
+
.optional()
|
|
74
|
+
.describe("Gemini API key (uses GEMINI_API_KEY env var if not provided)"),
|
|
75
|
+
model_id: z
|
|
76
|
+
.string()
|
|
77
|
+
.optional()
|
|
78
|
+
.describe("Model ID (default: veo-2.0-generate-001)"),
|
|
39
79
|
}),
|
|
40
80
|
execute: async (args: {
|
|
41
81
|
prompt: string;
|
|
@@ -52,47 +92,49 @@ export const imageToVideo = {
|
|
|
52
92
|
}) => {
|
|
53
93
|
const apiKey = args.gemini_api_key || process.env.GEMINI_API_KEY;
|
|
54
94
|
if (!apiKey) {
|
|
55
|
-
throw new Error(
|
|
95
|
+
throw new Error(
|
|
96
|
+
"Gemini API key is required. Set GEMINI_API_KEY environment variable or pass gemini_api_key parameter. Get one at https://aistudio.google.com/app/apikey"
|
|
97
|
+
);
|
|
56
98
|
}
|
|
57
99
|
|
|
58
100
|
const model = args.model_id || "veo-2.0-generate-001";
|
|
59
|
-
|
|
101
|
+
|
|
60
102
|
// Initialize Google GenAI client
|
|
61
103
|
const genai = new GoogleGenAI({ apiKey });
|
|
62
104
|
|
|
63
105
|
// Build config for video generation
|
|
64
106
|
const config: any = {};
|
|
65
|
-
|
|
107
|
+
|
|
66
108
|
if (args.duration_seconds !== undefined) {
|
|
67
109
|
config.duration_seconds = args.duration_seconds;
|
|
68
110
|
} else {
|
|
69
111
|
config.duration_seconds = 6; // default
|
|
70
112
|
}
|
|
71
|
-
|
|
113
|
+
|
|
72
114
|
if (args.aspect_ratio) {
|
|
73
115
|
config.aspect_ratio = args.aspect_ratio;
|
|
74
116
|
}
|
|
75
117
|
|
|
76
118
|
try {
|
|
77
119
|
// Start video generation operation
|
|
78
|
-
console.
|
|
120
|
+
console.error(`Starting video generation with model: ${model}`);
|
|
79
121
|
let operation = await genai.models.generateVideos({
|
|
80
122
|
model,
|
|
81
123
|
prompt: args.prompt,
|
|
82
124
|
config,
|
|
83
125
|
});
|
|
84
126
|
|
|
85
|
-
console.
|
|
86
|
-
|
|
127
|
+
console.error("Operation started, waiting for completion...");
|
|
128
|
+
|
|
87
129
|
// Poll until operation is complete (max 10 minutes)
|
|
88
130
|
let tries = 0;
|
|
89
131
|
const maxTries = 60; // 10 minutes with 10s intervals
|
|
90
|
-
|
|
132
|
+
|
|
91
133
|
while (!operation.done && tries < maxTries) {
|
|
92
134
|
await wait(10000); // Wait 10 seconds
|
|
93
135
|
tries++;
|
|
94
|
-
console.
|
|
95
|
-
|
|
136
|
+
console.error(`Polling attempt ${tries}/${maxTries}...`);
|
|
137
|
+
|
|
96
138
|
operation = await genai.operations.getVideosOperation({
|
|
97
139
|
operation: operation,
|
|
98
140
|
});
|
|
@@ -102,60 +144,75 @@ export const imageToVideo = {
|
|
|
102
144
|
throw new Error("Video generation timed out after 10 minutes");
|
|
103
145
|
}
|
|
104
146
|
|
|
105
|
-
console.
|
|
106
|
-
console.
|
|
147
|
+
console.error("Operation completed!");
|
|
148
|
+
console.error(
|
|
149
|
+
"Full Response:",
|
|
150
|
+
JSON.stringify(operation.response, null, 2)
|
|
151
|
+
);
|
|
107
152
|
|
|
108
153
|
// Extract generated videos from response
|
|
109
154
|
const generatedVideos = operation.response?.generatedVideos || [];
|
|
110
|
-
|
|
155
|
+
|
|
111
156
|
if (!generatedVideos || generatedVideos.length === 0) {
|
|
112
157
|
const respStr = JSON.stringify(operation.response, null, 2);
|
|
113
|
-
return `Video generation completed but no videos found in response.\n\nFull Response:\n${respStr.slice(
|
|
158
|
+
return `Video generation completed but no videos found in response.\n\nFull Response:\n${respStr.slice(
|
|
159
|
+
0,
|
|
160
|
+
2000
|
|
161
|
+
)}${respStr.length > 2000 ? "\n...(truncated)" : ""}`;
|
|
114
162
|
}
|
|
115
163
|
|
|
116
164
|
// Download and save videos
|
|
117
165
|
const outputs: string[] = [];
|
|
118
|
-
|
|
166
|
+
|
|
119
167
|
for (let i = 0; i < generatedVideos.length; i++) {
|
|
120
168
|
const generatedVideo = generatedVideos[i];
|
|
121
169
|
const videoUri = generatedVideo?.video?.uri;
|
|
122
|
-
|
|
170
|
+
|
|
123
171
|
if (!videoUri) {
|
|
124
172
|
console.warn(`Video ${i} has no URI`);
|
|
125
173
|
continue;
|
|
126
174
|
}
|
|
127
175
|
|
|
128
|
-
console.
|
|
129
|
-
|
|
176
|
+
console.error(
|
|
177
|
+
`Downloading video ${i + 1}/${generatedVideos.length}...`
|
|
178
|
+
);
|
|
179
|
+
|
|
130
180
|
// Download video from URI
|
|
131
181
|
const videoUrl = `${videoUri}&key=${apiKey}`;
|
|
132
182
|
const response = await fetch(videoUrl);
|
|
133
|
-
|
|
183
|
+
|
|
134
184
|
if (!response.ok) {
|
|
135
|
-
throw new Error(
|
|
185
|
+
throw new Error(
|
|
186
|
+
`Failed to download video: ${response.status} ${response.statusText}`
|
|
187
|
+
);
|
|
136
188
|
}
|
|
137
|
-
|
|
189
|
+
|
|
138
190
|
const buffer = await response.arrayBuffer();
|
|
139
|
-
|
|
191
|
+
|
|
140
192
|
// Save video to file
|
|
141
193
|
const filePath = args.output_path
|
|
142
|
-
?
|
|
143
|
-
|
|
194
|
+
? i === 0
|
|
195
|
+
? args.output_path
|
|
196
|
+
: args.output_path.replace(/\.mp4$/i, `_${i}.mp4`)
|
|
197
|
+
: `video_output_${Date.now()}${i === 0 ? "" : "_" + i}.mp4`;
|
|
144
198
|
const absPath = path.resolve(filePath);
|
|
145
|
-
|
|
199
|
+
|
|
146
200
|
fs.writeFileSync(absPath, Buffer.from(buffer));
|
|
147
201
|
outputs.push(absPath);
|
|
148
|
-
console.
|
|
202
|
+
console.error(`Saved video to: ${absPath}`);
|
|
149
203
|
}
|
|
150
204
|
|
|
151
205
|
if (outputs.length > 0) {
|
|
152
|
-
return `Video(s) saved successfully:\n${outputs
|
|
206
|
+
return `Video(s) saved successfully:\n${outputs
|
|
207
|
+
.map((p, i) => `${i + 1}. ${p}`)
|
|
208
|
+
.join("\n")}`;
|
|
153
209
|
}
|
|
154
210
|
|
|
155
211
|
return "Video generation completed but no videos were saved.";
|
|
156
|
-
|
|
157
212
|
} catch (error: any) {
|
|
158
|
-
throw new Error(
|
|
213
|
+
throw new Error(
|
|
214
|
+
`Video generation failed: ${error.message || JSON.stringify(error)}`
|
|
215
|
+
);
|
|
159
216
|
}
|
|
160
217
|
},
|
|
161
218
|
};
|
package/src/utils/fal.utils.ts
CHANGED
|
@@ -8,14 +8,16 @@ export async function callFalModel(
|
|
|
8
8
|
const { falKey, logs = true } = options;
|
|
9
9
|
const key = falKey || process.env.FAL_KEY;
|
|
10
10
|
if (!key) {
|
|
11
|
-
throw new Error(
|
|
11
|
+
throw new Error(
|
|
12
|
+
"FAL_KEY is required. Provide it via fal_key parameter or FAL_KEY environment variable."
|
|
13
|
+
);
|
|
12
14
|
}
|
|
13
15
|
|
|
14
16
|
fal.config({
|
|
15
17
|
credentials: key,
|
|
16
18
|
});
|
|
17
19
|
|
|
18
|
-
console.
|
|
20
|
+
console.error(`[${modelName}] Submitting request to FAL AI...`);
|
|
19
21
|
|
|
20
22
|
try {
|
|
21
23
|
const result = await fal.subscribe(modelName, {
|
|
@@ -23,23 +25,29 @@ export async function callFalModel(
|
|
|
23
25
|
logs,
|
|
24
26
|
onQueueUpdate: (update) => {
|
|
25
27
|
if (update.status === "IN_PROGRESS") {
|
|
26
|
-
console.
|
|
28
|
+
console.error(`[${modelName}] Status: ${update.status}`);
|
|
27
29
|
if (logs && "logs" in update && update.logs) {
|
|
28
30
|
update.logs.forEach((log) => {
|
|
29
|
-
console.
|
|
31
|
+
console.error(`[${modelName}] ${log.message}`);
|
|
30
32
|
});
|
|
31
33
|
}
|
|
32
34
|
} else if (update.status === "IN_QUEUE") {
|
|
33
|
-
console.
|
|
35
|
+
console.error(
|
|
36
|
+
`[${modelName}] Status: ${update.status} - Waiting in queue...`
|
|
37
|
+
);
|
|
34
38
|
}
|
|
35
39
|
},
|
|
36
40
|
});
|
|
37
41
|
|
|
38
|
-
console.
|
|
42
|
+
console.error(`[${modelName}] Generation completed successfully`);
|
|
39
43
|
|
|
40
44
|
return result;
|
|
41
45
|
} catch (error: any) {
|
|
42
46
|
console.error(`[${modelName}] Error:`, error);
|
|
43
|
-
throw new Error(
|
|
47
|
+
throw new Error(
|
|
48
|
+
`FAL AI ${modelName} generation failed: ${
|
|
49
|
+
error.message || JSON.stringify(error)
|
|
50
|
+
}`
|
|
51
|
+
);
|
|
44
52
|
}
|
|
45
53
|
}
|