每一个折腾 HomeLab 的兄弟,最终的归宿除了用来 “压泡面” 的各种派,大概率都逃不过这两个词 —— NAS家庭影音。毕竟,辛辛苦苦攒下来的 “学习资料”,总得有个像样的展示柜吧?

在开源界,Jellyfin 绝对是那个即使你一分钱不花,也能让你体验到 “尊贵 VIP” 待遇的神器。经过多年的调教(被坑)与折腾,这套方案现在的成熟度已经相当高了。

老规矩,先上一张架构图镇楼(没错,AI 画的,看起来是不是特别唬人?):

graph TD
    classDef core fill:#6d28d9,stroke:#a855f7,color:white,stroke-width:2px
    classDef client fill:#2563eb,stroke:#60a5fa,color:white,stroke-width:2px
    classDef media fill:#0f766e,stroke:#06b6d4,color:white,stroke-width:2px
    classDef plugin fill:#c2410c,stroke:#fb923c,color:white,stroke-width:2px
    classDef external fill:#475569,stroke:#94a3b8,color:white,stroke-width:2px
    classDef func fill:#1e40af,stroke:#3b82f6,color:white,stroke-width:2px

    subgraph "🌐 客户端层"
        A[Web客户端]:::client
        B[移动应用]:::client
        C[TV端]:::client
        D[桌面程序]:::client
    end

    subgraph "🚀 核心服务层"
        E[API网关]:::core
        F[认证授权]:::core
        G[媒体引擎]:::core
        H[插件管理器]:::core
    end

    subgraph "🎬 媒体处理层"
        I[智能转码]:::media
        J[元数据管理]:::media
        K[字幕服务]:::media
        L[章节分析]:::media
    end

    subgraph "🔌 插件生态"
        M[主题引擎]:::plugin
        N[元数据刮削]:::plugin
        O[通知服务]:::plugin
        P[播放器扩展]:::plugin
    end

    subgraph "🔗 外部系统"
        Q[存储系统]:::external
        R[直播源]:::external
        S[DVR服务]:::external
        T[云同步]:::external
    end



    A & B & C & D --> E
    E --> F & G & H
    G --> I & J & K & L
    H --> M & N & O & P
    G --> Q
    R --> S
    G --> T

从图中可以看到 Jellyfin 的功能特点总结为如下几点:

  1. 全平台客户端:Web/手机/TV/桌面,进度云端同步

  2. 实时硬件转码:NVENC/QSV/VAAPI,带宽自适应

  3. 自动媒体整理:TMDB 元数据 + 章节点生成,一键刮削海报与字幕

  4. 多用户家庭共享:分级权限、家长控制、离线缓存

  5. 插件扩展:主题、通知、第三方元数据,热插拔即装即用

  6. 开源:本地部署,无数据泄露风险,无需付费

部署

详细的部署方式多如牛毛,官方文档写得也很细。但在 2024 年(或者未来),Docker 绝对是首选。为什么?因为我们有洁癖,不想把宿主机搞得乱七八糟。

直接上 docker-compose.yaml,复制粘贴即可食用:

services:
  jellyfin:
    image: jellyfin/jellyfin:10.11.5
    container_name: jellyfin
    restart: unless-stopped
    environment:
      - TZ=Asia/Shanghai
      - JELLYFIN_PublishedServerUrl=http://<你宿主机 IP 地址>
    ports:
      - "8096:8096/tcp"
      - "7359:7359/udp"
    volumes:
      - /volume1/mnt/data/jellyfin/config:/config
      - /volume1/mnt/data/jellyfin/cache:/cache
      # 这里挂载你的媒体文件目录
      - /volume1/Documentary:/media/documentary:ro
      - /volume1/Movie:/media/movie:ro
      - /volume1/Series:/media/series:ro
      - /volume1/Villa:/media/villa:ro
    # 硬件加速
    # devices:
    #   - /dev/dri:/dev/dri

配置

详细的配置文档可以参考 Jellyfin Post-Install SetupJellyfin Administration Configuration,这里仅记录一些常用的配置。

面子工程

俗话说得好,颜值即正义。功能再强大,长得像 Windows 98 也是不行的。好在 Jellyfin 底子不错,稍微打扮一下就能 “艳压群芳”。在此基础之上还提供了两个方案:

我个人目前使用的是自定义 CSS 的方式,轻量且简单,使用别人写好的 CSS 都不需要做什么额外的配置,可参考下方配置

@import url("https://cdn.jsdelivr.net/gh/lscambo13/ElegantFin@main/Theme/ElegantFin-jellyfin-theme-build-latest-minified.css");

除了对界面进行美化之外,还有个插件可以在主页 Banner 实现随机推荐,效果也是一级棒。感兴趣的可以移步 Media Bar,这个项目是从 Jellyfin-Media-Bar Fork 二开而来,配置简单,只需要如下几步即可:

  1. https://www.iamparadox.dev/jellyfin/plugins/manifest.json 添加至 Plugin Repository

  2. 安装 Media BarFile Transformation 两个插件(注意,Jellyfin 的版本要求在 10.10.7 以上)

  3. 重启 Jellyfin 服务

这时你就能够在首页看到自定义的 CSSMedia Bar 的效果了

下面放几张图给大家看看效果

首页 Banner
电影墙
美化效果

媒体库配置

按照我个人的习惯,将媒体库分成了四个部分 —— 电影电视剧纪录片,以及那个只可意会不可言传九公斤。分别对应 MovieSeriesDocumentary 以及 Villa 四个挂载进来的目录。

九公斤 到底是什么?咳咳,这是一个关于成人向的深奥话题,为了保持本文的纯洁性,咱们留到后续的某篇文章中再单独探讨。

说回正经的,其实你大可以将 纪录片电视剧 合并在一个目录里,由于我在存储的时候就已经分开,这里我也分开配置了。

媒体库的基础配置如下:

  • 首选下载语言:Chinese

  • 国家 / 地区:People's Republic of China

  • 优先使用内置的标题而不是文件名:开启

  • 启用实时监控:开启

  • 自动添加到合集:开启

  • 自动从互联网获取元数据并刷新:每 30 天

  • 元数据存储方式:NFO

  • 将媒体图像保存到媒体所在文件夹:开启

  • 保存字幕到媒体所在文件夹:开启

除此之外,还需要额外再配置两个插件来完成 刮削字幕下载 的功能

想要拥有一面完美的 “海报墙”,光靠插件是不够的。文件的命名规范目录结构以及 刮削的调教 都是一门玄学。

这部分内容实在太过于庞大(且充满了踩坑血泪史),所以我决定将其剥离出来,作为 HomeLab 系列的独立篇章。

今天,为了让大家先跑起来,我们只进行最基础的能用就行的配置。

先说两个插件的安装:

  1. Step1:分别在 Plugin Repository 添加如下地址:

    • MetaShark(由国内加速地址和国外地址,大家按需选择):
      • 国内加速地址:https://ghfast.top/https://github.com/cxfksword/jellyfin-plugin-metashark/releases/download/manifest/manifest_cn.json
      • 国外地址:https://github.com/cxfksword/jellyfin-plugin-metashark/releases/download/manifest/manifest.json
    • MeiamSubtitles:https://github.com/91270/MeiamSubtitles.Release/raw/main/Plugin/manifest-stable.json
  2. Step2:安装插件 – MetaSharkMeiamSub.ThunderMeiamSub.Shooter

  3. Step3:重启 Jellyfin 服务

在媒体库中的配置就可以加入这两个插件相关的配置了:

  • 字幕下载器勾选:MeiamSub.ThunderMeiamSub.Shooter

  • 元数据下载器和图片获取器勾选:MetaShark

转码配置

Jellyfin 支持多种解码方式,具体可参考 Jellyfin Transcoding 中的内容,我们这里简单说说配置。根据官方文档提供的内容,整理出如下的硬件加速方案,大家根据你们 Jellyfin 部署的平台进行选择。

显卡品牌推荐加速方式 (Linux)推荐加速方式 (Windows)
Intel (核显 / 独显)QSV (Quick Sync) 或 VA - APIQSV
NVIDIA (英伟达)NVENC/NVDECNVENC
AMDVA-APIAMF
Apple (Mac)Video ToolboxVideo Toolbox
Rockchip (瑞芯微)RKMPPN/A

这里还有一个概念,即 完全加速部分加速。一个完整的转码过程包含多个阶段,我们的目标是让这些阶段全都使用 GPU 去完成,这样不仅节省了 CPU 的资源,同时也节省了 GPU 与 CPU 之间的数据交互(即 零拷贝),转码阶段参考如下:

  1. 解码(Decode):读取原视频

  2. 处理(Scaling/Tone-mapping):缩放分辨率、HDRSDR 色彩映射

  3. 编码(Encode):压缩成目标格式

但是某些老的显卡只支持解码而不支持编码,这就是 部分加速

对于一些限制和建议:

  • H.264 10-bit:官网文档里提到的几乎所有的 Intel、NVIDIA 和 AMD 显卡都不支持 H.264 10-bit 的硬件编码,如果遇到这种视频,系统会自动回退到 CPU 解码。建议优先使用 H.265 (HEVC) 10-bit 格式
  • HDR 色彩映射:如果你的设备是 HDR 的,但播放端(如旧手机或电脑)不支持 HDR,Jellyfin 可以通过显卡进行 硬件色调映射 (Tone-mapping),将 HDR 画面完美转换为 SDR,防止画面发灰
  • 树莓派用户:文档提到由于树莓派 5 删除了硬件编码器,Jellyfin 已经弃用了对树莓派的 V4L2 硬件加速支持,未来可能会出现兼容性问题

一些性能优化

  • 内存: 如果你使用的是 Intel 或 AMD 的核显,建议组建双通道内存,这能显著提升显存带宽。
  • 缓存: 转码会产生大量临时文件,建议将转码暂存目录设置在 SSD 上,避免机械硬盘成为瓶颈。

说到这里,不得不提一下我那令人心碎的配置。

虽然我的 CPU i3-7300T 自带了相当不错的核显,本应在转码界大杀四方。但遗憾的是,当初为了追求 Server 级的稳定性,我选了 Supermicro X11SSL-F 主板。这块主板搭载的 Intel® C232 芯片组,极其高冷地屏蔽了核显功能。

所以,上述那些酷炫的硬件加速功能,我一个都用不了。每当我在外面看 4K 视频时,我的 CPU 都在机箱里默默流泪(疯狂满载)。大家装机时千万避坑!

Nginx 配置

如果需要使用域名进行访问,可以参考官网文档 – Nginx 配置

这一段配置比较长,如果你看着眼晕,可以直接 CV 大法(Copy & Paste)。

点击展开查看 Nginx 详细配置
server {
    listen 80;
    listen [::]:80;
    server_name jellyfin.skyhive.tech;

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

server {
    listen 443 ssl http2;
    listen [::]:443 ssl http2;
    server_name jellyfin.skyhive.tech;

#NGINX_START


    ## The default `client_max_body_size` is 1M, this might not be enough for some posters, etc.
    client_max_body_size 100M;

    # 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 192.168.2.156;
    resolver 127.0.0.1 valid=30;
    ssl_certificate /etc/nginx/ssl/full.pem;
    ssl_certificate_key /etc/nginx/ssl/key.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
    # NOTE: X-Frame-Options may cause issues with the webOS app
    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.
    # NOTE: The default CSP headers may cause issues with the webOS app
    #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;
    }
}

最后碎碎念

折腾 Jellyfin 的过程,其实就是不断满足自己收藏癖的过程。看着海报墙一点点填满,那种成就感或许只有 Homelab 玩家才懂。

那么家庭影音的部分肯定也不会就到此为止了,刮削才是重头戏,未来还会继续更新以下内容:

  1. 从入门到入土:Jellyfin 完美刮削指南:教你如何整治那些乱七八糟的文件名。

  2. Metashark 调教手册:让你的海报墙不再有 “缺如”。

  3. 神秘的 九公斤 目录:教育资料如何刮削整理?

别急,后续慢慢更新 🤫。