Skip to content

Instantly share code, notes, and snippets.

Show Gist options
  • Save frostbitten/1fb155b3d89f8cc199d668a20257e87f to your computer and use it in GitHub Desktop.
Save frostbitten/1fb155b3d89f8cc199d668a20257e87f to your computer and use it in GitHub Desktop.
Animate Anything in Comfy UI (via the API) with Node (ts)
Animate Anything in Comfy UI (via the API) with Node (ts)
{
"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"
}
}
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