mirror of
				https://github.com/immich-app/immich.git
				synced 2025-11-04 03:39:37 -05:00 
			
		
		
		
	fix(server): use correct file extension for motion photo videos (#8659)
* fix(server): use mp4 file extension for motion photo videos in archive download * always use mp4 for videos * get file extension from originalPath * remove console log * store motion assets with mp4 extension * add migration * set originalFileName for live photo asset stubs * leave down migration empty * only set originalFileName for livePhotoStillAsset * use separate stub * shorter stub name
This commit is contained in:
		
							parent
							
								
									7168707395
								
							
						
					
					
						commit
						f197f5d530
					
				@ -0,0 +1,11 @@
 | 
			
		||||
import { MigrationInterface, QueryRunner } from 'typeorm';
 | 
			
		||||
 | 
			
		||||
export class MotionAssetExtensionMP41715435221124 implements MigrationInterface {
 | 
			
		||||
  public async up(queryRunner: QueryRunner): Promise<void> {
 | 
			
		||||
    await queryRunner.query(
 | 
			
		||||
      `UPDATE "assets" SET "originalFileName" = regexp_replace("originalFileName", '\\.[a-zA-Z0-9]+$', '.mp4') WHERE "originalPath" LIKE '%.mp4' AND "isVisible" = false`,
 | 
			
		||||
    );
 | 
			
		||||
  }
 | 
			
		||||
 | 
			
		||||
  public async down(): Promise<void> {}
 | 
			
		||||
}
 | 
			
		||||
@ -356,7 +356,7 @@ describe(MetadataService.name, () => {
 | 
			
		||||
    });
 | 
			
		||||
 | 
			
		||||
    it('should extract the MotionPhotoVideo tag from Samsung HEIC motion photos', async () => {
 | 
			
		||||
      assetMock.getByIds.mockResolvedValue([{ ...assetStub.livePhotoStillAsset, livePhotoVideoId: null }]);
 | 
			
		||||
      assetMock.getByIds.mockResolvedValue([{ ...assetStub.livePhotoWithOriginalFileName, livePhotoVideoId: null }]);
 | 
			
		||||
      metadataMock.readTags.mockResolvedValue({
 | 
			
		||||
        Directory: 'foo/bar/',
 | 
			
		||||
        MotionPhotoVideo: new BinaryField(0, ''),
 | 
			
		||||
@ -372,23 +372,23 @@ describe(MetadataService.name, () => {
 | 
			
		||||
      const video = randomBytes(512);
 | 
			
		||||
      metadataMock.extractBinaryTag.mockResolvedValue(video);
 | 
			
		||||
 | 
			
		||||
      await sut.handleMetadataExtraction({ id: assetStub.livePhotoStillAsset.id });
 | 
			
		||||
      await sut.handleMetadataExtraction({ id: assetStub.livePhotoWithOriginalFileName.id });
 | 
			
		||||
      expect(metadataMock.extractBinaryTag).toHaveBeenCalledWith(
 | 
			
		||||
        assetStub.livePhotoStillAsset.originalPath,
 | 
			
		||||
        assetStub.livePhotoWithOriginalFileName.originalPath,
 | 
			
		||||
        'MotionPhotoVideo',
 | 
			
		||||
      );
 | 
			
		||||
      expect(assetMock.getByIds).toHaveBeenCalledWith([assetStub.livePhotoStillAsset.id]);
 | 
			
		||||
      expect(assetMock.getByIds).toHaveBeenCalledWith([assetStub.livePhotoWithOriginalFileName.id]);
 | 
			
		||||
      expect(assetMock.create).toHaveBeenCalled(); // This could have arguments added
 | 
			
		||||
      expect(userMock.updateUsage).toHaveBeenCalledWith(assetStub.livePhotoMotionAsset.ownerId, 512);
 | 
			
		||||
      expect(storageMock.writeFile).toHaveBeenCalledWith(assetStub.livePhotoMotionAsset.originalPath, video);
 | 
			
		||||
      expect(assetMock.update).toHaveBeenNthCalledWith(1, {
 | 
			
		||||
        id: assetStub.livePhotoStillAsset.id,
 | 
			
		||||
        id: assetStub.livePhotoWithOriginalFileName.id,
 | 
			
		||||
        livePhotoVideoId: fileStub.livePhotoMotion.uuid,
 | 
			
		||||
      });
 | 
			
		||||
    });
 | 
			
		||||
 | 
			
		||||
    it('should extract the EmbeddedVideo tag from Samsung JPEG motion photos', async () => {
 | 
			
		||||
      assetMock.getByIds.mockResolvedValue([{ ...assetStub.livePhotoStillAsset, livePhotoVideoId: null }]);
 | 
			
		||||
      assetMock.getByIds.mockResolvedValue([{ ...assetStub.livePhotoWithOriginalFileName, livePhotoVideoId: null }]);
 | 
			
		||||
      metadataMock.readTags.mockResolvedValue({
 | 
			
		||||
        Directory: 'foo/bar/',
 | 
			
		||||
        EmbeddedVideoFile: new BinaryField(0, ''),
 | 
			
		||||
@ -401,23 +401,23 @@ describe(MetadataService.name, () => {
 | 
			
		||||
      const video = randomBytes(512);
 | 
			
		||||
      metadataMock.extractBinaryTag.mockResolvedValue(video);
 | 
			
		||||
 | 
			
		||||
      await sut.handleMetadataExtraction({ id: assetStub.livePhotoStillAsset.id });
 | 
			
		||||
      await sut.handleMetadataExtraction({ id: assetStub.livePhotoWithOriginalFileName.id });
 | 
			
		||||
      expect(metadataMock.extractBinaryTag).toHaveBeenCalledWith(
 | 
			
		||||
        assetStub.livePhotoStillAsset.originalPath,
 | 
			
		||||
        assetStub.livePhotoWithOriginalFileName.originalPath,
 | 
			
		||||
        'EmbeddedVideoFile',
 | 
			
		||||
      );
 | 
			
		||||
      expect(assetMock.getByIds).toHaveBeenCalledWith([assetStub.livePhotoStillAsset.id]);
 | 
			
		||||
      expect(assetMock.getByIds).toHaveBeenCalledWith([assetStub.livePhotoWithOriginalFileName.id]);
 | 
			
		||||
      expect(assetMock.create).toHaveBeenCalled(); // This could have arguments added
 | 
			
		||||
      expect(userMock.updateUsage).toHaveBeenCalledWith(assetStub.livePhotoMotionAsset.ownerId, 512);
 | 
			
		||||
      expect(storageMock.writeFile).toHaveBeenCalledWith(assetStub.livePhotoMotionAsset.originalPath, video);
 | 
			
		||||
      expect(assetMock.update).toHaveBeenNthCalledWith(1, {
 | 
			
		||||
        id: assetStub.livePhotoStillAsset.id,
 | 
			
		||||
        id: assetStub.livePhotoWithOriginalFileName.id,
 | 
			
		||||
        livePhotoVideoId: fileStub.livePhotoMotion.uuid,
 | 
			
		||||
      });
 | 
			
		||||
    });
 | 
			
		||||
 | 
			
		||||
    it('should extract the motion photo video from the XMP directory entry ', async () => {
 | 
			
		||||
      assetMock.getByIds.mockResolvedValue([{ ...assetStub.livePhotoStillAsset, livePhotoVideoId: null }]);
 | 
			
		||||
      assetMock.getByIds.mockResolvedValue([{ ...assetStub.livePhotoWithOriginalFileName, livePhotoVideoId: null }]);
 | 
			
		||||
      metadataMock.readTags.mockResolvedValue({
 | 
			
		||||
        Directory: 'foo/bar/',
 | 
			
		||||
        MotionPhoto: 1,
 | 
			
		||||
@ -431,20 +431,23 @@ describe(MetadataService.name, () => {
 | 
			
		||||
      const video = randomBytes(512);
 | 
			
		||||
      storageMock.readFile.mockResolvedValue(video);
 | 
			
		||||
 | 
			
		||||
      await sut.handleMetadataExtraction({ id: assetStub.livePhotoStillAsset.id });
 | 
			
		||||
      expect(assetMock.getByIds).toHaveBeenCalledWith([assetStub.livePhotoStillAsset.id]);
 | 
			
		||||
      expect(storageMock.readFile).toHaveBeenCalledWith(assetStub.livePhotoStillAsset.originalPath, expect.any(Object));
 | 
			
		||||
      await sut.handleMetadataExtraction({ id: assetStub.livePhotoWithOriginalFileName.id });
 | 
			
		||||
      expect(assetMock.getByIds).toHaveBeenCalledWith([assetStub.livePhotoWithOriginalFileName.id]);
 | 
			
		||||
      expect(storageMock.readFile).toHaveBeenCalledWith(
 | 
			
		||||
        assetStub.livePhotoWithOriginalFileName.originalPath,
 | 
			
		||||
        expect.any(Object),
 | 
			
		||||
      );
 | 
			
		||||
      expect(assetMock.create).toHaveBeenCalled(); // This could have arguments added
 | 
			
		||||
      expect(userMock.updateUsage).toHaveBeenCalledWith(assetStub.livePhotoMotionAsset.ownerId, 512);
 | 
			
		||||
      expect(storageMock.writeFile).toHaveBeenCalledWith(assetStub.livePhotoMotionAsset.originalPath, video);
 | 
			
		||||
      expect(assetMock.update).toHaveBeenNthCalledWith(1, {
 | 
			
		||||
        id: assetStub.livePhotoStillAsset.id,
 | 
			
		||||
        id: assetStub.livePhotoWithOriginalFileName.id,
 | 
			
		||||
        livePhotoVideoId: fileStub.livePhotoMotion.uuid,
 | 
			
		||||
      });
 | 
			
		||||
    });
 | 
			
		||||
 | 
			
		||||
    it('should delete old motion photo video assets if they do not match what is extracted', async () => {
 | 
			
		||||
      assetMock.getByIds.mockResolvedValue([assetStub.livePhotoStillAsset]);
 | 
			
		||||
      assetMock.getByIds.mockResolvedValue([assetStub.livePhotoWithOriginalFileName]);
 | 
			
		||||
      metadataMock.readTags.mockResolvedValue({
 | 
			
		||||
        Directory: 'foo/bar/',
 | 
			
		||||
        MotionPhoto: 1,
 | 
			
		||||
@ -457,10 +460,10 @@ describe(MetadataService.name, () => {
 | 
			
		||||
      const video = randomBytes(512);
 | 
			
		||||
      storageMock.readFile.mockResolvedValue(video);
 | 
			
		||||
 | 
			
		||||
      await sut.handleMetadataExtraction({ id: assetStub.livePhotoStillAsset.id });
 | 
			
		||||
      await sut.handleMetadataExtraction({ id: assetStub.livePhotoWithOriginalFileName.id });
 | 
			
		||||
      expect(jobMock.queue).toHaveBeenNthCalledWith(1, {
 | 
			
		||||
        name: JobName.ASSET_DELETION,
 | 
			
		||||
        data: { id: assetStub.livePhotoStillAsset.livePhotoVideoId },
 | 
			
		||||
        data: { id: assetStub.livePhotoWithOriginalFileName.livePhotoVideoId },
 | 
			
		||||
      });
 | 
			
		||||
      expect(jobMock.queue).toHaveBeenNthCalledWith(2, {
 | 
			
		||||
        name: JobName.METADATA_EXTRACTION,
 | 
			
		||||
 | 
			
		||||
@ -435,7 +435,7 @@ export class MetadataService {
 | 
			
		||||
          checksum,
 | 
			
		||||
          ownerId: asset.ownerId,
 | 
			
		||||
          originalPath: StorageCore.getAndroidMotionPath(asset, motionAssetId),
 | 
			
		||||
          originalFileName: asset.originalFileName,
 | 
			
		||||
          originalFileName: `${path.parse(asset.originalFileName).name}.mp4`,
 | 
			
		||||
          isVisible: false,
 | 
			
		||||
          deviceAssetId: 'NONE',
 | 
			
		||||
          deviceId: 'NONE',
 | 
			
		||||
 | 
			
		||||
							
								
								
									
										16
									
								
								server/test/fixtures/asset.stub.ts
									
									
									
									
										vendored
									
									
								
							
							
						
						
									
										16
									
								
								server/test/fixtures/asset.stub.ts
									
									
									
									
										vendored
									
									
								
							@ -486,6 +486,22 @@ export const assetStub = {
 | 
			
		||||
    },
 | 
			
		||||
  } as AssetEntity),
 | 
			
		||||
 | 
			
		||||
  livePhotoWithOriginalFileName: Object.freeze({
 | 
			
		||||
    id: 'live-photo-still-asset',
 | 
			
		||||
    originalPath: fileStub.livePhotoStill.originalPath,
 | 
			
		||||
    originalFileName: fileStub.livePhotoStill.originalName,
 | 
			
		||||
    ownerId: authStub.user1.user.id,
 | 
			
		||||
    type: AssetType.IMAGE,
 | 
			
		||||
    livePhotoVideoId: 'live-photo-motion-asset123',
 | 
			
		||||
    isVisible: true,
 | 
			
		||||
    fileModifiedAt: new Date('2022-06-19T23:41:36.910Z'),
 | 
			
		||||
    fileCreatedAt: new Date('2022-06-19T23:41:36.910Z'),
 | 
			
		||||
    exifInfo: {
 | 
			
		||||
      fileSizeInByte: 25_000,
 | 
			
		||||
      timeZone: `America/New_York`,
 | 
			
		||||
    },
 | 
			
		||||
  } as AssetEntity),
 | 
			
		||||
 | 
			
		||||
  withLocation: Object.freeze<AssetEntity>({
 | 
			
		||||
    id: 'asset-with-favorite-id',
 | 
			
		||||
    deviceAssetId: 'device-asset-id',
 | 
			
		||||
 | 
			
		||||
		Loading…
	
	
			
			x
			
			
		
	
		Reference in New Issue
	
	Block a user