@juspay/neurolink
Version:
Universal AI Development Platform with working MCP integration, multi-provider support, voice (TTS/STT/realtime), and professional CLI. 58+ external MCP servers discoverable, multimodal file processing, RAG pipelines. Build, test, and deploy AI applicatio
201 lines • 8.7 kB
JavaScript
/**
* Video Generation Processing Utility
*
* Central registry + dispatch for video-generation handlers across
* providers (Vertex Veo, Kling, Runway, Replicate-hosted models, etc.).
*
* Mirrors the static-handler-registry pattern established by
* `TTSProcessor` (`utils/ttsProcessor.ts`) and `STTProcessor`
* (`utils/sttProcessor.ts`).
*
* @module utils/videoProcessor
*/
import { ErrorCategory, ErrorSeverity } from "../constants/enums.js";
import { VIDEO_ERROR_CODES } from "../constants/videoErrors.js";
import { SpanSerializer, SpanStatus, SpanType, getMetricsAggregator, } from "../observability/index.js";
import { logger } from "./logger.js";
// VideoError is canonical in vertexVideoHandler.ts (existing). Re-export
// here so consumers of `VideoProcessor` can import the typed error from
// the same module. Both throws and instanceof checks resolve to the same
// class.
import { VideoError } from "../adapters/video/vertexVideoHandler.js";
export { VideoError, VIDEO_ERROR_CODES };
/**
* Static processor managing the video handler registry.
*
* Handlers register themselves during `ProviderRegistry._doRegister()`
* via `VideoProcessor.registerHandler(name, instance)`. Lookups are
* O(1) on a normalised lower-case provider key.
*/
export class VideoProcessor {
static handlers = new Map();
/**
* Register a video handler for a specific provider.
*/
static registerHandler(providerName, handler) {
if (!providerName) {
throw new Error("Provider name is required");
}
if (!handler) {
throw new Error("Handler is required");
}
const key = providerName.toLowerCase();
if (this.handlers.has(key)) {
logger.warn(`[VideoProcessor] Overwriting existing handler for provider: ${key}`);
}
this.handlers.set(key, handler);
logger.debug(`[VideoProcessor] Registered video handler: ${key}`);
}
/**
* Check if a provider has a registered video handler.
*/
static supports(providerName) {
if (!providerName) {
return false;
}
return this.handlers.has(providerName.toLowerCase());
}
/**
* List the names of all registered providers.
*/
static listProviders() {
return Array.from(this.handlers.keys());
}
static getHandler(providerName) {
return this.handlers.get(providerName.toLowerCase());
}
static buildSpanAttributes(provider, options) {
return {
"video.operation": "generate",
"video.provider": provider,
"video.resolution": options.resolution,
"video.duration": options.length,
"video.aspect_ratio": options.aspectRatio,
"video.audio": options.audio,
};
}
/**
* Generate a single video clip via the registered handler.
*
* @param provider - Registered provider name (e.g. "vertex", "kling")
* @param image - Source image buffer
* @param prompt - Text prompt describing the desired motion / content
* @param options - Resolution / length / aspect-ratio / audio options
* @param region - Optional region override (Vertex location, etc.)
* @throws VideoError on registry miss, handler-not-configured, or
* generation failure
*/
static async generate(provider, image, prompt, options, region) {
const span = SpanSerializer.createSpan(SpanType.MEDIA_GENERATION, "video.generate", this.buildSpanAttributes(provider, options));
try {
const handler = this.getHandler(provider);
if (!handler) {
throw new VideoError({
code: VIDEO_ERROR_CODES.PROVIDER_NOT_SUPPORTED,
message: `Video provider "${provider}" is not registered. Available: ${this.listProviders().join(", ")}`,
category: ErrorCategory.CONFIGURATION,
severity: ErrorSeverity.HIGH,
retriable: false,
context: { provider, available: this.listProviders() },
});
}
if (!handler.isConfigured()) {
throw new VideoError({
code: VIDEO_ERROR_CODES.PROVIDER_NOT_CONFIGURED,
message: `Video provider "${provider}" is not configured. Set the required credentials.`,
category: ErrorCategory.CONFIGURATION,
severity: ErrorSeverity.HIGH,
retriable: false,
context: { provider },
});
}
logger.debug(`[VideoProcessor] Starting video generation with provider: ${provider}`);
const result = await handler.generate(image, prompt, options, region);
const ended = SpanSerializer.endSpan(span, SpanStatus.OK);
getMetricsAggregator().recordSpan(ended);
logger.info(`[VideoProcessor] Generated ${result.data.length} bytes (${provider})`);
return result;
}
catch (err) {
const ended = SpanSerializer.endSpan(span, SpanStatus.ERROR, err instanceof Error ? err.message : String(err));
getMetricsAggregator().recordSpan(ended);
if (err instanceof VideoError) {
throw err;
}
const message = err instanceof Error ? err.message : String(err);
throw new VideoError({
code: VIDEO_ERROR_CODES.GENERATION_FAILED,
message: `Video generation failed for provider "${provider}": ${message}`,
category: ErrorCategory.EXECUTION,
severity: ErrorSeverity.HIGH,
retriable: true,
context: { provider, options, region },
originalError: err instanceof Error ? err : undefined,
});
}
}
/**
* Generate a transition clip via the registered handler (Director Mode).
*
* Providers without first-and-last-frame interpolation surface a typed
* `TRANSITION_NOT_SUPPORTED` error here; callers should fall back to
* generating a regular clip with a transition prompt.
*/
static async generateTransition(provider, firstFrame, lastFrame, prompt, options, region) {
const handler = this.getHandler(provider);
if (!handler) {
throw new VideoError({
code: VIDEO_ERROR_CODES.PROVIDER_NOT_SUPPORTED,
message: `Video provider "${provider}" is not registered for transitions`,
category: ErrorCategory.CONFIGURATION,
severity: ErrorSeverity.HIGH,
retriable: false,
context: { provider, available: this.listProviders() },
});
}
if (!handler.generateTransition) {
throw new VideoError({
code: VIDEO_ERROR_CODES.TRANSITION_NOT_SUPPORTED,
message: `Video provider "${provider}" does not support transition clips`,
category: ErrorCategory.VALIDATION,
severity: ErrorSeverity.MEDIUM,
retriable: false,
context: { provider },
});
}
if (!handler.isConfigured()) {
throw new VideoError({
code: VIDEO_ERROR_CODES.PROVIDER_NOT_CONFIGURED,
message: `Video provider "${provider}" is not configured`,
category: ErrorCategory.CONFIGURATION,
severity: ErrorSeverity.HIGH,
retriable: false,
context: { provider },
});
}
try {
return await handler.generateTransition(firstFrame, lastFrame, prompt, options, region);
}
catch (err) {
if (err instanceof VideoError) {
throw err;
}
const message = err instanceof Error ? err.message : String(err);
throw new VideoError({
code: VIDEO_ERROR_CODES.DIRECTOR_TRANSITION_FAILED,
message: `Video transition generation failed for provider "${provider}": ${message}`,
category: ErrorCategory.EXECUTION,
severity: ErrorSeverity.MEDIUM,
retriable: true,
context: {
provider,
firstFrameSize: firstFrame.length,
lastFrameSize: lastFrame.length,
durationSeconds: options?.durationSeconds,
},
originalError: err instanceof Error ? err : undefined,
});
}
}
}
//# sourceMappingURL=videoProcessor.js.map