使用Python下载本地的m3u8文件

1. 起因

最近有个朋友给我发了个小电影。

地址是https://xxxxxx.m3u8, 当我使用微信直接打开的时候是可以播放的,但是使用edge打开的时候却直接跳转到了下载连接里,无奈,只能下载下来一个m3u8的文件。

这边先简单解释一下什么是m3u8的视频格式。

根据维基百科的解释,

M3U8 是 Unicode 版本的 M3U,用 UTF-8 编码。"M3U" 和 "M3U8" 文件都是苹果公司使用的 HTTP Live Streaming(HLS) 协议格式的基础,这种协议格式可以在 iPhone 和 Macbook 等设备播放。

我们使用Visual Studio Code或者其他编辑器打开m3u8文件,我们可以比较清楚地看到,其实就是一个utf-8编码的播放列表。

 我们使用播放器播放的时候,实际上是在加载这些地址的视频片段。当我们直接访问这些链接的时候也是可以访问的。但是这样看起来依旧不太舒服,我们已经习惯了看一整段MP4的视频,并且还想把它存到本地自己搭建的NAS里,这样就可以长期播放了不是嘛。

2. 经过

        为了方便,我先是下载了一些下载站里的m3u8下载器,但是免费的东西总是会有一定的局限的。这些下载工具虽然UI做的精美,但是尝试了几个之后发现根本不能用。其中有一个倒是成功把每个片段的文件下载下来了。然后再进行合并的时候,根本没有合并成功,却把之前下载的文件全删光了。

 要不,还是自己实现吧。

扫描二维码关注公众号,回复: 15007810 查看本文章

由于只是实现一个小功能,并没有什么性能上的要求,因此决定使用python快速解决。

突然,我在pypi上发现了一个神奇的库。

import m3u8_to_mp4

m3u8_to_mp4.download('http://videoserver.com/playlist.m3u8',tmpdir='/tmp/m3u8_xx')

 根据提示,只需要两句话就能够把视频文件下载下来,并且使用ffmpeg(该包需要在电脑上先配置ffmpeg, 即执行指令"ffmpeg -version"有结果)将其打包成一个Mp4文件。

于是我兴致冲冲地用了一下,但是结果令人心凉。果然,轮子还是得自己造才行。

3. 造轮子

        造轮子也不是从种树开始的,有方便的工具尽管可以使用,这边使用的Pypi里面的m3u8这个库,可以很快解析m3u8文件内容,同时还使用了pycryptodom这个库中的AES解密(实际上在本次案例中并没有用到)。

# encoding=utf-8
import m3u8
import requests
import datetime
import os
from Crypto.Cipher import AES
from Crypto import Random
import glob
# Request header, not necessary, see website change
headers = {
    "User-Agent": "Mozilla/5.0 (Windows NT 10.0; WOW64) AppleWebKit/537.36 (KHTML, like Gecko) Chrome/86.0.4240.198 Safari/537.36"
}



def download(ts_urls, download_path, keys=[]):
    if not os.path.exists(download_path):
        os.mkdir(download_path)

    decrypt = True
    if len(keys == 0) or keys[0] is None:  # m3u8 will get [None] if not key or []
        decrypt = False

    for i in range(len(ts_urls)):
        ts_url = ts_urls[i]
        file_name = ts_url.uri
        print("start download %s" %file_name)
        start = datetime.datetime.now().replace(microsecond=0)
        try:
            response = requests.get(file_name, stream=True, verify=False)
        except Exception as e:
            print(e)
            return

        ts_path = download_path+"/{0}.ts".format(i)
        if decrypt:
            key = keys[i]
            iv = Random.new().read(AES.block_size)
            cryptor = AES.new(key.encode('utf-8'), AES.MODE_CBC)

        with open(ts_path,"wb+") as file:
            for chunk in response.iter_content(chunk_size=1024):
                if chunk:
                    if decrypt:
                        file.write(cryptor.decrypt(chunk))
                    else:
                        file.write(chunk)

        end = datetime.datetime.now().replace(microsecond=0)
        print("total time:%s"%(end-start))


def merge_to_mp4(dest_file, source_path, delete=False):
    with open(dest_file, 'wb') as fw:
        files = glob.glob(source_path + '/*.ts')
        for file in files:
            with open(file, 'rb') as fr:
                fw.write(fr.read())
                print(f'\r{file} Merged! Total:{len(files)}', end="     ")
            if delete:
                os.remove(file)


if __name__ == "__main__":
    url = "test.m3u8"
    video = m3u8.load(url)
    print(video.data)
    download(video.segments, 'tmp', video.keys)
    merge_to_mp4('result.mp4', 'tmp')

我们将步骤分为两部分,(1)为下载ts文件, (2)为合并ts文件为mp4

(1)下载ts文件

        下载ts文件相对简单。根据m3u8库解析出来的地址,我们直接进行request请求获得相应的response。 这时候需要注意的是,如果m3u8解析出来的文件中含有key,说明该文件是通过该key值进行AES加密的,需要对response进行解密后再保存下来,如果没有可以直接保存。

在m3u8库解析m3u8文件中,如果文件不带有key,那么获取到的keys=[None], 它的长度是1,所以不能通过直接判断keys的长度是否为0来决定需不需要解密。

(2)合并成Mp4文件

        合并的步骤比较简单,就是将ts按顺序读取之后写到同一个文件里。这边需要注意的是,在保存ts文件时要按照一定的顺序保存,合并时也使用该顺序,否则就会有一种乱序插入的感觉。

猜你喜欢

转载自blog.csdn.net/u013379032/article/details/120705889
今日推荐