]> git.immae.eu Git - github/Chocobozzz/PeerTube.git/commitdiff
Optimize remote image processing
authorChocobozzz <me@florianbigard.com>
Tue, 16 Feb 2021 09:19:09 +0000 (10:19 +0100)
committerChocobozzz <chocobozzz@cpy.re>
Tue, 16 Feb 2021 09:36:44 +0000 (10:36 +0100)
server/helpers/image-utils.ts
server/lib/thumbnail.ts
server/tests/fixtures/thumbnail-big.jpg [new file with mode: 0644]
server/tests/fixtures/thumbnail.png [new file with mode: 0644]
server/tests/helpers/image.ts [new file with mode: 0644]
server/tests/helpers/index.ts

index 3ebf073058bd1f85e00abc49d5a59748ed4c81a3..9285c12fc59aa383de2b4135b1933347ff67cf59 100644 (file)
@@ -1,10 +1,9 @@
-import { remove, rename } from 'fs-extra'
+import { copy, readFile, remove, rename } from 'fs-extra'
+import * as Jimp from 'jimp'
 import { extname } from 'path'
 import { convertWebPToJPG, processGIF } from './ffmpeg-utils'
 import { logger } from './logger'
 
-const Jimp = require('jimp')
-
 async function processImage (
   path: string,
   destination: string,
@@ -23,7 +22,7 @@ async function processImage (
   if (extension === '.gif') {
     await processGIF(path, destination, newSize)
   } else {
-    await jimpProcessor(path, destination, newSize)
+    await jimpProcessor(path, destination, newSize, extension)
   }
 
   if (keepOriginal !== true) await remove(path)
@@ -37,11 +36,12 @@ export {
 
 // ---------------------------------------------------------------------------
 
-async function jimpProcessor (path: string, destination: string, newSize: { width: number, height: number }) {
-  let jimpInstance: any
+async function jimpProcessor (path: string, destination: string, newSize: { width: number, height: number }, inputExt: string) {
+  let jimpInstance: Jimp
+  const inputBuffer = await readFile(path)
 
   try {
-    jimpInstance = await Jimp.read(path)
+    jimpInstance = await Jimp.read(inputBuffer)
   } catch (err) {
     logger.debug('Cannot read %s with jimp. Try to convert the image using ffmpeg first.', path, { err })
 
@@ -54,8 +54,34 @@ async function jimpProcessor (path: string, destination: string, newSize: { widt
 
   await remove(destination)
 
+  // Optimization if the source file has the appropriate size
+  if (await skipProcessing({ jimpInstance, newSize, imageBytes: inputBuffer.byteLength, inputExt, outputExt: extname(destination) })) {
+    return copy(path, destination)
+  }
+
   await jimpInstance
     .resize(newSize.width, newSize.height)
     .quality(80)
     .writeAsync(destination)
 }
+
+function skipProcessing (options: {
+  jimpInstance: Jimp
+  newSize: { width: number, height: number }
+  imageBytes: number
+  inputExt: string
+  outputExt: string
+}) {
+  const { jimpInstance, newSize, imageBytes, inputExt, outputExt } = options
+  const { width, height } = newSize
+
+  if (jimpInstance.getWidth() > width || jimpInstance.getHeight() > height) return false
+  if (inputExt !== outputExt) return false
+
+  const kB = 1000
+
+  if (height >= 1000) return imageBytes <= 200 * kB
+  if (height >= 500) return imageBytes <= 100 * kB
+
+  return imageBytes <= 15 * kB
+}
index 5d0c9f7424536040695876c7d99cd79aa8245a56..4bad8d6ca84e8a027bbf15e00a62188c4420ff35 100644 (file)
@@ -1,5 +1,4 @@
-import chaiJsonSchema = require('chai-json-schema')
-import { copy, move } from 'fs-extra'
+import { copy } from 'fs-extra'
 import { join } from 'path'
 import { ThumbnailType } from '../../shared/models/videos/thumbnail.type'
 import { generateImageFromVideoFile } from '../helpers/ffmpeg-utils'
diff --git a/server/tests/fixtures/thumbnail-big.jpg b/server/tests/fixtures/thumbnail-big.jpg
new file mode 100644 (file)
index 0000000..537720d
Binary files /dev/null and b/server/tests/fixtures/thumbnail-big.jpg differ
diff --git a/server/tests/fixtures/thumbnail.png b/server/tests/fixtures/thumbnail.png
new file mode 100644 (file)
index 0000000..b331aba
Binary files /dev/null and b/server/tests/fixtures/thumbnail.png differ
diff --git a/server/tests/helpers/image.ts b/server/tests/helpers/image.ts
new file mode 100644 (file)
index 0000000..5491169
--- /dev/null
@@ -0,0 +1,59 @@
+/* eslint-disable @typescript-eslint/no-unused-expressions,@typescript-eslint/require-await */
+
+import 'mocha'
+import { readFile, remove } from 'fs-extra'
+import { join } from 'path'
+import { processImage } from '../../../server/helpers/image-utils'
+import { buildAbsoluteFixturePath, root } from '../../../shared/extra-utils'
+import { expect } from 'chai'
+
+async function checkBuffers (path1: string, path2: string, equals: boolean) {
+  const [ buf1, buf2 ] = await Promise.all([
+    readFile(path1),
+    readFile(path2)
+  ])
+
+  if (equals) {
+    expect(buf1.equals(buf2)).to.be.true
+  } else {
+    expect(buf1.equals(buf2)).to.be.false
+  }
+}
+
+describe('Image helpers', function () {
+  const imageDestDir = join(root(), 'test-images')
+  const imageDest = join(imageDestDir, 'test.jpg')
+  const thumbnailSize = { width: 223, height: 122 }
+
+  it('Should skip processing if the source image is okay', async function () {
+    const input = buildAbsoluteFixturePath('thumbnail.jpg')
+    await processImage(input, imageDest, thumbnailSize, true)
+
+    await checkBuffers(input, imageDest, true)
+  })
+
+  it('Should not skip processing if the source image does not have the appropriate extension', async function () {
+    const input = buildAbsoluteFixturePath('thumbnail.png')
+    await processImage(input, imageDest, thumbnailSize, true)
+
+    await checkBuffers(input, imageDest, false)
+  })
+
+  it('Should not skip processing if the source image does not have the appropriate size', async function () {
+    const input = buildAbsoluteFixturePath('preview.jpg')
+    await processImage(input, imageDest, thumbnailSize, true)
+
+    await checkBuffers(input, imageDest, false)
+  })
+
+  it('Should not skip processing if the source image does not have the appropriate size', async function () {
+    const input = buildAbsoluteFixturePath('thumbnail-big.jpg')
+    await processImage(input, imageDest, thumbnailSize, true)
+
+    await checkBuffers(input, imageDest, false)
+  })
+
+  after(async function () {
+    await remove(imageDest)
+  })
+})
index 03b9717707e7b011438e39d21b52097508faa869..66db93c99d4cf12f25119597654613fe2048bed7 100644 (file)
@@ -1,3 +1,4 @@
+import './image'
 import './core-utils'
 import './comment-model'
 import './request'