Skip to main content
Glama

SRT Translation MCP Server

by omd0
unified-ai-interface.ts23.7 kB
/** * Unified AI Interface for SRT Chunking Functions * Provides a single interface that works with Claude, GPT, Gemini, and other AI models */ import { SRTSubtitle, SRTChunk } from '../types/srt.js'; import { detectConversations, detectConversationsAdvanced } from '../chunking/conversation-detector.js'; import { TodoToolInterface, SRTProcessingTodoManager, TodoToolFactory } from './todo-tool-integration.js'; import { ClaudeSRTIntegration, ClaudeConfig } from './claude-integration.js'; /** * Supported AI Models */ export type SupportedAIModel = 'claude' | 'gpt' | 'gemini' | 'generic'; /** * AI Model Capabilities */ export interface AIModelCapabilities { supportsTodoTool: boolean; maxContextSize: number; supportsFunctionCalling: boolean; supportsStreaming: boolean; supportsMultimodal: boolean; supportsReasoning: boolean; maxTokensPerRequest: number; temperature: number; processingStrategy: 'sequential' | 'parallel' | 'batch'; } /** * AI Model Configuration */ export interface UnifiedAIConfig { modelType: SupportedAIModel; capabilities: AIModelCapabilities; chunkingOptions: { boundaryThreshold: number; maxChunkSize: number; minChunkSize: number; enableSemanticAnalysis: boolean; enableSpeakerDiarization: boolean; }; contextOptimization: { enabled: boolean; maxContextSize: number; chunkSizeLimit: number; contextManagement: 'aggressive' | 'conservative' | 'balanced'; }; todoIntegration: { enabled: boolean; progressTracking: boolean; detailedLogging: boolean; }; } /** * Processing Request */ export interface ProcessingRequest { subtitles: SRTSubtitle[]; processingType: 'translation' | 'analysis' | 'conversation-detection'; targetLanguage?: string; sourceLanguage?: string; options?: { useReasoning?: boolean; temperature?: number; maxTokens?: number; preserveFormatting?: boolean; contextOptimization?: boolean; }; } /** * Processing Result */ export interface UnifiedProcessingResult { success: boolean; modelType: SupportedAIModel; chunks: SRTChunk[]; results: any[]; todoList?: any[]; metadata: ProcessingMetadata; errors: ProcessingError[]; warnings: ProcessingWarning[]; processingTime: number; } /** * Processing Metadata */ export interface ProcessingMetadata { totalChunks: number; processedChunks: number; totalSubtitles: number; totalDuration: number; contextEfficiency: number; modelCapabilities: AIModelCapabilities; chunkingStrategy: string; contextOptimization: boolean; todoIntegration: boolean; } /** * Processing Error */ export interface ProcessingError { chunkId?: string; error: string; context?: string; retryable: boolean; timestamp: Date; } /** * Processing Warning */ export interface ProcessingWarning { chunkId?: string; warning: string; context?: string; timestamp: Date; } /** * Unified AI Interface */ export class UnifiedAIInterface { private config: UnifiedAIConfig; private todoManager?: SRTProcessingTodoManager; private modelIntegration: any; constructor(config: UnifiedAIConfig) { this.config = config; if (config.todoIntegration.enabled) { this.todoManager = new SRTProcessingTodoManager(config.modelType); } this.modelIntegration = this.createModelIntegration(); } /** * Process SRT with unified AI interface */ async processSRT(request: ProcessingRequest): Promise<UnifiedProcessingResult> { const startTime = Date.now(); const errors: ProcessingError[] = []; const warnings: ProcessingWarning[] = []; try { // Step 1: Create processing todos if enabled let todoList: any[] = []; if (this.todoManager) { const todoResult = await this.todoManager.createSRTProcessingTodos( 'SRT File', request.subtitles.length, request.processingType, request.targetLanguage ); todoList = await this.todoManager.getTodosByStage('file-analysis'); } // Step 2: Detect conversations with model-optimized chunking const chunks = await this.detectConversationsOptimized(request.subtitles); // Step 3: Optimize chunks for AI model context const optimizedChunks = await this.optimizeChunksForModel(chunks); // Step 4: Process chunks with AI model const results = await this.processChunksWithModel( optimizedChunks, request, errors, warnings ); // Step 5: Update todos if enabled if (this.todoManager) { await this.updateProcessingTodos('processing', 'completed'); } const processingTime = Date.now() - startTime; return { success: true, modelType: this.config.modelType, chunks: optimizedChunks, results, todoList: this.todoManager ? [] : undefined, metadata: this.generateProcessingMetadata(optimizedChunks, results, processingTime), errors, warnings, processingTime }; } catch (error) { errors.push({ error: error instanceof Error ? error.message : 'Unknown error', retryable: true, timestamp: new Date() }); return { success: false, modelType: this.config.modelType, chunks: [], results: [], todoList: this.todoManager ? [] : undefined, metadata: this.generateProcessingMetadata([], [], Date.now() - startTime), errors, warnings, processingTime: Date.now() - startTime }; } } /** * Detect conversations with model-optimized parameters */ private async detectConversationsOptimized(subtitles: SRTSubtitle[]): Promise<SRTChunk[]> { const options = this.getOptimizedChunkingOptions(); return detectConversationsAdvanced(subtitles, { boundaryThreshold: options.boundaryThreshold, maxChunkSize: options.maxChunkSize, minChunkSize: options.minChunkSize, enableSemanticAnalysis: options.enableSemanticAnalysis, enableSpeakerDiarization: options.enableSpeakerDiarization }); } /** * Get optimized chunking options based on model capabilities */ private getOptimizedChunkingOptions() { const { capabilities, chunkingOptions, contextOptimization } = this.config; let maxChunkSize = Math.min(chunkingOptions.maxChunkSize, capabilities.maxContextSize / 1000); let boundaryThreshold = chunkingOptions.boundaryThreshold; // Adjust for context optimization if (contextOptimization.enabled) { maxChunkSize = Math.min(maxChunkSize, contextOptimization.chunkSizeLimit); if (contextOptimization.contextManagement === 'aggressive') { boundaryThreshold = Math.max(0.5, boundaryThreshold - 0.1); maxChunkSize = Math.min(maxChunkSize, 5); } else if (contextOptimization.contextManagement === 'conservative') { boundaryThreshold = Math.min(0.9, boundaryThreshold + 0.1); maxChunkSize = Math.min(maxChunkSize, 15); } } return { boundaryThreshold, maxChunkSize, minChunkSize: chunkingOptions.minChunkSize, enableSemanticAnalysis: chunkingOptions.enableSemanticAnalysis, enableSpeakerDiarization: chunkingOptions.enableSpeakerDiarization }; } /** * Optimize chunks for AI model context */ private async optimizeChunksForModel(chunks: SRTChunk[]): Promise<SRTChunk[]> { if (!this.config.contextOptimization.enabled) { return chunks; } const optimizedChunks: SRTChunk[] = []; const maxContextSize = this.config.contextOptimization.maxContextSize; const chunkSizeLimit = this.config.contextOptimization.chunkSizeLimit; for (const chunk of chunks) { const chunkContextSize = this.calculateChunkContextSize(chunk); if (chunkContextSize <= maxContextSize && chunk.subtitles.length <= chunkSizeLimit) { optimizedChunks.push(chunk); } else { // Split large chunks const splitChunks = this.splitChunkForModel(chunk, maxContextSize, chunkSizeLimit); optimizedChunks.push(...splitChunks); } } return optimizedChunks; } /** * Calculate chunk context size */ private calculateChunkContextSize(chunk: SRTChunk): number { let size = 0; // Base metadata size size += JSON.stringify(chunk).length; // Add subtitle text size for (const subtitle of chunk.subtitles) { size += (subtitle.text || '').length; } // Add context overhead size += chunk.subtitles.length * 200; return size; } /** * Split chunk for model context limits */ private splitChunkForModel( chunk: SRTChunk, maxContextSize: number, chunkSizeLimit: number ): SRTChunk[] { const subtitles = chunk.subtitles; const maxSubtitlesPerChunk = Math.min( chunkSizeLimit, Math.floor(maxContextSize / 1000) // Rough estimate ); const newChunks: SRTChunk[] = []; for (let i = 0; i < subtitles.length; i += maxSubtitlesPerChunk) { const chunkSubtitles = subtitles.slice(i, i + maxSubtitlesPerChunk); const newChunk: SRTChunk = { ...chunk, id: `${chunk.id}-part-${Math.floor(i / maxSubtitlesPerChunk) + 1}`, startIndex: chunkSubtitles[0].index, endIndex: chunkSubtitles[chunkSubtitles.length - 1].index, subtitles: chunkSubtitles, context: { ...chunk.context, conversationId: chunk.context?.conversationId || '', isSplitChunk: true, originalChunkId: chunk.id, partNumber: Math.floor(i / maxSubtitlesPerChunk) + 1, totalParts: Math.ceil(subtitles.length / maxSubtitlesPerChunk) } }; newChunks.push(newChunk); } return newChunks; } /** * Process chunks with AI model */ private async processChunksWithModel( chunks: SRTChunk[], request: ProcessingRequest, errors: ProcessingError[], warnings: ProcessingWarning[] ): Promise<any[]> { const results: any[] = []; // Choose processing strategy based on model capabilities const strategy = this.config.capabilities.processingStrategy; switch (strategy) { case 'sequential': await this.processSequentially(chunks, request, results, errors, warnings); break; case 'parallel': await this.processInParallel(chunks, request, results, errors, warnings); break; case 'batch': await this.processInBatches(chunks, request, results, errors, warnings); break; } return results; } /** * Process chunks sequentially */ private async processSequentially( chunks: SRTChunk[], request: ProcessingRequest, results: any[], errors: ProcessingError[], warnings: ProcessingWarning[] ): Promise<void> { for (const chunk of chunks) { try { const result = await this.processChunkWithModel(chunk, request); results.push(result); } catch (error) { errors.push({ chunkId: chunk.id, error: error instanceof Error ? error.message : 'Unknown error', retryable: true, timestamp: new Date() }); } } } /** * Process chunks in parallel */ private async processInParallel( chunks: SRTChunk[], request: ProcessingRequest, results: any[], errors: ProcessingError[], warnings: ProcessingWarning[] ): Promise<void> { const batchSize = this.calculateOptimalBatchSize(chunks); const batches = this.createBatches(chunks, batchSize); for (const batch of batches) { const batchPromises = batch.map(chunk => this.processChunkWithModel(chunk, request)); const batchResults = await Promise.allSettled(batchPromises); batchResults.forEach((result, index) => { if (result.status === 'fulfilled') { results.push(result.value); } else { errors.push({ chunkId: batch[index].id, error: result.reason instanceof Error ? result.reason.message : 'Unknown error', retryable: true, timestamp: new Date() }); } }); } } /** * Process chunks in batches */ private async processInBatches( chunks: SRTChunk[], request: ProcessingRequest, results: any[], errors: ProcessingError[], warnings: ProcessingWarning[] ): Promise<void> { const batchSize = this.calculateOptimalBatchSize(chunks); const batches = this.createBatches(chunks, batchSize); for (const batch of batches) { try { const batchResult = await this.processBatchWithModel(batch, request); results.push(...batchResult); } catch (error) { errors.push({ error: error instanceof Error ? error.message : 'Unknown error', retryable: true, timestamp: new Date() }); } } } /** * Process individual chunk with model */ private async processChunkWithModel(chunk: SRTChunk, request: ProcessingRequest): Promise<any> { // This would be replaced with actual AI model calls // For now, simulate processing return this.simulateChunkProcessing(chunk, request); } /** * Process batch with model */ private async processBatchWithModel(chunks: SRTChunk[], request: ProcessingRequest): Promise<any[]> { const results: any[] = []; for (const chunk of chunks) { const result = await this.processChunkWithModel(chunk, request); results.push(result); } return results; } /** * Simulate chunk processing (replace with actual AI model integration) */ private async simulateChunkProcessing(chunk: SRTChunk, request: ProcessingRequest): Promise<any> { // Simulate processing time based on chunk complexity const processingTime = Math.min(1000, chunk.subtitles.length * 100); await new Promise(resolve => setTimeout(resolve, processingTime)); return { chunkId: chunk.id, processed: true, processingType: request.processingType, timestamp: new Date().toISOString(), modelType: this.config.modelType, contextSize: this.calculateChunkContextSize(chunk) }; } /** * Calculate optimal batch size */ private calculateOptimalBatchSize(chunks: SRTChunk[]): number { const maxContext = this.config.capabilities.maxContextSize; const avgChunkSize = chunks.reduce((sum, chunk) => sum + this.calculateChunkContextSize(chunk), 0) / chunks.length; return Math.max(1, Math.floor(maxContext / (avgChunkSize * 2))); } /** * Create batches from chunks */ private createBatches(chunks: SRTChunk[], batchSize: number): SRTChunk[][] { const batches: SRTChunk[][] = []; for (let i = 0; i < chunks.length; i += batchSize) { batches.push(chunks.slice(i, i + batchSize)); } return batches; } /** * Update processing todos */ private async updateProcessingTodos(stage: string, status: string): Promise<void> { if (this.todoManager) { await this.todoManager.updateProcessingProgress(stage as any, status as any); } } /** * Generate processing metadata */ private generateProcessingMetadata( chunks: SRTChunk[], results: any[], processingTime: number ): ProcessingMetadata { const totalSubtitles = chunks.reduce((sum, chunk) => sum + chunk.subtitles.length, 0); const totalDuration = this.calculateTotalDuration(chunks); const contextEfficiency = this.calculateContextEfficiency(chunks); return { totalChunks: chunks.length, processedChunks: results.length, totalSubtitles, totalDuration, contextEfficiency, modelCapabilities: this.config.capabilities, chunkingStrategy: this.config.chunkingOptions.enableSemanticAnalysis ? 'semantic' : 'basic', contextOptimization: this.config.contextOptimization.enabled, todoIntegration: this.config.todoIntegration.enabled }; } /** * Calculate total duration */ private calculateTotalDuration(chunks: SRTChunk[]): number { if (chunks.length === 0) return 0; const firstChunk = chunks[0]; const lastChunk = chunks[chunks.length - 1]; const startTime = firstChunk.subtitles[0].startTime; const endTime = lastChunk.subtitles[lastChunk.subtitles.length - 1].endTime; const startMs = (startTime.hours * 3600 + startTime.minutes * 60 + startTime.seconds) * 1000 + startTime.milliseconds; const endMs = (endTime.hours * 3600 + endTime.minutes * 60 + endTime.seconds) * 1000 + endTime.milliseconds; return endMs - startMs; } /** * Calculate context efficiency */ private calculateContextEfficiency(chunks: SRTChunk[]): number { const totalContextUsed = chunks.reduce((sum, chunk) => sum + this.calculateChunkContextSize(chunk), 0); const maxPossibleContext = chunks.length * this.config.capabilities.maxContextSize; return totalContextUsed / maxPossibleContext; } /** * Create model-specific integration */ private createModelIntegration(): any { switch (this.config.modelType) { case 'claude': return this.createClaudeIntegration(); case 'gpt': return this.createGPTIntegration(); case 'gemini': return this.createGeminiIntegration(); default: return this.createGenericIntegration(); } } /** * Create Claude integration */ private createClaudeIntegration(): any { const claudeConfig: ClaudeConfig = { modelType: 'claude', supportsTodoTool: true, maxContextSize: 200000, chunkSizeLimit: 15, processingStrategy: 'sequential', contextOptimization: true, claudeSpecific: { useAnthropicFormat: true, enableReasoning: true, maxTokensPerRequest: 4000, temperature: 0.7 } }; return new ClaudeSRTIntegration(claudeConfig, { modelConfig: claudeConfig, chunkingStrategy: 'conversation-aware', contextManagement: 'balanced', todoIntegration: true, progressTracking: true }); } /** * Create GPT integration */ private createGPTIntegration(): any { // GPT-specific integration would go here return { modelType: 'gpt', processChunk: async (chunk: SRTChunk) => { return { chunkId: chunk.id, processed: true, modelType: 'gpt' }; } }; } /** * Create Gemini integration */ private createGeminiIntegration(): any { // Gemini-specific integration would go here return { modelType: 'gemini', processChunk: async (chunk: SRTChunk) => { return { chunkId: chunk.id, processed: true, modelType: 'gemini' }; } }; } /** * Create generic integration */ private createGenericIntegration(): any { return { modelType: 'generic', processChunk: async (chunk: SRTChunk) => { return { chunkId: chunk.id, processed: true, modelType: 'generic' }; } }; } } /** * Unified AI Interface Factory */ export class UnifiedAIFactory { /** * Create Claude configuration */ static createClaudeConfig(): UnifiedAIConfig { return { modelType: 'claude', capabilities: { supportsTodoTool: true, maxContextSize: 200000, supportsFunctionCalling: true, supportsStreaming: true, supportsMultimodal: false, supportsReasoning: true, maxTokensPerRequest: 4000, temperature: 0.7, processingStrategy: 'sequential' }, chunkingOptions: { boundaryThreshold: 0.7, maxChunkSize: 15, minChunkSize: 2, enableSemanticAnalysis: true, enableSpeakerDiarization: true }, contextOptimization: { enabled: true, maxContextSize: 50000, chunkSizeLimit: 8, contextManagement: 'balanced' }, todoIntegration: { enabled: true, progressTracking: true, detailedLogging: true } }; } /** * Create GPT configuration */ static createGPTConfig(): UnifiedAIConfig { return { modelType: 'gpt', capabilities: { supportsTodoTool: true, maxContextSize: 128000, supportsFunctionCalling: true, supportsStreaming: true, supportsMultimodal: true, supportsReasoning: false, maxTokensPerRequest: 4000, temperature: 0.7, processingStrategy: 'parallel' }, chunkingOptions: { boundaryThreshold: 0.7, maxChunkSize: 20, minChunkSize: 2, enableSemanticAnalysis: true, enableSpeakerDiarization: true }, contextOptimization: { enabled: true, maxContextSize: 100000, chunkSizeLimit: 12, contextManagement: 'balanced' }, todoIntegration: { enabled: true, progressTracking: true, detailedLogging: true } }; } /** * Create Gemini configuration */ static createGeminiConfig(): UnifiedAIConfig { return { modelType: 'gemini', capabilities: { supportsTodoTool: true, maxContextSize: 1000000, supportsFunctionCalling: true, supportsStreaming: true, supportsMultimodal: true, supportsReasoning: true, maxTokensPerRequest: 8000, temperature: 0.7, processingStrategy: 'batch' }, chunkingOptions: { boundaryThreshold: 0.7, maxChunkSize: 25, minChunkSize: 2, enableSemanticAnalysis: true, enableSpeakerDiarization: true }, contextOptimization: { enabled: true, maxContextSize: 200000, chunkSizeLimit: 20, contextManagement: 'conservative' }, todoIntegration: { enabled: true, progressTracking: true, detailedLogging: true } }; } /** * Create generic configuration */ static createGenericConfig(): UnifiedAIConfig { return { modelType: 'generic', capabilities: { supportsTodoTool: false, maxContextSize: 50000, supportsFunctionCalling: false, supportsStreaming: false, supportsMultimodal: false, supportsReasoning: false, maxTokensPerRequest: 2000, temperature: 0.7, processingStrategy: 'sequential' }, chunkingOptions: { boundaryThreshold: 0.7, maxChunkSize: 10, minChunkSize: 2, enableSemanticAnalysis: true, enableSpeakerDiarization: true }, contextOptimization: { enabled: true, maxContextSize: 30000, chunkSizeLimit: 5, contextManagement: 'aggressive' }, todoIntegration: { enabled: false, progressTracking: false, detailedLogging: false } }; } /** * Create unified AI interface */ static createUnifiedAI(modelType: SupportedAIModel): UnifiedAIInterface { let config: UnifiedAIConfig; switch (modelType) { case 'claude': config = this.createClaudeConfig(); break; case 'gpt': config = this.createGPTConfig(); break; case 'gemini': config = this.createGeminiConfig(); break; default: config = this.createGenericConfig(); } return new UnifiedAIInterface(config); } }

MCP directory API

We provide all the information about MCP servers via our MCP API.

curl -X GET 'https://glama.ai/api/mcp/v1/servers/omd0/srt-mcp'

If you have feedback or need assistance with the MCP directory API, please join our Discord server