@dondonudonjp/vertexai-imagen-mcp-server
Version:
[DEPRECATED] MCP Server for Vertex AI Imagen image generation. Imagen endpoints shut down 2026-06-30; migrate to the nanoBanana MCP Server (Gemini image models).
252 lines • 9.4 kB
JavaScript
/**
* 画像メタデータ管理ユーティリティ
*/
import { randomUUID, createHash } from 'crypto';
import sharp from 'sharp';
import extract from 'png-chunks-extract';
import encode from 'png-chunks-encode';
import text from 'png-chunk-text';
/**
* 新しい画像UUIDを生成
*/
export function generateImageUUID() {
return randomUUID();
}
/**
* パラメータのSHA-256ハッシュを計算
*
* @param params パラメータオブジェクト
* @returns SHA-256ハッシュ(16進数文字列)
*/
export function calculateParamsHash(params) {
// キーをソートして一貫性を保証
const sortedKeys = Object.keys(params).sort();
const sortedParams = {};
for (const key of sortedKeys) {
sortedParams[key] = params[key];
}
const paramsJson = JSON.stringify(sortedParams);
return createHash('sha256').update(paramsJson, 'utf8').digest('hex');
}
/**
* 画像バッファにメタデータを埋め込む
*
* @param imageBuffer 画像バッファ
* @param metadata 埋め込むメタデータ
* @param level メタデータレベル(環境変数から取得)
* @returns メタデータが埋め込まれた画像バッファ
*/
export async function embedMetadata(imageBuffer, metadata, level) {
const metadataLevel = level ||
process.env.VERTEXAI_IMAGEN_METADATA_LEVEL ||
'standard';
// レベルに応じてメタデータをフィルタリング
let filteredMetadata;
switch (metadataLevel) {
case 'minimal':
// UUIDとハッシュのみ
filteredMetadata = {
vertexai_imagen_uuid: metadata.vertexai_imagen_uuid,
params_hash: metadata.params_hash,
};
break;
case 'standard':
// UUID + ハッシュ + 基本パラメータ
filteredMetadata = {
vertexai_imagen_uuid: metadata.vertexai_imagen_uuid,
params_hash: metadata.params_hash,
tool_name: metadata.tool_name,
model: metadata.model,
created_at: metadata.created_at,
aspect_ratio: metadata.aspect_ratio,
sample_image_size: metadata.sample_image_size,
};
break;
case 'full':
// 全てのメタデータ
filteredMetadata = metadata;
break;
default:
filteredMetadata = metadata;
}
// メタデータをJSON文字列に変換
const metadataJson = JSON.stringify(filteredMetadata);
// 画像フォーマットを検出
const image = sharp(imageBuffer);
const imageMetadata = await image.metadata();
try {
if (imageMetadata.format === 'png') {
// PNGの場合、tEXtチャンクに埋め込み
// png-chunksを使用してtEXtチャンクを追加
const chunks = extract(imageBuffer);
// VertexAI Imagen用のtEXtチャンクを作成
const textChunk = text.encode('vertexai_imagen_metadata', metadataJson);
// IENDチャンクの前に挿入(IENDは最後のチャンクである必要がある)
const iendIndex = chunks.findIndex(chunk => chunk.name === 'IEND');
if (iendIndex !== -1) {
chunks.splice(iendIndex, 0, textChunk);
}
else {
chunks.push(textChunk);
}
// PNGバッファを再構築
return Buffer.from(encode(chunks));
}
else if (imageMetadata.format === 'jpeg' || imageMetadata.format === 'jpg') {
// JPEGの場合、EXIFのUserCommentに埋め込み
return await image
.jpeg({
quality: 95,
})
.withMetadata({
exif: {
IFD0: {
ImageDescription: metadataJson,
},
},
})
.toBuffer();
}
else if (imageMetadata.format === 'webp') {
// WebPの場合、EXIFメタデータに埋め込み
return await image
.webp({
quality: 95,
})
.withMetadata({
exif: {
IFD0: {
ImageDescription: metadataJson,
},
},
})
.toBuffer();
}
else {
// サポートされていないフォーマットの場合はそのまま返す
if (process.env.DEBUG) {
console.error(`[DEBUG] Unsupported image format for metadata embedding: ${imageMetadata.format}`);
}
return imageBuffer;
}
}
catch (error) {
// メタデータ埋め込みに失敗した場合は警告を出して元の画像を返す
const errorMessage = error instanceof Error ? error.message : String(error);
console.error(`[WARNING] Failed to embed metadata: ${errorMessage}`);
return imageBuffer;
}
}
/**
* 画像ファイルからメタデータを抽出
*
* @param imagePath 画像ファイルパス
* @returns 抽出されたメタデータ、存在しない場合はnull
*/
export async function extractMetadataFromImage(imagePath) {
try {
const image = sharp(imagePath);
const metadata = await image.metadata();
// PNG形式の場合、tEXtチャンクから読み取り
if (metadata.format === 'png') {
const fs = await import('fs/promises');
const fileBuffer = await fs.readFile(imagePath);
const chunks = extract(fileBuffer);
// vertexai_imagen_metadataチャンクを探す
const metadataChunk = chunks.find(chunk => chunk.name === 'tEXt' && text.decode(chunk.data).keyword === 'vertexai_imagen_metadata');
if (metadataChunk) {
const decoded = text.decode(metadataChunk.data);
try {
const extractedMetadata = JSON.parse(decoded.text);
return extractedMetadata;
}
catch (parseError) {
if (process.env.DEBUG) {
console.error(`[DEBUG] Failed to parse PNG metadata JSON: ${parseError}`);
}
}
}
}
else {
// JPEG/WebP形式の場合、EXIFメタデータから読み取り
if (metadata.exif) {
const exifBuffer = metadata.exif;
const exifString = exifBuffer.toString('utf8', 0, Math.min(exifBuffer.length, 10000));
const jsonMatch = exifString.match(/\{[^{}]*vertexai_imagen_uuid[^{}]*\}/);
if (jsonMatch) {
try {
const extractedMetadata = JSON.parse(jsonMatch[0]);
return extractedMetadata;
}
catch (parseError) {
if (process.env.DEBUG) {
console.error(`[DEBUG] Failed to parse EXIF metadata JSON: ${parseError}`);
}
}
}
}
}
return null;
}
catch (error) {
const errorMessage = error instanceof Error ? error.message : String(error);
console.error(`[WARNING] Failed to extract metadata from ${imagePath}: ${errorMessage}`);
return null;
}
}
/**
* 画像の整合性を検証(ハッシュ値の比較)
*
* @param imageMetadata 画像から抽出されたメタデータ
* @param dbParams データベースに保存されているパラメータ
* @returns 整合性検証結果
*/
export function verifyIntegrity(imageMetadata, dbParams) {
if (!imageMetadata.params_hash) {
return {
valid: false,
message: 'Image metadata does not contain params_hash',
};
}
const calculatedHash = calculateParamsHash(dbParams);
if (imageMetadata.params_hash === calculatedHash) {
return {
valid: true,
message: 'Image integrity verified: hash values match',
};
}
else {
return {
valid: false,
message: `Image integrity check failed: hash mismatch (image: ${imageMetadata.params_hash.substring(0, 8)}..., db: ${calculatedHash.substring(0, 8)}...)`,
};
}
}
/**
* メタデータ埋め込みが有効かどうかを確認
*
* @returns メタデータ埋め込みが有効な場合 true
*/
export function isMetadataEmbeddingEnabled() {
const envValue = process.env.VERTEXAI_IMAGEN_EMBED_METADATA;
// デフォルトは true
if (envValue === undefined || envValue === null || envValue === '') {
return true;
}
// 'false' または '0' の場合は無効
return envValue !== 'false' && envValue !== '0';
}
/**
* メタデータレベルを取得
*
* @returns 現在のメタデータレベル
*/
export function getMetadataLevel() {
const level = process.env.VERTEXAI_IMAGEN_METADATA_LEVEL;
if (level === 'minimal' || level === 'standard' || level === 'full') {
return level;
}
// デフォルトは standard
return 'standard';
}
//# sourceMappingURL=metadata.js.map