studio-lumiere-cli 0.1.4 → 0.1.5

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.
Files changed (59) hide show
  1. package/dist/config/constants.d.ts +1 -63
  2. package/dist/config/constants.js +3 -1004
  3. package/dist/config/constants.js.map +1 -1
  4. package/dist/pipelines/generateImages.js +5 -3
  5. package/dist/pipelines/generateImages.js.map +1 -1
  6. package/dist/storage/files.js +4 -1
  7. package/dist/storage/files.js.map +1 -1
  8. package/dist/studio/constants.d.ts +64 -0
  9. package/dist/studio/constants.js +1018 -0
  10. package/dist/studio/constants.js.map +1 -0
  11. package/dist/studio/promptBuilder.d.ts +4 -0
  12. package/{src/prompt/buildPrompt.ts → dist/studio/promptBuilder.js} +166 -221
  13. package/dist/studio/promptBuilder.js.map +1 -0
  14. package/dist/studio/types.d.ts +155 -0
  15. package/dist/studio/types.js +2 -0
  16. package/dist/studio/types.js.map +1 -0
  17. package/dist/types.d.ts +2 -49
  18. package/package.json +9 -3
  19. package/scripts/sync-studio.mjs +16 -0
  20. package/.agents/skills/generate-images/SKILL.md +0 -121
  21. package/.env.example +0 -2
  22. package/AGENTS.md +0 -66
  23. package/examples/generate.d.ts +0 -1
  24. package/examples/generate.js +0 -28
  25. package/examples/generate.js.map +0 -1
  26. package/examples/generate.ts +0 -30
  27. package/examples/muse.d.ts +0 -1
  28. package/examples/muse.js +0 -18
  29. package/examples/muse.js.map +0 -1
  30. package/examples/muse.ts +0 -20
  31. package/examples/video.d.ts +0 -1
  32. package/examples/video.js +0 -18
  33. package/examples/video.js.map +0 -1
  34. package/examples/video.ts +0 -20
  35. package/logo-round.png +0 -0
  36. package/logo.jpeg +0 -0
  37. package/skills/studio-lumiere-cli/SKILL.md +0 -212
  38. package/skills/studio-lumiere-cli/agents/openai.yaml +0 -4
  39. package/src/cli.ts +0 -259
  40. package/src/clients/geminiClient.ts +0 -168
  41. package/src/config/constants.ts +0 -1105
  42. package/src/config/options.ts +0 -15
  43. package/src/config/templates.ts +0 -4
  44. package/src/config/tiredGirl.ts +0 -11
  45. package/src/image/annotate.ts +0 -139
  46. package/src/image/grid.ts +0 -58
  47. package/src/index.ts +0 -27
  48. package/src/pipelines/createMuse.ts +0 -76
  49. package/src/pipelines/generateImages.ts +0 -203
  50. package/src/pipelines/generateTiredGirl.ts +0 -86
  51. package/src/pipelines/generateVideo.ts +0 -36
  52. package/src/pipelines/refineImage.ts +0 -36
  53. package/src/pipelines/resolve.ts +0 -88
  54. package/src/pipelines/upscaleImage.ts +0 -30
  55. package/src/prompt/tiredGirlPrompt.ts +0 -35
  56. package/src/storage/files.ts +0 -44
  57. package/src/storage/museStore.ts +0 -31
  58. package/src/types.ts +0 -198
  59. package/tsconfig.json +0 -15
@@ -1,212 +0,0 @@
1
- ---
2
- name: studio-lumiere-cli
3
- description: Command-line usage for Studio Lumiere. Use when running npx studio-lumiere-cli to generate, refine, upscale, create a Muse, generate video, tired-girl, annotate, grid, or list IDs.
4
- ---
5
-
6
- # Studio Lumiere CLI
7
-
8
- ## Requirements
9
- - `GEMINI_API_KEY` must be set in your environment.
10
- - `LUMIERE_OUTPUT_DIR` is optional (defaults to `outputs`).
11
-
12
- ## Invocation
13
- `npx studio-lumiere-cli <command> [options]`
14
-
15
- ## Timing
16
- Image and video generation can take time: expect ~1-2 minutes per image and ~4-5 minutes per video.
17
-
18
- ## list
19
- Print JSON for valid IDs.
20
-
21
- Example:
22
- ```bash
23
- npx studio-lumiere-cli list templates
24
- ```
25
-
26
- Allowed types:
27
- - `templates`
28
- - `ethnicities`
29
- - `skin-tones`
30
- - `hair-colors`
31
- - `backgrounds`
32
- - `background-types`
33
- - `vibes`
34
- - `resolutions`
35
- - `occasions`
36
-
37
- ## generate
38
- Generate images from input jewelry photos.
39
-
40
- Example:
41
- ```bash
42
- npx studio-lumiere-cli generate \
43
- --images ./inputs/ring.jpg \
44
- --template hand_model \
45
- --detail nail_nude \
46
- --ethnicity mena \
47
- --skin-tone medium \
48
- --hair-color brunette \
49
- --background cream_silk \
50
- --background-type studio \
51
- --vibe clean \
52
- --resolution portrait \
53
- --quantity 1
54
- ```
55
-
56
- Options:
57
- - `--images <paths>` (required): Comma-separated input image paths.
58
- - `--template <id>` (required): Template ID.
59
- - `--detail <id>`
60
- - `--secondary-detail <id>`
61
- - `--tertiary-detail <id>`
62
- - `--ethnicity <id>`
63
- - `--skin-tone <id>`
64
- - `--hair-color <id>`
65
- - `--background <id>`
66
- - `--background-type <id>`
67
- - `--vibe <id>`
68
- - `--resolution <id>`
69
- - `--occasion <id>`
70
- - `--quantity <n>`: Number of outputs (default `1`).
71
- - `--muse-id <id>`: Use a stored Muse.
72
- - `--muse-images <paths>`: Comma-separated Muse image paths.
73
- - `--no-enhance`: Disable prompt enhancement.
74
-
75
- Outputs:
76
- Generated files are written to `outputs/generations/<timestamp>/` (or `LUMIERE_OUTPUT_DIR/generations/<timestamp>/`) with `image_#.png` and `generation.json`.
77
-
78
- ## refine
79
- Refine an existing image.
80
-
81
- Example:
82
- ```bash
83
- npx studio-lumiere-cli refine \
84
- --image ./outputs/image.png \
85
- --instruction "Reduce glare and smooth background"
86
- ```
87
-
88
- Options:
89
- - `--image <path>` (required)
90
- - `--instruction <text>` (required)
91
- - `--size <percent>`: Resize jewelry to a percentage of current size.
92
-
93
- Outputs:
94
- `outputs/refinements/<timestamp>/refined.png` and `refine.json`.
95
-
96
- ## upscale
97
- Upscale an existing image.
98
-
99
- Example:
100
- ```bash
101
- npx studio-lumiere-cli upscale --image ./outputs/image.png --scale 2
102
- ```
103
-
104
- Options:
105
- - `--image <path>` (required)
106
- - `--scale <number>`: Scale factor (default `2`).
107
-
108
- Outputs:
109
- `outputs/upscales/<timestamp>/upscaled.png` and `upscale.json`.
110
-
111
- ## muse
112
- Create a Muse from a source portrait.
113
-
114
- Example:
115
- ```bash
116
- npx studio-lumiere-cli muse --name "Nadine" --source ./inputs/portrait.jpg --variations 3
117
- ```
118
-
119
- Options:
120
- - `--name <name>` (required)
121
- - `--source <path>` (required)
122
- - `--variations <n>`: Number of variations (default `3`).
123
-
124
- Outputs:
125
- `outputs/muses/<timestamp>/variation_#.png`, `muse.json`, and an updated `muses.json` under the output root.
126
-
127
- ## video
128
- Generate a video from a prompt.
129
-
130
- Example:
131
- ```bash
132
- npx studio-lumiere-cli video --prompt "Slow pan over a diamond ring" --aspect 16:9 --duration 5
133
- ```
134
-
135
- Options:
136
- - `--prompt <text>` (required)
137
- - `--aspect <ratio>`
138
- - `--duration <seconds>`
139
-
140
- Outputs:
141
- `outputs/videos/<timestamp>/video.mp4` (when available) and `video.json` with the operation name.
142
-
143
- ## tired-girl
144
- Generate a tired-girl before image.
145
-
146
- Example:
147
- ```bash
148
- npx studio-lumiere-cli tired-girl --muse-id muse_1700000000000 --quantity 2
149
- ```
150
-
151
- Options:
152
- - `--muse-id <id>`: Muse ID (preferred).
153
- - `--image <path>`: Single reference image (used if no muse-id).
154
- - `--styles <ids>`: Comma-separated style IDs.
155
- - `--quantity <n>`: Number of variants (default `1`).
156
-
157
- Outputs:
158
- `outputs/tired_girl/<timestamp>/tired_girl_#.png` and `tired_girl.json`.
159
-
160
- ## annotate
161
- Overlay text on an image.
162
-
163
- Example:
164
- ```bash
165
- npx studio-lumiere-cli annotate --input ./in.png --output ./out.png --text "Hello" --banner
166
- ```
167
-
168
- Options:
169
- - `--input <path>` (required)
170
- - `--output <path>` (required)
171
- - `--text <text>` (required)
172
- - `--font <name>`
173
- - `--size <px>`
174
- - `--weight <n>`
175
- - `--color <hex>`
176
- - `--stroke-color <value>`
177
- - `--stroke-width <px>`
178
- - `--banner`
179
- - `--banner-color <value>`
180
- - `--banner-padding <px>`
181
- - `--banner-radius <px>`
182
- - `--position <pos>`: `top-center|top-left|top-right|bottom-center|bottom-center-high|bottom-left|bottom-right|center`
183
- - `--padding <px>`
184
- - `--max-width <ratio>`
185
-
186
- Outputs:
187
- Writes the image to the provided `--output` path.
188
-
189
- ## grid
190
- Assemble multiple images into a grid.
191
-
192
- Example:
193
- ```bash
194
- npx studio-lumiere-cli grid \
195
- --inputs ./a.png,./b.png,./c.png,./d.png \
196
- --output ./grid.png \
197
- --columns 2 \
198
- --rows 2
199
- ```
200
-
201
- Options:
202
- - `--inputs <paths>` (required): Comma-separated image paths.
203
- - `--output <path>` (required)
204
- - `--columns <n>` (default `2`)
205
- - `--rows <n>` (default `2`)
206
- - `--padding <px>` (default `20`)
207
- - `--background <hex>` (default `#000000`)
208
- - `--tile-width <px>`
209
- - `--tile-height <px>`
210
-
211
- Outputs:
212
- Writes the image to the provided `--output` path.
@@ -1,4 +0,0 @@
1
- interface:
2
- display_name: "Studio Lumiere CLI"
3
- short_description: "CLI usage for Studio Lumiere image workflows"
4
- default_prompt: "Use $studio-lumiere-cli to run Studio Lumiere CLI commands."
package/src/cli.ts DELETED
@@ -1,259 +0,0 @@
1
- #!/usr/bin/env node
2
- import "dotenv/config";
3
- import { Command } from "commander";
4
- import {
5
- generateImages,
6
- refineImage,
7
- upscaleImage,
8
- generateTiredGirl,
9
- createMuse,
10
- generateVideo,
11
- annotateImage,
12
- createImageGrid,
13
- TEMPLATES,
14
- ETHNICITIES,
15
- SKIN_TONES,
16
- HAIR_COLORS,
17
- BACKGROUNDS,
18
- BACKGROUND_TYPES,
19
- VIBES,
20
- RESOLUTIONS,
21
- OCCASIONS
22
- } from "./index.js";
23
- import { LumiereConfig } from "./types.js";
24
-
25
- const program = new Command();
26
-
27
- const parseList = (value: string): string[] => value.split(",").map((v) => v.trim()).filter(Boolean);
28
-
29
- const loadConfig = (): LumiereConfig => {
30
- const apiKey = process.env.GEMINI_API_KEY;
31
- if (!apiKey) {
32
- throw new Error("Missing GEMINI_API_KEY in environment.");
33
- }
34
- return {
35
- apiKey,
36
- outputDir: process.env.LUMIERE_OUTPUT_DIR ?? "outputs"
37
- };
38
- };
39
-
40
- program
41
- .name("lumiere")
42
- .description("Local Studio Lumiere SDK + CLI")
43
- .version("0.1.0");
44
-
45
- program
46
- .command("list")
47
- .argument("type", "templates|ethnicities|skin-tones|hair-colors|backgrounds|background-types|vibes|resolutions|occasions")
48
- .action((type) => {
49
- const map: Record<string, unknown> = {
50
- templates: TEMPLATES,
51
- ethnicities: ETHNICITIES,
52
- "skin-tones": SKIN_TONES,
53
- "hair-colors": HAIR_COLORS,
54
- backgrounds: BACKGROUNDS,
55
- "background-types": BACKGROUND_TYPES,
56
- vibes: VIBES,
57
- resolutions: RESOLUTIONS,
58
- occasions: OCCASIONS
59
- };
60
- const payload = map[type];
61
- if (!payload) {
62
- throw new Error(`Unknown list type: ${type}`);
63
- }
64
- console.log(JSON.stringify(payload, null, 2));
65
- });
66
-
67
- program
68
- .command("generate")
69
- .requiredOption("--images <paths>", "Comma-separated image paths")
70
- .requiredOption("--template <id>", "Template id")
71
- .option("--detail <id>")
72
- .option("--secondary-detail <id>")
73
- .option("--tertiary-detail <id>")
74
- .option("--ethnicity <id>")
75
- .option("--skin-tone <id>")
76
- .option("--hair-color <id>")
77
- .option("--background <id>")
78
- .option("--background-type <id>")
79
- .option("--vibe <id>")
80
- .option("--resolution <id>")
81
- .option("--occasion <id>")
82
- .option("--quantity <n>", "Number of images", "1")
83
- .option("--muse-id <id>")
84
- .option("--muse-images <paths>", "Comma-separated muse image paths")
85
- .option("--no-enhance", "Skip prompt enhancement")
86
- .action(async (options) => {
87
- const config = loadConfig();
88
- const result = await generateImages(config, {
89
- inputImages: parseList(options.images),
90
- quantity: Number(options.quantity),
91
- selections: {
92
- templateId: options.template,
93
- detailId: options.detail,
94
- secondaryDetailId: options.secondaryDetail,
95
- tertiaryDetailId: options.tertiaryDetail,
96
- ethnicityId: options.ethnicity,
97
- skinToneId: options.skinTone,
98
- hairColorId: options.hairColor,
99
- backgroundId: options.background,
100
- backgroundTypeId: options.backgroundType,
101
- vibeId: options.vibe,
102
- resolutionId: options.resolution,
103
- occasionId: options.occasion
104
- },
105
- museId: options.museId,
106
- museImagePaths: options.museImages ? parseList(options.museImages) : undefined,
107
- enhancePrompt: options.enhance
108
- });
109
- console.log(JSON.stringify(result, null, 2));
110
- });
111
-
112
- program
113
- .command("refine")
114
- .requiredOption("--image <path>")
115
- .requiredOption("--instruction <text>")
116
- .option("--size <percent>")
117
- .action(async (options) => {
118
- const config = loadConfig();
119
- const output = await refineImage(config, {
120
- inputImage: options.image,
121
- instruction: options.instruction,
122
- sizeAdjustment: options.size ? Number(options.size) : undefined
123
- });
124
- console.log(output);
125
- });
126
-
127
- program
128
- .command("upscale")
129
- .requiredOption("--image <path>")
130
- .option("--scale <number>", "Scale factor", "2")
131
- .action(async (options) => {
132
- const config = loadConfig();
133
- const output = await upscaleImage(config, {
134
- inputImage: options.image,
135
- scale: Number(options.scale)
136
- });
137
- console.log(output);
138
- });
139
-
140
- program
141
- .command("muse")
142
- .requiredOption("--name <name>")
143
- .requiredOption("--source <path>")
144
- .option("--variations <n>", "Number of variations", "3")
145
- .action(async (options) => {
146
- const config = loadConfig();
147
- const result = await createMuse(config, {
148
- name: options.name,
149
- sourceImage: options.source,
150
- variations: Number(options.variations)
151
- });
152
- console.log(JSON.stringify(result, null, 2));
153
- });
154
-
155
- program
156
- .command("video")
157
- .requiredOption("--prompt <text>")
158
- .option("--aspect <ratio>")
159
- .option("--duration <seconds>")
160
- .action(async (options) => {
161
- const config = loadConfig();
162
- const result = await generateVideo(config, {
163
- prompt: options.prompt,
164
- aspectRatio: options.aspect,
165
- durationSeconds: options.duration ? Number(options.duration) : undefined
166
- });
167
- console.log(JSON.stringify(result, null, 2));
168
- });
169
-
170
- program
171
- .command("tired-girl")
172
- .option("--muse-id <id>", "Muse ID (preferred)")
173
- .option("--image <path>", "Single reference image (used if no muse-id)")
174
- .option("--styles <ids>", "Comma-separated style IDs")
175
- .option("--quantity <n>", "Number of variants", "1")
176
- .action(async (options) => {
177
- const config = loadConfig();
178
- const result = await generateTiredGirl(config, {
179
- museId: options.museId,
180
- inputImage: options.image,
181
- styleIds: options.styles ? parseList(options.styles) : undefined,
182
- quantity: Number(options.quantity)
183
- });
184
- console.log(JSON.stringify(result, null, 2));
185
- });
186
-
187
- program
188
- .command("annotate")
189
- .requiredOption("--input <path>", "Input image path")
190
- .requiredOption("--output <path>", "Output image path")
191
- .requiredOption("--text <text>", "Overlay text")
192
- .option("--font <name>", "Font family (default: Poppins)")
193
- .option("--size <px>", "Font size in px")
194
- .option("--weight <n>", "Font weight (default: 700)")
195
- .option("--color <hex>", "Text color (default: #FFFFFF)")
196
- .option("--stroke-color <value>", "Stroke/outline color (default: rgba(0,0,0,0.6))")
197
- .option("--stroke-width <px>", "Stroke/outline width (default: fontSize * 0.08)")
198
- .option("--banner", "Enable semi-transparent banner behind text")
199
- .option("--banner-color <value>", "Banner color (default: rgba(0,0,0,0.45))")
200
- .option("--banner-padding <px>", "Banner padding (default: fontSize * 0.6)")
201
- .option("--banner-radius <px>", "Banner corner radius (default: fontSize * 0.4)")
202
- .option(
203
- "--position <pos>",
204
- "top-center|top-left|top-right|bottom-center|bottom-center-high|bottom-left|bottom-right|center"
205
- )
206
- .option("--padding <px>", "Padding from edges")
207
- .option("--max-width <ratio>", "Max width ratio (0-1)")
208
- .action(async (options) => {
209
- await annotateImage(
210
- options.input,
211
- options.output,
212
- options.text,
213
- {
214
- fontFamily: options.font,
215
- fontSize: options.size ? Number(options.size) : undefined,
216
- fontWeight: options.weight ? Number(options.weight) : undefined,
217
- color: options.color,
218
- strokeColor: options.strokeColor,
219
- strokeWidth: options.strokeWidth ? Number(options.strokeWidth) : undefined,
220
- banner: options.banner ?? false,
221
- bannerColor: options.bannerColor,
222
- bannerPadding: options.bannerPadding ? Number(options.bannerPadding) : undefined,
223
- bannerRadius: options.bannerRadius ? Number(options.bannerRadius) : undefined,
224
- position: options.position,
225
- padding: options.padding ? Number(options.padding) : undefined,
226
- maxWidthRatio: options.maxWidth ? Number(options.maxWidth) : undefined
227
- }
228
- );
229
- console.log(options.output);
230
- });
231
-
232
- program
233
- .command("grid")
234
- .requiredOption("--inputs <paths>", "Comma-separated image paths")
235
- .requiredOption("--output <path>", "Output image path")
236
- .option("--columns <n>", "Number of columns", "2")
237
- .option("--rows <n>", "Number of rows", "2")
238
- .option("--padding <px>", "Padding between tiles (default: 20)")
239
- .option("--background <hex>", "Background color (default: #000000)")
240
- .option("--tile-width <px>", "Fixed tile width")
241
- .option("--tile-height <px>", "Fixed tile height")
242
- .action(async (options) => {
243
- await createImageGrid(
244
- parseList(options.inputs),
245
- options.output,
246
- {
247
- columns: Number(options.columns),
248
- rows: Number(options.rows),
249
- padding: options.padding ? Number(options.padding) : undefined,
250
- background: options.background,
251
- tileWidth: options.tileWidth ? Number(options.tileWidth) : undefined,
252
- tileHeight: options.tileHeight ? Number(options.tileHeight) : undefined
253
- }
254
- );
255
- console.log(options.output);
256
- });
257
-
258
- program.parseAsync();
259
-
@@ -1,168 +0,0 @@
1
- import { GoogleGenAI } from "@google/genai";
2
- import { AspectRatio, LumiereConfig } from "../types.js";
3
-
4
- type RetryConfig = {
5
- maxRetries: number;
6
- baseDelayMs: number;
7
- maxDelayMs: number;
8
- };
9
-
10
- type ModelConfig = {
11
- prompt: string;
12
- image: string;
13
- video: string;
14
- };
15
-
16
- const sleep = (ms: number) => new Promise((resolve) => setTimeout(resolve, ms));
17
-
18
- const isRetryableError = (error: unknown): boolean => {
19
- const message = String((error as any)?.message || "").toLowerCase();
20
- return (
21
- message.includes("429") ||
22
- message.includes("503") ||
23
- message.includes("overloaded") ||
24
- message.includes("unavailable") ||
25
- message.includes("timeout")
26
- );
27
- };
28
-
29
- const withRetry = async <T>(fn: () => Promise<T>, config: RetryConfig): Promise<T> => {
30
- let lastError: unknown;
31
- for (let attempt = 0; attempt <= config.maxRetries; attempt += 1) {
32
- try {
33
- return await fn();
34
- } catch (error) {
35
- lastError = error;
36
- if (attempt === config.maxRetries || !isRetryableError(error)) {
37
- throw error;
38
- }
39
- const delay = Math.min(config.baseDelayMs * 2 ** attempt, config.maxDelayMs);
40
- await sleep(delay + Math.random() * 0.3 * delay);
41
- }
42
- }
43
- throw lastError;
44
- };
45
-
46
- export class GeminiClient {
47
- private ai: GoogleGenAI;
48
- private models: ModelConfig;
49
- private retry: RetryConfig;
50
-
51
- constructor(config: LumiereConfig) {
52
- this.ai = new GoogleGenAI({ apiKey: config.apiKey });
53
- this.models = {
54
- prompt: config.models?.prompt ?? "gemini-3-flash-preview",
55
- image: config.models?.image ?? "gemini-3-pro-image-preview",
56
- video: config.models?.video ?? "veo-3.1-generate-preview"
57
- };
58
- this.retry = {
59
- maxRetries: config.retry?.maxRetries ?? 3,
60
- baseDelayMs: config.retry?.baseDelayMs ?? 1500,
61
- maxDelayMs: config.retry?.maxDelayMs ?? 12000
62
- };
63
- }
64
-
65
- async generateText(prompt: string): Promise<string> {
66
- return withRetry(async () => {
67
- const response = await this.ai.models.generateContent({
68
- model: this.models.prompt,
69
- contents: prompt
70
- });
71
- return response.text ?? "";
72
- }, this.retry);
73
- }
74
-
75
- async generateEnhancedPrompt(params: {
76
- parts: Array<{ text?: string; inlineData?: { mimeType: string; data: string } }>;
77
- systemInstruction?: string;
78
- }): Promise<string> {
79
- return withRetry(async () => {
80
- const response = await this.ai.models.generateContent({
81
- model: this.models.prompt,
82
- contents: [
83
- {
84
- role: "user",
85
- parts: params.parts
86
- }
87
- ],
88
- config: {
89
- systemInstruction: params.systemInstruction
90
- }
91
- });
92
- return response.text ?? "";
93
- }, this.retry);
94
- }
95
-
96
- async generateImage(params: {
97
- parts: Array<{ text?: string; inlineData?: { mimeType: string; data: string } }>;
98
- aspectRatio?: AspectRatio;
99
- imageSize?: "1K" | "2K" | "4K";
100
- }): Promise<string[]> {
101
- return withRetry(async () => {
102
- const response = await this.ai.models.generateContent({
103
- model: this.models.image,
104
- contents: [
105
- {
106
- role: "user",
107
- parts: params.parts
108
- }
109
- ],
110
- config: {
111
- imageConfig: {
112
- aspectRatio: params.aspectRatio,
113
- imageSize: params.imageSize ?? "2K"
114
- }
115
- }
116
- });
117
-
118
- const images: string[] = [];
119
- const parts = response.candidates?.[0]?.content?.parts ?? [];
120
- for (const part of parts) {
121
- const inlineData = (part as any)?.inlineData;
122
- if (inlineData?.data) {
123
- images.push(`data:${inlineData.mimeType ?? "image/png"};base64,${inlineData.data}`);
124
- }
125
- }
126
- if (images.length === 0) {
127
- throw new Error("No image data returned from Gemini.");
128
- }
129
- return images;
130
- }, this.retry);
131
- }
132
-
133
- async generateVideo(params: {
134
- prompt: string;
135
- aspectRatio?: AspectRatio;
136
- durationSeconds?: number;
137
- }): Promise<{ operationName?: string; videoBytes?: Uint8Array }>{
138
- return withRetry(async () => {
139
- let operation = await this.ai.models.generateVideos({
140
- model: this.models.video,
141
- prompt: params.prompt,
142
- config: {
143
- aspectRatio: params.aspectRatio,
144
- durationSeconds: params.durationSeconds
145
- }
146
- });
147
-
148
- while (!operation.done) {
149
- await sleep(10000);
150
- operation = await this.ai.operations.getVideosOperation({ operation });
151
- }
152
-
153
- const generated = operation.response?.generatedVideos?.[0]?.video;
154
- if (!generated) {
155
- return { operationName: operation.name };
156
- }
157
-
158
- const rawBytes = (generated as any).videoBytes;
159
- let videoBytes: Uint8Array | undefined;
160
- if (rawBytes) {
161
- videoBytes = typeof rawBytes === "string" ? Buffer.from(rawBytes, "base64") : rawBytes;
162
- }
163
-
164
- return { operationName: operation.name, videoBytes };
165
- }, this.retry);
166
- }
167
- }
168
-