aboutsummaryrefslogtreecommitdiffhomepage
diff options
context:
space:
mode:
authorChocobozzz <me@florianbigard.com>2021-02-16 10:19:09 +0100
committerChocobozzz <chocobozzz@cpy.re>2021-02-16 10:36:44 +0100
commit1664bc60eb7aa3fa3792b6acff50f9bbabd3d877 (patch)
tree2434f642c2a7e895317bf791500dd181cd8f18c7
parent374b725df52d941af1cf37cf211593340c05206c (diff)
downloadPeerTube-1664bc60eb7aa3fa3792b6acff50f9bbabd3d877.tar.gz
PeerTube-1664bc60eb7aa3fa3792b6acff50f9bbabd3d877.tar.zst
PeerTube-1664bc60eb7aa3fa3792b6acff50f9bbabd3d877.zip
Optimize remote image processing
-rw-r--r--server/helpers/image-utils.ts40
-rw-r--r--server/lib/thumbnail.ts3
-rw-r--r--server/tests/fixtures/thumbnail-big.jpgbin0 -> 16286 bytes
-rw-r--r--server/tests/fixtures/thumbnail.pngbin0 -> 4221 bytes
-rw-r--r--server/tests/helpers/image.ts59
-rw-r--r--server/tests/helpers/index.ts1
6 files changed, 94 insertions, 9 deletions
diff --git a/server/helpers/image-utils.ts b/server/helpers/image-utils.ts
index 3ebf07305..9285c12fc 100644
--- a/server/helpers/image-utils.ts
+++ b/server/helpers/image-utils.ts
@@ -1,10 +1,9 @@
1import { remove, rename } from 'fs-extra' 1import { copy, readFile, remove, rename } from 'fs-extra'
2import * as Jimp from 'jimp'
2import { extname } from 'path' 3import { extname } from 'path'
3import { convertWebPToJPG, processGIF } from './ffmpeg-utils' 4import { convertWebPToJPG, processGIF } from './ffmpeg-utils'
4import { logger } from './logger' 5import { logger } from './logger'
5 6
6const Jimp = require('jimp')
7
8async function processImage ( 7async function processImage (
9 path: string, 8 path: string,
10 destination: string, 9 destination: string,
@@ -23,7 +22,7 @@ async function processImage (
23 if (extension === '.gif') { 22 if (extension === '.gif') {
24 await processGIF(path, destination, newSize) 23 await processGIF(path, destination, newSize)
25 } else { 24 } else {
26 await jimpProcessor(path, destination, newSize) 25 await jimpProcessor(path, destination, newSize, extension)
27 } 26 }
28 27
29 if (keepOriginal !== true) await remove(path) 28 if (keepOriginal !== true) await remove(path)
@@ -37,11 +36,12 @@ export {
37 36
38// --------------------------------------------------------------------------- 37// ---------------------------------------------------------------------------
39 38
40async function jimpProcessor (path: string, destination: string, newSize: { width: number, height: number }) { 39async function jimpProcessor (path: string, destination: string, newSize: { width: number, height: number }, inputExt: string) {
41 let jimpInstance: any 40 let jimpInstance: Jimp
41 const inputBuffer = await readFile(path)
42 42
43 try { 43 try {
44 jimpInstance = await Jimp.read(path) 44 jimpInstance = await Jimp.read(inputBuffer)
45 } catch (err) { 45 } catch (err) {
46 logger.debug('Cannot read %s with jimp. Try to convert the image using ffmpeg first.', path, { err }) 46 logger.debug('Cannot read %s with jimp. Try to convert the image using ffmpeg first.', path, { err })
47 47
@@ -54,8 +54,34 @@ async function jimpProcessor (path: string, destination: string, newSize: { widt
54 54
55 await remove(destination) 55 await remove(destination)
56 56
57 // Optimization if the source file has the appropriate size
58 if (await skipProcessing({ jimpInstance, newSize, imageBytes: inputBuffer.byteLength, inputExt, outputExt: extname(destination) })) {
59 return copy(path, destination)
60 }
61
57 await jimpInstance 62 await jimpInstance
58 .resize(newSize.width, newSize.height) 63 .resize(newSize.width, newSize.height)
59 .quality(80) 64 .quality(80)
60 .writeAsync(destination) 65 .writeAsync(destination)
61} 66}
67
68function skipProcessing (options: {
69 jimpInstance: Jimp
70 newSize: { width: number, height: number }
71 imageBytes: number
72 inputExt: string
73 outputExt: string
74}) {
75 const { jimpInstance, newSize, imageBytes, inputExt, outputExt } = options
76 const { width, height } = newSize
77
78 if (jimpInstance.getWidth() > width || jimpInstance.getHeight() > height) return false
79 if (inputExt !== outputExt) return false
80
81 const kB = 1000
82
83 if (height >= 1000) return imageBytes <= 200 * kB
84 if (height >= 500) return imageBytes <= 100 * kB
85
86 return imageBytes <= 15 * kB
87}
diff --git a/server/lib/thumbnail.ts b/server/lib/thumbnail.ts
index 5d0c9f742..4bad8d6ca 100644
--- a/server/lib/thumbnail.ts
+++ b/server/lib/thumbnail.ts
@@ -1,5 +1,4 @@
1import chaiJsonSchema = require('chai-json-schema') 1import { copy } from 'fs-extra'
2import { copy, move } from 'fs-extra'
3import { join } from 'path' 2import { join } from 'path'
4import { ThumbnailType } from '../../shared/models/videos/thumbnail.type' 3import { ThumbnailType } from '../../shared/models/videos/thumbnail.type'
5import { generateImageFromVideoFile } from '../helpers/ffmpeg-utils' 4import { 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
index 000000000..537720d24
--- /dev/null
+++ b/server/tests/fixtures/thumbnail-big.jpg
Binary files differ
diff --git a/server/tests/fixtures/thumbnail.png b/server/tests/fixtures/thumbnail.png
new file mode 100644
index 000000000..b331aba3b
--- /dev/null
+++ b/server/tests/fixtures/thumbnail.png
Binary files differ
diff --git a/server/tests/helpers/image.ts b/server/tests/helpers/image.ts
new file mode 100644
index 000000000..54911697f
--- /dev/null
+++ b/server/tests/helpers/image.ts
@@ -0,0 +1,59 @@
1/* eslint-disable @typescript-eslint/no-unused-expressions,@typescript-eslint/require-await */
2
3import 'mocha'
4import { readFile, remove } from 'fs-extra'
5import { join } from 'path'
6import { processImage } from '../../../server/helpers/image-utils'
7import { buildAbsoluteFixturePath, root } from '../../../shared/extra-utils'
8import { expect } from 'chai'
9
10async function checkBuffers (path1: string, path2: string, equals: boolean) {
11 const [ buf1, buf2 ] = await Promise.all([
12 readFile(path1),
13 readFile(path2)
14 ])
15
16 if (equals) {
17 expect(buf1.equals(buf2)).to.be.true
18 } else {
19 expect(buf1.equals(buf2)).to.be.false
20 }
21}
22
23describe('Image helpers', function () {
24 const imageDestDir = join(root(), 'test-images')
25 const imageDest = join(imageDestDir, 'test.jpg')
26 const thumbnailSize = { width: 223, height: 122 }
27
28 it('Should skip processing if the source image is okay', async function () {
29 const input = buildAbsoluteFixturePath('thumbnail.jpg')
30 await processImage(input, imageDest, thumbnailSize, true)
31
32 await checkBuffers(input, imageDest, true)
33 })
34
35 it('Should not skip processing if the source image does not have the appropriate extension', async function () {
36 const input = buildAbsoluteFixturePath('thumbnail.png')
37 await processImage(input, imageDest, thumbnailSize, true)
38
39 await checkBuffers(input, imageDest, false)
40 })
41
42 it('Should not skip processing if the source image does not have the appropriate size', async function () {
43 const input = buildAbsoluteFixturePath('preview.jpg')
44 await processImage(input, imageDest, thumbnailSize, true)
45
46 await checkBuffers(input, imageDest, false)
47 })
48
49 it('Should not skip processing if the source image does not have the appropriate size', async function () {
50 const input = buildAbsoluteFixturePath('thumbnail-big.jpg')
51 await processImage(input, imageDest, thumbnailSize, true)
52
53 await checkBuffers(input, imageDest, false)
54 })
55
56 after(async function () {
57 await remove(imageDest)
58 })
59})
diff --git a/server/tests/helpers/index.ts b/server/tests/helpers/index.ts
index 03b971770..66db93c99 100644
--- a/server/tests/helpers/index.ts
+++ b/server/tests/helpers/index.ts
@@ -1,3 +1,4 @@
1import './image'
1import './core-utils' 2import './core-utils'
2import './comment-model' 3import './comment-model'
3import './request' 4import './request'