Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

fix(server): use correct file extension for motion photo videos #8659

Merged
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Jump to
Jump to file
Failed to load files.
Diff view
Diff view
11 changes: 11 additions & 0 deletions server/src/migrations/1715435221124-MotionAssetExtensionMP4.ts
Original file line number Diff line number Diff line change
@@ -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> {}
}
39 changes: 21 additions & 18 deletions server/src/services/metadata.service.spec.ts
Original file line number Diff line number Diff line change
Expand Up @@ -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, ''),
Expand All @@ -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, ''),
Expand All @@ -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,
Expand All @@ -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,
Expand All @@ -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,
Expand Down
2 changes: 1 addition & 1 deletion server/src/services/metadata.service.ts
Original file line number Diff line number Diff line change
Expand Up @@ -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`,
lukashass marked this conversation as resolved.
Show resolved Hide resolved
isVisible: false,
deviceAssetId: 'NONE',
deviceId: 'NONE',
Expand Down
16 changes: 16 additions & 0 deletions server/test/fixtures/asset.stub.ts
Original file line number Diff line number Diff line change
Expand Up @@ -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',
Expand Down