Skip to content
Open
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
6 changes: 3 additions & 3 deletions .travis.yml
Original file line number Diff line number Diff line change
Expand Up @@ -2,9 +2,9 @@ language: node_js
node_js:
- "7"
- "node"

before_script:
- wget http://johnvansickle.com/ffmpeg/releases/ffmpeg-release-amd64-static.tar.xz
- tar xvf ffmpeg-release-amd64-static.tar.xz
- mv ffmpeg-*.*.*-amd64-static ffmpeg
- export PATH=$PATH:$PWD/ffmpeg
- mv ffmpeg-*-amd64-static ffmpeg
- export PATH=$PATH:$PWD/ffmpeg
53 changes: 36 additions & 17 deletions README.md
Original file line number Diff line number Diff line change
Expand Up @@ -40,23 +40,25 @@ brew install ffmpeg --with-theora --with-libvorbis
> audiosprite --help
info: Usage: audiosprite [options] file1.mp3 file2.mp3 *.wav
info: Options:
--output, -o Name for the output files. [default: "output"]
--path, -u Path for files to be used on final JSON. [default: ""]
--export, -e Limit exported file types. Comma separated extension list. [default: "ogg,m4a,mp3,ac3"]
--format, -f Format of the output JSON file (jukebox, howler, howler2, createjs). [default: "jukebox"]
--log, -l Log level (debug, info, notice, warning, error). [default: "info"]
--autoplay, -a Autoplay sprite name. [default: null]
--loop Loop sprite name, can be passed multiple times. [default: null]
--silence, -s Add special "silence" track with specified duration. [default: 0]
--gap, -g Silence gap between sounds (in seconds). [default: 1]
--minlength, -m Minimum sound duration (in seconds). [default: 0]
--bitrate, -b Bit rate. Works for: ac3, mp3, mp4, m4a, ogg. [default: 128]
--vbr, -v VBR [0-9]. Works for: mp3. -1 disables VBR. [default: -1]
--samplerate, -r Sample rate. [default: 44100]
--channels, -c Number of channels (1=mono, 2=stereo). [default: 1]
--rawparts, -p Include raw slices(for Web Audio API) in specified formats. [default: ""]
--ignorerounding, -i Bypass sound placement on whole second boundaries (0=round,1=bypass). [default: 0]
--help, -h Show this help message.
--output, -o Name for the output files. [default: "output"]
--path, -u Path for files to be used on final JSON. [default: ""]
--ffmpegpath Path to your ffmpeg installation. If not specified, ffmpeg should be in your PATH. [default: ""]
--export, -e Limit exported file types. Comma separated extension list. [default: "ogg,m4a,mp3,ac3"]
--format, -f Format of the output JSON file (jukebox, howler, howler2, createjs). [default: "jukebox"]
--log, -l Log level (debug, info, notice, warning, error). [default: "info"]
--autoplay, -a Autoplay sprite name. [default: null]
--loop Loop sprite name, can be passed multiple times. [default: null]
--silence, -s Add special "silence" track with specified duration. [default: 0]
--gap, -g Silence gap between sounds (in seconds). [default: 1]
--minlength, -m Minimum sound duration (in seconds). [default: 0]
--bitrate, -b Bit rate. Works for: ac3, mp3, mp4, m4a, ogg. [default: 128]
--vbr, -v VBR [0-9]. Works for: mp3. -1 disables VBR. [default: -1]
--vbr:vorbis, -q qscale [0-10 is highest quality]. Works for: webm. -1 disables qscale. [default: -1]
--samplerate, -r Sample rate. [default: 44100]
--channels, -c Number of channels (1=mono, 2=stereo). [default: 1]
--rawparts, -p Include raw slices(for Web Audio API) in specified formats. [default: ""]
--ignorerounding, -i Bypass sound placement on whole second boundaries (0=round,1=bypass). [default: 0]
--help, -h Show this help message.


> audiosprite --autoplay bg_loop --output mygameaudio bg_loop.wav *.mp3
Expand Down Expand Up @@ -155,3 +157,20 @@ var myPlayer = new jukebox.Player(settings);
...
myPlayer.play('click');
```

### Usage with custom ffmpeg path

If your ffmpeg installation is not in your PATH, such as when you're using [@ffmpeg-installer/ffmpeg](https://www.npmjs.com/package/@ffmpeg-installer/ffmpeg), you can define a custom path.

```js
var audiosprite = require('audiosprite')

var files = ['file1.mp3', 'file2.mp3']
var opts = {output: 'result', ffmpegpath: '/path/to/ffmpeg'}

audiosprite(files, opts, function(err, obj) {
if (err) return console.error(err)

console.log(JSON.stringify(obj, null, 2))
})
```
84 changes: 43 additions & 41 deletions audiosprite.js
Original file line number Diff line number Diff line change
Expand Up @@ -25,45 +25,46 @@ const defaults = {
debug: function(){},
info: function(){},
log: function(){}
}
},
ffmpegpath: 'ffmpeg',
}

module.exports = function(files) {
let opts = {}, callback = function(){}

if (arguments.length === 2) {
callback = arguments[1]
} else if (arguments.length >= 3) {
opts = arguments[1]
callback = arguments[2]
}

if (!files || !files.length) {
return callback(new Error('No input files specified.'))
} else {
files = _.flatten(files.map(file => glob.sync(file)));
}

opts = _.extend({}, defaults, opts)

// make sure output directory exists
const outputDir = path.dirname(opts.output)
if (!fs.existsSync(outputDir)) {
require('mkdirp').sync(outputDir)
}

let offsetCursor = 0
const wavArgs = ['-ar', opts.samplerate, '-ac', opts.channels, '-f', 's16le']
const tempFile = mktemp('audiosprite')

opts.logger.debug('Created temporary file', { file: tempFile })

const json = {
resources: []
, spritemap: {}
}
spawn('ffmpeg', ['-version']).on('exit', code => {

spawn(opts.ffmpegpath, ['-version']).on('exit', code => {
if (code) {
callback(new Error('ffmpeg was not found on your path'))
}
Expand All @@ -84,17 +85,17 @@ module.exports = function(files) {
processFiles()
}
})

function mktemp(prefix) {
var tmpdir = require('os').tmpdir() || '.';
return path.join(tmpdir, prefix + '.' + Math.random().toString().substr(2));
}

function spawn(name, opt) {
opts.logger.debug('Spawn', { cmd: [name].concat(opt).join(' ') });
return require('child_process').spawn(name, opt);
}

function pad(num, size) {
var str = num.toString();

Expand All @@ -104,17 +105,17 @@ module.exports = function(files) {

return str;
}

function makeRawAudioFile(src, cb) {
var dest = mktemp('audiosprite')

opts.logger.debug('Start processing', { file: src })

fs.exists(src, function(exists) {
if (exists) {
let code = -1
let signal = undefined
let ffmpeg = spawn('ffmpeg', ['-i', path.resolve(src)]
let ffmpeg = spawn(opts.ffmpegpath, ['-i', path.resolve(src)]
.concat(wavArgs).concat('pipe:'))
let streamFinished = _.after(2, function () {
if (code) {
Expand All @@ -135,13 +136,13 @@ module.exports = function(files) {
signal = _signal
streamFinished()
})
}
}
else {
cb({ msg: 'File does not exist', file: src })
}
})
}

function appendFile(name, src, dest, cb) {
var size = 0
var reader = fs.createReadStream(src)
Expand Down Expand Up @@ -176,7 +177,7 @@ module.exports = function(files) {
})
reader.pipe(writer)
}

function appendSilence(duration, dest, cb) {
var buffer = new Buffer(Math.round(opts.samplerate * 2 * opts.channels * duration))
buffer.fill(0)
Expand All @@ -188,11 +189,11 @@ module.exports = function(files) {
cb()
})
}

function exportFile(src, dest, ext, opt, store, cb) {
var outfile = dest + '.' + ext;
spawn('ffmpeg',['-y', '-ar', opts.samplerate, '-ac', opts.channels, '-f', 's16le', '-i', src]

spawn(opts.ffmpegpath,['-y', '-ar', opts.samplerate, '-ac', opts.channels, '-f', 's16le', '-i', src]
.concat(opt).concat(outfile))
.on('exit', function(code, signal) {
if (code) {
Expand Down Expand Up @@ -220,12 +221,12 @@ module.exports = function(files) {
}
})
}

function exportFileCaf(src, dest, cb) {
if (process.platform !== 'darwin') {
return cb(true)
}

spawn('afconvert', ['-f', 'caff', '-d', 'ima4', src, dest])
.on('exit', function(code, signal) {
if (code) {
Expand All @@ -240,7 +241,7 @@ module.exports = function(files) {
return cb()
})
}

function processFiles() {
var formats = {
aiff: []
Expand All @@ -253,22 +254,22 @@ module.exports = function(files) {
, opus: ['-acodec', 'libopus', '-ab', opts.bitrate + 'k']
, webm: ['-acodec', 'libvorbis', '-f', 'webm', '-dash', '1']
};

if (opts.vbr >= 0 && opts.vbr <= 9) {
formats.mp3 = formats.mp3.concat(['-aq', opts.vbr])
}
else {
formats.mp3 = formats.mp3.concat(['-ab', opts.bitrate + 'k'])
}

// change quality of webm output - https://trac.ffmpeg.org/wiki/TheoraVorbisEncodingGuide
if (opts['vbr:vorbis'] >= 0 && opts['vbr:vorbis'] <= 10) {
formats.webm = formats.webm.concat(['-qscale:a', opts['vbr:vorbis']])
}
else {
formats.webm = formats.webm.concat(['-ab', opts.bitrate + 'k'])
}

if (opts.export.length) {
formats = opts.export.split(',').reduce(function(memo, val) {
if (formats[val]) {
Expand All @@ -277,7 +278,7 @@ module.exports = function(files) {
return memo
}, {})
}

var rawparts = opts.rawparts.length ? opts.rawparts.split(',') : null
var i = 0
opts.logger.info(files);
Expand All @@ -289,12 +290,12 @@ module.exports = function(files) {
opts.logger.debug(err);
return cb(err)
}

function tempProcessed() {
fs.unlinkSync(tmp)
cb()
}

var name = path.basename(file).replace(/\.[a-zA-Z0-9]+$/, '')
appendFile(name, tmp, tempFile, function(err) {
if (rawparts != null ? rawparts.length : void 0) {
Expand All @@ -310,9 +311,10 @@ module.exports = function(files) {
})
}, function(err) {
if (err) {
return callback(new Error('Error adding file ' + err.message))
var message = err.message || err.msg;
return callback(new Error('Error adding file ' + message))
}

async.forEachSeries(Object.keys(formats), function(ext, cb) {
opts.logger.debug('Start export', { format: ext })
exportFile(tempFile, opts.output, ext, formats[ext], true, cb)
Expand All @@ -323,15 +325,15 @@ module.exports = function(files) {
if (opts.autoplay) {
json.autoplay = opts.autoplay
}

json.resources = json.resources.map(function(e) {
return opts.path ? path.join(opts.path, path.basename(e)) : e
})

var finalJson = {}

switch (opts.format) {

case 'howler':
case 'howler2':
finalJson[opts.format === 'howler' ? 'urls' : 'src'] = [].concat(json.resources)
Expand All @@ -344,7 +346,7 @@ module.exports = function(files) {
}
}
break

case 'createjs':
finalJson.src = json.resources[0]
finalJson.data = {audioSprite: []}
Expand All @@ -357,13 +359,13 @@ module.exports = function(files) {
})
}
break

case 'default':
default:
finalJson = json
break
}

fs.unlinkSync(tempFile)
callback(null, finalJson)
})
Expand Down
4 changes: 4 additions & 0 deletions cli.js
Original file line number Diff line number Diff line change
Expand Up @@ -17,6 +17,10 @@ var optimist = require('optimist')
, 'default': ''
, describe: 'Path for files to be used on final JSON.'
})
.options('ffmpegpath', {
'default': ''
, describe: 'Path to your ffmpeg installation. If not specified, ffmpeg should be in your PATH.'
})
.options('export', {
alias: 'e'
, 'default': 'ogg,m4a,mp3,ac3'
Expand Down