Compiles, parses WebVTT files, segments and generates HLS playlists for them.
For a WebVTT file:
WEBVTT
00:00:00.000 --> 00:00:01.000
Hello world!
00:00:30.000 --> 00:00:31.000 align:start line:0%
This is a subtitle
00:01:00.000 --> 00:01:01.000
Foo
00:01:50.000 --> 00:01:51.000
Bar
We can parse, segment and create HLS playlists, and compile back to WebVTT format:
constwebvtt=require('node-webvtt');constsegmentDuration=10;// default to 10conststartOffset=0;// Starting MPEG TS offset to be used in timestamp map, default 900000constparsed=webvtt.parse(input);constcompile=webvtt.compile(input);constsegmented=webvtt.parse(input,segmentDuration);constplaylist=webvtt.hls.hlsSegmentPlaylist(input,segmentDuration);constsegments=webvtt.hls.hlsSegment(input,segmentDuration,startOffset);Parses the WebVTT file and returns an object with valid === true if parsed correctly and an array of cues parsed.
Each cue can have:
identifier- Id, if any of the cuestart- Start time of cue in secondsend- End time of cue in secondstext- Text of the subtitlestyles- If any of the cue
If the WebVTT file is invalid, the parser will throw a ParserError exception. So for safety, calls to parse should be in try catch.
For the above example we'd get:
{
"valid":true,
"cues":[
{
"identifier":"",
"start":0,
"end":1,
"text":"Hello world!",
"styles":""
},
{
"identifier":"",
"start":30,
"end":31,
"text":"This is a subtitle",
"styles":"align:start line:0%"
},
{
"identifier":"",
"start":60,
"end":61,
"text":"Foo",
"styles":""
},
{
"identifier":"",
"start":110,
"end":111,
"text":"Bar",
"styles":""
}
]
}By default the parser is strict. It will throw errors if:
- Header is incorrect, i.e. does not start with
WEBVTT - If any cue is malformed in any way
Setting the option parameter of strict to false will allow files with malformed cues to be parsed. The resulting object will have valid === false and all errors in an errors array.
If strict is set to false, the parser will also not categorize it as an error if a cue starts and ends at the same time. This might be the correct behaviour but changing would introduce a breaking change in version 1.x.
constinput=`WEBVTTMALFORMEDCUE -->This text is from a malformed cue. It should not be processed.100:00.000 --> 00:00.001test`;constresult=parse(input,{strict: false});/*result = { valid: false, strict: false, cues: [ { identifier: '1', start: 0, end: 0.001, text: 'test', styles: '' } ], errors: [ { Error: Invalid cue timestamp (cue #0) message: 'Invalid cue timestamp (cue #0)', error: undefined } ]}*/Some WebVTT strings may also contain lines of metadata after the initial WEBVTT line, for example:
WEBVTT
Kind: captions
Language: en
00:00:00.000 --> 00:00:01.000
Hello world!
By passing { meta: true } to the parse method, these metadata will be returned as an object called meta. For example, parsing the above example:
parse(webvtt,{meta: true});would return the following:
{
"valid":true,
"meta":{
"Kind": "captions",
"Language": "en"
},
"cues":[
{
"identifier":"",
"start":0,
"end":1,
"text":"Hello world!",
"styles":""
}
]
}If no metadata is available, meta will be set to null in the result if the option is specified.
Compiles JSON from the above format back into a WebVTT string. If a meta key is in the input,
it will be compiled as well. The meta value must be an object and each key and value must be a string.
If the object is missing any attributes, the compiler will throw a CompilerError exception. So
for safety, calls to compile should be in try catch.
constinput={meta: {Kind: 'captions',Language: 'en'},cues: [{end: 140,identifier: '1',start: 135.001,text: 'Hello world',styles: ''}],valid: true};constresult=compile(input);/*WEBVTTKind: captionsLanguage: en100:02:15.001 --> 00:02:20.000Hello world*/Segments a subtitle according to how it should be segmented for HLS subtitles.
- Does a one pass of the cues for segmenting, this might have been a good idea or bad, only time will tell
- The One and Only Source of Truth is Apple's
mediasubtitlesegmenterCLI
For the above example:
[{duration: 10,cues: [[Object]]},{duration: 30,cues: [[Object]]},{duration: 30,cues: [[Object]]},{duration: 41,cues: [[Object]]}]Creates a subtitle playlist. For the above:
#EXTM3U
#EXT-X-TARGETDURATION:41
#EXT-X-VERSION:3
#EXT-X-MEDIA-SEQUENCE:0
#EXT-X-PLAYLIST-TYPE:VOD
#EXTINF:10.00000,
0.vtt
#EXTINF:30.00000,
1.vtt
#EXTINF:30.00000,
2.vtt
#EXTINF:41.00000,
3.vtt
#EXT-X-ENDLIST
Creates a list of HLS segments for the subtitles, returning an array of them with filename and content.
[
{
"filename":"0.vtt",
"content":"WEBVTT\nX-TIMESTAMP-MAP=MPEGTS:900000,LOCAL:00:00:00.000\n\n00:00:00.000 --> 00:00:01.000\nHello world!\n"
},
{
"filename":"1.vtt",
"content":"WEBVTT\nX-TIMESTAMP-MAP=MPEGTS:900000,LOCAL:00:00:00.000\n\n00:00:30.000 --> 00:00:31.000 align:start line:0%\nThis is a subtitle\n"
},
{
"filename":"2.vtt",
"content":"WEBVTT\nX-TIMESTAMP-MAP=MPEGTS:900000,LOCAL:00:00:00.000\n\n00:01:00.000 --> 00:01:01.000\nFoo\n"
},
{
"filename":"3.vtt",
"content":"WEBVTT\nX-TIMESTAMP-MAP=MPEGTS:900000,LOCAL:00:00:00.000\n\n00:01:50.000 --> 00:01:51.000\nBar\n"
}
]For segmenting a WebVTT file quickly, you can use the included CLI tool:
./webvtt-segment.js -v --target-duration 10 -o ./subs subs.vtt$ ./webvtt-segment.js --help
Usage: webvtt-segment [options] <webvtt file>
Options:
-h, --help output usage information
-V, --version output the version number
-t, --target-duration [duration] Target duration foreach segmentin secods, defaults to 10
-o, --output-directory [dir] Output directory for segments and playlist
-v, --verbose Chatty output
-s, --silent No outputThis has been written with TDD so we've got a good coverage of the features.
npm install
npm test
mocha -w
<write failing test><write passing code><lather, rinse, repeat>- Anne van Kesteren's WebVTT validator
- WebVTT Ruby parser and segmenter
mediasubtitlesegmenterfrom Apple- WebVTT: The Web Video Text Tracks Format