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.
@@ -1,9 +1,12 @@
1
- Metadata-Version: 2.3
1
+ Metadata-Version: 2.4
2
2
  Name: hs-m3u8
3
- Version: 0.1.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.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 publish --repository testpypi --repository-url https://test.pypi.org/legacy/" }
135
- publish_pypi = { cmd = "rye publish" }
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
@@ -1,3 +1,3 @@
1
1
  from hs_m3u8.main import M3u8Downloader
2
2
 
3
- __version__ = "0.1.1"
3
+ __version__ = "0.1.3"
@@ -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