diff --git a/.gitignore b/.gitignore index 46c33e1..134e666 100644 --- a/.gitignore +++ b/.gitignore @@ -1,2 +1,4 @@ +.idea/ +yarn.lock /node_modules/ /test/__* diff --git a/index.js b/index.js index f515d2e..8ab8b90 100644 --- a/index.js +++ b/index.js @@ -2,236 +2,237 @@ "use strict"; var spawn = require("child_process").spawn, - ffmpeg = spawn.bind(null, process.env.FFMPEG_PATH || "ffmpeg"), - fs = require("fs"), - through = require("through"), - concat = require("concat-stream"); - -module.exports.read = function(src, options, callback) { - if (typeof options === "function") { - callback = options; - options = {}; - } - - var args = getReadArgs(src, options); - - if (options.dryRun) { - return args; - } - - var proc = spawnRead(args), - stream = through(), - output = parseini(), - error = concat(); - - // Proxy any child process error events along - proc.on("error", stream.emit.bind(stream, "error")); - - // Parse ffmetadata "ini" output - proc.stdout.pipe(output); - - // Capture stderr - proc.stderr.pipe(error); - - proc.on("close", function(code) { - if (code === 0) { - stream.emit("metadata", output.data); - } - else { - stream.emit("error", new Error(error.getBody().toString())); - } - }); - - if (callback) { - stream.on("metadata", callback.bind(null, null)); - stream.on("error", callback); - } - - return stream; + ffmpeg = spawn.bind(null, process.env.FFMPEG_PATH || "ffmpeg"), + fs = require("fs"), + through = require("through"), + concat = require("concat-stream"); + +module.exports.read = function (src, options, callback) { + if (typeof options === "function") { + callback = options; + options = {}; + } + + var args = getReadArgs(src, options); + + if (options.dryRun) { + return args; + } + + var proc = spawnRead(args), + stream = through(), + output = parseini(), + error = concat(); + + // Proxy any child process error events along + proc.on("error", stream.emit.bind(stream, "error")); + + // Parse ffmetadata "ini" output + proc.stdout.pipe(output); + + // Capture stderr + proc.stderr.pipe(error); + + proc.on("close", function (code) { + if (code === 0) { + stream.emit("metadata", output.data); + } + else { + stream.emit("error", new Error(error.getBody().toString())); + } + }); + + if (callback) { + stream.on("metadata", callback.bind(null, null)); + stream.on("error", callback); + } + + return stream; }; -module.exports.write = function(src, data, options, callback) { - if (typeof options === "function") { - callback = options; - options = {}; - } - - var dst = getTempPath(src), - args = getWriteArgs(src, dst, data, options); - - if (options.dryRun) { - return args; - } - - var proc = ffmpeg(args), - stream = through(), - error = concat(); - - // Proxy any child process error events - proc.on("error", stream.emit.bind(stream, "error")); - - // Proxy child process stdout but don't end the stream until we know - // the process exits with a zero exit code - proc.stdout.on("data", stream.emit.bind(stream, "data")); - - // Capture stderr (to use in case of non-zero exit code) - proc.stderr.pipe(error); - - proc.on("close", function(code) { - if (code === 0) { - finish(); - } - else { - handleError(new Error(error.getBody().toString())); - } - }); - - if (callback) { - stream.on("end", callback); - stream.on("error", callback); - } - - function handleError(err) { - fs.unlink(dst, function() { - stream.emit("error", err); - }); - } - - function finish() { - fs.rename(dst, src, function(err) { - if (err) { - handleError(err); - } - else { - stream.emit("end"); - } - }); - } - - return stream; +module.exports.write = function (src, data, options, callback) { + if (typeof options === "function") { + callback = options; + options = {}; + } + + var dst = getTempPath(src), + args = getWriteArgs(src, dst, data, options); + + if (options.dryRun) { + return args; + } + + var proc = ffmpeg(args), + stream = through(), + error = concat(); + + // Proxy any child process error events + proc.on("error", stream.emit.bind(stream, "error")); + + // Proxy child process stdout but don't end the stream until we know + // the process exits with a zero exit code + proc.stdout.on("data", stream.emit.bind(stream, "data")); + + // Capture stderr (to use in case of non-zero exit code) + proc.stderr.pipe(error); + + proc.on("close", function (code) { + if (code === 0) { + finish(); + } + else { + handleError(new Error(error.getBody().toString())); + } + }); + + if (callback) { + stream.on("end", callback); + stream.on("error", callback); + } + + function handleError(err) { + fs.unlink(dst, function () { + stream.emit("error", err); + }); + } + + function finish() { + fs.rename(dst, src, function (err) { + if (err) { + handleError(err); + } + else { + stream.emit("end"); + } + }); + } + + return stream; }; var path = require("path"); + function getTempPath(src) { - var ext = path.extname(src), - basename = path.basename(src).slice(0, -ext.length), - newName = basename + ".ffmetadata" + ext, - dirname = path.dirname(src), - newPath = path.join(dirname, newName); - return newPath; + var ext = path.extname(src), + basename = path.basename(src).slice(0, -ext.length), + newName = basename + ".ffmetadata" + ext, + dirname = path.dirname(src), + newPath = path.join(dirname, newName); + return newPath; } // -- Child process helpers function getReadArgs(src, options) { - if (typeof options.coverPath !== 'undefined' ) { - return [ - '-i', - src, - options.coverPath - ]; - } - - return [ - "-i", - src, - "-f", - "ffmetadata", - "pipe:1", // output to stdout - ]; + if (typeof options.coverPath !== 'undefined') { + return [ + '-i', + src, + options.coverPath + ]; + } + + return [ + "-i", + src, + "-f", + "ffmetadata", + "pipe:1", // output to stdout + ]; } function spawnRead(args) { - return ffmpeg(args, { detached: true, encoding: "binary" }); + return ffmpeg(args, {detached: true, encoding: "binary"}); } function getWriteArgs(src, dst, data, options) { - // ffmpeg options - var inputs = ["-i", src], // src input - maps = ['-map', '0:0'], // set as the first - args = ["-y"]; // overwrite file - - // Attach additional input files if included - getAttachments(options).forEach(function(el) { - var inputIndex = inputs.length / 2; - inputs.push('-i', el); - maps.push("-map", inputIndex + ":0"); - }); - - // Copy flag in order to not transcode - args = args.concat(inputs, maps, ["-codec", "copy"]); - - if (options["id3v2.3"]) { - args.push("-id3v2_version", "3"); - } - - // append metadata - Object.keys(data).forEach(function(name) { - args.push("-metadata"); - args.push(escapeini(name) + "=" + escapeini(data[name])); - }); - - args.push(dst); // output to src path - - return args; + // ffmpeg options + var inputs = ["-i", src], // src input + maps = ['-map', options.preserveStreams ? '0' : '0:0'], // set as the first + args = ["-y"]; // overwrite file + + // Attach additional input files if included + getAttachments(options).forEach(function (el) { + var inputIndex = inputs.length / 2; + inputs.push('-i', el); + maps.push("-map", inputIndex + ":0"); + }); + + // Copy flag in order to not transcode + args = args.concat(inputs, maps, ["-codec", "copy"]); + + if (options["id3v2.3"]) { + args.push("-id3v2_version", "3"); + } + + // append metadata + Object.keys(data).forEach(function (name) { + args.push("-metadata"); + args.push(escapeini(name) + "=" + escapeini(data[name])); + }); + + args.push(dst); // output to src path + + return args; } function getAttachments(options) { - if (Array.isArray(options)) { - return options; - } - return options.attachments || []; + if (Array.isArray(options)) { + return options; + } + return options.attachments || []; } // -- Parse ini var combine = require("stream-combiner"), - filter = require("stream-filter"), - split = require("split"); + filter = require("stream-filter"), + split = require("split"); function parseini(callback) { - var stream = combine( - split(), - filter(Boolean), - filter(isNotComment), - through(parseLine) - ); - - // Object to store INI data in - stream.data = {}; - - if (callback) { - stream.on("end", callback.bind(null, stream.data)); - } - - return stream; - - var key; - - function parseLine(data) { - data = unescapeini(data); - var index = data.indexOf("="); - - if (index === -1) { - stream.data[key] += data.slice(index + 1); - stream.data[key] = stream.data[key].replace('\\', '\n'); - } else { - key = data.slice(0, index); - stream.data[key] = data.slice(index + 1); - } - } + var stream = combine( + split(), + filter(Boolean), + filter(isNotComment), + through(parseLine) + ); + + // Object to store INI data in + stream.data = {}; + + if (callback) { + stream.on("end", callback.bind(null, stream.data)); + } + + return stream; + + var key; + + function parseLine(data) { + data = unescapeini(data); + var index = data.indexOf("="); + + if (index === -1) { + stream.data[key] += data.slice(index + 1); + stream.data[key] = stream.data[key].replace('\\', '\n'); + } else { + key = data.slice(0, index); + stream.data[key] = data.slice(index + 1); + } + } } function isNotComment(data) { - return data.slice(0, 1) !== ";"; + return data.slice(0, 1) !== ";"; } function escapeini(data) { - // @TODO - return data; + // @TODO + return data; } function unescapeini(data) { - // @TODO - return data; + // @TODO + return data; } diff --git a/test/test.js b/test/test.js index 3a117ac..1ad3d53 100644 --- a/test/test.js +++ b/test/test.js @@ -116,3 +116,17 @@ test("write metadata", function(t) { t.equal(args[index + 1], "3"); t.end(); }); + + +test("write metadata preserve streams", function(t) { + var data = {}; + var options = { + preserveStreams: true, + dryRun: true + }; + var args = ffmetadata.write(TEST_FILE, data, options); + var index = args.indexOf("-map"); + t.notEqual(index, -1); + t.equal(args[index + 1], "0"); + t.end(); +});