一、按F12,先找出m3u8的url,预览内容是如以下这种类型,然后复制出请求URL
#EXTM3U
#EXT-X-VERSION:3
#EXT-X-TARGETDURATION:16
#EXT-X-MEDIA-SEQUENCE:0
#EXT-X-KEY:METHOD=AES-128,URI="https://ke.qq.com/cgi-bin/qcloud/get_dk?edk=CiBENw8S52C77Pg9t2ak1u%2FD9NfhZ3F7%2BoiW%2BM5iBKyMbBCO08TAChiaoOvUBCokOTMyNDg4YmItOWZjYS00MzFiLWJiYjItNjFmMDhjYjNlYmM3&fileId=5285890815554519079&keySource=VodBuildInKMS&token=dWluPTM1MTc4MzU1NDQ7c2tleT1AQklIMFlFYU9SO3Bza2V5PUpPUE44RVUtLXdMUWxqeERZVHUyNEFoa3kqVGh5NGp5clBpbUIzbUJscW9fO3Bsc2tleT0wMDA0MDAwMDc2MTNiOTRmMjA4NmU1NTJjNzhkYzI3NzdhYjQ3NGQxYzZhNGEzN2ZkNTVmMDc4ODZhODFjNDI3MTBmZTA2MTJmN2EwZjFkNzczOTU1OTMyO2V4dD07dWlkX3R5cGU9MDt1aWRfb3JpZ2luX3VpZF90eXBlPTA7Y2lkPTMzODU0OTE7dGVybV9pZD0xMDM1MTg0OTg7dm9kX3R5cGU9MA%3D%3D",IV=0x00000000000000000000000000000000
#EXTINF:2.000000,
v.f30742.ts?start=0&end=506863&type=mpegts&exper=0&sign=4142361e5f70dc1887bb4374a6c8499a&t=60eab641&us=6759179987002315823
#EXT-X-KEY:METHOD=AES-128,URI="https://ke.qq.com/cgi-bin/qcloud/get_dk?edk=CiBENw8S52C77Pg9t2ak1u%2FD9NfhZ3F7%2BoiW%2BM5iBKyMbBCO08TAChiaoOvUBCokOTMyNDg4YmItOWZjYS00MzFiLWJiYjItNjFmMDhjYjNlYmM3&fileId=5285890815554519079&keySource=VodBuildInKMS&token=dWluPTM1MTc4MzU1NDQ7c2tleT1AQklIMFlFYU9SO3Bza2V5PUpPUE44RVUtLXdMUWxqeERZVHUyNEFoa3kqVGh5NGp5clBpbUIzbUJscW9fO3Bsc2tleT0wMDA0MDAwMDc2MTNiOTRmMjA4NmU1NTJjNzhkYzI3NzdhYjQ3NGQxYzZhNGEzN2ZkNTVmMDc4ODZhODFjNDI3MTBmZTA2MTJmN2EwZjFkNzczOTU1OTMyO2V4dD07dWlkX3R5cGU9MDt1aWRfb3JpZ2luX3VpZF90eXBlPTA7Y2lkPTMzODU0OTE7dGVybV9pZD0xMDM1MTg0OTg7dm9kX3R5cGU9MA%3D%3D",IV=0x00000000000000000000000000000000
#EXTINF:2.000000,
v.f30742.ts?start=506864&end=1098319&type=mpegts&exper=0&sign=4142361e5f70dc1887bb4374a6c8499a&t=60eab641&us=6759179987002315823
#EXT-X-KEY:METHOD=AES-128,URI="https://ke.qq.com/cgi-bin/qcloud/get_dk?edk=CiBENw8S52C77Pg9t2ak1u%2FD9NfhZ3F7%2BoiW%2BM5iBKyMbBCO08TAChiaoOvUBCokOTMyNDg4YmItOWZjYS00MzFiLWJiYjItNjFmMDhjYjNlYmM3&fileId=5285890815554519079&keySource=VodBuildInKMS&token=dWluPTM1MTc4MzU1NDQ7c2tleT1AQklIMFlFYU9SO3Bza2V5PUpPUE44RVUtLXdMUWxqeERZVHUyNEFoa3kqVGh5NGp5clBpbUIzbUJscW9fO3Bsc2tleT0wMDA0MDAwMDc2MTNiOTRmMjA4NmU1NTJjNzhkYzI3NzdhYjQ3NGQxYzZhNGEzN2ZkNTVmMDc4ODZhODFjNDI3MTBmZTA2MTJmN2EwZjFkNzczOTU1OTMyO2V4dD07dWlkX3R5cGU9MDt1aWRfb3JpZ2luX3VpZF90eXBlPTA7Y2lkPTMzODU0OTE7dGVybV9pZD0xMDM1MTg0OTg7dm9kX3R5cGU9MA%3D%3D",IV=0x00000000000000000000000000000000
#EXTINF:2.000000,
v.f30742.ts?start=1098320&end=1664207&type=mpegts&exper=0&sign=4142361e5f70dc1887bb4374a6c8499a&t=60eab641&us=6759179987002315823
#EXT-X-KEY:METHOD=AES-128,URI="https://ke.qq.com/cgi-bin/qcloud/get_dk?edk=CiBENw8S52C77Pg9t2ak1u%2FD9NfhZ3F7%2BoiW%2BM5iBKyMbBCO08TAChiaoOvUBCokOTMyNDg4YmItOWZjYS00MzFiLWJiYjItNjFmMDhjYjNlYmM3&fileId=5285890815554519079&keySource=VodBuildInKMS&token=dWluPTM1MTc4MzU1NDQ7c2tleT1AQklIMFlFYU9SO3Bza2V5PUpPUE44RVUtLXdMUWxqeERZVHUyNEFoa3kqVGh5NGp5clBpbUIzbUJscW9fO3Bsc2tleT0wMDA0MDAwMDc2MTNiOTRmMjA4NmU1NTJjNzhkYzI3NzdhYjQ3NGQxYzZhNGEzN2ZkNTVmMDc4ODZhODFjNDI3MTBmZTA2MTJmN2EwZjFkNzczOTU1OTMyO2V4dD07dWlkX3R5cGU9MDt1aWRfb3JpZ2luX3VpZF90eXBlPTA7Y2lkPTMzODU0OTE7dGVybV9pZD0xMDM1MTg0OTg7dm9kX3R5cGU9MA%3D%3D",IV=0x00000000000000000000000000000000
#EXTINF:2.000000,
.....
二、拿ts文件请求链接的前缀
(因为第一次拿到的链接,获取到的ts链接是没有前缀的)
随便找一个**v.f30742.ts?**开头的链接,复制它的请求url,只取v.f30742.ts?前面部分
取出链接:https://1258712167.vod2.myqcloud.com/5a81e359vodtranssh1258712167/fe20c2755285890815554519079/drm/
原本链接:https://1258712167.vod2.myqcloud.com/5a81e359vodtranssh1258712167/fe20c2755285890815554519079/drm/v.f30742.ts?start=2184608&end=2711583&type=mpegts&exper=0&sign=4142361e5f70dc1887bb4374a6c8499a&t=60eab641&us=6759179987002315823
三、对ts文件进行解密下载并合并,代码如下
import re
import requests
import os,uuid
from Crypto.Cipher import AES
class Demo(object):
'''
调用方法:
1、先实例化一个Demo对象
2、把提取的m3u8url放在url, 把提取的ts url前缀放在base_url
2、调用demo的download_ts方法,并将两个参数(url,base_url)传进去
只需要在download方法中传入一个m3u8url即可
'''
# 返回当前文件路径
def get_filpath(self):
# 先判断ts_files文件夹是否存在,不存在则创建
if not os.path.exists('ts_files'):
os.system('mkdir ts_files')
f_path=os.getcwd()+'\\ts_files\\'
return f_path
# 将ts文件合并转换成mp4
def ts_to_mp4(self):
path = self.get_filpath()
mp4_name = str(uuid.uuid1()) + '.mp4'
print(mp4_name)
merge_cmd = 'copy /b ' + path + '\*.ts ' + path + '\\' + mp4_name
# 转换成功后,删除ts文件
# del_cmd = 'del ' + path + '\*.ts'
os.system(merge_cmd)
# os.system(del_cmd)
print('转换完成')
def download_ts(self,url,base_url):
# 获取初始的ts_urls 和content
res = requests.get(url=url)
pay_list = res.text.split('\n')
ts_urls = pay_list[9:-1:3]
# 获取加密的方法和密钥
content=res.text
method = re.findall('#EXT-X-KEY:METHOD=(.*?),', content)[0]
# 获取加密url
key = re.findall('#EXT-X-KEY:METHOD=AES-128,(.*?)\n', content)[0]
key_url = re.findall('"(.*?)"', key)[0]
# 对加密url发起请求
res = requests.get(key_url)
# 获取返回来的加密密钥
key = res.content
# 获取偏移向量 iv
iv=re.findall('IV=(.*?)\n',content)[0]
iv=iv.replace('0x','')
iv=bytes.fromhex(iv)
cryptor = AES.new(key, AES.MODE_CBC, iv)
for i, p in enumerate(ts_urls):
ts_url = base_url + p
res = requests.get(ts_url)
'''
注意:ts文件名,要按001这种形式命令,不然,等下在window下合并顺序会出现错乱,导致视频无法播放
ts文件比较多的,可以添加位数 如,0001,00001等
rjust(3,'0'),3表示位数
'''
file_name = str(i).rjust(3,'0') + '.ts'
file=self.get_filpath()+file_name
with open(file, 'wb') as f:
# 对ts文件内容进行解密
f.write(cryptor.decrypt(res.content))
print('下载完成')
self.ts_to_mp4()
if __name__=='__main__':
# url为第一步提取的url
url = 'https://1258712167.vod2.myqcloud.com/5a81e359vodtranssh1258712167/fe20c2755285890815554519079/drm/voddrm.token.dWluPTM1MTc4MzU1NDQ7c2tleT1AQklIMFlFYU9SO3Bza2V5PUpPUE44RVUtLXdMUWxqeERZVHUyNEFoa3kqVGh5NGp5clBpbUIzbUJscW9fO3Bsc2tleT0wMDA0MDAwMDc2MTNiOTRmMjA4NmU1NTJjNzhkYzI3NzdhYjQ3NGQxYzZhNGEzN2ZkNTVmMDc4ODZhODFjNDI3MTBmZTA2MTJmN2EwZjFkNzczOTU1OTMyO2V4dD07dWlkX3R5cGU9MDt1aWRfb3JpZ2luX3VpZF90eXBlPTA7Y2lkPTMzODU0OTE7dGVybV9pZD0xMDM1MTg0OTg7dm9kX3R5cGU9MA==.v.f30742.m3u8?exper=0&sign=4142361e5f70dc1887bb4374a6c8499a&t=60eab641&us=6759179987002315823'
#base_url 为第二步提取的url前缀
base_url='https://1258712167.vod2.myqcloud.com/5a81e359vodtranssh1258712167/fe20c2755285890815554519079/drm/'
h = Demo()
h.download_ts(url=url,base_url=base_url)
注意:最后合成有可能出现,找不到文件的错误提示,原因:你路径有问题,可以自己在cmd进行合并
合并命令如下:
# path是路径
'copy /b '+path+'\*.ts '+ path+'\\new1.mp4'
(2) 如果遇到iv提示报错 ValueError: Incorrect IV length (it must be 16 bytes long)
则需要对iv进行处理
# 如IV=0x0E16B71765DC9B699C33DC5901307461
# 1、先将iv值首两位给剔除掉,因为首两位是0x是16进制标识,去掉后就剩下32位字符串了
iv=IV[2:]
# 2、将32位字符串转成16进制
iv=bytes.fromhex(iv)
最后效果如下:
下一次,再把多线程加进入,还有linux下的合并方法。
注意:最好用linux合并,window合并上传到服务器可能会播放不了
linux下合并脚本如下:
import os
#ts文件绝对路径
ts_path = '/home/ts_files'
#读取ts文件夹下所有的ts文件
path_list = os.listdir(ts_path)
#对文件进行排序
path_list.sort()
#将排序后的ts的绝对路径放入列表中
li = [os.path.join(ts_path,filename) for filename in path_list]
#类似于[001.ts|00.2ts|003.ts]
input_file = '|'.join(li)
#指定输出文件名称
output_file = ts_path + '.mp4'
#使用ffmpeg将ts合并为mp4
command = 'ffmpeg -i "concat:%s" -acodec copy -vcodec copy -absf aac_adtstoasc %s'% (input_file,output_file)
#指行命令
os.system(command)
注意
有的key的url需要加一个token才可以获取到密钥,如:技成培训网站
使用进程池和线程池、协程池三种方式,可以提高效率,但是,效率有一个峰值。代码如下:
# -*-coding:utf-8-*-
import re
import time
import requests
import os, uuid
from Crypto.Cipher import AES
from gevent import monkey
import gevent
from gevent.pool import Pool
from concurrent.futures import ThreadPoolExecutor,ProcessPoolExecutor
import sys
sys.setrecursionlimit(100000000)
# 获取key 和iv
def get_key_iv(content):
# 正则获取加密方法,正常都是AES-128
method = re.findall('#EXT-X-KEY:METHOD=(.*?),', content)[0]
# 正则获取密钥在服务器的url
key_url = re.findall('#EXT-X-KEY:METHOD=AES-128,URI="(.*?)"', content)[0]
# 对key_url发起请求获取密钥,如果请求失败,则在调试面板看是否缺少token等参数
key = requests.get(key_url).content
# 获取iv(偏移量)
iv = re.findall('IV=(.*?)\n', content)[0]
# 将iv的首两位标识(16进制标识)去除,可以采用切片或者替换
iv = iv.replace('0x', '')
# 将iv字符串转成16进制
iv = bytes.fromhex(iv)
dic = {
'key': key,
'iv': iv
}
return dic
# 获取ts列表url
def get_ts_list(content):
data_list = content.split('\n')
is_first = 0
first_num = 0
for i, data in enumerate(data_list):
if '.ts' in data and is_first == 0:
first_num = i
is_first = 1
elif '.ts' in data and is_first == 1:
is_first = i - first_num
break
ts_list = data_list[first_num::is_first]
return ts_list
# 设置保存路径
def set_file():
file_path = os.getcwd() + '\\tsfiles\\'
if not os.path.exists(file_path):
os.makedirs(file_path)
return file_path
# 合并ts视频
def ts_to_mp4():
path = set_file()
mp4_name = str(uuid.uuid1()) + '.mp4'
merge_cmd = 'copy /b ' + path + '\*.ts ' + path + '\\' + mp4_name
# 根据需求,看合并后,是否保留ts视频
del_cmd = 'del ' + path + '\*.ts'
os.system(merge_cmd)
# os.system(del_cmd)
def download_ts(dic):
# 统一默认都是有加密的,没有加密只需把true改为false
is_cry = True
i = dic['i']
ts = dic['ts']
content = dic['content']
if is_cry:
# 获取加密密钥和偏移值
key = get_key_iv(content).get('key')
iv = get_key_iv(content).get('iv')
# 实例化CBC模式的AES对象
cryptor = AES.new(key, AES.MODE_CBC, iv)
res = requests.get(ts)
# 可以根据ts文件数量,适当设置3(3表示是位数,如 001)
file_name = str(i).rjust(3, '0') + '.ts'
file = set_file() + file_name
with open(file, 'wb') as f:
if is_cry:
content = cryptor.decrypt(res.content)
else:
content = res.content
f.write(content)
print('ts{}下载成功'.format(i))
if __name__ == '__main__':
url = 'https://1258712167.vod2.myqcloud.com/25121a6avodtransbj1258712167/3522b9c75285890816911210574/drm/voddrm.token.dWluPTM1MTc4MzU1NDQ7c2tleT1AUUdUU0JlNzlBO3Bza2V5PVVkUXIwQWVZbVZjUWV4RVFzQk9TWUk5cypPSXZDKk51dXF2S25VSU9NdGdfO3Bsc2tleT0wMDA0MDAwMDNhYzI1YjE5ODFhNzVlZWFkMjNkOGNmMGQ5ODA5Mjk3ZTJlY2E4MzRhMTU5ZDgzM2FjYjU3MzYwYWZjNTk0YTQwZmNjZjBhYzJmNWU3ZWYwO2V4dD07dWlkX3R5cGU9MDt1aWRfb3JpZ2luX3VpZF90eXBlPTA7Y2lkPTM5OTAxNzt0ZXJtX2lkPTEwMDQ3NTk2NTt2b2RfdHlwZT0w.v.f30741.m3u8?exper=0&sign=81967dbbdb625de8455b566adf35c6d2&t=61206ef8&us=269233269408429750'
base_url = 'https://1258712167.vod2.myqcloud.com/25121a6avodtransbj1258712167/3522b9c75285890816911210574/drm/'
content = requests.get(url).text
ts_list = get_ts_list(content)
pool = Pool(10) #协程的
# 进程池线程池
# pool= ThreadPoolExecutor(50)
# pool = ProcessPoolExecutor(20)
s = time.time()
g_list=[]
print(len(ts_list))
for i, ts in enumerate(ts_list):
ts =base_url+ts
# download_ts({'ts': ts, 'i': i, 'content': content})
# 协程
g=pool.spawn(download_ts,({'ts':ts,'i':i,'content':content}))
# 线程、进程
# pool.submit(download_ts,({'ts':ts,'i':i,'content':content}))
# 协程
g_list.append(g)
# 进程线程
# pool.shutdown(wait=True)
# 协程
gevent.joinall(g_list)
print(time.time()-s)
'''
正常情况下是:342.5760555267334
协程池:70多秒
进程池和线程池: 50-70多秒左右
'''