diff --git a/lib/render.js b/lib/render.js index 73e8df6..1db7cf7 100644 --- a/lib/render.js +++ b/lib/render.js @@ -1,5 +1,4 @@ -var AudioContext = require('web-audio-api').AudioContext -var fs = require('fs') +var spawn = require('child_process').spawn var Canvas = require('canvas') var defaultSetting = { @@ -16,9 +15,64 @@ var defaultSetting = { baselineWidth: 0, baselineColor: 'white' } +//https://github.com/jhurliman/node-pcm +function getPcmData (filename, options, sampleCallback, endCallback) { + var outputStr = '' + var oddByte = null + var channel = 0 + var gotData = false + + options = options || {} + var channels = 2 + if (typeof options.stereo !== 'undefined') { + channels = (options.stereo) ? 2 : 1 + } + var sampleRate = 44100 + if (typeof options.sampleRate !== 'undefined') { + sampleRate = options.sampleRate + } + + var ffmpeg = spawn('ffmpeg', ['-i', filename, '-f', 's16le', '-ac', channels, + '-acodec', 'pcm_s16le', '-ar', sampleRate, '-y', 'pipe:1']) + + ffmpeg.stdout.on('data', function (data) { + gotData = true + + var value + var i = 0 + var dataLen = data.length + + // If there is a leftover byte from the previous block, combine it with the + // first byte from this block + if (oddByte !== null) { + value = ((data.readInt8(i++, true) << 8) | oddByte) / 32767.0 + sampleCallback(value, channel) + channel = ++channel % 2 + } + + for (; i < dataLen; i += 2) { + value = data.readInt16LE(i, true) / 32767.0 + sampleCallback(value, channel) + channel = ++channel % 2 + } + + oddByte = (i < dataLen) ? data.readUInt8(i, true) : null + }) + + ffmpeg.stderr.on('data', function (data) { + outputStr += data.toString() + }) + + ffmpeg.stderr.on('end', function () { + if (gotData) { + endCallback(null, outputStr) + } else { + endCallback(outputStr, null) + } + }) +} module.exports = function render (url, options, cb) { - var audioContext = new AudioContext() if (typeof options === 'function') { cb = options options = {} @@ -35,70 +89,76 @@ module.exports = function render (url, options, cb) { var canvas = new Canvas(options.width, options.height) var canvasContext = canvas.getContext('2d') - fs.readFile(url, function (err, buffer) { - if (err) return cb(err) - - audioContext.decodeAudioData(buffer, function (result) { - var data = result.getChannelData(0) - var step = Math.floor(data.length / options.width) - var ratio = options.baseline / options.height - var vals = [] - - canvasContext.fillStyle = options.backgroundColor - canvasContext.fillRect(0, 0, options.width, options.height) - - canvasContext.fillStyle = options.waveColor - - for (var i = 0; i < options.width; i += options.barWidth) { - var position = i * step - var sum = 0.0 - for (var j = position; j <= (position + step) - 1; j++) { - sum += Math.pow(data[j], 2) - } - vals.push(Math.sqrt(sum / data.length) * 10000) + var data = [] + + getPcmData(url, null, function (sample, channel) { + if (channel === 0) { + data.push(sample) + } + }, function (err) { + + if (err) { + return cb(err) + } + + var step = Math.floor(data.length / options.width) + var ratio = options.baseline / options.height + var vals = [] + + canvasContext.fillStyle = options.backgroundColor + canvasContext.fillRect(0, 0, options.width, options.height) + + canvasContext.fillStyle = options.waveColor + + for (var i = 0; i < options.width; i += options.barWidth) { + var position = i * step + var sum = 0.0 + for (var j = position; j <= (position + step) - 1; j++) { + sum += Math.pow(data[j], 2) } + vals.push(Math.sqrt(sum / data.length) * 10000) + } + + var maxValue = Math.max.apply(null, vals) - var maxValue = Math.max.apply(null, vals) - - vals.forEach(function (val, index) { - var scale = options.height / maxValue - val *= scale - var w = options.barWidth - if (options.barGap !== 0) { - w *= Math.abs(1 - options.barGap) - } - var x = index * options.barWidth + (w / 2) - - var lowerHeight = val * ratio - - if (lowerHeight < options.padding) { - lowerHeight = 1 - } else { - lowerHeight -= options.padding - } - - var upperHeight = val * (1 - ratio) - - if (upperHeight < options.padding) { - upperHeight = 1 - } else { - upperHeight -= options.padding - } - - if (options.waveAlpha < 1) { - canvasContext.clearRect(x, options.baseline, w, upperHeight) - canvasContext.clearRect(x, options.baseline, w, -lowerHeight) - canvasContext.globalAlpha = options.waveAlpha - } - canvasContext.fillRect(x, options.baseline, w, upperHeight) - canvasContext.fillRect(x, options.baseline, w, -lowerHeight) - }) - - if (options.baselineWidth >= 1) { - canvasContext.fillStyle = options.baselineColor - canvasContext.fillRect(0, options.baseline - (options.baselineWidth / 2), options.width, options.baselineWidth) + vals.forEach(function (val, index) { + var scale = options.height / maxValue + val *= scale + var w = options.barWidth + if (options.barGap !== 0) { + w *= Math.abs(1 - options.barGap) } - cb(null, canvas.toBuffer()) + var x = index * options.barWidth + (w / 2) + + var lowerHeight = val * ratio + + if (lowerHeight < options.padding) { + lowerHeight = 1 + } else { + lowerHeight -= options.padding + } + + var upperHeight = val * (1 - ratio) + + if (upperHeight < options.padding) { + upperHeight = 1 + } else { + upperHeight -= options.padding + } + + if (options.waveAlpha < 1) { + canvasContext.clearRect(x, options.baseline, w, upperHeight) + canvasContext.clearRect(x, options.baseline, w, -lowerHeight) + canvasContext.globalAlpha = options.waveAlpha + } + canvasContext.fillRect(x, options.baseline, w, upperHeight) + canvasContext.fillRect(x, options.baseline, w, -lowerHeight) }) + + if (options.baselineWidth >= 1) { + canvasContext.fillStyle = options.baselineColor + canvasContext.fillRect(0, options.baseline - (options.baselineWidth / 2), options.width, options.baselineWidth) + } + cb(null, canvas.toBuffer()) }) } diff --git a/package.json b/package.json index a72fa89..3b8e4b0 100644 --- a/package.json +++ b/package.json @@ -1,6 +1,6 @@ { "name": "node-wave", - "version": "0.0.3", + "version": "0.0.4", "description": "Render waveform image like SoundCloud", "main": "index.js", "scripts": { @@ -19,8 +19,7 @@ "url": "git+https://github.com/trquoccuong/node-wave.git" }, "dependencies": { - "canvas": "^1.6.2", - "web-audio-api": "^0.2.2" + "canvas": "^1.6.2" }, "devDependencies": { "mocha": "^3.2.0", diff --git a/test/out.png b/test/out.png deleted file mode 100644 index 015b09e..0000000 Binary files a/test/out.png and /dev/null differ