Your video library,
streamed on demand.
streamd turns a folder of video files — anything ffmpeg can read, from mp4 and mkv to mov, webm and ts — into adaptive HLS. It transcodes only the segments someone is watching, the moment they ask for them, and cleans up when they stop.
streamd
# Nothing is transcoded until someone presses play
The playlist lists every segment of the file from the start, so each one has an address before it exists. streamd makes a segment when it’s requested and throws it away once nobody needs it.
# Request
The player asks for the segment under the playhead — at the start, or eleven hours in. The address is already in the playlist.
GET …//seg_.ts# Transcode that segment
streamd starts ffmpeg at that segment’s timestamp and encodes just that range, then keeps pace with playback. Seams line up to 0.0 ms.
ffmpeg -ss -t -i marathon.mkv …# Stream, then clean up
The segment goes out as ordinary HLS. When the last viewer leaves, the working files are removed after the idle timeout.
viewers 0 → idle cleanup# What it does
# On-demand transcoding
Starts instantly and seeks anywhere, even 12 hours into a VOD. Nothing is prepared ahead of time; any format ffmpeg reads is playable as soon as it lands in the folder.
0 bytes pre-transcoded# Quality profiles
Define a ladder — 1080p, 720p, 480p or your own. The master playlist lets the player switch as bandwidth changes.
master.m3u8# Multi-audio
Each audio track is its own switchable rendition, delivered as stereo AAC. Surround is downmixed; stereo AAC at or below the configured bitrate is copied untouched.
5.1 → aac 2.0# Subtitles
SRT and ASS tracks are converted to WebVTT and offered to the browser’s own captions.
srt, ass → vtt# Sample-accurate seams
Audio/video gap at segment boundaries measures 0.0 ms. No clicks at the joins, no drift after hours.
0.0 ms# Watermarking
Text, a PNG, or an animated transparent WebM, scaled per quality so it sits the same at every rung.
text · png · webm# Access control
Asks your existing login and permission backend. If it can’t be reached, the request is denied.
fails closed# Operations
One YAML file or env vars. Built-in player page, idle cleanup, a concurrent-transcode cap and a debug status endpoint.
/debug/status# One command, one file
Point streamd at a folder and start it. The built-in player page is served on the same port, so you can check a file before wiring up your own front end.
$ streamd serve --config config.yaml library /srv/vods profiles 1080p 720p 480p player http://localhost:8080/
listen: ":8080" library: /srv/vods profiles: - { name: 1080p, height: 1080, bitrate: 6M } - { name: 720p, height: 720, bitrate: 3M } - { name: 480p, height: 480, bitrate: 1.2M } audio: bitrate: 160k # stereo aac at or below → copied watermark: image: /etc/streamd/logo.png auth: url: https://auth.example.com/check limits: max_transcodes: 4 idle_cleanup: 5m
# Runs next to your files
Static binaries for Linux and Windows, or a Docker image. amd64 today; arm64 builds are planned. No database, no job queue, no transcoding farm.
# Docker
Mount your library read-only and the config file. Everything else can come from environment variables.
$ docker run -p 8080:8080 \
-v /srv/vods:/vods:ro \
-v ./config.yaml:/config.yaml \
streamd serve --config /config.yaml# Linux
One static amd64 binary, no runtime to install. Drop it on the box that holds the videos and run it under your service manager.
$ chmod +x streamd $ ./streamd serve --config config.yaml
# Windows
The same static binary, built for Windows on amd64. Useful when the archive lives on the machine you stream from.
> .\streamd.exe serve --config config.yaml