使⽤Python下载本地的m3u8⽂件
1. 起因
最近有个朋友给我发了个⼩电影。
这边先简单解释⼀下什么是m3u8的视频格式。
根据的解释,
是 版本的 ,⽤ UTF-8 编码。"M3U" 和 "M3U8" ⽂件都是苹果公司使⽤的 协议格式的基础,这种协议格式可以在 iPhone 和 Macbook 等设备播放。
我们使⽤Visual Studio Code或者其他编辑器打开m3u8⽂件,我们可以⽐较清楚地看到,其实就是⼀个utf-8编码的播放列表。
我们使⽤播放器播放的时候,实际上是在加载这些地址的视频⽚段。当我们直接访问这些链接的时候也是可以访问的。但是这样看起来依旧不太舒服,我们已经习惯了看⼀整段MP4的视频,并且还想把它存到本地⾃⼰搭建的NAS⾥,这样就可以长期播放了不是嘛。
2. 经过
为了⽅便,我先是下载了⼀些下载站⾥的m3u8下载器,但是免费的东西总是会有⼀定的局限的。这些下载⼯具虽然UI做的精美,但是尝试了⼏个之后发现根本不能⽤。其中有⼀个倒是成功把每个⽚段的⽂件下载下来了。然后再进⾏合并的时候,根本没有合并成功,却把之前下载的⽂件全删光了。
要不,还是⾃⼰实现吧。
chrome直接下载由于只是实现⼀个⼩功能,并没有什么性能上的要求,因此决定使⽤python快速解决。
突然,我在pypi上发现了⼀个神奇的库。
1import m3u8_to_mp4
2
3m3u8_to_mp4.download('videoserver/playlist.m3u8',tmpdir='/tmp/m3u8_xx')
根据提⽰,只需要两句话就能够把视频⽂件下载下来,并且使⽤ffmpeg(该包需要在电脑上先配置ffmpeg, 即执⾏指令"ffmpeg -version"有结果)将其打包成⼀个Mp4⽂件。
于是我兴致冲冲地⽤了⼀下,但是结果令⼈⼼凉。果然,轮⼦还是得⾃⼰造才⾏。
3. 造轮⼦
造轮⼦也不是从种树开始的,有⽅便的⼯具尽管可以使⽤,这边使⽤的Pypi⾥⾯的m3u8这个库,可以很快解析m3u8⽂件内容,同时还使⽤了pycryptodom这个库中的AES解密(实际上在本次案例中并没有⽤到)。
1# encoding=utf-8
2import m3u8
3import requests
4import datetime
5import os
6from Crypto.Cipher import AES
7from Crypto import Random
8import glob
9# Request header, not necessary, see website change
10headers = {
11    "User-Agent": "Mozilla/5.0 (Windows NT 10.0; WOW64) AppleWebKit/537.36 (KHTML, like Gecko) Chrome/86.0.4240.198 Safari/537.36" 12}
13
14
15
16def download(ts_urls, download_path, keys=[]):
17    if not ists(download_path):
18        os.mkdir(download_path)
19
20    decrypt = True
21    if len(keys == 0) or keys[0] is None:  # m3u8 will get [None] if not key or []
22        decrypt = False
23
24    for i in range(len(ts_urls)):
25        ts_url = ts_urls[i]
26        file_name = ts_url.uri
27        print("start download %s" %file_name)
28        start = w().replace(microsecond=0)
29        try:
30            response = (file_name, stream=True, verify=False)
31        except Exception as e:
32            print(e)
33            return
34
35        ts_path = download_path+"/{0}.ts".format(i)
36        if decrypt:
37            key = keys[i]
38            iv = w().read(AES.block_size)
39            cryptor = de('utf-8'), AES.MODE_CBC)
40
41        with open(ts_path,"wb+") as file:
42            for chunk in response.iter_content(chunk_size=1024):
43                if chunk:
44                    if decrypt:
45                        file.write(cryptor.decrypt(chunk))
46                    else:
47                        file.write(chunk)
48
49        end = w().replace(microsecond=0)
50        print("total time:%s"%(end-start))
51
52
53def merge_to_mp4(dest_file, source_path, delete=False):
54    with open(dest_file, 'wb') as fw:
55        files = glob.glob(source_path + '/*.ts')
56        for file in files:
57            with open(file, 'rb') as fr:
58                fw.ad())
59                print(f'\r{file} Merged! Total:{len(files)}', end="    ")
60            if delete:
61                os.remove(file)
62
63
64if __name__ == "__main__":
65    url = "test.m3u8"
66    video = m3u8.load(url)
67    print(video.data)
68    download(video.segments, 'tmp', video.keys)
69    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⽂件时要按照⼀定的顺序保存,合并时也使⽤该顺序,否则就会有⼀种乱序插⼊的感觉。

版权声明:本站内容均来自互联网,仅供演示用,请勿用于商业和其他非法用途。如果侵犯了您的权益请与我们联系QQ:729038198,我们将在24小时内删除。