const SubtitleManager = { subtitles: [], addSubtitle: function(start, end, text) { this.subtitles.push({ start: start, end: end, text: text }); this.sortSubtitles(); this.renderSubtitleList(); this.updateParentEditor(); }, loadSubtitles: function(content) { this.subtitles = []; // Clear existing subtitles if (!content || typeof content !== 'string') { console.error("Invalid subtitle content:", content); return; } console.log("Loading subtitles from content:", content); try { // Parse VTT format if (content.includes('WEBVTT')) { console.log("Detected VTT format"); this.parseVTT(content); } else { // Assume SRT format console.log("Assuming SRT format"); this.parseSRT(content); } console.log("Parsed subtitles:", this.subtitles); if (this.subtitles.length === 0) { console.warn("No subtitles were parsed from the content"); } this.renderSubtitleList(); TimelineManager.render(); // Update timeline after loading subtitles } catch (error) { console.error("Error parsing subtitles:", error); } }, parseVTT: function(content) { this.subtitles = []; const lines = content.split('\n'); let current = null; for (let line of lines) { line = line.trim(); if (line.includes('-->')) { const [start, end] = line.split('-->').map(this.timeToSeconds); current = { start, end, text: '' }; } else if (current && line && !line.includes('WEBVTT')) { current.text += line + '\n'; } else if (line === '' && current) { current.text = current.text.trim(); this.subtitles.push(current); current = null; } } }, parseSRT: function(content) { console.log("Starting SRT parsing"); this.subtitles = []; // Normalize line endings and split into blocks const normalizedContent = content.replace(/\r\n/g, '\n').replace(/\r/g, '\n'); const blocks = normalizedContent.split('\n\n').filter(block => block.trim()); console.log(`Found ${blocks.length} subtitle blocks`); for (let i = 0; i < blocks.length; i++) { try { const lines = blocks[i].split('\n').map(line => line.trim()).filter(line => line); console.log(`Block ${i + 1} has ${lines.length} lines:`, lines); // Skip invalid blocks if (lines.length < 2) { console.warn(`Block ${i + 1} has insufficient lines, skipping`); continue; } // Find time line const timeLine = lines.find(line => line.includes('-->')); if (!timeLine) { console.warn(`Block ${i + 1} has no time line, skipping`); continue; } // Parse time values const [startStr, endStr] = timeLine.split('-->').map(t => t.trim()); console.log(`Time values - Start: "${startStr}", End: "${endStr}"`); const start = this.timeToSeconds(startStr); const end = this.timeToSeconds(endStr); if (isNaN(start) || isNaN(end)) { console.warn(`Block ${i + 1} has invalid time values:`, { start, end }); continue; } console.log(`Converted times - Start: ${start}s, End: ${end}s`); // Get text lines (exclude index number and time line) const textLines = lines.filter(line => !line.includes('-->') && !line.match(/^\d+$/) && line.trim().length > 0 ); const text = textLines.join('\n').trim(); console.log(`Extracted text: "${text}"`); if (text) { this.subtitles.push({ start, end, text }); console.log(`Successfully added subtitle ${i + 1}`); } else { console.warn(`Block ${i + 1} has no text content, skipping`); } } catch (error) { console.error(`Error parsing block ${i + 1}:`, error); } } this.subtitles.sort((a, b) => a.start - b.start); console.log(`Successfully parsed ${this.subtitles.length} subtitles`); }, timeToSeconds: function(timeStr) { try { timeStr = timeStr.trim().replace(',', '.'); // Convert SRT format to decimal console.log(`Processing time string: "${timeStr}"`); // Handle missing milliseconds if (!timeStr.includes('.')) { timeStr += '.000'; } const [time, ms] = timeStr.split('.'); const [hours, minutes, seconds] = time.split(':').map(str => { const num = Number(str); if (isNaN(num)) { throw new Error(`Invalid time component: "${str}"`); } return num; }); const msSeconds = ms ? Number('0.' + ms) : 0; if (isNaN(msSeconds)) { throw new Error(`Invalid milliseconds: "${ms}"`); } const totalSeconds = hours * 3600 + minutes * 60 + seconds + msSeconds; console.log(`Converted "${timeStr}" to ${totalSeconds} seconds`); return totalSeconds; } catch (error) { console.error(`Error converting time "${timeStr}":`, error); return NaN; } }, secondsToTime: function(seconds) { const date = new Date(seconds * 1000); const hh = date.getUTCHours(); const mm = date.getUTCMinutes(); const ss = date.getUTCSeconds(); const ms = date.getUTCMilliseconds(); return `${hh.toString().padStart(2, '0')}:${mm.toString().padStart(2, '0')}:${ss.toString().padStart(2, '0')}.${ms.toString().padStart(3, '0')}`; }, sortSubtitles: function() { this.subtitles.sort((a, b) => a.start - b.start); }, onTimelineUpdate: null, setTimelineUpdateCallback: function(callback) { this.onTimelineUpdate = callback; }, renderSubtitleList: function() { const listElement = document.getElementById('subtitle-list'); if (!listElement) return; listElement.innerHTML = ''; this.sortSubtitles(); this.subtitles.forEach((subtitle, index) => { const subtitleElement = document.createElement('div'); subtitleElement.className = 'subtitle-item'; subtitleElement.innerHTML = ` <div class="d-flex justify-content-between align-items-center"> <small class="text-muted"> ${this.secondsToTime(subtitle.start)} - ${this.secondsToTime(subtitle.end)} </small> <div> <button class="btn btn-sm btn-primary edit-subtitle" data-index="${index}"> <i class="fas fa-edit"></i> </button> <button class="btn btn-sm btn-danger delete-subtitle" data-index="${index}"> <i class="fas fa-trash"></i> </button> </div> </div> <div class="mt-1">${subtitle.text}</div> `; // Add click handlers const editBtn = subtitleElement.querySelector('.edit-subtitle'); const deleteBtn = subtitleElement.querySelector('.delete-subtitle'); editBtn.addEventListener('click', () => { const modal = new bootstrap.Modal(document.getElementById('editSubtitleModal')); const textArea = document.getElementById('subtitleText'); textArea.value = subtitle.text; const saveHandler = () => { subtitle.text = textArea.value; this.renderSubtitleList(); this.updateParentEditor(); modal.hide(); document.getElementById('saveSubtitle').removeEventListener('click', saveHandler); }; document.getElementById('saveSubtitle').addEventListener('click', saveHandler); modal.show(); }); deleteBtn.addEventListener('click', () => { if (confirm('Are you sure you want to delete this subtitle?')) { this.subtitles.splice(index, 1); this.renderSubtitleList(); TimelineManager.render(); this.updateParentEditor(); } }); listElement.appendChild(subtitleElement); }); TimelineManager.render(); }, updateParentEditor: function() { const srt = this.exportSRT(); // Aktualizace editoru v rodičovském okně window.parent.postMessage({ type: 'updateSubtitles', content: srt }, '*'); // Získání video_path ze sessionStorage a úprava cesty const video_path = window.parent.sessionStorage.getItem('video_path'); if (!video_path) { console.error('video_path není k dispozici v sessionStorage'); return; } // Odstranění '\video.mp4' a 'exports\' z cesty const project_path = video_path.replace('\\video.mp4', '').replace('exports\\', ''); console.log('Ukládám titulky do projektu:', project_path); // Okamžité uložení do souboru fetch('/save_srt', { method: 'POST', headers: { 'Content-Type': 'application/json' }, body: JSON.stringify({ srt_content: srt, project_name: project_path }) }) .then(response => response.json()) .then(data => { if (!data.success) { console.error('Chyba při ukládání titulků:', data.error); } else { console.log('Titulky byly úspěšně uloženy do:', project_path + '/titulky.srt'); } }) .catch(error => { console.error('Chyba při komunikaci se serverem:', error); }); }, exportVTT: function() { let output = 'WEBVTT\n\n'; this.subtitles.forEach((subtitle, index) => { output += `${index + 1}\n`; output += `${this.secondsToTime(subtitle.start)} --> ${this.secondsToTime(subtitle.end)}\n`; output += `${subtitle.text}\n\n`; }); return output; }, exportSRT: function() { let output = ''; this.subtitles.forEach((subtitle, index) => { output += `${index + 1}\n`; output += `${this.secondsToTime(subtitle.start).replace('.', ',')} --> ${this.secondsToTime(subtitle.end).replace('.', ',')}\n`; output += `${subtitle.text}\n\n`; }); return output; }, getSubtitles: function() { return this.subtitles; } };