Skip to main content
Glama
affogato-client.ts9.75 kB
import FormData from 'form-data'; import fs from 'fs'; import path from 'path'; import https from 'https'; import http from 'http'; export interface AffogatoUploadResponse { id: string; filename: string; url?: string; } export interface AffogatoCharacterResponse { character_id: string; asset_id: string; name: string; description: string; created_at: string; } export interface AffogatoGenerationResponse { data: { credits_remaining: number; generation_id: string; media: Array<{ id: string; url: string; dim: { height: number; width: number }; model: string; status: string; style?: string; type: string; }>; result: string; }; err: any; } export interface AffogatoNarratorConfig { audio_file: string; start_time: number; end_time: number; } export class AffogatoClient { private apiKey: string; private baseUrl: string = 'api.rendernet.ai'; private outputPath: string = './generated_assets'; private maxRetries: number = 3; private baseDelay: number = 1000; constructor(apiKey: string) { if (!apiKey) { throw new Error('Affogato API key is required'); } this.apiKey = apiKey; } async makeApiRequest(endpoint: string, data?: any, method: string = 'POST', retryCount: number = 0): Promise<any> { const postData = data ? JSON.stringify(data) : ''; console.log(`🔗 Affogato API Request: ${method} https://${this.baseUrl}${endpoint} (attempt ${retryCount + 1}/${this.maxRetries + 1})`); if (retryCount > 0) { console.log(` ⏰ Retry attempt after ${this.calculateDelay(retryCount - 1)}ms delay`); } try { const result = await this._executeRequest(endpoint, postData, method); return result; } catch (error: any) { if (retryCount < this.maxRetries && this.isRetryableError(error)) { const delay = this.calculateDelay(retryCount); console.log(`⚠️ Request failed, retrying in ${delay}ms...`); await this.delay(delay); return this.makeApiRequest(endpoint, data, method, retryCount + 1); } throw error; } } private async _executeRequest(endpoint: string, postData: string, method: string): Promise<any> { return new Promise((resolve, reject) => { const options = { hostname: this.baseUrl, path: endpoint, method: method, headers: { 'X-API-KEY': this.apiKey, 'Content-Type': 'application/json', 'Content-Length': Buffer.byteLength(postData) } }; const req = https.request(options, (res) => { let responseData = ''; res.on('data', (chunk) => responseData += chunk); res.on('end', () => { try { const parsedData = JSON.parse(responseData); if (res.statusCode && res.statusCode >= 200 && res.statusCode < 300) { resolve(parsedData.data || parsedData); } else { reject(new Error(`HTTP ${res.statusCode}: ${parsedData.message || parsedData.error || responseData}`)); } } catch (error: any) { reject(new Error(`Parse error: ${error.message}. Response: ${responseData}`)); } }); }); req.on('error', (error) => reject(new Error(`Request error: ${error.message}`))); if (postData) { req.write(postData); } req.end(); }); } private calculateDelay(retryCount: number): number { return Math.min(this.baseDelay * Math.pow(2, retryCount), 30000); } private isRetryableError(error: any): boolean { return error.message.includes('timeout') || error.message.includes('ECONNRESET') || error.message.includes('502') || error.message.includes('503'); } private async delay(ms: number): Promise<void> { return new Promise(resolve => setTimeout(resolve, ms)); } async uploadAsset(filePath: string): Promise<AffogatoUploadResponse> { return new Promise((resolve, reject) => { const formData = new FormData(); const fileName = path.basename(filePath); formData.append('file', fs.createReadStream(filePath), { filename: fileName, contentType: this.getMimeType(fileName) }); const options = { hostname: this.baseUrl, path: '/pub/v1/assets/upload', method: 'POST', headers: { 'X-API-KEY': this.apiKey, ...formData.getHeaders() } }; const req = https.request(options, (res) => { let responseData = ''; res.on('data', (chunk) => responseData += chunk); res.on('end', () => { try { const parsedData = JSON.parse(responseData); if (res.statusCode && res.statusCode >= 200 && res.statusCode < 300) { resolve(parsedData.data || parsedData); } else { reject(new Error(`Upload failed: ${res.statusCode} ${parsedData.message || responseData}`)); } } catch (error: any) { reject(new Error(`Upload parse error: ${error.message}`)); } }); }); req.on('error', (error) => reject(error)); formData.pipe(req); }); } async createCharacter( assetId: string, name: string, description: string = '', style: string = 'realistic' ): Promise<AffogatoCharacterResponse> { const characterData = { name, description, style, asset_id: assetId }; console.log(`🎭 Creating FaceLock character: ${name}`); const response = await this.makeApiRequest('/pub/v1/characters', characterData); return { character_id: response.character_id || response.id, asset_id: assetId, name, description, created_at: new Date().toISOString() }; } async getCharacter(characterId: string): Promise<AffogatoCharacterResponse> { const response = await this.makeApiRequest(`/pub/v1/characters/${characterId}`, null, 'GET'); return response; } async listCharacters(): Promise<AffogatoCharacterResponse[]> { const response = await this.makeApiRequest('/pub/v1/characters', null, 'GET'); return Array.isArray(response) ? response : [response]; } // Generate scene images with FaceLock character consistency (following guidance notes) async generateSceneImage( characterId: string, scenePrompt: string, aspectRatio: string = '16:9', mode: string = 'strong' ): Promise<AffogatoGenerationResponse> { console.log(`🎬 Generating scene with FaceLock character: ${characterId}`); const imageData = { positive: `${scenePrompt}, cinematic quality, 4K photorealistic`, character_id: characterId, mode: mode, // 'strong' for FaceLock consistency aspect_ratio: aspectRatio, quality: 'Plus', cfg_scale: 8, steps: 25 }; return await this.makeApiRequest('/pub/v1/generations', imageData); } // Generate lipsync video with narrator feature (following guidance notes) async generateLipsyncVideo( imageUrl: string, narratorConfig: AffogatoNarratorConfig, prompt: string = 'lipsync speaking video, professional', aspectRatio: string = '16:9' ): Promise<AffogatoGenerationResponse> { console.log(`🎭 Generating lipsync video with narrator feature`); const lipsyncData = { positive: prompt, image_url: imageUrl, narrator: { audio_file: narratorConfig.audio_file, start_time: narratorConfig.start_time, end_time: narratorConfig.end_time }, video_anyone: true, // Enable video generation quality: 'Plus', aspect_ratio: aspectRatio }; return await this.makeApiRequest('/pub/v1/generations', lipsyncData); } // Generate video from image (following guidance notes) async generateVideoFromImage( imageUrl: string, prompt: string, duration: number = 5, aspectRatio: string = '16:9' ): Promise<AffogatoGenerationResponse> { console.log(`🎥 Converting image to video with Video Agent`); const videoData = { positive: prompt, image_url: imageUrl, video_anyone: true, duration: duration, quality: 'Plus', aspect_ratio: aspectRatio }; return await this.makeApiRequest('/pub/v1/generations', videoData); } // Get generation status async getGenerationStatus(generationId: string): Promise<any> { return await this.makeApiRequest(`/pub/v1/generations/${generationId}`, null, 'GET'); } // Download generated media async downloadMedia(url: string, outputPath: string): Promise<string> { return new Promise((resolve, reject) => { const file = fs.createWriteStream(outputPath); const request = url.startsWith('https:') ? https : http; request.get(url, (response) => { response.pipe(file); file.on('finish', () => { file.close(); resolve(outputPath); }); file.on('error', (error: any) => { fs.unlink(outputPath, () => {}); reject(error); }); }).on('error', reject); }); } private getMimeType(filename: string): string { const ext = path.extname(filename).toLowerCase(); const mimeTypes: Record<string, string> = { '.jpg': 'image/jpeg', '.jpeg': 'image/jpeg', '.png': 'image/png', '.gif': 'image/gif', '.webp': 'image/webp', '.mp4': 'video/mp4', '.mov': 'video/quicktime', '.avi': 'video/x-msvideo', '.mp3': 'audio/mpeg', '.wav': 'audio/wav', '.m4a': 'audio/mp4' }; return mimeTypes[ext] || 'application/octet-stream'; } }

Latest Blog Posts

MCP directory API

We provide all the information about MCP servers via our MCP API.

curl -X GET 'https://glama.ai/api/mcp/v1/servers/bermingham85/mcp-puppet-pipeline'

If you have feedback or need assistance with the MCP directory API, please join our Discord server