Browse Source

Fix merge

Noah 7 years ago
parent
commit
6d54939d01

+ 1 - 2
Dockerfile View File

@@ -3,9 +3,8 @@ FROM ubuntu:16.04
3 3
 # Install dependencies
4 4
 RUN apt-get update --yes && apt-get upgrade --yes
5 5
 RUN apt-get install git nodejs npm \
6
-libcairo2-dev libjpeg8-dev libpango1.0-dev libgif-dev build-essential g++ \
6
+libcairo2-dev libjpeg8-dev libpango1.0-dev libgif-dev libpng-dev build-essential g++ \
7 7
 ffmpeg \
8
-libgroove-dev zlib1g-dev libpng-dev \
9 8
 redis-server --yes
10 9
 
11 10
 RUN ln -s `which nodejs` /usr/bin/node

+ 6 - 24
INSTALL.md View File

@@ -5,7 +5,6 @@ Audiogram has a number of dependencies:
5 5
 * [Node.js/NPM](https://nodejs.org/) v0.11.2 or greater
6 6
 * [node-canvas dependencies](https://github.com/Automattic/node-canvas#installation)
7 7
 * [FFmpeg](https://www.ffmpeg.org/)
8
-* [libgroove](https://github.com/andrewrk/libgroove)
9 8
 
10 9
 If you're using a particularly fancy distributed setup you'll also need to install [Redis](http://redis.io/).
11 10
 
@@ -22,10 +21,9 @@ Note: if you're using something with < 1GB of RAM, like a Digital Ocean micro dr
22 21
 An example bootstrap script for installing Audiogram on Ubuntu looks like this:
23 22
 
24 23
 ```sh
25
-# 14.04 only: add PPAs for FFmpeg and Libgroove
24
+# 14.04 only: add PPA for FFmpeg
26 25
 # Not required for 15.04+
27 26
 sudo add-apt-repository ppa:mc3man/trusty-media --yes
28
-sudo apt-add-repository ppa:andrewrk/libgroove --yes
29 27
 
30 28
 # Update/upgrade
31 29
 sudo apt-get update --yes && sudo apt-get upgrade --yes
@@ -35,11 +33,9 @@ sudo apt-get update --yes && sudo apt-get upgrade --yes
35 33
 # Git
36 34
 # node-canvas dependencies (Cairo, Pango, libgif, libjpeg)
37 35
 # FFmpeg
38
-# node-waveform dependencies (libgroove, zlib, libpng)
39 36
 sudo apt-get install git nodejs npm \
40
-libcairo2-dev libjpeg8-dev libpango1.0-dev libgif-dev build-essential g++ \
37
+libcairo2-dev libjpeg8-dev libpango1.0-dev libgif-dev libpng-dev build-essential g++ \
41 38
 ffmpeg \
42
-libgroove-dev zlib1g-dev libpng-dev \
43 39
 --yes
44 40
 
45 41
 # Install Redis if you plan to use it to share rendering among multiple processes/servers
@@ -82,21 +78,19 @@ Installing on a Mac can get a little rocky. Essentially, you need to install thr
82 78
 
83 79
 1. [Node.js/NPM](https://nodejs.org/)
84 80
 2. [node-canvas dependencies](https://github.com/Automattic/node-canvas#installation)
85
-4. [libgroove](https://github.com/andrewrk/libgroove)
86
-
87
-You probably don't need to install FFmpeg separately because libgroove depends on it.
81
+4. [FFmpeg](https://www.ffmpeg.org/)
88 82
 
89 83
 You can install Node.js by [downloading it from the website](https://nodejs.org/).
90 84
 
91
-Installation of node-canvas dependencies and libgroove might look like the following with [Homebrew](http://brew.sh/) (you'll want to make sure [XCode](https://developer.apple.com/xcode/) is installed and up-to-date too):
85
+Installation of node-canvas dependencies and FFmpeg might look like the following with [Homebrew](http://brew.sh/) (you'll want to make sure [XCode](https://developer.apple.com/xcode/) is installed and up-to-date too):
92 86
 
93 87
 ```sh
94 88
 # Install Git if you haven't already
95 89
 brew install git
96 90
 
97
-# Install Cairo, Pango, libgif, libjpeg, libgroove, and FFmpeg
91
+# Install Cairo, Pango, libgif, libjpeg, libpng, and FFmpeg
98 92
 # You may not need to install zlib
99
-brew install pkg-config cairo pango libpng jpeg giflib libgroove ffmpeg
93
+brew install pkg-config cairo pango libpng jpeg giflib ffmpeg
100 94
 
101 95
 # Go to the directory where you want the audiogram directory
102 96
 cd /where/to/put/this/
@@ -147,18 +141,6 @@ If things aren't working on a Mac, there are a few fixes you can try.
147 141
 
148 142
 Follow the [Homebrew troubleshooting guide](https://github.com/Homebrew/brew/blob/master/share/doc/homebrew/Troubleshooting.md#troubleshooting), particularly making sure that XCode is up to date.
149 143
 
150
-### Installing libgroove manually
151
-
152
-If you're on a Mac and installing [libgroove](https://github.com/andrewrk/libgroove) with Homebrew failed for some reason, you can try following the instructions in the repo to install it more manually.
153
-
154
-### Installing XCode command line tools
155
-
156
-Audiogram uses [waveform](https://github.com/andrewrk/waveform), which requires the development version of `zlib`. Your Mac might already have this, but if it doesn't, installing XCode command line tools might help:
157
-
158
-```sh
159
-xcode-select --install
160
-```
161
-
162 144
 ### Updating node-gyp
163 145
 
164 146
 Updating node-gyp to a current version with:

+ 0 - 35
audiogram/duration.js View File

@@ -1,35 +0,0 @@
1
-var probe = require("node-ffprobe");
2
-
3
-module.exports = function(filename, cb){
4
-
5
-  probe(filename, function(err, probeData) {
6
-
7
-    if (err) {
8
-      return cb(err);
9
-    }
10
-
11
-    var duration = getDuration(probeData);
12
-
13
-    if (!duration || duration === "N/A" || !(duration > 0)) {
14
-      return cb("Couldn't probe audio duration.");
15
-    }
16
-
17
-    return cb(null, duration);
18
-
19
-  });
20
-
21
-};
22
-
23
-function getDuration(probeData) {
24
-
25
-  if (probeData.format && probeData.format.duration) {
26
-    return probeData.format.duration;
27
-  }
28
-
29
-  if (Array.isArray(probeData.streams) && probeData.streams.length && probeData.streams[0].duration) {
30
-    return probeData.streams[0].duration;
31
-  }
32
-
33
-  return null;
34
-
35
-}

+ 23 - 27
audiogram/index.js View File

@@ -5,7 +5,8 @@ var path = require("path"),
5 5
     serverSettings = require("../settings/"),
6 6
     transports = require("../lib/transports/"),
7 7
     logger = require("../lib/logger/"),
8
-    getDuration = require("./duration.js"),
8
+    Profiler = require("../lib/profiler.js"),
9
+    probe = require("../lib/probe.js"),
9 10
     getWaveform = require("./waveform.js"),
10 11
     initializeCanvas = require("./initialize-canvas.js"),
11 12
     drawFrames = require("./draw-frames.js"),
@@ -24,48 +25,43 @@ function Audiogram(id) {
24 25
   this.videoPath = path.join(this.dir, "video.mp4");
25 26
   this.frameDir = path.join(this.dir, "frames");
26 27
 
28
+  this.profiler = new Profiler();
29
+
27 30
   return this;
28 31
 
29 32
 }
30 33
 
31
-// Probe an audio file for its duration, compute the number of frames required
32
-Audiogram.prototype.getDuration = function(cb) {
34
+// Get the waveform data from the audio file, split into frames
35
+Audiogram.prototype.getWaveform = function(cb) {
33 36
 
34 37
   var self = this;
35 38
 
36
-  this.status("duration");
39
+  this.status("probing");
37 40
 
38
-  getDuration(this.audioPath, function(err, duration){
41
+  probe(this.audioPath, function(err, data){
39 42
 
40 43
     if (err) {
41 44
       return cb(err);
42 45
     }
43 46
 
44
-    if (self.settings.theme.maxDuration && self.settings.theme.maxDuration < duration) {
45
-      cb("Exceeds max duration of " + self.settings.theme.maxDuration + "s");
47
+    if (self.settings.theme.maxDuration && self.settings.theme.maxDuration < data.duration) {
48
+      return cb("Exceeds max duration of " + self.settings.theme.maxDuration + "s");
46 49
     }
47 50
 
48
-    self.set("numFrames", self.numFrames = Math.floor(duration * self.settings.theme.framesPerSecond));
49
-
50
-    cb(null);
51
-
52
-  });
53
-
54
-};
51
+    self.profiler.size(data.duration);
52
+    self.set("numFrames", self.numFrames = Math.floor(data.duration * self.settings.theme.framesPerSecond));
53
+    self.status("waveform");
55 54
 
56
-// Get the waveform data from the audio file, split into frames
57
-Audiogram.prototype.getWaveform = function(cb) {
58
-
59
-  var self = this;
55
+    getWaveform(self.audioPath, {
56
+      numFrames: self.numFrames,
57
+      samplesPerFrame: self.settings.theme.samplesPerFrame,
58
+      channels: data.channels
59
+    }, function(waveformErr, waveform){
60 60
 
61
-  this.status("waveform");
61
+      return cb(waveformErr, self.waveform = waveform);
62 62
 
63
-  getWaveform(this.audioPath, {
64
-    numFrames: this.numFrames,
65
-    samplesPerFrame: this.settings.theme.samplesPerFrame
66
-  }, function(err, waveform){
63
+    });
67 64
 
68
-    return cb(err, self.waveform = waveform);
69 65
 
70 66
   });
71 67
 
@@ -162,9 +158,6 @@ Audiogram.prototype.render = function(cb) {
162 158
     q.defer(this.trimAudio.bind(this), this.settings.start || 0, this.settings.end || null);
163 159
   }
164 160
 
165
-  // Get the audio's duration for computing number of frames
166
-  q.defer(this.getDuration.bind(this));
167
-
168 161
   // Get the audio waveform data
169 162
   q.defer(this.getWaveform.bind(this));
170 163
 
@@ -187,6 +180,8 @@ Audiogram.prototype.render = function(cb) {
187 180
       self.set("url", transports.getURL(self.id));
188 181
     }
189 182
 
183
+    logger.debug(self.profiler.print());
184
+
190 185
     return cb(err);
191 186
 
192 187
   });
@@ -203,6 +198,7 @@ Audiogram.prototype.set = function(field, value) {
203 198
 
204 199
 // Convenience method for .set("status")
205 200
 Audiogram.prototype.status = function(value) {
201
+  this.profiler.start(value);
206 202
   return this.set("status", value);
207 203
 };
208 204
 

+ 3 - 3
audiogram/trim.js View File

@@ -1,16 +1,16 @@
1 1
 var exec = require("child_process").exec,
2
-    getDuration = require("./duration.js");
2
+    probe = require("../lib/probe.js");
3 3
 
4 4
 function trimAudio(options, cb) {
5 5
 
6 6
   if (!options.endTime) {
7 7
 
8
-    return getDuration(options.origin, function(err, duration){
8
+    return probe(options.origin, function(err, data){
9 9
       if (err) {
10 10
         return cb(err);
11 11
       }
12 12
 
13
-      options.endTime = duration;
13
+      options.endTime = data.duration;
14 14
       trimAudio(options, cb);
15 15
 
16 16
     });

+ 67 - 37
audiogram/waveform.js View File

@@ -1,59 +1,89 @@
1
-var waveform = require("waveform"),
2
-    d3 = require("d3");
1
+var probe = require("../lib/probe.js"),
2
+    d3 = require("d3"),
3
+    pcmStream = require("../lib/pcm.js");
3 4
 
4 5
 function getWaveform(filename, options, cb) {
5 6
 
6
-  var numSamples = options.numFrames * options.samplesPerFrame;
7
+  var stream = pcmStream(filename, {
8
+        channels: options.channels
9
+      }),
10
+      samples = [];
7 11
 
8
-  var waveformOptions = {
9
-    "scan": false,
10
-    "waveformjs": "-",
11
-    "wjs-width": numSamples,
12
-    "wjs-precision": 2,
13
-    "wjs-plain": true,
14
-    "encoding": "utf8"
15
-  };
12
+  stream.on("data",function(sample, channel){
16 13
 
17
-  waveform(filename, waveformOptions, function(err, buf) {
18
-
19
-    if (err) {
20
-      return cb(err);
14
+    // Average multiple channels
15
+    if (channel > 0) {
16
+      samples[samples.length - 1] = ((samples[samples.length - 1] * channel) + sample) / (channel + 1);
17
+    } else {
18
+      samples.push(sample);
21 19
     }
22 20
 
23
-    cb(null, processWaveform(JSON.parse(buf)));
21
+  });
22
+
23
+  stream.on("error", cb);
24 24
 
25
+  stream.on("end", function(output){
26
+    var processed = processSamples(samples, options.numFrames, options.samplesPerFrame);
27
+    return cb(null, processed);
25 28
   });
26 29
 
27
-  // Slice one-dimensional waveform data into array of arrays, one array per frame
28
-  function processWaveform(waveformData) {
30
+}
29 31
 
30
-    var max = -Infinity,
31
-        maxFrame;
32
+function processSamples(samples, numFrames, samplesPerFrame) {
32 33
 
33
-    waveformData.forEach(function(d, i){
34
-      if (d > max) {
35
-        max = d;
36
-        maxFrame = Math.floor(i / options.samplesPerFrame);
37
-      }
38
-    });
34
+  // TODO spread out slop across frames
35
+  var perFrame = Math.floor(samples.length / numFrames),
36
+      perPoint = Math.floor(perFrame / samplesPerFrame),
37
+      range = d3.range(samplesPerFrame),
38
+      maxFrame,
39
+      maxMedian = min = max = 0;
39 40
 
40
-    // Scale peaks to 1
41
-    var scaled = d3.scaleLinear()
42
-      .domain([0, max])
43
-      .range([0, 1]);
41
+  var unadjusted = d3.range(numFrames).map(function(frame){
44 42
 
45
-    var waveformFrames = d3.range(options.numFrames).map(function getFrame(frameNumber) {
43
+    var frameSamples = samples.slice(frame * perFrame, (frame + 1) * perFrame),
44
+        points = range.map(function(point){
46 45
 
47
-      return waveformData.slice(options.samplesPerFrame * frameNumber, options.samplesPerFrame * (frameNumber + 1)).map(scaled);
46
+          var pointSamples = frameSamples.slice(point * perPoint, (point + 1) * perPoint),
47
+              localMin = localMax = 0;
48 48
 
49
-    });
49
+          for (var i = 0, l = pointSamples.length; i < l; i++) {
50
+            if (pointSamples[i] < localMin) localMin = pointSamples[i];
51
+            if (pointSamples[i] > localMax) localMax = pointSamples[i];
52
+          }
53
+
54
+          if (localMin < min) min = localMin;
55
+          if (localMax > max) max = localMax;
56
+
57
+          // Min value, max value, and midpoint value
58
+          return [localMin, localMax, pointSamples[Math.floor(pointSamples.length / 2)]];
59
+
60
+        }),
61
+        median = d3.median(points.map(function(point){
62
+          return point[1] - point[0];
63
+        }));
50 64
 
51
-    // Set the first and last frame's waveforms to something peak-y for better thumbnails
52
-    waveformFrames[0] = waveformFrames[waveformFrames.length - 1] = waveformFrames[maxFrame];
65
+    if (median > maxMedian) {
66
+      maxMedian = median;
67
+      maxFrame = frame;
68
+    }
69
+
70
+    return points;
71
+
72
+  });
73
+
74
+  // Scale up to -1 / 1
75
+  var adjustment = 1 / Math.max(Math.abs(min), Math.abs(max));
76
+
77
+  var adjusted = unadjusted.map(function(frame){
78
+    return frame.map(function(point){
79
+      return [adjustment * point[0], adjustment * point[1]];
80
+    });
81
+  });
53 82
 
54
-    return waveformFrames;
83
+  // Make first and last frame peaky
84
+  adjusted[0] = adjusted[numFrames - 1] = adjusted[maxFrame];
55 85
 
56
-  }
86
+  return adjusted;
57 87
 
58 88
 }
59 89
 

+ 2 - 2
client/index.js View File

@@ -263,8 +263,8 @@ function statusMessage(result) {
263 263
       return "Downloading audio for processing";
264 264
     case "trim":
265 265
       return "Trimming audio";
266
-    case "duration":
267
-      return "Checking duration";
266
+    case "probing":
267
+      return "Probing audio file";
268 268
     case "waveform":
269 269
       return "Analyzing waveform";
270 270
     case "renderer":

File diff suppressed because it is too large
+ 1 - 1
client/sample-wave.js


+ 1 - 0
client/waveform.js View File

@@ -38,6 +38,7 @@ module.exports = function(file, cb) {
38 38
   var fileReader = new FileReader();
39 39
 
40 40
   var close = function(err, data) {
41
+    console.warn(err);
41 42
     ctx.close();
42 43
     cb(err, data);
43 44
   };

+ 55 - 0
lib/pcm.js View File

@@ -0,0 +1,55 @@
1
+var spawn = require("child_process").spawn,
2
+    stream = require("stream");
3
+
4
+// Based on https://github.com/jhurliman/node-pcm
5
+// Modified to respect channels, use fewer vars, and return a stream
6
+module.exports = function(filename, options) {
7
+  var sampleStream = new stream.Stream(),
8
+      channels = 2,
9
+      output = "",
10
+      channel = 0,
11
+      oddByte;
12
+
13
+  sampleStream.readable = true;
14
+
15
+  if (options && options.channels) channels = options.channels;
16
+
17
+  var args = ["-i", filename, "-f", "s16le", "-ac", channels, "-acodec", "pcm_s16le"];
18
+
19
+  if (options && options.sampleRate) {
20
+    args.push("-ar", options.sampleRate);
21
+  }
22
+
23
+  args.push("-y", "pipe:1");
24
+
25
+  var spawned = spawn("ffmpeg", args);
26
+
27
+  spawned.stdout.on("data", function(data) {
28
+
29
+    var len = data.length;
30
+
31
+    if (oddByte != null) {
32
+      sampleStream.emit("data", ((data.readInt8(i++, true) << 8) | oddByte) / 32767.0, channel);
33
+      channel = ++channel % channels;
34
+    }
35
+
36
+    for (var i = 0; i < len; i += 2) {
37
+      sampleStream.emit("data", data.readInt16LE(i, true) / 32767.0, channel);
38
+      channel = ++channel % channels;
39
+    }
40
+
41
+    oddByte = (i < len) ? data.readUInt8(i, true) : null;
42
+
43
+  });
44
+
45
+  spawned.stderr.on("data", function(data) {
46
+    output += data.toString();
47
+  });
48
+
49
+  spawned.stderr.on("end", function() {
50
+    sampleStream.emit(oddByte !== undefined ? "end" : "error", output);
51
+  });
52
+
53
+  return sampleStream;
54
+
55
+};

+ 43 - 0
lib/probe.js View File

@@ -0,0 +1,43 @@
1
+var probe = require("node-ffprobe");
2
+
3
+module.exports = function(filename, cb){
4
+
5
+  probe(filename, function(err, probeData) {
6
+
7
+    if (err) {
8
+      return cb(err);
9
+    }
10
+
11
+    var duration = getProperty(probeData, "duration"),
12
+        channels = getProperty(probeData, "channels");
13
+
14
+    if (!duration || duration === "N/A" || !(duration > 0)) {
15
+      return cb("Couldn't probe audio duration.");
16
+    }
17
+
18
+    if (typeof channels !== "number" || channels < 1 || channels > 2) {
19
+      return cb("Couldn't detect mono/stereo channels");
20
+    }
21
+
22
+    return cb(null, {
23
+      duration: duration,
24
+      channels: channels
25
+    });
26
+
27
+  });
28
+
29
+};
30
+
31
+function getProperty(probeData, property) {
32
+
33
+  if (probeData.format && probeData.format[property]) {
34
+    return probeData.format[property];
35
+  }
36
+
37
+  if (Array.isArray(probeData.streams) && probeData.streams.length && probeData.streams[0][property]) {
38
+    return probeData.streams[0][property];
39
+  }
40
+
41
+  return null;
42
+
43
+}

+ 42 - 0
lib/profiler.js View File

@@ -0,0 +1,42 @@
1
+function Profiler() {
2
+  this._times = {};
3
+  return this;
4
+};
5
+
6
+Profiler.prototype.start = function(key) {
7
+  this.end(this._current);
8
+  this._current = key;
9
+  this._times[this._current] = { start: Date.now() };
10
+  return this;
11
+};
12
+
13
+Profiler.prototype.size = function(size) {
14
+  if (!arguments.length) return this._size;
15
+  this._size = size;
16
+  return this;
17
+};
18
+
19
+Profiler.prototype.end = function(key) {
20
+  if (key in this._times) this._times[key].end = Date.now();
21
+  return this;
22
+};
23
+
24
+Profiler.prototype.print = function(size) {
25
+  var rows = [],
26
+      row;
27
+
28
+  this.end(this._current);
29
+
30
+  for (var key in this._times) {
31
+    row = { key: key, time: this._times[key].end - this._times[key].start };
32
+    if (this._size) row.per = row.time / this._size;
33
+    rows.push(row);
34
+  }
35
+
36
+  return rows.map(function(row){
37
+    return row.key + ": " + Math.round(row.time) + "ms total" + (row.per ? ", " + Math.round(row.per) + "ms per" : "");
38
+  }).join("\n");
39
+
40
+};
41
+
42
+module.exports = Profiler;

+ 1 - 2
package.json View File

@@ -25,6 +25,7 @@
25 25
   "dependencies": {
26 26
     "aws-sdk": "^2.2.39",
27 27
     "browserify": "^13.0.0",
28
+    "canvas": "git+https://github.com/chearon/node-canvas.git#b62dd3a9fa",
28 29
     "compression": "^1.6.1",
29 30
     "d3": "^4.1.1",
30 31
     "dotenv": "^2.0.0",
@@ -39,8 +40,6 @@
39 40
     "rimraf": "^2.5.0",
40 41
     "smartquotes": "^1.0.0",
41 42
     "underscore": "^1.8.3",
42
-    "canvas": "git+https://github.com/chearon/node-canvas.git#b62dd3a9fa",
43
-    "waveform": "git+https://github.com/veltman/node-waveform.git#8b1a22f109",
44 43
     "webaudio-peaks": "0.0.5",
45 44
     "winston": "^2.2.0"
46 45
   },

+ 10 - 9
renderer/patterns.js View File

@@ -36,13 +36,13 @@ function curve(interpolator) {
36 36
 
37 37
     var top = data.map(function(d,i){
38 38
 
39
-      return [x(i), baseline - height(d)];
39
+      return [x(i), baseline - height(d[1])];
40 40
 
41 41
     });
42 42
 
43 43
     var bottom = data.map(function(d,i){
44 44
 
45
-      return [x(i), baseline + height(d)];
45
+      return [x(i), baseline + height(-d[0])];
46 46
 
47 47
     }).reverse();
48 48
 
@@ -90,16 +90,17 @@ function bars(round) {
90 90
 
91 91
     data.forEach(function(val, i){
92 92
 
93
-      var h = height(val),
94
-          x = barX(i);
93
+      var h = height(-val[0]) + height(val[1]),
94
+          x = barX(i),
95
+          y = baseline - height(val[1]);
95 96
 
96
-      context.fillRect(x, baseline - h, barWidth, h * 2);
97
+      context.fillRect(x, y, barWidth, h);
97 98
 
98 99
       if (round) {
99 100
         context.beginPath();
100
-        context.arc(x + barWidth / 2, baseline - h, barWidth / 2, 0, 2 * Math.PI);
101
-        context.moveTo(x + barWidth / 2, baseline + h);
102
-        context.arc(x + barWidth / 2, baseline + h, barWidth / 2, 0, 2 * Math.PI);
101
+        context.arc(x + barWidth / 2, y, barWidth / 2, 0, 2 * Math.PI);
102
+        context.moveTo(x + barWidth / 2, y + h);
103
+        context.arc(x + barWidth / 2, y + h, barWidth / 2, 0, 2 * Math.PI);
103 104
         context.fill();
104 105
       }
105 106
 
@@ -132,7 +133,7 @@ function bricks(rainbow) {
132 133
 
133 134
     data.forEach(function(val, i){
134 135
 
135
-      var bricks = Math.max(1, Math.floor(height(val) / (brickHeight + brickGap))),
136
+      var bricks = Math.max(1, Math.floor(height(val[1]) / (brickHeight + brickGap))),
136 137
           x = barX(i);
137 138
 
138 139
       d3.range(bricks).forEach(function(b){

+ 1 - 1
server/status.js View File

@@ -29,7 +29,7 @@ module.exports = function(req, res) {
29 29
         hash = { status: "unknown" };
30 30
       }
31 31
 
32
-      ["duration","numFrames","framesComplete"].forEach(function(key) {
32
+      ["numFrames", "framesComplete"].forEach(function(key) {
33 33
         if (key in hash) {
34 34
           hash[key] = +hash[key];
35 35
         }

BIN
test/data/glazed-donut-mono.mp3 View File


BIN
test/data/glazed-donut-mono.wav View File


BIN
test/data/long-beeps.mp3 View File


BIN
test/data/short.wav View File


+ 8 - 1
test/frame-test.js View File

@@ -10,7 +10,14 @@ require("mkdirp").sync(path.join(__dirname, "tmp", "frames"));
10 10
 
11 11
 var frameDir = path.join(__dirname, "tmp", "frames");
12 12
 
13
-var waveform = [[0, 1, 0], [1, 0.1, 1]];
13
+var waveform = [
14
+  [
15
+    [0, 0], [-1, 1], [0, 0]
16
+  ],
17
+  [
18
+    [-1, 1], [-0.1, 0.1], [-1, 1]
19
+  ]
20
+];
14 21
 
15 22
 function tester(options) {
16 23
 

test/duration-test.js → test/probe-test.js View File

@@ -5,38 +5,54 @@ var tape = require("tape"),
5 5
 
6 6
 require("mkdirp").sync(path.join(__dirname, "tmp"));
7 7
 
8
-var getDuration = require("../audiogram/duration.js"),
8
+var probe = require("../lib/probe.js"),
9 9
     trimAudio = require("../audiogram/trim.js");
10 10
 
11
-tape("MP3 duration", function(test) {
11
+tape("MP3 probe", function(test) {
12 12
 
13
-  getDuration(path.join(__dirname, "data/glazed-donut.mp3"), function(err, duration){
13
+  probe(path.join(__dirname, "data/glazed-donut.mp3"), function(err, data){
14 14
 
15 15
     test.error(err);
16
-    test.equal(typeof duration, "number");
17
-    test.assert(Math.abs(duration - 26.67) < 0.1);
16
+    test.equal(typeof data.duration, "number");
17
+    test.equal(data.channels, 2);
18
+    test.assert(Math.abs(data.duration - 26.67) < 0.1);
18 19
     test.end();
19 20
 
20 21
   });
21 22
 
22 23
 });
23 24
 
24
-tape("WAV duration", function(test) {
25
+tape("WAV probe", function(test) {
25 26
 
26
-  getDuration(path.join(__dirname, "data/glazed-donut.wav"), function(err, duration){
27
+  probe(path.join(__dirname, "data/glazed-donut.wav"), function(err, data){
27 28
 
28 29
     test.error(err);
29
-    test.equal(typeof duration, "number");
30
-    test.assert(Math.abs(duration - 1.83) < 0.1);
30
+    test.equal(typeof data.duration, "number");
31
+    test.equal(data.channels, 2);
32
+    test.assert(Math.abs(data.duration - 1.83) < 0.1);
31 33
     test.end();
32 34
 
33 35
   });
34 36
 
35 37
 });
36 38
 
37
-tape("Duration error", function(test) {
39
+tape("Mono probe", function(test) {
38 40
 
39
-  getDuration(path.join(__dirname, "..", "README.md"), function(err){
41
+  probe(path.join(__dirname, "data/short.wav"), function(err, data){
42
+
43
+    test.error(err);
44
+    test.equal(typeof data.duration, "number");
45
+    test.equal(data.channels, 1);
46
+    test.assert(Math.abs(data.duration - 0.01) < 0.01);
47
+    test.end();
48
+
49
+  });
50
+
51
+});
52
+
53
+tape("Probe error", function(test) {
54
+
55
+  probe(path.join(__dirname, "..", "README.md"), function(err){
40 56
 
41 57
     test.ok(err);
42 58
     test.end();
@@ -55,12 +71,12 @@ tape("Trim start", function(test) {
55 71
 
56 72
   queue(1)
57 73
     .defer(trimAudio, options)
58
-    .defer(getDuration, options.destination)
59
-    .await(function(err, _, duration){
74
+    .defer(probe, options.destination)
75
+    .await(function(err, _, data){
60 76
 
61 77
       test.error(err);
62
-      test.equal(typeof duration, "number");
63
-      test.assert(Math.abs(duration - 20) < 0.1);
78
+      test.equal(typeof data.duration, "number");
79
+      test.assert(Math.abs(data.duration - 20) < 0.1);
64 80
       test.end();
65 81
 
66 82
     });
@@ -77,12 +93,12 @@ tape("Trim end", function(test) {
77 93
 
78 94
   queue(1)
79 95
     .defer(trimAudio, options)
80
-    .defer(getDuration, options.destination)
81
-    .await(function(err, _, duration){
96
+    .defer(probe, options.destination)
97
+    .await(function(err, _, data){
82 98
 
83 99
       test.error(err);
84
-      test.equal(typeof duration, "number");
85
-      test.assert(Math.abs(duration - 20) < 0.1);
100
+      test.equal(typeof data.duration, "number");
101
+      test.assert(Math.abs(data.duration - 20) < 0.1);
86 102
       test.end();
87 103
 
88 104
     });
@@ -100,12 +116,12 @@ tape("Trim start & end", function(test) {
100 116
 
101 117
   queue(1)
102 118
     .defer(trimAudio, options)
103
-    .defer(getDuration, options.destination)
104
-    .await(function(err, _, duration){
119
+    .defer(probe, options.destination)
120
+    .await(function(err, _, data){
105 121
 
106 122
       test.error(err);
107
-      test.equal(typeof duration, "number");
108
-      test.assert(Math.abs(duration - 5) < 0.1);
123
+      test.equal(typeof data.duration, "number");
124
+      test.assert(Math.abs(data.duration - 5) < 0.1);
109 125
       test.end();
110 126
 
111 127
     });
@@ -122,25 +138,11 @@ tape("Trim invalid", function(test) {
122 138
     endTime: 4
123 139
   };
124 140
 
125
-  queue(1)
126
-    .defer(trimAudio, options)
127
-    .defer(getDuration, options.destination)
128
-    .await(function(err, _, duration){
129
-
130
-      test.ok(err);
131
-      test.end();
132
-
133
-    });
134
-
135
-});
136
-
137
-// Cleanup
138
-tape.onFinish(function(){
139
-  require("rimraf")(path.join(__dirname, "tmp"), function(err){
140
-    if (err) {
141
-      throw err;
142
-    }
141
+  trimAudio(options, function(err){
142
+    test.ok(err);
143
+    test.end();
143 144
   });
145
+
144 146
 });
145 147
 
146 148
 // Cleanup

+ 29 - 22
test/waveform-test.js View File

@@ -1,66 +1,73 @@
1 1
 var tape = require("tape"),
2 2
     path = require("path");
3 3
 
4
-var getWaveform = require("../audiogram/waveform.js");
4
+var getWaveform = require("../audiogram/waveform.js"),
5
+    probe = require("../lib/probe.js");
5 6
 
6 7
 var sample = path.join(__dirname, "data/glazed-donut.mp3");
7 8
 
8 9
 tape("Waveform", function(test) {
9 10
 
10 11
   var options = {
11
-    numFrames: 500,
12
+    framesPerSecond: 20,
12 13
     samplesPerFrame: 10
13 14
   };
14 15
 
15
-  getWaveform(sample, options, function(err, waveform){
16
+  probe(sample, function(e1, data){
16 17
 
17
-    test.error(err);
18
-    test.assert(Array.isArray(waveform) && waveform.length === options.numFrames);
18
+    test.error(e1);
19 19
 
20
-    var firstMax = Math.max.apply(null, waveform[0]);
20
+    getWaveform(sample, options, function(e2, waveform){
21 21
 
22
-    test.assert(firstMax <= 1);
22
+      test.error(e2);
23
+      test.assert(Array.isArray(waveform) && waveform.length === Math.floor(data.duration * options.framesPerSecond));
23 24
 
24
-    test.assert(waveform.every(function(frame){
25
-      return frame.length === options.samplesPerFrame;
26
-    }));
25
+      var firstMax = Math.max.apply(null, waveform[0].map(function(d){ return d[1]; }));
27 26
 
28
-    test.assert(waveform.every(function(frame){
29
-      return frame.every(function(val){
30
-        return typeof val === "number" && val >= 0 && val <= firstMax;
31
-      });
32
-    }));
27
+      test.assert(firstMax <= 1);
33 28
 
34
-    test.end();
29
+      test.assert(waveform.every(function(frame){
30
+        return frame.length === options.samplesPerFrame && frame.every(function(f){
31
+          return f.length === 2 && typeof f[0] === "number" && typeof f[1] === "number" && f[0] <= 0 && f[0] >= -1 && f[1] >= 0 && f[1] <= firstMax;
32
+        });
33
+      }));
34
+
35
+      test.end();
36
+
37
+    });
35 38
 
36 39
   });
37 40
 
38 41
 });
39 42
 
40
-tape("Waveform missing numFrames", function(test) {
43
+tape("Max Duration Error", function(test) {
41 44
 
42 45
   var options = {
43
-    samplesPerFrame: 10
46
+    framesPerSecond: 20,
47
+    samplesPerFrame: 10,
48
+    maxDuration: 20
44 49
   };
45 50
 
46 51
   getWaveform(sample, options, function(err, waveform){
47 52
 
48
-    test.ok(err);
53
+    test.assert(err);
49 54
     test.end();
50 55
 
51 56
   });
52 57
 
53 58
 });
54 59
 
55
-tape("Waveform missing samplesPerFrame", function(test) {
60
+tape("Max Duration OK", function(test) {
56 61
 
57 62
   var options = {
58
-    numFrames: 500,
63
+    framesPerSecond: 20,
64
+    samplesPerFrame: 10,
65
+    maxDuration: 30
59 66
   };
60 67
 
61 68
   getWaveform(sample, options, function(err, waveform){
62 69
 
63
-    test.ok(err);
70
+    test.error(err);
64 71
     test.end();
65 72
 
66 73
   });