class CaptionCapture { constructor() { this.isCapturing = false; // Flag to control capturing this.intervalId = null; // Store interval reference this.lastSpans = []; // Store last seen spans this.captionsLog = []; // Stores captions with timestamps this.mergedCpation = []; this.subtitleText= null; this.checkSubtitleInterval=100; } // Function to detect and get the caption spans as an array detectCaptionAsArray() { let spans = document.querySelectorAll('div[jsname="tgaKEf"]'); return Array.from(spans).map(span => span.innerText.trim()) || ""; } // Function to get absolute time in HH:MM:SS,mmm format (Real-Time) getCurrentTimestamp() { let now = new Date(); let hours = String(now.getHours()).padStart(2, '0'); let minutes = String(now.getMinutes()).padStart(2, '0'); let seconds = String(now.getSeconds()).padStart(2, '0'); let millis = String(now.getMilliseconds()).padStart(3, '0'); return `${hours}:${minutes}:${seconds},${millis}`; // Absolute time format } // Function to save new words with timestamp saveCaptionByWords(timestamp, newText) { this.captionsLog.push({ time: timestamp, text: newText }); console.log(`${timestamp} --> ${newText}`); // Print in subtitle format } formatTimestamp(ms) { const h = String(Math.floor(ms / 3600000)).padStart(2, "0"); ms %= 3600000; const m = String(Math.floor(ms / 60000)).padStart(2, "0"); ms %= 60000; const s = String(Math.floor(ms / 1000)).padStart(2, "0"); const msStr = String(ms % 1000).padStart(3, "0"); return `${h}:${m}:${s},${msStr}`; } mergeCaptions(captions, gap = 1000) { if (!captions || captions.length === 0) return []; const parseTime = (timestamp) => { const [h, m, sMs] = timestamp.split(":"); const [s, ms] = sMs.split(","); return ( parseInt(h) * 3600000 + parseInt(m) * 60000 + parseInt(s) * 1000 + parseInt(ms) ); }; const merged = []; let current = { start: parseTime(captions[0].time), end: parseTime(captions[0].time), texts: [captions[0].text] }; for (let i = 1; i < captions.length; i++) { const entry = captions[i]; const time = parseTime(entry.time); const timeDiff = time - current.end; if (timeDiff > gap) { merged.push({ start: this.formatTimestamp(current.start), end: this.formatTimestamp(current.end), text: current.texts.join(" ") }); current = { start: time, end: time, texts: [entry.text] }; } else { current.end = time; current.texts.push(entry.text); } } // Push the last group merged.push({ start: this.formatTimestamp(current.start), end: this.formatTimestamp(current.end), text: current.texts.join(" ") }); this.mergedCpation=merged; return merged; } printMergedSubtitle() { const merged = this.mergeCaptions(this.getCapturedCaptions(), 1000); if (merged.length === 0) { console.log("No subtitles to print."); return; } const parseTime = (timestamp) => { const [h, m, sMs] = timestamp.split(":"); const [s, ms] = sMs.split(","); return ( parseInt(h) * 3600000 + parseInt(m) * 60000 + parseInt(s) * 1000 + parseInt(ms) ); }; const baseTime = parseTime(merged[0].start); // time of first word const toRelative = (absTime) => { const ms = parseTime(absTime) - baseTime; return this.formatTimestamp(ms); }; this.subtitleText = merged.map((item, index) => `${index + 1}\n${toRelative(item.start)} --> ${toRelative(item.end)}\n${item.text}\n` ).join("\n"); console.log(this.subtitleText); return this.subtitleText; } generateSrtFilename() { const now = new Date(); const pad = (n) => String(n).padStart(2, '0'); return `caption_${now.getFullYear()}${pad(now.getMonth() + 1)}${pad(now.getDate())}_${pad(now.getHours())}${pad(now.getMinutes())}.srt`; } downloadSrtFile() { if (!this.subtitleText) { console.warn("No subtitle content found. Please call printMergedSubtitle() first."); return; } const filename = this.generateSrtFilename(); const blob = new Blob([this.subtitleText], { type: "text/plain" }); const url = URL.createObjectURL(blob); const a = document.createElement("a"); a.href = url; a.download = filename; a.click(); URL.revokeObjectURL(url); } // Function to start capturing captions startCapturing() { if (this.isCapturing) { console.log("Caption capturing is already running."); return; } this.isCapturing = true; console.log("Caption capturing started."); this.intervalId = setInterval(() => { let currentSpans = this.detectCaptionAsArray(); let lastOldSpan = this.lastSpans[this.lastSpans.length - 1] || ""; let lastNewSpan = currentSpans[currentSpans.length - 1] || ""; let isLastCaptionChanged = lastNewSpan !== lastOldSpan; if (isLastCaptionChanged) { this.lastSpans = currentSpans; // Update stored spans let timestamp = this.getCurrentTimestamp(); // Get only the new part of the last span let newText = lastNewSpan.replace(lastOldSpan, "").trim(); if (newText) { console.log(newText) const wordCount = newText.trim().split(/\s+/).length; const BurstTextCount=80; if (wordCount <= (this.checkSubtitleInterval/100)*BurstTextCount) { this.saveCaptionByWords(timestamp, newText); } else { console.log(`Skipped burst text with ${wordCount} words at ${timestamp}`); } } } }, this.checkSubtitleInterval); // Runs every 100ms for better precision } // Function to stop capturing captions stopCapturing() { if (!this.isCapturing) { console.log("Caption capturing is not running."); return; } this.isCapturing = false; clearInterval(this.intervalId); console.log("Caption capturing stopped."); } // Function to get the saved captions log getCapturedCaptions() { return this.captionsLog; } } // Usage: const captionCapture = new CaptionCapture(); // Start capturing: captionCapture.startCapturing(); // Stop capturing: captionCapture.stopCapturing(); // Retrieve stored captions: console.log(captionCapture.getCapturedCaptions()); captionCapture.printMergedSubtitle(); captionCapture.downloadSrtFile();