1
0
mirror of https://github.com/immich-app/immich.git synced 2024-11-28 09:33:27 +02: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:
Lukas 2024-05-24 22:38:18 +02:00 committed by GitHub
parent 7168707395
commit f197f5d530
No known key found for this signature in database
GPG Key ID: B5690EEEBB952194
4 changed files with 49 additions and 19 deletions

View File

@ -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> {}
}

View File

@ -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,

View File

@ -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',

View File

@ -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',