/** * Video Subtitle OCR Application * Extracts hard-coded subtitles from videos using transformers.js OCR */ import { pipeline, env } from 'https://cdn.jsdelivr.net/npm/@huggingface/transformers@3.8.0'; // Configure transformers.js env.allowLocalModels = false; env.useBrowserCache = true; // ============================================ // STATE MANAGEMENT // ============================================ const state = { // Video videoFile: null, videoDuration: 0, videoWidth: 0, videoHeight: 0, // Subtitle Box subtitleBox: { x: 10, // percentage y: 75, // percentage width: 80, // percentage height: 15, // percentage aspectRatio: 5.33 }, isDragging: false, isResizing: false, resizeHandle: null, dragStart: { x: 0, y: 0, boxX: 0, boxY: 0 }, // Language selectedLanguage: 'eng', // Time Range startTime: 0, endTime: 0, // Processing ocrPipeline: null, isProcessing: false, processingCancelled: false, // Results subtitles: [], currentSubtitleIndex: -1 }; // Language configurations for OCR const LANGUAGE_CONFIGS = { eng: { name: 'English', code: 'en' }, jpn: { name: 'Japanese', code: 'ja' }, kor: { name: 'Korean', code: 'ko' }, chi_sim: { name: 'Chinese (Simplified)', code: 'zh' }, chi_tra: { name: 'Chinese (Traditional)', code: 'zh-Hant' }, tha: { name: 'Thai', code: 'th' } }; // ============================================ // DOM ELEMENTS // ============================================ const elements = { // Pages setupPage: document.getElementById('setup-page'), processingPage: document.getElementById('processing-page'), resultsPage: document.getElementById('results-page'), // Upload uploadZone: document.getElementById('upload-zone'), videoInput: document.getElementById('video-input'), // Preview previewSection: document.getElementById('preview-section'), videoPreview: document.getElementById('video-preview'), subtitleOverlay: document.getElementById('subtitle-overlay'), subtitleBox: document.getElementById('subtitle-box'), // Controls xPosition: document.getElementById('x-position'), yPosition: document.getElementById('y-position'), width: document.getElementById('width'), height: document.getElementById('height'), xValue: document.getElementById('x-value'), yValue: document.getElementById('y-value'), widthValue: document.getElementById('width-value'), heightValue: document.getElementById('height-value'), aspectLock: document.getElementById('aspect-lock'), resetBox: document.getElementById('reset-box'), autoDetect: document.getElementById('auto-detect'), // Language languageSection: document.getElementById('language-section'), langButtons: document.querySelectorAll('.lang-btn'), // Time timeSection: document.getElementById('time-section'), startHours: document.getElementById('start-hours'), startMinutes: document.getElementById('start-minutes'), startSeconds: document.getElementById('start-seconds'), startMs: document.getElementById('start-ms'), endHours: document.getElementById('end-hours'), endMinutes: document.getElementById('end-minutes'), endSeconds: document.getElementById('end-seconds'), endMs: document.getElementById('end-ms'), setStartCurrent: document.getElementById('set-start-current'), setEndCurrent: document.getElementById('set-end-current'), timeRangeSlider: document.getElementById('time-range-slider'), rangeFill: document.getElementById('range-fill'), startThumb: document.getElementById('start-thumb'), endThumb: document.getElementById('end-thumb'), sliderStartTime: document.getElementById('slider-start-time'), sliderEndTime: document.getElementById('slider-end-time'), // Process processSection: document.getElementById('process-section'), processBtn: document.getElementById('process-btn'), // Processing progressFill: document.getElementById('progress-fill'), progressText: document.getElementById('progress-text'), progressPercent: document.getElementById('progress-percent'), frames