相信不少人都知道B站吧,B站里面有大量的视频资源,像我就从那里面学到了不少东西(也看了不少番)。有时候我们想要在手机app上下载上面的视频,但是下载下来只能以缓存的形式在软件里播放。在大多数时候可能也没什么麻烦,但是有时想要在其他设备上看这些视频,就无法简单的把文件复制过去了。于是有人就研究出了把B站的缓存转换成MP4格式的方法,而这个脚本可以利用这种方法批量转换B站缓存,更为方便快捷,更符合实际的需要。
一、B站缓存机制
要想操作B站缓存,首先要知道它的缓存方式。手机bilibil的视频缓存目录为
手机存储/Android/data/tv.danmaku.bili/download
这个文件夹里有一个或多个子文件夹(名字都是数字),每一个就是一组视频,每一个子文件夹中可能有一个或多个文件夹(名字都是c_+数字),这取决于该视频是否有多集。这一级的文件夹每个代表一个视频,里面包含缓存文件与信息文件,也有一些文件夹是缓存过但是被删掉的视频,这样的文件夹就不含缓存文件,但是还会留下信息文件。
信息文件主要有两个:用于存储弹幕信息的danmaku.xml(danmaku即为日语的弹幕),还有保存视频信息的entry.json
视频缓存文件储存在一个两位数名字(16,32,64等等)的文件夹中,视频被分为视频文件video.m4s和音频文件audio.m4s两部分,该文件夹里还有一个index.json,应该是用来验证缓存文件是否有错或被修改的,而存储视频的文件夹名字(那个两位数)在entry.json中一个名为type_tag的项中保存。
直观的讲,文件结构如图(希望这样是等宽字体):
/download /123456789 /c_123456789 /entry.json
___________________________________/danmaku.xml
___________________________________/16 /vedio.m4s
_______________________________________/audio.m4s
_______________________________________/index.json
______________________/c_987654321(同上)
___________/987654321(同上)
二、如何合并缓存文件为MP4格式
合并视频就需要用到ffmpeg了。ffmpeg是个用来处理音视频的命令行软件,功能还是很强大的。
三、批量处理节省工作量
利用python实现批量处理,使操作简单化,代码如下(解释在代码的注释中):
"""使用方法:将手机的视频缓存(/download/里面的内容)复制到一个单独的文件夹,
然后填写缓存目录、输出目录还有ffmpeg.exe所在位置,之后运行这个脚本,就能在输出目录中得到所有的缓存视频啦
"""
import os
import json
import random
import re
# 请填写:
cache_directory = 'D:\\temp1' # 缓存文件所在目录,相当于之前的/download文件夹
# 请填写:
output_directory = 'D:\\temp2' # 输出目录,在此输出多个按原来的组分类的视频文件夹
# 请填写:
ffmpeg_path = 'D:\\Program Files\\ffmpeg-2021-08-04-git-3b298640e1-full_build\\bin' # ffmpeg.exe所在位置
if (os.path.exists(cache_directory) # 先对路径进行检测,如果有错就结束运行
and os.path.exists(output_directory)
and os.path.exists(ffmpeg_path)):
os.chdir(ffmpeg_path) # 在ffmpeg所在目录下运行,以执行ffmpeg命令
for part in os.listdir(cache_directory): # 每一组视频
episodes = os.listdir(cache_directory + '\\' + part) # 一组里面的每个视频
# 先读取一个entry.json,由此获取这组视频的名字还有分辨率
try: # 提高容错率,跳过空文件夹
with open(f'{cache_directory}\\{part}\\{episodes[0]}\\entry.json', mode='rt', encoding='utf8') as fp:
info = json.load(fp)
part_name = info['title']
except IndexError:
continue
# 没名字的解决办法
if not part_name:
part_name = 'NoName' + str(random.random())[2:10]
# 去除非法字符
part_name = ''.join(re.findall(r'[^*"/:?\\|<>]', part_name, re.S)) # 这组视频的名字
# 判定这个文件夹里是否有视频,因为很多被删掉的视频都只剩一个文件
if len(os.listdir(f'{cache_directory}\\{part}\\{episodes[0]}')) <= 1 and len(episodes) <= 1:
print(part_name, '可能已经被删除了')
continue
# 这组视频的输出位置
output_part_directory = f'{output_directory}\\{part_name}'
# 用视频组的名字创建输出文件夹
if not os.path.exists(output_part_directory):
os.mkdir(output_part_directory)
else:
print(output_part_directory, '已存在')
for episode in episodes: # 每集视频
file_directory = f'{cache_directory}\\{part}\\{episode}\\' # 每一集的文件夹
# 读取json,获得该视频的信息
with open(file_directory + 'entry.json', encoding='utf8')as fp:
info = json.load(fp)
title = info['page_data']['part']
type_tag = info['type_tag']
video_name = file_directory + f'{type_tag!s}\\video.m4s' # 视频文件路径
audio_name = file_directory + f'{type_tag!s}\\audio.m4s' # 音频文件路径
if os.path.exists(video_name) and os.path.exists(audio_name): # 判定缓存是否被删除
# 读取这一集的名字
if not title:
title = 'NoName' + str(random.random())[2:11]
title = ''.join(re.findall(r'[^*"/:?\\|<>]', title, re.S))
# 这一集的输出位置
output_name = output_part_directory + '\\' + title + '.mp4'
print('执行命令:',
f'ffmpeg -i {video_name} -i {audio_name} -c:v copy -strict experimental {output_name}')
# 最后一步,执行命令
os.system(
f'ffmpeg -i "{video_name}" -i "{audio_name}" -c:v copy -strict experimental "{output_name}"')
# 如果把os.system换成os.popen,则会开很多进程,速度虽会有所加快,但是会造成电脑超级卡顿(CPU占用100%),
# 而且想要结束只能用任务管理器结束ffmpeg.exe进程,所以不推荐使用
else:
print(file_directory, '里的视频可能已经被删除')
# 如果没有文件,则说明缓存被删除了,这个目录是没用的目录
if len(os.listdir(output_part_directory)) == 0:
os.rmdir(output_part_directory)
else:
print('填写的路径不正确')
附录:
在网上看到还有一种格式为.blv的缓存,对于这种缓存可以直接把后缀改成.flv
但是因为我还没有遇到就不好多说了
(于2021.9.6修改,之前没考虑到分辨率不同,文件夹名字不同的问题)