Setup Jellyfin as Your Home Media Server

There are mainly two ways of storing multimedia: keeping them in client, or in server. For the later option, three softwares can be chose: Plex, Emby and Jellyfin. Today let’s talk about the last one —— Jellyfin.

Background

We choose Jellyfin because it’s the only open source solution. Actually, Emby used to be open source as well, but after the company decided to close source, some developer were get annoyed and launched the opensource version of Emby, that’s Jellyfin.

The main page of Jellyfin is as bellow

Installation

I will try to install Jelyfin on Ubuntu, but it also supports a bunch of platforms, such as Windows, Mac and Android.

1
2
3
4
5
sudo apt install apt-transport-https
wget -O - https://repo.jellyfin.org/jellyfin_team.gpg.key | sudo apt-key add -
echo "deb [arch=$( dpkg --print-architecture )] https://repo.jellyfin.org/$( awk -F'=' '/^ID=/{ print $NF }' /etc/os-release ) $( awk -F'=' '/^VERSION_CODENAME=/{ print $NF }' /etc/os-release ) main" | sudo tee /etc/apt/sources.list.d/jellyfin.list
sudo apt update
sudo apt install jellyfin

Now you can access the server through http://your_local_IP_address:8096 to finish the initial setup.

Configuration

Nginx

If your server has public IP address, you may want to access Jellyfin through domain rather than IP:Port.

And normally a server will run multi service simultaneously, then you will need a reverse proxy to route different domain to the related service. Here I will take Nginx as example.

The default config will only use http as the lack of TLS cert. Your can uncomment the related line to enable https to increase security.

/etc/nginx/conf.d/jellyfin.conf

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
# Uncomment the commented sections after you have acquired a SSL Certificate
server {
listen 80;
listen [::]:80;
# server_name DOMAIN_NAME;

# Uncomment to redirect HTTP to HTTPS
# return 301 https://$host$request_uri;
#}

#server {
# listen 443 ssl http2;
# listen [::]:443 ssl http2;
server_name DOMAIN_NAME;

# use a variable to store the upstream proxy
# in this example we are using a hostname which is resolved via DNS
# (if you aren't using DNS remove the resolver line and change the variable to point to an IP address e.g `set $jellyfin 127.0.0.1`)
set $jellyfin jellyfin;
resolver 127.0.0.1 valid=30;

#ssl_certificate /etc/letsencrypt/live/DOMAIN_NAME/fullchain.pem;
#ssl_certificate_key /etc/letsencrypt/live/DOMAIN_NAME/privkey.pem;
#include /etc/letsencrypt/options-ssl-nginx.conf;
#ssl_dhparam /etc/letsencrypt/ssl-dhparams.pem;
#add_header Strict-Transport-Security "max-age=31536000" always;
#ssl_trusted_certificate /etc/letsencrypt/live/DOMAIN_NAME/chain.pem;
#ssl_stapling on;
#ssl_stapling_verify on;

# Security / XSS Mitigation Headers
add_header X-Frame-Options "SAMEORIGIN";
add_header X-XSS-Protection "1; mode=block";
add_header X-Content-Type-Options "nosniff";

# Content Security Policy
# See: https://developer.mozilla.org/en-US/docs/Web/HTTP/CSP
# Enforces https content and restricts JS/CSS to origin
# External Javascript (such as cast_sender.js for Chromecast) must be whitelisted.
#add_header Content-Security-Policy "default-src https: data: blob: http://image.tmdb.org; style-src 'self' 'unsafe-inline'; script-src 'self' 'unsafe-inline' https://www.gstatic.com/cv/js/sender/v1/cast_sender.js https://www.gstatic.com/eureka/clank/95/cast_sender.js https://www.gstatic.com/eureka/clank/96/cast_sender.js https://www.gstatic.com/eureka/clank/97/cast_sender.js https://www.youtube.com blob:; worker-src 'self' blob:; connect-src 'self'; object-src 'none'; frame-ancestors 'self'";

location = / {
return 302 http://$host/web/;
#return 302 https://$host/web/;
}

location / {
# Proxy main Jellyfin traffic
proxy_pass http://$jellyfin:8096;
proxy_set_header Host $host;
proxy_set_header X-Real-IP $remote_addr;
proxy_set_header X-Forwarded-For $proxy_add_x_forwarded_for;
proxy_set_header X-Forwarded-Proto $scheme;
proxy_set_header X-Forwarded-Protocol $scheme;
proxy_set_header X-Forwarded-Host $http_host;

# Disable buffering when the nginx proxy gets very resource heavy upon streaming
proxy_buffering off;
}

# location block for /web - This is purely for aesthetics so /web/#!/ works instead of having to go to /web/index.html/#!/
location = /web/ {
# Proxy main Jellyfin traffic
proxy_pass http://$jellyfin:8096/web/index.html;
proxy_set_header Host $host;
proxy_set_header X-Real-IP $remote_addr;
proxy_set_header X-Forwarded-For $proxy_add_x_forwarded_for;
proxy_set_header X-Forwarded-Proto $scheme;
proxy_set_header X-Forwarded-Protocol $scheme;
proxy_set_header X-Forwarded-Host $http_host;
}

location /socket {
# Proxy Jellyfin Websockets traffic
proxy_pass http://$jellyfin:8096;
proxy_http_version 1.1;
proxy_set_header Upgrade $http_upgrade;
proxy_set_header Connection "upgrade";
proxy_set_header Host $host;
proxy_set_header X-Real-IP $remote_addr;
proxy_set_header X-Forwarded-For $proxy_add_x_forwarded_for;
proxy_set_header X-Forwarded-Proto $scheme;
proxy_set_header X-Forwarded-Protocol $scheme;
proxy_set_header X-Forwarded-Host $http_host;
}
}

Hardware Acceleration

Media server is not exactly like the file sharing server, such as samba. As the media files will be decoded on server side rather than client side.

This patten can be usefule when the media file can not be decoded by client. For example, Chrome can not play HEVC video. And when it happens, the server will transcode the file to a format which can be decoded by client. This process includes two routines: Decoding and Encoding.

Of cource these two works can be done by CPU, but as we know, CPU is not the perfect role to play such tasks, they should belong to the GPU. Then we need to enable GPU acceleration.

I will take Intel Quick Sync Video (QSV) as example because nearly most of home-based NAS are equipped with Intel CPU.

  • Install VAAPI driver

    1
    2
    3
    4
    5
    6
    sudo apt update
    sudo apt install gpg-agent wget
    wget -qO - https://repositories.intel.com/graphics/intel-graphics.key | sudo apt-key add -
    sudo bash -c "echo 'deb [arch=amd64] https://repositories.intel.com/graphics/ubuntu focal main' >> /etc/apt/sources.list"
    sudo apt update
    sudo apt install intel-media-va-driver-non-free
  • Add group

    1
    2
    sudo usermod -aG render jellyfin
    sudo systemctl restart jellyfin
  • Check VA info

    1
    /usr/lib/jellyfin-ffmpeg/vainfo
  • Configure QSV acceleration

    Go to Dashboard -> Playback -> Transcoding, select Intel Quick Sync, and click Save.

  • Check GPU Usage

    1
    2
    sudo apt install intel-gpu-tools
    sudo intel_gpu_top

    Play a HEVC video on Chrome, then check if there’s any GPU usage shown up.

Reference

Downloads - Jellyfin: The Free Software Media System

Home | Documentation - Jellyfin Project

Intel media stack on Ubuntu · Intel-Media-SDK/MediaSDK Wiki · GitHub

software recommendation - How to measure GPU usage? - Ask Ubuntu

linuxserver/jellyfin:10.6.4-1-ls96 broke Intel Quicksync (most likely due to jellyfin-ffmpeg4.3.1-4-focal) · Issue #96 · linuxserver/docker-jellyfin · GitHub

Hardware video acceleration - ArchWiki