mirror of
https://github.com/CaramelFur/Picsur.git
synced 2025-11-13 07:15:39 +01:00
add seperate collection for image files and meta
This commit is contained in:
@@ -11,6 +11,11 @@ import { QOIColorSpace, QOIdecode, QOIencode } from 'qoi-img';
|
||||
import sharp from 'sharp';
|
||||
import { UsrPreferenceService } from '../../collections/preference-db/usr-preference-db.service';
|
||||
|
||||
interface ProcessResult {
|
||||
image: Buffer;
|
||||
mime: string;
|
||||
}
|
||||
|
||||
@Injectable()
|
||||
export class ImageProcessorService {
|
||||
constructor(private readonly userPref: UsrPreferenceService) {}
|
||||
@@ -19,7 +24,7 @@ export class ImageProcessorService {
|
||||
image: Buffer,
|
||||
mime: FullMime,
|
||||
userid: string,
|
||||
): AsyncFailable<Buffer> {
|
||||
): AsyncFailable<ProcessResult> {
|
||||
if (mime.type === SupportedMimeCategory.Image) {
|
||||
return await this.processStill(image, mime, {});
|
||||
} else if (mime.type === SupportedMimeCategory.Animation) {
|
||||
@@ -27,25 +32,17 @@ export class ImageProcessorService {
|
||||
} else {
|
||||
return Fail('Unsupported mime type');
|
||||
}
|
||||
|
||||
// // nothing happens right now
|
||||
// const keepOriginal = await this.userPref.getBooleanPreference(
|
||||
// userid,
|
||||
// UsrPreference.KeepOriginal,
|
||||
// );
|
||||
// if (HasFailed(keepOriginal)) return keepOriginal;
|
||||
|
||||
// if (keepOriginal) {
|
||||
// }
|
||||
}
|
||||
|
||||
private async processStill(
|
||||
image: Buffer,
|
||||
mime: FullMime,
|
||||
options: {},
|
||||
): AsyncFailable<Buffer> {
|
||||
): AsyncFailable<ProcessResult> {
|
||||
let processedMime = mime.mime;
|
||||
let sharpImage: sharp.Sharp;
|
||||
|
||||
// TODO: ensure mime and sharp are in agreement
|
||||
if (mime.mime === ImageMime.ICO) {
|
||||
sharpImage = this.icoSharp(image);
|
||||
} else if (mime.mime === ImageMime.BMP) {
|
||||
@@ -55,7 +52,7 @@ export class ImageProcessorService {
|
||||
} else {
|
||||
sharpImage = sharp(image);
|
||||
}
|
||||
mime.mime = ImageMime.QOI;
|
||||
processedMime = ImageMime.QOI;
|
||||
|
||||
sharpImage = sharpImage.toColorspace('srgb');
|
||||
|
||||
@@ -69,6 +66,10 @@ export class ImageProcessorService {
|
||||
)
|
||||
return Fail('Invalid image');
|
||||
|
||||
if (metadata.width >= 32768 || metadata.height >= 32768) {
|
||||
return Fail('Image too large');
|
||||
}
|
||||
|
||||
// Png can be more efficient than QOI, but its just sooooooo slow
|
||||
const qoiImage = QOIencode(pixels, {
|
||||
channels: metadata.hasAlpha ? 4 : 3,
|
||||
@@ -77,16 +78,22 @@ export class ImageProcessorService {
|
||||
width: metadata.width,
|
||||
});
|
||||
|
||||
return qoiImage;
|
||||
return {
|
||||
image: qoiImage,
|
||||
mime: processedMime,
|
||||
};
|
||||
}
|
||||
|
||||
private async processAnimation(
|
||||
image: Buffer,
|
||||
mime: FullMime,
|
||||
options: {},
|
||||
): AsyncFailable<Buffer> {
|
||||
): AsyncFailable<ProcessResult> {
|
||||
// Apng and gif are stored as is for now
|
||||
return image;
|
||||
return {
|
||||
image: image,
|
||||
mime: mime.mime,
|
||||
};
|
||||
}
|
||||
|
||||
private bmpSharp(image: Buffer) {
|
||||
|
||||
@@ -5,6 +5,9 @@ import { AsyncFailable, HasFailed } from 'picsur-shared/dist/types';
|
||||
import { ParseMime } from 'picsur-shared/dist/util/parse-mime';
|
||||
import { IsQOI } from 'qoi-img';
|
||||
import { ImageDBService } from '../../collections/image-db/image-db.service';
|
||||
import { ImageFileDBService } from '../../collections/image-db/image-file-db.service';
|
||||
import { ImageFileType } from '../../models/constants/image-file-types.const';
|
||||
import { EImageFileBackend } from '../../models/entities/image-file.entity';
|
||||
import { EImageBackend } from '../../models/entities/image.entity';
|
||||
import { ImageProcessorService } from './image-processor.service';
|
||||
|
||||
@@ -16,6 +19,7 @@ import { ImageProcessorService } from './image-processor.service';
|
||||
export class ImageManagerService {
|
||||
constructor(
|
||||
private readonly imagesService: ImageDBService,
|
||||
private readonly imageFilesService: ImageFileDBService,
|
||||
private readonly processService: ImageProcessorService,
|
||||
) {}
|
||||
|
||||
@@ -25,10 +29,8 @@ export class ImageManagerService {
|
||||
|
||||
// Image data buffer is not included by default, this also returns that buffer
|
||||
// Dont send to client, keep in backend
|
||||
public async retrieveComplete(
|
||||
id: string,
|
||||
): AsyncFailable<Required<EImageBackend>> {
|
||||
return await this.imagesService.findOne(id, true);
|
||||
public async retrieveComplete(id: string): AsyncFailable<EImageBackend> {
|
||||
return await this.imagesService.findOne(id);
|
||||
}
|
||||
|
||||
public async upload(
|
||||
@@ -38,22 +40,69 @@ export class ImageManagerService {
|
||||
const fullMime = await this.getFullMimeFromBuffer(image);
|
||||
if (HasFailed(fullMime)) return fullMime;
|
||||
|
||||
const processedImage = await this.processService.process(
|
||||
const processResult = await this.processService.process(
|
||||
image,
|
||||
fullMime,
|
||||
userid,
|
||||
);
|
||||
if (HasFailed(processedImage)) return processedImage;
|
||||
if (HasFailed(processResult)) return processResult;
|
||||
|
||||
const imageEntity = await this.imagesService.create(
|
||||
processedImage,
|
||||
fullMime.mime,
|
||||
);
|
||||
const imageEntity = await this.imagesService.create();
|
||||
if (HasFailed(imageEntity)) return imageEntity;
|
||||
|
||||
const imageFileEntity = await this.imageFilesService.setSingle(
|
||||
imageEntity.id,
|
||||
ImageFileType.MASTER,
|
||||
processResult.image,
|
||||
processResult.mime,
|
||||
);
|
||||
if (HasFailed(imageFileEntity)) return imageFileEntity;
|
||||
|
||||
// // nothing happens right now
|
||||
// const keepOriginal = await this.userPref.getBooleanPreference(
|
||||
// userid,
|
||||
// UsrPreference.KeepOriginal,
|
||||
// );
|
||||
// if (HasFailed(keepOriginal)) return keepOriginal;
|
||||
|
||||
// if (keepOriginal) {
|
||||
// }
|
||||
|
||||
return imageEntity;
|
||||
}
|
||||
|
||||
// File getters ==============================================================
|
||||
|
||||
public async getMaster(imageId: string): AsyncFailable<EImageFileBackend> {
|
||||
return this.imageFilesService.getSingle(imageId, ImageFileType.MASTER);
|
||||
}
|
||||
|
||||
public async getMasterMime(imageId: string): AsyncFailable<FullMime> {
|
||||
const mime = await this.imageFilesService.getSingleMime(
|
||||
imageId,
|
||||
ImageFileType.MASTER,
|
||||
);
|
||||
if (HasFailed(mime)) return mime;
|
||||
|
||||
return ParseMime(mime);
|
||||
}
|
||||
|
||||
public async getOriginal(imageId: string): AsyncFailable<EImageFileBackend> {
|
||||
return this.imageFilesService.getSingle(imageId, ImageFileType.ORIGINAL);
|
||||
}
|
||||
|
||||
public async getOriginalMime(imageId: string): AsyncFailable<FullMime> {
|
||||
const mime = await this.imageFilesService.getSingleMime(
|
||||
imageId,
|
||||
ImageFileType.ORIGINAL,
|
||||
);
|
||||
if (HasFailed(mime)) return mime;
|
||||
|
||||
return ParseMime(mime);
|
||||
}
|
||||
|
||||
// Util stuff ==================================================================
|
||||
|
||||
private async getFullMimeFromBuffer(image: Buffer): AsyncFailable<FullMime> {
|
||||
const filetypeResult: FileTypeResult | undefined = await fileTypeFromBuffer(
|
||||
image,
|
||||
|
||||
Reference in New Issue
Block a user