
m3u8是苹果公司推出的视频播放标准,是基于HTTP的流媒体网络传输协议(HLS)。HLS是把整个流分成一个个小的基于 HTTP 的文件来下载。m3u8 文件实质是一个播放列表(playlist),其内部文字使用的都是 utf-8 编码。
客户端只需按顺序下载上述片段资源,依次进行播放即可。而对于直播来说,客户端需要定时重新请求该 m3u8 文件,看下是否有新的片段数据需要进行下载并播放。
知道原理开始分流下载片段。其中有些网站的ts文件是采用AES方式进行加密,所以需要对其进行解密。因而我们需要去读取这个key文件,拿取密钥。
代码如下:
import m3u8
import requests
import os
import re
from Crypto.Cipher import AES
import glob
import concurrent.futures
import time
from concurrent.futures import as_completed
headers = {
"User-Agent": "Mozilla/5.0 (Windows NT 10.0; Win64; x64) AppleWebKit/537.36 (KHTML, like Gecko) Chrome/99.0.4844.82 Safari/537.36"
}
#正则表达判断是否为网站地址
def reurl(url):
pattern = re.compile(r'^((https|http|ftp|rtsp|mms)?:\/\/)[^\s]+')
m=pattern.search(url)
if m is None:
return False
else:
return True
#获取密钥
def getKey(keystr,url):
keyinfo= str(keystr)
method_pos= keyinfo.find('METHOD')
comma_pos = keyinfo.find(",")
method = keyinfo[method_pos:comma_pos].split('=')[1]
uri_pos = keyinfo.find("URI")
quotation_mark_pos = keyinfo.rfind('"')
key_url = keyinfo[uri_pos:quotation_mark_pos].split('"')[1]
if reurl(key_url) == False:
key_url = url.rsplit("/", 1)[0] + "/" + key_url
res = requests.get(key_url,headers=headers)
key = res.content
print(method)
print(key.decode('utf-8'))
return method, key
#下载文件
#down_url:ts文件地址
#url:*.m3u8文件地址
#decrypt:是否加密
#down_path:下载地址
#key:密钥
def download(down_url,url,decrypt,down_path,key):
if reurl(down_url) == False:
filename = down_url
down_url = url.rsplit("/", 1)[0] + "/" + down_url
else:
filename = down_url.rsplit("/", 1)[1]
try:
res = requests.get(down_url, stream=True, verify=False,headers=headers)
except Exception as e:
print(e)
return
down_ts_path = down_path+"/{0}".format(filename)
if decrypt:
cryptor = AES.new(key, AES.MODE_CBC, key)
with open(down_ts_path,"wb+") as file:
for chunk in res.iter_content(chunk_size=1024):
if chunk:
if decrypt:
file.write(cryptor.decrypt(chunk))
else:
file.write(chunk)
#合并ts文件
#dest_file:合成文件名
#source_path:ts文件目录
#ts_list:文件列表
#delete:合成结束是否删除ts文件
def merge_to_mp4(dest_file, source_path,ts_list, delete=False):
files = glob.glob(source_path + '/*.ts')
if len(files)!=len(ts_list):
print("文件不完整!")
return
with open(dest_file, 'wb') as fw:
for file in ts_list:
with open(source_path+"/"+file, 'rb') as fr:
fw.write(fr.read())
if delete:
os.remove(file)
def main():
url = "https://index.m3u8"
#使用m3u8库获取文件信息
video = m3u8.load(url)
#设置下载路径
down_path="tmp"
#设置是否加密标志
decrypt = False
#ts列表
ts_list=[]
#判断是否加密
if video.keys[0] is not None:
method,key =getKey(video.keys[0],url)
decrypt = True
#判断是否需要创建文件夹
if not os.path.exists(down_path):
os.mkdir(down_path)
#把ts文件名添加到列表中
for filename in video.segments:
if reurl(filename.uri):
ts_list.append(filename.uri.rsplit("/", 1)[1])
else:
ts_list.append(filename.uri)
#开启线程池
with concurrent.futures.ThreadPoolExecutor() as executor:
obj_list = []
begin = time.time()#记录线程开始时间
for i in range(len(video.segments)):
obj = executor.submit(download,video.segments[i].uri,url,decrypt,down_path,key)
obj_list.append(obj)
#查看线程池是否结束
for future in as_completed(obj_list):
data = future.result()
print(data)
merge_to_mp4('result.mp4', down_path,ts_list)#合并ts文件
times = time.time() - begin #记录线程完成时间
print(times)
if __name__ == "__main__":
main()
欢迎分享,转载请注明来源:内存溢出
微信扫一扫
支付宝扫一扫
评论列表(0条)