Skip to content

Conversation

@meesfrensel
Copy link
Contributor

@meesfrensel meesfrensel commented Nov 25, 2025

Description

Introduced in #23953, the copied metadata contains some pixel values referencing what part of the sphere is covered. These values have to be multplied by the ratio between preview and original size.

#23953 (comment)

Fixes #24333

How Has This Been Tested?

Tested on hte three test panoramas I use: one is (way) bigger than the preview size, one is a little smaller, and one is 'cropped' aka not covering the whole sphere.

Checklist:

  • I have performed a self-review of my own code
  • I have made corresponding changes to the documentation if applicable
  • I have no unrelated changes in the PR.
  • I have confirmed that any new dependencies are strictly necessary.
  • I have written tests for new code (if applicable)
  • I have followed naming conventions/patterns in the surrounding code
  • All code in src/services/ uses repositories implementations for database calls, filesystem operations, etc.
  • All code in src/repositories/ is pretty basic/simple and does not have any immich specific logic (that belongs in src/services/)

Please describe to which degree, if any, an LLM was used in creating this pull request.

...

@immich-push-o-matic
Copy link

immich-push-o-matic bot commented Nov 25, 2025

Label error. Requires exactly 1 of: changelog:.*. Found: 🗄️server. A maintainer will add the required label.

@meesfrensel meesfrensel force-pushed the fix/panorama-metadata branch 2 times, most recently from 320580e to 7b437af Compare November 26, 2025 10:07
@meesfrensel meesfrensel force-pushed the fix/panorama-metadata branch 2 times, most recently from f1f9798 to 9353105 Compare December 2, 2025 15:49
Copy link
Member

@mertalev mertalev left a comment

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

A few code style comments, but otherwise it looks right. I do want to get input from @bwees on this because image editing (not landed yet) can throw a serious curve ball here given the preview and full-size images can be cropped and rotated.


if (asset.exifInfo.projectionType === 'EQUIRECTANGULAR') {
const originalSize = asset.exifInfo.exifImageHeight;
if (asset.exifInfo.projectionType === 'EQUIRECTANGULAR' && originalSize) {
Copy link
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

It seems like it'd be cleaner if the logic inside this conditional were moved to a separate method.

Comment on lines 347 to 348
const previewTags = {} as Record<string, string | number | boolean>;
const fullsizeTags = {} as Record<string, string | number | boolean>;
Copy link
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

The preview and full-size handling seems the same aside from the scaling done for the preview (which could be a scaling of 1 for the full-size image). Could this be extracted to a helper method instead?

Copy link
Member

@mertalev mertalev left a comment

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Re: image editing, the consensus is for editing to not support panoramas in the first pass, so the earlier comment about crop/rotation isn't a blocker for this PR.

Comment on lines 361 to 367
if (fullsizePath) {
for (const key of PANORAMA_ALL) {
if (key in originalTags && originalTags[key]) {
fullsizeTags[key] = originalTags[key];
}
}
}
Copy link
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

This is assuming the full-size image has the same dimensions as the original, right? If we used the embedded preview above for the full-size image, then the dimensions could be different. sharp can return the output image dimensions from toBuffer, so using those values and scaling both is probably safer.

Copy link
Contributor Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

That's assumed, yes. I was not aware that the 'full size' image could be not-full size :o Good catch.

The ternary-based control flow confuses me quite a bit, but basically you're suggesting something like below right? And then propagate that to the (now extracted) helper method.

let fullsizePath: string | undefined;
+ let fullsizeSize: number | undefined;

if (convertFullsize) {
// convert a new fullsize image from the same source as the thumbnail
fullsizePath = StorageCore.getImagePath(asset, AssetPathType.FullSize, image.fullsize.format);
+ fullsizeSize = asset.exifInfo.exifImageHeight;
const fullsizeOptions = { format: image.fullsize.format, quality: image.fullsize.quality, ...thumbnailOptions };
promises.push(this.mediaRepository.generateThumbnail(data, fullsizeOptions, fullsizePath));
} else if (generateFullsize && extracted && extracted.format === RawExtractedFormat.Jpeg) {
fullsizePath = StorageCore.getImagePath(asset, AssetPathType.FullSize, extracted.format);
+ fullsizeSize = extracted.<somehow get the size here>;
this.storageCore.ensureFolders(fullsizePath);

One thing about this: I don't see a way to get the size of the extracted image buffer. It's not done with sharp, but exiftool's extractBinaryTagToBuffer which ofc only returns a buffer.

Copy link
Contributor Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Ok I found a way. Changes are in a separate second commit for easy reviewing. Let me know what you think!

@meesfrensel meesfrensel force-pushed the fix/panorama-metadata branch from 9353105 to 076cbfe Compare December 3, 2025 12:29
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Projects

None yet

Development

Successfully merging this pull request may close these issues.

360-degree photo preview is broken in the latest version 2.3.1. Disable preview generation for everyone?

2 participants