123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181182183184185186187188189190191192193194195196 |
- 'use strict';
- var cheerio = require('cheerio');
- var fs = require('fs');
- var request = require('request');
- var path = require('path');
- var subtitle = require('./subtitle');
- var video = require('./video');
- var xml2js = require('xml2js');
- /**
- * Streams the episode video and subtitle to disk.
- * @param {Object} config
- * @param {string} address
- * @param {function(Error)} done
- */
- module.exports = function (config, address, done) {
- _page(address, function(err, page) {
- if (err) return done(err);
- _player(address, page.id, function(err, player) {
- if (err) return done(err);
- _download(config, page, player, done);
- });
- });
- };
- /**
- * Affixes zero-padding to the value.
- * @private
- * @param {(number|string)} value
- * @param {number} length
- * @returns {string}
- */
- function _affix(value, length) {
- if (typeof value !== 'string') value = String(value);
- var suffix = value.indexOf('.') !== -1;
- var add = length - (suffix ? value.indexOf('.') : value.length);
- while ((add -= 1) >= 0) value = '0' + value;
- return value;
- }
- /**
- * Completes a download and writes the message with a time indication.
- * @param {string} message
- * @param {number} begin
- * @param {function(Error)} done
- */
- function _complete(message, begin, done) {
- var timeInMs = Date.now() - begin;
- var seconds = _affix(Math.floor(timeInMs / 1000) % 60, 2);
- var minutes = _affix(Math.floor(timeInMs / 1000 / 60) % 60, 2);
- var hours = _affix(Math.floor(timeInMs / 1000 / 60 / 60), 2);
- console.log(message + ' (' + hours + ':' + minutes + ':' + seconds + ')');
- done(undefined);
- }
- /**
- * Downloads the subtitle and video.
- * @private
- * @param {Object} config
- * @param {Object} page
- * @param {Object} player
- * @param {function(Error)} done
- */
- function _download(config, page, player, done) {
- var tag = config.tag || 'CrunchyRoll';
- var episode = (page.episode < 10 ? '0' : '') + page.episode;
- var fileName = page.series + ' - ' + episode + ' [' + tag + ']';
- var filePath = path.join(config.path || process.cwd(), fileName);
- _subtitle(config, player, filePath, function(err, exists) {
- if (err || exists) return done(err || undefined);
- var begin = Date.now();
- console.log('Fetching ' + fileName);
- _video(config, page, player, filePath, function(err, exists) {
- if (err || exists) return done(err || undefined);
- if (!config.merge) return _complete('Finished ' + fileName, begin, done);
- video.merge(config, player.video.file, filePath, function(err) {
- if (err) return done(err);
- _complete('Finished ' + fileName, begin, done);
- });
- });
- });
- }
- /**
- * Requests the page data and scrapes the id, episode, series and swf.
- * @private
- * @param {string} address
- * @param {function(Error, Object=)} done
- */
- function _page(address, done) {
- var id = parseInt((address.match(/[0-9]+$/) || [0])[0], 10);
- if (!id) return done(new Error('Invalid address.'));
- request.get(address, function(err, res, body) {
- if (err) return done(err);
- var $ = cheerio.load(body);
- var swf = /^([^?]+)/.exec($('link[rel=video_src]').attr('href'));
- var title = /Watch ([\w\W]+) Episode ([0-9]+)/.exec($('title').text());
- if (!swf || !title) return done(new Error('Invalid page.'));
- done(undefined, {
- id: id,
- episode: parseInt(title[2], 10),
- series: title[1],
- swf: swf[1]
- });
- });
- }
- /**
- * Requests the player data and scrapes the subtitle and video data.
- * @private
- * @param {string} address
- * @param {number} id
- * @param {function(Error, Object=)} done
- */
- function _player(address, id, done) {
- var url = address.match(/^(https?:\/\/[^\/]+)/);
- if (!url) return done(new Error('Invalid address.'));
- request.post({
- form: {current_page: address},
- url: url[1] + '/xml/?req=RpcApiVideoPlayer_GetStandardConfig&media_id=' + id
- }, function(err, res, xml) {
- if (err) return done(err);
- xml2js.parseString(xml, {
- explicitArray: false,
- explicitRoot: false
- }, function(err, player) {
- if (err) return done(err);
- try {
- done(undefined, {
- subtitle: {
- id: player['default:preload'].subtitle.$.id,
- iv: player['default:preload'].subtitle.iv,
- data: player['default:preload'].subtitle.data
- },
- video: {
- file: player['default:preload'].stream_info.file,
- host: player['default:preload'].stream_info.host
- }
- });
- } catch(err) {
- done(err);
- }
- });
- });
- }
- /**
- * Saves the subtitles to disk.
- * @private
- * @param {Object} config
- * @param {Object} player
- * @param {string} filePath
- * @param {function(Error, boolean=)} done
- */
- function _subtitle(config, player, filePath, done) {
- var format = subtitle.formats[config.format] ? config.format : 'srt';
- fs.exists(filePath + (config.merge ? '.mkv' : format), function(exists) {
- if (exists) return done(undefined, true);
- var enc = player.subtitle;
- subtitle.decode(enc.id, enc.iv, enc.data, function(err, data) {
- if (err) return done(err);
- subtitle.formats[format](data, function(err, decodedSubtitle) {
- if (err) return done(err);
- fs.writeFile(filePath + '.' + format, decodedSubtitle, function(err) {
- if (err) return done(err);
- done(undefined, false);
- });
- });
- });
- });
- }
- /**
- * Streams the video to disk.
- * @private
- * @param {Object} config
- * @param {Object} page
- * @param {Object} player
- * @param {string} filePath
- * @param {function(Error, boolean=)} done
- */
- function _video(config, page, player, filePath, done) {
- var extension = path.extname(player.video.file);
- fs.exists(filePath + (config.merge ? '.mkv' : extension), function(exists) {
- if (exists) return done(undefined, true);
- video.stream(
- player.video.host,
- player.video.file,
- page.swf,
- filePath + extension,
- function(err) {
- if (err) return done(err);
- done(undefined, false);
- });
- });
- }
|