Skip to content

Repository files navigation

Quality Gate banner

Quality Gate

GitHub Release Jellyfin Version .NET License CI Tests codecov

Cap the resolution a Jellyfin user may be served

Quality Gate caps how tall a video a user can be served. The cap is measured against the media's real height, read from its video stream, so it holds whatever the file is called and survives a rename, a re-encode or a symlink.

Media above the cap is not hidden. It is served as a capped transcode, or as a lower-resolution version of the same item where one exists, and requests for the original file are refused.

It also, optionally, groups an encoded copy with its original so a single film shows two versions instead of appearing twice.

Documentation

Guide What it covers
Getting started Install, cap one user at 720p, and prove the cap holds
Installation Every install method, upgrading, building from source, releases
Configuration Every setting, how a user's policy is chosen, and the fields that do not restrict playback
How it works The routes covered, the one that is not, and what to trust
One library, two qualities Keeping a smaller encode beside each original
Troubleshooting When it is not behaving

Read this before configuring anything

Quality Gate began as a filename-pattern filter. Since 3.4.0.0 those patterns enforce nothing.

The port to Jellyfin 12 stopped registering MediaSourceResultFilter, the component that read them. The fields are still in the config page and still saved, but no code consults them for playback. The same goes for the fallback-transcode and blocked-message settings.

Maximum Resolution is the only setting that restricts playback. A policy that blocks - 2160p by filename pattern and leaves the resolution at No limit restricts nobody. If you are coming from 3.3.x or earlier, translate your patterns into a height.

Configuration lists them exactly.

What it does

  • A measured resolution cap. 480p, 720p, 1080p, 1440p or 4K, checked against the item's video stream rather than its name.
  • Both delivery paths. It shapes playback negotiation, and refuses the direct stream, HLS, universal and original-file routes that skip negotiation entirely. One legacy HLS segment route is a known, documented exception.
  • Per-user policies. Assign policies individually, set a default, or mark a user explicitly unrestricted.
  • Version grouping. Optional, movies libraries only. Groups Film - 720p.mp4 with Film.mkv wherever the two sit, including a flat library root where Jellyfin would otherwise show two films. Applies to every movie library, or only the paths you list. Rebuilt on every scan, so it persists where a manual merge does not.
  • Per-policy intro videos. Optional. A different pre-roll for restricted users.
  • Logging you can debug from. Every decision names the cap, the user and the policy.

Requirements

Jellyfin 12, on net10.0. Version 3.4.0.0 and later will not load on Jellyfin 10.x; 3.3.6.0 is the last build for 10.11 and is unmaintained.

Install

Add this repository under Dashboard, Plugins, Repositories:

https://geiserx.github.io/quality-gate/manifest.json

Install from the catalogue, restart Jellyfin, then confirm the plugin shows Active. Full instructions, including manual installs, are in installation.

Quick start

  1. Dashboard, Plugins, QualityGate.
  2. Add a policy, name it, set Maximum Resolution to 720p. Leave the rest alone.
  3. Assign a user to it in the User Access table.
  4. Sign in as that user and play something in 1080p. Check Dashboard, Activity: the video being served must be at or below your cap. Both outcomes are correct, and which you get depends on the media. A capped transcode of the original, or direct play of a smaller version of the same item where one exists. Seeing "Direct playing" is not a failure by itself; being served something taller than the cap is.

Then verify it properly, because an unloaded plugin is indistinguishable from a working one that allows everything:

curl -s -o /dev/null -w '%{http_code}\n' \
  -H 'Authorization: MediaBrowser Token="<capped-user-token>"' \
  'https://your-server/Items/<item-id>/Download'

403 is correct for an item above the cap. Run it as an unrestricted user too and confirm 200, so you know the test can tell the two apart.

What this is not

This is not DRM. Quality Gate shapes what the Jellyfin API delivers. Somebody with filesystem access, or a copy they already downloaded, is out of scope.

There is also no fallback outside the plugin. If it fails to load, every user is unrestricted and Jellyfin reports nothing unusual. If you rely on the cap rather than on separate libraries, alert on the plugin not being Active. Troubleshooting covers a failure mode that does exactly that, silently.

Security

Quality Gate is access control, so review your policies deliberately. Only administrators can configure them. Report vulnerabilities through SECURITY.md.

One behaviour worth knowing: deleting or disabling a policy that users are assigned to currently grants those users full access rather than removing it. Point users at a low-cap policy instead. Configuration explains why.

Contributing

Pull requests are welcome. Fork, branch, and open a PR. CI has to be green and the patch covered. Installation has the build and test commands.

Other Jellyfin projects by GeiserX

  • smart-covers provides cover extraction for books, audiobooks, comics, magazines and music libraries, with online fallback
  • whisper-subs generates subtitles locally using Whisper
  • jellyfin-encoder does automatic 720p HEVC/AV1 transcoding, with optional symlinks for multi-version support
  • jellyfin-telegram-channel-sync syncs Jellyfin access with Telegram channel membership

License

GPL-3.0. See LICENSE.

Acknowledgments

  • Jellyfin, the Free Software Media System
  • The Jellyfin plugin development community

Releases

Sponsor this project

Packages

Used by

Contributors

Languages