document.addEventListener('DOMContentLoaded', () => { // --- DOM Elements --- const verifyBtn = document.getElementById('verifyButton'); const loader = document.getElementById('loader'); const progressBar = document.getElementById('progressBar'); const progressPercent = document.getElementById('progressPercent'); const loaderText = document.getElementById('loaderText'); const resultsWrapper = document.getElementById('resultsWrapper'); const resultsArea = document.getElementById('results'); const correctedSrtOutput = document.getElementById('correctedSrtOutput'); const ocrErrorOutput = document.getElementById('ocrErrorOutput'); const errorMessage = document.getElementById('errorMessage'); // Stats const statChunks = document.getElementById('statChunks'); const statCorrections = document.getElementById('statCorrections'); const statErrors = document.getElementById('statErrors'); const statOcrErrors = document.getElementById('statOcrErrors'); // File Inputs const pdfUpload = document.getElementById('pdfUpload'); const srtUpload = document.getElementById('srtUpload'); // Global Store for Original SRT Content let originalSrtContent = ""; // --- Event Listeners --- if (pdfUpload) { pdfUpload.addEventListener('change', function() { if(this.files[0]) document.getElementById('pdfFileName').textContent = this.files[0].name; }); } if (srtUpload) { srtUpload.addEventListener('change', function() { if(this.files[0]) { document.getElementById('srtFileName').textContent = this.files[0].name; // Read content immediately for later patching const reader = new FileReader(); reader.onload = (e) => { originalSrtContent = e.target.result; }; reader.readAsText(this.files[0]); } }); } if (verifyBtn) { verifyBtn.addEventListener('click', async () => { const apiKeys = document.getElementById('apiKeyInput').value.trim(); const pdfFile = pdfUpload ? pdfUpload.files[0] : null; const srtFile = srtUpload ? srtUpload.files[0] : null; const pagesPerRequest = document.getElementById('pagesPerRequestInput').value; const modelName = document.getElementById('modelSelect').value; if (errorMessage) errorMessage.classList.add('hidden'); if (!apiKeys) return showError("Please enter API Keys."); if (!pdfFile) return showError("Please upload a PDF."); if (!srtFile) return showError("Please upload an SRT."); // Reset UI verifyBtn.disabled = true; verifyBtn.classList.add('opacity-50', 'cursor-not-allowed'); if (loader) loader.classList.remove('hidden'); if (resultsWrapper) resultsWrapper.classList.add('hidden'); const formData = new FormData(); formData.append('api_keys', apiKeys); formData.append('pdf', pdfFile); formData.append('srt', srtFile); formData.append('pages_per_request', pagesPerRequest); formData.append('model_name', modelName); // Fake Progress let progress = 0; const interval = setInterval(() => { if (progress < 90) { progress += Math.random() * 5; if (progress > 90) progress = 90; if (progressBar) progressBar.style.width = `${progress}%`; if (progressPercent) progressPercent.textContent = `${Math.round(progress)}%`; } }, 800); try { const response = await fetch('/verify_batch', { method: 'POST', body: formData }); clearInterval(interval); const data = await response.json(); if (!response.ok) throw new Error(data.detail || data.error || "Server Error"); if (progressBar) progressBar.style.width = '100%'; if (progressPercent) progressPercent.textContent = '100%'; setTimeout(() => { if (loader) loader.classList.add('hidden'); displayResults(data); verifyBtn.disabled = false; verifyBtn.classList.remove('opacity-50', 'cursor-not-allowed'); }, 500); } catch (error) { clearInterval(interval); if (loader) loader.classList.add('hidden'); verifyBtn.disabled = false; verifyBtn.classList.remove('opacity-50', 'cursor-not-allowed'); showError(error.message); } }); } // --- Helper Functions --- function showError(msg) { if (errorMessage) { errorMessage.textContent = `Error: ${msg}`; errorMessage.classList.remove('hidden'); } else { alert(msg); } } function displayResults(data) { if (resultsWrapper) resultsWrapper.classList.remove('hidden'); // 1. Stats if (statChunks) statChunks.textContent = data.total_chunks || 0; if (statErrors) statErrors.textContent = data.system_errors ? data.system_errors.length : 0; const corrections = data.corrections || []; let validCorrections = []; let ocrReports = ""; // 2. Filter Data corrections.forEach((item, index) => { // Only count if it has a correctedSrt field if (item.correctedSrt) { validCorrections.push(item); } // Collect significant error reports if (item.errorReport && !item.errorReport.includes("No significant errors")) { ocrReports += `[Report ${index+1}]\n${item.errorReport}\n\n`; } }); if (statCorrections) statCorrections.textContent = validCorrections.length; if (statOcrErrors) statOcrErrors.textContent = ocrReports ? "!" : "0"; // 3. Fill Textareas if (resultsArea) resultsArea.value = JSON.stringify(data, null, 2); if (ocrErrorOutput) ocrErrorOutput.value = ocrReports || "No significant OCR errors reported."; if (correctedSrtOutput) correctedSrtOutput.value = JSON.stringify(validCorrections, null, 2); } // --- ROBUST SRT DOWNLOAD LOGIC --- const downloadSrtBtn = document.getElementById('downloadSrtBtn'); if (downloadSrtBtn) { downloadSrtBtn.addEventListener('click', () => { if (!originalSrtContent) { alert("Original SRT content is missing. Please re-upload the SRT file to enable patching."); return; } // Parse results from the text area let corrections = []; try { // We use the filtered list we displayed in correctedSrtOutput corrections = JSON.parse(correctedSrtOutput.value); } catch (e) { alert("No valid corrections data found to apply."); return; } if (!corrections || corrections.length === 0) { alert("There are no corrections to apply."); return; } try { const patchedSrt = patchSrtFile(originalSrtContent, corrections); downloadFile(patchedSrt, 'corrected_subtitles.srt'); } catch (e) { alert("Error applying patches: " + e.message); } }); } /** * Replaces blocks in the original SRT with the corrected blocks. */ function patchSrtFile(originalText, corrections) { // 1. Split original SRT into blocks (Double newline separator) // Normalize line endings to \n first const normalizedText = originalText.replace(/\r\n/g, '\n').replace(/\r/g, '\n'); let blocks = normalizedText.split(/\n\n+/); // 2. Create a Map for faster updates // We map "ID" -> Index in the blocks array let blockMap = new Map(); blocks.forEach((block, index) => { const lines = block.trim().split('\n'); if (lines.length > 0) { const id = lines[0].trim(); // Store the index so we can update the array directly blockMap.set(id, index); } }); // 3. Apply Corrections let appliedCount = 0; corrections.forEach(fix => { // Extract ID from the CORRECTION block (it's usually the first line) // Example fix.correctedSrt: "10\n00:00:10 --> ... \nHello" if (!fix.correctedSrt) return; const fixLines = fix.correctedSrt.trim().split('\n'); const fixId = fixLines[0].trim(); if (blockMap.has(fixId)) { const originalIndex = blockMap.get(fixId); // REPLACE the entire original block with the new corrected block blocks[originalIndex] = fix.correctedSrt.trim(); appliedCount++; } }); console.log(`Applied ${appliedCount} corrections.`); return blocks.join('\n\n'); } function downloadFile(content, filename) { const blob = new Blob([content], { type: 'text/plain' }); const url = URL.createObjectURL(blob); const a = document.createElement('a'); a.href = url; a.download = filename; document.body.appendChild(a); a.click(); document.body.removeChild(a); } });