4.6 KiB
Pretranscode
A small Jellyfin compatibility cache. It converts only known-incompatible video codecs to uniform 8-bit H.264 while preserving immutable originals.
Design
- Originals are mounted read-only at
/source. - Jellyfin reads a mirror tree at
/data/librarycontaining symlinks. - Each symlink points either to
/data/originalor a validated file under/data/cache. - Completed cache files replace symlinks atomically. Partial files are never exposed.
- Cache eviction restores the original symlink before deleting cached content.
- Jellyfin integration uses only its supported HTTP API.
There is one worker process. x264 uses available CPU cores; nice 19, idle I/O
priority, and minimal Docker CPU shares make normal services take precedence.
Requirements
- Linux, Docker Compose, and ext4 tools
- An existing Jellyfin server and API key
- A shared Docker network between Jellyfin and Pretranscode
- Jellyfin must have read-only access to the originals and cache
Installation
-
Copy
.env.exampleto.envand set all host paths and the Jellyfin API key. -
Provision the claimed cache allocation:
sudo scripts/provision-cache.sh .env -
Mount the same paths in Jellyfin while keeping its existing logical library path:
volumes: - ${PRETRANSCODE_VIEW_PATH}:/data/library:ro - ${MEDIA_LIBRARY_PATH}:/data/original:ro - ${PRETRANSCODE_CACHE_PATH}:/data/cache:ro -
Start Pretranscode:
docker compose --env-file .env up -d --build
For Coolify, import compose.yaml as a Custom Docker Compose resource and enter
the .env.example values through Coolify's environment-variable UI.
Codec policy
Cached video is H.264 High Profile, 8-bit yuv420p, CRF 18, capped at 15 Mbps.
Resolution and frame rate are preserved. Existing audio, subtitles, chapters,
and Matroska attachments are copied. Ten-bit SDR is converted with
error-diffusion dithering; sources carrying explicit HDR10 or HLG transfer
metadata are tone-mapped to BT.709 SDR. The filtered video is mapped as a new
stream so source-only Dolby Vision configuration data cannot leak into H.264
outputs.
The fixed source blacklist is:
- HEVC/H.265, including Dolby Vision
- H.264 Hi10/10-bit
- MPEG-2
- VC-1
Client error reports never modify this list. Encoding settings form part of each cache manifest, so a global quality-policy change automatically invalidates and replaces older renditions without touching originals.
Cache resizing
CACHE_MAX_BYTES is the worker's configurable LRU limit. After changing it and
restarting the container:
- A smaller value evicts the oldest unlocked entries until usage fits.
- A larger value immediately makes the additional capacity available for background population, up to the size of the mounted cache filesystem.
The host filesystem is deliberately separate from application logic. Enlarge or
shrink its preallocated image during infrastructure maintenance, then set the
matching CACHE_MAX_BYTES value in Coolify.
Playback locking
- An active movie locks that movie.
- An active episode locks its complete series.
- Encoding is cancelled and its partial deleted if the item becomes locked.
- A series remains locked for
SERIES_COOLDOWN_SECONDSafter playback. - Recently stopped content receives queue priority.
Failure behavior
Graceful stops delete partial output. After a crash, startup removes stale partials and orphan files. Atomic symlinks always point to either an original or a completed cache file. If the cache mount marker is absent, the worker refuses to start.
Upgrading from v1.2.0
Version 1.2.1 uses a v4 cache profile. Before starting the new worker, stop the old worker and migrate its manifests:
docker compose stop pretranscode
docker compose run --rm pretranscode migrate-v4
The migration re-probes immutable sources, updates every valid v3 manifest, and creates a state backup. Cached H.264 streams that inherited Dolby Vision configuration data are repaired with a lossless stream-copy remux; video is not re-encoded. Other cached media files are not rewritten. The command refuses to run while Jellyfin reports active playback.
Migration
Copy or recreate these resources on the destination:
- Repository and
.env - State directory
- Symlink-view directory
- Cache image/mount, if retaining completed transcodes
The cache is optional and can be rebuilt. The source fingerprint in every manifest prevents stale cache files from replacing changed originals. No Coolify UUID, Docker volume UUID, container name, or Jellyfin database path is stored by the application.