1
0
forked from Cutlery/immich

thumbnail config

This commit is contained in:
mertalev 2024-03-24 01:57:02 -04:00
parent 5a1a6493c6
commit 35a2aa472a
No known key found for this signature in database
GPG Key ID: 9181CD92C0A1C5E3
4 changed files with 49 additions and 30 deletions

View File

@ -10,6 +10,7 @@ import {
AudioCodec,
CQMode,
Colorspace,
ImageFormat,
LogLevel,
SystemConfig,
SystemConfigEntity,
@ -113,8 +114,10 @@ export const defaults = Object.freeze<SystemConfig>({
template: '{{y}}/{{y}}-{{MM}}-{{dd}}/{{filename}}',
},
thumbnail: {
webpSize: 250,
jpegSize: 1440,
thumbnailFormat: ImageFormat.WEBP,
thumbnailSize: 250,
previewFormat: ImageFormat.JPEG,
previewSize: 1440,
quality: 80,
colorspace: Colorspace.P3,
},

View File

@ -22,6 +22,7 @@ import {
AudioCodec,
CQMode,
Colorspace,
ImageFormat,
LogLevel,
SystemConfig,
ToneMapping,
@ -386,17 +387,25 @@ export class SystemConfigThemeDto {
}
class SystemConfigThumbnailDto {
@IsInt()
@Min(1)
@Type(() => Number)
@ApiProperty({ type: 'integer' })
webpSize!: number;
@IsEnum(ImageFormat)
@ApiProperty({ enumName: 'ImageFormat', enum: ImageFormat })
thumbnailFormat!: ImageFormat;
@IsInt()
@Min(1)
@Type(() => Number)
@ApiProperty({ type: 'integer' })
jpegSize!: number;
thumbnailSize!: number;
@IsEnum(ImageFormat)
@ApiProperty({ enumName: 'ImageFormat', enum: ImageFormat })
previewFormat!: ImageFormat;
@IsInt()
@Min(1)
@Type(() => Number)
@ApiProperty({ type: 'integer' })
previewSize!: number;
@IsInt()
@Min(1)

View File

@ -165,6 +165,11 @@ export enum Colorspace {
P3 = 'p3',
}
export enum ImageFormat {
JPEG = 'jpeg',
WEBP = 'webp',
}
export enum LogLevel {
VERBOSE = 'verbose',
DEBUG = 'debug',
@ -250,8 +255,10 @@ export interface SystemConfig {
template: string;
};
thumbnail: {
webpSize: number;
jpegSize: number;
thumbnailFormat: ImageFormat;
thumbnailSize: number;
previewFormat: ImageFormat;
previewSize: number;
quality: number;
colorspace: Colorspace;
};

View File

@ -1,5 +1,5 @@
import { Inject, Injectable, UnsupportedMediaTypeException } from '@nestjs/common';
import { StorageCore, StorageFolder } from 'src/cores/storage.core';
import { GeneratedImageType, StorageCore, StorageFolder } from 'src/cores/storage.core';
import { SystemConfigCore } from 'src/cores/system-config.core';
import { SystemConfigFFmpegDto } from 'src/dtos/system-config.dto';
import { AssetEntity, AssetType } from 'src/entities/asset.entity';
@ -7,6 +7,7 @@ import { AssetPathType } from 'src/entities/move.entity';
import {
AudioCodec,
Colorspace,
ImageFormat,
TranscodeHWAccel,
TranscodePolicy,
TranscodeTarget,
@ -81,12 +82,12 @@ export class MediaService {
const jobs: JobItem[] = [];
for (const asset of assets) {
if (!asset.resizePath || force) {
jobs.push({ name: JobName.GENERATE_JPEG_THUMBNAIL, data: { id: asset.id } });
if (!asset.previewPath || force) {
jobs.push({ name: JobName.GENERATE_THUMBNAIL, data: { id: asset.id } });
continue;
}
if (!asset.webpPath) {
jobs.push({ name: JobName.GENERATE_WEBP_THUMBNAIL, data: { id: asset.id } });
if (!asset.thumbnailPath) {
jobs.push({ name: JobName.GENERATE_PREVIEW, data: { id: asset.id } });
}
if (!asset.thumbhash) {
jobs.push({ name: JobName.GENERATE_THUMBHASH_THUMBNAIL, data: { id: asset.id } });
@ -157,29 +158,28 @@ export class MediaService {
return JobStatus.FAILED;
}
await this.storageCore.moveAssetFile(asset, AssetPathType.JPEG_THUMBNAIL);
await this.storageCore.moveAssetFile(asset, AssetPathType.WEBP_THUMBNAIL);
await this.storageCore.moveAssetFile(asset, AssetPathType.PREVIEW);
await this.storageCore.moveAssetFile(asset, AssetPathType.THUMBNAIL);
await this.storageCore.moveAssetFile(asset, AssetPathType.ENCODED_VIDEO);
return JobStatus.SUCCESS;
}
async handleGenerateJpegThumbnail({ id }: IEntityJob): Promise<JobStatus> {
async handleGeneratePreview({ id }: IEntityJob): Promise<JobStatus> {
const [asset] = await this.assetRepository.getByIds([id], { exifInfo: true });
if (!asset) {
return JobStatus.FAILED;
}
const resizePath = await this.generateThumbnail(asset, 'jpeg');
await this.assetRepository.update({ id: asset.id, resizePath });
const previewPath = await this.generateThumbnail(asset, AssetPathType.PREVIEW, ImageFormat.JPEG);
await this.assetRepository.update({ id: asset.id, previewPath });
return JobStatus.SUCCESS;
}
private async generateThumbnail(asset: AssetEntity, format: 'jpeg' | 'webp') {
private async generateThumbnail(asset: AssetEntity, type: GeneratedImageType, format: ImageFormat) {
const { thumbnail, ffmpeg } = await this.configCore.getConfig();
const size = format === 'jpeg' ? thumbnail.jpegSize : thumbnail.webpSize;
const path =
format === 'jpeg' ? StorageCore.getLargeThumbnailPath(asset) : StorageCore.getSmallThumbnailPath(asset);
const size = type === AssetPathType.PREVIEW ? thumbnail.previewSize : thumbnail.thumbnailSize;
const path = StorageCore.getImagePath(asset, type);
this.storageCore.ensureFolders(path);
switch (asset.type) {
@ -214,24 +214,24 @@ export class MediaService {
return path;
}
async handleGenerateWebpThumbnail({ id }: IEntityJob): Promise<JobStatus> {
async handleGenerateThumbnail({ id }: IEntityJob): Promise<JobStatus> {
const [asset] = await this.assetRepository.getByIds([id], { exifInfo: true });
if (!asset) {
return JobStatus.FAILED;
}
const webpPath = await this.generateThumbnail(asset, 'webp');
await this.assetRepository.update({ id: asset.id, webpPath });
const thumbnailPath = await this.generateThumbnail(asset, AssetPathType.THUMBNAIL, ImageFormat.WEBP);
await this.assetRepository.update({ id: asset.id, thumbnailPath });
return JobStatus.SUCCESS;
}
async handleGenerateThumbhashThumbnail({ id }: IEntityJob): Promise<JobStatus> {
async handleGenerateThumbhash({ id }: IEntityJob): Promise<JobStatus> {
const [asset] = await this.assetRepository.getByIds([id]);
if (!asset?.resizePath) {
if (!asset?.previewPath) {
return JobStatus.FAILED;
}
const thumbhash = await this.mediaRepository.generateThumbhash(asset.resizePath);
const thumbhash = await this.mediaRepository.generateThumbhash(asset.previewPath);
await this.assetRepository.update({ id: asset.id, thumbhash });
return JobStatus.SUCCESS;