From 5fe734dd21969cb25e2428c287542ca815deb753 Mon Sep 17 00:00:00 2001 From: Bumba Date: Sun, 30 Sep 2018 18:10:34 +0200 Subject: [PATCH 01/11] 2.11 - fixedgetParent() - fixed credits ribbon not appearing - fixed decoration overlapping next section - fixed "undefined" fields in metadata download - fixed deprecated GM_xmlHttpRequest() -> GM.xmlHttpRequest(). Thanks @pherk =) - removed the @grant in the script, not being necessary anymore to run GM.xmlHttpRequest() - updated setTimeout race condition workaround for slower connection. If someone knows where to plug the callback please shoot :) --- arte-downloader.user.js | 35 +++++++++++++++++++++-------------- 1 file changed, 21 insertions(+), 14 deletions(-) diff --git a/arte-downloader.user.js b/arte-downloader.user.js index 7514c5e..5c04540 100644 --- a/arte-downloader.user.js +++ b/arte-downloader.user.js @@ -3,9 +3,8 @@ // @namespace GuGuss // @description Download videos or get stream link of ARTE programs in the selected language. // @include *//*.arte.tv/* -// @version 2.10 +// @version 2.11 // @updateURL https://github.com/GuGuss/ARTE-7-Downloader/raw/master/arte-downloader.user.js -// @grant GM_xmlhttpRequest // @icon http://www.arte.tv/favicon.ico // ==/UserScript== /* @@ -61,7 +60,7 @@ function hasClass(element, cls) { // Get a parent node of the chosen type and class function getParent(nodeReference, nodeName, classString) { - var parent = node; + var parent = nodeReference; var nbNodeIteration = 0; var nbNodeIterationMax = 10; @@ -248,7 +247,7 @@ function findPlayerJson(videoElement, videoElementIndex) { // oembed if (playerUrl !== null && (key === "oembed" || (key === "live-oembed"))) { - GM_xmlhttpRequest({ + GM.xmlhttpRequest({ method: "GET", url: playerUrl, onload: function(response) { @@ -275,7 +274,7 @@ function findPlayerJson(videoElement, videoElementIndex) { parsePlayerJson(jsonUrl, videoElement, videoElementIndex); } else { console.log("> Searching a 360 video in: " + playerUrl); - GM_xmlhttpRequest({ + GM.xmlhttpRequest({ method: "GET", url: playerUrl, onload: function(response) { @@ -300,7 +299,7 @@ function findPlayerJson(videoElement, videoElementIndex) { else if (playerUrl.indexOf("360FlashPlayers") > -1) { console.log("> old player"); var xml = doc.split('xml:"')[1].split('"')[0]; - GM_xmlhttpRequest({ + GM.xmlhttpRequest({ method: "GET", url: playerUrl + xml, onload: function(response) { @@ -326,7 +325,7 @@ function findPlayerJson(videoElement, videoElementIndex) { // Look for player URL inside the player json else { - GM_xmlhttpRequest({ + GM.xmlhttpRequest({ method: "GET", url: playerUrl, onload: function(response) { @@ -351,7 +350,7 @@ function findPlayers() { console.log("> Found playlist json: " + playlistJson) console.log() videoPlayerElements = parent.document.querySelectorAll("div.arte-playerfs.arte-playerfs--show"); - GM_xmlhttpRequest({ + GM.xmlhttpRequest({ method: "GET", url: playlistJson, onload: function(response) { @@ -469,7 +468,11 @@ function createButtonMetadata(videoElementIndex) { button.setAttribute('class', 'btn btn-default'); button.setAttribute('style', 'line-height: 17px; margin-left:10px; text-align: center; padding: 10px; color:rgb(40, 40, 40); background-color: rgb(230, 230, 230); font-family: ProximaNova,Arial,Helvetica,sans-serif; font-size: 13px;'); button.innerHTML = "Download description "; - var metadata = "[Title]\n" + title + "\n\n[Subtitle]\n" + subtitle + "\n\n[Description-short]\n" + description_short + "\n\n[Description]\n" + description + "\n\n[Tags]\n" + tags; + var metadata = (title !== undefined ? "[Title]\n" + title:'') + + (subtitle !== undefined ? "\n\n[Subtitle]\n" + subtitle:'') + + (description_short !== undefined ? "\n\n[Description-short]\n" + description_short:'') + + (description !== undefined ? "\n\n[Description]\n" + description:'') + + (tags !== undefined ? "\n\n[Tags]\n" + tags:''); var encodedData = window.btoa(unescape(encodeURIComponent(metadata))); button.setAttribute('href', 'data:application/octet-stream;charset=utf-8;base64,' + encodedData); button.setAttribute('download', getVideoName(videoElementIndex) + '.txt'); @@ -612,7 +615,7 @@ function decoratePlayer(videoElement, videoElementIndex) { } } } - setTimeout(function(){ insertAfter(container, parent); }, 500); + setTimeout(function(){ insertAfter(container, parent); }, 2000); } } @@ -674,11 +677,11 @@ function decoratePlayer(videoElement, videoElementIndex) { container.appendChild(indexElement); } - // Create video name + description span + // Create video name span var videoNameSpan = document.createElement('span'); - var subtitle = playerJson[videoElementIndex]['videoJsonPlayer']['VSU']; + var subtitle = playerJson[videoElementIndex].videoJsonPlayer.VSU; videoNameSpan.innerHTML = "" + getVideoName(videoElementIndex) + (subtitle !== undefined ? " - " + subtitle : "") + "
"; - videoNameSpan.setAttribute('style', 'margin:10px; text-align: center; color:rgb(255, 255, 255); font-family: ProximaNova,Arial,Helvetica,sans-serif; font-size: 13px;'); + videoNameSpan.setAttribute('style', 'margin-top:10px; text-align: center; color:rgb(255, 255, 255); font-family: ProximaNova,Arial,Helvetica,sans-serif; font-size: 16px;'); container.appendChild(videoNameSpan); // Create language combobox @@ -709,8 +712,12 @@ function decoratePlayer(videoElement, videoElementIndex) { // Create credits element if not RoyalSlider or if last player from RoyalSlider if (bRoyalSlider === false || videoElementIndex === nbVideoPlayers - 1) { // glitch with javascript XHR requests concurrency var credits = createCreditsElement(); - parent.appendChild(credits); + container.appendChild(credits); } + + // Workaround decoration overlapping next SECTION + var parentSection = getParent(parent, 'SECTION', 'margin-bottom-s'); + parentSection.style.marginBottom = "9rem"; } is_playlist = findPlayers(); From c58131b824b9ec0d3f051b31a9a2f86e23de2073 Mon Sep 17 00:00:00 2001 From: Bumba Date: Sun, 30 Sep 2018 18:27:18 +0200 Subject: [PATCH 02/11] 2.11 Updated installation instruction for new browsers. --- README.md | 12 +++++------- 1 file changed, 5 insertions(+), 7 deletions(-) diff --git a/README.md b/README.md index af29e47..d916bb2 100644 --- a/README.md +++ b/README.md @@ -1,7 +1,7 @@ ARTE-Downloader ================= -Current version: **2.10**. +Current version: **2.11**. CONTRIBUTORS ----------- @@ -29,14 +29,12 @@ For livestreams, you can either: INSTALLATION ------------ ### Step 1 : Install a script manager -* [Firefox users] Install the [Greasemonkey](https://addons.mozilla.org/en-US/firefox/addon/greasemonkey/) script manager. -* [Chrome users] Install the [Tampermonkey](https://chrome.google.com/webstore/detail/tampermonkey/dhdgffkkebhmkfjojejmpbldmpobfkfo) script manager. -* [Opera users] Install the [Violent monkey](https://addons.opera.com/fr/extensions/details/violent-monkey) script manager. +* [Firefox] [Tampermonkey](https://addons.mozilla.org/en-GB/firefox/addon/tampermonkey/) or [Greasemonkey](https://addons.mozilla.org/en-GB/firefox/addon/greasemonkey/) or [Violentmonkey](https://addons.mozilla.org/en-US/firefox/addon/violentmonkey/). +* [Chromium(Chrome/Vivaldi/Brave)] [Tampermonkey](https://chrome.google.com/webstore/detail/tampermonkey/dhdgffkkebhmkfjojejmpbldmpobfkfo) or [Violentmonkey](https://chrome.google.com/webstore/detail/violentmonkey/jinjaccalgkegednnccohejagnlnfdag). +* [Opera] [Tampermonkey-beta](https://addons.opera.com/en/extensions/details/tampermonkey-beta/). ### Step 2 : Install the script -* Go to [arte-downloader.user.js](../../raw/master/arte-downloader.user.js): *Install* and you're all set! - -**Note** To get the latest version: either update through your script manager, or redo step 2 ;-) +* Open [arte-downloader.user.js](../../raw/master/arte-downloader.user.js): *Install/Reinstall* and you're all set! FAQ --- From b0fea65d6edbb71d97b317c1ac3b8296092bccf3 Mon Sep 17 00:00:00 2001 From: Bumba Date: Sun, 30 Sep 2018 18:55:29 +0200 Subject: [PATCH 03/11] 2.11.1 - changed colors to fit current ARTE design. --- arte-downloader.user.js | 10 +++++----- 1 file changed, 5 insertions(+), 5 deletions(-) diff --git a/arte-downloader.user.js b/arte-downloader.user.js index 5c04540..21bdaa5 100644 --- a/arte-downloader.user.js +++ b/arte-downloader.user.js @@ -3,7 +3,7 @@ // @namespace GuGuss // @description Download videos or get stream link of ARTE programs in the selected language. // @include *//*.arte.tv/* -// @version 2.11 +// @version 2.11.1 // @updateURL https://github.com/GuGuss/ARTE-7-Downloader/raw/master/arte-downloader.user.js // @icon http://www.arte.tv/favicon.ico // ==/UserScript== @@ -557,9 +557,9 @@ function createQualityComboBox(videoElementIndex) { function createCreditsElement() { var credits = document.createElement('div'); - credits.setAttribute('style', 'text-align: center; line-height: 20px; font-size: 11.2px; color: rgb(255, 255, 255); font-family: ProximaNova, Arial, Helvetica, sans-serif; padding: 5px; background-image:url("data:image/gif;base64,R0lGODlhAwADAIAAAMhFJuFdPiH5BAAAAAAALAAAAAADAAMAAAIERB5mBQA7")'); + credits.setAttribute('style', 'text-align: center; line-height: 20px; font-size: 11.2px; color: rgb(255, 255, 255); font-family: ProximaNova, Arial, Helvetica, sans-serif; padding: 5px; background:#262626'); credits.innerHTML = 'Arte Downloader v.' + scriptVersion + ' built by and for the community with love' + - '
Contribute Here.'; + '
Contribute Here.'; return credits; } @@ -567,7 +567,7 @@ function decoratePlayer360(videoElement, videoURL, videoName) { var container = document.createElement('div'); insertAfter(container, videoElement); container.setAttribute('class', 'ArteDownloader-v' + scriptVersion) - container.setAttribute('style', 'background-image:url("data:image/gif;base64,R0lGODlhAwADAIAAAMhFJuFdPiH5BAAAAAAALAAAAAADAAMAAAIERB5mBQA7"); padding: 10px;'); + container.setAttribute('style', 'background:#262626; padding: 10px;'); var button = document.createElement('a'); button.innerHTML = "Download " + videoName + " "; button.setAttribute('id', 'btnArteDownloader'); @@ -667,7 +667,7 @@ function decoratePlayer(videoElement, videoElementIndex) { } container.setAttribute('class', 'ArteDownloader-v' + scriptVersion) - container.setAttribute('style', 'background-image:url("data:image/gif;base64,R0lGODlhAwADAIAAAMhFJuFdPiH5BAAAAAAALAAAAAADAAMAAAIERB5mBQA7"); padding: 10px;'); + container.setAttribute('style', 'background:#262626; padding: 10px;'); // Create index indicator if Royal Slider if (bRoyalSlider === true) { From 1ec517026f0ffff3218d8b8ec495b0853953fb4f Mon Sep 17 00:00:00 2001 From: Bumba Date: Sun, 30 Sep 2018 19:21:26 +0200 Subject: [PATCH 04/11] 2.11.2 - typo on GM.xmlHttpRequest which was GM.xmlhttpRequest https://github.com/Tampermonkey/tampermonkey/issues/457 --- arte-downloader.user.js | 13 +++++++------ 1 file changed, 7 insertions(+), 6 deletions(-) diff --git a/arte-downloader.user.js b/arte-downloader.user.js index 21bdaa5..b957c16 100644 --- a/arte-downloader.user.js +++ b/arte-downloader.user.js @@ -3,7 +3,8 @@ // @namespace GuGuss // @description Download videos or get stream link of ARTE programs in the selected language. // @include *//*.arte.tv/* -// @version 2.11.1 +// @version 2.11.2 +// @grant GM.xmlHttpRequest // @updateURL https://github.com/GuGuss/ARTE-7-Downloader/raw/master/arte-downloader.user.js // @icon http://www.arte.tv/favicon.ico // ==/UserScript== @@ -247,7 +248,7 @@ function findPlayerJson(videoElement, videoElementIndex) { // oembed if (playerUrl !== null && (key === "oembed" || (key === "live-oembed"))) { - GM.xmlhttpRequest({ + GM.xmlHttpRequest({ method: "GET", url: playerUrl, onload: function(response) { @@ -274,7 +275,7 @@ function findPlayerJson(videoElement, videoElementIndex) { parsePlayerJson(jsonUrl, videoElement, videoElementIndex); } else { console.log("> Searching a 360 video in: " + playerUrl); - GM.xmlhttpRequest({ + GM.xmlHttpRequest({ method: "GET", url: playerUrl, onload: function(response) { @@ -299,7 +300,7 @@ function findPlayerJson(videoElement, videoElementIndex) { else if (playerUrl.indexOf("360FlashPlayers") > -1) { console.log("> old player"); var xml = doc.split('xml:"')[1].split('"')[0]; - GM.xmlhttpRequest({ + GM.xmlHttpRequest({ method: "GET", url: playerUrl + xml, onload: function(response) { @@ -325,7 +326,7 @@ function findPlayerJson(videoElement, videoElementIndex) { // Look for player URL inside the player json else { - GM.xmlhttpRequest({ + GM.xmlHttpRequest({ method: "GET", url: playerUrl, onload: function(response) { @@ -350,7 +351,7 @@ function findPlayers() { console.log("> Found playlist json: " + playlistJson) console.log() videoPlayerElements = parent.document.querySelectorAll("div.arte-playerfs.arte-playerfs--show"); - GM.xmlhttpRequest({ + GM.xmlHttpRequest({ method: "GET", url: playlistJson, onload: function(response) { From 7479c8a18b5cef57c3661e20d6a623fac74bd220 Mon Sep 17 00:00:00 2001 From: Bumba Date: Sun, 30 Sep 2018 19:21:48 +0200 Subject: [PATCH 05/11] 2.11.2 --- README.md | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/README.md b/README.md index d916bb2..2491668 100644 --- a/README.md +++ b/README.md @@ -1,7 +1,7 @@ ARTE-Downloader ================= -Current version: **2.11**. +Current version: **2.11.2**. CONTRIBUTORS ----------- From 81cec7a3b6fd145f73cdb04fe0cfa017dededfa0 Mon Sep 17 00:00:00 2001 From: Bumba Date: Tue, 2 Oct 2018 07:22:17 +0200 Subject: [PATCH 06/11] 2.12 - removed unused RTMP, - removed royalSlider decoration support - removed 360 flash and flash player support - added playlist decoration back (not full playlist support yet) --- arte-downloader.user.js | 202 +++++++++------------------------------- 1 file changed, 43 insertions(+), 159 deletions(-) diff --git a/arte-downloader.user.js b/arte-downloader.user.js index b957c16..0d160a2 100644 --- a/arte-downloader.user.js +++ b/arte-downloader.user.js @@ -3,15 +3,14 @@ // @namespace GuGuss // @description Download videos or get stream link of ARTE programs in the selected language. // @include *//*.arte.tv/* -// @version 2.11.2 +// @version 2.12 // @grant GM.xmlHttpRequest // @updateURL https://github.com/GuGuss/ARTE-7-Downloader/raw/master/arte-downloader.user.js // @icon http://www.arte.tv/favicon.ico // ==/UserScript== /* Support: - - new 360 HTML5 player: http://info.arte.tv/fr/wasala-arriver - - old 360 flash player: http://future.arte.tv/fr/5-metres-une-plongee-360deg-sur-votre-ordinateur + - 360 HTML5 player: http://info.arte.tv/fr/wasala-arriver */ /* --- GLOBAL VARIABLES --- */ @@ -22,7 +21,6 @@ var is_playlist = false; var playerJson; var nbVideos; var nbHTTP; -var nbRTMP; var nbHLS; var languages; var qualities; @@ -114,8 +112,6 @@ function preParsePlayerJson(videoElementIndex) { // Check if video format or media type if (video["videoFormat"] === "HBBTV" || video["mediaType"] === "mp4") { nbHTTP[videoElementIndex]++; - } else if (video["videoFormat"] === "RMP4" || video["mediaType"] === "rtmp") { - nbRTMP[videoElementIndex]++; } else if (video["videoFormat"] === "M3U8" || video["mediaType"] === "hls") { nbHLS[videoElementIndex]++; } @@ -149,7 +145,7 @@ function preParsePlayerJson(videoElementIndex) { // Display preparse info console.log("\n====== player #" + videoElementIndex + " ======\n> " + - nbVideos[videoElementIndex] + " formats: " + nbHTTP[videoElementIndex] + " HTTP videos | " + nbRTMP[videoElementIndex] + " RTMP streams | " + nbHLS[videoElementIndex] + " HLS streams."); + nbVideos[videoElementIndex] + " formats: " + nbHTTP[videoElementIndex] + " MP4 videos | " + nbHLS[videoElementIndex] + " streams."); var languagesFound = ""; for (l in languages[videoElementIndex]) { languagesFound += "\n - " + languages[videoElementIndex][l]; @@ -201,21 +197,6 @@ function getVideoUrl(videoElementIndex, quality, language) { } } - // Search RTMP streams - if (nbRTMP[videoElementIndex] > 0) { - for (var key in videos) { - var video = playerJson[videoElementIndex]["videoJsonPlayer"]["VSR"][videos[key]]; - if ((video["versionCode"] === language || language === undefined) && - (video["VQU"] === quality || video["quality"] === quality) && - (video["videoFormat"] === "RMP4" || video["mediaType"] === "rtmp") - ) { - var url = video["streamer"] + video["url"]; - console.log("> " + quality + " RTMP stream: " + url); - return url; - } - } - } - // Search HLS streams if (nbHLS[videoElementIndex] > 0) { for (var key in videos) { @@ -239,7 +220,7 @@ function findPlayerJson(videoElement, videoElementIndex) { // Get player URL to find its associated json var playerUrl = null; var jsonUrl = null; - for (key in videoPlayerClass) { + for (var key in videoPlayerClass) { playerUrl = videoElement.getAttribute(videoPlayerClass[key]); if (playerUrl !== null) { break; @@ -269,8 +250,13 @@ function findPlayerJson(videoElement, videoElementIndex) { // iframe embedded media else if (playerUrl === null) { playerUrl = unescape(videoElement.getAttribute('src')); - jsonUrl = playerUrl.split('json_url=')[1]; + if (jsonUrl == undefined) { + jsonUrl = playerUrl.split('json_playlist_url=')[1]; + if (jsonUrl !== undefined) { + return; + } + } if (jsonUrl !== undefined) { parsePlayerJson(jsonUrl, videoElement, videoElementIndex); } else { @@ -283,7 +269,7 @@ function findPlayerJson(videoElement, videoElementIndex) { var doc = response.responseText; var videoName, videoURL; - // new 360 HTML5 player + // 360 HTML5 player if (playerUrl.indexOf("arte360") > -1) { var playerJS = playerUrl.split("?root")[0] + "jsmin/output.min.js?" + doc.split("window.appVersion = \"")[1].split("\"")[0]; console.log("> 360 player JS: " + playerJS); @@ -295,25 +281,6 @@ function findPlayerJson(videoElement, videoElementIndex) { console.log("> Subtitles URL: " + subtitlesURL); decoratePlayer360(videoElement, videoURL, videoName, subtitlesURL); } - - // old 360 flash player - else if (playerUrl.indexOf("360FlashPlayers") > -1) { - console.log("> old player"); - var xml = doc.split('xml:"')[1].split('"')[0]; - GM.xmlHttpRequest({ - method: "GET", - url: playerUrl + xml, - onload: function(response) { - xml = response.responseText; - - // Get video URL - videoName = xml.split('videourl="%SWFPATH%/')[1].split('"')[0]; - videoURL = playerUrl + videoName; - console.log(videoURL); - decoratePlayer360(videoElement, videoURL, videoName); - } - }); - } } }); } @@ -331,13 +298,18 @@ function findPlayerJson(videoElement, videoElementIndex) { url: playerUrl, onload: function(response) { var json = JSON.parse(response.responseText); - playerUrl = json["videoJsonPlayer"]["videoPlayerUrl"]; + playerUrl = json["videoJsonPlayer.videoPlayerUrl"]; - if (playerUrl === undefined) { // not found ? Look for playlist file inside the livestream player + // not found ? Look for playlist file inside the livestream player + if (playerUrl === undefined) { console.log("Video player URL not available. Fetching livestream player URL"); playerUrl = videoElement.getAttribute(videoPlayerClass['live']); } - parsePlayerJson(playerUrl, videoElement, videoElementIndex); + if (playerUrl !== undefined) { + parsePlayerJson(playerUrl, videoElement, videoElementIndex); + } else { + console.log("Couldn't find a player URL."); + } } }); } @@ -345,17 +317,16 @@ function findPlayerJson(videoElement, videoElementIndex) { function findPlayers() { // Check playlist - playlistJson = /playlist_url=([^&]+)/.exec(window.location.href); + var playlistJson = /playlist_url=([^&]+)/.exec(window.location.href); if (playlistJson != null) { playlistJson=unescape(playlistJson[1]); - console.log("> Found playlist json: " + playlistJson) - console.log() - videoPlayerElements = parent.document.querySelectorAll("div.arte-playerfs.arte-playerfs--show"); + console.log("> Found playlist json: " + playlistJson); + videoPlayerElements = document.childNodes; GM.xmlHttpRequest({ method: "GET", url: playlistJson, onload: function(response) { - jsonUrl = JSON.parse(response.responseText); + var jsonUrl = JSON.parse(response.responseText); //check whether exists a valid entry if(typeof jsonUrl["videos"]!=="undefined" && typeof jsonUrl["videos"][0]!=="undefined") { jsonUrl = jsonUrl["videos"][0]["jsonUrl"]; @@ -368,7 +339,7 @@ function findPlayers() { return true; } else { // Check regular tags - for (tag in videoPlayerClass) { + for (var tag in videoPlayerClass) { videoPlayerElements = document.querySelectorAll("div[" + videoPlayerClass[tag] + "]"); if (videoPlayerElements.length > 0) { console.log(videoPlayerClass[tag]) @@ -412,10 +383,9 @@ function createButtonDownload(videoElementIndex, language) { var button = document.createElement('a'); var videoUrl; - for (q in qualities[videoElementIndex]) { + for (var q in qualities[videoElementIndex]) { videoUrl = getVideoUrl(videoElementIndex, q, language); if (videoUrl !== '') { - quality = q; break; } } @@ -426,19 +396,14 @@ function createButtonDownload(videoElementIndex, language) { return null; } - // Check RTMP stream - if (nbRTMP[videoElementIndex] > 0 && videoUrl.substring(0, 7) === "rtmp://") { // check first because it ends with .mp4 like HTTP - button.innerHTML = "Open VLC > CTRL+R > Network > Copy this link > Convert/Save video. "; - } - // Check HTTP - else if (nbHTTP[videoElementIndex] > 0 && videoUrl.substring(videoUrl.length - 4, videoUrl.length) === ".mp4") { + if (nbHTTP[videoElementIndex] > 0 && videoUrl.substring(videoUrl.length - 4, videoUrl.length) === ".mp4") { button.innerHTML = "Download video "; } - // Check HLS stream : should not happen + // Check HTTP Live Stream else if (nbHLS[videoElementIndex] > 0 && videoUrl.substring(videoUrl.length - 5, videoUrl.length === ".m3u8")) { - button.innerHTML = "Open VLC > CTRL+R > Network > Copy this link > Convert/Save video. "; + button.innerHTML = "Copy this link > Open VLC > CTRL+R > Network > CTRL+V > Convert/Save video. "; } // Unknown URL format : should not happen @@ -517,7 +482,7 @@ function createLanguageComboBox(videoElementIndex) { } } languageComboBox.setAttribute('class', 'btn btn-default'); - languageComboBox.setAttribute('style', (languageComboBox.innerHTML === "" ? "visibility:hidden;" : "max-width: 140px; padding: 6px; color:rgb(40, 40, 40); background-color: rgb(230, 230, 230); font-family: ProximaNova,Arial,Helvetica,sans-serif; font-size: 13px; font-weight: 400;")); + languageComboBox.setAttribute('style', (languageComboBox.innerHTML === "" ? "visibility:hidden;" : "max-width: 160px; padding: 6px; color:rgb(40, 40, 40); background-color: rgb(230, 230, 230); font-family: ProximaNova,Arial,Helvetica,sans-serif; font-size: 13px; font-weight: 400;")); return languageComboBox; } @@ -546,7 +511,7 @@ function createQualityComboBox(videoElementIndex) { }; // Fill with available qualities - for (q in qualities[videoElementIndex]) { + for (var q in qualities[videoElementIndex]) { if (qualities[videoElementIndex][q] !== 0) { qualityComboBox.innerHTML += ""; } @@ -581,103 +546,30 @@ function decoratePlayer360(videoElement, videoURL, videoName) { } function decoratePlayer(videoElement, videoElementIndex) { - var parent; - var bRoyalSlider = false; var container = document.createElement('div'); + var parent = videoElement.parentNode; - if (is_playlist) { - console.log("> Decorating playlist player") - videoElement.insertBefore(container, videoElement.firstChild) + // decorate iframe from embedment + if (window.frameElement !== null) { + parent = window.parent.document.querySelector('div.video-embed'); + setTimeout(function(){ insertAfter(container, parent); }, 2000); } else { - // Look for the parent to decorate - parent = videoElement.parentNode; - - if (videoElement.id == "jwPlayerContainer") { - console.log("> Decorating playlist player"); - } - - // iframe player - else if (videoElement.nodeName === "IFRAME") { + // decorate iframe from top window + if (videoElement.nodeName === "IFRAME") { console.log("> Decorating iFrame player"); - - // Arte touslesinternets - if (stringStartsWith(window.location.href, "http://touslesinternets.arte")) { - parent.insertBefore(container, videoElement); - } else { - // Arte Tracks - if (stringStartsWith(window.location.href, "http://tracks.arte")) { - parent = getParent(videoElement, '', "video"); - } else { - parent = document.querySelector('div.video-embed'); - if ( parent == null ) { - parent = document.querySelector('div.article-video'); - if ( parent == null ) { - console.log("> ERROR: could not find a parent to decorate."); - } - } + parent = document.querySelector('div.video-embed'); + if ( parent == null ) { + parent = document.querySelector('div.article-video'); + if ( parent == null ) { + console.error("> Could not find a parent to decorate."); } - setTimeout(function(){ insertAfter(container, parent); }, 2000); - } - } - - // http://www.arte.tv/arte_vp - else if (stringStartsWith(unescape(top.location), "http://www.arte.tv/arte_vp/")) { - console.log("> Decorating arte_vp"); - var child = document.getElementById("arte_vp_player_container"); - child.parentNode.insertBefore(container, child); - parent = container; - } - - // overlayed player for Arte Cinema or media embedded - else if (stringStartsWith(location.href, "http://cinema.arte") || - (parent.parentNode.getAttribute('id') === "embed_widget")) { - - console.log("> Decorating overlayed Cinema player"); - parent = parent.parentNode.parentNode; - parent.appendChild(container); - } - - // royal slider player - else if (stringStartsWith(videoElement.getAttribute('class'), 'rsContent')) { - console.log("> Decorating RoyalSlider player"); - bRoyalSlider = true; - - // Get the parent with SliderTeaserView type - while (parent.getAttribute('data-teaser-type') !== "SliderTeaserView" && parent.getAttribute('class') !== 'dnd-drop-wrapper') { - parent = parent.parentNode; - } - insertAfter(container, parent); - } - - // regular player - else { - console.log("> Decorating regular player"); - if (stringStartsWith(location.href, "http://concert.arte")) { - var playerSection = document.querySelector('section#section-player'); - if (playerSection !== null) { - insertAfter(container, playerSection); - } else { - parent = parent.parentNode; - parent.appendChild(container); - } - } else { - parent = parent.parentNode; - parent.appendChild(container); } + setTimeout(function(){ insertAfter(container, parent); }, 2000); } } - container.setAttribute('class', 'ArteDownloader-v' + scriptVersion) container.setAttribute('style', 'background:#262626; padding: 10px;'); - // Create index indicator if Royal Slider - if (bRoyalSlider === true) { - var indexElement = document.createElement('span'); - indexElement.innerHTML = "Video " + (videoElementIndex + 1) + " / " + nbVideoPlayers; - indexElement.setAttribute('style', 'margin:10px; text-align: center; color:rgb(255, 255, 255); font-family: ProximaNova,Arial,Helvetica,sans-serif; font-size: 13px;'); - container.appendChild(indexElement); - } - // Create video name span var videoNameSpan = document.createElement('span'); var subtitle = playerJson[videoElementIndex].videoJsonPlayer.VSU; @@ -710,12 +602,6 @@ function decoratePlayer(videoElement, videoElementIndex) { container.appendChild(btnMetadata); } - // Create credits element if not RoyalSlider or if last player from RoyalSlider - if (bRoyalSlider === false || videoElementIndex === nbVideoPlayers - 1) { // glitch with javascript XHR requests concurrency - var credits = createCreditsElement(); - container.appendChild(credits); - } - // Workaround decoration overlapping next SECTION var parentSection = getParent(parent, 'SECTION', 'margin-bottom-s'); parentSection.style.marginBottom = "9rem"; @@ -728,7 +614,6 @@ if (nbVideoPlayers > 0) { playerJson = [nbVideoPlayers]; nbVideos = [nbVideoPlayers]; nbHTTP = [nbVideoPlayers]; - nbRTMP = [nbVideoPlayers]; nbHLS = [nbVideoPlayers]; languages = [nbVideoPlayers]; qualities = [nbVideoPlayers]; @@ -736,7 +621,6 @@ if (nbVideoPlayers > 0) { playerJson[i] = 0; nbVideos[i] = 0; nbHTTP[i] = 0; - nbRTMP[i] = 0; nbHLS[i] = 0; languages[i] = new Object; qualities[i] = new Object; From 8c47647b9aa0b64c07a4ab18c01929db5bab6884 Mon Sep 17 00:00:00 2001 From: Bumba Date: Tue, 2 Oct 2018 07:22:37 +0200 Subject: [PATCH 07/11] 2.12 --- README.md | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/README.md b/README.md index 2491668..41b59e3 100644 --- a/README.md +++ b/README.md @@ -1,7 +1,7 @@ ARTE-Downloader ================= -Current version: **2.11.2**. +Current version: **2.12**. CONTRIBUTORS ----------- From d631d20d8bb1c4f98eff5a985994c8da5dcc31f5 Mon Sep 17 00:00:00 2001 From: Bumba Date: Tue, 2 Oct 2018 17:15:51 +0200 Subject: [PATCH 08/11] forgot credits ribbon --- arte-downloader.user.js | 4 ++++ 1 file changed, 4 insertions(+) diff --git a/arte-downloader.user.js b/arte-downloader.user.js index 0d160a2..e15ea2a 100644 --- a/arte-downloader.user.js +++ b/arte-downloader.user.js @@ -602,6 +602,10 @@ function decoratePlayer(videoElement, videoElementIndex) { container.appendChild(btnMetadata); } + // Create credits ribbon + var credits = createCreditsElement(); + container.appendChild(credits); + // Workaround decoration overlapping next SECTION var parentSection = getParent(parent, 'SECTION', 'margin-bottom-s'); parentSection.style.marginBottom = "9rem"; From 3bbf3f7619b5648939215ee9b2d5082cd8fc36b9 Mon Sep 17 00:00:00 2001 From: Bumba Date: Mon, 8 Oct 2018 22:13:00 +0200 Subject: [PATCH 09/11] 3.0 + supports playlist (e.g: "live" and "concert") + update to ES6 2015 "var" => "let" declarations. + replace property accesses w/ dot notation. + replace nameless functions w/ arrow functions + replace XHR w/ window.fetch() when possible + reworked the script entry point: findPlayers() + fix some procedures holes (failing on null) + fix bitrate display. Was in Kbps and not in bps. Also added a KBytes conversion. + added @TODOs - removes old tags parsing --- arte-downloader.user.js | 512 +++++++++++++++++++++------------------- 1 file changed, 268 insertions(+), 244 deletions(-) diff --git a/arte-downloader.user.js b/arte-downloader.user.js index e15ea2a..6197bcb 100644 --- a/arte-downloader.user.js +++ b/arte-downloader.user.js @@ -3,42 +3,25 @@ // @namespace GuGuss // @description Download videos or get stream link of ARTE programs in the selected language. // @include *//*.arte.tv/* -// @version 2.12 +// @version 3.0 // @grant GM.xmlHttpRequest // @updateURL https://github.com/GuGuss/ARTE-7-Downloader/raw/master/arte-downloader.user.js -// @icon http://www.arte.tv/favicon.ico +// @icon https://www.arte.tv/favicon.ico // ==/UserScript== /* Support: - - 360 HTML5 player: http://info.arte.tv/fr/wasala-arriver + - 360 HTML5 player: https://info.arte.tv/fr/wasala-arriver */ /* --- GLOBAL VARIABLES --- */ -var scriptVersion = GM_info.script.version; -var player = []; -var nbVideoPlayers = 0; -var is_playlist = false; -var playerJson; -var nbVideos; -var nbHTTP; -var nbHLS; -var languages; -var qualities; -var videoPlayerElements; - -var videoPlayerClass = { - 'standard': 'arte_vp_url', - 'oembed': 'arte_vp_url_oembed', - 'general': 'video-container', - 'live-oembed': 'arte_vp_live-url-oembed', - 'generic': 'data-url', - 'teaser': 'data-teaser-url' -}; - -var videoPlayerClassEmbedded = { - 'story': 'embed.embed--delay iframe', - 'embedded': 'media_embed iframe' -}; +let scriptVersion = GM_info.script.version; +let playerJson; +let nbVideos; +let nbHTTP; +let nbHLS; +let languages; +let qualities; + /* --- FUNCTIONS: utilities --- */ function getURLParameter(url, name) { @@ -46,7 +29,11 @@ function getURLParameter(url, name) { } function insertAfter(newNode, referenceNode) { - referenceNode.parentNode.insertBefore(newNode, referenceNode.nextSibling); + if (referenceNode.parentNode == null) { + referenceNode.insertBefore(newNode, referenceNode.nextSibling); + } else { + referenceNode.parentNode.insertBefore(newNode, referenceNode.nextSibling); + } } function stringStartsWith(string, prefix) { @@ -59,13 +46,12 @@ function hasClass(element, cls) { // Get a parent node of the chosen type and class function getParent(nodeReference, nodeName, classString) { - var parent = nodeReference; - var nbNodeIteration = 0; - var nbNodeIterationMax = 10; + let parent = nodeReference; + let nbNodeIteration = 0; + let nbNodeIterationMax = 10; // any node if (nodeName === '') { - console.log("> Looking for a parent node with class '" + classString + "'"); while (parent.nodeName !== "BODY" && nbNodeIteration < nbNodeIterationMax && hasClass(parent, classString) === false) { @@ -78,8 +64,9 @@ function getParent(nodeReference, nodeName, classString) { else { console.log("> Looking for a <" + nodeName + " class='" + classString + "'> parent node"); while (parent.nodeName !== "BODY" && - nbNodeIteration < nbNodeIterationMax && - (parent.nodeName !== nodeName.toUpperCase() || hasClass(parent, classString) === false)) { + parent.parentNode !== null && + nbNodeIteration < nbNodeIterationMax && + (parent.nodeName !== nodeName.toUpperCase() || hasClass(parent, classString) === false)) { nbNodeIteration++; parent = parent.parentNode; } @@ -102,52 +89,55 @@ function addQuality(videoElementIndex, quality, wording) { function preParsePlayerJson(videoElementIndex) { if (playerJson[videoElementIndex]) { - var videos = Object.keys(playerJson[videoElementIndex]["videoJsonPlayer"]["VSR"]); + let videos = Object.keys(playerJson[videoElementIndex].videoJsonPlayer.VSR); nbVideos[videoElementIndex] = videos.length; // Loop through all videos URLs. - for (var key in videos) { - var video = playerJson[videoElementIndex]["videoJsonPlayer"]["VSR"][videos[key]]; + for (let key in videos) { + let video = playerJson[videoElementIndex].videoJsonPlayer.VSR[videos[key]]; // Check if video format or media type - if (video["videoFormat"] === "HBBTV" || video["mediaType"] === "mp4") { + if (video.videoFormat === "HBBTV" || video.mediaType === "mp4") { nbHTTP[videoElementIndex]++; - } else if (video["videoFormat"] === "M3U8" || video["mediaType"] === "hls") { + } else if (video.videoFormat === "M3U8" || video.mediaType === "hls") { nbHLS[videoElementIndex]++; } - addLanguage(videoElementIndex, video["versionCode"], video["versionLibelle"]); + addLanguage(videoElementIndex, video.versionCode, video.versionLibelle); addQuality(videoElementIndex, ( - video["VQU"] !== undefined ? video["VQU"] : video["quality"]), - video["height"] ? video["height"] + "p@" + video["bitrate"] + "bps" : video["quality"]); + video.VQU !== undefined ? video.VQU : video.quality), + video.height ? video.height + + "p@" + Math.round(video.bitrate /1000*10) /10 + "Mbps (" // convert kbps > Mbps + + Math.round(video.bitrate *1000/8/1024) + "kB/s)" // convert kbps > kB/s + : video.quality); } // Remove Apple HLS if HTTP available if (nbHTTP[videoElementIndex] > 0) { - delete qualities[videoElementIndex]["XS"]; - delete qualities[videoElementIndex]["XQ"]; + delete qualities[videoElementIndex].XS; + delete qualities[videoElementIndex].XQ; } // Reorder qualities - var sortedKeys = Object.keys(qualities[videoElementIndex]).sort( + let sortedKeys = Object.keys(qualities[videoElementIndex]).sort( function(a, b) { // array of sorted keys - return qualities[videoElementIndex][b].split('@')[1].split('b')[0] * 1 - qualities[videoElementIndex][a].split('@')[1].split('b')[0] * 1; + return qualities[videoElementIndex][b].split('@')[1].split('M')[0] * 1 - qualities[videoElementIndex][a].split('@')[1].split('M')[0] * 1; } ); // Create new object to rearrange qualities according to new key order - var temp = new Object; - for (var i = 0; i < sortedKeys.length; i++) { + let temp = new Object; + for (let i = 0; i < sortedKeys.length; i++) { temp[sortedKeys[i]] = qualities[videoElementIndex][sortedKeys[i]]; } qualities[videoElementIndex] = temp; // replace with new ordered object // Display preparse info - console.log("\n====== player #" + videoElementIndex + " ======\n> " + + console.log("\n====== player #" + videoElementIndex+1 + " ======\n> " + nbVideos[videoElementIndex] + " formats: " + nbHTTP[videoElementIndex] + " MP4 videos | " + nbHLS[videoElementIndex] + " streams."); - var languagesFound = ""; - for (l in languages[videoElementIndex]) { + let languagesFound = ""; + for (let l in languages[videoElementIndex]) { languagesFound += "\n - " + languages[videoElementIndex][l]; } console.log("> Languages:" + languagesFound); @@ -165,9 +155,9 @@ function parsePlayerJson(playerJsonUrl, videoElement, videoElementIndex) { } function getVideoName(videoElementIndex) { - var name = playerJson[videoElementIndex]['videoJsonPlayer']['VTI']; + let name = playerJson[videoElementIndex].videoJsonPlayer.VTI; if (name === null) { - name = playerJson[videoElementIndex]['videoJsonPlayer']['VST']['VNA']; + name = playerJson[videoElementIndex].videoJsonPlayer.VST.VNA; if (name === null) { return "undefined"; } @@ -178,36 +168,36 @@ function getVideoName(videoElementIndex) { function getVideoUrl(videoElementIndex, quality, language) { // Get videos object - var videos = Object.keys(playerJson[videoElementIndex]["videoJsonPlayer"]["VSR"]); + let videos = Object.keys(playerJson[videoElementIndex].videoJsonPlayer.VSR); // Check if there are HTTP videos if (nbHTTP[videoElementIndex] > 0) { // Loop through all videos URLs. - for (var key in videos) { - var video = playerJson[videoElementIndex]["videoJsonPlayer"]["VSR"][videos[key]]; + for (let key in videos) { + let video = playerJson[videoElementIndex].videoJsonPlayer.VSR[videos[key]]; // Check language, format, quality - if (video["versionCode"] === language && - (video["videoFormat"] === "HBBTV" || video["mediaType"] === "mp4") && - (video["VQU"] === quality || video["quality"] === quality)) { - console.log("> " + quality + " MP4 in " + language + ": " + video["url"]); - return video["url"]; + if (video.versionCode === language && + (video.videoFormat === "HBBTV" || video.mediaType === "mp4") && + (video.VQU === quality || video.quality === quality)) { + console.log("> " + quality + " MP4 in " + language + ": " + video.url); + return video.url; } } } // Search HLS streams if (nbHLS[videoElementIndex] > 0) { - for (var key in videos) { - var video = playerJson[videoElementIndex]["videoJsonPlayer"]["VSR"][videos[key]]; + for(let key in videos) { + let video = playerJson[videoElementIndex].videoJsonPlayer.VSR[videos[key]]; if ( - (video["videoFormat"] === "M3U8" || video["mediaType"] === "hls") && - (video["VQU"] === quality || video["quality"] === quality) && - video["versionCode"] === language + (video.videoFormat === "M3U8" || video.mediaType === "hls") && + (video.VQU === quality || video.quality === quality) && + video.versionCode === language ) { - console.log("> HLS stream: " + video["url"]); - return video["url"]; + console.log("> HLS stream: " + video.url); + return video.url; } } } @@ -218,37 +208,11 @@ function getVideoUrl(videoElementIndex, quality, language) { function findPlayerJson(videoElement, videoElementIndex) { // Get player URL to find its associated json - var playerUrl = null; - var jsonUrl = null; - for (var key in videoPlayerClass) { - playerUrl = videoElement.getAttribute(videoPlayerClass[key]); - if (playerUrl !== null) { - break; - } - } - - // oembed - if (playerUrl !== null && (key === "oembed" || (key === "live-oembed"))) { - GM.xmlHttpRequest({ - method: "GET", - url: playerUrl, - onload: function(response) { - jsonUrl = unescape(response.responseText.split("json_url=")[1].split('"')[0]); - if (jsonUrl !== undefined) { - parsePlayerJson(jsonUrl, videoElement, videoElementIndex); - } - } - }); - } - - // http://www.arte.tv/arte_vp/ - else if (stringStartsWith(location.href, "http://www.arte.tv/arte_vp/")) { - playerUrl = unescape(location.href.split("json_url=")[1]); - parsePlayerJson(playerUrl, videoElement, videoElementIndex); - } + let playerUrl = null; + let jsonUrl = null; // iframe embedded media - else if (playerUrl === null) { + if (playerUrl === null) { playerUrl = unescape(videoElement.getAttribute('src')); jsonUrl = playerUrl.split('json_url=')[1]; if (jsonUrl == undefined) { @@ -264,12 +228,9 @@ function findPlayerJson(videoElement, videoElementIndex) { GM.xmlHttpRequest({ method: "GET", url: playerUrl, - onload: function(response) { - + onload: response => { var doc = response.responseText; var videoName, videoURL; - - // 360 HTML5 player if (playerUrl.indexOf("arte360") > -1) { var playerJS = playerUrl.split("?root")[0] + "jsmin/output.min.js?" + doc.split("window.appVersion = \"")[1].split("\"")[0]; console.log("> 360 player JS: " + playerJS); @@ -296,94 +257,171 @@ function findPlayerJson(videoElement, videoElementIndex) { GM.xmlHttpRequest({ method: "GET", url: playerUrl, - onload: function(response) { - var json = JSON.parse(response.responseText); + onload: response => { + let json = JSON.parse(response.responseText); playerUrl = json["videoJsonPlayer.videoPlayerUrl"]; - - // not found ? Look for playlist file inside the livestream player - if (playerUrl === undefined) { - console.log("Video player URL not available. Fetching livestream player URL"); - playerUrl = videoElement.getAttribute(videoPlayerClass['live']); - } if (playerUrl !== undefined) { parsePlayerJson(playerUrl, videoElement, videoElementIndex); } else { - console.log("Couldn't find a player URL."); + console.error("Couldn't find a player URL."); } } }); } } -function findPlayers() { - // Check playlist - var playlistJson = /playlist_url=([^&]+)/.exec(window.location.href); - if (playlistJson != null) { - playlistJson=unescape(playlistJson[1]); - console.log("> Found playlist json: " + playlistJson); - videoPlayerElements = document.childNodes; - GM.xmlHttpRequest({ - method: "GET", - url: playlistJson, - onload: function(response) { - var jsonUrl = JSON.parse(response.responseText); - //check whether exists a valid entry - if(typeof jsonUrl["videos"]!=="undefined" && typeof jsonUrl["videos"][0]!=="undefined") { - jsonUrl = jsonUrl["videos"][0]["jsonUrl"]; - jsonUrl = jsonUrl.replace(/\\/g, ''); // remove backslashes from the URL - parsePlayerJson(jsonUrl, videoPlayerElements[0], 0); - } - } - }); - nbVideoPlayers = videoPlayerElements.length; - return true; - } else { - // Check regular tags - for (var tag in videoPlayerClass) { - videoPlayerElements = document.querySelectorAll("div[" + videoPlayerClass[tag] + "]"); - if (videoPlayerElements.length > 0) { - console.log(videoPlayerClass[tag]) - break; - } +function initParsingSystem(nbVideoPlayers) { + if (nbVideoPlayers > 0) { + console.log("> Found " + nbVideoPlayers + " video player" + (nbVideoPlayers > 1 ? 's':'')); + playerJson = [nbVideoPlayers]; + nbVideos = [nbVideoPlayers]; + nbHTTP = [nbVideoPlayers]; + nbHLS = [nbVideoPlayers]; + languages = [nbVideoPlayers]; + qualities = [nbVideoPlayers]; + for (let i = 0; i < nbVideoPlayers; i++) { + playerJson[i] = 0; + nbVideos[i] = 0; + nbHTTP[i] = 0; + nbHLS[i] = 0; + languages[i] = new Object; + qualities[i] = new Object; } + } +} - // Check embedded tags - if (videoPlayerElements.length === 0) { - for (tag in videoPlayerClassEmbedded) { - videoPlayerElements = document.querySelectorAll("div." + videoPlayerClassEmbedded[tag]); - if (videoPlayerElements.length > 0) { - console.log(tag) - break; +/* +=========== +ENTRY POINT +=========== +*/ +(function findPlayers() { + console.log('\n===== ARTE DOWNLOADER v' + scriptVersion + ' started ====='); + // Look up inline scripts to find highlight of the day playlist + /*let scripts = document.querySelectorAll("script"); + if (scripts !== undefined) { + let matchStringStart = "window.__INITIAL_STATE__ = "; + let matchStringEnd = "}}}}"; + for(let i=0; i < scripts.length; i++) { + let str = scripts[i].innerHTML.trim(); + if (str.startsWith(matchStringStart)) { + str = str.split(matchStringStart)[1].split(matchStringEnd)[0] + matchStringEnd; + let inlineJson = JSON.parse(str); + inlineJson = inlineJson.pages.list["LIVE_fr_{}"].zones; + if (inlineJson[0].code.name == "today_guide_LIVE") { + inlineJson = inlineJson[0].data; + console.log(inlineJson); } } } + }*/ - // Check iframe - if (videoPlayerElements.length === 0) { - videoPlayerElements = document.querySelectorAll("iframe[arte-video]"); + // Check playlist + let playlistJson = /playlist_url=([^&]+)/.exec(window.location.href); + if (playlistJson != null) { + playlistJson = unescape(playlistJson[1]); + console.log("> Found playlist json: " + playlistJson); + + // Fetch playlist json + window.fetch(playlistJson).then(resp => resp.json()).then( jsonUrl => { + // Check for valid entry + if(typeof jsonUrl.videos !== "undefined" && typeof jsonUrl.videos[0] !== "undefined") { + let total = jsonUrl.videos.length; + initParsingSystem(total); + for (let i = 0; i < total; i++) { + let videoJsonUrl = jsonUrl.videos[i].jsonUrl; + videoJsonUrl = videoJsonUrl.replace(/\\/g, ''); // remove backslashes from the URL + parsePlayerJson(videoJsonUrl, document, i); // document shall be the inner player iframe + } - // Check 360 (no attributes yet) - if (videoPlayerElements.length === 0) { - videoPlayerElements = document.querySelectorAll("iframe"); + // Wait recursively for each video downloaders to be created + let playlistSelector; + (function fetchDownloaders (i) { + let videoDownloaders; + setTimeout( () => { + videoDownloaders = parent.document.querySelectorAll('div[id^=video_]'); + + // when each have been created + if (videoDownloaders.length == total) { + + // Create a playlist selector + console.log("\n===== PLAYLIST ======"); + console.log("> Creating playlist selector."); + let container = document.createElement('div'); + let span = document.createElement('span'); + span.innerHTML = 'Select video to download'; + span.setAttribute('style', "margin-top: 10px; padding: 10px; max-width: 720px; color:white; font-family: ProximaNova,Arial,Helvetica,sans-serif; font-size: 16px;"); + container.appendChild(span); + let cbVideoSelector = document.createElement('select'); + container.appendChild(cbVideoSelector); + cbVideoSelector.setAttribute('id', 'playlistSelector'); + cbVideoSelector.setAttribute('style', "margin-top: 10px; padding: 10px; max-width: 720px;"); + cbVideoSelector.onchange = () => { + // hide others + parent.document.querySelectorAll('div[id^=video_]').forEach( el => { + el.style.visibility = "hidden"; + el.style.height = "0px"; + el.style.padding = "0px"; + el.style.margin = "0px"; + el.style.lineHeight = "0px"; + el.style.visibility = "hidden" + }); + + let selection = cbVideoSelector.options[cbVideoSelector.selectedIndex].value; + let downloader = parent.document.getElementById(selection); + downloader.style.visibility = "visible"; + downloader.style.height = "7rem"; + downloader.style.padding = "10px"; + downloader.style.margin = "10px"; + downloader.style.lineHeight = "1.5"; + insertAfter(downloader, cbVideoSelector); + console.log("> Selected downloader for " + selection); + }; + + // Hide each downloader to only display the current selected by the playlist selector + videoDownloaders.forEach( (el, i) => { + el.style.visibility = "hidden"; + el.style.height = "0px"; + el.style.padding = "0px"; + el.style.margin = "0px"; + el.style.lineHeight = "0px"; + el.style.visibility = "hidden" + let id = "video_" + i; + cbVideoSelector.innerHTML += ""; + }); + videoDownloaders[0].parentNode.insertBefore(container, videoDownloaders[0]); + } else { + if (--i) { + fetchDownloaders(i); + } else { + console.log("===== PLAYLIST ======"); + if (videoDownloaders.length !== total) { + console.warn("Not every video downloaders were created: " + videoDownloaders.length + "/" + total); + } + } + } + }, 1000) + })(10); } - } + }); + } - // Check arte_vp with no parent frame - if (videoPlayerElements.length === 0 && stringStartsWith(unescape(top.location), "http://www.arte.tv/arte_vp/")) { - videoPlayerElements = document.querySelectorAll("body"); + // else deal w/ single video players + else { + let playerIframes = document.querySelectorAll("iframe"); + initParsingSystem(playerIframes.length); + for (let i = 0; i < playerIframes.length; i++) { + findPlayerJson(playerIframes[i], i); } - - nbVideoPlayers = videoPlayerElements.length; - return false; } -} +})(); /* --- FUNCTIONS: decorating --- */ function createButtonDownload(videoElementIndex, language) { - var button = document.createElement('a'); - var videoUrl; + let button = document.createElement('a'); + let videoUrl; - for (var q in qualities[videoElementIndex]) { + for (let q in qualities[videoElementIndex]) { videoUrl = getVideoUrl(videoElementIndex, q, language); if (videoUrl !== '') { break; @@ -408,7 +446,7 @@ function createButtonDownload(videoElementIndex, language) { // Unknown URL format : should not happen else { - console.log('Unknown URL format'); + console.error('Unknown URL format'); return null; } @@ -422,24 +460,24 @@ function createButtonDownload(videoElementIndex, language) { } function createButtonMetadata(videoElementIndex) { - var title = getVideoName(videoElementIndex); - var subtitle = playerJson[videoElementIndex].videoJsonPlayer.VSU; - var description_short = playerJson[videoElementIndex].videoJsonPlayer.V7T; - var description = playerJson[videoElementIndex].videoJsonPlayer.VDE; - var tags = playerJson[videoElementIndex].videoJsonPlayer.VTA; + let title = getVideoName(videoElementIndex); + let subtitle = playerJson[videoElementIndex].videoJsonPlayer.VSU; + let description_short = playerJson[videoElementIndex].videoJsonPlayer.V7T; + let description = playerJson[videoElementIndex].videoJsonPlayer.VDE; + let tags = playerJson[videoElementIndex].videoJsonPlayer.VTA; // Continue if at least one field is filled if (title !== undefined || description_short !== undefined || subtitle !== undefined || description !== undefined || tags !== undefined) { - var button = document.createElement('a'); + let button = document.createElement('a'); button.setAttribute('class', 'btn btn-default'); button.setAttribute('style', 'line-height: 17px; margin-left:10px; text-align: center; padding: 10px; color:rgb(40, 40, 40); background-color: rgb(230, 230, 230); font-family: ProximaNova,Arial,Helvetica,sans-serif; font-size: 13px;'); button.innerHTML = "Download description "; - var metadata = (title !== undefined ? "[Title]\n" + title:'') + let metadata = (title !== undefined ? "[Title]\n" + title:'') + (subtitle !== undefined ? "\n\n[Subtitle]\n" + subtitle:'') + (description_short !== undefined ? "\n\n[Description-short]\n" + description_short:'') + (description !== undefined ? "\n\n[Description]\n" + description:'') + (tags !== undefined ? "\n\n[Tags]\n" + tags:''); - var encodedData = window.btoa(unescape(encodeURIComponent(metadata))); + let encodedData = window.btoa(unescape(encodeURIComponent(metadata))); button.setAttribute('href', 'data:application/octet-stream;charset=utf-8;base64,' + encodedData); button.setAttribute('download', getVideoName(videoElementIndex) + '.txt'); return button; @@ -449,24 +487,32 @@ function createButtonMetadata(videoElementIndex) { } function getComboboxSelectedValue(combobox) { - var cb = document.getElementById(combobox); + let cb = document.getElementById(combobox); if (cb == null) { cb = parent.document.getElementById(combobox); } return cb[cb.selectedIndex].value; } +function getDownloadButton(index) { + let btn = document.getElementById('btnDownload' + index); + if (btn == null) { + btn = parent.document.getElementById('btnDownload' + index); + } + return btn; +} + function createLanguageComboBox(videoElementIndex) { - var languageComboBox = document.createElement('select'); + let languageComboBox = document.createElement('select'); languageComboBox.setAttribute('id', 'cbLanguage' + videoElementIndex); // Associate onchange event with function (bypass for GM) - languageComboBox.onchange = function() { - var selectedLanguage = languageComboBox.options[languageComboBox.selectedIndex].value; + languageComboBox.onchange = () => { + let selectedLanguage = languageComboBox.options[languageComboBox.selectedIndex].value; console.log("\n> Language changed to " + selectedLanguage); - var btn = document.getElementById('btnDownload' + videoElementIndex); - var selectedQuality = getComboboxSelectedValue('cbQuality' + videoElementIndex); - var url = getVideoUrl(videoElementIndex, selectedQuality, selectedLanguage); + let btn = getDownloadButton(videoElementIndex); + let selectedQuality = getComboboxSelectedValue('cbQuality' + videoElementIndex); + let url = getVideoUrl(videoElementIndex, selectedQuality, selectedLanguage); if (url !== '') { btn.style.visibility = "visible"; btn.setAttribute('href', url); @@ -476,31 +522,32 @@ function createLanguageComboBox(videoElementIndex) { }; // Fill with available languages - for (var l in languages[videoElementIndex]) { + for (let l in languages[videoElementIndex]) { if (languages[videoElementIndex][l] !== 0) { languageComboBox.innerHTML += ""; } } languageComboBox.setAttribute('class', 'btn btn-default'); - languageComboBox.setAttribute('style', (languageComboBox.innerHTML === "" ? "visibility:hidden;" : "max-width: 160px; padding: 6px; color:rgb(40, 40, 40); background-color: rgb(230, 230, 230); font-family: ProximaNova,Arial,Helvetica,sans-serif; font-size: 13px; font-weight: 400;")); + languageComboBox.setAttribute('style', (languageComboBox.innerHTML === "" ? "visibility:hidden;" + : "max-width: 160px; padding: 6px; color:rgb(40, 40, 40); background-color: rgb(230, 230, 230); font-family: ProximaNova,Arial,Helvetica,sans-serif; font-size: 13px; font-weight: 400;")); return languageComboBox; } function createQualityComboBox(videoElementIndex) { - var qualityComboBox = document.createElement('select'); + let qualityComboBox = document.createElement('select'); qualityComboBox.setAttribute('id', 'cbQuality' + videoElementIndex); // Associate onchange event with function (bypass for GM) - qualityComboBox.onchange = function() { - var selectedQuality = qualityComboBox.options[qualityComboBox.selectedIndex].value; + qualityComboBox.onchange = () => { + let selectedQuality = qualityComboBox.options[qualityComboBox.selectedIndex].value; console.log("\n> Quality changed to " + selectedQuality); - var btn = document.getElementById('btnDownload' + videoElementIndex); + let btn = document.getElementById('btnDownload' + videoElementIndex); if (btn == null) { btn = parent.document.getElementById('btnDownload' + videoElementIndex); } - var selectedLanguage = getComboboxSelectedValue('cbLanguage' + videoElementIndex); + let selectedLanguage = getComboboxSelectedValue('cbLanguage' + videoElementIndex); console.log(selectedLanguage); - var url = getVideoUrl(videoElementIndex, selectedQuality, selectedLanguage); + let url = getVideoUrl(videoElementIndex, selectedQuality, selectedLanguage); if (url !== '') { btn.style.visibility = "visible"; btn.setAttribute('href', url); @@ -511,18 +558,18 @@ function createQualityComboBox(videoElementIndex) { }; // Fill with available qualities - for (var q in qualities[videoElementIndex]) { + for (let q in qualities[videoElementIndex]) { if (qualities[videoElementIndex][q] !== 0) { qualityComboBox.innerHTML += ""; } } qualityComboBox.setAttribute('class', 'btn btn-default'); - qualityComboBox.setAttribute('style', 'width:140px; padding: 6px; margin-left:10px; color:rgb(40, 40, 40); background-color: rgb(230, 230, 230); font-family: ProximaNova,Arial,Helvetica,sans-serif; font-size: 13px; font-weight: 400;'); + qualityComboBox.setAttribute('style', 'width:200px; padding: 6px; margin-left:10px; color:rgb(40, 40, 40); background-color: rgb(230, 230, 230); font-family: ProximaNova,Arial,Helvetica,sans-serif; font-size: 13px; font-weight: 400;'); return qualityComboBox; } function createCreditsElement() { - var credits = document.createElement('div'); + let credits = document.createElement('div'); credits.setAttribute('style', 'text-align: center; line-height: 20px; font-size: 11.2px; color: rgb(255, 255, 255); font-family: ProximaNova, Arial, Helvetica, sans-serif; padding: 5px; background:#262626'); credits.innerHTML = 'Arte Downloader v.' + scriptVersion + ' built by and for the community with love' + '
Contribute Here.'; @@ -530,11 +577,11 @@ function createCreditsElement() { } function decoratePlayer360(videoElement, videoURL, videoName) { - var container = document.createElement('div'); + let container = document.createElement('div'); insertAfter(container, videoElement); container.setAttribute('class', 'ArteDownloader-v' + scriptVersion) container.setAttribute('style', 'background:#262626; padding: 10px;'); - var button = document.createElement('a'); + let button = document.createElement('a'); button.innerHTML = "Download " + videoName + " "; button.setAttribute('id', 'btnArteDownloader'); button.setAttribute('href', videoURL); @@ -546,43 +593,47 @@ function decoratePlayer360(videoElement, videoURL, videoName) { } function decoratePlayer(videoElement, videoElementIndex) { - var container = document.createElement('div'); - var parent = videoElement.parentNode; + let container = document.createElement('div'); + let parent = videoElement.parentNode; - // decorate iframe from embedment - if (window.frameElement !== null) { + // find parent to decorate + // @TODO: deco https://info.arte.tv/fr/litalie-veut-redonner-leur-identite-aux-refugies-noyes-en-mediterranee + if (videoElement.nodeName === "IFRAME" || window.frameElement !== null) { + // !hardcoded spaghetti, @TODO: find a better way to find parent parent = window.parent.document.querySelector('div.video-embed'); - setTimeout(function(){ insertAfter(container, parent); }, 2000); - } else { - // decorate iframe from top window - if (videoElement.nodeName === "IFRAME") { - console.log("> Decorating iFrame player"); - parent = document.querySelector('div.video-embed'); + if (parent == null) { + parent = window.parent.document.querySelector('div.next-video-playlist'); if ( parent == null ) { - parent = document.querySelector('div.article-video'); - if ( parent == null ) { - console.error("> Could not find a parent to decorate."); + parent = window.parent.document.querySelector('div.article-video'); + if (parent == null) { + parent = window.parent.document.querySelector('div.video-container'); + if (parent == null) { + console.error("Couldn't find parent to decorate."); + return; + } } } - setTimeout(function(){ insertAfter(container, parent); }, 2000); } } + + setTimeout( () => { insertAfter(container, parent); }, 3500); // !hardcoded, @TODO: callback after parent stops cleaning his childNodes + container.setAttribute('id', 'video_' + videoElementIndex); container.setAttribute('class', 'ArteDownloader-v' + scriptVersion) container.setAttribute('style', 'background:#262626; padding: 10px;'); // Create video name span - var videoNameSpan = document.createElement('span'); - var subtitle = playerJson[videoElementIndex].videoJsonPlayer.VSU; + let videoNameSpan = document.createElement('span'); + let subtitle = playerJson[videoElementIndex].videoJsonPlayer.subtitle; videoNameSpan.innerHTML = "" + getVideoName(videoElementIndex) + (subtitle !== undefined ? " - " + subtitle : "") + "
"; videoNameSpan.setAttribute('style', 'margin-top:10px; text-align: center; color:rgb(255, 255, 255); font-family: ProximaNova,Arial,Helvetica,sans-serif; font-size: 16px;'); container.appendChild(videoNameSpan); // Create language combobox - var languageComboBox = createLanguageComboBox(videoElementIndex) + let languageComboBox = createLanguageComboBox(videoElementIndex) container.appendChild(languageComboBox); // Check if there are languages available to select - var selectedLanguage; + let selectedLanguage; if (languageComboBox.options.length > 0) { selectedLanguage = languageComboBox.options[languageComboBox.selectedIndex].value; } @@ -591,49 +642,22 @@ function decoratePlayer(videoElement, videoElementIndex) { container.appendChild(createQualityComboBox(videoElementIndex)); // Create download button - var btnDownload = createButtonDownload(videoElementIndex, selectedLanguage); + let btnDownload = createButtonDownload(videoElementIndex, selectedLanguage); if (btnDownload !== null) { container.appendChild(btnDownload); } // Create metadata button - var btnMetadata = createButtonMetadata(videoElementIndex); + let btnMetadata = createButtonMetadata(videoElementIndex); if (btnMetadata !== null) { container.appendChild(btnMetadata); } // Create credits ribbon - var credits = createCreditsElement(); + let credits = createCreditsElement(); container.appendChild(credits); // Workaround decoration overlapping next SECTION - var parentSection = getParent(parent, 'SECTION', 'margin-bottom-s'); - parentSection.style.marginBottom = "9rem"; -} - -is_playlist = findPlayers(); -console.log("> Found " + nbVideoPlayers + " video player(s):"); -if (nbVideoPlayers > 0) { - // Init global vars - playerJson = [nbVideoPlayers]; - nbVideos = [nbVideoPlayers]; - nbHTTP = [nbVideoPlayers]; - nbHLS = [nbVideoPlayers]; - languages = [nbVideoPlayers]; - qualities = [nbVideoPlayers]; - for (i = 0; i < nbVideoPlayers; i++) { - playerJson[i] = 0; - nbVideos[i] = 0; - nbHTTP[i] = 0; - nbHLS[i] = 0; - languages[i] = new Object; - qualities[i] = new Object; - } - - if (!is_playlist) { - // Analyse each video player, then decorate them - for (var i = 0; i < nbVideoPlayers; i++) { - findPlayerJson(videoPlayerElements[i], i); - } - } + let parentSection = getParent(parent, 'SECTION', 'margin-bottom-s'); //!hardcoded, @TODO: find a better way to find proper parent + parentSection.style.marginBottom = playerJson.length * 8 + "rem"; } From a6efd718bec3a75703dfd5b18908e881897a21c5 Mon Sep 17 00:00:00 2001 From: Bumba Date: Mon, 8 Oct 2018 22:20:22 +0200 Subject: [PATCH 10/11] 3.0 --- README.md | 17 +++++------------ 1 file changed, 5 insertions(+), 12 deletions(-) diff --git a/README.md b/README.md index 41b59e3..ce2526a 100644 --- a/README.md +++ b/README.md @@ -1,7 +1,7 @@ ARTE-Downloader ================= -Current version: **2.12**. +Current version: **3.0**. CONTRIBUTORS ----------- @@ -11,20 +11,14 @@ CONTRIBUTORS DESCRIPTION ----------- -This script works as a browser extension to download any video on [Arte website](http://www.arte.tv/). - -Go to a video on [Arte website](http://www.arte.tv/) and you should see buttons under a video: +This script works as a browser extension to offer download options under videos on [Arte website](https://www.arte.tv/). * **Select** the language and the quality. * **Download** the video and its description. -NB: If your browser open the MP4 video in a new tab: save it (CTRL+S) and close the tab. - -For livestreams, you can either: -* Download directly the MP4 video if available. -* Open [VLC](http://www.videolan.org/vlc/) > Open its convert tool (CTRL+R) > Network tab > Copy the link > Convert/Save the video. +NB: If your browser open the MP4 video in a new tab: save it with CTRL+S. -![](http://i.imgur.com/GjvVHLv.jpg) +![](https://i.imgur.com/GjvVHLv.jpg) INSTALLATION ------------ @@ -44,8 +38,7 @@ If you often travel with poor Internet connections, now you can download videos **Why www.arte.tv ?** ARTE is an amazing place for culture. If you don't have a TV : ARTE website is the only way to watch it. -About ARTE: http://www.arte.tv/sites/en/arte-europe/about/ -TV Broadcast: http://reception.arte.tv/ +About ARTE: https://www.arte.tv/sites/en/corporate DISCLAIMER From 778ff284f1aaee280a2d36b2ac08af85f40de751 Mon Sep 17 00:00:00 2001 From: Bumba Date: Mon, 8 Oct 2018 22:26:10 +0200 Subject: [PATCH 11/11] minor typo --- README.md | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/README.md b/README.md index ce2526a..01f0f3f 100644 --- a/README.md +++ b/README.md @@ -16,7 +16,7 @@ This script works as a browser extension to offer download options under videos * **Select** the language and the quality. * **Download** the video and its description. -NB: If your browser open the MP4 video in a new tab: save it with CTRL+S. +NB: If your browser opens the video in a new tab: save it with CTRL+S. ![](https://i.imgur.com/GjvVHLv.jpg)