dasha 4.0.0-alpha.12 → 4.0.0-alpha.13

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 CHANGED
@@ -13,14 +13,14 @@ Library for parsing MPEG-DASH (.mpd) and HLS (.m3u8) manifests. Made with the pu
13
13
  ## Install
14
14
 
15
15
  ```shell
16
- npm i dasha@alpha
16
+ npm install dasha@alpha
17
17
  ```
18
18
 
19
19
  ## Usage
20
20
 
21
21
  ```js
22
22
  import fs from 'node:fs/promises';
23
- import { parse } from 'dasha';
23
+ import { StreamExtractor } from 'dasha';
24
24
 
25
25
  const url = 'https://dash.akamaized.net/dash264/TestCases/1a/sony/SNE_DASH_SD_CASE1A_REVISED.mpd';
26
26
  const streamExtractor = new StreamExtractor();
@@ -37,10 +37,47 @@ for (const stream of streams) {
37
37
  }
38
38
  ```
39
39
 
40
- ## Credits
40
+ ### New API (HLS-only for now)
41
+
42
+ ```ts
43
+ import fs from 'node:fs/promises';
44
+ import { getSegments, desc, HLS_FORMATS, Input, UrlSource } from 'dasha';
45
+
46
+ async function saveVideo() {
47
+ const input = new Input({
48
+ source: new UrlSource(
49
+ 'https://storage.googleapis.com/shaka-demo-assets/angel-one-widevine-hls/hls.m3u8',
50
+ { requestInit: { headers: { Referer: 'https://bitmovin.com/' } } },
51
+ ),
52
+ formats: HLS_FORMATS,
53
+ });
54
+
55
+ const videoTracks = await input.getVideoTracks({
56
+ sortBy: async (track) => [
57
+ desc(await track.getDisplayHeight()),
58
+ // Tracks with matching resolution are sorted by bitrate
59
+ desc(await track.getBitrate()),
60
+ ],
61
+ // Filter out #EXT-X-I-FRAME-STREAM-INF tracks
62
+ filter: async (track) => !(await track.hasOnlyKeyPackets()),
63
+ });
41
64
 
42
- This project is heavily influenced by the robust implementation found in [N_m3u8DL-RE](https://github.com/nilaoda/N_m3u8DL-RE). Special thanks to the open-source community and contributors who make projects like this possible.
65
+ const bestVideoTrack = videoTracks[0];
43
66
 
44
- ## Licenses
67
+ const segments = await getSegments(bestVideoTrack);
68
+
69
+ const outputPath = 'output.mp4';
70
+ const urls = segments.map((segment) => segment.location.path);
71
+ const initSegment = segments[0]?.initSegment;
72
+ if (initSegment) urls.unshift(initSegment.location.path);
73
+ for (const url of urls) {
74
+ const content = await fetch(url).then((res) => res.arrayBuffer());
75
+ await fs.appendFile(outputPath, new Uint8Array(content));
76
+ }
77
+ };
78
+ ```
79
+
80
+ ## Credits
45
81
 
46
- This project is licensed under the [MIT License](LICENSE).
82
+ [mediabunny](https://github.com/Vanilagy/mediabunny)
83
+ [N_m3u8DL-RE](https://github.com/nilaoda/N_m3u8DL-RE)
package/dist/dasha.d.mts CHANGED
@@ -1,3 +1,5 @@
1
+ import { HLS_FORMATS, Input, InputTrack as InputTrack$1, UrlSource, asc, desc } from "mediabunny";
2
+
1
3
  //#region lib/shared/codec.d.ts
2
4
  /**
3
5
  * List of known video codecs, ordered by encoding preference.
@@ -389,4 +391,51 @@ declare const HLS_TAGS: {
389
391
  */
390
392
  declare function getRange(input: string): [number, number | null];
391
393
  //#endregion
392
- export { ALL_STREAM_TYPES, AUDIO_CODECS, AudioCodec, AudioStreamInfo, ContentProcessor, DASH_TAGS, DashExtractor, DefaultDashContentProcessor, DefaultHlsContentProcessor, DefaultHlsKeyProcessor, DefaultUrlProcessor, ENCRYPT_METHODS, EXTRACTOR_TYPES, EncryptInfo, EncryptMethod, Extractor, ExtractorType, HLS_TAGS, HlsExtractor, KeyProcessor, MediaCodec, MediaPart, MediaSegment, MediaStreamInfo, ParserConfig, Playlist, ROLE_TYPE, RoleType, SUBTITLE_CODECS, StreamExtractor, StreamInfo, StreamType, SubtitleCodec, SubtitleStreamInfo, UrlProcessor, VIDEO_CODECS, VIDEO_DYNAMIC_RANGES, VideoCodec, VideoDynamicRange, VideoStreamInfo, getRange, parseRange };
394
+ //#region lib/mediabunny.d.ts
395
+ type Segment = {
396
+ timestamp: number;
397
+ duration: number;
398
+ relativeToUnixEpoch: boolean;
399
+ firstSegment: Segment | null;
400
+ };
401
+ type HlsSegmentLocation = {
402
+ path: string;
403
+ offset: number;
404
+ length: number | null;
405
+ };
406
+ type HlsEncryptionInfo = {
407
+ method: 'AES-128';
408
+ keyUri: string;
409
+ iv: Uint8Array | null;
410
+ keyFormat: string;
411
+ } | {
412
+ method: 'SAMPLE-AES' | 'SAMPLE-AES-CTR';
413
+ };
414
+ type HlsSegment = Segment & {
415
+ sequenceNumber: number | null;
416
+ location: HlsSegmentLocation;
417
+ encryption: HlsEncryptionInfo | null;
418
+ firstSegment: HlsSegment | null;
419
+ initSegment: HlsSegment | null;
420
+ lastProgramDateTimeSeconds: number | null;
421
+ };
422
+ type HlsSegmentedInput = {
423
+ segments: HlsSegment[];
424
+ runUpdateSegments(): Promise<void>;
425
+ };
426
+ type InputTrackWithBacking = InputTrack & {
427
+ _backing: {
428
+ internalTrack: {
429
+ fullPath: string;
430
+ demuxer: {
431
+ getSegmentedInputForPath(path: string): HlsSegmentedInput;
432
+ };
433
+ };
434
+ };
435
+ };
436
+ //#endregion
437
+ //#region lib/input.d.ts
438
+ declare const getSegmentedInput: (track: InputTrack$1) => HlsSegmentedInput;
439
+ declare const getSegments: (track: InputTrack$1) => Promise<HlsSegment[]>;
440
+ //#endregion
441
+ export { ALL_STREAM_TYPES, AUDIO_CODECS, AudioCodec, AudioStreamInfo, ContentProcessor, DASH_TAGS, DashExtractor, DefaultDashContentProcessor, DefaultHlsContentProcessor, DefaultHlsKeyProcessor, DefaultUrlProcessor, ENCRYPT_METHODS, EXTRACTOR_TYPES, EncryptInfo, EncryptMethod, Extractor, ExtractorType, HLS_FORMATS, HLS_TAGS, HlsExtractor, type HlsSegment, type HlsSegmentedInput, Input, InputTrack$1 as InputTrack, type InputTrackWithBacking, KeyProcessor, MediaCodec, MediaPart, MediaSegment, MediaStreamInfo, ParserConfig, Playlist, ROLE_TYPE, RoleType, SUBTITLE_CODECS, StreamExtractor, StreamInfo, StreamType, SubtitleCodec, SubtitleStreamInfo, UrlProcessor, UrlSource, VIDEO_CODECS, VIDEO_DYNAMIC_RANGES, VideoCodec, VideoDynamicRange, VideoStreamInfo, asc, desc, getRange, getSegmentedInput, getSegments, parseRange };
package/dist/dasha.mjs CHANGED
@@ -7,7 +7,7 @@ import { pathToFileURL } from "node:url";
7
7
  import path from "node:path";
8
8
  import { Temporal } from "temporal-polyfill";
9
9
  import { DOMParser } from "@xmldom/xmldom";
10
-
10
+ import { HLS_FORMATS, Input, InputTrack, UrlSource, asc, desc } from "mediabunny";
11
11
  //#region lib/shared/codec.ts
12
12
  /**
13
13
  * List of known video codecs, ordered by encoding preference.
@@ -65,7 +65,6 @@ const SUBTITLE_CODECS = [
65
65
  "stpp",
66
66
  "wvtt"
67
67
  ];
68
-
69
68
  //#endregion
70
69
  //#region lib/shared/encrypt-method.ts
71
70
  const ENCRYPT_METHODS = {
@@ -78,7 +77,6 @@ const ENCRYPT_METHODS = {
78
77
  CHACHA20: "chacha20",
79
78
  UNKNOWN: "unknown"
80
79
  };
81
-
82
80
  //#endregion
83
81
  //#region lib/shared/encrypt-info.ts
84
82
  var EncryptInfo = class {
@@ -95,7 +93,6 @@ var EncryptInfo = class {
95
93
  else return ENCRYPT_METHODS.UNKNOWN;
96
94
  }
97
95
  };
98
-
99
96
  //#endregion
100
97
  //#region lib/shared/extractor-type.ts
101
98
  const EXTRACTOR_TYPES = {
@@ -104,7 +101,6 @@ const EXTRACTOR_TYPES = {
104
101
  HTTP_LIVE: "HTTP_LIVE",
105
102
  MSS: "MSS"
106
103
  };
107
-
108
104
  //#endregion
109
105
  //#region lib/shared/media-part.ts
110
106
  var MediaPart = class {
@@ -113,7 +109,6 @@ var MediaPart = class {
113
109
  this.mediaSegments = segments || [];
114
110
  }
115
111
  };
116
-
117
112
  //#endregion
118
113
  //#region lib/shared/media-segment.ts
119
114
  var MediaSegment = class MediaSegment {
@@ -149,7 +144,6 @@ var MediaSegment = class MediaSegment {
149
144
  return crypto.createHash("md5").update(payload).digest("hex");
150
145
  }
151
146
  };
152
-
153
147
  //#endregion
154
148
  //#region lib/shared/playlist.ts
155
149
  var Playlist = class {
@@ -165,7 +159,6 @@ var Playlist = class {
165
159
  mediaInit;
166
160
  mediaParts = [];
167
161
  };
168
-
169
162
  //#endregion
170
163
  //#region lib/shared/role-type.ts
171
164
  const ROLE_TYPE = {
@@ -180,7 +173,6 @@ const ROLE_TYPE = {
180
173
  Metadata: 8,
181
174
  ForcedSubtitle: 9
182
175
  };
183
-
184
176
  //#endregion
185
177
  //#region lib/shared/stream-info.ts
186
178
  /**
@@ -324,7 +316,6 @@ var SubtitleStreamInfo = class extends StreamInfo {
324
316
  return text.trim();
325
317
  }
326
318
  };
327
-
328
319
  //#endregion
329
320
  //#region lib/processor.ts
330
321
  var DefaultUrlProcessor = class {
@@ -345,7 +336,6 @@ var DefaultUrlProcessor = class {
345
336
  return url;
346
337
  }
347
338
  };
348
-
349
339
  //#endregion
350
340
  //#region lib/dash/dash-content-processor.ts
351
341
  const namespaceMap = new Map([
@@ -370,7 +360,6 @@ var DefaultDashContentProcessor = class {
370
360
  return replaceFirst(mpdContent, "<MPD ", `<MPD ${missingNamespaceKeys.map((key) => `xmlns:${key}="${namespaceMap.get(key)}"`).join(" ")} `);
371
361
  }
372
362
  };
373
-
374
363
  //#endregion
375
364
  //#region lib/hls/hls-tags.ts
376
365
  const HLS_TAGS = {
@@ -401,7 +390,6 @@ const HLS_TAGS = {
401
390
  extXMap: "#EXT-X-MAP",
402
391
  extXStart: "#EXT-X-START"
403
392
  };
404
-
405
393
  //#endregion
406
394
  //#region lib/hls/hls-content-processor.ts
407
395
  var DefaultHlsContentProcessor = class DefaultHlsContentProcessor {
@@ -436,7 +424,6 @@ var DefaultHlsContentProcessor = class DefaultHlsContentProcessor {
436
424
  return content;
437
425
  }
438
426
  };
439
-
440
427
  //#endregion
441
428
  //#region lib/hls/hls-key-processor.ts
442
429
  var DefaultHlsKeyProcessor = class {
@@ -497,7 +484,6 @@ var DefaultHlsKeyProcessor = class {
497
484
  return processedUrl;
498
485
  }
499
486
  };
500
-
501
487
  //#endregion
502
488
  //#region lib/parser-config.ts
503
489
  var ParserConfig = class {
@@ -516,7 +502,6 @@ var ParserConfig = class {
516
502
  appendUrlParams = false;
517
503
  keyRetryCount = 3;
518
504
  };
519
-
520
505
  //#endregion
521
506
  //#region lib/dash/dash-tags.ts
522
507
  const DASH_TAGS = {
@@ -525,7 +510,6 @@ const DASH_TAGS = {
525
510
  TemplateNumber: "$Number$",
526
511
  TemplateTime: "$Time$"
527
512
  };
528
-
529
513
  //#endregion
530
514
  //#region lib/shared/util.ts
531
515
  const combineUrl = (baseUrl, relativeUrl) => {
@@ -579,7 +563,6 @@ const distinctBy = (array, callbackfn) => {
579
563
  });
580
564
  };
581
565
  const parseMimes = (codecs) => codecs.toLowerCase().split(",").map((codec) => codec.trim().split(".")[0]);
582
-
583
566
  //#endregion
584
567
  //#region lib/dash/dash-utils.ts
585
568
  /**
@@ -591,7 +574,6 @@ const parseRange = (range) => {
591
574
  const [startRange, end] = range.split("-").map(Number);
592
575
  return [startRange, end - startRange + 1];
593
576
  };
594
-
595
577
  //#endregion
596
578
  //#region lib/shared/video.ts
597
579
  const PRIMARIES = {
@@ -692,7 +674,6 @@ const parseDynamicRange = (codecs, supplementalProps = [], essentialProps = [])
692
674
  const getValues = (schemeIdUri) => allProps.filter((prop) => prop.schemeIdUri === schemeIdUri).map((prop) => parseInt(prop.value));
693
675
  return parseDynamicRangeFromCicp(getValues(primariesScheme).reduce((acc, current) => acc + current, 0), getValues(transferScheme).reduce((acc, current) => acc + current, 0), getValues(matrixScheme).reduce((acc, current) => acc + current, 0));
694
676
  };
695
-
696
677
  //#endregion
697
678
  //#region lib/shared/subtitle.ts
698
679
  const parseSubtitleCodecFromMime = (mime) => {
@@ -735,7 +716,6 @@ const checkIsSdh = (accessibilities = []) => {
735
716
  }
736
717
  return false;
737
718
  };
738
-
739
719
  //#endregion
740
720
  //#region lib/shared/audio.ts
741
721
  const parseAudioCodecFromMime = (mime) => {
@@ -787,11 +767,9 @@ const parseChannels = (channels) => {
787
767
  }
788
768
  return parseFloat(channels);
789
769
  };
790
-
791
770
  //#endregion
792
771
  //#region lib/shared/pipe.ts
793
772
  const pipe = (...fns) => (value) => fns.reduce((acc, fn) => fn(acc), value);
794
-
795
773
  //#endregion
796
774
  //#region lib/dash/dash-extractor.ts
797
775
  const createMediaStreamInfo = (params) => {
@@ -1164,8 +1142,8 @@ var DashExtractor = class DashExtractor {
1164
1142
  const subtitleList = streamInfos.filter((stream) => stream.type === "subtitle");
1165
1143
  const videoList = streamInfos.filter((stream) => stream.type === "video");
1166
1144
  for (const video of videoList) {
1167
- const audioGroupId = audioList.toSorted((a, b$1) => (b$1.bitrate || 0) - (a.bitrate || 0)).at(0)?.groupId;
1168
- const subtitleGroupId = subtitleList.toSorted((a, b$1) => (b$1.bitrate || 0) - (a.bitrate || 0)).at(0)?.groupId;
1145
+ const audioGroupId = audioList.toSorted((a, b) => (b.bitrate || 0) - (a.bitrate || 0)).at(0)?.groupId;
1146
+ const subtitleGroupId = subtitleList.toSorted((a, b) => (b.bitrate || 0) - (a.bitrate || 0)).at(0)?.groupId;
1169
1147
  if (audioGroupId) video.audioId = audioGroupId;
1170
1148
  if (subtitleGroupId) video.subtitleId = subtitleGroupId;
1171
1149
  }
@@ -1209,7 +1187,6 @@ var DashExtractor = class DashExtractor {
1209
1187
  for (const processor of this.#parserConfig.contentProcessors) if (processor.canProcess(this.extractorType, this.#mpdContent, this.#parserConfig)) this.#mpdContent = processor.process(this.#mpdContent, this.#parserConfig);
1210
1188
  }
1211
1189
  };
1212
-
1213
1190
  //#endregion
1214
1191
  //#region lib/hls/hls-utils.ts
1215
1192
  /**
@@ -1226,7 +1203,6 @@ function getRange(input) {
1226
1203
  default: return [0, null];
1227
1204
  }
1228
1205
  }
1229
-
1230
1206
  //#endregion
1231
1207
  //#region lib/hls/hls-extractor.ts
1232
1208
  var HlsExtractor = class {
@@ -1483,7 +1459,7 @@ var HlsExtractor = class {
1483
1459
  }
1484
1460
  async #refreshUrlFromMaster(lists) {
1485
1461
  await this.#loadM3u8FromUrl(this.parserConfig.url);
1486
- const newStreams = await this.#parseMasterList().then((lists$1) => distinctBy(lists$1, (list) => list.url));
1462
+ const newStreams = await this.#parseMasterList().then((lists) => distinctBy(lists, (list) => list.url));
1487
1463
  for (const list of lists) {
1488
1464
  const match = newStreams.filter((stream) => stream.toShortString() === list.toShortString());
1489
1465
  if (!match.length) continue;
@@ -1504,9 +1480,9 @@ var HlsExtractor = class {
1504
1480
  else list.playlist = newPlaylist;
1505
1481
  if (list.type === "subtitle") {
1506
1482
  const a = list.playlist.mediaParts.some((part) => part.mediaSegments.some((segment) => segment.url.includes(".ttml")));
1507
- const b$1 = list.playlist.mediaParts.some((part) => part.mediaSegments.some((segment) => segment.url.includes(".vtt") || segment.url.includes(".webvtt")));
1483
+ const b = list.playlist.mediaParts.some((part) => part.mediaSegments.some((segment) => segment.url.includes(".vtt") || segment.url.includes(".webvtt")));
1508
1484
  if (a) list.extension = "ttml";
1509
- if (b$1) list.extension = "vtt";
1485
+ if (b) list.extension = "vtt";
1510
1486
  } else list.extension = list.playlist.mediaInit ? "m4s" : "ts";
1511
1487
  }
1512
1488
  }
@@ -1514,7 +1490,6 @@ var HlsExtractor = class {
1514
1490
  await this.fetchPlayList(streamInfos);
1515
1491
  }
1516
1492
  };
1517
-
1518
1493
  //#endregion
1519
1494
  //#region lib/stream-extractor.ts
1520
1495
  var StreamExtractor = class {
@@ -1578,6 +1553,16 @@ var StreamExtractor = class {
1578
1553
  return this.#extractor.refreshPlayList(streamInfos);
1579
1554
  }
1580
1555
  };
1581
-
1582
1556
  //#endregion
1583
- export { ALL_STREAM_TYPES, AUDIO_CODECS, AudioStreamInfo, DASH_TAGS, DashExtractor, DefaultDashContentProcessor, DefaultHlsContentProcessor, DefaultHlsKeyProcessor, DefaultUrlProcessor, ENCRYPT_METHODS, EXTRACTOR_TYPES, EncryptInfo, HLS_TAGS, HlsExtractor, MediaPart, MediaSegment, ParserConfig, Playlist, ROLE_TYPE, SUBTITLE_CODECS, StreamExtractor, StreamInfo, SubtitleStreamInfo, VIDEO_CODECS, VIDEO_DYNAMIC_RANGES, VideoStreamInfo, getRange, parseRange };
1557
+ //#region lib/input.ts
1558
+ const getSegmentedInput = (track) => {
1559
+ const internalTrack = track._backing.internalTrack;
1560
+ return internalTrack.demuxer.getSegmentedInputForPath(internalTrack.fullPath);
1561
+ };
1562
+ const getSegments = async (track) => {
1563
+ const segmentedInput = getSegmentedInput(track);
1564
+ await segmentedInput.runUpdateSegments();
1565
+ return segmentedInput.segments;
1566
+ };
1567
+ //#endregion
1568
+ export { ALL_STREAM_TYPES, AUDIO_CODECS, AudioStreamInfo, DASH_TAGS, DashExtractor, DefaultDashContentProcessor, DefaultHlsContentProcessor, DefaultHlsKeyProcessor, DefaultUrlProcessor, ENCRYPT_METHODS, EXTRACTOR_TYPES, EncryptInfo, HLS_FORMATS, HLS_TAGS, HlsExtractor, Input, InputTrack, MediaPart, MediaSegment, ParserConfig, Playlist, ROLE_TYPE, SUBTITLE_CODECS, StreamExtractor, StreamInfo, SubtitleStreamInfo, UrlSource, VIDEO_CODECS, VIDEO_DYNAMIC_RANGES, VideoStreamInfo, asc, desc, getRange, getSegmentedInput, getSegments, parseRange };
package/package.json CHANGED
@@ -1,31 +1,18 @@
1
1
  {
2
2
  "name": "dasha",
3
- "version": "4.0.0-alpha.12",
3
+ "version": "4.0.0-alpha.13",
4
4
  "description": "Streaming manifest parser",
5
5
  "files": [
6
6
  "dist"
7
7
  ],
8
- "scripts": {
9
- "test": "vitest",
10
- "lint": "eslint . && prettier --check .",
11
- "fix": "eslint . --fix && prettier --write .",
12
- "typecheck": "tsc --noEmit -p tsconfig.json",
13
- "build": "tsdown dasha.ts --format esm --format cjs",
14
- "prepublishOnly": "npm run build"
15
- },
16
8
  "type": "module",
17
9
  "main": "./dist/dasha.mjs",
18
10
  "types": "./dist/dasha.d.mts",
19
11
  "exports": {
20
12
  ".": {
21
- "require": {
22
- "types": "./dist/dasha.d.cts",
23
- "default": "./dist/dasha.cjs"
24
- },
25
- "import": {
26
- "types": "./dist/dasha.d.mts",
27
- "default": "./dist/dasha.mjs"
28
- }
13
+ "types": "./dist/dasha.d.mts",
14
+ "module-sync": "./dist/dasha.mjs",
15
+ "default": "./dist/dasha.mjs"
29
16
  }
30
17
  },
31
18
  "repository": {
@@ -61,22 +48,25 @@
61
48
  "node": ">=22.16"
62
49
  },
63
50
  "dependencies": {
64
- "@xmldom/xmldom": "^0.9.8",
51
+ "@xmldom/xmldom": "^0.9.10",
65
52
  "barsic": "^0.2.0",
66
- "ky": "^1.14.3",
53
+ "ky": "^2.0.2",
54
+ "mediabunny": "1.42.0-beta.6",
67
55
  "temporal-polyfill": "^0.3.2"
68
56
  },
69
57
  "devDependencies": {
70
- "@eslint/js": "^9.39.1",
71
- "@types/node": "^24.10.0",
72
- "eslint": "^9.39.1",
73
- "eslint-config-prettier": "^10.1.8",
74
- "eslint-plugin-prettier": "^5.5.4",
75
- "globals": "^16.5.0",
76
- "prettier": "^3.6.2",
77
- "tsdown": "^0.16.1",
78
- "typescript": "^5.9.3",
79
- "typescript-eslint": "^8.46.3",
80
- "vitest": "^4.1.0"
58
+ "@types/node": "^25.6.0",
59
+ "oxfmt": "^0.46.0",
60
+ "oxlint": "^1.61.0",
61
+ "tsdown": "^0.21.10",
62
+ "typescript": "^6.0.3",
63
+ "vitest": "^4.1.5"
64
+ },
65
+ "scripts": {
66
+ "test": "vitest",
67
+ "lint": "oxlint . && oxfmt --check .",
68
+ "fix": "oxlint . --fix && oxfmt --write .",
69
+ "typecheck": "tsc --noEmit -p tsconfig.json",
70
+ "build": "tsdown dasha.ts --format esm"
81
71
  }
82
- }
72
+ }