UNPKG

@tsavo/creatify-api-ts

Version:

TypeScript client for the Creatify AI API

365 lines (323 loc) 10.8 kB
import { Creatify } from '../index'; import { CreatifyApiOptions, AspectRatio, AiEditing, LipsyncV2 } from '../types'; import { apiClientFactory } from '../client-factory'; /** * Batch processing options */ export interface BatchProcessingOptions { /** * Maximum number of concurrent tasks (default: 3) */ concurrency?: number; /** * Whether to continue processing if one task fails (default: false) */ continueOnError?: boolean; /** * Delay between starting new tasks in milliseconds (default: 500) */ taskStartDelay?: number; /** * Maximum number of retries for failed tasks (default: 0) */ maxRetries?: number; } /** * Default batch processing options */ const DEFAULT_BATCH_OPTIONS: Required<BatchProcessingOptions> = { concurrency: 3, continueOnError: false, taskStartDelay: 500, maxRetries: 0, }; /** * Result of a batch processing operation */ export interface BatchResult<T> { /** * Array of successful results */ successes: T[]; /** * Array of errors, with the index of the task that failed */ errors: Array<{ index: number; error: Error | unknown }>; /** * Whether all tasks were successful */ allSuccessful: boolean; } /** * Utility class for processing multiple API tasks in batch */ export class BatchProcessor { private api: Creatify; /** * Create a new BatchProcessor * @param apiIdOrOptions API ID or full options object * @param apiKey API key (only required if apiIdOrOptions is a string) * @param clientFactory Optional client factory for testing purposes */ constructor( apiIdOrOptions: string | CreatifyApiOptions, apiKey?: string, _clientFactory = apiClientFactory ) { // Handle different constructor argument formats const options: CreatifyApiOptions = typeof apiIdOrOptions === 'string' ? { apiId: apiIdOrOptions, apiKey: apiKey! } : apiIdOrOptions; this.api = new Creatify(options); } /** * Process an array of tasks in batch with controlled concurrency * @param tasks Array of async functions to execute * @param options Batch processing options * @returns Promise resolving to the batch result */ async processBatch<T>( tasks: Array<() => Promise<T>>, options: BatchProcessingOptions = {} ): Promise<BatchResult<T>> { // Merge options with defaults const opts: Required<BatchProcessingOptions> = { ...DEFAULT_BATCH_OPTIONS, ...options, }; const result: BatchResult<T> = { successes: [], errors: [], allSuccessful: true, }; // Keep track of active tasks let activeTasks = 0; let taskIndex = 0; // Create a queue of promises to resolve when all tasks are complete const queue: Promise<void>[] = []; // Process tasks while (taskIndex < tasks.length) { // Wait until we have an available slot if (activeTasks >= opts.concurrency) { await Promise.race(queue); continue; } // Start a task const currentIndex = taskIndex++; activeTasks++; // Define and create the task promise const taskPromise = (async () => { try { // Add delay between task starts if specified if (opts.taskStartDelay > 0 && currentIndex > 0) { await new Promise(resolve => setTimeout(resolve, opts.taskStartDelay)); } // Execute the task with retries let taskResult; let lastError; let retryCount = 0; while (retryCount <= opts.maxRetries) { try { taskResult = await tasks[currentIndex](); break; // Success, exit the retry loop } catch (error) { lastError = error; retryCount++; if (retryCount <= opts.maxRetries) { // Wait before retrying (exponential backoff) const retryDelay = Math.min(1000 * Math.pow(2, retryCount - 1), 30000); await new Promise(resolve => setTimeout(resolve, retryDelay)); console.warn(`Retrying task ${currentIndex}, attempt ${retryCount} of ${opts.maxRetries}`); } } } if (taskResult) { result.successes.push(taskResult); } else { // All retries failed result.errors.push({ index: currentIndex, error: lastError }); result.allSuccessful = false; // Handle error based on options if (!opts.continueOnError) { throw lastError; } } } catch (error) { // This catch block handles unexpected errors in the retry logic itself result.errors.push({ index: currentIndex, error }); result.allSuccessful = false; // Handle error based on options if (!opts.continueOnError) { throw error; } } finally { // Decrease active task count activeTasks--; // Remove this promise from the queue - find it by filtering instead of using indexOf const taskIndex = queue.findIndex(p => p === taskPromise); if (taskIndex !== -1) { queue.splice(taskIndex, 1); } } })(); // Add to queue queue.push(taskPromise); // If we should stop on error and an error occurs, break out of the loop if (!opts.continueOnError && !result.allSuccessful) { break; } } // Wait for all remaining tasks to complete if (queue.length > 0) { await Promise.all(queue).catch(error => { // If continueOnError is false, this error will have already been recorded // But we need to catch it here to prevent it from bubbling up if (opts.continueOnError) { console.error('Unexpected error in batch processing:', error); } }); } return result; } /** * Process an array of avatar video creation tasks in batch * @param avatarTasks Array of avatar tasks with text and avatarId * @param options Batch processing options * @returns Promise resolving to the batch result */ async processAvatarBatch( avatarTasks: Array<{ text: string; avatarId: string; voiceId?: string; aspectRatio?: string }>, options: BatchProcessingOptions = {} ) { const tasks = avatarTasks.map(task => { return async () => { return this.api.avatar.createAndWaitForLipsync({ text: task.text, creator: task.avatarId, voice_id: task.voiceId, aspect_ratio: task.aspectRatio as AspectRatio, }); }; }); return this.processBatch(tasks, options); } /** * Process an array of text-to-speech tasks in batch * @param textToSpeechTasks Array of text-to-speech tasks with script and accent * @param options Batch processing options * @returns Promise resolving to the batch result */ async processTextToSpeechBatch( textToSpeechTasks: Array<{ script: string; accent: string }>, options: BatchProcessingOptions = {} ) { const tasks = textToSpeechTasks.map(task => { return async () => { return this.api.textToSpeech.createAndWaitForTextToSpeech({ script: task.script, accent: task.accent, }); }; }); return this.processBatch(tasks, options); } /** * Process an array of AI editing tasks in batch * @param aiEditingTasks Array of AI editing tasks with videoUrl and editingStyle * @param options Batch processing options * @returns Promise resolving to the batch result */ async processAiEditingBatch( aiEditingTasks: Array<{ videoUrl: string; editingStyle: string }>, options: BatchProcessingOptions = {} ) { const tasks = aiEditingTasks.map(task => { return async () => { return this.api.aiEditing.createAndWaitForAiEditing({ video_url: task.videoUrl, editing_style: task.editingStyle as AiEditing.EditingStyle, }); }; }); return this.processBatch(tasks, options); } /** * Process an array of AI Shorts tasks in batch * @param aiShortsTasks Array of AI Shorts tasks with prompt and aspectRatio * @param options Batch processing options * @returns Promise resolving to the batch result */ async processAiShortsBatch( aiShortsTasks: Array<{ prompt: string; aspectRatio: string; targetPlatform?: string; targetAudience?: string; language?: string; }>, options: BatchProcessingOptions = {} ) { const tasks = aiShortsTasks.map(task => { return async () => { return this.api.aiShorts.createAndWaitForAiShorts({ prompt: task.prompt, aspect_ratio: task.aspectRatio as AspectRatio, target_platform: task.targetPlatform, target_audience: task.targetAudience, language: task.language, }); }; }); return this.processBatch(tasks, options); } /** * Process an array of AI Scripts tasks in batch * @param aiScriptsTasks Array of AI Scripts tasks with prompt and other optional parameters * @param options Batch processing options * @returns Promise resolving to the batch result */ async processAiScriptsBatch( aiScriptsTasks: Array<{ prompt: string; targetPlatform?: string; targetAudience?: string; language?: string; scriptLength?: number; }>, options: BatchProcessingOptions = {} ) { const tasks = aiScriptsTasks.map(task => { return async () => { return this.api.aiScripts.createAndWaitForAiScript({ prompt: task.prompt, target_platform: task.targetPlatform, target_audience: task.targetAudience, language: task.language, script_length: task.scriptLength, }); }; }); return this.processBatch(tasks, options); } /** * Process an array of Lipsync v2 tasks in batch * @param lipsyncV2Tasks Array of Lipsync v2 tasks with videoInputs and aspectRatio * @param options Batch processing options * @returns Promise resolving to the batch result */ async processLipsyncV2Batch( lipsyncV2Tasks: Array<{ videoInputs: Record<string, unknown>[]; aspectRatio: string }>, options: BatchProcessingOptions = {} ) { const tasks = lipsyncV2Tasks.map(task => { return async () => { return this.api.lipsyncV2.createAndWaitForLipsyncV2({ video_inputs: task.videoInputs as LipsyncV2.LipsyncV2Params['video_inputs'], aspect_ratio: task.aspectRatio as AspectRatio, }); }; }); return this.processBatch(tasks, options); } }