Library for working with MPEG-DASH (.mpd) manifests and HLS (.m3u8) playlists through a Mediabunny-compatible Input API. Made with the purpose of obtaining a simplified representation convenient for further downloading of segments by URLs and getting basic metadata about the tracks.
npm install dashaIn the example below, we read the segment information for a specific video track and save it to a file.
import fs from 'node:fs/promises';
import { desc, HLS_FORMATS, Input, UrlSource } from 'dasha';
async function saveVideo() {
const input = new Input({
source: new UrlSource(
'https://storage.googleapis.com/shaka-demo-assets/angel-one-widevine-hls/hls.m3u8',
{ requestInit: { headers: { Referer: 'https://bitmovin.com/' } } },
),
formats: HLS_FORMATS,
});
const videoTracks = await input.getVideoTracks({
sortBy: async (track) => [
desc(await track.getDisplayHeight()),
// Tracks with matching resolution are sorted by bitrate
desc(await track.getBitrate()),
],
// Filter out #EXT-X-I-FRAME-STREAM-INF tracks
filter: async (track) => !(await track.hasOnlyKeyPackets()),
});
const bestVideoTrack = videoTracks[0];
console.log('Dynamic range:', await bestVideoTrack.getDynamicRange()); // sdr
const segments = await bestVideoTrack.getSegments();
const outputPath = 'output.mp4';
const urls = segments.map((segment) => segment.location.path);
const initSegment = segments[0]?.initSegment;
if (initSegment) urls.unshift(initSegment.location.path);
for (const url of urls) {
const content = await fetch(url).then((res) => res.arrayBuffer());
await fs.appendFile(outputPath, new Uint8Array(content));
}
};Everything here is identical to the example above, with the sole exception that an URL to a DASH manifest is used instead of an HLS playlist.
import fs from 'node:fs/promises';
import { DASH_FORMATS, Input, UrlSource, desc } from 'dasha';
async function saveDashVideo() {
const input = new Input({
source: new UrlSource(
'https://dash.akamaized.net/dash264/TestCases/1a/netflix/exMPD_BIP_TC1.mpd',
),
formats: DASH_FORMATS,
});
const videoTracks = await input.getVideoTracks({
sortBy: async (track) => [
desc(await track.getDisplayHeight()),
desc(await track.getBitrate()),
],
});
const bestVideoTrack = videoTracks[0];
const segments = await bestVideoTrack.getSegments();
const outputPath = 'output.m4s';
const urls = segments.map((segment) => segment.location.path);
const initSegment = segments[0]?.initSegment;
if (initSegment) urls.unshift(initSegment.location.path);
for (const url of urls) {
const content = await fetch(url).then((res) => res.arrayBuffer());
await fs.appendFile(outputPath, new Uint8Array(content));
}
}addSubtitleTrack() is useful when subtitle URLs are provided separately from the HLS/DASH manifest. Added subtitles become part of the same Input, so they can be queried, filtered and downloaded through the regular subtitle track API.
import { DASH_FORMATS, Input, UrlSource } from 'dasha';
async function getEnglishSubtitles() {
const input = new Input({
source: new UrlSource('https://example.com/manifest.mpd'),
formats: DASH_FORMATS,
});
const primaryVideoTrack = await input.getPrimaryVideoTrack();
if (!primaryVideoTrack) {
throw new Error('No video tracks found');
}
input.addSubtitleTrack(new UrlSource('https://cdn.example.com/subtitles/en.vtt'), {
languageCode: 'en',
name: 'English',
pairWith: primaryVideoTrack,
});
const englishSubtitleTracks = await input.getSubtitleTracks({
filter: async (track) => (await track.getLanguageCode()) === 'en',
});
const englishSubtitleTrack = englishSubtitleTracks[0];
if (!englishSubtitleTrack) {
return [];
}
return await englishSubtitleTrack.getSegments();
}Some services expose track metadata outside the manifest. You can override the parsed language code on any dasha track, and the updated value will be visible through the regular query/filter APIs.
import { DASH_FORMATS, Input, UrlSource } from 'dasha';
async function getFrenchAudioTrack() {
const input = new Input({
source: new UrlSource('https://example.com/manifest.mpd'),
formats: DASH_FORMATS,
});
const audioTracks = await input.getAudioTracks();
const apiLanguageCode = 'fr';
audioTracks[0]?.setLanguageCode(apiLanguageCode);
return await input.getAudioTracks({
filter: async (track) => (await track.getLanguageCode()) === apiLanguageCode,
});
}Only reading is supported
Similar to downloading an HLS playlist as an MP4 you can do this:
import { Conversion, FilePathTarget, Mp4OutputFormat, Output, Input, UrlSource } from 'mediabunny';
import { DASH_FORMATS } from 'dasha';
const input = new Input({
source: new UrlSource('https://example.com/manifest.mpd'),
formats: DASH_FORMATS,
});
const output = new Output({
format: new Mp4OutputFormat(),
target: new FilePathTarget('output.mp4'),
});
const conversion = await Conversion.init({ input, output });
await conversion.execute();
// DoneSee reading HLS guide for more use cases (many things can be used with DASH as well).