Encode video with Handbrake on server

Encode video with Handbrake on server

In your homelab, you may have some sort of media server which serve video to different devices like Plex, Emby and Jellyfin. While it can transcode to support codec real time, it brings heavy loads to server. By transcoding video in advance, you can lower server load and provide better quality.

One of popular way is to use FFmpeg but there are many parameters to configure, especially you have more than 1 tracks in the source file. Handbrake provide a user friendly GUI on top of FFmpeg and provide presets for simple encoding. You can also customize preset and track selection behavior, so you can apply it to multiple files.

However, because of Handbrake is using GUI, you may not able to run it on your server if you only have terminal access. Someone has create a Docker image which redirects handbrake GUI to a web canvas. Now we can encoding video on the server instead.

Getting Started

Before you continue, make sure you know the followings:

  • Docker & Docker Compose
  • Handbrake

Here is the simple docker-compose.yml.

version: "3"

services:
  web:
    image: jlesage/handbrake
    volumes:
      - /docker/handbrake/output:/output
      - /docker/handbrake/storage:/storage:ro
    ports:
      - 5800:5800

/storage is the default folder for Open Source and /output is the default folder for output.

After running docker-compose up -d, you can to to http://<server-ip>:5800. You should able to see the following screen.

Handbrake Web UI
Handbrake Web UI

Then, you can use Handbrake normally.

SubStation Alpha / Advanced SubStation Alpha

I have faced some problems when I try to burn-in SubStation Alpha (.ssa) or Advanced SubStation Alpha (.ass) subtitle. Here are the problems and solutions.

Thicker border

The encoding video with burn-in subtitles has thicker border than playing with video player like VLC. I opened an issue in the past and found out it was caused by libass. Media players that use VSFilter default ScaledBorderAndShadow is no while libass default ScaledBorderAndShadow is yes.

To solve this problem, you can add ScaledBorderAndShadow: no under [Script Info] to the subtitle file if it does not have one. If the subtitle files is embedded in MKV file, you can use mkvextract to extract the subtitle track.

Using custom font

Those subtitle files often uses custom font which does not exists in the Docker image. You need to mount it to the Docker container.

version: "3"

services:
  web:
    image: jlesage/handbrake
    volumes:
      - /docker/handbrake/output:/output
      - /docker/handbrake/storage:/storage:ro
      - /docker/handbrake/fonts:/usr/share/fonts/custom:ro
      - /docker/handbrake/99-font.sh:/etc/cont-init.d/99-font.sh:ro
    ports:
      - 5800:5800
#!/usr/bin/with-contenv sh

fc-cache -fv

New docker-compose.yml now mount two more volumes. You can mount your fonts folder to /usr/share/fonts.

jlesage/handbrake is using s6-overlay. 99-font.sh is a script that run when the container start. It rebuilds the font cache. You should able to see the following log if it is loaded.

/usr/share/fonts/custom: caching, new cache contents: 1378 fonts, 0 dirs

Then, you can use Handbrake normally.

Not using correct font

When I was searching for issues, I found someone mention libass does not select unicode font name correctly (I did not face this problem). You can try fc-match <font name> to get the postfix font name and replace the one in the subtitle file.

You can also see Handbrake log to see if it uses the correct font.

[06:49:13] [ass] fontselect: (MS Gothic, 700, 0) -> /usr/share/fonts/custom/MSGOTHIC.TTC, 0, MS-Gothic