Files
blogs/docs/diaries/wbsy/nas-media.md
SilverAg.L 2b62d58c85
All checks were successful
部署文档 / build (push) Successful in 3m20s
Edit Diary: nas media
Signed-off-by: SilverAg.L <caclx@outlook.com>
2026-02-22 23:36:39 +08:00

205 lines
12 KiB
Markdown
Raw Blame History

This file contains ambiguous Unicode characters
This file contains Unicode characters that might be confused with other characters. If you think that this is intentional, you can safely ignore this warning. Use the Escape button to reveal them.
# Jellyfin 真的好用吗?
我的结论是:实则不然。但 DIY 这一块本就是矮个子拔高个,也没那么多平替可以挑。
> [!important]
> - 本文字里行间夹带着作者的私货,行文并不严谨,谨慎阅读。
> - 本文有关的折腾流程时间跨度较长,经验之谈不说没有,也只能说碎得跟渣一样。~~所以不是笔记而是大作文。~~
## 硬件加速,硬件呢
我是 Linux Docker 部署的。由于因特耳的核显驱动闭源,**官版 Docker 容器搞起来后还需要`exec -it`进去装 Intel 驱动**。即便是`nyanmisaka`版开箱即用 Jellyfin 镜像,也需要**手动映射渲染节点**(他们就没设置过`render`用户组,若要**映射整个`/dev/dri`后续就需要进去`groupadd`**)。
::: details docker-compose.yml
See [AgxCOy@liteyuki/agserver.svc](https://git.liteyuki.org/AgxCOy/agserver.svc).
```yaml
services:
jellyfin:
image: nyanmisaka/jellyfin:latest
container_name: jellyfin
network_mode: 'host'
volumes:
- /var/lib/jellyfin:/config
- jellyfin-cache:/cache
- /media:/media
# - /dev/dri/renderD128:/dev/dri/renderD128
- type: bind
source: /usr/share/fonts/opentype
target: /usr/local/share/fonts/custom
read_only: true
devices:
- /dev/dri/renderD128:/dev/dri/renderD128
#- /dev/dri/card0:/dev/dri/card0
restart: 'unless-stopped'
# Optional - alternative address used for autodiscovery
# environment:
# - JELLYFIN_PublishedServerUrl=
extra_hosts:
- 'host.docker.internal:host-gateway'
volumes:
jellyfin-cache:
driver: local
```
:::
## 媒体库组织
我还是更习惯树形文件系统,媒体库还是太难为我了。那样的话,事情或许也很简单——许多成品 NAS 都有自带视频、相册之类的浏览功能。但很遗憾,自己装的 Ubuntu Server 自然什么都没有。
我针对的还是 Jellyfin “神奇”的资源组织方式。就像[这篇《基于 Jellyfin 和音流的 nas 影音库搭建及踩坑》](https://sspai.com/post/90896)所说,“为什么不直接读取音乐标签进行专辑聚合”呢?
### 元数据刮削:一笔糊涂账
然而事实上,即使真的能做到自动专辑聚合,元数据本身的刻录和查询也是一片混乱。上面援引的博文推荐 MusicTag 这个工具,但同是从网易云那刮削,下载下来本是《紫罗兰永恒花园 OST》的曲目能给它识别成《凹凸世界五周年》也是给我看笑了。更别说国外的 MusicBrainZ 数据库有相当一部分冷门曲目并没有收录(尽管 Spotify 等平台能够找到)。
![误刮削](./musictag-misfetch.webp)
::: tip 音乐库最简方案
文档这么说:我管你怎么整理,我只知道**是同一张专辑就该塞在同一个文件夹**。
那么事情也好办,就按专辑名来呗。好比如:`music/{album or "[standalone]"}/{artist1,artist2} - {title}`
- 为什么要`or "[standalone]"`
- 不单独收纳**没有专辑**的单曲,结果就会被淹没在大几百首、好几页的“歌曲”页面。
- 要是有专辑重名了(比如两张专都叫`Fragrance`
- 没招,默认合并(甚至可以看到重复的曲号,都是第六首)。只能手工分离(`Fragrance - SoundzImage``Fragrance - Hatsune Miku`)。
:::
但比起前面所述的国内外两桌草台班子更令我恼火的当属大把大把的、查无此人的“黑户”。怎么来的音声、网文、3D 动画。
> 我的整理方式和 acgdb 类似3D 区、音声区作者分发、油管录播、DLsite 正作)、书目区(漫画和文集)、写真区、插画区,此外新增音乐区。
3D 区无论玩恋活还是 MMD既有图集又有视频无法按媒体类型区分。即便真要分它算电视节目电影MV分不明白。
音声区大量的音频资源,摆烂一点直接用电子书库、按文件夹访查也不是不行,但显示出来反倒是专辑名(或者说 DLsite、系列作品名更显眼而非每一集的标题若要用音乐库收纳就得改造一番目录树适配它那个“一个专辑只有一个目录”的原则工作量也不小。
至于书目区,正经出版物想获取元数据并不难。相对的,“不正经”读物呢?网文可以依靠豆瓣之流;同人文只要作者有公开发布,留心寻找也是能记下元数据的(同人圈子对打 Tag 这件事很看重那么只剩下来源不正经、题材也不正经的“那种”作品了。它们流通的地方通常都是论坛这种早期网络社区只知道个标题很难说发帖人就是作者万一是二道贩子呢甚至文件名也不一定就是完整标题。What can I say?
偏偏这种不在台面上的“资源”我收纳了不少,哪怕有自动化工具,后续校对也是个体力活,何况很多资源只能一件件手工入库。饶了我吧。
### 电子书:吃力不讨好
小说漫画,或者说书籍媒体库,只能说真搭一个私人图书馆大概也和 Jellyfin 别无二致——满满的工作量。可能甚至有过之而不及。
> 不禁想到米哈游创始人蔡浩宇的“快乐守恒定律”:你做的过程中如果很轻松快乐,那么你做出来的结果未必能给人带来快乐。
> 媒体库编排是不是也要“苦其心志、劳其筋骨”,才能有好的观影体验呢?但话又说回来,这种家庭影院通常都是自己管理自己用,费那么大功夫组织这些文件,观赏的体验又能改善多少呢?
我相信大多数\*并不在乎资源来源\*的读者去找网络上流通的小说肯定优先去找 txt、doc(x) 这种容易打开的格式。但现有的私人图书馆,无论 Jellyfin 这种顺手实现的,还是 Kavita 这种专用的,**都不支持这些格式**。Calibre 也许可以,但**书目的元数据就成了另一桩麻烦事**。
::: tip 最简方案
那当然是通通转换成 pdf单纯当作文件管理器那样访问文件夹、文件最简单。**但 pdf 对移动端并不友好,文字内容并不能自适应,更多还是用于漫画**。
:::
进阶一点的方案主要围绕 epub 电子书,毕竟漫画这块 pdf、epub、`.cb*`压缩包三者的页面版式不可能差太多,翻页起来其实没什么区别。
::: info 压缩包漫画
漫画本质上就是一页页的画嘛。许多网站本就提供试阅,给这些图片打个包也就是顺手的事。像我这样存储空间拮据的(主要还是[协议不通用](recents-11172025.md#硬件购置)),只能是哪种最节省空间就选哪种咯。
Jellyfin 本身支持`.zip` `.rar` `.7z`。如果你打算另起炉灶,但选配的服务不支持这样通用的后缀名,也可以改变后缀:
`.cbz`即 zip、`.cbr`即 rar、`.cb7`即 7z、`.cbt`即 tar。
至于元数据Jellyfin 文档指出**只有 epub 书籍才允许元数据集成在文件里**,对于这种漫画压缩包,需要另写`ComicInfo.xml`。也就是说,**每一本、系列的每一话,都需要单独的文件夹放置元数据和本体**。
:::
现有的 epub 编辑器并没有像写哔哩哔哩专栏(或者像 Word 文档)那样自动编纂页面的能力,操作起来更像是手搓 Web 三剑客HTML、CSS、JavaScript。所以**图方便的话更建议在线转换**`.txt` `.doc(x)`文档。
::: details EPUB 内容物
epub 的 mimetype 通常标`application/epub+zip`,说明其本质是压缩包。解压出来通常分这么几块:
- `mimetype`:资源类型标识,内容如上。
- `META-INF/container.xml`标明电子书OEBPS 包)的根目录。
- `OEBPS/`
- `Images/cover.*`:封面
- `Styles/*.css`:网页排版
- `Text/*.*htm*`:反正必须是 HTML。
- `content.opf`:电子书的元数据、内容物信息。
- `nav.*htm*` `nav.opf`:电子书的目录。前者为 epub3 标准,后者为 epub2 标准,通常在`content.opf`里特别标注。
- `Fonts/` `Audio/` ...:其他内容。
所以我称编辑 epub 这件事为“手搓网站”,因为本来就需要按章回编排 Web 内容,这 OEBPS 的目录树也很有网站的味。
:::
::: warning 书目目录索引
和 Calibre 等阅读器不同Jellyfin 对目录的跳转**以 OEBPS 包为基准**,而不是以目录文件为基准。如果发现 Jellyfin 里**点目录里的章标题没有反应**,你需要用 Sigil 等编辑器将`nav.*`移动到`OEBPS/`目录下。
:::
::: note 文件组织
实测结论是:可以不像漫画那样遵循官方文档“一书一目录”的要求,但**应尽量避免“一目录只有一 epub”的情形**。像以下情形 Jellyfin 会误判:
- Books
- 正经的
- 《时间简史》.epub
- 毛泽东
- 《实践论》.pdf
- 《矛盾论》.pdf
- 《葬送的芙莉莲》.pdf
- 不正经的
扫描结果是:“不正经的”和《时间简史》。其余读物无法直接访问(可能仍能搜索到)。
:::
## 穿透与反向代理
如今连网易云也改得不知为何物了,于是我随身听的需求便也转向 Jellyfin。最简单的方法可以是 ZeroTier但安卓有“同一时间只允许一个 VPN”的限制所以在群友指导下搞了内网穿透FRP
此后,我遇到了子网划分的问题。起初这个问题并不起眼,直到我在户外同时尝试走 ZeroTier 和穿透,我才发现 frp 连上之后似乎更像是在本地回环,而 Jellyfin 似乎**对本地回环不设限速**。
> ~~写着写着总会变成报流水账。~~
AI 对此的解法是在 frp 这里记录下真实 IP。我选配的服务商支持 Proxy Protocol也更推荐这么实现。但在实地测试后发现 Jellyfin 并不直接支持 Proxy Protocol那么就只能在 frpc 跟 Jellyfin 之间多加一层 nginx 做反向代理了frps 显然我是动不了的)。
方法很简单frp 侧仍旧启用 Proxy Protocol在 nginx 里剥离真实 ip。我是在 Ubuntu Server 上安装的 nginx据称已集成`real-ip`模块。那么编辑`/etc/nginx/sites-enabled/default`(我是图方便直接原地开刀了):
```
server {
listen 8097 proxy_protocol;
listen [::]:8097 proxy_protocol;
set_real_ip_from 127.0.0.1;
real_ip_header proxy_protocol;
server_name _;
add_header X-Content-Type-Options "nosniff";
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;
proxy_set_header X-Forwarded-Protocol $scheme;
proxy_set_header X-Forwarded-Host $http_host;
proxy_buffering off;
}
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;
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 推荐给真实客户端信息“打码”
access_log /var/log/nginx/access.log stripsecrets;
}
# 为了解说方便把 http 块里的全局“打码”配置拉下来叻(本文件本来就嵌套在 http 块)。
# 不清楚先后顺序有没有影响。
log_format stripsecrets '$remote_addr $host - $remote_user [$time_local] '
'"$secretfilter" $status $body_bytes_sent '
'$request_length $request_time $upstream_response_time '
'"$http_referer" "$http_user_agent"';
map $request $secretfilter {
~*^(?<prefix1>.*[\?&]api_key=)([^&]*)(?<suffix1>.*)$ "${prefix1}***$suffix1";
~*^(?<prefix1>.*[\?&]ApiKey=)([^&]*)(?<suffix1>.*)$ "${prefix1}***$suffix1";
default $request;
}
```