起因∶网易云无故被封
促进我自建媒体库的原因,是因为网易云乱封号,两次把我的号给封了。
理由居然是——播放数据异常
真就是,想封你,随便编个理由,直接就封。纯脑残!
其实之前我就有点想搞媒体服务器了,但是由于我看番都是用种子下载看番,而且番剧看一次也就过去了,我觉得也没用留着的必要。毕竟番剧的一话,1080P 60帧,都要差不多1GB了,看番多的话,文件破TB级那是轻轻松松。
这次封号,算是坚定了我的想法。我觉得国内互联网公司绝大部分都是狗屎,让人无语。
其实是2024年10月封的号,但是我2025年1月才开始折腾媒体服务器。因为之前都在学校,课业繁忙,乐的要死,因为整理音乐库费时费力,所以我打算放假再搞。事实证明我的想法是对的,我前前后后整理了快一个星期,整理了绝大部分,但是还有相当一部分歌曲还没整理。太累了,让我歇歇。目前已经整理了1300+首歌,文件夹已经90G+了。。。
为什么选择了Jellyfin?
免费,开源。开源,开源,开源,还是他妈的开源!
我是支持GNU自由软件运动的人,自然更倾向于使用开源的解决方案,尤其是使用GPL协议的开源软件。
顺带一提,我主力操作系统早就已经换成GNU/Linux了。
部署Jellyfin
下载Jellyfin
直接前往官方网页下载即可,我用的最新版,目前是10.10.3
。
下载链接:repo.jellyfin.org/?path=/server/linux/latest-stable
下载FFmpeg
由于我服务器使用的是AlmaLinux8.10
,是服务器发行版,其自带的FFmpeg比较老,新版的Jellyfin并不兼容。
虽然可以通过yum
包管理器直接安装对应版本的Jellyfin,并且不会出现兼容问题。但是旧版的Jellyfin并没有歌词支持,对我来说比较蛋疼,因为我主要想做的就是歌曲的媒体服务器。
我下载的是7.0.2
版本的,这种的话,我更倾向于使用最新版的前一个稳定版。
下载地址:ffmpeg.org/releases/ffmpeg-7.0.2.tar.xz
自定义环境变量,以使用自定义的ffmpeg
我的做法是,把FFmpeg
和Jellyfin
解压后,分别放到了不同的文件夹,然后在根目录文件夹放了一个启动脚本。
以下是启动脚本的内容:
#!/bin/bash
# 定义FFmpeg二进制文件所在的路径
FFMPEG_PATH="./ffmpeg/bin"
# 将FFmpeg路径添加到PATH环境变量的最前面
export PATH="$FFMPEG_PATH:$PATH"
# 启动Jellyfin
./jellyfin/jellyfin
脚本保存为run.sh
,然后再:
chmod +x ./run.sh
试一下执行这个脚本,看看Jellyfin有没有启动成功,如果启动成功后就完事了。
配置Jellyfin
初始化配置
打开浏览器,输入服务器对应的ip+8096
端口,例如:127.0.0.1:8096
按照指引,设置用户名和密码。这部过于简单,有手就行,不多赘述。
配置HTTPS访问
这个Jellyfin比较特别,需要使用pfx
证书,而不是常见的pem
+key
的证书组合。
我们需要把pem
和key
证书转成pfx
格式。
下面是一个shell脚本,可以便利当前目录的pem
+key
证书,并且转换为pfx
证书放到当前目录的pfx_cert/
文件夹内:
#!/bin/bash
# 定义输出目录
OUTPUT_DIR="pfx_cert"
# 创建输出目录(如果它不存在)
mkdir -p "$OUTPUT_DIR"
# 遍历所有的 .key 文件
for key in *.key; do
# 获取不带扩展名的文件名
base_name="${key%.key}"
# 检查对应的 .pem 文件是否存在
if [[ -f "${base_name}.pem" ]]; then
echo "正在处理 $base_name..."
# 转换为 PFX 格式
openssl pkcs12 -export \
-out "${OUTPUT_DIR}/${base_name}.pfx" \
-inkey "$key" \
-in "${base_name}.pem" \
-certfile le-chain.pem
echo "已生成 ${OUTPUT_DIR}/${base_name}.pfx"
else
echo "警告:找不到与 $key 对应的 .pem 文件"
fi
done
把以上内容保存到to_pfx.sh
,然后:
chmod +x ./to_pfx.sh
./to_pfx.sh
当要求输入密码的时候,直接回车,不设置密码。
在Jellyfin设置证书,选择得到的pfx
证书文件。
记得勾上强制HTTPS
和允许远程连接
保存后,重启Jellyfin,应该就可以通过HTTPS访问了。别忘了添加域名解析记录。
至此,基本上配置完成。
添加媒体注意事项
如果是音乐的话,最好的目录分类方式应该是:
- 歌手 > 专辑 > 音乐文件/歌词文件
例如,如果是Taylor Swift的《Red》专辑,目录结构就长这样:
/path/to/music_library/
│
├── Taylor Swift
│ └── Red (2012)
│ ├── 01. State of Grace.mp3
│ ├── 01. State of Grace.lrc
│ ├── 02. Red.mp3
│ ├── 02. Red.lrc
│ ├── 03. Treacherous.mp3
│ ├── 03. Treacherous.lrc
│ ├── 04. I Knew You Were Trouble.mp3
│ ├── 04. I Knew You Were Trouble.lrc
│ ├── 05. All Too Well.mp3
│ ├── 05. All Too Well.lrc
│ ├── 06. 22.mp3
│ ├── 06. 22.lrc
│ ├── 07. I Almost Do.mp3
│ ├── 07. I Almost Do.lrc
│ ├── 08. We Are Never Ever Getting Back Together.mp3
│ ├── 08. We Are Never Ever Getting Back Together.lrc
│ ├── 09. Stay Stay Stay.mp3
│ ├── 09. Stay Stay Stay.lrc
│ ├── 10. The Last Time.mp3
│ ├── 10. The Last Time.lrc
│ ├── 11. Holy Ground.mp3
│ ├── 11. Holy Ground.lrc
│ ├── 12. Sad Beautiful Tragic.mp3
│ ├── 12. Sad Beautiful Tragic.lrc
│ ├── 13. Begin Again.mp3
│ ├── 13. Begin Again.lrc
│ ├── 14. Girl At Home.mp3
│ ├── 14. Girl At Home.lrc
│ └── cover.jpg # 封面图片
整理好后,在Jellyfin添加媒体库,选择音乐,在添加上对应的文件夹路径,就可以了。
如果是按照这个方法添加的话,Jellyfin是可以自动检索元数据并且添加的,可以改善音乐库的观感:
歌词的效果:
效果还是可以的!
音乐库去哪找?
我是用了网易云无损解析这类工具,把歌曲一首一首下回来,自己整理,工作量巨大!
好处是,有一些工具可以一并把歌词下来,比较舒服。
这种工具估计网上一搜,都一大把,很多都是网站在线解析的,并不需要下载软件。
什么?你不会找?找不到?是不是搜索引擎是垃圾百度啊?还是360搜索?如果还在用国内这种垃圾搜索引擎,还不会换,那我只能说你是赛博文盲!
当然,你也可以去某宝,花几块钱去购买并下载资源,但是大概率给你个百度网盘链接(改名百度软盘得了),然后下载龟速。2333
我的收集方法
我是用了这个:
api.toubiec.cn/wyapi.html
我是一次下一个专辑,由于下载的每首歌都是一个zip
压缩包,本来下载就费时费力,整理更加费时费力。
于是,我就整了一点小工具做一下辅助:open_zip.sh
放在下载目录,负责遍历解压压缩包,文件内容如下:
#!/bin/bash
# 检查是否有zip文件存在
if [ "$(ls -A *.zip 2> /dev/null)" ]; then
# 遍历所有.zip文件
for zip_file in *.zip; do
# 获取不带扩展名的文件名
base_name="${zip_file%.zip}"
# 创建以文件名为名的新文件夹
mkdir -p "$base_name"
# 解压到新建的文件夹内
unzip -q "$zip_file" -d "$base_name"
# 检查解压是否成功
if [ $? -eq 0 ]; then
echo "Extracted $zip_file into folder $base_name"
# 如果解压成功,删除zip文件
rm "$zip_file"
echo "Deleted $zip_file"
else
echo "Failed to extract $zip_file"
fi
done
else
echo "No zip files found."
fi
out_folder.sh
放在下载目录,负责遍历移动子文件夹里面的内容到工作目录,文件内容如下:
#!/bin/bash
# 使用 find 命令查找所有文件并将它们移动到当前目录。
# -mindepth 2 确保我们只获取子文件夹中的文件,而不是当前目录中的文件。
# -maxdepth 2 确保我们不会深入到更深层次的子文件夹中。
# -type f 确保我们只处理文件,而不处理文件夹。
find . -mindepth 2 -maxdepth 2 -type f -exec mv {} . \;
# 查找并删除空的子文件夹
# -empty 匹配空文件或空文件夹
# -type d 确保我们只处理文件夹
# -delete 删除匹配到的文件夹
find . -mindepth 1 -maxdepth 1 -type d -empty -delete
echo "文件移动完成,并且空文件夹已被删除。"
rename_songs.py
负责重命名和整理歌曲相关文件,依据截取到的html进行排序整理。需要把网易云音乐网页版的专辑曲目部分的的html代码保存到同目录的content.txt
文件中,执行后会自动解析保存的html代码并依据此进行重命名和排序。
文件内容如下:
import os
from bs4 import BeautifulSoup
import re
import unicodedata
def sanitize_title(title):
"""清理标题以进行精确匹配,保留非ASCII字符"""
# 只移除ASCII范围内的非字母数字字符,并转换为小写
return ''.join([c for c in unicodedata.normalize('NFKC', title) if c.isalnum() or not (0 <= ord(c) < 128)]).lower()
def clean_title(title):
"""清理并标准化歌曲标题"""
# 将HTML实体替换为空格
title = re.sub(r' ', ' ', title)
# 移除多余的空白字符,但保留单词间的单个空格
title = re.sub(r'\s+', ' ', title).strip()
# 去除首尾可能出现的特殊字符或符号
title = re.sub(r'^[^\w\s]+|[^\w\s]+$', '', title)
# 统一下划线和空格
title = re.sub(r'_', ' ', title)
return title
def parse_html(html_content):
soup = BeautifulSoup(html_content, 'html.parser')
# 查找所有的表格行,仅限于<tbody>中的行
rows = soup.select('tbody tr')
songs = []
for row in rows:
# 获取编号
num_tag = row.find('span', class_='num')
number = num_tag.get_text(strip=True) if num_tag else 'N/A'
# 尝试获取歌曲标题,考虑了可能存在的多种情况
title_tag = row.select_one('td:nth-of-type(2) b, td:nth-of-type(2) a')
if title_tag:
# 清理标题文本,移除干扰元素
for unwanted in title_tag.find_all('div', class_='soil'):
unwanted.extract() # 移除不需要的<div>标签
title = clean_title(title_tag.get('title') or title_tag.get_text(strip=True))
sanitized_title = sanitize_title(title)
else:
title = 'Title Not Found'
sanitized_title = sanitize_title(title)
# 只有当number不是'N/A'且title不是'Title Not Found'时加入列表
if number != 'N/A' and title != 'Title Not Found':
songs.append((number, title, sanitized_title))
print("Parsed Songs:")
for song in songs:
print(f"Number: {song[0]}, Title: {song[1]}, Sanitized Title: {song[2]}")
return songs
def standardize_filename(filename):
"""标准化文件名,将下划线替换为空格,并清理多余空格"""
base_name, ext = os.path.splitext(filename)
standardized_base_name = re.sub(r'_', ' ', base_name)
standardized_base_name = re.sub(r'\s+', ' ', standardized_base_name).strip()
return f"{standardized_base_name}{ext}"
def rename_files_in_directory(directory_path, songs):
# 遍历目录中的所有文件
renamed_files = set() # 用于跟踪已经重命名的文件,避免重复重命名
for filename in os.listdir(directory_path):
file_path = os.path.join(directory_path, filename)
# 如果是文件并且文件名(不包括扩展名)与解析出的歌曲名称匹配,则重命名
if os.path.isfile(file_path) and not filename.endswith('.txt') and not filename.endswith('.py'):
standardized_filename = standardize_filename(filename)
base_name, ext = os.path.splitext(standardized_filename)
matched = False
for i, (number, title, sanitized_title) in enumerate(songs, start=1):
sanitized_base_name = sanitize_title(base_name)
# 使用清理后的字符串进行精确匹配
if sanitized_base_name == sanitized_title:
# 格式化编号为两位数
formatted_number = f"{i:02d}"
new_filename = f"{formatted_number}. {base_name}{ext}" # 使用标准化后的文件名作为歌曲名
new_file_path = os.path.join(directory_path, new_filename)
if not os.path.exists(new_file_path): # 检查新文件名是否已存在
if filename not in renamed_files:
print(f'Renaming "{filename}" to "{new_filename}"...')
try:
os.rename(file_path, new_file_path)
renamed_files.add(filename) # 添加到已重命名集合中
matched = True
except Exception as e:
print(f"Error renaming '{filename}': {e}")
else:
print(f"New filename '{new_filename}' already exists.")
break # 文件名匹配成功后跳出循环,避免重复重命名
if not matched:
print(f"No match found for file '{filename}'.")
def handle_jpg_files(directory_path):
"""处理目录中的所有 .jpg 文件,保留一个并重命名为 cover.jpg,删除其余的"""
jpg_files = [f for f in os.listdir(directory_path) if f.endswith('.jpg')]
if len(jpg_files) > 0:
# 选择第一个找到的 .jpg 文件作为封面
cover_file = jpg_files[0]
cover_path = os.path.join(directory_path, cover_file)
new_cover_path = os.path.join(directory_path, 'cover.jpg')
# 如果cover.jpg不存在,则重命名第一个找到的.jpg文件为cover.jpg
if not os.path.exists(new_cover_path):
os.rename(cover_path, new_cover_path)
print(f'Renamed "{cover_file}" to "cover.jpg".')
# 删除其他的 .jpg 文件
for jpg in jpg_files[1:]:
jpg_path = os.path.join(directory_path, jpg)
os.remove(jpg_path)
print(f'Deleted "{jpg}".')
else:
print("No .jpg files found to process.")
if __name__ == "__main__":
# 获取当前脚本所在的目录
directory_path = os.path.dirname(os.path.abspath(__file__))
# 处理 .jpg 文件
handle_jpg_files(directory_path)
# 读取同目录下的 content.txt 文件获取 HTML 内容
content_file_path = os.path.join(directory_path, 'content.txt')
try:
with open(content_file_path, 'r', encoding='utf-8') as file:
html_content = file.read()
except FileNotFoundError:
print("Error: The file 'content.txt' was not found in the current directory.")
exit(1)
except Exception as e:
print(f"An error occurred while reading 'content.txt': {e}")
exit(1)
songs = parse_html(html_content)
rename_files_in_directory(directory_path, songs)
print("All processes completed.")
至于怎么获取专辑曲目部分的的html代码,直接在浏览器右键歌曲列表,检查代码,一层一层往上找,当显示是整个列表的内容的元素,那就找到了。
然后,右键,复制内部html,这就是需要的东西了。把这些html代码粘贴到content.txt
就行了。
注意,上述的四个文件:open_zip.sh
,out_folder.sh
,rename_songs.py
,content.txt
都是放在专辑的下载目录下。
以上准备工作做完之后,在当前目录打开命令行,就可以施展组合拳了:
./open_zip.sh
./out_folder.sh
python rename_songs.py
什么?Python提示ModuleNotFoundError: No module named 'xxx'
?缺模块就去装啊,自己去搜,看了资料还不会装的话,只能说你唐!唐完了!!!
如果一切顺利,目录就会变成上面示例目录结构专辑文件夹里面的样子了。
小福利:Taylor Swift 的专辑
为了更直观的看到是怎么组织文件目录的,我就把我整理的Taylor Swift的几个专辑分享出来吧!
分别是:《Red》《1989》《Fearless》《Lover》《Speak Now》《Taylor Swift》
以下是压缩分卷,解压密码是GTX1080Ti
:
Taylor Swift.part1.rar
Taylor Swift.part5.rar
Taylor Swift.part4.rar
Taylor Swift.part3.rar
Taylor Swift.part2.rar 别告诉我你不会解压缩,不然我只会骂你唐货!