mirror of
https://github.com/immich-app/immich.git
synced 2025-01-04 02:46:47 +01:00
fix(server): override date via xmp (#5199)
* Fix * open api * Change to list and delete * Bug fix * Change name * refactor: clean up code and add test --------- Co-authored-by: Jason Rasmussen <jrasm91@gmail.com>
This commit is contained in:
parent
55fa3234fd
commit
0f657da5a4
2 changed files with 54 additions and 13 deletions
|
@ -14,6 +14,7 @@ import {
|
||||||
import { randomBytes } from 'crypto';
|
import { randomBytes } from 'crypto';
|
||||||
import { Stats } from 'fs';
|
import { Stats } from 'fs';
|
||||||
import { constants } from 'fs/promises';
|
import { constants } from 'fs/promises';
|
||||||
|
import { when } from 'jest-when';
|
||||||
import { JobName, QueueName } from '../job';
|
import { JobName, QueueName } from '../job';
|
||||||
import {
|
import {
|
||||||
IAlbumRepository,
|
IAlbumRepository,
|
||||||
|
@ -248,6 +249,30 @@ describe(MetadataService.name, () => {
|
||||||
expect(assetMock.save).not.toHaveBeenCalled();
|
expect(assetMock.save).not.toHaveBeenCalled();
|
||||||
});
|
});
|
||||||
|
|
||||||
|
it('should handle a date in a sidecar file', async () => {
|
||||||
|
const originalDate = new Date('2023-11-21T16:13:17.517Z');
|
||||||
|
const sidecarDate = new Date('2022-01-01T00:00:00.000Z');
|
||||||
|
assetMock.getByIds.mockResolvedValue([assetStub.sidecar]);
|
||||||
|
when(metadataMock.getExifTags)
|
||||||
|
.calledWith(assetStub.sidecar.originalPath)
|
||||||
|
// higher priority tag
|
||||||
|
.mockResolvedValue({ CreationDate: originalDate.toISOString() });
|
||||||
|
when(metadataMock.getExifTags)
|
||||||
|
.calledWith(assetStub.sidecar.sidecarPath as string)
|
||||||
|
// lower priority tag, but in sidecar
|
||||||
|
.mockResolvedValue({ CreateDate: sidecarDate.toISOString() });
|
||||||
|
|
||||||
|
await sut.handleMetadataExtraction({ id: assetStub.image.id });
|
||||||
|
expect(assetMock.getByIds).toHaveBeenCalledWith([assetStub.sidecar.id]);
|
||||||
|
expect(assetMock.upsertExif).toHaveBeenCalledWith(expect.objectContaining({ dateTimeOriginal: sidecarDate }));
|
||||||
|
expect(assetMock.save).toHaveBeenCalledWith({
|
||||||
|
id: assetStub.image.id,
|
||||||
|
duration: null,
|
||||||
|
fileCreatedAt: sidecarDate,
|
||||||
|
localDateTime: sidecarDate,
|
||||||
|
});
|
||||||
|
});
|
||||||
|
|
||||||
it('should handle lists of numbers', async () => {
|
it('should handle lists of numbers', async () => {
|
||||||
assetMock.getByIds.mockResolvedValue([assetStub.image]);
|
assetMock.getByIds.mockResolvedValue([assetStub.image]);
|
||||||
metadataMock.getExifTags.mockResolvedValue({ ISO: [160] as any });
|
metadataMock.getExifTags.mockResolvedValue({ ISO: [160] as any });
|
||||||
|
|
|
@ -25,6 +25,18 @@ import {
|
||||||
import { StorageCore } from '../storage';
|
import { StorageCore } from '../storage';
|
||||||
import { FeatureFlag, SystemConfigCore } from '../system-config';
|
import { FeatureFlag, SystemConfigCore } from '../system-config';
|
||||||
|
|
||||||
|
/** look for a date from these tags (in order) */
|
||||||
|
const EXIF_DATE_TAGS: Array<keyof Tags> = [
|
||||||
|
'SubSecDateTimeOriginal',
|
||||||
|
'DateTimeOriginal',
|
||||||
|
'SubSecCreateDate',
|
||||||
|
'CreationDate',
|
||||||
|
'CreateDate',
|
||||||
|
'SubSecMediaCreateDate',
|
||||||
|
'MediaCreateDate',
|
||||||
|
'DateTimeCreated',
|
||||||
|
];
|
||||||
|
|
||||||
interface DirectoryItem {
|
interface DirectoryItem {
|
||||||
Length?: number;
|
Length?: number;
|
||||||
Mime: string;
|
Mime: string;
|
||||||
|
@ -340,6 +352,15 @@ export class MetadataService {
|
||||||
const stats = await this.storageRepository.stat(asset.originalPath);
|
const stats = await this.storageRepository.stat(asset.originalPath);
|
||||||
const mediaTags = await this.repository.getExifTags(asset.originalPath);
|
const mediaTags = await this.repository.getExifTags(asset.originalPath);
|
||||||
const sidecarTags = asset.sidecarPath ? await this.repository.getExifTags(asset.sidecarPath) : null;
|
const sidecarTags = asset.sidecarPath ? await this.repository.getExifTags(asset.sidecarPath) : null;
|
||||||
|
|
||||||
|
// ensure date from sidecar is used if present
|
||||||
|
const hasDateOverride = !!this.getDateTimeOriginal(sidecarTags);
|
||||||
|
if (mediaTags && hasDateOverride) {
|
||||||
|
for (const tag of EXIF_DATE_TAGS) {
|
||||||
|
delete mediaTags[tag];
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
const tags = { ...mediaTags, ...sidecarTags };
|
const tags = { ...mediaTags, ...sidecarTags };
|
||||||
|
|
||||||
this.logger.verbose('Exif Tags', tags);
|
this.logger.verbose('Exif Tags', tags);
|
||||||
|
@ -350,19 +371,7 @@ export class MetadataService {
|
||||||
assetId: asset.id,
|
assetId: asset.id,
|
||||||
bitsPerSample: this.getBitsPerSample(tags),
|
bitsPerSample: this.getBitsPerSample(tags),
|
||||||
colorspace: tags.ColorSpace ?? null,
|
colorspace: tags.ColorSpace ?? null,
|
||||||
dateTimeOriginal:
|
dateTimeOriginal: this.getDateTimeOriginal(tags) ?? asset.fileCreatedAt,
|
||||||
exifDate(
|
|
||||||
firstDateTime(tags as Tags, [
|
|
||||||
'SubSecDateTimeOriginal',
|
|
||||||
'DateTimeOriginal',
|
|
||||||
'SubSecCreateDate',
|
|
||||||
'CreationDate',
|
|
||||||
'CreateDate',
|
|
||||||
'SubSecMediaCreateDate',
|
|
||||||
'MediaCreateDate',
|
|
||||||
'DateTimeCreated',
|
|
||||||
]),
|
|
||||||
) ?? asset.fileCreatedAt,
|
|
||||||
exifImageHeight: validate(tags.ImageHeight),
|
exifImageHeight: validate(tags.ImageHeight),
|
||||||
exifImageWidth: validate(tags.ImageWidth),
|
exifImageWidth: validate(tags.ImageWidth),
|
||||||
exposureTime: tags.ExposureTime ?? null,
|
exposureTime: tags.ExposureTime ?? null,
|
||||||
|
@ -387,6 +396,13 @@ export class MetadataService {
|
||||||
};
|
};
|
||||||
}
|
}
|
||||||
|
|
||||||
|
private getDateTimeOriginal(tags: ImmichTags | Tags | null) {
|
||||||
|
if (!tags) {
|
||||||
|
return null;
|
||||||
|
}
|
||||||
|
return exifDate(firstDateTime(tags as Tags, EXIF_DATE_TAGS));
|
||||||
|
}
|
||||||
|
|
||||||
private getBitsPerSample(tags: ImmichTags): number | null {
|
private getBitsPerSample(tags: ImmichTags): number | null {
|
||||||
const bitDepthTags = [
|
const bitDepthTags = [
|
||||||
tags.BitsPerSample,
|
tags.BitsPerSample,
|
||||||
|
|
Loading…
Reference in a new issue