3.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, Level 4.1, 8-bit yuv420p, CRF 20, capped
at 12 Mbps. Existing audio, subtitles, chapters, and Matroska attachments are
preserved when the container supports them.
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.
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.
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.