dasha
v4.6.0
Published
Streaming manifest parser
Maintainers
Readme
dasha
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.
Install
npm install dashaUsage
Reading HLS
In 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));
}
};Reading DASH
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));
}
}Adding external subtitle tracks
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();
}Overriding track metadata
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,
});
}Removing audio and subtitle tracks
Manifest tracks and tracks added through Dasha can be excluded from later queries. This is useful when a manifest advertises empty or otherwise unusable renditions.
const subtitleTracks = await input.getSubtitleTracks();
for (const track of subtitleTracks) {
if ((await track.getSegments()).length === 0) {
input.removeSubtitleTrack(track);
}
}Mediabunny with DASH support
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).
