├── .gitignore ├── LICENSE ├── README.md ├── examples ├── assets │ ├── utterance_0.raw │ ├── utterance_0.wav │ ├── utterance_1.raw │ ├── utterance_1.wav │ ├── utterance_2.raw │ └── utterance_2.wav ├── concat.js ├── transcode.js └── trim.js ├── index.js ├── lib ├── options │ ├── effect.js │ ├── input.js │ └── output.js ├── processor.js ├── recipes.js ├── sox.js └── utils.js └── package.json /.gitignore: -------------------------------------------------------------------------------- 1 | # Logs 2 | logs 3 | *.log 4 | 5 | # Runtime data 6 | pids 7 | *.pid 8 | *.seed 9 | 10 | # Directory for instrumented libs generated by jscoverage/JSCover 11 | lib-cov 12 | 13 | # Coverage directory used by tools like istanbul 14 | coverage 15 | 16 | # Grunt intermediate storage (http://gruntjs.com/creating-plugins#storing-task-files) 17 | .grunt 18 | 19 | # Compiled binary addons (http://nodejs.org/api/addons.html) 20 | build/Release 21 | 22 | # Dependency directory 23 | # Commenting this out is preferred by some people, see 24 | # https://www.npmjs.org/doc/misc/npm-faq.html#should-i-check-my-node_modules-folder-into-git- 25 | node_modules 26 | 27 | # Users Environment Variables 28 | .lock-wscript 29 | 30 | _*.js 31 | examples/outputs/* 32 | -------------------------------------------------------------------------------- /LICENSE: -------------------------------------------------------------------------------- 1 | The MIT License (MIT) 2 | 3 | Copyright (c) 2015 Patricia Saylor 4 | 5 | Permission is hereby granted, free of charge, to any person obtaining a copy 6 | of this software and associated documentation files (the "Software"), to deal 7 | in the Software without restriction, including without limitation the rights 8 | to use, copy, modify, merge, publish, distribute, sublicense, and/or sell 9 | copies of the Software, and to permit persons to whom the Software is 10 | furnished to do so, subject to the following conditions: 11 | 12 | The above copyright notice and this permission notice shall be included in all 13 | copies or substantial portions of the Software. 14 | 15 | THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR 16 | IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, 17 | FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE 18 | AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER 19 | LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, 20 | OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE 21 | SOFTWARE. 22 | 23 | -------------------------------------------------------------------------------- /README.md: -------------------------------------------------------------------------------- 1 | # sox-audio - A NodeJS interface to SoX audio utilities 2 | This Node.js module abstracts the command-line usage of SoX so you can create complex SoX commands and run them with ease. You must have SoX installed in order to use this module. 3 | 4 | ## Requirements 5 | You must have sox installed in order to use this module, and you must add it to your PATH. [Visit the sox website](http://sox.sourceforge.net/Main/HomePage) to download it. 6 | 7 | ## Installation 8 | You can install sox-audio through npm: 9 | ```` 10 | npm install sox-audio 11 | ```` 12 | 13 | ## Usage 14 | There are many usage examples in the [examples](./examples) folder, including how to concatenate and trim files, and how to transcode a raw audio stream into a wav audio stream. 15 | 16 | ### Creating a SoxCommand 17 | The sox-audio module returns a constructor that you can use to instantiate Sox commands. You can instantiate a SoxCommand with or without the `new` operator. 18 | ```js 19 | var SoxCommand = require('sox-audio'); 20 | var command = SoxCommand(); 21 | ``` 22 | You may pass an input file name or readable stream, and/or an options object, to the constructor. 23 | ```js 24 | var command = SoxCommand('examples/assets/utterance_0.wav'); 25 | var command = SoxCommand(fs.createReadStream('examples/assets/utterance_0.wav')); 26 | var command = SoxCommand({option: "value", ... }); 27 | ``` 28 | 29 | ### Inputs 30 | SoxCommands accept one or any number of inputs. There are 4 different acceptable types of inputs: 31 | * a file name, e.g. `'examples/assets/utterance_0.wav'` 32 | * a readable stream, e.g. `fs.createReadStream('examples/assets/utterance_0.wav')`, however only one input stream may be used per command 33 | * another SoxCommand, which must set its output to `'-p'` for piping the result of this subcommand as input into the main SoxCommand, and it must provide an outputFileType. You may use more than one input of this type in a command. 34 | * a string for a subcommand to be executed and whose output should be piped as input into the SoxCommand, e.g.`'|sox examples/assets/utterance_0.wav -t wav -p trim 5'`. For this string, follow the format specified in the [SoX documentation](http://sox.sourceforge.net/sox.html#FILENAMES). You may use more than one input of this type in a command. 35 | 36 | ```js 37 | // Passing an input to the constructor is the same as calling .input() 38 | var command1 = SoxCommand('examples/assets/utterance_0.wav') 39 | .input('examples/assets/utterance_1.wav') 40 | .input(fs.createReadStream('examples/assets/utterance_2.wav')); 41 | 42 | // A string for a subcommand may be passed as input, following the format '|program [options]'. 43 | // The program in the subcommand does not have to be sox, it could be any program whose stdout 44 | // you want to use as an input file. 45 | var command2 = SoxCommand() 46 | .input('|sox examples/assets/utterance_0.wav -t wav -p trim 5 35'); 47 | 48 | // We can implement the same behavior as command2 using another SoxCommand as a subcommand 49 | var trimSubcommand = SoxCommand() 50 | .input('examples/assets/utterance_0.wav') 51 | .outputFileType('wav') 52 | .output('-p') 53 | .trim(5, 35); 54 | var command3 = SoxCommand() 55 | .inputSubCommand(trimSubcommand); 56 | ``` 57 | 58 | #### Input Options 59 | These methods set input-related options on the input that was *most recently added*, so you must add an input before calling these. 60 | 61 | * **`inputSampleRate(sampleRate)`** Set the sample rate in Hz (or kHz if appended with a 'k') 62 | * **`inputBits(bitRate)`** Set the number of bits in each encoded sample 63 | * **`inputEncoding(encoding)`** Set the audio encoding type (sometimes needed with file-types that support more than one encoding, like raw or wav). The main available encoding types are: 64 | * signed-integer 65 | * unsigned-integer 66 | * floating-point 67 | * for more, see the [sox documentation](http://sox.sourceforge.net/sox.html#OPTIONS) for -e 68 | * **`inputChannels(numChannels)`** Set the number of audio channels in the audio file 69 | * **`inputFileType(fileType)`** Set the type of the audio file 70 | 71 | ```js 72 | var command = SoxCommand(); 73 | command.input(inputStream) 74 | .inputSampleRate(44100) 75 | .inputEncoding('signed') 76 | .inputBits(16) 77 | .inputChannels(1) 78 | .inputFileType('raw'); 79 | ``` 80 | 81 | ### Outputs 82 | SoxCommands accept one or any number of outputs. There are 3 different acceptable types of outputs: 83 | * a file name, e.g. `'examples/outputs/utterance_0.wav'` 84 | * a writable stream, e.g. `fs.createWriteStream('examples/outputs/utterance_0.wav')`, however only one output stream may be used per command 85 | * the string `'-p'` or `'--sox-pipe'`, this can be used in place of an output filename to specify that the Sox command should be used as an input pipe into another Sox command. You may refer to the [sox documentation](http://sox.sourceforge.net/sox.html#FILENAMES) for -p 86 | 87 | #### Output Options 88 | These methods set output-related options on the output that was *most recently added*, so you must add an output before calling these. 89 | 90 | * **`outputSampleRate(sampleRate)`** Set the sample rate in Hz (or kHz if appended with a 'k') 91 | * **`outputBits(bitRate)`** Set the number of bits in each encoded sample 92 | * **`outputEncoding(encoding)`** Set the audio encoding type (sometimes needed with file-types that support more than one encoding, like raw or wav). The main available encoding types are: 93 | * signed-integer 94 | * unsigned-integer 95 | * floating-point 96 | * for more, see the [sox documentation](http://sox.sourceforge.net/sox.html#OPTIONS) for -e 97 | * **`outputChannels(numChannels)`** Set the number of audio channels in the audio file 98 | * **`outputFileType(fileType)`** Set the type of the audio file to output, particularly important when the output is a stream 99 | 100 | ```js 101 | var command = SoxCommand(); 102 | command.input(inputStream) 103 | .inputSampleRate(44100) 104 | .inputEncoding('signed') 105 | .inputBits(16) 106 | .inputChannels(1) 107 | .inputFileType('raw'); 108 | 109 | command.output(outputStream) 110 | .outputSampleRate(1600) 111 | .outputEncoding('signed') 112 | .outputBits(16) 113 | .outputChannels(1) 114 | .outputFileType('wav'); 115 | ``` 116 | 117 | ### Effects 118 | SoX can be used to invoke a number of audio 'effects', which should be provided at the end of the command. Multiple effects may be applied by specifying them one after another. You can [learn about all of the effects available to SoX here](http://sox.sourceforge.net/sox.html#EFFECTS). SoxCommand currently providers helpers for some popular effects, and a catch-all method to apply any of the other effects. 119 | 120 | #### `trim(position(+))` 121 | Cuts portions out of the audio. Any number of _positions_ may be given, either individually or as a list, and each _position_ can be a number or formatted string. Once the first _position_ is reached, the effect alternates between copying and discarding the audio at each _position_. Using 0 for the first _position_ allows copying from the beginning of the audio. The format for a _position_ can be: 122 | * `5.2` a number indicating 5.2 seconds from the previous _position_ or the start of the audio if there was no previous 123 | * `'15:25.30'` a string indicating 15 minutes, 25 seconds, and 30 milliseconds from the previous _position_ or the start of the audio file 124 | * `'=2:05'` a string indicating 2 minutes and 5 seconds into the audio file, relative to the start of the audio 125 | * `'-3:30'` a string indicating 3 minutes and 30 seconds before the end of the audio file 126 | * `'=1250s'` a string indicate 1250 samples into the audio file 127 | 128 | #### `combine(method)` 129 | Select the input file combining method, which can be one of the following strings: 130 | * `'concatenate'` 131 | * `'sequence'` 132 | * `'mix'` 133 | * `'mix-power'` 134 | * `'merge'` 135 | * `'multiply'` 136 | 137 | #### `concat()` 138 | A shorthand for applying the concatenate combining method. The audio from each input will be concatenated in the order added to the command to form the output file. The input files must have the same number of channels. 139 | 140 | #### `addEffect(effectName, effectOptionsList)` 141 | A catch-all method allowing you to apply any SoX effect. Apply the SoX effect with `effectName` using the command line options provided in `effectOptionsList`. Please [refer to the SoX documentation on all available effects and their usages](http://sox.sourceforge.net/sox.html#EFFECTS). 142 | 143 | ### Running a SoxCommand 144 | Starting the SoxCommand is as easy as 145 | ```js 146 | command.run(); 147 | ``` 148 | Of course, you need to fully specify the command first, with inputs, outputs, and optional effects. You can also set listeners on your command for `prepare`, `start`, `progress`, `error`, and `end` events. 149 | ```js 150 | var command = SoxCommand() 151 | .input(...) 152 | .output(...) 153 | .addEffect(..., [...]); 154 | 155 | command.on('prepare', function(args) { 156 | console.log('Preparing sox command with args ' + args.join(' ')); 157 | }); 158 | 159 | command.on('start', function(commandLine) { 160 | console.log('Spawned sox with command ' + commandLine); 161 | }); 162 | 163 | command.on('progress', function(progress) { 164 | console.log('Processing progress: ', progress); 165 | }); 166 | 167 | command.on('error', function(err, stdout, stderr) { 168 | console.log('Cannot process audio: ' + err.message); 169 | console.log('Sox Command Stdout: ', stdout); 170 | console.log('Sox Command Stderr: ', stderr) 171 | }); 172 | 173 | command.on('end', function() { 174 | console.log('Sox command succeeded!'); 175 | }); 176 | 177 | command.run(); 178 | ``` 179 | 180 | ### Selected Examples 181 | The following example concatenates three audio files while trimming off the first 5 seconds of the first file, and the last 10 seconds of the last file through subcommands. 182 | ```js 183 | var SoxCommand = require('sox-audio'); 184 | var TimeFormat = SoxCommand.TimeFormat; 185 | 186 | var command = SoxCommand(); 187 | 188 | var startTimeFormatted = TimeFormat.formatTimeAbsolute(5); 189 | var endTimeFormatted = TimeFormat.formatTimeRelativeToEnd(10); 190 | 191 | var trimFirstFileSubCommand = SoxCommand() 192 | .input('./assets/utterance_0.wav') 193 | .output('-p') 194 | .outputFileType('wav') 195 | .trim(startTimeFormatted); 196 | 197 | var trimLastFileSubCommand = SoxCommand() 198 | .input('./assets/utterance_2.wav') 199 | .output('-p') 200 | .outputFileType('wav') 201 | .trim(0, endTimeFormatted); 202 | 203 | command.inputSubCommand(trimFirstFileSubCommand) 204 | .input('./assets/utterance_1.wav'); 205 | .inputSubCommand(trimLastFileSubCommand) 206 | .output(outputFileName) 207 | .concat(); 208 | 209 | command.run(); 210 | ``` 211 | 212 | In this example, a 44.1 kHz raw audio stream is converted on the fly to a 16 kHz wav audio stream. 213 | ```js 214 | var command = SoxCommand(); 215 | command.input(inputStream) 216 | .inputSampleRate('44.1k') 217 | .inputEncoding('signed') 218 | .inputBits(16) 219 | .inputChannels(1) 220 | .inputFileType('raw') 221 | .output(outputPipe) 222 | .outputFileType('wav') 223 | .outputSampleRate('16k'); 224 | 225 | command.run(); 226 | ``` 227 | 228 | ## Development 229 | Check out this repo, make your changes, commit the code and then publish a new release on npm: 230 | ``` 231 | $ cd sox-audio 232 | $ npm publish 233 | + sox-audio@ 234 | ``` 235 | * https://gist.github.com/coolaj86/1318304 236 | * https://quickleft.com/blog/creating-and-publishing-a-node-js-module/ 237 | 238 | ## License 239 | This code is under the MIT License. 240 | -------------------------------------------------------------------------------- /examples/assets/utterance_0.raw: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/psaylor/sox-audio/728f7ea2a586a9801f8259584f3f0ce881845c32/examples/assets/utterance_0.raw -------------------------------------------------------------------------------- /examples/assets/utterance_0.wav: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/psaylor/sox-audio/728f7ea2a586a9801f8259584f3f0ce881845c32/examples/assets/utterance_0.wav -------------------------------------------------------------------------------- /examples/assets/utterance_1.raw: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/psaylor/sox-audio/728f7ea2a586a9801f8259584f3f0ce881845c32/examples/assets/utterance_1.raw -------------------------------------------------------------------------------- /examples/assets/utterance_1.wav: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/psaylor/sox-audio/728f7ea2a586a9801f8259584f3f0ce881845c32/examples/assets/utterance_1.wav -------------------------------------------------------------------------------- /examples/assets/utterance_2.raw: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/psaylor/sox-audio/728f7ea2a586a9801f8259584f3f0ce881845c32/examples/assets/utterance_2.raw -------------------------------------------------------------------------------- /examples/assets/utterance_2.wav: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/psaylor/sox-audio/728f7ea2a586a9801f8259584f3f0ce881845c32/examples/assets/utterance_2.wav -------------------------------------------------------------------------------- /examples/concat.js: -------------------------------------------------------------------------------- 1 | var SoxCommand = require('../index'); 2 | 3 | var util = require('util'); 4 | var fs = require('fs'); 5 | var Stream = require('stream'); 6 | var TimeFormat = SoxCommand.TimeFormat; 7 | 8 | var addStandardListeners = function(command) { 9 | command.on('start', function(commandLine) { 10 | console.log('Spawned sox with command ' + commandLine); 11 | }); 12 | 13 | command.on('progress', function(progress) { 14 | console.log('Processing progress: ', progress); 15 | }); 16 | 17 | command.on('error', function(err, stdout, stderr) { 18 | console.log('Cannot process audio: ' + err.message); 19 | console.log('Sox Command Stdout: ', stdout); 20 | console.log('Sox Command Stderr: ', stderr) 21 | }); 22 | 23 | command.on('end', function() { 24 | console.log('Sox command succeeded!'); 25 | }); 26 | }; 27 | 28 | /* Concatenate all audio files in the list, and save the result in outputFileName */ 29 | var concatenateExample = function(fileNameList, outputFileName) { 30 | var command = SoxCommand(); 31 | fileNameList.forEach(function addInput(fileName) { 32 | command.input(fileName); 33 | }); 34 | command.output(outputFileName) 35 | .concat(); 36 | 37 | addStandardListeners(command); 38 | command.run() 39 | return command; 40 | } 41 | 42 | /* Concatenate all audio files in the list, trimming the first and last files. 43 | * The first file is trimmed to start at startTimeSeconds, and the last file is 44 | * trimmed to end at endTimeSeconds. The trimming is done by passing as input a 45 | * string to execute an internal sox command ('|sox ...') which does the trim. 46 | * The output is saved in outputFileName */ 47 | var concatenateAndTrimExample = function(fileNameList, startTimeSeconds, endTimeSeconds, outputFileName) { 48 | 49 | var command = SoxCommand(); 50 | 51 | var startTimeFormatted = TimeFormat.formatTimeAbsolute(startTimeSeconds); 52 | var endTimeFormatted = TimeFormat.formatTimeAbsolute(endTimeSeconds); 53 | 54 | var trimFirstFileSubCommand = util.format( 55 | '|sox %s -t wav -p trim %s', 56 | fileNameList[0], 57 | startTimeFormatted); 58 | 59 | var trimLastFileSubCommand = util.format( 60 | '|sox %s -t wav -p trim 0 %s', 61 | fileNameList[fileNameList.length - 1], 62 | endTimeFormatted); 63 | 64 | command.input(trimFirstFileSubCommand); 65 | fileNameList.slice(1, -1).forEach(function(fileName) { 66 | command.input(fileName); 67 | }); 68 | command.input(trimLastFileSubCommand) 69 | .output(outputFileName) 70 | .concat(); 71 | 72 | addStandardListeners(command); 73 | 74 | console.log('Command', command._getArguments()); 75 | 76 | command.run(); 77 | return command; 78 | }; 79 | 80 | /* Concatenate all audio files in the list, trimming the first and last files. 81 | * The first file is trimmed to start at startTimeSeconds, and the last file is 82 | * trimmed to end at endTimeSeconds. The trimming is done by passing as input 83 | * another SoxCommand object that is set up to just trim a certain file. 84 | * Executing the main SoxCommand simultaneously executes the sub-SoxCommands. 85 | The output is saved in outputFileName */ 86 | var concatenateAndTrimAnotherExample = function(fileNameList, startTimeSeconds, endTimeSeconds, outputFileName) { 87 | var command = SoxCommand(); 88 | 89 | var startTimeFormatted = TimeFormat.formatTimeAbsolute(startTimeSeconds); 90 | var endTimeFormatted = TimeFormat.formatTimeAbsolute(endTimeSeconds); 91 | 92 | var trimFirstFileSubCommand = SoxCommand() 93 | .input(fileNameList[0]) 94 | .output('-p') 95 | .outputFileType('wav') 96 | .trim(startTimeFormatted); 97 | 98 | var trimLastFileSubCommand = SoxCommand() 99 | .input(fileNameList[fileNameList.length - 1]) 100 | .output('-p') 101 | .outputFileType('wav') 102 | .trim(0, endTimeFormatted); 103 | 104 | command.inputSubCommand(trimFirstFileSubCommand); 105 | 106 | fileNameList.slice(1, -1).forEach(function(fileName) { 107 | command.input(fileName); 108 | }); 109 | command.inputSubCommand(trimLastFileSubCommand) 110 | .output(outputFileName) 111 | .concat(); 112 | 113 | addStandardListeners(command); 114 | console.log('Command', command._getArguments()); 115 | 116 | command.run(); 117 | return command; 118 | }; 119 | 120 | /* Concatenate all audio files in the list, streaming the result to outputPipe */ 121 | var concatenateAndPipeExample = function(fileNameList, outputPipe) { 122 | var command = SoxCommand(); 123 | fileNameList.forEach(function addInput(fileName) { 124 | command.input(fileName); 125 | }); 126 | command.output(outputPipe) 127 | .outputFileType('wav') 128 | .concat(); 129 | 130 | addStandardListeners(command); 131 | command.run() 132 | return command; 133 | }; 134 | 135 | var runExamples = function() { 136 | var fileNameList = ['./assets/utterance_0.wav', 137 | './assets/utterance_1.wav', './assets/utterance_2.wav']; 138 | var outputFileName = './outputs/triple_concatenation.wav'; 139 | var outputFileName2 = './outputs/trim_and_concat.wav'; 140 | var outputFileName3 = './outputs/trim_and_concat2.wav'; 141 | var outputPipe = fs.createWriteStream('./outputs/concat_and_pipe.wav'); 142 | 143 | console.log('\nConcatenate example '); 144 | concatenateExample(fileNameList, outputFileName); 145 | 146 | console.log('\nConcatenate and trim example'); 147 | concatenateAndTrimExample(fileNameList, 4.03, 2.54, outputFileName2); 148 | 149 | console.log('\nAnother concatenate and trim example'); 150 | concatenateAndTrimAnotherExample(fileNameList, 4.03, 2.54, outputFileName3); 151 | 152 | console.log('\nConcatenate and pipe example'); 153 | concatenateAndPipeExample(fileNameList.slice(0, -1), outputPipe); 154 | }; 155 | 156 | runExamples(); -------------------------------------------------------------------------------- /examples/transcode.js: -------------------------------------------------------------------------------- 1 | var SoxCommand = require('../index'); 2 | var fs = require('fs'); 3 | var spawn = require('child_process').spawn; 4 | var through = require('through'); 5 | 6 | /* All the provided raw assets have 44.1k sample rate */ 7 | var INPUT_SAMPLE_RATE = 44100; 8 | /* You can change the sample rate of the output */ 9 | var OUTPUT_SAMPLE_RATE = 16000; 10 | 11 | /* This command will transcode a raw signed PCM 16-bit file sampled at 44.1k to 12 | a PCM 16-bit wav file sampled at 16k */ 13 | var SOX_COMMAND_LINE_EXAMPLE = 'sox -r 44100 -e signed -b 16 -c 1 input.raw -r 16000 output.wav'; 14 | 15 | var addStandardListeners = function(command) { 16 | command.on('prepare', function(args) { 17 | console.log('Preparing sox command with args ' + args.join(' ')); 18 | }); 19 | 20 | command.on('start', function(commandLine) { 21 | console.log('Spawned sox with command ' + commandLine); 22 | }); 23 | 24 | command.on('progress', function(progress) { 25 | console.log('Processing progress: ', progress); 26 | }); 27 | 28 | command.on('error', function(err, stdout, stderr) { 29 | console.log('Cannot process audio: ' + err.message); 30 | console.log('Sox Command Stdout: ', stdout); 31 | console.log('Sox Command Stderr: ', stderr) 32 | }); 33 | 34 | command.on('end', function() { 35 | console.log('Sox command succeeded!'); 36 | }); 37 | }; 38 | 39 | /* Transcodes a raw 44.1k wav file to have another file type (generally wav) and 40 | a different sample rate (in this case 16k) */ 41 | var transcodeFile = function(rawInputFileName, outputSampleRate, outputFileName) { 42 | var command = SoxCommand(rawInputFileName) 43 | .inputSampleRate(INPUT_SAMPLE_RATE) 44 | .inputEncoding('signed') 45 | .inputBits(16) 46 | .inputChannels(1) 47 | .output(outputFileName) 48 | .outputSampleRate(outputSampleRate); 49 | addStandardListeners(command); 50 | command.run(); 51 | }; 52 | 53 | /* Streaming examples */ 54 | 55 | var genThroughStreamCounter = function() { 56 | var dataCounter = 0; 57 | var throughStream = through( 58 | function write(data) { 59 | dataCounter += data.length; 60 | console.log("ThroughStreamCounter: Writing through data ", data.length); 61 | this.queue(data); 62 | }, 63 | function end () { 64 | console.log("ThroughStreamCounter: END. Wrote through data ", dataCounter); 65 | this.queue(null); 66 | }); 67 | return throughStream; 68 | }; 69 | 70 | /* Transcodes a raw input stream from 41k to 16k, streaming out a 16k wav file 71 | to outputPipe. Optionally pass useThrough = true to insert a through stream 72 | between the rawInputStream and the SoxCommand to count and print the bytes of data */ 73 | var transcodeRawStream = function(rawInputStream, outputSampleRate, outputPipe, useThrough) { 74 | useThrough = useThrough || false; 75 | var inputStream = rawInputStream; 76 | 77 | if (useThrough) { 78 | var throughStreamCounter = genThroughStreamCounter(); 79 | rawInputStream.pipe(throughStreamCounter); 80 | inputStream = throughStreamCounter; 81 | } 82 | 83 | var command = SoxCommand(); 84 | command.input(inputStream) 85 | .inputSampleRate(INPUT_SAMPLE_RATE) 86 | .inputEncoding('signed') 87 | .inputBits(16) 88 | .inputChannels(1) 89 | .inputFileType('raw') 90 | .output(outputPipe) 91 | .outputFileType('wav') 92 | .outputSampleRate(outputSampleRate); 93 | 94 | addStandardListeners(command); 95 | command.run(); 96 | }; 97 | 98 | /* Transcodes a wav input stream from 41k to 16k, streaming out a 16k wav file 99 | to outputPipe. Optionally pass useThrough = true to insert a through stream 100 | between the wavInputStream and the SoxCommand to count and print the bytes of data */ 101 | var transcodeWavStream = function(wavInputStream, outputSampleRate, outputPipe, useThrough) { 102 | useThrough = useThrough || false; 103 | var inputStream = wavInputStream; 104 | 105 | if (useThrough) { 106 | var throughStreamCounter = genThroughStreamCounter(); 107 | wavInputStream.pipe(throughStreamCounter); 108 | inputStream = throughStreamCounter; 109 | } 110 | 111 | var command = SoxCommand(); 112 | command.input(inputStream) 113 | .inputSampleRate(INPUT_SAMPLE_RATE) 114 | .inputEncoding('signed') 115 | .inputBits(16) 116 | .inputChannels(1) 117 | .inputFileType('wav') 118 | .output(outputPipe) 119 | .outputFileType('wav') 120 | .outputSampleRate(outputSampleRate); 121 | 122 | addStandardListeners(command); 123 | command.run(); 124 | }; 125 | 126 | 127 | var runExamples = function() { 128 | var rawInputFileName = './assets/utterance_0.raw'; 129 | var wavInputFileName = './assets/utterance_0.wav'; 130 | 131 | var outputFileName1 = './outputs/transcoded.wav'; 132 | var outputFileName2 = './outputs/rawstream_transcoded.wav'; 133 | var outputFileName3 = './outputs/wavstream_transcoded.wav'; 134 | 135 | console.log("Transcoding %s to %s", rawInputFileName, outputFileName1); 136 | transcodeFile(rawInputFileName, OUTPUT_SAMPLE_RATE, outputFileName1); 137 | 138 | console.log("\nStreaming transcoding of %s to %s", rawInputFileName, outputFileName2); 139 | var rawInputStream = fs.createReadStream(rawInputFileName); 140 | var outputFileStream2 = fs.createWriteStream(outputFileName2); 141 | transcodeRawStream(rawInputStream, OUTPUT_SAMPLE_RATE, outputFileStream2); 142 | 143 | console.log("\nStreaming transcoding of %s to %s", rawInputFileName, outputFileName3); 144 | var wavInputStream = fs.createReadStream(wavInputFileName); 145 | var outputFileStream3 = fs.createWriteStream(outputFileName3); 146 | transcodeWavStream(wavInputStream, OUTPUT_SAMPLE_RATE, outputFileStream3); 147 | }; 148 | 149 | runExamples(); 150 | 151 | 152 | -------------------------------------------------------------------------------- /examples/trim.js: -------------------------------------------------------------------------------- 1 | var SoxCommand = require('../index'); 2 | var util = require('util'); 3 | var fs = require('fs'); 4 | var TimeFormat = SoxCommand.TimeFormat; 5 | 6 | /* Trims the input file to start at startTimeSeconds and end at endTimeSeconds, 7 | where both times are relative to the beginning of the file (thus they are 8 | absolute times). The output is saved to outputFile. */ 9 | var trimFileExample = function(inputFile, startTimeSeconds, endTimeSeconds, outputFile) { 10 | var startTimeFormatted = TimeFormat.formatTimeAbsolute(startTimeSeconds); 11 | var endTimeFormatted = TimeFormat.formatTimeAbsolute(endTimeSeconds); 12 | 13 | var command = SoxCommand() 14 | .input(inputFile) 15 | .output(outputFile) 16 | .trim(startTimeFormatted, endTimeFormatted) 17 | .run(); 18 | return command; 19 | }; 20 | 21 | /* Trims the input file to start at startTimeSeconds and end at endTimeSeconds, 22 | where both times are relative to the beginning of the file (thus they are 23 | absolute times). The output is piped in wav format to outputPipe */ 24 | var trimFileAndPipeExample = function(inputFile, startTimeSeconds, endTimeSeconds, outputPipe) { 25 | var startTimeFormatted = TimeFormat.formatTimeAbsolute(startTimeSeconds); 26 | var endTimeFormatted = TimeFormat.formatTimeAbsolute(endTimeSeconds); 27 | 28 | var command = SoxCommand() 29 | .input(inputFile) 30 | .output(outputPipe) 31 | .outputFileType('wav') 32 | .trim(startTimeFormatted, endTimeFormatted) 33 | .run(); 34 | return command; 35 | }; 36 | 37 | var runExamples = function () { 38 | var inputFile = './assets/utterance_0.wav'; 39 | var outputFile = './outputs/utterance_0_cut.wav'; 40 | var outputPipe = fs.createWriteStream('./outputs/utterance_0_cut2.wav'); 41 | 42 | var startTime= 1.67; 43 | var endTime = 2.25; 44 | 45 | trimFileExample(inputFile, startTime, endTime, outputFile); 46 | trimFileAndPipeExample(inputFile, startTime, endTime, outputPipe); 47 | }; 48 | 49 | runExamples(); 50 | -------------------------------------------------------------------------------- /index.js: -------------------------------------------------------------------------------- 1 | module.exports = require('./lib/sox'); -------------------------------------------------------------------------------- /lib/options/effect.js: -------------------------------------------------------------------------------- 1 | 'use strict'; 2 | 3 | var util = require('util'); 4 | var utils = require('../utils'); 5 | 6 | /* Effect-related methods */ 7 | 8 | var COMBINE_METHODS = /^(?:concatenate|merge|mix|mix−power|multiply|sequence)$/; 9 | 10 | module.exports = function(proto) { 11 | 12 | /* Add a Sox effect not otherwise implemented below with its name and the 13 | * ordered list of options for that effect 14 | */ 15 | proto.addEffect = function(effectName, optionsList) { 16 | var effect = { 17 | name: effectName, 18 | options : utils.args() 19 | }; 20 | effect.options(optionsList); 21 | this._effects.push(effect); 22 | return this; 23 | }; 24 | 25 | /* Cuts portions out of the audio according to the positions passed in, 26 | * either individually or as a list. 27 | * Ex: trim(0, 5, 17, 32) or trim([0, 5, 17, 32]) or trim('0s', '13s') 28 | * Any number of positions may be given; audio is not sent to the output 29 | * until the first position is reached. The effect then alternates between 30 | * copying and discarding audio at each position. 31 | */ 32 | proto.trim = function() { 33 | var positions; 34 | if (arguments.length === 1 && Array.isArray(arguments[0])) { 35 | positions = arguments[0]; 36 | } else { 37 | positions = [].slice.call(arguments); 38 | } 39 | return this.addEffect('trim', positions); 40 | }; 41 | 42 | /* Select the input file combining method to be applied to all inputs: 43 | * concatenate|merge|mix|mix−power|multiply|sequence 44 | */ 45 | proto.combine = function(method) { 46 | if (COMBINE_METHODS.test(method)) { 47 | return this.addEffect('--combine', method); 48 | } else { 49 | throw new Error('Invalid combining method ' + method); 50 | } 51 | }; 52 | 53 | /* Concatenate all the input audio files in order into one audio file */ 54 | proto.concat = function() { 55 | return this.combine('concatenate'); 56 | }; 57 | 58 | // TODO: implement 59 | proto.rateEffect = function() { 60 | return this; 61 | }; 62 | 63 | // TODO: implement 64 | proto.removeSilence = function() { 65 | return this; 66 | }; 67 | 68 | // TODO: implement 69 | proto.spectrogram = function() { 70 | return this; 71 | }; 72 | 73 | // TODO: implement 74 | proto.splice = function() { 75 | return this; 76 | }; 77 | 78 | }; -------------------------------------------------------------------------------- /lib/options/input.js: -------------------------------------------------------------------------------- 1 | 'use strict'; 2 | 3 | var utils = require('../utils'); 4 | 5 | /* Input-related methods */ 6 | 7 | module.exports = function(proto) { 8 | 9 | proto.input = function(inputSource) { 10 | var isFile = false; 11 | var isStream = false; 12 | 13 | if (typeof inputSource !== 'string') { 14 | if (!('readable' in inputSource) || !(inputSource.readable)){ 15 | throw new Error('Invalid input'); 16 | } 17 | isStream = true; 18 | // inputSource.pause(); 19 | 20 | } else { 21 | isFile = true; 22 | } 23 | 24 | this._currentInput = { 25 | source: inputSource, 26 | isFile: isFile, 27 | isStream: isStream, 28 | isSubCommand: false, 29 | options: utils.args() 30 | }; 31 | 32 | this._inputs.push(this._currentInput); 33 | 34 | return this; 35 | }; 36 | 37 | proto.inputSubCommand = function(inputCommand) { 38 | var isSubCommand = false; 39 | var isFile = false; 40 | 41 | if (inputCommand instanceof proto.constructor) { 42 | console.log('Input a subcommand SoxCommand'); 43 | isSubCommand = true; 44 | 45 | } else { 46 | throw new Error('Invalid input subcommand: must be instance of SoxCommand'); 47 | } 48 | 49 | this._currentInput = { 50 | source: inputCommand, 51 | isFile: isFile, 52 | isStream: false, 53 | isSubCommand: isSubCommand, 54 | options: utils.args() 55 | }; 56 | 57 | this._inputs.push(this._currentInput); 58 | return this; 59 | }; 60 | 61 | proto.inputSampleRate = function(sampleRate) { 62 | if (!this._currentInput) { 63 | throw new Error('No input specified'); 64 | } 65 | if (this._currentInput.isSubCommand) { 66 | throw new Error('Cannot add options to a SubCommand input'); 67 | } 68 | 69 | this._currentInput.options('-r', sampleRate); 70 | return this; 71 | }; 72 | 73 | /* Specify the number of bits in each encoded sample */ 74 | proto.inputBits = function(bitRate) { 75 | if (!this._currentInput) { 76 | throw new Error('No input specified'); 77 | } 78 | if (this._currentInput.isSubCommand) { 79 | throw new Error('Cannot add options to a SubCommand input'); 80 | } 81 | 82 | this._currentInput.options('-b', bitRate); 83 | return this; 84 | }; 85 | 86 | proto.inputEncoding = function(encoding) { 87 | if (!this._currentInput) { 88 | throw new Error('No input specified'); 89 | } 90 | if (this._currentInput.isSubCommand) { 91 | throw new Error('Cannot add options to a SubCommand input'); 92 | } 93 | 94 | this._currentInput.options('-e', encoding); 95 | return this; 96 | }; 97 | 98 | proto.inputChannels = function(numChannels) { 99 | if (!this._currentInput) { 100 | throw new Error('No input specified'); 101 | } 102 | if (this._currentInput.isSubCommand) { 103 | throw new Error('Cannot add options to a SubCommand input'); 104 | } 105 | 106 | this._currentInput.options('-c', numChannels); 107 | return this; 108 | }; 109 | 110 | proto.inputFileType = function(fileType) { 111 | if (!this._currentInput) { 112 | throw new Error('No input specified'); 113 | } 114 | if (this._currentInput.isSubCommand) { 115 | throw new Error('Cannot add options to a SubCommand input'); 116 | } 117 | 118 | this._currentInput.options('-t', fileType); 119 | return this; 120 | }; 121 | 122 | }; -------------------------------------------------------------------------------- /lib/options/output.js: -------------------------------------------------------------------------------- 1 | 'use strict'; 2 | 3 | var utils = require('../utils'); 4 | 5 | /* Output-related methods */ 6 | 7 | module.exports = function(proto) { 8 | 9 | proto.output = function(target, pipeopts) { 10 | var isFile = false; 11 | var isStream = false; 12 | var isSoxPipe = false; 13 | 14 | if (!target) { 15 | throw new Error('Invalid output, no target provided'); 16 | } 17 | 18 | if (target && typeof target !== 'string') { 19 | if (!('writable' in target) || !(target.writable)) { 20 | throw new Error('Invalid output'); 21 | } 22 | isStream = true; 23 | } else if (typeof target === 'string') { 24 | if (target === '-p' || target === '-' || target === '--sox-pipe') { 25 | isSoxPipe = true; 26 | } else { 27 | isFile = true; 28 | } 29 | } 30 | 31 | this._currentOutput = { 32 | target: target, 33 | isFile: isFile, 34 | isStream: isStream, 35 | isSoxPipe: isSoxPipe, 36 | pipeopts: pipeopts || {}, 37 | options: utils.args() 38 | }; 39 | this._outputs.push(this._currentOutput); 40 | 41 | return this; 42 | }; 43 | 44 | proto.outputSampleRate = function(sampleRate) { 45 | this._currentOutput.options('-r', sampleRate); 46 | return this; 47 | }; 48 | 49 | /* Specify the number of bits in each encoded sample */ 50 | proto.outputBits = function(bitRate) { 51 | this._currentOutput.options('-b', bitRate); 52 | return this; 53 | }; 54 | 55 | proto.outputEncoding = function(encoding) { 56 | this._currentOutput.options('-e', encoding); 57 | return this; 58 | }; 59 | 60 | proto.outputChannels = function(numChannels) { 61 | this._currentOutput.options('-c', numChannels); 62 | return this; 63 | }; 64 | 65 | proto.outputFileType = function(fileType) { 66 | this._currentOutput.options('-t', fileType); 67 | return this; 68 | }; 69 | 70 | }; -------------------------------------------------------------------------------- /lib/processor.js: -------------------------------------------------------------------------------- 1 | 'use strict'; 2 | 3 | var spawn = require('child_process').spawn; 4 | var path = require('path'); 5 | var fs = require('fs'); 6 | var utils = require('./utils'); 7 | var through = require('through'); 8 | 9 | /* Processor methods */ 10 | 11 | module.exports = function(proto) { 12 | 13 | proto._spawnSox = function(args, options, processCB, endCB) { 14 | 15 | utils.which('sox', function(err, soxPath) { 16 | if (err) { 17 | return endCB(err); 18 | } else if (!soxPath || soxPath.length === 0) { 19 | return endCB(new Error('Cannot find sox')); 20 | } 21 | 22 | var stdout = null; 23 | var stdoutClosed = false; 24 | 25 | var stderr = null; 26 | var stderrClosed = false; 27 | 28 | var soxProc = spawn(soxPath, args, options); 29 | 30 | if (soxProc.stderr && options.captureStderr) { 31 | soxProc.stderr.setEncoding('utf8'); 32 | } 33 | 34 | soxProc.on('error', function(err) { 35 | endCB(err); 36 | }); 37 | 38 | var exitError = null; 39 | function handleExit(err) { 40 | if (err) { 41 | exitError = err; 42 | } 43 | 44 | if (processExited && 45 | (stdoutClosed || !options.captureStdout) && 46 | (stderrClosed || !options.captureStderr)) { 47 | endCB(exitError, stdout, stderr); 48 | } 49 | } 50 | 51 | // Handle process exit 52 | var processExited = false; 53 | soxProc.on('exit', function(code, signal) { 54 | processExited = true; 55 | console.log('Sox process exited with code ' + code + 56 | ' and signal ' + signal); 57 | if (signal) { 58 | handleExit(new Error('Sox process was killed with signal ' + signal)); 59 | } else if (code) { 60 | handleExit(new Error('Sox process exited with code ' + code)); 61 | } else { 62 | handleExit(); 63 | } 64 | }); 65 | 66 | if (options.captureStdout) { 67 | stdout = ''; 68 | 69 | soxProc.stdout.on('data', function(data) { 70 | stdout += data; 71 | }); 72 | 73 | soxProc.stdout.on('close', function() { 74 | stdoutClosed = true; 75 | handleExit(); 76 | }); 77 | } 78 | 79 | if (options.captureStderr) { 80 | stderr = ''; 81 | 82 | soxProc.stderr.on('data', function(data) { 83 | stderr += data; 84 | }); 85 | 86 | soxProc.stderr.on('close', function() { 87 | stderrClosed = true; 88 | handleExit(); 89 | }); 90 | } 91 | 92 | // Call process callback 93 | processCB(soxProc); 94 | }); 95 | }; 96 | 97 | proto._getArguments = function() { 98 | var fileOutput = this._outputs.some(function(output) { 99 | return output.isFile; 100 | }); 101 | 102 | // Global options 103 | var globalOptions = this._global.get(); 104 | 105 | var inputArguments = this._inputs.reduce(function(args, input) { 106 | 107 | var source; 108 | var options = input.options.get(); 109 | 110 | if (typeof input.source === 'string') { 111 | source = input.source; 112 | 113 | } else { 114 | // The input source is a SoxCommand, so call _getArguments on it 115 | if (input.isSubCommand) { 116 | var subCommandArgs = input.source._getArguments(); 117 | source = '|sox ' + subCommandArgs.join(' '); 118 | 119 | // The input is a stream, so read from stdin 120 | } else if (input.isStream) { 121 | source = '-'; 122 | } 123 | } 124 | 125 | // For each input, add input options, then '' 126 | return args.concat( 127 | options, 128 | [source] 129 | ); 130 | }, []); 131 | 132 | // Outputs and output options 133 | var outputArguments = this._outputs.reduce(function(args, output) { 134 | var outputArg; 135 | if (!output.target) { 136 | outputArg = []; 137 | } else if (typeof output.target === 'string') { 138 | outputArg = [output.target]; 139 | } else { 140 | outputArg = ['-']; 141 | } 142 | 143 | return args.concat( 144 | output.options.get(), 145 | outputArg 146 | ); 147 | }, []); 148 | 149 | // Effects and effect options 150 | var effectArguments = this._effects.reduce(function(args, effect) { 151 | var effectName = [effect.name]; 152 | return args.concat( 153 | effectName, 154 | effect.options.get() 155 | ); 156 | }, []); 157 | 158 | return [].concat( 159 | globalOptions, 160 | inputArguments, 161 | outputArguments, 162 | effectArguments 163 | ); 164 | }; 165 | 166 | proto._prepare = function(callback, readMetadata) { 167 | // TODO: Check codecs and formats and other pre-requisites for execution of the command 168 | var args; 169 | try { 170 | args = this._getArguments(); 171 | 172 | } catch(e) { 173 | return callback(e) 174 | } 175 | 176 | callback(null, args); 177 | }; 178 | 179 | proto.run = function() { 180 | var self = this; 181 | var outputPresent = this._outputs.some(function(output) { 182 | return 'target' in output; 183 | }); 184 | 185 | if (!outputPresent) { 186 | throw new Error('No output specified'); 187 | } 188 | 189 | var outputStream = this._outputs.filter(function(output) { 190 | return output.isStream; 191 | })[0]; 192 | 193 | var inputStream = this._inputs.filter(function(input) { 194 | return input.isStream; 195 | })[0]; 196 | 197 | var ended = false; 198 | 199 | function emitEnd(err, stdout, stderr) { 200 | if (!ended) { 201 | ended = true; 202 | if (err) { 203 | self.emit('error', err, stdout, stderr); 204 | } else { 205 | self.emit('end', stdout, stderr); 206 | } 207 | } 208 | } 209 | 210 | self._prepare(function(err, args) { 211 | if (err) { 212 | return emitEnd(err); 213 | } 214 | 215 | // Run sox command 216 | var stdout = null; 217 | var stderr = ''; 218 | self.emit('prepare', args); 219 | self._spawnSox( 220 | args, 221 | {}, 222 | function processCB(soxProc) { 223 | self.soxProc = soxProc; 224 | self.emit('start', 'sox ' + args.join(' ')); 225 | 226 | // Pipe input stream if any 227 | if (inputStream) { 228 | console.log('Resume input stream'); 229 | inputStream.source.on('error', function(err) { 230 | emitEnd(new Error('Input stream error: ' + err.message)); 231 | soxProc.kill(); 232 | }); 233 | 234 | var dataCounter = 0; 235 | inputStream.source.on('end', function() { 236 | console.log('\nInput stream ended. Bytes read: ', dataCounter); 237 | }); 238 | 239 | // Hacky fix for piping raw input, the 2ms delay helps sox 240 | var WAIT_TIME = 2; 241 | var passThroughStream = through( 242 | 243 | function write(data){ 244 | var queueTheData = function(self, data) { 245 | return function() { 246 | dataCounter+= data.length; 247 | self.queue(data); 248 | }; 249 | }; 250 | setTimeout(queueTheData(this, data), WAIT_TIME); 251 | }, 252 | 253 | function end(){ 254 | var self = this; 255 | setTimeout(function() { 256 | self.queue(null); 257 | }, WAIT_TIME); 258 | console.log('End of pass through'); 259 | }); 260 | 261 | inputStream.source 262 | .pipe(passThroughStream) 263 | .pipe(soxProc.stdin); 264 | // inputStream.source.resume(); 265 | 266 | // Set stdin error handler on sox 267 | soxProc.stdin.on('error', function() {}); 268 | } 269 | 270 | // TODO: Setup timeout if requested 271 | 272 | if (outputStream) { 273 | console.log('Streaming output required'); 274 | // Pipe sox stdout to output stream 275 | soxProc.stdout.pipe(outputStream.target, outputStream.pipeopts); 276 | 277 | // Handle output stream events 278 | outputStream.target.on('close', function() { 279 | console.log('Cmd Output Stream closed'); 280 | setTimeout(function() { 281 | emitEnd(new Error('Output stream closed')); 282 | soxProc.kill(); 283 | }, 20); 284 | }); 285 | 286 | outputStream.target.on('error', function(err) { 287 | console.log('Cmd Output stream error!', err.message); 288 | emitEnd(new Error('Output stream error: ' + err.message)); 289 | soxProc.kill(); 290 | }); 291 | 292 | } else { 293 | // Gather sox stdout 294 | stdout = ''; 295 | soxProc.stdout.on('data', function(data) { 296 | stdout += data; 297 | }); 298 | } 299 | 300 | soxProc.stderr.on('data', function(data) { 301 | stderr += data; 302 | }); 303 | 304 | }, 305 | 306 | function endCB(err) { 307 | delete self.soxProc; 308 | 309 | if (err) { 310 | if (err.message.match(/sox exited with code/)) { 311 | // Add sox error message 312 | // err.message += ': ' + utils. 313 | } 314 | emitEnd(err, stdout, stderr); 315 | } else { 316 | emitEnd(null, stdout, stderr); 317 | } 318 | 319 | }); 320 | }) 321 | 322 | }; 323 | }; -------------------------------------------------------------------------------- /lib/recipes.js: -------------------------------------------------------------------------------- 1 | 'use strict'; 2 | 3 | module.exports = function recipes(proto) { 4 | 5 | /* Creates a SoxCommand for resampling input to ouput, according to the 6 | * provided options, which can optionally specify inputSampleRate, 7 | * inputEncoding, inputBitRate, inputChannels, and outputSampleRate 8 | */ 9 | proto.transcode = function(input, output, options) { 10 | options = options || {}; 11 | 12 | this.input(input) 13 | .inputSampleRate(options.inputSampleRate || 44100) 14 | .inputEncoding(options.inputEncoding || 'signed') 15 | .inputBitRate(options.inputBitRate || 16) 16 | .inputChannels(options.inputChannels || 1) 17 | .output(output) 18 | .outputSampleRate(options.outputSampleRate || 16000); 19 | return this; 20 | }; 21 | }; 22 | -------------------------------------------------------------------------------- /lib/sox.js: -------------------------------------------------------------------------------- 1 | 'use strict'; 2 | 3 | var util = require('util'); 4 | var EventEmitter = require('events').EventEmitter; 5 | 6 | var utils = require('./utils'); 7 | 8 | /* Create a sox command */ 9 | var SoxCommand = function(input, options) { 10 | 11 | // Make using the 'new' keyword optional 12 | if (!(this instanceof SoxCommand)) { 13 | return new SoxCommand(input, options); 14 | } 15 | 16 | EventEmitter.call(this); 17 | 18 | if (typeof input === 'object' && !('readable' in input)) { 19 | // Options object passed directly 20 | options = input; 21 | } else { 22 | // Input passed first 23 | options = options || {}; 24 | options.source = input; 25 | } 26 | 27 | // Add input if present 28 | this._inputs = []; 29 | if (options.source) { 30 | this.input(options.source); 31 | } 32 | 33 | this._outputs = []; 34 | 35 | this._effects = []; 36 | 37 | this._global = utils.args(); 38 | 39 | this.options = options; 40 | 41 | }; 42 | 43 | util.inherits(SoxCommand, EventEmitter); 44 | module.exports = SoxCommand; 45 | 46 | /* Add methods from options submodules */ 47 | require('./options/input')(SoxCommand.prototype); 48 | require('./options/output')(SoxCommand.prototype); 49 | require('./options/effect')(SoxCommand.prototype); 50 | 51 | /* Add processor methods */ 52 | require('./processor')(SoxCommand.prototype); 53 | 54 | /* Add util method */ 55 | SoxCommand.TimeFormat = new utils.TimeFormat; 56 | -------------------------------------------------------------------------------- /lib/utils.js: -------------------------------------------------------------------------------- 1 | 'use strict'; 2 | 3 | var exec = require('child_process').exec; 4 | var util = require('util'); 5 | 6 | var whichCache = {}; 7 | 8 | var utils = {}; 9 | 10 | utils.args = function() { 11 | var list = []; 12 | 13 | // Append argument(s) to the list 14 | var argfunc = function() { 15 | if (arguments.length === 1 && Array.isArray(arguments[0])) { 16 | list = list.concat(arguments[0]); 17 | } else { 18 | // convert the arguments list to an Array for concatenation 19 | list = list.concat([].slice.call(arguments)); 20 | } 21 | }; 22 | 23 | argfunc.clear = function() { 24 | list = []; 25 | }; 26 | 27 | argfunc.get = function() { 28 | return list; 29 | }; 30 | 31 | argfunc.clone = function() { 32 | var cloned = utils.args(); 33 | cloned(list); 34 | return cloned; 35 | }; 36 | 37 | return argfunc; 38 | }; 39 | 40 | utils.which = function(name, callback) { 41 | if (name in whichCache) { 42 | return callback(null, whichCache[name]); 43 | } 44 | var cmd = 'which ' + name; 45 | 46 | exec(cmd, function(err, stdout) { 47 | if (err) { 48 | callback(null, whichCache[name] = ''); 49 | } else { 50 | callback(null, whichCache[name] = stdout.trim()); 51 | } 52 | }); 53 | }; 54 | 55 | /* Time formatting helpers, particularly useful for trimming */ 56 | utils.TimeFormat = function() { 57 | var ABSOLUTE_TIME_FORMAT = '=%d'; 58 | var TIME_TO_END_FORMAT = '-%d' 59 | var SAMPLE_FORMAT = '%ds'; 60 | 61 | this.formatTimeAbsolute = function(timeInSeconds) { 62 | return util.format(ABSOLUTE_TIME_FORMAT, timeInSeconds); 63 | }; 64 | 65 | this.formatTimeRelativeToEnd = function(timeBeforeEndInSeconds) { 66 | return util.format(TIME_TO_END_FORMAT, timeBeforeEndInSeconds); 67 | }; 68 | 69 | this.formatTimeInSamples = function(sampleNumber) { 70 | return util.format(SAMPLE_FORMAT, sampleNumber); 71 | }; 72 | return this; 73 | }; 74 | 75 | module.exports = utils; -------------------------------------------------------------------------------- /package.json: -------------------------------------------------------------------------------- 1 | { 2 | "name": "sox-audio", 3 | "version": "0.3.0", 4 | "description": "A NodeJS interface to SoX audio utilities", 5 | "main": "index.js", 6 | "scripts": { 7 | "test": "echo \"Error: no test specified\" && exit 1" 8 | }, 9 | "repository": { 10 | "type": "git", 11 | "url": "https://github.com/psaylor/sox-audio.git" 12 | }, 13 | "keywords": [ 14 | "sox", 15 | "audio", 16 | "util", 17 | "stream", 18 | "transcode", 19 | "concat", 20 | "concatenate" 21 | ], 22 | "author": "Patricia Saylor (http://patriciasaylor.com/)", 23 | "license": "MIT", 24 | "bugs": { 25 | "url": "https://github.com/psaylor/sox-audio/issues" 26 | }, 27 | "homepage": "https://github.com/psaylor/sox-audio", 28 | "dependencies": { 29 | "through": "^2.3.6" 30 | } 31 | } 32 | --------------------------------------------------------------------------------