// ==UserScript== // @name Disney+ Audio Downloader // @name:fr Disney+ Audio Downloader // @namespace https://greasyfork.org/users/572942-stegner // @homepage https://greasyfork.org/scripts/405994-disney-audio-downloader // @description Download audio from Disney+ // @description:fr Télécharger l'audio de Disney+ // @version 1.8 // @author stegner // @license MIT; https://opensource.org/licenses/MIT // @match https://www.disneyplus.com/* // @grant none // @run-at document-start // @downloadURL none // ==/UserScript== (function(open, send) { 'use strict'; var debug = (location.hash=="#debug"); debuglog("Script loaded : Disney+ Audio Downloader"); function init(){ debuglog("Document state : "+document.readyState); if (document.readyState == "complete" || document.readyState == "loaded"){ start(); debuglog("Already loaded"); } else { if (window.addEventListener) { window.addEventListener("load", start, false); debuglog("Onload method : addEventListener"); } else if (window.attachEvent) { window.attachEvent("onload", start); debuglog("Onload method : attachEvent"); } else { window.onload = start; debuglog("Onload method : onload"); } } // Globalization fallback document.globalization = { "audio": [ { "language": "en", "renditions": { "primary": "English", "descriptive": "English [Audio Description]" } }, { "language": "en-AU", "renditions": { "primary": "English (Australia)", "descriptive": "English (Australia) [Audio Description]" } }, { "language": "en-GB", "renditions": { "primary": "English (UK)", "descriptive": "English (UK) [Audio Description]" } }, { "language": "nl", "renditions": { "primary": "Nederlands", "descriptive": "Nederlands [Audiobeschrijving]" } }, { "language": "nl-BE", "renditions": { "primary": "Vlaams", "descriptive": "Vlaams [Audiobeschrijving]" } }, { "language": "fr-FR", "renditions": { "primary": "Français", "descriptive": "Français [Audiodescription]" } }, { "language": "fr-CA", "renditions": { "primary": "Français (Canada)", "descriptive": "Français (Canada) [Vidéodescription]" } }, { "language": "es-419", "renditions": { "primary": "Español (Latinoamérica)", "descriptive": "Español (Latinoamérica) [Audiodescripción]" } }, { "language": "es-ES", "renditions": { "primary": "Español", "descriptive": "Español [Audiodescripción]" } }, { "language": "it", "renditions": { "primary": "Italiano", "descriptive": "Italiano [Audiodescrizione]" } }, { "language": "de", "renditions": { "primary": "Deutsch", "descriptive": "Deutsch [Audiodeskription]" } }, { "language": "de-AT", "renditions": { "primary": "Deutsch", "descriptive": "Deutsch (Österreich) [Audiodeskription]" } }, { "language": "da", "renditions": { "primary": "Dansk", "descriptive": "Dansk [Synstolkning]" } }, { "language": "fi", "renditions": { "primary": "Suomi", "descriptive": "Suomi [Kuvailutulkkaus]" } }, { "language": "no", "renditions": { "primary": "Norsk", "descriptive": "Norsk [Synstolking]" } }, { "language": "sv", "renditions": { "primary": "Svenska", "descriptive": "Svenska [Syntolkning]" } }, { "language": "pt-PT", "renditions": { "primary": "Português", "descriptive": "Português [Audiodescrição]" } }, { "language": "pt-BR", "renditions": { "primary": "Português (Brasil)", "descriptive": "Português (Brasil) [Audiodescrição]" } }, { "language": "pl", "renditions": { "primary": "Polski", "descriptive": "Polski [Audiodeskrypcja]" } }, { "language": "ja", "renditions": { "primary": "日本語", "descriptive": "日本語 [副音声]" } }, { "language": "zh-HK", "renditions": { "primary": "Chinese (粵語)", "descriptive": "Chinese (粵語) [口述影像]" } }, { "language": "zh-Hans", "renditions": { "primary": "Chinese (普通话)", "descriptive": "Chinese (普通话) [口述影像]" } }, { "language": "zh-Hant", "renditions": { "primary": "Chinese (中文)", "descriptive": "Chinese (中文) [口述影像]" } }, { "language": "yue", "renditions": { "primary": "Chinese (粵語)", "descriptive": "Chinese (粵語) [口述影像]" } }, { "language": "cmn", "renditions": { "primary": "Chinese (普通话)", "descriptive": "Chinese (普通话) [口述影像]" } }, { "language": "cmn-TW", "renditions": { "primary": "Chinese (中文)", "descriptive": "Chinese (中文) [口述影像]" } }, { "language": "ca", "renditions": { "primary": "Català", "descriptive": "Català [Audiodescripció]" } }, { "language": "is", "renditions": { "primary": "Íslenska", "descriptive": "Íslenska [hljóðlýsing]" } }, { "language": "ko", "renditions": { "primary": "한국어", "descriptive": "한국어 [화면 해설]" } }, { "language": "cs", "renditions": { "primary": "Čeština", "descriptive": "Čeština [zvukový popis]" } }, { "language": "sk", "renditions": { "primary": "Slovenčina", "descriptive": "Slovenčina [zvukový popis]" } }, { "language": "ro", "renditions": { "primary": "Română", "descriptive": "Română [Descriere audio]" } }, { "language": "hu", "renditions": { "primary": "Magyar", "descriptive": "Magyar [képnarrációval]" } }, { "language": "tr", "renditions": { "primary": "Türkçe", "descriptive": "Türkçe [Sesli Betimleme]" } }, { "language": "th", "renditions": { "primary": "ไทย", "descriptive": "ไทย [เสียงบรรยายภาพ]" } }, { "language": "vi", "renditions": { "primary": "Tiếng Việt", "descriptive": "Tiếng Việt [Mô tả âm thanh]" } }, { "language": "ms", "renditions": { "primary": "Bahasa Melayu", "descriptive": "Bahasa Melayu [Penerangan Audio]" } }, { "language": "id", "renditions": { "primary": "Bahasa Indonesia", "descriptive": "Bahasa Indonesia [Deskripsi Audio]" } }, { "language": "el", "renditions": { "primary": "Ελληνικά", "descriptive": "Ελληνικά [ηχητική περιγραφή]" } }, { "language": "mi", "renditions": { "primary": "Māori", "descriptive": "Māori [Whakaahuatanga orooro]" } } ], "timedText": [ { "language": "en", "renditions": { "captions": "English [CC]", "subtitles": "English", "forced": "en--forced--", "sdh": "English [CC]" } }, { "language": "en-AU", "renditions": { "captions": "English (Australia) [CC]", "subtitles": "English (Australia)", "forced": "en-AU--forced--", "sdh": "English (Australia) [CC]" } }, { "language": "en-GB", "renditions": { "captions": "English (UK) [CC]", "subtitles": "English (UK)", "forced": "en-GB--forced--", "sdh": "English (UK) [CC]" } }, { "language": "nl", "renditions": { "captions": "Nederlands [CC]", "subtitles": "Nederlands", "forced": "nl--forced--", "sdh": "Nederlands [CC]" } }, { "language": "nl-BE", "renditions": { "captions": "Vlaams [CC]", "subtitles": "Vlaams", "forced": "nl-BE--forced--", "sdh": "Vlaams [CC]" } }, { "language": "fr-FR", "renditions": { "captions": "Français [CC]", "subtitles": "Français", "forced": "fr-FR--forced--", "sdh": "Français [CC]" } }, { "language": "fr-CA", "renditions": { "captions": "Français (Canada) [CC]", "subtitles": "Français (Canada)", "forced": "fr-CA--forced--", "sdh": "Français (Canada) [CC]" } }, { "language": "it", "renditions": { "captions": "Italiano [CC]", "subtitles": "Italiano", "forced": "it--forced--", "sdh": "Italiano [CC]" } }, { "language": "de", "renditions": { "captions": "Deutsch [CC]", "subtitles": "Deutsch", "forced": "de--forced--", "sdh": "Deutsch [CC]" } }, { "language": "es-ES", "renditions": { "captions": "Español [CC]", "subtitles": "Español", "forced": "es-ES--forced--", "sdh": "Español [CC]" } }, { "language": "es-419", "renditions": { "captions": "Español (Latinoamérica) [CC]", "subtitles": "Español (Latinoamérica)", "forced": "es-419--forced--", "sdh": "Español (Latinoamérica) [CC]" } }, { "language": "da", "renditions": { "captions": "Dansk [CC]", "subtitles": "Dansk", "forced": "da--forced--", "sdh": "Dansk [CC]" } }, { "language": "fi", "renditions": { "captions": "Suomi [CC]", "subtitles": "Suomi", "forced": "fi--forced--", "sdh": "Suomi [CC]" } }, { "language": "nb", "renditions": { "captions": "Norsk (bokmål) [CC]", "subtitles": "Norsk (bokmål)", "forced": "no--forced--", "sdh": "Norsk (bokmål) [CC]" } }, { "language": "no", "renditions": { "captions": "Norsk (bokmål) [CC]", "subtitles": "Norsk (bokmål)", "forced": "no--forced--", "sdh": "Norsk (bokmål) [CC]" } }, { "language": "sv", "renditions": { "captions": "Svenska [CC]", "subtitles": "Svenska", "forced": "sv--forced--", "sdh": "Svenska [CC]" } }, { "language": "pt-PT", "renditions": { "captions": "Português [CC]", "subtitles": "Português", "forced": "pt-PT--forced--", "sdh": "Português [CC]" } }, { "language": "pt-BR", "renditions": { "captions": "Português (Brasil) [CC]", "subtitles": "Português (Brasil)", "forced": "pt-BR--forced--", "sdh": "Português (Brasil) [CC]" } }, { "language": "zh-Hans", "renditions": { "captions": "Chinese (简体) [CC]", "subtitles": "Chinese (简体)", "forced": "zh-Hans--forced--", "sdh": "Chinese (简体) [CC]" } }, { "language": "zh-Hant", "renditions": { "captions": "Chinese (繁體) [CC]", "subtitles": "Chinese (繁體)", "forced": "zh-Hant--forced--", "sdh": "Chinese (繁體) [CC]" } }, { "language": "zh-HK", "renditions": { "captions": "Chinese (粵語) [CC]", "subtitles": "Chinese (粵語)", "forced": "zh-HK--forced--", "sdh": "Chinese (粵語) [CC]" } }, { "language": "ko", "renditions": { "captions": "한국어 [CC]", "subtitles": "한국어", "forced": "ko--forced--", "sdh": "한국어 [CC]" } }, { "language": "ja", "renditions": { "captions": "日本語 [CC]", "subtitles": "日本語", "forced": "ja--forced--", "sdh": "日本語 [CC]" } }, { "language": "id", "renditions": { "captions": "Bahasa Indonesia [CC]", "subtitles": "Bahasa Indonesia", "forced": "id--forced--", "sdh": "Bahasa Indonesia [CC]" } }, { "language": "vi", "renditions": { "captions": "Tiếng Việt [CC]", "subtitles": "Tiếng Việt", "forced": "vi--forced--", "sdh": "Tiếng Việt [CC]" } }, { "language": "th", "renditions": { "captions": "ไทย [CC]", "subtitles": "ไทย", "forced": "th--forced--", "sdh": "ไทย [CC]" } }, { "language": "ms", "renditions": { "captions": "Bahasa Melayu [CC]", "subtitles": "Bahasa Melayu", "forced": "ms--forced--", "sdh": "Bahasa Melayu [CC]" } }, { "language": "el", "renditions": { "captions": "Ελληνικά [CC]", "subtitles": "Ελληνικά", "forced": "el--forced--", "sdh": "Ελληνικά [CC]" } }, { "language": "pl", "renditions": { "captions": "Polski [CC]", "subtitles": "Polski", "forced": "pl--forced--", "sdh": "Polski [CC]" } }, { "language": "is", "renditions": { "captions": "Íslenska [CC]", "subtitles": "Íslenska", "forced": "is--forced--", "sdh": "Íslenska [CC]" } }, { "language": "ca", "renditions": { "captions": "Català [CC]", "subtitles": "Català", "forced": "ca-forced--", "sdh": "Català [CC]" } }, { "language": "de-AT", "renditions": { "captions": "Deutsch (Österreich) [CC]", "subtitles": "Deutsch (Österreich)", "forced": "de-AT--forced--", "sdh": "Deutsch (Österreich) [CC]" } }, { "language": "tr", "renditions": { "captions": "Türkçe [CC]", "subtitles": "Türkçe", "forced": "tr--forced--", "sdh": "Türkçe [CC]" } }, { "language": "cs", "renditions": { "captions": "Čeština [CC]", "subtitles": "Čeština", "forced": "cs--forced--", "sdh": "Čeština [CC]" } }, { "language": "sk", "renditions": { "captions": "Slovenčina [CC]", "subtitles": "Slovenčina", "forced": "sk--forced--", "sdh": "Slovenčina [CC]" } }, { "language": "ro", "renditions": { "captions": "Română [CC]", "subtitles": "Română", "forced": "ro--forced--", "sdh": "Română [CC]" } }, { "language": "hu", "renditions": { "captions": "Magyar [CC]", "subtitles": "Magyar", "forced": "hu--forced--", "sdh": "Magyar [CC]" } } ] } document.listen=true; } function start(){ debuglog("start"); if (typeof document.initaudio !== "undefined") { document.initaudio(); } if (typeof document.initsub !== "undefined") { document.initsub(); } listensend(); document.handleinterval = setInterval(buttonhandle,100); } if(!document.listen){ init(); } document.initaudio = function(){ debuglog("initaudio"); document.audios = []; document.content = new Uint8Array(); document.baseurl=""; document.m3u8found=false; document.wait=false; document.downloading=false; document.filename=""; document.episode=""; document.audioid=null; // Add download icon document.styleSheets[0].addRule('#audioTrackPicker > div:before','content:"";color:#fff;padding-right:35px;padding-top:2px;background:url(data:image/png;base64,iVBORw0KGgoAAAANSUhEUgAAABQAAAAUCAYAAACNiR0NAAAACXBIWXMAAA7EAAAOxAGVKw4bAAAAIGNIUk0AAHonAACAgwAA+mQAAIDSAAB2hgAA7OkAADmeAAAV/sZ+0zoAAAE4SURBVHja1JS7LkRRFIa/M6aYRCEuCUEUgihFBolGVGqiFY1ConfpNB7CiygUGm8hOiMukwiCCMl8mj2xc5yZM8M0/mTlrLP2v75zydo7UclRL3AGlIAl4L6ZuUC+5oEZYBoo55lbAdai/LPTwFongG3pfwI3gZ3ovhjlXVG+BWz/6FbjKPuto1CbjWoLobYf1RZjRho4pt5F5g11QK2F6FFXo/UXdbwZEHVQvY2aztWPECdR/TkNawREHUpB03pSJ7J6Cf9gL3xOvDiiXmfAHtSplLek7qorqI/BeJjxxFG1kgNDPQjrn4VoLPozRqgCzAGXwFXILzJ8w+H6XgRegW7grcGs3gCTOfP8UgfGg139wwapxrugDl0H+oCkTZjAcsiTxBaO7HZUBI6BtfCmv4Un4aw8/RoA7wq6AO4uOhAAAAAASUVORK5CYII=) no-repeat right;width:20px;height:18px;position:absolute;top:6px;right:10px;opacity:0.6;cursor:pointer;'); document.styleSheets[0].addRule('#audioTrackPicker > div:hover:before','opacity:1;'); } // Catch M3U8 files function listensend(){ debuglog("listensend"); var newOpen = function(...args) { if(!document.m3u8found && args.length>=2){ if(args[1].indexOf(".m3u8")>0 && document.url!=args[1]) { // m3u8 url debuglog("m3u8 found : "+args[1]); document.url = args[1]; document.langs = []; document.baseurl=document.url.substring(0,document.url.lastIndexOf('/')+1); document.m3u8found=true; getpagecontent(m3u8loaded,document.url); } } open.call(this,...args); } var newSend = function(...args) { if(args[0] && args[0].match && args[0].match(/globalization/)){ this.addEventListener('readystatechange', function(e) { try { document.globalization = JSON.parse(e.target.response).data.globalization; } catch(e) {} }, false); } send.call(this,...args); } if(typeof unsafeWindow !== "undefined"){ debuglog("Window state : unsafe"); var define = Object.defineProperty; define(unsafeWindow.XMLHttpRequest.prototype, "open", {value: exportFunction(newOpen, window)}); define(unsafeWindow.XMLHttpRequest.prototype, "send", {value: exportFunction(newSend, window)}); } else { debuglog("Window state : safe"); XMLHttpRequest.prototype.open = newOpen; XMLHttpRequest.prototype.send = newSend; } } function m3u8loaded(response) { debuglog("m3u8loaded"); if (typeof document.m3u8sub !== "undefined") { document.m3u8sub(response); } if (typeof document.m3u8audio !== "undefined") { document.m3u8audio(response); } } document.m3u8audio = function(response){ var lines = response.split('#'); var found = false; if(lines[2].indexOf("EXT-X-INDEPENDENT-SEGMENTS")==0){ // Audio tracks list var quality=null; lines.forEach(function(line) { if(line.indexOf('TYPE=AUDIO')>0) { var lang = linetoarray(line); lang.LOCALIZED = document.globalization.audio.find(t => t.language == lang.LANGUAGE); // audio infos if(line.indexOf('GROUP-ID="eac-3"')>0 && (quality==null||quality=="eac-3")){ quality="eac-3"; document.audios.push(lang); debuglog("Audio found : "+document.audios[document.audios.length-1].NAME); } else if(line.indexOf('GROUP-ID="aac-128k"')>0 && (quality==null||quality=="aac-128k")){ quality="aac-128k"; document.audios.push(lang); debuglog("Audio found : "+document.audios[document.audios.length-1].NAME); } else if(line.indexOf('GROUP-ID="aac-64k"')>0 && (quality==null||quality=="aac-64k")){ quality="aac-64k"; document.audios.push(lang); debuglog("Audio found : "+document.audios[document.audios.length-1].NAME); } } }); } else if(response.indexOf(".mp4a")>0) { downloadmp4a(response); } } function downloadmp4a(m3u8data){ debuglog("downloadmp4a"); var lines = m3u8data.split(/\r?\n/g); var mapfound=false; var percent; var i=0; document.downloadInterval = setInterval(function () { var line = lines[i]; var url=null; if(line!=null){ var uri = document.audios[document.audioid].URI; if(line.indexOf("map.mp4a")>0 && !mapfound){ // Get mp4a map debuglog("Download map"); url = document.baseurl+uri.substring(0,uri.lastIndexOf("/")+1)+line.substring(line.indexOf('"')+1,line.lastIndexOf('"')); mapfound=true; } else if(line.indexOf("_000.mp4a")>0 && line.indexOf("MAIN")>0){ // Get mp4a data url = document.baseurl+uri.substring(0,uri.lastIndexOf("/")+1)+line; } if(url!=null && !document.downloading){ // Download file getpagecontent(mp4aloaded,url,true); document.downloading=true; i++; } else if(url==null){ // Skip line i++; } if(percent!=Math.round((i/lines.length)*100)){ percent=Math.round((i/lines.length)*100); document.styleSheets[0].addRule('#audioTrackPicker > div:nth-child('+(document.audioid+1)+'):before','content:"'+percent+'%";'); } } else { // Download finished clearInterval(document.downloadInterval); document.styleSheets[0].addRule('#audioTrackPicker > div:nth-child('+(document.audioid+1)+'):before','content:"";'); exportblob(document.content, 'video/mp4'); document.content=new Uint8Array(); document.wait=false; } },10); } function mp4aloaded(response) { debuglog("mp4aloaded"); document.downloading=false; document.content=appendbuffer(document.content,response); } function linetoarray(line) { var result = []; var values = line.split(','); values.forEach(function(value) { var data = value.replace(/\r\n|\r|\n/g,'').split('='); if(data.length>1) { var key = data[0]; var content = data[1].replace(/"/g,''); result[key]=content; } }); return result; } function buttonhandle() { var buttons = document.getElementsByClassName("control-icon-btn"); if(buttons.length>0) { if (typeof document.clickhandlesub !== "undefined") { document.clickhandlesub(); } if (typeof document.clickhandleaudio !== "undefined") { document.clickhandleaudio(); } document.filename = document.getElementsByClassName("title-field")[0]?.innerText; if(document.getElementsByClassName("subtitle-field").length>0) { document.episode = document.getElementsByClassName("subtitle-field")[0]?.innerText } } if(document.oldlocation!=window.location.href&&document.oldlocation!=null) { // location changed document.m3u8found=false; document.audios = []; document.langs = []; } document.oldlocation=window.location.href; } document.clickhandleaudio = function() { var picker = document.getElementsByClassName("options-picker audio-track-picker"); if(picker && picker[0]) { picker[0].childNodes.forEach(function(child) { var element = child.childNodes[0]; var lang = element.childNodes[1].innerHTML; if(child.onclick==null) { child.onclick = selectaudio; } }); } } function selectaudio(e) { debuglog("selectaudio"); var width = this.offsetWidth; // Check click position if(e.layerX>=width-30&&e.layerX<=width-10&&e.layerY>=5&&e.layerY<=25){ // Download audio download(this.childNodes[0].childNodes[1].innerHTML); // Cancel selection return false; } } function download(langname) { if(!document.wait){ debuglog("Download audio : "+langname); var count=0; document.audios.forEach(function(audio) { if(audio.LOCALIZED && Object.values(audio.LOCALIZED.renditions).includes(langname)) { document.audioid=count; document.ad=(audio.NAME.indexOf("[Audio Description]")>0); getpagecontent(m3u8loaded,document.baseurl+audio.URI); document.wait=true; } count++; }); if(count==0){ alert("An error has occurred, please reload the page."); } } } function getpagecontent(callback,url,binary) { debuglog("Downloading : "+url); var http=new XMLHttpRequest(); http.open("GET", url, true); if(binary){ http.responseType = "arraybuffer"; } http.onloadend = function() { if(http.readyState == 4 && http.status == 200) { if(binary){ callback(http.response); } else { callback(http.responseText); } } else if (http.status === 404) { debuglog("Not found"); callback(""); } else { debuglog("Unknown error, retrying"); setTimeout(function () { getpagecontent(callback,url,binary); },100); } } http.send(); } function appendbuffer(buffer1, buffer2) { var tmp = new Uint8Array(buffer1.byteLength + buffer2.byteLength); tmp.set(new Uint8Array(buffer1), 0); tmp.set(new Uint8Array(buffer2), buffer1.byteLength); return tmp; }; // Save file as arraybuffer function exportblob(data, mimeType) { debuglog("exportblob"); var blob, url; var output = document.filename; if(document.episode!="") { output+= " - "+document.episode.replace(':',''); } output += "."+document.audios[document.audioid].LANGUAGE; if(document.ad){ output +=".ad"; } output += ".mp4"; blob = new Blob([data], { type: mimeType }); url = window.URL.createObjectURL(blob); downloadurl(url, output); setTimeout(function() { return window.URL.revokeObjectURL(url); }, 1000); }; function downloadurl(data, fileName) { debuglog("Save audio"); var a; a = document.createElement('a'); a.href = data; a.download = fileName; document.body.appendChild(a); a.style = 'display: none'; a.click(); a.remove(); }; function debuglog(message){ if(debug){ console.log("%c [debug] "+message, 'background: #222; color: #bada55'); } } })(XMLHttpRequest.prototype.open, XMLHttpRequest.prototype.send);