// ==UserScript== // @name arte stream and download // @namespace http://tampermonkey.net/ // @version 3.0.1.1 // @description get arte stream url with just one click (or none at all) or download seperate audio/video mp4 // @author mihau // @match https://www.arte.tv/*/videos* // @match https://www.arte.tv/*/live* // @license MIT // @supportURL https://greasyfork.org/en/scripts/533451-arte-stream-and-download // @downloadURL none // ==/UserScript== // if you want to get the url in the very moment the page loads, you may change this from 0 to 1: var loadonload = 0; // best not edit below this line // from https://stackoverflow.com/questions/5525071/how-to-wait-until-an-element-exists function waitforit(selector) { return new Promise(resolve => { if (document.querySelector(selector)) { return resolve(document.querySelector(selector)); } var observer = new MutationObserver(mutations => { if (document.querySelector(selector)) { observer.disconnect(); resolve(document.querySelector(selector)); } }); // If you get "parameter 1 is not of type 'Node'" error, see https://stackoverflow.com/a/77855838/492336 observer.observe(document.body, { childList: true, subtree: true }); }); } waitforit('video').then((elm) => { $ = function(_) {return document.getElementById(_)} $tn = function(_) {return document.getElementsByTagName(_)} $cn = function(_) {return document.getElementsByClassName(_)} $qa = function(_) {return document.querySelectorAll(_)} var pathpart = window.location.pathname.split("/"), lang = pathpart[1], id = pathpart[3], name = pathpart[4], jsoncontainer, nix = "", url = "", filmid = "", cuescriptid = 0, liveid = "", archid = "", mode = "", kind = "", livelabel = "", chapterformat = "", trailer = "", loc_stream = "stream url", loc_video = "video", loc_audio = "audio", loc_subs = "subtitles", loc_set = "setlist", loc_cue = "cue file", loc_txt = "plain text", loc_dl = "download", loc_nodl = "(download unvailable)"; var trash = ["- Regarder le documentaire complet", " - Die ganze Doku", " - Komplette Sendung", " - Programm in voller Länge", " - Film in voller Länge", " - Regarder le film complet", " - Regarder l’émission complète"]; if (($qa('[data-testid="AUSSCHNITT"]')[0]) || ($qa('[data-testid="EXTRAIT"]')[0])) { trailer = "TRAILER"; var desc = '[data-testid="pc_desc-Heading"]'; var warn = $qa(desc)[0].innerText; var nraw = '' + trailer + ': '; $qa(desc)[0].innerHTML = nraw + warn; var newtitle = document.title; trash.forEach((item) => { newtitle = newtitle.replace(item, "") }); document.title = trailer + ": " + newtitle; } if (/ARTE Concert/.test(document.title)) { kind = "concert"; } if (lang == "fr") { loc_subs = "sous-titres"; loc_dl = "téléchargement"; loc_nodl = "(téléchargement indisponible)"; } if (lang == "de") { loc_stream = "Stream URL"; loc_video = "Video"; loc_audio = "Audio"; loc_subs = "Untertitel"; loc_set = "Setliste"; loc_cue = "cue-Datei"; loc_txt = "Textdatei"; loc_dl = "Download"; loc_nodl = "(Download nicht verfügbar)"; } function streamurl() { if (mode == "live") { var test = prompt(livelabel + loc_stream, url); } else { var test = prompt(loc_stream + " (click 'OK' for ffmpeg command)", url); if (test !== null) { prompt("ffmpeg command ('OK' for ffmpeg AUDIO-ONLY command)", 'ffmpeg -referer "' + location.href + '" -user_agent "' + window.navigator.userAgent + '" -i "' + url + '" -c copy -bsf:a aac_adtstoasc "' + filmtitle + '.mp4"'); if (test !== null) { prompt("ffmpeg AUDIO-ONLY command ('OK' for yt-dlp command)", 'ffmpeg -referer "' + location.href + '" -user_agent "' + window.navigator.userAgent + '" -i "' + url + '" -vn -c:a copy "' + filmtitle + '-audio.m4a"'); if (test !== null) { prompt("yt-dlp command", "yt-dlp '" + url + "'"); } } } } } function parsescripts(nextfid) { if (nextfid != -1) { nix = self.__next_f[nextfid].toString(); } if (mode == "live") { var mynewregex = new RegExp("m3u8.*", "gi"); url = nix.match("https://artesimulcast\.akamaized\.net.*m3u8")[0]; url = url.replace(mynewregex, "m3u8") if (lang == "de") { url = url.replace("artelive_fr", "artelive_de") } else { url = url.replace("artelive_de", "artelive_fr") } } else { var myregex = /Generate\/.*?\/\//; var match = nix.match(myregex); var mynewregex = new RegExp("\/.*", "gi") filmid = match[0].replace("Generate/", "").replace(mynewregex, ""); url = "https://manifest-arte.akamaized.net/api/manifest/v1/Generate/" + filmid + "/" + lang + "/XQ+KS+CHEV1/" + id + ".m3u8"; } return url; } function setlist() { var cue = "REM edit all fields to your liking, especially the filename (FILE ...)\n\n", txt = "", cuefilename = "", cuescript = "", schemaObj = ""; if (chapterformat == "chapter") { cuescript = self.__next_f[cuescriptid].toString().slice(22).slice(0, -2); schemaObj = JSON.parse(cuescript); var artistcreds = new Array(); artistcreds[0] = schemaObj.apiPlayerConfig.attributes.metadata.title; artistcreds[1] = schemaObj.apiPlayerConfig.attributes.metadata.subtitle; } else { cuescript = self.__next_f[cuescriptid].toString().slice(2); schemaObj = JSON.parse(cuescript); if (/ - /.test(schemaObj.name)) { var artistcreds = schemaObj.name.split(" - "); } else { var artistcreds = new Array(); artistcreds[0] = schemaObj.name; artistcreds[1] = schemaObj.name; } } cuefilename = artistcreds[0] + "-" + artistcreds[1]; cuefilename = cuefilename.replace(/[^a-z0-9-.]/gi, '_').toLowerCase(); cue += 'PERFORMER "' + artistcreds[0] + '"' + "\n"; cue += 'TITLE "' + artistcreds[1] + '"' + "\n"; cue += 'FILE "' + cuefilename + '.m4a" M4A' + "\n\n TRACK 01 AUDIO\n"; var cueperf = ' PERFORMER "' + artistcreds[0] + '"' + "\n"; cue += cueperf; cue += ' TITLE "Intro/Prologue"' + "\n"; cue += " INDEX 01 00:00:00\n"; txt += "00:00 Intro/Prologue\n"; var setlistlength = 0; if (chapterformat == "chapter") { setlistlength = schemaObj.apiPlayerConfig.attributes.chapters.elements.length; } else { setlistlength = schemaObj.hasPart.length; } for (var j = 0, k = setlistlength; j < k; ++j) { var songtitle = ""; var timestamp = ""; if (chapterformat == "chapter") { songtitle = schemaObj.apiPlayerConfig.attributes.chapters.elements[j].title; timestamp = schemaObj.apiPlayerConfig.attributes.chapters.elements[j].startTime; } else { songtitle = schemaObj.hasPart[j].name; timestamp = schemaObj.hasPart[j].startOffset; } var padm = ""; if (timestamp < 600) { padm = "0"; } var cuetrackid = j + 2; var ctidpad = ""; if (cuetrackid < 10) { ctidpad = "0"; } cue += "\n TRACK " + ctidpad + cuetrackid + " AUDIO \n"; cue += cueperf; cue += " TITLE " + '"' + songtitle + '"'; cue += "\n INDEX 01 " + padm + fmtMSS(timestamp) + ":00\n"; txt += padm + fmtMSS(timestamp) + " " + songtitle + "\n"; } cue += "\n"; window.cue = cue; window.txt = txt; window.cuefilename = cuefilename; } // https://stackoverflow.com/questions/3733227/javascript-seconds-to-minutes-and-seconds function fmtMSS(s) { return (s - (s %= 60)) / 60 + (9 < s ? ':' : ':0') + s; } // ... if (self.__next_f) { for (var i = 0, l = self.__next_f.length; i < l; ++i) { var it = self.__next_f[i].toString(); if (kind == "concert") { if (/startOffset/.test(it)) { cuescriptid = i; chapterformat = "offset"; } if (/chapterId/.test(it)) { cuescriptid = i; chapterformat = "chapter"; } } if (/m3u8/.test(it)) { if (/artelive/.test(it)) { liveid = i; mode = "live"; parsescripts(i); } else if (/Generate/.test(it)) { archid = i; mode = "archive"; var thestring = self.__next_f[archid].toString().slice(22, -1).slice(0, -1); if (/Generate\//.test(thestring)) { var schemaObj = JSON.parse(thestring); filmid = "notnull"; url = schemaObj.apiPlayerConfig.attributes.streams[0].url; } else { parsescripts(i); } } } } } else { for (var i = 0, l = $tn("script").length; i < l; ++i) { if ($tn("script")[i].innerText.indexOf("Generate/") != -1) { jsoncontainer = i; break; } } nix = $tn("script")[jsoncontainer].innerText; parsescripts(-1); } var download_url = ""; if ((url != "") && (url != null) && (url != "null")) { download_url = url; } else { download_url = "https://api.arte.tv/api/player/v2/config/" + lang + "/" + id; } var xhr = new XMLHttpRequest(); if (loadonload != 0) { if ((filmid != null) && (filmid != "null") && (filmid != "") && (filmid != "undefined") && (filmid != undefined) && (filmid != NaN)) { streamurl(); } } var filmtitle = document.querySelector('meta[property="og:title"]').content; filmtitle = filmtitle.replace(" | ARTE Concert", "").replace(" | ARTE", ""); trash.forEach((item) => { filmtitle = filmtitle.replace(item, "") }); filmtitle = filmtitle.replace(/ /g, "_").replace(/[^a-z0-9 \.,_-]/gim, "").replace("_-_", "-"); if (mode == "live") { livelabel = "live "; } if (lang == "de") { if (mode == "live") { livelabel = "Live "; } } var aclass = "ds-1nr7pfe"; var spanclass = "ds-92m6hs"; var topelementstream = document.createElement("a"); topelementstream.setAttribute('id', 'arteuserjsstream'); topelementstream.setAttribute('class', aclass); var innerelementstream = document.createElement("span"); innerelementstream.setAttribute('id', 'streamurl'); innerelementstream.setAttribute('class', spanclass); innerelementstream.setAttribute('style', 'color: #FA481C'); innerelementstream.innerText = livelabel + loc_stream; topelementstream.appendChild(innerelementstream); if (!($("arteuserjsstream"))) { if ($cn('ds-1r0jukn')[0]) { $cn('ds-1r0jukn')[0].insertBefore(topelementstream, null); } else if ($cn('ds-1rm5mah')[0]) { $cn('ds-1rm5mah')[0].insertBefore(topelementstream, null); } } if (mode == "archive") { var topelementdl = document.createElement("a"); topelementdl.setAttribute('id', 'arteuserjsdl'); topelementdl.setAttribute('class', 'ds-1nr7pfe'); var innerelementdl = document.createElement("span"); innerelementdl.setAttribute('id', 'arteuserjsdlinner'); innerelementdl.setAttribute('class', 'ds-11ckmbs'); innerelementdl.innerText = ""; topelementdl.appendChild(innerelementdl); if (!($("topelementdl"))) { if ($cn('ds-1r0jukn')[0]) { $cn('ds-1r0jukn')[0].insertBefore(topelementdl, null); } else if ($cn('ds-1rm5mah')[0]) { $cn('ds-1rm5mah')[0].insertBefore(topelementdl, null); } } var getJSON = function(url, callback) { xhr.open('GET', url, true); xhr.responseType = 'json'; xhr.onload = function() { var status = xhr.status; if (status == 200) { callback(null, xhr.response); } else { callback(status); } }; xhr.send(); }; var getM3U = function(url, callback) { xhr.open('GET', url, true); xhr.responseType = 'text'; xhr.onload = function() { var status = xhr.status; if (status == 200) { callback(null, xhr.response); } else { callback(status); } }; xhr.send(); }; if ((filmid != null) && (filmid != "null") && (filmid != "") && (filmid != "undefined") && (filmid != undefined) && (filmid != NaN)) { $("streamurl").onclick = function() { streamurl() } } else { getJSON(download_url, function(err, data) { if (err != null) { console.error(err); } else { url = data.data.attributes.streams[0].url; } }); } getM3U(url, function(err, data) { if (err != null) { console.error(err); } else { getM3U = function() {}; var mp4avail = 1, fhdavail = 0, videosarr = new Array(), audiosarr = new Array(), subtisarr = new Array(), videosarrng = new Array(), vid = 0, aud = 0, sub = 0, vidng = 0, videolinksng = "", result, videolinks, audiolinks, subtilinks = "", vregex = new RegExp(".*_v", "gi"), videoformats = /-([A-Z])_v/, uregex = /URI=(["'])(.*?)\1/, nregex = /NAME=(["'])(.*?)\1/; var lines = data.split(/[\r\n]/); for (var i in lines) { var line = lines[i]; if (!(/iframe/.test(line))) { if (!(/h265/.test(line))) { if (videoformats.test(line)) { if (/aka_me_session/.test(line)) { mp4avail = 0; } if (/v1080/.test(line)) { fhdavail = 1; } videosarr[vid] = line; vid++; } if (/TYPE=AUDIO/.test(line)) { var audiofile = line.match(uregex)[2]; var audiolabel = line.match(nregex)[2]; audiosarr[aud] = audiolabel + "#" + audiofile; aud++; } if (/TYPE=SUBTITLES/.test(line)) { var subfile = line.match(uregex)[2]; var sublabel = line.match(nregex)[2]; subtisarr[sub] = sublabel + "#" + subfile.replace("m3u8", "vtt"); sub++; } } else { videosarrng[vidng] = line; vidng++ } } } videosarr = videosarr.sort(); if (fhdavail == 1) { videosarr.push(videosarr.shift()); } videosarr = videosarr.reverse(); audiosarr = audiosarr.sort(); subtisarr = subtisarr.sort(); var bp = "• "; for (var i = 0, l = subtisarr.length; i < l; ++i) { var subcom = subtisarr[i].split("#"); subtilinks += ''; } for (var i = 0, l = audiosarr.length; i < l; ++i) { var audcom = audiosarr[i].split("#"); audiolinks += ''; } for (var i = 0, l = videosarr.length; i < l; ++i) { videolinks += ''; } if (videosarrng.length > 0) { videosarrng = videosarrng.sort(); videolinks += ''; for (var i = 0, l = videosarrng.length; i < l; ++i) { videolinks += ''; } } if (mp4avail == 1) { trailer = " " + trailer; result = '
'; result = result.replaceAll(".m3u8", ".mp4").replaceAll("undefined", ""); } else { result = loc_nodl; } $("arteuserjsdlinner").innerHTML = result; } }); var script = document.createElement("script"); script.innerHTML = ` function opennewtab() { var optionvalue = document.jump.dlmenu.options[document.jump.dlmenu.selectedIndex].value; if ((optionvalue == "cue") || (optionvalue == "txt")) { var hiddenElement = document.createElement('a'); var format; if (optionvalue == "cue") { format = encodeURIComponent(window.cue) } else { format = encodeURIComponent(window.txt) } hiddenElement.href = 'data:attachment/text,' + format; hiddenElement.download = window.cuefilename + '.' + optionvalue; hiddenElement.click(); } else if ((document.jump.dlmenu.selectedIndex > 0) && (optionvalue != "") && (optionvalue != "#")) { window.open(optionvalue, "artedltab"); } } `; document.body.appendChild(script); } $("streamurl").onclick = function() { streamurl() } if ((kind == "concert") && (cuescriptid > 0)) { setlist() } });