Sign inSign up

tces1/emby_transcoder

By tces1

•Updated about 2 months ago

A lightweight reverse proxy designed for Emby private servers provide transcode via local NAS

Image
0

4.1K

tces1/emby_transcoder repository overview

⁠Emby-Transcoder

https://github.com/tces1/emby_transcoder⁠

Docker Image Docker Pulls Image Go Platform HLS VAAPI License

Emby-Transcoder is a lightweight Go reverse proxy that adds local FFmpeg HLS transcoding fallback for Emby and Jellyfin clients.

It is intentionally narrow: normal API traffic is forwarded to the upstream server, while selected clients can receive a proxy-provided HLS TranscodingUrl when they request PlaybackInfo.

⁠How It Works

Emby / Jellyfin client
        |
        v
  Emby-Transcoder
  - transparent proxy for ordinary requests
  - PlaybackInfo matching by client profile
  - PlaybackInfo rewrite to local HLS
  - local FFmpeg transcode sessions
        |
        v
  upstream Emby / Jellyfin

⁠Current Scope

  • Native Linux-friendly Go binary.
  • Transparent reverse proxy for ordinary Emby/Jellyfin requests.
  • Client profile matching by User-Agent, X-Emby-Authorization, and X-MediaBrowser-Token.
  • PlaybackInfo rewriting for matched profiles.
  • Local FFmpeg HLS sessions under /streambridge/transcode/.
  • Audio track selection through Emby AudioStreamIndex, with local transcode restart on audio changes.
  • Playback lifecycle tracking through Emby /Sessions/Playing* check-ins plus HLS access.
  • Conservative output target: H.264 video, AAC audio, HLS MPEG-TS segments.
  • Software transcoding caps video output at 1920x1080 and keeps aspect ratio; VAAPI mode does not scale.
  • PlaybackInfo rewrite prewarms the transcode session before the first playlist request.
  • FFmpeg uses low-latency startup and GOP settings to cut first-segment delay.

Not included: virtual libraries, RSS, cover generation, scraping, database storage, or a management UI.

⁠Run

go run ./cmd/emby-transcoder -config config.example.json

Point a client at the proxy listen address, for example http://linux-host:8097.

⁠Docker Compose

Use the public Docker Hub image tces1/emby_transcoder:latest. The compose file in docker/ already points at this published linux/amd64 image and does not build locally.

Minimal compose service:

services:
  emby-transcoder:
    image: tces1/emby_transcoder:latest
    container_name: emby-transcoder
    restart: unless-stopped
    privileged: true
    ports:
      - "8097:8097"
    devices:
      - /dev/dri:/dev/dri
    volumes:
      - ./config/config.json:/app/config/config.json:ro
      - ./data/transcode:/var/lib/emby-transcoder/transcode
cd docker
mkdir -p data/transcode
cp config/config.json config/config.local.json

Edit docker/config/config.local.json before starting:

  • set upstream.url to your Emby or Jellyfin server
  • set server.public_url if clients reach the proxy through another reverse proxy
  • leave server.debug as false for concise logs, or set it to true for detailed diagnostics
  • leave transcode.hardware_decode as "" to disable hardware acceleration and use CPU transcoding
  • set transcode.hardware_decode to vaapi on Linux hosts with Intel or AMD /dev/dri VAAPI support
  • the current VAAPI path uses hardware decode plus h264_vaapi hardware encoding and does not add a scale filter
  • startup will probe VAAPI availability, including device initialization and h264_vaapi, and fail startup if the device, driver, or ffmpeg support is missing

Update docker/docker-compose.yml to mount the local config file if you use config.local.json:

volumes:
  - ./config/config.local.json:/app/config/config.json:ro
  - ./data/transcode:/var/lib/emby-transcoder/transcode

Start or update the service:

docker compose pull
docker compose up -d
docker compose logs -f

Stop the service:

docker compose down

⁠Build

go build ./cmd/emby-transcoder

⁠Configuration

Copy config.example.json and change the upstream URL:

{
  "server": {
    "listen": ":8097",
    "public_url": "",
    "debug": false
  },
  "upstream": {
    "url": "http://127.0.0.1:8096"
  },
  "transcode": {
    "enabled": true,
    "ffmpeg_path": "/usr/bin/ffmpeg",
    "temp_dir": "/var/lib/emby-transcoder/transcode",
    "hardware_decode": "",
    "hardware_device": "/dev/dri/renderD128",
    "max_sessions": 2,
    "buffer_pause_seconds": 300,
    "buffer_resume_seconds": 120,
    "segment_seconds": 2,
    "segment_retention_seconds": 300,
    "idle_timeout_seconds": 60
  }
}

Leave public_url empty when clients connect directly to Emby-Transcoder. Set it when Emby-Transcoder sits behind another reverse proxy. Leave debug as false for concise action-level logs. Set it to true when you want detailed TRACE_SWITCH and request-level diagnostics. Leave hardware_decode as "" to disable hardware acceleration and use CPU transcoding. Set it to vaapi to enable VAAPI hardware transcoding. The default hardware_device is /dev/dri/renderD128. The current VAAPI path uses hardware decode plus h264_vaapi hardware encoding and does not add a scale filter. If the device, driver, or h264_vaapi probe fails, startup stops with an error.

⁠Transcode Lifecycle

Emby-Transcoder keeps local FFmpeg sessions tied to Emby playback check-ins:

  • POST /Sessions/Playing and /Sessions/Playing/Progress update local playback state.
  • POST /Sessions/Playing/Stopped immediately stops the matching local FFmpeg session.
  • HLS playlist and segment requests refresh media activity.
  • segment_seconds controls HLS segment duration; default 2 balances startup latency with segment count, while 1 is fastest and higher values reduce disk churn.
  • When transcoded media gets more than buffer_pause_seconds ahead of playback, FFmpeg is paused.
  • When buffered media falls back under buffer_resume_seconds, FFmpeg resumes.
  • Segments older than segment_retention_seconds behind the current playback position are deleted from the local cache.
  • If neither playback activity nor HLS access arrives before idle_timeout_seconds, the idle reaper stops the session.
  • A new master.m3u8 request with a different upstream stream URL, such as a seek with a different StartTimeTicks, restarts the local session.

⁠License

This repository's code is licensed under the Apache License 2.0⁠, Copyright 2026 tces1.

Debian, FFmpeg, and VAAPI userspace components included in the Docker image remain under their respective upstream licenses.

Tag summary

Content type

Image

Digest

sha256:ffcb8ab74…

Size

209 MB

Last updated

about 2 months ago

docker pull tces1/emby_transcoder