Current state of development (~2 days in)
This commit is contained in:
parent
db3211cf8d
commit
5394596317
1
.gitignore
vendored
Normal file
1
.gitignore
vendored
Normal file
@ -0,0 +1 @@
|
||||
node_modules/
|
||||
60
.jscsrc
Normal file
60
.jscsrc
Normal file
@ -0,0 +1,60 @@
|
||||
{
|
||||
"disallowEmptyBlocks": true,
|
||||
"disallowImplicitTypeConversion": ["binary", "boolean", "numeric", "string"],
|
||||
"disallowKeywords": ["delete", "with"],
|
||||
"disallowKeywordsOnNewLine": ["catch", "else", "finally"],
|
||||
"disallowMixedSpacesAndTabs": true,
|
||||
"disallowMultipleLineBreaks": true,
|
||||
"disallowMultipleLineStrings": true,
|
||||
"disallowMultipleVarDecl": true,
|
||||
"disallowNewlineBeforeBlockStatements": true,
|
||||
"disallowPaddingNewlinesInBlocks": true,
|
||||
"disallowQuotedKeysInObjects": "allButReserved",
|
||||
"disallowSpaceAfterObjectKeys": true,
|
||||
"disallowSpaceAfterPrefixUnaryOperators": true,
|
||||
"disallowSpaceBeforePostfixUnaryOperators": true,
|
||||
"disallowSpacesInCallExpression": true,
|
||||
"disallowSpacesInFunction": {"beforeOpeningRoundBrace": true},
|
||||
"disallowSpacesInsideArrayBrackets": true,
|
||||
"disallowSpacesInsideObjectBrackets": true,
|
||||
"disallowSpacesInsideParentheses": true,
|
||||
"disallowTrailingComma": true,
|
||||
"disallowTrailingWhitespace": true,
|
||||
"disallowYodaConditions": true,
|
||||
"jsDoc": {
|
||||
"checkAnnotations": "closurecompiler",
|
||||
"checkParamNames": true,
|
||||
"checkRedundantAccess": true,
|
||||
"checkRedundantParams": true,
|
||||
"checkReturnTypes": true,
|
||||
"checkTypes": "strictNativeCase",
|
||||
"enforceExistence": true,
|
||||
"leadingUnderscoreAccess": true,
|
||||
"requireParamTypes": true,
|
||||
"requireReturnTypes": true
|
||||
},
|
||||
"maximumLineLength": 80,
|
||||
"plugins": ["jscs-jsdoc"],
|
||||
"requireBlocksOnNewline": true,
|
||||
"requireCamelCaseOrUpperCaseIdentifiers": true,
|
||||
"requireCapitalizedConstructors": true,
|
||||
"requireCommaBeforeLineBreak": true,
|
||||
"requireDotNotation": true,
|
||||
"requireFunctionDeclarations": true,
|
||||
"requireLineFeedAtFileEnd": true,
|
||||
"requireOperatorBeforeLineBreak": true,
|
||||
"requireParenthesesAroundIIFE": true,
|
||||
"requireSpaceAfterBinaryOperators": true,
|
||||
"requireSpaceAfterKeywords": ["case", "catch", "do", "else", "for", "if", "return", "switch", "try", "typeof", "void", "while", "with"],
|
||||
"requireSpaceAfterLineComment": true,
|
||||
"requireSpaceBeforeBinaryOperators": true,
|
||||
"requireSpaceBeforeBlockStatements": true,
|
||||
"requireSpaceBeforeObjectValues": true,
|
||||
"requireSpacesInConditionalExpression": true,
|
||||
"requireSpacesInFunction": {"beforeOpeningCurlyBrace": true},
|
||||
"safeContextKeyword": ["that"],
|
||||
"validateIndentation": 2,
|
||||
"validateParameterSeparator": ", ",
|
||||
"validateQuoteMarks": "'",
|
||||
"validateLineBreaks": "LF"
|
||||
}
|
||||
79
.jshintrc
Normal file
79
.jshintrc
Normal file
@ -0,0 +1,79 @@
|
||||
{
|
||||
"bitwise" : false,
|
||||
"camelcase" : false,
|
||||
"curly" : false,
|
||||
"eqeqeq" : true,
|
||||
"es3" : false,
|
||||
"forin" : true,
|
||||
"freeze" : true,
|
||||
"immed" : true,
|
||||
"indent" : 4,
|
||||
"latedef" : "nofunc",
|
||||
"newcap" : true,
|
||||
"noarg" : true,
|
||||
"noempty" : true,
|
||||
"nonbsp" : true,
|
||||
"nonew" : true,
|
||||
"plusplus" : true,
|
||||
"quotmark" : "single",
|
||||
"undef" : true,
|
||||
"unused" : true,
|
||||
"singleGroups" : true,
|
||||
"strict" : true,
|
||||
"maxparams" : 5,
|
||||
"maxdepth" : 5,
|
||||
"maxstatements": 25,
|
||||
"maxcomplexity": 5,
|
||||
"maxlen" : 80,
|
||||
|
||||
"asi" : false,
|
||||
"boss" : false,
|
||||
"debug" : false,
|
||||
"eqnull" : false,
|
||||
"evil" : false,
|
||||
"expr" : false,
|
||||
"esnext" : false,
|
||||
"funcscope" : false,
|
||||
"globalstrict" : false,
|
||||
"iterator" : false,
|
||||
"lastsemic" : false,
|
||||
"laxbreak" : false,
|
||||
"laxcomma" : false,
|
||||
"loopfunc" : false,
|
||||
"maxerr" : 50,
|
||||
"moz" : false,
|
||||
"multistr" : false,
|
||||
"notypeof" : false,
|
||||
"noyield" : false,
|
||||
"proto" : false,
|
||||
"scripturl" : false,
|
||||
"scope" : false,
|
||||
"shadow" : false,
|
||||
"sub" : false,
|
||||
"supernew" : false,
|
||||
"validthis" : false,
|
||||
"withstmt" : false,
|
||||
|
||||
"browser" : false,
|
||||
"browserify" : false,
|
||||
"couch" : false,
|
||||
"devel" : false,
|
||||
"dojo" : false,
|
||||
"jasmine" : false,
|
||||
"jquery" : false,
|
||||
"mootools" : false,
|
||||
"mocha" : false,
|
||||
"node" : true,
|
||||
"nonstandard" : false,
|
||||
"phantom" : false,
|
||||
"prototypejs" : false,
|
||||
"qunit" : false,
|
||||
"rhino" : false,
|
||||
"shelljs" : false,
|
||||
"typed" : false,
|
||||
"worker" : false,
|
||||
"wsh" : false,
|
||||
"yui" : false,
|
||||
|
||||
"globals" : []
|
||||
}
|
||||
19
LICENSE
Normal file
19
LICENSE
Normal file
@ -0,0 +1,19 @@
|
||||
Copyright (c) 2015 Roel van Uden
|
||||
|
||||
Permission is hereby granted, free of charge, to any person obtaining a copy
|
||||
of this software and associated documentation files (the "Software"), to
|
||||
deal in the Software without restriction, including without limitation the
|
||||
rights to use, copy, modify, merge, publish, distribute, sublicense, and/or
|
||||
sell copies of the Software, and to permit persons to whom the Software is
|
||||
furnished to do so, subject to the following conditions:
|
||||
|
||||
The above copyright notice and this permission notice shall be included in
|
||||
all copies or substantial portions of the Software.
|
||||
|
||||
THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
|
||||
IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
|
||||
FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
|
||||
AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
|
||||
LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING
|
||||
FROM, OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS
|
||||
IN THE SOFTWARE.
|
||||
46
README.md
46
README.md
@ -1,2 +1,44 @@
|
||||
# crunchyroll.js
|
||||
CrunchyRoll.js is capable of downloading anime from the popular CrunchyRoll streaming site.
|
||||
# CrunchyRoll.js
|
||||
|
||||
CrunchyRoll.js is capable of downloading anime from the popular CrunchyRoll
|
||||
streaming site. Each episode is stored with the original video encoding
|
||||
(usually H.264, MP4) and the configured subtitle format (ASS or SRT). The two
|
||||
output files can optionally be muxed into a single MKV file.
|
||||
|
||||
## Motivation
|
||||
|
||||
I **love** *CrunchyRoll*. They have been working hard to provide an amazing service to
|
||||
*anime* fans and provide the *absolute best* way to watch *anime* series in a legal
|
||||
way. As they offer a streaming service, they do not offer files to be downloaded
|
||||
and enjoyed while offline (or travelling). This is understandable from a
|
||||
business perspective, but extremely annoying for the end-user. I put this
|
||||
application together to enable the files to be stored and enjoyed while offline.
|
||||
Please do not download as much as you can and stop your premium subscription;
|
||||
without our financial support, they cannot give us such as great service!
|
||||
**Do not share downloaded files and delete them if you ever stop your
|
||||
premium subscription!**
|
||||
|
||||
## Status
|
||||
|
||||
### Implemented
|
||||
|
||||
* Subtitle decoding.
|
||||
* Subtitle converter for SRT subtitle output.
|
||||
* Video streaming.
|
||||
* Episode page scraping with subtitle saving and video streaming.
|
||||
|
||||
### Pending Implementation
|
||||
|
||||
* Detect and write the appropriate video extension (instead of hard-code mp4).
|
||||
* Improve SRT support for i, b and u.
|
||||
* Add ASS support.
|
||||
* Add muxing (MP4+ASS=MKV).
|
||||
* Add series API to download an entire series rather than per-episode.
|
||||
* Add batch-mode to queue a bunch of series and do incremental downloads.
|
||||
* Add authentication to the entire stack to support premium content.
|
||||
* Add CLI interface with all the options.
|
||||
* Enjoy beautiful anime series from disk when internet is down.
|
||||
|
||||
## Work In Progress
|
||||
|
||||
Open an issue or e-mail me directly. I'd be happy to answer your questions.
|
||||
|
||||
24
app.js
Normal file
24
app.js
Normal file
@ -0,0 +1,24 @@
|
||||
'use strict';
|
||||
|
||||
// TODO: Improve SRT support for <i>, <b> and <u>.
|
||||
// TODO: Add ASS support.
|
||||
// TODO: Add muxing (MP4+ASS=MKV).
|
||||
// TODO: Add series API to download an entire series rather than per-episode.
|
||||
// TODO: Add batch-mode to queue a bunch of series and do incremental downloads.
|
||||
// TODO: Add authentication to the entire stack to support premium content.
|
||||
// TODO: Add CLI interface with all the options.
|
||||
|
||||
var config = {
|
||||
format: undefined, // defaults to srt
|
||||
path: undefined, // defaults to process.cwd()
|
||||
tag: undefined, // defaults to CrunchyRoll
|
||||
};
|
||||
|
||||
var episode = require('./src/episode');
|
||||
episode(
|
||||
config,
|
||||
'http://www.crunchyroll.com/fairy-tail/episode-1-the-dragon-king-652167',
|
||||
function(err) {
|
||||
if (err) return console.log(err);
|
||||
console.log('All done!');
|
||||
});
|
||||
BIN
bin/rtmpdump.exe
Normal file
BIN
bin/rtmpdump.exe
Normal file
Binary file not shown.
17
package.json
Normal file
17
package.json
Normal file
@ -0,0 +1,17 @@
|
||||
{
|
||||
"name": "crunchyroll",
|
||||
"version": "1.0.0",
|
||||
"description": "",
|
||||
"main": "app.js",
|
||||
"scripts": {
|
||||
"test": "echo \"Error: no test specified\" && exit 1"
|
||||
},
|
||||
"author": "",
|
||||
"license": "ISC",
|
||||
"dependencies": {
|
||||
"big-integer": "^1.4.1",
|
||||
"cheerio": "^0.18.0",
|
||||
"request": "^2.51.0",
|
||||
"xml2js": "^0.4.4"
|
||||
}
|
||||
}
|
||||
137
src/episode.js
Normal file
137
src/episode.js
Normal file
@ -0,0 +1,137 @@
|
||||
'use strict';
|
||||
var cheerio = require('cheerio');
|
||||
var fs = require('fs');
|
||||
var request = require('request');
|
||||
var path = require('path');
|
||||
var subtitle = require('./subtitle');
|
||||
var video = require('./video');
|
||||
var xml2js = require('xml2js');
|
||||
|
||||
/**
|
||||
* Streams the episode video and subtitle to disk.
|
||||
* @param {Object} config
|
||||
* @param {string} address
|
||||
* @param {function(Error)} done
|
||||
*/
|
||||
module.exports = function (config, address, done) {
|
||||
_page(address, function(err, page) {
|
||||
if (err) return done(err);
|
||||
_player(address, page.id, function(err, player) {
|
||||
if (err) return done(err);
|
||||
_download(config, page, player, done);
|
||||
});
|
||||
});
|
||||
};
|
||||
|
||||
/**
|
||||
* Downloads the subtitle and video.
|
||||
* @param {Object} config
|
||||
* @param {Object} page
|
||||
* @param {Object} player
|
||||
* @param {function(Error)} done
|
||||
*/
|
||||
function _download(config, page, player, done) {
|
||||
var tag = config.tag || 'CrunchyRoll';
|
||||
var episode = (page.episode < 10 ? '0' : '') + page.episode;
|
||||
var fileName = page.series + ' - ' + episode + ' [' + tag + ']';
|
||||
var filePath = path.join(config.path || process.cwd(), fileName);
|
||||
_subtitle(config, player, filePath, function(err) {
|
||||
if (err) return done(err);
|
||||
_video(config, page, player, filePath, done);
|
||||
});
|
||||
}
|
||||
|
||||
/**
|
||||
* Requests the page data and scrapes the id, episode, series and swf.
|
||||
* @private
|
||||
* @param {string} address
|
||||
* @param {function(Error, Object=)} done
|
||||
*/
|
||||
function _page(address, done) {
|
||||
var id = parseInt((address.match(/[0-9]+$/) || [0])[0], 10);
|
||||
if (!id) return done(new Error('Invalid address.'));
|
||||
request.get(address, function(err, res, body) {
|
||||
if (err) return done(err);
|
||||
var $ = cheerio.load(body);
|
||||
var swf = /^([^?]+)/.exec($('link[rel=video_src]').attr('href'));
|
||||
var title = /Watch ([\w\W]+) Episode ([0-9]+)/.exec($('title').text());
|
||||
if (!swf || !title) return done(new Error('Invalid page.'));
|
||||
done(undefined, {
|
||||
id: id,
|
||||
episode: parseInt(title[2], 10),
|
||||
series: title[1],
|
||||
swf: swf[1]
|
||||
});
|
||||
});
|
||||
}
|
||||
|
||||
/**
|
||||
* Requests the player data and scrapes the subtitle and video data.
|
||||
* @private
|
||||
* @param {string} address
|
||||
* @param {number} id
|
||||
* @param {function(Error, Object=)} done
|
||||
*/
|
||||
function _player(address, id, done) {
|
||||
var url = address.match(/^(https?:\/\/[^\/]+)/);
|
||||
if (!url) return done(new Error('Invalid address.'));
|
||||
request.post({
|
||||
form: {current_page: address},
|
||||
url: url[1] + '/xml/?req=RpcApiVideoPlayer_GetStandardConfig&media_id=' + id
|
||||
}, function(err, res, xml) {
|
||||
if (err) return done(err);
|
||||
xml2js.parseString(xml, {
|
||||
explicitArray: false,
|
||||
explicitRoot: false
|
||||
}, function(err, player) {
|
||||
if (err) return done(err);
|
||||
done(undefined, {
|
||||
subtitle: {
|
||||
id: player['default:preload'].subtitle.$.id,
|
||||
iv: player['default:preload'].subtitle.iv,
|
||||
data: player['default:preload'].subtitle.data
|
||||
},
|
||||
video: {
|
||||
file: player['default:preload'].stream_info.file,
|
||||
host: player['default:preload'].stream_info.host
|
||||
}
|
||||
});
|
||||
});
|
||||
});
|
||||
}
|
||||
|
||||
/**
|
||||
* Saves the subtitles to disk.
|
||||
* @param {Object} config
|
||||
* @param {Object} player
|
||||
* @param {string} filePath
|
||||
* @param {function(Error)} done
|
||||
*/
|
||||
function _subtitle(config, player, filePath, done) {
|
||||
var contents = player.subtitle;
|
||||
subtitle.decode(contents.id, contents.iv, contents.data, function(err, data) {
|
||||
if (err) return done(err);
|
||||
var format = subtitle.formats[config.format] ? config.format : 'srt';
|
||||
subtitle.formats[format](data, function(err, decodedSubtitle) {
|
||||
if (err) return done(err);
|
||||
fs.writeFile(filePath + '.' + format, decodedSubtitle, done);
|
||||
});
|
||||
});
|
||||
}
|
||||
|
||||
/**
|
||||
* Streams the video to disk.
|
||||
* @param {Object} config
|
||||
* @param {Object} page
|
||||
* @param {Object} player
|
||||
* @param {string} filePath
|
||||
* @param {function(Error)} done
|
||||
*/
|
||||
function _video(config, page, player, filePath, done) {
|
||||
video.stream(
|
||||
player.video.host,
|
||||
player.video.file,
|
||||
page.swf,
|
||||
filePath + '.mp4',
|
||||
done);
|
||||
}
|
||||
98
src/subtitle/decode.js
Normal file
98
src/subtitle/decode.js
Normal file
@ -0,0 +1,98 @@
|
||||
'use strict';
|
||||
var crypto = require('crypto');
|
||||
var bigInt = require('big-integer');
|
||||
var zlib = require('zlib');
|
||||
|
||||
/**
|
||||
* Decodes the data.
|
||||
* @param {number} id
|
||||
* @param {(Buffer|string)} iv
|
||||
* @param {(Buffer|string)} data
|
||||
* @param {function(Error, Buffer=)} done
|
||||
*/
|
||||
module.exports = function(id, iv, data, done) {
|
||||
try {
|
||||
_decompress(_decrypt(id, iv, data), done);
|
||||
} catch(e) {
|
||||
done(e);
|
||||
}
|
||||
};
|
||||
|
||||
/**
|
||||
* Decrypts the data.
|
||||
* @private
|
||||
* @param {number} id
|
||||
* @param {(Buffer|string)} iv
|
||||
* @param {(Buffer|string)} data
|
||||
* @return {Buffer}
|
||||
*/
|
||||
function _decrypt(id, iv, data) {
|
||||
if (typeof iv === 'string') iv = new Buffer(iv, 'base64');
|
||||
if (typeof data === 'string') data = new Buffer(data, 'base64');
|
||||
var decipher = crypto.createDecipheriv('aes-256-cbc', _key(id), iv);
|
||||
decipher.setAutoPadding(false);
|
||||
return Buffer.concat([decipher.update(data), decipher.final()]);
|
||||
}
|
||||
|
||||
/**
|
||||
* Decompresses the data.
|
||||
* @private
|
||||
* @param {Buffer} data
|
||||
* @param {function(Error, Buffer=)} done
|
||||
*/
|
||||
function _decompress(data, done) {
|
||||
try {
|
||||
zlib.inflate(data, done);
|
||||
} catch(e) {
|
||||
done(undefined, data);
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* Generates a key.
|
||||
* @private
|
||||
* @param {number} subtitleId
|
||||
* @return {Buffer}
|
||||
*/
|
||||
function _key(subtitleId) {
|
||||
var hash = _secret(20, 97, 1, 2) + _magic(subtitleId);
|
||||
var result = new Buffer(32);
|
||||
result.fill(0);
|
||||
crypto.createHash('sha1').update(hash).digest().copy(result);
|
||||
return result;
|
||||
}
|
||||
|
||||
/**
|
||||
* Generates a magic number.
|
||||
* @private
|
||||
* @param {number} subtitleId
|
||||
* @return {number}
|
||||
*/
|
||||
function _magic(subtitleId) {
|
||||
var base = Math.floor(Math.sqrt(6.9) * Math.pow(2, 25));
|
||||
var hash = bigInt(base).xor(subtitleId);
|
||||
var multipliedHash = bigInt(hash).multiply(32);
|
||||
return bigInt(hash).xor(hash >> 3).xor(multipliedHash).toJSNumber();
|
||||
}
|
||||
|
||||
/**
|
||||
* Generates a secret string based on a Fibonacci sequence.
|
||||
* @private
|
||||
* @param {number} size
|
||||
* @param {number} modulo
|
||||
* @param {number} firstSeed
|
||||
* @param {number} secondSeed
|
||||
* @return {string}
|
||||
*/
|
||||
function _secret(size, modulo, firstSeed, secondSeed) {
|
||||
var currentValue = firstSeed + secondSeed;
|
||||
var previousValue = secondSeed;
|
||||
var result = '';
|
||||
for (var i = 0; i < size; i += 1) {
|
||||
var oldValue = currentValue;
|
||||
result += String.fromCharCode(currentValue % modulo + 33);
|
||||
currentValue += previousValue;
|
||||
previousValue = oldValue;
|
||||
}
|
||||
return result;
|
||||
}
|
||||
3
src/subtitle/formats/index.js
Normal file
3
src/subtitle/formats/index.js
Normal file
@ -0,0 +1,3 @@
|
||||
module.exports = {
|
||||
srt: require('./srt')
|
||||
};
|
||||
81
src/subtitle/formats/srt.js
Normal file
81
src/subtitle/formats/srt.js
Normal file
@ -0,0 +1,81 @@
|
||||
'use strict';
|
||||
var xml2js = require('xml2js');
|
||||
|
||||
/**
|
||||
* Converts an input buffer to a SRT subtitle.
|
||||
* @param {Buffer|string} input
|
||||
* @param {function(Error, string=)} done
|
||||
*/
|
||||
module.exports = function(input, done) {
|
||||
if (typeof buffer !== 'string') input = input.toString();
|
||||
xml2js.parseString(input, {
|
||||
explicitArray: false,
|
||||
explicitRoot: false
|
||||
}, function(err, xml) {
|
||||
try {
|
||||
if (err) return done(err);
|
||||
done(undefined, xml.events.event.map(_convert).join('\n'));
|
||||
} catch(err) {
|
||||
done(err);
|
||||
}
|
||||
});
|
||||
};
|
||||
|
||||
/**
|
||||
* Converts an event.
|
||||
* @param {Object} event
|
||||
* @param {number} index
|
||||
* @returns {string}
|
||||
*/
|
||||
function _convert(event, index) {
|
||||
var attributes = event.$;
|
||||
return (index + 1) + '\n' +
|
||||
_time(attributes.start) + ' --> ' + _time(attributes.end) + '\n' +
|
||||
_text(attributes.text) + '\n';
|
||||
}
|
||||
|
||||
/**
|
||||
* Prefixes a value.
|
||||
* @param {string} value
|
||||
* @param {number} length
|
||||
* @returns {string}
|
||||
*/
|
||||
function _prefix(value, length) {
|
||||
while (value.length < length) value = '0' + value;
|
||||
return value;
|
||||
}
|
||||
|
||||
/**
|
||||
* Suffixes a value.
|
||||
* @param {string} value
|
||||
* @param {number} length
|
||||
* @returns {string}
|
||||
*/
|
||||
function _suffix(value, length) {
|
||||
while (value.length < length) value = value + '0';
|
||||
return value;
|
||||
}
|
||||
|
||||
/**
|
||||
* Formats a text value.
|
||||
* @param {string} text
|
||||
* @returns {string}
|
||||
*/
|
||||
function _text(text) {
|
||||
return text.replace(/{[^}]+}/g, '').replace(/\\n/ig, '\n');
|
||||
}
|
||||
|
||||
/**
|
||||
* Formats a time stamp.
|
||||
* @param {string} time
|
||||
* @returns {string}
|
||||
*/
|
||||
function _time(time) {
|
||||
var all = time.match(/^([0-9]+):([0-9]+):([0-9]+)\.([0-9]+)$/);
|
||||
if (!all) throw new Error('Invalid time.');
|
||||
var hours = _prefix(all[1], 2);
|
||||
var minutes = _prefix(all[2], 2);
|
||||
var seconds = _prefix(all[3], 2);
|
||||
var milliseconds = _suffix(all[4], 3);
|
||||
return hours + ':' + minutes + ':' + seconds + ',' + milliseconds;
|
||||
}
|
||||
4
src/subtitle/index.js
Normal file
4
src/subtitle/index.js
Normal file
@ -0,0 +1,4 @@
|
||||
module.exports = {
|
||||
decode: require('./decode'),
|
||||
formats: require('./formats')
|
||||
};
|
||||
3
src/video/index.js
Normal file
3
src/video/index.js
Normal file
@ -0,0 +1,3 @@
|
||||
module.exports = {
|
||||
stream: require('./stream')
|
||||
};
|
||||
31
src/video/stream.js
Normal file
31
src/video/stream.js
Normal file
@ -0,0 +1,31 @@
|
||||
'use strict';
|
||||
var childProcess = require('child_process');
|
||||
var path = require('path');
|
||||
var os = require('os');
|
||||
|
||||
/**
|
||||
* Streams the video to disk using rtmpdump.
|
||||
* @param {string} rtmpUrl
|
||||
* @param {string} inputPath
|
||||
* @param {string} swfUrl
|
||||
* @param {string} outputPath
|
||||
* @param {function(Error)} done
|
||||
*/
|
||||
module.exports = function(rtmpUrl, inputPath, swfUrl, outputPath, done) {
|
||||
childProcess.exec(_command() + ' ' +
|
||||
'-r "' + rtmpUrl + '" ' +
|
||||
'-y "' + inputPath + '" ' +
|
||||
'-W "' + swfUrl + '" ' +
|
||||
'-o "' + outputPath + '"', {
|
||||
maxBuffer: Infinity
|
||||
}, done);
|
||||
};
|
||||
|
||||
/**
|
||||
* Determines the command for the operating system.
|
||||
* @returns {string}
|
||||
*/
|
||||
function _command() {
|
||||
if (os.platform() !== 'win32') return 'rtmpdump';
|
||||
return path.join(__dirname, '../../bin/rtmpdump.exe');
|
||||
}
|
||||
Loading…
x
Reference in New Issue
Block a user