Merge pull request #543 from lbryio/527-height-and-width-for-images

store height and width in File table
This commit is contained in:
Bill Bittner 2018-07-30 15:53:43 -07:00 committed by GitHub
commit eca67e016f
No known key found for this signature in database
GPG key ID: 4AEE18F83AFDEB23
20 changed files with 473 additions and 186 deletions

View file

@ -2,4 +2,7 @@ const path = require('path');
module.exports = {
'config': path.resolve('devConfig', 'sequelizeCliConfig.js'),
'models-path': path.resolve('server', 'models'),
'seeders-path': path.resolve('server', 'seeders'),
'migrations-path': path.resolve('server', 'migrations')
}

View file

@ -9,6 +9,11 @@ To get started running your own version of spee.ch, visit [lbryio/www.spee.ch](h
npm install spee.ch --save
```
### Dependenceis
Make sure the following are installed
* [imagemagick](https://www.imagemagick.org/script/download.php)
* [ffmpeg](https://www.ffmpeg.org/download.html)
## Development
* the `server/` folder contains all of the server code
* `index.js` is the entry point for the server. It creates the [express app](https://expressjs.com/), requires the routes, syncs the database, and starts the server listening on the `PORT` designated in the config file.
@ -23,21 +28,7 @@ npm install spee.ch --save
* To run only tests that do not require LBC, run `npm run test:no-lbc`
## API
#### GET
* /api/claim/resolve/:name/:claimId
* example: `curl https://spee.ch/api/claim/resolve/doitlive/xyz`
* /api/claim/list/:name
* example: `curl https://spee.ch/api/claim/list/doitlive`
* /api/claim/availability/:name
* returns the name if it is available
* example: `curl https://spee.ch/api/claim/availability/doitlive`
* /api/channel/availability/:name
* returns the name if it is available
* example: `curl https://spee.ch/api/channel/availability/@CoolChannel`
#### POST
* /api/claim/publish
* _(post)_ /api/claim/publish
* example: `curl -F 'name=MyPictureName' -F 'file=@/path/to/myPicture.jpeg' https://spee.ch/api/claim/publish`
* Parameters:
* `name` (required)
@ -49,6 +40,16 @@ npm install spee.ch --save
* `thumbnail` url to thumbnail image, for .mp4 uploads only (optional)
* `channelName`(optional)
* `channelPassword` (optional,; required if `channelName` is provided)
* _(get)_ /api/claim/resolve/:name/:claimId
* example: `curl https://spee.ch/api/claim/resolve/doitlive/xyz`
* _(get)_ /api/claim/list/:name
* example: `curl https://spee.ch/api/claim/list/doitlive`
* _(get)_ /api/claim/availability/:name
* returns the name if it is available
* example: `curl https://spee.ch/api/claim/availability/doitlive`
* _(get)_ /api/channel/availability/:name
* returns the name if it is available
* example: `curl https://spee.ch/api/channel/availability/@CoolChannel`
## Bugs
If you find a bug or experience a problem, please report your issue here on github and find us in the lbry discord!
@ -69,4 +70,13 @@ Issues with spee.ch strong familiarity with the spee.ch code base and how the lb
Issues with lbry (e.g. the spee.ch wallet, lbrynet configuration, etc.) that require strong familiarity with the lbry daemon and/or network to fix. Generally these issues are best suited for the lbry protocol team but are placed in this repo because of they are part of the spee.ch implementation
### Stack
* server
* [mysql](https://www.mysql.com/)
* [express](https://www.npmjs.com/package/express)
* [node](https://nodejs.org/)
* [lbry](https://github.com/lbryio/lbry)
* [imagemagick](https://www.imagemagick.org/)
* [ffmpeg](https://www.ffmpeg.org/)
* client
* [react](https://reactjs.org/)

54
package-lock.json generated
View file

@ -953,6 +953,11 @@
"resolved": "https://registry.npmjs.org/ansicolors/-/ansicolors-0.2.1.tgz",
"integrity": "sha1-vgiVmQl7dKXJxKhKDNvNtivYeu8="
},
"any-promise": {
"version": "1.3.0",
"resolved": "https://registry.npmjs.org/any-promise/-/any-promise-1.3.0.tgz",
"integrity": "sha1-q8av7tzqUugJzcA3au0845Y10X8="
},
"anymatch": {
"version": "1.3.2",
"resolved": "https://registry.npmjs.org/anymatch/-/anymatch-1.3.2.tgz",
@ -3984,6 +3989,14 @@
"integrity": "sha1-3BXKHGcjh8p2vTesCjlbogQqLCg=",
"dev": true
},
"get-video-dimensions": {
"version": "1.0.0",
"resolved": "https://registry.npmjs.org/get-video-dimensions/-/get-video-dimensions-1.0.0.tgz",
"integrity": "sha1-/H5ayBw5JEH1uG1Q3XeDiptTFHo=",
"requires": {
"mz": "1.3.0"
}
},
"getpass": {
"version": "0.1.7",
"resolved": "https://registry.npmjs.org/getpass/-/getpass-0.1.7.tgz",
@ -4377,6 +4390,16 @@
"integrity": "sha1-SMptcvbGo68Aqa1K5odr44ieKwk=",
"dev": true
},
"image-size": {
"version": "0.6.3",
"resolved": "https://registry.npmjs.org/image-size/-/image-size-0.6.3.tgz",
"integrity": "sha512-47xSUiQioGaB96nqtp5/q55m0aBQSQdyIloMOc/x+QVTDZLNmXE892IIDrJ0hM1A5vcNUDD5tDffkSP5lCaIIA=="
},
"imagemagick": {
"version": "0.1.3",
"resolved": "https://registry.npmjs.org/imagemagick/-/imagemagick-0.1.3.tgz",
"integrity": "sha1-dIPOoJO02fLi85aFetyIIbU3xWo="
},
"import-lazy": {
"version": "2.1.0",
"resolved": "https://registry.npmjs.org/import-lazy/-/import-lazy-2.1.0.tgz",
@ -5371,6 +5394,16 @@
}
}
},
"mz": {
"version": "1.3.0",
"resolved": "https://registry.npmjs.org/mz/-/mz-1.3.0.tgz",
"integrity": "sha1-BvCT/dmVagbTfhsegTROJ0eMQvA=",
"requires": {
"native-or-bluebird": "1.2.0",
"thenify": "3.3.0",
"thenify-all": "1.6.0"
}
},
"named-placeholders": {
"version": "1.1.1",
"resolved": "https://registry.npmjs.org/named-placeholders/-/named-placeholders-1.1.1.tgz",
@ -5431,6 +5464,11 @@
}
}
},
"native-or-bluebird": {
"version": "1.2.0",
"resolved": "https://registry.npmjs.org/native-or-bluebird/-/native-or-bluebird-1.2.0.tgz",
"integrity": "sha1-OcR7/Xgl0fuf+tMiEK4l2q3xAck="
},
"natural-compare": {
"version": "1.4.0",
"resolved": "https://registry.npmjs.org/natural-compare/-/natural-compare-1.4.0.tgz",
@ -7745,6 +7783,22 @@
"integrity": "sha1-f17oI66AUgfACvLfSoTsP8+lcLQ=",
"dev": true
},
"thenify": {
"version": "3.3.0",
"resolved": "https://registry.npmjs.org/thenify/-/thenify-3.3.0.tgz",
"integrity": "sha1-5p44obq+lpsBCCB5eLn2K4hgSDk=",
"requires": {
"any-promise": "1.3.0"
}
},
"thenify-all": {
"version": "1.6.0",
"resolved": "https://registry.npmjs.org/thenify-all/-/thenify-all-1.6.0.tgz",
"integrity": "sha1-GhkY1ALY/D+Y+/I02wvMjMEOlyY=",
"requires": {
"thenify": "3.3.0"
}
},
"through": {
"version": "2.3.8",
"resolved": "https://registry.npmjs.org/through/-/through-2.3.8.tgz",

View file

@ -37,7 +37,10 @@
"cookie-session": "^2.0.0-beta.3",
"express": "^4.15.2",
"express-handlebars": "^3.0.0",
"get-video-dimensions": "^1.0.0",
"helmet": "^3.8.1",
"image-size": "^0.6.3",
"imagemagick": "^0.1.3",
"module-alias": "^2.0.6",
"mysql2": "^1.3.5",
"passport": "^0.4.0",

View file

@ -1,5 +0,0 @@
module.exports = (fileInfo, getResult) => {
fileInfo.fileName = getResult.file_name;
fileInfo.filePath = getResult.download_path;
return fileInfo;
};

View file

@ -1,13 +0,0 @@
module.exports = ({ name, claimId, outpoint, height, address, nsfw, contentType }) => {
return {
name,
claimId,
outpoint,
height,
address,
fileName: '',
filePath: '',
fileType: contentType,
nsfw,
};
};

View file

@ -1,6 +1,5 @@
const { getClaim } = require('../../../../lbrynet');
const addGetResultsToFileData = require('./addGetResultsToFileData.js');
const createFileData = require('./createFileData.js');
const { createFileRecordDataAfterGet } = require('../../../../models/utils/createFileRecordData.js');
const { handleErrorResponse } = require('../../../utils/errorHandlers.js');
const db = require('../../../../models');
@ -13,23 +12,32 @@ const db = require('../../../../models');
const claimGet = ({ ip, originalUrl, params }, res) => {
const name = params.name;
const claimId = params.claimId;
let resolveResult;
let getResult;
// resolve the claim
db.Claim.resolveClaim(name, claimId)
.then(resolveResult => {
// make sure a claim actually exists at that uri
if (!resolveResult) {
.then(result => {
if (!result) {
throw new Error('No matching uri found in Claim table');
}
let fileData = createFileData(resolveResult);
// get the claim
return Promise.all([fileData, getClaim(`${name}#${claimId}`)]);
resolveResult = result;
return getClaim(`${name}#${claimId}`);
})
.then(([ fileData, getResult ]) => {
fileData = addGetResultsToFileData(fileData, getResult);
return Promise.all([db.upsert(db.File, fileData, {name, claimId}, 'File'), getResult]);
.then(result => {
getResult = result;
})
.then(([ fileRecord, {message, completed} ]) => {
res.status(200).json({ success: true, message, completed });
.then(() => {
const fileData = createFileRecordDataAfterGet(resolveResult, getResult);
const upsertCriteria = { name, claimId};
return db.upsert(db.File, fileData, upsertCriteria, 'File')
})
.then(() => {
const { message, completed } = getResult;
res.status(200).json({
success: true,
message,
completed,
});
})
.catch(error => {
handleErrorResponse(originalUrl, ip, error, res);

View file

@ -47,29 +47,38 @@ const authenticateChannelCredentials = (channelName, channelId, userPassword) =>
};
const authenticateUser = (channelName, channelId, channelPassword, user) => {
// case: no channelName or channel Id are provided (anonymous), regardless of whether user token is provided
if (!channelName && !channelId) {
return {
channelName : null,
channelClaimId: null,
};
}
// case: channelName or channel Id are provided with user token
if (user) {
if (channelName && channelName !== user.channelName) {
throw new Error('the provided channel name does not match user credentials');
return new Promise((resolve, reject) => {
// case: no channelName or channel Id are provided (anonymous), regardless of whether user token is provided
if (!channelName && !channelId) {
resolve({
channelName : null,
channelClaimId: null,
});
return;
}
if (channelId && channelId !== user.channelClaimId) {
throw new Error('the provided channel id does not match user credentials');
// case: channelName or channel Id are provided with user token
if (user) {
if (channelName && channelName !== user.channelName) {
reject(new Error('the provided channel name does not match user credentials'));
return;
}
if (channelId && channelId !== user.channelClaimId) {
reject(new Error('the provided channel id does not match user credentials'));
return;
}
resolve({
channelName : user.channelName,
channelClaimId: user.channelClaimId,
});
return;
}
return {
channelName : user.channelName,
channelClaimId: user.channelClaimId,
};
}
// case: channelName or channel Id are provided with password instead of user token
if (!channelPassword) throw new Error('no channel password provided');
return authenticateChannelCredentials(channelName, channelId, channelPassword);
// case: channelName or channel Id are provided with password instead of user token
if (!channelPassword) {
reject(new Error('no channel password provided'));
return;
}
resolve(authenticateChannelCredentials(channelName, channelId, channelPassword));
});
};
module.exports = authenticateUser;

View file

@ -1,8 +1,7 @@
const logger = require('winston');
const { details, publishing } = require('@config/siteConfig');
const createBasicPublishParams = (filePath, name, title, description, license, nsfw, thumbnail) => {
logger.debug(`Creating Publish Parameters`);
const createPublishParams = (filePath, name, title, description, license, nsfw, thumbnail, channelName, channelClaimId) => {
// provide defaults for title
if (title === null || title.trim() === '') {
title = name;
@ -15,7 +14,7 @@ const createBasicPublishParams = (filePath, name, title, description, license, n
if (license === null || license.trim() === '') {
license = ' '; // default to empty string
}
// create the publish params
// create the basic publish params
const publishParams = {
name,
file_path: filePath,
@ -34,8 +33,15 @@ const createBasicPublishParams = (filePath, name, title, description, license, n
if (thumbnail) {
publishParams['metadata']['thumbnail'] = thumbnail;
}
// add channel details if publishing to a channel
if (channelName && channelClaimId) {
publishParams['channel_name'] = channelName;
publishParams['channel_id'] = channelClaimId;
}
// log params
logger.debug('publish params:', publishParams);
// return
return publishParams;
};
module.exports = createBasicPublishParams;
module.exports = createPublishParams;

View file

@ -9,12 +9,14 @@ const { handleErrorResponse } = require('../../../utils/errorHandlers.js');
const checkClaimAvailability = require('../availability/checkClaimAvailability.js');
const publish = require('./publish.js');
const createBasicPublishParams = require('./createBasicPublishParams.js');
const createPublishParams = require('./createPublishParams.js');
const createThumbnailPublishParams = require('./createThumbnailPublishParams.js');
const parsePublishApiRequestBody = require('./parsePublishApiRequestBody.js');
const parsePublishApiRequestFiles = require('./parsePublishApiRequestFiles.js');
const authenticateUser = require('./authentication.js');
const CLAIM_TAKEN = 'CLAIM_TAKEN';
/*
route to publish a claim through the daemon
@ -50,18 +52,20 @@ const claimPublish = ({ body, files, headers, ip, originalUrl, user, tor }, res)
return res.status(400).json({success: false, message: error.message});
}
// check channel authorization
Promise
.all([
authenticateUser(channelName, channelId, channelPassword, user),
checkClaimAvailability(name),
createBasicPublishParams(filePath, name, title, description, license, nsfw, thumbnail),
createThumbnailPublishParams(thumbnailFilePath, name, license, nsfw),
])
.then(([{channelName, channelClaimId}, validatedClaimName, publishParams, thumbnailPublishParams]) => {
// add channel details to the publish params
if (channelName && channelClaimId) {
publishParams['channel_name'] = channelName;
publishParams['channel_id'] = channelClaimId;
authenticateUser(channelName, channelId, channelPassword, user)
.then(({ channelName, channelClaimId }) => {
return Promise.all([
checkClaimAvailability(name),
createPublishParams(filePath, name, title, description, license, nsfw, thumbnail, channelName, channelClaimId),
createThumbnailPublishParams(thumbnailFilePath, name, license, nsfw),
])
})
.then(([ claimAvailable, publishParams, thumbnailPublishParams ]) => {
if (!claimAvailable) {
throw {
name: CLAIM_TAKEN,
message: 'That claim name is already taken'
};
}
// publish the thumbnail, if one exists
if (thumbnailPublishParams) {
@ -87,6 +91,12 @@ const claimPublish = ({ body, files, headers, ip, originalUrl, user, tor }, res)
sendGATimingEvent('end-to-end', 'publish', fileType, gaStartTime, Date.now());
})
.catch(error => {
if (error.name = CLAIM_TAKEN) {
res.status(400).json({
success: false,
message: error.message,
});
}
handleErrorResponse(originalUrl, ip, error, res);
});
};

View file

@ -1,6 +1,8 @@
const logger = require('winston');
const db = require('../../../../models');
const { publishClaim } = require('../../../../lbrynet');
const db = require('../../../../models');
const { createFileRecordDataAfterPublish } = require('../../../../models/utils/createFileRecordData.js');
const { createClaimRecordDataAfterPublish } = require('../../../../models/utils/createClaimRecordData.js');
const deleteFile = require('./deleteFile.js');
const publish = (publishParams, fileName, fileType) => {
@ -35,51 +37,34 @@ const publish = (publishParams, fileName, fileType) => {
logger.debug(`certificateId: ${certificateId}`);
})
.then(() => {
// create the File record
const fileRecord = {
name : publishParams.name,
claimId : publishResults.claim_id,
title : publishParams.metadata.title,
description: publishParams.metadata.description,
address : publishParams.claim_address,
outpoint : `${publishResults.txid}:${publishResults.nout}`,
height : 0,
fileName,
filePath : publishParams.file_path,
fileType,
nsfw : publishParams.metadata.nsfw,
};
// create the Claim record
const claimRecord = {
name : publishParams.name,
claimId : publishResults.claim_id,
title : publishParams.metadata.title,
description: publishParams.metadata.description,
address : publishParams.claim_address,
thumbnail : publishParams.metadata.thumbnail,
outpoint : `${publishResults.txid}:${publishResults.nout}`,
height : 0,
contentType: fileType,
nsfw : publishParams.metadata.nsfw,
amount : publishParams.bid,
certificateId,
channelName,
};
// upsert criteria
const upsertCriteria = {
name : publishParams.name,
claimId: publishResults.claim_id,
};
return Promise.all([
createFileRecordDataAfterPublish(fileName, fileType, publishParams, publishResults),
createClaimRecordDataAfterPublish(certificateId, channelName, fileName, fileType, publishParams, publishResults),
]);
})
.then(([fileRecord, claimRecord]) => {
// upsert the records
return Promise.all([db.upsert(db.File, fileRecord, upsertCriteria, 'File'), db.upsert(db.Claim, claimRecord, upsertCriteria, 'Claim')]);
const {name, claim_id: claimId} = publishParams;
const upsertCriteria = {
name,
claimId,
};
return Promise.all([
db.upsert(db.File, fileRecord, upsertCriteria, 'File'),
db.upsert(db.Claim, claimRecord, upsertCriteria, 'Claim'),
]);
})
.then(([file, claim]) => {
logger.debug('File and Claim records successfully created');
return Promise.all([file.setClaim(claim), claim.setFile(file)]);
return Promise.all([
file.setClaim(claim),
claim.setFile(file),
]);
})
.then(() => {
logger.debug('File and Claim records successfully associated');
resolve(publishResults); // resolve the promise with the result from lbryApi publishClaim;
// resolve the promise with the result from lbryApi publishClaim;
resolve(publishResults);
})
.catch(error => {
logger.error('PUBLISH ERROR', error);

View file

@ -0,0 +1,88 @@
module.exports = {
up: (queryInterface, { INTEGER }) => {
// logic for transforming into the new state
return Promise.all([
queryInterface.addColumn(
'File',
'fileHeight',
{
type : INTEGER,
allowNull: false,
default : 0,
}
),
queryInterface.addColumn(
'File',
'fileWidth',
{
type : INTEGER,
allowNull: false,
default : 0,
}
),
queryInterface.removeColumn(
'File',
'address',
),
queryInterface.removeColumn(
'File',
'height',
),
queryInterface.removeColumn(
'File',
'nsfw',
),
queryInterface.removeColumn(
'File',
'trendingEligible',
),
]);
},
down: (queryInterface, { BOOLEAN, INTEGER, STRING }) => {
return Promise.all([
queryInterface.removeColumn(
'File',
'fileHeight',
),
queryInterface.removeColumn(
'File',
'fileWidth',
),
queryInterface.addColumn(
'File',
'address',
{
type : STRING,
allowNull: false,
}
),
queryInterface.addColumn(
'File',
'height',
{
type : INTEGER,
allowNull: false,
default : 0,
}
),
queryInterface.addColumn(
'File',
'nsfw',
{
type : BOOLEAN,
allowNull : false,
defaultValue: false,
}
),
queryInterface.addColumn(
'File',
'trendingEligible',
{
type : BOOLEAN,
allowNull : false,
defaultValue: true,
}
),
]);
},
};

View file

@ -10,15 +10,16 @@ module.exports = (sequelize, { STRING, BOOLEAN, INTEGER }) => {
type : STRING,
allowNull: false,
},
address: {
type : STRING,
allowNull: false,
},
outpoint: {
type : STRING,
allowNull: false,
},
height: {
fileHeight: {
type : INTEGER,
allowNull: false,
default : 0,
},
fileWidth: {
type : INTEGER,
allowNull: false,
default : 0,
@ -34,16 +35,6 @@ module.exports = (sequelize, { STRING, BOOLEAN, INTEGER }) => {
fileType: {
type: STRING,
},
nsfw: {
type : BOOLEAN,
allowNull : false,
defaultValue: false,
},
trendingEligible: {
type : BOOLEAN,
allowNull : false,
defaultValue: true,
},
},
{
freezeTableName: true,
@ -51,17 +42,8 @@ module.exports = (sequelize, { STRING, BOOLEAN, INTEGER }) => {
);
File.associate = db => {
File.hasMany(db.Request);
File.hasOne(db.Claim);
};
File.getRecentClaims = function () {
return this.findAll({
where: { nsfw: false, trendingEligible: true },
order: [['createdAt', 'DESC']],
limit: 25,
});
};
return File;
};

View file

@ -5,7 +5,6 @@ const Certificate = require('./certificate.js');
const Channel = require('./channel.js');
const Claim = require('./claim.js');
const File = require('./file.js');
const Request = require('./request.js');
const User = require('./user.js');
const Blocked = require('./blocked.js');
const Tor = require('./tor.js');
@ -48,7 +47,6 @@ db['Certificate'] = sequelize.import('Certificate', Certificate);
db['Channel'] = sequelize.import('Channel', Channel);
db['Claim'] = sequelize.import('Claim', Claim);
db['File'] = sequelize.import('File', File);
db['Request'] = sequelize.import('Request', Request);
db['User'] = sequelize.import('User', User);
db['Blocked'] = sequelize.import('Blocked', Blocked);
db['Tor'] = sequelize.import('Tor', Tor);

View file

@ -1,37 +0,0 @@
module.exports = (sequelize, { STRING, BOOLEAN, TEXT }) => {
const Request = sequelize.define(
'Request',
{
action: {
type : STRING,
allowNull: false,
},
url: {
type : STRING,
allowNull: false,
},
ipAddress: {
type : STRING,
allowNull: true,
},
result: {
type : TEXT('long'),
allowNull: true,
default : null,
},
},
{
freezeTableName: true,
}
);
Request.associate = db => {
Request.belongsTo(db.File, {
foreignKey: {
allowNull: true,
},
});
};
return Request;
};

View file

@ -0,0 +1,39 @@
const createClaimRecordDataAfterPublish = (certificateId, channelName, fileName, fileType, publishParams, publishResults) => {
const {
name,
metadata: {
title,
description,
thumbnail,
nsfw,
},
claim_address: address,
bid: amount,
} = publishParams;
const {
claim_id: claimId,
txid,
nout,
} = publishResults;
return {
name,
claimId,
title,
description,
address,
thumbnail,
outpoint : `${txid}:${nout}`,
height : 0,
contentType: fileType,
nsfw,
amount,
certificateId,
channelName,
};
};
module.exports = {
createClaimRecordDataAfterPublish,
};

View file

@ -0,0 +1,65 @@
const getMediaDimensions = require('../../utils/getMediaDimensions.js');
async function createFileRecordDataAfterGet (resolveResult, getResult) {
const {
name,
claimId,
outpoint,
contentType: fileType,
} = resolveResult;
const {
file_name: fileName,
download_path: filePath,
} = getResult;
const {
height: fileHeight,
width: fileWidth,
} = await getMediaDimensions(fileType, filePath);
return {
name,
claimId,
outpoint,
fileHeight,
fileWidth,
fileName,
filePath,
fileType,
};
};
async function createFileRecordDataAfterPublish (fileName, fileType, publishParams, publishResults) {
const {
name,
file_path: filePath,
} = publishParams;
const {
claim_id: claimId,
txid,
nout,
} = publishResults;
const {
height: fileHeight,
width: fileWidth,
} = await getMediaDimensions(fileType, filePath);
return {
name,
claimId,
outpoint: `${txid}:${nout}`,
fileHeight,
fileWidth,
fileName,
filePath,
fileType,
};
}
module.exports = {
createFileRecordDataAfterGet,
createFileRecordDataAfterPublish,
};

View file

@ -0,0 +1,30 @@
const logger = require('winston');
const { getImageHeightAndWidth } = require('./imageProcessing');
const { getVideoHeightAndWidth } = require('./videoProcessing');
async function getMediaDimensions (fileType, filePath) {
let height = 0;
let width = 0;
switch (fileType) {
case 'image/jpeg':
case 'image/jpg':
case 'image/png':
case 'image/gif':
logger.debug('creating File data for an image');
[ height, width ] = await getImageHeightAndWidth(filePath);
break;
case 'video/mp4':
logger.debug('creating File data for a video');
[ height, width ] = await getVideoHeightAndWidth(filePath);
break;
default:
logger.error('unable to create File data for unspported file type:', fileType);
break;
}
return {
height,
width,
};
}
module.exports = getMediaDimensions;

View file

@ -0,0 +1,41 @@
const imageMagick = require('imagemagick');
const sizeOf = require('image-size');
const getImageMetadata = (filePath) => {
return new Promise((resolve, reject) => {
imageMagick.readMetadata(filePath, (err, metadata) => {
if (err) {
reject(err);
}
resolve(metadata);
});
});
};
const getImageDetails = (filePath) => {
return new Promise((resolve, reject) => {
imageMagick.identify(filePath, (err, details) => {
if (err) {
reject(err);
}
resolve(details);
});
});
};
const getImageHeightAndWidth = (filePath) => {
return new Promise((resolve, reject) => {
try {
const { height, width } = sizeOf(filePath);
resolve([height, width]);
} catch (error) {
reject(error);
}
});
};
module.exports = {
getImageMetadata,
getImageDetails,
getImageHeightAndWidth,
};

View file

@ -0,0 +1,11 @@
const getVideoDimensions = require('get-video-dimensions');
async function getVideoHeightAndWidth (filePath) {
const videoDimensions = await getVideoDimensions(filePath);
const { height, width } = videoDimensions;
return [ height, width ];
}
module.exports = {
getVideoHeightAndWidth,
};