import os
import asyncio
import random
import shutil
import aiohttp
import ffmpeg
from concurrent.futures import ThreadPoolExecutor
# M3U8 文件的 URL
# m3u8_url = '********************.m3u8'
# 临时存储 TS 文件的文件夹
ts_folder = 'ts_files'
os.makedirs(ts_folder, exist_ok=True)
# 输出 MP4 文件的路径
output_mp4 = 'zu'
os.makedirs(output_mp4, exist_ok=True)
#拿到首页视频列表的id,再去请求得到具体视频的m3u8
async def get_video_id(page=1):
proxy = 'http://127.0.0.1:7890'
headers = {
'User-Agent': 'Mozilla/5.0 (Windows NT 10.0; Win64; x64) AppleWebKit/537.36 (KHTML, like Gecko) Chrome/84.0.4147.105 Safari/537.36'
}
url = "***********"
params = {
"lang": 'en',
'o': 't',
'u': 'f554f525-2d1e-4cac-b69a-f75f16be030d',
'page': page
}
async with aiohttp.ClientSession() as session:
async with session.get(url=url, params=params,headers=headers,ssl=False,proxy=proxy) as response:
response = await response.json()
videoList = response.get('data').get('videoList')
return videoList
#得到具体的视频的m3u8
async def get_video_url(video_id = "a02c6df4-32a5-4d0d-826a-9886cecae10a"):
proxy = 'http://127.0.0.1:7890'
headers = {
'User-Agent': 'Mozilla/5.0 (Windows NT 10.0; Win64; x64) AppleWebKit/537.36 (KHTML, like Gecko) Chrome/84.0.4147.105 Safari/537.36'
}
url = ***************"
params = {
"lang": 'en',
'v': video_id,
'u': 'f554f525-2d1e-4cac-b69a-f75f16be030d',
'fp': 'd8d2ba9595598a6486883d66791e1f11'
}
async with aiohttp.ClientSession() as session:
async with session.get(url=url, params=params,headers=headers,ssl=False,proxy=proxy) as response:
response = await response.json()
video_url = response.get('data').get('v')
m3u8_url = "https://ns" + str(video_url.get('l')) + ".zmq71.site/" + video_url.get('url')
return m3u8_url
#下载 M3U8 文件并提取所有 TS 文件的 URL
async def download_m3u8(m3u8_url):
async with aiohttp.ClientSession() as session:
async with session.get(m3u8_url) as response:
response_text = await response.text()
ts_urls = []
# 分析 M3U8 内容,提取 TS 文件的 URL
for line in response_text.splitlines():
if line.endswith('.ts'):
if line.startswith('http'):
ts_urls.append(line) # 完整的 URL
else:
ts_urls.append(m3u8_url.rsplit('/', 1)[0] + '/' + line) # 拼接相对路径
return ts_urls
#下载单个 TS 文件
async def download_single_ts(session, ts_url, ts_file_path, retries=3, delay=1):
headers = {
'User-Agent': 'Mozilla/5.0 (Windows NT 10.0; Win64; x64) AppleWebKit/537.36 (KHTML, like Gecko) Chrome/84.0.4147.105 Safari/537.36'
}
for attempt in range(retries + 1): # 包括初次尝试在内
try:
async with session.get(ts_url,headers=headers) as response:
response.raise_for_status() # 检查 HTTP 响应状态码,如果有问题抛出异常
with open(ts_file_path, 'wb') as f:
while chunk := await response.content.read(8192):
f.write(chunk)
print(f"下载成功:{ts_url}")
return # 成功后退出函数
except aiohttp.ClientError as e:
print(f"HTTP 请求错误:{ts_url} - {e}")
except Exception as e:
print(f"下载时发生错误:{ts_url} - {e}")
# 重试逻辑
if attempt < retries:
sleep_time = delay * (2 ** attempt) + random.uniform(0, 1) # 指数退避并加入随机抖动
print(f"将在 {sleep_time:.2f} 秒后重试(第 {attempt + 1}/{retries} 次)...")
await asyncio.sleep(sleep_time)
else:
print(f"下载失败:{ts_url},已重试 {retries} 次。")
#下载所有 TS 文件
#下载所有 TS 文件
async def download_ts(session, ts_urls):
ts_files = []
tasks = []
# 确保 ts_files 文件夹存在
os.makedirs(ts_folder, exist_ok=True) # 创建文件夹,如果文件夹已存在则不会报错
# 遍历每个 TS 文件 URL,创建任务并将 session 传递给下载函数
for i, ts_url in enumerate(ts_urls):
ts_file_path = os.path.join(ts_folder, f'file_{i}.ts')
ts_files.append(ts_file_path)
tasks.append(download_single_ts(session, ts_url, ts_file_path)) # 将 session 传递给下载函数
# 等待所有任务完成
await asyncio.gather(*tasks)
return ts_files
# 合并 TS 文件并转换为 MP4
def convert_to_mp4(ts_files, output_mp4):
# 使用 FFmpeg 合并 TS 文件并转换为 MP4
input_files = '|'.join(ts_files)
# vcodec='h264_amf' ffmpeg -encoders | grep amf
ffmpeg.input(f'concat:{input_files}').output(output_mp4).run()
# 主程序
async def main():
try:
# 1. 获取视频 ID 列表
print("获取视频 ID 列表...")
v_l = await get_video_id(1)
print(f"视频 ID 列表: {v_l}")
# 2. 获取 M3U8 URL
for video_info in v_l:
video_id = video_info.get('i') # 获取视频 ID
video_name = video_info.get('t') # 获取视频名称(假设键为 'n')
print(f"处理视频:ID={video_id}, 名称={video_name}")
# 动态生成输出路径
video_output_dir = os.path.join(output_mp4, video_id)
# 检查目录是否存在且不为空
if os.path.exists(video_output_dir) and os.listdir(video_output_dir):
print(f"视频 {video_id} 的输出目录已存在且不为空,跳过转换。")
continue
m3u8_url = await get_video_url(video_id)
# 示例 M3U8 URL(此处应从 get_video_url 函数返回)
print(f"M3U8 URL: {m3u8_url}")
# 3. 下载 TS 文件
print("开始下载 M3U8 文件...")
ts_urls = await download_m3u8(m3u8_url)
print(f"TS 文件 URLs: {ts_urls}")
async with aiohttp.ClientSession() as session:
print("开始下载 TS 文件...")
ts_files = await download_ts(session, ts_urls) # 将 session 传入
os.makedirs(video_output_dir, exist_ok=True)
output_mp4_path = os.path.join(video_output_dir, f"{video_name}.mp4")
# 4. 转换为 MP4
print(f"开始转换为 MP4,输出路径:{output_mp4_path}")
try:
with ThreadPoolExecutor() as executor:
loop = asyncio.get_event_loop()
await loop.run_in_executor(executor, convert_to_mp4, ts_files, output_mp4_path)
except Exception as e:
print(f"转换时发生错误:{e}")
continue
# 5. 清理临时文件
try:
print("清理临时文件...")
# 遍历 ts_files 并删除
for ts_file in ts_files:
if os.path.exists(ts_file): # 检查文件是否存在
os.remove(ts_file)
# 删除目录及其余下内容(如果有)
if os.path.exists(ts_folder):
shutil.rmtree(ts_folder) # 强制删除目录及内容
print("临时文件清理完成")
except Exception as e:
print(f"清理临时文件时发生错误: {e}")
print(f"转换完成:{output_mp4}")
# 删除临时文件夹
# if os.path.exists(ts_folder):
# os.rmdir(ts_folder)
except Exception as e:
print(f"程序运行时发生错误: {e}")
async def test():
# 示例 M3U8 URL(此处应从 get_video_url 函数返回)
m3u8_url = "****.m3u8"
print(f"M3U8 URL: {m3u8_url}")
video_name = "Dhabi"
# 3. 下载 TS 文件
print("开始下载 M3U8 文件...")
ts_urls = await download_m3u8(m3u8_url)
print(f"TS 文件 URLs: {ts_urls}")
print("开始下载 TS 文件...")
ts_files = await download_ts(ts_urls)
# 动态生成输出路径
video_output_dir = os.path.join(output_mp4, "7d9713e3-9b21-46ee-9eb0-33ff2e704350")
os.makedirs(video_output_dir, exist_ok=True)
output_mp4_path = os.path.join(video_output_dir, f"{video_name}.mp4")
# 4. 转换为 MP4
print(f"开始转换为 MP4,输出路径:{output_mp4_path}")
with ThreadPoolExecutor() as executor:
loop = asyncio.get_event_loop()
await loop.run_in_executor(executor, convert_to_mp4, ts_files, output_mp4_path)
# 5. 清理临时文件
print("清理临时文件...")
for ts_file in ts_files:
os.remove(ts_file)
os.rmdir(ts_folder)
print(f"转换完成:{output_mp4}")
# 删除临时文件夹
# if os.path.exists(ts_folder):
# os.rmdir(ts_folder)
if __name__ == '__main__':
asyncio.run(main())