- Python 98.7%
- Dockerfile 0.7%
- Shell 0.6%
| app | ||
| scripts | ||
| tests | ||
| .dockerignore | ||
| .gitignore | ||
| docker-compose.yml | ||
| Dockerfile | ||
| README.md | ||
| requirements-dev.txt | ||
| requirements.txt | ||
| variables.env.example | ||
Replay Service
Replay is a small Quart service that turns each top-level directory in a media library into an endless HLS channel.
Each discovered channel gets its own ffmpeg process, writes HLS output under /tmp/hls/<channel>/, and serves playlists and segments over HTTP.
What it does
- Discovers channels from subdirectories under
LIBRARY_DIR - Recursively scans each channel directory for
.mp4,.mkv, and.tsfiles - Shuffles files and loops them forever through
ffmpeg - Serves HLS playlists at
/<channel>/playlist.m3u8 - Polls the library for file or directory changes and restarts affected channels automatically
Repository layout
app/main.py- HTTP routes and service lifecycleapp/channel.py- channel discovery, file watching,ffmpegorchestration, and HLS generationapp/config.py- environment variables, defaults, logging, and shared in-memory statescripts/run.sh- container entrypointDockerfile- container image builddocker-compose.yml- compose service definition used by the original stack
Requirements
- Python 3.11+
ffmpegandffprobe- A media library where each top-level directory is a channel
Quick start with Docker
- Copy the example environment file:
cp variables.env.example variables.env
Keep LIBRARY_DIR aligned with the container bind mount. The examples in this repo mount the host library at /library, so variables.env should keep LIBRARY_DIR=/library unless you also change the container mount target.
- Create a library with at least one channel directory:
data/
library/
movies/
clip-01.mp4
clip-02.mp4
sports/
match-01.ts
- Build and run the container:
docker build -t replay-service .
docker run --rm \
-p 8090:8090 \
--env-file variables.env \
-v "$(pwd)/data/library:/library:ro" \
replay-service
- Check the service:
curl http://localhost:8090/health
- Open a playlist:
http://localhost:8090/movies/playlist.m3u8
Docker Compose
The checked-in docker-compose.yml mounts the library, loads variables.env, and publishes 8090 on the host.
For a standalone setup, either:
- use the
docker runcommand above, or - run
docker compose up --build
Example:
services:
replay:
ports:
- "8090:8090"
Local development
Install dependencies and run the app directly:
python -m venv .venv
. .venv/bin/activate
pip install -r requirements.txt
export LIBRARY_DIR="$(pwd)/data/library"
export REPLAY_PORT=8090
uvicorn app.main:app --host 0.0.0.0 --port "$REPLAY_PORT" --workers 1
Notes:
- local runs still require
ffmpegandffprobeon your PATH - keep
--workers 1; channel state is stored in process memory
Configuration
These environment variables are read in app/config.py:
| Variable | Default | Purpose |
|---|---|---|
LIBRARY_DIR |
/library |
Root directory scanned for channel folders |
REPLAY_PORT |
8090 |
HTTP listen port |
REPLAY_SCAN_INTERVAL |
60 |
Seconds between library scans |
HLS_SEGMENT_TIME |
4 |
HLS segment duration in seconds |
HLS_LIST_SIZE |
20 |
Number of entries kept in the live playlist |
TRANSCODE_ENABLED |
false |
Enables re-encoding for all channels when true |
VIDEO_BITRATE |
4000k |
Video bitrate used when transcoding |
AUDIO_BITRATE |
128k |
Audio bitrate used when transcoding |
FFMPEG_THREADS |
2 |
Number of threads each ffmpeg transcode process may use |
Channel layout
Each top-level directory inside LIBRARY_DIR becomes a channel name.
Example:
/library
/movies
movie-a.mp4
movie-b.mkv
/sports
game-01.ts
Behavior:
TRANSCODE_ENABLED=falsekeeps copy mode enabledTRANSCODE_ENABLED=trueforces re-encoding throughlibx264and AAC
Copy mode is the default and is the cheapest option, but it only keeps files that are compatible for concat playback. The service uses ffprobe to compare stream signatures and skips incompatible files. If channels contain mixed codecs, frame rates, or audio layouts, set TRANSCODE_ENABLED=true in variables.env.
HTTP endpoints
/- basic service metadata and discovered channel names/health- readiness, current source file and recent ffmpeg error summary for each channel/<channel>/playlist.m3u8- HLS playlist/<channel>/<filename>- HLS segments and related files
Operational notes
- the service writes generated HLS output to
/tmp/hls - one
ffmpegprocess runs per discovered channel - library scanning is polling-based and runs every
REPLAY_SCAN_INTERVALseconds - channel directory additions and removals are picked up on the next scan
- channel file additions and removals trigger a channel restart on the next scan
- changing
TRANSCODE_ENABLEDrequires restarting the service /healthalways returns a JSON summary; use each channel'sreadyflag to see whetherplaylist.m3u8exists yet, andcurrent_file/last_error_fileto identify files involved in playback or recent ffmpeg errors- files that fail
ffprobeare skipped before playback, including in transcode mode
Local checks
Run python -m compileall app && python -m pytest before shipping changes.