hs-m3u8 0.1.1__tar.gz → 0.1.3__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.1 → hs_m3u8-0.1.3}/PKG-INFO +13 -3
- {hs_m3u8-0.1.1 → hs_m3u8-0.1.3}/README.md +7 -0
- {hs_m3u8-0.1.1 → hs_m3u8-0.1.3}/pyproject.toml +9 -3
- {hs_m3u8-0.1.1 → hs_m3u8-0.1.3}/requirements-dev.lock +37 -0
- {hs_m3u8-0.1.1 → hs_m3u8-0.1.3}/src/hs_m3u8/__init__.py +1 -1
- {hs_m3u8-0.1.1 → hs_m3u8-0.1.3}/src/hs_m3u8/main.py +21 -39
- {hs_m3u8-0.1.1 → hs_m3u8-0.1.3}/.gitignore +0 -0
- {hs_m3u8-0.1.1 → hs_m3u8-0.1.3}/.pre-commit-config.yaml +0 -0
- {hs_m3u8-0.1.1 → hs_m3u8-0.1.3}/requirements.lock +0 -0
- {hs_m3u8-0.1.1 → hs_m3u8-0.1.3}/res/ffmpeg_win.exe.zip +0 -0
- {hs_m3u8-0.1.1 → hs_m3u8-0.1.3}/src/example/__init__.py +0 -0
- {hs_m3u8-0.1.1 → hs_m3u8-0.1.3}/src/example/jav_1.py +0 -0
- {hs_m3u8-0.1.1 → hs_m3u8-0.1.3}/src/example/jav_2.py +0 -0
- {hs_m3u8-0.1.1 → hs_m3u8-0.1.3}/src/example/jav_3.py +0 -0
- {hs_m3u8-0.1.1 → hs_m3u8-0.1.3}/src/example/movie_1.py +0 -0
- {hs_m3u8-0.1.1 → hs_m3u8-0.1.3}/src/example/movie_2.py +0 -0
|
@@ -1,9 +1,12 @@
|
|
|
1
|
-
Metadata-Version: 2.
|
|
1
|
+
Metadata-Version: 2.4
|
|
2
2
|
Name: hs-m3u8
|
|
3
|
-
Version: 0.1.
|
|
3
|
+
Version: 0.1.3
|
|
4
4
|
Summary: m3u8 下载器
|
|
5
|
+
Project-URL: homepage, https://github.com/x-haose/hs-m3u8
|
|
6
|
+
Project-URL: repository, https://github.com/x-haose/hs-m3u8
|
|
7
|
+
Project-URL: documentation, https://github.com/x-haose/hs-m3u8
|
|
5
8
|
Author-email: 昊色居士 <xhrtxh@gmail.com>
|
|
6
|
-
License: MIT
|
|
9
|
+
License-Expression: MIT
|
|
7
10
|
Classifier: Development Status :: 4 - Beta
|
|
8
11
|
Classifier: Intended Audience :: Developers
|
|
9
12
|
Classifier: License :: OSI Approved :: MIT License
|
|
@@ -33,6 +36,13 @@ m3u8 视频下载工具。支持大部分的m3u8视频下载。后续增加UI界
|
|
|
33
36
|
- 可选择保留ts文件
|
|
34
37
|
- 内置Windows平台ffmpeg可执行文件(由于Linux及Mac下权限问题,需自行安装ffmpeg文件)
|
|
35
38
|
|
|
39
|
+
## 计划
|
|
40
|
+
|
|
41
|
+
- 增加cli功能,通过终端执行命令去下载
|
|
42
|
+
- 增加支持curl参数功能。直接在curl里面读取请求头及cookie
|
|
43
|
+
- 编写详细文档
|
|
44
|
+
- 选择一个合适的技术栈,增加UI界面
|
|
45
|
+
|
|
36
46
|
## 安装
|
|
37
47
|
|
|
38
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.3"
|
|
4
4
|
description = "m3u8 下载器"
|
|
5
5
|
authors = [
|
|
6
6
|
{ name = "昊色居士", email = "xhrtxh@gmail.com" }
|
|
@@ -29,6 +29,11 @@ dependencies = [
|
|
|
29
29
|
"hssp>=0.4.4",
|
|
30
30
|
]
|
|
31
31
|
|
|
32
|
+
[project.urls]
|
|
33
|
+
homepage = "https://github.com/x-haose/hs-m3u8"
|
|
34
|
+
repository = "https://github.com/x-haose/hs-m3u8"
|
|
35
|
+
documentation = "https://github.com/x-haose/hs-m3u8"
|
|
36
|
+
|
|
32
37
|
[build-system]
|
|
33
38
|
requires = ["hatchling"]
|
|
34
39
|
build-backend = "hatchling.build"
|
|
@@ -37,6 +42,7 @@ build-backend = "hatchling.build"
|
|
|
37
42
|
managed = true
|
|
38
43
|
dev-dependencies = [
|
|
39
44
|
"pre-commit>=4.0.1",
|
|
45
|
+
"twine>=6.0.1",
|
|
40
46
|
]
|
|
41
47
|
include = [
|
|
42
48
|
"src/hs_m3u8/"
|
|
@@ -131,8 +137,8 @@ unfixable = []
|
|
|
131
137
|
|
|
132
138
|
|
|
133
139
|
[tool.rye.scripts]
|
|
134
|
-
publish_testpypi = { cmd = "rye
|
|
135
|
-
publish_pypi = { cmd = "rye
|
|
140
|
+
publish_testpypi = { cmd = "rye run twine upload -r testpypi dist/*" }
|
|
141
|
+
publish_pypi = { cmd = "rye run twine upload dist/*" }
|
|
136
142
|
sb = { cmd = "rye build --clean" }
|
|
137
143
|
spt = { chain = ["sb", "publish_testpypi"] }
|
|
138
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
|
|
@@ -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
|
|
@@ -80,6 +68,7 @@ class M3u8Downloader:
|
|
|
80
68
|
ts_url_list: list = []
|
|
81
69
|
ts_path_list: list = []
|
|
82
70
|
ts_key: M3u8Key = None
|
|
71
|
+
mp4_head_hrl: str = None
|
|
83
72
|
m3u8_md5 = ""
|
|
84
73
|
|
|
85
74
|
def __init__(
|
|
@@ -165,7 +154,7 @@ class M3u8Downloader:
|
|
|
165
154
|
if not merge:
|
|
166
155
|
return True
|
|
167
156
|
|
|
168
|
-
if self.merge():
|
|
157
|
+
if await self.merge():
|
|
169
158
|
self.logger.info("合并成功")
|
|
170
159
|
else:
|
|
171
160
|
self.logger.error(
|
|
@@ -185,30 +174,6 @@ class M3u8Downloader:
|
|
|
185
174
|
self.ts_path_list = [None] * len(self.ts_url_list)
|
|
186
175
|
await asyncio.gather(*[self._download_ts(url) for url in self.ts_url_list])
|
|
187
176
|
|
|
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
177
|
async def get_ts_list(self, url) -> list[dict]:
|
|
213
178
|
"""
|
|
214
179
|
解析m3u8并保存至列表
|
|
@@ -235,6 +200,14 @@ class M3u8Downloader:
|
|
|
235
200
|
self.logger.info(f"选择的播放地址:{play_url},比特率:{bandwidth}")
|
|
236
201
|
return await self.get_ts_list(urlparse(play_url))
|
|
237
202
|
|
|
203
|
+
# 处理具有 #EXT-X-MAP:URI="*.mp4" 的情况
|
|
204
|
+
segment_map_count = len(m3u8_obj.segment_map)
|
|
205
|
+
if segment_map_count > 0:
|
|
206
|
+
if segment_map_count > 1:
|
|
207
|
+
raise ValueError("暂不支持segment_map有多个的情况,请提交issues,并告知m3u8的地址,方便做适配")
|
|
208
|
+
self.mp4_head_hrl = prefix + m3u8_obj.segment_map[0].uri
|
|
209
|
+
m3u8_obj.segment_map[0].uri = "head.mp4"
|
|
210
|
+
|
|
238
211
|
# 遍历ts文件
|
|
239
212
|
for index, segments in enumerate(m3u8_obj.segments):
|
|
240
213
|
ts_uri = segments.uri if "http" in m3u8_obj.segments[index].uri else segments.absolute_uri
|
|
@@ -283,7 +256,7 @@ class M3u8Downloader:
|
|
|
283
256
|
self.logger.info(f"{ts_uri}下载成功")
|
|
284
257
|
self.ts_path_list[index] = str(ts_path)
|
|
285
258
|
|
|
286
|
-
def merge(self):
|
|
259
|
+
async def merge(self):
|
|
287
260
|
"""
|
|
288
261
|
合并ts文件为mp4文件
|
|
289
262
|
:return:
|
|
@@ -301,8 +274,17 @@ class M3u8Downloader:
|
|
|
301
274
|
# mp4路径
|
|
302
275
|
mp4_path = self.save_dir.parent / f"{self.save_name}.mp4"
|
|
303
276
|
|
|
277
|
+
# 如果保护mp4的头,则把ts放到后面
|
|
278
|
+
mp4_head_data = b""
|
|
279
|
+
if self.mp4_head_hrl:
|
|
280
|
+
resp = await self.net.get(self.mp4_head_hrl)
|
|
281
|
+
mp4_head_data = resp.content
|
|
282
|
+
mp4_head_file = self.save_dir / "head.mp4"
|
|
283
|
+
mp4_head_file.write_bytes(mp4_head_data)
|
|
284
|
+
|
|
304
285
|
# 把ts文件整合到一起
|
|
305
286
|
big_ts_file = big_ts_path.open("ab+")
|
|
287
|
+
big_ts_file.write(mp4_head_data)
|
|
306
288
|
for path in self.ts_path_list:
|
|
307
289
|
with open(path, "rb") as ts_file:
|
|
308
290
|
data = ts_file.read()
|
|
@@ -316,7 +298,7 @@ class M3u8Downloader:
|
|
|
316
298
|
ffmpeg_bin = get_ffmpeg()
|
|
317
299
|
command = (
|
|
318
300
|
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'
|
|
301
|
+
f'-c copy -map 0:v -map 0:a? -bsf:a aac_adtstoasc -threads 32 "{mp4_path}" -y'
|
|
320
302
|
)
|
|
321
303
|
self.logger.info(f"ts整合成功,开始转为mp4。 command:{command}")
|
|
322
304
|
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
|