UNPKG

@henteko/kumiki

Version:

A video generation tool that creates videos from JSON configurations

112 lines 4.54 kB
import { GoogleGenAI, Modality } from '@google/genai'; import { ConfigManager } from '../utils/config.js'; import { KumikiError } from '../utils/errors.js'; import { logger } from '../utils/logger.js'; export class GeminiError extends KumikiError { constructor(message, details) { super(message, 'GEMINI_ERROR', details); } } export class GeminiImageService { genAI = null; initialized = false; async initialize() { if (this.initialized) return; // Try to get API key from config first, then environment variable const apiKey = await ConfigManager.get('gemini.apiKey') || process.env.GEMINI_API_KEY; if (apiKey) { this.genAI = new GoogleGenAI({ apiKey: apiKey, }); } this.initialized = true; } async generateImage(params) { await this.initialize(); if (!this.genAI) { throw new GeminiError('Gemini API key is not configured. Set it using: kumiki config set gemini.apiKey <YOUR_API_KEY> or set GEMINI_API_KEY environment variable'); } logger.info('Generating image with Gemini', { prompt: params.prompt, style: params.style, aspectRatio: params.aspectRatio, }); try { const enhancedPrompt = this.enhancePrompt(params.prompt, params.style, params.aspectRatio); logger.info('Using Gemini 2.0 Flash model', { enhancedPrompt }); const response = await this.genAI.models.generateContent({ model: 'gemini-2.0-flash-preview-image-generation', contents: enhancedPrompt, config: { responseModalities: [Modality.TEXT, Modality.IMAGE], }, }); // レスポンスから画像データを抽出 if (!response.candidates || response.candidates.length === 0) { throw new Error('No candidates in response'); } const candidate = response.candidates[0]; if (!candidate || !candidate.content || !candidate.content.parts) { throw new Error('No content parts in response'); } // 画像データを探す let imageData = null; for (const part of candidate.content.parts) { if (part.inlineData && part.inlineData.mimeType?.startsWith('image/')) { imageData = Buffer.from(part.inlineData.data, 'base64'); break; } } if (!imageData) { throw new Error('No image data found in response'); } return imageData; } catch (error) { if (error instanceof GeminiError) { throw error; } logger.error('Failed to generate image', { error: error instanceof Error ? error.message : String(error), stack: error instanceof Error ? error.stack : undefined, params }); throw new GeminiError(error instanceof Error ? error.message : 'Failed to generate image', error); } } /** * プロンプトを最適化 */ enhancePrompt(prompt, style, aspectRatio) { const parts = [prompt]; if (style) { const styleDescriptions = { photorealistic: 'photorealistic, high quality photograph, professional photography', illustration: 'digital illustration, artistic style, clean and modern', anime: 'anime style, japanese animation, manga art', sketch: 'pencil sketch, hand drawn, artistic sketch', }; const styleDesc = styleDescriptions[style]; if (styleDesc) { parts.push(styleDesc); } } if (aspectRatio) { const aspectDescriptions = { '16:9': 'widescreen format, horizontal orientation', '9:16': 'vertical format, portrait orientation', '1:1': 'square format', '4:3': 'standard format', }; const aspectDesc = aspectDescriptions[aspectRatio]; if (aspectDesc) { parts.push(aspectDesc); } } return parts.join('. ') + '.'; } } // シングルトンインスタンス export const geminiImageService = new GeminiImageService(); //# sourceMappingURL=gemini.js.map