BBC iPlayer video download

This script allows to save videos from BBC iPlayer.

目前為 2015-08-04 提交的版本,檢視 最新版本

您需要先安裝使用者腳本管理器擴展,如 TampermonkeyGreasemonkeyViolentmonkey 之後才能安裝該腳本。

You will need to install an extension such as Tampermonkey to install this script.

您需要先安裝使用者腳本管理器擴充功能,如 TampermonkeyViolentmonkey 後才能安裝該腳本。

您需要先安裝使用者腳本管理器擴充功能,如 TampermonkeyUserscripts 後才能安裝該腳本。

你需要先安裝一款使用者腳本管理器擴展,比如 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