python M3U8转换为MP4

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())

你可能感兴趣的:(python)