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:
parent
6d3421a505
commit
dfd6846deb
@ -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({
|
||||
|
@ -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);
|
||||
|
Loading…
Reference in New Issue
Block a user