UNPKG

n8n-nodes-selfhosthub

Version:

Collection of n8n nodes for self-hosted AI services, including Leonardo.ai integration for AI image and content generation capabilities.

582 lines 26.1 kB
"use strict"; // nodes/CreateJ2vMovie/schema/rules.ts Object.defineProperty(exports, "__esModule", { value: true }); exports.validateImageElement = validateImageElement; exports.validateTextElementParams = validateTextElementParams; exports.validateSubtitleElementParams = validateSubtitleElementParams; exports.validateExportConfig = validateExportConfig; exports.validateExportDestination = validateExportDestination; exports.validateMovieElements = validateMovieElements; exports.validateSceneElements = validateSceneElements; exports.validateJSON2VideoRequest = validateJSON2VideoRequest; const schema_1 = require("./schema"); /** * Validate image element with src OR prompt requirement * Images can use either a source URL or AI generation prompt, but not both */ function validateImageElement(element, context = '') { const errors = []; if (!element || element.type !== 'image') { return errors; } const hasSrc = element.src && typeof element.src === 'string' && element.src.trim() !== ''; const hasPrompt = element.prompt && typeof element.prompt === 'string' && element.prompt.trim() !== ''; if (!hasSrc && !hasPrompt) { errors.push(`${context}: Image elements require either a source URL or AI prompt`); } else if (hasSrc && hasPrompt) { errors.push(`${context}: Image elements cannot have both source URL and AI prompt - choose one`); } // Validate AI-specific fields when using prompts if (hasPrompt) { if (element.model && !schema_1.API_RULES.VALID_AI_MODELS.includes(element.model)) { errors.push(`${context}: Invalid AI model '${element.model}'. Must be one of: ${schema_1.API_RULES.VALID_AI_MODELS.join(', ')}`); } if (element['aspect-ratio'] && !schema_1.API_RULES.VALID_ASPECT_RATIOS.includes(element['aspect-ratio'])) { errors.push(`${context}: Invalid aspect ratio '${element['aspect-ratio']}'. Must be one of: ${schema_1.API_RULES.VALID_ASPECT_RATIOS.join(', ')}`); } } // Validate URL format when using source if (hasSrc && !isValidUrl(element.src)) { errors.push(`${context}: Invalid source URL format`); } return errors; } /** * Validate text element parameters for completeness and format */ function validateTextElementParams(params) { const errors = []; if (!params.text || params.text.trim() === '') { errors.push('Text content is required and cannot be empty'); } // Font size validation if (params.fontSize !== undefined) { const fontSize = typeof params.fontSize === 'string' ? parseFloat(params.fontSize.replace('px', '')) : params.fontSize; if (isNaN(fontSize) || fontSize < 1 || fontSize > 200) { errors.push('Font size must be a number between 1 and 200'); } } // Font weight validation if (params.fontWeight !== undefined) { const weight = typeof params.fontWeight === 'string' ? parseInt(params.fontWeight) : params.fontWeight; if (isNaN(weight) || weight < 100 || weight > 900 || weight % 100 !== 0) { errors.push('Font weight must be a multiple of 100 between 100 and 900'); } } // Color validation const colorRegex = /^(#[0-9A-Fa-f]{6}|#[0-9A-Fa-f]{3}|rgb\([0-9,\s]+\)|rgba\([0-9,\s.]+\)|transparent)$/; if (params.fontColor && !colorRegex.test(params.fontColor)) { errors.push('fontColor must be a valid color (hex like #FF0000, rgb like rgb(255,0,0), rgba like rgba(255,0,0,0.5), or "transparent")'); } if (params.backgroundColor && !colorRegex.test(params.backgroundColor)) { errors.push('backgroundColor must be a valid color (hex like #FF0000, rgb like rgb(255,0,0), rgba like rgba(255,0,0,0.5), or "transparent")'); } // Position validation if (params.position && !(0, schema_1.isValidPosition)(params.position)) { errors.push(`Position must be one of: ${schema_1.API_RULES.VALID_POSITIONS.join(', ')}`); } // Coordinate validation for custom position if (params.position === 'custom') { if (params.x === undefined || params.y === undefined) { errors.push('X and Y coordinates are required when position is "custom"'); } } // Text alignment validation const validAlignments = ['left', 'center', 'right', 'justify']; if (params.textAlign && !validAlignments.includes(params.textAlign)) { errors.push(`Text align must be one of: ${validAlignments.join(', ')}`); } // Vertical/horizontal position validation const validVerticalPositions = ['top', 'center', 'bottom']; const validHorizontalPositions = ['left', 'center', 'right']; if (params.verticalPosition && !validVerticalPositions.includes(params.verticalPosition)) { errors.push(`Vertical position must be one of: ${validVerticalPositions.join(', ')}`); } if (params.horizontalPosition && !validHorizontalPositions.includes(params.horizontalPosition)) { errors.push(`Horizontal position must be one of: ${validHorizontalPositions.join(', ')}`); } // Duration and timing validation if (params.duration !== undefined && !(0, schema_1.isValidDuration)(params.duration)) { errors.push('Duration must be a positive number, -1 (intrinsic), or -2 (match container)'); } if (params.start !== undefined && (typeof params.start !== 'number' || params.start < 0)) { errors.push('Start time must be a non-negative number'); } return errors; } /** * Validate subtitle element parameters */ function validateSubtitleElementParams(params) { const errors = []; // Must have either captions, src, or text const hasCaptions = params.captions && params.captions.trim() !== ''; const hasSrc = params.src && params.src.trim() !== ''; const hasText = params.text && params.text.trim() !== ''; const sourceCount = [hasCaptions, hasSrc, hasText].filter(Boolean).length; if (sourceCount === 0) { errors.push('Either captions text, src URL, or text content is required'); } if (sourceCount > 1) { errors.push('Cannot specify multiple subtitle sources - choose one of: captions, src, or text'); } // URL validation for src if (params.src && !isValidUrl(params.src)) { errors.push('Subtitle src must be a valid URL'); } // Language validation if (params.language && !/^[a-z]{2}(-[A-Z]{2})?$/.test(params.language) && params.language !== 'auto') { errors.push('Language must be in format "en", "en-US", or "auto"'); } // Position validation if (params.position && !(0, schema_1.isValidPosition)(params.position)) { errors.push(`Position must be one of: ${schema_1.API_RULES.VALID_POSITIONS.join(', ')}`); } // Font size validation if (params.fontSize !== undefined) { if (typeof params.fontSize !== 'number' || params.fontSize < 1 || params.fontSize > 200) { errors.push('Font size must be a number between 1 and 200'); } } // Numeric range validations if (params.outlineWidth !== undefined) { if (typeof params.outlineWidth !== 'number' || params.outlineWidth < 0 || params.outlineWidth > 10) { errors.push('Outline width must be a number between 0 and 10'); } } if (params.shadowOffset !== undefined) { if (typeof params.shadowOffset !== 'number' || params.shadowOffset < 0 || params.shadowOffset > 10) { errors.push('Shadow offset must be a number between 0 and 10'); } } if (params.maxWordsPerLine !== undefined) { if (typeof params.maxWordsPerLine !== 'number' || params.maxWordsPerLine < 1 || params.maxWordsPerLine > 20) { errors.push('Max words per line must be a number between 1 and 20'); } } // Color validation const colorRegex = /^(#[0-9A-Fa-f]{6}|#[0-9A-Fa-f]{3}|rgb\([0-9,\s]+\)|rgba\([0-9,\s.]+\)|transparent)$/; const colorFields = ['wordColor', 'lineColor', 'boxColor', 'outlineColor', 'shadowColor']; colorFields.forEach(field => { const value = params[field]; if (value && typeof value === 'string' && !colorRegex.test(value)) { errors.push(`${field} must be a valid color (hex like #FF0000, rgb like rgb(255,0,0), rgba like rgba(255,0,0,0.5), or "transparent")`); } }); return errors; } /** * Validate export configuration according to v2 API rules */ function validateExportConfig(exportConfig, context = '') { const errors = []; const prefix = context ? `${context}: ` : ''; if (!exportConfig || typeof exportConfig !== 'object') { errors.push(`${prefix}Export config must be an object`); return errors; } if (!exportConfig.destinations) { errors.push(`${prefix}Export config must have a destinations array`); return errors; } if (!Array.isArray(exportConfig.destinations)) { errors.push(`${prefix}Destinations must be an array`); return errors; } if (exportConfig.destinations.length === 0) { errors.push(`${prefix}At least one destination is required`); return errors; } // Validate each destination exportConfig.destinations.forEach((destination, index) => { const destContext = `${prefix}Destination ${index + 1}`; const destErrors = validateExportDestination(destination, destContext); errors.push(...destErrors); }); return errors; } /** * Validate individual export destination */ function validateExportDestination(destination, context = '') { const errors = []; if (!destination || typeof destination !== 'object') { errors.push(`${context}: Destination must be an object`); return errors; } if (!destination.type) { errors.push(`${context}: Destination type is required`); return errors; } if (!schema_1.API_RULES.EXPORT_RULES.VALID_DESTINATION_TYPES.includes(destination.type)) { errors.push(`${context}: Invalid destination type '${destination.type}'. Must be one of: ${schema_1.API_RULES.EXPORT_RULES.VALID_DESTINATION_TYPES.join(', ')}`); return errors; } // Check required fields if (!(0, schema_1.hasRequiredExportFields)(destination)) { const requiredFields = schema_1.API_RULES.EXPORT_RULES.REQUIRED_FIELDS[destination.type]; errors.push(`${context}: Missing required fields for ${destination.type} destination: ${requiredFields.join(', ')}`); } // Type-specific validation if ((0, schema_1.isWebhookDestination)(destination)) { if (destination.endpoint && !isValidUrl(destination.endpoint)) { errors.push(`${context}: Invalid webhook endpoint URL format`); } if (destination.endpoint && !destination.endpoint.startsWith('https://')) { errors.push(`${context}: Webhook endpoint must use HTTPS`); } } else if ((0, schema_1.isFtpDestination)(destination)) { if (destination.port && !(0, schema_1.isValidFtpPort)(destination.port)) { errors.push(`${context}: FTP port must be between 1 and 65535`); } if (destination['remote-path'] && (!destination['remote-path'].startsWith('/') || destination['remote-path'].includes('..'))) { errors.push(`${context}: Remote path must be absolute and not contain '..'`); } } else if ((0, schema_1.isEmailDestination)(destination)) { if (destination.to) { const emails = Array.isArray(destination.to) ? destination.to : [destination.to]; emails.forEach((email, emailIndex) => { if (!isValidEmail(email)) { errors.push(`${context}: Invalid email address at index ${emailIndex + 1}: ${email}`); } }); } if (destination.from && !isValidEmail(destination.from)) { errors.push(`${context}: Invalid sender email address`); } } return errors; } /** * Validate movie-level elements against API rules */ function validateMovieElements(elements) { const errors = []; if (!Array.isArray(elements)) { errors.push('Movie elements must be an array'); return errors; } elements.forEach((element, index) => { const elementContext = `Movie element ${index + 1}`; if (!element || typeof element !== 'object') { errors.push(`${elementContext}: Element must be an object`); return; } if (!element.type) { errors.push(`${elementContext}: Element type is required`); return; } // Check if element type is allowed at movie level if (!schema_1.API_RULES.MOVIE_ELEMENT_TYPES.includes(element.type)) { errors.push(`${elementContext}: Element type '${element.type}' is not allowed at movie level. Allowed types: ${schema_1.API_RULES.MOVIE_ELEMENT_TYPES.join(', ')}`); return; } // Required fields validation if (!(0, schema_1.hasRequiredFields)(element)) { errors.push(`${elementContext}: Missing required fields for ${element.type}`); } // Element-specific validation if (element.type === 'text' && element.text) { const textErrors = validateTextElementParams(element); errors.push(...textErrors.map(err => `${elementContext}: ${err}`)); } if (element.type === 'subtitles') { const subtitleErrors = validateSubtitleElementParams(element); errors.push(...subtitleErrors.map(err => `${elementContext}: ${err}`)); } // Common field validations validateCommonElementFields(element, elementContext, errors); }); return errors; } /** * Validate scene-level elements against API rules */ function validateSceneElements(elements, sceneContext = '') { const errors = []; if (!Array.isArray(elements)) { errors.push(`${sceneContext}: Scene elements must be an array`); return errors; } elements.forEach((element, index) => { const elementContext = sceneContext ? `${sceneContext} element ${index + 1}` : `Element ${index + 1}`; if (!element || typeof element !== 'object') { errors.push(`${elementContext}: Element must be an object`); return; } if (!element.type) { errors.push(`${elementContext}: Element type is required`); return; } // Check if subtitles are in scenes (not allowed) if (element.type === 'subtitles') { errors.push(`${elementContext}: Subtitles can only be added at movie level, not in individual scenes`); return; } // Check if element type is allowed at scene level if (!schema_1.API_RULES.SCENE_ELEMENT_TYPES.includes(element.type)) { errors.push(`${elementContext}: Element type '${element.type}' is not allowed at scene level. Allowed types: ${schema_1.API_RULES.SCENE_ELEMENT_TYPES.join(', ')}`); return; } // Required fields validation if (!(0, schema_1.hasRequiredFields)(element)) { errors.push(`${elementContext}: Missing required fields for ${element.type}`); } // Element-specific validation if (element.type === 'text' && element.text) { const textErrors = validateTextElementParams(element); errors.push(...textErrors.map(err => `${elementContext}: ${err}`)); } // Image validation with src/prompt logic if (element.type === 'image') { const imageErrors = validateImageElement(element, elementContext); errors.push(...imageErrors); } // URL validation for media elements if (['video', 'audio'].includes(element.type) && element.src && !isValidUrl(element.src)) { errors.push(`${elementContext}: Invalid URL for ${element.type} source`); } // Component validation if (element.type === 'component' && (!element.component || element.component.trim() === '')) { errors.push(`${elementContext}: Component ID is required for component elements`); } // HTML validation (can have src OR html, but at least one) if (element.type === 'html') { const hasHtmlSrc = element.src && element.src.trim() !== ''; const hasHtmlContent = element.html && element.html.trim() !== ''; if (!hasHtmlSrc && !hasHtmlContent) { errors.push(`${elementContext}: HTML elements require either source URL or HTML content`); } else if (hasHtmlSrc && !isValidUrl(element.src)) { errors.push(`${elementContext}: Invalid URL for HTML source`); } } // Voice element validation if (element.type === 'voice') { if (!element.text || element.text.trim() === '') { errors.push(`${elementContext}: Text content is required for voice elements`); } if (element.model && !schema_1.API_RULES.VALID_TTS_MODELS.includes(element.model)) { errors.push(`${elementContext}: Invalid TTS model. Must be one of: ${schema_1.API_RULES.VALID_TTS_MODELS.join(', ')}`); } } // Common field validations validateCommonElementFields(element, elementContext, errors); }); return errors; } /** * Validate complete JSON2Video request structure */ function validateJSON2VideoRequest(request) { const errors = []; const warnings = []; if (!request || typeof request !== 'object') { return { isValid: false, errors: ['Request must be an object'], warnings: [] }; } // Required top-level field: scenes if (!request.scenes) { errors.push('Missing required field: scenes'); } else if (!Array.isArray(request.scenes)) { errors.push('Scenes must be an array'); } else if (request.scenes.length === 0) { warnings.push('No scenes defined - video will be empty'); } // Numeric field validations with ranges const numericFields = [ { field: 'width', range: schema_1.API_RULES.VALIDATION_RANGES.width }, { field: 'height', range: schema_1.API_RULES.VALIDATION_RANGES.height }, ]; numericFields.forEach(({ field, range }) => { const value = request[field]; if (value !== undefined) { if (typeof value !== 'number' || isNaN(value)) { errors.push(`${field} must be a number`); } else if (value < range.min || value > range.max) { errors.push(`${field} must be between ${range.min} and ${range.max}`); } } }); // Quality validation if (request.quality && !schema_1.API_RULES.VALID_QUALITIES.includes(request.quality)) { errors.push(`Quality must be one of: ${schema_1.API_RULES.VALID_QUALITIES.join(', ')}`); } // Resolution validation if (request.resolution && !schema_1.API_RULES.VALID_RESOLUTIONS.includes(request.resolution)) { errors.push(`Resolution must be one of: ${schema_1.API_RULES.VALID_RESOLUTIONS.join(', ')}`); } // Custom resolution requirements if (request.resolution === 'custom') { if (!request.width) errors.push('Width is required when resolution is "custom"'); if (!request.height) errors.push('Height is required when resolution is "custom"'); } // Validate movie elements if present if (request.elements) { const movieElementErrors = validateMovieElements(request.elements); errors.push(...movieElementErrors); } // Validate scenes if (request.scenes && Array.isArray(request.scenes)) { request.scenes.forEach((scene, sceneIndex) => { const sceneContext = `Scene ${sceneIndex + 1}`; if (!scene || typeof scene !== 'object') { errors.push(`${sceneContext}: Scene must be an object`); return; } if (!scene.elements) { errors.push(`${sceneContext}: Missing required field: elements`); } else { const sceneErrors = validateSceneElements(scene.elements, sceneContext); errors.push(...sceneErrors); } // Scene-specific validations if (scene.duration !== undefined && !(0, schema_1.isValidDuration)(scene.duration)) { errors.push(`${sceneContext}: Duration must be a positive number, -1 (auto), or -2 (match container)`); } if (scene['background-color'] && !isValidColor(scene['background-color'])) { errors.push(`${sceneContext}: Invalid background color format`); } }); } // Validate exports configuration if (request.exports) { if (!Array.isArray(request.exports)) { errors.push('Exports must be an array'); } else { // Check single export config rule if (request.exports.length > 1) { warnings.push('Currently only one export configuration is supported. Using the first configuration.'); } request.exports.forEach((exportConfig, index) => { const exportErrors = validateExportConfig(exportConfig, `Export config ${index + 1}`); errors.push(...exportErrors); }); } } // Validate movie-level vs scene-level element rules if (request.elements && request.scenes) { request.scenes.forEach((scene, sceneIndex) => { if (scene.elements && Array.isArray(scene.elements)) { scene.elements.forEach((element, elementIndex) => { if (element && element.type === 'subtitles') { errors.push(`Scene ${sceneIndex + 1} element ${elementIndex + 1}: Subtitles must be at movie level, not in scenes`); } }); } }); } return { isValid: errors.length === 0, errors, warnings }; } /** * Validate common element fields that apply to all element types */ function validateCommonElementFields(element, context, errors) { // Duration validation if (element.duration !== undefined && !(0, schema_1.isValidDuration)(element.duration)) { errors.push(`${context}: Duration must be a positive number, -1 (intrinsic), or -2 (match container)`); } // Start time validation if (element.start !== undefined && (typeof element.start !== 'number' || element.start < 0)) { errors.push(`${context}: Start time must be a non-negative number`); } // Z-index validation if (element['z-index'] !== undefined) { const zIndex = element['z-index']; const range = schema_1.API_RULES.VALIDATION_RANGES['z-index']; if (typeof zIndex !== 'number' || zIndex < range.min || zIndex > range.max) { errors.push(`${context}: Z-index must be a number between ${range.min} and ${range.max}`); } } // Volume validation if (element.volume !== undefined) { const volume = element.volume; const range = schema_1.API_RULES.VALIDATION_RANGES.volume; if (typeof volume !== 'number' || volume < range.min || volume > range.max) { errors.push(`${context}: Volume must be a number between ${range.min} and ${range.max}`); } } // Position validation if (element.position && !(0, schema_1.isValidPosition)(element.position)) { errors.push(`${context}: Invalid position. Must be one of: ${schema_1.API_RULES.VALID_POSITIONS.join(', ')}`); } // Coordinate validation for custom position if (element.position === 'custom') { if (element.x === undefined || element.y === undefined) { errors.push(`${context}: X and Y coordinates are required when position is "custom"`); } } // Fade validation if (element['fade-in'] !== undefined && (typeof element['fade-in'] !== 'number' || element['fade-in'] < 0)) { errors.push(`${context}: Fade-in must be a non-negative number`); } if (element['fade-out'] !== undefined && (typeof element['fade-out'] !== 'number' || element['fade-out'] < 0)) { errors.push(`${context}: Fade-out must be a non-negative number`); } // JSON object field validation const jsonObjectFields = ['crop', 'rotate', 'chroma-key', 'correction']; jsonObjectFields.forEach(field => { if (element[field] && typeof element[field] === 'object') { try { if (field === 'crop' && element[field]) { const crop = element[field]; if (crop.width !== undefined && (typeof crop.width !== 'number' || crop.width <= 0)) { errors.push(`${context}: Crop width must be a positive number`); } if (crop.height !== undefined && (typeof crop.height !== 'number' || crop.height <= 0)) { errors.push(`${context}: Crop height must be a positive number`); } } } catch (e) { errors.push(`${context}: Invalid ${field} object structure`); } } }); } /** * Validate URL format */ function isValidUrl(url) { try { new URL(url); return true; } catch { return false; } } /** * Validate email format */ function isValidEmail(email) { const emailRegex = /^[^\s@]+@[^\s@]+\.[^\s@]+$/; return emailRegex.test(email); } /** * Validate color format (hex, rgb, rgba, or transparent) */ function isValidColor(color) { const colorRegex = /^(#[0-9A-Fa-f]{6}|#[0-9A-Fa-f]{3}|rgb\([0-9,\s]+\)|rgba\([0-9,\s.]+\)|transparent)$/; return colorRegex.test(color); } //# sourceMappingURL=rules.js.map