episode.js 3.9 KB

123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137
  1. 'use strict';
  2. var cheerio = require('cheerio');
  3. var fs = require('fs');
  4. var request = require('request');
  5. var path = require('path');
  6. var subtitle = require('./subtitle');
  7. var video = require('./video');
  8. var xml2js = require('xml2js');
  9. /**
  10. * Streams the episode video and subtitle to disk.
  11. * @param {Object} config
  12. * @param {string} address
  13. * @param {function(Error)} done
  14. */
  15. module.exports = function (config, address, done) {
  16. _page(address, function(err, page) {
  17. if (err) return done(err);
  18. _player(address, page.id, function(err, player) {
  19. if (err) return done(err);
  20. _download(config, page, player, done);
  21. });
  22. });
  23. };
  24. /**
  25. * Downloads the subtitle and video.
  26. * @param {Object} config
  27. * @param {Object} page
  28. * @param {Object} player
  29. * @param {function(Error)} done
  30. */
  31. function _download(config, page, player, done) {
  32. var tag = config.tag || 'CrunchyRoll';
  33. var episode = (page.episode < 10 ? '0' : '') + page.episode;
  34. var fileName = page.series + ' - ' + episode + ' [' + tag + ']';
  35. var filePath = path.join(config.path || process.cwd(), fileName);
  36. _subtitle(config, player, filePath, function(err) {
  37. if (err) return done(err);
  38. _video(config, page, player, filePath, done);
  39. });
  40. }
  41. /**
  42. * Requests the page data and scrapes the id, episode, series and swf.
  43. * @private
  44. * @param {string} address
  45. * @param {function(Error, Object=)} done
  46. */
  47. function _page(address, done) {
  48. var id = parseInt((address.match(/[0-9]+$/) || [0])[0], 10);
  49. if (!id) return done(new Error('Invalid address.'));
  50. request.get(address, function(err, res, body) {
  51. if (err) return done(err);
  52. var $ = cheerio.load(body);
  53. var swf = /^([^?]+)/.exec($('link[rel=video_src]').attr('href'));
  54. var title = /Watch ([\w\W]+) Episode ([0-9]+)/.exec($('title').text());
  55. if (!swf || !title) return done(new Error('Invalid page.'));
  56. done(undefined, {
  57. id: id,
  58. episode: parseInt(title[2], 10),
  59. series: title[1],
  60. swf: swf[1]
  61. });
  62. });
  63. }
  64. /**
  65. * Requests the player data and scrapes the subtitle and video data.
  66. * @private
  67. * @param {string} address
  68. * @param {number} id
  69. * @param {function(Error, Object=)} done
  70. */
  71. function _player(address, id, done) {
  72. var url = address.match(/^(https?:\/\/[^\/]+)/);
  73. if (!url) return done(new Error('Invalid address.'));
  74. request.post({
  75. form: {current_page: address},
  76. url: url[1] + '/xml/?req=RpcApiVideoPlayer_GetStandardConfig&media_id=' + id
  77. }, function(err, res, xml) {
  78. if (err) return done(err);
  79. xml2js.parseString(xml, {
  80. explicitArray: false,
  81. explicitRoot: false
  82. }, function(err, player) {
  83. if (err) return done(err);
  84. done(undefined, {
  85. subtitle: {
  86. id: player['default:preload'].subtitle.$.id,
  87. iv: player['default:preload'].subtitle.iv,
  88. data: player['default:preload'].subtitle.data
  89. },
  90. video: {
  91. file: player['default:preload'].stream_info.file,
  92. host: player['default:preload'].stream_info.host
  93. }
  94. });
  95. });
  96. });
  97. }
  98. /**
  99. * Saves the subtitles to disk.
  100. * @param {Object} config
  101. * @param {Object} player
  102. * @param {string} filePath
  103. * @param {function(Error)} done
  104. */
  105. function _subtitle(config, player, filePath, done) {
  106. var contents = player.subtitle;
  107. subtitle.decode(contents.id, contents.iv, contents.data, function(err, data) {
  108. if (err) return done(err);
  109. var format = subtitle.formats[config.format] ? config.format : 'srt';
  110. subtitle.formats[format](data, function(err, decodedSubtitle) {
  111. if (err) return done(err);
  112. fs.writeFile(filePath + '.' + format, decodedSubtitle, done);
  113. });
  114. });
  115. }
  116. /**
  117. * Streams the video to disk.
  118. * @param {Object} config
  119. * @param {Object} page
  120. * @param {Object} player
  121. * @param {string} filePath
  122. * @param {function(Error)} done
  123. */
  124. function _video(config, page, player, filePath, done) {
  125. video.stream(
  126. player.video.host,
  127. player.video.file,
  128. page.swf,
  129. filePath + '.mp4',
  130. done);
  131. }