diff --git a/server/src/infra/repositories/media.repository.ts b/server/src/infra/repositories/media.repository.ts index 640b891a76..d94763433d 100644 --- a/server/src/infra/repositories/media.repository.ts +++ b/server/src/infra/repositories/media.repository.ts @@ -26,13 +26,16 @@ export class MediaRepository implements IMediaRepository { } async resize(input: string | Buffer, output: string, options: ResizeOptions): Promise { - const chromaSubsampling = options.quality >= 80 ? '4:4:4' : '4:2:0'; // this is default in libvips (except the threshold is 90), but we need to set it manually in sharp await sharp(input, { failOn: 'none' }) .pipelineColorspace(options.colorspace === Colorspace.SRGB ? 'srgb' : 'rgb16') .resize(options.size, options.size, { fit: 'outside', withoutEnlargement: true }) .rotate() - .withMetadata({ icc: options.colorspace }) - .toFormat(options.format, { quality: options.quality, chromaSubsampling }) + .withIccProfile(options.colorspace) + .toFormat(options.format, { + quality: options.quality, + // this is default in libvips (except the threshold is 90), but we need to set it manually in sharp + chromaSubsampling: options.quality >= 80 ? '4:4:4' : '4:2:0', + }) .toFile(output); } diff --git a/server/test/api/asset-api.ts b/server/test/api/asset-api.ts index e17e63a220..e40a8c150d 100644 --- a/server/test/api/asset-api.ts +++ b/server/test/api/asset-api.ts @@ -62,4 +62,18 @@ export const assetApi = { expect(status).toBe(201); return body as AssetFileUploadResponseDto; }, + getWebpThumbnail: async (server: any, accessToken: string, assetId: string) => { + const { body, status } = await request(server) + .get(`/asset/thumbnail/${assetId}`) + .set('Authorization', `Bearer ${accessToken}`); + expect(status).toBe(200); + return body; + }, + getJpegThumbnail: async (server: any, accessToken: string, assetId: string) => { + const { body, status } = await request(server) + .get(`/asset/thumbnail/${assetId}?format=JPEG`) + .set('Authorization', `Bearer ${accessToken}`); + expect(status).toBe(200); + return body; + }, }; diff --git a/server/test/e2e/metadata.e2e-spec.ts b/server/test/e2e/metadata.e2e-spec.ts new file mode 100644 index 0000000000..7ed5ff97d6 --- /dev/null +++ b/server/test/e2e/metadata.e2e-spec.ts @@ -0,0 +1,91 @@ +import { AssetResponseDto, LoginResponseDto } from '@app/domain'; +import { AssetController } from '@app/immich'; +import { INestApplication } from '@nestjs/common'; +import { api } from '@test/api'; +import * as fs from 'fs'; + +import { + IMMICH_TEST_ASSET_PATH, + IMMICH_TEST_ASSET_TEMP_PATH, + db, + itif, + restoreTempFolder, + runAllTests, + testApp, +} from '@test/test-utils'; +import { exiftool } from 'exiftool-vendored'; + +describe(`${AssetController.name} (e2e)`, () => { + let app: INestApplication; + let server: any; + let admin: LoginResponseDto; + + beforeAll(async () => { + app = await testApp.create({ jobs: true }); + server = app.getHttpServer(); + }); + + beforeEach(async () => { + await db.reset(); + await restoreTempFolder(); + await api.authApi.adminSignUp(server); + admin = await api.authApi.adminLogin(server); + }); + + afterAll(async () => { + await db.disconnect(); + await app.close(); + await restoreTempFolder(); + }); + + describe.only('should strip metadata of', () => { + let assetWithLocation: AssetResponseDto; + + beforeEach(async () => { + const fileContent = await fs.promises.readFile( + `${IMMICH_TEST_ASSET_PATH}/metadata/gps-position/thompson-springs.jpg`, + ); + + await api.assetApi.upload(server, admin.accessToken, 'test-asset-id', { content: fileContent }); + + const assets = await api.assetApi.getAllAssets(server, admin.accessToken); + + expect(assets).toHaveLength(1); + assetWithLocation = assets[0]; + + expect(assetWithLocation).toEqual( + expect.objectContaining({ + exifInfo: expect.objectContaining({ latitude: 39.115, longitude: -108.400968333333 }), + }), + ); + }); + + itif(runAllTests)('small webp thumbnails', async () => { + const assetId = assetWithLocation.id; + + const thumbnail = await api.assetApi.getWebpThumbnail(server, admin.accessToken, assetId); + + await fs.promises.writeFile(`${IMMICH_TEST_ASSET_TEMP_PATH}/thumbnail.webp`, thumbnail); + + const exifData = await exiftool.read(`${IMMICH_TEST_ASSET_TEMP_PATH}/thumbnail.webp`); + + expect(exifData).not.toHaveProperty('GPSLongitude'); + expect(exifData).not.toHaveProperty('GPSLatitude'); + }); + + itif(runAllTests)('large jpeg thumbnails', async () => { + const assetId = assetWithLocation.id; + + const thumbnail = await api.assetApi.getJpegThumbnail(server, admin.accessToken, assetId); + + await fs.promises.writeFile(`${IMMICH_TEST_ASSET_TEMP_PATH}/thumbnail.jpg`, thumbnail); + + const exifData = await exiftool.read(`${IMMICH_TEST_ASSET_TEMP_PATH}/thumbnail.jpg`); + + console.log(assetWithLocation); + + expect(exifData).not.toHaveProperty('GPSLongitude'); + expect(exifData).not.toHaveProperty('GPSLatitude'); + }); + }); +}); diff --git a/server/test/test-utils.ts b/server/test/test-utils.ts index c4167fb1c2..0a64132e41 100644 --- a/server/test/test-utils.ts +++ b/server/test/test-utils.ts @@ -112,6 +112,8 @@ export const testApp = { export const runAllTests: boolean = process.env.IMMICH_RUN_ALL_TESTS === 'true'; +export const itif = (condition: boolean) => (condition ? it : it.skip); + const directoryExists = async (dirPath: string) => await fs.promises .access(dirPath)