From 28467da8bb650b6bf29701ade335faf2f737f980 Mon Sep 17 00:00:00 2001 From: Clifton Campbell Date: Sat, 5 Sep 2020 19:08:32 -0700 Subject: [PATCH 01/18] preprocess grouphoto jpegs, clean up logs format --- package-lock.json | 13 ----- package.json | 1 - server/server.js | 107 +++++++++++++++++++++++------------- server/utils/group-photo.js | 96 +++++++++++++++----------------- 4 files changed, 114 insertions(+), 103 deletions(-) diff --git a/package-lock.json b/package-lock.json index 6abfbca..e281451 100644 --- a/package-lock.json +++ b/package-lock.json @@ -476,14 +476,6 @@ "integrity": "sha512-5P0QZ6J5xGikH780pghEdbEKijCTrruK9KxtPZCFWUpef0f6GipO+xEZ5GKCb020mmqgbiNO6TcA55CriL784Q==", "dev": true }, - "axios": { - "version": "0.20.0", - "resolved": "https://registry.npmjs.org/axios/-/axios-0.20.0.tgz", - "integrity": "sha512-ANA4rr2BDcmmAQLOKft2fufrtuvlqR+cXNNinUmvfeSNCOF98PZL+7M/v1zIdGo7OLjEA9J2gXJL+j4zGsl0bA==", - "requires": { - "follow-redirects": "^1.10.0" - } - }, "axobject-query": { "version": "2.2.0", "resolved": "https://registry.npmjs.org/axobject-query/-/axobject-query-2.2.0.tgz", @@ -2101,11 +2093,6 @@ "which": "^1.1.1" } }, - "follow-redirects": { - "version": "1.13.0", - "resolved": "https://registry.npmjs.org/follow-redirects/-/follow-redirects-1.13.0.tgz", - "integrity": "sha512-aq6gF1BEKje4a9i9+5jimNFIpq4Q1WiwBToeRK5NvZBd/TRsmW8BsJfOEGkr76TbOyPVD3OVDN910EcUNtRYEA==" - }, "forwarded": { "version": "0.1.2", "resolved": "https://registry.npmjs.org/forwarded/-/forwarded-0.1.2.tgz", diff --git a/package.json b/package.json index ebeca62..3713773 100644 --- a/package.json +++ b/package.json @@ -17,7 +17,6 @@ "license": "ISC", "dependencies": { "aws-sdk": "^2.729.0", - "axios": "^0.20.0", "concurrently": "^5.2.0", "dotenv": "^8.2.0", "express": "^4.17.1", diff --git a/server/server.js b/server/server.js index 7caf4e8..70691fb 100644 --- a/server/server.js +++ b/server/server.js @@ -8,9 +8,12 @@ const path = require('path') const fs = require('fs') const AWS = require('aws-sdk') const config = require('../config') -const { createGroupPhotoStream } = require('./utils/group-photo') +const { + outputGifToJpeg, + createGroupPhotoStream, +} = require('./utils/group-photo') -const makeFileLocation = (file) => +const getFileLocation = (file) => `https://${config.AWS_BUCKET_NAME}.s3.amazonaws.com/${file.Key}` const s3 = new AWS.S3({ @@ -46,11 +49,12 @@ const storage = multer.diskStorage({ const upload = multer({ storage }) const GREETING_PREFIX = 'public/gifs/greeting-' +const PHOTO_PREFIX = 'public/photos/photo-' -const listGifs = async () => { +const listByPrefix = async (Prefix) => { const params = { Bucket: config.AWS_BUCKET_NAME, - Prefix: GREETING_PREFIX, + Prefix, } const getAllContents = async (PrevContents = [], NextContinuationToken) => { @@ -72,7 +76,7 @@ const listGifs = async () => { const OrderedContents = Contents.map((file) => ({ ...file, - Location: makeFileLocation(file), + Location: getFileLocation(file), })).reverse() return OrderedContents @@ -80,7 +84,7 @@ const listGifs = async () => { app.get('/listGifs', async (_, res) => { try { - const result = await listGifs() + const result = await listByPrefix(GREETING_PREFIX) res.send(result) } catch (e) { console.log(e) @@ -97,16 +101,27 @@ app.post('/getGroupPhoto', async (_, res) => { const result = await s3.listObjects(params).promise() result.Contents = result.Contents.map((file) => ({ ...file, - Location: makeFileLocation(file), + Location: getFileLocation(file), })) res.send(result) }) +const fetchImageBuffer = (image) => { + const params = { Bucket: config.AWS_BUCKET_NAME, Key: image.Key } + return new Promise((resolve, reject) => + s3.getObject(params, (error, result) => + error ? reject(error) : resolve(result.Body), + ), + ) +} + app.post('/createGroupPhoto', async (_, res) => { try { - const result = await listGifs() - const urls = result.map((file) => makeFileLocation(file)) - const stream = await createGroupPhotoStream(urls) + const images = await listByPrefix(PHOTO_PREFIX) + const buffers = await Promise.all( + images.map((image) => fetchImageBuffer(image)), + ) + const stream = await createGroupPhotoStream(buffers) if (!stream) return const params = { Key: groupPhotoPath, @@ -119,9 +134,11 @@ app.post('/createGroupPhoto', async (_, res) => { if (err) { console.log(err, err.stack) } else { - console.log(`Group Photo Uploaded to s3: ${groupPhotoPath}`) - data.LastModified = Date.now() - res.send(data) + console.log(`Group photo uploaded to s3: ${groupPhotoPath}`) + res.send({ + ...data, + LastModified: Date.now(), + }) } }) } catch (e) { @@ -129,33 +146,50 @@ app.post('/createGroupPhoto', async (_, res) => { } }) -const uploadGIF = async (res, filename, folderName, onSuccess) => { +const uploadGIF = async (res, filename, folderName, onSuccess = () => {}) => { const filepath = `${folderName}/${filename}.gif` const fileStream = fs.createReadStream(filepath) + const GifKey = `${GREETING_PREFIX}${filename}.gif` + const PhotoKey = `${PHOTO_PREFIX}${filename}.jpeg` const params = { - Key: `${GREETING_PREFIX}${Date.now()}.gif`, + Key: GifKey, Bucket: config.AWS_BUCKET_NAME, Body: fileStream, ContentType: 'image/gif', ACL: 'public-read', } - await s3 - .upload(params) - .promise() - .then((data) => { - console.log('s3.upload', data) - res.send(data) - fs.unlink(filepath, () => - console.log(`${filepath} was deleted after upload`), - ) - if (onSuccess) onSuccess() - }) - .catch((e) => { - console.log(e, e.stack) - res.status(500).send(e) - }) + try { + const data = await s3.upload(params).promise() + console.log('Uploaded user gif to', data.Location) + + res.send(data) + onSuccess() + + const jpegPath = await outputGifToJpeg(filepath) + const jpegStream = fs.createReadStream(jpegPath) + + // upload the middle page of the gif to s3 as a JPEG to enable faster processing of group photo + await s3 + .upload({ + ...params, + Key: PhotoKey, + Body: jpegStream, + ContentType: 'image/jpeg', + }) + .promise() + + fs.unlink(filepath, () => + console.log(`${filepath} was deleted after upload`), + ) + fs.unlink(jpegPath, () => + console.log(`${jpegPath} was deleted after upload`), + ) + } catch (e) { + console.log(e, e.stack) + res.status(500).send(e) + } } app.post('/uploadUserGIF', upload.single('gif'), async (req, res) => { @@ -167,16 +201,15 @@ app.post('/uploadUserGIF', upload.single('gif'), async (req, res) => { }) } else { const filename = gif.filename.replace('.gif', '') - uploadGIF(res, filename, 'uploads') + await uploadGIF(res, filename, 'uploads') } }) -app.post('/uploadGIF', ({ body }, res) => { +app.post('/uploadGIF', async ({ body }, res) => { const { filename } = body uploadGIF(res, filename, 'temp', () => { - fs.unlink(`uploads/${filename}.webm`, () => - console.log('.webm file was deleted'), - ) + const filepath = `uploads/${filename}.webm` + fs.unlink(filepath, () => console.log(`${filepath} was deleted`)) }) }) @@ -208,7 +241,7 @@ app.post('/video2gif', upload.none(), ({ body }, res) => { res.send(body) }) .on('error', (err) => { - console.log(`an error happened: ${err.message}`) + console.log(`An error happened: ${err.message}`) res.send(err) }) .save(`temp/${videoId}.gif`) @@ -237,7 +270,7 @@ app.get('/download', (req, res) => { const filepath = `temp/${filename}.gif` res.download(filepath, (err) => { if (err) console.log(err) - console.log('Your file has been downloaded!') + console.log(`User downloaded ${filepath}`) }) }) diff --git a/server/utils/group-photo.js b/server/utils/group-photo.js index 2e8bee7..d1d03d9 100644 --- a/server/utils/group-photo.js +++ b/server/utils/group-photo.js @@ -1,5 +1,4 @@ const sharp = require('sharp') -const axios = require('axios') const fs = require('fs') const chunkArray = (array, size) => { @@ -9,23 +8,6 @@ const chunkArray = (array, size) => { return [firstChunk].concat(chunkArray(array.slice(size, array.length), size)) } -const fetchImg = async (url) => { - try { - const res = await axios({ - url, - responseType: 'arraybuffer', - }) - return res - } catch (e) { - return null - } -} - -const fetchImgs = async (imgs) => { - const fetched = await Promise.all(imgs.map(fetchImg)) - return fetched.filter((img) => img !== null) -} - const createImageLayout = (imgs) => { const aspectRatio = 1 + 1 / 3 @@ -72,41 +54,31 @@ const createImageLayout = (imgs) => { } } -const compositeGif = async (buffer, { width, height, top, left, id }) => { - const toSizedBuffer = (frame) => frame.resize(width, height).raw().toBuffer() - - let input - const firstFrame = await sharp(buffer) +const createJpegComposite = async (buffer, { width, height, top, left }) => { try { - const { pages } = await firstFrame.metadata() - const page = Math.round(pages / 2) || 0 - const middleFrame = await sharp(buffer, { page }) - input = await toSizedBuffer(middleFrame) + const resizedBuffer = await sharp(buffer) + .resize(width, height) + .raw() + .toBuffer({ resolveWithObject: true }) + + return { + input: resizedBuffer.data, + raw: resizedBuffer.info, + top, + left, + } } catch (e) { - // so far we have only seen faulty gifs cause this to catch - console.log( - 'Failed to slice middle GIF frame (or "page"), defaulting to first frame.', - { id }, - ) - input = await toSizedBuffer(firstFrame) - } - - return { - input, - raw: { width, height, channels: 4 }, - top, - left, + console.log('Image Resize Failed') + return null } } -const createGroupPhoto = async (urls) => { +const createGroupPhoto = async (buffers) => { const padding = 16 const brandingHeight = 80 const conferenceOutputPath = './temp/conference_logo.png' - const imgs = await fetchImgs(urls.slice(0, 80)) - - const layout = createImageLayout(imgs) + const layout = createImageLayout(buffers) await sharp('./branding/Logo.png') .resize(null, brandingHeight) @@ -125,12 +97,11 @@ const createGroupPhoto = async (urls) => { imgRows.map(async (row, i) => { const composites = await Promise.all( row.map((img) => - compositeGif(img.data.data, { + createJpegComposite(img.data, { top: 0, left: img.left, width: layout.imgWidth, height: layout.imgHeight, - id: img.data.config.url, }), ), ) @@ -142,7 +113,7 @@ const createGroupPhoto = async (urls) => { channels: 3, }, }) - .composite(composites) + .composite(composites.filter((c) => c)) .toBuffer({ resolveWithObject: true }) return { @@ -193,15 +164,15 @@ const createGroupPhoto = async (urls) => { const outputPath = './temp/group-photo.jpeg' -const createGroupPhotoStream = async (urls) => { +const createGroupPhotoStream = async (buffers) => { try { - const groupPhoto = await createGroupPhoto(urls) - console.log('Group Photo Processed') + const groupPhoto = await createGroupPhoto(buffers) + console.log('Group photo processed') const jpeg = await groupPhoto.jpeg({ quality: 75, }) await jpeg.toFile(outputPath) - console.log(`Group Photo Output to ${outputPath}`) + console.log(`Group photo output to ${outputPath}`) return fs.createReadStream(outputPath) } catch (e) { console.log(e) @@ -209,4 +180,25 @@ const createGroupPhotoStream = async (urls) => { } } -module.exports = { createGroupPhotoStream } +const outputGifToJpeg = async (path) => { + let frame + const firstPage = await sharp(path) + try { + const { pages } = await firstPage.metadata() + const page = Math.round(pages / 2) || 0 + const middlePage = await sharp(path, { page }) + frame = middlePage + } catch (e) { + // so far we have only seen faulty gifs cause this to catch + console.log( + 'Failed to slice middle GIF page, defaulting to first page:', + path, + ) + frame = firstPage + } + const filepath = path.replace('.gif', '.jpeg') + await frame.jpeg({ quality: 75 }).toFile(filepath) + return filepath +} + +module.exports = { outputGifToJpeg, createGroupPhotoStream } From 3dd567cdafd29eced591dd5cb4a7702c29c3756b Mon Sep 17 00:00:00 2001 From: Clifton Campbell Date: Sat, 5 Sep 2020 19:17:13 -0700 Subject: [PATCH 02/18] revert small changes --- server/server.js | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/server/server.js b/server/server.js index 70691fb..5909651 100644 --- a/server/server.js +++ b/server/server.js @@ -201,11 +201,11 @@ app.post('/uploadUserGIF', upload.single('gif'), async (req, res) => { }) } else { const filename = gif.filename.replace('.gif', '') - await uploadGIF(res, filename, 'uploads') + uploadGIF(res, filename, 'uploads') } }) -app.post('/uploadGIF', async ({ body }, res) => { +app.post('/uploadGIF', ({ body }, res) => { const { filename } = body uploadGIF(res, filename, 'temp', () => { const filepath = `uploads/${filename}.webm` From fe110a7f52442603219f951138390fd0d4ad0e4c Mon Sep 17 00:00:00 2001 From: Clifton Campbell Date: Sat, 5 Sep 2020 19:36:54 -0700 Subject: [PATCH 03/18] share greeting as filename beginning --- server/server.js | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/server/server.js b/server/server.js index 5909651..7da7911 100644 --- a/server/server.js +++ b/server/server.js @@ -49,7 +49,7 @@ const storage = multer.diskStorage({ const upload = multer({ storage }) const GREETING_PREFIX = 'public/gifs/greeting-' -const PHOTO_PREFIX = 'public/photos/photo-' +const PHOTO_PREFIX = 'public/photos/greeting-' const listByPrefix = async (Prefix) => { const params = { From f694c0dcd5de48eec301d1ef1ec82ba579df0513 Mon Sep 17 00:00:00 2001 From: Clifton Campbell Date: Sat, 5 Sep 2020 20:58:30 -0700 Subject: [PATCH 04/18] revert to old file prefix --- server/server.js | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/server/server.js b/server/server.js index 7da7911..5909651 100644 --- a/server/server.js +++ b/server/server.js @@ -49,7 +49,7 @@ const storage = multer.diskStorage({ const upload = multer({ storage }) const GREETING_PREFIX = 'public/gifs/greeting-' -const PHOTO_PREFIX = 'public/photos/greeting-' +const PHOTO_PREFIX = 'public/photos/photo-' const listByPrefix = async (Prefix) => { const params = { From 48fd1bea42b7afb984c76df5ad6409412a346447 Mon Sep 17 00:00:00 2001 From: Clifton Campbell Date: Sat, 5 Sep 2020 21:15:14 -0700 Subject: [PATCH 05/18] broaden error try catch scope for uplloadgif --- server/server.js | 25 ++++++++++++------------- 1 file changed, 12 insertions(+), 13 deletions(-) diff --git a/server/server.js b/server/server.js index 5909651..019251b 100644 --- a/server/server.js +++ b/server/server.js @@ -148,19 +148,18 @@ app.post('/createGroupPhoto', async (_, res) => { const uploadGIF = async (res, filename, folderName, onSuccess = () => {}) => { const filepath = `${folderName}/${filename}.gif` - const fileStream = fs.createReadStream(filepath) - const GifKey = `${GREETING_PREFIX}${filename}.gif` - const PhotoKey = `${PHOTO_PREFIX}${filename}.jpeg` - - const params = { - Key: GifKey, - Bucket: config.AWS_BUCKET_NAME, - Body: fileStream, - ContentType: 'image/gif', - ACL: 'public-read', - } - try { + const fileStream = fs.createReadStream(filepath) + const GifKey = `${GREETING_PREFIX}${filename}.gif` + const PhotoKey = `${PHOTO_PREFIX}${filename}.jpeg` + + const params = { + Key: GifKey, + Bucket: config.AWS_BUCKET_NAME, + Body: fileStream, + ContentType: 'image/gif', + ACL: 'public-read', + } const data = await s3.upload(params).promise() console.log('Uploaded user gif to', data.Location) @@ -187,7 +186,7 @@ const uploadGIF = async (res, filename, folderName, onSuccess = () => {}) => { console.log(`${jpegPath} was deleted after upload`), ) } catch (e) { - console.log(e, e.stack) + console.log(e, e.stack, filepath) res.status(500).send(e) } } From f9d071e96e3a5cc7b7e768104852feeffe31b056 Mon Sep 17 00:00:00 2001 From: Clifton Campbell Date: Sat, 5 Sep 2020 21:23:41 -0700 Subject: [PATCH 06/18] use uuid, add log --- package-lock.json | 13 ++++++++++--- package.json | 3 ++- server/server.js | 7 +++++-- 3 files changed, 17 insertions(+), 6 deletions(-) diff --git a/package-lock.json b/package-lock.json index e281451..7a35eb4 100644 --- a/package-lock.json +++ b/package-lock.json @@ -468,6 +468,13 @@ "url": "0.10.3", "uuid": "3.3.2", "xml2js": "0.4.19" + }, + "dependencies": { + "uuid": { + "version": "3.3.2", + "resolved": "https://registry.npmjs.org/uuid/-/uuid-3.3.2.tgz", + "integrity": "sha512-yXJmeNaw3DnnKAOKJE51sL/ZaYfWJRl1pK9dr19YFCu0ObS231AB1/LbqTKRAQ5kw8A90rA6fr4riOUpTZvQZA==" + } } }, "axe-core": { @@ -4904,9 +4911,9 @@ "integrity": "sha1-n5VxD1CiZ5R7LMwSR0HBAoQn5xM=" }, "uuid": { - "version": "3.3.2", - "resolved": "https://registry.npmjs.org/uuid/-/uuid-3.3.2.tgz", - "integrity": "sha512-yXJmeNaw3DnnKAOKJE51sL/ZaYfWJRl1pK9dr19YFCu0ObS231AB1/LbqTKRAQ5kw8A90rA6fr4riOUpTZvQZA==" + "version": "8.3.0", + "resolved": "https://registry.npmjs.org/uuid/-/uuid-8.3.0.tgz", + "integrity": "sha512-fX6Z5o4m6XsXBdli9g7DtWgAx+osMsRRZFKma1mIUsLCz6vRvv+pz5VNbyu9UEDzpMWulZfvpgb/cmDXVulYFQ==" }, "v8-compile-cache": { "version": "2.1.1", diff --git a/package.json b/package.json index 3713773..4dcef12 100644 --- a/package.json +++ b/package.json @@ -23,7 +23,8 @@ "express-basic-auth": "^1.2.0", "fluent-ffmpeg": "^2.1.2", "multer": "^1.4.2", - "sharp": "^0.26.0" + "sharp": "^0.26.0", + "uuid": "^8.3.0" }, "devDependencies": { "babel-eslint": "^10.0.1", diff --git a/server/server.js b/server/server.js index 019251b..697fd9d 100644 --- a/server/server.js +++ b/server/server.js @@ -7,6 +7,7 @@ const multer = require('multer') const path = require('path') const fs = require('fs') const AWS = require('aws-sdk') +const { v4: uuidv4 } = require('uuid') const config = require('../config') const { outputGifToJpeg, @@ -38,9 +39,10 @@ const storage = multer.diskStorage({ cb(null, './uploads/') }, filename(req, file, cb) { - let filename = `${Date.now()}.${file.originalname.split('.')[1]}` + const id = uuidv4() + let filename = `${id}.${file.originalname.split('.')[1]}` if (req.path === '/uploadBlob') { - filename = `${Date.now()}.webm` + filename = `${id}.webm` } cb(null, filename) }, @@ -247,6 +249,7 @@ app.post('/video2gif', upload.none(), ({ body }, res) => { }) app.post('/uploadBlob', upload.single('video'), ({ file }, res) => { + console.log('Uploading', file) res.send(file) }) From f77eb04eb75ce455f2170ca0088f12c52aa966f9 Mon Sep 17 00:00:00 2001 From: Clifton Campbell Date: Sat, 5 Sep 2020 21:32:44 -0700 Subject: [PATCH 07/18] push up temp log --- server/server.js | 1 + 1 file changed, 1 insertion(+) diff --git a/server/server.js b/server/server.js index 697fd9d..e66f58e 100644 --- a/server/server.js +++ b/server/server.js @@ -44,6 +44,7 @@ const storage = multer.diskStorage({ if (req.path === '/uploadBlob') { filename = `${id}.webm` } + console.log('Multer processing filename', { file, filename }) cb(null, filename) }, }) From 55613bd0af9bc2aadd28473ce2fc494b6f78d457 Mon Sep 17 00:00:00 2001 From: Clifton Campbell Date: Sat, 5 Sep 2020 22:00:02 -0700 Subject: [PATCH 08/18] try adding gitkeep --- .gitignore | 2 +- server/server.js | 3 +-- uploads/.gitkeep | 0 3 files changed, 2 insertions(+), 3 deletions(-) create mode 100644 uploads/.gitkeep diff --git a/.gitignore b/.gitignore index a3b5ad7..47fb82e 100644 --- a/.gitignore +++ b/.gitignore @@ -10,4 +10,4 @@ temp/* # ignore files in folder uploads/* # except -!uploads/README.md \ No newline at end of file +!uploads/README.md diff --git a/server/server.js b/server/server.js index e66f58e..7207279 100644 --- a/server/server.js +++ b/server/server.js @@ -44,7 +44,7 @@ const storage = multer.diskStorage({ if (req.path === '/uploadBlob') { filename = `${id}.webm` } - console.log('Multer processing filename', { file, filename }) + console.log(`Multer processing filename for ${filename}`) cb(null, filename) }, }) @@ -250,7 +250,6 @@ app.post('/video2gif', upload.none(), ({ body }, res) => { }) app.post('/uploadBlob', upload.single('video'), ({ file }, res) => { - console.log('Uploading', file) res.send(file) }) diff --git a/uploads/.gitkeep b/uploads/.gitkeep new file mode 100644 index 0000000..e69de29 From 935a94a26940203c1c5339e921e48d1a3f1a2f24 Mon Sep 17 00:00:00 2001 From: Clifton Campbell Date: Sat, 5 Sep 2020 22:04:00 -0700 Subject: [PATCH 09/18] rmv unecessary log --- server/server.js | 1 - 1 file changed, 1 deletion(-) diff --git a/server/server.js b/server/server.js index 7207279..878689e 100644 --- a/server/server.js +++ b/server/server.js @@ -44,7 +44,6 @@ const storage = multer.diskStorage({ if (req.path === '/uploadBlob') { filename = `${id}.webm` } - console.log(`Multer processing filename for ${filename}`) cb(null, filename) }, }) From 1f9216770c7432a2dd2c44226d86e54d255fa0a1 Mon Sep 17 00:00:00 2001 From: Clifton Campbell Date: Sat, 5 Sep 2020 23:56:04 -0700 Subject: [PATCH 10/18] add sort by date --- server/server.js | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/server/server.js b/server/server.js index 878689e..8b0f8b5 100644 --- a/server/server.js +++ b/server/server.js @@ -79,7 +79,7 @@ const listByPrefix = async (Prefix) => { const OrderedContents = Contents.map((file) => ({ ...file, Location: getFileLocation(file), - })).reverse() + })).sort((a, b) => b.LastModified - a.LastModified) return OrderedContents } From d54a78c1ee146a0bce6b2fe088b465496e6ef775 Mon Sep 17 00:00:00 2001 From: Clifton Campbell Date: Sun, 6 Sep 2020 00:05:11 -0700 Subject: [PATCH 11/18] set longer timeout for createGroupPhoto --- server/server.js | 3 ++- 1 file changed, 2 insertions(+), 1 deletion(-) diff --git a/server/server.js b/server/server.js index 8b0f8b5..20bbfad 100644 --- a/server/server.js +++ b/server/server.js @@ -117,7 +117,8 @@ const fetchImageBuffer = (image) => { ) } -app.post('/createGroupPhoto', async (_, res) => { +app.post('/createGroupPhoto', async (req, res) => { + req.setTimeout(500000) try { const images = await listByPrefix(PHOTO_PREFIX) const buffers = await Promise.all( From 347479c0430db83b989eca6caba2e742e0a98863 Mon Sep 17 00:00:00 2001 From: Clifton Campbell Date: Sun, 6 Sep 2020 09:45:49 -0700 Subject: [PATCH 12/18] add logs to group photo --- server/server.js | 3 ++- 1 file changed, 2 insertions(+), 1 deletion(-) diff --git a/server/server.js b/server/server.js index 20bbfad..3342642 100644 --- a/server/server.js +++ b/server/server.js @@ -118,12 +118,13 @@ const fetchImageBuffer = (image) => { } app.post('/createGroupPhoto', async (req, res) => { - req.setTimeout(500000) try { + console.log('Starting group photo creation') const images = await listByPrefix(PHOTO_PREFIX) const buffers = await Promise.all( images.map((image) => fetchImageBuffer(image)), ) + console.log('Group photo input buffers fetched from s3') const stream = await createGroupPhotoStream(buffers) if (!stream) return const params = { From f159eeebb0d79552138c5127fc0f37ba040a1b69 Mon Sep 17 00:00:00 2001 From: Clifton Campbell Date: Sun, 6 Sep 2020 16:48:09 -0700 Subject: [PATCH 13/18] add websocket to emit group photo creation event --- client/src/pages/GroupPhoto/GroupPhoto.js | 19 ++- package-lock.json | 5 + package.json | 3 +- server/server.js | 139 +++++++++++++--------- 4 files changed, 103 insertions(+), 63 deletions(-) diff --git a/client/src/pages/GroupPhoto/GroupPhoto.js b/client/src/pages/GroupPhoto/GroupPhoto.js index 24a2b24..7fa6257 100644 --- a/client/src/pages/GroupPhoto/GroupPhoto.js +++ b/client/src/pages/GroupPhoto/GroupPhoto.js @@ -4,6 +4,10 @@ import Page from '../../components/Page' import { downloadFromS3 } from '../../utils/download' import './GroupPhoto.css' +const ws = new WebSocket( + `ws://${window.location.hostname}:${process.env.PORT || 3001}`, +) + const GroupPhoto = () => { const [isLoading, setIsLoading] = useState(true) const [isGenerating, setIsGenerating] = useState(false) @@ -11,15 +15,12 @@ const GroupPhoto = () => { const createGroupPhoto = async () => { setIsGenerating(true) - const res = await fetch('/createGroupPhoto', { + fetch('/createGroupPhoto', { method: 'POST', headers: { 'Content-Type': 'application/json', }, }) - const json = await res.json() - setFile(json) - setIsGenerating(false) } const getGroupPhoto = async () => { @@ -36,6 +37,16 @@ const GroupPhoto = () => { useEffect(() => { getGroupPhoto() + ws.onmessage = (event) => { + const data = JSON.parse(event.data) + if (data.id !== 'group-photo') return + if (data.status === 200) { + setFile(data.data) + } else { + console.log(data.message, data.error) + } + setIsGenerating(false) + } }, []) const header =

Create Group Photo

diff --git a/package-lock.json b/package-lock.json index 7a35eb4..79c0027 100644 --- a/package-lock.json +++ b/package-lock.json @@ -5076,6 +5076,11 @@ "typedarray-to-buffer": "^3.1.5" } }, + "ws": { + "version": "7.3.1", + "resolved": "https://registry.npmjs.org/ws/-/ws-7.3.1.tgz", + "integrity": "sha512-D3RuNkynyHmEJIpD2qrgVkc9DQ23OrN/moAwZX4L8DfvszsJxpjQuUq3LMx6HoYji9fbIOBY18XWBsAux1ZZUA==" + }, "xdg-basedir": { "version": "4.0.0", "resolved": "https://registry.npmjs.org/xdg-basedir/-/xdg-basedir-4.0.0.tgz", diff --git a/package.json b/package.json index 4dcef12..2f3d4fa 100644 --- a/package.json +++ b/package.json @@ -24,7 +24,8 @@ "fluent-ffmpeg": "^2.1.2", "multer": "^1.4.2", "sharp": "^0.26.0", - "uuid": "^8.3.0" + "uuid": "^8.3.0", + "ws": "^7.3.1" }, "devDependencies": { "babel-eslint": "^10.0.1", diff --git a/server/server.js b/server/server.js index 3342642..34cfa4e 100644 --- a/server/server.js +++ b/server/server.js @@ -7,6 +7,7 @@ const multer = require('multer') const path = require('path') const fs = require('fs') const AWS = require('aws-sdk') +const { Server } = require('ws') const { v4: uuidv4 } = require('uuid') const config = require('../config') const { @@ -93,63 +94,6 @@ app.get('/listGifs', async (_, res) => { } }) -const groupPhotoPath = 'public/group_photo.jpeg' - -app.post('/getGroupPhoto', async (_, res) => { - const params = { - Bucket: config.AWS_BUCKET_NAME, - Prefix: groupPhotoPath, - } - const result = await s3.listObjects(params).promise() - result.Contents = result.Contents.map((file) => ({ - ...file, - Location: getFileLocation(file), - })) - res.send(result) -}) - -const fetchImageBuffer = (image) => { - const params = { Bucket: config.AWS_BUCKET_NAME, Key: image.Key } - return new Promise((resolve, reject) => - s3.getObject(params, (error, result) => - error ? reject(error) : resolve(result.Body), - ), - ) -} - -app.post('/createGroupPhoto', async (req, res) => { - try { - console.log('Starting group photo creation') - const images = await listByPrefix(PHOTO_PREFIX) - const buffers = await Promise.all( - images.map((image) => fetchImageBuffer(image)), - ) - console.log('Group photo input buffers fetched from s3') - const stream = await createGroupPhotoStream(buffers) - if (!stream) return - const params = { - Key: groupPhotoPath, - Bucket: config.AWS_BUCKET_NAME, - Body: stream, - ContentType: 'image/png', - ACL: 'public-read', - } - s3.upload(params, (err, data) => { - if (err) { - console.log(err, err.stack) - } else { - console.log(`Group photo uploaded to s3: ${groupPhotoPath}`) - res.send({ - ...data, - LastModified: Date.now(), - }) - } - }) - } catch (e) { - console.log(e) - } -}) - const uploadGIF = async (res, filename, folderName, onSuccess = () => {}) => { const filepath = `${folderName}/${filename}.gif` try { @@ -308,6 +252,85 @@ app.get('/*', (req, res) => res.sendFile(path.join(__dirname, '../client/build', 'index.html')), ) -app.listen(app.get('port'), () => { +const server = app.listen(app.get('port'), () => { console.log(`Find the server at: http://localhost:${app.get('port')}/`) }) + +const wss = new Server({ server }) + +const groupPhotoPath = 'public/group_photo.jpeg' + +app.post('/getGroupPhoto', async (_, res) => { + const params = { + Bucket: config.AWS_BUCKET_NAME, + Prefix: groupPhotoPath, + } + const result = await s3.listObjects(params).promise() + result.Contents = result.Contents.map((file) => ({ + ...file, + Location: getFileLocation(file), + })) + res.send(result) +}) + +const fetchImageBuffer = (image) => { + const params = { Bucket: config.AWS_BUCKET_NAME, Key: image.Key } + return new Promise((resolve, reject) => + s3.getObject(params, (error, result) => + error ? reject(error) : resolve(result.Body), + ), + ) +} + +app.post('/createGroupPhoto', async (req, res) => { + res.send({ message: 'Starting group photo processing' }) + try { + console.log('Starting group photo creation') + const images = await listByPrefix(PHOTO_PREFIX) + const buffers = await Promise.all( + images.map((image) => fetchImageBuffer(image)), + ) + console.log('Group photo input buffers fetched from s3') + const stream = await createGroupPhotoStream(buffers) + if (!stream) return + const params = { + Key: groupPhotoPath, + Bucket: config.AWS_BUCKET_NAME, + Body: stream, + ContentType: 'image/png', + ACL: 'public-read', + } + s3.upload(params, (err, data) => { + if (err) { + console.log(err, err.stack) + } else { + console.log(`Group photo uploaded to s3: ${groupPhotoPath}`) + const _data = { + ...data, + LastModified: Date.now(), + } + wss.clients.forEach((client) => { + client.send( + JSON.stringify({ + id: 'group-photo', + status: 200, + data: _data, + message: 'Group photo processed successfully', + }), + ) + }) + } + }) + } catch (e) { + wss.clients.forEach((client) => { + client.send( + JSON.stringify({ + id: 'group-photo', + status: 500, + message: 'Group photo processing failed', + error: e, + }), + ) + }) + } +}) From e7f3f6dfe6c4d2dcfc86b5747465f499b51fcb9e Mon Sep 17 00:00:00 2001 From: Clifton Campbell Date: Sun, 6 Sep 2020 16:53:24 -0700 Subject: [PATCH 14/18] change ws protocol --- client/src/pages/GroupPhoto/GroupPhoto.js | 4 +++- 1 file changed, 3 insertions(+), 1 deletion(-) diff --git a/client/src/pages/GroupPhoto/GroupPhoto.js b/client/src/pages/GroupPhoto/GroupPhoto.js index 7fa6257..5105b76 100644 --- a/client/src/pages/GroupPhoto/GroupPhoto.js +++ b/client/src/pages/GroupPhoto/GroupPhoto.js @@ -5,7 +5,9 @@ import { downloadFromS3 } from '../../utils/download' import './GroupPhoto.css' const ws = new WebSocket( - `ws://${window.location.hostname}:${process.env.PORT || 3001}`, + `${window.location.protocol === 'https:' ? 'wss' : 'ws'}://${ + window.location.hostname + }:${process.env.PORT || 3001}`, ) const GroupPhoto = () => { From eb4e0e90957775c3c54e78d1f3631217a22ff77b Mon Sep 17 00:00:00 2001 From: Clifton Campbell Date: Mon, 7 Sep 2020 09:27:22 -0700 Subject: [PATCH 15/18] dif string method for host --- client/src/pages/GroupPhoto/GroupPhoto.js | 13 ++++++++----- 1 file changed, 8 insertions(+), 5 deletions(-) diff --git a/client/src/pages/GroupPhoto/GroupPhoto.js b/client/src/pages/GroupPhoto/GroupPhoto.js index 5105b76..4699860 100644 --- a/client/src/pages/GroupPhoto/GroupPhoto.js +++ b/client/src/pages/GroupPhoto/GroupPhoto.js @@ -4,11 +4,14 @@ import Page from '../../components/Page' import { downloadFromS3 } from '../../utils/download' import './GroupPhoto.css' -const ws = new WebSocket( - `${window.location.protocol === 'https:' ? 'wss' : 'ws'}://${ - window.location.hostname - }:${process.env.PORT || 3001}`, -) +const HOST = window.location.origin + .replace(/^http/, 'ws') + .replace('3000', '3001') +const ws = new WebSocket(HOST) + +// `${window.location.protocol === 'https:' ? 'wss' : 'ws'}://${ +// window.location.hostname +// }:${process.env.PORT || 3001}`, const GroupPhoto = () => { const [isLoading, setIsLoading] = useState(true) From 5cec815ac2277d97c485e4e65ba9d26698a37db1 Mon Sep 17 00:00:00 2001 From: Clifton Campbell Date: Mon, 7 Sep 2020 09:55:24 -0700 Subject: [PATCH 16/18] abstract websockets a bit --- client/src/pages/GroupPhoto/GroupPhoto.js | 16 +++------------ client/src/websockets.js | 12 ++++++++++++ package-lock.json | 24 +++++++++++++++++++++++ package.json | 4 ++++ 4 files changed, 43 insertions(+), 13 deletions(-) create mode 100644 client/src/websockets.js diff --git a/client/src/pages/GroupPhoto/GroupPhoto.js b/client/src/pages/GroupPhoto/GroupPhoto.js index 4699860..ebcbdbd 100644 --- a/client/src/pages/GroupPhoto/GroupPhoto.js +++ b/client/src/pages/GroupPhoto/GroupPhoto.js @@ -2,17 +2,9 @@ import React, { useEffect, useState } from 'react' import Button from '../../components/Button' import Page from '../../components/Page' import { downloadFromS3 } from '../../utils/download' +import { onWsEvent } from '../../websockets' import './GroupPhoto.css' -const HOST = window.location.origin - .replace(/^http/, 'ws') - .replace('3000', '3001') -const ws = new WebSocket(HOST) - -// `${window.location.protocol === 'https:' ? 'wss' : 'ws'}://${ -// window.location.hostname -// }:${process.env.PORT || 3001}`, - const GroupPhoto = () => { const [isLoading, setIsLoading] = useState(true) const [isGenerating, setIsGenerating] = useState(false) @@ -42,16 +34,14 @@ const GroupPhoto = () => { useEffect(() => { getGroupPhoto() - ws.onmessage = (event) => { - const data = JSON.parse(event.data) - if (data.id !== 'group-photo') return + onWsEvent('group-photo', (data) => { if (data.status === 200) { setFile(data.data) } else { console.log(data.message, data.error) } setIsGenerating(false) - } + }) }, []) const header =

Create Group Photo

diff --git a/client/src/websockets.js b/client/src/websockets.js new file mode 100644 index 0000000..6d70d52 --- /dev/null +++ b/client/src/websockets.js @@ -0,0 +1,12 @@ +const HOST = window.location.origin.replace(/^http/, 'ws') + +if (process.env.NODE_ENV === 'development') HOST.replace('3000', '3001') + +export const ws = new WebSocket(HOST) + +export const onWsEvent = (eventId, callback) => { + ws.onmessage = (event) => { + const data = JSON.parse(event.data) + if (data.id === eventId) callback(data) + } +} diff --git a/package-lock.json b/package-lock.json index 79c0027..596ebb9 100644 --- a/package-lock.json +++ b/package-lock.json @@ -734,6 +734,15 @@ "resolved": "https://registry.npmjs.org/buffer-from/-/buffer-from-1.1.1.tgz", "integrity": "sha512-MQcXEUbCKtEo7bhqEs6560Hyd4XaovZlO/k9V3hjVUF/zwW7KBVdSK4gIt/bzwS9MbR5qob+F5jusZsb0YQK2A==" }, + "bufferutil": { + "version": "4.0.1", + "resolved": "https://registry.npmjs.org/bufferutil/-/bufferutil-4.0.1.tgz", + "integrity": "sha512-xowrxvpxojqkagPcWRQVXZl0YXhRhAtBEIq3VoER1NH5Mw1n1o0ojdspp+GS2J//2gCVyrzQDApQ4unGF+QOoA==", + "optional": true, + "requires": { + "node-gyp-build": "~3.7.0" + } + }, "busboy": { "version": "0.2.14", "resolved": "https://registry.npmjs.org/busboy/-/busboy-0.2.14.tgz", @@ -3413,6 +3422,12 @@ "resolved": "https://registry.npmjs.org/node-addon-api/-/node-addon-api-3.0.0.tgz", "integrity": "sha512-sSHCgWfJ+Lui/u+0msF3oyCgvdkhxDbkCS6Q8uiJquzOimkJBvX6hl5aSSA7DR1XbMpdM8r7phjcF63sF4rkKg==" }, + "node-gyp-build": { + "version": "3.7.0", + "resolved": "https://registry.npmjs.org/node-gyp-build/-/node-gyp-build-3.7.0.tgz", + "integrity": "sha512-L/Eg02Epx6Si2NXmedx+Okg+4UHqmaf3TNcxd50SF9NQGcJaON3AtU++kax69XV7YWz4tUspqZSAsVofhFKG2w==", + "optional": true + }, "nodemon": { "version": "2.0.4", "resolved": "https://registry.npmjs.org/nodemon/-/nodemon-2.0.4.tgz", @@ -4900,6 +4915,15 @@ "prepend-http": "^2.0.0" } }, + "utf-8-validate": { + "version": "5.0.2", + "resolved": "https://registry.npmjs.org/utf-8-validate/-/utf-8-validate-5.0.2.tgz", + "integrity": "sha512-SwV++i2gTD5qh2XqaPzBnNX88N6HdyhQrNNRykvcS0QKvItV9u3vPEJr+X5Hhfb1JC0r0e1alL0iB09rY8+nmw==", + "optional": true, + "requires": { + "node-gyp-build": "~3.7.0" + } + }, "util-deprecate": { "version": "1.0.2", "resolved": "https://registry.npmjs.org/util-deprecate/-/util-deprecate-1.0.2.tgz", diff --git a/package.json b/package.json index 2f3d4fa..382acbf 100644 --- a/package.json +++ b/package.json @@ -53,5 +53,9 @@ "eslint --fix", "prettier --write" ] + }, + "optionalDependencies": { + "bufferutil": "^4.0.1", + "utf-8-validate": "^5.0.2" } } From e3921361a628034ea88e97ee164ac93055ad91d4 Mon Sep 17 00:00:00 2001 From: Chris Castle Date: Fri, 16 Oct 2020 17:41:12 -0400 Subject: [PATCH 17/18] Handle eslint error --- server/server.js | 1 + 1 file changed, 1 insertion(+) diff --git a/server/server.js b/server/server.js index 34cfa4e..51f8f3c 100644 --- a/server/server.js +++ b/server/server.js @@ -1,3 +1,4 @@ +/* eslint-disable-next-line global-require */ if (process.env.NODE_ENV !== 'production') require('dotenv').config() const express = require('express') From a629355f7b259841209857c09097375d533cea60 Mon Sep 17 00:00:00 2001 From: Clifton Campbell Date: Fri, 8 Jan 2021 12:55:25 -0800 Subject: [PATCH 18/18] update text to retrigger build --- server/server.js | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/server/server.js b/server/server.js index 51f8f3c..6e7cec6 100644 --- a/server/server.js +++ b/server/server.js @@ -189,7 +189,7 @@ app.post('/video2gif', upload.none(), ({ body }, res) => { res.send(body) }) .on('error', (err) => { - console.log(`An error happened: ${err.message}`) + console.log(`The following error occured: ${err.message}`) res.send(err) }) .save(`temp/${videoId}.gif`)