Compare commits
30 Commits
| Author | SHA1 | Date | |
|---|---|---|---|
|
|
978a3282a4 | ||
|
|
9f0195bebc | ||
|
|
ea20108222 | ||
|
|
4ee814864c | ||
|
|
4cbfd691c3 | ||
|
|
7c04fb7282 | ||
|
|
849c7612aa | ||
|
|
6ad4cbed0a | ||
|
|
9e2f5401d0 | ||
|
|
b064b97f2d | ||
|
|
b248405437 | ||
|
|
bf941819a8 | ||
|
|
fcae53baae | ||
|
|
05ead50c0d | ||
|
|
0a80f80f91 | ||
|
|
3bf5fea735 | ||
|
|
3a95994cc2 | ||
|
|
a29870691b | ||
|
|
547fdc4aa0 | ||
|
|
c78552795f | ||
|
|
090c7e4789 | ||
|
|
bf8e1fe80f | ||
|
|
7344ce3d61 | ||
|
|
c642e76cce | ||
|
|
8ef27066f6 | ||
|
|
621df26b58 | ||
|
|
8060b1b73b | ||
|
|
11f6b3feff | ||
|
|
537639f2a8 | ||
|
|
813f8a997d |
@@ -1,11 +1,10 @@
|
||||
language: node_js
|
||||
sudo: false
|
||||
node_js:
|
||||
- 5
|
||||
- 6
|
||||
- 8
|
||||
- 9
|
||||
before_install:
|
||||
- npm install --only=dev
|
||||
script:
|
||||
- npm run types
|
||||
- npm run compile
|
||||
- npm run build
|
||||
- npm test
|
||||
|
||||
17
README.md
17
README.md
@@ -1,6 +1,6 @@
|
||||
# Crunchy: a fork of Deathspike/CrunchyRoll.js
|
||||
|
||||
[](http://issuestats.com/github/Godzil/Crunchy) [](https://travis-ci.org/Godzil/Crunchy)
|
||||
[](http://issuestats.com/github/Godzil/Crunchy) [](https://travis-ci.org/Godzil/Crunchy) [](https://codeclimate.com/github/Godzil/Crunchy/maintainability)
|
||||
|
||||
*Crunchy* is capable of downloading *anime* episodes from the popular *CrunchyRoll* streaming service. An episode is stored in the original video format (often H.264 in a MP4 container) and the configured subtitle format (ASS or SRT).The two output files are then merged into a single MKV file.
|
||||
|
||||
@@ -21,30 +21,39 @@ It is recommended to enable authentication (`-p` and `-u`) so your account permi
|
||||
|
||||
## Prerequisites
|
||||
|
||||
* NodeJS >= 5.x (http://nodejs.org/)
|
||||
* NPM >= 2.5.x (https://www.npmjs.org/)
|
||||
* NodeJS >= 8.1 (http://nodejs.org/)
|
||||
* NPM >= 5.8 (https://www.npmjs.org/)
|
||||
|
||||
## Installation
|
||||
|
||||
Use the applicable instructions to install. Is your operating system not listed? Please ask or contribute!
|
||||
|
||||
### Debian (Mint, Ubuntu, etc)
|
||||
### Linux (Debian, Mint, Ubuntu, etc)
|
||||
|
||||
1. Run in *Terminal*: `sudo apt-get install nodejs npm mkvtoolnix rtmpdump ffmpeg`
|
||||
2. Run in *Terminal*: `sudo ln -s /usr/bin/nodejs /usr/bin/node`
|
||||
3. Run in *Terminal*: `sudo npm install -g crunchy`
|
||||
|
||||
#### Updating:
|
||||
1. Run in *Terminal*: `sudo npm update -g crunchy`
|
||||
|
||||
### Mac OS X
|
||||
|
||||
1. Install *Homebrew* following the instructions at http://brew.sh/
|
||||
2. Run in *Terminal*: `brew install node mkvtoolnix rtmpdump ffmpeg`
|
||||
3. Run in *Terminal*: `npm install -g crunchy`
|
||||
|
||||
#### Updating:
|
||||
1. Run in *Terminal*: `sudo npm update -g crunchy`
|
||||
|
||||
### Windows
|
||||
|
||||
1. Install *NodeJS* following the instructions at http://nodejs.org/
|
||||
3. Run in *Command Prompt*: `npm install -g crunchy`
|
||||
|
||||
#### Updating:
|
||||
1. Run in *Command Prompt*: `npm update -g crunchy`
|
||||
|
||||
## Instructions
|
||||
|
||||
Use the applicable instructions for the interface of your choice (currently limited to command-line).
|
||||
|
||||
10
bin/crunchy.sh
Executable file
10
bin/crunchy.sh
Executable file
@@ -0,0 +1,10 @@
|
||||
#!/bin/bash
|
||||
PARAMS=$*
|
||||
for i in {1..20}; do
|
||||
crunchy ${PARAMS}
|
||||
if [ $? == 0 ]; then
|
||||
break
|
||||
fi
|
||||
echo "Going to retry..."
|
||||
sleep 3
|
||||
done
|
||||
1938
package-lock.json
generated
1938
package-lock.json
generated
File diff suppressed because it is too large
Load Diff
35
package.json
35
package.json
@@ -15,31 +15,40 @@
|
||||
"engines": {
|
||||
"node": ">=5.0"
|
||||
},
|
||||
"version": "1.1.20",
|
||||
"version": "1.3.0",
|
||||
"bin": {
|
||||
"crunchy": "./bin/crunchy"
|
||||
"crunchy": "./bin/crunchy",
|
||||
"crunchy.sh": "./bin/crunchy.sh"
|
||||
},
|
||||
"dependencies": {
|
||||
"big-integer": "^1.4.4",
|
||||
"big-integer": "^1.6.30",
|
||||
"bluebird": "^3.5.1",
|
||||
"cheerio": "^0.22.0",
|
||||
"cloudscraper": "^1.4.1",
|
||||
"commander": "^2.6.0",
|
||||
"fs-extra": "^2.0.0",
|
||||
"cloudscraper": "^1.5.0",
|
||||
"commander": "^2.15.1",
|
||||
"fs-extra": "^6.0.1",
|
||||
"mkdirp": "^0.5.0",
|
||||
"request": "^2.74.0",
|
||||
"request": "^2.87.0",
|
||||
"request-promise": "^4.2.2",
|
||||
"xml2js": "^0.4.5"
|
||||
},
|
||||
"devDependencies": {
|
||||
"@types/bluebird": "^3.5.20",
|
||||
"@types/cheerio": "^0.22.7",
|
||||
"@types/fs-extra": "^5.0.2",
|
||||
"@types/mkdirp": "^0.5.2",
|
||||
"@types/request": "^2.47.0",
|
||||
"@types/request-promise": "^4.1.41",
|
||||
"@types/xml2js": "^0.4.2",
|
||||
"tsconfig-lint": "^0.12.0",
|
||||
"tslint": "^4.4.2",
|
||||
"typescript": "^2.2.0",
|
||||
"typings": "^2.1.0"
|
||||
"tslint": "^5.10.0",
|
||||
"typescript": "^2.9.1"
|
||||
},
|
||||
"scripts": {
|
||||
"prepublish": "npm run types && tsc",
|
||||
"prepublishOnly": "npm run build",
|
||||
"compile": "tsc",
|
||||
"test": "tslint -c ./tslint.json --project ./tsconfig.json ./src/**/*.ts",
|
||||
"types": "typings install",
|
||||
"build": "tsc",
|
||||
"test": "tslint --project .",
|
||||
"start": "node ./bin/crunchy"
|
||||
},
|
||||
"bugs": {
|
||||
|
||||
48
src/batch.ts
48
src/batch.ts
@@ -2,33 +2,65 @@
|
||||
import commander = require('commander');
|
||||
import fs = require('fs');
|
||||
import path = require('path');
|
||||
import log = require('./log');
|
||||
import series from './series';
|
||||
|
||||
/* correspondances between resolution and value CR excpect */
|
||||
const resol_table: { [id: string]: IResolData; } =
|
||||
{
|
||||
360: {quality: '60', format: '106'},
|
||||
480: {quality: '61', format: '106'},
|
||||
720: {quality: '62', format: '106'},
|
||||
1080: {quality: '80', format: '108'},
|
||||
};
|
||||
|
||||
/**
|
||||
* Streams the batch of series to disk.
|
||||
*/
|
||||
export default function(args: string[], done: (err?: Error) => void)
|
||||
{
|
||||
const config = parse(args);
|
||||
const batchPath = path.join(config.output || process.cwd(), 'CrunchyRoll.txt');
|
||||
const batchPath = path.join(config.output || process.cwd(), config.batch);
|
||||
|
||||
tasks(config, batchPath, (err, tasks) =>
|
||||
// set resolution
|
||||
if (config.resolution)
|
||||
{
|
||||
try
|
||||
{
|
||||
config.video_format = resol_table[config.resolution].format;
|
||||
config.video_quality = resol_table[config.resolution].quality;
|
||||
}
|
||||
catch (e)
|
||||
{
|
||||
log.warn('Invalid resolution ' + config.resolution + 'p. Setting to 1080p');
|
||||
config.video_format = resol_table['1080'].format;
|
||||
config.video_quality = resol_table['1080'].quality;
|
||||
}
|
||||
}
|
||||
else
|
||||
{
|
||||
/* 1080 by default */
|
||||
config.video_format = resol_table['1080'].format;
|
||||
config.video_quality = resol_table['1080'].quality;
|
||||
}
|
||||
|
||||
tasks(config, batchPath, (err, tasksArr) =>
|
||||
{
|
||||
if (err)
|
||||
{
|
||||
return done(err);
|
||||
return done(err);
|
||||
}
|
||||
|
||||
let i = 0;
|
||||
|
||||
(function next()
|
||||
{
|
||||
if (i >= tasks.length)
|
||||
if (i >= tasksArr.length)
|
||||
{
|
||||
return done();
|
||||
}
|
||||
|
||||
series(tasks[i].config, tasks[i].address, (errin) =>
|
||||
series(tasksArr[i].config, tasksArr[i].address, (errin) =>
|
||||
{
|
||||
if (errin)
|
||||
{
|
||||
@@ -150,6 +182,10 @@ function parse(args: string[]): IConfigLine
|
||||
.option('-o, --output <s>', 'The output path.')
|
||||
.option('-s, --series <s>', 'The series override.')
|
||||
.option('-n, --filename <s>', 'The name override.')
|
||||
.option('-t, --tag <s>', 'The subgroup. (Default: CrunchyRoll)')
|
||||
.option('-t, --tag <s>', 'The subgroup. (Default: CrunchyRoll)', 'CrunchyRoll')
|
||||
.option('-r, --resolution <s>', 'The video resolution. (Default: 1080 (360, 480, 720, 1080))',
|
||||
'1080')
|
||||
.option('-g, --rebuildcrp', 'Rebuild the crpersistant file.')
|
||||
.option('-b, --batch <s>', 'Batch file', 'CrunchyRoll.txt')
|
||||
.parse(args);
|
||||
}
|
||||
|
||||
@@ -6,5 +6,8 @@ batch(process.argv, (err: any) =>
|
||||
if (err)
|
||||
{
|
||||
console.error(err.stack || err);
|
||||
process.exit(-1);
|
||||
}
|
||||
console.info('Done!');
|
||||
process.exit(0);
|
||||
});
|
||||
|
||||
@@ -57,7 +57,8 @@ function fileExist(path: string)
|
||||
{
|
||||
fs.statSync(path);
|
||||
return true;
|
||||
} catch (e)
|
||||
}
|
||||
catch (e)
|
||||
{
|
||||
return false;
|
||||
}
|
||||
@@ -82,6 +83,13 @@ function download(config: IConfig, page: IEpisodePage, player: IEpisodePlayer, d
|
||||
if (fileExist(filePath + '.mkv'))
|
||||
{
|
||||
let count = 0;
|
||||
|
||||
if (config.rebuildcrp)
|
||||
{
|
||||
log.warn('Adding \'' + fileName + '\' to the DB...');
|
||||
return done(null, false);
|
||||
}
|
||||
|
||||
log.warn('File \'' + fileName + '\' already exist...');
|
||||
|
||||
do
|
||||
@@ -94,6 +102,11 @@ function download(config: IConfig, page: IEpisodePage, player: IEpisodePlayer, d
|
||||
log.warn('Renaming to \'' + fileName + '\'...');
|
||||
}
|
||||
|
||||
if (config.rebuildcrp)
|
||||
{
|
||||
return done(null, true);
|
||||
}
|
||||
|
||||
mkdirp(path.dirname(filePath), (errM: Error) =>
|
||||
{
|
||||
if (errM)
|
||||
@@ -258,7 +271,7 @@ function scrapePage(config: IConfig, address: string, done: (err: Error, page?:
|
||||
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();
|
||||
let episodeTitle = $('#showmedia_about_name').text().replace(/[“”]/g, '');
|
||||
const episodeTitle = $('#showmedia_about_name').text().replace(/[“”]/g, '');
|
||||
const data = regexp.exec(look);
|
||||
|
||||
if (!swf || !data)
|
||||
@@ -303,7 +316,12 @@ function scrapePlayer(config: IConfig, address: string, id: number, done: (err:
|
||||
}
|
||||
|
||||
my_request.post(config, {
|
||||
form: {current_page: address},
|
||||
form: {
|
||||
current_page: address,
|
||||
video_format: config.video_format,
|
||||
video_quality: config.video_quality,
|
||||
media_id: id
|
||||
},
|
||||
url: url[1] + '/xml/?req=RpcApiVideoPlayer_GetStandardConfig&media_id=' + id,
|
||||
}, (err, result) =>
|
||||
{
|
||||
@@ -328,9 +346,9 @@ function scrapePlayer(config: IConfig, address: string, id: number, done: (err:
|
||||
let streamMode = 'RTMP';
|
||||
|
||||
if (player['default:preload'].stream_info.host === '')
|
||||
{
|
||||
streamMode = 'HLS';
|
||||
}
|
||||
{
|
||||
streamMode = 'HLS';
|
||||
}
|
||||
|
||||
done(null, {
|
||||
subtitle: isSubtitled ? {
|
||||
|
||||
5
src/interface/IConfig.d.ts
vendored
5
src/interface/IConfig.d.ts
vendored
@@ -14,4 +14,9 @@ interface IConfig {
|
||||
series?: string;
|
||||
filename?: string;
|
||||
tag?: string;
|
||||
resolution?: string;
|
||||
video_format?: string;
|
||||
video_quality?: string;
|
||||
rebuildcrp?: boolean;
|
||||
batch?: string;
|
||||
}
|
||||
|
||||
4
src/interface/IResolData.d.ts
vendored
Normal file
4
src/interface/IResolData.d.ts
vendored
Normal file
@@ -0,0 +1,4 @@
|
||||
interface IResolData {
|
||||
quality: string;
|
||||
format: string;
|
||||
}
|
||||
@@ -1,7 +1,13 @@
|
||||
'use strict';
|
||||
import request = require('request');
|
||||
import cheerio = require('cheerio');
|
||||
import request = require('request');
|
||||
import rp = require('request-promise');
|
||||
import Promise = require('bluebird');
|
||||
import log = require('./log');
|
||||
import { RequestPromise } from 'request-promise';
|
||||
import { Response } from 'request';
|
||||
|
||||
// tslint:disable-next-line:no-var-requires
|
||||
const cloudscraper = require('cloudscraper');
|
||||
|
||||
let isAuthenticated = false;
|
||||
@@ -9,29 +15,85 @@ 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',
|
||||
'Connection': 'keep-alive'
|
||||
'User-Agent': 'Mozilla/5.0 (Windows NT 6.2; WOW64; x64; rv:58.0) Gecko/20100101 Firefox/58.0',
|
||||
'Connection': 'keep-alive',
|
||||
'Referer': 'https://www.crunchyroll.com/login',
|
||||
};
|
||||
|
||||
function generateDeviceId(): string
|
||||
{
|
||||
let id = '';
|
||||
const possible = 'ABCDEFGHIJKLMNOPQRSTUVWXYZabcdefghijklmnopqrstuvwxyz0123456789';
|
||||
|
||||
for (let i = 0; i < 32; i++)
|
||||
{
|
||||
id += possible.charAt(Math.floor(Math.random() * possible.length));
|
||||
}
|
||||
|
||||
return id;
|
||||
}
|
||||
|
||||
function startSession(): Promise<string>
|
||||
{
|
||||
return rp(
|
||||
{
|
||||
method: 'GET',
|
||||
url: 'CR_SESSION_URL',
|
||||
qs:
|
||||
{
|
||||
device_id: generateDeviceId(),
|
||||
device_type: 'CR_DEVICE_TYPE',
|
||||
access_token: 'CR_SESSION_KEY',
|
||||
version: 'CR_API_VERSION',
|
||||
locale: 'CR_LOCALE',
|
||||
},
|
||||
json: true,
|
||||
})
|
||||
.then((response: any) =>
|
||||
{
|
||||
return response.data.session_id;
|
||||
});
|
||||
}
|
||||
|
||||
function login(sessionId: string, user: string, pass: string): Promise<any>
|
||||
{
|
||||
return rp(
|
||||
{
|
||||
method: 'POST',
|
||||
url: 'CR_LOGIN_URL',
|
||||
form:
|
||||
{
|
||||
account: user,
|
||||
password: pass,
|
||||
session_id: sessionId,
|
||||
version: 'CR_API_VERSION',
|
||||
},
|
||||
json: true,
|
||||
})
|
||||
.then((response) =>
|
||||
{
|
||||
if (response.error) throw new Error('Login failed: ' + response.message);
|
||||
return response.data;
|
||||
});
|
||||
}
|
||||
|
||||
// TODO: logout
|
||||
|
||||
/**
|
||||
* Performs a GET request for the resource.
|
||||
*/
|
||||
export function get(config: IConfig, options: string|request.Options, done: (err: Error, result?: string) => void)
|
||||
{
|
||||
authenticate(config, err =>
|
||||
authenticate(config, (err) =>
|
||||
{
|
||||
if (err)
|
||||
{
|
||||
return done(err);
|
||||
return done(err);
|
||||
}
|
||||
|
||||
cloudscraper.request(modify(options, 'GET'), (err: Error, response: any, body: any) =>
|
||||
cloudscraper.request(modify(options, 'GET'), (error: Error, response: any, body: any) =>
|
||||
{
|
||||
if (err)
|
||||
{
|
||||
return done(err);
|
||||
}
|
||||
|
||||
if (error) return done(error);
|
||||
done(null, typeof body === 'string' ? body : String(body));
|
||||
});
|
||||
});
|
||||
@@ -42,20 +104,19 @@ export function get(config: IConfig, options: string|request.Options, done: (err
|
||||
*/
|
||||
export function post(config: IConfig, options: request.Options, done: (err: Error, result?: string) => void)
|
||||
{
|
||||
authenticate(config, err =>
|
||||
authenticate(config, (err) =>
|
||||
{
|
||||
if (err)
|
||||
{
|
||||
return done(err);
|
||||
}
|
||||
|
||||
cloudscraper.request(modify(options, 'POST'), (err: Error, response: any, body: any) =>
|
||||
cloudscraper.request(modify(options, 'POST'), (error: Error, response: any, body: any) =>
|
||||
{
|
||||
if (err)
|
||||
if (error)
|
||||
{
|
||||
return done(err);
|
||||
return done(error);
|
||||
}
|
||||
|
||||
done(null, typeof body === 'string' ? body : String(body));
|
||||
});
|
||||
});
|
||||
@@ -71,107 +132,70 @@ function authenticate(config: IConfig, done: (err: Error) => void)
|
||||
return done(null);
|
||||
}
|
||||
|
||||
/* Bypass the login page and send a login request directly */
|
||||
let options =
|
||||
startSession()
|
||||
.then((sessionId: string) =>
|
||||
{
|
||||
headers: defaultHeaders,
|
||||
jar: true,
|
||||
gzip: false,
|
||||
method: 'GET',
|
||||
url: 'https://www.crunchyroll.com/login'
|
||||
};
|
||||
|
||||
cloudscraper.request(options, (err: Error, rep: string, body: string) =>
|
||||
defaultHeaders.Cookie = `sess_id=${sessionId}; c_locale=enUS`;
|
||||
return login(sessionId, config.user, config.pass);
|
||||
})
|
||||
.then((userData) =>
|
||||
{
|
||||
if (err) return done(err);
|
||||
|
||||
const $ = cheerio.load(body);
|
||||
|
||||
/* Get the token from the login page */
|
||||
const token = $('input[name="login_form[_token]"]').attr('value');
|
||||
if (token === '')
|
||||
{
|
||||
return done(new Error('Can`t find token!'));
|
||||
}
|
||||
|
||||
let options =
|
||||
/**
|
||||
* 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.
|
||||
*/
|
||||
const options =
|
||||
{
|
||||
headers: defaultHeaders,
|
||||
form:
|
||||
{
|
||||
'login_form[redirect_url]': '/',
|
||||
'login_form[name]': config.user,
|
||||
'login_form[password]': config.pass,
|
||||
'login_form[_token]': token
|
||||
},
|
||||
jar: true,
|
||||
gzip: false,
|
||||
method: 'POST',
|
||||
url: 'https://www.crunchyroll.com/login'
|
||||
url: 'http://www.crunchyroll.com/',
|
||||
method: 'GET',
|
||||
};
|
||||
|
||||
cloudscraper.request(options, (err: Error, rep: string, body: string) =>
|
||||
{
|
||||
if (err)
|
||||
{
|
||||
return done(err);
|
||||
return done(err);
|
||||
}
|
||||
|
||||
/* 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.
|
||||
*/
|
||||
let options =
|
||||
const $ = 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++)
|
||||
{
|
||||
headers: defaultHeaders,
|
||||
jar: true,
|
||||
url: 'http://www.crunchyroll.com/',
|
||||
method: 'GET'
|
||||
};
|
||||
if ((dims[i] !== undefined) && (dims[i] !== '') && (dims[i] !== 'not-registered'))
|
||||
{
|
||||
isAuthenticated = true;
|
||||
}
|
||||
|
||||
cloudscraper.request(options, (err: Error, rep: string, body: string) =>
|
||||
if ((dims[i] === 'premium') || (dims[i] === 'premiumplus'))
|
||||
{
|
||||
isPremium = true;
|
||||
}
|
||||
}
|
||||
|
||||
if (isAuthenticated === false)
|
||||
{
|
||||
if (err)
|
||||
{
|
||||
return done(err);
|
||||
}
|
||||
const error = $('ul.message, li.error').text();
|
||||
return done(new Error('Authentication failed: ' + error));
|
||||
}
|
||||
|
||||
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)
|
||||
{
|
||||
const error = $('ul.message, li.error').text();
|
||||
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!');
|
||||
}
|
||||
done(null);
|
||||
});
|
||||
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);
|
||||
});
|
||||
});
|
||||
})
|
||||
.catch(done);
|
||||
}
|
||||
|
||||
/**
|
||||
@@ -186,5 +210,10 @@ function modify(options: string|request.Options, reqMethod: string): request.Opt
|
||||
options.method = reqMethod;
|
||||
return options;
|
||||
}
|
||||
return { jar: true, headers: defaultHeaders, url: options.toString(), method: reqMethod };
|
||||
}
|
||||
return {
|
||||
jar: true,
|
||||
headers: defaultHeaders,
|
||||
url: options.toString(),
|
||||
method: reqMethod
|
||||
};
|
||||
}
|
||||
@@ -2,7 +2,7 @@
|
||||
import cheerio = require('cheerio');
|
||||
import episode from './episode';
|
||||
import fs = require('fs');
|
||||
const fse = require('fs-extra');
|
||||
import fse = require('fs-extra');
|
||||
import my_request = require('./my_request');
|
||||
import path = require('path');
|
||||
import url = require('url');
|
||||
@@ -153,7 +153,7 @@ function page(config: IConfig, address: string, done: (err: Error, result?: ISer
|
||||
episode: '',
|
||||
volume: 0,
|
||||
});
|
||||
done(null, {episodes: episodes.reverse(), series: ""});
|
||||
done(null, {episodes: episodes.reverse(), series: ''});
|
||||
}
|
||||
else
|
||||
{
|
||||
@@ -195,7 +195,7 @@ function page(config: IConfig, address: string, done: (err: Error, result?: ISer
|
||||
});
|
||||
if (episodeCount === 0)
|
||||
{
|
||||
log.warn("No episodes found for " + title + ". Could it be a movie?");
|
||||
log.warn('No episodes found for ' + title + '. Could it be a movie?');
|
||||
}
|
||||
done(null, {episodes: episodes.reverse(), series: title});
|
||||
});
|
||||
|
||||
@@ -1,14 +1,14 @@
|
||||
/* tslint:disable:no-bitwise false */
|
||||
'use strict';
|
||||
import crypto = require('crypto');
|
||||
import bigInt = require('big-integer');
|
||||
import crypto = require('crypto');
|
||||
import zlib = require('zlib');
|
||||
|
||||
/**
|
||||
* 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,
|
||||
done: (err?: Error, result?: Buffer) => void)
|
||||
{
|
||||
try
|
||||
{
|
||||
|
||||
@@ -33,10 +33,10 @@ export default function(input: string|Buffer, done: (err: Error, subtitle?: stri
|
||||
*/
|
||||
function event(block: ISubtitleEvent): string
|
||||
{
|
||||
var format = 'Layer,Start,End,Style,Name,MarginL,MarginR,MarginV,Effect,Text';
|
||||
const format = 'Layer,Start,End,Style,Name,MarginL,MarginR,MarginV,Effect,Text';
|
||||
|
||||
return '[Events]\n' +
|
||||
'Format: ' + format + '\n' + [].concat(block.event).map(style => ('Dialogue: 0,' +
|
||||
'Format: ' + format + '\n' + [].concat(block.event).map((style) => ('Dialogue: 0,' +
|
||||
style.$.start + ',' +
|
||||
style.$.end + ',' +
|
||||
style.$.style + ',' +
|
||||
@@ -70,13 +70,13 @@ function script(block: ISubtitle): string
|
||||
*/
|
||||
function style(block: ISubtitleStyle): string
|
||||
{
|
||||
var format = 'Name,Fontname,Fontsize,PrimaryColour,SecondaryColour,' +
|
||||
const format = 'Name,Fontname,Fontsize,PrimaryColour,SecondaryColour,' +
|
||||
'OutlineColour,BackColour,Bold,Italic,Underline,StrikeOut,ScaleX,' +
|
||||
'ScaleY,Spacing,Angle,BorderStyle,Outline,Shadow,Alignment,' +
|
||||
'MarginL,MarginR,MarginV,Encoding';
|
||||
|
||||
return '[V4+ Styles]\n' +
|
||||
'Format: ' + format + '\n' + [].concat(block.style).map(style => 'Style: ' +
|
||||
'Format: ' + format + '\n' + [].concat(block.style).map((style) => 'Style: ' +
|
||||
style.$.name + ',' +
|
||||
style.$.font_name + ',' +
|
||||
style.$.font_size + ',' +
|
||||
|
||||
@@ -2,7 +2,7 @@
|
||||
import ass from './ass';
|
||||
import srt from './srt';
|
||||
|
||||
export default <IFormatterTable> {
|
||||
ass: ass,
|
||||
srt: srt
|
||||
};
|
||||
export default {
|
||||
ass,
|
||||
srt
|
||||
} as IFormatterTable;
|
||||
|
||||
@@ -1,22 +1,23 @@
|
||||
'use strict';
|
||||
import childProcess = require('child_process');
|
||||
import fs = require('fs');
|
||||
import path = require('path');
|
||||
import os = require('os');
|
||||
import path = require('path');
|
||||
|
||||
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,
|
||||
streamMode: string, done: (err: Error) => void)
|
||||
export default function(config: IConfig, isSubtitled: boolean, rtmpInputPath: string, filePath: string,
|
||||
streamMode: string, done: (err: Error) => void)
|
||||
{
|
||||
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
|
||||
{
|
||||
|
||||
@@ -1,7 +1,8 @@
|
||||
'use strict';
|
||||
import childProcess = require('child_process');
|
||||
import path = require('path');
|
||||
import os = require('os');
|
||||
import path = require('path');
|
||||
|
||||
import log = require('../log');
|
||||
|
||||
/**
|
||||
@@ -10,30 +11,31 @@ import log = require('../log');
|
||||
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')
|
||||
{
|
||||
const cmd = command('ffmpeg') + ' ' +
|
||||
'-i "' + rtmpInputPath + '" ' +
|
||||
'-c copy -bsf:a aac_adtstoasc ' +
|
||||
'"' + filePath + '.mp4"';
|
||||
childProcess.exec(cmd, {
|
||||
maxBuffer: Infinity,
|
||||
}, done);
|
||||
}
|
||||
else
|
||||
{
|
||||
log.error('No such mode: ' + mode);
|
||||
}
|
||||
if (mode === 'RTMP')
|
||||
{
|
||||
childProcess.exec(command('rtmpdump') + ' ' +
|
||||
'-r "' + rtmpUrl + '" ' +
|
||||
'-y "' + rtmpInputPath + '" ' +
|
||||
'-W "' + swfUrl + '" ' +
|
||||
'-o "' + filePath + fileExt + '"', {
|
||||
maxBuffer: Infinity,
|
||||
}, done);
|
||||
}
|
||||
else if (mode === 'HLS')
|
||||
{
|
||||
const cmd = command('ffmpeg') + ' ' +
|
||||
'-i "' + rtmpInputPath + '" ' +
|
||||
'-c copy -bsf:a aac_adtstoasc ' +
|
||||
'"' + filePath + '.mp4"';
|
||||
childProcess.exec(cmd,
|
||||
{
|
||||
maxBuffer: Infinity,
|
||||
}, done);
|
||||
}
|
||||
else
|
||||
{
|
||||
log.error('No such mode: ' + mode);
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
@@ -43,7 +45,7 @@ function command(exe: string): string
|
||||
{
|
||||
if (os.platform() !== 'win32')
|
||||
{
|
||||
return exe;
|
||||
return exe;
|
||||
}
|
||||
|
||||
return '"' + path.join(__dirname, '../../bin/' + exe + '.exe') + '"';
|
||||
|
||||
@@ -1,5 +1,5 @@
|
||||
{
|
||||
"version": "1.5.1-beta",
|
||||
"target": "es6",
|
||||
"compilerOptions": {
|
||||
"declaration": true,
|
||||
"noImplicitAny": true,
|
||||
@@ -7,40 +7,8 @@
|
||||
"module": "commonjs",
|
||||
"outDir": "dist",
|
||||
"sourceMap": true,
|
||||
"target": "es5"
|
||||
},
|
||||
"filesGlob": [
|
||||
"src/**/*.ts",
|
||||
"typings/**/*.ts"
|
||||
],
|
||||
"files": [
|
||||
"src/batch.ts",
|
||||
"src/cli.ts",
|
||||
"src/episode.ts",
|
||||
"src/index.ts",
|
||||
"src/log.ts",
|
||||
"src/interface/IConfig.d.ts",
|
||||
"src/interface/IConfigLine.d.ts",
|
||||
"src/interface/IConfigTask.d.ts",
|
||||
"src/interface/IEpisodePage.d.ts",
|
||||
"src/interface/IEpisodePlayer.d.ts",
|
||||
"src/interface/IEpisodePlayerConfig.d.ts",
|
||||
"src/interface/IFormatterTable.d.ts",
|
||||
"src/interface/ISeries.d.ts",
|
||||
"src/interface/ISeriesEpisode.d.ts",
|
||||
"src/interface/ISubtitle.d.ts",
|
||||
"src/interface/ISubtitleEvent.d.ts",
|
||||
"src/interface/ISubtitleStyle.d.ts",
|
||||
"src/my_request.ts",
|
||||
"src/series.ts",
|
||||
"src/subtitle/decode.ts",
|
||||
"src/subtitle/formats/ass.ts",
|
||||
"src/subtitle/formats/index.ts",
|
||||
"src/subtitle/formats/srt.ts",
|
||||
"src/subtitle/index.ts",
|
||||
"src/video/index.ts",
|
||||
"src/video/merge.ts",
|
||||
"src/video/stream.ts",
|
||||
"typings/index.d.ts"
|
||||
]
|
||||
"lib": [
|
||||
"es2015"
|
||||
]
|
||||
}
|
||||
}
|
||||
|
||||
18
tslint.json
18
tslint.json
@@ -9,23 +9,22 @@
|
||||
"curly": false,
|
||||
"eofline": false,
|
||||
"forin": true,
|
||||
"indent": [true, 2],
|
||||
"indent": [true, "spaces", 2],
|
||||
"interface-name": true,
|
||||
"jsdoc-format": true,
|
||||
"label-position": true,
|
||||
"max-line-length": [true, 140],
|
||||
"member-ordering": [true,
|
||||
"public-before-private",
|
||||
"static-before-instance",
|
||||
"variables-before-functions"
|
||||
],
|
||||
"member-ordering": false,
|
||||
"no-shadowed-variable": false,
|
||||
"array-type": [true, "array"],
|
||||
"trailing-comma": false,
|
||||
"no-any": false,
|
||||
"no-arg": true,
|
||||
"no-bitwise": true,
|
||||
"space-within-parens": false,
|
||||
"no-object-literal-type-assertion": false,
|
||||
"no-console": [true,
|
||||
"debug",
|
||||
"info",
|
||||
"time",
|
||||
"timeEnd",
|
||||
"trace"
|
||||
@@ -42,7 +41,6 @@
|
||||
"no-use-before-declare": false,
|
||||
"no-var-requires": true,
|
||||
"one-line": [true,
|
||||
"check-catch",
|
||||
"check-whitespace"
|
||||
],
|
||||
"quotemark": [true, "single"],
|
||||
@@ -67,6 +65,8 @@
|
||||
"check-operator",
|
||||
"check-separator",
|
||||
"check-type"
|
||||
]
|
||||
],
|
||||
"object-literal-sort-keys": false,
|
||||
"ordered-imports": false
|
||||
}
|
||||
}
|
||||
13
typings.json
13
typings.json
@@ -1,13 +0,0 @@
|
||||
{
|
||||
"name": "crunchy",
|
||||
"globalDependencies": {
|
||||
"node": "github:DefinitelyTyped/DefinitelyTyped/node/node.d.ts#3882d337bb0808cde9fe4c08012508a48c135482",
|
||||
"commander": "github:DefinitelyTyped/DefinitelyTyped/commander/commander.d.ts#3882d337bb0808cde9fe4c08012508a48c135482",
|
||||
"xml2js": "github:DefinitelyTyped/DefinitelyTyped/xml2js/xml2js.d.ts#3882d337bb0808cde9fe4c08012508a48c135482",
|
||||
"cheerio": "github:DefinitelyTyped/DefinitelyTyped/cheerio/cheerio.d.ts#3882d337bb0808cde9fe4c08012508a48c135482",
|
||||
"mkdirp": "github:DefinitelyTyped/DefinitelyTyped/mkdirp/mkdirp.d.ts#3882d337bb0808cde9fe4c08012508a48c135482",
|
||||
"request": "github:DefinitelyTyped/DefinitelyTyped/request/request.d.ts#3882d337bb0808cde9fe4c08012508a48c135482",
|
||||
"big-integer": "github:DefinitelyTyped/DefinitelyTyped/big-integer/big-integer.d.ts#3882d337bb0808cde9fe4c08012508a48c135482",
|
||||
"form-data": "github:DefinitelyTyped/DefinitelyTyped/form-data/form-data.d.ts#3882d337bb0808cde9fe4c08012508a48c135482"
|
||||
}
|
||||
}
|
||||
Reference in New Issue
Block a user