// ==UserScript== // @name Plus7 VOD FFMPEG download details // @namespace plsu7 // @description Plus7 VOD FFMPEG download detailss // @include https://au.tv.yahoo.com/plus7/*/-/watch/* // @version 0.1 // @noframes // @grant GM_xmlhttpRequest // @grant GM_addStyle // @grant GM_registerMenuCommand // @downloadURL none // ==/UserScript== // partly inspired by https://gist.github.com/adammw/6484147 let videoData = unsafeWindow.videoData; GM_registerMenuCommand("Get FFMPEG details", function(e) { checkIfVideoDataReady(); }); function sanitizeTitle(vTitle){ //try to sanitize the title a bit let sanitizedTitle = ''; for (let i = 0, len = vTitle.length; i < len; i++) { sanitizedTitle +=vTitle[i].replace(/[^a-zA-Z0-9]/,"_"); } return sanitizedTitle; } function checkIfVideoDataReady(){ document.querySelector('object').style.display='none'; let metaVidID = document.querySelector('meta[property="og:url"]') .getAttribute('content').split('/watch/')[1].split('/')[0]; let vidPlayerCssID = "brightcove_player"+metaVidID+"_plyr"; if(!videoData[vidPlayerCssID] || !videoData[vidPlayerCssID].videoID){ setTimeout(checkIfVideoDataReady, 1000); } else{ let sanitizedTitle = sanitizeTitle(videoData[vidPlayerCssID].videoTitle); getM3U(videoData[vidPlayerCssID].videoID, sanitizedTitle); } } function createInitButton(){ let menuTab = document.createElement('li'); menuTab.setAttribute('class', 'vod_menu_tab'); let menuItem = document.createElement('a'); menuItem.setAttribute('class', 'vod_menu_item'); menuItem.setAttribute('href', '#'); menuItem.innerHTML = "FFMPEG"; menuTab.appendChild(menuItem); let vMenu = document.querySelector('.navigation .nav0 .last ul'); if(vMenu){ vMenu.appendChild(menuTab); //vMenu.insertBefore(menuTab, vMenu.querySelector('.last')); menuTab.addEventListener('mouseup', function(e) { checkIfVideoDataReady(); }, false); } } function getM3U(videoID, sanitizedTitle){ GM_xmlhttpRequest({ method: 'GET', url: 'http://c.brightcove.com/services/mobile/streaming/index/master.m3u8?videoId='+videoID, onload: function(res) { let xhrResp = res.responseText; let streamURLS = []; let availableVideoBandwidth = []; let availableVideoResolution = []; xhrResp.split('\n').forEach(function(item, index, arr){ let textTrim = item.trim(); //return if it's an empty string or the #EXTM3U if(!textTrim.length || textTrim.startsWith('#EXTM3U')){ return; } if(textTrim.startsWith('#EXT-X-STREAM-INF')){ availableVideoBandwidth.push(textTrim.split('BANDWIDTH=')[1].split(',')[0]); if(textTrim.indexOf('RESOLUTION=')>-1){ //sometimes there's no resolution details availableVideoResolution.push(textTrim.split('RESOLUTION=')[1].split(',')[0]); } else{ availableVideoResolution.push('N/A'); } } if(textTrim.startsWith('https')){ streamURLS.push(item); } }); createModal(sanitizedTitle, streamURLS, availableVideoBandwidth, availableVideoResolution); }, onerror: function(res) { console.log(msg); } }); } function createModal(sanitizedTitle, streamURLS, availableVideoBandwidth, availableVideoResolution){ let modalStyles = ` #gm_modalContainer { height: 100%; width: 100%; overflow: auto; margin: auto; position: absolute; z-index: 1000; background: rgba(0,0,0,.5); top: 0; left: 0; bottom: 0; right: 0; } #gm_modal{ background-color: grey; border: 10px solid grey; bottom: 0; display: flex; flex-direction: row; height: 360px; left: 0; margin: auto; overflow: auto; position: relative; right: 0; top: 150px; width: 800px; } #gm_button_containers { display: flex; flex-direction: column; } #gm_textArea { margin-left: 20px; width: 680px; } #gm_closeModal { color: black; font-size: 5em; height: 20px; margin-left: 25px; margin-top: -18px; } #gm_closeModal:hover { text-decoration: none; cursor: pointer; } .gm_modalButtons { cursor: pointer; margin-bottom: 10px; margin-right: 10px; padding: 5px; text-align: left; border: 2px solid white; color: white; font-size: 1.2em; } .gm_modalButtons:hover { border: 2px solid blue; } .gm_vidDownLink{ color: white; font-size: 2em; margin-bottom: 10px; text-decoration: none; margin-right: 10px; }`; GM_addStyle(modalStyles); let modalContainer = document.createElement('div'); modalContainer.setAttribute('id', 'gm_modalContainer'); let gmModal = document.createElement('div'); gmModal.setAttribute('id', 'gm_modal'); let gmButtonCon = document.createElement('div'); gmButtonCon.setAttribute('id', 'gm_button_containers'); gmModal.appendChild(gmButtonCon); let gmcloseModal = document.createElement('a'); gmcloseModal.setAttribute('id', 'gm_closeModal'); gmcloseModal.innerHTML = '✕'; modalContainer.appendChild(gmModal); let gmTextArea = document.createElement('textarea'); gmTextArea.setAttribute('id', 'gm_textArea'); gmModal.appendChild(gmTextArea); //sort them, sometimes the highest bandwidth one isn't the first let videos = {}; availableVideoBandwidth.forEach(function(item, index, arr){ videos[Number(item)] = { bandwidth: item, url: streamURLS[index], resolution: availableVideoResolution[index] } }); let vidBandNumberArr = availableVideoBandwidth.map(function(item, index, arr){ return Number(item); }).sort(function(a, b) { return b - a; }); vidBandNumberArr.forEach(function(item, index, arr){ let buttonDiv = document.createElement('div'); buttonDiv.setAttribute('class', 'gm_modalButtons'); buttonDiv.setAttribute('data-streamurl', videos[item].url); let bandwidthDetails = document.createElement('div'); bandwidthDetails.innerHTML = 'Video Bandwidth: '+videos[item].bandwidth; let resolutionDetails = document.createElement('div'); resolutionDetails.innerHTML = 'Resolution: '+videos[item].resolution; buttonDiv.appendChild(bandwidthDetails); buttonDiv.appendChild(resolutionDetails); gmButtonCon.appendChild(buttonDiv); buttonDiv.addEventListener('mouseup', function(e) { let streamAtt = decodeURIComponent(e.currentTarget.getAttribute('data-streamurl')); gmTextArea.value = 'ffmpeg -i "'+streamAtt+'" -c copy '+sanitizedTitle+'.ts'; }, false); }); gmModal.appendChild(gmcloseModal); gmcloseModal.addEventListener('click', function(e) { e.preventDefault(); document.body.removeChild(modalContainer); }, false); document.body.appendChild(modalContainer); } createInitButton(); /** Other idea of listening for object change ***/ // window.addEventListener("message", function(event){ // // console.log('received a post message====+++++++'); // // console.log('event.data'); // // console.log(event.data); // // console.log('event.origin'); // // console.log(event.origin); // // console.log('event.source'); // // console.log(event.source); // let winLocation = window.location; // let winUrlWithNoQueries = winLocation.origin+winLocation.pathname; // let eventWinLocation = event.source.location; // let eventWinUrlWithNoQueries = eventWinLocation.origin+eventWinLocation.pathname; // if(event.origin === 'https://au.tv.yahoo.com' && eventWinUrlWithNoQueries === winUrlWithNoQueries){ // let messageJSON; // try{ // messageJSON = JSON.parse(event.data); // } // catch(e){ // console.log('error parsing JSON in greasemonkey script ==> '+e); // } // console.log('messageJSON'); // console.log(messageJSON); // if(!messageJSON || !messageJSON.videoID){ // return; // } // let sanitizedTitle = sanitizeTitle(messageJSON.videoTitle); // } // }, false); // let insertedScript = document.createElement('script'); // insertedScript.innerHTML = ` // var metaVidID = document.querySelector('meta[property="og:url"]') // .getAttribute('content').split('/watch/')[1].split('/')[0]; // var vidPlayerCssID = "brightcove_player"+metaVidID+"_plyr"; // //Object.observe is not yet supported in Firefox, so using Object.watch // videoData.watch(vidPlayerCssID, function (id, oldval, newval) { // if(id === vidPlayerCssID){ // videoData.unwatch(vidPlayerCssID); // var message = JSON.stringify(newval); // window.postMessage(message, "https://au.tv.yahoo.com"); // return newval; // } // }); // `; // document.body.appendChild(insertedScript);