Python爬取m3u8格式视频并解密ts文件合并转为mp4格式
一. m3u8是什么格式
m3u8是苹果公司推出的视频播放标准,是m3u的一种,只是编码格式采用的是UTF-8。
m3u8准确来说是一种索引文件,使用m3u8文件实际上是通过它来解析对应的放在服务器上的视频网络地址,从而实现在线播放。使用m3u8格式文件主要因为可以实现多码率视频的适配,视频网站可以根据用户的网络带宽情况,自动为客户端匹配一个合适的码率文件进行播放,从而保证视频的流畅度。
它将视频切割成一小段一小段的ts格式的视频文件,然后存在服务器中(现在为了减少I/o访问次数,一般存在服务器的内存中),通过m3u8解析出来路径,然后去请求。
二. 如何处理经过AES-128加密的ts文件
示例链接:https://cdn.letv-cdn.com/2018/12/05/JOCeEEUuoteFrjCg/playlist.m3u8
输入这个链接并不会跳转到源播放地址,而是下载一个.m3u8格式的文件,文件内容一般长这样:
#EXTM3U
#EXT-X-TARGETDURATION:12
#EXT-X-MEDIA-SEQUENCE:0
#EXT-X-KEY:METHOD=AES-128,URI="https:/*******/**********/hlskey?info=C77821E76077257C9C33DC5901307461&t=1602634262&key=81E7CD7DD2D96CEF9F2B1B18AC2707C9",IV=0xC77821E76077257C9C33DC5901307461
#EXT-X-VERSION:4
#EXTINF:6.756756,
out000.ts
#EXTINF:10.427089,
out001.ts
#EXTINF:3.378378,
out002.ts
#EXTINF:1.126133,
.............
out.....ts
#EXT-X-ENDLIST
将文件中的url中的playlist.m3u8 替换为 out000.ts会下载一个.ts文件,这个ts文件就是视频的一个片段,整个视频由若干个小片段构成,没有经过加密的文件下载后直接可以播放,但经过AES-128加密后的文件下载后会无法播放
我们主要研究的是经过加密的ts文件,并解密它。我们去把 index.m3u8 替换成 key.key,即可获取到加密的**,这个** key 将会是我们后续解码文件的关键,没了它下载得到的文件是没有意义的。
因为需要解码 AES-128 这种加密方式需要用到特殊的模块 AES,所以我们要去安装 Crypto,按照如下的方法导入类来使用。可以通过 pip install Crypto 安装 Crypto库,如果发生了 ImportError,显示没有 Crypro 库,那么就去 site-packages 里面看看文件名是不是 crypto,改成 Crypto 应该就没问题了。
下载后的ts文件我们可以使用win自带的 copy /b '下载的文件目录路径'/*.ts ' '合并后的文件目录路径'/new.mp4 来合并ts文件,之后再使用 del *.ts 命令来删除所有的.ts文件,在python中我们可以使用os.system()命令来处理cmd命令
具体代码如下:包括未加密和加密两种处理方式
import requests
import os
from Crypto.Cipher import AES
def m3u8(url):
base_url = url[:url.rfind('/')+1]#如果需要拼接url,则启用 , +1 把 / 加上
headers = {'User-Agent':'Mozilla/5.0 (Windows NT 10.0; Win64; x64) AppleWebKit/537.36 (KHTML, like Gecko) Chrome/75.0.3770.100 Safari/537.36'}
rs = requests.get(url,headers=headers).text
list_content = rs.split('\n')
player_list = []
#如果没有merge文件夹则新建merge文件夹,用于存放ts文件
if not os.path.exists('merge'):
os.system('mkdir merge')
key = ''
for index,line in enumerate(list_content):
# 判断视频是否经过AES-128加密
if "#EXT-X-KEY" in line:
method_pos = line.find("METHOD")
comma_pos = line.find(",")
method = line[method_pos:comma_pos].split('=')[1]#获取加密方式
print("Decode Method:", method)
uri_pos = line.find("URI")
quotation_mark_pos = line.rfind('"')
key_path = line[uri_pos:quotation_mark_pos].split('"')[1]
key_url = key_path
res = requests.get(key_url)
key = res.content #获取加***
print("key:", key)
#以下拼接方式可能会根据自己的需求进行改动
if '#EXTINF' in line:
# href = ''
# 如果加密,直接提取每一级的.ts文件链接地址
if 'http' in list_content[index + 1]:
href = list_content[index + 1]
player_list.append(href)
# 如果没有加密,构造出url链接
elif('ad0.ts' not in list_content[index + 1]):
href = base_url + list_content[index+1]
player_list.append(href)
if(len(key)):
print('此视频经过加密')
print(player_list)#打印ts地址列表
for i,j in enumerate(player_list):
cryptor = AES.new(key, AES.MODE_CBC, key)
res = requests.get(j,headers=headers)
with open('merge/' + str(i+1) + '.ts','wb') as file:
file.write(cryptor.decrypt(res.content))#将解密后的视频写入文件
print('正在写入第{}个文件'.format(i+1))
else:
print('此视频未加密')
print(player_list)#打印ts地址列表
for i,j in enumerate(player_list):
res = requests.get(j,headers=headers)
with open('merge/' + str(i+1) + '.ts','wb') as file:
file.write(res.content)
print('正在写入第{}个文件'.format(i+1))
print('下载完成')
#当全写下载完之后合并文件并删除所有.ts文件
def merge_ts():
path = os.getcwd() + '\merge'#获取视频存放路径
merge_cmd = 'copy /b ' + path + '\*.ts ' + path + '\\new.mp4'
del_cmd = 'del ' + path + '\*.ts'
os.system(merge_cmd)#执行合并命令
os.system(del_cmd)#执行删除命令
print('合并完成')
if __name__ == '__main__':
url = 'https://cdn.letv-cdn.com/2018/12/05/JOCeEEUuoteFrjCg/playlist.m3u8'
#下载视频
m3u8(url)
#合并视频
merge_ts()
上一篇: Nginx实现反向代理
下一篇: MP4格式详解