Przeglądaj źródła

fix(server): exif duration with scale (#4541)

Jason Rasmussen 1 rok temu
rodzic
commit
29182cfc9a

+ 48 - 0
server/src/domain/metadata/metadata.service.spec.ts

@@ -409,6 +409,54 @@ describe(MetadataService.name, () => {
         localDateTime: new Date('1970-01-01'),
       });
     });
+
+    it('should handle duration', async () => {
+      assetMock.getByIds.mockResolvedValue([assetStub.image]);
+      metadataMock.getExifTags.mockResolvedValue({ Duration: 6.21 });
+
+      await sut.handleMetadataExtraction({ id: assetStub.image.id });
+
+      expect(assetMock.getByIds).toHaveBeenCalledWith([assetStub.image.id]);
+      expect(assetMock.upsertExif).toHaveBeenCalled();
+      expect(assetMock.save).toHaveBeenCalledWith(
+        expect.objectContaining({
+          id: assetStub.image.id,
+          duration: '00:00:06.210',
+        }),
+      );
+    });
+
+    it('should handle duration as an object without Scale', async () => {
+      assetMock.getByIds.mockResolvedValue([assetStub.image]);
+      metadataMock.getExifTags.mockResolvedValue({ Duration: { Value: 6.2 } });
+
+      await sut.handleMetadataExtraction({ id: assetStub.image.id });
+
+      expect(assetMock.getByIds).toHaveBeenCalledWith([assetStub.image.id]);
+      expect(assetMock.upsertExif).toHaveBeenCalled();
+      expect(assetMock.save).toHaveBeenCalledWith(
+        expect.objectContaining({
+          id: assetStub.image.id,
+          duration: '00:00:06.200',
+        }),
+      );
+    });
+
+    it('should handle duration with scale', async () => {
+      assetMock.getByIds.mockResolvedValue([assetStub.image]);
+      metadataMock.getExifTags.mockResolvedValue({ Duration: { Scale: 1.11111111111111e-5, Value: 558720 } });
+
+      await sut.handleMetadataExtraction({ id: assetStub.image.id });
+
+      expect(assetMock.getByIds).toHaveBeenCalledWith([assetStub.image.id]);
+      expect(assetMock.upsertExif).toHaveBeenCalled();
+      expect(assetMock.save).toHaveBeenCalledWith(
+        expect.objectContaining({
+          id: assetStub.image.id,
+          duration: '00:00:06.207',
+        }),
+      );
+    });
   });
 
   describe('handleQueueSidecar', () => {

+ 7 - 2
server/src/domain/metadata/metadata.service.ts

@@ -7,6 +7,7 @@ import { Duration } from 'luxon';
 import { usePagination } from '../domain.util';
 import { IBaseJob, IEntityJob, JOBS_ASSET_PAGINATION_SIZE, JobName, QueueName } from '../job';
 import {
+  ExifDuration,
   IAlbumRepository,
   IAssetRepository,
   ICryptoRepository,
@@ -398,7 +399,11 @@ export class MetadataService {
     return bitsPerSample;
   }
 
-  private getDuration(seconds?: number): string {
-    return Duration.fromObject({ seconds }).toFormat('hh:mm:ss.SSS');
+  private getDuration(seconds?: number | ExifDuration): string {
+    let _seconds = seconds as number;
+    if (typeof seconds === 'object') {
+      _seconds = seconds.Value * (seconds?.Scale || 1);
+    }
+    return Duration.fromObject({ seconds: _seconds }).toFormat('hh:mm:ss.SSS');
   }
 }

+ 7 - 1
server/src/domain/repositories/metadata.repository.ts

@@ -14,7 +14,12 @@ export interface ReverseGeocodeResult {
   city: string | null;
 }
 
-export interface ImmichTags extends Omit<Tags, 'FocalLength'> {
+export interface ExifDuration {
+  Value: number;
+  Scale?: number;
+}
+
+export interface ImmichTags extends Omit<Tags, 'FocalLength' | 'Duration'> {
   ContentIdentifier?: string;
   MotionPhoto?: number;
   MotionPhotoVersion?: number;
@@ -22,6 +27,7 @@ export interface ImmichTags extends Omit<Tags, 'FocalLength'> {
   MediaGroupUUID?: string;
   ImagePixelDepth?: string;
   FocalLength?: number;
+  Duration?: number | ExifDuration;
 }
 
 export interface IMetadataRepository {