文章目录
- 1,一些理论
- 获取视频的m3u8地址
- 第一个m3u8
- 第二个m3u8
- 后续步骤
- 2,获取第一个m3u8
- 3,获取第二个m3u8
- 4,m3u8解析
- 5,清空缓存文件夹
- 6,下载ts文件
- 目前全部代码
- 8,ts文件解密
- 9,ts文件合并转码
- 全部代码
- 总结
ffmpeg能够下载m3u8,但很慢。
它是下一段拼接一段,不能多线程。
python调用ffmpeg:
import os
# 下载地址,文件名
url = "https://iqiyi.sd-play.com/20211007/KGTJvkvQ/index.m3u8"
name = "a1"
# 调用cmd
os.system(f"ffmpeg -i {url} -c copy -bsf:a aac_adtstoasc ./{name}.mp4")
打开开发者工具,切换到网络面板,搜索m3u8。
然后刷新页面。就可以看到m3u8的地址了。
作用是找到第二个m3u8。
有两个比较重要的部分:
#EXT-X-KEY那一行存储了加密相关的信息,比如方式是aes128,密钥在那个链接里面。
ts文件是一个短视频,解密完毕后,可以直接播放。
合并,转码成mp4。用ffmpeg就可以做到。
发请求就行。
import requests
url1 = "https://iqiyi.sd-play.com"
url2 = "/20211007/KGTJvkvQ/index.m3u8"
# 获取第一个m3u8
text = requests.get(url1 + url2).text
print(text)
效果:
因为m3u8有两种,不含ts列表的和含有ts列表的。
我们无法确定之前获取的是哪一个,所以要对内容进行判断。
如果是第一个m3u8,发请求获取第二个。
如果是第二个m3u8,那么不动。
import requests
def step1(str0):
# 一行一行判断
list0 = str0.split("\n")
for index, value in enumerate(list0):
# 区别
if value.startswith("#EXT-X-STREAM-INF"):
# 发请求
return requests.get(url1 + list0[index + 1]).text
# 保持原样
return str0
url1 = "https://iqiyi.sd-play.com"
url2 = "/20211007/KGTJvkvQ/index.m3u8"
# 获取第一个m3u8
text = requests.get(url1 + url2).text
# 获取第二个m3u8
text = step1(text)
print(text)
效果:
获取两部分内容:
import requests
def step1(str0):
# 一行一行判断
list0 = str0.split("\n")
for index, value in enumerate(list0):
# 区别
if value.startswith("#EXT-X-STREAM-INF"):
# 发请求
return requests.get(url1 + list0[index + 1]).text
# 保持原样
return str0
def step2(str0):
method0 = None
key0 = None
list0 = []
# 获取加密信息
if str0.__contains__("#EXT-X-KEY"):
for item in str0.split("\n"):
if item.startswith("#EXT-X-KEY"):
method0 = item[item.index("METHOD=") + 7:item.index(",URI")]
key0 = requests.get(item[item.index("\"") + 1: item.rindex("\"")]).text
break
# 获取ts文件列表
for item in str0.split("\n"):
if item.startswith("http"):
list0.append(item)
return {
"method": method0,
"key": key0,
"list": list0
}
url1 = "https://iqiyi.sd-play.com"
url2 = "/20211007/KGTJvkvQ/index.m3u8"
# 获取第一个m3u8
text = requests.get(url1 + url2).text
# 获取第二个m3u8
text = step1(text)
# 解析m3u8
info = step2(text)
print(info)
效果:
删除缓存文件夹,然后重新创建。
def rebuildTemp():
if os.path.exists("temp0"):
shutil.rmtree("temp0")
os.mkdir("temp0")
# 删除,创建缓存文件夹
rebuildTemp()
多线程模板:
import time
from concurrent.futures import ThreadPoolExecutor
# 一个任务
def action(amount):
print(amount)
time.sleep(1) # 耗时操作
print(-amount)
# 创建一个包含2条线程的线程池
pool = ThreadPoolExecutor(max_workers=2)
# 向线程池提交两个任务
future1 = pool.submit(action, 1)
future2 = pool.submit(action, 2)
# 等全部结束
pool.shutdown(wait=True)
print("Q")
下载一个:
def downOne(index, url):
data = requests.get(url).content
file = open(f"./temp0/{index}.ts", "wb")
file.write(data)
file.close()
print(index, end="-")
downOne(0, "https://iqiyi.shanshanku.com/20211007/KGTJvkvQ/1000kb/hls/0QITO9qF.ts")
多线程下载:
def downAll(list0):
pool = ThreadPoolExecutor(max_workers=64)
for index, value in enumerate(list0):
pool.submit(downOne, index, value)
pool.shutdown(wait=True)
效果:
import os
import shutil
from concurrent.futures import ThreadPoolExecutor
import requests
def step1(str0):
list0 = str0.split("\n")
for index, value in enumerate(list0):
if value.startswith("#EXT-X-STREAM-INF"):
return requests.get(url1 + list0[index + 1]).text
return str0
def step2(str0):
method0 = None
key0 = None
list0 = []
if str0.__contains__("#EXT-X-KEY"):
for item in str0.split("\n"):
if item.startswith("#EXT-X-KEY"):
method0 = item[item.index("METHOD=") + 7:item.index(",URI")]
key0 = requests.get(item[item.index("\"") + 1: item.rindex("\"")]).text
break
for item in str0.split("\n"):
if item.startswith("http"):
list0.append(item)
return {
"method": method0,
"key": key0,
"list": list0
}
def rebuildTemp():
if os.path.exists("temp0"):
shutil.rmtree("temp0")
os.mkdir("temp0")
def downOne(index, url):
data = requests.get(url).content
file = open(f"./temp0/{index}.ts", "wb")
file.write(data)
file.close()
print(index, end="-")
def downAll(list0):
pool = ThreadPoolExecutor(max_workers=64)
for index, value in enumerate(list0):
pool.submit(downOne, index, value)
pool.shutdown(wait=True)
# 初始数据
url1 = "https://iqiyi.sd-play.com"
url2 = "/20211007/KGTJvkvQ/index.m3u8"
# 获取第一个m3u8
text = requests.get(url1 + url2).text
print("下载第一个m3u8成功!")
# 获取第二个m3u8
text = step1(text)
print("下载第二个m3u8成功!")
# 解析m3u8
info = step2(text)
print("m3u8解析成功!")
# 删除,创建缓存文件夹
rebuildTemp()
print("目录temp0删除,创建成功!")
# 批量下载
downAll(info.get('list'))
print("下载成功!")
# 结束
print("完成!")
效果:
库名叫:pycryptodome。
这部分比较费脑子,而且加密方式千奇百怪。
我百度找了一段,当前案例能用。
虽然还没看懂。。。
import requests
from Crypto.Cipher import AES
def downOne(index, url, key0):
data = requests.get(url).iter_content(chunk_size=1024)
file = open(f"{index}.ts", "wb")
key1 = bytes(key0, 'utf8')
cryptor = AES.new(key1, AES.MODE_CBC, key1)
for chunk in data:
if chunk:
file.write(cryptor.decrypt(chunk))
file.close()
print(index, end="-")
fileName = 0
fileUrl = "https://iqiyi.shanshanku.com/20211007/KGTJvkvQ/1000kb/hls/0QITO9qF.ts"
key = 'dd38a8dcedfb8fbf'
downOne(fileName, fileUrl, key)
成功的标志,是播放器可以打开ts,而且正常播放。
def downOne(index, url, key):
data = requests.get(url).iter_content(chunk_size=1024)
key0 = bytes(key, 'utf8')
file = open(f"./temp0/{index}.ts", "wb")
cryptor = AES.new(key0, AES.MODE_CBC, key0)
for chunk in data:
if chunk:
file.write(cryptor.decrypt(chunk))
file.close()
print(index, end="-")
def downAll(list0, key):
pool = ThreadPoolExecutor(max_workers=64)
for index, value in enumerate(list0):
pool.submit(downOne, index, value, key)
pool.shutdown(wait=True)
downAll(info.get('list'),info.get('key'))
现在,所有ts都是可以播放的了。
因为文件很多,要把所有ts写在一个txt里,然后交给ffmpeg来处理合并。
def step3(size):
file = open("temp0/info.txt", "w")
index = 0
while index < size:
file.write(f"file '{index}.ts'\n")
index = index + 1
file.close()
step3(len(info.get('list')))
txt是这样的:
然后就可以交给ffmpeg进行合并了。
import os
os.system("cd temp0")
os.system("ffmpeg -f concat -safe 0 -i info.txt -c copy out.mp4")
最终产物:一个可以播放的mp4文件。
"""
内置库
"""
import os
import shutil
import urllib.parse
from concurrent.futures import ThreadPoolExecutor
"""
三方库:
requests
pycryptodome
"""
import requests
from Crypto.Cipher import AES
# 获取第二个m3u8
def step0(str0, url):
url_info = urllib.parse.urlsplit(url)
url = url_info.scheme + "://" + url_info.netloc
list0 = str0.split("\n")
for index, value in enumerate(list0):
if value.startswith("#EXT-X-STREAM-INF"):
return requests.get(url + list0[index + 1]).text
return str0
# 解析m3u8
def step1(str0):
method0 = None
key0 = None
list0 = []
# 加密信息
if str0.__contains__("#EXT-X-KEY"):
for item in str0.split("\n"):
if item.startswith("#EXT-X-KEY"):
method0 = item[item.index("METHOD=") + 7:item.index(",URI")]
key0 = requests.get(item[item.index("\"") + 1: item.rindex("\"")]).text
break
# ts文件列表
for item in str0.split("\n"):
if item.startswith("http"):
list0.append(item)
return {
"method": method0,
"key": key0,
"list": list0
}
# 创建缓存文件夹
def rebuildTemp(str0):
if os.path.exists(str0):
shutil.rmtree(str0)
os.mkdir(str0)
# 一个下载任务
def downOne(index, url, key, temp):
if key is None:
data = requests.get(url).content
file = open(f"./{temp}/{index}.ts", "wb")
file.write(data)
file.close()
print(f'[{index}]', end="")
else:
data = requests.get(url).iter_content(chunk_size=1024)
key0 = bytes(key, 'utf8')
file = open(f"./{temp}/{index}.ts", "wb")
cryptor = AES.new(key0, AES.MODE_CBC, key0)
for chunk in data:
if chunk:
file.write(cryptor.decrypt(chunk))
file.close()
print(f'[{index}]', end="")
# 线程池批量下载
def downAll(list0, key, temp, size):
pool = ThreadPoolExecutor(max_workers=size)
for index, value in enumerate(list0):
pool.submit(downOne, index, value, key, temp)
pool.shutdown(wait=True)
# 准备合并
def step2(size, temp):
file = open(f"{temp}/info.txt", "w")
index = 0
while index < size:
file.write(f"file '{index}.ts'\n")
index = index + 1
file.close()
# 进行合并
def step3(temp, filename):
os.system(f"ffmpeg -f concat -safe 0 -i {temp}/info.txt -c copy {filename}.mp4")
# 删除缓存文件夹
def removeTemp(str0):
if os.path.exists(str0):
shutil.rmtree(str0)
class m3u8:
def __init__(self, kv):
self.url = kv.get('url')
self.temp = kv.get('temp')
self.size = kv.get('size')
self.filename = kv.get('filename')
# 只获取分析结果
def analysis(self):
# 获取第一个m3u8
text = requests.get(self.url).text
# 获取第二个m3u8
text = step0(text, self.url)
# 对内容进行分析
info = step1(text)
return info
# 直接下载
def download(self):
# 获取分析结果
info = self.analysis()
# 创建缓存文件夹
rebuildTemp(self.temp)
# 开始下载
downAll(info.get('list'), info.get('key'), self.temp, self.size)
# 准备合并
step2(len(info.get('list')), self.temp)
# 进行合并
step3(self.temp, self.filename)
# 删除缓存文件夹
removeTemp(self.temp)
m3u8({
"url": "https://iqiyi.sd-play.com/20211007/KGTJvkvQ/index.m3u8",
"temp": "temp0",
"size": 64,
"filename": "abc"
}).download()
优化方案:
请求部分:可以设置超时以及重试。
解密部分,可以补充其他解密方式,让它能处理更多。
下载部分,目前是利用线程池进行多线下载。