UNPKG

n8n-nodes-ksc-comfyui

Version:

n8n node to integrate with ComfyUI stable diffusion workflows for image transformations

314 lines 15.2 kB
"use strict"; var __importDefault = (this && this.__importDefault) || function (mod) { return (mod && mod.__esModule) ? mod : { "default": mod }; }; Object.defineProperty(exports, "__esModule", { value: true }); exports.ComfyuiImageToVideo = void 0; const n8n_workflow_1 = require("n8n-workflow"); const form_data_1 = __importDefault(require("form-data")); class ComfyuiImageToVideo { constructor() { this.description = { displayName: 'ComfyUI Image to Video', name: 'comfyuiImageToVideo', icon: 'file:comfyui.svg', group: ['transform'], version: 1, description: 'Convert images to videos using ComfyUI workflow', defaults: { name: 'ComfyUI Image to Video', }, credentials: [ { name: 'comfyUIApi', required: true, }, ], inputs: ['main'], outputs: ['main'], properties: [ { displayName: 'Workflow JSON', name: 'workflow', type: 'string', typeOptions: { rows: 10, }, default: '', required: true, description: 'The ComfyUI workflow in JSON format', }, { displayName: 'Input Type', name: 'inputType', type: 'options', options: [ { name: 'URL', value: 'url' }, { name: 'Base64', value: 'base64' }, { name: 'Binary', value: 'binary' } ], default: 'url', required: true, }, { displayName: 'Input Image', name: 'inputImage', type: 'string', default: '', required: true, displayOptions: { show: { inputType: ['url', 'base64'], }, }, description: 'URL or base64 data of the input image', }, { displayName: 'Binary Property', name: 'binaryPropertyName', type: 'string', default: 'data', required: true, displayOptions: { show: { inputType: ['binary'], }, }, description: 'Name of the binary property containing the image', }, { displayName: 'Timeout', name: 'timeout', type: 'number', default: 30, description: 'Maximum time in minutes to wait for video generation', }, ], }; } async execute() { var _a, _b, _c; const credentials = await this.getCredentials('comfyUIApi'); const workflow = this.getNodeParameter('workflow', 0); const inputType = this.getNodeParameter('inputType', 0); const timeout = this.getNodeParameter('timeout', 0); const apiUrl = credentials.apiUrl; const apiKey = credentials.apiKey; console.log('[ComfyUI] Executing image to video conversion with API URL:', apiUrl); const headers = { 'Content-Type': 'application/json', }; if (apiKey) { console.log('[ComfyUI] Using API key authentication'); headers['Authorization'] = `Bearer ${apiKey}`; } try { console.log('[ComfyUI] Checking API connection...'); await this.helpers.request({ method: 'GET', url: `${apiUrl}/system_stats`, headers, json: true, }); let imageBuffer; if (inputType === 'url') { const inputImage = this.getNodeParameter('inputImage', 0); console.log('[ComfyUI] Downloading image from URL:', inputImage); const response = await this.helpers.request({ method: 'GET', url: inputImage, encoding: null, }); imageBuffer = Buffer.from(response); } else if (inputType === 'binary') { console.log('[ComfyUI] Getting binary data from input'); const binaryPropertyName = this.getNodeParameter('binaryPropertyName', 0); console.log('[ComfyUI] Looking for binary property:', binaryPropertyName); const items = this.getInputData(); const binaryProperties = Object.keys(items[0].binary || {}); console.log('[ComfyUI] Available binary properties:', binaryProperties); let actualPropertyName = binaryPropertyName; if (!((_a = items[0].binary) === null || _a === void 0 ? void 0 : _a[binaryPropertyName])) { console.log(`[ComfyUI] Binary property "${binaryPropertyName}" not found, searching for alternatives...`); const imageProperty = binaryProperties.find(key => { var _a; return (_a = items[0].binary[key].mimeType) === null || _a === void 0 ? void 0 : _a.startsWith('image/'); }); if (imageProperty) { console.log(`[ComfyUI] Found alternative image property: "${imageProperty}"`); actualPropertyName = imageProperty; } else { throw new n8n_workflow_1.NodeApiError(this.getNode(), { message: `No binary data found in property "${binaryPropertyName}" and no image alternatives found` }); } } imageBuffer = await this.helpers.getBinaryDataBuffer(0, actualPropertyName); console.log('[ComfyUI] Got binary data, size:', imageBuffer.length, 'bytes'); const mimeType = items[0].binary[actualPropertyName].mimeType; console.log('[ComfyUI] Binary data mime type:', mimeType); if (!mimeType || !mimeType.startsWith('image/')) { throw new n8n_workflow_1.NodeApiError(this.getNode(), { message: `Invalid media type: ${mimeType}. Only images are supported.` }); } } else { const inputImage = this.getNodeParameter('inputImage', 0); imageBuffer = Buffer.from(inputImage, 'base64'); } console.log('[ComfyUI] Uploading image...'); const formData = new form_data_1.default(); formData.append('image', imageBuffer, 'input.png'); formData.append('subfolder', ''); formData.append('overwrite', 'true'); const uploadResponse = await this.helpers.request({ method: 'POST', url: `${apiUrl}/upload/image`, headers: { ...headers, ...formData.getHeaders(), }, body: formData, }); const imageInfo = JSON.parse(uploadResponse); console.log('[ComfyUI] Image uploaded:', imageInfo); 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 loadImageNode = Object.values(workflowData).find((node) => node.class_type === 'LoadImage' && node.inputs && node.inputs.image !== undefined); if (!loadImageNode) { throw new n8n_workflow_1.NodeApiError(this.getNode(), { message: 'No LoadImage node found in the workflow. The workflow must contain a LoadImage node with an image input.' }); } loadImageNode.inputs.image = imageInfo.name; console.log('[ComfyUI] Queueing video 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] Video 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] Checking video 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] Prompt not found in history'); continue; } if (promptResult.status === undefined) { console.log('[ComfyUI] Execution status not found'); continue; } if ((_b = promptResult.status) === null || _b === void 0 ? void 0 : _b.completed) { console.log('[ComfyUI] Video generation completed'); if (((_c = promptResult.status) === null || _c === void 0 ? void 0 : _c.status_str) === 'error') { throw new n8n_workflow_1.NodeApiError(this.getNode(), { message: '[ComfyUI] Video generation failed' }); } console.log('[ComfyUI] Raw outputs structure:', JSON.stringify(promptResult.outputs, null, 2)); const mediaOutputs = Object.values(promptResult.outputs) .flatMap((nodeOutput) => nodeOutput.images || nodeOutput.gifs || []) .filter((image) => image.type === 'output' || image.type === 'temp') .map((img) => ({ ...img, url: `${apiUrl}/view?filename=${img.filename}&subfolder=${img.subfolder || ''}&type=${img.type}` })); console.log('[ComfyUI] Found media outputs:', mediaOutputs); if (mediaOutputs.length === 0) { throw new n8n_workflow_1.NodeApiError(this.getNode(), { message: '[ComfyUI] No media outputs found in results' }); } const videoOutputs = mediaOutputs.filter(output => output.filename.endsWith('.webp') || output.filename.endsWith('.mp4') || output.filename.endsWith('.gif')); if (videoOutputs.length === 0) { throw new n8n_workflow_1.NodeApiError(this.getNode(), { message: '[ComfyUI] No video outputs found in results' }); } console.log('[ComfyUI] Found video outputs:', videoOutputs); const videoOutput = videoOutputs[0]; const videoResponse = await this.helpers.request({ method: 'GET', url: videoOutput.url, encoding: null, resolveWithFullResponse: true }); if (videoResponse.statusCode === 404) { throw new n8n_workflow_1.NodeApiError(this.getNode(), { message: `Video file not found at ${videoOutput.url}` }); } console.log('[ComfyUI] Using media directly from ComfyUI'); const buffer = Buffer.from(videoResponse.body); const base64Data = buffer.toString('base64'); const fileSize = Math.round(buffer.length / 1024 * 10) / 10 + " kB"; let mimeType = 'image/webp'; let fileExtension = 'webp'; if (videoOutput.filename.endsWith('.mp4')) { mimeType = 'video/mp4'; fileExtension = 'mp4'; } else if (videoOutput.filename.endsWith('.gif')) { mimeType = 'image/gif'; fileExtension = 'gif'; } return [[{ json: { mimeType, fileName: videoOutput.filename, data: base64Data, status: promptResult.status, }, binary: { data: { fileName: videoOutput.filename, data: base64Data, fileType: 'video', fileSize, fileExtension, mimeType } } }]]; } } throw new n8n_workflow_1.NodeApiError(this.getNode(), { message: `Video generation timeout after ${timeout} minutes` }); } catch (error) { console.error('[ComfyUI] Video generation error:', error); throw new n8n_workflow_1.NodeApiError(this.getNode(), { message: `ComfyUI API Error: ${error.message}`, description: error.description || '' }); } } } exports.ComfyuiImageToVideo = ComfyuiImageToVideo; //# sourceMappingURL=ComfyuiImageToVideo.node.js.map