Update npm packages, cleanup the code, cleanup all tslint complain

This commit is contained in:
Godzil 2017-02-10 17:43:52 +00:00
parent 5d9c25491d
commit bee3f33e20
13 changed files with 751 additions and 389 deletions

View File

@ -17,22 +17,29 @@
"crunchy": "./bin/crunchy" "crunchy": "./bin/crunchy"
}, },
"dependencies": { "dependencies": {
"big-integer": "1.4.4", "big-integer": "^1.4.4",
"cheerio": "0.22.0", "cheerio": "^0.22.0",
"cloudscraper": "1.4.1", "cloudscraper": "^1.4.1",
"commander": "2.6.0", "commander": "^2.6.0",
"mkdirp": "0.5.0", "mkdirp": "^0.5.0",
"request": "2.74.0", "request": "^2.74.0",
"xml2js": "0.4.5" "xml2js": "^0.4.5"
}, },
"devDependencies": { "devDependencies": {
"typings": "2.1.0", "tsconfig-lint": "^0.12.0",
"tslint": "2.3.0-beta", "tslint": "^4.4.2",
"typescript": "1.5.0-beta" "typescript": "^2.2.0",
"typings": "^2.1.0"
}, },
"scripts": { "scripts": {
"prepublish": "npm run types && tsc", "prepublish": "npm run types && tsc",
"test": "node ts --only-test", "compile": "tsc",
"types": "typings install" "test": "tslint -c ./tslint.json --project ./tsconfig.json ./src/**/*.ts",
"types": "typings install",
"reinstall": "tsd reinstall; npm run types",
"start": "node ./bin/crunchy"
},
"bugs": {
"url": "https://github.com/Godzil/Crunchy/issues"
} }
} }

View File

@ -7,16 +7,33 @@ import series from './series';
/** /**
* Streams the batch of series to disk. * Streams the batch of series to disk.
*/ */
export default function(args: string[], done: (err?: Error) => void) { export default function(args: string[], done: (err?: Error) => void)
var config = parse(args); {
var batchPath = path.join(config.output || process.cwd(), 'CrunchyRoll.txt'); const config = parse(args);
tasks(config, batchPath, (err, tasks) => { const batchPath = path.join(config.output || process.cwd(), 'CrunchyRoll.txt');
if (err) return done(err);
var i = 0; tasks(config, batchPath, (err, tasks) =>
(function next() { {
if (i >= tasks.length) return done(); if (err)
series(tasks[i].config, tasks[i].address, err => { {
if (err) return done(err); return done(err);
}
let i = 0;
(function next()
{
if (i >= tasks.length)
{
return done();
}
series(tasks[i].config, tasks[i].address, err =>
{
if (err)
{
return done(err);
}
i += 1; i += 1;
next(); next();
}); });
@ -27,42 +44,82 @@ export default function(args: string[], done: (err?: Error) => void) {
/** /**
* Splits the value into arguments. * Splits the value into arguments.
*/ */
function split(value: string): string[] { function split(value: string): string[]
var inQuote = false; {
var i: number; let inQuote = false;
var pieces: string[] = []; let i: number;
var previous = 0; let pieces: string[] = [];
for (i = 0; i < value.length; i += 1) { let previous = 0;
if (value.charAt(i) === '"') inQuote = !inQuote;
if (!inQuote && value.charAt(i) === ' ') { for (i = 0; i < value.length; i += 1)
{
if (value.charAt(i) === '"')
{
inQuote = !inQuote;
}
if (!inQuote && value.charAt(i) === ' ')
{
pieces.push(value.substring(previous, i).match(/^"?(.+?)"?$/)[1]); pieces.push(value.substring(previous, i).match(/^"?(.+?)"?$/)[1]);
previous = i + 1; previous = i + 1;
} }
} }
var lastPiece = value.substring(previous, i).match(/^"?(.+?)"?$/);
if (lastPiece) pieces.push(lastPiece[1]); let lastPiece = value.substring(previous, i).match(/^"?(.+?)"?$/);
if (lastPiece)
{
pieces.push(lastPiece[1]);
}
return pieces; return pieces;
} }
/** /**
* Parses the configuration or reads the batch-mode file for tasks. * Parses the configuration or reads the batch-mode file for tasks.
*/ */
function tasks(config: IConfigLine, batchPath: string, done: (err: Error, tasks?: IConfigTask[]) => void) { function tasks(config: IConfigLine, batchPath: string, done: (err: Error, tasks?: IConfigTask[]) => void)
if (config.args.length) { {
return done(null, config.args.map(address => { if (config.args.length)
{
return done(null, config.args.map(address =>
{
return {address: address, config: config}; return {address: address, config: config};
})); }));
} }
fs.exists(batchPath, exists => {
if (!exists) return done(null, []); fs.exists(batchPath, exists =>
fs.readFile(batchPath, 'utf8', (err, data) => { {
if (err) return done(err); if (!exists)
var map: IConfigTask[] = []; {
data.split(/\r?\n/).forEach(line => { return done(null, []);
if (/^(\/\/|#)/.test(line)) return; }
var lineConfig = parse(process.argv.concat(split(line)));
lineConfig.args.forEach(address => { fs.readFile(batchPath, 'utf8', (err, data) =>
if (!address) return; {
if (err)
{
return done(err);
}
let map: IConfigTask[] = [];
data.split(/\r?\n/).forEach(line =>
{
if (/^(\/\/|#)/.test(line))
{
return;
}
let lineConfig = parse(process.argv.concat(split(line)));
lineConfig.args.forEach(address =>
{
if (!address)
{
return;
}
map.push({address: address, config: lineConfig}); map.push({address: address, config: lineConfig});
}); });
}); });
@ -74,7 +131,8 @@ function tasks(config: IConfigLine, batchPath: string, done: (err: Error, tasks?
/** /**
* Parses the arguments and returns a configuration. * Parses the arguments and returns a configuration.
*/ */
function parse(args: string[]): IConfigLine { function parse(args: string[]): IConfigLine
{
return new commander.Command().version(require('../package').version) return new commander.Command().version(require('../package').version)
// Authentication // Authentication
.option('-p, --pass <s>', 'The password.') .option('-p, --pass <s>', 'The password.')

View File

@ -1,6 +1,10 @@
'use strict'; 'use strict';
import batch from './batch'; import batch from './batch';
batch(process.argv, (err: any) => { batch(process.argv, (err: any) =>
if (err) console.error(err.stack || err); {
if (err)
{
console.error(err.stack || err);
}
}); });

View File

@ -12,11 +12,22 @@ import log = require('./log');
/** /**
* Streams the episode to disk. * Streams the episode to disk.
*/ */
export default function(config: IConfig, address: string, done: (err: Error, ign: boolean) => void) { export default function(config: IConfig, address: string, done: (err: Error, ign: boolean) => void)
scrapePage(config, address, (err, page) => { {
if (err) return done(err, false); scrapePage(config, address, (err, page) =>
scrapePlayer(config, address, page.id, (err, player) => { {
if (err) return done(err, false); if (err)
{
return done(err, false);
}
scrapePlayer(config, address, page.id, (err, player) =>
{
if (err)
{
return done(err, false);
}
download(config, page, player, done); download(config, page, player, done);
}); });
}); });
@ -25,63 +36,98 @@ export default function(config: IConfig, address: string, done: (err: Error, ign
/** /**
* Completes a download and writes the message with an elapsed time. * Completes a download and writes the message with an elapsed time.
*/ */
function complete(epName: string, message: string, begin: number, done: (err: Error, ign: boolean) => void) { function complete(epName: string, message: string, begin: number, done: (err: Error, ign: boolean) => void)
var timeInMs = Date.now() - begin; {
var seconds = prefix(Math.floor(timeInMs / 1000) % 60, 2); const timeInMs = Date.now() - begin;
var minutes = prefix(Math.floor(timeInMs / 1000 / 60) % 60, 2); const seconds = prefix(Math.floor(timeInMs / 1000) % 60, 2);
var hours = prefix(Math.floor(timeInMs / 1000 / 60 / 60), 2); const minutes = prefix(Math.floor(timeInMs / 1000 / 60) % 60, 2);
const hours = prefix(Math.floor(timeInMs / 1000 / 60 / 60), 2);
log.dispEpisode(epName, message + ' (' + hours + ':' + minutes + ':' + seconds + ')', true); log.dispEpisode(epName, message + ' (' + hours + ':' + minutes + ':' + seconds + ')', true);
done(null, false); done(null, false);
} }
/** /**
* Check if a file exist.. * Check if a file exist..
*/ */
function fileExist(path: string) { function fileExist(path: string)
{
try try
{ {
fs.statSync(path); fs.statSync(path);
return true; return true;
} } catch (e)
catch (e) { } {
return false; return false;
}
} }
/** /**
* Downloads the subtitle and video. * Downloads the subtitle and video.
*/ */
function download(config: IConfig, page: IEpisodePage, player: IEpisodePlayer, done: (err: Error, ign: boolean) => void) { function download(config: IConfig, page: IEpisodePage, player: IEpisodePlayer, done: (err: Error, ign: boolean) => void)
var series = config.series || page.series; {
series = series.replace("/","_").replace("'","_").replace(":","_"); let series = config.series || page.series;
var fileName = name(config, page, series, "").replace("/","_").replace("'","_").replace(":","_");
var filePath = path.join(config.output || process.cwd(), series, fileName); series = series.replace('/', '_').replace('\'', '_').replace(':', '_');
if (fileExist(filePath + ".mkv")) let fileName = name(config, page, series, '').replace('/', '_').replace('\'', '_').replace(':', '_');
let filePath = path.join(config.output || process.cwd(), series, fileName);
if (fileExist(filePath + '.mkv'))
{ {
var count = 0; let count = 0;
log.warn("File '"+fileName+"' already exist..."); log.warn('File \'' + fileName + '\' already exist...');
do do
{ {
count = count + 1; count = count + 1;
fileName = name(config, page, series, "-" + count).replace("/","_").replace("'","_").replace(":","_"); fileName = name(config, page, series, '-' + count).replace('/', '_').replace('\'', '_').replace(':', '_');
filePath = path.join(config.output || process.cwd(), series, fileName); filePath = path.join(config.output || process.cwd(), series, fileName);
} while(fileExist(filePath + ".mkv")) } while (fileExist(filePath + '.mkv'));
log.warn("Renaming to '"+fileName+"'...");
log.warn('Renaming to \'' + fileName + '\'...');
} }
mkdirp(path.dirname(filePath), (err: Error) => { mkdirp(path.dirname(filePath), (err: Error) =>
if (err) return done(err, false); {
downloadSubtitle(config, player, filePath, err => { if (err)
if (err) return done(err, false); {
var now = Date.now(); return done(err, false);
if (player.video.file != undefined) }
downloadSubtitle(config, player, filePath, err =>
{
if (err)
{
return done(err, false);
}
const now = Date.now();
if (player.video.file !== undefined)
{ {
log.dispEpisode(fileName, 'Fetching...', false); log.dispEpisode(fileName, 'Fetching...', false);
downloadVideo(config, page, player, filePath, err => { downloadVideo(config, page, player, filePath, err =>
if (err) return done(err, false); {
if (config.merge) return complete(fileName, 'Finished!', now, done); if (err)
var isSubtited = Boolean(player.subtitle); {
video.merge(config, isSubtited, player.video.file, filePath, player.video.mode, err => { return done(err, false);
if (err) return done(err, false); }
if (config.merge)
{
return complete(fileName, 'Finished!', now, done);
}
const isSubtited = Boolean(player.subtitle);
video.merge(config, isSubtited, player.video.file, filePath, player.video.mode, err =>
{
if (err)
{
return done(err, false);
}
complete(fileName, 'Finished!', now, done); complete(fileName, 'Finished!', now, done);
}); });
}); });
@ -98,15 +144,32 @@ function download(config: IConfig, page: IEpisodePage, player: IEpisodePlayer, d
/** /**
* Saves the subtitles to disk. * Saves the subtitles to disk.
*/ */
function downloadSubtitle(config: IConfig, player: IEpisodePlayer, filePath: string, done: (err?: Error) => void) { function downloadSubtitle(config: IConfig, player: IEpisodePlayer, filePath: string, done: (err?: Error) => void)
var enc = player.subtitle; {
if (!enc) return done(); const enc = player.subtitle;
subtitle.decode(enc.id, enc.iv, enc.data, (err, data) => {
if (err) return done(err); if (!enc)
var formats = subtitle.formats; {
var format = formats[config.format] ? config.format : 'ass'; return done();
formats[format](data, (err: Error, decodedSubtitle: string) => { }
if (err) return done(err);
subtitle.decode(enc.id, enc.iv, enc.data, (err, data) =>
{
if (err)
{
return done(err);
}
const formats = subtitle.formats;
const format = formats[config.format] ? config.format : 'ass';
formats[format](data, (err: Error, decodedSubtitle: string) =>
{
if (err)
{
return done(err);
}
fs.writeFile(filePath + '.' + format, '\ufeff' + decodedSubtitle, done); fs.writeFile(filePath + '.' + format, '\ufeff' + decodedSubtitle, done);
}); });
}); });
@ -115,66 +178,78 @@ function downloadSubtitle(config: IConfig, player: IEpisodePlayer, filePath: str
/** /**
* Streams the video to disk. * Streams the video to disk.
*/ */
function downloadVideo(config: IConfig, function downloadVideo(config: IConfig, page: IEpisodePage, player: IEpisodePlayer,
page: IEpisodePage, filePath: string, done: (err: Error) => void)
player: IEpisodePlayer, {
filePath: string, video.stream(player.video.host,player.video.file, page.swf, filePath, path.extname(player.video.file),
done: (err: Error) => void) { player.video.mode, done);
video.stream(
player.video.host,
player.video.file,
page.swf,
filePath, path.extname(player.video.file),
player.video.mode,
done);
} }
/** /**
* Names the file based on the config, page, series and tag. * Names the file based on the config, page, series and tag.
*/ */
function name(config: IConfig, page: IEpisodePage, series: string, extra: string) { function name(config: IConfig, page: IEpisodePage, series: string, extra: string)
var episodeNum = parseInt(page.episode, 10); {
var volumeNum = parseInt(page.volume, 10); const episodeNum = parseInt(page.episode, 10);
var episode = (episodeNum < 10 ? '0' : '') + page.episode; const volumeNum = parseInt(page.volume, 10);
var volume = (volumeNum < 10 ? '0' : '') + page.volume; const episode = (episodeNum < 10 ? '0' : '') + page.episode;
var tag = config.tag || 'CrunchyRoll'; const volume = (volumeNum < 10 ? '0' : '') + page.volume;
return series + ' - s' + volume + 'e' + episode +' - [' + tag + ']' + extra; const tag = config.tag || 'CrunchyRoll';
return series + ' - s' + volume + 'e' + episode + ' - [' + tag + ']' + extra;
} }
/** /**
* Prefixes a value. * Prefixes a value.
*/ */
function prefix(value: number|string, length: number) { function prefix(value: number|string, length: number)
var valueString = typeof value !== 'string' ? String(value) : value; {
while (valueString.length < length) valueString = '0' + valueString; let valueString = (typeof value !== 'string') ? String(value) : value;
while (valueString.length < length)
{
valueString = '0' + valueString;
}
return valueString; return valueString;
} }
/** /**
* Requests the page data and scrapes the id, episode, series and swf. * Requests the page data and scrapes the id, episode, series and swf.
*/ */
function scrapePage(config: IConfig, address: string, done: (err: Error, page?: IEpisodePage) => void) { function scrapePage(config: IConfig, address: string, done: (err: Error, page?: IEpisodePage) => void)
var id = parseInt((address.match(/[0-9]+$/) || ['0'])[0], 10); {
if (!id) return done(new Error('Invalid address.')); const id = parseInt((address.match(/[0-9]+$/) || ['0'])[0], 10);
request.get(config, address, (err, result) => {
if (err) return done(err); if (!id)
var $ = cheerio.load(result); {
var swf = /^([^?]+)/.exec($('link[rel=video_src]').attr('href')); return done(new Error('Invalid address.'));
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(); request.get(config, address, (err, result) =>
var data = regexp.exec(look); {
if (err)
{
return done(err);
}
const $ = cheerio.load(result);
const swf = /^([^?]+)/.exec($('link[rel=video_src]').attr('href'));
const 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]?)/;
const look = $('#showmedia_about_media').text();
const seasonTitle = $('span[itemprop="title"]').text();
const data = regexp.exec(look);
if (!swf || !data) if (!swf || !data)
{ {
log.warn('Something wrong in the page at '+address+' (data are: '+look+')'); log.warn('Something wrong in the page at ' + address + ' (data are: ' + look + ')');
log.warn('Setting Season to 0 and episode to \0\...'); log.warn('Setting Season to 0 and episode to 0...');
done(null, { done(null, {
id: id, id: id,
episode: "0", episode: '0',
series: seasonTitle, series: seasonTitle,
swf: swf[1], swf: swf[1],
volume: "0" volume: '0'
}); });
} }
done(null, { done(null, {
@ -182,7 +257,7 @@ function scrapePage(config: IConfig, address: string, done: (err: Error, page?:
episode: data[3], episode: data[3],
series: data[1], series: data[1],
swf: swf[1], swf: swf[1],
volume: data[2] || "1" volume: data[2] || '1'
}); });
}); });
} }
@ -190,26 +265,45 @@ function scrapePage(config: IConfig, address: string, done: (err: Error, page?:
/** /**
* Requests the player data and scrapes the subtitle and video data. * Requests the player data and scrapes the subtitle and video data.
*/ */
function scrapePlayer(config: IConfig, address: string, id: number, done: (err: Error, player?: IEpisodePlayer) => void) { function scrapePlayer(config: IConfig, address: string, id: number, done: (err: Error, player?: IEpisodePlayer) => void)
var url = address.match(/^(https?:\/\/[^\/]+)/); {
if (!url) return done(new Error('Invalid address.')); const url = address.match(/^(https?:\/\/[^\/]+)/);
if (!url)
{
return done(new Error('Invalid address.'));
}
request.post(config, { request.post(config, {
form: {current_page: address}, form: {current_page: address},
url: url[1] + '/xml/?req=RpcApiVideoPlayer_GetStandardConfig&media_id=' + id url: url[1] + '/xml/?req=RpcApiVideoPlayer_GetStandardConfig&media_id=' + id
}, (err, result) => { }, (err, result) =>
if (err) return done(err); {
if (err)
{
return done(err);
}
xml2js.parseString(result, { xml2js.parseString(result, {
explicitArray: false, explicitArray: false,
explicitRoot: false explicitRoot: false
}, (err: Error, player: IEpisodePlayerConfig) => { }, (err: Error, player: IEpisodePlayerConfig) =>
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"; if (err)
{
return done(err);
} }
try
{
const isSubtitled = Boolean(player['default:preload'].subtitle);
let streamMode = 'RTMP';
if (player['default:preload'].stream_info.host === '')
{
streamMode = 'HLS';
}
done(null, { done(null, {
subtitle: isSubtitled ? { subtitle: isSubtitled ? {
id: parseInt(player['default:preload'].subtitle.$.id, 10), id: parseInt(player['default:preload'].subtitle.$.id, 10),
@ -222,7 +316,8 @@ function scrapePlayer(config: IConfig, address: string, id: number, done: (err:
host: player['default:preload'].stream_info.host host: player['default:preload'].stream_info.host
} }
}); });
} catch (parseError) { } catch (parseError)
{
done(parseError); done(parseError);
} }
}); });

View File

@ -2,11 +2,13 @@
import request = require('request'); import request = require('request');
import cheerio = require('cheerio'); import cheerio = require('cheerio');
import log = require('./log'); import log = require('./log');
var cloudscraper = require('cloudscraper'); const cloudscraper = require('cloudscraper');
var isAuthenticated = false;
var isPremium = false;
var defaultHeaders: request.Headers = { let isAuthenticated = false;
let isPremium = false;
const defaultHeaders: request.Headers =
{
'User-Agent': 'Mozilla/5.0 (Windows NT 6.2; WOW64; rv:34.0) Gecko/20100101 Firefox/34.0', 'User-Agent': 'Mozilla/5.0 (Windows NT 6.2; WOW64; rv:34.0) Gecko/20100101 Firefox/34.0',
'Connection': 'keep-alive' 'Connection': 'keep-alive'
}; };
@ -14,11 +16,22 @@ var defaultHeaders: request.Headers = {
/** /**
* Performs a GET request for the resource. * Performs a GET request for the resource.
*/ */
export function get(config: IConfig, options: request.Options, done: (err: Error, result?: string) => void) { export function get(config: IConfig, options: request.Options, done: (err: Error, result?: string) => void)
authenticate(config, err => { {
if (err) return done(err); authenticate(config, err =>
cloudscraper.request(modify(options, 'GET'), (err: Error, response: any, body: any) => { {
if (err) return done(err); if (err)
{
return done(err);
}
cloudscraper.request(modify(options, 'GET'), (err: Error, response: any, body: any) =>
{
if (err)
{
return done(err);
}
done(null, typeof body === 'string' ? body : String(body)); done(null, typeof body === 'string' ? body : String(body));
}); });
}); });
@ -27,11 +40,22 @@ export function get(config: IConfig, options: request.Options, done: (err: Error
/** /**
* Performs a POST request for the resource. * Performs a POST request for the resource.
*/ */
export function post(config: IConfig, options: request.Options, done: (err: Error, result?: string) => void) { export function post(config: IConfig, options: request.Options, done: (err: Error, result?: string) => void)
authenticate(config, err => { {
if (err) return done(err); authenticate(config, err =>
cloudscraper.request(modify(options, 'POST'), (err: Error, response: any, body: any) => { {
if (err) return done(err); if (err)
{
return done(err);
}
cloudscraper.request(modify(options, 'POST'), (err: Error, response: any, body: any) =>
{
if (err)
{
return done(err);
}
done(null, typeof body === 'string' ? body : String(body)); done(null, typeof body === 'string' ? body : String(body));
}); });
}); });
@ -40,11 +64,15 @@ export function post(config: IConfig, options: request.Options, done: (err: Erro
/** /**
* Authenticates using the configured pass and user. * Authenticates using the configured pass and user.
*/ */
function authenticate(config: IConfig, done: (err: Error) => void) { function authenticate(config: IConfig, done: (err: Error) => void)
if (isAuthenticated || !config.pass || !config.user) return done(null); {
if (isAuthenticated || !config.pass || !config.user)
{
return done(null);
}
/* Bypass the login page and send a login request directly */ /* Bypass the login page and send a login request directly */
var options = let options =
{ {
headers: defaultHeaders, headers: defaultHeaders,
jar: true, jar: true,
@ -53,18 +81,20 @@ function authenticate(config: IConfig, done: (err: Error) => void) {
url: 'https://www.crunchyroll.com/login' url: 'https://www.crunchyroll.com/login'
}; };
// request(options, (err: Error, rep: string, body: string) =>
cloudscraper.request(options, (err: Error, rep: string, body: string) => cloudscraper.request(options, (err: Error, rep: string, body: string) =>
{ {
if (err) return done(err); if (err) return done(err);
var $ = cheerio.load(body); const $ = cheerio.load(body);
/* Get the token from the login page */ /* Get the token from the login page */
var token = $('input[name="login_form[_token]"]').attr('value'); const token = $('input[name="login_form[_token]"]').attr('value');
if (token === '') return done(new Error('Can`t find token!')); if (token === '')
{
return done(new Error('Can`t find token!'));
}
var options = let options =
{ {
headers: defaultHeaders, headers: defaultHeaders,
form: form:
@ -79,14 +109,18 @@ function authenticate(config: IConfig, done: (err: Error) => void) {
method: 'POST', method: 'POST',
url: 'https://www.crunchyroll.com/login' url: 'https://www.crunchyroll.com/login'
}; };
// request.post(options, (err: Error, rep: string, body: string) =>
cloudscraper.request(options, (err: Error, rep: string, body: string) => cloudscraper.request(options, (err: Error, rep: string, body: string) =>
{ {
if (err) return done(err); if (err)
{
return done(err);
}
/* The page return with a meta based redirection, as we wan't to check that everything is fine, reload /* The page return with a meta based redirection, as we wan't to check that everything is fine, reload
* the main page. A bit convoluted, but more sure. * the main page. A bit convoluted, but more sure.
*/ */
var options = let options =
{ {
headers: defaultHeaders, headers: defaultHeaders,
jar: true, jar: true,
@ -96,23 +130,44 @@ function authenticate(config: IConfig, done: (err: Error) => void) {
cloudscraper.request(options, (err: Error, rep: string, body: string) => cloudscraper.request(options, (err: Error, rep: string, body: string) =>
{ {
if (err) return done(err); if (err)
var $ = cheerio.load(body);
/* Check if auth worked */
var regexps = /ga\(\'set\', \'dimension[5-8]\', \'([^']*)\'\);/g;
var dims = regexps.exec($('script').text());
for (var i = 1; i < 5; i++)
{ {
if ((dims[i] !== undefined) && (dims[i] !== '') && (dims[i] !== 'not-registered')) { isAuthenticated = true; } return done(err);
if ((dims[i] === 'premium') || (dims[i] === 'premiumplus')) { isPremium = true; }
} }
let $ = cheerio.load(body);
/* Check if auth worked */
const regexps = /ga\('set', 'dimension[5-8]', '([^']*)'\);/g;
const dims = regexps.exec($('script').text());
for (let i = 1; i < 5; i++)
{
if ((dims[i] !== undefined) && (dims[i] !== '') && (dims[i] !== 'not-registered'))
{
isAuthenticated = true;
}
if ((dims[i] === 'premium') || (dims[i] === 'premiumplus'))
{
isPremium = true;
}
}
if (isAuthenticated === false) if (isAuthenticated === false)
{ {
var error = $('ul.message, li.error').text(); const error = $('ul.message, li.error').text();
return done(new Error('Authentication failed: ' + error)); return done(new Error('Authentication failed: ' + error));
} }
if (isPremium === false) { log.warn('Do not use this app without a premium account.'); }
else { log.info('You have a premium account! Good!'); } if (isPremium === false)
{
log.warn('Do not use this app without a premium account.');
}
else
{
log.info('You have a premium account! Good!');
}
done(null); done(null);
}); });
}); });
@ -122,12 +177,14 @@ function authenticate(config: IConfig, done: (err: Error) => void) {
/** /**
* Modifies the options to use the authenticated cookie jar. * Modifies the options to use the authenticated cookie jar.
*/ */
function modify(options: string|request.Options, reqMethod: string): request.Options { function modify(options: string|request.Options, reqMethod: string): request.Options
if (typeof options !== 'string') { {
if (typeof options !== 'string')
{
options.jar = true; options.jar = true;
options.headers = defaultHeaders; options.headers = defaultHeaders;
options.method = reqMethod; options.method = reqMethod;
return options; return options;
} }
return { jar: true, headers: defaultHeaders, url: options.toString(), method: reqMethod}; return { jar: true, headers: defaultHeaders, url: options.toString(), method: reqMethod };
} }

View File

@ -6,26 +6,42 @@ import request = require('./request');
import path = require('path'); import path = require('path');
import url = require('url'); import url = require('url');
import log = require('./log'); import log = require('./log');
var persistent = '.crpersistent'; const persistent = '.crpersistent';
/** /**
* Streams the series to disk. * Streams the series to disk.
*/ */
export default function(config: IConfig, address: string, done: (err: Error) => void) { export default function(config: IConfig, address: string, done: (err: Error) => void)
var persistentPath = path.join(config.output || process.cwd(), persistent); {
fs.readFile(persistentPath, 'utf8', (err, contents) => { const persistentPath = path.join(config.output || process.cwd(), persistent);
var cache = config.cache ? {} : JSON.parse(contents || '{}');
page(config, address, (err, page) => { fs.readFile(persistentPath, 'utf8', (err, contents) =>
if (err) return done(err); {
var i = 0; const cache = config.cache ? {} : JSON.parse(contents || '{}');
(function next() {
if (i >= page.episodes.length) return done(null); page(config, address, (err, page) =>
download(cache, config, address, page.episodes[i], (err, ignored) => { {
if (err) return done(err); if (err)
if ((ignored == false) || (ignored == undefined)) {
return done(err);
}
let i = 0;
(function next()
{
if (i >= page.episodes.length) return done(null);
download(cache, config, address, page.episodes[i], (err, ignored) =>
{
if (err)
{
return done(err);
}
if ((ignored === false) || (ignored === undefined))
{
const newCache = JSON.stringify(cache, null, ' ');
fs.writeFile(persistentPath, newCache, err =>
{ {
var newCache = JSON.stringify(cache, null, ' ');
fs.writeFile(persistentPath, newCache, err => {
if (err) return done(err); if (err) return done(err);
i += 1; i += 1;
next(); next();
@ -45,16 +61,29 @@ export default function(config: IConfig, address: string, done: (err: Error) =>
/** /**
* Downloads the episode. * Downloads the episode.
*/ */
function download(cache: {[address: string]: number}, function download(cache: {[address: string]: number}, config: IConfig,
config: IConfig, baseAddress: string, item: ISeriesEpisode,
baseAddress: string, done: (err: Error, ign: boolean) => void)
item: ISeriesEpisode, {
done: (err: Error, ign: boolean) => void) { if (!filter(config, item))
if (!filter(config, item)) return done(null, false); {
var address = url.resolve(baseAddress, item.address); return done(null, false);
if (cache[address]) return done(null, false); }
episode(config, address, (err, ignored) => {
if (err) return done(err, false); const address = url.resolve(baseAddress, item.address);
if (cache[address])
{
return done(null, false);
}
episode(config, address, (err, ignored) =>
{
if (err)
{
return done(err, false);
}
cache[address] = Date.now(); cache[address] = Date.now();
done(null, ignored); done(null, ignored);
}); });
@ -63,14 +92,17 @@ function download(cache: {[address: string]: number},
/** /**
* Filters the item based on the configuration. * Filters the item based on the configuration.
*/ */
function filter(config: IConfig, item: ISeriesEpisode) { function filter(config: IConfig, item: ISeriesEpisode)
{
// Filter on chapter. // Filter on chapter.
var episodeFilter = config.episode; const episodeFilter = config.episode;
if (episodeFilter > 0 && parseInt(item.episode, 10) <= episodeFilter) return false; if (episodeFilter > 0 && parseInt(item.episode, 10) <= 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. // Filter on volume.
var volumeFilter = config.volume; const volumeFilter = config.volume;
if (volumeFilter > 0 && item.volume <= volumeFilter) return false; if (volumeFilter > 0 && item.volume <= volumeFilter) return false;
if (volumeFilter < 0 && item.volume >= -volumeFilter) return false; if (volumeFilter < 0 && item.volume >= -volumeFilter) return false;
return true; return true;
@ -79,27 +111,50 @@ function filter(config: IConfig, item: ISeriesEpisode) {
/** /**
* Requests the page and scrapes the episodes and series. * Requests the page and scrapes the episodes and series.
*/ */
function page(config: IConfig, address: string, done: (err: Error, result?: ISeries) => void) { function page(config: IConfig, address: string, done: (err: Error, result?: ISeries) => void)
request.get(config, address, (err, result) => { {
if (err) return done(err); request.get(config, address, (err, result) =>
var $ = cheerio.load(result); {
var title = $('span[itemprop=name]').text(); if (err)
if (!title) return done(new Error('Invalid page.(' + address + ')')); {
log.info("Checking availability for " + title); return done(err);
var episodes: ISeriesEpisode[] = []; }
$('.episode').each((i, el) => {
if ($(el).children('img[src*=coming_soon]').length) return; const $ = cheerio.load(result);
var volume = /([0-9]+)\s*$/.exec($(el).closest('ul').prev('a').text()); const title = $('span[itemprop=name]').text();
var regexp = /Episode\s+((PV )?[S0-9][P0-9.]*[a-fA-F]?)\s*$/i;
var episode = regexp.exec($(el).children('.series-title').text()); if (!title)
var address = $(el).attr('href'); {
if (!address || !episode) return; return done(new Error('Invalid page.(' + address + ')'));
}
log.info('Checking availability for ' + title);
const episodes: ISeriesEpisode[] = [];
$('.episode').each((i, el) =>
{
if ($(el).children('img[src*=coming_soon]').length)
{
return;
}
const volume = /([0-9]+)\s*$/.exec($(el).closest('ul').prev('a').text());
const regexp = /Episode\s+((PV )?[S0-9][P0-9.]*[a-fA-F]?)\s*$/i;
const episode = regexp.exec($(el).children('.series-title').text());
const address = $(el).attr('href');
if (!address || !episode)
{
return;
}
episodes.push({ episodes.push({
address: address, address: address,
episode: episode[1], episode: episode[1],
volume: volume ? parseInt(volume[0], 10) : 1 volume: volume ? parseInt(volume[0], 10) : 1
}); });
}); });
done(null, {episodes: episodes.reverse(), series: title}); done(null, {episodes: episodes.reverse(), series: title});
}); });
} }

View File

@ -7,10 +7,14 @@ import zlib = require('zlib');
/** /**
* Decodes the data. * Decodes the data.
*/ */
export default function(id: number, iv: Buffer|string, data: Buffer|string, done: (err?: Error, result?: Buffer) => void) { export default function(id: number, iv: Buffer|string, data: Buffer|string,
try { done: (err?: Error, result?: Buffer) => void)
{
try
{
decompress(decrypt(id, iv, data), done); decompress(decrypt(id, iv, data), done);
} catch (e) { } catch (e)
{
done(e); done(e);
} }
} }
@ -18,21 +22,27 @@ import zlib = require('zlib');
/** /**
* Decrypts the data. * Decrypts the data.
*/ */
function decrypt(id: number, iv: Buffer|string, data: Buffer|string) { function decrypt(id: number, iv: Buffer|string, data: Buffer|string)
var ivBuffer = typeof iv === 'string' ? new Buffer(iv, 'base64') : iv; {
var dataBuffer = typeof data === 'string' ? new Buffer(data, 'base64') : data; const ivBuffer = typeof iv === 'string' ? new Buffer(iv, 'base64') : iv;
var decipher = crypto.createDecipheriv('aes-256-cbc', key(id), ivBuffer); const dataBuffer = typeof data === 'string' ? new Buffer(data, 'base64') : data;
const decipher = crypto.createDecipheriv('aes-256-cbc', key(id), ivBuffer);
decipher.setAutoPadding(false); decipher.setAutoPadding(false);
return Buffer.concat([decipher.update(dataBuffer), decipher.final()]); return Buffer.concat([decipher.update(dataBuffer), decipher.final()]);
} }
/** /**
* Decompresses the data. * Decompresses the data.
*/ */
function decompress(data: Buffer, done: (err: Error, result?: Buffer) => void) { function decompress(data: Buffer, done: (err: Error, result?: Buffer) => void)
try { {
try
{
zlib.inflate(data, done); zlib.inflate(data, done);
} catch (e) { } catch (e)
{
done(null, data); done(null, data);
} }
} }
@ -40,36 +50,45 @@ function decompress(data: Buffer, done: (err: Error, result?: Buffer) => void) {
/** /**
* Generates a key. * Generates a key.
*/ */
function key(subtitleId: number): Buffer { function key(subtitleId: number): Buffer
var hash = secret(20, 97, 1, 2) + magic(subtitleId); {
var result = new Buffer(32); const hash = secret(20, 97, 1, 2) + magic(subtitleId);
const result = new Buffer(32);
result.fill(0); result.fill(0);
crypto.createHash('sha1').update(hash).digest().copy(result); crypto.createHash('sha1').update(hash).digest().copy(result);
return result; return result;
} }
/** /**
* Generates a magic number. * Generates a magic number.
*/ */
function magic(subtitleId: number): number { function magic(subtitleId: number): number
var base = Math.floor(Math.sqrt(6.9) * Math.pow(2, 25)); {
var hash = bigInt(base).xor(subtitleId).toJSNumber(); const base = Math.floor(Math.sqrt(6.9) * Math.pow(2, 25));
var multipliedHash = bigInt(hash).multiply(32).toJSNumber(); const hash = bigInt(base).xor(subtitleId).toJSNumber();
const multipliedHash = bigInt(hash).multiply(32).toJSNumber();
return bigInt(hash).xor(hash >> 3).xor(multipliedHash).toJSNumber(); return bigInt(hash).xor(hash >> 3).xor(multipliedHash).toJSNumber();
} }
/** /**
* Generates a secret string based on a Fibonacci sequence. * Generates a secret string based on a Fibonacci sequence.
*/ */
function secret(size: number, modulo: number, firstSeed: number, secondSeed: number): string { function secret(size: number, modulo: number, firstSeed: number, secondSeed: number): string
var currentValue = firstSeed + secondSeed; {
var previousValue = secondSeed; let currentValue = firstSeed + secondSeed;
var result = ''; let previousValue = secondSeed;
for (var i = 0; i < size; i += 1) { let result = '';
var oldValue = currentValue;
for (let i = 0; i < size; i += 1)
{
const oldValue = currentValue;
result += String.fromCharCode(currentValue % modulo + 33); result += String.fromCharCode(currentValue % modulo + 33);
currentValue += previousValue; currentValue += previousValue;
previousValue = oldValue; previousValue = oldValue;
} }
return result; return result;
} }

View File

@ -4,17 +4,25 @@ import xml2js = require('xml2js');
/** /**
* Converts an input buffer to a SubStation Alpha subtitle. * Converts an input buffer to a SubStation Alpha subtitle.
*/ */
export default function(input: string|Buffer, done: (err: Error, subtitle?: string) => void) { export default function(input: string|Buffer, done: (err: Error, subtitle?: string) => void)
{
xml2js.parseString(input.toString(), { xml2js.parseString(input.toString(), {
explicitArray: false, explicitArray: false,
explicitRoot: false explicitRoot: false
}, (err: Error, xml: ISubtitle) => { }, (err: Error, xml: ISubtitle) =>
if (err) return done(err); {
try { if (err)
{
return done(err);
}
try
{
done(null, script(xml) + '\n' + done(null, script(xml) + '\n' +
style(xml.styles) + '\n' + style(xml.styles) + '\n' +
event(xml.events)); event(xml.events));
} catch (err) { } catch (err)
{
done(err); done(err);
} }
}); });
@ -23,11 +31,12 @@ export default function(input: string|Buffer, done: (err: Error, subtitle?: stri
/** /**
* Converts the event block. * Converts the event block.
*/ */
function event(block: ISubtitleEvent): string { function event(block: ISubtitleEvent): string
{
var format = 'Layer,Start,End,Style,Name,MarginL,MarginR,MarginV,Effect,Text'; var format = 'Layer,Start,End,Style,Name,MarginL,MarginR,MarginV,Effect,Text';
return '[Events]\n' + return '[Events]\n' +
'Format: ' + format + '\n' + 'Format: ' + format + '\n' + [].concat(block.event).map(style => ('Dialogue: 0,' +
[].concat(block.event).map(style => ('Dialogue: 0,' +
style.$.start + ',' + style.$.start + ',' +
style.$.end + ',' + style.$.end + ',' +
style.$.style + ',' + style.$.style + ',' +
@ -42,7 +51,9 @@ function event(block: ISubtitleEvent): string {
/** /**
* Converts the script block. * Converts the script block.
*/ */
function script(block: ISubtitle): string { function script(block: ISubtitle): string
{
return '[Script Info]\n' + return '[Script Info]\n' +
'Title: ' + block.$.title + '\n' + 'Title: ' + block.$.title + '\n' +
'ScriptType: v4.00+\n' + 'ScriptType: v4.00+\n' +
@ -57,14 +68,15 @@ function script(block: ISubtitle): string {
/** /**
* Converts the style block. * Converts the style block.
*/ */
function style(block: ISubtitleStyle): string { function style(block: ISubtitleStyle): string
{
var format = 'Name,Fontname,Fontsize,PrimaryColour,SecondaryColour,' + var format = 'Name,Fontname,Fontsize,PrimaryColour,SecondaryColour,' +
'OutlineColour,BackColour,Bold,Italic,Underline,StrikeOut,ScaleX,' + 'OutlineColour,BackColour,Bold,Italic,Underline,StrikeOut,ScaleX,' +
'ScaleY,Spacing,Angle,BorderStyle,Outline,Shadow,Alignment,' + 'ScaleY,Spacing,Angle,BorderStyle,Outline,Shadow,Alignment,' +
'MarginL,MarginR,MarginV,Encoding'; 'MarginL,MarginR,MarginV,Encoding';
return '[V4+ Styles]\n' + return '[V4+ Styles]\n' +
'Format: ' + format + '\n' + 'Format: ' + format + '\n' + [].concat(block.style).map(style => 'Style: ' +
[].concat(block.style).map(style => 'Style: ' +
style.$.name + ',' + style.$.name + ',' +
style.$.font_name + ',' + style.$.font_name + ',' +
style.$.font_size + ',' + style.$.font_size + ',' +

View File

@ -4,18 +4,30 @@ import xml2js = require('xml2js');
/** /**
* Converts an input buffer to a SubRip subtitle. * Converts an input buffer to a SubRip subtitle.
*/ */
export default function(input: Buffer|string, done: (err: Error, subtitle?: string) => void) { export default function(input: Buffer|string, done: (err: Error, subtitle?: string) => void)
var options = {explicitArray: false, explicitRoot: false}; {
xml2js.parseString(input.toString(), options, (err: Error, xml: ISubtitle) => { const options = {explicitArray: false, explicitRoot: false};
try {
if (err) return done(err); xml2js.parseString(input.toString(), options, (err: Error, xml: ISubtitle) =>
done(null, xml.events.event.map((event, index) => { {
var attributes = event.$; try
{
if (err)
{
return done(err);
}
done(null, xml.events.event.map((event, index) =>
{
const attributes = event.$;
return (index + 1) + '\n' + return (index + 1) + '\n' +
time(attributes.start) + ' --> ' + time(attributes.end) + '\n' + time(attributes.start) + ' --> ' + time(attributes.end) + '\n' +
text(attributes.text) + '\n'; text(attributes.text) + '\n';
}).join('\n')); }).join('\n'));
} catch (err) {
} catch (err)
{
done(err); done(err);
} }
}); });
@ -24,23 +36,34 @@ import xml2js = require('xml2js');
/** /**
* Prefixes a value. * Prefixes a value.
*/ */
function prefix(value: string, length: number): string { function prefix(value: string, length: number): string
while (value.length < length) value = '0' + value; {
while (value.length < length)
{
value = '0' + value;
}
return value; return value;
} }
/** /**
* Suffixes a value. * Suffixes a value.
*/ */
function suffix(value: string, length: number): string { function suffix(value: string, length: number): string
while (value.length < length) value = value + '0'; {
while (value.length < length)
{
value = value + '0';
}
return value; return value;
} }
/** /**
* Formats a text value. * Formats a text value.
*/ */
function text(value: string): string { function text(value: string): string
{
return value return value
.replace(/{\\i1}/g, '<i>').replace(/{\\i0}/g, '</i>') .replace(/{\\i1}/g, '<i>').replace(/{\\i0}/g, '</i>')
.replace(/{\\b1}/g, '<b>').replace(/{\\b0}/g, '</b>') .replace(/{\\b1}/g, '<b>').replace(/{\\b0}/g, '</b>')
@ -53,12 +76,19 @@ function text(value: string): string {
/** /**
* Formats a time stamp. * Formats a time stamp.
*/ */
function time(value: string): string { function time(value: string): string
var all = value.match(/^([0-9]+):([0-9]+):([0-9]+)\.([0-9]+)$/); {
if (!all) throw new Error('Invalid time.'); const all = value.match(/^([0-9]+):([0-9]+):([0-9]+)\.([0-9]+)$/);
var hours = prefix(all[1], 2);
var minutes = prefix(all[2], 2); if (!all)
var seconds = prefix(all[3], 2); {
var milliseconds = suffix(all[4], 3); throw new Error('Invalid time.');
}
const hours = prefix(all[1], 2);
const minutes = prefix(all[2], 2);
const seconds = prefix(all[3], 2);
const milliseconds = suffix(all[4], 3);
return hours + ':' + minutes + ':' + seconds + ',' + milliseconds; return hours + ':' + minutes + ':' + seconds + ',' + milliseconds;
} }

View File

@ -8,26 +8,40 @@ import subtitle from '../subtitle/index';
/** /**
* Merges the subtitle and video files into a Matroska Multimedia Container. * Merges the subtitle and video files into a Matroska Multimedia Container.
*/ */
export default function(config: IConfig, isSubtitled: boolean, rtmpInputPath: string, filePath: string, streamMode: string, done: (err: Error) => void) { export default function(config: IConfig, isSubtitled: boolean, rtmpInputPath: string, filePath: string,
var subtitlePath = filePath + '.' + (subtitle.formats[config.format] ? config.format : 'ass'); streamMode: string, done: (err: Error) => void)
var videoPath = filePath; {
if (streamMode == "RTMP") const subtitlePath = filePath + '.' + (subtitle.formats[config.format] ? config.format : 'ass');
let videoPath = filePath;
if (streamMode === 'RTMP')
{ {
videoPath += path.extname(rtmpInputPath); videoPath += path.extname(rtmpInputPath);
} }
else else
{ {
videoPath += ".mp4"; videoPath += '.mp4';
} }
childProcess.exec(command() + ' ' + childProcess.exec(command() + ' ' +
'-o "' + filePath + '.mkv" ' + '-o "' + filePath + '.mkv" ' +
'"' + videoPath + '" ' + '"' + videoPath + '" ' +
(isSubtitled ? '"' + subtitlePath + '"' : ''), { (isSubtitled ? '"' + subtitlePath + '"' : ''), {
maxBuffer: Infinity maxBuffer: Infinity,
}, err => { }, (err) =>
if (err) return done(err); {
unlink(videoPath, subtitlePath, err => { if (err)
if (err) unlinkTimeout(videoPath, subtitlePath, 5000); {
return done(err);
}
unlink(videoPath, subtitlePath, (errin) =>
{
if (errin)
{
unlinkTimeout(videoPath, subtitlePath, 5000);
}
done(null); done(null);
}); });
}); });
@ -36,8 +50,13 @@ import subtitle from '../subtitle/index';
/** /**
* Determines the command for the operating system. * Determines the command for the operating system.
*/ */
function command(): string { function command(): string
if (os.platform() !== 'win32') return 'mkvmerge'; {
if (os.platform() !== 'win32')
{
return 'mkvmerge';
}
return '"' + path.join(__dirname, '../../bin/mkvmerge.exe') + '"'; return '"' + path.join(__dirname, '../../bin/mkvmerge.exe') + '"';
} }
@ -45,9 +64,15 @@ function command(): string {
* Unlinks the video and subtitle. * Unlinks the video and subtitle.
* @private * @private
*/ */
function unlink(videoPath: string, subtitlePath: string, done: (err: Error) => void) { function unlink(videoPath: string, subtitlePath: string, done: (err: Error) => void)
fs.unlink(videoPath, err => { {
if (err) return done(err); fs.unlink(videoPath, (err) =>
{
if (err)
{
return done(err);
}
fs.unlink(subtitlePath, done); fs.unlink(subtitlePath, done);
}); });
} }
@ -55,10 +80,16 @@ function unlink(videoPath: string, subtitlePath: string, done: (err: Error) => v
/** /**
* Attempts to unlink the video and subtitle with a timeout between each try. * Attempts to unlink the video and subtitle with a timeout between each try.
*/ */
function unlinkTimeout(videoPath: string, subtitlePath: string, timeout: number) { function unlinkTimeout(videoPath: string, subtitlePath: string, timeout: number)
setTimeout(() => { {
unlink(videoPath, subtitlePath, err => { setTimeout(() =>
if (err) unlinkTimeout(videoPath, subtitlePath, timeout); {
unlink(videoPath, subtitlePath, (err) =>
{
if (err)
{
unlinkTimeout(videoPath, subtitlePath, timeout);
}
}); });
}, timeout); }, timeout);
} }

View File

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

View File

@ -41,13 +41,6 @@
"src/video/index.ts", "src/video/index.ts",
"src/video/merge.ts", "src/video/merge.ts",
"src/video/stream.ts", "src/video/stream.ts",
"typings/big-integer/big-integer.d.ts", "typings/index.d.ts"
"typings/cheerio/cheerio.d.ts",
"typings/commander/commander.d.ts",
"typings/form-data/form-data.d.ts",
"typings/mkdirp/mkdirp.d.ts",
"typings/node/node.d.ts",
"typings/request/request.d.ts",
"typings/xml2js/xml2js.d.ts"
] ]
} }

View File

@ -1,4 +1,5 @@
{ {
"extends": "tslint:latest",
"rules": { "rules": {
"ban": false, "ban": false,
"class-name": true, "class-name": true,
@ -12,13 +13,13 @@
"interface-name": true, "interface-name": true,
"jsdoc-format": true, "jsdoc-format": true,
"label-position": true, "label-position": true,
"label-undefined": true,
"max-line-length": [true, 140], "max-line-length": [true, 140],
"member-ordering": [true, "member-ordering": [true,
"public-before-private", "public-before-private",
"static-before-instance", "static-before-instance",
"variables-before-functions" "variables-before-functions"
], ],
"array-type": [true, "array"],
"no-any": false, "no-any": false,
"no-arg": true, "no-arg": true,
"no-bitwise": true, "no-bitwise": true,
@ -30,19 +31,14 @@
"trace" "trace"
], ],
"no-construct": true, "no-construct": true,
"no-constructor-vars": true,
"no-debugger": true, "no-debugger": true,
"no-duplicate-key": true,
"no-duplicate-variable": true, "no-duplicate-variable": true,
"no-empty": true, "no-empty": true,
"no-eval": true, "no-eval": true,
"no-string-literal": true, "no-string-literal": true,
"no-switch-case-fall-through": true, "no-switch-case-fall-through": true,
"no-trailing-comma": true,
"no-trailing-whitespace": true, "no-trailing-whitespace": true,
"no-unused-expression": true, "no-unused-expression": true,
"no-unused-variable": true,
"no-unreachable": true,
"no-use-before-declare": false, "no-use-before-declare": false,
"no-var-requires": true, "no-var-requires": true,
"one-line": [true, "one-line": [true,
@ -64,7 +60,6 @@
"property-declaration": "nospace", "property-declaration": "nospace",
"variable-declaration": "nospace" "variable-declaration": "nospace"
}], }],
"use-strict": false,
"variable-name": false, "variable-name": false,
"whitespace": [true, "whitespace": [true,
"check-branch", "check-branch",