// Based of https://github.com/GilgusMaximus/yt-dash-manifest-generator/blob/master/src/DashGenerator.js const xml = require('xml-js') const DashUtils = { generate_dash_file_from_formats(VideoFormats, VideoLength) { const generatedJSON = this.generate_xmljs_json_from_data(VideoFormats, VideoLength) return xml.json2xml(generatedJSON) }, generate_xmljs_json_from_data(VideoFormatArray, VideoLength) { const convertJSON = { "declaration": { "attributes": { "version": "1.0", "encoding": "utf-8" } }, "elements": [ { "type": "element", "name": "MPD", "attributes": { "xmlns": "urn:mpeg:dash:schema:mpd:2011", "profiles": "urn:mpeg:dash:profile:full:2011", "minBufferTime": "PT1.5S", "type": "static", "mediaPresentationDuration": `PT${VideoLength}S` }, "elements": [ { "type": "element", "name": "Period", "elements": this.generate_adaptation_set(VideoFormatArray) } ] } ] } return convertJSON }, generate_adaptation_set(VideoFormatArray) { const adaptationSets = [] const mimeTypes = [] const mimeObjects = [[]] // sort the formats by mime types VideoFormatArray.forEach((videoFormat) => { // the dual formats should not be used if (videoFormat.mimeType.indexOf("video") != -1 && !videoFormat.videoOnly) { return } // if these properties are not available, then we skip it because we cannot set these properties //if (!(videoFormat.hasOwnProperty('initRange') && videoFormat.hasOwnProperty('indexRange'))) { // return //} const mimeType = videoFormat.mimeType const mimeTypeIndex = mimeTypes.indexOf(mimeType) if (mimeTypeIndex > -1) { mimeObjects[mimeTypeIndex].push(videoFormat) } else { mimeTypes.push(mimeType) mimeObjects.push([]) mimeObjects[mimeTypes.length - 1].push(videoFormat) } }) // for each MimeType generate a new Adaptation set with Representations as sub elements for (let i = 0; i < mimeTypes.length; i++) { let isVideoFormat = false const adapSet = { "type": "element", "name": "AdaptationSet", "attributes": { "id": i, "mimeType": mimeTypes[i], "startWithSAP": "1", "subsegmentAlignment": "true" }, "elements": [] } if (!mimeTypes[i].includes("audio")) { adapSet.attributes.scanType = "progressive" isVideoFormat = true } mimeObjects[i].forEach((format) => { if (isVideoFormat) { adapSet.elements.push(this.generate_representation_video(format)) } else { adapSet.elements.push(this.generate_representation_audio(format)) } }) adaptationSets.push(adapSet) } return adaptationSets }, generate_representation_audio(Format) { const representation = { "type": "element", "name": "Representation", "attributes": { "id": Format.itag, "codecs": Format.codec, "bandwidth": Format.bitrate }, "elements": [ { "type": "element", "name": "AudioChannelConfiguration", "attributes": { "schemeIdUri": "urn:mpeg:dash:23003:3:audio_channel_configuration:2011", "value": "2" }, }, { "type": "element", "name": "BaseURL", "elements": [ { "type": "text", "text": Format.url } ] }, { "type": "element", "name": "SegmentBase", "attributes": { "indexRange": `${Format.indexStart}-${Format.indexEnd}` }, "elements": [ { "type": "element", "name": "Initialization", "attributes": { "range": `${Format.initStart}-${Format.initEnd}` } } ] } ] } return representation }, generate_representation_video(Format) { const representation = { "type": "element", "name": "Representation", "attributes": { "id": Format.itag, "codecs": Format.codec, "bandwidth": Format.bitrate, "width": Format.width, "height": Format.height, "maxPlayoutRate": "1", "frameRate": Format.fps }, "elements": [ { "type": "element", "name": "BaseURL", "elements": [ { "type": "text", "text": Format.url } ] }, { "type": "element", "name": "SegmentBase", "attributes": { "indexRange": `${Format.indexStart}-${Format.indexEnd}` }, "elements": [ { "type": "element", "name": "Initialization", "attributes": { "range": `${Format.initStart}-${Format.initEnd}` } } ] } ] } return representation } } export default DashUtils;