利用Python操控ffmpeg批量转换哔哩哔哩m4s缓存

相信不少人都知道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修改,之前没考虑到分辨率不同,文件夹名字不同的问题)

你可能感兴趣的:(利用Python操控ffmpeg批量转换哔哩哔哩m4s缓存)