diff options
author | Chocobozzz <me@florianbigard.com> | 2021-02-16 10:19:09 +0100 |
---|---|---|
committer | Chocobozzz <chocobozzz@cpy.re> | 2021-02-16 10:36:44 +0100 |
commit | 1664bc60eb7aa3fa3792b6acff50f9bbabd3d877 (patch) | |
tree | 2434f642c2a7e895317bf791500dd181cd8f18c7 | |
parent | 374b725df52d941af1cf37cf211593340c05206c (diff) | |
download | PeerTube-1664bc60eb7aa3fa3792b6acff50f9bbabd3d877.tar.gz PeerTube-1664bc60eb7aa3fa3792b6acff50f9bbabd3d877.tar.zst PeerTube-1664bc60eb7aa3fa3792b6acff50f9bbabd3d877.zip |
Optimize remote image processing
-rw-r--r-- | server/helpers/image-utils.ts | 40 | ||||
-rw-r--r-- | server/lib/thumbnail.ts | 3 | ||||
-rw-r--r-- | server/tests/fixtures/thumbnail-big.jpg | bin | 0 -> 16286 bytes | |||
-rw-r--r-- | server/tests/fixtures/thumbnail.png | bin | 0 -> 4221 bytes | |||
-rw-r--r-- | server/tests/helpers/image.ts | 59 | ||||
-rw-r--r-- | server/tests/helpers/index.ts | 1 |
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 @@ | |||
1 | import { remove, rename } from 'fs-extra' | 1 | import { copy, readFile, remove, rename } from 'fs-extra' |
2 | import * as Jimp from 'jimp' | ||
2 | import { extname } from 'path' | 3 | import { extname } from 'path' |
3 | import { convertWebPToJPG, processGIF } from './ffmpeg-utils' | 4 | import { convertWebPToJPG, processGIF } from './ffmpeg-utils' |
4 | import { logger } from './logger' | 5 | import { logger } from './logger' |
5 | 6 | ||
6 | const Jimp = require('jimp') | ||
7 | |||
8 | async function processImage ( | 7 | async 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 | ||
40 | async function jimpProcessor (path: string, destination: string, newSize: { width: number, height: number }) { | 39 | async 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 | |||
68 | function 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 @@ | |||
1 | import chaiJsonSchema = require('chai-json-schema') | 1 | import { copy } from 'fs-extra' |
2 | import { copy, move } from 'fs-extra' | ||
3 | import { join } from 'path' | 2 | import { join } from 'path' |
4 | import { ThumbnailType } from '../../shared/models/videos/thumbnail.type' | 3 | import { ThumbnailType } from '../../shared/models/videos/thumbnail.type' |
5 | import { generateImageFromVideoFile } from '../helpers/ffmpeg-utils' | 4 | 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 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 | |||
3 | import 'mocha' | ||
4 | import { readFile, remove } from 'fs-extra' | ||
5 | import { join } from 'path' | ||
6 | import { processImage } from '../../../server/helpers/image-utils' | ||
7 | import { buildAbsoluteFixturePath, root } from '../../../shared/extra-utils' | ||
8 | import { expect } from 'chai' | ||
9 | |||
10 | async 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 | |||
23 | describe('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 @@ | |||
1 | import './image' | ||
1 | import './core-utils' | 2 | import './core-utils' |
2 | import './comment-model' | 3 | import './comment-model' |
3 | import './request' | 4 | import './request' |