查看原文
其他

用 Python 处理 B 站下载视频

Airwolf Python中文社区 2022-12-04

bilibili(哔哩哔哩,又称B站)是2009年6月推出的一个AGC相关的弹幕视频分享网站,是年轻人潮流文化的娱乐社区,可能对于听过但是不经常上b站的童鞋来说,对于b站最大的影响的就是二次元、动漫、弹幕等等。但是作为国内知名的弹幕视频网站,b站已经不仅仅局限于动漫,还有着丰富的学习资源。

B站图标

作者本人常在b站搜索一些关于人工智能、机器学习类的视频资源,常常都是使用手机下载后离线观看,为了电脑观看方便也会使用“视频合并助手”一类的APP对视频进行转换处理后导入电脑观看,适逢春节假期再次下载视频想导入到电脑上观看,发现以前的视频转换APP已经失效,无法搜索到下载到手机里的b站视频资源,随后开始了下文描述的视频合成工作。

基本思路

  • 目的:合成哔哩哔哩APP缓存到手机的文件,并转换为MP4格式

  • 基本思路:

    1.分析下载文件目录结构和缓存文件  
    2.使用库来合成文件
  • 开发环境:

    1.手机:华为Mate20x EMUI10   哔哩哔哩APP 版本:5.53.1
    2.开发环境:MacBook Pro 2015,PYTHON3.7.6 64-bitVisual Studio Code  1.41.1

0x00 哔哩哔哩APP缓存文件目录结构及文件分析

打开手机文件管理器,找到Android/data/tv.danmaku.bili/down文件夹,结构如下图所示:

app目录

其中以8位数字命名的文件夹用于单个视频专辑的存储,其一级子目录是从数字1开始递增命名,每个目录内存储的是缓存的分节文件(可以理解为每一集)。其二级子目录均是以数字16命名,这是很规律的。

在每一个视频专辑下有以下几个文件:

1.在一级子目录下有danmaku.xml和entry.json,其中danmaku.xml为弹幕文件

<?xml version="1.0" encoding="UTF-8"?>
<i>
    <chatserver>chat.bilibili.com</chatserver>
    <chatid>132379211</chatid>
    <mission>0</mission>
    <maxlimit>3000</maxlimit>
    <state>0</state>
    <real_name>0</real_name>
    <source>k-v</source>
    <d p="22.23400,1,25,16777215,1575199941,0,aaaeeaeb,25196110486700034">地气儿</d>
    <d p="1318.88600,1,25,16777215,1578391805,0,48b91c28,26869566679810052">指定了版本的那个装不上,只能装最新的</d>
    <d p="582.62400,1,25,16777215,1578964914,0,15eedcf5,27170040630476802">nice</d>
    <d p="26.29000,1,25,16777215,1579009720,0,c1d89d8e,27193531775320068">哈哈哈确实</d>
</i>

entry.json文件则是关于缓存视频的描述文件:

{
    "media_type"2,
    "has_dash_audio"true,
    "is_completed"true,
    "total_bytes"21176174,
    "downloaded_bytes"21176174,
    "title""(全)基于python的Opencv项目实战",
    "type_tag""16",
    "cover""http:\/\/i2.hdslb.com\/bfs\/archive\/afae181e4bb00d7ca2e97f192e6f11dc2c3d8142.jpg",
    "prefered_video_quality"16,
    "guessed_total_bytes"0,
    "total_time_milli"1152336,
    "danmaku_count"0,
    "time_update_stamp"1580398289030,
    "time_create_stamp"1580348758458,
    "avid"77390697,
    "spid"0,
    "seasion_id"0,
    "bvid""",
    "page_data": {
        "cid"132379572,
        "page"6,
        "from""vupload",
        "part""06、边缘检测",
        "link""",
        "rich_vid""",
        "vid""",
        "has_alias"false,
        "weblink""",
        "offsite""",
        "tid"39,
        "width"960,
        "height"540,
        "rotate"0,
        "download_title""视频已缓存完成",
        "download_subtitle""(全)基于python的Opencv项目实战 06、边缘检测"
    }
}

我们需要从这个json中提取“download_subtitle”字段作为文件的命名。
2.在以“16”命名的二级子目录下有3个文件,从文件的名称可以判断audio.m4s和vedio.m4s两个文件应该是缓存的音频和视频文件,我们可以尝试使用播放器播放这两个文件,发现能够成功的播放但是视频中没有声音,可以断定B站将一个视频的音频和视频分开存储了。

视频

音频
还剩下一个index.json文件
{
    "video": [{
        "id"16,
        "base_url""https:\/\/upos-sz-mirrorhw.bilivideo.com\/upgcxcode\/11\/92\/132379211\/132379211-1-30015.m4s?e=ig8euxZM2rNcNbdlhoNvNC8BqJIzNbfqXBvEuENvNC8aNEVEtEvE9IMvXBvE2ENvNCImNEVEIj0Y2J_aug859r1qXg8xNEVE5XREto8GuFGv2U7SuxI72X6fTr859IB_&uipk=5&nbs=1&deadline=1580403879&gen=playurl&os=hwbv&oi=611071466&trid=69dea77b6c6a4049a6f88615e82ada05u&platform=android&upsig=1e3e3eedc71aba8b50ce51e67f3ca508&uparams=e,uipk,nbs,deadline,gen,os,oi,trid,platform&mid=280178137",
        "backup_url": ["https:\/\/upos-sz-mirrorks3.bilivideo.com\/upgcxcode\/11\/92\/132379211\/132379211-1-30015.m4s?e=ig8euxZM2rNcNbdlhoNvNC8BqJIzNbfqXBvEuENvNC8aNEVEtEvE9IMvXBvE2ENvNCImNEVEIj0Y2J_aug859r1qXg8xNEVE5XREto8GuFGv2U7SuxI72X6fTr859IB_&uipk=5&nbs=1&deadline=1580403879&gen=playurl&os=ks3bv&oi=611071466&trid=69dea77b6c6a4049a6f88615e82ada05u&platform=android&upsig=6de0ffa1809d46318ca36387ca8d8634&uparams=e,uipk,nbs,deadline,gen,os,oi,trid,platform&mid=280178137"],
        "bandwidth"104293,
        "codecid"7,
        "size"19069578,
        "md5""eab8c79d8ab56a973626a20e1dee6c25"
    }],
    "audio": [{
        "id"30216,
        "base_url""https:\/\/upos-sz-mirrorkodo.bilivideo.com\/upgcxcode\/11\/92\/132379211\/132379211-1-30216.m4s?e=ig8euxZM2rNcNbdlhoNvNC8BqJIzNbfqXBvEuENvNC8aNEVEtEvE9IMvXBvE2ENvNCImNEVEIj0Y2J_aug859r1qXg8xNEVE5XREto8GuFGv2U7SuxI72X6fTr859IB_&uipk=5&nbs=1&deadline=1580403879&gen=playurl&os=kodobv&oi=611071466&trid=69dea77b6c6a4049a6f88615e82ada05u&platform=android&upsig=59e394e791a6ccb7c32b7d2eb1f0957d&uparams=e,uipk,nbs,deadline,gen,os,oi,trid,platform&mid=280178137",
        "backup_url": ["https:\/\/upos-sz-mirrorks3.bilivideo.com\/upgcxcode\/11\/92\/132379211\/132379211-1-30216.m4s?e=ig8euxZM2rNcNbdlhoNvNC8BqJIzNbfqXBvEuENvNC8aNEVEtEvE9IMvXBvE2ENvNCImNEVEIj0Y2J_aug859r1qXg8xNEVE5XREto8GuFGv2U7SuxI72X6fTr859IB_&uipk=5&nbs=1&deadline=1580403879&gen=playurl&os=ks3bv&oi=611071466&trid=69dea77b6c6a4049a6f88615e82ada05u&platform=android&upsig=856c6fed7b7f7c1a4967a8e40cf8fc59&uparams=e,uipk,nbs,deadline,gen,os,oi,trid,platform&mid=280178137"],
        "bandwidth"67113,
        "codecid"0,
        "size"12272062,
        "md5""5d7a6a8e6f4c2809ac61eeafa1d9eaae"
    }]
}

这个json文件包含了音频文件和视频文件的相关信息。

0x01 合成音视频文件

通过上述分析,我们找到了单个专辑的缓存文件,下面需要做的就是将音轨合并到视频中去,为此我们需要使用Moviepy这个库。MoviePy是一个用于视频编辑的python模块,你可以用它实现一些基本的操作(比如视频剪辑,视频拼接,插入标题),还可以实现视频合成,还有视频处理,抑或用它加入一些自定义的高级的特效。此外,MoviePy可以读写绝大多数常见的视频格式,甚至包括GIF格式!详细的使用说明可以参考MoviePy - 中文文档和官方文档。
首先需要安装Moviepy库,使用pip直接安装即可,所需要的依赖库如numpy等会在安装时自动下载并配置:

pip install moviepy

安装完毕后即可使用,我们先用单个文件尝试一下,代码如下:

from moviepy.editor import VideoFileClip,AudioFileClip #从moviepy中导入editor包

audioFile  = r"/Users/airwolf/Desktop/81427329/1/16/audio.m4s"   #指定需要读取的音频文件
videoFile  = r"/Users/airwolf/Desktop/81427329/1/16/video.m4s"   #指定需要读取的视频文件
outputfile = r"/Users/airwolf/Desktop/81427329/output.mp4"       #指定输出文件
video_in = VideoFileClip(videoFile) #读取视频文件
audio_in =AudioFileClip(audioFile) #读取音频文件
video_out = video_in.set_audio(audio)   #video_out文件的输出是将音频文件合并到video_in文件的音轨中
video.write_videofile(outputfile) #输出video_out文件


处理


此时我们用播放器打开输入文件output.mp4,发现音频文件已经合成到视频文件中了。

合成信息


下一步我们就着手批量的文件合成,首先将音视频合成的方法封装成一个函数:


def set_audio(proc_file, output_path):
    (file_name, audio_file, vedio_file) = proc_file
    file_name = file_name.replace('.''-').replace('“'"").replace('”'"")#对文件名称中含有的影响命名的特殊字符进行处理
    original_vedio = VideoFileClip(vedio_file)
    audio = AudioFileClip(audio_file)
    video = original_vedio.set_audio(audio)
    outputfile = os.path.join(output_path, file_name)+".mp4"#形成输出文件名
    video.write_videofile(outputfile)

函数的输入有2个参数,参数proc_file表示要处理的文件信息,按照[文件名称,音频文件名,视频文件名]的列表形式输入,output_path为合成后的MP4文件输出路径。接着需要遍历视频专辑下所有的子目录,把待处理的视频放入一个proc_fileList列表中:

import os
import json
proc_fileLis=[]
def get_proList(init_path):
    folder = os.listdir(init_path)
    for subfolder in folder:
        name_path = os.path.join(init_path, subfolder)
        json_file = os.path.join(name_path, "entry.json")
        if os.path.exists(json_file):
            file_info = []  #用于封装带处理的文件信息,格式为:[文件名,音频文件名,视频文件名]
            with open(json_file, 'r'as f: #从json文件中提取文件名
                data = json.load(f)
                file_name = data["page_data"]["part"]
                file_info.append(file_name)
            a_filename = os.path.join(name_path, "16/audio.m4s")
            v_filename = os.path.join(name_path, "16/video.m4s")
            file_info.append(a_filename)
            file_info.append(v_filename)
            proc_fileList.append(file_info)#将带处理文件加入到proc_fileList中

函数的输入参数init_path为待处理第一级目录,即上文所指的以8个数字命名的文件夹。
接着编写主函数:

import sys
if __name__ == "__main__":
    init_path = sys.argv[1]
    get_proList(init_path)
    for proc_file in proc_fileList:
        print(proc_file)
        set_audio(proc_file,init_path)

最后保存为proc.py。
使用时打开终端,输入如下命令,即可完成视频的转换:

python proc.py 处理文件路径

作者简介:

Airwolf,非IT行业码农,国家嵌入式系统设计师。自小学6年级自学BASIC开启编程生涯,酷爱编程,喜欢用实用简洁的程序解决工作生活中的问题。


打赏码

Python中文社区作为一个去中心化的全球技术社区,以成为全球20万Python中文开发者的精神部落为愿景,目前覆盖各大主流媒体和协作平台,与阿里、腾讯、百度、微软、亚马逊、开源中国、CSDN等业界知名公司和技术社区建立了广泛的联系,拥有来自十多个国家和地区数万名登记会员,会员来自以工信部、清华大学、北京大学、北京邮电大学、中国人民银行、中科院、中金、华为、BAT、谷歌、微软等为代表的政府机关、科研单位、金融机构以及海内外知名公司,全平台近20万开发者关注。

▼ 点击成为社区注册会员      喜欢文章,点个在看

您可能也对以下帖子感兴趣

文章有问题?点此查看未经处理的缓存