BBC iPlayer video download

This script allows to save videos from BBC iPlayer.

当前为 2015-08-04 提交的版本,查看 最新版本

您需要先安装一款用户脚本管理器扩展,例如 Tampermonkey 篡改猴Greasemonkey 油猴子Violentmonkey 暴力猴,才能安装此脚本。

您需要先安装一款用户脚本管理器扩展,例如 Tampermonkey 篡改猴,才能安装此脚本。

您需要先安装一款用户脚本管理器扩展,例如 Tampermonkey 篡改猴Violentmonkey 暴力猴,才能安装此脚本。

您需要先安装一款用户脚本管理器扩展,例如 Tampermonkey 篡改猴Userscripts ,才能安装此脚本。

您需要先安装一款用户脚本管理器扩展,例如 Tampermonkey 篡改猴,才能安装此脚本。

您需要先安装一款用户脚本管理器扩展后才能安装此脚本。

(我已经安装了用户脚本管理器,让我安装!)

您需要先安装一款用户样式管理器扩展,比如 Stylus,才能安装此样式。

您需要先安装一款用户样式管理器扩展,比如 Stylus,才能安装此样式。

您需要先安装一款用户样式管理器扩展,比如 Stylus,才能安装此样式。

您需要先安装一款用户样式管理器扩展后才能安装此样式。

您需要先安装一款用户样式管理器扩展后才能安装此样式。

您需要先安装一款用户样式管理器扩展后才能安装此样式。

(我已经安装了用户样式管理器,让我安装!)

// ==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/*
// @version     3.0
// @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
// ==/UserScript==

get_title = function() {
	var title = $('meta[property="og:title"]').attr('content') || 'output';
	return title.replace(/\W+/g, '_');
}

appendURL = function(element, url) {
	element.after('<div id="direct-link"></div>');
	$('#direct-link').css({
		'padding': '.75em',
		'margin': '25px auto',
		'width': $('#player-outer-outer').width(),
		'border': '1px solid #444',
		'color': 'white',
		'font-family': 'sans-serif',
		'box-sizing': 'border-box'
	}).append('<p>To record the video, use <code>avconv</code> with the following command line:</p>' + 
			  '<pre>avconv -i "' + url + '" -codec copy -qscale 0 ' + get_title() + '.mp4</pre>' +
			  '<p>Alternatively, you may also try to record the M3U8 stream URL with VLC.</p>');
	$('#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('<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;
}

render_p = function(html) {
	var tree = $(html);
	var begin = tree.attr('begin').replace('.', ',');
	var end = tree.attr('end').replace('.', ',');
	var id = tree.attr('id').replace('p', '');
	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(){
				srt_list.push(render_p($(this)[0].outerHTML));
			});
			
			$('#direct-link p:last-child').css('margin-bottom', 'auto');
			$('#direct-link').append('<ul><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() + '.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 = {};
				$xml.find('media[kind^="video"]').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 = {};
						$xml.find('media[kind^="video"]').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 = urls[1];
								
								// fix the final url
								var old_pieces = final_url.split(',');
								var pieces = [old_pieces[0], old_pieces[1], old_pieces[old_pieces.length-1]];
								var p = 1;
								
								var high_quality_piece = high_quality['object'].replace('.mp4','')
									.replace('secure_auth/','').split('/').slice(1).join('/');
								var new_p = pieces[p].split('/').slice(0,2).join('/') + '/' + high_quality_piece;
								
								pieces[p] = new_p;
								final_url = pieces.join(',');
								
								// output the M3U8 URL
								appendURL($(selector), final_url);
								console.log("doing subtitles...");
								handle_subtitles(subURL);
							}
						});
					}
				});
			}
		});
	}); // getJSON
}

$(document).ready(function(){
	var isProgramme = !!unsafeWindow.bbcProgrammes;
	
	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');
		});
	}
	
	else {
		var spid = $('script:contains("mediator.bind")').html();
		var vpid = spid.split('vpid')[1].split('"')[2];
		handle_pid(vpid, '#player-outer-outer');
	}
}); // $(document).ready.ready