edde746 2cb2c3eb95 feat(ratings): show every rating source the server already sent
Plezy rendered exactly one score per item. MediaRatingBadge._ratingDataFor
took `rating` and fell back to `audienceRating` only when it was null, so a
Plex movie carrying four attributed scores surfaced one, and which one was
whatever the server happened to put in the scalar slot. #1755 asked for a
setting to choose the source; showing all of them answers it without one.

The data was already on the wire and being thrown away. `/library/metadata/
{id}` returns a `Rating[]` child array — IMDb, both Rotten Tomatoes panels,
TMDB — with no extra query parameter, but PlexMetadataDto declared no field
for it, so json_serializable dropped the key. The identical parse already
existed in plex_catalog_source for the Explore tab and had simply never been
wired to library items.

Model the scores as a list rather than widening the scalar pair. The neutral
MediaItem gains `ratings`; PlexMediaItem loses audienceRating, ratingImage
and audienceRatingImage, which the list subsumes — Plex sends those images
on listings too, so the same field covers both response shapes and no caller
narrows to a backend type to read a score any more. CatalogRatingSource is
promoted to lib/media as MediaRatingSource instead of growing a second
near-identical type beside it, and plex_catalog_source's _ratingsFor becomes
the shared plexRatingSources so one implementation serves both paths. There
is no persistence to migrate: MediaItem.toJson has no production caller, the
offline path re-parses raw Plex JSON through the same mapper, and Plex's
audienceRating sort is server-supplied data, not a model read.

Cards and the dashboard still show fewer scores than detail screens, and
that part is a real Plex limit rather than a shortcut. Section listings send
only the scalar pair; includeRatings, includeElements=Rating,
includeFields=Rating, includeChildren and includeExtras were each probed
against a live server and none surfaced the array, while includeGuids=1
demonstrably does add Guid[] — the probe works, the parameter does not
exist. Hydrating every card would be one request per row, so listings render
whatever their own response carried, which is one or two attributed scores
rather than the single one they showed before.

Jellyfin has no per-source array at all: the server collapses whatever its
fetchers found into CommunityRating and CriticRating. CommunityRating's
provenance is unknowable from the DTO — TMDB vote_average, IMDb via OMDb or
a local NFO, last writer wins — so it stays the generic `audience` source
with no brand mark. CriticRating is the Rotten Tomatoes Tomatometer as a
0-100 percent and is divided by ten explicitly rather than folded by
magnitude, because a Tomatometer of 9 means 9% and range-sniffing would have
promoted a rotten score to fresh. Photo rows are skipped, since Jellyfin
reuses CommunityRating for the EXIF 0-5 star.

The badges share one slot on every surface. On the phone hero the scores go
in a single pill because that chip row is a height-clipped Wrap and a chip
per source would push year, certification and runtime out of the visible
band on short heroes; on the TV detail line and the dashboard spotlight the
group occupies the one metadata slot so bullet separators do not multiply.
The group announces itself as a single semantics node naming each source,
because a bare row of four percentages tells a screen reader nothing about
which score is which. rating_utils drops parseRatingImage and
isRottenTomatoes — the URI vocabulary now lives only in the Plex mapper —
and the source-key resolver and label map, previously private to the Explore
detail screen, become the shared pair both screens use. The label strings
move from explore.ratingSource to common.ratingSource accordingly, which
costs no translations because every non-English value was empty; running
clean_translations also scaffolds startup.quitPlezy and
startup.restartRequiredBody, which were already drifted.

Verified against the live server the probes came from: a detail response now
yields TMDB 83%, IMDb 8.3 and Rotten Tomatoes audience 96% through the
production mapper and badge resolver, and the listing response for the same
title yields TMDB 83% alone. Both payloads are pinned verbatim as fixtures.
Coverage adds mapper ordering, dedupe against the array's repeat of the
scalar, out-of-range rejection, the Jellyfin scale and photo guard, the
CatalogItem conversion that feeds Explore's dashboard hubs, and the three
render surfaces including the semantics announcement.

close #1755
2026-08-02 03:59:56 +02:00
2026-07-30 01:44:55 +00:00
2025-10-22 12:45:29 +02:00
2026-05-18 22:52:51 +02:00
2025-10-22 12:45:29 +02:00
2025-10-22 12:45:29 +02:00

Plezy Logo Plezy

A modern client for Plex and Jellyfin on desktop, mobile, and TV. Built with Flutter for native performance and a clean interface.

Website · Screenshots · Download · Contributing · License

Plezy mobile screenshots

Download

Download on the App Store Get it on Google Play Available at the Amazon App Store Get it from Microsoft

Platform Download
macOS DMG (x64, arm64)
Linux x64 .deb · .rpm · .pkg.tar.zst · portable tar.gz
Linux arm64 .deb · .rpm · .pkg.tar.zst · portable tar.gz

Package managers:

  • Nix - Community package by @mio-19 and @MiniHarinn
  • Homebrew (macOS):
    brew tap edde746/plezy https://github.com/edde746/plezy
    brew install --cask plezy
    
  • AUR (Arch Linux) - Community maintained by @jianglai:
    yay -S plezy-bin
    
  • WinGet (Windows):
    winget install edde746.Plezy
    

Features

Browse & Discover

  • Libraries, collections, and playlists — video and audio
  • Discover hub — Continue Watching, Next Up, trending, and recommendations
  • Cross-server search across every connected Plex and Jellyfin server
  • Filtering, sorting, and alphabetical jump navigation
  • Folder browsing and folder playback — home-video libraries open in folder view
  • Resolution, HDR/Dolby Vision, and audio-format badges on cards and detail pages
  • Favorites and unwatched library filters1
  • Extras — trailers, deleted scenes, behind-the-scenes

Explore & Requests

  • Explore tab — watchlist, trending, popular, and recommendation rows from Plex Discover2 , Trakt, MyAnimeList, AniList, Simkl, and Seerr3
  • Search any connected catalog source
  • Catalog titles matched back to your own libraries by external ID
  • Seerr — request movies and shows with per-season, 4K, and advanced destination options, and see request status inline
  • Watchlist sync — add and remove titles on Plex, Trakt, MyAnimeList, AniList, and Simkl from anywhere in the app

Playback

  • Wide codec support (HEVC, AV1, VP9, and more)
  • HDR and Dolby Vision4
  • Direct play, or transcode presets from 240p/320 kbps to 1080p/20 Mbps
  • Multi-version switching with per-version file details
  • Full ASS/SSA subtitles with customizable styling
  • Online subtitle search & download2
  • Audio & subtitle choices remembered per title, or follow the server's per-episode selections
  • Progress sync and resume
  • Auto-play next episode with skip intro / skip credits
  • Chapter navigation with thumbnail scrub previews
  • Playback speed from 0.25x to 8x, audio sync offset, sleep timer (fixed durations or end of video)
  • Video zoom 50-200% with pinch, presets, and hotkeys
  • Audio passthrough5 , stereo downmix with center-channel boost, and loudness normalization
  • File Info sheet — every version, file, and stream the server reports
  • Ambient lighting and GLSL shader presets6
  • Picture-in-Picture7
  • Refresh-rate matching8
  • External player launch (VLC, MX Player, etc.) with progress sync back9

Music

  • Music libraries — artist, album, and track browsing with square artwork
  • Album and artist screens with play, shuffle, and Instant Mix
  • Gapless playback with a full play queue — reorder, remove, play next, add to queue
  • Now Playing with synced lyrics10 , persistent mini-player, and sleep timer
  • Background playback with lock-screen, media-key, and notification controls11
  • Offline playback of downloaded albums and tracks
  • Streaming quality presets — Original, 320, 192, or 128 kbps

Live TV & DVR

  • Live TV channel browsing, tuning, and favorites
  • EPG guide with What's On and per-show schedules
  • DVR recording rules, scheduled recordings, and a rememberable recording target library2
  • Multi-server Live TV support where available

Downloads & Offline

  • Download movies, shows, and music for offline playback12
  • Background queue with pause / resume
  • Sync rules for automatic downloads, with per-show "Include Specials"
  • Offline browsing with watch state sync-back on reconnect

Watch Together

  • Synchronized playback with friends
  • Real-time play / pause / seek sync

Integrations

  • Discord Rich Presence13
  • Trakt, MyAnimeList, AniList, and Simkl — ratings, watched sync, and real-time scrobbling14
  • Plezy Remote — control desktop and TV from mobile
  • Watch Next row and tvOS Top Shelf15

Platform & Customization

  • Desktop, mobile, and TV — full D-pad, keyboard, and gamepad support
  • Multiple servers at once — Plex and Jellyfin side by side
  • Profiles with per-profile downloads, watch state, and settings; Plex Home switching with PIN
  • Jellyfin local-server discovery, multiple URLs per server, and Quick Connect sign-in
  • TV layout options — corner spotlight backdrop, full-card artwork, and Force TV mode on desktop
  • Customizable keyboard shortcuts13
  • Metadata and artwork editing
  • Settings import/export
  • Localized in English plus 21 translations

Building from Source

Prerequisites

  • Flutter SDK 3.44.0+
  • A Plex account or Jellyfin server with user credentials

Setup

git clone https://github.com/edde746/plezy.git
cd plezy
flutter pub get
scripts/codegen.sh
flutter run

Code Generation

After modifying model classes or other generated sources:

scripts/codegen.sh

After modifying translations:

dart run slang

Local Checks

scripts/ci_checks.sh

To install the same pre-commit checks locally:

scripts/setup_hooks.sh

End-to-end tests (Android emulator plus a Dockerized Jellyfin fixture):

python3 scripts/run_maestro.py basic

Contributing

See CONTRIBUTING.md for development workflow, formatting, tests, and translation guidelines.

License

Plezy is licensed under GPL-3.0.

Acknowledgments


  1. Jellyfin only. ↩︎

  2. Plex only. ↩︎

  3. Requires connecting the service under Settings > Services. ↩︎

  4. In-app HDR toggle on Windows, macOS, iOS, and tvOS. Dolby Vision on Android and Apple TV. ↩︎

  5. Desktop, Android TV, and Apple TV. ↩︎

  6. Requires the mpv player backend — unavailable on iOS and tvOS, and Android defaults to ExoPlayer. ↩︎

  7. Android, iOS, and macOS — not on Android TV or Apple TV. ↩︎

  8. Windows, Android, and tvOS. ↩︎

  9. Progress sync on Android. ↩︎

  10. Where your server provides lyrics. ↩︎

  11. tvOS pauses music when the app is backgrounded. ↩︎

  12. Not available on tvOS. ↩︎

  13. Desktop only. ↩︎

  14. Real-time scrobbling on Trakt and Simkl; MyAnimeList and AniList update on completion. ↩︎

  15. Android TV / Fire TV and tvOS. ↩︎

S
Description
Modern cross-platform Plex & Jellyfin client built with Flutter
Readme GPL-3.0
223 MiB
Languages
Dart 73.8%
GLSL 13.3%
Kotlin 4.3%
C++ 2.3%
Python 1.7%
Other 4.3%