/*jshint node:true*/
'use strict';
var EventEmitter = require('events').EventEmitter;
var util = require('util');
var path = require('path');
var spawn = require('child_process').spawn;
var exec = require('child_process').exec;
/**
* A FFmpeg helper class
*
* This provides a nicer interface for building & running ffmpeg commands.
* As a general run each function called runs on the last input / ouput addec
*
* @returns {FfmpegCommand}
* @constructor
*/
function FfprobeCommand() {
// Make 'new' optional
if (!(this instanceof FfprobeCommand)) {
return new FfprobeCommand();
}
EventEmitter.call(this);
}
util.inherits(FfprobeCommand, EventEmitter);
FfprobeCommand.prototype.ffprobeBinaryPath = 'ffprobe';
FfprobeCommand.prototype.ffprobeBinary = FfprobeCommand.ffprobeBinary = function (path) {
if (path) {
FfprobeCommand.prototype.ffprobeBinaryPath = path;
}
return FfprobeCommand.prototype.ffprobeBinaryPath;
};
function findBlocks(raw) {
var stream_start = raw.indexOf('[STREAM]') + 8,
stream_end = raw.lastIndexOf('[/STREAM]'),
format_start = raw.indexOf('[FORMAT]') + 8,
format_end = raw.lastIndexOf('[/FORMAT]');
var blocks = {streams: null, format: null};
if (stream_start !== 7 && stream_end !== -1) {
blocks.streams = raw.slice(stream_start, stream_end).trim();
}
if (format_start !== 7 && format_end !== -1) {
blocks.format = raw.slice(format_start, format_end).trim();
}
return blocks;
};
function parseField(str) {
str = ("" + str).trim();
return str.match(/^\d+\.?\d*$/) ? parseFloat(str) : str;
};
function parseBlock(block) {
var block_object = {}, lines = block.split('\n');
lines.forEach(function (line) {
var data = line.split('=');
if (data && data.length === 2) {
block_object[data[0]] = parseField(data[1]);
}
});
return block_object;
};
function parseStreams(text, callback) {
if (!text) return {streams: null};
var streams = [];
var blocks = text.replace('[STREAM]\n', '').split('[/STREAM]');
blocks.forEach(function (stream, idx) {
var codec_data = parseBlock(stream);
var sindex = codec_data.index;
delete codec_data.index;
if (sindex) streams[sindex] = codec_data;
else streams.push(codec_data);
});
return {streams: streams};
};
function parseFormat(text, callback) {
if (!text) return {format: null}
var block = text.replace('[FORMAT]\n', '').replace('[/FORMAT]', '');
var raw_format = parseBlock(block),
format = {},
metadata = {};
//REMOVE metadata
delete raw_format.filename;
for (var attr in raw_format) {
if (raw_format.hasOwnProperty(attr)) {
if (attr.indexOf('TAG') === -1) format[attr] = raw_format[attr];
else metadata[attr.slice(4)] = raw_format[attr];
}
}
return {format: format, metadata: metadata};
};
FfprobeCommand.prototype.run = function (file, callback) {
var self = this;
var stdout = '';
var stdoutClosed = false;
var stderr = '';
var stderrClosed = false;
var processExited = false;
// Ensure we send 'end' or 'error' only once
var ended = false;
function emitEnd(err) {
if (!ended) {
if (err) {
ended = true;
callback(err, stdout, stderr);
//self.emit('error', err);
} else if (processExited && stdoutClosed && stderrClosed) {
ended = true;
var blocks = findBlocks(stdout);
var s = parseStreams(blocks.streams),
f = parseFormat(blocks.format);
callback(null, {
filename: path.basename(file),
filepath: path.dirname(file),
fileext: path.extname(file),
file: file,
streams: s.streams,
format: f.format,
metadata: f.metadata
});
//self.emit('end');
}
}
}
var args = ['-show_streams', '-show_format', '-loglevel', 'warning', file];
stdout += this.ffprobeBinary() + ' ' + args.join(' ') + "\n";
this.ffmpegProc = spawn(this.ffprobeBinary(), args);
self.emit('start', this.ffprobeBinary() + ' ' + args.join(' '));
if (this.ffmpegProc.stderr) {
this.ffmpegProc.stderr.setEncoding('utf8');
}
if (this.ffmpegProc.stdout) {
this.ffmpegProc.stdout.setEncoding('utf8');
}
this.ffmpegProc.on('error', function (err) {
emitEnd(err);
});
// Handle process exit
this.ffmpegProc.on('exit', function (code, signal) {
processExited = true;
if (signal) {
emitEnd(new Error('ffprobe was killed with signal ' + signal));
} else if (code) {
emitEnd(new Error('ffprobe exited with code ' + code));
} else {
emitEnd();
}
});
this.ffmpegProc.stdout.on('data', function (data) {
stdout += data;
});
this.ffmpegProc.stdout.on('close', function () {
stdoutClosed = true;
emitEnd();
});
this.ffmpegProc.stderr.on('data', function (data) {
stderr += data;
});
this.ffmpegProc.stderr.on('close', function () {
stderrClosed = true;
emitEnd();
});
return this;
};
module.exports = FfprobeCommand;