UNPKG

seedance-video-sdk

Version:

Seedance AI Video Generation SDK - Create professional videos from text and images using ByteDance's advanced AI technology

771 lines (763 loc) 28.1 kB
'use strict'; var axios = require('axios'); var FormData = require('form-data'); var jsxRuntime = require('react/jsx-runtime'); var react = require('react'); /** * Seedance AI Video Generation Client * * Official SDK for integrating Seedance AI video generation capabilities * into your applications. Supports text-to-video and image-to-video generation * with advanced features like multi-shot narrative and style control. * * @example * ```typescript * import { SeedanceClient } from '@seedance/video-sdk'; * * const client = new SeedanceClient({ * apiKey: 'your-api-key' * }); * * // Generate video from text * const task = await client.generateVideo({ * prompt: 'A beautiful sunset over the ocean', * duration: 5, * resolution: '1080p' * }); * * // Check progress * const status = await client.getTask(task.taskId); * ``` */ class SeedanceClient { constructor(config) { this.config = { baseURL: 'https://api.seedance.studio/v1', timeout: 30000, debug: false, ...config, }; this.client = axios.create({ baseURL: this.config.baseURL, timeout: this.config.timeout, headers: { 'Authorization': `Bearer ${this.config.apiKey}`, 'Content-Type': 'application/json', 'User-Agent': '@seedance/video-sdk/1.0.0', }, }); // Add request/response interceptors for debugging and error handling this.setupInterceptors(); } /** * Generate a video from text prompt or image */ async generateVideo(request) { try { const formData = new FormData(); // Add text prompt formData.append('prompt', request.prompt); // Add optional parameters if (request.duration) formData.append('duration', request.duration.toString()); if (request.resolution) formData.append('resolution', request.resolution); if (request.style) formData.append('style', request.style); if (request.aspectRatio) formData.append('aspectRatio', request.aspectRatio); if (request.frameRate) formData.append('frameRate', request.frameRate.toString()); // Add generation parameters if (request.parameters) { formData.append('parameters', JSON.stringify(request.parameters)); } // Add image if provided if (request.image) { if (request.image instanceof File) { formData.append('image', request.image); } else if (typeof request.image === 'string') { // URL or base64 formData.append('imageUrl', request.image); } else { // Buffer formData.append('image', request.image, 'image.jpg'); } } const response = await this.client.post('/videos/generate', formData, { headers: { ...formData.getHeaders(), }, }); return response.data; } catch (error) { throw this.handleError(error); } } /** * Get task status and details */ async getTask(taskId) { try { const response = await this.client.get(`/videos/tasks/${taskId}`); return response.data; } catch (error) { throw this.handleError(error); } } /** * List user's video tasks with pagination */ async listTasks(options = {}) { try { const params = new URLSearchParams(); if (options.page) params.append('page', options.page.toString()); if (options.pageSize) params.append('pageSize', options.pageSize.toString()); if (options.status) params.append('status', options.status); if (options.sortBy) params.append('sortBy', options.sortBy); if (options.sortOrder) params.append('sortOrder', options.sortOrder); const response = await this.client.get(`/videos/tasks?${params}`); return response.data; } catch (error) { throw this.handleError(error); } } /** * Cancel a pending or processing task */ async cancelTask(taskId) { try { await this.client.post(`/videos/tasks/${taskId}/cancel`); } catch (error) { throw this.handleError(error); } } /** * Delete a completed task */ async deleteTask(taskId) { try { await this.client.delete(`/videos/tasks/${taskId}`); } catch (error) { throw this.handleError(error); } } /** * Upload an image for later use in video generation */ async uploadImage(image, filename) { try { const formData = new FormData(); formData.append('image', image, filename || 'image.jpg'); const response = await this.client.post('/upload/image', formData, { headers: { ...formData.getHeaders(), }, }); return response.data; } catch (error) { throw this.handleError(error); } } /** * Get user quota information */ async getQuota() { try { const response = await this.client.get('/user/quota'); return response.data; } catch (error) { throw this.handleError(error); } } /** * Get user information */ async getUserInfo() { try { const response = await this.client.get('/user/info'); return response.data; } catch (error) { throw this.handleError(error); } } /** * Configure webhook for task notifications */ async configureWebhook(config) { try { await this.client.post('/webhooks/configure', config); } catch (error) { throw this.handleError(error); } } /** * Wait for task completion with polling */ async waitForCompletion(taskId, options = {}) { const { pollInterval = 2000, timeout = 300000, onProgress } = options; const startTime = Date.now(); while (Date.now() - startTime < timeout) { const task = await this.getTask(taskId); if (onProgress) { onProgress(task); } if (task.status === 'completed') { return task; } if (task.status === 'failed') { throw new Error(`Task failed: ${task.error}`); } if (task.status === 'cancelled') { throw new Error('Task was cancelled'); } await new Promise(resolve => setTimeout(resolve, pollInterval)); } throw new Error('Task timeout'); } setupInterceptors() { // Request interceptor for debugging this.client.interceptors.request.use((config) => { if (this.config.debug) { console.log('Seedance SDK Request:', config); } return config; }, (error) => { if (this.config.debug) { console.error('Seedance SDK Request Error:', error); } return Promise.reject(error); }); // Response interceptor for debugging this.client.interceptors.response.use((response) => { if (this.config.debug) { console.log('Seedance SDK Response:', response); } return response; }, (error) => { if (this.config.debug) { console.error('Seedance SDK Response Error:', error); } return Promise.reject(error); }); } handleError(error) { if (error.response) { // Server responded with error status const { status, data } = error.response; return { code: data?.code || `HTTP_${status}`, message: data?.message || error.message, details: data, }; } else if (error.request) { // Network error return { code: 'NETWORK_ERROR', message: 'Network request failed', details: error.request, }; } else { // Other error return { code: 'UNKNOWN_ERROR', message: error.message, details: error, }; } } } /** * Utility functions for Seedance Video SDK */ /** * Convert video resolution to dimensions */ function getResolutionDimensions(resolution) { switch (resolution) { case '480p': return { width: 854, height: 480 }; case '720p': return { width: 1280, height: 720 }; case '1080p': return { width: 1920, height: 1080 }; case '4K': return { width: 3840, height: 2160 }; default: return { width: 1920, height: 1080 }; } } /** * Calculate video dimensions based on resolution and aspect ratio */ function calculateVideoDimensions(resolution, aspectRatio) { const baseDimensions = getResolutionDimensions(resolution); const [widthRatio, heightRatio] = aspectRatio.split(':').map(Number); // Calculate dimensions maintaining aspect ratio const aspectValue = widthRatio / heightRatio; if (aspectValue > 1) { // Landscape const width = baseDimensions.width; const height = Math.round(width / aspectValue); return { width, height }; } else { // Portrait or square const height = baseDimensions.height; const width = Math.round(height * aspectValue); return { width, height }; } } /** * Validate video generation parameters */ function validateVideoParams(params) { const errors = []; if (params.duration !== undefined) { if (params.duration < 1 || params.duration > 30) { errors.push('Duration must be between 1 and 30 seconds'); } } if (params.frameRate !== undefined) { if (params.frameRate < 12 || params.frameRate > 60) { errors.push('Frame rate must be between 12 and 60 fps'); } } return { isValid: errors.length === 0, errors, }; } /** * Format file size in human readable format */ function formatFileSize(bytes) { if (bytes === 0) return '0 Bytes'; const k = 1024; const sizes = ['Bytes', 'KB', 'MB', 'GB']; const i = Math.floor(Math.log(bytes) / Math.log(k)); return parseFloat((bytes / Math.pow(k, i)).toFixed(2)) + ' ' + sizes[i]; } /** * Format duration in human readable format */ function formatDuration(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`; } /** * Get status color for UI components */ function getStatusColor(status) { switch (status) { case 'pending': return '#f59e0b'; // amber case 'processing': return '#3b82f6'; // blue case 'completed': return '#10b981'; // green case 'failed': return '#ef4444'; // red case 'cancelled': return '#6b7280'; // gray default: return '#6b7280'; } } /** * Get status display text */ function getStatusText(status) { switch (status) { case 'pending': return 'Pending'; case 'processing': return 'Processing'; case 'completed': return 'Completed'; case 'failed': return 'Failed'; case 'cancelled': return 'Cancelled'; default: return 'Unknown'; } } /** * Estimate video file size based on parameters */ function estimateVideoSize(duration, resolution, frameRate = 30) { const dimensions = getResolutionDimensions(resolution); const pixels = dimensions.width * dimensions.height; // Rough estimation: 0.1 bits per pixel per frame const bitsPerFrame = pixels * 0.1; const totalFrames = duration * frameRate; const totalBits = bitsPerFrame * totalFrames; return Math.round(totalBits / 8); // Convert to bytes } /** * Generate a unique task ID (for client-side tracking) */ function generateTaskId() { return `task_${Date.now()}_${Math.random().toString(36).substr(2, 9)}`; } /** * Check if a URL is valid */ function isValidUrl(url) { try { new URL(url); return true; } catch { return false; } } /** * Check if a file is a valid image */ function isValidImageFile(file) { const validTypes = ['image/jpeg', 'image/jpg', 'image/png', 'image/webp', 'image/gif']; return validTypes.includes(file.type); } /** * Get file extension from filename */ function getFileExtension(filename) { return filename.split('.').pop()?.toLowerCase() || ''; } /** * Convert File to base64 string */ function fileToBase64(file) { return new Promise((resolve, reject) => { const reader = new FileReader(); reader.readAsDataURL(file); reader.onload = () => { if (typeof reader.result === 'string') { resolve(reader.result); } else { reject(new Error('Failed to convert file to base64')); } }; reader.onerror = error => reject(error); }); } /** * Debounce function for API calls */ function debounce(func, wait) { let timeout; return (...args) => { clearTimeout(timeout); timeout = setTimeout(() => func(...args), wait); }; } /** * Retry function with exponential backoff */ async function retry(fn, options = {}) { const { retries = 3, delay = 1000, backoff = 2 } = options; try { return await fn(); } catch (error) { if (retries > 0) { await new Promise(resolve => setTimeout(resolve, delay)); return retry(fn, { retries: retries - 1, delay: delay * backoff, backoff, }); } throw error; } } /** * Create a progress tracker for long-running operations */ class ProgressTracker { constructor() { this.callbacks = []; this._progress = 0; } get progress() { return this._progress; } setProgress(progress) { this._progress = Math.max(0, Math.min(100, progress)); this.callbacks.forEach(callback => callback(this._progress)); } onProgress(callback) { this.callbacks.push(callback); // Return unsubscribe function return () => { const index = this.callbacks.indexOf(callback); if (index > -1) { this.callbacks.splice(index, 1); } }; } reset() { this._progress = 0; this.callbacks.forEach(callback => callback(0)); } } /** * Seedance Video Player Component * * A customizable video player optimized for AI-generated videos * with built-in loading states and error handling. */ const VideoPlayer = ({ src, poster, controls = true, autoplay = false, loop = false, muted = false, className = '', onLoadStart, onLoadedData, onError, }) => { const videoRef = react.useRef(null); const [isLoading, setIsLoading] = react.useState(true); const [hasError, setHasError] = react.useState(false); const [errorMessage, setErrorMessage] = react.useState(''); react.useEffect(() => { const video = videoRef.current; if (!video) return; const handleLoadStart = () => { setIsLoading(true); setHasError(false); onLoadStart?.(); }; const handleLoadedData = () => { setIsLoading(false); onLoadedData?.(); }; const handleError = (event) => { setIsLoading(false); setHasError(true); const error = new Error('Video failed to load'); setErrorMessage(error.message); onError?.(error); }; video.addEventListener('loadstart', handleLoadStart); video.addEventListener('loadeddata', handleLoadedData); video.addEventListener('error', handleError); return () => { video.removeEventListener('loadstart', handleLoadStart); video.removeEventListener('loadeddata', handleLoadedData); video.removeEventListener('error', handleError); }; }, [onLoadStart, onLoadedData, onError]); const baseStyles = ` relative w-full h-auto rounded-lg overflow-hidden bg-gray-900 `; const videoStyles = ` w-full h-full object-cover `; const loadingStyles = ` absolute inset-0 flex items-center justify-center bg-gray-900 text-white `; const errorStyles = ` absolute inset-0 flex flex-col items-center justify-center bg-gray-900 text-white p-4 text-center `; return (jsxRuntime.jsxs("div", { className: `${baseStyles} ${className}`, children: [jsxRuntime.jsx("video", { ref: videoRef, src: src, poster: poster, controls: controls, autoPlay: autoplay, loop: loop, muted: muted, className: videoStyles, playsInline: true }), isLoading && (jsxRuntime.jsxs("div", { className: loadingStyles, children: [jsxRuntime.jsx("div", { className: "animate-spin rounded-full h-8 w-8 border-b-2 border-white" }), jsxRuntime.jsx("p", { className: "mt-2 text-sm", children: "Loading video..." })] })), hasError && (jsxRuntime.jsxs("div", { className: errorStyles, children: [jsxRuntime.jsx("svg", { className: "w-12 h-12 text-red-400 mb-2", fill: "none", stroke: "currentColor", viewBox: "0 0 24 24", children: jsxRuntime.jsx("path", { strokeLinecap: "round", strokeLinejoin: "round", strokeWidth: 2, d: "M12 8v4m0 4h.01M21 12a9 9 0 11-18 0 9 9 0 0118 0z" }) }), jsxRuntime.jsx("p", { className: "text-sm font-medium", children: "Failed to load video" }), jsxRuntime.jsx("p", { className: "text-xs text-gray-400 mt-1", children: errorMessage })] }))] })); }; /** * Progress Indicator Component * * Displays the progress and status of video generation tasks * with visual indicators and estimated time remaining. */ const ProgressIndicator = ({ progress, status, estimatedTime, className = '', showPercentage = true, showStatus = true, }) => { const statusColor = getStatusColor(status); const statusText = getStatusText(status); const getProgressBarColor = (status) => { switch (status) { case 'processing': return 'bg-blue-500'; case 'completed': return 'bg-green-500'; case 'failed': return 'bg-red-500'; case 'cancelled': return 'bg-gray-500'; default: return 'bg-yellow-500'; } }; const getIcon = (status) => { switch (status) { case 'pending': return (jsxRuntime.jsx("svg", { className: "w-4 h-4 animate-pulse", fill: "currentColor", viewBox: "0 0 20 20", children: jsxRuntime.jsx("path", { fillRule: "evenodd", d: "M10 18a8 8 0 100-16 8 8 0 000 16zM9.555 7.168A1 1 0 008 8v4a1 1 0 001.555.832l3-2a1 1 0 000-1.664l-3-2z", clipRule: "evenodd" }) })); case 'processing': return (jsxRuntime.jsx("svg", { className: "w-4 h-4 animate-spin", fill: "none", stroke: "currentColor", viewBox: "0 0 24 24", children: jsxRuntime.jsx("path", { strokeLinecap: "round", strokeLinejoin: "round", strokeWidth: 2, d: "M4 4v5h.582m15.356 2A8.001 8.001 0 004.582 9m0 0H9m11 11v-5h-.581m0 0a8.003 8.003 0 01-15.357-2m15.357 2H15" }) })); case 'completed': return (jsxRuntime.jsx("svg", { className: "w-4 h-4", fill: "currentColor", viewBox: "0 0 20 20", children: jsxRuntime.jsx("path", { fillRule: "evenodd", d: "M16.707 5.293a1 1 0 010 1.414l-8 8a1 1 0 01-1.414 0l-4-4a1 1 0 011.414-1.414L8 12.586l7.293-7.293a1 1 0 011.414 0z", clipRule: "evenodd" }) })); case 'failed': return (jsxRuntime.jsx("svg", { className: "w-4 h-4", fill: "currentColor", viewBox: "0 0 20 20", children: jsxRuntime.jsx("path", { fillRule: "evenodd", d: "M18 10a8 8 0 11-16 0 8 8 0 0116 0zm-7 4a1 1 0 11-2 0 1 1 0 012 0zm-1-9a1 1 0 00-1 1v4a1 1 0 102 0V6a1 1 0 00-1-1z", clipRule: "evenodd" }) })); case 'cancelled': return (jsxRuntime.jsx("svg", { className: "w-4 h-4", fill: "currentColor", viewBox: "0 0 20 20", children: jsxRuntime.jsx("path", { fillRule: "evenodd", d: "M4.293 4.293a1 1 0 011.414 0L10 8.586l4.293-4.293a1 1 0 111.414 1.414L11.414 10l4.293 4.293a1 1 0 01-1.414 1.414L10 11.414l-4.293 4.293a1 1 0 01-1.414-1.414L8.586 10 4.293 5.707a1 1 0 010-1.414z", clipRule: "evenodd" }) })); default: return null; } }; const baseStyles = ` w-full p-4 bg-white dark:bg-gray-800 rounded-lg border border-gray-200 dark:border-gray-700 `; return (jsxRuntime.jsxs("div", { className: `${baseStyles} ${className}`, children: [jsxRuntime.jsxs("div", { className: "flex items-center justify-between mb-3", children: [jsxRuntime.jsxs("div", { className: "flex items-center space-x-2", children: [jsxRuntime.jsx("div", { style: { color: statusColor }, children: getIcon(status) }), showStatus && (jsxRuntime.jsx("span", { className: "text-sm font-medium text-gray-900 dark:text-white", children: statusText }))] }), showPercentage && (jsxRuntime.jsxs("span", { className: "text-sm text-gray-500 dark:text-gray-400", children: [Math.round(progress), "%"] }))] }), jsxRuntime.jsx("div", { className: "w-full bg-gray-200 dark:bg-gray-700 rounded-full h-2 mb-3", children: jsxRuntime.jsx("div", { className: `h-2 rounded-full transition-all duration-300 ease-out ${getProgressBarColor(status)}`, style: { width: `${Math.min(100, Math.max(0, progress))}%` } }) }), jsxRuntime.jsxs("div", { className: "flex items-center justify-between text-xs text-gray-500 dark:text-gray-400", children: [jsxRuntime.jsxs("div", { children: [status === 'processing' && estimatedTime && (jsxRuntime.jsxs("span", { children: ["Estimated time: ", formatDuration(estimatedTime)] })), status === 'completed' && (jsxRuntime.jsx("span", { children: "Video generation completed" })), status === 'failed' && (jsxRuntime.jsx("span", { children: "Generation failed" })), status === 'pending' && (jsxRuntime.jsx("span", { children: "Waiting in queue..." })), status === 'cancelled' && (jsxRuntime.jsx("span", { children: "Generation cancelled" }))] }), status === 'processing' && (jsxRuntime.jsxs("div", { className: "flex items-center space-x-1", children: [jsxRuntime.jsx("div", { className: "w-1 h-1 bg-blue-500 rounded-full animate-pulse" }), jsxRuntime.jsx("div", { className: "w-1 h-1 bg-blue-500 rounded-full animate-pulse", style: { animationDelay: '0.2s' } }), jsxRuntime.jsx("div", { className: "w-1 h-1 bg-blue-500 rounded-full animate-pulse", style: { animationDelay: '0.4s' } })] }))] })] })); }; /** * @seedance/video-sdk * * Official Seedance AI Video Generation SDK * * Create professional videos from text and images using ByteDance's advanced AI technology. * This SDK provides a simple and powerful interface for integrating Seedance's video generation * capabilities into your applications. * * @example * ```typescript * import { SeedanceClient } from '@seedance/video-sdk'; * * const client = new SeedanceClient({ * apiKey: 'your-api-key' * }); * * // Generate video from text * const task = await client.generateVideo({ * prompt: 'A beautiful sunset over the ocean', * duration: 5, * resolution: '1080p' * }); * * // Wait for completion * const completedTask = await client.waitForCompletion(task.taskId, { * onProgress: (task) => console.log(`Progress: ${task.progress}%`) * }); * * console.log('Video URL:', completedTask.videoUrl); * ``` * * @example React Components * ```tsx * import { VideoPlayer, ProgressIndicator } from '@seedance/video-sdk/react'; * * function MyComponent() { * return ( * <div> * <VideoPlayer * src="https://example.com/video.mp4" * controls * autoplay * /> * <ProgressIndicator * progress={75} * status="processing" * estimatedTime={30} * /> * </div> * ); * } * ``` * * @author Seedance Studio * @version 1.0.0 * @license MIT * @homepage https://seedance.studio * @repository https://github.com/saasfly/saasfly */ // Core client // Constants const SDK_VERSION = '1.0.0'; const DEFAULT_API_BASE_URL = 'https://api.seedance.studio/v1'; const SUPPORTED_IMAGE_FORMATS = ['image/jpeg', 'image/jpg', 'image/png', 'image/webp', 'image/gif']; const MAX_FILE_SIZE = 10 * 1024 * 1024; // 10MB const MAX_VIDEO_DURATION = 30; // seconds const MIN_VIDEO_DURATION = 1; // seconds /** * Create a new Seedance client instance * * @param config - Configuration options * @returns Configured SeedanceClient instance */ function createClient(config) { return new SeedanceClient(config); } /** * Quick video generation helper function * * @param apiKey - Your Seedance API key * @param prompt - Text prompt for video generation * @param options - Additional generation options * @returns Promise resolving to video generation response */ async function generateVideo(apiKey, prompt, options = {}) { const client = new SeedanceClient({ apiKey }); return client.generateVideo({ prompt, ...options }); } /** * Quick image-to-video generation helper function * * @param apiKey - Your Seedance API key * @param image - Input image (File, URL, or Buffer) * @param prompt - Text prompt describing the desired video * @param options - Additional generation options * @returns Promise resolving to video generation response */ async function generateVideoFromImage(apiKey, image, prompt, options = {}) { const client = new SeedanceClient({ apiKey }); return client.generateVideo({ prompt, image, ...options }); } // Note: No default export to avoid CommonJS/ESM compatibility issues // Use: import { SeedanceClient } from '@seedance/video-sdk' exports.DEFAULT_API_BASE_URL = DEFAULT_API_BASE_URL; exports.MAX_FILE_SIZE = MAX_FILE_SIZE; exports.MAX_VIDEO_DURATION = MAX_VIDEO_DURATION; exports.MIN_VIDEO_DURATION = MIN_VIDEO_DURATION; exports.ProgressIndicator = ProgressIndicator; exports.ProgressTracker = ProgressTracker; exports.SDK_VERSION = SDK_VERSION; exports.SUPPORTED_IMAGE_FORMATS = SUPPORTED_IMAGE_FORMATS; exports.SeedanceClient = SeedanceClient; exports.VideoPlayer = VideoPlayer; exports.calculateVideoDimensions = calculateVideoDimensions; exports.createClient = createClient; exports.debounce = debounce; exports.estimateVideoSize = estimateVideoSize; exports.fileToBase64 = fileToBase64; exports.formatDuration = formatDuration; exports.formatFileSize = formatFileSize; exports.generateTaskId = generateTaskId; exports.generateVideo = generateVideo; exports.generateVideoFromImage = generateVideoFromImage; exports.getFileExtension = getFileExtension; exports.getResolutionDimensions = getResolutionDimensions; exports.getStatusColor = getStatusColor; exports.getStatusText = getStatusText; exports.isValidImageFile = isValidImageFile; exports.isValidUrl = isValidUrl; exports.retry = retry; exports.validateVideoParams = validateVideoParams; //# sourceMappingURL=index.js.map