hs-m3u8 0.1.2__tar.gz → 0.1.4__tar.gz
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-0.1.2 → hs_m3u8-0.1.4}/PKG-INFO +10 -3
- {hs_m3u8-0.1.2 → hs_m3u8-0.1.4}/README.md +7 -0
- {hs_m3u8-0.1.2 → hs_m3u8-0.1.4}/pyproject.toml +4 -3
- {hs_m3u8-0.1.2 → hs_m3u8-0.1.4}/requirements-dev.lock +37 -0
- hs_m3u8-0.1.4/src/example/kyt.py +41 -0
- hs_m3u8-0.1.4/src/example/siyuanren.py +14 -0
- hs_m3u8-0.1.4/src/example/x.py +16 -0
- hs_m3u8-0.1.4/src/hs_m3u8/__init__.py +3 -0
- {hs_m3u8-0.1.2 → hs_m3u8-0.1.4}/src/hs_m3u8/main.py +35 -44
- hs_m3u8-0.1.2/src/hs_m3u8/__init__.py +0 -3
- {hs_m3u8-0.1.2 → hs_m3u8-0.1.4}/.gitignore +0 -0
- {hs_m3u8-0.1.2 → hs_m3u8-0.1.4}/.pre-commit-config.yaml +0 -0
- {hs_m3u8-0.1.2 → hs_m3u8-0.1.4}/requirements.lock +0 -0
- {hs_m3u8-0.1.2 → hs_m3u8-0.1.4}/res/ffmpeg_win.exe.zip +0 -0
- {hs_m3u8-0.1.2 → hs_m3u8-0.1.4}/src/example/__init__.py +0 -0
- {hs_m3u8-0.1.2 → hs_m3u8-0.1.4}/src/example/jav_1.py +0 -0
- {hs_m3u8-0.1.2 → hs_m3u8-0.1.4}/src/example/jav_2.py +0 -0
- {hs_m3u8-0.1.2 → hs_m3u8-0.1.4}/src/example/jav_3.py +0 -0
- {hs_m3u8-0.1.2 → hs_m3u8-0.1.4}/src/example/movie_1.py +0 -0
- {hs_m3u8-0.1.2 → hs_m3u8-0.1.4}/src/example/movie_2.py +0 -0
|
@@ -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包安装
|
|
@@ -10,6 +10,13 @@ m3u8 视频下载工具。支持大部分的m3u8视频下载。后续增加UI界
|
|
|
10
10
|
- 可选择保留ts文件
|
|
11
11
|
- 内置Windows平台ffmpeg可执行文件(由于Linux及Mac下权限问题,需自行安装ffmpeg文件)
|
|
12
12
|
|
|
13
|
+
## 计划
|
|
14
|
+
|
|
15
|
+
- 增加cli功能,通过终端执行命令去下载
|
|
16
|
+
- 增加支持curl参数功能。直接在curl里面读取请求头及cookie
|
|
17
|
+
- 编写详细文档
|
|
18
|
+
- 选择一个合适的技术栈,增加UI界面
|
|
19
|
+
|
|
13
20
|
## 安装
|
|
14
21
|
|
|
15
22
|
### pip包安装
|
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
[project]
|
|
2
2
|
name = "hs-m3u8"
|
|
3
|
-
version = "0.1.
|
|
3
|
+
version = "0.1.4"
|
|
4
4
|
description = "m3u8 下载器"
|
|
5
5
|
authors = [
|
|
6
6
|
{ name = "昊色居士", email = "xhrtxh@gmail.com" }
|
|
@@ -42,6 +42,7 @@ build-backend = "hatchling.build"
|
|
|
42
42
|
managed = true
|
|
43
43
|
dev-dependencies = [
|
|
44
44
|
"pre-commit>=4.0.1",
|
|
45
|
+
"twine>=6.0.1",
|
|
45
46
|
]
|
|
46
47
|
include = [
|
|
47
48
|
"src/hs_m3u8/"
|
|
@@ -136,8 +137,8 @@ unfixable = []
|
|
|
136
137
|
|
|
137
138
|
|
|
138
139
|
[tool.rye.scripts]
|
|
139
|
-
publish_testpypi = { cmd = "rye
|
|
140
|
-
publish_pypi = { cmd = "rye
|
|
140
|
+
publish_testpypi = { cmd = "rye run twine upload -r testpypi dist/*" }
|
|
141
|
+
publish_pypi = { cmd = "rye run twine upload dist/*" }
|
|
141
142
|
sb = { cmd = "rye build --clean" }
|
|
142
143
|
spt = { chain = ["sb", "publish_testpypi"] }
|
|
143
144
|
sp = { chain = ["sb", "publish_pypi"] }
|
|
@@ -46,6 +46,8 @@ datarecorder==3.6.2
|
|
|
46
46
|
# via downloadkit
|
|
47
47
|
distlib==0.3.9
|
|
48
48
|
# via virtualenv
|
|
49
|
+
docutils==0.21.2
|
|
50
|
+
# via readme-renderer
|
|
49
51
|
downloadkit==2.0.5
|
|
50
52
|
# via drissionpage
|
|
51
53
|
drissionpage==4.1.0.12
|
|
@@ -84,8 +86,16 @@ idna==3.10
|
|
|
84
86
|
# via requests
|
|
85
87
|
# via tldextract
|
|
86
88
|
# via yarl
|
|
89
|
+
jaraco-classes==3.4.0
|
|
90
|
+
# via keyring
|
|
91
|
+
jaraco-context==6.0.1
|
|
92
|
+
# via keyring
|
|
93
|
+
jaraco-functools==4.1.0
|
|
94
|
+
# via keyring
|
|
87
95
|
jmespath==1.0.1
|
|
88
96
|
# via parsel
|
|
97
|
+
keyring==25.6.0
|
|
98
|
+
# via twine
|
|
89
99
|
loguru==0.7.2
|
|
90
100
|
# via hssp
|
|
91
101
|
lxml==5.3.0
|
|
@@ -93,9 +103,18 @@ lxml==5.3.0
|
|
|
93
103
|
# via parsel
|
|
94
104
|
m3u8==6.0.0
|
|
95
105
|
# via hs-m3u8
|
|
106
|
+
markdown-it-py==3.0.0
|
|
107
|
+
# via rich
|
|
108
|
+
mdurl==0.1.2
|
|
109
|
+
# via markdown-it-py
|
|
110
|
+
more-itertools==10.6.0
|
|
111
|
+
# via jaraco-classes
|
|
112
|
+
# via jaraco-functools
|
|
96
113
|
multidict==6.1.0
|
|
97
114
|
# via aiohttp
|
|
98
115
|
# via yarl
|
|
116
|
+
nh3==0.2.20
|
|
117
|
+
# via readme-renderer
|
|
99
118
|
nodeenv==1.9.1
|
|
100
119
|
# via pre-commit
|
|
101
120
|
openpyxl==3.1.5
|
|
@@ -104,8 +123,11 @@ orderedmultidict==1.0.1
|
|
|
104
123
|
# via furl
|
|
105
124
|
packaging==24.2
|
|
106
125
|
# via parsel
|
|
126
|
+
# via twine
|
|
107
127
|
parsel==1.9.1
|
|
108
128
|
# via hssp
|
|
129
|
+
pkginfo==1.12.0
|
|
130
|
+
# via twine
|
|
109
131
|
platformdirs==4.3.6
|
|
110
132
|
# via virtualenv
|
|
111
133
|
pre-commit==4.0.1
|
|
@@ -124,6 +146,9 @@ pydantic-core==2.23.4
|
|
|
124
146
|
# via pydantic
|
|
125
147
|
pydantic-settings==2.6.1
|
|
126
148
|
# via hssp
|
|
149
|
+
pygments==2.19.1
|
|
150
|
+
# via readme-renderer
|
|
151
|
+
# via rich
|
|
127
152
|
python-dotenv==1.0.1
|
|
128
153
|
# via pydantic-settings
|
|
129
154
|
pytz==2024.2
|
|
@@ -131,14 +156,24 @@ pytz==2024.2
|
|
|
131
156
|
pyyaml==6.0.2
|
|
132
157
|
# via pre-commit
|
|
133
158
|
# via pydantic-settings
|
|
159
|
+
readme-renderer==44.0
|
|
160
|
+
# via twine
|
|
134
161
|
requests==2.32.3
|
|
135
162
|
# via downloadkit
|
|
136
163
|
# via drissionpage
|
|
137
164
|
# via hssp
|
|
138
165
|
# via requests-file
|
|
166
|
+
# via requests-toolbelt
|
|
139
167
|
# via tldextract
|
|
168
|
+
# via twine
|
|
140
169
|
requests-file==2.1.0
|
|
141
170
|
# via tldextract
|
|
171
|
+
requests-toolbelt==1.0.0
|
|
172
|
+
# via twine
|
|
173
|
+
rfc3986==2.0.0
|
|
174
|
+
# via twine
|
|
175
|
+
rich==13.9.4
|
|
176
|
+
# via twine
|
|
142
177
|
six==1.16.0
|
|
143
178
|
# via apscheduler
|
|
144
179
|
# via furl
|
|
@@ -152,6 +187,7 @@ tldextract==5.1.3
|
|
|
152
187
|
# via drissionpage
|
|
153
188
|
tomli==2.1.0
|
|
154
189
|
# via pydantic-settings
|
|
190
|
+
twine==6.0.1
|
|
155
191
|
typing-extensions==4.12.2
|
|
156
192
|
# via curl-cffi
|
|
157
193
|
# via pydantic
|
|
@@ -160,6 +196,7 @@ tzlocal==5.2
|
|
|
160
196
|
# via apscheduler
|
|
161
197
|
urllib3==2.2.3
|
|
162
198
|
# via requests
|
|
199
|
+
# via twine
|
|
163
200
|
uvloop==0.21.0
|
|
164
201
|
# via hssp
|
|
165
202
|
virtualenv==20.27.1
|
|
@@ -0,0 +1,41 @@
|
|
|
1
|
+
import asyncio
|
|
2
|
+
|
|
3
|
+
from hs_m3u8 import M3u8Downloader, M3u8Key
|
|
4
|
+
|
|
5
|
+
|
|
6
|
+
async def main():
|
|
7
|
+
url = "https://r1-ndr-private.ykt.cbern.com.cn/edu_product/esp/assets/68b6bed7-d093-7c8c-9133-95cf8205d21d.t/zh-CN/1712805650284/transcode/videos/68b6bed7-d093-7c8c-9133-95cf8205d21d-1920x1080-true-47fe81c5c8d91daf25e9fffd7082f934-eed6db5a85074b6dbbe5fa71f1243b26.m3u8"
|
|
8
|
+
name = "ykt"
|
|
9
|
+
headers = {
|
|
10
|
+
"Accept": "*/*",
|
|
11
|
+
"Accept-Language": "zh-CN,zh;q=0.9,en-US;q=0.8,en;q=0.7",
|
|
12
|
+
"Cache-Control": "no-cache",
|
|
13
|
+
"Connection": "keep-alive",
|
|
14
|
+
"Origin": "https://basic.smartedu.cn",
|
|
15
|
+
"Pragma": "no-cache",
|
|
16
|
+
"Referer": "https://basic.smartedu.cn/",
|
|
17
|
+
"Sec-Fetch-Dest": "empty",
|
|
18
|
+
"Sec-Fetch-Mode": "cors",
|
|
19
|
+
"Sec-Fetch-Site": "cross-site",
|
|
20
|
+
"User-Agent": "Mozilla/5.0 (Windows NT 10.0; Win64; x64) AppleWebKit/537.36 (KHTML, like Gecko) "
|
|
21
|
+
"Chrome/126.0.0.0 Safari/537.36",
|
|
22
|
+
"X-ND-AUTH": 'MAC id="7F938B205F876FC3C7550081F114A1A4028222C3BFB978FD9B439192D004CB8EEB65E66BB'
|
|
23
|
+
'C63E66FED6DD51F34F99411A6039E623E9A9D05",nonce="1742462574752:Z4IGAAV6"'
|
|
24
|
+
',mac="EUr56dXrCO1YGd3Ub1fj9MyJY9NxQPi7ZI14N/GFwpQ="',
|
|
25
|
+
"sec-ch-ua": '"Not/A)Brand";v="8", "Chromium";v="126", "Google Chrome";v="126"',
|
|
26
|
+
"sec-ch-ua-mobile": "?0",
|
|
27
|
+
"sec-ch-ua-platform": '"Windows"',
|
|
28
|
+
}
|
|
29
|
+
key = M3u8Key(key=bytes.fromhex("34623235336163353939353834643437"))
|
|
30
|
+
dl = M3u8Downloader(
|
|
31
|
+
m3u8_url=url,
|
|
32
|
+
save_path=f"../../downloads/{name}",
|
|
33
|
+
max_workers=64,
|
|
34
|
+
headers=headers,
|
|
35
|
+
key=key,
|
|
36
|
+
)
|
|
37
|
+
await dl.run(del_hls=False, merge=True)
|
|
38
|
+
|
|
39
|
+
|
|
40
|
+
if __name__ == "__main__":
|
|
41
|
+
asyncio.run(main())
|
|
@@ -0,0 +1,14 @@
|
|
|
1
|
+
import asyncio
|
|
2
|
+
|
|
3
|
+
from hs_m3u8 import M3u8Downloader
|
|
4
|
+
|
|
5
|
+
|
|
6
|
+
async def main():
|
|
7
|
+
url = "http://1251107588.vod2.myqcloud.com/40f34e4dvodtransgzp1251107588/722ec94f387702303749471522/voddrm.token.eyJhbGciOiJIUzI1NiIsInR5cCI6IkpXVCJ9~eyJ0eXBlIjoiRHJtVG9rZW4iLCJhcHBJZCI6MTI1MTEwNzU4OCwiZmlsZUlkIjoiMzg3NzAyMzAzNzQ5NDcxNTIyIiwiY3VycmVudFRpbWVTdGFtcCI6MCwiZXhwaXJlVGltZVN0YW1wIjoyMTQ3NDgzNjQ3LCJyYW5kb20iOjAsIm92ZXJsYXlLZXkiOiIiLCJvdmVybGF5SXYiOiIiLCJjaXBoZXJlZE92ZXJsYXlLZXkiOiI0NTQ2NzliYjY5Zjg1N2M3NGZjY2YxOGM1ZTk4ZjQyMjk4YTMxMzhkYjgyMTNkZThmYjA4ZDM2MzY0MjQ0NjQ3OWFhYmZlYjk0MmYzZTE1MWNkZjM2OGQ5NGEwMzhlNjI4YjlmMTM1OGM4ODkwMjlkMzcwMjZiYzQ3MTY0MGViMWI5ODExYTg5MWU1ZmYxODk3MjliNGIyOWU4ZWExZjNkZWNkZWJmYTVjZmY0N2U4YzBjYjU4OGE2MWUxZmMzNzNmZWQxYWZhOGU5MmJmMGQ4YjFmNjIwMjM4YWJjMzYyM2FlYWVjYjlkNDI3MmI2ZmMzMmRmNjBlN2VmYjc1NjkzIiwiY2lwaGVyZWRPdmVybGF5SXYiOiIwMTYxOTE2YmI5NWRjOWJlZDgyNmI5NmE0MTY1OThkM2IyZmYyYzJmM2JhZDVmNzg2NDEyMWFlYjJiMWVmNjQwNzg0NmNmZmI5YjkyN2E1YzFlMTFhMGE0MDNlMzg5MWE4Y2VkMTJhMTYyNWRlZDFlYWYwMmJkNTI2ZGFjZWE2OGRjNjc1NTE5ZmE3MjBhYTcxNGU3MGE2MjdhM2IwM2I0YzFjMWJjNjJmODYxMDAyN2M3ZjhlYzc4MTg1MDhlNDc0YTJiZmM3NTZjOTVmMWY3NGMxYWQ5NTkyNjBhZTM0NDczNDA4OWZlMGY5YWIzZmYwYzQxZTFlNGNjYWE4YjcxIiwia2V5SWQiOjEsInN0cmljdE1vZGUiOjAsInBlcnNpc3RlbnQiOiIiLCJyZW50YWxEdXJhdGlvbiI6MCwiZm9yY2VMMVRyYWNrVHlwZXMiOm51bGx9~OvCFF2x41XbwQK_C1fUE6Orr77BeCttlt6-H5Uj8izg.video_1407500_1.m3u8?encdomain=cmv1&sign=b574e853145eeb93fcec3b26d28b9665&t=7fffffff"
|
|
8
|
+
name = "siyuanren"
|
|
9
|
+
dl = M3u8Downloader(m3u8_url=url, save_path=f"../../downloads/{name}", max_workers=64)
|
|
10
|
+
await dl.run(del_hls=False, merge=True)
|
|
11
|
+
|
|
12
|
+
|
|
13
|
+
if __name__ == "__main__":
|
|
14
|
+
asyncio.run(main())
|
|
@@ -0,0 +1,16 @@
|
|
|
1
|
+
import asyncio
|
|
2
|
+
|
|
3
|
+
from hs_m3u8 import M3u8Downloader
|
|
4
|
+
|
|
5
|
+
|
|
6
|
+
async def main():
|
|
7
|
+
url = (
|
|
8
|
+
"https://video.twimg.com/ext_tw_video/1879556885663342592/pu/pl/Vcvv0UK9lOhezJt1.m3u8?variant_version=1&tag=12"
|
|
9
|
+
)
|
|
10
|
+
name = "x"
|
|
11
|
+
dl = M3u8Downloader(m3u8_url=url, save_path=f"../../downloads/{name}", max_workers=64)
|
|
12
|
+
await dl.run(del_hls=False, merge=True)
|
|
13
|
+
|
|
14
|
+
|
|
15
|
+
if __name__ == "__main__":
|
|
16
|
+
asyncio.run(main())
|
|
@@ -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)
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|