From 7c2c15aaa25f0002e440d25eafb98e9624ea61c7 Mon Sep 17 00:00:00 2001 From: Dennis Heaney Date: Fri, 27 Jun 2014 15:40:39 +0100 Subject: [PATCH 1/2] fixes #388 --- lib/assetmanager.js | 99 ++++++++++++++++++++++++++---- lib/dml/schema/system/asset.schema | 4 ++ package.json | 1 + 3 files changed, 91 insertions(+), 13 deletions(-) diff --git a/lib/assetmanager.js b/lib/assetmanager.js index 32927fcd4d..0e3c788c9e 100644 --- a/lib/assetmanager.js +++ b/lib/assetmanager.js @@ -15,15 +15,19 @@ var path = require('path'), configuration = require('./configuration'), formidable = require('formidable'), probe = require('node-ffprobe'), - IncomingForm = formidable.IncomingForm; + IncomingForm = formidable.IncomingForm, + FFMpeg = require('fluent-ffmpeg'); /** * CONSTANTS */ var MODNAME = 'assetmanager', - WAITFOR = 'contentmanager' - STREAM_BUFFER_SIZE = 64 * 1024; + WAITFOR = 'contentmanager', + STREAM_BUFFER_SIZE = 64 * 1024, + THUMBNAIL_WIDTH = 64, + THUMBNAIL_HEIGHT = 64, + DEFAULT_THUMBNAIL_IMAGE = 'none'; // errors function AssetNotFoundError (message, assetID) { @@ -78,6 +82,7 @@ exports = module.exports = { rest.get('/asset/query', instance.queryAssets.bind(instance)); rest.get('/asset/:id', instance.getAsset.bind(instance)); rest.get('/asset/serve/:id', instance.serveAsset.bind(instance)); + rest.get('/asset/thumb/:id', instance.assetThumb.bind(instance)); rest.delete('/asset/:id', instance.deleteAsset.bind(instance)); preloader.emit('preloadChange', MODNAME, app.preloadConstants.COMPLETE); @@ -246,12 +251,15 @@ exports = module.exports = { return next(error); } + // create thumbnail + var filePath = storedFile.path; var asset = { title: fields.title, description: fields.description, repository: repository, path: storedFile.path, + thumbnailPath: DEFAULT_THUMBNAIL_IMAGE, size: storedFile.size, directory: directory, isDirectory: false, @@ -291,23 +299,52 @@ exports = module.exports = { // Derive assetType and (if available) store extra metadata depending on the type of file uploaded switch (storedFile.type.split('/')[0]) { case 'image': - asset.assetType = 'image'; - asset.metadata = probeData ? {width: probeData.streams[0].width, height: probeData.streams[0].height} : null; - break; + asset.assetType = 'image'; + asset.metadata = probeData ? {width: probeData.streams[0].width, height: probeData.streams[0].height} : null; + var imageThumbnailPath = asset.path.substr(0, asset.path.lastIndexOf(fileExt)) + '_thumb' + fileExt ; + new FFMpeg({ source: asset.path }) + .withSize(THUMBNAIL_WIDTH + 'x' + THUMBNAIL_HEIGHT) + .keepPixelAspect(true) + .on('error', function (err) { + // keep default thumbnail, but log error + logger.log('error', 'Failed to create thumbnail: ' + err.message); + return doCreate(asset); + }) + .on('end', function () { + asset.thumbnailPath = imageThumbnailPath; + return doCreate(asset); + }) + .saveToFile(imageThumbnailPath); + return; // return is important! case 'video': - asset.assetType = 'video'; - asset.metadata = probeData ? {duration: probeData.streams[0].duration, width: probeData.streams[0].width, height: probeData.streams[0].height} : null; - break; + asset.assetType = 'video'; + asset.metadata = probeData ? {duration: probeData.streams[0].duration, width: probeData.streams[0].width, height: probeData.streams[0].height} : null; + new FFMpeg({ source : asset.path }) + .withSize(THUMBNAIL_WIDTH + 'x' + THUMBNAIL_HEIGHT) + .on('error', function (err) { + // keep default thumbnail, but log error + logger.log('error', 'Failed to create thumbnail: ' + err.message); + return doCreate(asset); + }) + .on('end', function (filenames) { + // hmmm - do we keep the original file name? should we rename? + if (filenames && filenames.length) { + asset.thumbnailPath = filenames[0]; + } + return doCreate(asset); + }) + .takeScreenshots(1, asset.path.substr(0, asset.path.lastIndexOf('/'))); + return; // return is important! case 'audio': - asset.assetType = 'audio'; - asset.metadata = probeData ? {duration: probeData.streams[0].duration} : null; + asset.assetType = 'audio'; + asset.metadata = probeData ? {duration: probeData.streams[0].duration} : null; break; default: - asset.assetType = 'other'; - asset.metadata = null; + asset.assetType = 'other'; + asset.metadata = null; break; } @@ -532,6 +569,42 @@ exports = module.exports = { }); }, + /** + * serves the thumb for an asset - one at a time! + * + * @param {object} req + * @param {object} res + * @param {callback} next + */ + + assetThumb: function (req, res, next) { + this.retrieveAsset({ _id: req.params.id }, function (error, assetRecs) { + if (error) { + return next(error); + } + + // record was not found + if (!assetRecs || assetRecs.length !== 1) { + res.statusCode = 404; + return res.end(); + } + + var assetRec = assetRecs[0]; + filestorage.getStorage(assetRec.repository, function (error, storage) { + if (error) { + return next(error); + } + + storage.createReadStream(assetRec.thumbnailPath, { bufferSize: STREAM_BUFFER_SIZE }, function (stream) { + stream.pipe(res); + stream.on('end', function () { + return res.end(); + }); + }); + }); + }); + }, + /** * responder for delete requests * diff --git a/lib/dml/schema/system/asset.schema b/lib/dml/schema/system/asset.schema index f5fac894af..dc44f52b91 100644 --- a/lib/dml/schema/system/asset.schema +++ b/lib/dml/schema/system/asset.schema @@ -20,6 +20,10 @@ "type": "string", "required": "true" }, + "thumbnailPath": { + "type": "string", + "required": "true" + }, "repository": { "type": "string", "required": "true" diff --git a/package.json b/package.json index 7e118a6d97..a49019cd03 100644 --- a/package.json +++ b/package.json @@ -40,6 +40,7 @@ "connect-mongodb": "1.1.x", "consolidate": "0.10.0", "express": "3.4.0", + "fluent-ffmpeg": "1.7.1", "formidable": "~1.0.14", "handlebars-form-helpers": "0.1.3", "hbs": "2.4.0", From 3f23704e76e2a8cbc76ed8b9e78c77cbd078ccb9 Mon Sep 17 00:00:00 2001 From: Dennis Heaney Date: Fri, 27 Jun 2014 15:54:37 +0100 Subject: [PATCH 2/2] issue #388 - changed thumbnail resolution - fixed video thumbnail paths --- lib/assetmanager.js | 11 +++++------ 1 file changed, 5 insertions(+), 6 deletions(-) diff --git a/lib/assetmanager.js b/lib/assetmanager.js index 0e3c788c9e..5dac4f3807 100644 --- a/lib/assetmanager.js +++ b/lib/assetmanager.js @@ -25,8 +25,8 @@ var path = require('path'), var MODNAME = 'assetmanager', WAITFOR = 'contentmanager', STREAM_BUFFER_SIZE = 64 * 1024, - THUMBNAIL_WIDTH = 64, - THUMBNAIL_HEIGHT = 64, + THUMBNAIL_WIDTH = '?', + THUMBNAIL_HEIGHT = '48', DEFAULT_THUMBNAIL_IMAGE = 'none'; // errors @@ -251,8 +251,6 @@ exports = module.exports = { return next(error); } - // create thumbnail - var filePath = storedFile.path; var asset = { title: fields.title, @@ -320,6 +318,7 @@ exports = module.exports = { case 'video': asset.assetType = 'video'; asset.metadata = probeData ? {duration: probeData.streams[0].duration, width: probeData.streams[0].width, height: probeData.streams[0].height} : null; + var pathToDir = asset.path.substr(0, asset.path.lastIndexOf('/')); new FFMpeg({ source : asset.path }) .withSize(THUMBNAIL_WIDTH + 'x' + THUMBNAIL_HEIGHT) .on('error', function (err) { @@ -330,11 +329,11 @@ exports = module.exports = { .on('end', function (filenames) { // hmmm - do we keep the original file name? should we rename? if (filenames && filenames.length) { - asset.thumbnailPath = filenames[0]; + asset.thumbnailPath = path.join(pathToDir, filenames[0]); } return doCreate(asset); }) - .takeScreenshots(1, asset.path.substr(0, asset.path.lastIndexOf('/'))); + .takeScreenshots(1, pathToDir); return; // return is important! case 'audio':