37b01102 by Gary Katsevman

Add m3u8.pegjs.

Generate the parser file via 'npm run peg'.
There is a simple test script in test/pegtest.js.
The test can be run via 'npm run testpeg' which will generate a new copy
of the parser and then run the test file.
1 parent 0201bdab
...@@ -6,7 +6,10 @@ ...@@ -6,7 +6,10 @@
6 }, 6 },
7 "license": "Apache 2", 7 "license": "Apache 2",
8 "scripts": { 8 "scripts": {
9 "test": "grunt qunit" 9 "test": "grunt qunit",
10 "prepublish": "npm run peg",
11 "peg": "pegjs src/m3u8/m3u8.pegjs src/m3u8/m3u8-generated.js",
12 "testpeg": "npm run peg && node test/pegtest.js"
10 }, 13 },
11 "devDependencies": { 14 "devDependencies": {
12 "grunt-contrib-jshint": "~0.6.0", 15 "grunt-contrib-jshint": "~0.6.0",
...@@ -15,7 +18,8 @@ ...@@ -15,7 +18,8 @@
15 "grunt-contrib-uglify": "~0.2.0", 18 "grunt-contrib-uglify": "~0.2.0",
16 "grunt-contrib-watch": "~0.4.0", 19 "grunt-contrib-watch": "~0.4.0",
17 "grunt-contrib-clean": "~0.4.0", 20 "grunt-contrib-clean": "~0.4.0",
18 "grunt": "~0.4.1" 21 "grunt": "~0.4.1",
22 "pegjs": "~0.7.0"
19 }, 23 },
20 "dependencies": { 24 "dependencies": {
21 "video.js": "~4.2.2", 25 "video.js": "~4.2.2",
......
1 /***** Start *****/
2 start
3 = tags:tags+ .* {
4 var obj = {};
5 tags.forEach(function(tag) { for (var p in tag) { obj[p] = tag[p]; }});
6 return obj;
7 }
8
9 tags
10 = tag:m3uTag _ { return tag; }
11 / tag:extinfTag _ { return tag; }
12 / tag:targetDurationTag _ { return tag; }
13 / tag:mediaSequenceTag _ { return tag; }
14 / tag:keyTag _ { return tag; }
15 / tag:programDateTimeTag _ { return tag; }
16 / tag:allowCacheTag _ { return tag; }
17 / tag:playlistTypeTag _ { return tag; }
18 / tag:endlistTag _ { return tag; }
19 / tag:mediaTag _ { return tag; }
20 / tag:streamInfTag _ { return tag; }
21 / tag:discontinuityTag _ { return tag; }
22 / tag:discontinuitySequenceTag _ { return tag; }
23 / tag:iframesOnlyTag _ { return tag; }
24 / tag:mapTag _ { return tag; }
25 / tag:iframeStreamInf _ { return tag; }
26 / tag:startTag _ { return tag; }
27 / tag:versionTag _ { return tag; }
28
29 /***** Tags *****/
30
31 m3uTag
32 = tag:"#EXTM3U" { return {openTag: true}; }
33
34 extinfTag
35 = tag:'#EXTINF' ":" duration:number "," _ title:text? _ byteRange:byteRangeTag? file:mediaFile {
36 var fileObj = {};
37 fileObj[file] = {
38 byteRange: byteRange,
39 title: title,
40 duration: duration,
41 file: file
42 };
43 return fileObj;
44 }
45
46 byteRangeTag
47 = tag:"EXT-X-BYTERANGE" ":" length:int ("@" offset:int)? { return {length: length, offset: offset}; }
48
49 targetDurationTag
50 = tag:"#EXT-X-TARGETDURATION" ":" seconds:int { return {targetDuration: seconds}; }
51
52 mediaSequenceTag
53 = tag:'#EXT-X-MEDIA-SEQUENCE' ":" sequenceNumber:int { return {mediaSequence: sequenceNumber}; }
54
55 keyTag
56 = tag:'#EXT-X-KEY' ":" attrs:keyAttributes { return {key: attrs}; }
57
58 programDateTimeTag
59 = tag:'#EXT-X-PROGRAM-DATE-TIME' ":" date:date
60
61 allowCacheTag
62 = tag:'#EXT-X-ALLOW-CACHE' ":" answer:answer { return {allowCache: answer}; }
63
64 playlistTypeTag
65 = tag:'#EXT-X-PLAYLIST-TYPE' ":" type:playlistType { return {playlistType: type}; }
66
67 endlistTag
68 = tag:'#EXT-X-ENDLIST' { return {closeTag: true}; }
69
70 mediaTag
71 = tag:'#EXT-MEDIA' ":" attrs:mediaAttributes { return {media: attrs}; }
72
73 streamInfTag
74 = tag:'#EXT-X-STREAM-INF' ":" attrs:streamInfAttrs _ file:mediaFile {
75 var fileObj = {};
76 fileObj[file] = {
77 attributes: attrs,
78 file: file
79 };
80 return fileObj;
81 }
82
83 discontinuityTag
84 = tag:'#EXT-X-DISCONTINUITY'
85
86 discontinuitySequenceTag
87 = tag:'#EXT-X-DISCONTINUITY-SEQUENCE' ":" sequence:int { return {discontinuitySequence: sequence}; }
88
89 iframesOnlyTag
90 = tag:'#EXT-X-I-FRAMES-ONLY'
91
92 mapTag
93 = tag:'#EXT-X-MAP' ":" attrs:mapAttributes { return {map: attrs}; }
94
95 iframeStreamInf
96 = tag:'#EXT-X-I-FRAME-STREAM-INF' ":" attrs:iframeStreamAttrs { return {iframeStream: attrs}; }
97
98 startTag
99 = tag:'EXT-X-START' ":" attrs:startAttributes { return {start: attrs}; }
100
101 versionTag
102 = tag:'#EXT-X-VERSION' ":" version:int { return {version: version}; }
103
104 /***** Helpers *****/
105
106 mediaFile
107 = file:[ -~]+ { return file.join(''); }
108
109 keyAttributes
110 = attrs:keyAttribute+
111
112 keyAttribute
113 = "METHOD" "=" method:keyMethod
114 / "URI" "=" uri:quotedString
115 / "IV" "=" iv:hexint
116 / "KEYFORMAT" "=" keyFormat:quotedString
117 / "KEYFORMATVERSIONS" "=" keyFormatVersions:quotedString
118
119 keyMethod
120 = "NONE"
121 / "AES-128"
122 / "SAMPLE-AES"
123
124 mediaAttributes
125 = attrs:mediaAttribute+
126
127 mediaAttribute
128 = "TYPE" "=" type:enumeratedString
129 / "URI" "=" uri:quotedString
130 / "GROUP-ID" "=" groupId:quotedString
131 / "LANGUAGE" "=" langauge:quotedString
132 / "ASSOC-LANGUAGE" "=" assocLanguage:quotedString
133 / "NAME" "=" name:quotedString
134 / "DEFAULT" "=" default:answer
135 / "AUTOSELECT" "="autoselect:answer
136 / "FORCE" "=" force:answer
137 / "INSTREAM-ID" "=" instreamId:quotedString
138 / "CHARACTERISTICS" "=" characteristics:quotedString
139
140 streamInfAttrs
141 = attrs:streamInfAttr+
142
143 streamInfAttr
144 = streamInfSharedAttr
145 / "AUDIO" "=" audio:quotedString
146 / "SUBTITLES" "=" subtitles:quotedString
147 / "CLOSED-CAPTION" "=" captions:"NONE"
148 / "CLOSED-CAPTION" "=" captions:quotedString
149
150 streamInfSharedAttr
151 = "BANDWIDTH" "=" bandwidth:int
152 / "CODECS" "=" codec:quotedString
153 / "RESOLUTION" "=" resolution:resolution
154 / "VIDEO" "=" video:quotedString
155
156 mapAttributes
157 = attrs:mapAttribute+
158
159 mapAttribute
160 = "URI" "=" uri:quotedString
161 / "BYTERANGE" "=" byteRange:quotedString
162
163 iframeStreamAttrs
164 = iframeStreamAttr+
165
166 iframeStreamAttr
167 = streamInfSharedAttr
168 / "URI" "=" uri:quotedString
169
170 startAttributes
171 = attrs:startAttribute+
172
173 startAttribute
174 = "TIME-OFFSET" "=" timeOffset:number
175 / "PRECISE" "=" precise:answer
176
177 answer "answer"
178 = "YES"
179 / "NO"
180
181 playlistType
182 = "EVENT"
183 / "VOD"
184
185 /***** Date *****/
186
187 date "date"
188 = year:year "-" month:month "-" day:day "T" time:time timezone:timezone
189
190 year "year"
191 = digit digit digit digit
192
193 month "month"
194 = [01] digit
195
196 day "day"
197 = [0-3] digit
198
199 time "time"
200 = [0-2] digit ":" [0-5] digit ":" [0-5] digit "." digit+
201 / [0-2] digit ":" [0-5] digit ":" [0-5] digit
202 / [0-2] digit ":" [0-5] digit
203
204 timezone "timezone"
205 = [+-] [0-2] digit ":" [0-5] digit
206 / "Z"
207
208 /***** Numbers *****/
209
210 number "number"
211 = parts:(int frac) _ { return parseFloat(parts.join('')); }
212 / parts:(int) _ { return parts; }
213
214 resolution
215 = int "x" int
216
217 int
218 = first:digit19 rest:digits { return parseInt(first + rest.join(''), 10); }
219 / digit:digit { return parseInt(digit, 10); }
220 / neg:"-" first:digit19 rest:digits { return parseInt(neg + first + rest.join(''), 10); }
221 / neg:"-" digit { return parseInt(neg + digit, 10); }
222
223 hexint
224 = "0x" hexDigits:hexDigit+ { return '0x' + hexDigits.join(''); }
225 / "0X" hexDigits:hexDigit+ { return '0x' + hexDigits.join(''); }
226
227 frac
228 = dec:"." digits:digits { return parseFloat(dec + digits.join('')); }
229
230 digits
231 = digit+
232
233 digit
234 = [0-9]
235
236 digit19
237 = [1-9]
238
239 hexDigit
240 = [0-9a-fA-F]
241
242 /***** Text *****/
243
244 enumeratedString
245 = chars:enumeratedChar+ _ { return chars.join('') }
246
247 quotedString
248 = '"' '"' _ { return ""; }
249 / '"' chars:quotedChar+ '"' _ { return chars.join(''); }
250
251 enumeratedChar
252 = [^'" \n\t\r]
253 / [a-zA-Z0-9]
254
255 quotedChar
256 = [^\r\n"]
257 / char:char
258
259 text "text"
260 = text:char+ { return text.join(''); }
261
262 char "char"
263 = [ -~]
264
265 _ "whitespace"
266 = whitespace*
267
268 whitespace
269 = [ \t\n\r]
1 var fs = require('fs');
2 var path = require('path');
3 var manifest = fs.readFileSync(__dirname + '/fixtures/prog_index.m3u8').toString();
4 var parser = require('../src/m3u8/m3u8-generated.js');
5 var parsed = parser.parse(manifest);
6 console.log(parsed);