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:
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…
Reference in New Issue
Block a user