1
0
mirror of https://github.com/immich-app/immich.git synced 2025-04-25 13:24:51 +02:00

chore: remove exif entity (#17499)

This commit is contained in:
Daniel Dietzler 2025-04-10 18:36:29 +02:00 committed by GitHub
parent 8aea07b750
commit 7a1e8ce6d8
No known key found for this signature in database
GPG Key ID: B5690EEEBB952194
10 changed files with 44 additions and 76 deletions

View File

@ -1,3 +1,5 @@
import { Selectable } from 'kysely';
import { Exif as DatabaseExif } from 'src/db';
import { AlbumUserRole, AssetStatus, AssetType, MemoryType, Permission, UserStatus } from 'src/enum';
import { OnThisDayData, UserMetadataItem } from 'src/types';
@ -189,6 +191,8 @@ export type Session = {
deviceType: string;
};
export type Exif = Omit<Selectable<DatabaseExif>, 'updatedAt' | 'updateId'>;
const userColumns = ['id', 'name', 'email', 'profileImagePath', 'profileChangedAt'] as const;
export const columns = {

View File

@ -1,5 +1,5 @@
import { ApiProperty } from '@nestjs/swagger';
import { ExifEntity } from 'src/entities/exif.entity';
import { Exif } from 'src/database';
export class ExifResponseDto {
make?: string | null = null;
@ -28,7 +28,7 @@ export class ExifResponseDto {
rating?: number | null = null;
}
export function mapExif(entity: ExifEntity): ExifResponseDto {
export function mapExif(entity: Exif): ExifResponseDto {
return {
make: entity.make,
model: entity.model,
@ -55,7 +55,7 @@ export function mapExif(entity: ExifEntity): ExifResponseDto {
};
}
export function mapSanitizedExif(entity: ExifEntity): ExifResponseDto {
export function mapSanitizedExif(entity: Exif): ExifResponseDto {
return {
fileSizeInByte: entity.fileSizeInByte ? Number.parseInt(entity.fileSizeInByte.toString()) : null,
orientation: entity.orientation,

View File

@ -1,12 +1,11 @@
import { DeduplicateJoinsPlugin, ExpressionBuilder, Kysely, SelectQueryBuilder, sql } from 'kysely';
import { jsonArrayFrom, jsonObjectFrom } from 'kysely/helpers/postgres';
import { Tag, User } from 'src/database';
import { Exif, Tag, User } from 'src/database';
import { DB } from 'src/db';
import { AlbumEntity } from 'src/entities/album.entity';
import { AssetFaceEntity } from 'src/entities/asset-face.entity';
import { AssetFileEntity } from 'src/entities/asset-files.entity';
import { AssetJobStatusEntity } from 'src/entities/asset-job-status.entity';
import { ExifEntity } from 'src/entities/exif.entity';
import { SharedLinkEntity } from 'src/entities/shared-link.entity';
import { StackEntity } from 'src/entities/stack.entity';
import { AssetFileType, AssetStatus, AssetType } from 'src/enum';
@ -47,7 +46,7 @@ export class AssetEntity {
livePhotoVideoId!: string | null;
originalFileName!: string;
sidecarPath!: string | null;
exifInfo?: ExifEntity;
exifInfo?: Exif;
tags?: Tag[];
sharedLinks!: SharedLinkEntity[];
albums?: AlbumEntity[];
@ -65,7 +64,9 @@ export type AssetEntityPlaceholder = AssetEntity & {
};
export function withExif<O>(qb: SelectQueryBuilder<DB, 'assets', O>) {
return qb.leftJoin('exif', 'assets.id', 'exif.assetId').select((eb) => eb.fn.toJson(eb.table('exif')).as('exifInfo'));
return qb
.leftJoin('exif', 'assets.id', 'exif.assetId')
.select((eb) => eb.fn.toJson(eb.table('exif')).$castTo<Exif>().as('exifInfo'));
}
export function withExifInner<O>(qb: SelectQueryBuilder<DB, 'assets', O>) {

View File

@ -1,36 +0,0 @@
import { AssetEntity } from 'src/entities/asset.entity';
export class ExifEntity {
asset?: AssetEntity;
assetId!: string;
updatedAt?: Date;
updateId?: string;
description!: string; // or caption
exifImageWidth!: number | null;
exifImageHeight!: number | null;
fileSizeInByte!: number | null;
orientation!: string | null;
dateTimeOriginal!: Date | null;
modifyDate!: Date | null;
timeZone!: string | null;
latitude!: number | null;
longitude!: number | null;
projectionType!: string | null;
city!: string | null;
livePhotoCID!: string | null;
autoStackId!: string | null;
state!: string | null;
country!: string | null;
make!: string | null;
model!: string | null;
lensModel!: string | null;
fNumber!: number | null;
focalLength!: number | null;
iso!: number | null;
exposureTime!: string | null;
profileDescription!: string | null;
colorspace!: string | null;
bitsPerSample!: number | null;
rating!: number | null;
fps?: number | null;
}

View File

@ -6,7 +6,7 @@ import fs from 'node:fs/promises';
import { Writable } from 'node:stream';
import sharp from 'sharp';
import { ORIENTATION_TO_SHARP_ROTATION } from 'src/constants';
import { ExifEntity } from 'src/entities/exif.entity';
import { Exif } from 'src/database';
import { Colorspace, LogLevel } from 'src/enum';
import { LoggingRepository } from 'src/repositories/logging.repository';
import {
@ -66,7 +66,7 @@ export class MediaRepository {
return true;
}
async writeExif(tags: Partial<ExifEntity>, output: string): Promise<boolean> {
async writeExif(tags: Partial<Exif>, output: string): Promise<boolean> {
try {
const tagsToWrite: WriteTags = {
ExifImageWidth: tags.exifImageWidth,

View File

@ -43,7 +43,7 @@ export class AssetService extends BaseService {
yearsAgo,
// TODO move this to clients
title: `${yearsAgo} year${yearsAgo > 1 ? 's' : ''} ago`,
assets: assets.map((asset) => mapAsset(asset as AssetEntity, { auth })),
assets: assets.map((asset) => mapAsset(asset as unknown as AssetEntity, { auth })),
};
});
}

View File

@ -1,8 +1,8 @@
import { OutputInfo } from 'sharp';
import { SystemConfig } from 'src/config';
import { Exif } from 'src/database';
import { AssetMediaSize } from 'src/dtos/asset-media.dto';
import { AssetEntity } from 'src/entities/asset.entity';
import { ExifEntity } from 'src/entities/exif.entity';
import {
AssetFileType,
AssetPathType,
@ -319,7 +319,7 @@ describe(MediaService.name, () => {
it('should generate P3 thumbnails for a wide gamut image', async () => {
mocks.asset.getById.mockResolvedValue({
...assetStub.image,
exifInfo: { profileDescription: 'Adobe RGB', bitsPerSample: 14 } as ExifEntity,
exifInfo: { profileDescription: 'Adobe RGB', bitsPerSample: 14 } as Exif,
});
const thumbhashBuffer = Buffer.from('a thumbhash', 'utf8');
mocks.media.generateThumbhash.mockResolvedValue(thumbhashBuffer);
@ -2608,47 +2608,47 @@ describe(MediaService.name, () => {
describe('isSRGB', () => {
it('should return true for srgb colorspace', () => {
const asset = { ...assetStub.image, exifInfo: { colorspace: 'sRGB' } as ExifEntity };
const asset = { ...assetStub.image, exifInfo: { colorspace: 'sRGB' } as Exif };
expect(sut.isSRGB(asset)).toEqual(true);
});
it('should return true for srgb profile description', () => {
const asset = { ...assetStub.image, exifInfo: { profileDescription: 'sRGB v1.31' } as ExifEntity };
const asset = { ...assetStub.image, exifInfo: { profileDescription: 'sRGB v1.31' } as Exif };
expect(sut.isSRGB(asset)).toEqual(true);
});
it('should return true for 8-bit image with no colorspace metadata', () => {
const asset = { ...assetStub.image, exifInfo: { bitsPerSample: 8 } as ExifEntity };
const asset = { ...assetStub.image, exifInfo: { bitsPerSample: 8 } as Exif };
expect(sut.isSRGB(asset)).toEqual(true);
});
it('should return true for image with no colorspace or bit depth metadata', () => {
const asset = { ...assetStub.image, exifInfo: {} as ExifEntity };
const asset = { ...assetStub.image, exifInfo: {} as Exif };
expect(sut.isSRGB(asset)).toEqual(true);
});
it('should return false for non-srgb colorspace', () => {
const asset = { ...assetStub.image, exifInfo: { colorspace: 'Adobe RGB' } as ExifEntity };
const asset = { ...assetStub.image, exifInfo: { colorspace: 'Adobe RGB' } as Exif };
expect(sut.isSRGB(asset)).toEqual(false);
});
it('should return false for non-srgb profile description', () => {
const asset = { ...assetStub.image, exifInfo: { profileDescription: 'sP3C' } as ExifEntity };
const asset = { ...assetStub.image, exifInfo: { profileDescription: 'sP3C' } as Exif };
expect(sut.isSRGB(asset)).toEqual(false);
});
it('should return false for 16-bit image with no colorspace metadata', () => {
const asset = { ...assetStub.image, exifInfo: { bitsPerSample: 16 } as ExifEntity };
const asset = { ...assetStub.image, exifInfo: { bitsPerSample: 16 } as Exif };
expect(sut.isSRGB(asset)).toEqual(false);
});
it('should return true for 16-bit image with sRGB colorspace', () => {
const asset = { ...assetStub.image, exifInfo: { colorspace: 'sRGB', bitsPerSample: 16 } as ExifEntity };
const asset = { ...assetStub.image, exifInfo: { colorspace: 'sRGB', bitsPerSample: 16 } as Exif };
expect(sut.isSRGB(asset)).toEqual(true);
});
it('should return true for 16-bit image with sRGB profile', () => {
const asset = { ...assetStub.image, exifInfo: { profileDescription: 'sRGB', bitsPerSample: 16 } as ExifEntity };
const asset = { ...assetStub.image, exifInfo: { profileDescription: 'sRGB', bitsPerSample: 16 } as Exif };
expect(sut.isSRGB(asset)).toEqual(true);
});
});

View File

@ -3,8 +3,8 @@ import { randomBytes } from 'node:crypto';
import { Stats } from 'node:fs';
import { constants } from 'node:fs/promises';
import { defaults } from 'src/config';
import { Exif } from 'src/database';
import { AssetEntity } from 'src/entities/asset.entity';
import { ExifEntity } from 'src/entities/exif.entity';
import { AssetType, ExifOrientation, ImmichWorker, JobName, JobStatus, SourceType } from 'src/enum';
import { WithoutProperty } from 'src/repositories/asset.repository';
import { ImmichTags } from 'src/repositories/metadata.repository';
@ -1190,7 +1190,7 @@ describe(MetadataService.name, () => {
mocks.asset.getByIds.mockResolvedValue([
{
...assetStub.livePhotoStillAsset,
exifInfo: { livePhotoCID: assetStub.livePhotoMotionAsset.id } as ExifEntity,
exifInfo: { livePhotoCID: assetStub.livePhotoMotionAsset.id } as Exif,
},
]);
mocks.asset.findLivePhotoMatch.mockResolvedValue(assetStub.livePhotoMotionAsset);

View File

@ -1,6 +1,6 @@
import { Exif } from 'src/database';
import { AssetFileEntity } from 'src/entities/asset-files.entity';
import { AssetEntity } from 'src/entities/asset.entity';
import { ExifEntity } from 'src/entities/exif.entity';
import { StackEntity } from 'src/entities/stack.entity';
import { AssetFileType, AssetStatus, AssetType } from 'src/enum';
import { StorageAsset } from 'src/types';
@ -128,7 +128,7 @@ export const assetStub = {
isExternal: false,
exifInfo: {
fileSizeInByte: 123_000,
} as ExifEntity,
} as Exif,
deletedAt: null,
duplicateId: null,
isOffline: false,
@ -202,7 +202,7 @@ export const assetStub = {
fileSizeInByte: 5000,
exifImageHeight: 1000,
exifImageWidth: 1000,
} as ExifEntity,
} as Exif,
stackId: 'stack-1',
stack: stackStub('stack-1', [
{ id: 'primary-asset-id' } as AssetEntity,
@ -247,7 +247,7 @@ export const assetStub = {
fileSizeInByte: 5000,
exifImageHeight: 3840,
exifImageWidth: 2160,
} as ExifEntity,
} as Exif,
duplicateId: null,
isOffline: false,
}),
@ -285,7 +285,7 @@ export const assetStub = {
fileSizeInByte: 5000,
exifImageHeight: 3840,
exifImageWidth: 2160,
} as ExifEntity,
} as Exif,
duplicateId: null,
isOffline: false,
status: AssetStatus.TRASHED,
@ -326,7 +326,7 @@ export const assetStub = {
fileSizeInByte: 5000,
exifImageHeight: 3840,
exifImageWidth: 2160,
} as ExifEntity,
} as Exif,
duplicateId: null,
isOffline: true,
}),
@ -364,7 +364,7 @@ export const assetStub = {
fileSizeInByte: 5000,
exifImageHeight: 3840,
exifImageWidth: 2160,
} as ExifEntity,
} as Exif,
duplicateId: null,
isOffline: false,
}),
@ -402,7 +402,7 @@ export const assetStub = {
sidecarPath: null,
exifInfo: {
fileSizeInByte: 5000,
} as ExifEntity,
} as Exif,
duplicateId: null,
isOffline: false,
}),
@ -439,7 +439,7 @@ export const assetStub = {
sidecarPath: null,
exifInfo: {
fileSizeInByte: 5000,
} as ExifEntity,
} as Exif,
duplicateId: null,
isOffline: false,
}),
@ -475,7 +475,7 @@ export const assetStub = {
sidecarPath: null,
exifInfo: {
fileSizeInByte: 5000,
} as ExifEntity,
} as Exif,
deletedAt: null,
duplicateId: null,
isOffline: false,
@ -514,7 +514,7 @@ export const assetStub = {
fileSizeInByte: 100_000,
exifImageHeight: 2160,
exifImageWidth: 3840,
} as ExifEntity,
} as Exif,
deletedAt: null,
duplicateId: null,
isOffline: false,
@ -605,7 +605,7 @@ export const assetStub = {
city: 'test-city',
state: 'test-state',
country: 'test-country',
} as ExifEntity,
} as Exif,
deletedAt: null,
duplicateId: null,
isOffline: false,
@ -710,7 +710,7 @@ export const assetStub = {
sidecarPath: null,
exifInfo: {
fileSizeInByte: 100_000,
} as ExifEntity,
} as Exif,
deletedAt: null,
duplicateId: null,
isOffline: false,
@ -749,7 +749,7 @@ export const assetStub = {
sidecarPath: null,
exifInfo: {
fileSizeInByte: 5000,
} as ExifEntity,
} as Exif,
duplicateId: null,
isOffline: false,
}),
@ -788,7 +788,7 @@ export const assetStub = {
fileSizeInByte: 5000,
profileDescription: 'Adobe RGB',
bitsPerSample: 14,
} as ExifEntity,
} as Exif,
duplicateId: null,
isOffline: false,
}),
@ -827,7 +827,7 @@ export const assetStub = {
fileSizeInByte: 5000,
profileDescription: 'Adobe RGB',
bitsPerSample: 14,
} as ExifEntity,
} as Exif,
duplicateId: null,
isOffline: false,
}),

View File

@ -232,7 +232,6 @@ export const sharedLinkStub = {
iso: 100,
exposureTime: '1/16',
fps: 100,
asset: null as any,
profileDescription: 'sRGB',
bitsPerSample: 8,
colorspace: 'sRGB',