BBC iPlayer video download

This script allows you to save videos from BBC iPlayer.

目前為 2018-01-30 提交的版本,檢視 最新版本

您需要先安裝使用者腳本管理器擴展,如 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/*
// @include     https://www.bbc.co.uk/*
// @version     4.1.3
// @description This script allows you to save videos from BBC iPlayer.
// @copyright   2015+, Andrea Lazzarotto - GPLv3 License
// @require     http://code.jquery.com/jquery-latest.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 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 results = {
        video: [],
        audio: [],
        captions: []
    };
    for (var i = 0; i < media.length; i++) {
        var element = media[i];
        filter(element);
        if(results.hasOwnProperty(element.kind) && ((!element.hasOwnProperty('connection') || element.connection.length)))
            results[element.kind].push(element);
    }
    results.video.sort(comparator);
    results.audio.sort(comparator);
    results.hasCaptions = results.captions.length > 0;
    results.kind = results.video.length ? "video" : "audio";
    return results;
};

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');

    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);
});