diff options
-rw-r--r-- | client/src/app/shared/shared-main/account/actor-avatar-info.component.html | 3 | ||||
-rw-r--r-- | client/src/app/shared/shared-main/account/actor-avatar-info.component.ts | 10 | ||||
-rw-r--r-- | server/helpers/ffmpeg-utils.ts | 35 | ||||
-rw-r--r-- | server/helpers/image-utils.ts | 10 | ||||
-rw-r--r-- | server/initializers/constants.ts | 2 |
5 files changed, 52 insertions, 8 deletions
diff --git a/client/src/app/shared/shared-main/account/actor-avatar-info.component.html b/client/src/app/shared/shared-main/account/actor-avatar-info.component.html index d01b9ac7f..e63d8de2d 100644 --- a/client/src/app/shared/shared-main/account/actor-avatar-info.component.html +++ b/client/src/app/shared/shared-main/account/actor-avatar-info.component.html | |||
@@ -4,7 +4,8 @@ | |||
4 | <img [src]="actor.avatarUrl" alt="Avatar" /> | 4 | <img [src]="actor.avatarUrl" alt="Avatar" /> |
5 | 5 | ||
6 | <div class="actor-img-edit-container"> | 6 | <div class="actor-img-edit-container"> |
7 | <div class="actor-img-edit-button" [ngbTooltip]="'(extensions: '+ avatarExtensions +', '+ maxSizeText +': '+ maxAvatarSizeInBytes +')'" placement="right" container="body"> | 7 | <div class="actor-img-edit-button" [ngbTooltip]="avatarFormat" |
8 | placement="right" container="body"> | ||
8 | <my-global-icon iconName="edit"></my-global-icon> | 9 | <my-global-icon iconName="edit"></my-global-icon> |
9 | <label for="avatarfile" i18n>Change your avatar</label> | 10 | <label for="avatarfile" i18n>Change your avatar</label> |
10 | <input #avatarfileInput type="file" title=" " name="avatarfile" id="avatarfile" [accept]="avatarExtensions" (change)="onAvatarChange()"/> | 11 | <input #avatarfileInput type="file" title=" " name="avatarfile" id="avatarfile" [accept]="avatarExtensions" (change)="onAvatarChange()"/> |
diff --git a/client/src/app/shared/shared-main/account/actor-avatar-info.component.ts b/client/src/app/shared/shared-main/account/actor-avatar-info.component.ts index 5daa54cb5..de78a390e 100644 --- a/client/src/app/shared/shared-main/account/actor-avatar-info.component.ts +++ b/client/src/app/shared/shared-main/account/actor-avatar-info.component.ts | |||
@@ -17,16 +17,12 @@ export class ActorAvatarInfoComponent implements OnInit { | |||
17 | 17 | ||
18 | @Output() avatarChange = new EventEmitter<FormData>() | 18 | @Output() avatarChange = new EventEmitter<FormData>() |
19 | 19 | ||
20 | maxSizeText: string | ||
21 | |||
22 | private serverConfig: ServerConfig | 20 | private serverConfig: ServerConfig |
23 | 21 | ||
24 | constructor ( | 22 | constructor ( |
25 | private serverService: ServerService, | 23 | private serverService: ServerService, |
26 | private notifier: Notifier | 24 | private notifier: Notifier |
27 | ) { | 25 | ) { } |
28 | this.maxSizeText = $localize`max size` | ||
29 | } | ||
30 | 26 | ||
31 | ngOnInit (): void { | 27 | ngOnInit (): void { |
32 | this.serverConfig = this.serverService.getTmpConfig() | 28 | this.serverConfig = this.serverService.getTmpConfig() |
@@ -58,4 +54,8 @@ export class ActorAvatarInfoComponent implements OnInit { | |||
58 | get avatarExtensions () { | 54 | get avatarExtensions () { |
59 | return this.serverConfig.avatar.file.extensions.join(', ') | 55 | return this.serverConfig.avatar.file.extensions.join(', ') |
60 | } | 56 | } |
57 | |||
58 | get avatarFormat () { | ||
59 | return `${$localize`max size`}: 192*192px, ${this.maxAvatarSizeInBytes} ${$localize`extensions`}: ${this.avatarExtensions}` | ||
60 | } | ||
61 | } | 61 | } |
diff --git a/server/helpers/ffmpeg-utils.ts b/server/helpers/ffmpeg-utils.ts index c8d6969ff..66b9d2e44 100644 --- a/server/helpers/ffmpeg-utils.ts +++ b/server/helpers/ffmpeg-utils.ts | |||
@@ -355,6 +355,40 @@ function convertWebPToJPG (path: string, destination: string): Promise<void> { | |||
355 | }) | 355 | }) |
356 | } | 356 | } |
357 | 357 | ||
358 | function processGIF ( | ||
359 | path: string, | ||
360 | destination: string, | ||
361 | newSize: { width: number, height: number }, | ||
362 | keepOriginal = false | ||
363 | ): Promise<void> { | ||
364 | return new Promise<void>(async (res, rej) => { | ||
365 | if (path === destination) { | ||
366 | throw new Error('FFmpeg needs an input path different that the output path.') | ||
367 | } | ||
368 | |||
369 | logger.debug('Processing gif %s to %s.', path, destination) | ||
370 | |||
371 | try { | ||
372 | const command = ffmpeg(path) | ||
373 | .fps(20) | ||
374 | .size(`${newSize.width}x${newSize.height}`) | ||
375 | .output(destination) | ||
376 | |||
377 | command.on('error', (err, stdout, stderr) => { | ||
378 | logger.error('Error in ffmpeg gif resizing process.', { stdout, stderr }) | ||
379 | return rej(err) | ||
380 | }) | ||
381 | .on('end', async () => { | ||
382 | if (keepOriginal !== true) await remove(path) | ||
383 | res() | ||
384 | }) | ||
385 | .run() | ||
386 | } catch (err) { | ||
387 | return rej(err) | ||
388 | } | ||
389 | }) | ||
390 | } | ||
391 | |||
358 | function runLiveTranscoding (rtmpUrl: string, outPath: string, resolutions: number[], fps, deleteSegments: boolean) { | 392 | function runLiveTranscoding (rtmpUrl: string, outPath: string, resolutions: number[], fps, deleteSegments: boolean) { |
359 | const command = getFFmpeg(rtmpUrl) | 393 | const command = getFFmpeg(rtmpUrl) |
360 | command.inputOption('-fflags nobuffer') | 394 | command.inputOption('-fflags nobuffer') |
@@ -474,6 +508,7 @@ export { | |||
474 | getAudioStreamCodec, | 508 | getAudioStreamCodec, |
475 | runLiveMuxing, | 509 | runLiveMuxing, |
476 | convertWebPToJPG, | 510 | convertWebPToJPG, |
511 | processGIF, | ||
477 | getVideoStreamSize, | 512 | getVideoStreamSize, |
478 | getVideoFileResolution, | 513 | getVideoFileResolution, |
479 | getMetadataFromFile, | 514 | getMetadataFromFile, |
diff --git a/server/helpers/image-utils.ts b/server/helpers/image-utils.ts index 5f254a7aa..fdf06e848 100644 --- a/server/helpers/image-utils.ts +++ b/server/helpers/image-utils.ts | |||
@@ -1,5 +1,6 @@ | |||
1 | import { extname } from 'path' | ||
1 | import { remove, rename } from 'fs-extra' | 2 | import { remove, rename } from 'fs-extra' |
2 | import { convertWebPToJPG } from './ffmpeg-utils' | 3 | import { convertWebPToJPG, processGIF } from './ffmpeg-utils' |
3 | import { logger } from './logger' | 4 | import { logger } from './logger' |
4 | 5 | ||
5 | const Jimp = require('jimp') | 6 | const Jimp = require('jimp') |
@@ -10,6 +11,13 @@ async function processImage ( | |||
10 | newSize: { width: number, height: number }, | 11 | newSize: { width: number, height: number }, |
11 | keepOriginal = false | 12 | keepOriginal = false |
12 | ) { | 13 | ) { |
14 | const extension = extname(path) | ||
15 | |||
16 | // Use FFmpeg to process GIF | ||
17 | if (extension === '.gif') { | ||
18 | return processGIF(path, destination, newSize, keepOriginal) | ||
19 | } | ||
20 | |||
13 | if (path === destination) { | 21 | if (path === destination) { |
14 | throw new Error('Jimp needs an input path different that the output path.') | 22 | throw new Error('Jimp needs an input path different that the output path.') |
15 | } | 23 | } |
diff --git a/server/initializers/constants.ts b/server/initializers/constants.ts index 79e6a744c..5c6d06077 100644 --- a/server/initializers/constants.ts +++ b/server/initializers/constants.ts | |||
@@ -291,7 +291,7 @@ const CONSTRAINTS_FIELDS = { | |||
291 | PRIVATE_KEY: { min: 10, max: 5000 }, // Length | 291 | PRIVATE_KEY: { min: 10, max: 5000 }, // Length |
292 | URL: { min: 3, max: 2000 }, // Length | 292 | URL: { min: 3, max: 2000 }, // Length |
293 | AVATAR: { | 293 | AVATAR: { |
294 | EXTNAME: [ '.png', '.jpeg', '.jpg' ], | 294 | EXTNAME: [ '.png', '.jpeg', '.jpg', '.gif' ], |
295 | FILE_SIZE: { | 295 | FILE_SIZE: { |
296 | max: 2 * 1024 * 1024 // 2MB | 296 | max: 2 * 1024 * 1024 // 2MB |
297 | } | 297 | } |