mirror of
https://github.com/CaramelFur/Picsur.git
synced 2025-10-25 23:46:06 +02:00
Store animated images as lossless webp
This commit is contained in:
@@ -2,21 +2,28 @@ import { Injectable } from '@nestjs/common';
|
||||
import ms from 'ms';
|
||||
import { ImageRequestParams } from 'picsur-shared/dist/dto/api/image.dto';
|
||||
import {
|
||||
FileType,
|
||||
SupportedFileTypeCategory,
|
||||
FileType,
|
||||
SupportedFileTypeCategory,
|
||||
} from 'picsur-shared/dist/dto/mimes.dto';
|
||||
import { SysPreference } from 'picsur-shared/dist/dto/sys-preferences.enum';
|
||||
import {
|
||||
AsyncFailable,
|
||||
Fail,
|
||||
FT,
|
||||
HasFailed,
|
||||
AsyncFailable,
|
||||
Fail,
|
||||
FT,
|
||||
HasFailed,
|
||||
} from 'picsur-shared/dist/types/failable';
|
||||
import { SharpOptions } from 'sharp';
|
||||
import { SysPreferenceDbService } from '../../collections/preference-db/sys-preference-db.service.js';
|
||||
import { SharpWrapper } from '../../workers/sharp.wrapper.js';
|
||||
import { ImageResult } from './imageresult.js';
|
||||
|
||||
interface InternalConvertOptions {
|
||||
lossless?: boolean;
|
||||
effort?: number;
|
||||
}
|
||||
|
||||
export type ConvertOptions = ImageRequestParams & InternalConvertOptions;
|
||||
|
||||
@Injectable()
|
||||
export class ImageConverterService {
|
||||
constructor(private readonly sysPref: SysPreferenceDbService) {}
|
||||
@@ -25,7 +32,7 @@ export class ImageConverterService {
|
||||
image: Buffer,
|
||||
sourceFiletype: FileType,
|
||||
targetFiletype: FileType,
|
||||
options: ImageRequestParams,
|
||||
options: ConvertOptions,
|
||||
): AsyncFailable<ImageResult> {
|
||||
if (
|
||||
sourceFiletype.identifier === targetFiletype.identifier &&
|
||||
@@ -37,23 +44,22 @@ export class ImageConverterService {
|
||||
};
|
||||
}
|
||||
|
||||
if (targetFiletype.category === SupportedFileTypeCategory.Image) {
|
||||
return this.convertStill(image, sourceFiletype, targetFiletype, options);
|
||||
} else if (
|
||||
if (
|
||||
targetFiletype.category === SupportedFileTypeCategory.Image ||
|
||||
targetFiletype.category === SupportedFileTypeCategory.Animation
|
||||
) {
|
||||
return this.convertStill(image, sourceFiletype, targetFiletype, options);
|
||||
return this.convertImage(image, sourceFiletype, targetFiletype, options);
|
||||
//return this.convertAnimation(image, targetmime, options);
|
||||
} else {
|
||||
return Fail(FT.SysValidation, 'Unsupported mime type');
|
||||
}
|
||||
}
|
||||
|
||||
private async convertStill(
|
||||
private async convertImage(
|
||||
image: Buffer,
|
||||
sourceFiletype: FileType,
|
||||
targetFiletype: FileType,
|
||||
options: ImageRequestParams,
|
||||
options: ConvertOptions,
|
||||
): AsyncFailable<ImageResult> {
|
||||
const [memLimit, timeLimit] = await Promise.all([
|
||||
this.sysPref.getNumberPreference(SysPreference.ConversionMemoryLimit),
|
||||
|
||||
@@ -1,15 +1,15 @@
|
||||
import { Injectable } from '@nestjs/common';
|
||||
import {
|
||||
FileType,
|
||||
ImageFileType,
|
||||
SupportedFileTypeCategory,
|
||||
FileType,
|
||||
ImageFileType,
|
||||
SupportedFileTypeCategory,
|
||||
} from 'picsur-shared/dist/dto/mimes.dto';
|
||||
|
||||
import {
|
||||
AsyncFailable,
|
||||
Fail,
|
||||
FT,
|
||||
HasFailed,
|
||||
AsyncFailable,
|
||||
Fail,
|
||||
FT,
|
||||
HasFailed,
|
||||
} from 'picsur-shared/dist/types/failable';
|
||||
import { ParseFileType } from 'picsur-shared/dist/util/parse-mime';
|
||||
import { ImageConverterService } from './image-converter.service.js';
|
||||
@@ -46,10 +46,12 @@ export class ImageProcessorService {
|
||||
image: Buffer,
|
||||
filetype: FileType,
|
||||
): AsyncFailable<ImageResult> {
|
||||
// Webps and gifs are stored as is for now
|
||||
return {
|
||||
image: image,
|
||||
filetype: filetype.identifier,
|
||||
};
|
||||
const outputFileType = ParseFileType(AnimFileType.WEBP);
|
||||
if (HasFailed(outputFileType)) return outputFileType;
|
||||
|
||||
return this.imageConverter.convert(image, filetype, outputFileType, {
|
||||
lossless: true,
|
||||
effort: 0,
|
||||
});
|
||||
}
|
||||
}
|
||||
|
||||
@@ -26,6 +26,10 @@ export type SharpWorkerOperation =
|
||||
|
||||
export interface SharpWorkerFinishOptions {
|
||||
quality?: number;
|
||||
|
||||
// Only for internal use
|
||||
lossless?: boolean;
|
||||
effort?: number;
|
||||
}
|
||||
|
||||
// Messages
|
||||
|
||||
@@ -2,10 +2,11 @@ import { BMPdecode, BMPencode } from 'bmp-img';
|
||||
import {
|
||||
AnimFileType,
|
||||
FileType,
|
||||
ImageFileType,
|
||||
ImageFileType
|
||||
} from 'picsur-shared/dist/dto/mimes.dto';
|
||||
import { QOIdecode, QOIencode } from 'qoi-img';
|
||||
import sharp, { Sharp, SharpOptions } from 'sharp';
|
||||
import { SharpWorkerFinishOptions } from './sharp.message';
|
||||
|
||||
export interface SharpResult {
|
||||
data: Buffer;
|
||||
@@ -72,9 +73,7 @@ function qoiSharpIn(image: Buffer, options?: SharpOptions) {
|
||||
export async function UniversalSharpOut(
|
||||
image: Sharp,
|
||||
filetype: FileType,
|
||||
options?: {
|
||||
quality?: number;
|
||||
},
|
||||
options?: SharpWorkerFinishOptions,
|
||||
): Promise<SharpResult> {
|
||||
let result: SharpResult | undefined;
|
||||
|
||||
@@ -103,7 +102,11 @@ export async function UniversalSharpOut(
|
||||
case ImageFileType.WEBP:
|
||||
case AnimFileType.WEBP:
|
||||
result = await image
|
||||
.webp({ quality: options?.quality })
|
||||
.webp({
|
||||
quality: options?.quality,
|
||||
lossless: options?.lossless,
|
||||
effort: options?.effort,
|
||||
})
|
||||
.toBuffer({ resolveWithObject: true });
|
||||
break;
|
||||
case AnimFileType.GIF:
|
||||
|
||||
Reference in New Issue
Block a user