mirror of
https://github.com/immich-app/immich.git
synced 2024-11-24 08:52:28 +02:00
feat(server,web): configure image format (#8581)
This commit is contained in:
parent
55b9acca78
commit
105a74caca
@ -210,25 +210,21 @@ describe(MediaService.name, () => {
|
||||
expect(assetMock.update).not.toHaveBeenCalledWith();
|
||||
});
|
||||
|
||||
it('should generate a thumbnail for an image', async () => {
|
||||
it.each(Object.values(ImageFormat))('should generate a %s preview for an image when specified', async (format) => {
|
||||
configMock.load.mockResolvedValue([{ key: SystemConfigKey.IMAGE_PREVIEW_FORMAT, value: format }]);
|
||||
assetMock.getByIds.mockResolvedValue([assetStub.image]);
|
||||
const previewPath = `upload/thumbs/user-id/as/se/asset-id-preview.${format}`;
|
||||
|
||||
await sut.handleGeneratePreview({ id: assetStub.image.id });
|
||||
|
||||
expect(storageMock.mkdirSync).toHaveBeenCalledWith('upload/thumbs/user-id/as/se');
|
||||
expect(mediaMock.resize).toHaveBeenCalledWith(
|
||||
'/original/path.jpg',
|
||||
'upload/thumbs/user-id/as/se/asset-id-preview.jpeg',
|
||||
{
|
||||
size: 1440,
|
||||
format: ImageFormat.JPEG,
|
||||
quality: 80,
|
||||
colorspace: Colorspace.SRGB,
|
||||
},
|
||||
);
|
||||
expect(assetMock.update).toHaveBeenCalledWith({
|
||||
id: 'asset-id',
|
||||
previewPath: 'upload/thumbs/user-id/as/se/asset-id-preview.jpeg',
|
||||
expect(mediaMock.resize).toHaveBeenCalledWith('/original/path.jpg', previewPath, {
|
||||
size: 1440,
|
||||
format,
|
||||
quality: 80,
|
||||
colorspace: Colorspace.SRGB,
|
||||
});
|
||||
expect(assetMock.update).toHaveBeenCalledWith({ id: 'asset-id', previewPath });
|
||||
});
|
||||
|
||||
it('should generate a P3 thumbnail for a wide gamut image', async () => {
|
||||
@ -342,25 +338,25 @@ describe(MediaService.name, () => {
|
||||
expect(assetMock.update).not.toHaveBeenCalledWith();
|
||||
});
|
||||
|
||||
it('should generate a thumbnail', async () => {
|
||||
assetMock.getByIds.mockResolvedValue([assetStub.image]);
|
||||
await sut.handleGenerateThumbnail({ id: assetStub.image.id });
|
||||
it.each(Object.values(ImageFormat))(
|
||||
'should generate a %s thumbnail for an image when specified',
|
||||
async (format) => {
|
||||
configMock.load.mockResolvedValue([{ key: SystemConfigKey.IMAGE_THUMBNAIL_FORMAT, value: format }]);
|
||||
assetMock.getByIds.mockResolvedValue([assetStub.image]);
|
||||
const thumbnailPath = `upload/thumbs/user-id/as/se/asset-id-thumbnail.${format}`;
|
||||
|
||||
expect(mediaMock.resize).toHaveBeenCalledWith(
|
||||
'/original/path.jpg',
|
||||
'upload/thumbs/user-id/as/se/asset-id-thumbnail.webp',
|
||||
{
|
||||
format: ImageFormat.WEBP,
|
||||
await sut.handleGenerateThumbnail({ id: assetStub.image.id });
|
||||
|
||||
expect(storageMock.mkdirSync).toHaveBeenCalledWith('upload/thumbs/user-id/as/se');
|
||||
expect(mediaMock.resize).toHaveBeenCalledWith('/original/path.jpg', thumbnailPath, {
|
||||
size: 250,
|
||||
format,
|
||||
quality: 80,
|
||||
colorspace: Colorspace.SRGB,
|
||||
},
|
||||
);
|
||||
expect(assetMock.update).toHaveBeenCalledWith({
|
||||
id: 'asset-id',
|
||||
thumbnailPath: 'upload/thumbs/user-id/as/se/asset-id-thumbnail.webp',
|
||||
});
|
||||
});
|
||||
});
|
||||
expect(assetMock.update).toHaveBeenCalledWith({ id: 'asset-id', thumbnailPath });
|
||||
},
|
||||
);
|
||||
});
|
||||
|
||||
it('should generate a P3 thumbnail for a wide gamut image', async () => {
|
||||
|
@ -167,12 +167,15 @@ export class MediaService {
|
||||
}
|
||||
|
||||
async handleGeneratePreview({ id }: IEntityJob): Promise<JobStatus> {
|
||||
const [asset] = await this.assetRepository.getByIds([id], { exifInfo: true });
|
||||
const [{ image }, [asset]] = await Promise.all([
|
||||
this.configCore.getConfig(),
|
||||
this.assetRepository.getByIds([id], { exifInfo: true }),
|
||||
]);
|
||||
if (!asset) {
|
||||
return JobStatus.FAILED;
|
||||
}
|
||||
|
||||
const previewPath = await this.generateThumbnail(asset, AssetPathType.PREVIEW, ImageFormat.JPEG);
|
||||
const previewPath = await this.generateThumbnail(asset, AssetPathType.PREVIEW, image.previewFormat);
|
||||
await this.assetRepository.update({ id: asset.id, previewPath });
|
||||
return JobStatus.SUCCESS;
|
||||
}
|
||||
@ -210,18 +213,21 @@ export class MediaService {
|
||||
}
|
||||
}
|
||||
this.logger.log(
|
||||
`Successfully generated ${format.toUpperCase()} ${asset.type.toLowerCase()} thumbnail for asset ${asset.id}`,
|
||||
`Successfully generated ${format.toUpperCase()} ${asset.type.toLowerCase()} ${type} for asset ${asset.id}`,
|
||||
);
|
||||
return path;
|
||||
}
|
||||
|
||||
async handleGenerateThumbnail({ id }: IEntityJob): Promise<JobStatus> {
|
||||
const [asset] = await this.assetRepository.getByIds([id], { exifInfo: true });
|
||||
const [{ image }, [asset]] = await Promise.all([
|
||||
this.configCore.getConfig(),
|
||||
this.assetRepository.getByIds([id], { exifInfo: true }),
|
||||
]);
|
||||
if (!asset) {
|
||||
return JobStatus.FAILED;
|
||||
}
|
||||
|
||||
const thumbnailPath = await this.generateThumbnail(asset, AssetPathType.THUMBNAIL, ImageFormat.WEBP);
|
||||
const thumbnailPath = await this.generateThumbnail(asset, AssetPathType.THUMBNAIL, image.thumbnailFormat);
|
||||
await this.assetRepository.update({ id: asset.id, thumbnailPath });
|
||||
return JobStatus.SUCCESS;
|
||||
}
|
||||
|
@ -1,5 +1,5 @@
|
||||
<script lang="ts">
|
||||
import { Colorspace, type SystemConfigDto } from '@immich/sdk';
|
||||
import { Colorspace, ImageFormat, type SystemConfigDto } from '@immich/sdk';
|
||||
import { isEqual } from 'lodash-es';
|
||||
import { createEventDispatcher } from 'svelte';
|
||||
import { fade } from 'svelte/transition';
|
||||
@ -24,6 +24,19 @@
|
||||
<div in:fade={{ duration: 500 }}>
|
||||
<form autocomplete="off" on:submit|preventDefault>
|
||||
<div class="ml-4 mt-4 flex flex-col gap-4">
|
||||
<SettingSelect
|
||||
label="THUMBNAIL FORMAT"
|
||||
desc="WebP produces smaller files than JPEG, but is slower to encode."
|
||||
bind:value={config.image.thumbnailFormat}
|
||||
options={[
|
||||
{ value: ImageFormat.Jpeg, text: 'JPEG' },
|
||||
{ value: ImageFormat.Webp, text: 'WebP' },
|
||||
]}
|
||||
name="format"
|
||||
isEdited={config.image.thumbnailFormat !== savedConfig.image.thumbnailFormat}
|
||||
{disabled}
|
||||
/>
|
||||
|
||||
<SettingSelect
|
||||
label="THUMBNAIL RESOLUTION"
|
||||
desc="Used when viewing groups of photos (main timeline, album view, etc.). Higher resolutions can preserve more detail but take longer to encode, have larger file sizes, and can reduce app responsiveness."
|
||||
@ -41,6 +54,19 @@
|
||||
{disabled}
|
||||
/>
|
||||
|
||||
<SettingSelect
|
||||
label="PREVIEW FORMAT"
|
||||
desc="WebP produces smaller files than JPEG, but is slower to encode."
|
||||
bind:value={config.image.previewFormat}
|
||||
options={[
|
||||
{ value: ImageFormat.Jpeg, text: 'JPEG' },
|
||||
{ value: ImageFormat.Webp, text: 'WebP' },
|
||||
]}
|
||||
name="format"
|
||||
isEdited={config.image.previewFormat !== savedConfig.image.previewFormat}
|
||||
{disabled}
|
||||
/>
|
||||
|
||||
<SettingSelect
|
||||
label="PREVIEW RESOLUTION"
|
||||
desc="Used when viewing a single photo and for machine learning. Higher resolutions can preserve more detail but take longer to encode, have larger file sizes, and can reduce app responsiveness."
|
||||
|
Loading…
Reference in New Issue
Block a user