mirror of
https://github.com/CaramelFur/Picsur.git
synced 2025-10-30 00:45:47 +01: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 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),
|
||||||
|
|||||||
@@ -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,
|
||||||
|
});
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -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
|
||||||
|
|||||||
@@ -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:
|
||||||
|
|||||||
Reference in New Issue
Block a user