Compare commits
6 Commits
1.1.3
...
ffmpeg_pul
| Author | SHA1 | Date | |
|---|---|---|---|
|
|
62a08e14bb | ||
|
|
422d0827f9 | ||
|
|
546a849aa5 | ||
|
|
e06ff53210 | ||
|
|
18375d3d22 | ||
|
|
5fdee94b38 |
@@ -26,14 +26,14 @@ Use the applicable instructions to install. Is your operating system not listed?
|
||||
|
||||
### Debian (Mint, Ubuntu, etc)
|
||||
|
||||
1. Run in *Terminal*: `sudo apt-get install nodejs npm mkvtoolnix rtmpdump`
|
||||
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 crunchyroll`
|
||||
|
||||
### Mac OS X
|
||||
|
||||
1. Install *Homebrew* following the instructions at http://brew.sh/
|
||||
2. Run in *Terminal*: `brew install node mkvtoolnix rtmpdump`
|
||||
2. Run in *Terminal*: `brew install node mkvtoolnix rtmpdump ffmpeg`
|
||||
3. Run in *Terminal*: `npm install -g crunchyroll`
|
||||
|
||||
### Windows
|
||||
|
||||
BIN
bin/ffmpeg.exe
Executable file
BIN
bin/ffmpeg.exe
Executable file
Binary file not shown.
24
package.json
24
package.json
@@ -11,26 +11,26 @@
|
||||
"type": "git",
|
||||
"url": "git://github.com/Deathspike/crunchyroll.js.git"
|
||||
},
|
||||
"version": "1.1.3",
|
||||
"version": "1.1.5",
|
||||
"bin": {
|
||||
"crunchyroll": "./bin/crunchyroll"
|
||||
},
|
||||
"dependencies": {
|
||||
"big-integer": "^1.4.4",
|
||||
"cheerio": "^0.18.0",
|
||||
"commander": "^2.6.0",
|
||||
"mkdirp": "^0.5.0",
|
||||
"request": "^2.53.0",
|
||||
"xml2js": "^0.4.5"
|
||||
"big-integer": "1.4.4",
|
||||
"cheerio": "0.18.0",
|
||||
"commander": "2.6.0",
|
||||
"mkdirp": "0.5.0",
|
||||
"request": "2.53.0",
|
||||
"xml2js": "0.4.5"
|
||||
},
|
||||
"devDependencies": {
|
||||
"tsd": "^0.5.7",
|
||||
"tslint": "^2.1.1",
|
||||
"typescript": "^1.4.1"
|
||||
"tsd": "0.5.7",
|
||||
"tslint": "2.3.0-beta",
|
||||
"typescript": "1.5.0-beta"
|
||||
},
|
||||
"scripts": {
|
||||
"prepublish": "npm run tsd && node ts",
|
||||
"prepublish": "npm run tsd && tsc",
|
||||
"test": "node ts --only-test",
|
||||
"tsd": "tsd reinstall --overwrite"
|
||||
"tsd": "tsd reinstall -o -s"
|
||||
}
|
||||
}
|
||||
|
||||
15
src/batch.ts
15
src/batch.ts
@@ -1,15 +1,13 @@
|
||||
'use strict';
|
||||
export = main;
|
||||
import commander = require('commander');
|
||||
import fs = require('fs');
|
||||
import path = require('path');
|
||||
import series = require('./series');
|
||||
import typings = require('./typings');
|
||||
import series from './series';
|
||||
|
||||
/**
|
||||
* Streams the batch of series to disk.
|
||||
*/
|
||||
function main(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');
|
||||
tasks(config, batchPath, (err, tasks) => {
|
||||
@@ -41,14 +39,15 @@ function split(value: string): string[] {
|
||||
previous = i + 1;
|
||||
}
|
||||
}
|
||||
pieces.push(value.substring(previous, i).match(/^"?(.+?)"?$/)[1]);
|
||||
var lastPiece = value.substring(previous, i).match(/^"?(.+?)"?$/);
|
||||
if (lastPiece) pieces.push(lastPiece[1]);
|
||||
return pieces;
|
||||
}
|
||||
|
||||
/**
|
||||
* Parses the configuration or reads the batch-mode file for tasks.
|
||||
*/
|
||||
function tasks(config: typings.IConfigLine, batchPath: string, done: (err: Error, tasks?: typings.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 => {
|
||||
return {address: address, config: config};
|
||||
@@ -58,7 +57,7 @@ function tasks(config: typings.IConfigLine, batchPath: string, done: (err: Error
|
||||
if (!exists) return done(null, []);
|
||||
fs.readFile(batchPath, 'utf8', (err, data) => {
|
||||
if (err) return done(err);
|
||||
var map: typings.IConfigTask[] = [];
|
||||
var map: IConfigTask[] = [];
|
||||
data.split(/\r?\n/).forEach(line => {
|
||||
if (/^(\/\/|#)/.test(line)) return;
|
||||
var lineConfig = parse(process.argv.concat(split(line)));
|
||||
@@ -75,7 +74,7 @@ function tasks(config: typings.IConfigLine, batchPath: string, done: (err: Error
|
||||
/**
|
||||
* Parses the arguments and returns a configuration.
|
||||
*/
|
||||
function parse(args: string[]): typings.IConfigLine {
|
||||
function parse(args: string[]): IConfigLine {
|
||||
return new commander.Command().version(require('../package').version)
|
||||
// Authentication
|
||||
.option('-p, --pass <s>', 'The password.')
|
||||
|
||||
@@ -1,5 +1,5 @@
|
||||
'use strict';
|
||||
import batch = require('./batch');
|
||||
import batch from './batch';
|
||||
|
||||
batch(process.argv, (err: any) => {
|
||||
if (err) console.error(err.stack || err);
|
||||
|
||||
@@ -1,19 +1,17 @@
|
||||
'use strict';
|
||||
export = main;
|
||||
import cheerio = require('cheerio');
|
||||
import fs = require('fs');
|
||||
import mkdirp = require('mkdirp');
|
||||
import request = require('./request');
|
||||
import path = require('path');
|
||||
import subtitle = require('./subtitle/index');
|
||||
import typings = require('./typings');
|
||||
import video = require('./video/index');
|
||||
import subtitle from './subtitle/index';
|
||||
import video from './video/index';
|
||||
import xml2js = require('xml2js');
|
||||
|
||||
/**
|
||||
* Streams the episode to disk.
|
||||
*/
|
||||
function main(config: typings.IConfig, address: string, done: (err: Error) => void) {
|
||||
export default function(config: IConfig, address: string, done: (err: Error) => void) {
|
||||
scrapePage(config, address, (err, page) => {
|
||||
if (err) return done(err);
|
||||
scrapePlayer(config, address, page.id, (err, player) => {
|
||||
@@ -38,7 +36,7 @@ function complete(message: string, begin: number, done: (err: Error) => void) {
|
||||
/**
|
||||
* Downloads the subtitle and video.
|
||||
*/
|
||||
function download(config: typings.IConfig, page: typings.IEpisodePage, player: typings.IEpisodePlayer, done: (err: Error) => void) {
|
||||
function download(config: IConfig, page: IEpisodePage, player: IEpisodePlayer, done: (err: Error) => void) {
|
||||
var series = config.series || page.series;
|
||||
var fileName = name(config, page, series);
|
||||
var filePath = path.join(config.output || process.cwd(), series, fileName);
|
||||
@@ -52,7 +50,7 @@ function download(config: typings.IConfig, page: typings.IEpisodePage, player: t
|
||||
if (err) return done(err);
|
||||
if (config.merge) return complete('Finished ' + fileName, now, done);
|
||||
var isSubtited = Boolean(player.subtitle);
|
||||
video.merge(config, isSubtited, player.video.file, filePath, err => {
|
||||
video.merge(config, isSubtited, player.video.file, filePath, player.mode, err => {
|
||||
if (err) return done(err);
|
||||
complete('Finished ' + fileName, now, done);
|
||||
});
|
||||
@@ -64,7 +62,7 @@ function download(config: typings.IConfig, page: typings.IEpisodePage, player: t
|
||||
/**
|
||||
* Saves the subtitles to disk.
|
||||
*/
|
||||
function downloadSubtitle(config: typings.IConfig, player: typings.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();
|
||||
subtitle.decode(enc.id, enc.iv, enc.data, (err, data) => {
|
||||
@@ -81,23 +79,24 @@ function downloadSubtitle(config: typings.IConfig, player: typings.IEpisodePlaye
|
||||
/**
|
||||
* Streams the video to disk.
|
||||
*/
|
||||
function downloadVideo(config: typings.IConfig,
|
||||
page: typings.IEpisodePage,
|
||||
player: typings.IEpisodePlayer,
|
||||
function downloadVideo(config: IConfig,
|
||||
page: IEpisodePage,
|
||||
player: IEpisodePlayer,
|
||||
filePath: string,
|
||||
done: (err: Error) => void) {
|
||||
video.stream(
|
||||
player.video.host,
|
||||
player.video.file,
|
||||
page.swf,
|
||||
filePath + path.extname(player.video.file),
|
||||
filePath, path.extname(player.video.file),
|
||||
player.video.mode,
|
||||
done);
|
||||
}
|
||||
|
||||
/**
|
||||
* Names the file based on the config, page, series and tag.
|
||||
*/
|
||||
function name(config: typings.IConfig, page: typings.IEpisodePage, series: string) {
|
||||
function name(config: IConfig, page: IEpisodePage, series: string) {
|
||||
var episode = (page.episode < 10 ? '0' : '') + page.episode;
|
||||
var volume = (page.volume < 10 ? '0' : '') + page.volume;
|
||||
var tag = config.tag || 'CrunchyRoll';
|
||||
@@ -116,7 +115,7 @@ function prefix(value: number|string, length: number) {
|
||||
/**
|
||||
* Requests the page data and scrapes the id, episode, series and swf.
|
||||
*/
|
||||
function scrapePage(config: typings.IConfig, address: string, done: (err: Error, page?: typings.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.'));
|
||||
request.get(config, address, (err, result) => {
|
||||
@@ -139,7 +138,7 @@ function scrapePage(config: typings.IConfig, address: string, done: (err: Error,
|
||||
/**
|
||||
* Requests the player data and scrapes the subtitle and video data.
|
||||
*/
|
||||
function scrapePlayer(config: typings.IConfig, address: string, id: number, done: (err: Error, player?: typings.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.'));
|
||||
request.post(config, {
|
||||
@@ -150,10 +149,15 @@ function scrapePlayer(config: typings.IConfig, address: string, id: number, done
|
||||
xml2js.parseString(result, {
|
||||
explicitArray: false,
|
||||
explicitRoot: false
|
||||
}, (err: Error, player: typings.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";
|
||||
}
|
||||
done(null, {
|
||||
subtitle: isSubtitled ? {
|
||||
id: parseInt(player['default:preload'].subtitle.$.id, 10),
|
||||
@@ -161,6 +165,7 @@ function scrapePlayer(config: typings.IConfig, address: string, id: number, done
|
||||
data: player['default:preload'].subtitle.data
|
||||
} : null,
|
||||
video: {
|
||||
mode: streamMode;
|
||||
file: player['default:preload'].stream_info.file,
|
||||
host: player['default:preload'].stream_info.host
|
||||
}
|
||||
|
||||
@@ -1,4 +1,5 @@
|
||||
'use strict';
|
||||
export import batch = require('./batch');
|
||||
export import episode = require('./episode');
|
||||
export import series = require('./series');
|
||||
import batch from './batch';
|
||||
import episode from './episode';
|
||||
import series from './series';
|
||||
export {batch, episode, series};
|
||||
|
||||
16
src/interface/IConfig.d.ts
vendored
Normal file
16
src/interface/IConfig.d.ts
vendored
Normal file
@@ -0,0 +1,16 @@
|
||||
interface IConfig {
|
||||
// Authentication
|
||||
pass?: string;
|
||||
user?: string;
|
||||
// Disables
|
||||
cache?: boolean;
|
||||
merge?: boolean;
|
||||
// Filters
|
||||
episode?: number;
|
||||
volume?: number;
|
||||
// Settings
|
||||
format?: string;
|
||||
output?: string;
|
||||
series?: string;
|
||||
tag?: string;
|
||||
}
|
||||
3
src/interface/IConfigLine.d.ts
vendored
Normal file
3
src/interface/IConfigLine.d.ts
vendored
Normal file
@@ -0,0 +1,3 @@
|
||||
interface IConfigLine extends IConfig {
|
||||
args: string[];
|
||||
}
|
||||
4
src/interface/IConfigTask.d.ts
vendored
Normal file
4
src/interface/IConfigTask.d.ts
vendored
Normal file
@@ -0,0 +1,4 @@
|
||||
interface IConfigTask {
|
||||
address: string;
|
||||
config: IConfigLine;
|
||||
}
|
||||
7
src/interface/IEpisodePage.d.ts
vendored
Normal file
7
src/interface/IEpisodePage.d.ts
vendored
Normal file
@@ -0,0 +1,7 @@
|
||||
interface IEpisodePage {
|
||||
id: number;
|
||||
episode: number;
|
||||
series: string;
|
||||
volume: number;
|
||||
swf: string;
|
||||
}
|
||||
11
src/interface/IEpisodePlayer.d.ts
vendored
Normal file
11
src/interface/IEpisodePlayer.d.ts
vendored
Normal file
@@ -0,0 +1,11 @@
|
||||
interface IEpisodePlayer {
|
||||
subtitle?: {
|
||||
id: number;
|
||||
iv: string;
|
||||
data: string;
|
||||
};
|
||||
video: {
|
||||
file: string;
|
||||
host: string;
|
||||
};
|
||||
}
|
||||
15
src/interface/IEpisodePlayerConfig.d.ts
vendored
Normal file
15
src/interface/IEpisodePlayerConfig.d.ts
vendored
Normal file
@@ -0,0 +1,15 @@
|
||||
interface IEpisodePlayerConfig {
|
||||
'default:preload': {
|
||||
subtitle: {
|
||||
$: {
|
||||
id: string;
|
||||
};
|
||||
iv: string;
|
||||
data: string;
|
||||
};
|
||||
stream_info: {
|
||||
file: string;
|
||||
host: string;
|
||||
};
|
||||
};
|
||||
}
|
||||
3
src/interface/IFormatterTable.d.ts
vendored
Normal file
3
src/interface/IFormatterTable.d.ts
vendored
Normal file
@@ -0,0 +1,3 @@
|
||||
interface IFormatterTable {
|
||||
[key: string]: (input: string|Buffer, done: (err: Error, subtitle?: string) => void) => void;
|
||||
}
|
||||
4
src/interface/ISeries.d.ts
vendored
Normal file
4
src/interface/ISeries.d.ts
vendored
Normal file
@@ -0,0 +1,4 @@
|
||||
interface ISeries {
|
||||
episodes: ISeriesEpisode[];
|
||||
series: string;
|
||||
}
|
||||
5
src/interface/ISeriesEpisode.d.ts
vendored
Normal file
5
src/interface/ISeriesEpisode.d.ts
vendored
Normal file
@@ -0,0 +1,5 @@
|
||||
interface ISeriesEpisode {
|
||||
address: string;
|
||||
episode: number;
|
||||
volume: number;
|
||||
}
|
||||
13
src/interface/ISubtitle.d.ts
vendored
Normal file
13
src/interface/ISubtitle.d.ts
vendored
Normal file
@@ -0,0 +1,13 @@
|
||||
interface ISubtitle {
|
||||
$: {
|
||||
title: string;
|
||||
wrap_style: string;
|
||||
play_res_x: string;
|
||||
play_res_y: string;
|
||||
id: string;
|
||||
lang_string: string;
|
||||
created: string;
|
||||
};
|
||||
events: ISubtitleEvent;
|
||||
styles: ISubtitleStyle;
|
||||
}
|
||||
15
src/interface/ISubtitleEvent.d.ts
vendored
Normal file
15
src/interface/ISubtitleEvent.d.ts
vendored
Normal file
@@ -0,0 +1,15 @@
|
||||
interface ISubtitleEvent {
|
||||
event: {
|
||||
$: {
|
||||
end: string;
|
||||
start: string;
|
||||
style: string;
|
||||
name: string;
|
||||
margin_l: string;
|
||||
margin_r: string;
|
||||
margin_v: string;
|
||||
effect: string;
|
||||
text: string;
|
||||
};
|
||||
}[];
|
||||
}
|
||||
29
src/interface/ISubtitleStyle.d.ts
vendored
Normal file
29
src/interface/ISubtitleStyle.d.ts
vendored
Normal file
@@ -0,0 +1,29 @@
|
||||
interface ISubtitleStyle {
|
||||
style: {
|
||||
$: {
|
||||
name: string;
|
||||
font_name: string;
|
||||
font_size: string;
|
||||
primary_colour: string;
|
||||
secondary_colour: string;
|
||||
outline_colour: string;
|
||||
back_colour: string;
|
||||
bold: string;
|
||||
italic: string;
|
||||
underline: string;
|
||||
strikeout: string;
|
||||
scale_x: string;
|
||||
scale_y: string;
|
||||
spacing: string;
|
||||
angle: string;
|
||||
border_style: string;
|
||||
outline: string;
|
||||
shadow: string;
|
||||
alignment: string;
|
||||
margin_l: string;
|
||||
margin_r: string;
|
||||
margin_v: string;
|
||||
encoding: string;
|
||||
};
|
||||
}[];
|
||||
}
|
||||
@@ -1,12 +1,11 @@
|
||||
'use strict';
|
||||
import request = require('request');
|
||||
import typings = require('./typings');
|
||||
var isAuthenticated = false;
|
||||
|
||||
/**
|
||||
* Performs a GET request for the resource.
|
||||
*/
|
||||
export function get(config: typings.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);
|
||||
request.get(modify(options), (err: Error, response: any, body: any) => {
|
||||
@@ -19,7 +18,7 @@ export function get(config: typings.IConfig, options: request.Options, done: (er
|
||||
/**
|
||||
* Performs a POST request for the resource.
|
||||
*/
|
||||
export function post(config: typings.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);
|
||||
request.post(modify(options), (err: Error, response: any, body: any) => {
|
||||
@@ -32,7 +31,7 @@ export function post(config: typings.IConfig, options: request.Options, done: (e
|
||||
/**
|
||||
* Authenticates using the configured pass and user.
|
||||
*/
|
||||
function authenticate(config: typings.IConfig, done: (err: Error) => void) {
|
||||
function authenticate(config: IConfig, done: (err: Error) => void) {
|
||||
if (isAuthenticated || !config.pass || !config.user) return done(null);
|
||||
var options = {
|
||||
form: {
|
||||
|
||||
@@ -1,18 +1,16 @@
|
||||
'use strict';
|
||||
export = main;
|
||||
import cheerio = require('cheerio');
|
||||
import episode = require('./episode');
|
||||
import episode from './episode';
|
||||
import fs = require('fs');
|
||||
import request = require('./request');
|
||||
import path = require('path');
|
||||
import typings = require('./typings');
|
||||
import url = require('url');
|
||||
var persistent = '.crpersistent';
|
||||
|
||||
/**
|
||||
* Streams the series to disk.
|
||||
*/
|
||||
function main(config: typings.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) => {
|
||||
var cache = config.cache ? {} : JSON.parse(contents || '{}');
|
||||
@@ -39,9 +37,9 @@ function main(config: typings.IConfig, address: string, done: (err: Error) => vo
|
||||
* Downloads the episode.
|
||||
*/
|
||||
function download(cache: {[address: string]: number},
|
||||
config: typings.IConfig,
|
||||
config: IConfig,
|
||||
baseAddress: string,
|
||||
item: typings.ISeriesEpisode,
|
||||
item: ISeriesEpisode,
|
||||
done: (err: Error) => void) {
|
||||
if (!filter(config, item)) return done(null);
|
||||
var address = url.resolve(baseAddress, item.address);
|
||||
@@ -56,7 +54,7 @@ function download(cache: {[address: string]: number},
|
||||
/**
|
||||
* Filters the item based on the configuration.
|
||||
*/
|
||||
function filter(config: typings.IConfig, item: typings.ISeriesEpisode) {
|
||||
function filter(config: IConfig, item: ISeriesEpisode) {
|
||||
// Filter on chapter.
|
||||
var episodeFilter = config.episode;
|
||||
if (episodeFilter > 0 && item.episode <= episodeFilter) return false;
|
||||
@@ -72,13 +70,13 @@ function filter(config: typings.IConfig, item: typings.ISeriesEpisode) {
|
||||
/**
|
||||
* Requests the page and scrapes the episodes and series.
|
||||
*/
|
||||
function page(config: typings.IConfig, address: string, done: (err: Error, result?: typings.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);
|
||||
var $ = cheerio.load(result);
|
||||
var title = $('span[itemprop=name]').text();
|
||||
if (!title) return done(new Error('Invalid page.'));
|
||||
var episodes: typings.ISeriesEpisode[] = [];
|
||||
var episodes: ISeriesEpisode[] = [];
|
||||
$('.episode').each((i, el) => {
|
||||
if ($(el).children('img[src*=coming_soon]').length) return;
|
||||
var volume = /([0-9]+)\s*$/.exec($(el).closest('ul').prev('a').text());
|
||||
|
||||
@@ -1,6 +1,5 @@
|
||||
/* tslint:disable:no-bitwise false */
|
||||
'use strict';
|
||||
export = main;
|
||||
import crypto = require('crypto');
|
||||
import bigInt = require('big-integer');
|
||||
import zlib = require('zlib');
|
||||
@@ -8,7 +7,7 @@ import zlib = require('zlib');
|
||||
/**
|
||||
* Decodes the data.
|
||||
*/
|
||||
function main(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 {
|
||||
decompress(decrypt(id, iv, data), done);
|
||||
} catch (e) {
|
||||
|
||||
@@ -1,16 +1,14 @@
|
||||
'use strict';
|
||||
export = main;
|
||||
import xml2js = require('xml2js');
|
||||
import typings = require('../../typings');
|
||||
|
||||
/**
|
||||
* Converts an input buffer to a SubStation Alpha subtitle.
|
||||
*/
|
||||
function main(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(), {
|
||||
explicitArray: false,
|
||||
explicitRoot: false
|
||||
}, (err: Error, xml: typings.ISubtitle) => {
|
||||
}, (err: Error, xml: ISubtitle) => {
|
||||
if (err) return done(err);
|
||||
try {
|
||||
done(null, script(xml) + '\n' +
|
||||
@@ -25,7 +23,7 @@ function main(input: string|Buffer, done: (err: Error, subtitle?: string) => voi
|
||||
/**
|
||||
* Converts the event block.
|
||||
*/
|
||||
function event(block: typings.ISubtitleEvent): string {
|
||||
function event(block: ISubtitleEvent): string {
|
||||
var format = 'Layer,Start,End,Style,Name,MarginL,MarginR,MarginV,Effect,Text';
|
||||
return '[Events]\n' +
|
||||
'Format: ' + format + '\n' +
|
||||
@@ -44,7 +42,7 @@ function event(block: typings.ISubtitleEvent): string {
|
||||
/**
|
||||
* Converts the script block.
|
||||
*/
|
||||
function script(block: typings.ISubtitle): string {
|
||||
function script(block: ISubtitle): string {
|
||||
return '[Script Info]\n' +
|
||||
'Title: ' + block.$.title + '\n' +
|
||||
'ScriptType: v4.00+\n' +
|
||||
@@ -59,7 +57,7 @@ function script(block: typings.ISubtitle): string {
|
||||
/**
|
||||
* Converts the style block.
|
||||
*/
|
||||
function style(block: typings.ISubtitleStyle): string {
|
||||
function style(block: ISubtitleStyle): string {
|
||||
var format = 'Name,Fontname,Fontsize,PrimaryColour,SecondaryColour,' +
|
||||
'OutlineColour,BackColour,Bold,Italic,Underline,StrikeOut,ScaleX,' +
|
||||
'ScaleY,Spacing,Angle,BorderStyle,Outline,Shadow,Alignment,' +
|
||||
|
||||
@@ -1,10 +1,8 @@
|
||||
'use strict';
|
||||
export = main;
|
||||
import ass = require('./ass');
|
||||
import srt = require('./srt');
|
||||
import typings = require('../../typings');
|
||||
import ass from './ass';
|
||||
import srt from './srt';
|
||||
|
||||
var main: typings.IFormatterTable = {
|
||||
export default <IFormatterTable> {
|
||||
ass: ass,
|
||||
srt: srt
|
||||
};
|
||||
|
||||
@@ -1,14 +1,12 @@
|
||||
'use strict';
|
||||
export = srt;
|
||||
import xml2js = require('xml2js');
|
||||
import typings = require('../../typings');
|
||||
|
||||
/**
|
||||
* Converts an input buffer to a SubRip subtitle.
|
||||
*/
|
||||
function srt(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: typings.ISubtitle) => {
|
||||
xml2js.parseString(input.toString(), options, (err: Error, xml: ISubtitle) => {
|
||||
try {
|
||||
if (err) return done(err);
|
||||
done(null, xml.events.event.map((event, index) => {
|
||||
|
||||
@@ -1,3 +1,4 @@
|
||||
'use strict';
|
||||
export import decode = require('./decode');
|
||||
export import formats = require('./formats/index');
|
||||
import decode from './decode';
|
||||
import formats from './formats/index';
|
||||
export default {decode, formats};
|
||||
|
||||
136
src/typings.ts
136
src/typings.ts
@@ -1,136 +0,0 @@
|
||||
export interface IConfig {
|
||||
// Authentication
|
||||
pass?: string;
|
||||
user?: string;
|
||||
// Disables
|
||||
cache?: boolean;
|
||||
merge?: boolean;
|
||||
// Filters
|
||||
episode?: number;
|
||||
volume?: number;
|
||||
// Settings
|
||||
format?: string;
|
||||
output?: string;
|
||||
series?: string;
|
||||
tag?: string;
|
||||
}
|
||||
|
||||
export interface IConfigLine extends IConfig {
|
||||
args: string[];
|
||||
}
|
||||
|
||||
export interface IConfigTask {
|
||||
address: string;
|
||||
config: IConfigLine;
|
||||
}
|
||||
|
||||
export interface IEpisodePage {
|
||||
id: number;
|
||||
episode: number;
|
||||
series: string;
|
||||
volume: number;
|
||||
swf: string;
|
||||
}
|
||||
|
||||
export interface IEpisodePlayer {
|
||||
subtitle?: {
|
||||
id: number;
|
||||
iv: string;
|
||||
data: string;
|
||||
};
|
||||
video: {
|
||||
file: string;
|
||||
host: string;
|
||||
};
|
||||
}
|
||||
|
||||
export interface IEpisodePlayerConfig {
|
||||
'default:preload': {
|
||||
subtitle: {
|
||||
$: {
|
||||
id: string;
|
||||
};
|
||||
iv: string;
|
||||
data: string;
|
||||
};
|
||||
stream_info: {
|
||||
file: string;
|
||||
host: string;
|
||||
};
|
||||
};
|
||||
}
|
||||
|
||||
export interface IFormatterTable {
|
||||
[key: string]: (input: string|Buffer, done: (err: Error, subtitle?: string) => void) => void;
|
||||
}
|
||||
|
||||
export interface ISeries {
|
||||
episodes: ISeriesEpisode[];
|
||||
series: string;
|
||||
}
|
||||
|
||||
export interface ISeriesEpisode {
|
||||
address: string;
|
||||
episode: number;
|
||||
volume: number;
|
||||
}
|
||||
|
||||
export interface ISubtitle {
|
||||
$: {
|
||||
title: string;
|
||||
wrap_style: string;
|
||||
play_res_x: string;
|
||||
play_res_y: string;
|
||||
id: string;
|
||||
lang_string: string;
|
||||
created: string;
|
||||
};
|
||||
events: ISubtitleEvent;
|
||||
styles: ISubtitleStyle;
|
||||
}
|
||||
|
||||
export interface ISubtitleEvent {
|
||||
event: {
|
||||
$: {
|
||||
end: string;
|
||||
start: string;
|
||||
style: string;
|
||||
name: string;
|
||||
margin_l: string;
|
||||
margin_r: string;
|
||||
margin_v: string;
|
||||
effect: string;
|
||||
text: string;
|
||||
};
|
||||
}[];
|
||||
}
|
||||
|
||||
export interface ISubtitleStyle {
|
||||
style: {
|
||||
$: {
|
||||
name: string;
|
||||
font_name: string;
|
||||
font_size: string;
|
||||
primary_colour: string;
|
||||
secondary_colour: string;
|
||||
outline_colour: string;
|
||||
back_colour: string;
|
||||
bold: string;
|
||||
italic: string;
|
||||
underline: string;
|
||||
strikeout: string;
|
||||
scale_x: string;
|
||||
scale_y: string;
|
||||
spacing: string;
|
||||
angle: string;
|
||||
border_style: string;
|
||||
outline: string;
|
||||
shadow: string;
|
||||
alignment: string;
|
||||
margin_l: string;
|
||||
margin_r: string;
|
||||
margin_v: string;
|
||||
encoding: string;
|
||||
};
|
||||
}[];
|
||||
}
|
||||
@@ -1,3 +1,4 @@
|
||||
'use strict';
|
||||
export import merge = require('./merge');
|
||||
export import stream = require('./stream');
|
||||
import merge from './merge';
|
||||
import stream from './stream';
|
||||
export default {merge, stream};
|
||||
|
||||
@@ -1,18 +1,24 @@
|
||||
'use strict';
|
||||
export = main;
|
||||
import childProcess = require('child_process');
|
||||
import fs = require('fs');
|
||||
import path = require('path');
|
||||
import os = require('os');
|
||||
import subtitle = require('../subtitle/index');
|
||||
import typings = require('../typings');
|
||||
import subtitle from '../subtitle/index';
|
||||
|
||||
/**
|
||||
* Merges the subtitle and video files into a Matroska Multimedia Container.
|
||||
*/
|
||||
function main(config: typings.IConfig, isSubtitled: boolean, rtmpInputPath: string, filePath: string, done: (err: Error) => void) {
|
||||
export default function(config: IConfig, isSubtitled: boolean, rtmpInputPath: string, filePath: string, streamMode: string, done: (err: Error) => void) {
|
||||
var subtitlePath = filePath + '.' + (subtitle.formats[config.format] ? config.format : 'ass');
|
||||
var videoPath = filePath + path.extname(rtmpInputPath);
|
||||
var videoPath = filePath;
|
||||
if (streamMode == "RTMP")
|
||||
{
|
||||
videoPath += path.extname(rtmpInputPath);
|
||||
}
|
||||
else
|
||||
{
|
||||
videoPath += ".mp4";
|
||||
}
|
||||
childProcess.exec(command() + ' ' +
|
||||
'-o "' + filePath + '.mkv" ' +
|
||||
'"' + videoPath + '" ' +
|
||||
|
||||
@@ -1,5 +1,4 @@
|
||||
'use strict';
|
||||
export = main;
|
||||
import childProcess = require('child_process');
|
||||
import path = require('path');
|
||||
import os = require('os');
|
||||
@@ -7,20 +6,38 @@ import os = require('os');
|
||||
/**
|
||||
* Streams the video to disk.
|
||||
*/
|
||||
function main(rtmpUrl: string, rtmpInputPath: string, swfUrl: string, filePath: string, done: (err: Error) => void) {
|
||||
childProcess.exec(command() + ' ' +
|
||||
'-r "' + rtmpUrl + '" ' +
|
||||
'-y "' + rtmpInputPath + '" ' +
|
||||
'-W "' + swfUrl + '" ' +
|
||||
'-o "' + filePath + '"', {
|
||||
maxBuffer: Infinity
|
||||
}, done);
|
||||
export default function(rtmpUrl: string, rtmpInputPath: string, swfUrl: string, filePath: string, fileExt: string, mode: string, done: (err: Error) => void) {
|
||||
if (mode == "RTMP")
|
||||
{
|
||||
childProcess.exec(command("rtmpdump") + ' ' +
|
||||
'-r "' + rtmpUrl + '" ' +
|
||||
'-y "' + rtmpInputPath + '" ' +
|
||||
'-W "' + swfUrl + '" ' +
|
||||
'-o "' + filePath + fileExt + '"', {
|
||||
maxBuffer: Infinity
|
||||
}, done);
|
||||
}
|
||||
else if (mode == "HLS")
|
||||
{
|
||||
console.info("Experimental FFMPEG, MAY FAIL!!!");
|
||||
var cmd=command("ffmpeg") + ' ' +
|
||||
'-i "' + rtmpInputPath + '" ' +
|
||||
'-c copy -bsf:a aac_adtstoasc ' +
|
||||
'"' + filePath + '.mp4"';
|
||||
childProcess.exec(cmd, {
|
||||
maxBuffer: Infinity
|
||||
}, done);
|
||||
}
|
||||
else
|
||||
{
|
||||
console.error("No such mode: " + mode);
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* Determines the command for the operating system.
|
||||
*/
|
||||
function command(): string {
|
||||
if (os.platform() !== 'win32') return 'rtmpdump';
|
||||
return '"' + path.join(__dirname, '../../bin/rtmpdump.exe') + '"';
|
||||
function command(exe: string): string {
|
||||
if (os.platform() !== 'win32') return exe;
|
||||
return '"' + path.join(__dirname, '../../bin/' + exe + '.exe') + '"';
|
||||
}
|
||||
|
||||
11
ts.js
11
ts.js
@@ -4,9 +4,14 @@ var fs = require('fs');
|
||||
var path = require('path');
|
||||
var isTest = process.argv[2] === '--only-test';
|
||||
|
||||
// TODO: This file can use some cleaning up. We want to use the tsconfig.json
|
||||
// and go from there, but then without source maps. That should give us a final
|
||||
// build output. For now, this legacy build file will remain to do its job.
|
||||
// TODO: This build task should be removed upon release of TypeScript 1.5 with
|
||||
// the support for `tsconfig.json`. Invoking `tsc` from `package.json` will then
|
||||
// read the configuration and compile accordingly. It seems that `TSLint` will,
|
||||
// eventually, support this mechanism too. That prevents the need for any kind
|
||||
// of build task and will run entirely based on instructions from `npm`.
|
||||
//
|
||||
// Reference #1: https://github.com/Microsoft/TypeScript/issues/1667
|
||||
// Reference #2: https://github.com/palantir/tslint/issues/281
|
||||
|
||||
read(function(err, fileNames) {
|
||||
clean(fileNames, function() {
|
||||
|
||||
@@ -1,5 +1,5 @@
|
||||
{
|
||||
"version": "1.4.1",
|
||||
"version": "1.5.1-beta",
|
||||
"compilerOptions": {
|
||||
"declaration": true,
|
||||
"noImplicitAny": true,
|
||||
@@ -18,6 +18,18 @@
|
||||
"src/cli.ts",
|
||||
"src/episode.ts",
|
||||
"src/index.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",
|
||||
@@ -25,7 +37,6 @@
|
||||
"src/subtitle/formats/index.ts",
|
||||
"src/subtitle/formats/srt.ts",
|
||||
"src/subtitle/index.ts",
|
||||
"src/typings.ts",
|
||||
"src/video/index.ts",
|
||||
"src/video/merge.ts",
|
||||
"src/video/stream.ts",
|
||||
@@ -38,4 +49,4 @@
|
||||
"typings/request/request.d.ts",
|
||||
"typings/xml2js/xml2js.d.ts"
|
||||
]
|
||||
}
|
||||
}
|
||||
|
||||
Reference in New Issue
Block a user