dasha 3.0.2 → 3.0.4
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/README.md +13 -0
- package/dasha.js +15 -1
- package/lib/dash.js +29 -19
- package/lib/hls.js +91 -27
- package/lib/subtitle.js +1 -0
- package/lib/track.js +38 -1
- package/lib/util.js +1 -0
- package/lib/xml.js +6 -10
- package/package.json +6 -6
- package/types/dasha.d.ts +13 -0
package/README.md
CHANGED
|
@@ -6,6 +6,11 @@
|
|
|
6
6
|
|
|
7
7
|
Library for parsing MPEG-DASH and HLS manifests. Made with the purpose of obtaining a simplified representation convenient for further downloading of segments.
|
|
8
8
|
|
|
9
|
+
<div align="left">
|
|
10
|
+
<span>English</span> •
|
|
11
|
+
<a href="https://github.com/vitalygashkov/dasha/tree/main/docs/README.ru.md">Pусский</a>
|
|
12
|
+
</div>
|
|
13
|
+
|
|
9
14
|
## Install
|
|
10
15
|
|
|
11
16
|
```shell
|
|
@@ -15,9 +20,17 @@ npm i dasha
|
|
|
15
20
|
## Quick start
|
|
16
21
|
|
|
17
22
|
```js
|
|
23
|
+
import fs from 'node:fs/promises';
|
|
18
24
|
import { parse } from 'dasha';
|
|
19
25
|
|
|
20
26
|
const url = 'https://dash.akamaized.net/dash264/TestCases/1a/sony/SNE_DASH_SD_CASE1A_REVISED.mpd';
|
|
21
27
|
const body = await fetch(url).then((res) => res.text());
|
|
22
28
|
const manifest = await parse(body, url);
|
|
29
|
+
|
|
30
|
+
for (const track of manifest.tracks.all) {
|
|
31
|
+
for (const segment of track.segments) {
|
|
32
|
+
const content = await fetch(url).then((res) => res.arrayBuffer());
|
|
33
|
+
await fs.appendFile(`${track.id}.mp4`, content);
|
|
34
|
+
}
|
|
35
|
+
}
|
|
23
36
|
```
|
package/dasha.js
CHANGED
|
@@ -2,6 +2,13 @@
|
|
|
2
2
|
|
|
3
3
|
const dash = require('./lib/dash');
|
|
4
4
|
const hls = require('./lib/hls');
|
|
5
|
+
const {
|
|
6
|
+
filterByResolution,
|
|
7
|
+
filterByQuality,
|
|
8
|
+
filterByCodecs,
|
|
9
|
+
filterByLanguages,
|
|
10
|
+
filterByChannels,
|
|
11
|
+
} = require('./lib/track');
|
|
5
12
|
|
|
6
13
|
const parse = (text, url, fallbackLanguage) => {
|
|
7
14
|
if (text.includes('<MPD')) return dash.parseManifest(text, url, fallbackLanguage);
|
|
@@ -9,4 +16,11 @@ const parse = (text, url, fallbackLanguage) => {
|
|
|
9
16
|
else throw new Error('Invalid manifest');
|
|
10
17
|
};
|
|
11
18
|
|
|
12
|
-
module.exports = {
|
|
19
|
+
module.exports = {
|
|
20
|
+
parse,
|
|
21
|
+
filterByResolution,
|
|
22
|
+
filterByQuality,
|
|
23
|
+
filterByCodecs,
|
|
24
|
+
filterByLanguages,
|
|
25
|
+
filterByChannels,
|
|
26
|
+
};
|
package/lib/dash.js
CHANGED
|
@@ -21,6 +21,9 @@ const {
|
|
|
21
21
|
createVideoQualityFilter,
|
|
22
22
|
createAudioLanguageFilter,
|
|
23
23
|
createSubtitleLanguageFilter,
|
|
24
|
+
createVideoCodecFilter,
|
|
25
|
+
createAudioCodecFilter,
|
|
26
|
+
createAudioChannelsFilter,
|
|
24
27
|
} = require('./track');
|
|
25
28
|
|
|
26
29
|
const appendUtils = (element) => {
|
|
@@ -56,16 +59,12 @@ const combineGetters = (representation, adaptationSet) => {
|
|
|
56
59
|
};
|
|
57
60
|
|
|
58
61
|
const parseBaseUrl = (manifestUrl, mpd, period, representation) => {
|
|
59
|
-
let
|
|
60
|
-
if (!
|
|
61
|
-
else if (!
|
|
62
|
-
|
|
63
|
-
const
|
|
64
|
-
|
|
65
|
-
representation.getBaseUrl() || '',
|
|
66
|
-
periodBaseUrl
|
|
67
|
-
).toString();
|
|
68
|
-
return representationBaseUrl;
|
|
62
|
+
let base = mpd.getBaseUrl();
|
|
63
|
+
if (!base) base = manifestUrl;
|
|
64
|
+
else if (!base.startsWith('https://')) base = new URL(base, manifestUrl).toString();
|
|
65
|
+
if (!!period.getBaseUrl() || !!base) base = new URL(period.getBaseUrl() || '', base).toString();
|
|
66
|
+
const baseUrl = new URL(representation.getBaseUrl() || '', base).toString();
|
|
67
|
+
return baseUrl;
|
|
69
68
|
};
|
|
70
69
|
|
|
71
70
|
const parseContentTypes = (representation) => {
|
|
@@ -91,7 +90,7 @@ const parseLanguage = (representation, adaptationSet, fallbackLanguage) => {
|
|
|
91
90
|
options.push(lang);
|
|
92
91
|
if (id) {
|
|
93
92
|
const m = id.match(/\w+_(\w+)=\d+/);
|
|
94
|
-
if (m) options.push(m
|
|
93
|
+
if (m && m[1]) options.push(m[1]);
|
|
95
94
|
}
|
|
96
95
|
}
|
|
97
96
|
options.push(adaptationSet.get('lang'));
|
|
@@ -177,10 +176,14 @@ const parseSegmentsFromTemplate = (
|
|
|
177
176
|
const startNumber = Number(segmentTemplate.get('startNumber') || 1);
|
|
178
177
|
const segmentTimeline = segmentTemplate.get('SegmentTimeline');
|
|
179
178
|
resolveSegmentTemplateUrls(segmentTemplate, baseUrl, manifestUrl);
|
|
180
|
-
if (!duration) throw new Error('Duration of the Period was unable to be determined.');
|
|
181
179
|
const segmentDuration = parseFloat(segmentTemplate.get('duration'));
|
|
182
180
|
const segmentTimescale = parseFloat(segmentTemplate.get('timescale') || 1);
|
|
183
|
-
|
|
181
|
+
// TODO: Support live manifests with type=dynamic
|
|
182
|
+
const DEFAULT_SEGMENTS_COUNT = 35;
|
|
183
|
+
// if (!duration) throw new Error('Duration of the Period was unable to be determined.');
|
|
184
|
+
const segmentsCount = duration
|
|
185
|
+
? Math.ceil(duration / (segmentDuration / segmentTimescale))
|
|
186
|
+
: DEFAULT_SEGMENTS_COUNT;
|
|
184
187
|
const bandwidth = representation.get('bandwidth');
|
|
185
188
|
const id = representation.get('id');
|
|
186
189
|
const segments = [];
|
|
@@ -226,12 +229,13 @@ const parseSegmentFromBase = async (segmentBase, baseUrl) => {
|
|
|
226
229
|
const initialization = segmentBase.get('Initialization');
|
|
227
230
|
let mediaRange = '';
|
|
228
231
|
if (initialization) {
|
|
229
|
-
const range = initialization.get('range');
|
|
230
|
-
const headers = range ? { Range: `bytes=${range}` } : undefined;
|
|
231
|
-
const response = await fetch(baseUrl, headers);
|
|
232
|
-
const initData = await response.arrayBuffer();
|
|
233
|
-
|
|
234
|
-
|
|
232
|
+
// const range = initialization.get('range');
|
|
233
|
+
// const headers = range ? { Range: `bytes=${range}` } : undefined;
|
|
234
|
+
// const response = await fetch(baseUrl, headers);
|
|
235
|
+
// const initData = await response.arrayBuffer();
|
|
236
|
+
// console.log(response.headers);
|
|
237
|
+
// const totalSize = response.headers.get('Content-Range').split('/')[-1];
|
|
238
|
+
// if (totalSize) mediaRange = `${initData.byteLength}-${totalSize}`;
|
|
235
239
|
}
|
|
236
240
|
return { url: baseUrl, range: mediaRange };
|
|
237
241
|
};
|
|
@@ -402,6 +406,9 @@ const parseManifest = async (text, url, fallbackLanguage) => {
|
|
|
402
406
|
}
|
|
403
407
|
}
|
|
404
408
|
}
|
|
409
|
+
|
|
410
|
+
videos.sort((a, b) => b.bitrate.bps - a.bitrate.bps);
|
|
411
|
+
|
|
405
412
|
return {
|
|
406
413
|
duration,
|
|
407
414
|
tracks: {
|
|
@@ -410,8 +417,11 @@ const parseManifest = async (text, url, fallbackLanguage) => {
|
|
|
410
417
|
audios,
|
|
411
418
|
subtitles,
|
|
412
419
|
withResolution: createResolutionFilter(videos),
|
|
420
|
+
withVideoCodecs: createVideoCodecFilter(videos),
|
|
413
421
|
withVideoQuality: createVideoQualityFilter(videos),
|
|
422
|
+
withAudioCodecs: createAudioCodecFilter(audios),
|
|
414
423
|
withAudioLanguages: createAudioLanguageFilter(audios),
|
|
424
|
+
withAudioChannels: createAudioChannelsFilter(audios),
|
|
415
425
|
withSubtitleLanguages: createSubtitleLanguageFilter(subtitles),
|
|
416
426
|
},
|
|
417
427
|
};
|
package/lib/hls.js
CHANGED
|
@@ -1,5 +1,6 @@
|
|
|
1
1
|
'use strict';
|
|
2
2
|
|
|
3
|
+
const { dirname, basename } = require('node:path');
|
|
3
4
|
const m3u8Parser = require('m3u8-parser');
|
|
4
5
|
const { parseBitrate, getQualityLabel } = require('./util');
|
|
5
6
|
const {
|
|
@@ -7,7 +8,12 @@ const {
|
|
|
7
8
|
createVideoQualityFilter,
|
|
8
9
|
createAudioLanguageFilter,
|
|
9
10
|
createSubtitleLanguageFilter,
|
|
11
|
+
createVideoCodecFilter,
|
|
12
|
+
createAudioCodecFilter,
|
|
13
|
+
createAudioChannelsFilter,
|
|
10
14
|
} = require('./track');
|
|
15
|
+
const { createAudioTrack } = require('./audio');
|
|
16
|
+
const { createVideoTrack } = require('./video');
|
|
11
17
|
|
|
12
18
|
const parseM3u8 = (manifestString) => {
|
|
13
19
|
const parser = new m3u8Parser.Parser();
|
|
@@ -22,9 +28,9 @@ const fetchPlaylist = async (url) =>
|
|
|
22
28
|
.then(parseM3u8);
|
|
23
29
|
|
|
24
30
|
const parseUrl = (playlistUri, manifestUri) => {
|
|
25
|
-
|
|
26
|
-
|
|
27
|
-
return
|
|
31
|
+
let value = playlistUri;
|
|
32
|
+
if (!value.startsWith('https://')) value = new URL(value, manifestUri).toString();
|
|
33
|
+
return value;
|
|
28
34
|
};
|
|
29
35
|
|
|
30
36
|
const urlsSame = (url1, url2) => {
|
|
@@ -51,18 +57,22 @@ const parseMediaGroup = (groups, manifestUri) => {
|
|
|
51
57
|
};
|
|
52
58
|
|
|
53
59
|
const getAudioPlaylists = (m3u8, manifestUri) => {
|
|
60
|
+
if (!m3u8.mediaGroups) return [];
|
|
54
61
|
return parseMediaGroup(m3u8.mediaGroups.AUDIO, manifestUri);
|
|
55
62
|
};
|
|
56
63
|
|
|
57
64
|
const getSubtitlePlaylists = (m3u8, manifestUri) => {
|
|
65
|
+
if (!m3u8.mediaGroups) return [];
|
|
58
66
|
return parseMediaGroup(m3u8.mediaGroups.SUBTITLES, manifestUri);
|
|
59
67
|
};
|
|
60
68
|
|
|
61
69
|
const getVideoPlaylists = (m3u8, manifestUri) => {
|
|
70
|
+
if (!m3u8.playlists) return [];
|
|
62
71
|
return m3u8.playlists.map((data) => {
|
|
63
|
-
const bandwidth = data.attributes
|
|
72
|
+
const bandwidth = data.attributes?.BANDWIDTH;
|
|
64
73
|
const url = data.resolvedUri || parseUrl(data.uri, manifestUri);
|
|
65
74
|
const track = { bitrate: parseBitrate(bandwidth), url };
|
|
75
|
+
track.type = 'video';
|
|
66
76
|
if (data.attributes.RESOLUTION) {
|
|
67
77
|
track.resolution = data.attributes.RESOLUTION;
|
|
68
78
|
track.quality = getQualityLabel(track.resolution);
|
|
@@ -73,13 +83,20 @@ const getVideoPlaylists = (m3u8, manifestUri) => {
|
|
|
73
83
|
});
|
|
74
84
|
};
|
|
75
85
|
|
|
76
|
-
const segmentsDto = (data = []) => {
|
|
77
|
-
const mapSegment = (item) =>
|
|
78
|
-
url
|
|
79
|
-
|
|
80
|
-
|
|
81
|
-
|
|
82
|
-
|
|
86
|
+
const segmentsDto = (data = [], track) => {
|
|
87
|
+
const mapSegment = (item) => {
|
|
88
|
+
let url = item.resolvedUri || item.uri;
|
|
89
|
+
if (!url.startsWith('https://')) {
|
|
90
|
+
const baseUrl = dirname(track.url) + '/';
|
|
91
|
+
url = new URL(url, baseUrl).toString();
|
|
92
|
+
}
|
|
93
|
+
return {
|
|
94
|
+
url,
|
|
95
|
+
duration: item.duration,
|
|
96
|
+
number: item.number,
|
|
97
|
+
presentationTime: item.presentationTime,
|
|
98
|
+
};
|
|
99
|
+
};
|
|
83
100
|
const segments = data.map(mapSegment);
|
|
84
101
|
if (data.length && data[0].map?.resolvedUri)
|
|
85
102
|
segments.unshift({
|
|
@@ -92,21 +109,25 @@ const segmentsDto = (data = []) => {
|
|
|
92
109
|
return segments;
|
|
93
110
|
};
|
|
94
111
|
|
|
112
|
+
const parseSegments = (playlist, track) => {
|
|
113
|
+
track.segments = segmentsDto(playlist.segments, track);
|
|
114
|
+
if (playlist.contentProtection) {
|
|
115
|
+
track.protection = {};
|
|
116
|
+
const fairplayLegacy = playlist.contentProtection['com.apple.fps.1_0'];
|
|
117
|
+
if (fairplayLegacy)
|
|
118
|
+
track.protection.fairplay = {
|
|
119
|
+
keyFormat: fairplayLegacy.attributes.KEYFORMAT,
|
|
120
|
+
uri: fairplayLegacy.attributes.URI,
|
|
121
|
+
method: fairplayLegacy.attributes.METHOD,
|
|
122
|
+
};
|
|
123
|
+
}
|
|
124
|
+
};
|
|
125
|
+
|
|
95
126
|
const fetchTrackSegments = (tracks) => {
|
|
96
127
|
return Promise.all(
|
|
97
128
|
tracks.map(async (track) => {
|
|
98
129
|
const playlist = await fetchPlaylist(track.url);
|
|
99
|
-
|
|
100
|
-
if (playlist.contentProtection) {
|
|
101
|
-
track.protection = {};
|
|
102
|
-
const fairplayLegacy = playlist.contentProtection['com.apple.fps.1_0'];
|
|
103
|
-
if (fairplayLegacy)
|
|
104
|
-
track.protection.fairplay = {
|
|
105
|
-
keyFormat: fairplayLegacy.attributes.KEYFORMAT,
|
|
106
|
-
uri: fairplayLegacy.attributes.URI,
|
|
107
|
-
method: fairplayLegacy.attributes.METHOD,
|
|
108
|
-
};
|
|
109
|
-
}
|
|
130
|
+
parseSegments(playlist, track);
|
|
110
131
|
})
|
|
111
132
|
);
|
|
112
133
|
};
|
|
@@ -117,11 +138,51 @@ const parseManifest = async (manifestString, manifestUri) => {
|
|
|
117
138
|
const audios = getAudioPlaylists(m3u8, manifestUri);
|
|
118
139
|
const subtitles = getSubtitlePlaylists(m3u8, manifestUri);
|
|
119
140
|
|
|
120
|
-
|
|
121
|
-
|
|
122
|
-
|
|
123
|
-
|
|
124
|
-
|
|
141
|
+
if (!m3u8.playlists && m3u8.segments) {
|
|
142
|
+
// TODO: Handle audio-only manifests
|
|
143
|
+
const { pathname } = new URL(manifestUri);
|
|
144
|
+
const isAudio =
|
|
145
|
+
pathname.includes('.m4a') || pathname.includes('.mp3') || pathname.includes('.opus');
|
|
146
|
+
if (isAudio) {
|
|
147
|
+
const track = createAudioTrack({
|
|
148
|
+
id: 'audio' + basename(pathname),
|
|
149
|
+
label: 'audio',
|
|
150
|
+
type: 'audio',
|
|
151
|
+
codec: '',
|
|
152
|
+
channels: 2,
|
|
153
|
+
jointObjectCoding: '',
|
|
154
|
+
isDescriptive: false,
|
|
155
|
+
bitrate: NaN,
|
|
156
|
+
duration: NaN,
|
|
157
|
+
language: '',
|
|
158
|
+
});
|
|
159
|
+
parseSegments(m3u8, track);
|
|
160
|
+
audios.push(track);
|
|
161
|
+
} else {
|
|
162
|
+
const track = createVideoTrack({
|
|
163
|
+
id: 'video' + basename(pathname),
|
|
164
|
+
label: 'video',
|
|
165
|
+
type: 'video',
|
|
166
|
+
codec: '',
|
|
167
|
+
dynamicRange: '',
|
|
168
|
+
contentProtection: '',
|
|
169
|
+
bitrate: NaN,
|
|
170
|
+
duration: NaN,
|
|
171
|
+
width: NaN,
|
|
172
|
+
height: NaN,
|
|
173
|
+
fps: NaN,
|
|
174
|
+
language: '',
|
|
175
|
+
});
|
|
176
|
+
parseSegments(m3u8, track);
|
|
177
|
+
videos.push(track);
|
|
178
|
+
}
|
|
179
|
+
} else {
|
|
180
|
+
await Promise.all([
|
|
181
|
+
fetchTrackSegments(videos),
|
|
182
|
+
fetchTrackSegments(audios),
|
|
183
|
+
fetchTrackSegments(subtitles),
|
|
184
|
+
]);
|
|
185
|
+
}
|
|
125
186
|
|
|
126
187
|
const manifest = {
|
|
127
188
|
tracks: {
|
|
@@ -130,8 +191,11 @@ const parseManifest = async (manifestString, manifestUri) => {
|
|
|
130
191
|
audios,
|
|
131
192
|
subtitles,
|
|
132
193
|
withResolution: createResolutionFilter(videos),
|
|
194
|
+
withVideoCodecs: createVideoCodecFilter(videos),
|
|
133
195
|
withVideoQuality: createVideoQualityFilter(videos),
|
|
196
|
+
withAudioCodecs: createAudioCodecFilter(audios),
|
|
134
197
|
withAudioLanguages: createAudioLanguageFilter(audios),
|
|
198
|
+
withAudioChannels: createAudioChannelsFilter(audios),
|
|
135
199
|
withSubtitleLanguages: createSubtitleLanguageFilter(subtitles),
|
|
136
200
|
},
|
|
137
201
|
};
|
package/lib/subtitle.js
CHANGED
package/lib/track.js
CHANGED
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
'use strict';
|
|
2
2
|
|
|
3
|
-
const {
|
|
3
|
+
const { getBestTrack } = require('./util');
|
|
4
4
|
|
|
5
5
|
const parseMimes = (codecs) =>
|
|
6
6
|
codecs
|
|
@@ -16,15 +16,29 @@ const createResolutionFilter = (videos) => {
|
|
|
16
16
|
};
|
|
17
17
|
};
|
|
18
18
|
|
|
19
|
+
const createCodecFilter = (tracks) => {
|
|
20
|
+
return (codecs) => {
|
|
21
|
+
if (!codecs?.length) return tracks;
|
|
22
|
+
const results = tracks.filter((track) => codecs.includes(track.codec));
|
|
23
|
+
results.sort((a, b) => b.bitrate.bps - a.bitrate.bps);
|
|
24
|
+
return results;
|
|
25
|
+
};
|
|
26
|
+
};
|
|
27
|
+
|
|
28
|
+
const createVideoCodecFilter = (videos) => createCodecFilter(videos);
|
|
29
|
+
|
|
19
30
|
const createVideoQualityFilter = (videos) => {
|
|
20
31
|
return (quality) => {
|
|
21
32
|
if (!quality) return [getBestTrack(videos)];
|
|
22
33
|
const trackQuality = String(quality).includes('p') ? quality : `${quality}p`;
|
|
23
34
|
const results = videos.filter((track) => track.quality === trackQuality);
|
|
35
|
+
results.sort((a, b) => b.bitrate.bps - a.bitrate.bps);
|
|
24
36
|
return results.length ? results : [getBestTrack(videos)];
|
|
25
37
|
};
|
|
26
38
|
};
|
|
27
39
|
|
|
40
|
+
const createAudioCodecFilter = (audios) => createCodecFilter(audios);
|
|
41
|
+
|
|
28
42
|
const createAudioLanguageFilter = (audios) => {
|
|
29
43
|
return (languages = [], maxTracksPerLanguage) => {
|
|
30
44
|
if (!languages.length) {
|
|
@@ -50,6 +64,14 @@ const createAudioLanguageFilter = (audios) => {
|
|
|
50
64
|
};
|
|
51
65
|
};
|
|
52
66
|
|
|
67
|
+
const createAudioChannelsFilter = (audios) => {
|
|
68
|
+
return (channels) => {
|
|
69
|
+
if (!channels) return audios;
|
|
70
|
+
const value = typeof channels === 'string' ? parseFloat(channels) : channels;
|
|
71
|
+
return audios.filter((track) => track.channels === value);
|
|
72
|
+
};
|
|
73
|
+
};
|
|
74
|
+
|
|
53
75
|
const createSubtitleLanguageFilter = (subtitles) => {
|
|
54
76
|
return (languages) => {
|
|
55
77
|
if (!languages.length) return subtitles;
|
|
@@ -61,10 +83,25 @@ const createSubtitleLanguageFilter = (subtitles) => {
|
|
|
61
83
|
};
|
|
62
84
|
};
|
|
63
85
|
|
|
86
|
+
const filterByResolution = (tracks, resolution) => createResolutionFilter(tracks)(resolution);
|
|
87
|
+
const filterByQuality = (tracks, quality) => createVideoQualityFilter(tracks)(quality);
|
|
88
|
+
const filterByCodecs = (tracks, codecs) => createCodecFilter(tracks)(codecs);
|
|
89
|
+
const filterByLanguages = (tracks, languages, maxTracksPerLanguage) =>
|
|
90
|
+
createAudioLanguageFilter(tracks)(languages, maxTracksPerLanguage);
|
|
91
|
+
const filterByChannels = (tracks, channels) => createAudioChannelsFilter(tracks)(channels);
|
|
92
|
+
|
|
64
93
|
module.exports = {
|
|
65
94
|
parseMimes,
|
|
66
95
|
createResolutionFilter,
|
|
96
|
+
createVideoCodecFilter,
|
|
67
97
|
createVideoQualityFilter,
|
|
98
|
+
createAudioCodecFilter,
|
|
68
99
|
createAudioLanguageFilter,
|
|
100
|
+
createAudioChannelsFilter,
|
|
69
101
|
createSubtitleLanguageFilter,
|
|
102
|
+
filterByResolution,
|
|
103
|
+
filterByQuality,
|
|
104
|
+
filterByCodecs,
|
|
105
|
+
filterByLanguages,
|
|
106
|
+
filterByChannels,
|
|
70
107
|
};
|
package/lib/util.js
CHANGED
package/lib/xml.js
CHANGED
|
@@ -28,7 +28,7 @@ function parse(text, options = {}) {
|
|
|
28
28
|
|
|
29
29
|
var closeTag = text.substring(closeStart, pos);
|
|
30
30
|
if (closeTag.indexOf(tagName) == -1) {
|
|
31
|
-
|
|
31
|
+
const parsedText = text.substring(0, pos).split('\n');
|
|
32
32
|
throw new Error(
|
|
33
33
|
'Unexpected close tag\nLine: ' +
|
|
34
34
|
(parsedText.length - 1) +
|
|
@@ -103,7 +103,7 @@ function parse(text, options = {}) {
|
|
|
103
103
|
node.children = [];
|
|
104
104
|
}
|
|
105
105
|
} else {
|
|
106
|
-
|
|
106
|
+
const parsedText = parseText();
|
|
107
107
|
if (keepWhitespace) {
|
|
108
108
|
if (parsedText.length > 0) {
|
|
109
109
|
children.push(parsedText);
|
|
@@ -191,12 +191,12 @@ function parse(text, options = {}) {
|
|
|
191
191
|
// optional parsing of children
|
|
192
192
|
if (text.charCodeAt(pos - 1) !== slashCC) {
|
|
193
193
|
if (tagName == 'script') {
|
|
194
|
-
|
|
194
|
+
const start = pos + 1;
|
|
195
195
|
pos = text.indexOf('</script>', pos);
|
|
196
196
|
children = [text.slice(start, pos)];
|
|
197
197
|
pos += 9;
|
|
198
198
|
} else if (tagName == 'style') {
|
|
199
|
-
|
|
199
|
+
const start = pos + 1;
|
|
200
200
|
pos = text.indexOf('</style>', pos);
|
|
201
201
|
children = [text.slice(start, pos)];
|
|
202
202
|
pos += 8;
|
|
@@ -241,10 +241,10 @@ function parse(text, options = {}) {
|
|
|
241
241
|
}
|
|
242
242
|
}
|
|
243
243
|
|
|
244
|
-
|
|
244
|
+
let out = null;
|
|
245
245
|
if (options.attrValue !== undefined) {
|
|
246
246
|
options.attrName = options.attrName || 'id';
|
|
247
|
-
|
|
247
|
+
out = [];
|
|
248
248
|
|
|
249
249
|
while ((pos = findElements()) !== -1) {
|
|
250
250
|
pos = text.lastIndexOf('<', pos);
|
|
@@ -264,10 +264,6 @@ function parse(text, options = {}) {
|
|
|
264
264
|
out = filter(out, options.filter);
|
|
265
265
|
}
|
|
266
266
|
|
|
267
|
-
if (options.simplify) {
|
|
268
|
-
return simplify(Array.isArray(out) ? out : [out]);
|
|
269
|
-
}
|
|
270
|
-
|
|
271
267
|
if (options.setPos) {
|
|
272
268
|
out.pos = pos;
|
|
273
269
|
}
|
package/package.json
CHANGED
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
{
|
|
2
2
|
"name": "dasha",
|
|
3
|
-
"version": "3.0.
|
|
3
|
+
"version": "3.0.4",
|
|
4
4
|
"author": "Vitaly Gashkov <vitalygashkov@vk.com>",
|
|
5
5
|
"description": "Parser for MPEG-DASH & HLS manifests",
|
|
6
6
|
"license": "AGPL-3.0",
|
|
@@ -48,13 +48,13 @@
|
|
|
48
48
|
"m3u8-parser": "^7.1.0"
|
|
49
49
|
},
|
|
50
50
|
"devDependencies": {
|
|
51
|
-
"@types/node": "^
|
|
51
|
+
"@types/node": "^22.0.0",
|
|
52
52
|
"eslint": "^8.57.0",
|
|
53
53
|
"eslint-config-prettier": "^9.1.0",
|
|
54
|
-
"eslint-plugin-import": "^
|
|
54
|
+
"eslint-plugin-import-x": "^3.1.0",
|
|
55
55
|
"eslint-plugin-prettier": "^5.1.3",
|
|
56
|
-
"prettier": "^3.
|
|
57
|
-
"tsup": "^8.
|
|
58
|
-
"typescript": "^5.4
|
|
56
|
+
"prettier": "^3.3.3",
|
|
57
|
+
"tsup": "^8.2.3",
|
|
58
|
+
"typescript": "^5.5.4"
|
|
59
59
|
}
|
|
60
60
|
}
|
package/types/dasha.d.ts
CHANGED
|
@@ -8,12 +8,25 @@ export interface Manifest {
|
|
|
8
8
|
audios: AudioTrack[];
|
|
9
9
|
subtitles: SubtitleTrack[];
|
|
10
10
|
withResolution(resolution: { width?: string; height?: string }): VideoTrack[];
|
|
11
|
+
withVideoCodecs(codecs: VideoCodec[]): VideoTrack[];
|
|
11
12
|
withVideoQuality(quality: number | string): VideoTrack[];
|
|
13
|
+
withAudioCodecs(codecs: AudioCodec[]): AudioTrack[];
|
|
12
14
|
withAudioLanguages(languages: string[], maxTracksPerLanguage?: number): AudioTrack[];
|
|
13
15
|
withSubtitleLanguages(languages: string[]): SubtitleTrack[];
|
|
14
16
|
};
|
|
15
17
|
}
|
|
16
18
|
|
|
19
|
+
export function filterByResolution(resolution: { width?: string; height?: string }): VideoTrack[];
|
|
20
|
+
export function filterByCodecs(tracks: VideoTrack[], codecs: VideoCodec[]): VideoTrack[];
|
|
21
|
+
export function filterByCodecs(tracks: AudioTrack[], codecs: AudioCodec[]): AudioTrack[];
|
|
22
|
+
export function filterByQuality(tracks: VideoTrack[], quality: number | string): VideoTrack[];
|
|
23
|
+
export function filterByLanguages(
|
|
24
|
+
tracks: AudioTrack[],
|
|
25
|
+
languages: string[],
|
|
26
|
+
maxTracksPerLanguage?: number
|
|
27
|
+
): AudioTrack[];
|
|
28
|
+
export function filterByChannels(tracks: AudioTrack[], channels: number | string): AudioTrack[];
|
|
29
|
+
|
|
17
30
|
export interface Track {
|
|
18
31
|
id: string;
|
|
19
32
|
type: 'video' | 'audio' | 'text';
|