云端部署

Docker + Jellyfin:搭建支持硬件转码的家庭影音服务器

用一台低功耗迷你主机加 Docker 搭建家庭影音服务器 Jellyfin:电视、手机、平板都能播放你自己拍摄或合法购买并备份的视频,利用 Intel 核显硬件转码,配合一个 Python 脚本检查媒体库命名是否规范。

BINBHO · 图文教程

用一台低功耗迷你主机加 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

项目资源

用途 地址
Jellyfin 官网 https://jellyfin.org
容器安装文档 https://jellyfin.org/docs/general/installation/container/
Intel 硬件加速 https://jellyfin.org/docs/general/post-install/transcoding/hardware-acceleration/intel
电影命名规则 https://jellyfin.org/docs/general/server/media/movies/
剧集命名规则 https://jellyfin.org/docs/general/server/media/shows/
客户端下载 https://jellyfin.org/downloads/clients/
Docker Engine 安装 https://docs.docker.com/engine/install/
Intel 处理器规格查询 https://www.intel.com/content/www/us/en/ark.html

建议先不接核显、不接大硬盘,用一台现有的 Linux 电脑跑起容器,放两三个自己拍的视频走一遍初始化和播放。

流程跑通后再换到专用迷你主机,开启硬件转码,最后才把整个媒体库迁移过来。