@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
222 lines • 7.06 kB
JavaScript
/**
* Video file utilities for CLI
*
* Provides functionality for saving generated video to files with proper
* error handling and directory creation. Follows the pattern established
* by audioFileUtils.ts for TTS output handling.
*
* @module cli/utils/videoFileUtils
*/
import fs from "fs";
import path from "path";
/**
* Format file size in human-readable format
*
* @param bytes - Size in bytes
* @returns Formatted string (e.g., "32 KB", "1.5 MB")
*/
export function formatVideoFileSize(bytes) {
if (bytes < 1024) {
return `${bytes} B`;
}
else if (bytes < 1024 * 1024) {
return `${(bytes / 1024).toFixed(1)} KB`;
}
else if (bytes < 1024 * 1024 * 1024) {
return `${(bytes / (1024 * 1024)).toFixed(1)} MB`;
}
else {
return `${(bytes / (1024 * 1024 * 1024)).toFixed(2)} GB`;
}
}
/**
* Resolve the output path, handling both absolute and relative paths
*
* @param outputPath - User-specified output path
* @returns Resolved absolute path
*/
export function resolveVideoOutputPath(outputPath) {
if (path.isAbsolute(outputPath)) {
return outputPath;
}
return path.resolve(process.cwd(), outputPath);
}
/**
* Ensure parent directories exist, creating them if necessary
*
* @param filePath - Full path to the file
*/
export async function ensureVideoDirectoryExists(filePath) {
const directory = path.dirname(filePath);
try {
await fs.promises.access(directory, fs.constants.F_OK);
}
catch {
// Directory doesn't exist, create it recursively
await fs.promises.mkdir(directory, { recursive: true });
}
}
/**
* Get appropriate file extension for video media type
*
* @param mediaType - Video media type
* @returns File extension (including dot)
*/
export function getVideoExtension(mediaType) {
switch (mediaType) {
case "video/mp4":
return ".mp4";
case "video/webm":
return ".webm";
default:
// Unreachable but kept for runtime safety
return ".mp4";
}
}
/**
* Validate and normalize output path, adding extension if needed
*
* If the specified path has no extension or an invalid extension, the appropriate
* extension (.mp4 or .webm) will be added based on the mediaType.
* If the extension doesn't match the mediaType (e.g., .webm specified but mediaType
* is video/mp4), the extension will be replaced to match the actual format.
*
* @param outputPath - User-specified output path
* @param mediaType - Video media type for extension
* @returns Normalized output path with correct extension
*/
export function normalizeVideoOutputPath(outputPath, mediaType = "video/mp4") {
const resolvedPath = resolveVideoOutputPath(outputPath);
const ext = path.extname(resolvedPath).toLowerCase();
const expectedExt = getVideoExtension(mediaType);
const validExtensions = [".mp4", ".webm"];
if (!ext || !validExtensions.includes(ext)) {
return resolvedPath + expectedExt;
}
// If extension doesn't match mediaType, replace it
if (ext !== expectedExt) {
const basePath = resolvedPath.slice(0, -ext.length);
return basePath + expectedExt;
}
return resolvedPath;
}
/**
* Format video duration in human-readable format
*
* @param seconds - Duration in seconds
* @returns Formatted string (e.g., "4s", "1m 30s")
*/
export function formatVideoDuration(seconds) {
if (seconds < 60) {
return `${seconds}s`;
}
const minutes = Math.floor(seconds / 60);
const remainingSeconds = seconds % 60;
if (remainingSeconds === 0) {
return `${minutes}m`;
}
return `${minutes}m ${remainingSeconds}s`;
}
/**
* Format video dimensions in human-readable format
*
* @param width - Video width in pixels
* @param height - Video height in pixels
* @returns Formatted string (e.g., "1920x1080")
*/
export function formatVideoDimensions(width, height) {
return `${width}x${height}`;
}
/**
* Save generated video result to a file
*
* Creates parent directories if they don't exist and handles both
* absolute and relative paths.
*
* @param video - Video generation result containing video buffer
* @param outputPath - Path where the video should be saved
* @returns Save result with success status, path, and size
*
* @example
* ```typescript
* const result = await saveVideoToFile(videoResult, "./output/video.mp4");
* if (result.success) {
* console.log(`Saved to ${result.path} (${formatVideoFileSize(result.size)})`);
* }
* ```
*/
export async function saveVideoToFile(video, outputPath) {
let normalizedPath = outputPath;
try {
// Normalize the output path
normalizedPath = normalizeVideoOutputPath(outputPath, video.mediaType);
// Ensure parent directories exist
await ensureVideoDirectoryExists(normalizedPath);
// Write the video buffer to file
await fs.promises.writeFile(normalizedPath, video.data);
// Get the actual file size
const stats = await fs.promises.stat(normalizedPath);
return {
success: true,
path: normalizedPath,
size: stats.size,
};
}
catch (error) {
const errorMessage = error instanceof Error ? error.message : "Unknown error occurred";
return {
success: false,
path: normalizedPath,
size: 0,
error: errorMessage,
};
}
}
/**
* Type guard to check if an object is a valid VideoGenerationResult
*
* @param obj - Object to check
* @returns True if object is a valid VideoGenerationResult
*/
export function isVideoGenerationResult(obj) {
if (!obj || typeof obj !== "object") {
return false;
}
const video = obj;
// Check required fields
if (!(video.data instanceof Buffer)) {
return false;
}
// Check mediaType is valid
if (video.mediaType !== "video/mp4" && video.mediaType !== "video/webm") {
return false;
}
return true;
}
/**
* Get video metadata summary for display
*
* @param video - Video generation result
* @returns Formatted metadata summary string
*/
export function getVideoMetadataSummary(video) {
const parts = [];
if (video.metadata?.duration) {
parts.push(`Duration: ${formatVideoDuration(video.metadata.duration)}`);
}
if (video.metadata?.dimensions) {
parts.push(`Resolution: ${formatVideoDimensions(video.metadata.dimensions.width, video.metadata.dimensions.height)}`);
}
if (video.metadata?.aspectRatio) {
parts.push(`Aspect: ${video.metadata.aspectRatio}`);
}
if (video.metadata?.audioEnabled !== undefined) {
parts.push(`Audio: ${video.metadata.audioEnabled ? "Yes" : "No"}`);
}
if (video.metadata?.processingTime) {
const seconds = (video.metadata.processingTime / 1000).toFixed(1);
parts.push(`Processing: ${seconds}s`);
}
return parts.join(" | ");
}
//# sourceMappingURL=videoFileUtils.js.map