订阅制流媒体每年都在涨价,想看的片子还动不动就下架;自己下载存的电影又散落在各个硬盘里,找一部片要翻半天文件夹。用 Jellyfin 搭一套私人影视库,就能把这些零散的视频统一成一个带封面海报、剧情简介、演员表、还能断点续播的「私人 Netflix」。它是完全开源免费的自托管媒体服务器,配合一个已经跑起来的 Docker 环境,半小时就能有模有样。这篇文章把部署、扫描、硬解、外网访问和六个必踩的坑一次讲透。
一、Jellyfin 是什么,和 Plex、Emby 怎么选
Jellyfin 是一个开源的媒体服务器,把散落在硬盘上的电影、剧集、音乐、照片整理成带元数据的资料库,通过网页、手机 App、电视盒子、投屏设备播放。
三个同类产品的关系要先理清:
- Plex:功能最成熟、客户端覆盖最广,但核心功能越来越倾向付费(Plex Pass),而且账号和部分元数据要经过它的云端服务器。
- Emby:开源起家后来闭源,硬件转码等高级功能需要订阅,界面精致但商业味重。
- Jellyfin:从 Emby 的一个分支 fork 出来的完全开源项目,全部功能免费、无账号锁死、不需要连官方云端。
对个人站长和自建玩家来说,Jellyfin 最大的价值就是「没有后门式的云端依赖」——服务器在你手上,刮削出来的海报简介也存在你自己的数据库里,断网也能用。
二、部署前的三个决定
1. 存储怎么摆
影视库的特点是大文件、长时间顺序读,机械硬盘完全够用,没必要上 SSD。推荐的做法是:媒体文件放机械盘,Jellyfin 的配置和数据库放 SSD,因为配置数据库里全是小文件随机读写,放机械盘会让界面加载很卡。
2. 转码能力怎么来
能直接播放的格式(Direct Play)最省 CPU,但手机在弱网下往往需要转码降码率,这时候如果只有 CPU 转码,一台小 VPS 的核数根本扛不住 1080p 实时转码。解决办法是用核显硬解(Intel 的 QSV 或 QuickSync Video),它能在几乎不占 CPU 的情况下完成转码。
3. 目录怎么组织
Jellyfin 认文件夹结构,命名规范它才能正确刮削。推荐结构:
/mnt/media/
├── movies/
│ └── 让子弹飞 (2010)/
│ └── 让子弹飞 (2010).mp4
└── tv/
└── 绝命毒师 (2008)/
└── Season 01/
└── 绝命毒师 S01E01.mkv
电影建议「一部一个文件夹」,剧集按「剧名/季/集」三级。文件名里带上年份能大幅减少刮削错配,同名电影隔了很多年翻拍的例子太多了。
三、Docker Compose 部署
先建目录再写 compose 文件:
mkdir -p /opt/jellyfin/{config,cache}
services:
jellyfin:
image: jellyfin/jellyfin:latest
container_name: jellyfin
user: "1000:1000"
group_add:
- "107"
- "44"
environment:
- TZ=Asia/Shanghai
- JELLYFIN_PublishedServerUrl=https://media.example.com
volumes:
- /opt/jellyfin/config:/config
- /opt/jellyfin/cache:/cache
- /mnt/media:/media:ro
ports:
- "127.0.0.1:8096:8096"
restart: unless-stopped
几个参数必须解释清楚:
- user: "1000:1000":让容器用普通用户身份跑,避免以 root 写出来的配置文件在宿主机上不可读。这个 UID/GID 要和你宿主机上拥有媒体目录的用户一致,用
id 用户名查。 - group_add 里的 107 和 44:107 通常是
render组、44 是video组,不做硬解可以不写,要做 QSV/VAAPI 硬解就必须加上,否则容器看不到/dev/dri,硬解开关是灰的。 - 媒体目录挂成 :ro(只读):Jellyfin 从不需要写媒体目录,只读挂载能防住「误删原片」这类事故。
- 端口只绑 127.0.0.1:8096 不直接开公网,由 Nginx 反代出去,这也是官方推荐的做法。
拉起:
docker compose up -d docker compose logs -f jellyfin
四、初始化与添加媒体库
浏览器访问 http://127.0.0.1:8096,走完初始向导(选语言、建管理员账号)。向导里有一个关键选择:「为媒体启用自动端口映射」和「允许远程访问」都要看准——如果只在反代后面跑,远程访问的相关开关按需关掉,不然后面 TLS 会很别扭。
进入控制台后加媒体库:控制台 → 媒体库 → 添加媒体库。电影选「电影」类型、路径填 /media/movies,剧集选「节目」类型、路径填 /media/tv。
刮削器(元数据抓取)建议:
- 优先语言顺序设成「中文优先、英文兜底」,这样海报和简介能先抓中文,抓不到再退回英文。
- 勾上「优先使用本地图片」,你自己放进文件夹的 poster.jpg 就不会被在线刮削覆盖。
- 媒体库添加后不要急着反复点「刷新元数据」,刮削是异步的,几千部片要跑很久,反复触发只会互相打架。
五、硬件转码(QSV / VAAPI)怎么开
这是 Jellyfin 体验的关键。Intel 平台用 QSV 最简单,把设备的 /dev/dri 挂进容器即可:
devices:
- /dev/dri:/dev/dri
然后在 Jellyfin 控制台 → 播放 → 转码里,把「硬件加速」选成 Intel QuickSync,勾上「启用硬件解码」和「启用硬件编码」,编码格式优先 HEVC(如果能播)其次 H.264,并勾上「启用低电压编码」(Low-Power Encoding),能明显减少功耗。
验证是否真的在硬解,最直接的办法是播放时需要转码的视频,然后进「控制台 → 仪表盘 → 正在播放」,看它显示的是 Transcode (QSV) 还是 Transcode (Software)。如果显示 Software,说明硬件加速没生效,九成是容器没拿到 /dev/dri 或者权限组没加。
六、Nginx 反代与外网访问
用子域名反代并上 HTTPS:
server {
listen 443 ssl http2;
server_name media.example.com;
ssl_certificate /etc/letsencrypt/live/media.example.com/fullchain.pem;
ssl_certificate_key /etc/letsencrypt/live/media.example.com/privkey.pem;
client_max_body_size 20M;
location / {
proxy_pass http://127.0.0.1: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;
}
location /socket {
proxy_pass http://127.0.0.1:8096;
proxy_http_version 1.1;
proxy_set_header Upgrade $http_upgrade;
proxy_set_header Connection "upgrade";
proxy_set_header Host $host;
}
}
/socket 这个 location 必须单独配,并且带 WebSocket 的 Upgrade 头。Jellyfin 的控制台实时状态、播放进度同步都走 WebSocket,不配的话页面能打开但很多交互是死的。另外 X-Forwarded-Proto 一定要传给后端,否则 Jellyfin 会以为自己在 http 下,生成的播放地址会带错协议。
外网访问更稳的方案是不要直接开公网,而是走 VPN 回家(WireGuard / Tailscale),把 8096 藏在私网里,手机和电脑连上 VPN 就能看,安全性和便利性兼顾。
七、字幕、外挂音轨与扫描性能
中文用户看欧美剧几乎离不开字幕,Jellyfin 对字幕的处理有几个要点值得单独讲。
外挂字幕的命名要和视频文件同名同目录,后缀区分语言,例如:
绝命毒师 S01E01.mkv 绝命毒师 S01E01.zh.srt 绝命毒师 S01E01.en.srt
用 .zh / .en 这样的语言后缀,Jellyfin 才能自动把中文字幕设为首选,否则它不认语言,客户端要每次手动切。字幕编码尽量用 UTF-8,GBK 编码的字幕在部分客户端上会显示成乱码,可以用 iconv 批量转:
find /mnt/media -name "*.srt" -exec sh -c 'iconv -f GBK -t UTF-8 "$1" -o "$1.utf8" && mv "$1.utf8" "$1"' _ {} \;
性能方面,媒体库第一次扫描几千个文件会持续占用 CPU 做缩略图抽帧,建议在控制台 → 播放里把「章节图片提取」和「缩略图生成」按需关闭,或者把扫描安排在夜里。文件系统变动后,用「扫描媒体库」而不是「刷新所有元数据」——前者只处理新增文件,后者会把所有条目的元数据重新抓一遍,既慢又可能覆盖你手动改过的封面。
八、六个必踩的坑
- 容器以 root 身份跑:配置文件属主变成 root,宿主机上的普通用户改不动,后续调试到处碰壁。一开始就用 user: "1000:1000"。
- 硬解开关是灰的:没挂 /dev/dri,或者 group_add 里缺 render/video 组。检查
ls -l /dev/dri和容器里的id。 - 媒体库路径填成宿主机路径:在容器里必须填容器内的路径(/media/movies),填宿主机的 /mnt/media/movies 会扫不到任何文件。
- 文件名不规范导致刮削全错:把「电影名 (年份)」写规范,剧集用 SxxEyy 命名,能省掉 90% 的手动修正。
- 忘了调 client_max_body_size 或漏配 WebSocket:前者让字幕/插件上传失败,后者让控制台半死不活。
- 把转码缓存目录留在系统盘:transcoding 临时文件不小,长期跑会把系统盘写满,记得在控制台里把转码临时目录指到数据盘。
八、小结
Jellyfin 把一堆散乱的视频文件变成了一个真正能用的私人影院:自动刮削海报和简介、手机电视多端续播、核显硬解让弱机也能转码。整套系统跑在 Docker 里,配置和数据库都在自己手上,既不用担心订阅涨价,也不用担心服务下架。真正的关键只有三件事——给对 UID/GID、挂对 /dev/dri、配好反代的 WebSocket。把这三件事做对,你就拥有了一个永不涨价的私人流媒体服务。