mirror of
https://github.com/immich-app/immich.git
synced 2024-12-28 22:51:59 +00:00
fix(server): handle numeric hierarchical subject values (#12949)
This commit is contained in:
parent
62a490eca2
commit
b6f871786c
3 changed files with 39 additions and 12 deletions
|
@ -7,7 +7,18 @@ export interface ExifDuration {
|
|||
Scale?: number;
|
||||
}
|
||||
|
||||
type TagsWithWrongTypes = 'FocalLength' | 'Duration' | 'Description' | 'ImageDescription' | 'RegionInfo';
|
||||
type StringOrNumber = string | number;
|
||||
|
||||
type TagsWithWrongTypes =
|
||||
| 'FocalLength'
|
||||
| 'Duration'
|
||||
| 'Description'
|
||||
| 'ImageDescription'
|
||||
| 'RegionInfo'
|
||||
| 'TagsList'
|
||||
| 'Keywords'
|
||||
| 'HierarchicalSubject'
|
||||
| 'ISO';
|
||||
export interface ImmichTags extends Omit<Tags, TagsWithWrongTypes> {
|
||||
ContentIdentifier?: string;
|
||||
MotionPhoto?: number;
|
||||
|
@ -20,10 +31,14 @@ export interface ImmichTags extends Omit<Tags, TagsWithWrongTypes> {
|
|||
EmbeddedVideoType?: string;
|
||||
EmbeddedVideoFile?: BinaryField;
|
||||
MotionPhotoVideo?: BinaryField;
|
||||
TagsList?: StringOrNumber[];
|
||||
HierarchicalSubject?: StringOrNumber[];
|
||||
Keywords?: StringOrNumber | StringOrNumber[];
|
||||
ISO?: number | number[];
|
||||
|
||||
// Type is wrong, can also be number.
|
||||
Description?: string | number;
|
||||
ImageDescription?: string | number;
|
||||
Description?: StringOrNumber;
|
||||
ImageDescription?: StringOrNumber;
|
||||
|
||||
// Extended properties for image regions, such as faces
|
||||
RegionInfo?: {
|
||||
|
|
|
@ -316,7 +316,7 @@ describe(MetadataService.name, () => {
|
|||
|
||||
it('should handle lists of numbers', async () => {
|
||||
assetMock.getByIds.mockResolvedValue([assetStub.image]);
|
||||
metadataMock.readTags.mockResolvedValue({ ISO: [160] as any });
|
||||
metadataMock.readTags.mockResolvedValue({ ISO: [160] });
|
||||
|
||||
await sut.handleMetadataExtraction({ id: assetStub.image.id });
|
||||
expect(assetMock.getByIds).toHaveBeenCalledWith([assetStub.image.id]);
|
||||
|
@ -411,7 +411,7 @@ describe(MetadataService.name, () => {
|
|||
|
||||
it('should extract tags from Keywords as a list with a number', async () => {
|
||||
assetMock.getByIds.mockResolvedValue([assetStub.image]);
|
||||
metadataMock.readTags.mockResolvedValue({ Keywords: ['Parent', 2024] as any[] });
|
||||
metadataMock.readTags.mockResolvedValue({ Keywords: ['Parent', 2024] });
|
||||
tagMock.upsertValue.mockResolvedValue(tagStub.parent);
|
||||
|
||||
await sut.handleMetadataExtraction({ id: assetStub.image.id });
|
||||
|
@ -467,6 +467,17 @@ describe(MetadataService.name, () => {
|
|||
expect(tagMock.upsertValue).toHaveBeenNthCalledWith(3, { userId: 'user-id', value: 'TagA', parent: undefined });
|
||||
});
|
||||
|
||||
it('should extract tags from HierarchicalSubject as a list with a number', async () => {
|
||||
assetMock.getByIds.mockResolvedValue([assetStub.image]);
|
||||
metadataMock.readTags.mockResolvedValue({ HierarchicalSubject: ['Parent', 2024] });
|
||||
tagMock.upsertValue.mockResolvedValue(tagStub.parent);
|
||||
|
||||
await sut.handleMetadataExtraction({ id: assetStub.image.id });
|
||||
|
||||
expect(tagMock.upsertValue).toHaveBeenCalledWith({ userId: 'user-id', value: 'Parent', parent: undefined });
|
||||
expect(tagMock.upsertValue).toHaveBeenCalledWith({ userId: 'user-id', value: '2024', parent: undefined });
|
||||
});
|
||||
|
||||
it('should extract ignore / characters in a HierarchicalSubject tag', async () => {
|
||||
assetMock.getByIds.mockResolvedValue([assetStub.image]);
|
||||
metadataMock.readTags.mockResolvedValue({ HierarchicalSubject: ['Mom/Dad'] });
|
||||
|
|
|
@ -11,6 +11,7 @@ import { SystemConfigCore } from 'src/cores/system-config.core';
|
|||
import { OnEmit } from 'src/decorators';
|
||||
import { AssetFaceEntity } from 'src/entities/asset-face.entity';
|
||||
import { AssetEntity } from 'src/entities/asset.entity';
|
||||
import { ExifEntity } from 'src/entities/exif.entity';
|
||||
import { PersonEntity } from 'src/entities/person.entity';
|
||||
import { AssetType, SourceType } from 'src/enum';
|
||||
import { IAlbumRepository } from 'src/interfaces/album.interface';
|
||||
|
@ -236,7 +237,7 @@ export class MetadataService {
|
|||
const { dateTimeOriginal, localDateTime, timeZone, modifyDate } = this.getDates(asset, exifTags);
|
||||
const { latitude, longitude, country, state, city } = await this.getGeo(exifTags, reverseGeocoding);
|
||||
|
||||
const exifData = {
|
||||
const exifData: Partial<ExifEntity> = {
|
||||
assetId: asset.id,
|
||||
|
||||
// dates
|
||||
|
@ -264,7 +265,7 @@ export class MetadataService {
|
|||
make: exifTags.Make ?? null,
|
||||
model: exifTags.Model ?? null,
|
||||
fps: validate(Number.parseFloat(exifTags.VideoFrameRate!)),
|
||||
iso: validate(exifTags.ISO),
|
||||
iso: validate(exifTags.ISO) as number,
|
||||
exposureTime: exifTags.ExposureTime ?? null,
|
||||
lensModel: exifTags.LensModel ?? null,
|
||||
fNumber: validate(exifTags.FNumber),
|
||||
|
@ -395,13 +396,13 @@ export class MetadataService {
|
|||
}
|
||||
|
||||
private async applyTagList(asset: AssetEntity, exifTags: ImmichTags) {
|
||||
const tags: Array<string | number> = [];
|
||||
const tags: string[] = [];
|
||||
if (exifTags.TagsList) {
|
||||
tags.push(...exifTags.TagsList);
|
||||
tags.push(...exifTags.TagsList.map(String));
|
||||
} else if (exifTags.HierarchicalSubject) {
|
||||
tags.push(
|
||||
...exifTags.HierarchicalSubject.map((tag) =>
|
||||
tag
|
||||
String(tag)
|
||||
// convert | to /
|
||||
.replaceAll('/', '<PLACEHOLDER>')
|
||||
.replaceAll('|', '/')
|
||||
|
@ -413,10 +414,10 @@ export class MetadataService {
|
|||
if (!Array.isArray(keywords)) {
|
||||
keywords = [keywords];
|
||||
}
|
||||
tags.push(...keywords);
|
||||
tags.push(...keywords.map(String));
|
||||
}
|
||||
|
||||
const results = await upsertTags(this.tagRepository, { userId: asset.ownerId, tags: tags.map(String) });
|
||||
const results = await upsertTags(this.tagRepository, { userId: asset.ownerId, tags });
|
||||
await this.tagRepository.upsertAssetTags({ assetId: asset.id, tagIds: results.map((tag) => tag.id) });
|
||||
}
|
||||
|
||||
|
|
Loading…
Reference in a new issue