hs-m3u8 0.1.2__py3-none-any.whl → 0.1.4__py3-none-any.whl
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
hs_m3u8/__init__.py
CHANGED
|
@@ -1,3 +1,3 @@
|
|
|
1
|
-
from hs_m3u8.main import M3u8Downloader
|
|
1
|
+
from hs_m3u8.main import M3u8Downloader, M3u8Key
|
|
2
2
|
|
|
3
|
-
__version__ = "0.1.
|
|
3
|
+
__version__ = "0.1.4"
|
hs_m3u8/main.py
CHANGED
|
@@ -8,10 +8,8 @@ import posixpath
|
|
|
8
8
|
import shutil
|
|
9
9
|
import subprocess
|
|
10
10
|
from collections.abc import Callable
|
|
11
|
-
from enum import Enum, auto
|
|
12
11
|
from hashlib import md5
|
|
13
12
|
from pathlib import Path
|
|
14
|
-
from typing import Any
|
|
15
13
|
from urllib.parse import urljoin, urlparse
|
|
16
14
|
from zipfile import ZipFile
|
|
17
15
|
|
|
@@ -46,16 +44,6 @@ def get_ffmpeg():
|
|
|
46
44
|
return ffmpeg_bin
|
|
47
45
|
|
|
48
46
|
|
|
49
|
-
class ContentType(Enum):
|
|
50
|
-
"""
|
|
51
|
-
获取URL数据的,类型枚举
|
|
52
|
-
"""
|
|
53
|
-
|
|
54
|
-
Text = auto()
|
|
55
|
-
Json = auto()
|
|
56
|
-
Bytes = auto()
|
|
57
|
-
|
|
58
|
-
|
|
59
47
|
class M3u8Key:
|
|
60
48
|
"""
|
|
61
49
|
M3u8key
|
|
@@ -66,8 +54,9 @@ class M3u8Key:
|
|
|
66
54
|
:param key: 密钥
|
|
67
55
|
:param iv: 偏移
|
|
68
56
|
"""
|
|
57
|
+
iv = bytes.fromhex(iv[2:]) if iv and iv.startswith("0x") else iv
|
|
69
58
|
self.key = key
|
|
70
|
-
self.iv = iv
|
|
59
|
+
self.iv = iv
|
|
71
60
|
|
|
72
61
|
|
|
73
62
|
class M3u8Downloader:
|
|
@@ -80,6 +69,7 @@ class M3u8Downloader:
|
|
|
80
69
|
ts_url_list: list = []
|
|
81
70
|
ts_path_list: list = []
|
|
82
71
|
ts_key: M3u8Key = None
|
|
72
|
+
mp4_head_hrl: str = None
|
|
83
73
|
m3u8_md5 = ""
|
|
84
74
|
|
|
85
75
|
def __init__(
|
|
@@ -89,6 +79,7 @@ class M3u8Downloader:
|
|
|
89
79
|
decrypt=False,
|
|
90
80
|
max_workers=None,
|
|
91
81
|
headers=None,
|
|
82
|
+
key: M3u8Key = None,
|
|
92
83
|
get_m3u8_func: Callable = None,
|
|
93
84
|
):
|
|
94
85
|
"""
|
|
@@ -112,6 +103,7 @@ class M3u8Downloader:
|
|
|
112
103
|
self.save_dir = Path(save_path) / "hls"
|
|
113
104
|
self.save_name = Path(save_path).name
|
|
114
105
|
self.key_path = self.save_dir / "key.key"
|
|
106
|
+
self.custom_key = key
|
|
115
107
|
|
|
116
108
|
if not self.save_dir.exists():
|
|
117
109
|
self.save_dir.mkdir(parents=True)
|
|
@@ -165,7 +157,7 @@ class M3u8Downloader:
|
|
|
165
157
|
if not merge:
|
|
166
158
|
return True
|
|
167
159
|
|
|
168
|
-
if self.merge():
|
|
160
|
+
if await self.merge():
|
|
169
161
|
self.logger.info("合并成功")
|
|
170
162
|
else:
|
|
171
163
|
self.logger.error(
|
|
@@ -185,30 +177,6 @@ class M3u8Downloader:
|
|
|
185
177
|
self.ts_path_list = [None] * len(self.ts_url_list)
|
|
186
178
|
await asyncio.gather(*[self._download_ts(url) for url in self.ts_url_list])
|
|
187
179
|
|
|
188
|
-
async def get_url_content(self, url: str, content_type: ContentType) -> bytes | str | Any:
|
|
189
|
-
"""
|
|
190
|
-
按照类型获取url内容
|
|
191
|
-
:param url: 请求地址
|
|
192
|
-
:param content_type: 内容类型
|
|
193
|
-
:return:
|
|
194
|
-
"""
|
|
195
|
-
data = None
|
|
196
|
-
try:
|
|
197
|
-
resp = await self.net.get(url, headers=self.headers)
|
|
198
|
-
if content_type == ContentType.Bytes:
|
|
199
|
-
data = resp.content
|
|
200
|
-
if content_type == ContentType.Text:
|
|
201
|
-
data = resp.text
|
|
202
|
-
if content_type == ContentType.Json:
|
|
203
|
-
data = resp.json
|
|
204
|
-
if resp.status_code != 200:
|
|
205
|
-
self.logger.error(f"请求{url}内容时返回码不正确,类型为:{content_type}, 返回码为:{resp.status_code}")
|
|
206
|
-
return None
|
|
207
|
-
except BaseException as exception:
|
|
208
|
-
self.logger.error(f"请求{url}内容时发生异常,类型为:{content_type}, 异常信息为:{exception}")
|
|
209
|
-
|
|
210
|
-
return data
|
|
211
|
-
|
|
212
180
|
async def get_ts_list(self, url) -> list[dict]:
|
|
213
181
|
"""
|
|
214
182
|
解析m3u8并保存至列表
|
|
@@ -235,6 +203,14 @@ class M3u8Downloader:
|
|
|
235
203
|
self.logger.info(f"选择的播放地址:{play_url},比特率:{bandwidth}")
|
|
236
204
|
return await self.get_ts_list(urlparse(play_url))
|
|
237
205
|
|
|
206
|
+
# 处理具有 #EXT-X-MAP:URI="*.mp4" 的情况
|
|
207
|
+
segment_map_count = len(m3u8_obj.segment_map)
|
|
208
|
+
if segment_map_count > 0:
|
|
209
|
+
if segment_map_count > 1:
|
|
210
|
+
raise ValueError("暂不支持segment_map有多个的情况,请提交issues,并告知m3u8的地址,方便做适配")
|
|
211
|
+
self.mp4_head_hrl = prefix + m3u8_obj.segment_map[0].uri
|
|
212
|
+
m3u8_obj.segment_map[0].uri = "head.mp4"
|
|
213
|
+
|
|
238
214
|
# 遍历ts文件
|
|
239
215
|
for index, segments in enumerate(m3u8_obj.segments):
|
|
240
216
|
ts_uri = segments.uri if "http" in m3u8_obj.segments[index].uri else segments.absolute_uri
|
|
@@ -243,10 +219,16 @@ class M3u8Downloader:
|
|
|
243
219
|
|
|
244
220
|
# 保存解密key
|
|
245
221
|
if len(m3u8_obj.keys) > 0 and m3u8_obj.keys[0]:
|
|
246
|
-
|
|
247
|
-
|
|
222
|
+
iv = m3u8_obj.keys[0].iv
|
|
223
|
+
if not self.custom_key:
|
|
224
|
+
resp = await self.net.get(m3u8_obj.keys[0].absolute_uri, headers=self.headers)
|
|
225
|
+
key_data = resp.content
|
|
226
|
+
else:
|
|
227
|
+
key_data = self.custom_key.key
|
|
228
|
+
iv = self.custom_key.iv or iv
|
|
229
|
+
|
|
248
230
|
self.save_file(key_data, self.key_path)
|
|
249
|
-
self.ts_key = M3u8Key(key=key_data, iv=
|
|
231
|
+
self.ts_key = M3u8Key(key=key_data, iv=iv)
|
|
250
232
|
key = m3u8_obj.segments[0].key
|
|
251
233
|
key.uri = "key.key"
|
|
252
234
|
m3u8_obj.segments[0].key = key
|
|
@@ -271,7 +253,7 @@ class M3u8Downloader:
|
|
|
271
253
|
if Path(ts_path).exists():
|
|
272
254
|
self.ts_path_list[index] = str(ts_path)
|
|
273
255
|
return
|
|
274
|
-
resp = await self.net.get(ts_item["uri"])
|
|
256
|
+
resp = await self.net.get(ts_item["uri"], self.headers)
|
|
275
257
|
ts_content = resp.content
|
|
276
258
|
if ts_content is None:
|
|
277
259
|
return
|
|
@@ -283,7 +265,7 @@ class M3u8Downloader:
|
|
|
283
265
|
self.logger.info(f"{ts_uri}下载成功")
|
|
284
266
|
self.ts_path_list[index] = str(ts_path)
|
|
285
267
|
|
|
286
|
-
def merge(self):
|
|
268
|
+
async def merge(self):
|
|
287
269
|
"""
|
|
288
270
|
合并ts文件为mp4文件
|
|
289
271
|
:return:
|
|
@@ -301,8 +283,17 @@ class M3u8Downloader:
|
|
|
301
283
|
# mp4路径
|
|
302
284
|
mp4_path = self.save_dir.parent / f"{self.save_name}.mp4"
|
|
303
285
|
|
|
286
|
+
# 如果保护mp4的头,则把ts放到后面
|
|
287
|
+
mp4_head_data = b""
|
|
288
|
+
if self.mp4_head_hrl:
|
|
289
|
+
resp = await self.net.get(self.mp4_head_hrl)
|
|
290
|
+
mp4_head_data = resp.content
|
|
291
|
+
mp4_head_file = self.save_dir / "head.mp4"
|
|
292
|
+
mp4_head_file.write_bytes(mp4_head_data)
|
|
293
|
+
|
|
304
294
|
# 把ts文件整合到一起
|
|
305
295
|
big_ts_file = big_ts_path.open("ab+")
|
|
296
|
+
big_ts_file.write(mp4_head_data)
|
|
306
297
|
for path in self.ts_path_list:
|
|
307
298
|
with open(path, "rb") as ts_file:
|
|
308
299
|
data = ts_file.read()
|
|
@@ -316,7 +307,7 @@ class M3u8Downloader:
|
|
|
316
307
|
ffmpeg_bin = get_ffmpeg()
|
|
317
308
|
command = (
|
|
318
309
|
f'{ffmpeg_bin} -i "{big_ts_path}" '
|
|
319
|
-
f'-c copy -map 0:v -map 0:a -bsf:a aac_adtstoasc -threads 32 "{mp4_path}" -y'
|
|
310
|
+
f'-c copy -map 0:v -map 0:a? -bsf:a aac_adtstoasc -threads 32 "{mp4_path}" -y'
|
|
320
311
|
)
|
|
321
312
|
self.logger.info(f"ts整合成功,开始转为mp4。 command:{command}")
|
|
322
313
|
result = subprocess.run(command, shell=True, capture_output=True, text=True)
|
|
@@ -1,12 +1,12 @@
|
|
|
1
|
-
Metadata-Version: 2.
|
|
1
|
+
Metadata-Version: 2.4
|
|
2
2
|
Name: hs-m3u8
|
|
3
|
-
Version: 0.1.
|
|
3
|
+
Version: 0.1.4
|
|
4
4
|
Summary: m3u8 下载器
|
|
5
5
|
Project-URL: homepage, https://github.com/x-haose/hs-m3u8
|
|
6
6
|
Project-URL: repository, https://github.com/x-haose/hs-m3u8
|
|
7
7
|
Project-URL: documentation, https://github.com/x-haose/hs-m3u8
|
|
8
8
|
Author-email: 昊色居士 <xhrtxh@gmail.com>
|
|
9
|
-
License: MIT
|
|
9
|
+
License-Expression: MIT
|
|
10
10
|
Classifier: Development Status :: 4 - Beta
|
|
11
11
|
Classifier: Intended Audience :: Developers
|
|
12
12
|
Classifier: License :: OSI Approved :: MIT License
|
|
@@ -36,6 +36,13 @@ m3u8 视频下载工具。支持大部分的m3u8视频下载。后续增加UI界
|
|
|
36
36
|
- 可选择保留ts文件
|
|
37
37
|
- 内置Windows平台ffmpeg可执行文件(由于Linux及Mac下权限问题,需自行安装ffmpeg文件)
|
|
38
38
|
|
|
39
|
+
## 计划
|
|
40
|
+
|
|
41
|
+
- 增加cli功能,通过终端执行命令去下载
|
|
42
|
+
- 增加支持curl参数功能。直接在curl里面读取请求头及cookie
|
|
43
|
+
- 编写详细文档
|
|
44
|
+
- 选择一个合适的技术栈,增加UI界面
|
|
45
|
+
|
|
39
46
|
## 安装
|
|
40
47
|
|
|
41
48
|
### pip包安装
|
|
@@ -0,0 +1,5 @@
|
|
|
1
|
+
hs_m3u8/__init__.py,sha256=WxYMLrnGwdYDEmbxZmUV-l2DPs9jrYDP0cAnHxkqtFs,72
|
|
2
|
+
hs_m3u8/main.py,sha256=RELt4z7AM7OuXYiRkCe-yf4A1JGQ9FtIaBj6Vov3Ig0,11574
|
|
3
|
+
hs_m3u8-0.1.4.dist-info/METADATA,sha256=ddVI5SkdoINuFHSPS9uFGDeOmCybhT1al4_4SmXPHRs,2204
|
|
4
|
+
hs_m3u8-0.1.4.dist-info/WHEEL,sha256=qtCwoSJWgHk21S1Kb4ihdzI2rlJ1ZKaIurTj_ngOhyQ,87
|
|
5
|
+
hs_m3u8-0.1.4.dist-info/RECORD,,
|
hs_m3u8-0.1.2.dist-info/RECORD
DELETED
|
@@ -1,5 +0,0 @@
|
|
|
1
|
-
hs_m3u8/__init__.py,sha256=VWzgd1ZOktIFqMuyNnZFhKltLMFS7g9IOS5FUZXZpoE,63
|
|
2
|
-
hs_m3u8/main.py,sha256=tq-JtMzfEy1YTTpZFL9Oas5ZzrON3eigWxlKAduc-oU,11592
|
|
3
|
-
hs_m3u8-0.1.2.dist-info/METADATA,sha256=e6BAuDJEiq0k_mhn5uuYUpLcW6f6eEw1yocday1-NW0,1979
|
|
4
|
-
hs_m3u8-0.1.2.dist-info/WHEEL,sha256=C2FUgwZgiLbznR-k0b_5k3Ai_1aASOXDss3lzCUsUug,87
|
|
5
|
-
hs_m3u8-0.1.2.dist-info/RECORD,,
|