安装依赖

pip install m3u8 requests

程序

import os
import time
import requests
import m3u8
from urllib.parse import urljoin
from Crypto.Cipher import AES
from Crypto.Util.Padding import pad


def download_m3u8_video(url, save_name='output.mp4'):
    headers = {'User-Agent': 'Mozilla/5.0 (Windows NT 10.0; Win64; x64)'}

    print("📡 正在解析 m3u8 播放列表...")
    try:
        playlist = m3u8.load(uri=url, headers=headers)
    except Exception as e:
        print(f"❌ 解析失败: {e}")
        return

    # 检查是否存在 AES 加密
    key, iv = None, None
    if playlist.keys and playlist.keys[-1] is not None:
        key_info = playlist.keys[-1]
        key_uri = key_info.uri

        # 【核心修复】:如果密钥是相对路径,使用 urljoin 拼接完整的 URL
        if not key_uri.startswith('http'):
            key_uri = urljoin(url, key_uri)

        print(f"🔑 检测到加密,正在获取完整密钥链接: {key_uri}")

        # 获取密钥内容(建议加上与下载视频相同的 headers,防止防盗链拦截)
        key_resp = requests.get(key_uri, headers=headers, timeout=15)
        key_resp.raise_for_status()
        key = key_resp.content

        # 获取 IV 向量,如果没有则默认使用全零的 16 字节
        iv = key_info.iv
        if iv:
            iv = bytes.fromhex(iv.replace('0x', ''))
        else:
            iv = b'\x00' * 16

    total = len(playlist.segments)
    print(f"✅ 解析成功,共 {total} 个片段,开始下载并合并...")
    start_time = time.time()
    size = 0

    with open(save_name, 'wb') as f:
        for i, seg in enumerate(playlist.segments, 1):
            try:
                # 同样确保 TS 分片也是完整 URL
                ts_url = seg.absolute_uri
                r = requests.get(ts_url, headers=headers, timeout=15)
                r.raise_for_status()
                data = r.content

                # 如果有密钥,进行 AES 解密
                if key:
                    cipher = AES.new(key=key, mode=AES.MODE_CBC, iv=iv)
                    data = cipher.decrypt(pad(data, AES.block_size))

                f.write(data)
                size += len(data)

                # 打印进度
                elapsed = time.time() - start_time
                speed = size / elapsed / 1024 / 1024
                print(f"\r⏳ 进度: {i}/{total} | 已下载: {size / 1024 / 1024:.2f}MB | 速度: {speed:.2f}MB/s", end='')

            except Exception as e:
                print(f"\n❌ 第 {i} 个片段下载失败: {e}")
                continue

    print(f"\n🎉 视频已成功保存为: {save_name}")


if __name__ == '__main__':
    M3U8_URL = "xxx"
    download_m3u8_video(M3U8_URL, 'my_video.mp4')

Logo

Agent 垂直技术社区,欢迎活跃、内容共建。

更多推荐