fix(server): handle orientation of imported face regions (#18021)
* Transform imported face RegionInfo according to Exif Orientation * Add unit tests for re-orienting metadata face regions * Make code DRY using ImmichTagsWithFaces instead of NonNullable * Add e2e test for importing metadata face regions when orientation is RotateCW90 * Disable new e2e test until its asset is added to the test-assets project * Simplify unit tests by using the same face tag definition * Combine similar e2e tests * Disable new e2e test until portrait-orientation-6.jpg is added to test-assets * Fix lint error: Expected property shorthand * Update test-assets ref to latest * Enable new e2e test after updating test-assets
This commit is contained in:
@@ -596,6 +596,80 @@ export class MetadataService extends BaseService {
|
||||
);
|
||||
}
|
||||
|
||||
private orientRegionInfo(
|
||||
regionInfo: ImmichTagsWithFaces['RegionInfo'],
|
||||
orientation: ExifOrientation | undefined,
|
||||
): ImmichTagsWithFaces['RegionInfo'] {
|
||||
// skip default Orientation
|
||||
if (orientation === undefined || orientation === ExifOrientation.Horizontal) {
|
||||
return regionInfo;
|
||||
}
|
||||
|
||||
const isSidewards = [
|
||||
ExifOrientation.MirrorHorizontalRotate270CW,
|
||||
ExifOrientation.Rotate90CW,
|
||||
ExifOrientation.MirrorHorizontalRotate90CW,
|
||||
ExifOrientation.Rotate270CW,
|
||||
].includes(orientation);
|
||||
|
||||
// swap image dimensions in AppliedToDimensions if orientation is sidewards
|
||||
const adjustedAppliedToDimensions = isSidewards
|
||||
? {
|
||||
...regionInfo.AppliedToDimensions,
|
||||
W: regionInfo.AppliedToDimensions.H,
|
||||
H: regionInfo.AppliedToDimensions.W,
|
||||
}
|
||||
: regionInfo.AppliedToDimensions;
|
||||
|
||||
// update area coordinates and dimensions in RegionList assuming "normalized" unit as per MWG guidelines
|
||||
const adjustedRegionList = regionInfo.RegionList.map((region) => {
|
||||
let { X, Y, W, H } = region.Area;
|
||||
switch (orientation) {
|
||||
case ExifOrientation.MirrorHorizontal: {
|
||||
X = 1 - X;
|
||||
break;
|
||||
}
|
||||
case ExifOrientation.Rotate180: {
|
||||
[X, Y] = [1 - X, 1 - Y];
|
||||
break;
|
||||
}
|
||||
case ExifOrientation.MirrorVertical: {
|
||||
Y = 1 - Y;
|
||||
break;
|
||||
}
|
||||
case ExifOrientation.MirrorHorizontalRotate270CW: {
|
||||
[X, Y] = [Y, X];
|
||||
break;
|
||||
}
|
||||
case ExifOrientation.Rotate90CW: {
|
||||
[X, Y] = [1 - Y, X];
|
||||
break;
|
||||
}
|
||||
case ExifOrientation.MirrorHorizontalRotate90CW: {
|
||||
[X, Y] = [1 - Y, 1 - X];
|
||||
break;
|
||||
}
|
||||
case ExifOrientation.Rotate270CW: {
|
||||
[X, Y] = [Y, 1 - X];
|
||||
break;
|
||||
}
|
||||
}
|
||||
if (isSidewards) {
|
||||
[W, H] = [H, W];
|
||||
}
|
||||
return {
|
||||
...region,
|
||||
Area: { ...region.Area, X, Y, W, H },
|
||||
};
|
||||
});
|
||||
|
||||
return {
|
||||
...regionInfo,
|
||||
AppliedToDimensions: adjustedAppliedToDimensions,
|
||||
RegionList: adjustedRegionList,
|
||||
};
|
||||
}
|
||||
|
||||
private async applyTaggedFaces(
|
||||
asset: { id: string; ownerId: string; faces: AssetFace[]; originalPath: string },
|
||||
tags: ImmichTags,
|
||||
@@ -609,13 +683,16 @@ export class MetadataService extends BaseService {
|
||||
const existingNameMap = new Map(existingNames.map(({ id, name }) => [name.toLowerCase(), id]));
|
||||
const missing: (Insertable<Person> & { ownerId: string })[] = [];
|
||||
const missingWithFaceAsset: { id: string; ownerId: string; faceAssetId: string }[] = [];
|
||||
for (const region of tags.RegionInfo.RegionList) {
|
||||
|
||||
const adjustedRegionInfo = this.orientRegionInfo(tags.RegionInfo, tags.Orientation);
|
||||
const imageWidth = adjustedRegionInfo.AppliedToDimensions.W;
|
||||
const imageHeight = adjustedRegionInfo.AppliedToDimensions.H;
|
||||
|
||||
for (const region of adjustedRegionInfo.RegionList) {
|
||||
if (!region.Name) {
|
||||
continue;
|
||||
}
|
||||
|
||||
const imageWidth = tags.RegionInfo.AppliedToDimensions.W;
|
||||
const imageHeight = tags.RegionInfo.AppliedToDimensions.H;
|
||||
const loweredName = region.Name.toLowerCase();
|
||||
const personId = existingNameMap.get(loweredName) || this.cryptoRepository.randomUUID();
|
||||
|
||||
|
||||
Reference in New Issue
Block a user