哔哩哔哩缓存视频转换脚本

发布于 2022-07-23  798 次阅读


warning 警告
本内容仅供学习交流,所有资料请于2小时内删除,严禁侵犯相关视频版权。

哔哩哔哩的windows客户端可以方便地缓存视频至本地。然而,其缓存的文件为.m4s格式,无法直接播放,也无法直接通过ffmpeg进行转码。

经查询,发现其原因是视频二进制文件开头嵌入了9个字节的0。如图所示。

因此,只需删去这些0,就可以播放,或者通过ffmpeg把分离的音频文件和视频文件不经转码直接复制封装进一个mp4容器,实现缓存视频转换。

这个方法不同于网页脚本、第三方脚本直接爬取哔哩哔哩网站,而是通过官方方法缓存至本地后再破解转码,既快速又安全。

本脚本需要依赖于python环境和ffmpeg,自动将所有缓存下来的哔哩哔哩视频批量转换为mp4格式。需要填写ffmpeg路径、bilibili缓存根路径(可在bilibili设置中查看)。

#Code by ZZX https://blog.xzzzx.xyz
import json,subprocess,os
# specify where you want to store the videos here
storeDir = 'D:/videos'
# specity your bilibili download dir here
bliDownDir = 'C:/Users/Administrator/Videos/bilibili'
# specify your ffmpeg.exe path here
ffmpegPath = 'C:/ffmpeg/bin/ffmpeg.exe'
tmpDir = storeDir + '/tmp'


class videos():
    def __init__(self, bliVideoDir) -> None:
        self.bliVideoDir = bliVideoDir
        self.videoInfoPath = bliVideoDir + '/.videoInfo'
        self.videoName, self.videoId = getInfo(self.videoInfoPath)
        self.rawVideoPath = f'{bliVideoDir}/{self.videoId}_nb2-1-30064.m4s'
        self.rawAudioPath = f'{bliVideoDir}/{self.videoId}_nb2-1-30280.m4s'
        self.tmpVideoPath = f'{tmpDir}/{self.videoId}.mp4'
        self.tmpAudioPath = f'{tmpDir}/{self.videoId}.mp3'
        self.finalPath = f'{storeDir}/{self.videoName}.mp4'
        self.checkRawDir()

    def handle(self):
        RmZero(self.rawVideoPath, self.tmpVideoPath)
        RmZero(self.rawAudioPath, self.tmpAudioPath)
        print(self.finalPath)
        mix(self.tmpVideoPath, self.tmpAudioPath, self.finalPath)

    def checkRawDir(self):  # some videos are awfully named
        if not (
            os.path.exists(self.rawVideoPath) and
            os.path.exists(self.rawAudioPath)
        ):
            b = os.listdir(bliVideoDir)
            m4ss = filter(lambda x: x.endswith('.m4s'), b)
            dirs = list(map(lambda x: os.path.join(bliVideoDir, x), m4ss))
            self.rawVideoPath = dirs[0]
            self.rawAudioPath = dirs[1]


def getInfo(videoInfoPath):
    with open(videoInfoPath, 'r', encoding='utf8') as f:
        tmp = f.read()
    tmp = json.loads(tmp)
    videoName = tmp['title']
    videoId = tmp['cid']
    return videoName, videoId


def RmZero(inBinPath: str, outBinPath: str):
    with open(inBinPath, 'rb') as inFile:
        inFile.seek(9)  # skip the first nine b'0'
        with open(outBinPath, 'wb') as outFile:
            for part in iter(lambda: inFile.read(32768), b''):
                outFile.write(part)


def mix(videoPath, audioPath, outPath):
    command = f'\"{ffmpegPath}\" -i \"{videoPath}\" -i \"{audioPath}\" -codec copy \"{outPath}\"'
    subprocess.run(command, shell=True)


if __name__ == '__main__':
    try:
        os.mkdir(tmpDir)
    except FileExistsError:
        print('tmp folder already exists, continuing...')
    except Exception as e:
        print(e)
        print('please confirm storeDir exists')
    dirs = os.listdir(bliDownDir)
    rawdirs = map(lambda x: os.path.join(bliDownDir, x), dirs)
    bliVideoDirs = filter(os.path.isdir, rawdirs)
    for bliVideoDir in bliVideoDirs:
        video = videos(bliVideoDir)
        video.handle()
    print('###################')
    print('Finished')
    print(f'Don\'t forget to delete {tmpDir} !!!!!')

参考资料:

https://www.bilibili.com/read/cv14802750

https://stackoverflow.com/a/18466723