B站缓存视频导出

cuixiaogang

前言

如何白嫖B站的大会员视频?某多多上买一个1天的大会员,然后把想要看的番下载下来,这样就能低价看番了

目前B站的视频下载主要有两种解决方案:

  1. 使用外置工具来下载,比如JiJiDown这个下载工具,不过这个下载工具还是有些问题的,还需要大佬们继续打磨。另外,使用外置工具下载都会有封号、封IP的风险,请自行选择!!!
  2. 使用B站PC端的视频缓存工具来下载,不过这个缓存的视频都是经过特殊处理的,只能在B站客户端上观看?如何将这个缓存视频转换为正常格式的视频文件,就是这边博客需要做的事情。

另外,还有一个工具也可以试试:哔哩下载姬,不过这个工具我没有用过

JiJiDown的处理方案

非常感谢JiJiDown的各位大佬,都是在用爱发电,非常敬佩、感激

JiJiDown 1.0的安装及使用

安装文档:https://client.sabe.cc/installation/windows/
配置文档:https://client.sabe.cc/quick_start/configuration/

这是JiJiDown的最初版本,个人感觉还是挺好用的,不过这个程序存在一些问题目前依然无法解决,比如:https://client.sabe.cc/support/faq/#issue_4

另外,我这边在使用这个工具的时候,有些大视频文件下载不下来,没有深入研究详细原因。所以后来就放弃这个工具了

JiJiDown 2.0的安装及使用

2.0版本我曾尝试在NAS上安装,使用docker构建一个linux的环境,然后在其中部署JiJiDown 2.0。但存在如下几个问题:

  1. 因为JiJiDown的linux客户端监听的是127.0.0.1:4100,而非0.0.0.0:4100,导致无法在宿主机上监听JiJiDown的端口,所以需要socat工具来做端口监听的转换
  2. 需要再NAS上安装一个浏览器,然后使用WEBUI来操作,这个我试了很多次,都是失败的。导致后来放弃了这个方式
  • Dockerfile文件
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
FROM ubuntu:22.04

RUN apt update \
&& echo -e "Asia\nShanghai" | apt-get install -y tzdata socat \
&& apt-get install -y wget ffmpeg \
&& mkdir -p /data/app \
&& mkdir -p /data/tmp \
&& wget -O /data/app/JiJiDownCore "https://jj.xn--5nx14y.top/PC/ReWPF/core/JiJiDownCore-linux-amd64" \
&& chmod +x /data/app/JiJiDownCore \
&& mkdir -p /root/.config/JiJiDown

COPY ./config.yaml /root/.config/JiJiDown/config.yaml
COPY ./start.sh /data/start.sh

RUN chmod +x /data/start.sh

VOLUME /data/data

EXPOSE 4000
EXPOSE 4100
EXPOSE 14001

CMD ["/data/start.sh"]
  • config.yaml配置文件
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
log-level: warning
external-controller-port:
grpc: 14000
grpc-web: 14100
restful-api: 64001
user-info:
access-token:
refresh-token:
cookies:
raw-access-token:
raw-cookies:
hide-nickname: false
download-task:
temp-dir: "/data/tmp"
download-dir: "/data/data"
ffmpeg-path: "/usr/bin/ffmpeg"
max-task: 2
download-speed-limit: 0
disable-mcdn: false
jdm:
max-retry: 10
retry-wait: 30
session-workers: 1
part-workers: 5
min-split-size: 30
proxy-addr:
check-best-mirror: true
cache-in-ram: false
cache-in-ram-limit: 500
insecure-skip-verify: false
custom-root-certificates:
  • start.sh文件
1
2
3
4
5
6
7
#!/bin/bash

/usr/bin/socat TCP-LISTEN:4100,bind=0.0.0.0,reuseaddr,fork TCP:127.0.0.1:14100 &
/usr/bin/socat TCP-LISTEN:4000,bind=0.0.0.0,reuseaddr,fork TCP:127.0.0.1:14000 &
/usr/bin/socat TCP-LISTEN:14001,bind=0.0.0.0,reuseaddr,fork TCP:127.0.0.1:64001 &

/data/app/JiJiDownCore --debug
  • NAS上执行命令
1
2
docker build -t bili-down:2.0.1 -f ./Dockerfile .
docker run -d -v /service/jijidown:/data/data -p 8100:4100 -p 8000:4000 -p 8001:14001 bili-down:2.0.1

使用B站客户端

上面说了,B站客户端缓存的视频文件是特殊处理后的,如果我能将处理后的视频文件转换为正常的视频文件,也就能解决这个问题了,并且避免了封号、封IP的风险。

基础知识

需要先知道B站的缓存视频有哪些特殊的处理

缓存视频文件目录
缓存视频文件目录

上图是一个视频的所有缓存文件,其中需要注意的文件如下:

  • *-30080.m4s,这个文件是特殊处理后的视频文件
  • *-30280.m4s,这个文件是特殊处理后的音频文件
  • videoInfo.json,这个文件是视频的相关信息,包括视频标题、第几P等

其中的视频和音频文件,都被B站做了特殊处理,使用010Editor查看文件的数据,发现前置了9个0,同样的,视频文件也存在这些特殊字符。只要将其去掉,就可以了

16进制的音频文件数据
16进制的音频文件数据

基于以上的情报汇总,使用脚本来实现转换的功能,另外,还需要2个工具:

  • ffmpeg:开源的音视频处理工具,这里用来对独立的音频、视频文件进行合流
  • jq:一款轻量级的命令行工具,专门用于解析、过滤、转换和处理JSON数据,这里用来解析videoInfo.json中的数据

WSL环境的解决方案

因为WSL的ffmpeg和jq工具好安装,所以先使用WSL来处理

安装工具

1
2
3
apt update
apt install ffmpeg -y
apt install jq -y

脚本

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
96
97
98
99
100
101
102
103
104
105
106
107
108
109
110
111
112
113
114
115
116
117
118
119
120
121
122
123
124
125
126
127
128
#!/bin/bash

# WSL中的ffmpeg命令的安装地址
# ubuntu使用命令安装:apt install ffmpeg -y
FFMPEG_PATH=/usr/bin/ffmpeg
# WSL中的jq命令安装地址
# ubuntu使用命令安装:apt install jq -y
JQ_PATH=/usr/bin/jq
# B站视频的window缓存地址
# 一般来说C盘地址就是/mnt/c;D盘地址就是/mnt/d;后面将win地址中的\转为/既可
ROOT=/mnt/c/Users/Administrator/Videos/bilibili

# 检查依赖工具是否存在
check_dependency() {
if [ ! -x "$1" ]; then
echo "错误:未找到工具 $1,请检查路径是否正确"
exit 1
fi
}

# 检查根目录是否存在
check_root_dir() {
if [ ! -d "$ROOT" ]; then
echo "错误:媒体根目录 $ROOT 不存在,请检查路径"
exit 1
fi
}

# 前置检查
check_dependency "$FFMPEG_PATH"
check_dependency "$JQ_PATH"
check_root_dir

# 进入媒体根目录
cd "$ROOT" || {
echo "错误:无法进入根目录 $ROOT"
exit 1
}

# 遍历根目录下的所有子目录
for subRoot in */; do
# 去除目录名末尾的斜杠
subRoot="${subRoot%/}"

# 跳过非目录文件(仅处理子目录)
if [ ! -d "$subRoot" ]; then
continue
fi

echo "===== 开始处理子目录:$subRoot ====="

# 处理videoInfo.json
video_info="$subRoot/videoInfo.json"
if [ ! -f "$video_info" ]; then
echo "警告:$subRoot 中未找到 videoInfo.json,跳过该目录"
continue
fi

# 提取p和tabName(处理JSON解析错误)
p=$("$JQ_PATH" -r '.p' "$video_info" 2>/dev/null)
tabName=$("$JQ_PATH" -r '.tabName' "$video_info" 2>/dev/null)

# 检查提取结果是否有效
if [ "$p" = "null" ] || [ -z "$p" ] || [ "$tabName" = "null" ] || [ -z "$tabName" ]; then
echo "警告:$video_info 中未正确提取到 p 或 tabName,跳过该目录"
continue
fi

echo "提取到参数:p=$p, tabName=$tabName"

# 查找并处理m4s文件(去除前9个字节,用tail高效处理)
# 查找视频文件(*-30080.m4s)
video_m4s=$(find "$subRoot" -maxdepth 1 -type f -name "*-30080.m4s" | head -n 1)
# 查找音频文件(*-30280.m4s)
audio_m4s=$(find "$subRoot" -maxdepth 1 -type f -name "*-30280.m4s" | head -n 1)

# 检查文件是否存在
if [ -z "$video_m4s" ] || [ -z "$audio_m4s" ]; then
echo "警告:$subRoot 中未找到完整的m4s文件,跳过该目录"
continue
fi

# 定义处理后的文件名
video_sp="${video_m4s}.sp"
audio_yp="${audio_m4s}.yp"

# 去除前9个字节(用tail -c +10:从第10字节开始输出,直接跳过前9个)
echo "处理视频文件:$video_m4s"
tail -c +10 "$video_m4s" > "$video_sp" || {
echo "警告:视频文件处理失败,跳过该目录"
continue
}

echo "处理音频文件:$audio_m4s"
tail -c +10 "$audio_m4s" > "$audio_yp" || {
echo "警告:音频文件处理失败,清理临时文件后跳过"
rm -f "$video_sp" # 清理已生成的视频临时文件
continue
}

# 用ffmpeg混流
output_file="${p}.${tabName}.mp4" # 输出文件名(添加.mp4扩展名,可根据需要修改)
output_path="$ROOT/$output_file"

echo "开始混流,输出文件:$output_path"
"$FFMPEG_PATH" -i "$video_sp" -i "$audio_yp" -c:v copy -c:a ac3 -y "$output_path" 2>/dev/null

# 检查混流结果
if [ $? -eq 0 ]; then
echo "混流成功:$output_file"

# 混流成功后删除subRoot目录
if [ -d "$subRoot" ]; then # 再次确认目录存在
echo "删除子目录:$subRoot"
rm -rf "$subRoot" # 强制删除目录及所有内容
if [ $? -ne 0 ]; then
echo "警告:删除目录 $subRoot 失败(可能存在权限问题)"
fi
fi
else
echo "警告:混流失败,输出文件可能不完整"
fi

echo "===== 子目录 $subRoot 处理结束 ====="
echo
done

echo "所有子目录处理完成"

处理结果

处理结果
处理结果

混流后的视频文件
混流后的视频文件

Window环境的解决方案

如果不喜欢使用WSL工具,想在WIN上直接转换,可以尝试下面的脚本(这个我没有做测试,不过原理与上述的原理相同)

安装工具

Windows 版 jq:https://github.com/jqlang/jq/releases
Windows 版 ffmpeg:https://github.com/BtbN/FFmpeg-Builds/releases

脚本

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
96
97
98
99
100
101
102
103
104
105
106
107
108
109
110
111
112
113
114
115
116
117
118
119
120
121
122
123
124
125
126
127
128
129
130
131
132
133
134
135
136
137
138
139
140
141
142
143
144
145
146
147
148
149
150
151
152
153
154
155
156
157
158
159
160
161
@echo off
chcp 65001 > nul 2>&1
:: 启用UTF-8编码,解决中文乱码;开启延迟变量扩展,支持循环内变量赋值
setlocal enabledelayedexpansion

:::::::::::::::::::::::::::::::::::::::::::::::::::::::::::
:: 请务必修改以下3个路径为你的实际路径(关键!)
:: 示例:
:: set "FFMPEG_PATH=C:\tools\ffmpeg\bin\ffmpeg.exe"
:: set "JQ_PATH=C:\tools\jq\jq.exe"
:: set "ROOT=C:\Users\Administrator\Videos\bilibili"
:::::::::::::::::::::::::::::::::::::::::::::::::::::::::::
set "FFMPEG_PATH=C:\tools\ffmpeg\bin\ffmpeg.exe"
set "JQ_PATH=C:\tools\jq\jq.exe"
set "ROOT=C:\Users\Administrator\Videos\bilibili"


:: -------------------------- 1. 检查依赖工具和根目录 --------------------------
:: 检查ffmpeg是否存在
if not exist "%FFMPEG_PATH%" (
echo 错误:未找到ffmpeg,路径 "%FFMPEG_PATH%" 无效
echo 请下载Windows版ffmpeg并修改脚本中的FFMPEG_PATH
pause > nul
exit /b 1
)

:: 检查jq是否存在
if not exist "%JQ_PATH%" (
echo 错误:未找到jq,路径 "%JQ_PATH%" 无效
echo 请下载Windows版jq并修改脚本中的JQ_PATH
pause > nul
exit /b 1
)

:: 检查缓存根目录是否存在
if not exist "%ROOT%" (
echo 错误:缓存根目录 "%ROOT%" 不存在,请修改ROOT变量
pause > nul
exit /b 1
)

echo 工具和目录检查通过,开始处理...
echo ==================================================
echo.


:: -------------------------- 2. 遍历所有子目录 --------------------------
:: for /d:仅遍历根目录下的子目录(对应Shell的 for subRoot in */)
for /d %%d in ("%ROOT%\*") do (
:: %%d 是子目录完整路径(如 C:\xxx\bilibili\12345)
:: 提取子目录名(仅最后一级,对应Shell的 ${subRoot%/}
for %%n in ("%%d") do set "subRootName=%%~nn"

echo ===== 开始处理子目录:!subRootName! =====
echo 子目录路径:%%d

:: -------------------------- 3. 读取并解析videoInfo.json --------------------------
set "videoInfoPath=%%d\videoInfo.json"
:: 检查JSON文件是否存在
if not exist "!videoInfoPath!" (
echo 警告:!subRootName! 中未找到 videoInfo.json,跳过
echo.
continue
)

:: 用jq提取p和tabName(for /f捕获jq输出,赋值给BAT变量)
:: 2^>nul:重定向jq错误输出(^转义>,避免BAT解析错误)
for /f "delims=" %%p in ('"%JQ_PATH%" -r ".p" "!videoInfoPath!" 2^>nul') do set "p=%%p"
for /f "delims=" %%t in ('"%JQ_PATH%" -r ".tabName" "!videoInfoPath!" 2^>nul') do set "tabName=%%t"

:: 检查提取结果是否有效(排除null或空值)
if "!p!"=="null" if "!p!"=="" if "!tabName!"=="null" if "!tabName!"=="" (
echo 警告:!videoInfoPath! 未正确提取p/tabName,跳过
echo.
continue
)
echo 提取参数:p=!p!,tabName=!tabName!


:: -------------------------- 4. 查找并处理m4s文件(去前9字节) --------------------------
:: 查找视频文件(*-30080.m4s)
set "videoM4s="
for %%f in ("%%d\*-30080.m4s") do (
set "videoM4s=%%f"
goto findVideoEnd :: 找到第一个就退出循环
)
:findVideoEnd
:: 查找音频文件(*-30280.m4s)
set "audioM4s="
for %%f in ("%%d\*-30280.m4s") do (
set "audioM4s=%%f"
goto findAudioEnd
)
:findAudioEnd

:: 检查m4s文件是否完整
if not exist "!videoM4s!" if not exist "!audioM4s!" (
echo 警告:!subRootName! 未找到完整m4s文件,跳过
echo.
continue
)
echo 找到视频文件:!videoM4s!
echo 找到音频文件:!audioM4s!

:: 定义处理后文件名(临时文件,混流后删除)
set "videoSP=%%d\video_temp.sp"
set "audioYP=%%d\audio_temp.yp"

:: 用PowerShell截取文件(跳过前9字节):Windows无tail,PowerShell原生支持字节操作
:: -AsByteStream:按字节读取,避免编码干扰;[9..$content.Length]:从第10字节开始(索引9)
echo 处理视频文件(去前9字节)...
powershell -Command "$content = Get-Content -Path '!videoM4s!' -Raw -AsByteStream; if ($content) { $content[9..$content.Length] | Set-Content -Path '!videoSP!' -AsByteStream }"
if not exist "!videoSP!" (
echo 警告:视频文件处理失败,跳过
echo.
continue
)

echo 处理音频文件(去前9字节)...
powershell -Command "$content = Get-Content -Path '!audioM4s!' -Raw -AsByteStream; if ($content) { $content[9..$content.Length] | Set-Content -Path '!audioYP!' -AsByteStream }"
if not exist "!audioYP!" (
echo 警告:音频文件处理失败,清理临时文件
del /f /q "!videoSP!" > nul 2>&1
echo.
continue
)


:: -------------------------- 5. ffmpeg混流 --------------------------
:: 输出文件名:{p}.{tabName}.mp4(保存到根目录)
set "outputFile=!p!.!tabName!.mp4"
set "outputPath=%ROOT%\!outputFile!"

echo 开始混流:!outputPath!
:: 调用Windows版ffmpeg,参数和原逻辑一致(-c:v copy复制视频,-c:a ac3转音频)
"%FFMPEG_PATH%" -i "!videoSP!" -i "!audioYP!" -c:v copy -c:a ac3 -y "!outputPath!" 2>nul

:: 检查混流结果(%errorlevel%:0=成功,非0=失败)
if %errorlevel% equ 0 (
echo 混流成功:!outputFile!
:: 混流成功:删除子目录(/s递归删除,/q静默)
echo 删除子目录:!subRootName!
rd /s /q "%%d" > nul 2>&1
if %errorlevel% neq 0 (
echo 警告:子目录删除失败(可能文件被占用)
)
) else (
echo 警告:混流失败,保留子目录
)

:: 清理临时文件(.sp和.yp)
del /f /q "!videoSP!" "!audioYP!" > nul 2>&1

echo ===== 子目录 !subRootName! 处理结束 =====
echo.
)


echo 所有子目录处理完成!
pause > nul
endlocal