5559cd333d
Stable twin of #1194. generateThumbnail, generateHeroImage and generatePreviewImage went straight from sharp(imagePath) to .resize(), so a photo whose Orientation tag is not 1 — routine for portrait shots on bodies that tag rather than rotate the sensor data — was resized from the raw frame and came out sideways. The same pipelines then call .withMetadata(false), stripping the tag from the output, so nothing downstream could correct it either. The download path already had this right, which is why the same photo looked correct on download and rotated in the gallery. watermarkService had it too, and it is the one a guest actually sees: gallery.js serves photos.watermark_path ahead of the original when branding watermarking is on. Two details there — metadata() is read from a separate unrotated handle, because .rotate() does not change what it reports and every use of those numbers is positioning; and the composite offsets are floored, because getPositionCoordinates returns fractional pixels, sharp rejects them, and applyWatermark catches its own error and silently returns the image unwatermarked. The rotate is unconditional in the thumbnail and hero generators — neither passes `animated: true`, so both already flatten a multi-frame source and guarding there would protect an animation that was being discarded anyway. generatePreviewImage keeps the guard, since it genuinely does preserve animation. photos.width/height were stored from sharp's metadata, which reports pixels as STORED, not displayed. For orientation 5-8 those are swapped, so a portrait photo landed in the database as landscape and masonry sized its tile with the wrong aspect ratio on top of the image being unrotated. A shared orientedDimensions() helper now does the conversion at all eight image ingest sites. The video path is deliberately untouched: its dimensions come from ffprobe, where EXIF orientation does not apply. Existing rows keep their pre-rotation dimensions until reprocessed; the backfill for those is #1199 on main and is not ported here yet. Co-authored-by: Paul Nothaft <paul@MacStudio-von-Paul.local>