Skip to content

Getting Started

Back to Docs

Get preview thumbnails generating in minutes — for Plex, Emby, Jellyfin, or any combination of them.

Important

This page is the source of truth for installation and first-time setup. For operations and troubleshooting, use Guides & Troubleshooting. For exact settings and API contracts, use Configuration & API Reference.

Contents


Prerequisites

  1. At least one media server reachable from this container — any of:
  2. Plex Media Server — sign in with your Plex account in the Setup Wizard, or paste a server URL + token if you'd rather skip the sign-in
  3. Emby Server — server URL + API key
  4. Jellyfin Server — use Quick Connect in the Setup Wizard (a friendlier sign-in code, no token to copy), or paste a server URL + API key
  5. Docker installed on your server

You can configure several servers — even a mix of vendors — and a single FFmpeg pass will publish to every server that owns the file.


Quick Start (Docker)

Step 1: Run the Container

docker run -d \
  --name media-preview-generator \
  --restart unless-stopped \
  -p 8080:8080 \
  --device /dev/dri:/dev/dri \
  -e PUID=1000 \
  -e PGID=1000 \
  -v /path/to/media:/media:ro \
  -v /path/to/plex/config:/plex:rw \
  -v /path/to/app/config:/config:rw \
  -v /etc/localtime:/etc/localtime:ro \
  stevezzau/media_preview_generator:latest

Replace /path/to/media, /path/to/plex/config, and /path/to/app/config with your actual paths.

Important

This command is for Plex, which only writes into /plex, so the media can stay :ro. Using Emby, or Jellyfin? Change the media mount to :rw, because those previews are written next to each video. (Jellyfin's off-media mode is the exception.) See Volume Mounts.

Note

No environment variables are required for first-time setup. Server connections (Plex / Emby / Jellyfin), libraries, GPU/CPU threads, and path mappings are all configured in the Setup Wizard and Settings. Environment variables are optional overrides (see Reference).

Tip

Timezone: The /etc/localtime mount ensures log timestamps and scheduled jobs use your local time. If your host doesn't have this file (e.g. some NAS devices), use -e TZ=America/New_York instead (replace with your timezone).

Step 2: Get Your Access Token

Find your token using the Authentication Token section below.

Step 3: Complete the Setup Wizard

  1. Open http://YOUR_SERVER_IP:8080
  2. Enter the authentication token from the logs
  3. Follow the wizard:
  4. Step 1 — Pick your first server: Plex, Emby, or Jellyfin. The vendor card expands to its friendliest sign-in flow:
    • PlexSign in with Plex OAuth (or paste a URL + token). If your Plex account has access to multiple servers, the wizard lists them all — tick one to configure in detail, or tick several to batch-add with shared defaults.
    • Emby → enter the server URL + username/password (or API key).
    • Jellyfin → enter the server URL + Quick Connect code (shown inside Jellyfin's web UI — no password leaves your browser), or paste a URL + API key.
  5. Step 2 — Libraries (Plex single-server flow): pick which Plex libraries to enable. Emby and Jellyfin flows skip this step; libraries are managed later from the Servers page alongside their per-server path mappings.
  6. Step 3 — Path Configuration (Plex single-server flow): confirm the Plex application data folder (where Plex stores its preview bundles — usually mounted as /plex on this container) and any media path mappings.
  7. Step 4 — Processing Options: GPU/CPU workers, FFmpeg threads, thumbnail interval, quality — these are global (shared across every configured server).
  8. Step 5 — Security: view or replace your access token (optional).

You can add more servers — any vendor, any number of each — at any time from Servers in the top nav. A file present on multiple servers is generated once and published to each in its native format.


This tool generates video preview thumbnails only — the small frames you see when you drag the scrub bar. Plex stores them as BIF bundles, Emby reads a BIF sidecar next to the media file, and Jellyfin reads a folder of JPG tile sheets called trickplay next to the media file. The app writes the right format for each. It does not generate chapter thumbnails, intro/credit detection, or other media analysis.

Turn off built-in preview generation on each server you configure so this tool isn't competing with a redundant CPU job:

  • PlexSettings → Library → Generate video preview thumbnails → Never.
  • Emby — Emby can make its own thumbnails during library scans. Turn off its scan-time thumbnail extraction and chapter-image extraction on each library (ExtractTrickplayImagesDuringLibraryScan and ExtractChapterImagesDuringLibraryScan). The Previews Readiness card recommends this and turns them off when you click Disable; the app never changes them on its own.
  • Jellyfin — keep "Trickplay image extraction" enabled on each library (Jellyfin only reads this app's published tiles when that flag is on). What to do with "Extract trickplay images during library scan" depends on the Media Preview Bridge plugin:
  • Plugin installed: turn it off. The plugin registers new tiles directly, so Jellyfin's own extraction is wasted CPU.
  • No plugin: keep it on. Without the plugin, this flag is what lets Jellyfin pick up the tiles on its next library scan. Off means new previews wait for Jellyfin's daily trickplay task (3 AM by default).

The Previews Readiness card (Servers → Edit → Setup Health) checks all of these and can toggle them for you.

Tip

After setup, you probably want one or both of: - Radarr/Sonarr webhooks — auto-process new imports. - A daily cron schedule (0 2 * * *) in the web UI under Schedules — catches anything the webhooks miss.


Volume Mounts

Container Path Purpose Mode Required when…
/media Your media files. FFmpeg reads the videos from here. ro if only Plex is configured. rw if Emby is configured, or Jellyfin in its default layout. Always.
/plex Plex application data (where Plex stores BIF bundles) rw Plex is configured — BIFs land inside Plex's config at Media/localhost/<hash>/.../index-sd.bif. Omit this mount entirely if you don't configure any Plex server.
/config This app's settings, schedules, and job history rw Always

Which servers need the media mounted read-write:

  • Plex: no. Previews go into /plex. Nothing is written next to the video, so :ro is fine.
  • Emby: yes. The BIF is written next to each video (for example <video>-320-10.bif).
  • Jellyfin, default layout: yes. Tiles are written next to each video (<video>.trickplay/).
  • Jellyfin, off-media mode: no. Tiles go into Jellyfin's data folder instead. This needs the Media Preview Bridge plugin and Jellyfin's config folder mounted read-write in this container. See off-media mode.

With a :ro media mount, Emby and default-layout Jellyfin can't save anything. Each file fails for that server with "Could not write preview file: … Read-only file system" in the job's per-server result and the logs. Those files aren't retried. Other servers on the same file are unaffected. No setup check warns about this in advance.

Note

Jellyfin and Emby don't need Plex's config mount. Emby BIFs and default-layout Jellyfin tiles are written next to each media file under /media, and the scan-nudge is sent over HTTP. So a Jellyfin-only or Emby-only setup needs /media (read-write) + /config. Jellyfin's off-media mode needs Jellyfin's config folder (read-write) as well. A mixed setup needs whichever mount(s) each configured server requires.


Authentication Token

Use this section whenever documentation asks for your authentication token.

docker logs media-preview-generator | grep "Token:"

You can also set a fixed token for predictable logins:

WEB_AUTH_TOKEN=your-password

Docker Compose

See docker-compose.example.yml for ready-to-use configurations:

Configuration Use Case
GPU Single service covering Intel, AMD, and NVIDIA with inline comments
CPU-Only Minimal service; disable all GPUs in Settings after first run
Unraid Intel iGPU default with NVIDIA alternative, Unraid paths and permissions

Copy the file, uncomment the section for your hardware, and adjust volume paths.

Warning

Don't set init: true. This container manages its own processes internally; init: true conflicts with that and prevents the container from starting.


GPU Acceleration

Hardware-accelerated video processing for faster thumbnail generation. To check what's detected on your system, open the web UI (http://YOUR_IP:8080) and go to Settings or Setup — detected GPUs are listed there with device IDs, names, and types.

Supported GPUs

GPU Type Platform Acceleration Docker Support
NVIDIA Linux CUDA/NVENC NVIDIA Container Toolkit
AMD Linux VAAPI Device passthrough
Intel Linux VAAPI/QuickSync Device passthrough
NVIDIA Windows CUDA/NVENC Docker Desktop (WSL2 backend) + --gpus all
AMD/Intel Windows Not available — CPU only
Apple Silicon / Intel macOS Not available — CPU only

Note

NVIDIA on Windows works under Docker. The NVIDIA Windows driver exposes CUDA and NVDEC into WSL2, so Docker Desktop using the WSL2 backend accelerates much like Linux does. Follow the NVIDIA GPU steps below — you do not need to install anything inside WSL. Treat it as best-effort: GPU detection under WSL2 is less reliable than on a native Linux host.

Note

AMD/Intel on Windows and all GPUs on macOS cannot be accelerated under Docker. Docker Desktop runs a Linux VM, and D3D11VA (Windows AMD/Intel) and VideoToolbox (macOS) are host-OS frameworks that the VM cannot reach. Those setups process on CPU — raise CPU Workers in Settings to compensate. If you need GPU acceleration, run the container on a Linux host. Apple Silicon users still benefit from the native ARM64 image (no Rosetta overhead).

Intel iGPU (QuickSync)

Most common setup, especially on Unraid.

docker run -d \
  --device /dev/dri:/dev/dri \
  -e PUID=1000 \
  -e PGID=1000 \
  stevezzau/media_preview_generator:latest

Verify the device exists:

ls -la /dev/dri
# Should show: card0, renderD128

The container auto-detects GPU device groups at startup and adds the internal user to them. If you still see permission errors, check that you are passing the entire /dev/dri directory (not a single sub-device):

# Correct — pass the whole directory
--device /dev/dri:/dev/dri

# Wrong — single device may prevent group auto-detection for other nodes
--device /dev/dri/renderD128:/dev/dri/renderD128

To debug, find the device group on the host:

stat -c '%g' /dev/dri/renderD128
# Common groups: 'video' (44), 'render' (105) — varies by distro

NVIDIA GPU

Prerequisites (Linux hosts):

  1. Install NVIDIA drivers
  2. Install NVIDIA Container Toolkit

Note

On Windows, skip step 2. Docker Desktop provides the toolkit itself — you only need the NVIDIA Windows driver and the WSL2 backend. See Windows below. The docker run command itself is identical.

docker run -d \
  --gpus all \
  -e NVIDIA_VISIBLE_DEVICES=all \
  -e NVIDIA_DRIVER_CAPABILITIES=all \
  stevezzau/media_preview_generator:latest

Tip

Why NVIDIA_DRIVER_CAPABILITIES=all? Dolby Vision Profile 5 videos need the NVIDIA Vulkan driver to be tone-mapped; the all value is what makes that driver available inside the container. Without it, the app skips tone mapping for those files and their thumbnails come out visibly dim. (The older compute,video,utility setting is fine for everything except Dolby Vision Profile 5.)

Tip

Multi-GPU? Hosts with two or more NVIDIA cards are detected automatically — each card appears as a separate row in Settings → Processing Options → GPU Configuration with its own enable toggle, worker count, and FFmpeg thread setting. Work spreads across cards.

Docker Compose:

services:
  plex-previews:
    image: stevezzau/media_preview_generator:latest
    deploy:
      resources:
        reservations:
          devices:
            - driver: nvidia
              count: all
              capabilities: [gpu]

AMD GPU

docker run -d \
  --device /dev/dri:/dev/dri \
  -e PUID=1000 \
  -e PGID=1000 \
  stevezzau/media_preview_generator:latest

AMD requires proper VAAPI drivers on the host system. GPU device groups are auto-detected at container startup.

Windows

NVIDIA: works under Docker. Requirements:

  1. Docker Desktop using the WSL2 backend (Settings → General → Use the WSL 2 based engine) — GPU support is WSL2-only, it does not work with the Hyper-V backend.
  2. A current NVIDIA Windows driver with WSL2 GPU paravirtualization support. This is what provides CUDA and NVDEC inside WSL2.
  3. An up-to-date WSL2 kernel — run wsl --update.

Then use the same docker run command as Linux, including --gpus all; see NVIDIA GPU above. There is nothing to install inside WSL itself, and the NVIDIA Container Toolkit step is not needed — Docker Desktop wires that up for you.

Omit --device /dev/dri:/dev/dri on Windows. Under WSL2 that node is a WSLg/d3d12 stub rather than a real GPU, and passing it through can register a phantom device or muddy vendor detection.

Verify by opening the web UI and checking SettingsProcessing OptionsGPU Configuration; your card should be listed with CUDA acceleration.

Note

Treat WSL2 as best-effort. GPU vendor detection via lspci is unreliable inside WSL2, so the app leans on nvidia-smi instead and logs a note saying so. If GPU jobs misbehave, run the container on a Linux host.

AMD and Intel: not accelerated under Docker. These rely on D3D11VA, a Windows-only framework that Docker Desktop's Linux VM cannot reach, so processing falls back to CPU. Raise CPU Workers in Settings to compensate, or run the container on a Linux host to use VAAPI/QuickSync.

macOS

Not accelerated under Docker, on either Apple Silicon or Intel. VideoToolbox is a macOS framework and Docker Desktop's Linux VM has no access to it, so processing uses CPU. Raise CPU Workers in Settings to compensate, or run the container on a Linux host for GPU acceleration.

Apple Silicon Macs do still benefit from the native ARM64 image — the container runs without Rosetta emulation overhead, which makes CPU processing meaningfully faster than an emulated x86 image.

Worker Configuration

A worker is a parallel slot that processes one file at a time — more workers means more files processed simultaneously, but higher CPU/GPU load.

In SettingsProcessing Options, the GPU panel lists all detected GPUs. Enable or disable each GPU independently and set workers and FFmpeg threads per GPU. For CPU-only mode, disable every GPU (or set workers to 0) and set CPU Workers to your desired value (e.g. 8).

Performance Tuning

Workers Recommendation
1 GPU × 1 worker, CPU: 1 Default (safe for all hardware)
1 GPU × 4 workers, CPU: 2 Balanced (mid-range systems)
1 GPU × 8 workers, CPU: 4 High-end systems
0 GPU, CPU: 8 CPU-only

Configure per-GPU workers and FFmpeg threads in SettingsProcessing Options. Start with the defaults and increase gradually; monitor system load to find the best balance.


Unraid

Two install paths: the Community Applications template (easiest) or a manual docker run command (more control).

Easiest: search for media-preview-generator in Community Applications and install the template.

Quick start (either path):

  1. Run the container (CA template or docker run below).
  2. Open the Web UI at http://YOUR_UNRAID_IP:8080.
  3. Get the authentication token from container logs, or set WEB_AUTH_TOKEN on the container.
  4. Complete the Setup Wizard — sign in with Plex, configure settings.

Tip

The setup wizard guides you through Plex OAuth, Emby URL + API key, or Jellyfin Quick Connect — no need to copy tokens by hand for any of the supported vendors.

Manual Docker Run — Intel iGPU (Most Common)

docker run -d \
  --name media-preview-generator \
  --restart unless-stopped \
  -p 8080:8080 \
  -l net.unraid.docker.webui="http://[IP]:[PORT:8080]/" \
  --device /dev/dri:/dev/dri \
  -e WEB_AUTH_TOKEN=my-secret-password \
  -e PUID=99 \
  -e PGID=100 \
  -v /mnt/user/data/plex:/data/plex:ro \
  -v "/mnt/cache/appdata/plex/Library/Application Support/Plex Media Server":/plex:rw \
  -v /mnt/user/appdata/media-preview-generator:/config:rw \
  -v /etc/localtime:/etc/localtime:ro \
  stevezzau/media_preview_generator:latest
Setting Value Description
-l net.unraid.docker.webui Web UI label Adds a Web UI button in Unraid
--device /dev/dri GPU passthrough Intel VAAPI/QuickSync
WEB_AUTH_TOKEN my-secret-password Your login password (set your own!)
PUID/PGID 99/100 Unraid nobody:users account
/data/plex Media path Same as Plex container = no path mapping
/config App config Settings persist here

Intel iGPU with Custom Network (br0/br1)

If you use a custom Docker network with fixed IPs (common on Unraid):

docker run -d \
  --name media-preview-generator \
  --restart unless-stopped \
  --network=br0 \
  --ip=192.168.1.50 \
  --device /dev/dri:/dev/dri \
  -e WEB_AUTH_TOKEN=my-secret-password \
  -e PUID=99 \
  -e PGID=100 \
  -v /mnt/user/data/plex:/data/plex:ro \
  -v "/mnt/cache/appdata/plex/Library/Application Support/Plex Media Server":/plex:rw \
  -v /mnt/user/appdata/media-preview-generator:/config:rw \
  -v /etc/localtime:/etc/localtime:ro \
  stevezzau/media_preview_generator:latest

Note

When using --network with a fixed IP, you don't need -p 8080:8080 — access the web UI directly at http://192.168.1.50:8080

NVIDIA GPU on Unraid

Requires: Nvidia-Driver plugin from Community Applications.

docker run -d \
  --name media-preview-generator \
  --restart unless-stopped \
  --runtime=nvidia \
  -p 8080:8080 \
  -e NVIDIA_VISIBLE_DEVICES=all \
  -e NVIDIA_DRIVER_CAPABILITIES=all \
  -e WEB_AUTH_TOKEN=my-secret-password \
  -e PUID=99 \
  -e PGID=100 \
  -v /mnt/user/data/plex:/data/plex:ro \
  -v "/mnt/cache/appdata/plex/Library/Application Support/Plex Media Server":/plex:rw \
  -v /mnt/user/appdata/media-preview-generator:/config:rw \
  -v /etc/localtime:/etc/localtime:ro \
  stevezzau/media_preview_generator:latest

Warning

On Unraid, use --runtime=nvidianot --gpus all. If you see docker: Error response from daemon: AMD CDI spec not found, that's Docker trying to resolve --gpus all through the Container Device Interface (CDI). Unraid doesn't ship CDI specs, so the daemon falls through to whatever spec it finds. Switching to --runtime=nvidia bypasses CDI and uses the Nvidia-Driver plugin directly.

Important Unraid Notes

PUID/PGID Values — Unraid uses nobody:users by default:

Variable Value Description
PUID 99 nobody user
PGID 100 users group

Emby or Jellyfin on Unraid — the commands above are for Plex. For Emby, or Jellyfin in its default layout, change /data/plex:ro to /data/plex:rw, because those previews are written next to each video. See Volume Mounts.

Network Considerations — When completing the Setup Wizard, make sure each media server you pick is reachable from the container (not localhost from Unraid's perspective). Plex appears as a dropdown after OAuth sign-in; Emby and Jellyfin are connected by URL.

Check Intel GPU Exists:

ls -la /dev/dri
# Should show: card0, renderD128

Path Mapping for Plex

Path mapping is only needed when this container mounts media to a different path than Plex uses.

No Mapping Needed (Recommended) — mount media to the same container path as Plex:

Plex:           /mnt/user/data/plex → /data/plex
This container: /mnt/user/data/plex → /data/plex  ← Same path!

With Path Mapping — if you prefer mounting to /media:

Plex:           /mnt/user/data/plex → /data/plex
This container: /mnt/user/data/plex → /media      ← Different path

Add path mapping:

-e PLEX_VIDEOS_PATH_MAPPING=/data/plex \
-e PLEX_LOCAL_VIDEOS_PATH_MAPPING=/media \

See Path Mappings for more examples.

TRaSH Guide Folder Structure

For users following TRaSH Guides:

  1. Configure Plex Container — add a second container path:
  2. Container path: /server/media/plex/
  3. Host path: /mnt/user/media/plex/

  4. Update Plex Libraries — use the new mapping:

  5. Format: //server/media/plex/<media-folder>
  6. Example: //server/media/plex/tv

  7. Set Permissions:

    # Make the Plex Media folder writable by the Unraid 'users' group (PGID=100)
    # so this app's container (running as PUID=99 / PGID=100) can write previews into it.
    chown -R 99:100 /mnt/cache/appdata/plex/Library/Application\ Support/Plex\ Media\ Server/Media/
    chmod -R 775   /mnt/cache/appdata/plex/Library/Application\ Support/Plex\ Media\ Server/Media/
    
    (Older guides recommended chmod 777 — that works but grants world-writable access; chown + 775 is the safer equivalent.)


Networking

Important

Use a routable IP address for your media servers, not localhost. The container can't reach localhost on your Docker host. If you set a server URL manually, use something like http://192.168.1.100:32400 (Plex), http://192.168.1.100:8096 (Emby/Jellyfin), etc. The Setup Wizard picks routable URLs for you when it can.

Quick Decision Tree

Where is the media server?
├── Same Docker host as this app?
│   ├── Server uses host network      → Use --network host
│   ├── Server uses bridge (default)  → Use same network or server IP:port
│   └── Server uses custom (br1, macvlan) → Use same custom network
└── Different machine?
    └── Use bridge network with the server's IP address

The same logic applies whether the target is Plex (port 32400), Emby (port 8096), or Jellyfin (port 8096). If you run more than one server, check each is reachable from the container independently — the Servers page shows a red connection pill for any server the app can't reach.

Custom Network Example (Unraid)

docker run -d \
  --name media-preview-generator \
  --restart unless-stopped \
  --network=br1 \
  --ip=192.168.1.51 \
  -l net.unraid.docker.webui="http://[IP]:[PORT:8080]/" \
  --device /dev/dri:/dev/dri \
  -e WEB_AUTH_TOKEN=your-password \
  -e PUID=99 \
  -e PGID=100 \
  -v /mnt/user/data/plex:/data/plex:ro \
  -v "/mnt/cache/appdata/plex/Library/Application Support/Plex Media Server":/plex:rw \
  -v /mnt/user/appdata/media-preview-generator:/config:rw \
  -v /etc/localtime:/etc/localtime:ro \
  stevezzau/media_preview_generator:latest

Note

Use Unraid's PUID=99, PGID=100 (nobody:users).


Common Operations

View Logs

docker logs media-preview-generator          # All logs
docker logs -f media-preview-generator       # Follow logs

Update

docker pull stevezzau/media_preview_generator:latest
docker stop media-preview-generator
docker rm media-preview-generator
# Re-run your docker run command

Your /config/settings.json persists between upgrades, so Plex auth, GPU config, and schedules come back automatically after re-running the container.

Image Tags

Tag Source Use for
:latest Latest GitHub release Recommended. Stable.
:X.Y.Z (version) A specific release (e.g. :3.7.5) Pinning to a known-good version
:dev Every push to dev Bleeding edge — may break

See the releases page for version history and per-release notes.

The web UI's version banner behaves accordingly: :latest / pinned versions compare against the latest GitHub release; :dev compares the baked commit SHA against the dev branch HEAD on GitHub.


Next Steps


Back to Docs | Main README