用一台低功耗迷你主机加 Docker 搭建家庭影音服务器 Jellyfin:电视、手机、平板都能播放你自己拍摄或合法购买并备份的视频,利用 Intel 核显硬件转码,配合一个 Python 脚本检查媒体库命名是否规范。
先看清成品
成品是一台放在电视柜里的小主机,开机自动运行 Jellyfin。家里任意设备打开
http://主机IP:8096就能看片,格式不兼容时由核显实时转码。本文的 Compose 配置已在 Linux 上实际启动 Jellyfin 10.11.11,健康检查返回Healthy,并通过官方接口完成初始化、添加电影库、识别出测试视频。测试环境没有核显,所以硬件转码部分没有实际运行,完全按官方文档编写。
注意:Jellyfin 是开源媒体服务器,只负责管理和播放你已有的文件。请只放入自己拍摄、制作,或依法获得并允许个人备份的内容,不要下载、传播盗版影视。
做完之后你会得到什么?
| 成果 | 说明 |
|---|---|
| 媒体服务器 | Jellyfin 容器,开机自启 |
| 硬件转码 | Intel 核显 QSV/VA-API,降低 CPU 占用 |
| 规范媒体库 | 电影、剧集按推荐结构存放,封面和信息自动匹配 |
| 命名检查脚本 | 只读检查,列出需要改名的文件 |
| 多端播放 | 浏览器、手机 App、电视 App |
需要准备哪些硬件?
| 部件 | 建议 | 原因 |
|---|---|---|
| 主机 | 带 Intel 核显的迷你主机,如 N100、N150 或酷睿系列 | 低功耗,可以 24 小时开机,核显能硬件转码 |
| 内存 | 8 GB 起 | 系统加 Jellyfin 足够 |
| 系统盘 | SSD | 存放系统、Jellyfin 配置和转码缓存 |
| 媒体盘 | 机械硬盘或外接硬盘盒 | 存放视频,容量按需要选 |
| 网络 | 有线千兆接入路由器 | 4K 视频走 Wi-Fi 容易卡 |
| 播放端 | 智能电视、电视盒子、手机、电脑 | 安装 Jellyfin 客户端或用浏览器 |
注意:不要选型号以 F 结尾的 Intel 处理器,官方文档特别说明这类处理器没有核显。买前可以在 Intel 官网查询是否支持 Quick Sync Video。
官方文档对核显能力的说明:
| 编码 | 要求 |
|---|---|
| H.264 8-bit | 支持 Quick Sync Video 的 Intel 核显 |
| HEVC 10-bit | 第 7 代酷睿(Kaby Lake)、Apollo Lake、Gemini Lake 及更新 |
| AV1 解码 | 第 11 代酷睿(Tiger Lake)及更新 |
系统建议装 Ubuntu Server 或 Debian。官方说明 Jellyfin 容器只在 Linux 上提供支持,在 Windows 或 macOS 的 Docker 里硬件转码等功能无法正常工作。
第一步:怎样准备系统和目录?
1. 安装 Docker
按 Docker 官方文档为你的发行版安装 Docker Engine 和 Compose 插件,然后确认:
docker --version
docker compose version
2. 确认核显设备存在
ls -l /dev/dri
getent group render | cut -d: -f3
能看到 renderD128 说明核显驱动正常。第二条命令输出 render 组的数字 ID,后面要用。
没有 /dev/dri 时,进 BIOS 确认核显已开启,或升级内核。
3. 准备媒体目录
把媒体硬盘挂载到 /srv/media,按下面的结构存放:
/srv/media/
├── movies/
│ └── 我的旅行记录 (2024)/
│ └── 我的旅行记录 (2024).mp4
└── shows/
└── 家庭日常/
├── Season 01/
│ ├── 家庭日常 S01E01.mp4
│ └── 家庭日常 S01E02.mp4
└── Season 02/
└── 家庭日常 S02E01.mp4
| 规则 | 示例 |
|---|---|
| 电影一部一个文件夹 | 片名 (年份)/片名 (年份).mp4 |
| 剧集按季分文件夹 | 剧名/Season 01/ |
| 剧集文件带季集编号 | 剧名 S01E02.mp4 |
4. 项目目录
jellyfin/
├── compose.yaml
├── .env
├── check_library.py
├── config/ # 自动生成,Jellyfin 配置和数据库
└── cache/ # 自动生成,转码缓存
第二步:怎样写 Compose 文件?
1. compose.yaml
services:
jellyfin:
image: jellyfin/jellyfin:10.11
container_name: jellyfin
user: "1000:1000"
# 硬件转码:把 render 组 ID 和显卡设备交给容器(没有核显就删掉这两段)
group_add:
- "${RENDER_GID:-993}"
devices:
- /dev/dri/renderD128:/dev/dri/renderD128
ports:
- "8096:8096/tcp"
- "7359:7359/udp"
volumes:
- ./config:/config
- ./cache:/cache
- type: bind
source: ${MEDIA_DIR:-/srv/media}
target: /media
read_only: true
environment:
- TZ=Asia/Shanghai
restart: unless-stopped
| 配置 | 作用 |
|---|---|
user: "1000:1000" |
用普通用户运行,换成你自己的 id -u 和 id -g |
group_add |
加入宿主机的 render 组,才能使用核显 |
devices |
把核显设备交给容器 |
8096/tcp |
网页和客户端访问端口 |
7359/udp |
局域网内客户端自动发现服务器 |
read_only: true |
媒体目录只读,Jellyfin 不会改动你的文件 |
2. .env
RENDER_GID=993
MEDIA_DIR=/srv/media
993 只是示例,填第一步查到的 render 组 ID。
3. 启动
mkdir -p config cache
docker compose up -d
curl -s localhost:8096/health
本文实际输出:
Healthy
如果主机没有核显,删掉 group_add 和 devices 两段再启动,Jellyfin 会用 CPU 转码。
第三步:怎样完成初始化和添加媒体库?
1. 打开向导
浏览器访问 http://主机IP:8096,按向导操作:
| 步骤 | 填写 |
|---|---|
| 语言 | 中文(简体) |
| 管理员账号 | 用户名和强密码 |
| 媒体库 | 添加“电影”指向 /media/movies,“节目”指向 /media/shows |
| 元数据语言 | Chinese,国家 China |
| 远程访问 | 只在局域网使用时可以不勾选 |
2. 检查结果
| 检查项 | 通过标准 |
|---|---|
| 首页 | 出现“电影”“节目”两个库 |
| 电影 | 显示片名和年份 |
| 剧集 | 按季分组 |
| 播放 | 点击能在浏览器里播放 |
本文测试时添加了一个 /media/movies 电影库,放入一段用 FFmpeg 生成的 20 秒测试视频,扫描后接口返回 1 部电影,年份识别为 2026。
3. 用脚本检查命名
"""检查媒体库目录是否符合 Jellyfin 推荐的命名规则,并给出改名建议(默认只读,不改任何文件)。
用法:python check_library.py /srv/media
"""
import re
import sys
from pathlib import Path
VIDEO = {".mkv", ".mp4", ".m4v", ".avi", ".mov", ".ts", ".webm"}
MOVIE_DIR = re.compile(r"^.+ \((19|20)\d{2}\)$") # 电影名 (2023)
EPISODE = re.compile(r"[Ss](\d{1,2})[Ee](\d{1,3})") # S01E02
SEASON_DIR = re.compile(r"^(Season \d{1,2}|Specials)$")
def check_movies(root: Path):
problems = []
for f in root.rglob("*"):
if f.suffix.lower() not in VIDEO:
continue
folder = f.parent
if folder == root:
problems.append((f, "电影文件应放在“片名 (年份)”文件夹里"))
elif not MOVIE_DIR.match(folder.name):
problems.append((folder, "文件夹名建议为“片名 (年份)”,例如 我的旅行记录 (2024)"))
return problems
def check_shows(root: Path):
problems = []
for f in root.rglob("*"):
if f.suffix.lower() not in VIDEO:
continue
rel = f.relative_to(root).parts
if len(rel) < 3:
problems.append((f, "剧集应放在“剧名/Season 01/”下"))
continue
if not SEASON_DIR.match(rel[1]):
problems.append((f.parent, "季文件夹建议命名为 Season 01、Season 02"))
m = EPISODE.search(f.stem)
if not m:
problems.append((f, "文件名缺少 S01E01 形式的季集编号"))
elif SEASON_DIR.match(rel[1]) and rel[1] != "Specials" and int(m.group(1)) != int(rel[1].split()[1]):
problems.append((f, f"文件名是第 {int(m.group(1))} 季,但放在了 {rel[1]}"))
return problems
def main(base):
base = Path(base)
total = 0
for name, fn in (("movies", check_movies), ("shows", check_shows)):
root = base / name
if not root.exists():
print(f"跳过:没有 {root}")
continue
probs = fn(root)
total += len(probs)
print(f"[{name}] 发现 {len(probs)} 个问题")
for p, msg in probs:
print(f" - {p.relative_to(base)}:{msg}")
print("全部符合规范" if total == 0 else f"共 {total} 个问题,修正后在 Jellyfin 里“扫描媒体库”")
return 1 if total else 0
if __name__ == "__main__":
sys.exit(main(sys.argv[1] if len(sys.argv) > 1 else "/srv/media"))
python3 check_library.py /srv/media
本文用故意放错的测试目录运行,输出:
[movies] 发现 2 个问题
- movies/b.mkv:电影文件应放在“片名 (年份)”文件夹里
- movies/random:文件夹名建议为“片名 (年份)”,例如 我的旅行记录 (2024)
[shows] 发现 3 个问题
- shows/家庭日常/Season 01/家庭日常 S02E01.mp4:文件名是第 2 季,但放在了 Season 01
- shows/家庭日常/第二季:季文件夹建议命名为 Season 01、Season 02
- shows/家庭日常/第二季/ep2.mp4:文件名缺少 S01E01 形式的季集编号
共 5 个问题,修正后在 Jellyfin 里“扫描媒体库”
脚本只读不改,改名由你手动确认后进行。
第四步:怎样开启硬件转码?
1. 在容器里确认核显可用
docker exec -it jellyfin /usr/lib/jellyfin-ffmpeg/vainfo
输出里有 iHD driver 说明支持 QSV 和 VA-API;只有 i965 driver 的是老平台,只能用 VA-API。
2. 在后台开启
进入 控制台 → 播放 → 转码:
| 设置 | 建议 |
|---|---|
| 硬件加速 | Intel QuickSync (QSV);老平台选 VA-API |
| 设备 | /dev/dri/renderD128 |
| 硬件解码 | 只勾选 vainfo 列出支持的格式 |
| 硬件编码 | 开启 |
3. 检查结果
在宿主机安装 Intel 工具,然后播放一个视频并在播放器里把清晰度调低,强制触发转码:
sudo apt install -y intel-gpu-tools
sudo intel_gpu_top
Video 一行有占用,说明转码由核显完成。控制台的“活动”里也能看到“转码”状态。
第五步:怎样在电视和手机上看?
| 设备 | 方式 |
|---|---|
| 安卓电视、电视盒子 | 安装 Jellyfin for Android TV |
| 手机 | 安装 Jellyfin 官方 App |
| 电脑 | 浏览器访问,或安装 Jellyfin Media Player |
| 其他 | 官网“客户端”页面查看支持列表 |
同一局域网内,客户端通常能通过 7359 端口自动发现服务器;找不到时手动输入 http://主机IP:8096。
注意:不建议把 8096 端口直接映射到公网。需要在外面访问时,使用带认证的隧道或 VPN 方案,并只给家庭成员开账号,不要把账号分享给他人。
出问题时该怎么调?
1. 媒体库是空的
多数是权限问题。容器用 1000:1000 运行,确认媒体目录对这个用户可读:
ls -ln /srv/media
2. 硬件转码报错 failed to initialize display
group_add 的 ID 和宿主机 render 组不一致,或者设备没有传进容器。重新执行 getent group render,修改 .env 后:
docker compose up -d --force-recreate
3. 片名后面带着年份括号
本文测试时,测试视频没有联网匹配到资料,显示的名称就是文件夹名“测试片 (2026)”。真实影片一般能自动匹配;匹配不到时在影片页面点“识别”手动搜索。
4. 播放卡顿
| 原因 | 检查 |
|---|---|
| Wi-Fi 带宽不够 | 播放端换有线,或降低清晰度 |
| CPU 软件转码 | 控制台“活动”里看是否在转码,确认硬件加速已开启 |
| 字幕烧录 | 图形字幕常常触发转码,换用文本字幕 |
常见问题怎么处理?
| 现象 | 原因 | 解决方法 |
|---|---|---|
/dev/dri 不存在 |
核显未开启或驱动缺失 | BIOS 开启核显,升级内核 |
| 容器反复重启 | 配置目录权限不对 | chown -R 1000:1000 config cache |
| 客户端搜不到服务器 | UDP 7359 未放行 | 防火墙放行,或手动输入地址 |
| 中文字幕方框 | 缺少中文字体 | 按官方文档挂载字体目录 |
| 升级后出问题 | 跨大版本升级 | 固定 10.11 这类小版本标签,升级前备份 config |
| 4K HDR 画面发灰 | 需要色调映射 | 转码设置里开启色调映射,Intel 需要 OpenCL |
项目资源
建议先不接核显、不接大硬盘,用一台现有的 Linux 电脑跑起容器,放两三个自己拍的视频走一遍初始化和播放。
流程跑通后再换到专用迷你主机,开启硬件转码,最后才把整个媒体库迁移过来。