diff --git a/lib/assetmanager.js b/lib/assetmanager.js index 32927fcd4d..5dac4f3807 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 = '?', + THUMBNAIL_HEIGHT = '48', + 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); @@ -252,6 +257,7 @@ exports = module.exports = { description: fields.description, repository: repository, path: storedFile.path, + thumbnailPath: DEFAULT_THUMBNAIL_IMAGE, size: storedFile.size, directory: directory, isDirectory: false, @@ -291,23 +297,53 @@ 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; + var pathToDir = asset.path.substr(0, asset.path.lastIndexOf('/')); + 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 = path.join(pathToDir, filenames[0]); + } + return doCreate(asset); + }) + .takeScreenshots(1, pathToDir); + 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 +568,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",