BBC iPlayer video download

This script allows you to save videos from BBC iPlayer.

目前为 2017-05-01 提交的版本,查看 最新版本

  1. // ==UserScript==
  2. // @name BBC iPlayer video download
  3. // @namespace http://andrealazzarotto.com/
  4. // @include http://www.bbc.co.uk/*
  5. // @include https://www.bbc.co.uk/*
  6. // @version 4.0.3
  7. // @description This script allows you to save videos from BBC iPlayer.
  8. // @copyright 2015+, Andrea Lazzarotto - GPLv3 License
  9. // @require http://code.jquery.com/jquery-latest.min.js
  10. // @grant GM_xmlhttpRequest
  11. // @connect bbc.co.uk
  12. // @connect akamaized.net
  13. // @connect llnwd.net
  14. // @license GPL version 3 or any later version; http://www.gnu.org/copyleft/gpl.html
  15. // ==/UserScript==
  16.  
  17. var count = 0;
  18.  
  19. var containers = '.playback-content > button,' +
  20. '.content-item-description__text-container,' +
  21. '.episode-panel__intro,' +
  22. '.vxp-media__summary,' +
  23. '#programme-clip,' +
  24. '[class*="__media-asset"],' +
  25. '.msc-media-player-wrapper';
  26.  
  27. var get_title = function(name) {
  28. var title = name || $('meta[property="og:title"]').attr('content') || 'output';
  29. return title.replace(/\W+/g, '_');
  30. };
  31.  
  32. var get_JSON = function(url, callback) {
  33. GM_xmlhttpRequest({
  34. method: 'GET',
  35. url: url,
  36. onload: function(responseDetails) {
  37. var r = responseDetails.responseText;
  38. var json = $.parseJSON(r);
  39. callback(json);
  40. }
  41. });
  42. };
  43.  
  44. var place_link_box = function(element, id) {
  45. element.after('<div id="' + id + '" />');
  46. $('#' + id).css({
  47. 'padding': '.75em',
  48. 'margin': '25px auto',
  49. //'width': $('#player-outer-outer').width(),
  50. 'border': '1px solid #2C2C2C',
  51. 'background-color': '#0A0C16',
  52. 'color': 'white',
  53. 'font-family': 'sans-serif',
  54. 'box-sizing': 'border-box',
  55. 'font-size': '0.9rem'
  56. });
  57. };
  58.  
  59. var render_piece = function(html) {
  60. var tree = $(html);
  61. if (!tree.length)
  62. return '';
  63. var output = [];
  64. var nodes = tree[0].childNodes;
  65. var hyph = html.toString().indexOf('<span') > 0 ? '- ' : '';
  66. for (var o = 0; o < nodes.length; o++) {
  67. if (nodes[o].toString().indexOf('Text') > 0)
  68. output.push(hyph + nodes[o].textContent);
  69. else {
  70. var name = nodes[o].tagName.toLowerCase();
  71. switch(name) {
  72. case 'br':
  73. output.push(' ');
  74. break;
  75. case 'span':
  76. output.push('\n' + hyph);
  77. output.push(render_piece(nodes[o]));
  78. output.push('\n');
  79. break;
  80. }
  81. }
  82. }
  83. var joined = output.join('');
  84. joined = joined.replace(/\s+\n/, '\n').replace(/(^\n|\n$)/, '');
  85. joined = joined.replace(/\n+/, '\n').replace(/\s+/, ' ');
  86. return joined;
  87. };
  88.  
  89. var render_part = function(html, id) {
  90. var tree = $(html);
  91. var begin = tree.attr('begin').replace('.', ',');
  92. var end = tree.attr('end').replace('.', ',');
  93. return id + '\n' +
  94. begin + ' --> ' + end + '\n' +
  95. render_piece(html);
  96. };
  97.  
  98. var handle_subtitles = function(subURL, element_id, title) {
  99. if (!subURL)
  100. return;
  101.  
  102. GM_xmlhttpRequest({
  103. method: 'GET',
  104. url: subURL,
  105. onload: function(responseDetails) {
  106. var r = responseDetails.responseText;
  107. var doc = $.parseXML(r);
  108. var $xml = $(doc);
  109.  
  110. var srt_list = [];
  111. $xml.find('p').each(function(index, value){
  112. srt_list.push(render_part(value.outerHTML, index+1));
  113. });
  114.  
  115. $('#' + element_id + ' #subtitles').remove();
  116. $('#' + element_id + ' p:last-child').css('margin-bottom', 'auto');
  117. $('#' + element_id).append('<ul id="subtitles"><li><a id="srt-link">Download converted subtitles (SRT)</a></li>' +
  118. '<li><a href="' + subURL + '">Download original subtitles (TTML)</a></li></ul>');
  119. $('#srt-link').attr('href', 'data:text/plain;charset=utf-8,' +
  120. encodeURIComponent(srt_list.join('\n\n'))).attr('download', get_title(title) + '.srt');
  121. $('#' + element_id + ' a').css({
  122. 'color': 'white',
  123. 'font-weight': 'bold'
  124. });
  125. $('#' + element_id + ' ul').css({
  126. 'list-style': 'initial',
  127. 'padding-left': '2em',
  128. 'margin-top': '.5em'
  129. });
  130. }
  131. });
  132. };
  133.  
  134. var append = function(elements, id, parsed, title) {
  135. var type = parsed.kind || 'video';
  136. var tool = 'youtube-dl';
  137. var safe_title = get_title(title);
  138. var element = $(elements.get(0));
  139.  
  140. var objects = parsed[type];
  141. if (objects.length === 0)
  142. return;
  143.  
  144. $("#" + id).remove();
  145. element.after('<div id=' + id + '"></div>');
  146. place_link_box(element, id);
  147.  
  148. if (objects.length > 1)
  149. $('#' + id).append('<h4>Quality level: <select /></h4>');
  150. $('#' + id).append('<p>To record the ' + type + ', use <code>' + tool + '</code> with the following command line:</p>');
  151.  
  152. for (var i in objects) {
  153. var label = parseInt(objects[i].bitrate);
  154. var url = objects[i].connection[0].href;
  155. $('#' + id).append('<div id="wrapper-' + i + '"><pre>' + tool + ' -f bestvideo+bestaudio "' + url + '" -o ' + safe_title + '</pre></div>');
  156. $('#' + id + ' select').append('<option value="' + i + '">' + label + "</option>");
  157. }
  158. $('#' + id + ' div[id*=wrapper]').hide();
  159. $('#' + id + ' #wrapper-0').show();
  160.  
  161. $('#' + id + ' select').css('color', 'black').on('change', function() {
  162. var index = this.value;
  163. $('#' + id + ' div[id*=wrapper]').hide();
  164. $('#' + id + ' #wrapper-' + index).show();
  165. });
  166.  
  167. $('#' + id + ' pre, #' + id + ' code').css({
  168. 'white-space': 'normal',
  169. 'word-break': 'break-all',
  170. 'font-size': $('#direct-link p').css('font-size'),
  171. 'margin': '.75em 0',
  172. 'padding': '.75em',
  173. 'background-color': '#2C2C2C',
  174. 'font-family': '"DejaVu Sans Mono", Menlo, "Andale Mono", monospace'
  175. });
  176. $('#' + id + ' code').css('padding','.25em');
  177. $('#' + id + ' p:last-child').css('margin-bottom', '0');
  178. $('#' + id + ' *').css({
  179. 'color': 'white',
  180. 'line-height': '1em',
  181. 'font-size': '.9rem'
  182. });
  183.  
  184. return id;
  185. };
  186.  
  187. var comparator = function(a,b) { return parseInt(b.bitrate)-parseInt(a.bitrate); };
  188.  
  189. var filter = function(media) {
  190. if (!media.hasOwnProperty('connection'))
  191. return;
  192. if (media.kind != 'video' && media.kind != 'audio')
  193. return;
  194. for (var i = media.connection.length - 1; i >= 0; i--) {
  195. var format = media.connection[i].transferFormat || media.connection[i].format;
  196. if (format !== 'dash')
  197. media.connection.splice(i, 1);
  198. }
  199. };
  200.  
  201. var classify = function(media) {
  202. var results = {
  203. video: [],
  204. audio: [],
  205. captions: []
  206. };
  207. for (var i = 0; i < media.length; i++) {
  208. var element = media[i];
  209. filter(element);
  210. if(results.hasOwnProperty(element.kind) && ((!element.hasOwnProperty('connection') || element.connection.length)))
  211. results[element.kind].push(element);
  212. }
  213. results.video.sort(comparator);
  214. results.audio.sort(comparator);
  215. results.hasCaptions = results.captions.length > 0;
  216. results.kind = results.video.length ? "video" : "audio";
  217. return results;
  218. };
  219.  
  220. var handle_player = function(player) {
  221. var container = player._container;
  222. var title = player.playlist.title;
  223. var vpid, type, kind;
  224.  
  225. for (var i = 0; i < player.playlist.items.length; i++) {
  226. vpid = vpid || player.playlist.items[i].vpid || player.playlist.items[i].identifier;
  227. type = type || player.playlist.items[i].type;
  228. kind = kind || player.playlist.items[i].kind;
  229. if (vpid !== null)
  230. break;
  231. }
  232.  
  233. if (vpid === null)
  234. return false;
  235.  
  236. var elements = $(containers);
  237. if (elements.length === 0)
  238. elements = container.closest('figure');
  239.  
  240. if (kind === 'trailer') {
  241. try {
  242. var contents = $('script:contains(window.mediatorDefer = mediator.bind)').html().split('mediator.bind(')[1].split(', docu')[0];
  243. var data = JSON.parse(contents);
  244. vpid = data.episode.versions[0].id;
  245. }
  246. catch(e) {
  247. var id = 'video-trailer-warning-' + vpid;
  248. if (!$('#' + id).length) {
  249. place_link_box(elements, id);
  250. $('#' + id).append('<p>To download this ' + type + ', press <i>Play</i> and skip the trailer.</p>');
  251. }
  252. return false;
  253. }
  254. }
  255.  
  256. get_JSON('http://open.live.bbc.co.uk/mediaselector/5/select/version/2.0/mediaset/pc/vpid/' + vpid + '/format/json/', function(data) {
  257. var parsed = classify(data.media);
  258. var id = append(elements, 'video-download-' + vpid, parsed, title);
  259. if (parsed.hasCaptions)
  260. handle_subtitles(parsed.captions[0].connection[0].href, id, title);
  261. });
  262.  
  263. return true;
  264. };
  265.  
  266. var monitor = function() {
  267. if(!unsafeWindow.embeddedMedia)
  268. return;
  269.  
  270. var players = unsafeWindow.embeddedMedia.players;
  271. if (players.length != count && players[0].playlist) {
  272. if (count === 0) {
  273. for (var i = 0; i < players.length; i++)
  274. if (handle_player(players[i]))
  275. count++;
  276. }
  277. else
  278. if (handle_player(players[players.length - 1]))
  279. count++;
  280. }
  281. };
  282.  
  283. $(document).ready(function(){
  284. setInterval(monitor, 1000);
  285. });