mirror of
https://github.com/immich-app/immich.git
synced 2025-01-04 02:46:47 +01:00
test: full-size previews
This commit is contained in:
parent
dcf2ce38e3
commit
2fe37a2607
3 changed files with 182 additions and 17 deletions
|
@ -1,4 +1,5 @@
|
|||
import { SystemConfig } from 'src/config';
|
||||
import { AssetMediaSize } from 'src/dtos/asset-media.dto';
|
||||
import { AssetEntity } from 'src/entities/asset.entity';
|
||||
import { ExifEntity } from 'src/entities/exif.entity';
|
||||
import {
|
||||
|
@ -275,12 +276,20 @@ describe(MediaService.name, () => {
|
|||
|
||||
describe('handleGenerateThumbnails', () => {
|
||||
let rawBuffer: Buffer;
|
||||
let fullsizeBuffer: Buffer;
|
||||
let rawInfo: RawImageInfo;
|
||||
|
||||
beforeEach(() => {
|
||||
fullsizeBuffer = Buffer.from('embedded image data');
|
||||
rawBuffer = Buffer.from('image data');
|
||||
rawInfo = { width: 100, height: 100, channels: 3 };
|
||||
mediaMock.decodeImage.mockResolvedValue({ data: rawBuffer, info: rawInfo });
|
||||
mediaMock.decodeImage.mockImplementation((path) =>
|
||||
Promise.resolve(
|
||||
path.includes(AssetMediaSize.FULLSIZE)
|
||||
? { data: fullsizeBuffer, info: rawInfo }
|
||||
: { data: rawBuffer, info: rawInfo },
|
||||
),
|
||||
);
|
||||
});
|
||||
|
||||
it('should skip thumbnail generation if asset not found', async () => {
|
||||
|
@ -627,7 +636,7 @@ describe(MediaService.name, () => {
|
|||
|
||||
await sut.handleGenerateThumbnails({ id: assetStub.image.id });
|
||||
|
||||
const convertedPath = mediaMock.extract.mock.calls.at(-1)?.[1].toString();
|
||||
const convertedPath = mediaMock.extract.mock.lastCall?.[1].toString();
|
||||
expect(mediaMock.decodeImage).toHaveBeenCalledOnce();
|
||||
expect(mediaMock.decodeImage).toHaveBeenCalledWith(convertedPath, {
|
||||
colorspace: Colorspace.P3,
|
||||
|
@ -644,17 +653,14 @@ describe(MediaService.name, () => {
|
|||
|
||||
await sut.handleGenerateThumbnails({ id: assetStub.image.id });
|
||||
|
||||
const convertedPath = mediaMock.extract.mock.calls.at(-1)?.[1].toString();
|
||||
expect(mediaMock.generateThumbnail).toHaveBeenCalledWith(
|
||||
rawBuffer,
|
||||
expect.objectContaining({ size: 1440 }),
|
||||
convertedPath,
|
||||
);
|
||||
expect(convertedPath).toMatch(/-converted\.jpeg$/);
|
||||
const extractedPath = mediaMock.extract.mock.lastCall?.[1].toString();
|
||||
expect(extractedPath).toMatch(/-fullsize\.jpeg$/);
|
||||
|
||||
expect(mediaMock.decodeImage).toHaveBeenCalledOnce();
|
||||
expect(mediaMock.decodeImage).toHaveBeenCalledWith(assetStub.imageDng.originalPath, {
|
||||
colorspace: Colorspace.P3,
|
||||
processInvalidImages: false,
|
||||
size: 1440,
|
||||
});
|
||||
});
|
||||
|
||||
|
@ -668,6 +674,7 @@ describe(MediaService.name, () => {
|
|||
expect(mediaMock.decodeImage).toHaveBeenCalledWith(assetStub.imageDng.originalPath, {
|
||||
colorspace: Colorspace.P3,
|
||||
processInvalidImages: false,
|
||||
size: 1440,
|
||||
});
|
||||
expect(mediaMock.getImageDimensions).not.toHaveBeenCalled();
|
||||
});
|
||||
|
@ -683,6 +690,7 @@ describe(MediaService.name, () => {
|
|||
expect(mediaMock.decodeImage).toHaveBeenCalledWith(assetStub.imageDng.originalPath, {
|
||||
colorspace: Colorspace.P3,
|
||||
processInvalidImages: false,
|
||||
size: 1440,
|
||||
});
|
||||
expect(mediaMock.getImageDimensions).not.toHaveBeenCalled();
|
||||
});
|
||||
|
@ -700,12 +708,7 @@ describe(MediaService.name, () => {
|
|||
expect.objectContaining({ processInvalidImages: true }),
|
||||
);
|
||||
|
||||
expect(mediaMock.generateThumbnail).toHaveBeenCalledTimes(3);
|
||||
expect(mediaMock.generateThumbnail).toHaveBeenCalledWith(
|
||||
rawBuffer,
|
||||
expect.objectContaining({ processInvalidImages: true }),
|
||||
'upload/thumbs/user-id/as/se/asset-id-converted.jpeg',
|
||||
);
|
||||
expect(mediaMock.generateThumbnail).toHaveBeenCalledTimes(2);
|
||||
expect(mediaMock.generateThumbnail).toHaveBeenCalledWith(
|
||||
rawBuffer,
|
||||
expect.objectContaining({ processInvalidImages: true }),
|
||||
|
@ -726,6 +729,128 @@ describe(MediaService.name, () => {
|
|||
expect(mediaMock.getImageDimensions).not.toHaveBeenCalled();
|
||||
vi.unstubAllEnvs();
|
||||
});
|
||||
|
||||
it('should generate full-size preview using embedded JPEG from RAW images when extractEmbedded is true', async () => {
|
||||
systemMock.get.mockResolvedValue({ image: { fullsize: { enabled: true }, extractEmbedded: true } });
|
||||
mediaMock.extract.mockResolvedValue(true);
|
||||
mediaMock.getImageDimensions.mockResolvedValue({ width: 3840, height: 2160 });
|
||||
assetMock.getById.mockResolvedValue(assetStub.imageDng);
|
||||
|
||||
await sut.handleGenerateThumbnails({ id: assetStub.image.id });
|
||||
|
||||
const extractedPath = mediaMock.extract.mock.lastCall?.[1].toString();
|
||||
expect(mediaMock.decodeImage).toHaveBeenCalledOnce();
|
||||
expect(mediaMock.decodeImage).toHaveBeenCalledWith(extractedPath, {
|
||||
colorspace: Colorspace.P3,
|
||||
processInvalidImages: false,
|
||||
});
|
||||
|
||||
expect(mediaMock.generateThumbnail).toHaveBeenCalledTimes(2);
|
||||
expect(mediaMock.generateThumbnail).toHaveBeenCalledWith(
|
||||
fullsizeBuffer,
|
||||
{
|
||||
colorspace: Colorspace.P3,
|
||||
format: ImageFormat.JPEG,
|
||||
size: 1440,
|
||||
quality: 80,
|
||||
processInvalidImages: false,
|
||||
raw: rawInfo,
|
||||
},
|
||||
'upload/thumbs/user-id/as/se/asset-id-preview.jpeg',
|
||||
);
|
||||
});
|
||||
|
||||
it('should generate full-size preview directly from RAW images when extractEmbedded is false', async () => {
|
||||
systemMock.get.mockResolvedValue({ image: { fullsize: { enabled: true }, extractEmbedded: false } });
|
||||
mediaMock.extract.mockResolvedValue(true);
|
||||
mediaMock.getImageDimensions.mockResolvedValue({ width: 3840, height: 2160 });
|
||||
assetMock.getById.mockResolvedValue(assetStub.imageDng);
|
||||
|
||||
await sut.handleGenerateThumbnails({ id: assetStub.image.id });
|
||||
|
||||
expect(mediaMock.decodeImage).toHaveBeenCalledOnce();
|
||||
expect(mediaMock.decodeImage).toHaveBeenCalledWith(assetStub.imageDng.originalPath, {
|
||||
colorspace: Colorspace.P3,
|
||||
processInvalidImages: false,
|
||||
});
|
||||
|
||||
expect(mediaMock.generateThumbnail).toHaveBeenCalledTimes(3);
|
||||
expect(mediaMock.generateThumbnail).toHaveBeenCalledWith(
|
||||
rawBuffer,
|
||||
{
|
||||
colorspace: Colorspace.P3,
|
||||
format: ImageFormat.JPEG,
|
||||
quality: 80,
|
||||
processInvalidImages: false,
|
||||
raw: rawInfo,
|
||||
},
|
||||
'upload/thumbs/user-id/as/se/asset-id-fullsize.jpeg',
|
||||
);
|
||||
expect(mediaMock.generateThumbnail).toHaveBeenCalledWith(
|
||||
rawBuffer,
|
||||
{
|
||||
colorspace: Colorspace.P3,
|
||||
format: ImageFormat.JPEG,
|
||||
quality: 80,
|
||||
size: 1440,
|
||||
processInvalidImages: false,
|
||||
raw: rawInfo,
|
||||
},
|
||||
'upload/thumbs/user-id/as/se/asset-id-preview.jpeg',
|
||||
);
|
||||
});
|
||||
|
||||
it('should generate full-size preview from non-web-friendly images', async () => {
|
||||
systemMock.get.mockResolvedValue({ image: { fullsize: { enabled: true } } });
|
||||
mediaMock.extract.mockResolvedValue(true);
|
||||
mediaMock.getImageDimensions.mockResolvedValue({ width: 3840, height: 2160 });
|
||||
// HEIF/HIF image taken by cameras are not web-friendly, only has limited support on Safari.
|
||||
assetMock.getById.mockResolvedValue(assetStub.imageHif);
|
||||
|
||||
await sut.handleGenerateThumbnails({ id: assetStub.image.id });
|
||||
|
||||
expect(mediaMock.decodeImage).toHaveBeenCalledOnce();
|
||||
expect(mediaMock.decodeImage).toHaveBeenCalledWith(assetStub.imageHif.originalPath, {
|
||||
colorspace: Colorspace.P3,
|
||||
processInvalidImages: false,
|
||||
});
|
||||
|
||||
expect(mediaMock.generateThumbnail).toHaveBeenCalledTimes(3);
|
||||
expect(mediaMock.generateThumbnail).toHaveBeenCalledWith(
|
||||
rawBuffer,
|
||||
{
|
||||
colorspace: Colorspace.P3,
|
||||
format: ImageFormat.JPEG,
|
||||
quality: 80,
|
||||
processInvalidImages: false,
|
||||
raw: rawInfo,
|
||||
},
|
||||
'upload/thumbs/user-id/as/se/asset-id-fullsize.jpeg',
|
||||
);
|
||||
});
|
||||
|
||||
it('should skip generating full-size preview for web-friendly images', async () => {
|
||||
systemMock.get.mockResolvedValue({ image: { fullsize: { enabled: true } } });
|
||||
mediaMock.extract.mockResolvedValue(true);
|
||||
mediaMock.getImageDimensions.mockResolvedValue({ width: 3840, height: 2160 });
|
||||
assetMock.getById.mockResolvedValue(assetStub.image);
|
||||
|
||||
await sut.handleGenerateThumbnails({ id: assetStub.image.id });
|
||||
|
||||
expect(mediaMock.decodeImage).toHaveBeenCalledOnce();
|
||||
expect(mediaMock.decodeImage).toHaveBeenCalledWith(assetStub.image.originalPath, {
|
||||
colorspace: Colorspace.SRGB,
|
||||
processInvalidImages: false,
|
||||
size: 1440,
|
||||
});
|
||||
|
||||
expect(mediaMock.generateThumbnail).toHaveBeenCalledTimes(2);
|
||||
expect(mediaMock.generateThumbnail).not.toHaveBeenCalledWith(
|
||||
expect.anything(),
|
||||
expect.anything(),
|
||||
'upload/thumbs/user-id/as/se/asset-id-fullsize.jpeg',
|
||||
);
|
||||
});
|
||||
});
|
||||
|
||||
describe('handleQueueVideoConversion', () => {
|
||||
|
|
|
@ -151,9 +151,9 @@ const updatedConfig = Object.freeze<SystemConfig>({
|
|||
format: ImageFormat.JPEG,
|
||||
quality: 80,
|
||||
},
|
||||
fullsize: { enabled: false, format: ImageFormat.JPEG, quality: 80 },
|
||||
colorspace: Colorspace.P3,
|
||||
extractEmbedded: false,
|
||||
fullsizePreview: false,
|
||||
},
|
||||
newVersionCheck: {
|
||||
enabled: true,
|
||||
|
|
42
server/test/fixtures/asset.stub.ts
vendored
42
server/test/fixtures/asset.stub.ts
vendored
|
@ -774,7 +774,47 @@ export const assetStub = {
|
|||
livePhotoVideoId: null,
|
||||
tags: [],
|
||||
sharedLinks: [],
|
||||
originalFileName: 'asset-id.jpg',
|
||||
originalFileName: 'asset-id.dng',
|
||||
faces: [],
|
||||
deletedAt: null,
|
||||
sidecarPath: null,
|
||||
exifInfo: {
|
||||
fileSizeInByte: 5000,
|
||||
profileDescription: 'Adobe RGB',
|
||||
bitsPerSample: 14,
|
||||
} as ExifEntity,
|
||||
duplicateId: null,
|
||||
isOffline: false,
|
||||
}),
|
||||
|
||||
imageHif: Object.freeze<AssetEntity>({
|
||||
id: 'asset-id',
|
||||
status: AssetStatus.ACTIVE,
|
||||
deviceAssetId: 'device-asset-id',
|
||||
fileModifiedAt: new Date('2023-02-23T05:06:29.716Z'),
|
||||
fileCreatedAt: new Date('2023-02-23T05:06:29.716Z'),
|
||||
owner: userStub.user1,
|
||||
ownerId: 'user-id',
|
||||
deviceId: 'device-id',
|
||||
originalPath: '/original/path.hif',
|
||||
checksum: Buffer.from('file hash', 'utf8'),
|
||||
type: AssetType.IMAGE,
|
||||
files,
|
||||
thumbhash: Buffer.from('blablabla', 'base64'),
|
||||
encodedVideoPath: null,
|
||||
createdAt: new Date('2023-02-23T05:06:29.716Z'),
|
||||
updatedAt: new Date('2023-02-23T05:06:29.716Z'),
|
||||
localDateTime: new Date('2023-02-23T05:06:29.716Z'),
|
||||
isFavorite: true,
|
||||
isArchived: false,
|
||||
duration: null,
|
||||
isVisible: true,
|
||||
isExternal: false,
|
||||
livePhotoVideo: null,
|
||||
livePhotoVideoId: null,
|
||||
tags: [],
|
||||
sharedLinks: [],
|
||||
originalFileName: 'asset-id.hif',
|
||||
faces: [],
|
||||
deletedAt: null,
|
||||
sidecarPath: null,
|
||||
|
|
Loading…
Reference in a new issue