日常中我们在一些网站上看到有意思的电影或者视频,想保存下来,点击下载却发现这是一个以 .m3u8结尾的视频链接。就算我们用手机下载下来,却发现下载后得到的不是一个完整的视频文件,而是一大堆ts结尾的视频文件,整个视频被切割为一个个的几秒的视频文件。这里就使用python实现将视频链接下载为一个完整的mp4视频文件。
1.了解 m3u8 视频链接
m3u8文件其实是 HTTP Live Streaming(缩写为 HLS)协议的部分内容,而 HLS 是一个由苹果公司提出的基于 HTTP 的 流媒体 网络传输协议。
简而言之,HLS 是新一代流媒体传输协议,其基本实现原理为将一个大的媒体文件进行分片,将该分片文件资源路径记录于 m3u8 文件(即 playlist)内,其中附带一些额外描述用于提供给客户端。客户端依据该 m3u8 文件即可获取对应的媒体资源,进行播放。
因此,客户端获取 HLS 流文件,主要就是对 m3u8 文件进行解析操作。
2.解析 m3u8 文件
我们将获取的视频链接在浏览器上打开,会得到一个以 .m3u8 为结尾的文件,使用记事本打开
#EXTM3U
#EXT-X-TARGETDURATION:12
#EXT-X-MEDIA-SEQUENCE:0
#EXTINF:2,
http://play1.cp21.ott.cibntv.net/play.videocache.lecloud.com/ver_00_22_0_0_1_81028_0_0.ts
#EXTINF:3,
http://play1.cp21.ott.cibntv.net/play.videocache.lecloud.com/ver_00_22_1_1_1_113176_81028_0.ts
#EXTINF:5,
http://play1.cp21.ott.cibntv.net/play.videocache.lecloud.com/ver_00_22_2_2_1_248724_194204_0.ts
......
#EXT-X-ENDLIST
第一行 “#EXTM3U” 表明这个一个 m3u8 格式的视频文件,“#EXTINF”后面的一行链接便是每一个视频流的文件地址。如果将链接输入到浏览器中访问,你将得到一个以 .ts 结尾的几秒钟视频文件。这个文件中所有的链接全部请求得到的就是一个完整的视频。
有时候得到的 m3u8 文件不一样,会有一行 “#EXT-X-KEY” ,说明这个视频是经过加密的,需要我们去解密才能得到视频,否则得到的视频文件打开就会报错。
#EXTM3U
#EXT-X-VERSION:3
#EXT-X-TARGETDURATION:2
#EXT-X-MEDIA-SEQUENCE:0
#EXT-X-KEY:METHOD=AES-128,URI="key.key"
#EXTINF:1.668333,
ir7QcW6705000.ts
#EXTINF:0.834167,
ir7QcW6705001.ts
#EXTINF:0.834167,
ir7QcW6705002.ts
这个文件中多了一行 “#EXT-X-KEY” 这个是向客户端表明这个一个加密的视频,加密方式为 AES-128 ,而加密文件是 key.key ,由于这个加密文件和视频链接地址都无前缀,所以他们的链接前缀应该是得到这个m3u8文件的链接,将末尾修改为key文件和视频流名称就可得到完整的链接。
有时候我们得到的 m3u8 文件是这样的
#EXTM3U
#EXT-X-STREAM-INF:PROGRAM-ID=1,BANDWIDTH=732000,RESOLUTION=720x480
hls/index.m3u8
我们将第一个链接后改为 http://www.play1.com/hls/index.m3u8 再次访问才能得到像上面格式的文件。这个只是将文件设置为二次访问获取真实地址。
整个文件解析基本完成,现在进行脚本编写,实现下载。
3.Python实现下载流程
这里实现加密视频的下载
首先获取 m3u8 文件并解析
import requests
import re
from Crypto.Cipher import AES
def m3u8(url):
header = {
'User-Agent': 'Mozilla/5.0 (Macintosh; Intel Mac OS X 10_14_2) AppleWebKit/537.36 (KHTML, like Gecko) Chrome/71.0.3578.98 Safari/537.36'
}
# requests得到m3u8文件内容
content = requests.get(url,headers=header).text
if "#EXTM3U" not in content:
print("这不是一个m3u8的视频链接!")
return False
if "EXT-X-KEY" not in content:
print("没有加密")
return False
# 使用re正则得到key和视频地址
jiami=re.findall('#EXT-X-KEY:(.*)\n',content)
key=re.findall('URI="(.*)"',jiami[0])
#得到每一个ts视频链接
tslist=re.findall('EXTINF:(.*),\n(.*)\n#',content)
newlist=[]
for i in tslist:
newlist.append(i[1])
# 先获取URL/后的后缀,再替换为空
urlkey=url.split('/')[-1]
url2 = url.replace(urlkey, '') #这里为得到url地址的前面部分,为后面key的链接和视频链接拼接使用
#得到key的链接并请求得到加密的key值
keyurl=url2+key[0]
keycontent= requests.get(keyurl,headers=header).text
#得到每一个完整视频的链接地址
tslisturl=[]
for i in newlist:
tsurl=url2+i
tslisturl.append(tsurl)
#得到解密方法,这里要导入第三方库 pycrypto
#这里有一个问题,安装pycrypto成功后,导入from Crypto.Cipher import AES报错
#找到使用python环境的文件夹,在Lib文件夹下有一个 site-packages 文件夹,里面是我们环境安装的包。
#找到一个crypto文件夹,打开可以看到 Cipher文件夹,此时我们将 crypto文件夹改为 Crypto 即可使用了
cryptor = AES.new(keycontent, AES.MODE_CBC, keycontent)
#for循环获取视频文件
for i in tslisturl:
res = requests.get(i, header)
#使用解密方法解密得到的视频文件
cont=cryptor.decrypt(res.content)
#以追加的形式保存为mp4文件
with open('xx.mp4', 'ab+') as f:
f.write(cont)
return True
if __name__ == '__main__':
url = "https://xxxxxxx/hls/index.m3u8"
pd = m3u8(url)
if pd:
print('视频下载完成!')
至此整个视频文件下载脚本结束。启动脚本等待视频下载完成,即可得到一个完整的 mp4 格式视频文件。
后记
整个脚本实现过程比较粗糙。只是完成视频的分析下载。小伙伴们可以根据自己自行修改优化。这里也是参考不少大佬的博客技术文档,站在大佬的肩上学习实现了这个脚本。感谢大佬们的技术博客分享,向大佬们致敬!