源代码展示

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() #完成后进入操作菜单