mirror of
https://github.com/immich-app/immich.git
synced 2025-12-22 17:24:56 +03:00
fix(server): use stat instead of exifinfo for file date metadata (#17311)
* use stat instead of filecreatedate * update tests * unused import
This commit is contained in:
@@ -1,5 +1,6 @@
|
||||
import { BinaryField, ExifDateTime } from 'exiftool-vendored';
|
||||
import { randomBytes } from 'node:crypto';
|
||||
import { Stats } from 'node:fs';
|
||||
import { constants } from 'node:fs/promises';
|
||||
import { defaults } from 'src/config';
|
||||
import { AssetEntity } from 'src/entities/asset.entity';
|
||||
@@ -21,14 +22,8 @@ describe(MetadataService.name, () => {
|
||||
let mocks: ServiceMocks;
|
||||
|
||||
const mockReadTags = (exifData?: Partial<ImmichTags>, sidecarData?: Partial<ImmichTags>) => {
|
||||
exifData = {
|
||||
FileSize: '123456',
|
||||
FileCreateDate: '2024-01-01T00:00:00.000Z',
|
||||
FileModifyDate: '2024-01-01T00:00:00.000Z',
|
||||
...exifData,
|
||||
};
|
||||
mocks.metadata.readTags.mockReset();
|
||||
mocks.metadata.readTags.mockResolvedValueOnce(exifData);
|
||||
mocks.metadata.readTags.mockResolvedValueOnce(exifData ?? {});
|
||||
mocks.metadata.readTags.mockResolvedValueOnce(sidecarData ?? {});
|
||||
};
|
||||
|
||||
@@ -114,6 +109,17 @@ describe(MetadataService.name, () => {
|
||||
});
|
||||
|
||||
describe('handleMetadataExtraction', () => {
|
||||
beforeEach(() => {
|
||||
const time = new Date('2022-01-01T00:00:00.000Z');
|
||||
const timeMs = time.valueOf();
|
||||
mocks.storage.stat.mockResolvedValue({
|
||||
size: 123_456,
|
||||
mtime: time,
|
||||
mtimeMs: timeMs,
|
||||
birthtimeMs: timeMs,
|
||||
} as Stats);
|
||||
});
|
||||
|
||||
it('should handle an asset that could not be found', async () => {
|
||||
await expect(sut.handleMetadataExtraction({ id: assetStub.image.id })).resolves.toBe(JobStatus.FAILED);
|
||||
|
||||
@@ -145,10 +151,13 @@ describe(MetadataService.name, () => {
|
||||
const fileCreatedAt = new Date('2022-01-01T00:00:00.000Z');
|
||||
const fileModifiedAt = new Date('2021-01-01T00:00:00.000Z');
|
||||
mocks.asset.getByIds.mockResolvedValue([assetStub.image]);
|
||||
mockReadTags({
|
||||
FileCreateDate: fileCreatedAt.toISOString(),
|
||||
FileModifyDate: fileModifiedAt.toISOString(),
|
||||
});
|
||||
mocks.storage.stat.mockResolvedValue({
|
||||
size: 123_456,
|
||||
mtime: fileModifiedAt,
|
||||
mtimeMs: fileModifiedAt.valueOf(),
|
||||
birthtimeMs: fileCreatedAt.valueOf(),
|
||||
} as Stats);
|
||||
mockReadTags();
|
||||
|
||||
await sut.handleMetadataExtraction({ id: assetStub.image.id });
|
||||
expect(mocks.asset.getByIds).toHaveBeenCalledWith([assetStub.image.id], { faces: { person: false } });
|
||||
@@ -168,10 +177,13 @@ describe(MetadataService.name, () => {
|
||||
const fileCreatedAt = new Date('2021-01-01T00:00:00.000Z');
|
||||
const fileModifiedAt = new Date('2022-01-01T00:00:00.000Z');
|
||||
mocks.asset.getByIds.mockResolvedValue([assetStub.image]);
|
||||
mockReadTags({
|
||||
FileCreateDate: fileCreatedAt.toISOString(),
|
||||
FileModifyDate: fileModifiedAt.toISOString(),
|
||||
});
|
||||
mocks.storage.stat.mockResolvedValue({
|
||||
size: 123_456,
|
||||
mtime: fileModifiedAt,
|
||||
mtimeMs: fileModifiedAt.valueOf(),
|
||||
birthtimeMs: fileCreatedAt.valueOf(),
|
||||
} as Stats);
|
||||
mockReadTags();
|
||||
|
||||
await sut.handleMetadataExtraction({ id: assetStub.image.id });
|
||||
expect(mocks.asset.getByIds).toHaveBeenCalledWith([assetStub.image.id], { faces: { person: false } });
|
||||
@@ -206,10 +218,14 @@ describe(MetadataService.name, () => {
|
||||
|
||||
it('should handle lists of numbers', async () => {
|
||||
mocks.asset.getByIds.mockResolvedValue([assetStub.image]);
|
||||
mocks.storage.stat.mockResolvedValue({
|
||||
size: 123_456,
|
||||
mtime: assetStub.image.fileModifiedAt,
|
||||
mtimeMs: assetStub.image.fileModifiedAt.valueOf(),
|
||||
birthtimeMs: assetStub.image.fileCreatedAt.valueOf(),
|
||||
} as Stats);
|
||||
mockReadTags({
|
||||
ISO: [160],
|
||||
FileCreateDate: assetStub.image.fileCreatedAt.toISOString(),
|
||||
FileModifyDate: assetStub.image.fileModifiedAt.toISOString(),
|
||||
});
|
||||
|
||||
await sut.handleMetadataExtraction({ id: assetStub.image.id });
|
||||
@@ -228,11 +244,15 @@ describe(MetadataService.name, () => {
|
||||
mocks.asset.getByIds.mockResolvedValue([assetStub.withLocation]);
|
||||
mocks.systemMetadata.get.mockResolvedValue({ reverseGeocoding: { enabled: true } });
|
||||
mocks.map.reverseGeocode.mockResolvedValue({ city: 'City', state: 'State', country: 'Country' });
|
||||
mocks.storage.stat.mockResolvedValue({
|
||||
size: 123_456,
|
||||
mtime: assetStub.withLocation.fileModifiedAt,
|
||||
mtimeMs: assetStub.withLocation.fileModifiedAt.valueOf(),
|
||||
birthtimeMs: assetStub.withLocation.fileCreatedAt.valueOf(),
|
||||
} as Stats);
|
||||
mockReadTags({
|
||||
GPSLatitude: assetStub.withLocation.exifInfo!.latitude!,
|
||||
GPSLongitude: assetStub.withLocation.exifInfo!.longitude!,
|
||||
FileCreateDate: assetStub.withLocation.fileCreatedAt.toISOString(),
|
||||
FileModifyDate: assetStub.withLocation.fileModifiedAt.toISOString(),
|
||||
});
|
||||
|
||||
await sut.handleMetadataExtraction({ id: assetStub.image.id });
|
||||
@@ -475,6 +495,12 @@ describe(MetadataService.name, () => {
|
||||
|
||||
it('should extract the MotionPhotoVideo tag from Samsung HEIC motion photos', async () => {
|
||||
mocks.asset.getByIds.mockResolvedValue([{ ...assetStub.livePhotoWithOriginalFileName, livePhotoVideoId: null }]);
|
||||
mocks.storage.stat.mockResolvedValue({
|
||||
size: 123_456,
|
||||
mtime: assetStub.livePhotoWithOriginalFileName.fileModifiedAt,
|
||||
mtimeMs: assetStub.livePhotoWithOriginalFileName.fileModifiedAt.valueOf(),
|
||||
birthtimeMs: assetStub.livePhotoWithOriginalFileName.fileCreatedAt.valueOf(),
|
||||
} as Stats);
|
||||
mockReadTags({
|
||||
Directory: 'foo/bar/',
|
||||
MotionPhoto: 1,
|
||||
@@ -483,8 +509,6 @@ describe(MetadataService.name, () => {
|
||||
// instead of the EmbeddedVideoFile, since HEIC MotionPhotos include both
|
||||
EmbeddedVideoFile: new BinaryField(0, ''),
|
||||
EmbeddedVideoType: 'MotionPhoto_Data',
|
||||
FileCreateDate: assetStub.livePhotoWithOriginalFileName.fileCreatedAt.toISOString(),
|
||||
FileModifyDate: assetStub.livePhotoWithOriginalFileName.fileModifiedAt.toISOString(),
|
||||
});
|
||||
mocks.crypto.hashSha1.mockReturnValue(randomBytes(512));
|
||||
mocks.asset.create.mockResolvedValue(assetStub.livePhotoMotionAsset);
|
||||
@@ -525,14 +549,18 @@ describe(MetadataService.name, () => {
|
||||
});
|
||||
|
||||
it('should extract the EmbeddedVideo tag from Samsung JPEG motion photos', async () => {
|
||||
mocks.storage.stat.mockResolvedValue({
|
||||
size: 123_456,
|
||||
mtime: assetStub.livePhotoWithOriginalFileName.fileModifiedAt,
|
||||
mtimeMs: assetStub.livePhotoWithOriginalFileName.fileModifiedAt.valueOf(),
|
||||
birthtimeMs: assetStub.livePhotoWithOriginalFileName.fileCreatedAt.valueOf(),
|
||||
} as Stats);
|
||||
mocks.asset.getByIds.mockResolvedValue([{ ...assetStub.livePhotoWithOriginalFileName, livePhotoVideoId: null }]);
|
||||
mockReadTags({
|
||||
Directory: 'foo/bar/',
|
||||
EmbeddedVideoFile: new BinaryField(0, ''),
|
||||
EmbeddedVideoType: 'MotionPhoto_Data',
|
||||
MotionPhoto: 1,
|
||||
FileCreateDate: assetStub.livePhotoWithOriginalFileName.fileCreatedAt.toISOString(),
|
||||
FileModifyDate: assetStub.livePhotoWithOriginalFileName.fileModifiedAt.toISOString(),
|
||||
});
|
||||
mocks.crypto.hashSha1.mockReturnValue(randomBytes(512));
|
||||
mocks.asset.create.mockResolvedValue(assetStub.livePhotoMotionAsset);
|
||||
@@ -574,13 +602,17 @@ describe(MetadataService.name, () => {
|
||||
|
||||
it('should extract the motion photo video from the XMP directory entry ', async () => {
|
||||
mocks.asset.getByIds.mockResolvedValue([{ ...assetStub.livePhotoWithOriginalFileName, livePhotoVideoId: null }]);
|
||||
mocks.storage.stat.mockResolvedValue({
|
||||
size: 123_456,
|
||||
mtime: assetStub.livePhotoWithOriginalFileName.fileModifiedAt,
|
||||
mtimeMs: assetStub.livePhotoWithOriginalFileName.fileModifiedAt.valueOf(),
|
||||
birthtimeMs: assetStub.livePhotoWithOriginalFileName.fileCreatedAt.valueOf(),
|
||||
} as Stats);
|
||||
mockReadTags({
|
||||
Directory: 'foo/bar/',
|
||||
MotionPhoto: 1,
|
||||
MicroVideo: 1,
|
||||
MicroVideoOffset: 1,
|
||||
FileCreateDate: assetStub.livePhotoWithOriginalFileName.fileCreatedAt.toISOString(),
|
||||
FileModifyDate: assetStub.livePhotoWithOriginalFileName.fileModifiedAt.toISOString(),
|
||||
});
|
||||
mocks.crypto.hashSha1.mockReturnValue(randomBytes(512));
|
||||
mocks.asset.create.mockResolvedValue(assetStub.livePhotoMotionAsset);
|
||||
|
||||
Reference in New Issue
Block a user