From cbee86dfb65de86849b7549b1c0c318585cb3038 Mon Sep 17 00:00:00 2001 From: SrGooglo Date: Fri, 23 Jun 2023 21:27:36 +0000 Subject: [PATCH] improve `useCompression` service --- .../services/useCompression/image/index.js | 59 +++++++++++++ .../src/services/useCompression/index.js | 82 +------------------ .../services/useCompression/video/index.js | 45 ++++++++++ 3 files changed, 106 insertions(+), 80 deletions(-) create mode 100644 packages/file_server/src/services/useCompression/image/index.js create mode 100644 packages/file_server/src/services/useCompression/video/index.js diff --git a/packages/file_server/src/services/useCompression/image/index.js b/packages/file_server/src/services/useCompression/image/index.js new file mode 100644 index 00000000..67005958 --- /dev/null +++ b/packages/file_server/src/services/useCompression/image/index.js @@ -0,0 +1,59 @@ +import fs from "node:fs" +import path from "node:path" +import Sharp from "sharp" + +const imageProcessingConf = { + // TODO: Get image sizeThreshold from DB + sizeThreshold: 10 * 1024 * 1024, + // TODO: Get image quality from DB + imageQuality: 80, +} + +const imageTypeToConfig = { + png: { + compressionLevel: Math.floor(imageProcessingConf.imageQuality / 100), + }, + default: { + quality: imageProcessingConf.imageQuality + } +} + +/** + * Processes an image file and transforms it if it's above a certain size threshold. + * + * @async + * @function + * @param {Object} file - The file to be processed. + * @param {string} file.filepath - The path of the file to be processed. + * @param {string} file.hash - The hash of the file to be processed. + * @param {string} file.cachePath - The cache path of the file to be processed. + * @throws {Error} If the file parameter is not provided. + * @return {Object} The processed file object. + */ +async function processImage(file) { + if (!file) { + throw new Error("file is required") + } + + const stat = await fs.promises.stat(file.filepath) + + if (stat.size < imageProcessingConf.sizeThreshold) { + return file + } + + let image = await Sharp(file.filepath) + + const { format } = await image.metadata() + + image = await image[format](imageTypeToConfig[format] ?? imageTypeToConfig.default).withMetadata() + + const outputFilepath = path.resolve(file.cachePath, `${file.hash}_transformed.${format}`) + + await transformResult.toFile(outputFilepath) + + file.filepath = outputFilepath + + return file +} + +export default processImage \ No newline at end of file diff --git a/packages/file_server/src/services/useCompression/index.js b/packages/file_server/src/services/useCompression/index.js index 479c0ad9..bebdf6b3 100644 --- a/packages/file_server/src/services/useCompression/index.js +++ b/packages/file_server/src/services/useCompression/index.js @@ -1,10 +1,8 @@ import fs from "node:fs" -import Jimp from "jimp" import mimetypes from "mime-types" -import videoTranscode from "@services/videoTranscode" - -const cachePath = global.cache.constructor.cachePath +import processVideo from "./video" +import processImage from "./image" const fileTransformer = { "video/avi": processVideo, @@ -20,82 +18,6 @@ const fileTransformer = { "image/jfif": processImage, } -const maximuns = { - imageResolution: { - width: 3840, - height: 2160, - }, - imageQuality: 80, -} - -async function processVideo(file) { - if (!file) { - throw new Error("file is required") - } - - const result = await videoTranscode(file.filepath, file.cachePath, { - videoCodec: "libx264", - format: "mp4", - audioBitrate: 128, - videoBitrate: 1024, - }) - - file.filepath = result.filepath - file.filename = result.filename - - return file -} - -async function processImage(file) { - if (!file) { - throw new Error("file is required") - } - - const { width, height } = await new Promise((resolve, reject) => { - Jimp.read(file.filepath) - .then((image) => { - resolve({ - width: image.bitmap.width, - height: image.bitmap.height, - }) - }) - .catch((err) => { - reject(err) - }) - }) - - if (width > maximuns.imageResolution.width || height > maximuns.imageResolution.height) { - await new Promise((resolve, reject) => { - // calculate max resolution respecting aspect ratio - const resizedResolution = { - width: maximuns.imageResolution.width, - height: maximuns.imageResolution.height, - } - - if (width > height) { - resizedResolution.height = Math.floor((height / width) * maximuns.imageResolution.width) - } - - if (height > width) { - resizedResolution.width = Math.floor((width / height) * maximuns.imageResolution.height) - } - - Jimp.read(file.filepath) - .then((image) => { - image - .resize(resizedResolution.width, resizedResolution.height) - .quality(maximuns.imageQuality) - .write(file.filepath, resolve) - }) - .catch((err) => { - reject(err) - }) - }) - } - - return file -} - export default async (file) => { if (!file) { throw new Error("file is required") diff --git a/packages/file_server/src/services/useCompression/video/index.js b/packages/file_server/src/services/useCompression/video/index.js new file mode 100644 index 00000000..9d4d0ac0 --- /dev/null +++ b/packages/file_server/src/services/useCompression/video/index.js @@ -0,0 +1,45 @@ +import videoTranscode from "@services/videoTranscode" + +/** + * Processes a video file based on the specified options. + * + * @async + * @param {Object} file - The video file to process. + * @param {Object} [options={}] - The options object to use for processing. + * @param {string} [options.videoCodec="libx264"] - The video codec to use. + * @param {string} [options.format="mp4"] - The format to use. + * @param {number} [options.audioBitrate=128] - The audio bitrate to use. + * @param {number} [options.videoBitrate=1024] - The video bitrate to use. + * @throws {Error} Throws an error if file parameter is not provided. + * @return {Object} The processed video file object. + */ +async function processVideo( + file, + options = {} +) { + if (!file) { + throw new Error("file is required") + } + + // TODO: Get values from db + const { + videoCodec = "libx264", + format = "mp4", + audioBitrate = 128, + videoBitrate = 1024, + } = options + + const result = await videoTranscode(file.filepath, file.cachePath, { + videoCodec, + format, + audioBitrate, + videoBitrate, + }) + + file.filepath = result.filepath + file.filename = result.filename + + return file +} + +export default processVideo \ No newline at end of file