Skip to content

Commit 118c73c

Browse files
committed
Resolves #5
1 parent 2a8965c commit 118c73c

File tree

2 files changed

+238
-2
lines changed

2 files changed

+238
-2
lines changed

dist/source.es.js

Lines changed: 234 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,234 @@
1+
/**
2+
* A valid VTT cue
3+
*
4+
* The properties of this class are compatible with `VTTCue`
5+
* and can be used to create a `new VTTCue` instance
6+
*
7+
* *Example*
8+
* ```js
9+
* new VTTCue(cue.startTime, cue.endTime, cue.text);
10+
* ```
11+
*
12+
* @property {number} number
13+
* @property {number} startTime
14+
* @property {number} endTime
15+
* @property {string} text
16+
*/
17+
class Cue {
18+
/**
19+
* @readonly
20+
* @type {number}
21+
*/
22+
number;
23+
/**
24+
* @readonly
25+
* @type {number}
26+
*/
27+
startTime;
28+
/**
29+
* @readonly
30+
* @type {number}
31+
*/
32+
endTime;
33+
/**
34+
* @readonly
35+
* @type {string}
36+
*/
37+
text;
38+
/**
39+
*
40+
* @param {number} number
41+
* @param {number} startTime
42+
* @param {number} endTime
43+
* @param {string} text
44+
*/
45+
constructor(number, startTime, endTime, text) {
46+
this.number = number;
47+
this.startTime = startTime;
48+
this.endTime = endTime;
49+
this.text = text;
50+
}
51+
}
52+
53+
/**
54+
* Converts an SRT cue into a VTT compatible cue
55+
*
56+
* For example
57+
*
58+
* ```txt
59+
* 1
60+
* 00:00:00,498 --> 00:00:02,827
61+
* Here's what I love most
62+
* about food and diet.
63+
* ```
64+
*
65+
* Will be converted into
66+
*
67+
* ```js
68+
* Cue {
69+
* number: 1,
70+
* startTime: 0.498
71+
* endTime: 2.827,
72+
* text: 'Here\'s what I love most\n about food and diet.'
73+
* }
74+
* ```
75+
*
76+
* @param {string} srtCue
77+
* @returns Cue
78+
*/
79+
function toVttCue(srtCue) {
80+
const convertedCue = {
81+
number: parseInt(srtCue.match(/^\d+/g)[0]),
82+
timing: {
83+
start: srtCue.match(/(\d+:){2}\d+,\d+/g)[0].replace(',', '.'),
84+
end: srtCue.match(/(\d+:){2}\d+,\d+/g)[1].replace(',', '.')
85+
},
86+
text: srtCue.split(/\r?\n/g).slice(2, srtCue.split(/\r?\n/g).length).join('\n')
87+
};
88+
return new Cue(convertedCue.number, hmsToSeconds(convertedCue.timing.start), hmsToSeconds(convertedCue.timing.end), convertedCue.text);
89+
}
90+
/**
91+
* Converts a VTT or SRT timing `string`
92+
* to a `number` in seconds + milliseconds
93+
*
94+
* *Example*
95+
* ```js
96+
* const seconds = hmsToSeconds('00:00:02.827'); // 2.827
97+
* ```
98+
*
99+
* @param {string} str
100+
* @returns number
101+
*/
102+
function hmsToSeconds(str) {
103+
let p = str.split(':'),
104+
s = 0,
105+
m = 1;
106+
while (p.length > 0) {
107+
s += m * parseFloat(p.pop(), 10);
108+
m *= 60;
109+
}
110+
return s;
111+
}
112+
/**
113+
* Fetches the contents of a track source
114+
*
115+
* *Example*
116+
* ```js
117+
* const content = await fetchTrack('https://example.com/path/my-subtitle.srt')
118+
* ```
119+
* @param {string} src - url
120+
* @param {string} encoding - file encoding format
121+
* @returns Promise<string>
122+
*/
123+
async function fetchTrack(src, encoding = 'utf-8') {
124+
return fetch(src).then(r => r.arrayBuffer()).then(r => new TextDecoder(encoding).decode(r));
125+
}
126+
127+
/**
128+
* @property {string} src
129+
* @property {string} encoding
130+
* @property {string} lang
131+
* @property {string} kind
132+
* @property {string} label
133+
* @property {string} default
134+
* @property {string} body
135+
* @property {boolean} needsTransform
136+
* @property {Cue[]} cues
137+
*/
138+
class Track {
139+
/**
140+
* @readonly
141+
* @type {string}
142+
*/
143+
src;
144+
/**
145+
* @readonly
146+
* @type {string}
147+
*/
148+
encoding;
149+
/**
150+
* @readonly
151+
* @type {string}
152+
*/
153+
lang;
154+
/**
155+
* @readonly
156+
* @type {string}
157+
*/
158+
kind;
159+
/**
160+
* @readonly
161+
* @type {string}
162+
*/
163+
label;
164+
/**
165+
* @readonly
166+
* @type {boolean}
167+
*/
168+
default;
169+
/**
170+
* @readonly
171+
* @type {string}
172+
*/
173+
body;
174+
/**
175+
* @readonly
176+
* @type {boolean}
177+
*/
178+
needsTransform;
179+
/**
180+
* @readonly
181+
* @type {Cue[]}
182+
*/
183+
cues = [];
184+
/**
185+
* Parses a `HTMLTrackElement`
186+
*
187+
* @param {HTMLTrackElement} track
188+
*/
189+
constructor(track) {
190+
this.src = track.src;
191+
this.encoding = track.dataset.encoding;
192+
this.lang = track.srclang;
193+
this.kind = track.kind;
194+
this.label = track.label;
195+
this.default = track.default;
196+
this.needsTransform = !this.src.endsWith('.vtt');
197+
}
198+
async parse() {
199+
this.body = await fetchTrack(this.src);
200+
this.cues = this.body.split(/\r?\n\r?\n/g).map(toVttCue);
201+
}
202+
}
203+
204+
/**
205+
* Converts all SRT files into WebVTT files.
206+
*
207+
* @param {HTMLVideoElement} video
208+
*/
209+
async function transformSrtTracks(video) {
210+
const tracks = [...video.querySelectorAll('track')].map(track => new Track(track));
211+
for (const track of tracks) {
212+
if ( ! track.needsTransform ) continue;
213+
/**
214+
* Fetch track from URL and parse its content
215+
* We need to do before we can use it.
216+
*/
217+
await track.parse();
218+
/**
219+
* Add new TextTrack to video
220+
* We later fill this with the transformed data
221+
*/
222+
const t = video.addTextTrack(track.kind, track.label, track.lang);
223+
/**
224+
* Add all cue's we retrieved from the original
225+
* SRT file to the newly created track
226+
*/
227+
track.cues.forEach(cue => t.addCue(new VTTCue(cue.startTime, cue.endTime, cue.text)));
228+
if ( track.default ) {
229+
t.mode = 'showing';
230+
}
231+
}
232+
}
233+
234+
document.addEventListener('DOMContentLoaded', () => [...document.querySelectorAll('video')].forEach(transformSrtTracks));

package.json

Lines changed: 4 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -11,13 +11,15 @@
1111
},
1212
"scripts": {
1313
"dev": "vite",
14-
"build": "npm run build:iife && npm run build:esm",
15-
"build:iife": "vite build",
14+
"build": "rmdir dist /S /Q && npm run build:iife && npm run build:esm && npm run build:source",
15+
"build:source": "vite --config vite.config.js build",
16+
"build:iife": "vite --config vite.config.iife.js build",
1617
"build:esm": "vite --config vite.config.esm.js build",
1718
"preview": "vite preview",
1819
"publish": "npm run build"
1920
},
2021
"devDependencies": {
22+
"rollup-plugin-cleanup": "^3.2.1",
2123
"vite": "^3.0.0"
2224
}
2325
}

0 commit comments

Comments
 (0)