BBC iPlayer video download

This script allows to save videos from BBC iPlayer.

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

  1. // ==UserScript==
  2. // @name BBC iPlayer video download
  3. // @namespace http://andrealazzarotto.com/
  4. // @include http://www.bbc.co.uk/iplayer/episode/*
  5. // @include http://www.bbc.co.uk/programmes/*
  6. // @version 3.0
  7. // @description This script allows 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. // @license GPL version 3 or any later version; http://www.gnu.org/copyleft/gpl.html
  12. // ==/UserScript==
  13.  
  14. get_title = function() {
  15. var title = $('meta[property="og:title"]').attr('content') || 'output';
  16. return title.replace(/\W+/g, '_');
  17. }
  18.  
  19. appendURL = function(element, url) {
  20. element.after('<div id="direct-link"></div>');
  21. $('#direct-link').css({
  22. 'padding': '.75em',
  23. 'margin': '25px auto',
  24. 'width': $('#player-outer-outer').width(),
  25. 'border': '1px solid #444',
  26. 'color': 'white',
  27. 'font-family': 'sans-serif',
  28. 'box-sizing': 'border-box'
  29. }).append('<p>To record the video, use <code>avconv</code> with the following command line:</p>' +
  30. '<pre>avconv -i "' + url + '" -codec copy -qscale 0 ' + get_title() + '.mp4</pre>' +
  31. '<p>Alternatively, you may also try to record the M3U8 stream URL with VLC.</p>');
  32. $('#direct-link pre, #direct-link code').css({
  33. 'white-space': 'normal',
  34. 'word-break': 'break-word',
  35. 'font-size': $('#direct-link p').css('font-size'),
  36. 'margin': '.75em 0',
  37. 'padding': '.75em',
  38. 'background-color': '#444'
  39. });
  40. $('#direct-link code').css('padding','.25em');
  41. $('#direct-link p:last-child').css('margin-bottom', '0');
  42. }
  43.  
  44. get_biggest = function(dict) {
  45. var s = 0;
  46. var o = null;
  47. for(var key in dict) {
  48. key = parseInt(key) || 0;
  49. if (key > s) {
  50. s = key;
  51. o = dict[key];
  52. }
  53. }
  54. return {'size': s, 'object': o};
  55. }
  56.  
  57. render_piece = function(html) {
  58. var tree = $(html);
  59. if (tree.length == 0)
  60. return '';
  61. var output = [];
  62. var nodes = tree[0].childNodes;
  63. var hyph = html.toString().indexOf('<span') > 0 ? '- ' : '';
  64. for (var o = 0; o < nodes.length; o++) {
  65. if (nodes[o].toString().indexOf('Text') > 0)
  66. output.push(hyph + nodes[o].textContent);
  67. else {
  68. var name = nodes[o].tagName.toLowerCase();
  69. switch(name) {
  70. case 'br':
  71. output.push(' ');
  72. break;
  73. case 'span':
  74. output.push('\n' + hyph)
  75. output.push(render_piece(nodes[o]));
  76. output.push('\n');
  77. break;
  78. }
  79. }
  80. }
  81. var joined = output.join('');
  82. joined = joined.replace(/\s+\n/, '\n').replace(/(^\n|\n$)/, '');
  83. joined = joined.replace(/\n+/, '\n').replace(/\s+/, ' ');
  84. return joined;
  85. }
  86.  
  87. render_p = function(html) {
  88. var tree = $(html);
  89. var begin = tree.attr('begin').replace('.', ',');
  90. var end = tree.attr('end').replace('.', ',');
  91. var id = tree.attr('id').replace('p', '');
  92. return id + '\n' +
  93. begin + ' --> ' + end + '\n' +
  94. render_piece(html);
  95. }
  96.  
  97. handle_subtitles = function(subURL) {
  98. GM_xmlhttpRequest({
  99. method: 'GET',
  100. url: subURL,
  101. onload: function(responseDetails) {
  102. var r = responseDetails.responseText;
  103. var doc = $.parseXML(r);
  104. var $xml = $(doc);
  105. var srt_list = [];
  106. $xml.find('p').each(function(){
  107. srt_list.push(render_p($(this)[0].outerHTML));
  108. });
  109. $('#direct-link p:last-child').css('margin-bottom', 'auto');
  110. $('#direct-link').append('<ul><li><a id="srt-link">Download converted subtitles (SRT)</a></li>' +
  111. '<li><a href="' + subURL + '">Download original subtitles (TTML)</a></li></ul>');
  112. $('#srt-link').attr('href', 'data:text/plain;charset=utf-8,'
  113. + encodeURIComponent(srt_list.join('\n\n'))).attr('download', get_title() + '.srt');
  114. $('#direct-link a').css({
  115. 'color': 'white',
  116. 'font-weight': 'bold'
  117. });
  118. $('#direct-link ul').css({
  119. 'list-style': 'initial',
  120. 'padding-left': '2em',
  121. 'margin-top': '.5em'
  122. });
  123. }
  124. });
  125. }
  126.  
  127. handle_pid = function(vpid, selector){
  128. var config_url = 'http://www.bbc.co.uk/iplayer/config/windows-phone';
  129. // figure out the mediaselector URL
  130. $.getJSON(config_url, function(data) {
  131. var selector_mobile = data['mediaselector'].replace('{vpid}', vpid);
  132. var selector_pc = selector_mobile.replace(/mobile-.*vpid/, 'pc/vpid');
  133. // get mobile data
  134. GM_xmlhttpRequest({
  135. method: 'GET',
  136. url: selector_mobile,
  137. onload: function(responseDetails) {
  138. var r = responseDetails.responseText;
  139. var doc = $.parseXML(r);
  140. var $xml = $(doc);
  141. var media = {};
  142. $xml.find('media[kind^="video"]').each(function() {
  143. var bitrate = $(this).attr('bitrate');
  144. var href = $(this).find('connection').attr('href');
  145. media[bitrate] = href;
  146. });
  147. var subURL = $xml.find('media[service="captions"] connection').attr('href');
  148. var m3u8_url = get_biggest(media);
  149. console.log("M3U8_URL: " + m3u8_url['object']);
  150. // get desktop data for higher quality
  151. GM_xmlhttpRequest({
  152. method: 'GET',
  153. url: selector_pc,
  154. onload: function(responseDetails) {
  155. var r = responseDetails.responseText;
  156. var doc = $.parseXML(r);
  157. var $xml = $(doc);
  158. var media = {};
  159. $xml.find('media[kind^="video"]').each(function() {
  160. var bitrate = $(this).attr('bitrate');
  161. var identifier = $(this).find('connection[application="ondemand"], ' +
  162. 'connection[application*="/e3"]').attr('identifier');
  163. if(identifier)
  164. media[bitrate] = identifier;
  165. });
  166. var high_quality = get_biggest(media);
  167. console.log("HIGH_QUALITY: " + high_quality['object']);
  168. // compose the M3U8 stream URL
  169. GM_xmlhttpRequest({
  170. method: 'GET',
  171. url: m3u8_url['object'],
  172. onload: function(responseDetails) {
  173. var r = responseDetails.responseText;
  174. var urls = r.split('\n').slice(1);
  175. var final_url = urls[1];
  176. // fix the final url
  177. var old_pieces = final_url.split(',');
  178. var pieces = [old_pieces[0], old_pieces[1], old_pieces[old_pieces.length-1]];
  179. var p = 1;
  180. var high_quality_piece = high_quality['object'].replace('.mp4','')
  181. .replace('secure_auth/','').split('/').slice(1).join('/');
  182. var new_p = pieces[p].split('/').slice(0,2).join('/') + '/' + high_quality_piece;
  183. pieces[p] = new_p;
  184. final_url = pieces.join(',');
  185. // output the M3U8 URL
  186. appendURL($(selector), final_url);
  187. console.log("doing subtitles...");
  188. handle_subtitles(subURL);
  189. }
  190. });
  191. }
  192. });
  193. }
  194. });
  195. }); // getJSON
  196. }
  197.  
  198. $(document).ready(function(){
  199. var isProgramme = !!unsafeWindow.bbcProgrammes;
  200. if (isProgramme) {
  201. var clipid = location.href.split("/")[4];
  202. $.getJSON('http://www.bbc.co.uk/programmes/' + clipid + '/playlist.json', function(data) {
  203. var vpid = data.defaultAvailableVersion.pid;
  204. console.log("VPID: " + vpid.toString());
  205. handle_pid(vpid, '.island .cf.component');
  206. });
  207. }
  208. else {
  209. var spid = $('script:contains("mediator.bind")').html();
  210. var vpid = spid.split('vpid')[1].split('"')[2];
  211. handle_pid(vpid, '#player-outer-outer');
  212. }
  213. }); // $(document).ready.ready