Uh oh!
There was an error while loading. Please reload this page.
- Notifications
You must be signed in to change notification settings - Fork 134
Store multiple resolution copies of every uploaded image#25
New issue
Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.
By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.
Already on GitHub? Sign in to your account
Uh oh!
There was an error while loading. Please reload this page.
Changes from all commits
e70e6be2bbf0601e3aead7bffe786358a8e57e60561e944f1d605c29c238fccbfec95d5ad4bf5883116ca148e2cbbc12daa662776444a3c229f2936File filter
Filter by extension
Conversations
Uh oh!
There was an error while loading. Please reload this page.
Jump to
Uh oh!
There was an error while loading. Please reload this page.
Diff view
Diff view
There are no files selected for viewing
Large diffs are not rendered by default.
Uh oh!
There was an error while loading. Please reload this page.
| Original file line number | Diff line number | Diff line change |
|---|---|---|
| @@ -0,0 +1,76 @@ | ||
| 'use strict' | ||
| module.exports = { | ||
| up: async (queryInterface, Sequelize) => { | ||
| const t = await queryInterface.sequelize.transaction() | ||
| try { | ||
| const userMetadataFileUUIDSet = new Set( | ||
| ((await queryInterface.sequelize.query(`SELECT "metadataFileUUID" FROM "AudiusUsers" WHERE "metadataFileUUID" IS NOT NULL;`, { transaction: t }))[0]).map(obj => obj.metadataFileUUID) | ||
| ) | ||
| const userCoverArtFileUUIDSet = new Set( | ||
| ((await queryInterface.sequelize.query(`SELECT "coverArtFileUUID" FROM "AudiusUsers" WHERE "coverArtFileUUID" IS NOT NULL;`, { transaction: t }))[0]).map(obj => obj.coverArtFileUUID) | ||
| ) | ||
| const userProfilePicFileUUIDSet = new Set( | ||
| ((await queryInterface.sequelize.query(`SELECT "profilePicFileUUID" FROM "AudiusUsers" WHERE "profilePicFileUUID" IS NOT NULL;`, { transaction: t }))[0]).map(obj => obj.profilePicFileUUID) | ||
| ) | ||
| const trackMetadataFileUUIDSet = new Set( | ||
| ((await queryInterface.sequelize.query(`SELECT "metadataFileUUID" FROM "Tracks" WHERE "metadataFileUUID" IS NOT NULL;`, { transaction: t }))[0]).map(obj => obj.metadataFileUUID) | ||
| ) | ||
| const trackCoverArtFileUUIDSet = new Set( | ||
| ((await queryInterface.sequelize.query(`SELECT "coverArtFileUUID" FROM "Tracks" WHERE "coverArtFileUUID" IS NOT NULL;`, { transaction: t }))[0]).map(obj => obj.coverArtFileUUID) | ||
| ) | ||
| // Populate type for all files | ||
| const files = (await queryInterface.sequelize.query(`SELECT * FROM "Files";`, { transaction: t }))[0] | ||
| let orphanedFiles = [] // no types | ||
| let corruptedFiles = [] // multiple types | ||
| let trackFiles = [] | ||
| let metadataFiles = [] | ||
| let imageFiles = [] | ||
| for (let file of files) { | ||
| let type = null | ||
| const fileUUID = file.fileUUID | ||
| // Determine type of file | ||
| if (file.sourceFile) { | ||
| type = 'track' | ||
| trackFiles.push(file) | ||
| } | ||
| if (userMetadataFileUUIDSet.has(fileUUID) || trackMetadataFileUUIDSet.has(fileUUID)) { | ||
| if (type != null) { | ||
| corruptedFiles.push(file) | ||
| } | ||
| type = 'metadata' | ||
| metadataFiles.push(file) | ||
| } | ||
| if (userCoverArtFileUUIDSet.has(fileUUID) || userProfilePicFileUUIDSet.has(fileUUID) || trackCoverArtFileUUIDSet.has(fileUUID)) { | ||
| if (type != null) { | ||
| corruptedFiles.push(file) | ||
| } | ||
| type = 'image' | ||
| imageFiles.push(file) | ||
| } | ||
| if (type == null) { | ||
| orphanedFiles.push(file) | ||
| } | ||
| // Write file type to DB | ||
| await queryInterface.sequelize.query( | ||
| `UPDATE "Files" SET "type" = '${type}' WHERE "fileUUID" = '${fileUUID}'`, | ||
| { transaction: t } | ||
| ) | ||
| } | ||
| console.log('num orphaned files', orphanedFiles.length) | ||
| console.log('num corrupted files', corruptedFiles.length) | ||
| console.log(`num track files ${trackFiles.length}`) | ||
| console.log(`num metadata files ${metadataFiles.length}`) | ||
| console.log(`num image files ${imageFiles.length}`) | ||
| await t.commit() | ||
| } catch (e) { | ||
| await t.rollback() | ||
| } | ||
| }, | ||
| down: async (queryInterface, Sequelize) => { | ||
| await queryInterface.removeColumn('Files', 'type') | ||
| } | ||
| } |
| Original file line number | Diff line number | Diff line change |
|---|---|---|
| @@ -0,0 +1,99 @@ | ||
| const Jimp = require('jimp') | ||
| const ExifParser = require('exif-parser') | ||
| const MAX_HEIGHT = 6000 // No image should be taller than this. | ||
| const COLOR_WHITE = 0xFFFFFFFF | ||
| const IMAGE_QUALITY = 90 | ||
| const MIME_TYPE_JPEG = 'image/jpeg' | ||
| /** | ||
| * Returns an image that's been resized, cropped into a square, converted into JPEG, and compressed. | ||
| * @param {Object} req | ||
| * @param {string} imageBuffer the buffer of the image to use | ||
| * @param {number} maxWidth max width of the returned image (default is 1,000px) | ||
| * @param {boolean} square whether or not to square the image | ||
| * @return {Buffer} the converted image | ||
| * @dev TODO - replace with child node process bc need for speed | ||
| */ | ||
| async function resizeImage (req, imageBuffer, maxWidth, square) { | ||
| // eslint-disable-next-line | ||
| let exif | ||
| let time = Date.now() | ||
| req.logger.info(`resize image ${maxWidth} - start`) | ||
| try { | ||
| exif = ExifParser.create(imageBuffer).parse() | ||
| req.logger.info(`resize image ${maxWidth} - create time ${Date.now() - time}`) | ||
| time = Date.now() | ||
| } catch (error) { | ||
| req.logger.error(error) | ||
| exif = null | ||
| } | ||
| let img = await Jimp.read(imageBuffer) | ||
| req.logger.info(`resize image ${maxWidth} - read time ${Date.now() - time}`) | ||
| img = _exifRotate(img, exif) | ||
| img.background(COLOR_WHITE) | ||
| let width = img.bitmap.width | ||
| let height = img.bitmap.height | ||
| if (square) { | ||
| // If both sides are larger than maxWidth, resizing must occur | ||
| if (width > maxWidth && height > maxWidth) { | ||
| width > height ? img.resize(Jimp.AUTO, maxWidth) : img.resize(maxWidth, Jimp.AUTO) | ||
| } | ||
| // Crop the image to be square | ||
| let min = Math.min(img.bitmap.width, img.bitmap.height) | ||
| img.cover(min, min) | ||
| } else { | ||
| // Resize to max width and crop at crazy height | ||
| if (width > maxWidth) { | ||
| img.resize(maxWidth, Jimp.AUTO) | ||
| } | ||
| img.cover(img.bitmap.width, Math.min(img.bitmap.height, MAX_HEIGHT)) | ||
| } | ||
| // Very high quality, decent size reduction | ||
| img.quality(IMAGE_QUALITY) | ||
| return img.getBufferAsync(MIME_TYPE_JPEG) | ||
| } | ||
| // Copied directly from Jimp. | ||
SidSethi marked this conversation as resolved.
Outdated
Uh oh!There was an error while loading. Please reload this page. | ||
| // https://github.com/oliver-moran/jimp/blob/12248941fd481121dc5372f6a8154f01930c8d0f/packages/core/src/utils/image-bitmap.js#L31 | ||
| function _exifRotate (img, exif) { | ||
| if (exif && exif.tags && exif.tags.Orientation) { | ||
| switch (exif.tags.Orientation) { | ||
| case 1: // Horizontal (normal) | ||
| // do nothing | ||
| break | ||
| case 2: // Mirror horizontal | ||
| img.mirror(true, false) | ||
| break | ||
| case 3: // Rotate 180 | ||
| img.rotate(180, false) | ||
| break | ||
| case 4: // Mirror vertical | ||
| img.mirror(false, true) | ||
| break | ||
| case 5: // Mirror horizontal and rotate 270 CW | ||
| img.rotate(-90, false).mirror(true, false) | ||
| break | ||
| case 6: // Rotate 90 CW | ||
| img.rotate(-90, false) | ||
| break | ||
| case 7: // Mirror horizontal and rotate 90 CW | ||
| img.rotate(90, false).mirror(true, false) | ||
| break | ||
| case 8: // Rotate 270 CW | ||
| img.rotate(-270, false) | ||
| break | ||
| default: | ||
| break | ||
| } | ||
| } | ||
| return img | ||
| } | ||
| module.exports = resizeImage | ||
Uh oh!
There was an error while loading. Please reload this page.
Uh oh!
There was an error while loading. Please reload this page.