Last active
August 8, 2024 18:53
-
-
Save frostbitten/1fb155b3d89f8cc199d668a20257e87f to your computer and use it in GitHub Desktop.
Animate Anything in Comfy UI (via the API) with Node (ts)
This file contains hidden or bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Animate Anything in Comfy UI (via the API) with Node (ts) |
This file contains hidden or bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
{ | |
"name": "comfyui-api-animation", | |
"version": "1.0.0", | |
"description": "", | |
"main": "index.js", | |
"scripts": { | |
"test": "echo \"Error: no test specified\" && exit 1" | |
}, | |
"keywords": [], | |
"author": "", | |
"license": "ISC", | |
"dependencies": { | |
"@types/node": "^22.1.0", | |
"axios": "^1.7.3", | |
"crc": "^4.3.2", | |
"typescript": "^5.5.4", | |
"uuid": "^10.0.0", | |
"ws": "^8.18.0" | |
}, | |
"devDependencies": { | |
"@types/uuid": "^10.0.0", | |
"@types/ws": "^8.5.12" | |
} | |
} |
This file contains hidden or bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
import axios from 'axios'; | |
import fs from 'fs'; | |
import path from 'path'; | |
import { v4 as uuidv4 } from 'uuid'; | |
import { WebSocket } from 'ws'; | |
import { crc32 } from 'crc'; | |
let frame = 0 | |
const apiUrl = 'http://localhost:8188'; // Replace with your ComfyUI API URL | |
const clientId = uuidv4(); // Generate a unique client ID for the WebSocket connection | |
const ws:any = new WebSocket(`ws://localhost:8188/ws?clientId=${clientId}`); | |
const workflowFile = 'workflow_api.json'; // Replace with your workflow.json file path | |
const SaveImageNodeID = "9" | |
const KSamplerNodeId = "3"; // Replace with the ID of the node you want to animate | |
const KSamplerConfig = { | |
steps: 10, | |
seed: 1, | |
cfg: 7, | |
sampler_name: "euler", | |
scheduler: "normal", | |
} | |
let denoiseValue = 0.1; | |
let denoiseGrowth = 0.05; | |
let denoiseMax = 1; | |
const projectName = "MonaLisaAni" | |
function frameDone(){ | |
denoiseValue += denoiseGrowth; | |
frame++; | |
} | |
function isLoopRunning(i: number){ | |
return denoiseValue < denoiseMax | |
} | |
const animateWorkflow = async () => { | |
console.log(`animateWorkflow...`); | |
try { | |
const workflowPath = path.resolve(__dirname, workflowFile); | |
const workflowData = fs.readFileSync(workflowPath, 'utf-8'); | |
const workflow = JSON.parse(workflowData); | |
// Find the node to animate | |
const samplerNode = workflow[KSamplerNodeId]; | |
if (!samplerNode) { | |
throw new Error(`Node with ID ${KSamplerNodeId} not found`); | |
} | |
console.log(`Got node ${KSamplerNodeId}:`, samplerNode); | |
Object.assign(samplerNode.inputs, KSamplerConfig); | |
const jobId = crc32(JSON.stringify(workflow)); | |
// Example: Animate the parameter | |
console.log(`Begin animating workflow...`); | |
console.log(`isLoopRunning? ${isLoopRunning(0)}`); | |
for (let i = 0; isLoopRunning(i); i++) { | |
console.log(`rendering frame ${frame}...`); | |
const frameNo = frame.toString().padStart(5, '0'); | |
samplerNode.inputs.denoise = denoiseValue | |
workflow[SaveImageNodeID].inputs.filename_prefix = `${projectName}_j${jobId}_f${frameNo}`; | |
// Send the modified workflow to the API | |
const response = await axios.post(`${apiUrl}/prompt`, { prompt: workflow }); | |
console.log(`Step ${i + 1}:`, response.data); | |
// Wait until the WebSocket receives the execution completion message | |
await new Promise((resolve, reject) => { | |
function handleMessage(data: string) { | |
const message = JSON.parse(data); | |
console.log('WebSocket message:', JSON.stringify(message,null,2)); | |
if (message.type === 'status' && message.data.status.exec_info.queue_remaining === 0) { | |
ws.off('message', handleMessage); | |
ws.off('error', handleError); | |
resolve(null); // Execution is done | |
} | |
} | |
ws.on('message', handleMessage); | |
ws.on('error', handleError); | |
function handleError(error: Error) { | |
ws.off('message', handleMessage); | |
ws.off('error', handleError); | |
reject(error); | |
} | |
}); | |
// Optional: Add a delay between steps | |
await new Promise(resolve => setTimeout(resolve, 1000)); | |
frameDone() | |
} | |
} catch (error) { | |
console.error('Error animating workflow:', error); | |
} | |
ws.close(); | |
}; | |
animateWorkflow(); |
Sign up for free
to join this conversation on GitHub.
Already have an account?
Sign in to comment