mirror of
https://github.com/photoprism/photoprism.git
synced 2026-07-17 16:49:04 +00:00
This commit is contained in:
parent
4ab00fa32d
commit
d52554fc50
5 changed files with 35 additions and 13 deletions
|
|
@ -31,15 +31,21 @@ const EQUIRECTANGULAR_RATIO_TOLERANCE = 0.15;
|
|||
|
||||
// is360Equirectangular reports whether a slide/thumb model is equirectangular 360°
|
||||
// content that should open in the sphere viewer. An explicit "equirectangular"
|
||||
// projection is authoritative and any other non-empty projection (cubemap, …) is
|
||||
// never 360°. Many 360° videos carry no projection tag PhotoPrism can read, so a
|
||||
// panorama-flagged video with no projection falls back to equirectangular's defining
|
||||
// 2:1 frame — this keeps genuine spherical videos opening while cubemap (4:3, 6:1)
|
||||
// and ultrawide (~2.35:1) clips stay in the flat player.
|
||||
// projection is authoritative, but a full sphere is ~2:1: when the frame size is known
|
||||
// and clearly not 2:1 it stays in the flat viewer, since a partial/cylindrical panorama
|
||||
// tagged equirectangular (or promoted from GPano markers) would otherwise render as a
|
||||
// vertically distorted sphere. Any other non-empty projection (cubemap, …) is never 360°.
|
||||
// Many 360° videos carry no projection tag PhotoPrism can read, so a panorama-flagged
|
||||
// video with no projection falls back to the same 2:1 frame test — this keeps genuine
|
||||
// spherical videos opening while cubemap (4:3, 6:1) and ultrawide (~2.35:1) clips stay flat.
|
||||
export function is360Equirectangular(model) {
|
||||
const projection = (model?.Projection || "").toLowerCase();
|
||||
const w = Number(model?.Width);
|
||||
const h = Number(model?.Height);
|
||||
const known = w > 0 && h > 0;
|
||||
const is2to1 = known && Math.abs(w / h - 2) <= EQUIRECTANGULAR_RATIO_TOLERANCE;
|
||||
if (projection === "equirectangular") {
|
||||
return true;
|
||||
return !known || is2to1;
|
||||
}
|
||||
if (projection) {
|
||||
return false;
|
||||
|
|
@ -48,9 +54,7 @@ export function is360Equirectangular(model) {
|
|||
if (!isVideo || model?.Panorama !== true) {
|
||||
return false;
|
||||
}
|
||||
const w = Number(model?.Width);
|
||||
const h = Number(model?.Height);
|
||||
return w > 0 && h > 0 && Math.abs(w / h - 2) <= EQUIRECTANGULAR_RATIO_TOLERANCE;
|
||||
return is2to1;
|
||||
}
|
||||
|
||||
// createSphereViewer mounts a Photo Sphere Viewer instance for an equirectangular photo or video.
|
||||
|
|
|
|||
|
|
@ -672,7 +672,9 @@ export default {
|
|||
const isEquirect = is360Equirectangular(model);
|
||||
if (isEquirect && model?.Hash) {
|
||||
const isVideo = model?.Type === media.Video || model?.Type === media.Animated;
|
||||
const src = isVideo ? this.$util.videoUrl(model.Hash, model?.Codec, model?.Mime) : this.$util.thumb(model.Thumbs, 8192, 4096).src;
|
||||
// Request the largest available size so 360° photos stay crisp when zoomed; $util.thumb
|
||||
// clamps to the biggest size the server advertises (fit_15360 when the on-demand limit allows).
|
||||
const src = isVideo ? this.$util.videoUrl(model.Hash, model?.Codec, model?.Mime) : this.$util.thumb(model.Thumbs, 15360, 8640).src;
|
||||
return {
|
||||
type: "html",
|
||||
html: `<div class="pswp__html"></div>`,
|
||||
|
|
|
|||
|
|
@ -234,7 +234,7 @@
|
|||
<v-list-subheader class="pa-0">
|
||||
{{ $gettextInterpolate($gettext("Static Size Limit: %{n}px"), { n: parseInt(settings.ThumbSize) }) }}
|
||||
</v-list-subheader>
|
||||
<v-slider v-model="settings.ThumbSize" :min="720" :max="7680" :step="4" :disabled="isDemo" hide-details class="ma-0" @end="onChange"></v-slider>
|
||||
<v-slider v-model="settings.ThumbSize" :min="720" :max="15360" :step="4" :disabled="isDemo" hide-details class="ma-0" @end="onChange"></v-slider>
|
||||
</v-col>
|
||||
|
||||
<v-col cols="12" sm="6" lg="4" class="py-2">
|
||||
|
|
@ -248,7 +248,7 @@
|
|||
<v-slider
|
||||
v-model="settings.ThumbSizeUncached"
|
||||
:min="720"
|
||||
:max="7680"
|
||||
:max="15360"
|
||||
:step="4"
|
||||
:disabled="isDemo"
|
||||
hide-details
|
||||
|
|
|
|||
|
|
@ -27,10 +27,18 @@ vi.mock("@photo-sphere-viewer/video-plugin/index.css", () => ({}));
|
|||
import { createSphereViewer, destroySphereViewer, is360Equirectangular } from "common/sphere";
|
||||
|
||||
describe("common/sphere is360Equirectangular", () => {
|
||||
it("is true for an explicit equirectangular projection", () => {
|
||||
it("is true for an explicit equirectangular projection without known dimensions", () => {
|
||||
expect(is360Equirectangular({ Type: "image", Projection: "equirectangular" })).toBe(true);
|
||||
expect(is360Equirectangular({ Type: "video", Projection: "equirectangular" })).toBe(true);
|
||||
});
|
||||
it("is true for an explicit equirectangular projection with ~2:1 dimensions", () => {
|
||||
expect(is360Equirectangular({ Type: "image", Projection: "equirectangular", Width: 6656, Height: 3328 })).toBe(true);
|
||||
expect(is360Equirectangular({ Type: "image", Projection: "equirectangular", Width: 15520, Height: 7760 })).toBe(true);
|
||||
});
|
||||
it("is false for an equirectangular-tagged frame that is clearly not 2:1 (partial/cylindrical panorama)", () => {
|
||||
expect(is360Equirectangular({ Type: "image", Projection: "equirectangular", Width: 8192, Height: 1024 })).toBe(false);
|
||||
expect(is360Equirectangular({ Type: "video", Projection: "equirectangular", Width: 8192, Height: 1024 })).toBe(false);
|
||||
});
|
||||
it("is false for any other non-empty projection", () => {
|
||||
expect(is360Equirectangular({ Type: "video", Projection: "cubemap", Panorama: true, Width: 3840, Height: 1920 })).toBe(false);
|
||||
expect(is360Equirectangular({ Type: "image", Projection: "cubestrip" })).toBe(false);
|
||||
|
|
|
|||
|
|
@ -1284,6 +1284,14 @@ describe("PLightbox (low-mock, jsdom-friendly)", () => {
|
|||
expect(slide.isSphere).toBeUndefined();
|
||||
expect(slide.src).toBeTruthy();
|
||||
});
|
||||
it("does NOT mount the sphere for an equirectangular-tagged photo that is not 2:1 (partial panorama)", () => {
|
||||
const wrapper = mountLightbox();
|
||||
const model = { Hash: "strip", Projection: "equirectangular", Type: "image", Width: 8192, Height: 1024, Thumbs: baseThumbs, Title: "Wide" };
|
||||
const ctx = { ...wrapper.vm, models: [model], $util, getSlidePixels: () => ({ width: 2048, height: 1024 }) };
|
||||
|
||||
const slide = wrapper.vm.$options.methods.getItemData.call(ctx, null, 0);
|
||||
expect(slide.isSphere).toBeUndefined();
|
||||
});
|
||||
it("does NOT mount the sphere for a cubemap video even when Panorama is flagged", () => {
|
||||
const wrapper = mountLightbox();
|
||||
const model = { Hash: "cube", Projection: "cubemap", Panorama: true, Type: "video", Thumbs: baseThumbs, Codec: "avc1", Mime: "video/mp4", Playable: true };
|
||||
|
|
|
|||
Loading…
Add table
Add a link
Reference in a new issue