Merge branch 'master' into master

This commit is contained in:
Mateusz Majewski
2017-01-18 12:49:49 +02:00
committed by GitHub
14 changed files with 179 additions and 81 deletions

View File

@@ -11,11 +11,11 @@ import xml2js = require('xml2js');
/**
* Streams the episode to disk.
*/
export default function(config: IConfig, address: string, done: (err: Error) => void) {
export default function(config: IConfig, address: string, done: (err: Error, ign: boolean) => void) {
scrapePage(config, address, (err, page) => {
if (err) return done(err);
if (err) return done(err, false);
scrapePlayer(config, address, page.id, (err, player) => {
if (err) return done(err);
if (err) return done(err, false);
download(config, page, player, done);
});
});
@@ -24,37 +24,72 @@ export default function(config: IConfig, address: string, done: (err: Error) =>
/**
* Completes a download and writes the message with an elapsed time.
*/
function complete(message: string, begin: number, done: (err: Error) => void) {
function complete(message: string, begin: number, done: (err: Error, ign: boolean) => void) {
var timeInMs = Date.now() - begin;
var seconds = prefix(Math.floor(timeInMs / 1000) % 60, 2);
var minutes = prefix(Math.floor(timeInMs / 1000 / 60) % 60, 2);
var hours = prefix(Math.floor(timeInMs / 1000 / 60 / 60), 2);
console.log(message + ' (' + hours + ':' + minutes + ':' + seconds + ')');
done(null);
done(null, false);
}
/**
* Check if a file exist..
*/
function fileExist(path: string) {
try
{
fs.statSync(path);
return true;
}
catch (e) { }
return false;
}
/**
* Downloads the subtitle and video.
*/
function download(config: IConfig, page: IEpisodePage, player: IEpisodePlayer, done: (err: Error) => void) {
function download(config: IConfig, page: IEpisodePage, player: IEpisodePlayer, done: (err: Error, ign: boolean) => void) {
var series = config.series || page.series;
var fileName = name(config, page, series);
series = series.replace("/","_").replace("'","_").replace(":","_");
var fileName = name(config, page, series, "").replace("/","_").replace("'","_").replace(":","_");
var filePath = path.join(config.output || process.cwd(), series, fileName);
if (fileExist(filePath + ".mkv"))
{
var count = 0;
console.info("File '"+fileName+"' already exist...");
do
{
count = count + 1;
fileName = name(config, page, series, "-" + count).replace("/","_").replace("'","_").replace(":","_");
filePath = path.join(config.output || process.cwd(), series, fileName);
} while(fileExist(filePath + ".mkv"))
console.info("Renaming to '"+fileName+"'...");
}
mkdirp(path.dirname(filePath), (err: Error) => {
if (err) return done(err);
if (err) return done(err, false);
downloadSubtitle(config, player, filePath, err => {
if (err) return done(err);
if (err) return done(err, false);
var now = Date.now();
console.log('Fetching ' + fileName);
downloadVideo(config, page, player, filePath, err => {
if (err) return done(err);
if (config.merge) return complete('Finished ' + fileName, now, done);
var isSubtited = Boolean(player.subtitle);
video.merge(config, isSubtited, player.video.file, filePath, err => {
if (err) return done(err);
complete('Finished ' + fileName, now, done);
if (player.video.file != undefined)
{
console.log('Fetching ' + fileName);
downloadVideo(config, page, player, filePath, err => {
if (err) return done(err, false);
if (config.merge) return complete('Finished ' + fileName, now, done);
var isSubtited = Boolean(player.subtitle);
video.merge(config, isSubtited, player.video.file, filePath, player.video.mode, err => {
if (err) return done(err, false);
complete('Finished ' + fileName, now, done);
});
});
});
}
else
{
console.log('Ignoring ' + fileName + ': not released yet');
done(null, true);
}
});
});
}
@@ -88,18 +123,21 @@ function downloadVideo(config: IConfig,
player.video.host,
player.video.file,
page.swf,
filePath + path.extname(player.video.file),
filePath, path.extname(player.video.file),
player.video.mode,
done);
}
/**
* Names the file based on the config, page, series and tag.
*/
function name(config: IConfig, page: IEpisodePage, series: string) {
var episode = (page.episode < 10 ? '0' : '') + page.episode;
var volume = (page.volume < 10 ? '0' : '') + page.volume;
function name(config: IConfig, page: IEpisodePage, series: string, extra: string) {
var episodeNum = parseInt(page.episode, 10);
var volumeNum = parseInt(page.volume, 10);
var episode = (episodeNum < 10 ? '0' : '') + page.episode;
var volume = (volumeNum < 10 ? '0' : '') + page.volume;
var tag = config.tag || 'CrunchyRoll';
return series + ' ' + volume + 'x' + episode + ' [' + tag + ']';
return series + ' - s' + volume + 'e' + episode +' - [' + tag + ']' + extra;
}
/**
@@ -121,15 +159,29 @@ function scrapePage(config: IConfig, address: string, done: (err: Error, page?:
if (err) return done(err);
var $ = cheerio.load(result);
var swf = /^([^?]+)/.exec($('link[rel=video_src]').attr('href'));
var regexp = /-\s+(?:Watch\s+)?(.+?)(?:\s+Season\s+([0-9]+))?(?:\s+-)?\s+Episode\s+([0-9]+)/;
var data = regexp.exec($('title').text());
if (!swf || !data) return done(new Error('Invalid page.'));
var regexp = /\s*([^\n\r\t\f]+)\n?\s*[^0-9]*([0-9][0-9.]*)?,?\n?\s\s*[^0-9]*((PV )?[S0-9][P0-9.]*[a-fA-F]?)/;
var look = $('#showmedia_about_media').text();
var seasonTitle = $('span[itemprop="title"]').text();
var data = regexp.exec(look);
if (!swf || !data)
{
console.info('Something wrong in the page at '+address+' (data are: '+look+')');
console.info('Setting Season to 0 and episode to \0\...');
done(null, {
id: id,
episode: "0",
series: seasonTitle,
swf: swf[1],
volume: "0"
});
}
done(null, {
id: id,
episode: parseInt(data[3], 10),
episode: data[3],
series: data[1],
swf: swf[1],
volume: parseInt(data[2], 10) || 1
volume: data[2] || "1"
});
});
}
@@ -152,6 +204,11 @@ function scrapePlayer(config: IConfig, address: string, id: number, done: (err:
if (err) return done(err);
try {
var isSubtitled = Boolean(player['default:preload'].subtitle);
var streamMode="RTMP";
if (player['default:preload'].stream_info.host == "")
{
streamMode="HLS";
}
done(null, {
subtitle: isSubtitled ? {
id: parseInt(player['default:preload'].subtitle.$.id, 10),
@@ -159,6 +216,7 @@ function scrapePlayer(config: IConfig, address: string, id: number, done: (err:
data: player['default:preload'].subtitle.data
} : null,
video: {
mode: streamMode,
file: player['default:preload'].stream_info.file,
host: player['default:preload'].stream_info.host
}

View File

@@ -1,7 +1,7 @@
interface IEpisodePage {
id: number;
episode: number;
episode: string;
series: string;
volume: number;
volume: string;
swf: string;
}

View File

@@ -5,6 +5,7 @@ interface IEpisodePlayer {
data: string;
};
video: {
mode: string;
file: string;
host: string;
};

View File

@@ -1,5 +1,5 @@
interface ISeriesEpisode {
address: string;
episode: number;
episode: string;
volume: number;
}

View File

@@ -97,4 +97,4 @@ function modify(options: string|request.Options): request.Options {
return options;
}
return {jar: true, headers: defaultHeaders, url: options.toString()};
}
}

View File

@@ -19,14 +19,22 @@ export default function(config: IConfig, address: string, done: (err: Error) =>
var i = 0;
(function next() {
if (i >= page.episodes.length) return done(null);
download(cache, config, address, page.episodes[i], err => {
download(cache, config, address, page.episodes[i], (err, ignored) => {
if (err) return done(err);
var newCache = JSON.stringify(cache, null, ' ');
fs.writeFile(persistentPath, newCache, err => {
if (err) return done(err);
if ((ignored == false) || (ignored == undefined))
{
var newCache = JSON.stringify(cache, null, ' ');
fs.writeFile(persistentPath, newCache, err => {
if (err) return done(err);
i += 1;
next();
});
}
else
{
i += 1;
next();
});
}
});
})();
});
@@ -40,14 +48,14 @@ function download(cache: {[address: string]: number},
config: IConfig,
baseAddress: string,
item: ISeriesEpisode,
done: (err: Error) => void) {
if (!filter(config, item)) return done(null);
done: (err: Error, ign: boolean) => void) {
if (!filter(config, item)) return done(null, false);
var address = url.resolve(baseAddress, item.address);
if (cache[address]) return done(null);
episode(config, address, err => {
if (err) return done(err);
if (cache[address]) return done(null, false);
episode(config, address, (err, ignored) => {
if (err) return done(err, false);
cache[address] = Date.now();
done(null);
done(null, ignored);
});
}
@@ -57,8 +65,8 @@ function download(cache: {[address: string]: number},
function filter(config: IConfig, item: ISeriesEpisode) {
// Filter on chapter.
var episodeFilter = config.episode;
if (episodeFilter > 0 && item.episode <= episodeFilter) return false;
if (episodeFilter < 0 && item.episode >= -episodeFilter) return false;
if (episodeFilter > 0 && parseInt(item.episode, 10) <= episodeFilter) return false;
if (episodeFilter < 0 && parseInt(item.episode, 10) >= -episodeFilter) return false;
// Filter on volume.
var volumeFilter = config.volume;
@@ -75,18 +83,18 @@ function page(config: IConfig, address: string, done: (err: Error, result?: ISer
if (err) return done(err);
var $ = cheerio.load(result);
var title = $('span[itemprop=name]').text();
if (!title) return done(new Error('Invalid page.'));
if (!title) return done(new Error('Invalid page.(' + address + ')'));
var episodes: ISeriesEpisode[] = [];
$('.episode').each((i, el) => {
if ($(el).children('img[src*=coming_soon]').length) return;
var volume = /([0-9]+)\s*$/.exec($(el).closest('ul').prev('a').text());
var regexp = /Episode\s+([0-9]+)\s*$/i;
var regexp = /Episode\s+((PV )?[S0-9][P0-9.]*[a-fA-F]?)\s*$/i;
var episode = regexp.exec($(el).children('.series-title').text());
var address = $(el).attr('href');
if (!address || !episode) return;
episodes.push({
address: address,
episode: parseInt(episode[0], 10),
episode: episode[1],
volume: volume ? parseInt(volume[0], 10) : 1
});
});

View File

@@ -8,9 +8,17 @@ import subtitle from '../subtitle/index';
/**
* Merges the subtitle and video files into a Matroska Multimedia Container.
*/
export default function(config: IConfig, isSubtitled: boolean, rtmpInputPath: string, filePath: string, done: (err: Error) => void) {
export default function(config: IConfig, isSubtitled: boolean, rtmpInputPath: string, filePath: string, streamMode: string, done: (err: Error) => void) {
var subtitlePath = filePath + '.' + (subtitle.formats[config.format] ? config.format : 'ass');
var videoPath = filePath + path.extname(rtmpInputPath);
var videoPath = filePath;
if (streamMode == "RTMP")
{
videoPath += path.extname(rtmpInputPath);
}
else
{
videoPath += ".mp4";
}
childProcess.exec(command() + ' ' +
'-o "' + filePath + '.mkv" ' +
'"' + videoPath + '" ' +

View File

@@ -6,20 +6,38 @@ import os = require('os');
/**
* Streams the video to disk.
*/
export default function(rtmpUrl: string, rtmpInputPath: string, swfUrl: string, filePath: string, done: (err: Error) => void) {
childProcess.exec(command() + ' ' +
'-r "' + rtmpUrl + '" ' +
'-y "' + rtmpInputPath + '" ' +
'-W "' + swfUrl + '" ' +
'-o "' + filePath + '"', {
maxBuffer: Infinity
}, done);
export default function(rtmpUrl: string, rtmpInputPath: string, swfUrl: string, filePath: string, fileExt: string, mode: string, done: (err: Error) => void) {
if (mode == "RTMP")
{
childProcess.exec(command("rtmpdump") + ' ' +
'-r "' + rtmpUrl + '" ' +
'-y "' + rtmpInputPath + '" ' +
'-W "' + swfUrl + '" ' +
'-o "' + filePath + fileExt + '"', {
maxBuffer: Infinity
}, done);
}
else if (mode == "HLS")
{
console.info("Experimental FFMPEG, MAY FAIL!!!");
var cmd=command("ffmpeg") + ' ' +
'-i "' + rtmpInputPath + '" ' +
'-c copy -bsf:a aac_adtstoasc ' +
'"' + filePath + '.mp4"';
childProcess.exec(cmd, {
maxBuffer: Infinity
}, done);
}
else
{
console.error("No such mode: " + mode);
}
}
/**
* Determines the command for the operating system.
*/
function command(): string {
if (os.platform() !== 'win32') return 'rtmpdump';
return '"' + path.join(__dirname, '../../bin/rtmpdump.exe') + '"';
function command(exe: string): string {
if (os.platform() !== 'win32') return exe;
return '"' + path.join(__dirname, '../../bin/' + exe + '.exe') + '"';
}