process-srt-with-mcp.js•10.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;