From d55735c5ebe8a9124794e936e7e926862aaa8771 Mon Sep 17 00:00:00 2001 From: Mark Date: Wed, 29 Dec 2021 15:18:32 +0000 Subject: [PATCH 1/2] fix: wipe the tmp directory each time a thumbnail generation is called --- backend/src/lib/thumbnail-generation/make-thumbnail.js | 9 +++++++++ 1 file changed, 9 insertions(+) diff --git a/backend/src/lib/thumbnail-generation/make-thumbnail.js b/backend/src/lib/thumbnail-generation/make-thumbnail.js index 21d9113..e7a914c 100644 --- a/backend/src/lib/thumbnail-generation/make-thumbnail.js +++ b/backend/src/lib/thumbnail-generation/make-thumbnail.js @@ -1,3 +1,5 @@ +const fs = require("fs"); +const path = require("path"); const doesFileExist = require("./does-file-exist"); const downloadVideoToTmpDirectory = require("./download-video-to-tmp-directory"); const generateThumbnailsFromVideo = require("./generate-thumbnails-from-video"); @@ -6,6 +8,7 @@ const generateThumbnailsFromVideo = require("./generate-thumbnails-from-video"); const THUMBNAILS_TO_CREATE = 3; module.exports = async event => { + await wipeTmpDirectory(); const { videoFileName, triggerBucketName } = extractParams(event); const tmpVideoPath = await downloadVideoToTmpDirectory(triggerBucketName, videoFileName); @@ -20,3 +23,9 @@ const extractParams = event => { return { videoFileName, triggerBucketName }; }; + +const wipeTmpDirectory = async () => { + const files = await fs.promises.readdir("/tmp/"); + const filePaths = files.map(file => path.join("/tmp/", file)); + await Promise.all(filePaths.map(file => fs.promises.unlink(file))); +}; From e2e3df13d33888fc6eb017c1681361357a622c61 Mon Sep 17 00:00:00 2001 From: Mark Date: Wed, 29 Dec 2021 15:18:50 +0000 Subject: [PATCH 2/2] refactor: thumbnail generation code --- .../generate-thumbnails-from-video.js | 87 +++++++++++-------- 1 file changed, 51 insertions(+), 36 deletions(-) diff --git a/backend/src/lib/thumbnail-generation/generate-thumbnails-from-video.js b/backend/src/lib/thumbnail-generation/generate-thumbnails-from-video.js index d8685b3..22363bb 100644 --- a/backend/src/lib/thumbnail-generation/generate-thumbnails-from-video.js +++ b/backend/src/lib/thumbnail-generation/generate-thumbnails-from-video.js @@ -1,6 +1,7 @@ +/* eslint-disable no-restricted-syntax */ /* eslint-disable no-await-in-loop */ const fs = require("fs"); -const { spawnSync, exec } = require("child_process"); +const { spawnSync } = require("child_process"); const S3Factory = require("../factories/S3Factory"); const doesFileExist = require("./does-file-exist"); const generateTmpFilePath = require("./generate-tmp-file-path"); @@ -8,21 +9,50 @@ const generateTmpFilePath = require("./generate-tmp-file-path"); const ffprobePath = "/opt/bin/ffprobe"; const ffmpegPath = "/opt/bin/ffmpeg"; -module.exports = async (tmpVideoPath, numberOfThumbnailsToCreate, videoFileName) => { - const durationInSeconds = getVideoDuration(tmpVideoPath); - const randomTimes = generateRandomTimes(durationInSeconds, numberOfThumbnailsToCreate); +module.exports = async (tmpVideoPath, numberOfThumbnails, videoFileName) => { + const randomTimes = generateRandomTimes(tmpVideoPath, numberOfThumbnails); - for (let i = 0; i < randomTimes.length; i += 1) { - const time = randomTimes[i]; - const nameOfImageToCreate = generateNameOfImageToUpload(videoFileName, i); - const tmpThumbnailPath = createImageInTmpDirectory(tmpVideoPath, time); + for (const [index, randomTime] of Object.entries(randomTimes)) { + const tmpThumbnailPath = await createImageFromVideo(tmpVideoPath, randomTime); if (doesFileExist(tmpThumbnailPath)) { + const nameOfImageToCreate = generateNameOfImageToUpload(videoFileName, index); await uploadFileToS3(tmpThumbnailPath, nameOfImageToCreate); } } }; +const generateRandomTimes = (tmpVideoPath, numberOfTimesToGenerate) => { + const timesInSeconds = []; + const videoDuration = getVideoDuration(tmpVideoPath); + + for (let x = 0; x < numberOfTimesToGenerate; x += 1) { + const randomNum = getRandomNumberNotInExistingList(timesInSeconds, videoDuration); + + if (randomNum >= 0) { + timesInSeconds.push(randomNum); + } + } + + return timesInSeconds; +}; + +const getRandomNumberNotInExistingList = (existingList, maxValueOfNumber) => { + for (let attemptNumber = 0; attemptNumber < 3; attemptNumber += 1) { + const randomNum = getRandomNumber(maxValueOfNumber); + + if (!existingList.includes(randomNum)) { + return randomNum; + } + } + + return -1; +}; + +const getRandomNumber = upperLimit => { + return Math.floor(Math.random() * upperLimit); +}; + const getVideoDuration = tmpVideoPath => { const ffprobe = spawnSync(ffprobePath, [ "-v", @@ -37,35 +67,10 @@ const getVideoDuration = tmpVideoPath => { return Math.floor(ffprobe.stdout.toString()); }; -const generateRandomTimes = (videoDuration, numberOfTimesToGenerate) => { - const timesInSeconds = []; - - const getRandomNumber = () => { - return Math.floor(Math.random() * videoDuration); - }; - - for (let x = 0; x < numberOfTimesToGenerate; x += 1) { - for (let attemptNumber = 0; attemptNumber < 3; attemptNumber += 1) { - const randomNum = getRandomNumber(); - if (!timesInSeconds.includes(randomNum)) { - timesInSeconds.push(randomNum); - break; - } - } - } - - return timesInSeconds; -}; - -const createImageInTmpDirectory = (tmpVideoPath, targetSecond) => { +const createImageFromVideo = (tmpVideoPath, targetSecond) => { const tmpThumbnailPath = generateThumbnailPath(targetSecond); - spawnSync(ffmpegPath, [ - "-ss", targetSecond, - "-i", tmpVideoPath, - "-vf", "thumbnail,scale=-1:140", // preserve aspect ratio whilst forcing the height to be 140px - "-vframes", 1, - tmpThumbnailPath - ]); + const ffmpegParams = createFfmpegParams(tmpVideoPath, tmpThumbnailPath, targetSecond); + spawnSync(ffmpegPath, ffmpegParams); return tmpThumbnailPath; }; @@ -78,6 +83,16 @@ const generateThumbnailPath = targetSecond => { return thumbnailPathWithNumber; }; +const createFfmpegParams = (tmpVideoPath, tmpThumbnailPath, targetSecond) => { + return [ + "-ss", targetSecond, + "-i", tmpVideoPath, + "-vf", "thumbnail,scale=80:140", + "-vframes", 1, + tmpThumbnailPath + ]; +}; + const generateNameOfImageToUpload = (videoFileName, i) => { const strippedExtension = videoFileName.replace(".mp4", ""); return `${strippedExtension}-${i}.jpg`;