Skip to main content
Glama

SRT Translation MCP Server

by omd0
process-srt-with-mcp.js10.1 kB
#!/usr/bin/env node /** * SRT Processor with MCP Integration * Uses MCP tools for conversation detection and timing alignment */ const fs = require('fs'); const path = require('path'); class MCPEnhancedSRTProcessor { constructor(inputFile, outputFile) { this.inputFile = inputFile; this.outputFile = outputFile; this.chunkSize = 50; // Smaller chunks for better MCP processing this.processedCount = 0; this.totalCount = 0; } async processFile() { try { console.log(`Starting MCP-enhanced processing of ${this.inputFile}...`); // Read the input file const content = fs.readFileSync(this.inputFile, 'utf8'); console.log(`File loaded: ${content.length} characters`); // Step 1: Detect conversation boundaries using MCP console.log('Step 1: Detecting conversation boundaries...'); const conversationBoundaries = await this.detectConversationBoundaries(content); console.log(`Found ${conversationBoundaries.length} conversation boundaries`); // Step 2: Parse SRT content const srtData = this.parseSRT(content); this.totalCount = srtData.length; console.log(`Found ${this.totalCount} subtitle entries`); // Step 3: Process in chunks with conversation context const processedEntries = []; for (let i = 0; i < srtData.length; i += this.chunkSize) { const chunk = srtData.slice(i, i + this.chunkSize); console.log(`Processing chunk ${Math.floor(i / this.chunkSize) + 1}/${Math.ceil(srtData.length / this.chunkSize)} (${chunk.length} entries)`); const processedChunk = await this.processChunkWithMCP(chunk, conversationBoundaries); processedEntries.push(...processedChunk); this.processedCount += chunk.length; console.log(`Progress: ${this.processedCount}/${this.totalCount} (${Math.round((this.processedCount / this.totalCount) * 100)}%)`); } // Step 4: Write the processed file const outputContent = this.writeSRT(processedEntries); fs.writeFileSync(this.outputFile, outputContent, 'utf8'); console.log(`Processing complete! Output saved to ${this.outputFile}`); console.log(`Processed ${processedEntries.length} subtitle entries`); } catch (error) { console.error('Error processing file:', error); throw error; } } async detectConversationBoundaries(content) { // This would integrate with MCP conversation detection tool // For now, we'll simulate the detection console.log('Simulating MCP conversation boundary detection...'); // Simulate conversation detection const boundaries = []; const lines = content.split('\n'); let currentConversation = null; for (let i = 0; i < lines.length; i++) { const line = lines[i]; // Detect conversation start if (this.isConversationStart(line)) { if (currentConversation) { currentConversation.end = i - 1; boundaries.push(currentConversation); } currentConversation = { start: i, type: 'conversation', speaker: this.extractSpeaker(line) }; } // Detect conversation end if (currentConversation && this.isConversationEnd(line)) { currentConversation.end = i; boundaries.push(currentConversation); currentConversation = null; } } // Close last conversation if exists if (currentConversation) { currentConversation.end = lines.length - 1; boundaries.push(currentConversation); } return boundaries; } isConversationStart(line) { // Detect conversation start patterns const patterns = [ /<b>Speaker \d+:<\/b>/i, /^[A-Z][^.!?]*\?/, /^[A-Z][^.!?]*!/, /^[A-Z][^.!?]*:/ ]; return patterns.some(pattern => pattern.test(line.trim())); } isConversationEnd(line) { // Detect conversation end patterns const patterns = [ /^[A-Z][^.!?]*\.$/, /^[A-Z][^.!?]*\?$/, /^[A-Z][^.!?]*!$/, /^\s*$/ ]; return patterns.some(pattern => pattern.test(line.trim())); } extractSpeaker(line) { const speakerMatch = line.match(/<b>Speaker (\d+):<\/b>/i); return speakerMatch ? `Speaker ${speakerMatch[1]}` : 'Unknown'; } parseSRT(content) { const entries = []; const blocks = content.split(/\n\s*\n/); for (const block of blocks) { if (block.trim()) { const lines = block.trim().split('\n'); if (lines.length >= 3) { const entry = { index: parseInt(lines[0]), timing: lines[1], text: lines.slice(2).join('\n') }; entries.push(entry); } } } return entries; } writeSRT(entries) { return entries.map(entry => `${entry.index}\n${entry.timing}\n${entry.text}\n` ).join('\n'); } async processChunkWithMCP(chunk, conversationBoundaries) { // This would integrate with actual MCP tools // For now, we'll simulate the processing with conversation context const processedChunk = []; for (const entry of chunk) { // Find conversation context for this entry const conversationContext = this.findConversationContext(entry.index, conversationBoundaries); // Process with conversation context const processedEntry = { ...entry, timing: this.alignTimingWithConversation(entry.timing, entry.text, conversationContext) }; processedChunk.push(processedEntry); } return processedChunk; } findConversationContext(entryIndex, boundaries) { // Find which conversation this entry belongs to for (const boundary of boundaries) { if (entryIndex >= boundary.start && entryIndex <= boundary.end) { return boundary; } } return null; } alignTimingWithConversation(timing, text, conversationContext) { if (!conversationContext) { return timing; } // Advanced timing alignment based on conversation context const [start, end] = timing.split(' --> '); const startTime = this.parseTime(start); const endTime = this.parseTime(end); // Calculate conversation flow adjustments const adjustments = this.calculateConversationAdjustments(text, conversationContext); // Apply adjustments const adjustedStart = startTime + adjustments.startOffset; const adjustedEnd = endTime + adjustments.endOffset; return `${this.formatTime(adjustedStart)} --> ${this.formatTime(adjustedEnd)}`; } calculateConversationAdjustments(text, conversationContext) { // Calculate timing adjustments based on conversation flow const adjustments = { startOffset: 0, endOffset: 0 }; // Add pause for questions if (text.includes('?')) { adjustments.endOffset += 300; // 300ms pause for questions } // Add pause for exclamations if (text.includes('!')) { adjustments.endOffset += 200; // 200ms pause for exclamations } // Add pause for speaker changes if (text.includes('<b>Speaker')) { adjustments.startOffset += 100; // 100ms pause before new speaker } // Add pause for conversation endings if (text.includes('.')) { adjustments.endOffset += 150; // 150ms pause for statements } return adjustments; } parseTime(timeStr) { const [time, ms] = timeStr.split(','); const [hours, minutes, seconds] = time.split(':').map(Number); return (hours * 3600 + minutes * 60 + seconds) * 1000 + parseInt(ms); } formatTime(milliseconds) { const totalSeconds = Math.floor(milliseconds / 1000); const ms = milliseconds % 1000; const hours = Math.floor(totalSeconds / 3600); const minutes = Math.floor((totalSeconds % 3600) / 60); const seconds = totalSeconds % 60; return `${hours.toString().padStart(2, '0')}:${minutes.toString().padStart(2, '0')}:${seconds.toString().padStart(2, '0')},${ms.toString().padStart(3, '0')}`; } } // Main execution async function main() { const args = process.argv.slice(2); if (args.length < 2) { console.log('Usage: node process-srt-with-mcp.js <input-file> <output-file>'); console.log('Example: node process-srt-with-mcp.js Arabic_Rephrased_Full.srt Arabic_Rephrased_Full_Aligned.srt'); process.exit(1); } const [inputFile, outputFile] = args; if (!fs.existsSync(inputFile)) { console.error(`Input file not found: ${inputFile}`); process.exit(1); } const processor = new MCPEnhancedSRTProcessor(inputFile, outputFile); await processor.processFile(); } if (require.main === module) { main().catch(console.error); } module.exports = MCPEnhancedSRTProcessor;

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