123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181182183184185186187188189190191192193194195196197198199200201202203204205206 |
- var path = require("path"),
- queue = require("d3").queue,
- mkdirp = require("mkdirp"),
- rimraf = require("rimraf"),
- serverSettings = require("../lib/settings/"),
- transports = require("../lib/transports/"),
- logger = require("../lib/logger/"),
- Profiler = require("../lib/profiler.js"),
- probe = require("../lib/probe.js"),
- getWaveform = require("./waveform.js"),
- initializeCanvas = require("./initialize-canvas.js"),
- drawFrames = require("./draw-frames.js"),
- combineFrames = require("./combine-frames.js"),
- trimAudio = require("./trim.js");
-
- function Audiogram(id) {
-
- // Unique audiogram ID
- this.id = id;
-
- // File locations to use
- this.dir = path.join(serverSettings.workingDirectory, this.id);
-
- this.audioPath = path.join(this.dir, "audio");
- this.videoPath = path.join(this.dir, "video.mp4");
- this.frameDir = path.join(this.dir, "frames");
-
- this.profiler = new Profiler();
-
- return this;
-
- }
-
- // Get the waveform data from the audio file, split into frames
- Audiogram.prototype.getWaveform = function(cb) {
-
- var self = this;
-
- this.status("probing");
-
- probe(this.audioPath, function(err, data){
-
- if (err) {
- return cb(err);
- }
-
- if (self.settings.theme.maxDuration && self.settings.theme.maxDuration < data.duration) {
- return cb("Exceeds max duration of " + self.settings.theme.maxDuration + "s");
- }
-
- self.profiler.size(data.duration);
- self.set("numFrames", self.numFrames = Math.floor(data.duration * self.settings.theme.framesPerSecond));
- self.status("waveform");
-
- getWaveform(self.audioPath, {
- numFrames: self.numFrames,
- samplesPerFrame: self.settings.theme.samplesPerFrame,
- channels: data.channels
- }, function(waveformErr, waveform){
-
- return cb(waveformErr, self.waveform = waveform);
-
- });
-
-
- });
-
- };
-
- // Trim the audio by the start and end time specified
- Audiogram.prototype.trimAudio = function(start, end, cb) {
-
- var self = this;
-
- this.status("trim");
-
- // FFmpeg needs an extension to sniff
- var trimmedPath = this.audioPath + "-trimmed.mp3";
-
- trimAudio({
- origin: this.audioPath,
- destination: trimmedPath,
- startTime: start,
- endTime: end
- }, function(err){
- if (err) {
- return cb(err);
- }
-
- self.audioPath = trimmedPath;
-
- return cb(null);
- });
-
- };
-
- // Initialize the canvas and draw all the frames
- Audiogram.prototype.drawFrames = function(cb) {
-
- var self = this;
-
- this.status("renderer");
-
- initializeCanvas(this.settings.theme, function(err, renderer){
-
- if (err) {
- return cb(err);
- }
-
- self.status("frames");
-
- drawFrames(renderer, {
- width: self.settings.theme.width,
- height: self.settings.theme.height,
- numFrames: self.numFrames,
- frameDir: self.frameDir,
- caption: self.settings.caption,
- waveform: self.waveform,
- tick: function() {
- transports.incrementField(self.id, "framesComplete");
- }
- }, cb);
-
- });
-
- };
-
- // Combine the frames and audio into the final video with FFmpeg
- Audiogram.prototype.combineFrames = function(cb) {
-
- this.status("combine");
-
- combineFrames({
- framePath: path.join(this.frameDir, "%06d.png"),
- audioPath: this.audioPath,
- videoPath: this.videoPath,
- framesPerSecond: this.settings.theme.framesPerSecond
- }, cb);
-
- };
-
- // Master render function, queue up steps in order
- Audiogram.prototype.render = function(cb) {
-
- var self = this,
- q = queue(1);
-
- this.status("audio-download");
-
- // Set up tmp directory
- q.defer(mkdirp, this.frameDir);
-
- // Download the stored audio file
- q.defer(transports.downloadAudio, "audio/" + this.id, this.audioPath);
-
- // If the audio needs to be clipped, clip it first and update the path
- if (this.settings.start || this.settings.end) {
- q.defer(this.trimAudio.bind(this), this.settings.start || 0, this.settings.end || null);
- }
-
- // Get the audio waveform data
- q.defer(this.getWaveform.bind(this));
-
- // Draw all the frames
- q.defer(this.drawFrames.bind(this));
-
- // Combine audio and frames together with ffmpeg
- q.defer(this.combineFrames.bind(this));
-
- // Upload video to S3 or move to local storage
- q.defer(transports.uploadVideo, this.videoPath, "video/" + this.id + ".mp4");
-
- // Delete working directory
- q.defer(rimraf, this.dir);
-
- // Final callback, results in a URL where the finished video is accessible
- q.await(function(err){
-
- if (!err) {
- self.set("url", transports.getURL(self.id));
- }
-
- logger.debug(self.profiler.print());
-
- return cb(err);
-
- });
-
- return this;
-
- };
-
- Audiogram.prototype.set = function(field, value) {
- logger.debug(field + "=" + value);
- transports.setField(this.id, field, value);
- return this;
- };
-
- // Convenience method for .set("status")
- Audiogram.prototype.status = function(value) {
- this.profiler.start(value);
- return this.set("status", value);
- };
-
- module.exports = Audiogram;
|