https://github.com/geiserx/quality-gate
Jellyfin plugin to restrict users to specific media versions based on path-based policies
https://github.com/geiserx/quality-gate
4k access-control bandwidth csharp docker dotnet hacktoberfest homelab jellyfin jellyfin-plugin media-management media-server multi-version open-source quality-control self-hosted streaming transcoding user-management video
Last synced: 2 months ago
JSON representation
Jellyfin plugin to restrict users to specific media versions based on path-based policies
- Host: GitHub
- URL: https://github.com/geiserx/quality-gate
- Owner: GeiserX
- License: gpl-3.0
- Created: 2026-01-05T09:02:49.000Z (6 months ago)
- Default Branch: main
- Last Pushed: 2026-04-14T22:23:25.000Z (2 months ago)
- Last Synced: 2026-04-15T00:33:23.787Z (2 months ago)
- Topics: 4k, access-control, bandwidth, csharp, docker, dotnet, hacktoberfest, homelab, jellyfin, jellyfin-plugin, media-management, media-server, multi-version, open-source, quality-control, self-hosted, streaming, transcoding, user-management, video
- Language: C#
- Size: 227 KB
- Stars: 6
- Watchers: 1
- Forks: 0
- Open Issues: 1
-
Metadata Files:
- Readme: README.md
- Funding: .github/FUNDING.yml
- License: LICENSE
- Security: SECURITY.md
- Agents: AGENTS.md
Awesome Lists containing this project
README

Quality Gate
Intelligent media access control for Jellyfin
---
## Features
- **Filename Regex Patterns** -- Match against filenames with regex for [Jellyfin multi-version](https://jellyfin.org/docs/general/server/media/movies/#multiple-versions) setups
- **Per-User Assignments** -- Assign different policies to different users
- **Web Configuration** -- Easy-to-use admin interface in Jellyfin dashboard
- **Multi-Version Support** -- Seamlessly filter available media versions per user
- **Custom Intros** -- Optional intro video per policy (e.g. a "lite" branding for restricted users)
- **Dangling Symlink Protection** -- Sources whose files don't exist on disk are automatically hidden
- **Detailed Logging** -- Full audit trail of access decisions
## Use Cases
This plugin is designed for Jellyfin's [multi-version naming convention](https://jellyfin.org/docs/general/server/media/movies/#multiple-versions), where multiple quality versions of the same movie live together:
```text
movies/Movie (2021)/Movie (2021) - 2160p.mkv
movies/Movie (2021)/Movie (2021) - 1080p.mkv
movies/Movie (2021)/Movie (2021) - 720p.mkv
```
| Scenario | Solution |
|----------|----------|
| **Bandwidth Management** | Restrict remote users to lower-bitrate versions |
| **Tiered Access** | Premium users get 4K, standard users get 1080p |
| **Device Optimization** | Mobile users automatically get mobile-optimized versions |
## Installation
### Method 1: Plugin Repository (Recommended)
Add this repository to your Jellyfin instance for automatic updates:
1. Go to **Dashboard > Plugins > Repositories**
2. Click **Add** and enter:
- **Name**: `Quality Gate`
- **URL**: `https://geiserx.github.io/quality-gate/manifest.json`
3. Go to **Catalog** and install **Quality Gate**
4. Restart Jellyfin
### Method 2: Manual Installation
Docker
```bash
VERSION="3.2.0.0"
curl -L -o QualityGate.zip \
"https://github.com/GeiserX/quality-gate/releases/download/v${VERSION}/quality-gate_${VERSION}.zip"
unzip QualityGate.zip -d /path/to/jellyfin/plugins/QualityGate/
docker restart jellyfin
```
Or add to your `docker-compose.yml`:
```yaml
volumes:
- ./plugins/QualityGate:/config/plugins/QualityGate
```
Linux (Native)
```bash
VERSION="3.2.0.0"
curl -L -o QualityGate.zip \
"https://github.com/GeiserX/quality-gate/releases/download/v${VERSION}/quality-gate_${VERSION}.zip"
sudo unzip QualityGate.zip -d /var/lib/jellyfin/plugins/QualityGate/
sudo chown -R jellyfin:jellyfin /var/lib/jellyfin/plugins/QualityGate/
sudo systemctl restart jellyfin
```
Windows
1. Download the [latest release](https://github.com/GeiserX/quality-gate/releases/latest)
2. Extract to `%LOCALAPPDATA%\jellyfin\plugins\QualityGate\`
3. Restart Jellyfin from Services or the tray icon
macOS
```bash
VERSION="3.2.0.0"
curl -L -o QualityGate.zip \
"https://github.com/GeiserX/quality-gate/releases/download/v${VERSION}/quality-gate_${VERSION}.zip"
unzip QualityGate.zip -d ~/.local/share/jellyfin/plugins/QualityGate/
```
## Configuration
Navigate to **Dashboard > Quality Gate** to configure the plugin.
### Step 1: Create Policies
Policies define which filename patterns are allowed or blocked. Click **"Add Policy"** to create one.
| Field | Description |
|-------|-------------|
| **Policy Name** | A descriptive name (e.g., "720p Only", "No 4K") |
| **Allowed Filename Patterns** | Regex patterns matched against the filename. Files must match at least one pattern. |
| **Blocked Filename Patterns** | Regex patterns matched against the filename. Matching files are always blocked. |
| **Custom Intro Video** | Optional intro video for users under this policy. Disable the built-in "Local Intros" plugin if you only want Quality Gate intros. |
| **Enabled** | Toggle policy on/off |
### Step 2: Set Default Policy
Choose a policy from the **Default Policy** dropdown. This applies to ALL users who don't have a specific override.
- Select **(No default -- Full Access)** to allow unrestricted access by default
- Select a policy to restrict all users by default
### Step 3: Configure User Access
The **User Access** table shows all Jellyfin users and their current policy:
- **Use Default** -- inherits the default policy
- **Full Access** -- no restrictions
- Any named policy -- applies that policy's rules
If an override or the default policy points to a deleted or disabled policy, the dropdown shows **DENIED** until you choose a replacement (fail-closed). This applies to both per-user overrides and the default policy.
### Policy Logic
Evaluation order:
1. **Blocked Filename Patterns**: If filename matches any blocked regex -- **BLOCKED**
2. **Allowed Filename Patterns**: If defined and filename doesn't match any -- **BLOCKED**
3. **File existence**: If the file doesn't exist on disk (dangling symlink) -- **BLOCKED**
4. Otherwise -- **ALLOWED**
| Allowed Pattern | Blocked Pattern | Filename | Result |
|-----------------|-----------------|----------|--------|
| `- 720p` | -- | `Movie (2021) - 720p.mkv` | Allowed |
| `- 720p` | -- | `Movie (2021) - 2160p.mkv` | Blocked |
| (empty) | `- 2160p\|- 4K` | `Movie (2021) - 1080p.mkv` | Allowed |
| (empty) | `- 2160p\|- 4K` | `Movie (2021) - 2160p.mkv` | Blocked |
> **Tip**: Patterns are case-insensitive regex with a 1-second timeout to prevent ReDoS. Jellyfin also supports bracketed labels (e.g. `Movie (2021) - [1080p].mkv`), so use `\[?1080p\]?` to match both formats.
---
## Examples
### Restrict to 720p Only
```text
Policy Name: 720p Only
Allowed Filename Patterns:
- 720p
```
Only files with `- 720p` in the filename are visible.
### Block 4K Content
```text
Policy Name: No 4K
Blocked Filename Patterns:
- 2160p
- 4K
```
Everything is visible except 4K versions.
### Standard Quality (1080p max)
```text
Policy Name: Standard
Blocked Filename Patterns:
- 2160p
- 4K
- Remux
```
### Tiered Access
1. Create **"Standard"** policy (block 4K as above)
2. Set **Default Policy** to "Standard"
3. Add **Full Access** overrides for premium users
---
## How It Works
1. **Result Filter**: The plugin uses an ASP.NET Core `IAsyncResultFilter` that intercepts API responses **before serialization**, operating on C# objects directly.
2. **MediaSource Filtering**: When Jellyfin returns media sources/versions to the client, the filter removes blocked versions so they don't appear in the UI.
3. **Filename Matching**: Each media version's filename is matched against your policy's regex patterns. For symlinked files, both the symlink filename and the resolved target filename are checked.
4. **File Existence**: Sources whose files don't exist on disk (e.g. dangling symlinks from in-progress transcodes) are automatically hidden, preventing playback errors.
### Library Setup
All quality versions must be in the **same Jellyfin library** using Jellyfin's [multi-version naming](https://jellyfin.org/docs/general/server/media/movies/#multiple-versions). Each version needs a ` - label` suffix (space, hyphen, space, label):
```text
movies/
Movie (2021)/
Movie (2021) - 2160p.mkv
Movie (2021) - 1080p.mkv
Movie (2021) - 720p.mkv
```
Jellyfin merges these into a single item with multiple MediaSources. The plugin then filters which sources each user can see.
## Building from Source
### Prerequisites
- [.NET 9.0 SDK](https://dotnet.microsoft.com/download/dotnet/9.0)
- Git
### Build
```bash
git clone https://github.com/GeiserX/quality-gate.git
cd quality-gate/Jellyfin.Plugin.QualityGate
dotnet build -c Release
```
The compiled plugin will be in `bin/Release/net9.0/`.
## Security
- This plugin handles access control -- review your policies carefully
- Only administrators can configure policies
- See [SECURITY.md](SECURITY.md) for vulnerability reporting
## Contributing
Contributions are welcome! Please feel free to submit a Pull Request.
1. Fork the repository
2. Create your feature branch (`git checkout -b feature/AmazingFeature`)
3. Commit your changes (`git commit -m 'Add some AmazingFeature'`)
4. Push to the branch (`git push origin feature/AmazingFeature`)
5. Open a Pull Request
## Other Jellyfin Projects by GeiserX
- [smart-covers](https://github.com/GeiserX/smart-covers) -- Cover extraction for books, audiobooks, comics, magazines, and music libraries with online fallback
- [whisper-subs](https://github.com/GeiserX/whisper-subs) -- Automatically generates subtitles using local AI models powered by Whisper
- [jellyfin-encoder](https://github.com/GeiserX/jellyfin-encoder) -- Automatic 720p HEVC/AV1 transcoding service with optional symlink creation for Jellyfin multi-version support
- [jellyfin-telegram-channel-sync](https://github.com/GeiserX/jellyfin-telegram-channel-sync) -- Sync Jellyfin access with Telegram channel membership
## License
This project is licensed under the GPL-3.0 License -- see the [LICENSE](LICENSE) file for details.
## Acknowledgments
- [Jellyfin](https://jellyfin.org) -- The Free Software Media System
- The Jellyfin plugin development community
---
**[Back to Top](#quality-gate)**