Easily download videos from BBC iPlayer (with youtube-dl)
// ==UserScript== // @name BBC iPlayer video download // @name:it BBC iPlayer - Download dei video // @namespace http://andrealazzarotto.com/ // @include http://www.bbc.co.uk/* // @include https://www.bbc.co.uk/* // @version 4.1.6 // @description Easily download videos from BBC iPlayer (with youtube-dl) // @description:it Scarica facilmente i video da BBC iPlayer (con youtube-dl) // @author Andrea Lazzarotto // @require https://cdnjs.cloudflare.com/ajax/libs/jquery/1.11.1/jquery.min.js // @grant GM_xmlhttpRequest // @grant GM.xmlHttpRequest // @connect bbc.co.uk // @connect akamaized.net // @connect llnwd.net // @license GPL version 3 or any later version; http://www.gnu.org/copyleft/gpl.html // ==/UserScript== /* Greasemonkey 4 wrapper */ if (typeof GM !== "undefined" && !!GM.xmlHttpRequest) GM_xmlhttpRequest = GM.xmlHttpRequest; var count = 0; var containers = '.playback-content > button,' + '.content-item-description__text-container,' + '.episode-panel__intro,' + '.vxp-media__summary,' + '#programme-clip,' + '[class*="__media-asset"],' + '.msc-media-player-wrapper'; var get_title = function(name) { var title = name || $('meta[property="og:title"]').attr('content') || 'output'; return title.replace(/\W+/g, '_'); }; var get_JSON = function(url, callback) { GM_xmlhttpRequest({ method: 'GET', url: url, onload: function(responseDetails) { var r = responseDetails.responseText; var json = $.parseJSON(r); callback(json); } }); }; var place_link_box = function(element, id) { element.after('<div class="gel-wrap" id="' + id + '" />'); $('#' + id).css({ 'padding': '.75em', 'margin': '25px auto', //'width': $('#player-outer-outer').width(), 'border': '1px solid #2C2C2C', 'background-color': '#0A0C16', 'color': 'white', 'font-family': 'sans-serif', 'box-sizing': 'border-box', 'font-size': '0.9rem' }); }; var render_piece = function(html) { var tree = $(html); if (!tree.length) return ''; var output = []; var nodes = tree[0].childNodes; var hyph = html.toString().indexOf('<span') > 0 ? '- ' : ''; for (var o = 0; o < nodes.length; o++) { if (nodes[o].toString().indexOf('Text') > 0) output.push(hyph + nodes[o].textContent); else { var name = nodes[o].tagName.toLowerCase(); switch(name) { case 'br': output.push(' '); break; case 'span': output.push('\n' + hyph); output.push(render_piece(nodes[o])); output.push('\n'); break; } } } var joined = output.join(''); joined = joined.replace(/\s+\n/, '\n').replace(/(^\n|\n$)/, ''); joined = joined.replace(/\n+/, '\n').replace(/\s+/, ' '); return joined; }; var render_part = function(html, id) { var tree = $(html); var begin = tree.attr('begin').replace('.', ','); var end = tree.attr('end').replace('.', ','); return id + '\n' + begin + ' --> ' + end + '\n' + render_piece(html); }; var handle_subtitles = function(subURL, element_id, title) { if (!subURL) return; GM_xmlhttpRequest({ method: 'GET', url: subURL, onload: function(responseDetails) { var r = responseDetails.responseText; var doc = $.parseXML(r); var $xml = $(doc); var srt_list = []; $xml.find('p').each(function(index, value){ srt_list.push(render_part(value.outerHTML, index+1)); }); $('#' + element_id + ' #subtitles').remove(); $('#' + element_id + ' p:last-child').css('margin-bottom', 'auto'); $('#' + element_id).append('<ul id="subtitles"><li><a id="srt-link">Download converted subtitles (SRT)</a></li>' + '<li><a href="' + subURL + '">Download original subtitles (TTML)</a></li></ul>'); $('#srt-link').attr('href', 'data:text/plain;charset=utf-8,' + encodeURIComponent(srt_list.join('\n\n'))).attr('download', get_title(title) + '.srt'); $('#' + element_id + ' a').css({ 'color': 'white', 'font-weight': 'bold' }); $('#' + element_id + ' ul').css({ 'list-style': 'initial', 'padding-left': '2em', 'margin-top': '.5em' }); } }); }; var append = function(elements, id, parsed, title) { var type = parsed.kind || 'video'; var tool = 'youtube-dl'; var safe_title = get_title(title); var element = $(elements.get(0)); var objects = parsed[type]; if (objects.length === 0) return; $("#" + id).remove(); element.after('<div id=' + id + '"></div>'); place_link_box(element, id); if (objects.length > 1) $('#' + id).append('<h4>Quality level: <select /></h4>'); $('#' + id).append('<p>To record the ' + type + ', use <code>' + tool + '</code> with the following command line:</p>'); for (var i in objects) { var label = parseInt(objects[i].bitrate); var url = objects[i].connection[0].href; var format = parsed.kind == 'video' ? 'bestvideo+bestaudio' : 'bestaudio'; $('#' + id).append('<div id="wrapper-' + i + '"><pre>' + tool + ' -f ' + format + ' "' + url + '" -o ' + safe_title + '</pre></div>'); $('#' + id + ' select').append('<option value="' + i + '">' + label + "</option>"); } if (location.href.indexOf('iplayer/episode/') > 0) { $('#' + id).append('<p><b>Good news!</b> This page is supported by <code>' + tool + '</code>. You can use it directly:</p>'); $('#' + id).append('<pre>' + tool + ' "' + location.href + '"</pre>'); } $('#' + id + ' div[id*=wrapper]').hide(); $('#' + id + ' #wrapper-0').show(); $('#' + id + ' select').css('color', 'black').on('change', function() { var index = this.value; $('#' + id + ' div[id*=wrapper]').hide(); $('#' + id + ' #wrapper-' + index).show(); }); $('#' + id + ' pre, #' + id + ' code').css({ 'white-space': 'normal', 'word-break': 'break-all', 'font-size': $('#direct-link p').css('font-size'), 'margin': '.75em 0', 'padding': '.75em', 'background-color': '#2C2C2C', 'font-family': '"DejaVu Sans Mono", Menlo, "Andale Mono", monospace' }); $('#' + id + ' code').css('padding','.25em'); $('#' + id + ' p:last-child').css('margin-bottom', '0'); $('#' + id + ' *').css({ 'color': 'white', 'line-height': '1em', 'font-size': '.9rem' }); return id; }; var comparator = function(a,b) { return parseInt(b.bitrate)-parseInt(a.bitrate); }; var filter = function(media) { if (!media.hasOwnProperty('connection')) return; if (media.kind != 'video' && media.kind != 'audio') return; for (var i = media.connection.length - 1; i >= 0; i--) { var format = media.connection[i].transferFormat || media.connection[i].format; if (format !== 'dash') media.connection.splice(i, 1); } }; var classify = function(media) { var r###lts = { video: [], audio: [], captions: [] }; for (var i = 0; i < media.length; i++) { var element = media[i]; filter(element); if(r###lts.hasOwnProperty(element.kind) && ((!element.hasOwnProperty('connection') || element.connection.length))) r###lts[element.kind].push(element); } r###lts.video.sort(comparator); r###lts.audio.sort(comparator); r###lts.hasCaptions = r###lts.captions.length > 0; r###lts.kind = r###lts.video.length ? "video" : "audio"; return r###lts; }; var handle_player = function(player) { var container = player._container; var title = player.playlist.title; var vpid, type, kind; for (var i = player.playlist.items.length - 1; i >= 0; i--) { vpid = vpid || player.playlist.items[i].vpid || player.playlist.items[i].identifier; type = type || player.playlist.items[i].type; kind = kind || player.playlist.items[i].kind; if (vpid !== null) break; } if (vpid === null) return false; var elements = $(containers); if (elements.length === 0) elements = container.closest('figure, .play'); if (kind === 'trailer' || kind === 'ident') { try { var contents = $('script:contains(window.mediatorDefer = mediator.bind)').html().split('mediator.bind(')[1].split(', docu')[0]; var data = JSON.parse(contents); vpid = data.episode.versions[0].id; } catch(e) { var id = 'video-trailer-warning-' + vpid; if (!$('#' + id).length) { place_link_box(elements, id); $('#' + id).append('<p>To download this ' + type + ', press <i>Play</i> and skip the trailer.</p>'); } return false; } } get_JSON('http://open.live.bbc.co.uk/mediaselector/5/select/version/2.0/mediaset/pc/vpid/' + vpid + '/format/json/', function(data) { var parsed = classify(data.media); var id = append(elements, 'video-download-' + vpid, parsed, title); if (parsed.hasCaptions) handle_subtitles(parsed.captions[0].connection[0].href, id, title); }); return true; }; var monitor = function() { if(!unsafeWindow.embeddedMedia) return; var players = unsafeWindow.embeddedMedia.players; if (players.length != count && players[0].playlist) { if (count === 0) { for (var i = 0; i < players.length; i++) if (handle_player(players[i])) count++; } else if (handle_player(players[players.length - 1])) count++; } }; $(document).ready(function(){ setInterval(monitor, 1000); });