Files
pretranscode/README.md
T

115 lines
3.7 KiB
Markdown

# Pretranscode
A small, single-writer Jellyfin compatibility cache.
Immutable source media is the only source of truth. The cache is a disposable,
filesystem-derived projection and contains no manifests, inventory database, or
migration state.
## Compatibility output
A cached rendition contains only:
- one H.264 High, 8-bit `yuv420p` video stream;
- one AAC-LC, 192 kbps stereo audio stream when the source has audio;
- BT.709 SDR output for HDR10, HLG, and Dolby Vision sources.
Embedded subtitles, attachments, chapters, and inherited metadata are omitted.
Sidecar subtitle files are mirrored unchanged. This deliberately small profile
prevents EAC3/Atmos, PGS, or stale Dolby Vision metadata from forcing another
Jellyfin transcode.
A source needs a compatibility rendition unless it is already H.264 8-bit with
AAC stereo audio and no embedded subtitles.
## Durable model
- Originals are mounted read-only at `/source`.
- Jellyfin reads `/data/library`, a symlink mirror under `/view`.
- A link targets either `/data/original` or an immutable cache object.
- Cache names encode hashes of the logical path, source generation, complete
encoding profile, and output bytes.
- FFmpeg writes only a `.partial` file.
- Publication is validation, `fsync`, an atomic output rename, then an atomic
symlink rename.
- Open clients retain their old inode during publication.
- A profile change builds a new object while the published old-profile object
remains available. The old object is collected only after replacement.
- LRU updates file access time only; published media modification time and bytes
never change.
- Scheduling, retries, priorities, and cooldowns exist only in process memory.
- One controller owns the cache directory through `flock()`.
There is no cache migration command. Restarting with a changed profile is enough.
## Installation
1. Copy `.env.example` to `.env` and set the host paths and Jellyfin API key.
2. Provision the cache filesystem:
```bash
sudo scripts/provision-cache.sh .env
```
3. Mount the same paths in Jellyfin:
```yaml
volumes:
- ${PRETRANSCODE_VIEW_PATH}:/data/library:ro
- ${MEDIA_LIBRARY_PATH}:/data/original:ro
- ${PRETRANSCODE_CACHE_PATH}:/data/cache:ro
```
4. Start the controller:
```bash
docker compose --env-file .env up -d --build
```
For Coolify, import `compose.yaml` as a Custom Docker Compose resource and enter
`.env.example` values through the environment-variable UI.
## Operation
The controller has one worker. FFmpeg and file hashing run with `SCHED_IDLE` and
idle I/O priority so foreground services take precedence.
Playback locking rules:
- an active movie locks that movie;
- an active episode locks its series;
- an encode is cancelled if its item becomes locked;
- a series remains locked for `SERIES_COOLDOWN_SECONDS` after playback;
- recently watched content receives in-memory queue priority.
Commands:
```bash
pretranscode run
pretranscode sync
pretranscode active
pretranscode trim BYTES
pretranscode purge
pretranscode --version
```
`purge` and `trim` refuse to operate during active playback.
## Failure behaviour
Partial outputs are never published. Startup removes stale partials and
unreferenced objects. A missing cache mount marker, second controller, failed
probe, failed validation, failed `fsync`, or unavailable Jellyfin session API
causes work to stop safely rather than mutate published media.
## Tests
The image build treats compiler warnings as errors and runs the C core tests.
The integration test creates incompatible media and verifies canonical streams,
atomic publication, refresh retry, and stateless profile replacement:
```bash
podman build -t pretranscode-test .
IMAGE=pretranscode-test tests/integration.sh
```