Skip to content

Commit

Permalink
feat: add track protection field, add new types
Browse files Browse the repository at this point in the history
  • Loading branch information
vitalygashkov committed Mar 25, 2024
1 parent 7904855 commit 8413492
Show file tree
Hide file tree
Showing 7 changed files with 249 additions and 145 deletions.
37 changes: 28 additions & 9 deletions dasha.js
Original file line number Diff line number Diff line change
Expand Up @@ -46,14 +46,37 @@ const trackDto = (data, duration) => {
segments: segmentsDto(data.segments),
};
if (data.attributes.RESOLUTION) result.resolution = data.attributes.RESOLUTION;
if (data.language) result.language = data.language;
if (data.contentProtection) {
result.protection = {};
if (data.contentProtection?.mp4protection)
result.protection.common = {
id: data.contentProtection.mp4protection.attributes.schemeIdUri,
value: data.contentProtection.mp4protection.attributes.value,
keyId: data.contentProtection.mp4protection.attributes['cenc:default_KID'],
};
if (data.contentProtection['com.microsoft.playready'])
result.protection.playready = {
id: data.contentProtection['com.microsoft.playready'].attributes.schemeIdUri,
value: data.contentProtection['com.microsoft.playready'].attributes.value,
pssh: Buffer.from(data.contentProtection['com.microsoft.playready'].pssh).toString(
'base64'
),
};
if (data.contentProtection['com.widevine.alpha'])
result.protection.widevine = {
id: data.contentProtection['com.widevine.alpha'].attributes.schemeIdUri,
pssh: Buffer.from(data.contentProtection['com.widevine.alpha'].pssh).toString('base64'),
};
}
return result;
};

const audioDto = (data) => {
const result = [];
if (!data) return result;
for (const [key, value] of Object.entries(data)) {
result.push(...value.playlists);
result.push(...value.playlists.map((item) => ({ ...item, language: value.language })));
}
return result;
};
Expand All @@ -62,7 +85,7 @@ const subsDto = (data) => {
const result = [];
if (!data) return result;
for (const [key, value] of Object.entries(data)) {
result.push(...value.playlists);
result.push(...value.playlists.map((item) => ({ ...item, language: value.language })));
}
return result;
};
Expand All @@ -77,7 +100,6 @@ const parseMpd = (manifestString, manifestUri) => {
contentSteering: parsedManifestInfo.contentSteeringInfo,
eventStream: parsedManifestInfo.eventStream,
});
manifest.allPlaylists = playlists;

const toTrackWithSize = (data) => trackDto(data, manifest.duration);
const videoPlaylists = manifest.playlists;
Expand All @@ -91,7 +113,6 @@ const parseMpd = (manifestString, manifestUri) => {
subtitles: subtitlePlaylists.map(toTrackWithSize),
},
};

return mpd;
};

Expand All @@ -103,9 +124,9 @@ const parseM3U8 = (manifestString) => {
return manifest;
};

const parse = (text, url, eventHandler) => {
if (text.includes('MPD')) return parseMpd(text, url, eventHandler);
else if (text.includes('#EXTM3U')) return parseM3U8(text);
const parse = (body, url) => {
if (body.includes('<MPD')) return parseMpd(body, url);
else if (body.includes('#EXTM3U')) return parseM3U8(body);
else return null;
};

Expand Down Expand Up @@ -222,8 +243,6 @@ const getSubtitleTracks = (manifest, languages = []) => {

module.exports = {
parse,
parseMpd,
parseM3U8,
getPssh,
getVideoTrack,
getAudioTracks,
Expand Down
2 changes: 1 addition & 1 deletion package.json
Original file line number Diff line number Diff line change
@@ -1,6 +1,6 @@
{
"name": "dasha",
"version": "3.0.0.alpha.1",
"version": "3.0.0.alpha.2",
"author": "Vitaly Gashkov <[email protected]>",
"description": "Parser for MPEG-DASH & HLS manifests",
"license": "AGPL-3.0",
Expand Down
15 changes: 6 additions & 9 deletions test/dasha.test.js
Original file line number Diff line number Diff line change
@@ -1,19 +1,16 @@
const { test } = require('node:test');
const { strictEqual } = require('node:assert');
const { getQualityLabel, parseMpd } = require('../dasha');
const { readFileSync } = require('node:fs');
const { parse } = require('../lib/xml-parser');
const { getQualityLabel, parse } = require('../dasha');

test('getQualityLabel', () => {
strictEqual(getQualityLabel({ width: 1920, height: 1080 }), '1080p');
});

const iviMpdBody = readFileSync('./test/ivi.mpd', 'utf8');
const iviMpdUrl =
'https://region.dfs.ivi.ru/jW1IJMiotdNBiHD4lSg9lP6hagbTPkSWIEwaCAAZ06jiyKhGIU4g50599/voddash-abrshq,4000/k5SE_TzrVR8MZwRunaX1ZQ,1711416086/storage4/contents/3/c/fb597d9676983b3c79d547dc082f70.ks/3438f09cad005bf5eae8b2fea20500c7.mpd';
const kionMpdBody = readFileSync('./test/kion.mpd', 'utf8');
const kionMpdUrl =
'https://htv-mag2-moscow2.mts.ru/htv-rrs.mts.ru/88888888/16/20230707/268697239/268697239.mpd';

test('parseMpd', () => {
const manifest = parseMpd(iviMpdBody);
// console.log(manifest);
// strictEqual(getQualityLabel({ width: 1920, height: 1080 }), '1080p');
test('DASH parsing', () => {
const manifest = parse(kionMpdBody, kionMpdUrl);
});
23 changes: 0 additions & 23 deletions test/ivi.mpd

This file was deleted.

33 changes: 33 additions & 0 deletions test/kion.mpd
Original file line number Diff line number Diff line change
@@ -0,0 +1,33 @@
<?xml version="1.0" encoding="UTF-8"?>
<MPD xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance" xmlns="urn:mpeg:dash:schema:mpd:2011" xmlns:cenc="urn:mpeg:cenc:2013" type="static" profiles="urn:mpeg:dash:profile:isoff-live:2011" mediaPresentationDuration="PT2H4M9.600S" minBufferTime="PT0H0M2.000S" xsi:schemalocation="urn:mpeg:dash:schema:mpd:2011 DASH-MPD.xsd">
<Period start="PT0S" duration="PT2H4M9.600S">
<AdaptationSet segmentAlignment="true" mimeType="video/mp4" contentType="video" maxWidth="1920" maxHeight="1080" maxFrameRate="25" frameRate="25" scanType="progressive" par="16:9" startWithSAP="1">
<Role schemeIdUri="urn:mpeg:dash:role:2011" value="main"/>
<SegmentTemplate timescale="90000" duration="180000" initialization="$RepresentationID$_init.m4i?hw_dash=1&amp;servicetype=0&amp;zoneoffset=0&amp;limitflux=-1&amp;limitdur=-1&amp;tenantId=703&amp;validdev=392&amp;pbf=2_1_2_3077962695" media="$RepresentationID$-$Number$.m4v?hw_dash=1&amp;servicetype=0&amp;zoneoffset=0&amp;limitflux=-1&amp;limitdur=-1&amp;tenantId=703&amp;validdev=392&amp;pbf=2_1_2_3077962695" startNumber="1"/>
<ContentProtection schemeIdUri="urn:mpeg:dash:mp4protection:2011" value="cenc"/>
<ContentProtection schemeIdUri="urn:uuid:9A04F079-9840-4286-AB92-E65BE0885F95">
<cenc:pssh>AAADPnBzc2gAAAAAmgTweZhAQoarkuZb4IhflQAAAx4eAwAAAQABABQDPABXAFIATQBIAEUAQQBEAEUAUgAgAHgAbQBsAG4AcwA9ACIAaAB0AHQAcAA6AC8ALwBzAGMAaABlAG0AYQBzAC4AbQBpAGMAcgBvAHMAbwBmAHQALgBjAG8AbQAvAEQAUgBNAC8AMgAwADAANwAvADAAMwAvAFAAbABhAHkAUgBlAGEAZAB5AEgAZQBhAGQAZQByACIAIAB2AGUAcgBzAGkAbwBuAD0AIgA0AC4AMAAuADAALgAwACIAPgA8AEQAQQBUAEEAPgA8AFAAUgBPAFQARQBDAFQASQBOAEYATwA+ADwASwBFAFkATABFAE4APgAxADYAPAAvAEsARQBZAEwARQBOAD4APABBAEwARwBJAEQAPgBBAEUAUwBDAFQAUgA8AC8AQQBMAEcASQBEAD4APAAvAFAAUgBPAFQARQBDAFQASQBOAEYATwA+ADwASwBJAEQAPgBuADgAZgB4AFEAVwB2AGoAUgA5AEQAMQBRAHAAcwBQAEsAeAB1AGEAcgBRAD0APQA8AC8ASwBJAEQAPgA8AEMASABFAEMASwBTAFUATQA+AHgAMQBRAFUATgBBAFQATgBBAGIANAA9ADwALwBDAEgARQBDAEsAUwBVAE0APgA8AEwAQQBfAFUAUgBMAD4AaAB0AHQAcABzADoALwAvAGgAdAB2AC0AcAByAGwAcwAuAG0AdABzAC4AcgB1AC8AUABsAGEAeQBSAGUAYQBkAHkALwByAGkAZwBoAHQAcwBtAGEAbgBhAGcAZQByAC4AYQBzAG0AeAA8AC8ATABBAF8AVQBSAEwAPgA8AEwAVQBJAF8AVQBSAEwAPgBoAHQAdABwAHMAOgAvAC8AaAB0AHYALQBwAHIAbABzAC4AbQB0AHMALgByAHUALwBQAGwAYQB5AFIAZQBhAGQAeQAvAHIAaQBnAGgAdABzAG0AYQBuAGEAZwBlAHIALgBhAHMAbQB4ADwALwBMAFUASQBfAFUAUgBMAD4APAAvAEQAQQBUAEEAPgA8AC8AVwBSAE0ASABFAEEARABFAFIAPgA=</cenc:pssh>
</ContentProtection>
<ContentProtection schemeIdUri="urn:uuid:EDEF8BA9-79D6-4ACE-A3C8-27DCD51D21ED">
<cenc:pssh>AAAAXXBzc2gAAAAA7e+LqXnWSs6jyCfc1R0h7QAAAD0IARIQQfHHn+Nr0Ef1QpsPKxuarRoNdmVyaW1hdHJpeG10cyIRcj03NTY4MDYwNDImcz05NTMqBVNEX0hE</cenc:pssh>
</ContentProtection>
<Representation id="video_HD_AB_4" codecs="avc1.42E028" bandwidth="6146832" width="1920" height="1080" sar="1:1"/>
<Representation id="video_HD_AB_3" codecs="avc1.42E01F" bandwidth="3842832" width="1280" height="720" sar="1:1"/>
<Representation id="video_HD_AB_2" codecs="avc1.42E01E" bandwidth="2102800" width="720" height="576" sar="64:45"/>
<Representation id="video_HD_AB_1" codecs="avc1.42E00D" bandwidth="514832" width="320" height="240" sar="4:3"/>
</AdaptationSet>
<AdaptationSet segmentAlignment="true" mimeType="audio/mp4" contentType="audio" codecs="mp4a.40.2" audioSamplingRate="48000" lang="ru" startWithSAP="1">
<AudioChannelConfiguration schemeIdUri="urn:mpeg:mpegB:cicp:ChannelConfiguration" value="2"/>
<Role schemeIdUri="urn:mpeg:dash:role:2011" value="main"/>
<SegmentTemplate timescale="90000" duration="180000" initialization="$RepresentationID$_init.m4i?hw_dash=1&amp;servicetype=0&amp;zoneoffset=0&amp;limitflux=-1&amp;limitdur=-1&amp;tenantId=703&amp;validdev=392&amp;pbf=2_1_2_3077962695" media="$RepresentationID$-$Number$.m4a?hw_dash=1&amp;servicetype=0&amp;zoneoffset=0&amp;limitflux=-1&amp;limitdur=-1&amp;tenantId=703&amp;validdev=392&amp;pbf=2_1_2_3077962695" startNumber="1"/>
<ContentProtection schemeIdUri="urn:mpeg:dash:mp4protection:2011" value="cenc"/>
<ContentProtection schemeIdUri="urn:uuid:9A04F079-9840-4286-AB92-E65BE0885F95">
<cenc:pssh>AAADPnBzc2gAAAAAmgTweZhAQoarkuZb4IhflQAAAx4eAwAAAQABABQDPABXAFIATQBIAEUAQQBEAEUAUgAgAHgAbQBsAG4AcwA9ACIAaAB0AHQAcAA6AC8ALwBzAGMAaABlAG0AYQBzAC4AbQBpAGMAcgBvAHMAbwBmAHQALgBjAG8AbQAvAEQAUgBNAC8AMgAwADAANwAvADAAMwAvAFAAbABhAHkAUgBlAGEAZAB5AEgAZQBhAGQAZQByACIAIAB2AGUAcgBzAGkAbwBuAD0AIgA0AC4AMAAuADAALgAwACIAPgA8AEQAQQBUAEEAPgA8AFAAUgBPAFQARQBDAFQASQBOAEYATwA+ADwASwBFAFkATABFAE4APgAxADYAPAAvAEsARQBZAEwARQBOAD4APABBAEwARwBJAEQAPgBBAEUAUwBDAFQAUgA8AC8AQQBMAEcASQBEAD4APAAvAFAAUgBPAFQARQBDAFQASQBOAEYATwA+ADwASwBJAEQAPgBuADgAZgB4AFEAVwB2AGoAUgA5AEQAMQBRAHAAcwBQAEsAeAB1AGEAcgBRAD0APQA8AC8ASwBJAEQAPgA8AEMASABFAEMASwBTAFUATQA+AHgAMQBRAFUATgBBAFQATgBBAGIANAA9ADwALwBDAEgARQBDAEsAUwBVAE0APgA8AEwAQQBfAFUAUgBMAD4AaAB0AHQAcABzADoALwAvAGgAdAB2AC0AcAByAGwAcwAuAG0AdABzAC4AcgB1AC8AUABsAGEAeQBSAGUAYQBkAHkALwByAGkAZwBoAHQAcwBtAGEAbgBhAGcAZQByAC4AYQBzAG0AeAA8AC8ATABBAF8AVQBSAEwAPgA8AEwAVQBJAF8AVQBSAEwAPgBoAHQAdABwAHMAOgAvAC8AaAB0AHYALQBwAHIAbABzAC4AbQB0AHMALgByAHUALwBQAGwAYQB5AFIAZQBhAGQAeQAvAHIAaQBnAGgAdABzAG0AYQBuAGEAZwBlAHIALgBhAHMAbQB4ADwALwBMAFUASQBfAFUAUgBMAD4APAAvAEQAQQBUAEEAPgA8AC8AVwBSAE0ASABFAEEARABFAFIAPgA=</cenc:pssh>
</ContentProtection>
<ContentProtection schemeIdUri="urn:uuid:EDEF8BA9-79D6-4ACE-A3C8-27DCD51D21ED">
<cenc:pssh>AAAAXXBzc2gAAAAA7e+LqXnWSs6jyCfc1R0h7QAAAD0IARIQQfHHn+Nr0Ef1QpsPKxuarRoNdmVyaW1hdHJpeG10cyIRcj03NTY4MDYwNDImcz05NTMqBVNEX0hE</cenc:pssh>
</ContentProtection>
<Representation id="audio_HD_AB_5" bandwidth="132900"/>
</AdaptationSet>
</Period>
</MPD>
139 changes: 36 additions & 103 deletions types/dasha.d.ts
Original file line number Diff line number Diff line change
@@ -1,121 +1,54 @@
export * from './manifest';
export * from './constants';

export type ParseEventHandler = (event: { type: string; message: string }) => void;
export function parse(body: string, url: string): Manifest | null;

export interface Manifest {
uri: string;
resolvedUri: string;
duration: number;
targetDuration: number;
discontinuityStarts: number[];
timelineStarts: { start: number; timeline: number }[];
timeline: number;
mediaSequence: number;
discontinuitySequence: number;
allowCache: boolean;
endList: boolean;
segments: Segment[];
playlists: Playlist[];
mediaGroups: MediaGroups;

allPlaylists: Playlist[];

// TODO: Check types below
contentSteering?: {
defaultServiceLocation: string;
proxyServerURL: string;
queryBeforeStart: boolean;
serverURL: string;
tracks: {
videos: VideoTrack[];
audios: AudioTrack[];
subtitles: SubtitleTrack[];
};
playlistType?: string;
dateTimeString?: string;
dateTimeObject?: Date;
totalDuration?: number;
}

export interface Segment {
number: number;
uri: string;
resolvedUri: string;
timeline: number;
duration: number;
presentationTime: number;
map: {
uri: string;
resolvedUri: string;
// TODO: Check types below
byterange?: {
length: number;
offset: number;
};
};

// TODO: Check types below
byterange?: {
length: number;
offset: number;
export interface Track {
id: string;
codecs: string;
bandwidth: {
bps: number;
kbps: number;
mbps: number;
gbps: number;
toString: () => string;
};
discontinuity?: number;
key?: {
method: string;
uri: string;
iv: string;
size: {
b: number;
kb: number;
mb: number;
gb: number;
toString: () => string;
};
'cue-out'?: string;
'cue-out-cont'?: string;
'cue-in'?: string;
segments: Segment[];
protection?: TrackProtection;
}

export interface Playlist extends Omit<Manifest, 'playlists' | 'mediaGroups'> {
attributes: Attributes;
contentProtection?: {
mp4protection?: {
attributes: { schemeIdUri: string; value: string; 'cenc:default_KID': string };
};
'com.microsoft.playready'?: {
attributes: { schemeIdUri: string; value: string };
pssh: Uint8Array;
};
'com.widevine.alpha'?: {
attributes: { schemeIdUri: string };
pssh: Uint8Array;
};
};
export interface Segment {
url: string;
init?: boolean;
}

export interface MediaGroups {
AUDIO: {
audio?: { [groupId: string]: MediaGroup };
};
SUBTITLES: {
subs?: { [groupId: string]: MediaGroup };
};
VIDEO: {};
'CLOSED-CAPTIONS': {};
export interface TrackProtection {
common?: { id: string; value: string; keyId?: string };
playready?: { id: string; pssh: string; value?: string };
widevine?: { id: string; pssh: string };
}

export interface MediaGroup {
language: string;
default: boolean;
autoselect: boolean;
playlists: Playlist[];
uri: string;
export interface VideoTrack extends Track {
resolution: { width: number; height: number };
}

// TODO: Check types below
instreamId?: string;
characteristics?: string;
forced?: boolean;
export interface AudioTrack extends Track {
language: string;
}

export interface Attributes {
NAME: string;
AUDIO: string;
SUBTITLES: string;
RESOLUTION: { width: number; height: number };
CODECS: string;
BANDWIDTH: number;
'PROGRAM-ID': number;
'FRAME-RATE': number;
[key: string]: any;
export interface SubtitleTrack extends Track {
language: string;
}
Loading

0 comments on commit 8413492

Please sign in to comment.