mirror of
				https://github.com/immich-app/immich.git
				synced 2025-11-03 19:29:32 -05:00 
			
		
		
		
	fix(server): exif duration with scale (#4541)
This commit is contained in:
		
							parent
							
								
									5a7ef02387
								
							
						
					
					
						commit
						29182cfc9a
					
				@ -409,6 +409,54 @@ describe(MetadataService.name, () => {
 | 
				
			|||||||
        localDateTime: new Date('1970-01-01'),
 | 
					        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', () => {
 | 
					  describe('handleQueueSidecar', () => {
 | 
				
			||||||
 | 
				
			|||||||
@ -7,6 +7,7 @@ import { Duration } from 'luxon';
 | 
				
			|||||||
import { usePagination } from '../domain.util';
 | 
					import { usePagination } from '../domain.util';
 | 
				
			||||||
import { IBaseJob, IEntityJob, JOBS_ASSET_PAGINATION_SIZE, JobName, QueueName } from '../job';
 | 
					import { IBaseJob, IEntityJob, JOBS_ASSET_PAGINATION_SIZE, JobName, QueueName } from '../job';
 | 
				
			||||||
import {
 | 
					import {
 | 
				
			||||||
 | 
					  ExifDuration,
 | 
				
			||||||
  IAlbumRepository,
 | 
					  IAlbumRepository,
 | 
				
			||||||
  IAssetRepository,
 | 
					  IAssetRepository,
 | 
				
			||||||
  ICryptoRepository,
 | 
					  ICryptoRepository,
 | 
				
			||||||
@ -398,7 +399,11 @@ export class MetadataService {
 | 
				
			|||||||
    return bitsPerSample;
 | 
					    return bitsPerSample;
 | 
				
			||||||
  }
 | 
					  }
 | 
				
			||||||
 | 
					
 | 
				
			||||||
  private getDuration(seconds?: number): string {
 | 
					  private getDuration(seconds?: number | ExifDuration): string {
 | 
				
			||||||
    return Duration.fromObject({ seconds }).toFormat('hh:mm:ss.SSS');
 | 
					    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');
 | 
				
			||||||
  }
 | 
					  }
 | 
				
			||||||
}
 | 
					}
 | 
				
			||||||
 | 
				
			|||||||
@ -14,7 +14,12 @@ export interface ReverseGeocodeResult {
 | 
				
			|||||||
  city: string | null;
 | 
					  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;
 | 
					  ContentIdentifier?: string;
 | 
				
			||||||
  MotionPhoto?: number;
 | 
					  MotionPhoto?: number;
 | 
				
			||||||
  MotionPhotoVersion?: number;
 | 
					  MotionPhotoVersion?: number;
 | 
				
			||||||
@ -22,6 +27,7 @@ export interface ImmichTags extends Omit<Tags, 'FocalLength'> {
 | 
				
			|||||||
  MediaGroupUUID?: string;
 | 
					  MediaGroupUUID?: string;
 | 
				
			||||||
  ImagePixelDepth?: string;
 | 
					  ImagePixelDepth?: string;
 | 
				
			||||||
  FocalLength?: number;
 | 
					  FocalLength?: number;
 | 
				
			||||||
 | 
					  Duration?: number | ExifDuration;
 | 
				
			||||||
}
 | 
					}
 | 
				
			||||||
 | 
					
 | 
				
			||||||
export interface IMetadataRepository {
 | 
					export interface IMetadataRepository {
 | 
				
			||||||
 | 
				
			|||||||
		Loading…
	
	
			
			x
			
			
		
	
		Reference in New Issue
	
	Block a user