dasha 2.3.5 → 3.0.0

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
package/lib/xml.js CHANGED
@@ -1,132 +1,300 @@
1
- 'use strict';
2
-
3
- const PREFIX_LINE_PATTERN = /\s*(<\/?[^>]+>)/;
4
- const TAG_ATTR_PATTERN = /\s*(?:<\/?([^\s>]+))?\s*([^>]+)*(?:\/?>)?/;
5
- const ATTR_PAIR_PATTERN = /\s*([^=]+)="([^"]+)"/g;
6
-
7
- const getTagList = (xmlString) => {
8
- const list = xmlString.split(PREFIX_LINE_PATTERN).filter(Boolean);
9
- if (list[0].startsWith(`<?xml`)) list.splice(0, 1);
10
- return list;
11
- };
12
-
13
- const parseTag = (tagString) => {
14
- const match = tagString.match(TAG_ATTR_PATTERN);
15
- if (!match) return null;
16
-
17
- const tagNameWithBrackets = match[0];
18
- const tagName = match[1];
19
- const tagAttributes = match[2];
20
-
21
- const isClosedTag = /^<\//.test(tagNameWithBrackets);
22
- const isNormalTag = /^</.test(tagNameWithBrackets);
23
- const isCommentTag = /^<!/.test(tagNameWithBrackets);
24
- if (isCommentTag) return;
25
-
26
- // const formattedTagName = formatTagName(tagName);
27
-
28
- const tag = {
29
- tagName,
30
- };
31
-
32
- // <TagName xx=xx xxx=xxx>
33
- // <TagName xx=xxx xx=xxx />
34
- if (tagName && tagAttributes) {
35
- const isSelfClosed = /\/$/.test(tagAttributes);
36
- const attrs = {};
37
- let currentAttribute = ATTR_PAIR_PATTERN.exec(tagAttributes);
38
- while (currentAttribute !== null) {
39
- const [rawAttribute, attributeName, attributeValue] = currentAttribute;
40
- attrs[attributeName] = attributeValue;
41
- currentAttribute = ATTR_PAIR_PATTERN.exec(tagAttributes);
42
- }
43
- tag.attrs = attrs;
44
- tag.closed = isSelfClosed;
45
- return tag;
46
- }
1
+ function parse(text, options = {}) {
2
+ var pos = options.pos || 0;
3
+ var keepComments = !!options.keepComments;
4
+ var keepWhitespace = !!options.keepWhitespace;
5
+
6
+ var openBracket = '<';
7
+ var openBracketCC = '<'.charCodeAt(0);
8
+ var closeBracket = '>';
9
+ var closeBracketCC = '>'.charCodeAt(0);
10
+ var minusCC = '-'.charCodeAt(0);
11
+ var slashCC = '/'.charCodeAt(0);
12
+ var exclamationCC = '!'.charCodeAt(0);
13
+ var singleQuoteCC = "'".charCodeAt(0);
14
+ var doubleQuoteCC = '"'.charCodeAt(0);
15
+ var openCornerBracketCC = '['.charCodeAt(0);
16
+ var closeCornerBracketCC = ']'.charCodeAt(0);
17
+
18
+ /**
19
+ * parsing a list of entries
20
+ */
21
+ function parseChildren(tagName) {
22
+ var children = [];
23
+ while (text[pos]) {
24
+ if (text.charCodeAt(pos) == openBracketCC) {
25
+ if (text.charCodeAt(pos + 1) === slashCC) {
26
+ var closeStart = pos + 2;
27
+ pos = text.indexOf(closeBracket, pos);
28
+
29
+ var closeTag = text.substring(closeStart, pos);
30
+ if (closeTag.indexOf(tagName) == -1) {
31
+ var parsedText = text.substring(0, pos).split('\n');
32
+ throw new Error(
33
+ 'Unexpected close tag\nLine: ' +
34
+ (parsedText.length - 1) +
35
+ '\nColumn: ' +
36
+ (parsedText[parsedText.length - 1].length + 1) +
37
+ '\nChar: ' +
38
+ text[pos]
39
+ );
40
+ }
47
41
 
48
- // </TagName>
49
- if (isClosedTag) {
50
- tag.closed = true;
51
- return tag;
42
+ if (pos + 1) pos += 1;
43
+
44
+ return children;
45
+ } else if (text.charCodeAt(pos + 1) === exclamationCC) {
46
+ if (text.charCodeAt(pos + 2) == minusCC) {
47
+ //comment support
48
+ const startCommentPos = pos;
49
+ while (
50
+ pos !== -1 &&
51
+ !(
52
+ text.charCodeAt(pos) === closeBracketCC &&
53
+ text.charCodeAt(pos - 1) == minusCC &&
54
+ text.charCodeAt(pos - 2) == minusCC &&
55
+ pos != -1
56
+ )
57
+ ) {
58
+ pos = text.indexOf(closeBracket, pos + 1);
59
+ }
60
+ if (pos === -1) {
61
+ pos = text.length;
62
+ }
63
+ if (keepComments) {
64
+ children.push(text.substring(startCommentPos, pos + 1));
65
+ }
66
+ } else if (
67
+ text.charCodeAt(pos + 2) === openCornerBracketCC &&
68
+ text.charCodeAt(pos + 8) === openCornerBracketCC &&
69
+ text.substr(pos + 3, 5).toLowerCase() === 'cdata'
70
+ ) {
71
+ // cdata
72
+ var cdataEndIndex = text.indexOf(']]>', pos);
73
+ if (cdataEndIndex == -1) {
74
+ children.push(text.substr(pos + 9));
75
+ pos = text.length;
76
+ } else {
77
+ children.push(text.substring(pos + 9, cdataEndIndex));
78
+ pos = cdataEndIndex + 3;
79
+ }
80
+ continue;
81
+ } else {
82
+ // doctypesupport
83
+ const startDoctype = pos + 1;
84
+ pos += 2;
85
+ var encapsuled = false;
86
+ while ((text.charCodeAt(pos) !== closeBracketCC || encapsuled === true) && text[pos]) {
87
+ if (text.charCodeAt(pos) === openCornerBracketCC) {
88
+ encapsuled = true;
89
+ } else if (encapsuled === true && text.charCodeAt(pos) === closeCornerBracketCC) {
90
+ encapsuled = false;
91
+ }
92
+ pos++;
93
+ }
94
+ children.push(text.substring(startDoctype, pos));
95
+ }
96
+ pos++;
97
+ continue;
98
+ }
99
+ var node = parseNode();
100
+ children.push(node);
101
+ if (node.tagName[0] === '?') {
102
+ children.push(...node.children);
103
+ node.children = [];
104
+ }
105
+ } else {
106
+ var parsedText = parseText();
107
+ if (keepWhitespace) {
108
+ if (parsedText.length > 0) {
109
+ children.push(parsedText);
110
+ }
111
+ } else {
112
+ var trimmed = parsedText.trim();
113
+ if (trimmed.length > 0) {
114
+ children.push(trimmed);
115
+ }
116
+ }
117
+ pos++;
118
+ }
119
+ }
120
+ return children;
52
121
  }
53
122
 
54
- // <TagName>
55
- if (isNormalTag) {
56
- tag.attrs = {};
57
- return tag;
123
+ /**
124
+ * returns the text outside of texts until the first '<'
125
+ */
126
+ function parseText() {
127
+ var start = pos;
128
+ pos = text.indexOf(openBracket, pos) - 1;
129
+ if (pos === -2) pos = text.length;
130
+ return text.slice(start, pos + 1);
58
131
  }
132
+ /**
133
+ * returns text until the first nonAlphabetic letter
134
+ */
135
+ var nameSpacer = '\r\n\t>/= ';
59
136
 
60
- // pure value
61
- tag.tagName = 'pureValue';
62
- tag.attrs = { value: tagAttributes };
63
- tag.closed = true;
64
- return tag;
65
- };
66
-
67
- const mergeTags = (tagList) => {
68
- const stack = [];
69
- let last;
70
- let lastPre;
71
-
72
- for (const tag of tagList) {
73
- // start tag
74
- if (!tag.closed) {
75
- stack.push(tag);
76
- continue;
137
+ function parseName() {
138
+ var start = pos;
139
+ while (nameSpacer.indexOf(text[pos]) === -1 && text[pos]) {
140
+ pos++;
77
141
  }
142
+ return text.slice(start, pos);
143
+ }
144
+ /**
145
+ * is parsing a node, including tagName, Attributes and its children,
146
+ * to parse children it uses the parseChildren again, that makes the parsing recursive
147
+ */
148
+ var NoChildNodes = options.noChildNodes || ['img', 'br', 'input', 'meta', 'link', 'hr'];
78
149
 
79
- last = stack.pop();
150
+ function parseNode() {
151
+ pos++;
152
+ const tagName = parseName();
153
+ const attributes = {};
154
+ let children = [];
80
155
 
81
- if (tag.tagName === 'pureValue') {
82
- last.attrs ? (last.attrs.value = tag.attrs.value) : (last.attrs = tag.attrs);
83
- stack.push(last);
84
- continue;
156
+ // parsing attributes
157
+ while (text.charCodeAt(pos) !== closeBracketCC && text[pos]) {
158
+ var c = text.charCodeAt(pos);
159
+ if ((c > 64 && c < 91) || (c > 96 && c < 123)) {
160
+ //if('abcdefghijklmnopqrstuvwxyzABCDEFGHIJKLMNOPQRSTUVWXYZ'.indexOf(S[pos])!==-1 ){
161
+ var name = parseName();
162
+ // search beginning of the string
163
+ var code = text.charCodeAt(pos);
164
+ while (
165
+ code &&
166
+ code !== singleQuoteCC &&
167
+ code !== doubleQuoteCC &&
168
+ !((code > 64 && code < 91) || (code > 96 && code < 123)) &&
169
+ code !== closeBracketCC
170
+ ) {
171
+ pos++;
172
+ code = text.charCodeAt(pos);
173
+ }
174
+ if (code === singleQuoteCC || code === doubleQuoteCC) {
175
+ var value = parseString();
176
+ if (pos === -1) {
177
+ return {
178
+ tagName,
179
+ attributes,
180
+ children,
181
+ };
182
+ }
183
+ } else {
184
+ value = null;
185
+ pos--;
186
+ }
187
+ attributes[name] = value;
188
+ }
189
+ pos++;
85
190
  }
86
-
87
- if (last && last.tagName === tag.tagName) {
88
- lastPre = stack.pop();
191
+ // optional parsing of children
192
+ if (text.charCodeAt(pos - 1) !== slashCC) {
193
+ if (tagName == 'script') {
194
+ var start = pos + 1;
195
+ pos = text.indexOf('</script>', pos);
196
+ children = [text.slice(start, pos)];
197
+ pos += 9;
198
+ } else if (tagName == 'style') {
199
+ var start = pos + 1;
200
+ pos = text.indexOf('</style>', pos);
201
+ children = [text.slice(start, pos)];
202
+ pos += 8;
203
+ } else if (NoChildNodes.indexOf(tagName) === -1) {
204
+ pos++;
205
+ children = parseChildren(tagName);
206
+ } else {
207
+ pos++;
208
+ }
89
209
  } else {
90
- // self closed tag
91
- lastPre = last;
92
- last = tag;
210
+ pos++;
93
211
  }
212
+ return {
213
+ tagName,
214
+ attributes,
215
+ children,
216
+ };
217
+ }
94
218
 
95
- if (!lastPre) {
96
- stack.push(last);
97
- break;
98
- }
219
+ /**
220
+ * is parsing a string, that starts with a char and with the same usually ' or "
221
+ */
99
222
 
100
- const { tagName } = last;
101
- let { attrs } = last;
102
- const prop = lastPre.attrs[tagName];
223
+ function parseString() {
224
+ var startChar = text[pos];
225
+ var startpos = pos + 1;
226
+ pos = text.indexOf(startChar, startpos);
227
+ return text.slice(startpos, pos);
228
+ }
103
229
 
104
- if (prop) {
105
- lastPre.attrs[tagName] = Array.isArray(prop) ? prop.concat(attrs) : [prop, attrs];
230
+ /**
231
+ *
232
+ */
233
+ function findElements() {
234
+ var r = new RegExp('\\s' + options.attrName + '\\s*=[\'"]' + options.attrValue + '[\'"]').exec(
235
+ text
236
+ );
237
+ if (r) {
238
+ return r.index;
106
239
  } else {
107
- lastPre.attrs[tagName] = attrs;
240
+ return -1;
108
241
  }
109
- stack.push(lastPre);
110
242
  }
111
243
 
112
- const firstTag = stack[0];
244
+ var out = null;
245
+ if (options.attrValue !== undefined) {
246
+ options.attrName = options.attrName || 'id';
247
+ var out = [];
248
+
249
+ while ((pos = findElements()) !== -1) {
250
+ pos = text.lastIndexOf('<', pos);
251
+ if (pos !== -1) {
252
+ out.push(parseNode());
253
+ }
254
+ text = text.substr(pos);
255
+ pos = 0;
256
+ }
257
+ } else if (options.parseNode) {
258
+ out = parseNode();
259
+ } else {
260
+ out = parseChildren('');
261
+ }
113
262
 
114
- if (!firstTag) return null;
115
- if (firstTag.tagName !== undefined) return { [firstTag.tagName]: firstTag.attrs };
263
+ if (options.filter) {
264
+ out = filter(out, options.filter);
265
+ }
116
266
 
117
- return firstTag.tagName;
118
- };
267
+ if (options.simplify) {
268
+ return simplify(Array.isArray(out) ? out : [out]);
269
+ }
119
270
 
120
- const parseXml = (text) => {
121
- if (!text) return { error: 1, message: 'invalid input' };
122
- try {
123
- const rawTags = getTagList(text);
124
- const parsedTags = rawTags.map((tag) => parseTag(tag)).filter(Boolean);
125
- const mergedTags = mergeTags(parsedTags);
126
- return mergedTags;
127
- } catch (e) {
128
- return { error: 1, message: e.message };
271
+ if (options.setPos) {
272
+ out.pos = pos;
129
273
  }
130
- };
131
274
 
132
- module.exports = { parseXml };
275
+ return out;
276
+ }
277
+
278
+ /**
279
+ * behaves the same way as Array.filter, if the filter method return true, the element is in the resultList
280
+ * @params children{Array} the children of a node
281
+ * @param f{function} the filter method
282
+ */
283
+ function filter(children, f, dept = 0, path = '') {
284
+ var out = [];
285
+ children.forEach(function (child, i) {
286
+ if (typeof child === 'object' && f(child, i, dept, path)) out.push(child);
287
+ if (child.children) {
288
+ var kids = filter(
289
+ child.children,
290
+ f,
291
+ dept + 1,
292
+ (path ? path + '.' : '') + i + '.' + child.tagName
293
+ );
294
+ out = out.concat(kids);
295
+ }
296
+ });
297
+ return out;
298
+ }
299
+
300
+ module.exports = { parse, filter };
package/package.json CHANGED
@@ -1,46 +1,60 @@
1
1
  {
2
2
  "name": "dasha",
3
- "version": "2.3.5",
3
+ "version": "3.0.0",
4
4
  "author": "Vitaly Gashkov <vitalygashkov@vk.com>",
5
- "description": "MPD-manifest parser for MPEG DASH",
6
- "license": "Apache-2.0",
5
+ "description": "Parser for MPEG-DASH & HLS manifests",
6
+ "license": "AGPL-3.0",
7
7
  "keywords": [
8
8
  "mpeg",
9
9
  "dash",
10
+ "hls",
10
11
  "adaptive",
11
12
  "mpd",
13
+ "m3u8",
12
14
  "manifest"
13
15
  ],
16
+ "readmeFilename": "README.md",
17
+ "repository": {
18
+ "type": "git",
19
+ "url": "https://github.com/vitalygashkov/dasha"
20
+ },
21
+ "bugs": {
22
+ "url": "https://github.com/vitalygashkov/dasha/issues",
23
+ "email": "vitalygashkov@vk.com"
24
+ },
14
25
  "main": "dasha.js",
15
26
  "types": "types/dasha.d.ts",
27
+ "funding": [
28
+ {
29
+ "type": "individual",
30
+ "url": "https://boosty.to/vitalygashkov/donate"
31
+ }
32
+ ],
16
33
  "files": [
17
34
  "lib",
18
35
  "types"
19
36
  ],
20
- "repository": {
21
- "type": "git",
22
- "url": "git+https://github.com/vitalygashkov/dasha.git"
23
- },
24
- "funding": {
25
- "type": "individual",
26
- "url": "https://boosty.to/vitalygashkov"
27
- },
28
37
  "scripts": {
29
- "test": "jest",
38
+ "test": "node --test",
30
39
  "types": "tsc -p tsconfig.json",
31
40
  "lint": "eslint \"**/*.js\" --fix",
32
41
  "lint:check": "eslint \"**/*.js\"",
33
42
  "format": "prettier --loglevel warn --write \"**/*.{ts,js,json,yaml}\"",
34
- "format:check": "prettier --loglevel warn --check \"**/*.{ts,js,json,yaml}\""
43
+ "format:check": "prettier --loglevel warn --check \"**/*.{ts,js,json,yaml}\"",
44
+ "build": "tsup ./dasha.js --format cjs",
45
+ "build:bun": "bun build ./dasha.js --outdir ./dist --format cjs"
46
+ },
47
+ "dependencies": {
48
+ "m3u8-parser": "^7.1.0"
35
49
  },
36
50
  "devDependencies": {
37
- "@types/node": "^17.0.23",
38
- "eslint": "^8.12.0",
39
- "eslint-config-prettier": "^8.5.0",
40
- "eslint-plugin-import": "^2.25.4",
41
- "eslint-plugin-prettier": "^4.0.0",
42
- "jest": "^27.5.1",
43
- "prettier": "^2.6.1",
44
- "typescript": "^4.6.3"
51
+ "@types/node": "^20.12.7",
52
+ "eslint": "^8.57.0",
53
+ "eslint-config-prettier": "^9.1.0",
54
+ "eslint-plugin-import": "^2.29.1",
55
+ "eslint-plugin-prettier": "^5.1.3",
56
+ "prettier": "^3.2.5",
57
+ "tsup": "^8.0.2",
58
+ "typescript": "^5.4.5"
45
59
  }
46
60
  }
package/types/dasha.d.ts CHANGED
@@ -1,2 +1,98 @@
1
- export * from './manifest';
2
- export * from './constants';
1
+ export function parse(text: string, url: string, fallbackLanguage?: string): Promise<Manifest>;
2
+
3
+ export interface Manifest {
4
+ duration?: number;
5
+ tracks: {
6
+ all: (VideoTrack | AudioTrack | SubtitleTrack)[];
7
+ videos: VideoTrack[];
8
+ audios: AudioTrack[];
9
+ subtitles: SubtitleTrack[];
10
+ withResolution(resolution: { width?: string; height?: string }): VideoTrack[];
11
+ withVideoQuality(quality: number | string): VideoTrack[];
12
+ withAudioLanguages(languages: string[], maxTracksPerLanguage?: number): AudioTrack[];
13
+ withSubtitleLanguages(languages: string[]): SubtitleTrack[];
14
+ };
15
+ }
16
+
17
+ export interface Track {
18
+ id: string;
19
+ type: 'video' | 'audio' | 'text';
20
+ segments: Segment[];
21
+ size?: Size;
22
+ label?: string;
23
+ protection?: TrackProtection;
24
+ toString: () => string;
25
+ }
26
+
27
+ export interface Bitrate {
28
+ bps: number;
29
+ kbps: number;
30
+ mbps: number;
31
+ gbps: number;
32
+ toString: () => string;
33
+ }
34
+
35
+ export interface Size {
36
+ b: number;
37
+ kb: number;
38
+ mb: number;
39
+ gb: number;
40
+ toString: () => string;
41
+ }
42
+
43
+ export interface Segment {
44
+ url: string;
45
+ range?: string;
46
+ init?: boolean;
47
+ duration?: number;
48
+ number?: number;
49
+ presentationTime?: number;
50
+ }
51
+
52
+ export interface TrackProtection {
53
+ common?: { id: string; value: 'cenc' | 'cbcs'; keyId?: string };
54
+ playready?: { id: string; pssh?: string; value?: string };
55
+ widevine?: { id: string; pssh: string };
56
+ fairplay?: { keyFormat: string; uri: string; method: string };
57
+ }
58
+
59
+ export type VideoCodec = 'H.264' | 'H.265' | 'VC-1' | 'VP8' | 'VP9' | 'AV1';
60
+ export type DynamicRange = 'SDR' | 'HLG' | 'HDR10' | 'HDR10+' | 'DV';
61
+
62
+ export interface VideoTrack extends Track {
63
+ type: 'video';
64
+ codec: VideoCodec;
65
+ bitrate: Bitrate;
66
+ width: number;
67
+ height: number;
68
+ quality: '144p' | '240p' | '360p' | '480p' | '720p' | '1080p' | '2160p' | '4320p' | string;
69
+ dynamicRange: DynamicRange;
70
+ fps?: string;
71
+ language?: string;
72
+ }
73
+
74
+ export type AudioCodec = 'AAC' | 'DD' | 'DD+' | 'OPUS' | 'VORB' | 'DTS' | 'ALAC' | 'FLAC';
75
+
76
+ export interface AudioTrack extends Track {
77
+ type: 'audio';
78
+ codec: AudioCodec;
79
+ bitrate: Bitrate;
80
+ language: string;
81
+ channels?: number;
82
+ jointObjectCoding?: number;
83
+ isDescriptive?: boolean;
84
+ }
85
+
86
+ export type SubtitleCodec = 'SRT' | 'SSA' | 'ASS' | 'TTML' | 'VTT' | 'STPP' | 'fTTML' | 'fVTT';
87
+
88
+ export interface SubtitleTrack extends Track {
89
+ type: 'text';
90
+ codec: SubtitleCodec;
91
+ bitrate?: Bitrate;
92
+ language: string;
93
+ isClosedCaption?: boolean;
94
+ isSdh?: boolean;
95
+ isForced?: boolean;
96
+ }
97
+
98
+ export type AnyTrack = VideoTrack | AudioTrack | SubtitleTrack;
package/lib/constants.js DELETED
@@ -1,16 +0,0 @@
1
- const CONTENT_TYPE = {
2
- video: 'video',
3
- audio: 'audio',
4
- text: 'text',
5
- image: 'image',
6
- application: 'application',
7
- };
8
-
9
- const KEY_SYSTEMS = {
10
- 'org.w3.clearkey': 'urn:uuid:1077efec-c0b2-4d02-ace3-3c1e52e2fb4b',
11
- 'com.widevine.alpha': 'urn:uuid:edef8ba9-79d6-4ace-a3c8-27dcd51d21ed',
12
- 'com.microsoft.playready': 'urn:uuid:9a04f079-9840-4286-ab92-e65be0885f95',
13
- 'com.adobe.primetime': 'urn:uuid:f239e769-efa3-4850-9c16-a903c6932efb',
14
- };
15
-
16
- module.exports = { CONTENT_TYPE, KEY_SYSTEMS };