5 Commits

Author SHA1 Message Date
Godzil
0124e38a89 1.3.5 2018-07-30 22:47:36 +01:00
Godzil
6765b517ec Add a new episode filter and completely remove some dependencies on the config object. 2018-07-30 22:47:36 +01:00
Godzil
8c1e0f2e0c Stop messing with the config objet 2018-07-30 22:47:36 +01:00
Godzil
817843c40c Add more output to debug.txt 2018-07-30 22:47:36 +01:00
Godzil
04b22fdce5 Update readme file 2018-07-30 22:47:36 +01:00
9 changed files with 154 additions and 30 deletions

View File

@@ -12,11 +12,11 @@
This application is not endorsed or affliated with *CrunchyRoll*. The usage of this application enables episodes to be downloaded for offline convenience which may be forbidden by law in your country. Usage of this application may also cause a violation of the agreed *Terms of Service* between you and the stream provider. A tool is not responsible for your actions; please make an informed decision prior to using this application.
**PLEASE _ONLY_ USE THIS TOOL IF YOU HAVE A _PREMIUM ACCOUNT_**
**_ONLY_ USE THIS TOOL IF YOU HAVE A _PREMIUM ACCOUNT_**
## Configuration
It is recommended to enable authentication (`-p` and `-u`) so your account permissions and settings are available for use. It is not possible to download non-free material without an account and premium subscription. Furthermore, the default account settings are used when downloading. If you want the highest quality videos, configure these preferences at https://www.crunchyroll.com/acct/?action=video.
You need to authentication (`-p` and `-u`) to use Crunchy so you need to have an account on *CrunchyRool*. It is not possible to download non-free material without an account and premium subscription.
## Prerequisites
@@ -71,6 +71,7 @@ The [command-line interface](http://en.wikipedia.org/wiki/Command-line_interface
-u, --user <s> The e-mail address or username.
-c, --cache Disables the cache.
-m, --merge Disables merging subtitles and videos.
-e, --episodes <s> Episode list. Read documentation on how to use
-f, --format <s> The subtitle format. (Default: ass)
-o, --output <s> The output path.
-s, --series <s> The series override.
@@ -85,7 +86,7 @@ The [command-line interface](http://en.wikipedia.org/wiki/Command-line_interface
#### Batch-mode
When no sequence of series addresses is provided, the batch-mode source file will be read (which is *CrunchyRoll.txt* in the current work directory. Each line in this file is processed as a seperate command-line statement. This makes it ideal to manage a large sequence of series addresses with variating command-line options or incremental episode updates.
When no sequence of series addresses is provided, the batch-mode source file will be read (which is *CrunchyRoll.txt* in the current work directory. Each line in this file is processed contain the URL of a series and can support some of the command line paramter (like *-e*). This makes it ideal to manage a large sequence of series addresses.
#### Examples
@@ -101,6 +102,29 @@ Download *Fairy Tail* to `C:\Anime`:
crunchy --output C:\Anime http://www.crunchyroll.com/fairy-tail
Download episode 42 of *Fairy Tail* to `C:\Anime`:
crunchy --output C:\Anime @http://www.crunchyroll.com/fairy-tail/episode-46-the-silver-labyrinth-662721
*Notice the '@' in front of the URL, it is there to tell Crunchy that the URL is an episode URL and not a series URL.*
or
crunchy --output C:\Anime http://www.crunchyroll.com/fairy-tail -e 42
Download episode 10 to 42 (both included) of *Fairy Tail*:
crunchy http://www.crunchyroll.com/fairy-tail -e 10-42
Download episode up to 42 (included) of *Fairy Tail*:
crunchy http://www.crunchyroll.com/fairy-tail -e -42
Download episodes starting from 42 to the last available of *Fairy Tail*:
crunchy http://www.crunchyroll.com/fairy-tail -e 42-
#### Command line parameters
##### Authentication
@@ -117,6 +141,7 @@ Download *Fairy Tail* to `C:\Anime`:
##### Settings
* `-e or --episodes <s>` set an episode
* `-f or --format <s>` sets the subtitle format. (Default: ass)
* `-o or --output <s>` sets the output path.
* `-s or --series <s>` sets the series override.

2
package-lock.json generated
View File

@@ -1,6 +1,6 @@
{
"name": "crunchy",
"version": "1.3.4",
"version": "1.3.5",
"lockfileVersion": 1,
"requires": true,
"dependencies": {

View File

@@ -15,7 +15,7 @@
"engines": {
"node": ">=5.0"
},
"version": "1.3.4",
"version": "1.3.5",
"bin": {
"crunchy": "./bin/crunchy",
"crunchy.sh": "./bin/crunchy.sh"

View File

@@ -71,13 +71,22 @@ export default function(args: string[], done: (err?: Error) => void)
return done();
}
series(tasksArr[i].config, tasksArr[i].address, (errin) =>
if (config.debug)
{
log.dumpToDebug('Task ' + i, JSON.stringify(tasksArr[i]));
}
series(config, tasksArr[i], (errin) =>
{
if (errin)
{
if (tasksArr[i].retry <= 0)
{
console.error(errin);
log.error(errin.stack || errin);
if (config.debug)
{
log.dumpToDebug('BatchGiveUp', errin.stack || errin);
}
log.error('Cannot get episodes from "' + tasksArr[i].address + '", please rerun later');
/* Go to the next on the list */
i += 1;
@@ -86,7 +95,11 @@ export default function(args: string[], done: (err?: Error) => void)
{
if (config.verbose)
{
console.error(errin);
log.error(errin);
}
if (config.debug)
{
log.dumpToDebug('BatchRetry', errin.stack || errin);
}
log.warn('Retrying to fetch episodes list from' + tasksArr[i].retry + ' / ' + config.retry);
tasksArr[i].retry -= 1;
@@ -136,6 +149,52 @@ function split(value: string): string[]
return pieces;
}
function get_min_filter(filter: string): number
{
if (filter !== undefined)
{
const tok = filter.split('-');
if (tok.length > 2)
{
log.error('Invalid episode filter \'' + filter + '\'');
process.exit(-1);
}
if (tok[0] !== '')
{
return parseInt(tok[0], 10);
}
}
return 0;
}
function get_max_filter(filter: string): number
{
if (filter !== undefined)
{
const tok = filter.split('-');
if (tok.length > 2)
{
log.error('Invalid episode filter \'' + filter + '\'');
process.exit(-1);
}
if ((tok.length > 1) && (tok[1] !== ''))
{
/* We have a max value */
return parseInt(tok[1], 10);
}
else if ((tok.length === 1) && (tok[0] !== ''))
{
/* A single episode has been requested */
return parseInt(tok[0], 10);
}
}
return +Infinity;
}
/**
* Parses the configuration or reads the batch-mode file for tasks.
*/
@@ -143,11 +202,10 @@ function tasks(config: IConfigLine, batchPath: string, done: (err: Error, tasks?
{
if (config.args.length)
{
const configIn = config;
return done(null, config.args.map((addressIn) =>
{
return {address: addressIn, config: configIn, retry: config.retry};
return {address: addressIn, retry: config.retry,
episode_min: get_min_filter(config.episodes), episode_max: get_max_filter(config.episodes)};
}));
}
@@ -183,7 +241,8 @@ function tasks(config: IConfigLine, batchPath: string, done: (err: Error, tasks?
return;
}
map.push({address: addressIn, config: lineConfig, retry: config.retry});
map.push({address: addressIn, retry: lineConfig.retry,
episode_min: get_min_filter(lineConfig.episodes), episode_max: get_max_filter(lineConfig.episodes)});
});
});
done(null, map);
@@ -203,6 +262,8 @@ function parse(args: string[]): IConfigLine
// Disables
.option('-c, --cache', 'Disables the cache.')
.option('-m, --merge', 'Disables merging subtitles and videos.')
// Episode filter
.option('-e, --episodes <s>', 'Episode list. Read documentation on how to use')
// Settings
.option('-f, --format <s>', 'The subtitle format. (Default: ass)')
.option('-o, --output <s>', 'The output path.')

View File

@@ -101,7 +101,7 @@ function download(config: IConfig, page: IEpisodePage, player: IEpisodePlayer, d
log.warn('Renaming to \'' + fileName + '\'...');
config.filename = fileName;
page.filename = fileName;
}
if (config.rebuildcrp)
@@ -219,11 +219,11 @@ function name(config: IConfig, page: IEpisodePage, series: string, extra: string
const volume = (volumeNum < 10 ? '0' : '') + page.volume;
const tag = config.tag || 'CrunchyRoll';
if (!config.filename) {
if (!page.filename) {
return page.series + ' - s' + volume + 'e' + episode + ' - [' + tag + ']' + extra;
}
return config.filename
return page.filename
.replace(/{EPISODE_ID}/g, page.id.toString())
.replace(/{EPISODE_NUMBER}/g, episode)
.replace(/{SEASON_NUMBER}/g, volume)
@@ -277,10 +277,21 @@ function scrapePage(config: IConfig, address: string, done: (err: Error, page?:
const episodeTitle = $('#showmedia_about_name').text().replace(/[“”]/g, '');
const data = regexp.exec(look);
if (config.debug)
{
log.dumpToDebug('episode page', $.html());
}
if (!swf || !data)
{
log.warn('Somethig unexpected in the page at ' + address + ' (data are: ' + look + ')');
log.warn('Setting Season to 0 and episode to 0...');
if (config.debug)
{
log.dumpToDebug('episode unexpected', look);
}
done(null, {
episode: '0',
id: epId,
@@ -289,6 +300,7 @@ function scrapePage(config: IConfig, address: string, done: (err: Error, page?:
title: episodeTitle,
swf: swf[1],
volume: '0',
filename: '',
});
}
else
@@ -301,6 +313,7 @@ function scrapePage(config: IConfig, address: string, done: (err: Error, page?:
title: episodeTitle,
swf: swf[1],
volume: data[2] || '1',
filename: '',
});
}
});
@@ -367,6 +380,11 @@ function scrapePlayer(config: IConfig, address: string, id: number, done: (err:
});
} catch (parseError)
{
if (config.debug)
{
log.dumpToDebug('player scrape', parseError);
}
done(parseError);
}
});

View File

@@ -5,6 +5,7 @@ interface IConfig {
// Disables
cache?: boolean;
merge?: boolean;
episodes?: string;
// Settings
format?: string;
output?: string;

View File

@@ -1,5 +1,6 @@
interface IConfigTask {
address: string;
config: IConfigLine;
retry: number;
episode_min: number;
episode_max: number;
}

View File

@@ -6,4 +6,5 @@ interface IEpisodePage {
season: string;
title: string;
swf: string;
filename: string;
}

View File

@@ -27,7 +27,7 @@ function fileExist(path: string)
/**
* Streams the series to disk.
*/
export default function(config: IConfig, address: string, done: (err: Error) => void)
export default function(config: IConfig, task: IConfigTask, done: (err: Error) => void)
{
const persistentPath = path.join(config.output || process.cwd(), persistent);
@@ -41,7 +41,7 @@ export default function(config: IConfig, address: string, done: (err: Error) =>
{
const cache = config.cache ? {} : JSON.parse(contents || '{}');
page(config, address, (errP, page) =>
pageScrape(config, task, (errP, page) =>
{
if (errP)
{
@@ -51,8 +51,13 @@ export default function(config: IConfig, address: string, done: (err: Error) =>
let i = 0;
(function next()
{
if (config.debug)
{
log.dumpToDebug('Episode ' + i, JSON.stringify(page.episodes[i]));
}
if (i >= page.episodes.length) return done(null);
download(cache, config, address, page.episodes[i], (errD, ignored) =>
download(cache, config, task, page.episodes[i], (errD, ignored) =>
{
if (errD)
{
@@ -72,7 +77,7 @@ export default function(config: IConfig, address: string, done: (err: Error) =>
{
if (config.debug)
{
log.dumpToDebug('series address', address);
log.dumpToDebug('series address', task.address);
log.dumpToDebug('series error', errD.stack || errD);
log.dumpToDebug('series data', JSON.stringify(page));
}
@@ -116,10 +121,18 @@ export default function(config: IConfig, address: string, done: (err: Error) =>
* Downloads the episode.
*/
function download(cache: {[address: string]: number}, config: IConfig,
baseAddress: string, item: ISeriesEpisode,
task: IConfigTask, item: ISeriesEpisode,
done: (err: Error, ign: boolean) => void)
{
const address = url.resolve(baseAddress, item.address);
const episodeNumber = parseInt(item.episode, 10);
if ( (episodeNumber < task.episode_min) ||
(episodeNumber > task.episode_max) )
{
return done(null, false);
}
const address = url.resolve(task.address, item.address);
if (cache[address])
{
@@ -141,14 +154,14 @@ function download(cache: {[address: string]: number}, config: IConfig,
/**
* Requests the page and scrapes the episodes and series.
*/
function page(config: IConfig, address: string, done: (err: Error, result?: ISeries) => void)
function pageScrape(config: IConfig, task: IConfigTask, done: (err: Error, result?: ISeries) => void)
{
if (address[0] === '@')
if (task.address[0] === '@')
{
log.info('Trying to fetch from ' + address.substr(1));
log.info('Trying to fetch from ' + task.address.substr(1));
const episodes: ISeriesEpisode[] = [];
episodes.push({
address: address.substr(1),
address: task.address.substr(1),
episode: '',
volume: 0,
retry: config.retry,
@@ -158,7 +171,7 @@ function page(config: IConfig, address: string, done: (err: Error, result?: ISer
else
{
let episodeCount = 0;
my_request.get(config, address, (err, result) => {
my_request.get(config, task.address, (err, result) => {
if (err) {
return done(err);
}
@@ -166,13 +179,17 @@ function page(config: IConfig, address: string, done: (err: Error, result?: ISer
const $ = cheerio.load(result);
const title = $('span[itemprop=name]').text();
if (config.debug)
{
log.dumpToDebug('serie page', $.html());
}
if (!title) {
if (config.debug)
{
log.dumpToDebug('inval page addr', address);
log.dumpToDebug('inval page data', $);
log.dumpToDebug('missing title', task.address);
}
return done(new Error('Invalid page.(' + address + ')'));
return done(new Error('Invalid page.(' + task.address + ')'));
}
log.info('Checking availability for ' + title);