diff --git a/server/src/config.ts b/server/src/config.ts index cc85ddf86b..33a863b846 100644 --- a/server/src/config.ts +++ b/server/src/config.ts @@ -12,7 +12,7 @@ import { VideoContainer, } from 'src/enum'; import { ConcurrentQueueName, QueueName } from 'src/interfaces/job.interface'; -import { ImageOptions } from 'src/interfaces/media.interface'; +import { FullsizeImageOptions, ImageOptions } from 'src/interfaces/media.interface'; export interface SystemConfig { backup: { @@ -112,7 +112,7 @@ export interface SystemConfig { preview: ImageOptions; colorspace: Colorspace; extractEmbedded: boolean; - fullsizePreview: boolean; + fullsize: FullsizeImageOptions; }; newVersionCheck: { enabled: boolean; @@ -282,7 +282,11 @@ export const defaults = Object.freeze({ }, colorspace: Colorspace.P3, extractEmbedded: false, - fullsizePreview: false, + fullsize: { + enabled: false, + format: ImageFormat.JPEG, + quality: 80, + }, }, newVersionCheck: { enabled: true, diff --git a/server/src/dtos/system-config.dto.ts b/server/src/dtos/system-config.dto.ts index fdf1d1d5ab..e96046215b 100644 --- a/server/src/dtos/system-config.dto.ts +++ b/server/src/dtos/system-config.dto.ts @@ -530,6 +530,24 @@ class SystemConfigGeneratedImageDto { size!: number; } +class SystemConfigGeneratedFullsizeImageDto { + @IsBoolean() + @Type(() => Boolean) + @ApiProperty({ type: 'boolean' }) + enabled!: boolean; + + @IsEnum(ImageFormat) + @ApiProperty({ enumName: 'ImageFormat', enum: ImageFormat }) + format!: ImageFormat; + + @IsInt() + @Min(1) + @Max(100) + @Type(() => Number) + @ApiProperty({ type: 'integer' }) + quality!: number; +} + export class SystemConfigImageDto { @Type(() => SystemConfigGeneratedImageDto) @ValidateNested() @@ -541,15 +559,17 @@ export class SystemConfigImageDto { @IsObject() preview!: SystemConfigGeneratedImageDto; + @Type(() => SystemConfigGeneratedFullsizeImageDto) + @ValidateNested() + @IsObject() + fullsize!: SystemConfigGeneratedFullsizeImageDto; + @IsEnum(Colorspace) @ApiProperty({ enumName: 'Colorspace', enum: Colorspace }) colorspace!: Colorspace; @ValidateBoolean() extractEmbedded!: boolean; - - @ValidateBoolean() - fullsizePreview!: boolean; } class SystemConfigTrashDto { diff --git a/server/src/interfaces/media.interface.ts b/server/src/interfaces/media.interface.ts index 83e7760d7f..3575902e15 100644 --- a/server/src/interfaces/media.interface.ts +++ b/server/src/interfaces/media.interface.ts @@ -11,6 +11,12 @@ export interface CropOptions { height: number; } +export interface FullsizeImageOptions { + format: ImageFormat; + quality: number; + enabled: boolean; +} + export interface ImageOptions { format: ImageFormat; quality: number; diff --git a/server/src/services/media.service.ts b/server/src/services/media.service.ts index 5d8261b262..10229d21c8 100644 --- a/server/src/services/media.service.ts +++ b/server/src/services/media.service.ts @@ -235,7 +235,8 @@ export class MediaService extends BaseService { const imageIsWebSupported = mimeTypes.isWebSupportedImage(asset.originalFileName); const imageIsRaw = mimeTypes.isRaw(asset.originalFileName); - const shouldConvertFullsize = !imageIsWebSupported && image.fullsizePreview; + const { enabled: enableFullsizeImage, ...fullsizeImageOptions } = image.fullsize; + const shouldConvertFullsize = !imageIsWebSupported && enableFullsizeImage; const shouldExtractEmbedded = imageIsRaw && image.extractEmbedded; const decodeOptions: DecodeToBufferOptions = { colorspace, @@ -246,7 +247,8 @@ export class MediaService extends BaseService { let useExtracted = false; let decodeInputPath: string = asset.originalPath; // Converted or extracted image from non-web-supported formats (e.g. RAW) - let fullsizePath: string | undefined; + let fullsizePath: string = StorageCore.getImagePath(asset, AssetPathType.FULLSIZE, image.preview.format); + if (shouldConvertFullsize) { // unset size to decode fullsize image decodeOptions.size = undefined; @@ -275,8 +277,6 @@ export class MediaService extends BaseService { extractedPath, ); } - } else { - fullsizePath = StorageCore.getImagePath(asset, AssetPathType.FULLSIZE, image.preview.format); } } @@ -291,7 +291,7 @@ export class MediaService extends BaseService { !useExtracted && // did not extract a usable image from RAW this.mediaRepository.generateThumbnail( data, - { ...image.preview, ...thumbnailOptions, size: undefined }, + { ...fullsizeImageOptions, ...thumbnailOptions, size: undefined }, fullsizePath, ), ]); diff --git a/web/src/lib/components/admin-page/settings/image/image-settings.svelte b/web/src/lib/components/admin-page/settings/image/image-settings.svelte index bb2836aa59..947e1593b9 100644 --- a/web/src/lib/components/admin-page/settings/image/image-settings.svelte +++ b/web/src/lib/components/admin-page/settings/image/image-settings.svelte @@ -132,6 +132,44 @@ /> + + (config.image.fullsize.enabled = isChecked)} + isEdited={config.image.fullsize.enabled !== savedConfig.image.fullsize.enabled} + {disabled} + /> + + + + + + - - (config.image.fullsizePreview = isChecked)} - isEdited={config.image.fullsizePreview !== savedConfig.image.fullsizePreview} - {disabled} - />