Added MKV muxing support
This commit is contained in:
parent
ed8f7c90f5
commit
256f9cd887
@ -34,10 +34,10 @@ prior to using this application.
|
|||||||
* Video streaming.
|
* Video streaming.
|
||||||
* Episode page scraping with subtitle saving and video streaming.
|
* Episode page scraping with subtitle saving and video streaming.
|
||||||
* Add ASS support.
|
* Add ASS support.
|
||||||
|
* Add muxing (MP4+ASS=MKV).
|
||||||
|
|
||||||
### Pending Implementation
|
### Pending Implementation
|
||||||
|
|
||||||
* Add muxing (MP4+ASS=MKV).
|
|
||||||
* Add series API to save an entire series rather than per-episode.
|
* Add series API to save an entire series rather than per-episode.
|
||||||
* Add batch-mode to queue a bunch of series and do incremental saves.
|
* Add batch-mode to queue a bunch of series and do incremental saves.
|
||||||
* Add authentication to the entire stack to support premium content.
|
* Add authentication to the entire stack to support premium content.
|
||||||
|
|||||||
16
app.js
16
app.js
@ -1,17 +1,9 @@
|
|||||||
'use strict';
|
'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 = {
|
var config = {
|
||||||
format: undefined, // defaults to srt
|
format: 'ass', // defaults to srt
|
||||||
path: undefined, // defaults to process.cwd()
|
merge: true, // defaults to false
|
||||||
tag: undefined, // defaults to CrunchyRoll
|
path: 'F:\\Anime', // defaults to process.cwd()
|
||||||
|
tag: undefined, // defaults to CrunchyRoll
|
||||||
};
|
};
|
||||||
|
|
||||||
var episode = require('./src/episode');
|
var episode = require('./src/episode');
|
||||||
|
|||||||
BIN
bin/mkvmerge.exe
Normal file
BIN
bin/mkvmerge.exe
Normal file
Binary file not shown.
@ -23,6 +23,36 @@ module.exports = function (config, address, done) {
|
|||||||
});
|
});
|
||||||
};
|
};
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Affixes zero-padding to the value.
|
||||||
|
* @private
|
||||||
|
* @param {(number|string)} value
|
||||||
|
* @param {number} length
|
||||||
|
* @returns {string}
|
||||||
|
*/
|
||||||
|
function _affix(value, length) {
|
||||||
|
if (typeof value !== 'string') value = String(value);
|
||||||
|
var suffix = value.indexOf('.') !== -1;
|
||||||
|
var add = length - (suffix ? value.indexOf('.') : value.length);
|
||||||
|
while ((add -= 1) >= 0) value = '0' + value;
|
||||||
|
return value;
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Completes a download and writes the message with a time indication.
|
||||||
|
* @param {string} message
|
||||||
|
* @param {number} begin
|
||||||
|
* @param {function(Error)} done
|
||||||
|
*/
|
||||||
|
function _complete(message, begin, done) {
|
||||||
|
var timeInMs = Date.now() - begin;
|
||||||
|
var seconds = _affix(Math.floor(timeInMs / 1000) % 60, 2);
|
||||||
|
var minutes = _affix(Math.floor(timeInMs / 1000 / 60) % 60, 2);
|
||||||
|
var hours = _affix(Math.floor(timeInMs / 1000 / 60 / 60), 2);
|
||||||
|
console.log(message + ' (' + hours + ':' + minutes + ':' + seconds + ')');
|
||||||
|
done(undefined);
|
||||||
|
}
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* Downloads the subtitle and video.
|
* Downloads the subtitle and video.
|
||||||
* @private
|
* @private
|
||||||
@ -36,9 +66,18 @@ function _download(config, page, player, done) {
|
|||||||
var episode = (page.episode < 10 ? '0' : '') + page.episode;
|
var episode = (page.episode < 10 ? '0' : '') + page.episode;
|
||||||
var fileName = page.series + ' - ' + episode + ' [' + tag + ']';
|
var fileName = page.series + ' - ' + episode + ' [' + tag + ']';
|
||||||
var filePath = path.join(config.path || process.cwd(), fileName);
|
var filePath = path.join(config.path || process.cwd(), fileName);
|
||||||
_subtitle(config, player, filePath, function(err) {
|
_subtitle(config, player, filePath, function(err, exists) {
|
||||||
if (err) return done(err);
|
if (err || exists) return done(err || undefined);
|
||||||
_video(config, page, player, filePath, done);
|
var begin = Date.now();
|
||||||
|
console.log('Fetching ' + fileName);
|
||||||
|
_video(config, page, player, filePath, function(err, exists) {
|
||||||
|
if (err || exists) return done(err || undefined);
|
||||||
|
if (!config.merge) return _complete('Finished ' + fileName, begin, done);
|
||||||
|
video.merge(config, player.video.file, filePath, function(err) {
|
||||||
|
if (err) return done(err);
|
||||||
|
_complete('Finished ' + fileName, begin, done);
|
||||||
|
});
|
||||||
|
});
|
||||||
});
|
});
|
||||||
}
|
}
|
||||||
|
|
||||||
@ -111,16 +150,22 @@ function _player(address, id, done) {
|
|||||||
* @param {Object} config
|
* @param {Object} config
|
||||||
* @param {Object} player
|
* @param {Object} player
|
||||||
* @param {string} filePath
|
* @param {string} filePath
|
||||||
* @param {function(Error)} done
|
* @param {function(Error, boolean=)} done
|
||||||
*/
|
*/
|
||||||
function _subtitle(config, player, filePath, done) {
|
function _subtitle(config, player, filePath, done) {
|
||||||
var contents = player.subtitle;
|
var format = subtitle.formats[config.format] ? config.format : 'srt';
|
||||||
subtitle.decode(contents.id, contents.iv, contents.data, function(err, data) {
|
fs.exists(filePath + (config.merge ? '.mkv' : format), function(exists) {
|
||||||
if (err) return done(err);
|
if (exists) return done(undefined, true);
|
||||||
var format = subtitle.formats[config.format] ? config.format : 'srt';
|
var enc = player.subtitle;
|
||||||
subtitle.formats[format](data, function(err, decodedSubtitle) {
|
subtitle.decode(enc.id, enc.iv, enc.data, function(err, data) {
|
||||||
if (err) return done(err);
|
if (err) return done(err);
|
||||||
fs.writeFile(filePath + '.' + format, decodedSubtitle, done);
|
subtitle.formats[format](data, function(err, decodedSubtitle) {
|
||||||
|
if (err) return done(err);
|
||||||
|
fs.writeFile(filePath + '.' + format, decodedSubtitle, function(err) {
|
||||||
|
if (err) return done(err);
|
||||||
|
done(undefined, false);
|
||||||
|
});
|
||||||
|
});
|
||||||
});
|
});
|
||||||
});
|
});
|
||||||
}
|
}
|
||||||
@ -132,13 +177,20 @@ function _subtitle(config, player, filePath, done) {
|
|||||||
* @param {Object} page
|
* @param {Object} page
|
||||||
* @param {Object} player
|
* @param {Object} player
|
||||||
* @param {string} filePath
|
* @param {string} filePath
|
||||||
* @param {function(Error)} done
|
* @param {function(Error, boolean=)} done
|
||||||
*/
|
*/
|
||||||
function _video(config, page, player, filePath, done) {
|
function _video(config, page, player, filePath, done) {
|
||||||
video.stream(
|
var extension = path.extname(player.video.file);
|
||||||
player.video.host,
|
fs.exists(filePath + (config.merge ? '.mkv' : extension), function(exists) {
|
||||||
player.video.file,
|
if (exists) return done(undefined, true);
|
||||||
page.swf,
|
video.stream(
|
||||||
filePath + path.extname(player.video.file),
|
player.video.host,
|
||||||
done);
|
player.video.file,
|
||||||
|
page.swf,
|
||||||
|
filePath + extension,
|
||||||
|
function(err) {
|
||||||
|
if (err) return done(err);
|
||||||
|
done(undefined, false);
|
||||||
|
});
|
||||||
|
});
|
||||||
}
|
}
|
||||||
|
|||||||
@ -1,3 +1,4 @@
|
|||||||
module.exports = {
|
module.exports = {
|
||||||
|
merge: require('./merge'),
|
||||||
stream: require('./stream')
|
stream: require('./stream')
|
||||||
};
|
};
|
||||||
|
|||||||
39
src/video/merge.js
Normal file
39
src/video/merge.js
Normal file
@ -0,0 +1,39 @@
|
|||||||
|
'use strict';
|
||||||
|
var childProcess = require('child_process');
|
||||||
|
var fs = require('fs');
|
||||||
|
var path = require('path');
|
||||||
|
var os = require('os');
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Merges the subtitle and video files into a Matroska Multimedia Container.
|
||||||
|
* @param {Object} config
|
||||||
|
* @param {string} rtmpInputPath
|
||||||
|
* @param {string} filePath
|
||||||
|
* @param {function(Error)} done
|
||||||
|
*/
|
||||||
|
module.exports = function(config, rtmpInputPath, filePath, done) {
|
||||||
|
var subtitlePath = filePath + '.' + config.format;
|
||||||
|
var videoPath = filePath + path.extname(rtmpInputPath);
|
||||||
|
childProcess.exec(_command() + ' ' +
|
||||||
|
'-o "' + filePath + '.mkv" ' +
|
||||||
|
'"' + videoPath + '" ' +
|
||||||
|
'"' + subtitlePath + '"', {
|
||||||
|
maxBuffer: Infinity
|
||||||
|
}, function(err) {
|
||||||
|
if (err) return done(err);
|
||||||
|
fs.unlink(videoPath, function(err) {
|
||||||
|
if (err) return done(err);
|
||||||
|
fs.unlink(subtitlePath, done);
|
||||||
|
});
|
||||||
|
});
|
||||||
|
};
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Determines the command for the operating system.
|
||||||
|
* @private
|
||||||
|
* @returns {string}
|
||||||
|
*/
|
||||||
|
function _command() {
|
||||||
|
if (os.platform() !== 'win32') return 'mkvmerge';
|
||||||
|
return path.join(__dirname, '../../bin/mkvmerge.exe');
|
||||||
|
}
|
||||||
@ -23,6 +23,7 @@ module.exports = function(rtmpUrl, rtmpInputPath, swfUrl, filePath, done) {
|
|||||||
|
|
||||||
/**
|
/**
|
||||||
* Determines the command for the operating system.
|
* Determines the command for the operating system.
|
||||||
|
* @private
|
||||||
* @returns {string}
|
* @returns {string}
|
||||||
*/
|
*/
|
||||||
function _command() {
|
function _command() {
|
||||||
|
|||||||
Loading…
x
Reference in New Issue
Block a user