n8n-nodes-comfyui-image-to-image
Version:
n8n nodes to integrate with ComfyUI for image transformations, dual image processing, image+video processing, and text-to-video generation using stable diffusion workflows
392 lines โข 18.5 kB
JavaScript
;
Object.defineProperty(exports, "__esModule", { value: true });
exports.ComfyuiTextToAudio = void 0;
const n8n_workflow_1 = require("n8n-workflow");
class ComfyuiTextToAudio {
constructor() {
this.description = {
displayName: 'ComfyUI Text to Audio',
name: 'comfyuiTextToAudio',
icon: 'file:comfyui.svg',
group: ['transform'],
version: 1,
description: '๐ต Generate audio from text using ComfyUI workflows with ACE Step Audio models',
defaults: {
name: 'ComfyUI Text to Audio',
},
credentials: [
{
name: 'comfyUIApi',
required: true,
},
],
inputs: ['main'],
outputs: ['main'],
properties: [
{
displayName: 'Workflow JSON',
name: 'workflow',
type: 'string',
typeOptions: {
rows: 10,
},
default: JSON.stringify({
"14": {
"inputs": {
"tags": "Contemporary rock instrumental with powerful electric guitar power chords, dynamic drumming, driving bass, soaring lead guitar solos, arena rock energy, 135 BPM, stadium-worthy hooks, freedom and adventure spirit, no vocals, pure instrumental",
"lyrics": "",
"lyrics_strength": 0.99,
"clip": ["40", 1]
},
"class_type": "TextEncodeAceStepAudio",
"_meta": {
"title": "TextEncodeAceStepAudio"
}
},
"17": {
"inputs": {
"seconds": 180,
"batch_size": 1
},
"class_type": "EmptyAceStepLatentAudio",
"_meta": {
"title": "EmptyAceStepLatentAudio"
}
},
"18": {
"inputs": {
"samples": ["52", 0],
"vae": ["40", 2]
},
"class_type": "VAEDecodeAudio",
"_meta": {
"title": "์ค๋์ค VAE ๋์ฝ๋"
}
},
"40": {
"inputs": {
"ckpt_name": "ace_step_v1_3.5b.safetensors"
},
"class_type": "CheckpointLoaderSimple",
"_meta": {
"title": "์ฒดํฌํฌ์ธํธ ๋ก๋"
}
},
"44": {
"inputs": {
"conditioning": ["14", 0]
},
"class_type": "ConditioningZeroOut",
"_meta": {
"title": "์กฐ๊ฑด (0์ผ๋ก ์ถ๋ ฅ)"
}
},
"49": {
"inputs": {
"model": ["51", 0],
"operation": ["50", 0]
},
"class_type": "LatentApplyOperationCFG",
"_meta": {
"title": "์ ์ฌ ๋ฐ์ดํฐ CFG ์ฐ์ฐ (์ฐ์ฐ ์ ์ฉ)"
}
},
"50": {
"inputs": {
"multiplier": 1.0
},
"class_type": "LatentOperationTonemapReinhard",
"_meta": {
"title": "์ ์ฌ ๋ฐ์ดํฐ ์ฐ์ฐ (ํค๋งต ๋ ์ธํ๋ฅดํธ)"
}
},
"51": {
"inputs": {
"shift": 5.0,
"model": ["40", 0]
},
"class_type": "ModelSamplingSD3",
"_meta": {
"title": "๋ชจ๋ธ ์ํ๋ง (SD3)"
}
},
"52": {
"inputs": {
"seed": 763036669622641,
"steps": 50,
"cfg": 5,
"sampler_name": "euler",
"scheduler": "simple",
"denoise": 1,
"model": ["49", 0],
"positive": ["14", 0],
"negative": ["44", 0],
"latent_image": ["17", 0]
},
"class_type": "KSampler",
"_meta": {
"title": "KSampler"
}
},
"59": {
"inputs": {
"filename_prefix": "audio/ComfyUI",
"quality": "128k",
"audioUI": "",
"audio": ["18", 0]
},
"class_type": "SaveAudioMP3",
"_meta": {
"title": "Save Audio (MP3)"
}
}
}, null, 2),
required: true,
description: 'The ComfyUI workflow in JSON format for text-to-audio generation',
},
{
displayName: 'Text Prompt',
name: 'textPrompt',
type: 'string',
typeOptions: {
rows: 3,
},
default: 'Contemporary rock instrumental with powerful electric guitar power chords, dynamic drumming, driving bass, soaring lead guitar solos, arena rock energy, 135 BPM, stadium-worthy hooks, freedom and adventure spirit, no vocals, pure instrumental',
required: true,
description: 'Description of the audio you want to generate',
},
{
displayName: 'Audio Duration (Seconds)',
name: 'audioDuration',
type: 'number',
default: 180,
description: 'Duration of the generated audio in seconds',
},
{
displayName: 'Audio Quality',
name: 'audioQuality',
type: 'options',
options: [
{ name: '128k', value: '128k' },
{ name: '192k', value: '192k' },
{ name: '256k', value: '256k' },
{ name: '320k', value: '320k' },
{ name: '64k', value: '64k' }
],
default: '128k',
description: 'Quality/bitrate of the generated audio',
},
{
displayName: 'Seed',
name: 'seed',
type: 'number',
default: -1,
description: 'Random seed for generation (-1 for random)',
},
{
displayName: 'Steps',
name: 'steps',
type: 'number',
default: 50,
description: 'Number of sampling steps',
},
{
displayName: 'CFG Scale',
name: 'cfg',
type: 'number',
default: 5,
description: 'CFG scale for guidance strength',
},
{
displayName: 'Timeout',
name: 'timeout',
type: 'number',
default: 30,
description: 'Maximum time in minutes to wait for audio generation',
},
],
};
}
async execute() {
var _a, _b;
const credentials = await this.getCredentials('comfyUIApi');
const workflow = this.getNodeParameter('workflow', 0);
const textPrompt = this.getNodeParameter('textPrompt', 0);
const audioDuration = this.getNodeParameter('audioDuration', 0);
const audioQuality = this.getNodeParameter('audioQuality', 0);
const seed = this.getNodeParameter('seed', 0);
const steps = this.getNodeParameter('steps', 0);
const cfg = this.getNodeParameter('cfg', 0);
const timeout = this.getNodeParameter('timeout', 0);
const apiUrl = credentials.apiUrl;
const apiKey = credentials.apiKey;
console.log('[ComfyUI Audio] Executing audio generation with API URL:', apiUrl);
const headers = {
'Content-Type': 'application/json',
};
if (apiKey) {
console.log('[ComfyUI Audio] Using API key authentication');
headers['Authorization'] = `Bearer ${apiKey}`;
}
try {
console.log('[ComfyUI Audio] Checking API connection...');
await this.helpers.request({
method: 'GET',
url: `${apiUrl}/system_stats`,
headers,
json: true,
});
let workflowData;
try {
workflowData = JSON.parse(workflow);
}
catch (error) {
throw new n8n_workflow_1.NodeApiError(this.getNode(), {
message: 'Invalid workflow JSON. Please check the JSON syntax and try again.',
description: error.message
});
}
if (typeof workflowData !== 'object' || workflowData === null) {
throw new n8n_workflow_1.NodeApiError(this.getNode(), {
message: 'Invalid workflow structure. The workflow must be a valid JSON object.'
});
}
const textEncodeNode = Object.values(workflowData).find((node) => node.class_type === 'TextEncodeAceStepAudio');
if (!textEncodeNode) {
throw new n8n_workflow_1.NodeApiError(this.getNode(), {
message: 'No TextEncodeAceStepAudio node found in the workflow. The workflow must contain a TextEncodeAceStepAudio node.'
});
}
textEncodeNode.inputs.tags = textPrompt;
const latentAudioNode = Object.values(workflowData).find((node) => node.class_type === 'EmptyAceStepLatentAudio');
if (latentAudioNode) {
latentAudioNode.inputs.seconds = audioDuration;
}
const ksamplerNode = Object.values(workflowData).find((node) => node.class_type === 'KSampler');
if (ksamplerNode) {
ksamplerNode.inputs.seed = seed === -1 ? Math.floor(Math.random() * 1000000000000000) : seed;
ksamplerNode.inputs.steps = steps;
ksamplerNode.inputs.cfg = cfg;
}
const saveAudioNode = Object.values(workflowData).find((node) => node.class_type === 'SaveAudioMP3');
if (saveAudioNode) {
saveAudioNode.inputs.quality = audioQuality;
}
console.log('[ComfyUI Audio] Queueing audio generation...');
const response = await this.helpers.request({
method: 'POST',
url: `${apiUrl}/prompt`,
headers,
body: {
prompt: workflowData,
},
json: true,
});
if (!response.prompt_id) {
throw new n8n_workflow_1.NodeApiError(this.getNode(), { message: 'Failed to get prompt ID from ComfyUI' });
}
const promptId = response.prompt_id;
console.log('[ComfyUI Audio] Audio generation queued with ID:', promptId);
let attempts = 0;
const maxAttempts = 60 * timeout;
await new Promise(resolve => setTimeout(resolve, 5000));
while (attempts < maxAttempts) {
console.log(`[ComfyUI Audio] Checking audio generation status (attempt ${attempts + 1}/${maxAttempts})...`);
await new Promise(resolve => setTimeout(resolve, 1000));
attempts++;
const history = await this.helpers.request({
method: 'GET',
url: `${apiUrl}/history/${promptId}`,
headers,
json: true,
});
const promptResult = history[promptId];
if (!promptResult) {
console.log('[ComfyUI Audio] Prompt not found in history');
continue;
}
if (promptResult.status === undefined) {
console.log('[ComfyUI Audio] Execution status not found');
continue;
}
if ((_a = promptResult.status) === null || _a === void 0 ? void 0 : _a.completed) {
console.log('[ComfyUI Audio] Audio generation completed');
if (((_b = promptResult.status) === null || _b === void 0 ? void 0 : _b.status_str) === 'error') {
throw new n8n_workflow_1.NodeApiError(this.getNode(), { message: '[ComfyUI Audio] Audio generation failed' });
}
console.log('[ComfyUI Audio] Raw outputs structure:', JSON.stringify(promptResult.outputs, null, 2));
const audioOutputs = Object.values(promptResult.outputs)
.flatMap((nodeOutput) => nodeOutput.audio || [])
.filter((audio) => audio.type === 'output' || audio.type === 'temp')
.map((audio) => ({
...audio,
url: `${apiUrl}/view?filename=${audio.filename}&subfolder=${audio.subfolder || ''}&type=${audio.type}`
}));
console.log('[ComfyUI Audio] Found audio outputs:', audioOutputs);
if (audioOutputs.length === 0) {
throw new n8n_workflow_1.NodeApiError(this.getNode(), { message: '[ComfyUI Audio] No audio outputs found in results' });
}
const audioOutput = audioOutputs[0];
const audioResponse = await this.helpers.request({
method: 'GET',
url: audioOutput.url,
encoding: null,
resolveWithFullResponse: true
});
if (audioResponse.statusCode === 404) {
throw new n8n_workflow_1.NodeApiError(this.getNode(), { message: `Audio file not found at ${audioOutput.url}` });
}
console.log('[ComfyUI Audio] Using audio directly from ComfyUI');
const buffer = Buffer.from(audioResponse.body);
const base64Data = buffer.toString('base64');
const fileSize = Math.round(buffer.length / 1024 * 10) / 10 + " kB";
let mimeType = 'audio/mpeg';
let fileExtension = 'mp3';
if (audioOutput.filename.endsWith('.wav')) {
mimeType = 'audio/wav';
fileExtension = 'wav';
}
else if (audioOutput.filename.endsWith('.ogg')) {
mimeType = 'audio/ogg';
fileExtension = 'ogg';
}
else if (audioOutput.filename.endsWith('.m4a')) {
mimeType = 'audio/mp4';
fileExtension = 'm4a';
}
return [[{
json: {
mimeType,
fileName: audioOutput.filename,
data: base64Data,
status: promptResult.status,
prompt: textPrompt,
duration: audioDuration,
quality: audioQuality,
},
binary: {
data: {
fileName: audioOutput.filename,
data: base64Data,
fileType: 'audio',
fileSize,
fileExtension,
mimeType
}
}
}]];
}
}
throw new n8n_workflow_1.NodeApiError(this.getNode(), { message: `Audio generation timeout after ${timeout} minutes` });
}
catch (error) {
console.error('[ComfyUI Audio] Audio generation error:', error);
throw new n8n_workflow_1.NodeApiError(this.getNode(), {
message: `ComfyUI API Error: ${error.message}`,
description: error.description || ''
});
}
}
}
exports.ComfyuiTextToAudio = ComfyuiTextToAudio;
//# sourceMappingURL=ComfyuiTextToAudio.node.js.map