27 Commits

Author SHA1 Message Date
Godzil
b248405437 1.2.2 2018-05-09 22:33:45 +01:00
Godzil
bf941819a8 remove unwwanted parameter 2018-05-09 22:31:50 +01:00
Godzil
fcae53baae Node 5, 6 and 7 seems to not like something. Delete them from Travis build 2018-05-08 21:50:58 +01:00
Godzil
05ead50c0d Let's try to make travis happy with older node version 2018-05-08 21:49:11 +01:00
Godzil
0a80f80f91 1.2.1 2018-05-08 21:37:43 +01:00
Godzil
3bf5fea735 Make Crunchy to properly return a return code when running fine or failing 2018-05-08 21:37:34 +01:00
Godzil
3a95994cc2 1.2.0 2018-03-29 22:33:15 +01:00
Godzil
a29870691b Update deps 2018-03-29 22:33:06 +01:00
Godzil
547fdc4aa0 Add a way to select the resolution. Use 1080p by default
Fix #58
2018-03-29 22:29:13 +01:00
Godzil
c78552795f 1.1.22 2018-03-29 20:41:45 +01:00
Godzil
090c7e4789 Trying to fix #59 by adding a referer to the header. Seems to fix it but need to be throughfully tested.. 2018-03-29 20:40:17 +01:00
Godzil
bf8e1fe80f Update cloudscraper 2018-03-29 20:38:38 +01:00
Manoël Trapier
7344ce3d61 Update README.md 2018-01-31 17:09:58 +00:00
Godzil
c642e76cce Make sure that it is rebuild before publishing 2017-12-27 05:34:22 +01:00
Godzil
8ef27066f6 1.1.21 2017-12-27 05:19:28 +01:00
Godzil
621df26b58 Try to make travis happy (again) 2017-12-27 05:16:58 +01:00
Godzil
8060b1b73b Update travis definition 2017-12-27 04:58:21 +01:00
Godzil
11f6b3feff Make tslint happy 2017-12-27 04:57:45 +01:00
Godzil
537639f2a8 Simplify tsconfig to no longer list .ts file, also simplify commands as typings is no longuer there 2017-12-27 04:57:24 +01:00
Godzil
813f8a997d Completely remote typings to use TypeScript2.0 type management, update also some deps 2017-12-27 04:56:26 +01:00
Godzil
48544020a1 1.1.20 2017-09-16 22:58:27 +01:00
Godzil
cc68d21107 correct permissions 2017-09-16 22:54:49 +01:00
Godzil
acd91e2679 Add (unless) node minimum version in packages.json 2017-09-16 22:54:27 +01:00
Godzil
53f0a9462a Better filename forbidden character handling
Logs are a bit better
2017-09-16 22:51:49 +01:00
Godzil
10d71944d9 Fix lint error 2017-08-21 16:08:58 +02:00
Manoël Trapier
b5bbde7cdd Change to make travis npm happy 2017-08-21 14:24:23 +01:00
Manoël Trapier
c406bc70ee Sanitise more characters from filenames 2017-05-17 16:17:26 +01:00
19 changed files with 1294 additions and 96 deletions

View File

@@ -1,11 +1,10 @@
language: node_js language: node_js
sudo: false sudo: false
node_js: node_js:
- 5 - 8
- 6 - 9
before_install: before_install:
- npm install --dev - npm install --only=dev
script: script:
- npm run types - npm run build
- npm run compile
- npm test - npm test

View File

@@ -1,6 +1,6 @@
# Crunchy: a fork of Deathspike/CrunchyRoll.js # Crunchy: a fork of Deathspike/CrunchyRoll.js
[![Issue Stats](http://issuestats.com/github/Godzil/Crunchy/badge/issue)](http://issuestats.com/github/Godzil/Crunchy) [![Travis CI](https://travis-ci.org/Godzil/Crunchy.svg?branch=master)](https://travis-ci.org/Godzil/Crunchy) [![Issue Stats](http://issuestats.com/github/Godzil/Crunchy/badge/issue)](http://issuestats.com/github/Godzil/Crunchy) [![Travis CI](https://travis-ci.org/Godzil/Crunchy.svg?branch=master)](https://travis-ci.org/Godzil/Crunchy) [![Maintainability](https://api.codeclimate.com/v1/badges/413c7ca11c0805b1ef3e/maintainability)](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. *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.

0
bin/crunchy Normal file → Executable file
View File

10
bin/crunchy.sh Executable file
View 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

1173
package-lock.json generated Normal file

File diff suppressed because it is too large Load Diff

View File

@@ -12,31 +12,37 @@
"type": "git", "type": "git",
"url": "git://github.com/Godzil/crunchyroll.js.git" "url": "git://github.com/Godzil/crunchyroll.js.git"
}, },
"version": "1.1.19", "engines": {
"node": ">=5.0"
},
"version": "1.2.2",
"bin": { "bin": {
"crunchy": "./bin/crunchy" "crunchy": "./bin/crunchy",
"crunchy.sh": "./bin/crunchy.sh"
}, },
"dependencies": { "dependencies": {
"big-integer": "^1.4.4", "big-integer": "^1.6.27",
"cheerio": "^0.22.0", "cheerio": "^0.22.0",
"cloudscraper": "^1.4.1", "cloudscraper": "^1.5.0",
"commander": "^2.6.0", "commander": "^2.15.1",
"fs-extra": "^2.0.0", "fs-extra": "^5.0.0",
"mkdirp": "^0.5.0", "mkdirp": "^0.5.0",
"request": "^2.74.0", "request": "^2.85.0",
"xml2js": "^0.4.5" "xml2js": "^0.4.5"
}, },
"devDependencies": { "devDependencies": {
"@types/cheerio": "^0.22.7",
"@types/mkdirp": "^0.5.2",
"@types/request": "^2.47.0",
"@types/xml2js": "^0.4.2",
"tsconfig-lint": "^0.12.0", "tsconfig-lint": "^0.12.0",
"tslint": "^4.4.2", "tslint": "^5.9.1",
"typescript": "^2.2.0", "typescript": "^2.8.1"
"typings": "^2.1.0"
}, },
"scripts": { "scripts": {
"prepublish": "npm run types && tsc", "prepublishOnly": "npm run build",
"compile": "tsc", "build": "tsc",
"test": "tslint -c ./tslint.json --project ./tsconfig.json ./src/**/*.ts", "test": "tslint -c ./tslint.json --project ./tsconfig.json ./src/**/*.ts",
"types": "typings install",
"start": "node ./bin/crunchy" "start": "node ./bin/crunchy"
}, },
"bugs": { "bugs": {

View File

@@ -3,6 +3,15 @@ import commander = require('commander');
import fs = require('fs'); import fs = require('fs');
import path = require('path'); import path = require('path');
import series from './series'; import series from './series';
import log = require('./log');
/* correspondances between resolution and value CR excpect */
let 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. * Streams the batch of series to disk.
@@ -12,6 +21,28 @@ export default function(args: string[], done: (err?: Error) => void)
const config = parse(args); const config = parse(args);
const batchPath = path.join(config.output || process.cwd(), 'CrunchyRoll.txt'); const batchPath = path.join(config.output || process.cwd(), 'CrunchyRoll.txt');
// 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, tasks) => tasks(config, batchPath, (err, tasks) =>
{ {
if (err) if (err)
@@ -151,5 +182,6 @@ function parse(args: string[]): IConfigLine
.option('-s, --series <s>', 'The series override.') .option('-s, --series <s>', 'The series override.')
.option('-n, --filename <s>', 'The name override.') .option('-n, --filename <s>', 'The name override.')
.option('-t, --tag <s>', 'The subgroup. (Default: CrunchyRoll)') .option('-t, --tag <s>', 'The subgroup. (Default: CrunchyRoll)')
.option('-r, --resolution <s>', 'The video resolution. (Default: 1080 (360, 480, 720, 1080))')
.parse(args); .parse(args);
} }

View File

@@ -6,5 +6,8 @@ batch(process.argv, (err: any) =>
if (err) if (err)
{ {
console.error(err.stack || err); console.error(err.stack || err);
process.exit(-1)
} }
console.info("Done!")
process.exit(0)
}); });

View File

@@ -2,7 +2,7 @@
import cheerio = require('cheerio'); import cheerio = require('cheerio');
import fs = require('fs'); import fs = require('fs');
import mkdirp = require('mkdirp'); import mkdirp = require('mkdirp');
import request = require('./request'); import my_request = require('./my_request');
import path = require('path'); import path = require('path');
import subtitle from './subtitle/index'; import subtitle from './subtitle/index';
import video from './video/index'; import video from './video/index';
@@ -63,6 +63,11 @@ function fileExist(path: string)
} }
} }
function sanitiseFileName(str: string)
{
return str.replace(/[\/':\?\*"<>\.]/g, '_');
}
/** /**
* Downloads the subtitle and video. * Downloads the subtitle and video.
*/ */
@@ -70,8 +75,8 @@ function download(config: IConfig, page: IEpisodePage, player: IEpisodePlayer, d
{ {
let series = config.series || page.series; let series = config.series || page.series;
series = series.replace('/', '_').replace('\'', '_').replace(':', '_'); series = sanitiseFileName(series);
let fileName = name(config, page, series, '').replace('/', '_').replace('\'', '_').replace(':', '_'); let fileName = sanitiseFileName(name(config, page, series, ''));
let filePath = path.join(config.output || process.cwd(), series, fileName); let filePath = path.join(config.output || process.cwd(), series, fileName);
if (fileExist(filePath + '.mkv')) if (fileExist(filePath + '.mkv'))
@@ -82,7 +87,7 @@ function download(config: IConfig, page: IEpisodePage, player: IEpisodePlayer, d
do do
{ {
count = count + 1; count = count + 1;
fileName = name(config, page, series, '-' + count).replace('/', '_').replace('\'', '_').replace(':', '_'); fileName = sanitiseFileName(name(config, page, series, '-' + count));
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'));
@@ -96,6 +101,7 @@ function download(config: IConfig, page: IEpisodePage, player: IEpisodePlayer, d
return done(errM, false); return done(errM, false);
} }
log.dispEpisode(fileName, 'Fetching...', false);
downloadSubtitle(config, player, filePath, (errDS) => downloadSubtitle(config, player, filePath, (errDS) =>
{ {
if (errDS) if (errDS)
@@ -106,7 +112,7 @@ function download(config: IConfig, page: IEpisodePage, player: IEpisodePlayer, d
const now = Date.now(); const now = Date.now();
if (player.video.file !== undefined) if (player.video.file !== undefined)
{ {
log.dispEpisode(fileName, 'Fetching...', false); log.dispEpisode(fileName, 'Fetching video...', false);
downloadVideo(config, page, player, filePath, (errDV) => downloadVideo(config, page, player, filePath, (errDV) =>
{ {
if (errDV) if (errDV)
@@ -121,6 +127,7 @@ function download(config: IConfig, page: IEpisodePage, player: IEpisodePlayer, d
const isSubtited = Boolean(player.subtitle); const isSubtited = Boolean(player.subtitle);
log.dispEpisode(fileName, 'Merging...', false);
video.merge(config, isSubtited, player.video.file, filePath, player.video.mode, (errVM) => video.merge(config, isSubtited, player.video.file, filePath, player.video.mode, (errVM) =>
{ {
if (errVM) if (errVM)
@@ -239,7 +246,7 @@ function scrapePage(config: IConfig, address: string, done: (err: Error, page?:
return done(new Error('Invalid address.')); return done(new Error('Invalid address.'));
} }
request.get(config, address, (err, result) => my_request.get(config, address, (err, result) =>
{ {
if (err) if (err)
{ {
@@ -295,8 +302,13 @@ function scrapePlayer(config: IConfig, address: string, id: number, done: (err:
return done(new Error('Invalid address.')); return done(new Error('Invalid address.'));
} }
request.post(config, { 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, url: url[1] + '/xml/?req=RpcApiVideoPlayer_GetStandardConfig&media_id=' + id,
}, (err, result) => }, (err, result) =>
{ {
@@ -321,9 +333,9 @@ function scrapePlayer(config: IConfig, address: string, id: number, done: (err:
let streamMode = 'RTMP'; let streamMode = 'RTMP';
if (player['default:preload'].stream_info.host === '') if (player['default:preload'].stream_info.host === '')
{ {
streamMode = 'HLS'; streamMode = 'HLS';
} }
done(null, { done(null, {
subtitle: isSubtitled ? { subtitle: isSubtitled ? {

View File

@@ -14,4 +14,7 @@ interface IConfig {
series?: string; series?: string;
filename?: string; filename?: string;
tag?: string; tag?: string;
resolution?: string;
video_format?: string;
video_quality?: string;
} }

4
src/interface/IResolData.d.ts vendored Normal file
View File

@@ -0,0 +1,4 @@
interface IResolData {
quality: string;
format: string;
}

View File

@@ -28,10 +28,10 @@ export function warn(str: string)
export function dispEpisode(name: string, status: string, addNL: boolean) export function dispEpisode(name: string, status: string, addNL: boolean)
{ {
/* Do fancy output */ /* Do fancy output */
process.stdout.write(' \x1B[1;33m> \x1B[37m' + name + '\x1B[0m : \x1B[33m' + status + '\x1B[0m\x1B[0G'); process.stdout.write('\x1B[K \x1B[1;33m> \x1B[37m' + name + '\x1B[0m : \x1B[33m' + status + '\x1B[0m\x1B[0G');
if (addNL) if (addNL)
{ {
console.log(''); console.log('');
} }
} }

View File

@@ -9,14 +9,16 @@ let isPremium = false;
const defaultHeaders: request.Headers = 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; x64; rv:58.0) Gecko/20100101 Firefox/58.0',
'Connection': 'keep-alive' // Mozilla/5.0 (Windows NT 6.2; WOW64; rv:34.0) Gecko/20100101 Firefox/34.0',
'Connection': 'keep-alive',
'Referer': 'https://www.crunchyroll.com/login'
}; };
/** /**
* 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: string|request.Options, done: (err: Error, result?: string) => void)
{ {
authenticate(config, err => authenticate(config, err =>
{ {
@@ -99,9 +101,9 @@ function authenticate(config: IConfig, done: (err: Error) => void)
headers: defaultHeaders, headers: defaultHeaders,
form: form:
{ {
'login_form[redirect_url]': '/',
'login_form[name]': config.user, 'login_form[name]': config.user,
'login_form[password]': config.pass, 'login_form[password]': config.pass,
'login_form[redirect_url]': '/',
'login_form[_token]': token 'login_form[_token]': token
}, },
jar: true, jar: true,

View File

@@ -3,7 +3,7 @@ import cheerio = require('cheerio');
import episode from './episode'; import episode from './episode';
import fs = require('fs'); import fs = require('fs');
const fse = require('fs-extra'); const fse = require('fs-extra');
import request = require('./request'); import my_request = require('./my_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');
@@ -158,7 +158,7 @@ function page(config: IConfig, address: string, done: (err: Error, result?: ISer
else else
{ {
let episodeCount = 0; let episodeCount = 0;
request.get(config, address, (err, result) => { my_request.get(config, address, (err, result) => {
if (err) { if (err) {
return done(err); return done(err);
} }

View File

@@ -1,14 +1,14 @@
/* tslint:disable:no-bitwise false */ /* tslint:disable:no-bitwise false */
'use strict'; 'use strict';
import crypto = require('crypto');
import bigInt = require('big-integer'); import bigInt = require('big-integer');
import crypto = require('crypto');
import zlib = require('zlib'); import zlib = require('zlib');
/** /**
* Decodes the data. * Decodes the data.
*/ */
export default function(id: number, iv: Buffer|string, data: Buffer|string, export default function(id: number, iv: Buffer|string, data: Buffer|string,
done: (err?: Error, result?: Buffer) => void) done: (err?: Error, result?: Buffer) => void)
{ {
try try
{ {

View File

@@ -1,15 +1,16 @@
'use strict'; 'use strict';
import childProcess = require('child_process'); import childProcess = require('child_process');
import fs = require('fs'); import fs = require('fs');
import path = require('path');
import os = require('os'); import os = require('os');
import path = require('path');
import subtitle from '../subtitle/index'; 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, export default function(config: IConfig, isSubtitled: boolean, rtmpInputPath: string, filePath: string,
streamMode: string, done: (err: Error) => void) streamMode: string, done: (err: Error) => void)
{ {
const subtitlePath = filePath + '.' + (subtitle.formats[config.format] ? config.format : 'ass'); const subtitlePath = filePath + '.' + (subtitle.formats[config.format] ? config.format : 'ass');
let videoPath = filePath; let videoPath = filePath;

View File

@@ -1,7 +1,8 @@
'use strict'; 'use strict';
import childProcess = require('child_process'); import childProcess = require('child_process');
import path = require('path');
import os = require('os'); import os = require('os');
import path = require('path');
import log = require('../log'); import log = require('../log');
/** /**

View File

@@ -1,46 +1,11 @@
{ {
"version": "1.5.1-beta", "target": "es6",
"compilerOptions": { "compilerOptions": {
"declaration": true, "declaration": true,
"noImplicitAny": true, "noImplicitAny": true,
"removeComments": false, "removeComments": false,
"module": "commonjs", "module": "commonjs",
"outDir": "dist", "outDir": "dist",
"sourceMap": true, "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/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"
]
} }

View File

@@ -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"
}
}