dasha 3.0.3 → 3.0.5
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 +12 -4
- package/dasha.js +15 -1
- package/lib/dash.js +23 -9
- 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 +11 -10
- package/types/dasha.d.ts +13 -0
package/README.md
CHANGED
|
@@ -1,10 +1,10 @@
|
|
|
1
1
|
# dasha
|
|
2
2
|
|
|
3
|
-
[](https://www.npmjs.com/package/dasha)
|
|
4
|
+
[](https://www.npmjs.com/package/dasha)
|
|
5
|
+
[](https://www.npmjs.com/package/dasha)
|
|
6
6
|
|
|
7
|
-
Library for parsing MPEG-DASH and HLS manifests. Made with the purpose of obtaining a simplified representation convenient for further downloading of segments.
|
|
7
|
+
Library for parsing MPEG-DASH (.mpd) and HLS (.m3u8) manifests. Made with the purpose of obtaining a simplified representation convenient for further downloading of segments.
|
|
8
8
|
|
|
9
9
|
## Install
|
|
10
10
|
|
|
@@ -15,9 +15,17 @@ npm i dasha
|
|
|
15
15
|
## Quick start
|
|
16
16
|
|
|
17
17
|
```js
|
|
18
|
+
import fs from 'node:fs/promises';
|
|
18
19
|
import { parse } from 'dasha';
|
|
19
20
|
|
|
20
21
|
const url = 'https://dash.akamaized.net/dash264/TestCases/1a/sony/SNE_DASH_SD_CASE1A_REVISED.mpd';
|
|
21
22
|
const body = await fetch(url).then((res) => res.text());
|
|
22
23
|
const manifest = await parse(body, url);
|
|
24
|
+
|
|
25
|
+
for (const track of manifest.tracks.all) {
|
|
26
|
+
for (const segment of track.segments) {
|
|
27
|
+
const content = await fetch(url).then((res) => res.arrayBuffer());
|
|
28
|
+
await fs.appendFile(`${track.id}.mp4`, content);
|
|
29
|
+
}
|
|
30
|
+
}
|
|
23
31
|
```
|
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) => {
|
|
@@ -87,7 +90,7 @@ const parseLanguage = (representation, adaptationSet, fallbackLanguage) => {
|
|
|
87
90
|
options.push(lang);
|
|
88
91
|
if (id) {
|
|
89
92
|
const m = id.match(/\w+_(\w+)=\d+/);
|
|
90
|
-
if (m) options.push(m
|
|
93
|
+
if (m && m[1]) options.push(m[1]);
|
|
91
94
|
}
|
|
92
95
|
}
|
|
93
96
|
options.push(adaptationSet.get('lang'));
|
|
@@ -173,10 +176,14 @@ const parseSegmentsFromTemplate = (
|
|
|
173
176
|
const startNumber = Number(segmentTemplate.get('startNumber') || 1);
|
|
174
177
|
const segmentTimeline = segmentTemplate.get('SegmentTimeline');
|
|
175
178
|
resolveSegmentTemplateUrls(segmentTemplate, baseUrl, manifestUrl);
|
|
176
|
-
if (!duration) throw new Error('Duration of the Period was unable to be determined.');
|
|
177
179
|
const segmentDuration = parseFloat(segmentTemplate.get('duration'));
|
|
178
180
|
const segmentTimescale = parseFloat(segmentTemplate.get('timescale') || 1);
|
|
179
|
-
|
|
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;
|
|
180
187
|
const bandwidth = representation.get('bandwidth');
|
|
181
188
|
const id = representation.get('id');
|
|
182
189
|
const segments = [];
|
|
@@ -222,12 +229,13 @@ const parseSegmentFromBase = async (segmentBase, baseUrl) => {
|
|
|
222
229
|
const initialization = segmentBase.get('Initialization');
|
|
223
230
|
let mediaRange = '';
|
|
224
231
|
if (initialization) {
|
|
225
|
-
const range = initialization.get('range');
|
|
226
|
-
const headers = range ? { Range: `bytes=${range}` } : undefined;
|
|
227
|
-
const response = await fetch(baseUrl, headers);
|
|
228
|
-
const initData = await response.arrayBuffer();
|
|
229
|
-
|
|
230
|
-
|
|
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}`;
|
|
231
239
|
}
|
|
232
240
|
return { url: baseUrl, range: mediaRange };
|
|
233
241
|
};
|
|
@@ -398,6 +406,9 @@ const parseManifest = async (text, url, fallbackLanguage) => {
|
|
|
398
406
|
}
|
|
399
407
|
}
|
|
400
408
|
}
|
|
409
|
+
|
|
410
|
+
videos.sort((a, b) => b.bitrate.bps - a.bitrate.bps);
|
|
411
|
+
|
|
401
412
|
return {
|
|
402
413
|
duration,
|
|
403
414
|
tracks: {
|
|
@@ -406,8 +417,11 @@ const parseManifest = async (text, url, fallbackLanguage) => {
|
|
|
406
417
|
audios,
|
|
407
418
|
subtitles,
|
|
408
419
|
withResolution: createResolutionFilter(videos),
|
|
420
|
+
withVideoCodecs: createVideoCodecFilter(videos),
|
|
409
421
|
withVideoQuality: createVideoQualityFilter(videos),
|
|
422
|
+
withAudioCodecs: createAudioCodecFilter(audios),
|
|
410
423
|
withAudioLanguages: createAudioLanguageFilter(audios),
|
|
424
|
+
withAudioChannels: createAudioChannelsFilter(audios),
|
|
411
425
|
withSubtitleLanguages: createSubtitleLanguageFilter(subtitles),
|
|
412
426
|
},
|
|
413
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,8 +1,8 @@
|
|
|
1
1
|
{
|
|
2
2
|
"name": "dasha",
|
|
3
|
-
"version": "3.0.
|
|
3
|
+
"version": "3.0.5",
|
|
4
4
|
"author": "Vitaly Gashkov <vitalygashkov@vk.com>",
|
|
5
|
-
"description": "
|
|
5
|
+
"description": "Streaming manifest parser",
|
|
6
6
|
"license": "AGPL-3.0",
|
|
7
7
|
"keywords": [
|
|
8
8
|
"mpeg",
|
|
@@ -11,7 +11,8 @@
|
|
|
11
11
|
"adaptive",
|
|
12
12
|
"mpd",
|
|
13
13
|
"m3u8",
|
|
14
|
-
"manifest"
|
|
14
|
+
"manifest",
|
|
15
|
+
"playlist"
|
|
15
16
|
],
|
|
16
17
|
"readmeFilename": "README.md",
|
|
17
18
|
"repository": {
|
|
@@ -45,16 +46,16 @@
|
|
|
45
46
|
"build:bun": "bun build ./dasha.js --outdir ./dist --format cjs"
|
|
46
47
|
},
|
|
47
48
|
"dependencies": {
|
|
48
|
-
"m3u8-parser": "^7.
|
|
49
|
+
"m3u8-parser": "^7.2.0"
|
|
49
50
|
},
|
|
50
51
|
"devDependencies": {
|
|
51
|
-
"@types/node": "^
|
|
52
|
+
"@types/node": "^22.5.5",
|
|
52
53
|
"eslint": "^8.57.0",
|
|
53
54
|
"eslint-config-prettier": "^9.1.0",
|
|
54
|
-
"eslint-plugin-import": "^2.
|
|
55
|
-
"eslint-plugin-prettier": "^5.1
|
|
56
|
-
"prettier": "^3.
|
|
57
|
-
"tsup": "^8.
|
|
58
|
-
"typescript": "^5.
|
|
55
|
+
"eslint-plugin-import-x": "^4.2.1",
|
|
56
|
+
"eslint-plugin-prettier": "^5.2.1",
|
|
57
|
+
"prettier": "^3.3.3",
|
|
58
|
+
"tsup": "^8.2.4",
|
|
59
|
+
"typescript": "^5.6.2"
|
|
59
60
|
}
|
|
60
61
|
}
|
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';
|