Skip to main content
Glama
affogato_video_generator_1758338978524.js10.2 kB
#!/usr/bin/env node const https = require('https'); const fs = require('fs').promises; const path = require('path'); require('dotenv').config({ path: path.join(__dirname, '..', 'config', '.env') }); class AffogatoVideoGenerator { constructor() { this.name = "Affogato Video Generator"; this.apiKey = process.env.AFFOGATO_API_KEY; this.baseUrl = 'api.rendernet.ai'; this.outputPath = path.join(__dirname, '..', 'data', 'FINAL_DELIVERY', 'generated_assets'); } async executeTask(input) { console.log(`🎬 ${this.name} executing task...`); const { sceneSpecs, characterId, audioFile, outputDirectory } = input; if (outputDirectory) { this.outputPath = outputDirectory; } await this.ensureOutputDirectory(); try { // Generate scene images using existing Director character const sceneImages = await this.generateSceneImages(sceneSpecs, characterId); // Create lipsync videos for singing scenes const lipsyncVideos = await this.createLipsyncVideos(sceneImages, audioFile); // Download all generated assets await this.downloadAssets(sceneImages, lipsyncVideos); return { success: true, sceneImages, lipsyncVideos, outputPath: this.outputPath, message: `Generated ${sceneImages.length} scenes and ${lipsyncVideos.length} lipsync videos` }; } catch (error) { console.error(`❌ ${this.name} failed:`, error.message); return { success: false, error: error.message, fallbackInstructions: this.createFallbackInstructions(sceneSpecs) }; } } async generateSceneImages(sceneSpecs, characterId) { console.log('🎨 Generating scene images with existing Director character...'); const sceneImages = []; for (const scene of sceneSpecs) { console.log(` 🎬 Scene ${scene.id}: ${scene.title}`); try { const imageData = { positive: `Professional creative director, ${scene.description}, cinematic music video quality, 4K photorealistic`, character_id: characterId, mode: "strong", aspect_ratio: "16:9", quality: "Plus", cfg_scale: 8, steps: 25 }; const response = await this.makeApiRequest('/pub/v1/generations', imageData); sceneImages.push({ id: scene.id, title: scene.title, image_url: response.media?.[0]?.url, generation_id: response.generation_id, status: 'generated', filename: `scene_${scene.id.toString().padStart(2, '0')}.jpg` }); console.log(` ✅ Scene ${scene.id} generated`); await this.delay(5000); // Respect rate limits } catch (error) { console.log(` ⚠️ Scene ${scene.id} failed: ${error.message}`); sceneImages.push({ id: scene.id, title: scene.title, status: 'failed', error: error.message }); } } return sceneImages; } async createLipsyncVideos(sceneImages, audioFile) { console.log('🎭 Creating lipsync videos for singing scenes...'); const lipsyncVideos = []; // Scenes that need lipsync (singing scenes) const lipsyncSceneIds = [2, 3, 4, 5, 7, 8, 9, 11, 12]; const timings = { 2: { start: 15, end: 30 }, 3: { start: 30, end: 45 }, 4: { start: 45, end: 60 }, 5: { start: 60, end: 75 }, 7: { start: 90, end: 105 }, 8: { start: 105, end: 120 }, 9: { start: 120, end: 135 }, 11: { start: 150, end: 195 }, 12: { start: 195, end: 225 } }; for (const sceneId of lipsyncSceneIds) { const sceneImage = sceneImages.find(s => s.id === sceneId); if (!sceneImage || sceneImage.status !== 'generated') { console.log(` ⚠️ Scene ${sceneId} image not available, skipping lipsync`); continue; } console.log(` 🎭 Scene ${sceneId} lipsync video...`); try { const lipsyncData = { positive: `lipsync speaking video, professional director`, image_url: sceneImage.image_url, narrator: { audio_file: audioFile, start_time: timings[sceneId].start, end_time: timings[sceneId].end }, video_anyone: true, quality: "Plus", aspect_ratio: "16:9" }; const response = await this.makeApiRequest('/pub/v1/generations', lipsyncData); lipsyncVideos.push({ sceneId, video_url: response.media?.[0]?.url, generation_id: response.generation_id, status: 'generated', filename: `scene_${sceneId.toString().padStart(2, '0')}_lipsync.mp4`, timing: timings[sceneId] }); console.log(` ✅ Scene ${sceneId} lipsync created`); await this.delay(8000); // Longer delay for video processing } catch (error) { console.log(` ⚠️ Scene ${sceneId} lipsync failed: ${error.message}`); lipsyncVideos.push({ sceneId, status: 'failed', error: error.message }); } } return lipsyncVideos; } async downloadAssets(sceneImages, lipsyncVideos) { console.log('📥 Downloading generated assets...'); // Download scene images for (const scene of sceneImages.filter(s => s.status === 'generated' && s.image_url)) { try { const filepath = path.join(this.outputPath, scene.filename); await this.downloadFile(scene.image_url, filepath); console.log(` ✅ Downloaded ${scene.filename}`); } catch (error) { console.log(` ⚠️ Failed to download ${scene.filename}: ${error.message}`); } } // Download lipsync videos for (const video of lipsyncVideos.filter(v => v.status === 'generated' && v.video_url)) { try { const filepath = path.join(this.outputPath, video.filename); await this.downloadFile(video.video_url, filepath); console.log(` ✅ Downloaded ${video.filename}`); } catch (error) { console.log(` ⚠️ Failed to download ${video.filename}: ${error.message}`); } } } async makeApiRequest(endpoint, data) { return new Promise((resolve, reject) => { const postData = JSON.stringify(data); const options = { hostname: this.baseUrl, path: endpoint, method: 'POST', 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 >= 200 && res.statusCode < 300) { resolve(parsedData.data || parsedData); } else { reject(new Error(`HTTP ${res.statusCode}: ${parsedData.message || parsedData.error || responseData}`)); } } catch (error) { reject(new Error(`Parse error: ${error.message}. Response: ${responseData}`)); } }); }); req.on('error', (error) => reject(new Error(`Request error: ${error.message}`))); req.write(postData); req.end(); }); } async downloadFile(url, outputPath) { return new Promise((resolve, reject) => { const file = require('fs').createWriteStream(outputPath); const request = url.startsWith('https:') ? https : require('http'); request.get(url, (response) => { response.pipe(file); file.on('finish', () => { file.close(); resolve(outputPath); }); file.on('error', (error) => { require('fs').unlink(outputPath, () => {}); reject(error); }); }).on('error', reject); }); } async ensureOutputDirectory() { await fs.mkdir(this.outputPath, { recursive: true }); } async delay(ms) { return new Promise(resolve => setTimeout(resolve, ms)); } createFallbackInstructions(sceneSpecs) { return `If API generation fails, use the manual generation guide with these exact prompts:\n${sceneSpecs.map(s => `Scene ${s.id}: ${s.description}`).join('\n')}`; } } module.exports = AffogatoVideoGenerator;

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