From 56c9ee7b198cd5925bfd6bf4134acc2ba7b45328 Mon Sep 17 00:00:00 2001 From: SrGooglo Date: Thu, 24 Apr 2025 11:18:43 +0000 Subject: [PATCH] Replace image and video compression with unified optimize handler --- .../Transformation/handlers/img-compress.js | 43 ------------- .../Transformation/handlers/optimize.js | 63 +++++++++++++++++++ .../Transformation/handlers/video-compress.js | 6 -- .../server/classes/Transformation/index.ts | 3 +- packages/server/classes/Upload/index.ts | 28 +++++++-- .../files/routes/upload/chunk/post.js | 6 +- 6 files changed, 91 insertions(+), 58 deletions(-) delete mode 100644 packages/server/classes/Transformation/handlers/img-compress.js create mode 100644 packages/server/classes/Transformation/handlers/optimize.js delete mode 100644 packages/server/classes/Transformation/handlers/video-compress.js diff --git a/packages/server/classes/Transformation/handlers/img-compress.js b/packages/server/classes/Transformation/handlers/img-compress.js deleted file mode 100644 index eb1c68fa..00000000 --- a/packages/server/classes/Transformation/handlers/img-compress.js +++ /dev/null @@ -1,43 +0,0 @@ -import fs from "node:fs" -import path from "node:path" - -import Sharp from "sharp" - -const imageProcessingConf = { - sizeThreshold: 10 * 1024 * 1024, - imageQuality: 80, -} - -const imageTypeToConfig = { - png: { - compressionLevel: Math.floor(imageProcessingConf.imageQuality / 100), - }, - default: { - quality: imageProcessingConf.imageQuality, - }, -} - -export default async ({ filePath, workPath }) => { - const stat = await fs.promises.stat(file.filepath) - - // ignore if too small - if (stat.size < imageProcessingConf.sizeThreshold) { - return file - } - - let image = await Sharp(filePath) - - const { format } = await image.metadata() - - image = await image[format]( - imageTypeToConfig[format] ?? imageTypeToConfig.default, - ).withMetadata() - - filePath = path.resolve(workPath, `${path.basename(filePath)}_ff`) - - await image.toFile(outputFilepath) - - return { - filePath: filePath, - } -} diff --git a/packages/server/classes/Transformation/handlers/optimize.js b/packages/server/classes/Transformation/handlers/optimize.js new file mode 100644 index 00000000..7a20f3b1 --- /dev/null +++ b/packages/server/classes/Transformation/handlers/optimize.js @@ -0,0 +1,63 @@ +import fs from "node:fs" +import path from "node:path" +import { fileTypeFromBuffer } from "file-type" + +import readChunk from "@shared-utils/readChunk" + +import Sharp from "sharp" + +const thresholds = { + size: 10 * 1024 * 1024, +} + +const sharpConfigs = { + png: { + compressionLevel: 6, + //quality: 80, + }, + jpeg: { + quality: 80, + mozjpeg: true, + }, + default: { + quality: 80, + }, +} + +export default async ({ filePath, workPath, onProgress }) => { + const stat = await fs.promises.stat(filePath) + + const firstBuffer = await readChunk(filePath, { + length: 4100, + }) + const fileType = await fileTypeFromBuffer(firstBuffer) + + // first check if size over threshold + if (stat.size < thresholds.size) { + return { + outputFile: filePath, + } + } + + // get the type of the file mime + const type = fileType.mime.split("/")[0] + + switch (type) { + case "image": { + let image = Sharp(filePath) + + const metadata = await image.metadata() + const config = sharpConfigs[metadata.format] ?? sharpConfigs.default + + image = await image[metadata.format](config).withMetadata() + + filePath = path.resolve(workPath, `${path.basename(filePath)}_ff`) + + await image.toFile(filePath) + } + } + + return { + outputFile: filePath, + } +} diff --git a/packages/server/classes/Transformation/handlers/video-compress.js b/packages/server/classes/Transformation/handlers/video-compress.js deleted file mode 100644 index c95e28c6..00000000 --- a/packages/server/classes/Transformation/handlers/video-compress.js +++ /dev/null @@ -1,6 +0,0 @@ -export default async ({ filePath, workPath }) => { - // TODO: Implement video compression logic - return { - filePath: filePath, - } -} diff --git a/packages/server/classes/Transformation/index.ts b/packages/server/classes/Transformation/index.ts index 18087fd0..f99be3b5 100644 --- a/packages/server/classes/Transformation/index.ts +++ b/packages/server/classes/Transformation/index.ts @@ -1,8 +1,7 @@ const Handlers = { "a-dash": require("./handlers/a-dash").default, "mq-hls": require("./handlers/mq-hls").default, - "img-compress": require("./handlers/img-compress").default, - "video-compress": require("./handlers/video-compress").default, + optimize: require("./handlers/optimize").default, } export type TransformationPayloadType = { diff --git a/packages/server/classes/Upload/index.ts b/packages/server/classes/Upload/index.ts index 684894fd..b2d42eab 100644 --- a/packages/server/classes/Upload/index.ts +++ b/packages/server/classes/Upload/index.ts @@ -15,6 +15,7 @@ export type FileHandlePayload = { targetPath?: string // mostly provided by processed results //uploadId?: string transformations?: Array + useCompression?: boolean s3Provider?: string onProgress?: Function } @@ -29,11 +30,26 @@ export type S3UploadPayload = { export default class Upload { static fileHandle = async (payload: FileHandlePayload) => { - // process - const processed = await Upload.process(payload) + if (!payload.transformations) { + payload.transformations = [] + } - // overwrite filePath - payload.filePath = processed.filePath + // if compression is enabled and no transformations are provided, add basic transformations for images or videos + if ( + payload.useCompression === true && + payload.transformations.length === 0 + ) { + payload.transformations.push("optimize") + } + + // process file upload if transformations are provided + if (payload.transformations.length > 0) { + // process + const processed = await Upload.transform(payload) + + // overwrite filePath + payload.filePath = processed.filePath + } // upload const result = await Upload.toS3({ @@ -50,7 +66,7 @@ export default class Upload { return result } - static process = async (payload: FileHandlePayload) => { + static transform = async (payload: FileHandlePayload) => { if (Array.isArray(payload.transformations)) { for await (const transformation of payload.transformations) { const transformationResult = await Transformation.transform({ @@ -91,7 +107,7 @@ export default class Upload { let uploadPath = path.join(basePath, metadata["File-Hash"]) if (isDirectory) { - uploadPath = path.join(basePath, nanoid()) + uploadPath = path.join(basePath, global.nanoid()) } if (typeof onProgress === "function") { diff --git a/packages/server/services/files/routes/upload/chunk/post.js b/packages/server/services/files/routes/upload/chunk/post.js index a2f12c95..9fd08b53 100644 --- a/packages/server/services/files/routes/upload/chunk/post.js +++ b/packages/server/services/files/routes/upload/chunk/post.js @@ -86,10 +86,14 @@ export default { workPath: workPath, transformations: transformations, s3Provider: config.useProvider, + useCompression: config.useCompression, } // if has transformations, use background job - if (transformations && transformations.length > 0) { + if ( + (transformations && transformations.length > 0) || + config.useCompression + ) { const job = await global.queues.createJob( "file-process", payload,