// ==UserScript== // @name BBC iPlayer video download // @namespace http://andrealazzarotto.com/ // @include http://www.bbc.co.uk/iplayer/episode/* // @include http://www.bbc.co.uk/programmes/* // @include http://www.bbc.co.uk/*radio/* // @version 3.2.1 // @description This script allows to save videos from BBC iPlayer. // @copyright 2015+, Andrea Lazzarotto - GPLv3 License // @require http://code.jquery.com/jquery-latest.min.js // @grant GM_xmlhttpRequest // @license GPL version 3 or any later version; http://www.gnu.org/copyleft/gpl.html // @downloadURL none // ==/UserScript== get_title = function() { var title = $('meta[property="og:title"]').attr('content') || 'output'; return title.replace(/\W+/g, '_'); } appendURL = function(element, url, ext, kind) { var extension = ext || 'mp4'; var type = kind || 'video'; var codec = type == 'video' ? ' -codec copy -qscale 0 ' : ' '; element.after('
'); $('#direct-link').css({ 'padding': '.75em', 'margin': '25px auto', 'width': $('#player-outer-outer').width(), 'border': '1px solid #444', 'background-color': '#252525', 'color': 'white', 'font-family': 'sans-serif', 'box-sizing': 'border-box' }).append('To record the ' + type + ', use avconv
with the following command line:
avconv -i "' + url + '"' + codec + get_title() + '.' + extension + '' + '
If you get an error about Malformed AAC bitstream
, add option ' +
'-bsf:a aac_adtstoasc
before the file name.
Alternatively, you may also try to record the M3U8 stream URL with VLC.
'); $('#direct-link pre, #direct-link code').css({ 'white-space': 'normal', 'word-break': 'break-word', 'font-size': $('#direct-link p').css('font-size'), 'margin': '.75em 0', 'padding': '.75em', 'background-color': '#444' }); $('#direct-link code').css('padding','.25em'); $('#direct-link p:last-child').css('margin-bottom', '0'); } get_biggest = function(dict) { var s = 0; var o = null; for(var key in dict) { key = parseInt(key) || 0; if (key > s) { s = key; o = dict[key]; } } return {'size': s, 'object': o}; } render_piece = function(html) { var tree = $(html); if (tree.length == 0) return ''; var output = []; var nodes = tree[0].childNodes; var hyph = html.toString().indexOf(' 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; } render_p = 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); } handle_subtitles = function(subURL) { 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_p(value.outerHTML, index+1)); }); $('#direct-link p:last-child').css('margin-bottom', 'auto'); $('#direct-link').append(''); $('#srt-link').attr('href', 'data:text/plain;charset=utf-8,' + encodeURIComponent(srt_list.join('\n\n'))).attr('download', get_title() + '.srt'); $('#direct-link a').css({ 'color': 'white', 'font-weight': 'bold' }); $('#direct-link ul').css({ 'list-style': 'initial', 'padding-left': '2em', 'margin-top': '.5em' }); } }); } handle_pid = function(vpid, selector){ var config_url = 'http://www.bbc.co.uk/iplayer/config/windows-phone'; // figure out the mediaselector URL $.getJSON(config_url, function(data) { var selector_mobile = data['mediaselector'].replace('{vpid}', vpid); var selector_pc = selector_mobile.replace(/mobile-.*vpid/, 'pc/vpid'); // get mobile data GM_xmlhttpRequest({ method: 'GET', url: selector_mobile, onload: function(responseDetails) { var r = responseDetails.responseText; var doc = $.parseXML(r); var $xml = $(doc); var media = {}; console.log('SELECTOR_MOBILE: ' + selector_mobile); var urls = $xml.find('media[kind^="video"]'); var kind = 'video'; if (!urls.length) { urls = $xml.find('media[kind^="audio"]'); kind = 'audio'; } urls.each(function() { var bitrate = $(this).attr('bitrate'); var href = $(this).find('connection').attr('href'); media[bitrate] = href; }); var subURL = $xml.find('media[service="captions"] connection').attr('href'); var m3u8_url = get_biggest(media); console.log("M3U8_URL: " + m3u8_url['object']); // get desktop data for higher quality GM_xmlhttpRequest({ method: 'GET', url: selector_pc, onload: function(responseDetails) { var r = responseDetails.responseText; var doc = $.parseXML(r); var $xml = $(doc); var media = {}; var urls = $xml.find('media[kind^="video"]'); if (!urls.length) urls = $xml.find('media[kind^="audio"]'); urls.each(function() { var bitrate = $(this).attr('bitrate'); var identifier = $(this).find('connection[application="ondemand"], ' + 'connection[application*="/e3"]').attr('identifier'); if(identifier) media[bitrate] = identifier; }); var high_quality = get_biggest(media); console.log("HIGH_QUALITY: " + high_quality['object']); // compose the M3U8 stream URL GM_xmlhttpRequest({ method: 'GET', url: m3u8_url['object'], onload: function(responseDetails) { var r = responseDetails.responseText; var urls = r.split('\n').slice(1); var final_url = m3u8_url['object'].indexOf('prod_af') < 0 ? urls[1] : m3u8_url['object']; console.log('FINAL_URL: ' + final_url); var ext = kind == 'video' ? 'mp4' : 'mp3'; // fix the final url if (kind == 'video' || final_url.indexOf('kbps') > 0) { var old_pieces = final_url.split(','); var pieces = [old_pieces[0], old_pieces[1], old_pieces[old_pieces.length-1]]; var p = 1; var strpiv = 'kbps/'; var template = pieces[p]; var cutter = template.indexOf(strpiv); var pivot = template.substring(0, cutter).lastIndexOf('/'); template = template.substring(0, pivot+1); var hq_piece = high_quality['object']; cutter = hq_piece.indexOf(strpiv); pivot = hq_piece.substring(0, cutter).lastIndexOf('/'); hq_piece = hq_piece.substring(pivot+1, 1000).replace('.mp4', ''); console.log(pivot); console.log(hq_piece); pieces[p] = [template, hq_piece].join(''); console.log(pieces); final_url = pieces.join(','); } console.log('KIND: ' + kind); // output the M3U8 URL appendURL($(selector), final_url, ext, kind); console.log("SUBURL: " + subURL); handle_subtitles(subURL); } }); } }); } }); }); // getJSON } $(document).ready(function(){ var isRadio = (unsafeWindow.location.href.indexOf('radio/') > 0) && !!($('#empbox').length); var isProgramme = !!unsafeWindow.bbcProgrammes; if (isRadio) { var playlist = unsafeWindow.clipcontentPlaylist; var empconf = unsafeWindow.empConfig; var subdir = empconf.split('.co.uk/')[1].split('/')[0]; var parts = playlist.split('iplayer/playlist'); var playlist = parts[0] + subdir + '/iplayer/playlist' + parts[1]; $.getJSON(playlist, function(data) { console.log('VPID: ' + data.pid); handle_pid(data.pid, '#empbox'); }); return; } if (isProgramme) { var clipid = location.href.split("/")[4]; $.getJSON('http://www.bbc.co.uk/programmes/' + clipid + '/playlist.json', function(data) { var vpid = data.defaultAvailableVersion.pid; console.log("VPID: " + vpid.toString()); handle_pid(vpid, '.island .cf.component, .episode-playout'); }); } else { var spid = $('script:contains("mediator.bind")').html(); var vpid = spid.split('vpid')[1].split('"')[2]; handle_pid(vpid, '#player-outer-outer'); } }); // $(document).ready