add seperate collection for image files and meta

This commit is contained in:
rubikscraft
2022-04-21 16:53:40 +02:00
parent 1e692506bd
commit 47210fabce
13 changed files with 241 additions and 111 deletions

View File

@@ -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) {

View File

@@ -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,