源代码展示
import threading #使用多线程
import requests #使用requests请求下载
import time
import os
import re
from Crypto.Cipher import AES #需要安装pycryptodome模块,用于key解密 D:\PythonLocat\Lib\site-packages\crypto\cipher\路径下有个AES.py文件,Crypto.Cipher区分大小写,否则报错
import ctypes #禁用cmd快速编辑模式
Headers={
"User-Agent":"Mozilla/5.0 (Windows NT 6.1; Win64; x64) AppleWebKit/537.36 (KHTML, like Gecko) Chrome/102.0.0.0 Safari/537.36"
}
def disable_cmd():#禁用cmd快速编辑模式
kernel32 = ctypes.windll.kernel32
kernel32.SetConsoleMode(kernel32.GetStdHandle(-10),128)
def del_log():#删除上一个视频的信息
print("布置环境……")
os.system("del *.ts") #删除ts
if os.path.exists('./filelist.txt'):
os.system("del filelist.txt")
print("正在删除 filelist.txt文件")
if os.path.exists('./contras.log'):
os.system("del contras.log")
print("正在删除 contras.log文件")
if os.path.exists('./index.m3u8'):
os.system("del index.m3u8")
print("正在删除 index.m3u8文件")
if os.path.exists('./key.m3u8'):
os.system("del key.m3u8")
print("正在删除 key.m3u8文件")
if os.path.exists('./unplayerror.log'):
os.system("del unplayerror.log")
print("正在删除 unplayerror.log文件")
if os.path.exists('./exsislist.log'):
os.system("del exsislist.log")
print("正在删除 exsislist.log文件")
os.system("cls")
def down_play_list(play_list_url,name): #下载
try:
play_url =requests.get(url=play_list_url,headers=Headers,timeout=None).content
with open (name,mode='wb') as ts:
if not key:
ts.write(play_url)
else:
cryptor = AES.new(key,AES.MODE_CBC,key) #解密
ts.write(cryptor.decrypt(play_url)) #解密后写入
except ValueError:
print(play_list_url + "第一次下载失败,尝试重新下载")
play_url=play_list_url
play_url =requests.get(url=play_list_url,headers=Headers,timeout=None).content
with open (name,mode='wb') as ts:
if not key:
ts.write(play_url)
else:
cryptor = AES.new(key,AES.MODE_CBC,key)
ts.write(cryptor.decrypt(play_url))
def self_check(): #检查视频是否完全下载,如果还未下载完全,则重新下载缺失的ts文件。
print("正在校验ts文件是否下载完全……")
times=0
while times < 3:#设置自检次数为三次
if os.path.exists('./exsislist.log'):
print("正在删除exsislist.log文件")
os.system("del exsislist.log")
time.sleep(20)
file_list = os.listdir()#获取路径文件列表
num = 0
list_ts = []
for ts_list in file_list:
split_list = os.path.splitext(ts_list)#分割文件
if split_list[1] == ".ts":
if os.path.getsize(r'./' + ts_list) <1024*10:#获取每一个ts文件的大小,如果小于10k,则删除
os.system("del " + ts_list)
else:
with open ('exsislist.log','a') as f: #将已存在的ts序号写入exsislist.log文件。
f.write(split_list[0] + '\n')
f.close()
with open ('contras.log','r') as r:#打开contras.log文件,与exsislist.log文件进行对比,将缺失的的ts打印出来,并进行下载
list_contrat = r.readlines()
for i in list_contrat:
i =i.split(' ')
number = i[0]
url = i[1]
with open ('exsislist.log','r') as r_exsist:
r_exsist_list = r_exsist.readlines()
if number not in str(r_exsist_list):#这里需要将r_exsist_list转换成字符(str)形式,否则无法正确打印出
time.sleep(2)
threading.Thread(target=down_play_list,args=(url,str(number)+'.ts',)).start()
print("正在下载缺失的ts文件:" + str(number)+'.ts')
print(number + " " + i[1].replace('\n',''))#使用replace去除回车符
f.close()
times +=1
def output(): #输出
print("正在合成mp4,请稍等……")
if os.path.exists('./filelist.txt'):
print("正在删除filelist.txt文件")
os.system("del filelist.txt")
time.sleep(5)
if os.path.exists('./unplayerror.log'):
print("正在删除unplayerror.log文件")
os.system("del unplayerror.log")
time.sleep(5)
file_list = os.listdir()
num = 0
list_ts = []
for ts_list in file_list:
split_list = os.path.splitext(ts_list)
if split_list[1] == ".ts":
if os.path.getsize(r'./' + ts_list) < 1024*10:#获取每一个ts文件的大小,如果小于10k,则删除
with open('unplayerror.log','a') as f:
f.write(ts_list + '\n')
f.close()
os.system("del " + ts_list)#删除ts小于10k的ts,否则合成视频时报错
else:
file_ts =split_list[0]
list_ts.append(file_ts)
time.sleep(60) #等待60秒
list_ts.sort() #对list_ts字典进行升序排序
for index in list_ts:
with open('filelist.txt',mode = 'a') as f:
f.write('file ' + "'" +str(index) + '.ts' + "'"+'\n')
f.close()
os.system("ffmpeg -f concat -safe 0 -i filelist.txt -c copy " + outname) #使用ffmpeg工具合成为mp4视频格式
time.sleep(5)
print("视频合成完成")
def main(): #主函数
with open ('index.m3u8',mode='r') as f: #从index.m3u8文件中获取ts列表
lists = f.readlines()
nu = 0
ts_list = []
for i in lists[0:]:
if '#' not in i: #如果存在'#',则去掉
i = i.replace('\n','')
ts_list.append(i)
number =len(str(len(ts_list))) #str(len(ts_list)) ts_list长度转换成字符串形式
print("共有" + str(len(ts_list)) + "个ts文件") #打印出ts的个数
index=0
for i in ts_list:
change = '%0' + str(number) +'d'
num = change % index #进行数据的转换,如果ts有99个,则命名为01、02、……,如果是999个,则为001.ts、002、……,方便合成视频
if 'http' not in i:#判断ts文件是否完整(含有http头)
if i[0] == '/': #如果ts是从/开始的如 /202203/dfdf/dkdkg/dkdddk.ts,则使用这条路径下载
threading.Thread(target=down_play_list,args=(base_url_1 + i,str(num)+'.ts',)).start()
print(num + ' ' + base_url_1 + i)
with open ('contras.log',mode='a') as f: #生成对照表,将每个序号对应的链接写入
f.write(num + ' ' + base_url_1 + i + '\n')
f.close()
else: #如果ts是从http开始的如 http:/vs.cskd.cn/202203/dfdf/dkdkg/dkdddk.ts,则使用这条路径下载
threading.Thread(target=down_play_list,args=(base_url_2 + i,str(num)+'.ts',)).start()
print(num + ' ' + base_url_2 + i)
with open ('contras.log',mode='a') as f:
f.write(num + ' ' + base_url_2 + i + '\n')
f.close()
else:
threading.Thread(target=down_play_list,args=(i,str(num)+'.ts',)).start()
print(num + ' ' + i)
with open ('contras.log',mode='a') as f: #生成对照表,将每个序号对应的链接写入
f.write(num + ' ' + i + '\n')
f.close()
index+=1
time.sleep(1)
def choose_menu(): #选择菜单,下载完成以后可以选择菜单
while True:
print("输入0-->退出")
print("输入1-->手动合成视频(通常在文件较大,自动合成失败的形况下)")
print("输入2-->手动下载缺失的视频,并合成mp4")
print("输入3-->删除上一个视频相关文件(ts片段、log信息、filelist、m3u8))")
choose = int(input("请输入:"))
if choose ==0:
break
if choose ==1:
os.system("del filelist.txt")
time.sleep(3)
output()
if choose ==2:
self_check()
output()
if choose ==3:
del_log()
if __name__=='__main__':
while True:
del_log()#删除上一条视频的相关文件
while True:
try:
m3u8_url = str(input("请输入m3u8链接:"))
outname = input("请输入视频名称:") + '.mp4'
start_time = time.time()
get_url = m3u8_url.split('/')
base_url_1 = get_url[0]+'//' + get_url[2] #得到http://*** get_url[0]=http: get_url[2]=*** 第一种情况
base_url_2 = m3u8_url.replace(get_url[-1],'') #替换掉m3u8_url链接最后部分,主要针对ts前部分没有http的情况 第二种情况
print("正在下载index.m3u8文件……") #下载index.m3u8
m3u8_index = requests.get(m3u8_url).content
break
except IndexError:
print('m3u8网址不正确')
disable_cmd()#禁用cmd快速编辑模式
with open(f"index.m3u8", mode='wb') as f:
f.write(m3u8_index)
response = requests.get(m3u8_url).text#打印index.m3u8文本形式
if "#EXT-X-KEY" not in response: #判断是否存在key加密
print("视频未加密")
key=False
main() #进入main()方法
else:
print("视频已加密,查找密钥")
key_search =re.compile(get_url[0] + r"//.*?\.key")#有的网址开头是https,有的是http,所以用get_url[0]来确定
#key_search =re.compile(r"\/.*?\.key")#找key,有的key不是以https形式开头的,可能是一个/开头
key_url = key_search.findall(response)[0] #获取到的是字典的形式,所以加上[0]取出字典里面的值
print("获得key链接",key_url)
#key = requests.get(base_url_1+key_url).content
key = requests.get(key_url).content
print("获得密钥:",key)
with open('key.m3u8','wb') as key_f:
key_f.write(key)
main()
self_check() #自检
output() #输出MP4
stop_time = time.time()
print("总共用时(秒):",stop_time-start_time)
choose_menu() #完成后进入操作菜单