@tsavo/creatify-api-ts
Version:
TypeScript client for the Creatify AI API
365 lines (323 loc) • 10.8 kB
text/typescript
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);
}
}