Fixed resolution showing the raw cropped height (e.g. 804p) instead of the nominal class Plex/Jellyfin display (1080p) - letterboxed/cinemascope sources are shorter than their resolution tier, so classify off the width-derived 16:9 height instead
This commit is contained in:
1 parent
90fa9e8741
commit
a4bbed0cb8
2 files changed
+39
-2
No files matched your search
@@ -9,7 +9,12 @@ import { afterEach, beforeEach, describe, expect, test, vi } from "vitest";
|
||||
|
||||
import type { StreamSession } from "@homarr/integrations";
|
||||
|
||||
import { getMediaServerColumnVisibility, getSeasonEpisodeParams, SessionDetailsPopover } from "./component";
|
||||
import {
|
||||
getMediaServerColumnVisibility,
|
||||
getResolutionLabel,
|
||||
getSeasonEpisodeParams,
|
||||
SessionDetailsPopover,
|
||||
} from "./component";
|
||||
|
||||
vi.mock("@homarr/translation/client", () => ({
|
||||
useScopedI18n: () => (key: string) => key,
|
||||
@@ -124,3 +129,24 @@ describe("getSeasonEpisodeParams", () => {
|
||||
expect(getSeasonEpisodeParams(null, undefined)).toBeNull();
|
||||
});
|
||||
});
|
||||
|
||||
describe("getResolutionLabel", () => {
|
||||
test("labels a standard 16:9 resolution by its exact height", () => {
|
||||
expect(getResolutionLabel(1920, 1080)).toBe("1080p");
|
||||
expect(getResolutionLabel(1280, 720)).toBe("720p");
|
||||
});
|
||||
|
||||
test("classifies a cinemascope source by its width, not its cropped height", () => {
|
||||
// A 1920x804 encode (2.35:1, cropped top and bottom) is still a 1080p source.
|
||||
expect(getResolutionLabel(1920, 804)).toBe("1080p");
|
||||
expect(getResolutionLabel(1280, 640)).toBe("720p");
|
||||
});
|
||||
|
||||
test("rounds a near-standard resolution up to its nearest tier", () => {
|
||||
expect(getResolutionLabel(1912, 796)).toBe("1080p");
|
||||
});
|
||||
|
||||
test("falls back to the raw height when it doesn't clear any known tier", () => {
|
||||
expect(getResolutionLabel(100, 100)).toBe("100p");
|
||||
});
|
||||
});
|
||||
@@ -77,6 +77,17 @@ function formatBitrate(bitrateKbps: number | null | undefined): string | null {
|
||||
return bitrateKbps >= 1000 ? `${(bitrateKbps / 1000).toFixed(1)} Mbps` : `${Math.round(bitrateKbps)} kbps`;
|
||||
}
|
||||
|
||||
const RESOLUTION_TIER_HEIGHTS = [4320, 2160, 1440, 1080, 720, 480, 360, 240] as const;
|
||||
|
||||
export function getResolutionLabel(width: number, height: number): string {
|
||||
// Letterboxed/cinemascope sources are cropped shorter than their nominal resolution class
|
||||
// (a 1920x804 encode is still "1080p"), so classify off the width-derived 16:9 height instead
|
||||
// of the raw (possibly cropped) height - matches how Plex/Jellyfin label these themselves.
|
||||
const effectiveHeight = Math.max(height, Math.round((width * 9) / 16));
|
||||
const tier = RESOLUTION_TIER_HEIGHTS.find((candidate) => effectiveHeight >= candidate * 0.9);
|
||||
return `${tier ?? effectiveHeight}p`;
|
||||
}
|
||||
|
||||
export function getSeasonEpisodeParams(
|
||||
seasonNumber: number | null | undefined,
|
||||
episodeNumber: number | null | undefined,
|
||||
@@ -305,7 +316,7 @@ export default function MediaServerWidget({
|
||||
// touches audio/container and leaves the video resolution untouched).
|
||||
const resolution =
|
||||
currentlyPlaying?.metadata?.transcoding.resolution ?? currentlyPlaying?.metadata?.video.resolution;
|
||||
const resolutionLabel = resolution ? `${resolution.height}p` : null;
|
||||
const resolutionLabel = resolution ? getResolutionLabel(resolution.width, resolution.height) : null;
|
||||
const toggleDetails = () => setSelectedRowId((current) => (current === rowId ? null : rowId));
|
||||
|
||||
return (
|
||||
|
||||
Reference in new issue
Block a user