Store animated images as lossless webp

This commit is contained in:
rubikscraft
2022-12-28 16:49:45 +01:00
committed by Caramel
parent d24e4a7723
commit 10dfb3a579
4 changed files with 45 additions and 30 deletions

View File

@@ -2,21 +2,28 @@ import { Injectable } from '@nestjs/common';
import ms from 'ms'; import ms from 'ms';
import { ImageRequestParams } from 'picsur-shared/dist/dto/api/image.dto'; import { ImageRequestParams } from 'picsur-shared/dist/dto/api/image.dto';
import { import {
FileType, FileType,
SupportedFileTypeCategory, SupportedFileTypeCategory,
} from 'picsur-shared/dist/dto/mimes.dto'; } from 'picsur-shared/dist/dto/mimes.dto';
import { SysPreference } from 'picsur-shared/dist/dto/sys-preferences.enum'; import { SysPreference } from 'picsur-shared/dist/dto/sys-preferences.enum';
import { import {
AsyncFailable, AsyncFailable,
Fail, Fail,
FT, FT,
HasFailed, HasFailed,
} from 'picsur-shared/dist/types/failable'; } from 'picsur-shared/dist/types/failable';
import { SharpOptions } from 'sharp'; import { SharpOptions } from 'sharp';
import { SysPreferenceDbService } from '../../collections/preference-db/sys-preference-db.service.js'; import { SysPreferenceDbService } from '../../collections/preference-db/sys-preference-db.service.js';
import { SharpWrapper } from '../../workers/sharp.wrapper.js'; import { SharpWrapper } from '../../workers/sharp.wrapper.js';
import { ImageResult } from './imageresult.js'; import { ImageResult } from './imageresult.js';
interface InternalConvertOptions {
lossless?: boolean;
effort?: number;
}
export type ConvertOptions = ImageRequestParams & InternalConvertOptions;
@Injectable() @Injectable()
export class ImageConverterService { export class ImageConverterService {
constructor(private readonly sysPref: SysPreferenceDbService) {} constructor(private readonly sysPref: SysPreferenceDbService) {}
@@ -25,7 +32,7 @@ export class ImageConverterService {
image: Buffer, image: Buffer,
sourceFiletype: FileType, sourceFiletype: FileType,
targetFiletype: FileType, targetFiletype: FileType,
options: ImageRequestParams, options: ConvertOptions,
): AsyncFailable<ImageResult> { ): AsyncFailable<ImageResult> {
if ( if (
sourceFiletype.identifier === targetFiletype.identifier && sourceFiletype.identifier === targetFiletype.identifier &&
@@ -37,23 +44,22 @@ export class ImageConverterService {
}; };
} }
if (targetFiletype.category === SupportedFileTypeCategory.Image) { if (
return this.convertStill(image, sourceFiletype, targetFiletype, options); targetFiletype.category === SupportedFileTypeCategory.Image ||
} else if (
targetFiletype.category === SupportedFileTypeCategory.Animation targetFiletype.category === SupportedFileTypeCategory.Animation
) { ) {
return this.convertStill(image, sourceFiletype, targetFiletype, options); return this.convertImage(image, sourceFiletype, targetFiletype, options);
//return this.convertAnimation(image, targetmime, options); //return this.convertAnimation(image, targetmime, options);
} else { } else {
return Fail(FT.SysValidation, 'Unsupported mime type'); return Fail(FT.SysValidation, 'Unsupported mime type');
} }
} }
private async convertStill( private async convertImage(
image: Buffer, image: Buffer,
sourceFiletype: FileType, sourceFiletype: FileType,
targetFiletype: FileType, targetFiletype: FileType,
options: ImageRequestParams, options: ConvertOptions,
): AsyncFailable<ImageResult> { ): AsyncFailable<ImageResult> {
const [memLimit, timeLimit] = await Promise.all([ const [memLimit, timeLimit] = await Promise.all([
this.sysPref.getNumberPreference(SysPreference.ConversionMemoryLimit), this.sysPref.getNumberPreference(SysPreference.ConversionMemoryLimit),

View File

@@ -1,15 +1,15 @@
import { Injectable } from '@nestjs/common'; import { Injectable } from '@nestjs/common';
import { import {
FileType, FileType,
ImageFileType, ImageFileType,
SupportedFileTypeCategory, SupportedFileTypeCategory,
} from 'picsur-shared/dist/dto/mimes.dto'; } from 'picsur-shared/dist/dto/mimes.dto';
import { import {
AsyncFailable, AsyncFailable,
Fail, Fail,
FT, FT,
HasFailed, HasFailed,
} from 'picsur-shared/dist/types/failable'; } from 'picsur-shared/dist/types/failable';
import { ParseFileType } from 'picsur-shared/dist/util/parse-mime'; import { ParseFileType } from 'picsur-shared/dist/util/parse-mime';
import { ImageConverterService } from './image-converter.service.js'; import { ImageConverterService } from './image-converter.service.js';
@@ -46,10 +46,12 @@ export class ImageProcessorService {
image: Buffer, image: Buffer,
filetype: FileType, filetype: FileType,
): AsyncFailable<ImageResult> { ): AsyncFailable<ImageResult> {
// Webps and gifs are stored as is for now const outputFileType = ParseFileType(AnimFileType.WEBP);
return { if (HasFailed(outputFileType)) return outputFileType;
image: image,
filetype: filetype.identifier, return this.imageConverter.convert(image, filetype, outputFileType, {
}; lossless: true,
effort: 0,
});
} }
} }

View File

@@ -26,6 +26,10 @@ export type SharpWorkerOperation =
export interface SharpWorkerFinishOptions { export interface SharpWorkerFinishOptions {
quality?: number; quality?: number;
// Only for internal use
lossless?: boolean;
effort?: number;
} }
// Messages // Messages

View File

@@ -2,10 +2,11 @@ import { BMPdecode, BMPencode } from 'bmp-img';
import { import {
AnimFileType, AnimFileType,
FileType, FileType,
ImageFileType, ImageFileType
} from 'picsur-shared/dist/dto/mimes.dto'; } from 'picsur-shared/dist/dto/mimes.dto';
import { QOIdecode, QOIencode } from 'qoi-img'; import { QOIdecode, QOIencode } from 'qoi-img';
import sharp, { Sharp, SharpOptions } from 'sharp'; import sharp, { Sharp, SharpOptions } from 'sharp';
import { SharpWorkerFinishOptions } from './sharp.message';
export interface SharpResult { export interface SharpResult {
data: Buffer; data: Buffer;
@@ -72,9 +73,7 @@ function qoiSharpIn(image: Buffer, options?: SharpOptions) {
export async function UniversalSharpOut( export async function UniversalSharpOut(
image: Sharp, image: Sharp,
filetype: FileType, filetype: FileType,
options?: { options?: SharpWorkerFinishOptions,
quality?: number;
},
): Promise<SharpResult> { ): Promise<SharpResult> {
let result: SharpResult | undefined; let result: SharpResult | undefined;
@@ -103,7 +102,11 @@ export async function UniversalSharpOut(
case ImageFileType.WEBP: case ImageFileType.WEBP:
case AnimFileType.WEBP: case AnimFileType.WEBP:
result = await image result = await image
.webp({ quality: options?.quality }) .webp({
quality: options?.quality,
lossless: options?.lossless,
effort: options?.effort,
})
.toBuffer({ resolveWithObject: true }); .toBuffer({ resolveWithObject: true });
break; break;
case AnimFileType.GIF: case AnimFileType.GIF: