Skip to content

dermute/transqrate

Repository files navigation

transQrate

Tell transQrate how good the result should look — it finds the encoder settings. Instead of guessing quality values, you set a VMAF target (e.g. 95) per media folder: transQrate cuts short sample clips from every file, encodes them at candidate quality levels, scores them with libvmaf and binary-searches the highest ICQ — the smallest file — that still hits your target. The ab-av1 idea, built into a self-hosted transcoding server.

Under the hood: Intel QSV hardware AV1 encoding, Opus audio and a slim web UI — inspired by Unmanic, FileFlows, Tdarr and ab-av1.

Everything runs in a single Docker container built on linuxserver.io's ffmpeg image (ffmpeg 8.x with av1_qsv, libvmaf, libopus and the Intel media stack prebuilt — nothing is compiled from source).

Features

  • VMAF matching (à la ab-av1): set a VMAF target instead of a fixed quality — every file gets exactly the quality you asked for, at the smallest size the encoder can deliver
  • Slim web UI (no build step, no external assets) with dashboard, sources, profiles, logs and settings pages
  • Source folders: assign a transcoding profile per folder, hit Scan now, or enable Watch to queue new files automatically (files are only queued once their size is stable between two scans)
  • Transcoding profiles: predefined AV1 QSV + Opus (ICQ 22) — AV1 via av1_qsv in ICQ mode, audio in Opus keeping the source channel count at 64 kbps per channel. Create as many profiles as you like (fixed ICQ or VMAF target, preset, audio copy/re-encode, container, extra ffmpeg args)
  • Dashboard: live progress (%, fps, speed, ETA), queue, per-file and total saved space, retry/cancel, deletable history (entries and their logs)
  • Logs in the UI: full ffmpeg output per job plus the application log
  • Finished files are tagged with a TRANSQRATE metadata tag (visible via ffprobe), so they are recognized and never transcoded twice — even after moving them around
  • SQLite for all state (profiles, sources, jobs, settings) in /config

Quick start

# docker-compose.yml
services:
  transqrate:
    image: ghcr.io/dermute/transqrate:latest
    container_name: transqrate
    ports:
      - "8585:8585"
    devices:
      - /dev/dri:/dev/dri           # Intel GPU for QSV
    volumes:
      - ./config:/config            # database, logs, temp files
      - /path/to/media:/media/library      # a source folder
      - /path/to/transcoded:/output        # optional output folder
    environment:
      - PUID=1000                   # files on the volumes are written as
      - PGID=1000                   # this user/group ...
      - UMASK=022                   # ... with these permissions
    restart: unless-stopped
docker compose up -d

Open http://localhost:8585, add /media/library as a source, pick a profile, press Scan now.

User / group / permissions

The container starts as root (as Portainer & friends do), then drops to an unprivileged user before running the app — the familiar linuxserver.io mechanism, since the image is built on their s6-overlay base:

  • PUID / PGID (default 911): everything the app writes to the volumes — transcoded files, database, logs — is owned by this uid/gid. Match them to the owner of your media folders (id youruser).
  • UMASK (default 022): permission mask for created files (022644, 002 → group-writable 664).
  • /config is chowned to PUID:PGID on start; GPU access is set up automatically (the user is added to the group owning /dev/dri/*).

Don't add init: true / --init: the image's s6 init must be PID 1 and already reaps ffmpeg children itself.

Source & output folders

Mount every folder transQrate should see as a volume, then reference the container paths in the web UI. Each source folder has an optional output folder:

  • No output folder — transcode in place: the finished file replaces the original (same location and name, container extension of the profile).
  • With an output folder (e.g. /output) — the source tree is mirrored there (/media/library/A/b.mkv/output/A/b.mkv). The per-source "Delete original after transcoding" toggle (on by default for new sources) decides whether the source file is removed after a successful encode or kept.

Hardware requirements

AV1 hardware encoding (av1_qsv) needs an Intel GPU with an AV1 encoder: Arc (DG2) discrete GPUs or Meteor Lake (Core Ultra) and newer iGPUs. Older iGPUs (UHD 6xx/7xx) can decode AV1 but not encode it — use a profile with hevc_qsv instead. The container needs access to /dev/dri.

Multiple GPUs (e.g. iGPU + Arc): map the render node you want at its original path (renaming breaks libva's sysfs lookup) and point the qsv_device setting (Settings page) at it, e.g. /dev/dri/renderD129.

How VMAF matching works

For a profile in VMAF target mode, each file first goes through an analyzing phase:

  1. 2–6 sample clips (20 s each, spread across the runtime) are extracted losslessly
  2. Samples are encoded with the profile's settings at a candidate ICQ and compared against the source clips with libvmaf
  3. A binary search over the ICQ range (default 16–35) finds the highest ICQ whose mean sample VMAF still reaches the target — highest ICQ means smallest file
  4. The whole file is then encoded once with the chosen ICQ; the predicted VMAF and chosen ICQ are shown on the dashboard

Sample count, clip length and the ICQ search range are configurable on the Settings page. Cost: roughly 4–6 short sample encodes + VMAF runs per file.

If the profile downscales, the reference sample is passed through the identical scale filter (pure filtering, no re-encode) so both sides are compared at the output resolution — the score isolates codec fidelity instead of punishing the intentional resolution change (same approach as ab-av1's --reference-vfilter default).

The VMAF model is chosen automatically: comparisons at 4K (4K source, no downscale) use Netflix's vmaf_4k_v0.6.1 — the default 1080p model is several points too pessimistic on 4K frames. Everything at 1080p or below (including downscaled targets) uses the standard model.

Profiles

Field Meaning
Video codec any ffmpeg encoder, default av1_qsv
Preset veryfastveryslow (QSV target usage)
Quality mode Fixed ICQ or VMAF target
ICQ QSV Intelligent Constant Quality, lower = better (default 22)
VMAF target e.g. 95 — auto-derives the ICQ per file
Output resolution keep source, or downscale to 480p/720p/1080p/4K (never upscales; width-based so scope content maps correctly)
Bit depth keep source (default; HDR content stays 10-bit) or force 8-bit
Audio Opus at N kbps × channel count (channels preserved), or copy
Audio channel limit optional downmix cap, e.g. max 5.1 turns 7.1 into 5.1 but leaves stereo untouched
Container mkv (recommended) or mp4
Extra video args appended verbatim, e.g. -look_ahead_depth 100 -extbrc 1 -adaptive_i 1 -adaptive_b 1 -g 240 (the default profiles' setting)

Subtitles, chapters, metadata and (for mkv) font attachments are carried over; mov_text subtitles are converted to SRT.

Behavior details

  • In place vs. output folder: without an output folder the original is replaced (same name, profile's container extension). With an output folder the source tree is mirrored there; the per-source delete original toggle (default on for new sources) controls whether the source file is removed after a successful encode.
  • Skip if larger: if the encode ends up bigger than the source, the result is discarded and the original is moved (delete original on) or copied (off) to the output folder, so the output tree always holds every processed file (configurable via skip_if_larger).
  • Tagging: outputs carry TRANSQRATE=1, TRANSQRATE_PROFILE, TRANSQRATE_ICQ and TRANSQRATE_DATE format tags.
  • Watch: every scan_interval_s (default 300 s) watched folders are scanned; unstable (still copying) files wait one more round.
  • HDR: with bit depth keep source, 10-bit and the PQ/HLG colorimetry survive the encode (HDR10-compatible AV1 output). HDR10+ and Dolby Vision dynamic metadata is not carried over.
  • Crash safety: encodes write to hidden .…tqtmp.… temp files that are atomically renamed on success and ignored by the scanner.

Development

docker compose up --build          # run
# API docs: http://localhost:8585/docs

Layout: transqrate/ Python package (FastAPI app in main.py, workers in worker.py, VMAF search in vmaf.py, scanner in scanner.py), vanilla-JS UI in transqrate/static/. State lives in /config/transqrate.db; logs in /config/logs/.

AI attribution

This project was developed with AI assistance (Anthropic Claude, Fable 5).

License

MIT — see LICENSE.

About

Self-hosted media transcoding with Intel QSV AV1, Opus audio and VMAF-targeted quality search

Resources

License

Stars

0 stars

Watchers

0 watching

Forks

Releases

No releases published

Packages

 
 
 

Contributors