Added MKV muxing support

This commit is contained in:
Roel van Uden 2015-01-23 20:38:23 +01:00
parent ed8f7c90f5
commit 256f9cd887
7 changed files with 115 additions and 30 deletions

View File

@ -34,10 +34,10 @@ prior to using this application.
* Video streaming.
* Episode page scraping with subtitle saving and video streaming.
* Add ASS support.
* Add muxing (MP4+ASS=MKV).
### Pending Implementation
* Add muxing (MP4+ASS=MKV).
* 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 authentication to the entire stack to support premium content.

14
app.js
View File

@ -1,16 +1,8 @@
'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()
format: 'ass', // defaults to srt
merge: true, // defaults to false
path: 'F:\\Anime', // defaults to process.cwd()
tag: undefined, // defaults to CrunchyRoll
};

BIN
bin/mkvmerge.exe Normal file

Binary file not shown.

View File

@ -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.
* @private
@ -36,9 +66,18 @@ function _download(config, page, player, done) {
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) {
_subtitle(config, player, filePath, function(err, exists) {
if (err || exists) return done(err || undefined);
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);
_video(config, page, player, filePath, done);
_complete('Finished ' + fileName, begin, done);
});
});
});
}
@ -111,16 +150,22 @@ function _player(address, id, done) {
* @param {Object} config
* @param {Object} player
* @param {string} filePath
* @param {function(Error)} done
* @param {function(Error, boolean=)} 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';
fs.exists(filePath + (config.merge ? '.mkv' : format), function(exists) {
if (exists) return done(undefined, true);
var enc = player.subtitle;
subtitle.decode(enc.id, enc.iv, enc.data, function(err, data) {
if (err) return done(err);
subtitle.formats[format](data, function(err, decodedSubtitle) {
if (err) return done(err);
fs.writeFile(filePath + '.' + format, decodedSubtitle, done);
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} player
* @param {string} filePath
* @param {function(Error)} done
* @param {function(Error, boolean=)} done
*/
function _video(config, page, player, filePath, done) {
var extension = path.extname(player.video.file);
fs.exists(filePath + (config.merge ? '.mkv' : extension), function(exists) {
if (exists) return done(undefined, true);
video.stream(
player.video.host,
player.video.file,
page.swf,
filePath + path.extname(player.video.file),
done);
filePath + extension,
function(err) {
if (err) return done(err);
done(undefined, false);
});
});
}

View File

@ -1,3 +1,4 @@
module.exports = {
merge: require('./merge'),
stream: require('./stream')
};

39
src/video/merge.js Normal file
View 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');
}

View File

@ -23,6 +23,7 @@ module.exports = function(rtmpUrl, rtmpInputPath, swfUrl, filePath, done) {
/**
* Determines the command for the operating system.
* @private
* @returns {string}
*/
function _command() {