Development tools
fake_jellyfin_server.py — fake Jellyfin server
Minimal server (standard Python + ffmpeg) that imitates the behavior of Jellyfin 12.1 as observed on a
real server: authentication through the MediaBrowser header only, progressive MPEG-TS stream without
Accept-Ranges, startTimeTicks, reuse of the transcoding job for identical deviceId +
playSessionId, DELETE /Videos/ActiveEncodings (400 without playSessionId), reporting through
/Sessions/Playing[/Progress|/Stopped]. It is used by the automated tests (ctest) and for manual trials.
# Test file: the luminance of the picture is 16 + 5 x T (the position can be read from the content)
python3 tools/fake_jellyfin_server.py --make-media /tmp/pattern.mp4 --seconds 600
# Realistic server: ~10 s before the first frames, transcoding at 1.5x real time
python3 tools/fake_jellyfin_server.py --media /tmp/pattern.mp4 --port 8096 \
--startup-delay 10 --readrate 1.5 --verbose
# Credentials: test / test (--user, --password)
User interface catalog: --catalog demo serves two libraries (Movies, Series), about fifteen movies
and two playable series (all of them stream the test pattern), PNG posters generated on the fly
(/Items/{id}/Images/Primary, one movie without a poster, one whose poster answers 404), Unicode
titles taken from a real library (U+2010 hyphen, U+2019 apostrophe, "F1®", a title prefixed with
U+200E, accented capitals, ligatures) and two started playbacks ("Continue watching").
The default catalog (minimal) is the one used by the network playback tests.
--language en|fr (default en) sets the language of the demo content (titles, library names,
subtitle cues, track titles).
python3 tools/fake_jellyfin_server.py --media /tmp/pattern.mp4 --port 8096 --catalog demo
./build-linux/pelagia --server http://127.0.0.1:8096 # credentials test / test
Audio tracks and subtitles: --catalog tracks = demo + four movies that carry MediaStreams:
"1917" (real case observed: HEVC video, English TrueHD, French E-AC3 by default, two French PGS of
which one is the default, none forced), a subbed movie (3 audio tracks, one of them a mono 44.1 kHz AAC
that the server "copies", SRT / ASS / PGS subtitles of which one is forced), a dubbed movie and a
movie with external subtitles. Each audio track is a sine wave with its own frequency (the decoded
content tells which track was served), subtitleStreamIndex + subtitleMethod=Encode burns a red
square into the top left corner, Subtitles/{n}/Stream.srt serves a 3.5 s cue every 5 s (the ASS is
"converted" with tags), PGS tracks answer 404. The profile (User.Configuration) is set with
--audio-language, --subtitle-language, --subtitle-mode, --no-play-default-audio,
--no-remember, or live (/__test/config: subtitle_mode, omit_defaults, remember_selections,
subtitle_delay...); the tracks reported by Progress are remembered and returned as the default
tracks.
Driving the server during a trial (another terminal):
curl -s localhost:8096/__test/state | python3 -m json.tool # requests, jobs, reports
curl -s -X POST localhost:8096/__test/fault -d '{"drop": true}' # cuts the open streams (RST)
curl -s -X POST localhost:8096/__test/fault -d '{"stall_s": 5}' # freezes sending for 5 s
curl -s -X POST localhost:8096/__test/fault -d '{"drop": true, "refuse_s": 8}' # "Wi-Fi down" for 8 s
curl -s -X POST localhost:8096/__test/config -d '{"stream_auth": "header"}' # refuses api_key
Useful options: --pts-mode absolute (absolute PTS after startTimeTicks instead of restarting
from ~0), --stream-auth query|header|both, --min-resume-duration (300 s by default, like Jellyfin).