部分代码摘录于某位大哥(写代码的时候收藏书签了的打算写博客的时候带上链接的,无奈手贱删除了chrome用户,所有的书签也没了,找到再补上),在此基础上添加了多线程,日志,以及防止重复下载文件的代码,弃用“copy \b”合并视频,发现新大陆“ffmpeg”
敲黑板一次:有时拿到的m3u8地址并不是真正的m3u8地址,它包含了真实的m3u8地址,如下图
敲黑板两次:m3u8中给出的ts文件有的是经过加密的,所以需要解密,如下图:
敲黑板三次对于最终下载下来的一堆ts文件,要合并,之前找了一堆合并软件发现都有各种各样的问题。其实系统自带的“copy \b”命令很好用,但是无奈这个命令对合并的文件命名要求有点麻烦。比如我有文件**000.ts,**001.ts,**002.ts,…,**999.ts,**1000.ts等,当文件合并到999的时候就停下了。当然自己下载文件的时候改一下文件的命名就可以了。但是自己后来发现另一个牛b的工具ffmepg。
# -*- coding:utf-8 -*-
import os
import sys
import requests
import datetime
import threading
from Crypto.Cipher import AES
from binascii import b2a_hex, a2b_hex
import logging
reload(sys)
sys.setdefaultencoding('utf-8')
logfile = 'trans.log'
logger = logging.getLogger(__name__)
logger.setLevel(level=logging.DEBUG)
handler = logging.FileHandler(logfile, mode='a')
handler.setLevel(logging.DEBUG)
formatter = logging.Formatter("%(asctime)s - %(filename)s[line:%(lineno)d] - %(levelname)s: %(message)s")
handler.setFormatter(formatter)
console = logging.StreamHandler()
console.setLevel(logging.INFO)
console.setFormatter(formatter)
logger.addHandler(handler)
logger.addHandler(console)
def download(url, num_thread=4):
"""
:param url: m3u8文件url
:param num_thread: 启动线程数
:return:
"""
download_path = os.getcwd() + "\\download"
if not os.path.exists(download_path):
os.mkdir(download_path)
all_content = requests.get(url).text # 获取第一层M3U8文件内容
if "#EXTM3U" not in all_content:
logger.error("非M3U8的链接")
return
if "EXT-X-STREAM-INF" in all_content: # 第一层
file_line = all_content.split("\n")
for line in file_line:
if '.m3u8' in line:
url = url.rsplit("/", 1)[0] + "/" + line # 拼出第二层m3u8的URL
all_content = requests.get(url).text
file_line = all_content.split("\n")
unknow = True
key = ""
# 已下载列表
local_list = os.listdir(download_path)
# 所有要下载列表
all_list = []
# 待下载列表
s_list = []
for index, line in enumerate(file_line):
if "#EXT-X-KEY" in line:
# 有的网站提供的ts格式的视频是经过AES加密的,需要解密
method_pos = line.find("METHOD")
comma_pos = line.find(",")
method = line[method_pos:comma_pos].split('=')[1]
logger.info("Decode Method:%s" % method)
uri_pos = line.find("URI")
quotation_mark_pos = line.rfind('"')
key_path = line[uri_pos:quotation_mark_pos].split('"')[1]
key_url = url.rsplit("/", 1)[0] + "/" + key_path # 拼出key解密密钥URL
res = requests.get(key_url)
key = res.content
# key = "9826c9209bdddcbe"
logger.info("key:%s" % key)
if "EXTINF" in line:
unknow = False
# 找ts文件名
file_name = file_line[index + 1]
all_list.append(file_name )
if unknow:
logger.error("未找到对应的下载链接")
else:
# 启动多线程下载文件
s_list = list(set(all_list) - set(local_list))
file_size = len(s_list)
part = file_size // num_thread # 如果不能整除,最后一块应该多几个字节
thread_list = []
for i in range(num_thread):
start = part * i
if i == num_thread - 1: # 最后一块
end = file_size
else:
end = start + part
if end > start:
t = threading.Thread(
target=multi_download,
kwargs={'start': start, 'end': end, 'url': url.rsplit("/", 1)[0] + "/", 'file_list': s_list, 'key': key})
t.setDaemon(True)
t.start()
thread_list.append(t)
logger.info("启动线程%s" % t.name)
# 等待所有线程下载完成
for t in thread_list:
t.join()
logger.info("结束线程%s" % t.name)
logger.info('所有线程结束')
def multi_download(start, end, url, file_list, key):
"""
:param start: 下载文件列表开始索引
:param end: 下载文件列表结束索引
:param url: 下载ts文件的url
:param file_list: 下载文件列表
:param key: AES解密用的key
:return:
"""
download_path = os.getcwd() + "\\download"
for i in file_list[start:end]:
global count
down_url = url + i
try:
logger.debug("正在下载:%s" % i)
res = requests.get(down_url)
if key: # AES 解密
cryptor = AES.new(key, AES.MODE_CBC, key)
with open(os.path.join(download_path, i), 'ab') as f:
f.write(cryptor.decrypt(res.content))
else:
with open(os.path.join(download_path, i), 'ab') as f:
f.write(res.content)
f.flush()
except:
logger.warning("下载失败:%s" % i)
count = count + 1
logger.info("下载进度:%.2f%s-----%s/%s" % (100*float(count) / len(file_list), "%", count, len(file_list)))
def merge_file(path):
os.chdir(path)
# 直接调用系统copy命令
cmd = "copy /b *.ts new.mp4"
os.system(cmd)
if __name__ == '__main__':
count = 0
m3u8_url = "https://****/hls/index.m3u8"
download(m3u8_url)
# download_path = os.getcwd() + "\\download"
# merge_file(download_path)
当有文件下载失败时,直接重新运行即可下载之前下载失败的文件。
下载ffmpeg后,把m3u8文件下载到本地,执行命令:ffmpeg -i m3u8文件本地路径 -c copy new.mp4。
也可直接执行命令:ffmpeg -i m3u8文件url -c copy new.mp4 直接下载完整视频。但是如果遇到网络影响容易失败,卡住切无法多线程下载文件。但是ffmpeg有很多其他强大的功能,自行查找相关知识吧。
除了 ffmpeg 可以一行代码下载需要的视频,还有另一样神器:you-get,只需要执行命令:“you-get 视频url”。只支持部分视频网站(youtube,腾讯视频,b站等)。
报出SNIMissingWarning和InsecurePlatformWarning警告
解决方法:pip install pyopenssl ndg-httpsclient pyasn1