import * as fs from 'fs/promises'; import * as path from 'path'; import axios from 'axios'; import dotenv from 'dotenv'; dotenv.config(); interface VideoSize { width: number; height: number; } async function generateVideo( prompt: string, imagePath: string, newFileName: string, comfyBaseUrl: string, comfyOutputDir: string, size: VideoSize = { width: 720, height: 1280 } ): Promise { const COMFY_BASE_URL = comfyBaseUrl.replace(/\/$/, ''); const COMFY_OUTPUT_DIR = comfyOutputDir; const workflow = JSON.parse(await fs.readFile('src/comfyworkflows/generate_video_text.json', 'utf-8')); workflow['27']['inputs']['text'] = prompt; workflow['28']['inputs']['width'] = size.width; workflow['28']['inputs']['height'] = size.height; const response = await axios.post(`${COMFY_BASE_URL}/prompt`, { prompt: workflow }); const promptId = response.data.prompt_id; let history; do { await new Promise(resolve => setTimeout(resolve, 1000)); const historyResponse = await axios.get(`${COMFY_BASE_URL}/history/${promptId}`); history = historyResponse.data[promptId]; } while (!history || Object.keys(history.outputs).length === 0); const files = await fs.readdir(COMFY_OUTPUT_DIR!); // find latest .mp4 file (video) in the comfy output dir const generatedFiles = files.filter(file => file.endsWith('.mp4')); const fileStats = await Promise.all( generatedFiles.map(async (file) => { const stat = await fs.stat(path.join(COMFY_OUTPUT_DIR!, file)); return { file, mtime: stat.mtime }; }) ); if (fileStats.length === 0) { throw new Error('No generated mp4 files found in comfy output directory.'); } fileStats.sort((a, b) => b.mtime.getTime() - a.mtime.getTime()); const latestFile = fileStats[0].file; const newFilePath = path.resolve('./generated', newFileName); await fs.mkdir('./generated', { recursive: true }); const sourcePath = path.join(COMFY_OUTPUT_DIR!, latestFile); await fs.copyFile(sourcePath, newFilePath); // Handle the static image (.png) // Expected image name: same base name as the video, but .png extension const expectedImageName = path.basename(newFileName, path.extname(newFileName)) + '.png'; const pngFiles = files.filter(file => file.endsWith('.png')); let sourcePngFile: string | null = null; // Prefer exact match (Comfy sometimes names the image exactly as the video base name) if (pngFiles.includes(expectedImageName)) { sourcePngFile = expectedImageName; } else if (pngFiles.length > 0) { // Fallback: pick the most recent .png by timestamp const pngStats = await Promise.all( pngFiles.map(async (file) => { const stat = await fs.stat(path.join(COMFY_OUTPUT_DIR!, file)); return { file, mtime: stat.mtime }; }) ); pngStats.sort((a, b) => b.mtime.getTime() - a.mtime.getTime()); sourcePngFile = pngStats[0].file; } if (sourcePngFile) { const targetPngPath = path.resolve('./generated', expectedImageName); // Delete existing target image if present try { await fs.unlink(targetPngPath); } catch (err) { // ignore if not exists } const sourcePngPath = path.join(COMFY_OUTPUT_DIR!, sourcePngFile); // Copy and rename the png to the generated folder with the expected name await fs.copyFile(sourcePngPath, targetPngPath); } return newFilePath; } export { generateVideo };