| document.addEventListener('DOMContentLoaded', () => { |
| |
| const dropZone = document.getElementById('drop-zone'); |
| const fileInput = document.getElementById('file-input'); |
| const filePreview = document.getElementById('file-preview'); |
| const fileName = document.getElementById('file-name'); |
| const fileSize = document.getElementById('file-size'); |
| const removeFileBtn = document.getElementById('remove-file-btn'); |
| const transcribeBtn = document.getElementById('transcribe-btn'); |
| |
| const uploadSection = document.getElementById('upload-section'); |
| const processingSection = document.getElementById('processing-section'); |
| const resultSection = document.getElementById('result-section'); |
| |
| const detectedLang = document.getElementById('detected-lang'); |
| const captionsList = document.getElementById('captions-list'); |
| const fullTextContent = document.getElementById('full-text-content'); |
| |
| const copyTextBtn = document.getElementById('copy-text-btn'); |
| const downloadTxtBtn = document.getElementById('download-txt-btn'); |
| const downloadSrtBtn = document.getElementById('download-srt-btn'); |
| const newTranscriptionBtn = document.getElementById('new-transcription-btn'); |
| |
| const tabBtns = document.querySelectorAll('.tab-btn'); |
| const tabContents = document.querySelectorAll('.tab-content'); |
| |
| const toast = document.getElementById('toast'); |
| const toastMessage = document.getElementById('toast-message'); |
|
|
| |
| let selectedFile = null; |
| let transcriptionResult = null; |
|
|
| |
| |
| |
| dropZone.addEventListener('click', (e) => { |
| if (e.target !== removeFileBtn && !removeFileBtn.contains(e.target)) { |
| fileInput.click(); |
| } |
| }); |
|
|
| fileInput.addEventListener('change', (e) => { |
| if (e.target.files.length > 0) { |
| handleFile(e.target.files[0]); |
| } |
| }); |
|
|
| |
| dropZone.addEventListener('dragover', (e) => { |
| e.preventDefault(); |
| dropZone.classList.add('dragover'); |
| }); |
|
|
| dropZone.addEventListener('dragleave', () => { |
| dropZone.classList.remove('dragover'); |
| }); |
|
|
| dropZone.addEventListener('drop', (e) => { |
| e.preventDefault(); |
| dropZone.classList.remove('dragover'); |
| if (e.dataTransfer.files.length > 0) { |
| handleFile(e.dataTransfer.files[0]); |
| } |
| }); |
|
|
| function handleFile(file) { |
| |
| const validTypes = ['video/mp4', 'video/quicktime', 'video/x-msvideo', 'video/webm', 'video/x-matroska']; |
| if (!validTypes.includes(file.type) && !file.name.match(/\.(mp4|mov|avi|webm|mkv)$/i)) { |
| showToast('Invalid file format. Please upload a video.', true); |
| return; |
| } |
|
|
| |
| const maxSize = 50 * 1024 * 1024; |
| if (file.size > maxSize) { |
| showToast('File is too large. Max size is 50MB.', true); |
| return; |
| } |
|
|
| selectedFile = file; |
| fileName.textContent = file.name; |
| fileSize.textContent = formatBytes(file.size); |
| |
| dropZone.style.display = 'none'; |
| filePreview.classList.remove('hidden'); |
| transcribeBtn.classList.remove('hidden'); |
| } |
|
|
| removeFileBtn.addEventListener('click', (e) => { |
| e.stopPropagation(); |
| selectedFile = null; |
| fileInput.value = ''; |
| dropZone.style.display = 'block'; |
| filePreview.classList.add('hidden'); |
| transcribeBtn.classList.add('hidden'); |
| }); |
|
|
| |
|
|
| transcribeBtn.addEventListener('click', async () => { |
| if (!selectedFile) return; |
|
|
| |
| uploadSection.classList.add('hidden'); |
| processingSection.classList.remove('hidden'); |
|
|
| const formData = new FormData(); |
| formData.append('file', selectedFile); |
| |
| const apiKeyInput = document.getElementById('api-key-input'); |
| if (apiKeyInput && apiKeyInput.value.trim() !== '') { |
| formData.append('api_key', apiKeyInput.value.trim()); |
| } |
|
|
| let apiUrl = '/api/transcribe'; |
| |
| if (window.location.protocol === 'file:' || window.location.port === '5500' || window.location.hostname === '127.0.0.1' && window.location.port !== '8000') { |
| apiUrl = 'http://127.0.0.1:8000/api/transcribe'; |
| } |
|
|
| try { |
| const response = await fetch(apiUrl, { |
| method: 'POST', |
| body: formData |
| }); |
|
|
| if (!response.ok) { |
| const errorData = await response.json().catch(() => ({})); |
| throw new Error(errorData.detail || `Server error: ${response.status}`); |
| } |
|
|
| const data = await response.json(); |
| transcriptionResult = data; |
| |
| renderResults(data); |
| |
| processingSection.classList.add('hidden'); |
| resultSection.classList.remove('hidden'); |
| showToast('Transcription successful!'); |
| |
| } catch (error) { |
| console.error('Transcription error:', error); |
| showToast(error.message || 'Transcription failed', true); |
| |
| |
| processingSection.classList.add('hidden'); |
| uploadSection.classList.remove('hidden'); |
| } |
| }); |
|
|
| |
|
|
| function renderResults(data) { |
| |
| let langName = 'Mixed / Unknown'; |
| if (data.segments && data.segments.length > 0) { |
| const lang = data.segments[0].lang || 'hi-en'; |
| if (lang === 'en') langName = 'English'; |
| else if (lang === 'hi') langName = 'Hindi'; |
| else if (lang.includes('hi') && lang.includes('en')) langName = 'Hinglish'; |
| else langName = lang.toUpperCase(); |
| } |
| detectedLang.textContent = `Detected: ${langName}`; |
|
|
| |
| fullTextContent.textContent = data.text; |
|
|
| |
| captionsList.innerHTML = ''; |
| if (data.segments && data.segments.length > 0) { |
| data.segments.forEach(seg => { |
| const item = document.createElement('div'); |
| item.className = 'caption-item'; |
| item.innerHTML = ` |
| <div class="caption-ts">${seg.ts}</div> |
| <div class="caption-text">${seg.text}</div> |
| `; |
| captionsList.appendChild(item); |
| }); |
| } else { |
| captionsList.innerHTML = '<div style="padding:1rem;text-align:center;color:var(--text-muted)">No timeline data available.</div>'; |
| } |
| } |
|
|
| |
| |
| tabBtns.forEach(btn => { |
| btn.addEventListener('click', () => { |
| |
| tabBtns.forEach(b => b.classList.remove('active')); |
| tabContents.forEach(c => c.classList.remove('active')); |
| |
| |
| btn.classList.add('active'); |
| document.getElementById(btn.dataset.target).classList.add('active'); |
| }); |
| }); |
|
|
| |
|
|
| copyTextBtn.addEventListener('click', () => { |
| if (!transcriptionResult || !transcriptionResult.text) return; |
| |
| navigator.clipboard.writeText(transcriptionResult.text).then(() => { |
| showToast('Text copied to clipboard'); |
| }).catch(err => { |
| console.error('Copy failed', err); |
| showToast('Failed to copy text', true); |
| }); |
| }); |
|
|
| downloadTxtBtn.addEventListener('click', () => { |
| if (!transcriptionResult || !transcriptionResult.text) return; |
| downloadFile(transcriptionResult.text, 'transcript.txt', 'text/plain'); |
| }); |
|
|
| downloadSrtBtn.addEventListener('click', () => { |
| if (!transcriptionResult || !transcriptionResult.srt) { |
| showToast('SRT data not available', true); |
| return; |
| } |
| downloadFile(transcriptionResult.srt, 'subtitles.srt', 'text/plain'); |
| }); |
|
|
| newTranscriptionBtn.addEventListener('click', () => { |
| resultSection.classList.add('hidden'); |
| uploadSection.classList.remove('hidden'); |
| |
| |
| removeFileBtn.click(); |
| transcriptionResult = null; |
| |
| |
| tabBtns[0].click(); |
| }); |
|
|
| function downloadFile(content, fileName, mimeType) { |
| const blob = new Blob([content], { type: mimeType }); |
| 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); |
| URL.revokeObjectURL(url); |
| } |
|
|
| |
|
|
| function formatBytes(bytes, decimals = 2) { |
| if (!+bytes) return '0 Bytes'; |
| const k = 1024; |
| const dm = decimals < 0 ? 0 : decimals; |
| const sizes = ['Bytes', 'KB', 'MB', 'GB']; |
| const i = Math.floor(Math.log(bytes) / Math.log(k)); |
| return `${parseFloat((bytes / Math.pow(k, i)).toFixed(dm))} ${sizes[i]}`; |
| } |
|
|
| let toastTimeout; |
| function showToast(message, isError = false) { |
| toastMessage.textContent = message; |
| |
| if (isError) { |
| toast.classList.add('error'); |
| toast.querySelector('i').className = 'fa-solid fa-circle-exclamation'; |
| } else { |
| toast.classList.remove('error'); |
| toast.querySelector('i').className = 'fa-solid fa-circle-check'; |
| } |
| |
| toast.classList.add('show'); |
| |
| clearTimeout(toastTimeout); |
| toastTimeout = setTimeout(() => { |
| toast.classList.remove('show'); |
| }, 3000); |
| } |
| }); |
|
|