You can not select more than 25 topics
Topics must start with a letter or number, can include dashes ('-') and can be up to 35 characters long.
322 lines
8.7 KiB
322 lines
8.7 KiB
/***** Start *****/
|
|
{
|
|
function reduce(rest, attr) {
|
|
return rest.reduce(function(prev, curr) {
|
|
var p,
|
|
currentItem = curr.pop();
|
|
for (p in currentItem) {
|
|
prev[p] = currentItem[p];
|
|
};
|
|
return prev;
|
|
}, attr);
|
|
}
|
|
}
|
|
|
|
start
|
|
= tags:lines+ .* {
|
|
var choices = {
|
|
segments: 1,
|
|
comments: 1,
|
|
playlists: 1
|
|
};
|
|
return tags.reduce(function(obj, tag) {
|
|
for (var p in tag) {
|
|
if (p in choices) {
|
|
if (Object.prototype.toString.call(obj[p]) === '[object Array]') {
|
|
obj[p].push(tag[p]);
|
|
} else {
|
|
obj[p] = [tag[p]];
|
|
}
|
|
} else {
|
|
obj[p] = tag[p];
|
|
}
|
|
|
|
return obj;
|
|
}
|
|
}, {});
|
|
}
|
|
|
|
lines
|
|
= comment:comment _ { var obj = {}; obj["comments"] = comment; return obj; }
|
|
/ ! comment tag:tag _ { return tag; }
|
|
|
|
tag
|
|
= & comment
|
|
/ tag:m3uTag _ { return tag; }
|
|
/ tag:extinfTag _ { return tag; }
|
|
/ tag:targetDurationTag _ { return tag; }
|
|
/ tag:mediaSequenceTag _ { return tag; }
|
|
/ tag:keyTag _ { return tag; }
|
|
/ tag:programDateTimeTag _ { return tag; }
|
|
/ tag:allowCacheTag _ { return tag; }
|
|
/ tag:playlistTypeTag _ { return tag; }
|
|
/ tag:endlistTag _ { return tag; }
|
|
/ tag:mediaTag _ { return tag; }
|
|
/ tag:streamInfTag _ { return tag; }
|
|
/ tag:discontinuityTag _ { return tag; }
|
|
/ tag:discontinuitySequenceTag _ { return tag; }
|
|
/ tag:iframesOnlyTag _ { return tag; }
|
|
/ tag:mapTag _ { return tag; }
|
|
/ tag:iframeStreamInf _ { return tag; }
|
|
/ tag:startTag _ { return tag; }
|
|
/ tag:versionTag _ { return tag; }
|
|
|
|
comment "comment"
|
|
= & "#" ! "#EXT" text:text+ { return text.join(); }
|
|
|
|
/***** Tags *****/
|
|
|
|
m3uTag
|
|
= tag:"#EXTM3U" { return {openTag: true}; }
|
|
|
|
extinfTag
|
|
= tag:'#EXTINF' ":" duration:number "," optional:extinfOptionalParts _ url:mediaURL {
|
|
return {segments: {
|
|
byterange: optional.byteRange || -1,
|
|
title: optional.title,
|
|
targetDuration: duration,
|
|
url: url
|
|
}
|
|
};
|
|
}
|
|
|
|
byteRangeTag
|
|
= tag:"#EXT-X-BYTERANGE" ":" length:int ("@" offset:int)? { return {length: length, offset: offset}; }
|
|
|
|
targetDurationTag
|
|
= tag:"#EXT-X-TARGETDURATION" ":" seconds:int { return {targetDuration: seconds}; }
|
|
|
|
mediaSequenceTag
|
|
= tag:'#EXT-X-MEDIA-SEQUENCE' ":" sequenceNumber:int { return {mediaSequence: sequenceNumber}; }
|
|
|
|
keyTag
|
|
= tag:'#EXT-X-KEY' ":" attrs:keyAttributes { return {key: attrs}; }
|
|
|
|
programDateTimeTag
|
|
= tag:'#EXT-X-PROGRAM-DATE-TIME' ":" date:date
|
|
|
|
allowCacheTag
|
|
= tag:'#EXT-X-ALLOW-CACHE' ":" answer:answer { return {allowCache: answer}; }
|
|
|
|
playlistTypeTag
|
|
= tag:'#EXT-X-PLAYLIST-TYPE' ":" type:playlistType { return {playlistType: type}; }
|
|
|
|
endlistTag
|
|
= tag:'#EXT-X-ENDLIST' { return {closeTag: true}; }
|
|
|
|
mediaTag
|
|
= tag:'#EXT-MEDIA' ":" attrs:mediaAttributes { return {media: attrs}; }
|
|
|
|
streamInfTag
|
|
= tag:'#EXT-X-STREAM-INF' ":" attrs:streamInfAttrs _ url:mediaURL? {
|
|
return {playlists: {
|
|
attributes: attrs,
|
|
url: url
|
|
}
|
|
};
|
|
}
|
|
|
|
discontinuityTag
|
|
= tag:'#EXT-X-DISCONTINUITY'
|
|
|
|
discontinuitySequenceTag
|
|
= tag:'#EXT-X-DISCONTINUITY-SEQUENCE' ":" sequence:int { return {discontinuitySequence: sequence}; }
|
|
|
|
iframesOnlyTag
|
|
= tag:'#EXT-X-I-FRAMES-ONLY'
|
|
|
|
mapTag
|
|
= tag:'#EXT-X-MAP' ":" attrs:mapAttributes { return {map: attrs}; }
|
|
|
|
iframeStreamInf
|
|
= tag:'#EXT-X-I-FRAME-STREAM-INF' ":" attrs:iframeStreamAttrs { return {iframeStream: attrs}; }
|
|
|
|
startTag
|
|
= tag:'EXT-X-START' ":" attrs:startAttributes { return {start: attrs}; }
|
|
|
|
versionTag
|
|
= tag:'#EXT-X-VERSION' ":" version:int { return {version: version}; }
|
|
|
|
/***** Helpers *****/
|
|
|
|
extinfOptionalParts
|
|
= nonbreakingWhitespace title:text _ byteRange:byteRangeTag? { return {title: title, byteRange: byteRange} }
|
|
/ _ byteRange:byteRangeTag? { return {title: '', byteRange: byteRange}; }
|
|
|
|
mediaURL
|
|
= & tag
|
|
/ ! tag file:[ -~]+ { return file.join(''); }
|
|
|
|
keyAttributes
|
|
= (attr:keyAttribute rest:(attrSeparator streamInfAttrs)*) { return reduce(rest, attr); }
|
|
/ attr:keyAttribute? { return [attr]; }
|
|
|
|
keyAttribute
|
|
= "METHOD" "=" method:keyMethod { return {keyMethod: method}; }
|
|
/ "URI" "=" uri:quotedString { return {uri: uri}; }
|
|
/ "IV" "=" iv:hexint { return {IV: iv}; }
|
|
/ "KEYFORMAT" "=" keyFormat:quotedString { return {keyFormat: keyFormat}; }
|
|
/ "KEYFORMATVERSIONS" "=" keyFormatVersions:quotedString { return {keyFormatVersions: keyFormatVersions}; }
|
|
|
|
keyMethod
|
|
= "NONE"
|
|
/ "AES-128"
|
|
/ "SAMPLE-AES"
|
|
|
|
mediaAttributes
|
|
= (attr:mediaAttribute rest:(attrSeparator mediaAttribute)*) { return reduce(rest, attr); }
|
|
/ attr:mediaAttribute? { return [attr] }
|
|
|
|
mediaAttribute
|
|
= "TYPE" "=" type:mediaTypes { return {type: type}; }
|
|
/ "URI" "=" uri:quotedString { return {uri: uri}; }
|
|
/ "GROUP-ID" "=" groupId:quotedString { return {groupId: groupdId}; }
|
|
/ "LANGUAGE" "=" langauge:quotedString { return {language: language}; }
|
|
/ "ASSOC-LANGUAGE" "=" assocLanguage:quotedString { return {assocLanguage: assocLanguage}; }
|
|
/ "NAME" "=" name:quotedString { return {name: name}; }
|
|
/ "DEFAULT" "=" def:answer { return {defaultAnswer: def}; }
|
|
/ "AUTOSELECT" "=" autoselect:answer { return {autoselect: autoselect}; }
|
|
/ "FORCE" "=" force:answer { return {force: force}; }
|
|
/ "INSTREAM-ID" "=" instreamId:quotedString { return {instreamId: instreamId}; }
|
|
/ "CHARACTERISTICS" "=" characteristics:quotedString { return {characteristics: characteristics}; }
|
|
|
|
streamInfAttrs
|
|
= (attr:streamInfAttr rest:(attrSeparator streamInfAttr)*) { return reduce(rest, attr); }
|
|
/ attr:streamInfAttr?
|
|
|
|
streamInfAttr
|
|
= streamInfSharedAttr
|
|
/ "AUDIO" "=" audio:quotedString { return {audio: audio}; }
|
|
/ "SUBTITLES" "=" subtitles:quotedString { return {video: video}; }
|
|
/ "CLOSED-CAPTIONS" "=" captions:"NONE" { return {closedCaptions: captions}; }
|
|
/ "CLOSED-CAPTIONS" "=" captions:quotedString { return {closedCaptions: captions}; }
|
|
|
|
streamInfSharedAttr
|
|
= "PROGRAM-ID" "=" programId:int { return {programId: programId}; }
|
|
/ "BANDWIDTH" "=" bandwidth:int { return {bandwidth: bandwidth}; }
|
|
/ "CODECS" "=" codec:quotedString { return {codecs: codec}; }
|
|
/ "RESOLUTION" "=" resolution:resolution { return {resolution: resolution}; }
|
|
/ "VIDEO" "=" video:quotedString { return {video: video}; }
|
|
|
|
mapAttributes
|
|
= (attr:mapAttribute rest:(attrSeparator mapAttribute)*) { return reduce(rest, attr); }
|
|
/ attr:mapAttribute?
|
|
|
|
mapAttribute
|
|
= "URI" "=" uri:quotedString { return {uri: uri}; }
|
|
/ "BYTERANGE" "=" byteRange:quotedString { return {byterange: byterange}; }
|
|
|
|
iframeStreamAttrs
|
|
= (attr:iframeStreamAttr rest:(attrSeparator iframeStreamAttr)*) { return reduce(rest, attr); }
|
|
/ attr:iframeStreamAttr?
|
|
|
|
iframeStreamAttr
|
|
= streamInfSharedAttr
|
|
/ "URI" "=" uri:quotedString { return {uri: uri}; }
|
|
|
|
startAttributes
|
|
= (attr:startAttribute rest:(attrSeparator startAttribute)*) { return reduce(rest, attr); }
|
|
/ attr:startAttribute?
|
|
|
|
startAttribute
|
|
= "TIME-OFFSET" "=" timeOffset:number { return {timeOffset: timeOffset}; }
|
|
/ "PRECISE" "=" precise:answer { return {precise: precise}; }
|
|
|
|
answer "answer"
|
|
= "YES"
|
|
/ "NO"
|
|
|
|
mediaTypes
|
|
= "AUDIO"
|
|
/ "VIDEO"
|
|
/ "SUBTITLES"
|
|
/ "CLOSED-CAPTIONS"
|
|
|
|
playlistType
|
|
= "EVENT"
|
|
/ "VOD"
|
|
|
|
attrSeparator
|
|
= "," nonbreakingWhitespace { return; }
|
|
|
|
/***** Date *****/
|
|
|
|
date "date"
|
|
= year:year "-" month:month "-" day:day "T" time:time timezone:timezone
|
|
|
|
year "year"
|
|
= digit digit digit digit
|
|
|
|
month "month"
|
|
= [01] digit
|
|
|
|
day "day"
|
|
= [0-3] digit
|
|
|
|
time "time"
|
|
= [0-2] digit ":" [0-5] digit ":" [0-5] digit "." digit+
|
|
/ [0-2] digit ":" [0-5] digit ":" [0-5] digit
|
|
/ [0-2] digit ":" [0-5] digit
|
|
|
|
timezone "timezone"
|
|
= [+-] [0-2] digit ":" [0-5] digit
|
|
/ "Z"
|
|
|
|
/***** Numbers *****/
|
|
|
|
number "number"
|
|
= parts:(int frac) _ { return parseFloat(parts.join('')); }
|
|
/ parts:(int) _ { return parts; }
|
|
|
|
resolution
|
|
= width:int "x" height:int { return {width: width, height: height}; }
|
|
|
|
int
|
|
= first:digit19 rest:digits { return parseInt(first + rest.join(''), 10); }
|
|
/ digit:digit { return parseInt(digit, 10); }
|
|
/ neg:"-" first:digit19 rest:digits { return parseInt(neg + first + rest.join(''), 10); }
|
|
/ neg:"-" digit:digit { return parseInt(neg + digit, 10); }
|
|
|
|
hexint
|
|
= "0x" hexDigits:hexDigit+ { return '0x' + hexDigits.join(''); }
|
|
/ "0X" hexDigits:hexDigit+ { return '0x' + hexDigits.join(''); }
|
|
|
|
frac
|
|
= dec:"." digits:digits { return dec + digits.join(''); }
|
|
|
|
digits
|
|
= digit+
|
|
|
|
digit
|
|
= [0-9]
|
|
|
|
digit19
|
|
= [1-9]
|
|
|
|
hexDigit
|
|
= [0-9a-fA-F]
|
|
|
|
/***** Text *****/
|
|
|
|
quotedString
|
|
= '"' '"' _ { return ""; }
|
|
/ '"' chars:quotedChar+ '"' _ { return chars.join(''); }
|
|
|
|
quotedChar
|
|
= [^\r\n"]
|
|
/ char:char
|
|
|
|
text "text"
|
|
= text:char+ { return text.join(''); }
|
|
|
|
char "char"
|
|
= [ -~]
|
|
|
|
_ "whitespace"
|
|
= whitespace*
|
|
|
|
whitespace
|
|
= [ \t\n\r]
|
|
|
|
nonbreakingWhitespace
|
|
= [ \t]*
|