写在前面

虽说有收藏动画片的习惯,但也不过是简单地按放送年份分类就丢进移动硬盘里吃灰的程度。 并且在收藏数量增多、内容不局限于动画片后,简单的「文件夹分类法」就显得有些力不从心:动画片源多为日语罗马字命名,本就不便索引,量一大更是得头顶谷歌翻译看片;收藏时没有及时归类,长期堆积的各种资源像毛线团一样杂糅在一起,难以整理;移动硬盘是从笔记本上拆机下来的 2.5 寸机械硬盘,搭配硬盘盒改装而来的,单盘容量最大只有 1T,三五个下来占满了笔记本的 USB 接口……

尽管上面提到的「空间不足」、「整理不便」等问题都可以通过「换一个大容量硬盘」完美解决,但我不满足于此。在拥有上百部动画片收藏以及数百G音乐资源后,我更希望能以此为基础搭建一个私有的媒体库。

于是,「NAS」成了解决上述问题的最终方案。

NAS」即「Network Attached Storage」,意为「网络附属存储」,可以类比成「连接在路由器上的硬盘」,以便通过网络管理硬盘中存储的内容。

最经济划算的NAS自然是自行组装——毕竟NAS的本质就是一台主机,知名的四盘位星际蜗牛只要数百元——再安装上黑群晖,省出来的钱可以匀给硬盘。但自打工作以来——并不是想说钱包有多鼓,总感觉腰腿酸痛,精神不振,好像身体被掏空,分不出精力来折腾黑群晖;也害怕DIY失败,造成巨额的经济损失。考量过后,还是决定直接入手白群晖。

群里大佬推荐的是「群晖 DS918+」,性能强劲,四盘位可以杜绝后续升级之忧;再加上两块16T的硬盘, 不愁收藏的资源无处安放,保证短期内不会有更换硬盘的想法。可目前经济并不允许我一步到位,退而求其次,选购了二盘位的「群晖 DS220+ 」和两块西数「HC320」8T企业级硬盘。

光是这就叫我够呛。本来就是月光族,这下更是连土也吃不上了。

硬件准备好,接下来就要考虑选择哪种软件提供媒体服务了。一番搜索后,我确定使用免费且开源的「Jellyfin」(PLEX、Emby……好像就 Jellyfin 不要钱)。

题外话:
这篇文章是我在刚接触到 NAS 和媒体库程序的时候写的。当时一昧追求新鲜,忽视很多比如稳定性、是否有汉化等重要因素。现在看来,元数据的选择上只能是 tmdb ,因为有中文,想要补充的话编辑也相对 tvdb 要简单。而 bangumi 没有汉化的地方太多了,不方便使用,用作元数据的补充倒是可行,但万万不可放在首位。
因此,本文只能帮助大家对媒体库程序及文件编排有一个初步的了解,不建议照抄使用 bgm 插件来获取元数据,效果太差。
再说,后来我又换到了 PLEX,就更只能选 tmdb 了。

PLEX YYDS !!!

安装 Jellyfin

关于 Jellyfin 的安装教程,网上随便一搜都有,遍地都是。但我决定再造一个轮子。因为这次是在DSM 7.1-42661版本下操作的,这个版本更新之后,Docker新建容器的界面变得有一点点不一样。

首先在群晖的套件中心中安装 Docker。

因为 Jellyfin 不是套件中心中现成的应用,只能安装 Docker 容器版。安装完成后可以将Docker图标添加到桌面上,方便下次打开。

接着下载容器映像。

在「注册表」中搜索「Jellyfin」,找到名称为「nyanmisaka/jellyfin」的映像。

让我来解释一下为何选择这个映像。一开始也是自然而然地下载了官方映像,就是下图中第一个星星最多的「jellyfin/jellyfin」。官方映像覆盖设备广,教程也多,但我媒体库的设置部分总是搞不定,于是换成第二个「linuxserver/jellyfin」。这个映像也是教程介绍比较多的一种,只不过想要启用「Intel QuickSync(QSV)」硬件解码加速功能的话,需要手动进终端里敲命令安装解码驱动1才能实现,算不上麻烦,但我这里实际解码效果不佳。就这样用了几周后,发现了「nyanmisaka/jellyfin」,根据张大妈上的教程2以及作者nyanmisaka在B站上对该映像的自述3,这是个针对Intel 的「QuickSync QSV」硬件加速进行过本地优化的版本。那么理应首选「nyanmisaka/jellyfin」。

双击映像,在弹出的标签中选择latest,点击下载。

整个映像的大小约为1GB,如果网络不畅,可以考虑修改Docker注册表镜像源4

下载完成后启用映像。在「映像」中找到下载好的映像,双击或是点击上方「启动」按钮启动。

来到创建容器的设置界面。这里可要万分仔细了,一不小心NAS就会爆炸提示容器启用失败。

DSM 7.1版本的区别就在这里了,创建容器的第一步改成了选择网络。这里建议勾选「使用与Docker Host相同的网络」,因为Jellyfin的各种程序都是默认8096这个端口,自行设置别的端口反倒会徒增麻烦。

如果还是想要自设端口,建议将端口改成和容器端口一致的8096。放行这一个就够,HTTPS端口可以不放行,一般情况用不到它。

常规设置主要勾选「使用高权限执行容器」和「启用自动重新启动」。前者给予充分权限避免后续出幺蛾子,后者则是为了让容器能在NAS重启后跟着自动重启。

如果你知道且拥有代理,可以在「高级设置」中点击「新增」,在环境变量中添加两行:

http_proxy 192.168.0.1:7890
https_proxy 192.168.0.1:7890

其中,192.168.0.1:7890 以你实际的代理 IP 为准。

这么做的目的是为了让元数据网站通过代理正常访问。

最后一步,存储空间设置。


这里是将NAS中创建的共享文件夹映射到容器中。必须要映射的是容器的configcache这两个文件夹,docker共享文件夹是安装Docker会自动创建的,选中该文件夹,点击上方「创建文件夹」按钮创建「jellyfin」文件夹即可。

媒体文件夹我选择的是anime,这个根据个人喜好选择就行。这里需要事先修改该共享文件夹的权限。

在File station中,对该文件夹右键,点击「属性」-「权限」- 「新增」,在用户组别中选择「Everyone」,授予所有的「读取」和「写入」权,再勾选左下方「应用到这个文件夹、子文件夹及文件」,点击「完成」后「保存」。

建议映射到/media文件夹内,这是Jellyfin的媒体库。

最后的最后,点击完成,等待容器启动。

如果一切顺利,访问http://ip:8096就可以看到Jellyfin的初始化界面。

如果你愿意使用 Docker Compose,那么上面的长篇大论只要一个文件就能搞定。

version: "3.9"
services:

  jellyfin:
    image: nyanmisaka/jellyfin:latest
    container_name: jellyfin
    # network_mode: host
    ports:
      - 8086:8096
      # - 8920:8920
      # - 1900:1900/udp
      # - 7359:7359/udp
    environment:
      - PUID=1026
      - PGID=100
      - TZ=Asia/Shanghai
      - HTTP_PROXY=http://192.168.31.123:7890
      - HTTPS_PROXY=http://192.168.31.123:7890
    devices:
      - /dev/dri:/dev/dri
    volumes:
      - ./jellyfin/config:/config
      - ./jellyfin/cache:/cache
      - /volume1/media:/media
    restart: unless-stopped

设置 Jellyfin

初始化

初始设置很简单。

先选择语言。nyanmisaka的版本一律将「中文」显示成了「汉语(简化字)」。

用户名默认是root,可以修改成自己想要的。密码也可以留空,毕竟是自用嘛!

媒体库可以稍后设置,这里点击下一步。

元数据语言按图中所示选择。关于何为「元数据」,我会在稍后解释。

远程访问这一页保持默认,不勾选「开启自动端口映射」也能用。

这样初始化就搞定啦!相当简单的是吧,复杂的地方在后头。

转码设置

在开始使用前,先不要着急添加媒体库。把转码功能开了。「硬件转码」是流媒体中很重要的一部分,不管是PLEX亦或是Emby,硬解功能都是收费的。而选择「nyanmisaka/jellyfin」这个映像,为的就是能简单快捷地用上「Intel QuickSync(QSV)」硬解。

所以只要进入「控制台」-「播放」,选择「Intel QuickSync(QSV)」,把能勾选的视频编码格式全勾上,其他选项根据自己的理解勾选。

至于图中未截出的「备用字体」,没能实际测试成功过。不管塞woff2也好,丢全量的ttf进去也罢,显示出来的都是Jellyfin的默认字体。


而本地播放器是这样的。

高下立判。

关于「元数据」

在安装插件之前,来介绍什么是「元数据(metadata)」。简单地说,封面、标题 、简介、发行时间、制作公司、演员表等等一切和该影片相关的信息,都是元数据。元数据越完善,流媒体程序展现出来的界面就越美观。这些信息自然不会在下载资源的时候附带,因此就需要使用Jellyfin中的「元数据插件」,将这些信息从网上「刮削」下来。「刮削」这个词,大概是源自英语「Scraper」的直译。例如「Meta Data Scrapers」即为「元数据刮削」。按理说译成「问数据采集」会更合适些,但现在全中文站点都将这种行为称为「刮削」,也就这么用着吧!

安装 Bangumi 插件

一般来说,Jellyfin自带的这些插件够用,尤其是「TMDB」。

The Movie Database」,简称「TMDb」,是目前接触到的元数据网站中唯一一个提供中文界面的网站,并且支持采集电视剧5、电影和人物信息,我愿称之为「刮削界顶梁柱」。

只是顶梁柱在国内的访问不是很顺畅,刮削失败是常有的事。因此,选用其他支持中文且能直接访问的插件是进行刮削工作前的首要指标。例如豆瓣,也有爱好者编写了相关插件供Jellyfin使用,但就目前的使用体验来看,对动画的数据采集不算很好,时常会采集到一些带视频网站水印的背景图。

因此,这里用到的是来自「kookxiang」的「Bangumi」插件。

Bangumi 番组计划」是一个中文ACG互联网分享与交流网站,功能与豆瓣类似。因为专精ACG,所以Bangumi上的元数据信息要比豆瓣详细。更关键的是,Bangumi在国内是可以直接访问的。

如果网络顺畅,可以直接在线安装。在「插件」菜单的「存储库」页面点击加号,添加如下网址:

https://jellyfin-plugin-bangumi.pages.dev/repository.json

保存后,就可以在「目录」页的「元数据」分类中找到「Bangumi」插件。虽然排头几个一看就是「动画元数据插件」,但由于它们都不支持中文元数据,故PASS。

选择「Bangumi」插件并安装。安装完成后会提示需要重启才能使用该插件。前往Docker容器中重启Jellyfin即可。

在线安装的好处是可以自动更新,但网络不畅的时候6,这个「目录」页加载十分钟都不一定出得来。

所以第二种方法是手动安装,在该插件「Github releases」页面下载插件 DLL 文件至 Jellyfin 数据目录/Plugins/Bangumi中,再重启 Jellyfin。

安装成功的话,插件页面的Bangumi状态应该显示为「Active」。

添加媒体库

元数据插件准备完毕,就可以添加媒体库了。

内容类型选择「节目」,显示名称就随意了,我这里用的是「动画」。

点击文件夹旁的按钮添加媒体库文件夹,也就是一开始安装Jellyfin映射到容器中的那个。推荐将SP季显示的名称设置为「番外」,勾选所有的元数据插件,将Bangumi排在首位,次位为TMDB。勾选「优先使用内置的剧集信息而不是文件名」、「媒体资料储存方式︰Nfo」、「将媒体图像保存到媒体所在文件夹」……

其余部分的设置还请参考图片,太多了。

点击展开超长长图
点击展开超长长图

媒体库准备

动画分类

这里说的不是动画的风格或是发行年份,而是指动画的类型。不同的类型,使用到的「TMDB」数据库是不一样的。因此需要区分「TV」和「MOVIE」,也就是「TV动画」和「剧场版动画」,创建媒体库时要与之对应。

重命名

From 未来的 mikusa:尽管如此,还是建议重命名时尽量符合各大程序的标准,方便后续更换媒体库程序。如:

俗话说磨刀不误砍柴工。前面折腾了这么久,元数据的获取反倒没什么好说的。因为这是全自动的。为了最大程度减少元数据的获取工作,自然是将这一环节全权交给Jellyfin。但全自动也有前提,按照Jellyfin的官方介绍7,视频的文件夹及文件命名有一定要求。

第一个种格式是按季分类,先创建(剧集)文件夹,再新建(季度)文件夹。番外放在「Season 00」文件夹中,第一季放在「Season 01」文件夹中,以此类推。

第二种格式则是直接剧集的所有节目都放在同一级文件夹中。

因为 Bangumi / TMDB 插件可以直接索引中文,命名无需刻意使用英文(好耶!)。

动画
├── 狼与香辛料 (2008) [第一种方法]
│   ├── Season 00
│   │   ├── 狼与香辛料 S00E02.mkv
│   │   └── 狼与香辛料 S00E02.mkv
│   ├── Season 01
│   │   ├── 狼与香辛料 S01E01.mkv
│   │   ├── 狼与香辛料 S01E02.mkv
│   │   ├── 狼与香辛料 S01E03.mkv
│   │   └── 狼与香辛料 S01E04.mkv
│   └── Season 02
│       ├── 狼与香辛料 S02E01.mkv
│       └── 狼与香辛料 S02E02.mkv
└── 3月的狮子 (2016) [第二种方法]
    ├── 3月的狮子 S01E01.mkv
    ├── 3月的狮子 S01E02.mkv
    ├── 3月的狮子 S02E01.mkv
    ├── 3月的狮子 S02E02.mkv
    └── 3月的狮子 S02E03.mkv

实际测试下来,重命名的方式可以更简单,「Season 00」、「Season 01」可以缩写为「S00」、「S01」;视频文件亦可以保留原名称,改为在开头加上「S01E01」的字样即可被插件读取。

例如:

动画
├── 狼与香辛料 (2008) 
│   ├── S00
│   │   └── S00E01 - 狼与香辛料的原视频文件名.mkv
│   ├── S01
│   │   ├── S01E01 - 狼与香辛料的原视频文件名.mkv
│   │   └── ……
│   └── S02
│       ├── S02E01 - 狼与香辛料的原视频文件名.mkv
│       └── ……
└── ……

这里解释下我的命名方式:

  • 「狼与香辛料 (2008) 」的信息复制自「TMDB」中搜索到的标题
  • 「S00」等季度信息完全参考「TMDB」的数据
  • 「 - 」分隔符是我为了保持文件整体美观,在「S01E01」和视频文件中额外插入的
  • 如果某部动画只有一季,可以不必创建「S01」文件夹,直接全丢到主文件夹中

下面是两种分类方式的示意图。

主文件夹
主文件夹

季文件夹
季文件夹

不设季文件夹
不设季文件夹

批量重命名

重命名倒也简单。如果你的视频比较多,可以使用 NASTool 之类的媒体程序工具。如果不是特别多,且追求准确率的话,有现成的重命名工具「ReNamer」。就不详细介绍使用方法了,添加两三个规则就能批量重命名。

如果是外挂字幕,一定要将字幕和视频文件放在一起。若名和视频文件名不匹配,这里再推荐一个字幕重命名工具「SubRenamer」。使用方法同样很简单,将视频文件和字幕文件丢进工具里即可。

为了保证 Jellyfin 能准确判断字幕的语言类型,建议将字幕扩展名设置为「zh」或「chs」。

例如,视频文件的名称为:

3月的狮子 (2016) S01E01.mkv

那么字幕的名称应为:

3月的狮子 (2016) S01E01.zh.ass

即将字幕重命名为与视频相同后,额外在最后添加 .zh 即可被媒体程序识别。

另外,由于 Plex 仅支持「zh」,若有跳槽到其他媒体程序的需求,建议优先设置为「zh」。繁体中文设置为「zh-tw」即可。

重命名步骤结束后,你的 Jellyfin 应该可以完美地将视频元数据都采集下来,并将海报展示在主界面了。

接下来是失败案例。

反面教材

现实是,我下载了这些资源,压根不想动它。或者是为了保证继续做种,文件结构不能变动,这种情况该怎么办?

先前提到,我最原始的文件夹是按放送年份分类的,大概是这样:

动画
├── 2010
│   ├── 缘之空
│   ├── 爆漫王。
│   ├── 妄想学生会
│   ├── 四叠半神话大系
│   ├── 笨蛋,测验,召唤兽
│   └── ……
├── 2011
│   ├── 命运石之门
│   ├── 花开伊吕波
│   ├── 日常
│   └── ……
├── 2012
│   ├── ……

按照第一级目录「2010」索引,被识别成了这样……

这怪我一开始没有考察清楚 Jellyfin 文件夹的规则,希望各位不要重蹈我的覆辙。

于是开始手动新建主文件夹,并将资源原封不动地丢进去,例如:


├── [2017]月色真美
│   └── [XXX-SUB] XXX [1080p]
├── [2017]终物语(下)
│   └── [XXX-SUB] XXX [1080p]
├── [2017]路人女主的养成方法b
│   └── [XXX-SUB] XXX [1080p]
├── [2018]恋如雨止
│   └── [XXX-SUB] XXX [1080p]
├── [2018]佐贺偶像是传奇
│   └── [XXX-SUB] XXX [1080p]
├──  ……

 title=
title=

还是以《狼与香辛料》为例。「[XXX-SUB] Spice and Wolf」这个资源的结构大概是这样的:

[XXX-SUB] Spice and Wolf
├── [XXX-SUB] Spice and Wolf [Ma10p_1080p]
│   ├── CDs
│   │   └── ……
│   ├── Scans
│   │   └── BDBOX
│   ├── SPs
│   │   ├── [XXX-SUB] Spice and Wolf [CM01][Ma10p_1080p][x265_flac].mkv
│   │   └── ……
│   ├── [XXX-SUB] Spice and Wolf [01][Ma10p_1080p][x265_flac].mkv
│   └── ……
└── [XXX-SUB] Spice and Wolf II [Ma10p_1080p]
    ├── CDs
    │   └── ……
    ├── Scans
    │   └── BDBOX
    ├── SPs
    │   ├── [XXX-SUB] Spice and Wolf II [CM01][Ma10p_1080p]  [x265_flac].mkv
    │   └── ……
    ├── [XXX-SUB] Spice and Wolf II [01][Ma10p_1080p][x265_flac].mkv
    └── ……

因为是 BDRip,其中包含BD所有的内容,有 CD、SP、BD 扫图、正片。收到的资源结构也不一定都是这样的,也有可能全都一股脑放在一起……

识别出来就会是这样:

剧集内是这样:

这个鬼样,搭建媒体库不就没有意义了嘛!

怎么办?手动吧!

对着无法识别的剧集右键,点击识别。

把名字填进去,建议先在 Bangumi 搜索一下,有时候英文识别不出来。

选第一个。因为第二个是第二季。

然后确定。

喔!看起来还不错!!

另一个也如法炮制。

等等,我正片呢??

原来是因为文件夹格式命中第一个格式,于是就把SP、CD等识别成了分季。

那我不分开索引,而是把这些视频还原同一级目录下,应该就能识别到了吧!

果然可行!

只是它把第二季也识别成了第一季罢了。

累了,毁灭吧。

硬链接

经过一番试验,我确定了一条真理:为了保证资源的完整,唯一可走的道路只有硬链接一条。

硬链接(hard link),是指系统中的多个文件平等地共享同一个文件存储单元,与之对应的是软链接(Symbolic link)。硬链接指向的是文件本体,软链接指向的是文件的存放路径。硬链接在生活中并不常用,但大家都用过软链接,Windows上的快捷方式就属于软链接。

概念就不再多做介绍了,因为我也不是很懂,只要知道「只要不改动源文件,硬链接可以对链接出来的文件任意改名」和「硬链接出来的文件不重复占用硬盘空间」这两点就足够了。

但还有一点,硬链接的文件必须在同一个文件系统中。放在群晖系统中就是,硬链接只能在同一个共享文件夹中进行,不能跨共享文件夹硬链接。

网上那些进入ssh控制台操作的方法暂且不用,我怕手一抖把系统搞坏了。这里用一个比较稳定可靠的方法:将文件夹挂载到Windows系统中,在电脑上使用PC端工具手动硬链接。

这里用到的硬链接工具是「HardLinkShellExt」,可以在官网或在我的备份中下载。

选中想要硬链接的文件,右键「选择源链接点」。

在另一文件夹,右键「创建为 > 硬链接」。接下来就可以继续重命名工作啦!

你也可以直接将整个需要重命名的文件夹硬链接到同一处,删除不需要的部分,再慢慢整理、分类、重命名。

上面的方法还是太过繁琐了,使用 Hlink 硬链工具会更加快捷。简单说明一下部署方法。

如果是在 Linux 系统中,使用 docker-compose.yml 来部署:

version: "3.9"
services:
  hlink:
    image: likun7981/hlink:latest
    container_name: hlink
    restart: unless-stopped
    ports:
      - 2233:9090 #左侧2233可以任意修改
    volumes:
      # 这个表示存储空间映射
      - /volume1/media:/media #需要硬链的文件夹
      - ./hlink:/config #存放hlink配置文件的地方,要与下面一致
    environment:
      - PUID=1026
      - PGID=100
      - HLINK_HOME=/config #hlink配置文件路径

如果是群晖 NAS,可以这么来,废话我就不多说了,咱们直接看图:

接着访问你设置好的端口,访问 hlink 的webui。

点击创建配置。这里举个硬链接 VCB-Studio 的 BDRip 资源的例子。

假设你的动画片都在 /media/down 中,需要硬链接到 /media/wait 中。同时,保留资源中的字幕文件,排除不想硬连接的 CDs、SP 等资源,可以这么做:

// 重要说明路径地址都请填写 绝对路径!!!!
export default {
  /**
   * 源路径与目标路径的映射关系
   * 例子:
   *  pathsMapping: {
   *     '/path/to/exampleSource': '/path/to/exampleDest',
   *     '/path/to/exampleSource2': '/path/to/exampleDest2'
   *  }
   */
  pathsMapping: {
         '/media/down': '/media/wait'
  },
  /**
   * 需要包含的后缀,如果与exclude同时配置,则取两者的交集
   * include 留空表示包含所有文件
   *
   * 后缀不够用? 高阶用法: https://hlink.likun.me/other/v2.html#%E6%96%B0%E5%A2%9E%E5%8A%9F%E8%83%BD
   */
  include: [
    'mp4',
    'mkv',
    'mka',
    'ass'
  ],
  /**
   * 需要排除的后缀,如果与include同时配置,则取两者的交集
   *
   * 后缀不够用? 高阶用法: https://hlink.likun.me/other/v2.html#%E6%96%B0%E5%A2%9E%E5%8A%9F%E8%83%BD
   */
  exclude: {
    globs: [
      '**/CDs/**',
      '**/SPs/**',
      '**/Scans/**'
      ],
    exts: ['cht.ass', 'tc.ass'],
  },
  /**
   * @scope 该配置项 hlink 专用
   * 是否保持原有目录结构,为false时则只保存一级目录结构
   * 可选值: true/false
   * 例子:
   *  - 源地址目录为:/a
   *  - 目标地址目录为: /d
   *  - 链接的文件地址为 /a/b/c/z/y/mv.mkv;
   *  如果设置为true  生成的硬链地址为: /d/b/c/z/y/mv.mkv
   *  如果设置为false 生成的硬链地址为:/d/y/mv.mkv
   */
  keepDirStruct: true,
  /**
   * @scope 该配置项 hlink 专用
   * 是否打开缓存,为true表示打开
   * 可选值: true/false
   * 打开后,每次硬链后会把对应文件存入缓存,就算下次删除硬链,也不会进行硬链
   */
  openCache: false,
  /**
   * @scope 该配置项 hlink 专用
   * 是否为独立文件创建同名文件夹,为true表示创建
   * 可选值: true/false
   */
  mkdirIfSingle: true,
  /**
   * @scope 该配置项为 hlink prune 命令专用
   * 是否删除文件及所在目录,为false只会删除文件
   * 可选值: true/false
   */
  deleteDir: false,
}

之后再新建任务,选择这个配置。一键运行就完事了。快如闪电。

下面是我的硬链接文件夹设置,仅供参考。

NAS
├── Media
│   ├── anime [下载下来的资源,原封不动]
│   ├── TV动画 [TV动画硬链接到这里]
│   │   └── ……
│   └── 剧场版动画 [剧场版动画硬链接到这里]
│       └── ……

不过,在没日没夜整理硬链接文件的途中叛逃到 Emby 就是另一回事了。

谁让 Emby 更好看呢。

参考