|
|
@ -177,15 +177,18 @@ IU.process = function () {
|
|
|
|
if (image.ext == '.mov')
|
|
|
|
if (image.ext == '.mov')
|
|
|
|
image.ext = '.mp4';
|
|
|
|
image.ext = '.mp4';
|
|
|
|
if (IMAGE_EXTS.indexOf(image.ext) < 0
|
|
|
|
if (IMAGE_EXTS.indexOf(image.ext) < 0
|
|
|
|
&& (!config.VIDEO || config.VIDEO_EXTS.indexOf(image.ext) < 0))
|
|
|
|
&& (!config.VIDEO || config.VIDEO_EXTS.indexOf(image.ext) < 0)
|
|
|
|
|
|
|
|
&& (!config.VIDEO || config.AUDIO_EXTS.indexOf(image.ext) < 0))
|
|
|
|
return this.failure(Muggle('Invalid image format.'));
|
|
|
|
return this.failure(Muggle('Invalid image format.'));
|
|
|
|
image.imgnm = filename.substr(0, 256);
|
|
|
|
image.imgnm = filename.substr(0, 256);
|
|
|
|
|
|
|
|
|
|
|
|
this.status('Verifying...');
|
|
|
|
this.status('Verifying...');
|
|
|
|
if (config.VIDEO_EXTS.indexOf(image.ext) >= 0)
|
|
|
|
if (config.VIDEO_EXTS.indexOf(image.ext) >= 0)
|
|
|
|
video_still(image.path, image.ext, this.verify_video.bind(this));
|
|
|
|
video_still(image.path, image.ext, this.verify_video.bind(this));
|
|
|
|
|
|
|
|
else if (config.AUDIO_EXTS.indexOf(image.ext) >= 0)
|
|
|
|
|
|
|
|
audio_still(image.path, this.verify_audio.bind(this));
|
|
|
|
else if (image.ext == '.jpg' && jpegtranBin && jheadBin)
|
|
|
|
else if (image.ext == '.jpg' && jpegtranBin && jheadBin)
|
|
|
|
jobs.schedule(new AutoRotateJob(image.path), this.verify_image.bind(this));
|
|
|
|
jobs.schedule(new AutoRotateJob(image.path), this.verify_image.bind(this));
|
|
|
|
else
|
|
|
|
else
|
|
|
|
this.verify_image();
|
|
|
|
this.verify_image();
|
|
|
|
};
|
|
|
|
};
|
|
|
@ -210,6 +213,166 @@ AutoRotateJob.prototype.perform_job = function () {
|
|
|
|
});
|
|
|
|
});
|
|
|
|
};
|
|
|
|
};
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
function AudioStillJob(src) {
|
|
|
|
|
|
|
|
jobs.Job.call(this);
|
|
|
|
|
|
|
|
this.src = src;
|
|
|
|
|
|
|
|
}
|
|
|
|
|
|
|
|
util.inherits(AudioStillJob, jobs.Job);
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
AudioStillJob.prototype.describe_job = function () {
|
|
|
|
|
|
|
|
return "FFmpeg audio still of " + this.src;
|
|
|
|
|
|
|
|
};
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
AudioStillJob.prototype.perform_job = function () {
|
|
|
|
|
|
|
|
var self = this;
|
|
|
|
|
|
|
|
self.get_info();
|
|
|
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
AudioStillJob.prototype.get_info = function () {
|
|
|
|
|
|
|
|
var self = this;
|
|
|
|
|
|
|
|
var audioData = {};
|
|
|
|
|
|
|
|
child_process.execFile(ffprobeBin, [this.src],
|
|
|
|
|
|
|
|
function(err, stdout, stderr){
|
|
|
|
|
|
|
|
var type = stderr.match(/Input #0, (.*),/);
|
|
|
|
|
|
|
|
if (type)
|
|
|
|
|
|
|
|
audioData.type = type[1];
|
|
|
|
|
|
|
|
var title = stderr.match(/title\s+: (.*)/i);
|
|
|
|
|
|
|
|
if (title)
|
|
|
|
|
|
|
|
audioData.title = title[1];
|
|
|
|
|
|
|
|
var artist = stderr.match(/artist\s+: (.*)/i);
|
|
|
|
|
|
|
|
if (artist)
|
|
|
|
|
|
|
|
audioData.artist = artist[1];
|
|
|
|
|
|
|
|
var l = stderr.match(/Duration: (\d{2}):(\d{2}):(\d{2})\.(\d{2})/);
|
|
|
|
|
|
|
|
if (l){
|
|
|
|
|
|
|
|
var h = (l[1] != '00' ? l[1] + 'h' : '');
|
|
|
|
|
|
|
|
var m = (l[2] != '00' ? l[2] + 'm' : '');
|
|
|
|
|
|
|
|
var s = (l[3] != '00' ? l[3] + 's' : '');
|
|
|
|
|
|
|
|
audioData.total = parseFloat(parseFloat(l[1])*3600 +
|
|
|
|
|
|
|
|
parseFloat(l[2])*60 + parseFloat(l[3]) + '.' + parseFloat(l[4]));
|
|
|
|
|
|
|
|
audioData.length = h + m + s;
|
|
|
|
|
|
|
|
}
|
|
|
|
|
|
|
|
self.short_test(audioData);
|
|
|
|
|
|
|
|
});
|
|
|
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
AudioStillJob.prototype.short_test = function (audioData) {
|
|
|
|
|
|
|
|
var self = this;
|
|
|
|
|
|
|
|
var dest = index.media_path('tmp', 'still_'+etc.random_id());
|
|
|
|
|
|
|
|
if (audioData.total <= 10) {
|
|
|
|
|
|
|
|
if (!config.AUDIOFILE_IMAGE) {
|
|
|
|
|
|
|
|
var msg = "Failure serverside.";
|
|
|
|
|
|
|
|
var prob = "Missing AUDIOFILE_IMAGE.";
|
|
|
|
|
|
|
|
winston.warn(prob);
|
|
|
|
|
|
|
|
fs.unlink(dest, function (err) {
|
|
|
|
|
|
|
|
self.finish_job(Muggle(msg, prob));
|
|
|
|
|
|
|
|
});
|
|
|
|
|
|
|
|
return;
|
|
|
|
|
|
|
|
}
|
|
|
|
|
|
|
|
fs.readFile(config.AUDIOFILE_IMAGE, function (errRead, img) {
|
|
|
|
|
|
|
|
if (errRead) {
|
|
|
|
|
|
|
|
var msg = "Failure serverside.";
|
|
|
|
|
|
|
|
var prob = "Error reading AUDIOFILE_IMAGE";
|
|
|
|
|
|
|
|
winston.warn(prob);
|
|
|
|
|
|
|
|
fs.unlink(dest, function (err) {
|
|
|
|
|
|
|
|
self.finish_job(Muggle(msg, prob));
|
|
|
|
|
|
|
|
});
|
|
|
|
|
|
|
|
return;
|
|
|
|
|
|
|
|
}
|
|
|
|
|
|
|
|
fs.writeFile(dest, img, function (errWrite) {
|
|
|
|
|
|
|
|
if (errWrite) {
|
|
|
|
|
|
|
|
var msg = "Failure serverside.";
|
|
|
|
|
|
|
|
var prob = "Error copying AUDIOFILE_IMAGE";
|
|
|
|
|
|
|
|
winston.warn(prob);
|
|
|
|
|
|
|
|
fs.unlink(dest, function (err) {
|
|
|
|
|
|
|
|
self.finish_job(Muggle(msg, prob));
|
|
|
|
|
|
|
|
});
|
|
|
|
|
|
|
|
return;
|
|
|
|
|
|
|
|
}
|
|
|
|
|
|
|
|
self.finish_job(null, {
|
|
|
|
|
|
|
|
still_path: dest,
|
|
|
|
|
|
|
|
duration: audioData.length,
|
|
|
|
|
|
|
|
audiotype: audioData.type,
|
|
|
|
|
|
|
|
title: audioData.title,
|
|
|
|
|
|
|
|
artist: audioData.artist,
|
|
|
|
|
|
|
|
});
|
|
|
|
|
|
|
|
});
|
|
|
|
|
|
|
|
});
|
|
|
|
|
|
|
|
} else {
|
|
|
|
|
|
|
|
self.encode_thumb(audioData, dest);
|
|
|
|
|
|
|
|
}
|
|
|
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
AudioStillJob.prototype.encode_thumb = function (audioData, dest) {
|
|
|
|
|
|
|
|
var self = this;
|
|
|
|
|
|
|
|
var args = ['-hide_banner', '-loglevel', 'info',
|
|
|
|
|
|
|
|
'-f', 'lavfi', '-ss', (Math.floor(audioData.total/2) <= 10 ?
|
|
|
|
|
|
|
|
Math.floor(audioData.total - audioData.total/10) : Math.floor(audioData.total/2)),
|
|
|
|
|
|
|
|
'-i', 'amovie=' + this.src + ', asplit [a][out1];[a] showspectrum=mode=separate:color=intensity:slide=1:scale=cbrt [out0]',
|
|
|
|
|
|
|
|
'-f', 'image2', '-vframes', '1', '-vcodec', 'png',
|
|
|
|
|
|
|
|
'-y', dest];
|
|
|
|
|
|
|
|
var opts = {env: {AV_LOG_FORCE_NOCOLOR: '1'}};
|
|
|
|
|
|
|
|
child_process.execFile(ffmpegBin, args, opts,
|
|
|
|
|
|
|
|
function (err, stdout, stderr) {
|
|
|
|
|
|
|
|
var lines = stderr ? stderr.split('\n') : [];
|
|
|
|
|
|
|
|
var first = lines[0];
|
|
|
|
|
|
|
|
if (err) {
|
|
|
|
|
|
|
|
var msg;
|
|
|
|
|
|
|
|
if (/no such file or directory/i.test(first))
|
|
|
|
|
|
|
|
msg = "Audio went missing.";
|
|
|
|
|
|
|
|
else if (/invalid data found when/i.test(first))
|
|
|
|
|
|
|
|
msg = "Invalid audio file.";
|
|
|
|
|
|
|
|
else if (/^ffmpeg version/i.test(first))
|
|
|
|
|
|
|
|
msg = "Server's ffmpeg is too old.";
|
|
|
|
|
|
|
|
else {
|
|
|
|
|
|
|
|
msg = "Unknown audio reading error.";
|
|
|
|
|
|
|
|
winston.warn("Unknown ffmpeg output: "+first);
|
|
|
|
|
|
|
|
}
|
|
|
|
|
|
|
|
fs.unlink(dest, function (err) {
|
|
|
|
|
|
|
|
self.finish_job(Muggle(msg, stderr));
|
|
|
|
|
|
|
|
});
|
|
|
|
|
|
|
|
return;
|
|
|
|
|
|
|
|
}
|
|
|
|
|
|
|
|
self.finish_job(null, {
|
|
|
|
|
|
|
|
still_path: dest,
|
|
|
|
|
|
|
|
duration: audioData.length,
|
|
|
|
|
|
|
|
audiotype: audioData.type,
|
|
|
|
|
|
|
|
title: audioData.title,
|
|
|
|
|
|
|
|
artist: audioData.artist,
|
|
|
|
|
|
|
|
});
|
|
|
|
|
|
|
|
});
|
|
|
|
|
|
|
|
};
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
function audio_still(src, cb) {
|
|
|
|
|
|
|
|
jobs.schedule(new AudioStillJob(src), cb);
|
|
|
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
IU.verify_audio = function (err, info) {
|
|
|
|
|
|
|
|
if (err)
|
|
|
|
|
|
|
|
return this.failure(err);
|
|
|
|
|
|
|
|
var self = this;
|
|
|
|
|
|
|
|
this.db.track_temporary(info.still_path, function (err) {
|
|
|
|
|
|
|
|
if (err)
|
|
|
|
|
|
|
|
winston.warn("Tracking error: " + err);
|
|
|
|
|
|
|
|
// pretend it's a PNG for the next steps
|
|
|
|
|
|
|
|
var image = self.image;
|
|
|
|
|
|
|
|
image.video_path = image.path;
|
|
|
|
|
|
|
|
image.path = info.still_path;
|
|
|
|
|
|
|
|
image.ext = '.png';
|
|
|
|
|
|
|
|
if (info.duration)
|
|
|
|
|
|
|
|
image.duration = info.duration;
|
|
|
|
|
|
|
|
if (info.audiotype)
|
|
|
|
|
|
|
|
image.audiofile = info.audiotype;
|
|
|
|
|
|
|
|
if (info.title)
|
|
|
|
|
|
|
|
image.title = info.title;
|
|
|
|
|
|
|
|
if (info.artist)
|
|
|
|
|
|
|
|
image.artist = info.artist;
|
|
|
|
|
|
|
|
self.verify_image();
|
|
|
|
|
|
|
|
});
|
|
|
|
|
|
|
|
};
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
function StillJob(src, ext) {
|
|
|
|
function StillJob(src, ext) {
|
|
|
|
jobs.Job.call(this);
|
|
|
|
jobs.Job.call(this);
|
|
|
|
this.src = src;
|
|
|
|
this.src = src;
|
|
|
@ -305,6 +468,10 @@ function video_still(src, ext, cb) {
|
|
|
|
jobs.schedule(new StillJob(src, ext), cb);
|
|
|
|
jobs.schedule(new StillJob(src, ext), cb);
|
|
|
|
}
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
function audio_thumb(src, ext, cb) {
|
|
|
|
|
|
|
|
jobs.schedule(new AudioJob(src, ext), cb);
|
|
|
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
|
|
IU.verify_video = function (err, info) {
|
|
|
|
IU.verify_video = function (err, info) {
|
|
|
|
if (err)
|
|
|
|
if (err)
|
|
|
|
return this.failure(err);
|
|
|
|
return this.failure(err);
|
|
|
@ -474,8 +641,8 @@ IU.got_nails = function () {
|
|
|
|
var image = this.image;
|
|
|
|
var image = this.image;
|
|
|
|
if (image.video_path) {
|
|
|
|
if (image.video_path) {
|
|
|
|
// stop pretending this is just a still image
|
|
|
|
// stop pretending this is just a still image
|
|
|
|
image.path = image.video_path;
|
|
|
|
image.path = image.video_path;
|
|
|
|
image.ext = '.' + image.video;
|
|
|
|
image.ext = image.audiofile ? '.'+image.audiofile : '.'+image.video;
|
|
|
|
delete image.video_path;
|
|
|
|
delete image.video_path;
|
|
|
|
}
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
|
@ -532,9 +699,10 @@ var identifyBin, convertBin;
|
|
|
|
which('identify', function (err, bin) { if (err) throw err; identifyBin = bin; });
|
|
|
|
which('identify', function (err, bin) { if (err) throw err; identifyBin = bin; });
|
|
|
|
which('convert', function (err, bin) { if (err) throw err; convertBin = bin; });
|
|
|
|
which('convert', function (err, bin) { if (err) throw err; convertBin = bin; });
|
|
|
|
|
|
|
|
|
|
|
|
var ffmpegBin;
|
|
|
|
var ffmpegBin, ffprobeBin;
|
|
|
|
if (config.VIDEO) {
|
|
|
|
if (config.VIDEO) {
|
|
|
|
which('ffmpeg', function (err, bin) { if (err) throw err; ffmpegBin = bin; });
|
|
|
|
which('ffmpeg', function (err, bin) { if (err) throw err; ffmpegBin = bin; });
|
|
|
|
|
|
|
|
which('ffprobe', function (err, bin) { if (err) throw err; ffprobeBin = bin; });
|
|
|
|
}
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
|
|
/* optional JPEG auto-rotation */
|
|
|
|
/* optional JPEG auto-rotation */
|
|
|
|