Kuschel-code/JellyfinUpscalerPlugin

JellyfinUpscalerPlugin

C#

152

875 commits

updated Oct 4, 2026

See the code

README

Jellyfin AI Upscaler Plugin v1.8.3.36 — Jellyfin 12

Built with Claude Opus 5.5

Built with Claude Opus 5.5 — this plugin is developed with Anthropic's Claude models: Opus 5.5 since v1.8.3.33 (including the review of v1.8.3.31–v1.8.3.32 before it), Opus 5 for v1.8.3.13–v1.8.3.30, Fable 5 for v1.8.3.5–v1.8.3.12 and Opus 4.8 before that. v1.8.3.31 and v1.8.3.32 were prepared with OpenAI Codex. Code, Dockerfiles, CI workflows and documentation are written in a pair-programming style with the model; the maintainer (Kuschel-code) reviews, tests and publishes every change. Commits made with Claude carry a Co-Authored-By: Claude trailer as disclosure.


License: MIT Jellyfin Version Docker Hub Docker Image Documentation

AI-powered video upscaling for Jellyfin. Upscale SD content to HD/4K using neural networks, with AI inference in a Docker service and video decoding/encoding on the Jellyfin host.

Release status (2026-10-04): v1.8.3.36 is released for Jellyfin 12.0+ / .NET 10. Library jobs now wait out a busy AI service (HTTP 429/503) instead of failing the whole video, and the AI service explains why an Intel/AMD GPU is not used (see issue #90: pass the host's numeric render group ID, not the name render). Target-server acceptance was not run; actual playback, GPU and HDR target-hardware behavior remain unverified.

Previous release status (2026-09-26): v1.8.3.35 is released for Jellyfin 12.0+ / .NET 10. The player button no longer needs write access to Jellyfin's web folder, and since v1.8.3.34 real-time upscaling starts again in the web client. Jellyfin 10.11 users retain v1.8.3.31, which has both defects. Target-server acceptance was not run; actual playback, GPU and HDR target-hardware behavior remain unverified.

Fixed in v1.8.3.35 (reported in GitHub issue #75):

  • The player button never appeared when Jellyfin could not write its web client's index.html: on a default Windows install (the server runs as NetworkService under Program Files), with the Debian package, snaps and read-only containers. It showed only in a tab that had opened the plugin's settings. The plugin now adds its script to the page as Jellyfin serves it; the file on disk is left alone.
  • Behind a Jellyfin base URL (for example /jellyfin), the Lanczos, Anime4K and WebGPU real-time engines never loaded: they were requested without the base URL, and Jellyfin redirected the request to its start page. They now load relative to the web client.

Fixed in v1.8.3.34 (reported in GitHub issues #86 and #87 on 2026-09-24):

  • Real-time upscaling never started in the web client. Every video showed "Video color metadata unavailable; realtime processing cannot be validated" and the menu stayed on Standby. Since v1.8.3.31 the player looked for the playing item in the page address, where Jellyfin 10.9 and later no longer put it. It now reads the item from the video's stream address, or from the web client's playback request when Jellyfin transcodes, and checks the colour format of the version that actually plays.

Fixed in v1.8.3.33 (found in a review of v1.8.3.31–v1.8.3.32 on 2026-09-24):

  • Real-time upscaling refused some SDR videos with an "HDR" message, in every mode: DVD/SD rips tagged smpte170m/bt470bg and untagged 10-bit files. HDR now needs real evidence (a PQ/HLG transfer, an HDR range, dynamic metadata, or BT.2020 without an SDR transfer).
  • Library jobs refused untagged 10-bit files (common for anime encodes) with HDR requires PQ/ST.2084.
  • HDR batch output kept the model's colour but not its detail. It keeps the detail now, and the model gets a fuller-range frame to work on.
  • Server AI mode showed a slideshow on a slow server and froze the last frame during outages. A stale frame is hidden after 1.5 s now, and a server delivering under half the video's frame rate for 5 s hands over to Lanczos.

New in v1.8.3.33: a redesigned in-player menu (live frame-rate readout, filter previews on the playing frame, phone and TV layouts) and settings page (keyboard- and remote-friendly tabs).

Docker Images (released in lockstep with the plugin, both at v1.8.3.36): All seven regular version pins and docker7 tags are published and registry-verified, including ten platform configurations. NVIDIA latest also points to v1.8.3.35. The AI service itself is unchanged since v1.8.3.33. Release notes.

  • kuscheltier/jellyfin-ai-upscaler:docker7 (NVIDIA CUDA + cuDNN 9)
  • kuscheltier/jellyfin-ai-upscaler:docker7-amd (AMD ROCm)
  • kuscheltier/jellyfin-ai-upscaler:docker7-intel (Intel Arc/iGPU OpenVINO)
  • kuscheltier/jellyfin-ai-upscaler:docker7-apple (Apple Silicon Docker — CPU inference, multi-arch amd64/arm64)
  • kuscheltier/jellyfin-ai-upscaler:docker7-vulkan (Vulkan/ncnn — AMD pre-RDNA2, Intel iGPU)
  • kuscheltier/jellyfin-ai-upscaler:docker7-cpu (CPU Only — multi-threaded ONNXRuntime, multi-arch)
  • kuscheltier/jellyfin-ai-upscaler:docker7-converter (CPU + pth→ONNX converter for OpenModelDB community models — opt-in)

Download sizes range from 0.27 GB (docker7-cpu) to 20 GB (docker7-amd, ROCm base) — see docs/DOCKER-IMAGES.md for the full table, the converter's RAM guidance and why the AMD stack is frozen.

Report bugs: GitHub Issues

Contents: Architecture · How It Works · Installation · Features · AI Models · Configuration · Docker Tags · Changelog · Troubleshooting · Support


Download Jellyfin 12 release v1.8.3.35 (requires Jellyfin 12.0+, five runtime DLLs plus meta.json).

Architecture

Jellyfin's plugin system tries to load ALL .dll files as .NET assemblies. Native C++ libraries (ONNX Runtime, CUDA, OpenCV) caused BadImageFormatException crashes in older versions. The solution: a Docker microservice architecture where the plugin (only ~1.6 MB) communicates with an external AI container via HTTP.

┌──────────────────────────────────────────┐
│  Jellyfin Server                         │
│  ┌────────────────────────────────────┐  │
│  │  AI Upscaler Plugin v1.8.3.36   │  │
│  │  ~1.6 MB — No native DLLs         │  │
│  │  Sends frames via HTTP             │  │
│  └──────────────┬─────────────────────┘  │
└─────────────────┼────────────────────────┘
                  │ HTTP POST /upscale
                  ▼
┌──────────────────────────────────────────┐
│  AI Upscaler Docker Container            │
│  ┌────────────────────────────────────┐  │
│  │  Python + FastAPI + OpenCV DNN     │  │
│  │  CUDA / ROCm / OpenVINO / CPU     │  │
│  │  Real-ESRGAN, SPAN, SwinIR, DAT2 │  │
│  │  EDVR-M, RealBasicVSR, AnimeSR  │  │
│  │  EDSR, FSRCNN, ESPCN (76+ models) │  │
│  │  Web UI for Model Management      │  │
│  └────────────────────────────────────┘  │
└──────────────────────────────────────────┘

How It Works

The plugin supports four upscaling modes:

Pre-Upscaling (Batch Processing)

The Scheduled Task ("Scan & Upscale Library") runs daily at 3 AM and:

  1. Scans your library for videos below the configured resolution (default: 1080p)
  2. Skips already upscaled files (files with _upscaled suffix)
  3. Auto-selects the best model per video from genre (anime vs live-action), resolution, available multi-frame models and what your hardware can actually run — an over-budget pick is swapped for an affordable one with the reason written to the log, so a CPU-only NAS is never handed a full restoration net
  4. For each low-res video: extracts frames via FFmpeg → upscales through AI → reassembles into a new video
  5. With multi-frame VSR models (EDVR-M, RealBasicVSR, AnimeSR): uses 5-frame sliding window for temporal consistency
  6. Saves the upscaled version alongside the original (e.g., Movie_upscaled.mkv)

This is ideal for users without powerful servers — upscaling happens overnight.

Image Upscaling (NEW in v1.5.4.0)

The Scheduled Task ("Scan & Upscale Library Images") runs weekly on Sunday at 4 AM and:

  1. Scans all library items for low-resolution posters, backdrops, thumbnails, logos, and banners
  2. Uses different thresholds: posters < 600x900, backdrops < 1280x720
  3. Auto-scales: 4x for very low-res images, 2x otherwise
  4. Also available on-demand via POST /api/upscaler/upscale-images/{itemId}

Auto Mode — what it decides, and what it will not

Auto mode is on by default. For every video it answers three questions and tells you the answer:

It decidesFromWhat it will not do
Which modelContent type (genre), source resolution, whether multi-frame models are loaded, and the hardware class the AI service reportsOverride a model you selected yourself under Preferred Anime / Live-Action Model — that is annotated, never replaced
Which scaleThe source size. SD gets a full 4x restore; 720p and 1080p get 2x; a source that is already 4K is cleaned up, not enlargedOvershoot that target silently — going past it is always a reported substitution with a reason you can read
Which filterContent typeApply one. Ever. As of v1.8.3.20 auto never writes a filter preset - it offers one in the player's Auto tab and the sidebar with an Apply button, and only when its opinion differs from yours. Filters are taste; models are technique

The reason is visible wherever the decision is: the dashboard, the in-player Auto tab, and the library-scan log (at Information level, because batch runs happen when nobody is watching).

If the AI service is unreachable, auto does not block — it runs uncapped with the hardware class marked unknown.

In the player, the panel's first tab is Auto: the decision for the file that is playing, plus live switches for auto mode, video filters, face restoration and real-time upscaling. Everything there takes effect immediately, without opening the full configuration page.

Real-Time Upscaling During Playback

When you press play, the plugin enhances the video in real-time. It offers several honest tiers (pick one, or let Auto choose) — each labelled for what it actually is, not marketing:

  • WebGL (sharpen) — a Lanczos2 + CAS sharpening shader on your browser's GPU. Zero latency, works on any WebGL device. Not AI — the always-available baseline.
  • Anime4K (anime shader) — the Anime4K 4.0.1 GLSL filter (a shader, not a neural net), embedded in the plugin and run client-side via WebGL. Best for anime; auto-falls back to WebGL if the client lacks WebGL2 float textures.
  • WebGPU AI (client GPU) — a real Real-ESRGAN compact model via onnxruntime-web on WebGPU, running on your GPU in the browser. Real neural-net AI, no server needed.
  • Server AI — frames are sent to the Docker AI service, upscaled with the selected model (Real-ESRGAN / SwinIR / DAT2 / …), and rendered back. Highest live quality; needs a capable server GPU.
  • Batch (scheduled task) — for guaranteed best quality, upscale the whole file once on the server and just play the _upscaled result — and reach the TVs/phones nothing else can.

Pair it with what you already have: on a desktop browser you can also use your GPU's own VSR (NVIDIA RTX Video Super Resolution / Intel VSR); for mpv there's mpv-shim + Anime4K. This plugin is the hub that brings anime/AI upscaling to every web/TV/mobile client and batch-upscales your whole library.

How it decides: At playback start, a benchmark runs against the Docker service. If the server can process frames fast enough (≥80% of video FPS), it uses Server AI mode. Otherwise, it falls back to WebGL. If the server cannot keep up during playback (under half the video's frame rate for 5 s) it switches to Lanczos (WebGL); while the service asks it to wait, it hides the stale frame and resumes when the server recovers.

Visual indicators:

  • FPS overlay (top-left corner): Shows current FPS, mode, and model
  • Button dot: Green = Server AI, Blue = WebGL
  • Menu section: Toggle on/off, switch modes manually

Object Masking (NEW in v1.8.3.23, live during playback since v1.8.3.24)

Covers things you do not want on screen. It exists because of discussion #11: a user's dog loses her mind whenever a dog or cat appears on TV.

Setup (three steps):

  1. Models tab → download YOLOv3-tiny (object detection). Since v1.8.3.26 it is in the catalog with a verified sha256, so the plugin fetches and checks it for you (earlier builds needed a manual import, and before v1.8.3.25 the import was rejected outright).
  2. On the Settings tab, tick Enable Object Masking, enter the model id, pick the classes (animals by default), save, then press Load Detection Model.
  3. Start a video. The player's capture loop sends each frame to the masking endpoint instead of the upscaler, and draws the covered frame back onto the overlay. The switch is also in the player menu under Auto → Cover objects.

It replaces real-time upscaling while it runs. Two full inference passes per frame — upscale and detect — do not keep up with playback on any realistic server, so the stream does one or the other. Batch upscaling is unaffected.

You can also drive it directly, without the player:

# load a detector you imported earlier (none ships - see below)
curl -X POST "$BASE/models/load-detector" -H "X-Api-Token: $TOKEN" \
     -F model_name=tiny-yolov3 -F input_size=416

# cover every animal in a frame with a solid box
curl -X POST "$BASE/detect-mask?classes=animals&mode=box&pad=12" \
     -H "X-Api-Token: $TOKEN" --data-binary @frame.jpg -o masked.jpg

classes takes COCO names or the animals group, mode is box or blur, and pad grows each box — a detector's box hugs the animal, and the ears sticking out set a dog off just as well. The reply carries X-Detections.

Why not ffmpeg's dnn_detect, which is what was asked for: jellyfin-ffmpeg is built without any DNN backend — 46 --enable-* flags and not one of libopenvino / libtensorflow / libtorch, with the string dnn absent from the build entirely. Those filters cannot run on the ffmpeg Jellyfin ships, whatever the plugin passes them. Accepting arbitrary -vf would also be a security hole rather than a feature: ffmpeg filter syntax includes movie= and subtitles=, both of which read files, so it would hand every authenticated user a file-read primitive.

The detector is downloaded, not bundled. Since v1.8.3.26 tiny-yolov3 is a catalog entry with a verified sha256, so the Models tab fetches and checks it exactly like an upscaler. That pin was computed from the bytes of the file itself, after checking its sha384 against the OpenVINO model zoo's own model.yml — until that was done, the honest answer was "bring your own", because inventing a pin would break the guarantee the importer exists to provide. You can still import any other detector through the upload path, which pins and verifies it the same way. Both families work: single-head exports (YOLOv5/v7/v8/v9) and the NMS-head ONNX YOLOv3 exports. The service tells them apart by reading the loaded model's own inputs and outputs, and rejects one it does not recognise at load time — a misread tensor does not raise, it paints boxes over the wrong part of the picture.

Player Integration

The in-player button lets you:

  • Select from 76 curated models (+ hundreds importable from OpenModelDB) across 12 categories (Real-ESRGAN, SPAN, SwinIR, DAT2, EDVR-M, RealBasicVSR, AnimeSR, APISR, EDSR, LapSRN, FSRCNN, ESPCN, ncnn-Vulkan)
  • Choose scale factor (2x, 3x, 4x, 8x)
  • Toggle real-time upscaling and switch modes
  • Quick access via keyboard shortcuts (Alt+U, Alt+M)

Jellyfin 12 compatibility

The v1.8.3.35 release targets Jellyfin.Controller 12.0.0 / .NET 10. Jellyfin 12.1 is also published; see the official releases. Build and runtime evidence is tracked in docs/JELLYFIN-12-READINESS.md; GPU, real-player and HDR target-hardware acceptance remain separate.

Installation

Step 1: Start the Docker AI Service

Choose the command that matches your GPU:

NVIDIA GPU (recommended):

docker run -d \
  --name jellyfin-ai-upscaler \
  --gpus all \
  -p 5000:5000 \
  -v ai-models:/app/models \
  kuscheltier/jellyfin-ai-upscaler:docker7

Intel GPU (Arc / Iris):

docker run -d \
  --name jellyfin-ai-upscaler \
  --device=/dev/dri \
  --group-add=render \
  -p 5000:5000 \
  -v ai-models:/app/models \
  kuscheltier/jellyfin-ai-upscaler:docker7-intel

AMD GPU (ROCm):

docker run -d \
  --name jellyfin-ai-upscaler \
  --device=/dev/kfd --device=/dev/dri \
  -p 5000:5000 \
  -v ai-models:/app/models \
  kuscheltier/jellyfin-ai-upscaler:docker7-amd

Vulkan GPU (AMD RX 5700, Intel iGPU, etc.):

docker run -d \
  --name jellyfin-ai-upscaler \
  --device=/dev/dri \
  --group-add=render \
  -p 5000:5000 \
  -v ai-models:/app/models \
  kuscheltier/jellyfin-ai-upscaler:docker7-vulkan

CPU Only (any platform):

docker run -d \
  --name jellyfin-ai-upscaler \
  -p 5000:5000 \
  -v ai-models:/app/models \
  kuscheltier/jellyfin-ai-upscaler:docker7-cpu

CPU + Model Converter (adds .pth community-model conversion, ~2 GB):

docker run -d \r
  --name jellyfin-ai-upscaler \r
  -p 5000:5000 \r
  -v ai-models:/app/models \r
  kuscheltier/jellyfin-ai-upscaler:docker7-converter

Verify the container is running: curl http://YOUR_SERVER_IP:5000/health

Step 2: Install the Jellyfin Plugin

  1. Open Jellyfin Dashboard → Plugins → Repositories → Add
  2. Enter this repository URL:
    https://raw.githubusercontent.com/Kuschel-code/JellyfinUpscalerPlugin/main/repository-jellyfin.json
    
  3. Go to Catalog → find AI Upscaler → click Install
  4. Restart Jellyfin (required after any plugin install)
  5. Go to Dashboard → Plugins → AI Upscaler Plugin → set AI Service URL to http://YOUR_SERVER_IP:5000

Step 3: Use the Player Button

After installation, play any video in a web browser (Chrome, Edge, Firefox). You will see an AI upscaler button (sparkle icon) in the player controls. Click it to access:

  • Quick model selection across 12 categories (Real-ESRGAN, SPAN, SwinIR, DAT2, EDVR-M, RealBasicVSR, AnimeSR, APISR, EDSR, LapSRN, FSRCNN, ESPCN, ncnn-Vulkan)
  • Scale factor (2x, 3x, 4x)
  • Toggle upscaling on/off

Note: The player button only works in web browsers. It does NOT appear in native Jellyfin apps (Windows, Android, iOS, TV).

Note (Docker users): If the button doesn't appear immediately, visit the plugin config page once — this activates the player script for the current session.

Step 4: Pre-Upscaling (Optional)

To batch-upscale your low-resolution content:

  1. Go to Dashboard → Scheduled Tasks → AI Upscaler
  2. "Scan & Upscale Library" runs automatically at 3 AM daily
  3. Or click Run to start immediately
  4. Configure resolution threshold in plugin settings (default: 1920x1080)

Features

  • 76 Curated AI Models: Real-ESRGAN, SPAN, SwinIR, DAT2, EDVR-M, RealBasicVSR, AnimeSR, APISR, EDSR, FSRCNN, ESPCN, LapSRN (2x–8x)
  • OpenModelDB Importer (v1.8.3.8+): one-click import of 660+ community models from the config page or the :5000 dashboard — sha256-pinned, ZIP-aware; the opt-in docker7-converter image converts .pth models to ONNX with output verification; installs land in the ★ Favorites card
  • Multi-Frame VSR: 5-frame sliding window for temporal consistency (EDVR-M, RealBasicVSR, AnimeSR v2)
  • Auto-Model Selection: Picks best model per video based on genre (anime/live-action), resolution, and mode
  • Real-Time Upscaling: Honest tiers — WebGL (sharpen) · Anime4K (anime shader) · WebGPU AI (client GPU) · Server AI · Batch (best) — with auto-fallback
  • Pre-Upscaling: Scheduled task batch-processes low-res videos overnight
  • Image Upscaling: Scheduled task for posters, backdrops, thumbnails, logos, banners
  • Quality Metrics (PSNR/SSIM): Compare bicubic vs AI upscale quality with Gaussian-based SSIM (NEW in v1.5.5.4)
  • Face Enhancement (GFPGAN): Haar cascade detection → ONNX inference or bilateral filter fallback, soft elliptical blending (NEW in v1.5.5.4)
  • Film Grain Management: NL-means denoising for removal, Gaussian noise for re-grain, configurable "both" mode (NEW in v1.5.5.4)
  • Camera-Style Video Filters: 7 presets (Cinematic, Vintage, Vivid, Noir, Warm, Cool, HDR Pop) + full custom mode with brightness, contrast, saturation, gamma, sharpness, color temperature, vignette, film grain, denoise, and LUT color grading (NEW in v1.6.1)
  • Custom ONNX Model Upload: Upload and validate custom ONNX models at runtime with NCHW shape validation
  • OpenAPI/Swagger Docs: Togglable /docs and /redoc endpoints for API exploration
  • Model Fallback Chain: Comma-separated models — tries next on failure (images + videos)
  • Priority Queue: Pause/resume, priority 1-10, optional persistence across restarts
  • Prometheus Metrics: /metrics endpoint with per-model jobs, failures, frames, timing
  • Circuit Breaker: Auto-opens after consecutive failures, resets after timeout
  • Health Monitoring: /health/detailed with GPU health, circuit breaker state, model info
  • Webhook Notifications: HTTP POST on job complete/failure
  • Model Management: Disk usage tracking, LRU cleanup of unused models
  • Docker Microservice: AI runs isolated in a container (no DLL conflicts, ~1.6 MB plugin)
  • 7 Image Variants: NVIDIA CUDA (TensorRT opt-in), AMD ROCm, Intel OpenVINO, Vulkan/ncnn, Apple Silicon, CPU, CPU+Converter
  • Player Integration: In-player button with quick settings menu, FPS overlay, keyboard shortcuts (Alt+U, Alt+M)
  • Web UI: Model management at http://YOUR_SERVER_IP:5000

AI Models (76 curated + 660+ importable)

CategoryModelsScaleSpeedBest For
Real-ESRGANrealesrgan-x4, x4-256, x2-plus, animevideo-x42-4xSlowBest overall quality
SPANspan-x2, span-x42-4xFastReal-time video
SwinIRswinir-x4, swinir-small-x2/x42-4xMediumPhotos & live-action
APISRapisr-x3, apisr-anime-x22-3xMediumCVPR 2024, general & anime
Video Real-Timeclearreality-x4, nomosuni-compact-x2, lsdir-compact-x42-4xFastLow-latency video
Video Qualityultrasharp-v2-x4, nomos2-dat2-x4, nomos2-realplksr-x44xSlowMaximum detail
Film Restorationfsdedither-x4, nomos8k-hat-x44xMediumDVD/VHS cleanup
Animeanime-compact-x44xFastLightweight anime
Multi-Frame VSRedvr-m-x4, realbasicvsr-x4, animesr-v2-x44xSlowTemporal consistency (5 frames)
OpenCV Classicedsr-x2/x3/x4, lapsrn-x2/x4/x8, fsrcnn-x2/x3/x4, espcn-x2/x3/x42-8xFast-MediumCPU-only, lightweight
Vulkan/ncnnrealesrgan-x4-vulkan, realesrgan-anime-x4-vulkan, span-x4-vulkan4xFastAMD pre-RDNA2, Intel iGPU

Beyond the curated catalog, the Importable models page lists 660+ OpenModelDB community models — import them one-click from the plugin config page or the :5000 dashboard (.pth models convert automatically with the docker7-converter image).


Configuration

After installation, find settings under Dashboard → Plugins → AI Upscaler Plugin.

SettingDescription
AI Service URLURL to Docker container (e.g., http://192.168.1.100:5000)
Enable PluginGlobal on/off switch
AI ModelChoose upscaling model (auto = intelligent selection per content)
Scale Factor2x, 3x, or 4x
Min ResolutionThreshold for scheduled task (default: 1920x1080)
Model Fallback ChainComma-separated fallback models (e.g., realesrgan-x4,span-x4,edsr-x4)
Preferred Anime ModelModel for anime content (empty = let the heuristic decide). A value here is treated as a deliberate override: it is exempt from the hardware cap and the scale logic
Preferred Live-Action ModelModel for live-action content (empty = let the heuristic decide). Same override semantics as above
Enable Processing QueuePriority queue with pause/resume (default: true)
Max Queue SizeMaximum items in queue (default: 100)
Pause Queue During PlaybackPause processing when user is watching (default: true)
Webhook URLHTTP POST notifications on job complete/failure
Enable Health MonitoringCircuit breaker + health checks (default: true)
Circuit Breaker ThresholdConsecutive failures before circuit opens (default: 5)
Model Disk Quota MBMax disk space for cached models (default: 2048)
Enable Model Auto CleanupLRU cleanup of unused models (default: true)
Enable Quality MetricsCompute PSNR/SSIM scores after upscaling (default: true)
Enable Face EnhancementDetect and enhance faces via GFPGAN ONNX or fallback (default: true)
Face Enhance StrengthBlend ratio 0.0–1.0 for face enhancement (default: 0.7)
Enable Grain ManagementFilm grain removal/re-addition pipeline (default: true)
Grain Denoise StrengthNL-means filter strength 1–30 (default: 5)
Grain Re-add IntensityGaussian noise sigma 0–50 for re-grain (default: 0)
Enable Custom Model UploadAllow uploading custom ONNX models at runtime (default: true)
Enable API DocsToggle /docs and /redoc Swagger endpoints (default: true)
Player ButtonShow/hide AI button in video player
Real-Time UpscalingEnable/disable real-time enhancement during playback
Output CodecCodec for upscaled videos: H.264, H.265, or copy
MaxItemsPerScanLimit items per scan run (default: unlimited)

Docker Image Tags

TagGPUUse Case
:docker7NVIDIA CUDA 12.8 (TensorRT opt-in)RTX 50/40/30/20, GTX 16/10
:docker7-amdAMD ROCm 6.2RX 7000, RX 6000
:docker7-intelIntel OpenVINO 2025.4Arc A-Series, Iris Xe, iGPU
:docker7-appleARM64 Optimized (multi-arch)Apple M1–M5 (Docker=CPU, native=CoreML)
:docker7-vulkanVulkan (ncnn)AMD pre-RDNA2, Intel iGPU, any Vulkan GPU
:docker7-cpuMulti-threaded CPU (multi-arch)Any platform (amd64/arm64)
:docker7-converterCPU + Torch/SpandrelSupported community-model conversion to ONNX

Each tag is published three ways so you can pin precisely:

  • :docker7 — rolling tag family (Watchtower auto-updates)
  • :docker7-v1.8.3.35 — NVIDIA pin for the currently published release
  • :v1.8.3.35-<backend> — published backend pin (e.g. :v1.8.3.35-cpu)
  • :rc-v<version>[-backend] — release-candidate images, published ahead of a release when one is wanted; :rc-v<version>-<commit>[-backend] pins the exact build. The last candidates were :rc-v1.8.3.32…; v1.8.3.33 to v1.8.3.35 went straight to release.

CUDA is the default: keep SKIP_TENSORRT=true. Enable TensorRT only with compatible libraries in the image. See Docker setup and controlled updates.


Changelog

The full version history lives on the website and the release pages — this README no longer duplicates it:

Release: v1.8.3.35 — the player button no longer needs write access to Jellyfin's web folder (Windows installs, Debian package, snaps, read-only containers), and the browser real-time engines load behind a base URL; all seven Docker variants. v1.8.3.31 remains available for Jellyfin 10.11.


Troubleshooting

Plugin shows "Not Supported"

  1. Uninstall old versions (v1.4.x)
  2. Delete old plugin folder from Jellyfin plugins directory
  3. Restart Jellyfin
  4. Install the latest version fresh from repository

Player button not showing

  1. The button only appears in web browsers (Chrome, Edge, Firefox, Brave), not in the native apps (Windows app, mobile, TV). Open Jellyfin via http://YOUR_IP:8096.

  2. Since v1.8.3.35 the plugin adds its script to the web client's page as Jellyfin serves it, so a read-only web folder no longer matters (Windows installer, Debian package, snap, read-only containers). After a restart and the first page load, the log (Dashboard → Logs) shows Player script injected via … or Player script added to index.html as Jellyfin serves it.

  3. Neither line? Then Jellyfin is not serving the web client itself (for example, a separate web server serves jellyfin-web), and the plugin cannot reach the page.

  4. On v1.8.3.34 or older, including v1.8.3.31 for Jellyfin 10.11, the plugin can only edit the file itself. If the log says Could not inject player script into index.html, give Jellyfin write access to the index.html it names, then restart Jellyfin:

    • Windows installer: Jellyfin runs as the NetworkService account by default, which cannot write to Program Files. In an administrator Command Prompt, run icacls "C:\Program Files\Jellyfin\Server\jellyfin-web\index.html" /grant *S-1-5-20:M and restart the "Jellyfin Server" service. If you run the tray app instead of the service, use /grant "%USERNAME%":M.
    • Debian/Ubuntu package: sudo chown jellyfin /usr/share/jellyfin/web/index.html, then sudo systemctl restart jellyfin.

    Until then, opening the plugin's settings page loads the button into that browser tab only; reloading the tab removes it again. A Jellyfin update replaces index.html, so repeat the step if the warning returns.

There is no "Upscale" button on item pages: whole libraries are upscaled by the scheduled task "Scan & Upscale Library" (Dashboard → Scheduled Tasks).

Docker container not starting

# Check logs
docker logs jellyfin-ai-upscaler --tail 50

# Health check
curl http://YOUR_SERVER_IP:5000/health

# GPU diagnostics
curl http://YOUR_SERVER_IP:5000/gpu-verify

# Check GPU (NVIDIA)
docker run --rm --gpus all nvidia/cuda:12.2.2-base-ubuntu22.04 nvidia-smi

GPU not detected

  • NVIDIA RTX 5000 (Blackwell): Requires CUDA 12.8+ — use latest :docker7 image. Compute capability sm_120 auto-detected.
  • NVIDIA RTX 4000/3000/2000: Install nvidia-container-toolkit, use --gpus all. TensorRT is skipped by default — set SKIP_TENSORRT=false only when the image includes compatible TensorRT libraries. CUDA remains the default.
  • Intel: Use :docker7-intel tag with --device=/dev/dri --group-add=render. Check diagnostics: curl http://YOUR_SERVER_IP:5000/gpu-verify
  • AMD: Use :docker7-amd tag with --device=/dev/kfd --device=/dev/dri
  • Apple M1–M5: Docker on macOS runs CPU-only. For GPU acceleration via CoreML/Neural Engine, use the native install:
    cd docker-ai-service && chmod +x install-native-macos.sh && ./install-native-macos.sh
    
  • Windows Docker Desktop (WSL2): Intel/AMD GPUs accessible via /dev/dxg + WSL2-driver mount — see docker-ai-service/docker-compose.yml WSL2 section. NVIDIA: use NVIDIA Container Toolkit. FP16 mismatch (Issue #67) is auto-detected in v1.7.4+ based on the loaded ONNX model's input type.

Proxmox LXC GPU Passthrough

# Add to LXC config (/etc/pve/lxc/<id>.conf):
lxc.cgroup2.devices.allow: c 226:* rwm
lxc.mount.entry: /dev/dri dev/dri none bind,optional,create=dir

# Inside LXC, use Docker with:
--device=/dev/dri --group-add=render

# Verify GPU visibility:
docker exec jellyfin-ai-upscaler curl http://localhost:5000/gpu-verify

Upscaling not working

  1. Verify Docker container is running: docker ps --filter name=jellyfin-ai-upscaler
  2. Test connection: curl http://YOUR_SERVER_IP:5000/health
  3. Check GPU diagnostics: curl http://YOUR_SERVER_IP:5000/gpu-verify
  4. Check AI Service URL in plugin settings
  5. Check Docker logs for errors

Checksum mismatch on install

The plugin repository auto-updates checksums via CI. If you see a mismatch:

  1. Wait 5 minutes for GitHub CDN to refresh
  2. Remove and re-add the repository URL
  3. Try installing again

Support

  • 💬 Support Assistant — an in-browser bot on the docs site (button, bottom-right) that answers from every issue we've ever handled: install errors, GPU not used, Docker/NAS setup, API token, model choice, and more. No login, runs entirely in your browser.
  • Project Website
  • GitHub Issues
  • GitHub Wiki

License

MIT License - See LICENSE for details.

Kuschel-code/JellyfinUpscalerPlugin

JellyfinUpscalerPlugin

C#

152

875 commits

updated Oct 4, 2026

See the code

README

Jellyfin AI Upscaler Plugin v1.8.3.36 — Jellyfin 12

Built with Claude Opus 5.5

Built with Claude Opus 5.5 — this plugin is developed with Anthropic's Claude models: Opus 5.5 since v1.8.3.33 (including the review of v1.8.3.31–v1.8.3.32 before it), Opus 5 for v1.8.3.13–v1.8.3.30, Fable 5 for v1.8.3.5–v1.8.3.12 and Opus 4.8 before that. v1.8.3.31 and v1.8.3.32 were prepared with OpenAI Codex. Code, Dockerfiles, CI workflows and documentation are written in a pair-programming style with the model; the maintainer (Kuschel-code) reviews, tests and publishes every change. Commits made with Claude carry a Co-Authored-By: Claude trailer as disclosure.


License: MIT Jellyfin Version Docker Hub Docker Image Documentation

AI-powered video upscaling for Jellyfin. Upscale SD content to HD/4K using neural networks, with AI inference in a Docker service and video decoding/encoding on the Jellyfin host.

Release status (2026-10-04): v1.8.3.36 is released for Jellyfin 12.0+ / .NET 10. Library jobs now wait out a busy AI service (HTTP 429/503) instead of failing the whole video, and the AI service explains why an Intel/AMD GPU is not used (see issue #90: pass the host's numeric render group ID, not the name render). Target-server acceptance was not run; actual playback, GPU and HDR target-hardware behavior remain unverified.

Previous release status (2026-09-26): v1.8.3.35 is released for Jellyfin 12.0+ / .NET 10. The player button no longer needs write access to Jellyfin's web folder, and since v1.8.3.34 real-time upscaling starts again in the web client. Jellyfin 10.11 users retain v1.8.3.31, which has both defects. Target-server acceptance was not run; actual playback, GPU and HDR target-hardware behavior remain unverified.

Fixed in v1.8.3.35 (reported in GitHub issue #75):

  • The player button never appeared when Jellyfin could not write its web client's index.html: on a default Windows install (the server runs as NetworkService under Program Files), with the Debian package, snaps and read-only containers. It showed only in a tab that had opened the plugin's settings. The plugin now adds its script to the page as Jellyfin serves it; the file on disk is left alone.
  • Behind a Jellyfin base URL (for example /jellyfin), the Lanczos, Anime4K and WebGPU real-time engines never loaded: they were requested without the base URL, and Jellyfin redirected the request to its start page. They now load relative to the web client.

Fixed in v1.8.3.34 (reported in GitHub issues #86 and #87 on 2026-09-24):

  • Real-time upscaling never started in the web client. Every video showed "Video color metadata unavailable; realtime processing cannot be validated" and the menu stayed on Standby. Since v1.8.3.31 the player looked for the playing item in the page address, where Jellyfin 10.9 and later no longer put it. It now reads the item from the video's stream address, or from the web client's playback request when Jellyfin transcodes, and checks the colour format of the version that actually plays.

Fixed in v1.8.3.33 (found in a review of v1.8.3.31–v1.8.3.32 on 2026-09-24):

  • Real-time upscaling refused some SDR videos with an "HDR" message, in every mode: DVD/SD rips tagged smpte170m/bt470bg and untagged 10-bit files. HDR now needs real evidence (a PQ/HLG transfer, an HDR range, dynamic metadata, or BT.2020 without an SDR transfer).
  • Library jobs refused untagged 10-bit files (common for anime encodes) with HDR requires PQ/ST.2084.
  • HDR batch output kept the model's colour but not its detail. It keeps the detail now, and the model gets a fuller-range frame to work on.
  • Server AI mode showed a slideshow on a slow server and froze the last frame during outages. A stale frame is hidden after 1.5 s now, and a server delivering under half the video's frame rate for 5 s hands over to Lanczos.

New in v1.8.3.33: a redesigned in-player menu (live frame-rate readout, filter previews on the playing frame, phone and TV layouts) and settings page (keyboard- and remote-friendly tabs).

Docker Images (released in lockstep with the plugin, both at v1.8.3.36): All seven regular version pins and docker7 tags are published and registry-verified, including ten platform configurations. NVIDIA latest also points to v1.8.3.35. The AI service itself is unchanged since v1.8.3.33. Release notes.

  • kuscheltier/jellyfin-ai-upscaler:docker7 (NVIDIA CUDA + cuDNN 9)
  • kuscheltier/jellyfin-ai-upscaler:docker7-amd (AMD ROCm)
  • kuscheltier/jellyfin-ai-upscaler:docker7-intel (Intel Arc/iGPU OpenVINO)
  • kuscheltier/jellyfin-ai-upscaler:docker7-apple (Apple Silicon Docker — CPU inference, multi-arch amd64/arm64)
  • kuscheltier/jellyfin-ai-upscaler:docker7-vulkan (Vulkan/ncnn — AMD pre-RDNA2, Intel iGPU)
  • kuscheltier/jellyfin-ai-upscaler:docker7-cpu (CPU Only — multi-threaded ONNXRuntime, multi-arch)
  • kuscheltier/jellyfin-ai-upscaler:docker7-converter (CPU + pth→ONNX converter for OpenModelDB community models — opt-in)

Download sizes range from 0.27 GB (docker7-cpu) to 20 GB (docker7-amd, ROCm base) — see docs/DOCKER-IMAGES.md for the full table, the converter's RAM guidance and why the AMD stack is frozen.

Report bugs: GitHub Issues

Contents: Architecture · How It Works · Installation · Features · AI Models · Configuration · Docker Tags · Changelog · Troubleshooting · Support


Download Jellyfin 12 release v1.8.3.35 (requires Jellyfin 12.0+, five runtime DLLs plus meta.json).

Architecture

Jellyfin's plugin system tries to load ALL .dll files as .NET assemblies. Native C++ libraries (ONNX Runtime, CUDA, OpenCV) caused BadImageFormatException crashes in older versions. The solution: a Docker microservice architecture where the plugin (only ~1.6 MB) communicates with an external AI container via HTTP.

┌──────────────────────────────────────────┐
│  Jellyfin Server                         │
│  ┌────────────────────────────────────┐  │
│  │  AI Upscaler Plugin v1.8.3.36   │  │
│  │  ~1.6 MB — No native DLLs         │  │
│  │  Sends frames via HTTP             │  │
│  └──────────────┬─────────────────────┘  │
└─────────────────┼────────────────────────┘
                  │ HTTP POST /upscale
                  ▼
┌──────────────────────────────────────────┐
│  AI Upscaler Docker Container            │
│  ┌────────────────────────────────────┐  │
│  │  Python + FastAPI + OpenCV DNN     │  │
│  │  CUDA / ROCm / OpenVINO / CPU     │  │
│  │  Real-ESRGAN, SPAN, SwinIR, DAT2 │  │
│  │  EDVR-M, RealBasicVSR, AnimeSR  │  │
│  │  EDSR, FSRCNN, ESPCN (76+ models) │  │
│  │  Web UI for Model Management      │  │
│  └────────────────────────────────────┘  │
└──────────────────────────────────────────┘

How It Works

The plugin supports four upscaling modes:

Pre-Upscaling (Batch Processing)

The Scheduled Task ("Scan & Upscale Library") runs daily at 3 AM and:

  1. Scans your library for videos below the configured resolution (default: 1080p)
  2. Skips already upscaled files (files with _upscaled suffix)
  3. Auto-selects the best model per video from genre (anime vs live-action), resolution, available multi-frame models and what your hardware can actually run — an over-budget pick is swapped for an affordable one with the reason written to the log, so a CPU-only NAS is never handed a full restoration net
  4. For each low-res video: extracts frames via FFmpeg → upscales through AI → reassembles into a new video
  5. With multi-frame VSR models (EDVR-M, RealBasicVSR, AnimeSR): uses 5-frame sliding window for temporal consistency
  6. Saves the upscaled version alongside the original (e.g., Movie_upscaled.mkv)

This is ideal for users without powerful servers — upscaling happens overnight.

Image Upscaling (NEW in v1.5.4.0)

The Scheduled Task ("Scan & Upscale Library Images") runs weekly on Sunday at 4 AM and:

  1. Scans all library items for low-resolution posters, backdrops, thumbnails, logos, and banners
  2. Uses different thresholds: posters < 600x900, backdrops < 1280x720
  3. Auto-scales: 4x for very low-res images, 2x otherwise
  4. Also available on-demand via POST /api/upscaler/upscale-images/{itemId}

Auto Mode — what it decides, and what it will not

Auto mode is on by default. For every video it answers three questions and tells you the answer:

It decidesFromWhat it will not do
Which modelContent type (genre), source resolution, whether multi-frame models are loaded, and the hardware class the AI service reportsOverride a model you selected yourself under Preferred Anime / Live-Action Model — that is annotated, never replaced
Which scaleThe source size. SD gets a full 4x restore; 720p and 1080p get 2x; a source that is already 4K is cleaned up, not enlargedOvershoot that target silently — going past it is always a reported substitution with a reason you can read
Which filterContent typeApply one. Ever. As of v1.8.3.20 auto never writes a filter preset - it offers one in the player's Auto tab and the sidebar with an Apply button, and only when its opinion differs from yours. Filters are taste; models are technique

The reason is visible wherever the decision is: the dashboard, the in-player Auto tab, and the library-scan log (at Information level, because batch runs happen when nobody is watching).

If the AI service is unreachable, auto does not block — it runs uncapped with the hardware class marked unknown.

In the player, the panel's first tab is Auto: the decision for the file that is playing, plus live switches for auto mode, video filters, face restoration and real-time upscaling. Everything there takes effect immediately, without opening the full configuration page.

Real-Time Upscaling During Playback

When you press play, the plugin enhances the video in real-time. It offers several honest tiers (pick one, or let Auto choose) — each labelled for what it actually is, not marketing:

  • WebGL (sharpen) — a Lanczos2 + CAS sharpening shader on your browser's GPU. Zero latency, works on any WebGL device. Not AI — the always-available baseline.
  • Anime4K (anime shader) — the Anime4K 4.0.1 GLSL filter (a shader, not a neural net), embedded in the plugin and run client-side via WebGL. Best for anime; auto-falls back to WebGL if the client lacks WebGL2 float textures.
  • WebGPU AI (client GPU) — a real Real-ESRGAN compact model via onnxruntime-web on WebGPU, running on your GPU in the browser. Real neural-net AI, no server needed.
  • Server AI — frames are sent to the Docker AI service, upscaled with the selected model (Real-ESRGAN / SwinIR / DAT2 / …), and rendered back. Highest live quality; needs a capable server GPU.
  • Batch (scheduled task) — for guaranteed best quality, upscale the whole file once on the server and just play the _upscaled result — and reach the TVs/phones nothing else can.

Pair it with what you already have: on a desktop browser you can also use your GPU's own VSR (NVIDIA RTX Video Super Resolution / Intel VSR); for mpv there's mpv-shim + Anime4K. This plugin is the hub that brings anime/AI upscaling to every web/TV/mobile client and batch-upscales your whole library.

How it decides: At playback start, a benchmark runs against the Docker service. If the server can process frames fast enough (≥80% of video FPS), it uses Server AI mode. Otherwise, it falls back to WebGL. If the server cannot keep up during playback (under half the video's frame rate for 5 s) it switches to Lanczos (WebGL); while the service asks it to wait, it hides the stale frame and resumes when the server recovers.

Visual indicators:

  • FPS overlay (top-left corner): Shows current FPS, mode, and model
  • Button dot: Green = Server AI, Blue = WebGL
  • Menu section: Toggle on/off, switch modes manually

Object Masking (NEW in v1.8.3.23, live during playback since v1.8.3.24)

Covers things you do not want on screen. It exists because of discussion #11: a user's dog loses her mind whenever a dog or cat appears on TV.

Setup (three steps):

  1. Models tab → download YOLOv3-tiny (object detection). Since v1.8.3.26 it is in the catalog with a verified sha256, so the plugin fetches and checks it for you (earlier builds needed a manual import, and before v1.8.3.25 the import was rejected outright).
  2. On the Settings tab, tick Enable Object Masking, enter the model id, pick the classes (animals by default), save, then press Load Detection Model.
  3. Start a video. The player's capture loop sends each frame to the masking endpoint instead of the upscaler, and draws the covered frame back onto the overlay. The switch is also in the player menu under Auto → Cover objects.

It replaces real-time upscaling while it runs. Two full inference passes per frame — upscale and detect — do not keep up with playback on any realistic server, so the stream does one or the other. Batch upscaling is unaffected.

You can also drive it directly, without the player:

# load a detector you imported earlier (none ships - see below)
curl -X POST "$BASE/models/load-detector" -H "X-Api-Token: $TOKEN" \
     -F model_name=tiny-yolov3 -F input_size=416

# cover every animal in a frame with a solid box
curl -X POST "$BASE/detect-mask?classes=animals&mode=box&pad=12" \
     -H "X-Api-Token: $TOKEN" --data-binary @frame.jpg -o masked.jpg

classes takes COCO names or the animals group, mode is box or blur, and pad grows each box — a detector's box hugs the animal, and the ears sticking out set a dog off just as well. The reply carries X-Detections.

Why not ffmpeg's dnn_detect, which is what was asked for: jellyfin-ffmpeg is built without any DNN backend — 46 --enable-* flags and not one of libopenvino / libtensorflow / libtorch, with the string dnn absent from the build entirely. Those filters cannot run on the ffmpeg Jellyfin ships, whatever the plugin passes them. Accepting arbitrary -vf would also be a security hole rather than a feature: ffmpeg filter syntax includes movie= and subtitles=, both of which read files, so it would hand every authenticated user a file-read primitive.

The detector is downloaded, not bundled. Since v1.8.3.26 tiny-yolov3 is a catalog entry with a verified sha256, so the Models tab fetches and checks it exactly like an upscaler. That pin was computed from the bytes of the file itself, after checking its sha384 against the OpenVINO model zoo's own model.yml — until that was done, the honest answer was "bring your own", because inventing a pin would break the guarantee the importer exists to provide. You can still import any other detector through the upload path, which pins and verifies it the same way. Both families work: single-head exports (YOLOv5/v7/v8/v9) and the NMS-head ONNX YOLOv3 exports. The service tells them apart by reading the loaded model's own inputs and outputs, and rejects one it does not recognise at load time — a misread tensor does not raise, it paints boxes over the wrong part of the picture.

Player Integration

The in-player button lets you:

  • Select from 76 curated models (+ hundreds importable from OpenModelDB) across 12 categories (Real-ESRGAN, SPAN, SwinIR, DAT2, EDVR-M, RealBasicVSR, AnimeSR, APISR, EDSR, LapSRN, FSRCNN, ESPCN, ncnn-Vulkan)
  • Choose scale factor (2x, 3x, 4x, 8x)
  • Toggle real-time upscaling and switch modes
  • Quick access via keyboard shortcuts (Alt+U, Alt+M)

Jellyfin 12 compatibility

The v1.8.3.35 release targets Jellyfin.Controller 12.0.0 / .NET 10. Jellyfin 12.1 is also published; see the official releases. Build and runtime evidence is tracked in docs/JELLYFIN-12-READINESS.md; GPU, real-player and HDR target-hardware acceptance remain separate.

Installation

Step 1: Start the Docker AI Service

Choose the command that matches your GPU:

NVIDIA GPU (recommended):

docker run -d \
  --name jellyfin-ai-upscaler \
  --gpus all \
  -p 5000:5000 \
  -v ai-models:/app/models \
  kuscheltier/jellyfin-ai-upscaler:docker7

Intel GPU (Arc / Iris):

docker run -d \
  --name jellyfin-ai-upscaler \
  --device=/dev/dri \
  --group-add=render \
  -p 5000:5000 \
  -v ai-models:/app/models \
  kuscheltier/jellyfin-ai-upscaler:docker7-intel

AMD GPU (ROCm):

docker run -d \
  --name jellyfin-ai-upscaler \
  --device=/dev/kfd --device=/dev/dri \
  -p 5000:5000 \
  -v ai-models:/app/models \
  kuscheltier/jellyfin-ai-upscaler:docker7-amd

Vulkan GPU (AMD RX 5700, Intel iGPU, etc.):

docker run -d \
  --name jellyfin-ai-upscaler \
  --device=/dev/dri \
  --group-add=render \
  -p 5000:5000 \
  -v ai-models:/app/models \
  kuscheltier/jellyfin-ai-upscaler:docker7-vulkan

CPU Only (any platform):

docker run -d \
  --name jellyfin-ai-upscaler \
  -p 5000:5000 \
  -v ai-models:/app/models \
  kuscheltier/jellyfin-ai-upscaler:docker7-cpu

CPU + Model Converter (adds .pth community-model conversion, ~2 GB):

docker run -d \r
  --name jellyfin-ai-upscaler \r
  -p 5000:5000 \r
  -v ai-models:/app/models \r
  kuscheltier/jellyfin-ai-upscaler:docker7-converter

Verify the container is running: curl http://YOUR_SERVER_IP:5000/health

Step 2: Install the Jellyfin Plugin

  1. Open Jellyfin Dashboard → Plugins → Repositories → Add
  2. Enter this repository URL:
    https://raw.githubusercontent.com/Kuschel-code/JellyfinUpscalerPlugin/main/repository-jellyfin.json
    
  3. Go to Catalog → find AI Upscaler → click Install
  4. Restart Jellyfin (required after any plugin install)
  5. Go to Dashboard → Plugins → AI Upscaler Plugin → set AI Service URL to http://YOUR_SERVER_IP:5000

Step 3: Use the Player Button

After installation, play any video in a web browser (Chrome, Edge, Firefox). You will see an AI upscaler button (sparkle icon) in the player controls. Click it to access:

  • Quick model selection across 12 categories (Real-ESRGAN, SPAN, SwinIR, DAT2, EDVR-M, RealBasicVSR, AnimeSR, APISR, EDSR, LapSRN, FSRCNN, ESPCN, ncnn-Vulkan)
  • Scale factor (2x, 3x, 4x)
  • Toggle upscaling on/off

Note: The player button only works in web browsers. It does NOT appear in native Jellyfin apps (Windows, Android, iOS, TV).

Note (Docker users): If the button doesn't appear immediately, visit the plugin config page once — this activates the player script for the current session.

Step 4: Pre-Upscaling (Optional)

To batch-upscale your low-resolution content:

  1. Go to Dashboard → Scheduled Tasks → AI Upscaler
  2. "Scan & Upscale Library" runs automatically at 3 AM daily
  3. Or click Run to start immediately
  4. Configure resolution threshold in plugin settings (default: 1920x1080)

Features

  • 76 Curated AI Models: Real-ESRGAN, SPAN, SwinIR, DAT2, EDVR-M, RealBasicVSR, AnimeSR, APISR, EDSR, FSRCNN, ESPCN, LapSRN (2x–8x)
  • OpenModelDB Importer (v1.8.3.8+): one-click import of 660+ community models from the config page or the :5000 dashboard — sha256-pinned, ZIP-aware; the opt-in docker7-converter image converts .pth models to ONNX with output verification; installs land in the ★ Favorites card
  • Multi-Frame VSR: 5-frame sliding window for temporal consistency (EDVR-M, RealBasicVSR, AnimeSR v2)
  • Auto-Model Selection: Picks best model per video based on genre (anime/live-action), resolution, and mode
  • Real-Time Upscaling: Honest tiers — WebGL (sharpen) · Anime4K (anime shader) · WebGPU AI (client GPU) · Server AI · Batch (best) — with auto-fallback
  • Pre-Upscaling: Scheduled task batch-processes low-res videos overnight
  • Image Upscaling: Scheduled task for posters, backdrops, thumbnails, logos, banners
  • Quality Metrics (PSNR/SSIM): Compare bicubic vs AI upscale quality with Gaussian-based SSIM (NEW in v1.5.5.4)
  • Face Enhancement (GFPGAN): Haar cascade detection → ONNX inference or bilateral filter fallback, soft elliptical blending (NEW in v1.5.5.4)
  • Film Grain Management: NL-means denoising for removal, Gaussian noise for re-grain, configurable "both" mode (NEW in v1.5.5.4)
  • Camera-Style Video Filters: 7 presets (Cinematic, Vintage, Vivid, Noir, Warm, Cool, HDR Pop) + full custom mode with brightness, contrast, saturation, gamma, sharpness, color temperature, vignette, film grain, denoise, and LUT color grading (NEW in v1.6.1)
  • Custom ONNX Model Upload: Upload and validate custom ONNX models at runtime with NCHW shape validation
  • OpenAPI/Swagger Docs: Togglable /docs and /redoc endpoints for API exploration
  • Model Fallback Chain: Comma-separated models — tries next on failure (images + videos)
  • Priority Queue: Pause/resume, priority 1-10, optional persistence across restarts
  • Prometheus Metrics: /metrics endpoint with per-model jobs, failures, frames, timing
  • Circuit Breaker: Auto-opens after consecutive failures, resets after timeout
  • Health Monitoring: /health/detailed with GPU health, circuit breaker state, model info
  • Webhook Notifications: HTTP POST on job complete/failure
  • Model Management: Disk usage tracking, LRU cleanup of unused models
  • Docker Microservice: AI runs isolated in a container (no DLL conflicts, ~1.6 MB plugin)
  • 7 Image Variants: NVIDIA CUDA (TensorRT opt-in), AMD ROCm, Intel OpenVINO, Vulkan/ncnn, Apple Silicon, CPU, CPU+Converter
  • Player Integration: In-player button with quick settings menu, FPS overlay, keyboard shortcuts (Alt+U, Alt+M)
  • Web UI: Model management at http://YOUR_SERVER_IP:5000

AI Models (76 curated + 660+ importable)

CategoryModelsScaleSpeedBest For
Real-ESRGANrealesrgan-x4, x4-256, x2-plus, animevideo-x42-4xSlowBest overall quality
SPANspan-x2, span-x42-4xFastReal-time video
SwinIRswinir-x4, swinir-small-x2/x42-4xMediumPhotos & live-action
APISRapisr-x3, apisr-anime-x22-3xMediumCVPR 2024, general & anime
Video Real-Timeclearreality-x4, nomosuni-compact-x2, lsdir-compact-x42-4xFastLow-latency video
Video Qualityultrasharp-v2-x4, nomos2-dat2-x4, nomos2-realplksr-x44xSlowMaximum detail
Film Restorationfsdedither-x4, nomos8k-hat-x44xMediumDVD/VHS cleanup
Animeanime-compact-x44xFastLightweight anime
Multi-Frame VSRedvr-m-x4, realbasicvsr-x4, animesr-v2-x44xSlowTemporal consistency (5 frames)
OpenCV Classicedsr-x2/x3/x4, lapsrn-x2/x4/x8, fsrcnn-x2/x3/x4, espcn-x2/x3/x42-8xFast-MediumCPU-only, lightweight
Vulkan/ncnnrealesrgan-x4-vulkan, realesrgan-anime-x4-vulkan, span-x4-vulkan4xFastAMD pre-RDNA2, Intel iGPU

Beyond the curated catalog, the Importable models page lists 660+ OpenModelDB community models — import them one-click from the plugin config page or the :5000 dashboard (.pth models convert automatically with the docker7-converter image).


Configuration

After installation, find settings under Dashboard → Plugins → AI Upscaler Plugin.

SettingDescription
AI Service URLURL to Docker container (e.g., http://192.168.1.100:5000)
Enable PluginGlobal on/off switch
AI ModelChoose upscaling model (auto = intelligent selection per content)
Scale Factor2x, 3x, or 4x
Min ResolutionThreshold for scheduled task (default: 1920x1080)
Model Fallback ChainComma-separated fallback models (e.g., realesrgan-x4,span-x4,edsr-x4)
Preferred Anime ModelModel for anime content (empty = let the heuristic decide). A value here is treated as a deliberate override: it is exempt from the hardware cap and the scale logic
Preferred Live-Action ModelModel for live-action content (empty = let the heuristic decide). Same override semantics as above
Enable Processing QueuePriority queue with pause/resume (default: true)
Max Queue SizeMaximum items in queue (default: 100)
Pause Queue During PlaybackPause processing when user is watching (default: true)
Webhook URLHTTP POST notifications on job complete/failure
Enable Health MonitoringCircuit breaker + health checks (default: true)
Circuit Breaker ThresholdConsecutive failures before circuit opens (default: 5)
Model Disk Quota MBMax disk space for cached models (default: 2048)
Enable Model Auto CleanupLRU cleanup of unused models (default: true)
Enable Quality MetricsCompute PSNR/SSIM scores after upscaling (default: true)
Enable Face EnhancementDetect and enhance faces via GFPGAN ONNX or fallback (default: true)
Face Enhance StrengthBlend ratio 0.0–1.0 for face enhancement (default: 0.7)
Enable Grain ManagementFilm grain removal/re-addition pipeline (default: true)
Grain Denoise StrengthNL-means filter strength 1–30 (default: 5)
Grain Re-add IntensityGaussian noise sigma 0–50 for re-grain (default: 0)
Enable Custom Model UploadAllow uploading custom ONNX models at runtime (default: true)
Enable API DocsToggle /docs and /redoc Swagger endpoints (default: true)
Player ButtonShow/hide AI button in video player
Real-Time UpscalingEnable/disable real-time enhancement during playback
Output CodecCodec for upscaled videos: H.264, H.265, or copy
MaxItemsPerScanLimit items per scan run (default: unlimited)

Docker Image Tags

TagGPUUse Case
:docker7NVIDIA CUDA 12.8 (TensorRT opt-in)RTX 50/40/30/20, GTX 16/10
:docker7-amdAMD ROCm 6.2RX 7000, RX 6000
:docker7-intelIntel OpenVINO 2025.4Arc A-Series, Iris Xe, iGPU
:docker7-appleARM64 Optimized (multi-arch)Apple M1–M5 (Docker=CPU, native=CoreML)
:docker7-vulkanVulkan (ncnn)AMD pre-RDNA2, Intel iGPU, any Vulkan GPU
:docker7-cpuMulti-threaded CPU (multi-arch)Any platform (amd64/arm64)
:docker7-converterCPU + Torch/SpandrelSupported community-model conversion to ONNX

Each tag is published three ways so you can pin precisely:

  • :docker7 — rolling tag family (Watchtower auto-updates)
  • :docker7-v1.8.3.35 — NVIDIA pin for the currently published release
  • :v1.8.3.35-<backend> — published backend pin (e.g. :v1.8.3.35-cpu)
  • :rc-v<version>[-backend] — release-candidate images, published ahead of a release when one is wanted; :rc-v<version>-<commit>[-backend] pins the exact build. The last candidates were :rc-v1.8.3.32…; v1.8.3.33 to v1.8.3.35 went straight to release.

CUDA is the default: keep SKIP_TENSORRT=true. Enable TensorRT only with compatible libraries in the image. See Docker setup and controlled updates.


Changelog

The full version history lives on the website and the release pages — this README no longer duplicates it:

Release: v1.8.3.35 — the player button no longer needs write access to Jellyfin's web folder (Windows installs, Debian package, snaps, read-only containers), and the browser real-time engines load behind a base URL; all seven Docker variants. v1.8.3.31 remains available for Jellyfin 10.11.


Troubleshooting

Plugin shows "Not Supported"

  1. Uninstall old versions (v1.4.x)
  2. Delete old plugin folder from Jellyfin plugins directory
  3. Restart Jellyfin
  4. Install the latest version fresh from repository

Player button not showing

  1. The button only appears in web browsers (Chrome, Edge, Firefox, Brave), not in the native apps (Windows app, mobile, TV). Open Jellyfin via http://YOUR_IP:8096.

  2. Since v1.8.3.35 the plugin adds its script to the web client's page as Jellyfin serves it, so a read-only web folder no longer matters (Windows installer, Debian package, snap, read-only containers). After a restart and the first page load, the log (Dashboard → Logs) shows Player script injected via … or Player script added to index.html as Jellyfin serves it.

  3. Neither line? Then Jellyfin is not serving the web client itself (for example, a separate web server serves jellyfin-web), and the plugin cannot reach the page.

  4. On v1.8.3.34 or older, including v1.8.3.31 for Jellyfin 10.11, the plugin can only edit the file itself. If the log says Could not inject player script into index.html, give Jellyfin write access to the index.html it names, then restart Jellyfin:

    • Windows installer: Jellyfin runs as the NetworkService account by default, which cannot write to Program Files. In an administrator Command Prompt, run icacls "C:\Program Files\Jellyfin\Server\jellyfin-web\index.html" /grant *S-1-5-20:M and restart the "Jellyfin Server" service. If you run the tray app instead of the service, use /grant "%USERNAME%":M.
    • Debian/Ubuntu package: sudo chown jellyfin /usr/share/jellyfin/web/index.html, then sudo systemctl restart jellyfin.

    Until then, opening the plugin's settings page loads the button into that browser tab only; reloading the tab removes it again. A Jellyfin update replaces index.html, so repeat the step if the warning returns.

There is no "Upscale" button on item pages: whole libraries are upscaled by the scheduled task "Scan & Upscale Library" (Dashboard → Scheduled Tasks).

Docker container not starting

# Check logs
docker logs jellyfin-ai-upscaler --tail 50

# Health check
curl http://YOUR_SERVER_IP:5000/health

# GPU diagnostics
curl http://YOUR_SERVER_IP:5000/gpu-verify

# Check GPU (NVIDIA)
docker run --rm --gpus all nvidia/cuda:12.2.2-base-ubuntu22.04 nvidia-smi

GPU not detected

  • NVIDIA RTX 5000 (Blackwell): Requires CUDA 12.8+ — use latest :docker7 image. Compute capability sm_120 auto-detected.
  • NVIDIA RTX 4000/3000/2000: Install nvidia-container-toolkit, use --gpus all. TensorRT is skipped by default — set SKIP_TENSORRT=false only when the image includes compatible TensorRT libraries. CUDA remains the default.
  • Intel: Use :docker7-intel tag with --device=/dev/dri --group-add=render. Check diagnostics: curl http://YOUR_SERVER_IP:5000/gpu-verify
  • AMD: Use :docker7-amd tag with --device=/dev/kfd --device=/dev/dri
  • Apple M1–M5: Docker on macOS runs CPU-only. For GPU acceleration via CoreML/Neural Engine, use the native install:
    cd docker-ai-service && chmod +x install-native-macos.sh && ./install-native-macos.sh
    
  • Windows Docker Desktop (WSL2): Intel/AMD GPUs accessible via /dev/dxg + WSL2-driver mount — see docker-ai-service/docker-compose.yml WSL2 section. NVIDIA: use NVIDIA Container Toolkit. FP16 mismatch (Issue #67) is auto-detected in v1.7.4+ based on the loaded ONNX model's input type.

Proxmox LXC GPU Passthrough

# Add to LXC config (/etc/pve/lxc/<id>.conf):
lxc.cgroup2.devices.allow: c 226:* rwm
lxc.mount.entry: /dev/dri dev/dri none bind,optional,create=dir

# Inside LXC, use Docker with:
--device=/dev/dri --group-add=render

# Verify GPU visibility:
docker exec jellyfin-ai-upscaler curl http://localhost:5000/gpu-verify

Upscaling not working

  1. Verify Docker container is running: docker ps --filter name=jellyfin-ai-upscaler
  2. Test connection: curl http://YOUR_SERVER_IP:5000/health
  3. Check GPU diagnostics: curl http://YOUR_SERVER_IP:5000/gpu-verify
  4. Check AI Service URL in plugin settings
  5. Check Docker logs for errors

Checksum mismatch on install

The plugin repository auto-updates checksums via CI. If you see a mismatch:

  1. Wait 5 minutes for GitHub CDN to refresh
  2. Remove and re-add the repository URL
  3. Try installing again

Support

  • 💬 Support Assistant — an in-browser bot on the docs site (button, bottom-right) that answers from every issue we've ever handled: install errors, GPU not used, Docker/NAS setup, API token, model choice, and more. No login, runs entirely in your browser.
  • Project Website
  • GitHub Issues
  • GitHub Wiki

License

MIT License - See LICENSE for details.

Languages

C#

35.1%

HTML

22.5%

Python

21.4%

JavaScript

16.5%

CSS

2.0%

PowerShell

1.7%