Recently Written · git

subread-extension

Show your own .srt over a YouTube video, in time with it. Chrome and Firefox.

git clone https://github.com/equwal/subread-extension

Log | Files | Refs


src/srt.js (2326 bytes)

1 /* Subtitles as data: read an .srt (or .vtt), and find the cue for a moment of
2  * the video. No browser APIs here, so Node can test it.
3  *
4  * The video on YouTube is often not the same file as the audio the subtitles
5  * were made from: an intro was added, or the speed differs a little. So the
6  * time of the video is first moved onto the clock of the subtitles:
7  *
8  *     subtitle time = (video time - offset) * rate
9  */
10 (function (root) {
11   'use strict';
12 
13   const STAMP = /(?:(\d+):)?(\d{1,2}):(\d{2})[.,](\d{1,3})/;
14 
15   function seconds(stamp) {
16     const m = STAMP.exec(stamp);
17     if (!m) return NaN;
18     return (+(m[1] || 0)) * 3600 + (+m[2]) * 60 + (+m[3]) + (+m[4].padEnd(3, '0')) / 1000;
19   }
20 
21   /** Cues in order of start: [{ start, end, text }]. Blocks that are not cues are passed over. */
22   function parse(source) {
23     const cues = [];
24     const blocks = String(source).replace(/^/, '').replace(/\r\n?/g, '\n').split(/\n{2,}/);
25     for (const block of blocks) {
26       const lines = block.split('\n');
27       const at = lines.findIndex((l) => l.includes('-->'));
28       if (at < 0) continue;
29       const [from, to] = lines[at].split('-->');
30       const start = seconds(from), end = seconds(to);
31       const text = lines.slice(at + 1).join('\n').replace(/<[^>]*>/g, '').trim();
32       if (Number.isFinite(start) && Number.isFinite(end) && end > start && text) cues.push({ start, end, text });
33     }
34     return cues.sort((a, b) => a.start - b.start);
35   }
36 
37   /** The cue that is on at `t` (seconds on the clock of the subtitles), or null. */
38   function cueAt(cues, t) {
39     let lo = 0, hi = cues.length - 1, found = -1;
40     while (lo <= hi) {                       // the last cue that starts at or before t
41       const mid = (lo + hi) >> 1;
42       if (cues[mid].start <= t) { found = mid; lo = mid + 1; } else hi = mid - 1;
43     }
44     // Cues can overlap a little; the latest one that is still on wins.
45     for (let i = found; i >= 0 && i > found - 4; i--) if (t < cues[i].end) return cues[i];
46     return null;
47   }
48 
49   const subtitleTime = (videoTime, offset, rate) => (videoTime - offset) * rate;
50 
51   const api = { parse, cueAt, subtitleTime, seconds };
52   if (typeof module !== 'undefined' && module.exports) module.exports = api;
53   else root.SubReadSrt = api;
54 })(typeof globalThis !== 'undefined' ? globalThis : this);