n8n-nodes-selfhosthub
Version:
Collection of n8n nodes for self-hosted AI services, including Leonardo.ai integration for AI image and content generation capabilities.
608 lines • 24.6 kB
JavaScript
;
// nodes/CreateJ2vMovie/core/collector.ts
Object.defineProperty(exports, "__esModule", { value: true });
exports.collectParameters = collectParameters;
exports.validateCollectedParameters = validateCollectedParameters;
// API defaults to optimize request size by skipping default values
const API_DEFAULTS = {
// Timing
start: 0,
duration: -1,
'extra-time': 0,
'z-index': 0,
'fade-in': 0,
'fade-out': 0,
// Position and sizing
position: 'center-center',
x: 0,
y: 0,
width: -1,
height: -1,
// Audio
volume: 1,
muted: false,
seek: 0,
loop: 0,
// Visual effects
'pan-distance': 0.1,
'pan-crop': true,
zoom: 0,
'flip-horizontal': false,
'flip-vertical': false,
// Image AI
model: 'flux-schnell',
'aspect-ratio': 'horizontal',
// Voice
voice: 'en-US-AriaNeural',
// HTML
tailwindcss: false,
wait: 2,
// Audiogram
color: '#ffffff',
opacity: 0.5,
amplitude: 5,
// Component
cache: true,
// FTP
port: 21,
secure: false
};
/**
* Check if a value should be included in API request
* Skips undefined, null, empty strings, and API defaults
*/
function shouldIncludeValue(key, value) {
if (value === undefined || value === null)
return false;
if (typeof value === 'string' && value.trim() === '')
return false;
const defaultValue = API_DEFAULTS[key];
if (defaultValue !== undefined && value === defaultValue)
return false;
return true;
}
/**
* Process unified element from n8n parameters into API format
* Handles camelCase to kebab-case conversion and field optimization
*/
function processUnifiedElement(element) {
const processed = {
type: element.type,
};
// Required content fields
const requiredContentFields = ['text', 'src', 'html', 'component', 'prompt'];
requiredContentFields.forEach(prop => {
if (element[prop] && element[prop].toString().trim() !== '') {
processed[prop] = element[prop];
}
});
// Basic metadata fields
if (element.id && element.id.trim())
processed.id = element.id;
if (element.comment && element.comment.trim())
processed.comment = element.comment;
if (element.condition && element.condition.trim())
processed.condition = element.condition;
if (element.variables !== undefined) {
try {
const vars = typeof element.variables === 'string'
? JSON.parse(element.variables)
: element.variables;
if (vars && Object.keys(vars).length > 0) {
processed.variables = vars;
}
}
catch (e) {
// Skip invalid JSON
}
}
// Timing properties
if (shouldIncludeValue('start', element.start)) {
processed.start = Number(element.start);
}
if (shouldIncludeValue('duration', element.duration)) {
processed.duration = Number(element.duration);
}
if (shouldIncludeValue('extra-time', element.extraTime)) {
processed['extra-time'] = Number(element.extraTime);
}
// Layer and fade properties
if (shouldIncludeValue('z-index', element.zIndex)) {
processed['z-index'] = Number(element.zIndex);
}
if (shouldIncludeValue('fade-in', element.fadeIn)) {
processed['fade-in'] = Number(element.fadeIn);
}
if (shouldIncludeValue('fade-out', element.fadeOut)) {
processed['fade-out'] = Number(element.fadeOut);
}
// Position and sizing properties
const positionProps = [
{ key: 'position', prop: 'position' },
{ key: 'x', prop: 'x' },
{ key: 'y', prop: 'y' },
{ key: 'width', prop: 'width' },
{ key: 'height', prop: 'height' },
{ key: 'resize', prop: 'resize' }
];
positionProps.forEach(({ key, prop }) => {
if (shouldIncludeValue(key, element[prop])) {
processed[prop] = element[prop];
}
});
// Resize property - always include when set (no API default to skip)
if (element.resize && element.resize.trim() !== '') {
processed.resize = element.resize;
}
// Audio properties
const audioProps = [
{ key: 'volume', prop: 'volume' },
{ key: 'muted', prop: 'muted' },
{ key: 'seek', prop: 'seek' },
{ key: 'loop', prop: 'loop' }
];
audioProps.forEach(({ key, prop }) => {
if (shouldIncludeValue(key, element[prop])) {
processed[prop] = element[prop];
}
});
// Visual effect properties
if (shouldIncludeValue('pan', element.pan)) {
processed.pan = element.pan;
}
if (shouldIncludeValue('pan-distance', element.panDistance)) {
processed['pan-distance'] = element.panDistance;
}
if (shouldIncludeValue('pan-crop', element.panCrop)) {
processed['pan-crop'] = element.panCrop;
}
if (shouldIncludeValue('zoom', element.zoom)) {
processed.zoom = element.zoom;
}
if (shouldIncludeValue('flip-horizontal', element.flipHorizontal)) {
processed['flip-horizontal'] = element.flipHorizontal;
}
if (shouldIncludeValue('flip-vertical', element.flipVertical)) {
processed['flip-vertical'] = element.flipVertical;
}
if (shouldIncludeValue('mask', element.mask)) {
processed.mask = element.mask;
}
// Complex object properties (crop, rotate, etc.)
const jsonObjectProps = ['crop', 'rotate', 'correction', 'chromaKey'];
jsonObjectProps.forEach(prop => {
const kebabProp = prop === 'chromaKey' ? 'chroma-key' : prop;
if (element[prop] !== undefined) {
if (typeof element[prop] === 'string' && element[prop].trim()) {
try {
const parsed = JSON.parse(element[prop]);
if (parsed && Object.keys(parsed).length > 0) {
processed[kebabProp] = parsed;
}
}
catch (e) {
// Skip invalid JSON
}
}
else if (typeof element[prop] === 'object' && element[prop] !== null) {
if (Object.keys(element[prop]).length > 0) {
processed[kebabProp] = element[prop];
}
}
}
});
// camelCase to kebab-case conversions
const kebabConversions = [
{ from: 'aspectRatio', to: 'aspect-ratio' },
{ from: 'modelSettings', to: 'model-settings' }
];
kebabConversions.forEach(({ from, to }) => {
if (element[from] !== undefined) {
if (typeof element[from] === 'string' && element[from].trim()) {
try {
const parsed = JSON.parse(element[from]);
if (parsed && Object.keys(parsed).length > 0) {
processed[to] = parsed;
}
}
catch (e) {
if (element[from].trim() !== '') {
processed[to] = element[from];
}
}
}
else if (element[from] !== null && element[from] !== '') {
processed[to] = element[from];
}
}
});
if (element.type === 'text') {
if (element.textSettings && element.textSettings.trim() !== '{}') {
try {
const settings = JSON.parse(element.textSettings);
if (settings && Object.keys(settings).length > 0) {
processed.settings = settings;
}
}
catch (e) {
// Skip invalid JSON
}
}
if (element.textStyle && element.textStyle !== '001') {
processed.style = element.textStyle;
}
}
// Type-specific properties
if (element.type === 'voice') {
if (element.voice && shouldIncludeValue('voice', element.voice)) {
processed.voice = element.voice;
}
if (element.model && element.model !== 'azure') {
processed.model = element.model;
}
if (element.connection && element.connection.trim()) {
processed.connection = element.connection;
}
}
if (element.type === 'image') {
if (element.model && shouldIncludeValue('model', element.model)) {
processed.model = element.model;
}
if (element.connection && element.connection.trim()) {
processed.connection = element.connection;
}
}
if (element.type === 'html') {
if (shouldIncludeValue('tailwindcss', element.tailwindcss)) {
processed.tailwindcss = element.tailwindcss;
}
if (shouldIncludeValue('wait', element.wait)) {
processed.wait = element.wait;
}
}
if (element.type === 'audiogram') {
if (shouldIncludeValue('color', element.color)) {
processed.color = element.color;
}
if (shouldIncludeValue('opacity', element.opacity)) {
processed.opacity = element.opacity;
}
if (shouldIncludeValue('amplitude', element.amplitude)) {
processed.amplitude = element.amplitude;
}
}
if (element.type === 'component' && element.settings) {
try {
const settings = typeof element.settings === 'string'
? JSON.parse(element.settings)
: element.settings;
if (settings && Object.keys(settings).length > 0) {
processed.settings = settings;
}
}
catch (e) {
if (typeof element.settings === 'object' && Object.keys(element.settings).length > 0) {
processed.settings = element.settings;
}
}
}
return processed;
}
/**
* Collect unified elements from n8n parameters
*/
function collectUnifiedElements(executeFunctions, itemIndex) {
const collection = executeFunctions.getNodeParameter('elements', itemIndex, {});
if (!(collection === null || collection === void 0 ? void 0 : collection.elementValues))
return [];
const elements = Array.isArray(collection.elementValues)
? collection.elementValues
: [collection.elementValues];
return elements.map(processUnifiedElement);
}
/**
* Collect export configurations from n8n parameters with v2 API format
*/
function collectExportConfigs(executeFunctions, itemIndex) {
const exportSettings = executeFunctions.getNodeParameter('exportSettings', itemIndex, {});
if (!(exportSettings === null || exportSettings === void 0 ? void 0 : exportSettings.exportValues))
return [];
const exports = Array.isArray(exportSettings.exportValues)
? exportSettings.exportValues
: [exportSettings.exportValues];
// Group destinations by their configuration
const destinations = [];
exports.forEach((exportConfig) => {
switch (exportConfig.exportType) {
case 'webhook':
if (exportConfig.webhookUrl) {
destinations.push({
type: 'webhook',
endpoint: exportConfig.webhookUrl
});
}
break;
case 'ftp':
const ftpDestination = {
type: 'ftp',
host: exportConfig.ftpHost,
username: exportConfig.ftpUsername,
password: exportConfig.ftpPassword
};
// Optional FTP fields with proper handling
if (exportConfig.ftpPort && exportConfig.ftpPort !== 21) {
ftpDestination.port = Number(exportConfig.ftpPort);
}
if (exportConfig.ftpPath && exportConfig.ftpPath.trim() !== '' && exportConfig.ftpPath !== '/') {
ftpDestination['remote-path'] = exportConfig.ftpPath;
}
if (exportConfig.ftpFile && exportConfig.ftpFile.trim() !== '') {
ftpDestination.file = exportConfig.ftpFile;
}
if (exportConfig.ftpSecure === true) {
ftpDestination.secure = true;
}
destinations.push(ftpDestination);
break;
case 'email':
const emailDestination = {
type: 'email',
to: exportConfig.emailTo
};
// Optional email fields
if (exportConfig.emailFrom && exportConfig.emailFrom.trim() !== '') {
emailDestination.from = exportConfig.emailFrom;
}
if (exportConfig.emailSubject && exportConfig.emailSubject.trim() !== '' && exportConfig.emailSubject !== 'Your video is ready') {
emailDestination.subject = exportConfig.emailSubject;
}
if (exportConfig.emailMessage && exportConfig.emailMessage.trim() !== '') {
emailDestination.message = exportConfig.emailMessage;
}
destinations.push(emailDestination);
break;
}
});
// Return single export config with all destinations (v2 API format)
if (destinations.length > 0) {
return [{
destinations: destinations
}];
}
return [];
}
/**
* Collect movie settings from n8n parameters
*/
function collectMovieSettings(executeFunctions, itemIndex) {
const movieSettings = {};
const showMovieSettings = executeFunctions.getNodeParameter('showMovieSettings', itemIndex, false);
if (!showMovieSettings)
return movieSettings;
const movieId = executeFunctions.getNodeParameter('movieId', itemIndex, '');
const movieComment = executeFunctions.getNodeParameter('movieComment', itemIndex, '');
const movieVariables = executeFunctions.getNodeParameter('movieVariables', itemIndex, '{}');
const movieCache = executeFunctions.getNodeParameter('movieCache', itemIndex, true);
const movieDraft = executeFunctions.getNodeParameter('movieDraft', itemIndex, false);
const clientData = executeFunctions.getNodeParameter('clientData', itemIndex, '{}');
const movieResolution = executeFunctions.getNodeParameter('movieResolution', itemIndex, 'custom');
if (movieId)
movieSettings.id = movieId;
if (movieComment)
movieSettings.comment = movieComment;
if (movieCache !== undefined)
movieSettings.cache = movieCache;
if (movieDraft !== undefined)
movieSettings.draft = movieDraft;
if (movieResolution && movieResolution !== 'custom')
movieSettings.resolution = movieResolution;
if (movieVariables && movieVariables.trim() !== '{}') {
try {
const parsed = JSON.parse(movieVariables);
if (Object.keys(parsed).length > 0) {
movieSettings.variables = parsed;
}
}
catch (e) {
// Skip invalid JSON
}
}
if (clientData && clientData.trim() !== '{}') {
try {
const parsed = JSON.parse(clientData);
if (Object.keys(parsed).length > 0) {
movieSettings['client-data'] = parsed;
}
}
catch (e) {
// Skip invalid JSON
}
}
return movieSettings;
}
/**
* Collect all parameters from n8n execution context
*/
function collectParameters(itemIndex) {
const collected = {
recordId: this.getNodeParameter('recordId', itemIndex, ''),
};
collected.isAdvancedMode = this.getNodeParameter('advancedMode', itemIndex, false);
if (collected.isAdvancedMode) {
// Get the template type to determine which JSON field to read
const templateType = this.getNodeParameter('templateType', itemIndex, 'blank');
// Map template type to parameter name
const templateFieldMap = {
'blank': 'jsonTemplateBlank',
'videoImage': 'jsonTemplateVideoImage',
'videoAudio': 'jsonTemplateVideoAudio',
'videoSequence': 'jsonTemplateVideoSequence',
'slideshow': 'jsonTemplateSlideshow',
'textOverlay': 'jsonTemplateTextOverlay',
'faceless': 'jsonTemplateFaceless',
'socialStory': 'jsonTemplateSocialStory',
'presentation': 'jsonTemplatePresentation',
};
const jsonFieldName = templateFieldMap[templateType] || 'jsonTemplateBlank';
collected.jsonTemplate = this.getNodeParameter(jsonFieldName, itemIndex, '');
}
else {
collected.movieSettings = collectMovieSettings(this, itemIndex);
const enableSubtitles = this.getNodeParameter('enableSubtitles', itemIndex, false);
if (enableSubtitles) {
const subtitleElement = {
type: 'subtitles'
};
const captions = this.getNodeParameter('captions', itemIndex, '');
const subtitleComment = this.getNodeParameter('subtitleComment', itemIndex, '');
const subtitleLanguage = this.getNodeParameter('subtitleLanguage', itemIndex, 'auto');
const subtitleModel = this.getNodeParameter('subtitleModel', itemIndex, 'default');
if (captions)
subtitleElement.captions = captions;
if (subtitleComment)
subtitleElement.comment = subtitleComment;
if (subtitleLanguage)
subtitleElement.language = subtitleLanguage;
if (subtitleModel && subtitleModel !== 'default')
subtitleElement.model = subtitleModel;
const subtitleSettings = this.getNodeParameter('subtitleSettings', itemIndex, '{}');
if (subtitleSettings && subtitleSettings.trim() !== '{}') {
try {
const settings = JSON.parse(subtitleSettings);
if (settings && Object.keys(settings).length > 0) {
subtitleElement.settings = settings;
}
}
catch (e) {
// Skip invalid JSON
}
}
collected.subtitles = subtitleElement;
}
collected.elements = collectUnifiedElements(this, itemIndex);
const outputSettings = this.getNodeParameter('outputSettings', itemIndex, {});
if (outputSettings === null || outputSettings === void 0 ? void 0 : outputSettings.outputValues) {
collected.outputSettings = outputSettings.outputValues;
}
else {
collected.outputSettings = {
width: 1920,
height: 1080,
quality: 'high',
cache: true
};
}
collected.exportConfigs = collectExportConfigs(this, itemIndex);
}
return collected;
}
/**
* Validate collected parameters for completeness and correctness
*/
function validateCollectedParameters(collected) {
const errors = [];
if (collected.isAdvancedMode) {
if (!collected.jsonTemplate || collected.jsonTemplate.trim() === '') {
errors.push('JSON template is required when advanced mode is enabled');
}
return { isValid: errors.length === 0, errors };
}
const hasElements = collected.elements && collected.elements.length > 0;
if (!hasElements && !collected.subtitles) {
errors.push('At least one element is required');
}
if (hasElements) {
collected.elements.forEach((element, index) => {
if (!element.type) {
errors.push(`Element ${index + 1}: type is required`);
}
switch (element.type) {
case 'video':
case 'audio':
case 'audiogram':
if (!element.src) {
errors.push(`Element ${index + 1}: source URL is required for ${element.type} elements`);
}
break;
case 'text':
case 'voice':
if (!element.text) {
errors.push(`Element ${index + 1}: text content is required for ${element.type} elements`);
}
break;
case 'component':
if (!element.component) {
errors.push(`Element ${index + 1}: component ID is required for component elements`);
}
break;
case 'image':
const hasSrc = element.src && element.src.trim() !== '';
const hasPrompt = element.prompt && element.prompt.trim() !== '';
if (!hasSrc && !hasPrompt) {
errors.push(`Element ${index + 1}: either source URL or AI prompt is required for image elements`);
}
else if (hasSrc && hasPrompt) {
errors.push(`Element ${index + 1}: cannot specify both source URL and AI prompt for image elements - choose one`);
}
break;
case 'html':
const hasHtmlSrc = element.src && element.src.trim() !== '';
const hasHtmlContent = element.html && element.html.trim() !== '';
if (!hasHtmlSrc && !hasHtmlContent) {
errors.push(`Element ${index + 1}: either source URL or HTML content is required for HTML elements`);
}
break;
}
});
}
// Validate export configurations
if (collected.exportConfigs && collected.exportConfigs.length > 0) {
collected.exportConfigs.forEach((exportConfig, configIndex) => {
if (!exportConfig.destinations || !Array.isArray(exportConfig.destinations) || exportConfig.destinations.length === 0) {
errors.push(`Export config ${configIndex + 1}: at least one destination is required`);
return;
}
exportConfig.destinations.forEach((destination, destIndex) => {
const context = `Export config ${configIndex + 1}, destination ${destIndex + 1}`;
if (!destination || typeof destination !== 'object') {
errors.push(`${context}: destination must be an object`);
return;
}
if (!destination.type) {
errors.push(`${context}: destination type is required`);
return;
}
switch (destination.type) {
case 'webhook':
if (!destination.endpoint || destination.endpoint.trim() === '') {
errors.push(`${context}: webhook endpoint URL is required`);
}
break;
case 'ftp':
if (!destination.host || destination.host.trim() === '') {
errors.push(`${context}: FTP host is required`);
}
if (!destination.username || destination.username.trim() === '') {
errors.push(`${context}: FTP username is required`);
}
if (!destination.password || destination.password.trim() === '') {
errors.push(`${context}: FTP password is required`);
}
if (destination.port && (typeof destination.port !== 'number' || destination.port < 1 || destination.port > 65535)) {
errors.push(`${context}: FTP port must be a number between 1 and 65535`);
}
break;
case 'email':
if (!destination.to || destination.to.toString().trim() === '') {
errors.push(`${context}: email recipient is required`);
}
break;
default:
errors.push(`${context}: invalid destination type '${destination.type}'. Must be 'webhook', 'ftp', or 'email'`);
}
});
});
}
return { isValid: errors.length === 0, errors };
}
//# sourceMappingURL=collector.js.map