1
0
mirror of https://github.com/immich-app/immich.git synced 2024-11-24 08:52:28 +02:00

fix(server): video orientation (#5455)

* fix: video orientation

* pr feedback
This commit is contained in:
martin 2023-12-03 23:34:23 +01:00 committed by GitHub
parent 6d3421a505
commit dfd6846deb
No known key found for this signature in database
GPG Key ID: 4AEE18F83AFDEB23
2 changed files with 55 additions and 1 deletions

View File

@ -5,11 +5,13 @@ import {
newAssetRepositoryMock,
newCryptoRepositoryMock,
newJobRepositoryMock,
newMediaRepositoryMock,
newMetadataRepositoryMock,
newMoveRepositoryMock,
newPersonRepositoryMock,
newStorageRepositoryMock,
newSystemConfigRepositoryMock,
probeStub,
} from '@test';
import { randomBytes } from 'crypto';
import { Stats } from 'fs';
@ -21,6 +23,7 @@ import {
IAssetRepository,
ICryptoRepository,
IJobRepository,
IMediaRepository,
IMetadataRepository,
IMoveRepository,
IPersonRepository,
@ -30,7 +33,7 @@ import {
WithProperty,
WithoutProperty,
} from '../repositories';
import { MetadataService } from './metadata.service';
import { MetadataService, Orientation } from './metadata.service';
describe(MetadataService.name, () => {
let albumMock: jest.Mocked<IAlbumRepository>;
@ -40,6 +43,7 @@ describe(MetadataService.name, () => {
let jobMock: jest.Mocked<IJobRepository>;
let metadataMock: jest.Mocked<IMetadataRepository>;
let moveMock: jest.Mocked<IMoveRepository>;
let mediaMock: jest.Mocked<IMediaRepository>;
let personMock: jest.Mocked<IPersonRepository>;
let storageMock: jest.Mocked<IStorageRepository>;
let sut: MetadataService;
@ -54,6 +58,7 @@ describe(MetadataService.name, () => {
moveMock = newMoveRepositoryMock();
personMock = newPersonRepositoryMock();
storageMock = newStorageRepositoryMock();
mediaMock = newMediaRepositoryMock();
sut = new MetadataService(
albumMock,
@ -63,6 +68,7 @@ describe(MetadataService.name, () => {
metadataMock,
storageMock,
configMock,
mediaMock,
moveMock,
personMock,
);
@ -277,6 +283,7 @@ describe(MetadataService.name, () => {
it('should not apply motion photos if asset is video', async () => {
assetMock.getByIds.mockResolvedValue([{ ...assetStub.livePhotoMotionAsset, isVisible: true }]);
mediaMock.probe.mockResolvedValue(probeStub.matroskaContainer);
await sut.handleMetadataExtraction({ id: assetStub.livePhotoMotionAsset.id });
expect(assetMock.getByIds).toHaveBeenCalledWith([assetStub.livePhotoMotionAsset.id]);
@ -287,6 +294,19 @@ describe(MetadataService.name, () => {
);
});
it('should extract the correct video orientation', async () => {
assetMock.getByIds.mockResolvedValue([assetStub.video]);
mediaMock.probe.mockResolvedValue(probeStub.videoStreamVertical2160p);
metadataMock.readTags.mockResolvedValue(null);
await sut.handleMetadataExtraction({ id: assetStub.video.id });
expect(assetMock.getByIds).toHaveBeenCalledWith([assetStub.video.id]);
expect(assetMock.upsertExif).toHaveBeenCalledWith(
expect.objectContaining({ orientation: Orientation.Rotate270CW }),
);
});
it('should apply motion photos', async () => {
assetMock.getByIds.mockResolvedValue([{ ...assetStub.livePhotoStillAsset, livePhotoVideoId: null }]);
metadataMock.readTags.mockResolvedValue({

View File

@ -14,6 +14,7 @@ import {
IAssetRepository,
ICryptoRepository,
IJobRepository,
IMediaRepository,
IMetadataRepository,
IMoveRepository,
IPersonRepository,
@ -49,6 +50,17 @@ interface DirectoryEntry {
Item: DirectoryItem;
}
export enum Orientation {
Horizontal = '1',
MirrorHorizontal = '2',
Rotate180 = '3',
MirrorVertical = '4',
MirrorHorizontalRotate270CW = '5',
Rotate90CW = '6',
MirrorHorizontalRotate90CW = '7',
Rotate270CW = '8',
}
type ExifEntityWithoutGeocodeAndTypeOrm = Omit<
ExifEntity,
'city' | 'state' | 'country' | 'description' | 'exifTextSearchableColumn'
@ -90,6 +102,7 @@ export class MetadataService {
@Inject(IMetadataRepository) private repository: IMetadataRepository,
@Inject(IStorageRepository) private storageRepository: IStorageRepository,
@Inject(ISystemConfigRepository) configRepository: ISystemConfigRepository,
@Inject(IMediaRepository) private mediaRepository: IMediaRepository,
@Inject(IMoveRepository) moveRepository: IMoveRepository,
@Inject(IPersonRepository) personRepository: IPersonRepository,
) {
@ -182,6 +195,27 @@ export class MetadataService {
const { exifData, tags } = await this.exifData(asset);
if (asset.type === AssetType.VIDEO) {
const { videoStreams } = await this.mediaRepository.probe(asset.originalPath);
if (videoStreams[0]) {
switch (videoStreams[0].rotation) {
case -90:
exifData.orientation = Orientation.Rotate90CW;
break;
case 0:
exifData.orientation = Orientation.Horizontal;
break;
case 90:
exifData.orientation = Orientation.Rotate270CW;
break;
case 180:
exifData.orientation = Orientation.Rotate180;
break;
}
}
}
await this.applyMotionPhotos(asset, tags);
await this.applyReverseGeocoding(asset, exifData);
await this.assetRepository.upsertExif(exifData);