easyrip 4.5.1__tar.gz → 4.6.1__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.
Files changed (37) hide show
  1. {easyrip-4.5.1 → easyrip-4.6.1}/PKG-INFO +1 -1
  2. {easyrip-4.5.1 → easyrip-4.6.1}/easyrip/__main__.py +1 -1
  3. {easyrip-4.5.1 → easyrip-4.6.1}/easyrip/easyrip_command.py +1 -1
  4. {easyrip-4.5.1 → easyrip-4.6.1}/easyrip/easyrip_config/config.py +87 -22
  5. easyrip-4.6.1/easyrip/easyrip_config/config_key.py +28 -0
  6. {easyrip-4.5.1 → easyrip-4.6.1}/easyrip/easyrip_main.py +30 -41
  7. {easyrip-4.5.1 → easyrip-4.6.1}/easyrip/easyrip_mlang/lang_zh_Hans_CN.py +4 -3
  8. {easyrip-4.5.1 → easyrip-4.6.1}/easyrip/global_val.py +1 -1
  9. {easyrip-4.5.1 → easyrip-4.6.1}/easyrip/ripper/sub_and_font/font.py +5 -5
  10. {easyrip-4.5.1 → easyrip-4.6.1}/easyrip/utils.py +80 -1
  11. {easyrip-4.5.1 → easyrip-4.6.1}/easyrip.egg-info/PKG-INFO +1 -1
  12. easyrip-4.5.1/easyrip/easyrip_config/config_key.py +0 -14
  13. {easyrip-4.5.1 → easyrip-4.6.1}/LICENSE +0 -0
  14. {easyrip-4.5.1 → easyrip-4.6.1}/README.md +0 -0
  15. {easyrip-4.5.1 → easyrip-4.6.1}/easyrip/__init__.py +0 -0
  16. {easyrip-4.5.1 → easyrip-4.6.1}/easyrip/easyrip_log.py +0 -0
  17. {easyrip-4.5.1 → easyrip-4.6.1}/easyrip/easyrip_mlang/__init__.py +0 -0
  18. {easyrip-4.5.1 → easyrip-4.6.1}/easyrip/easyrip_mlang/global_lang_val.py +0 -0
  19. {easyrip-4.5.1 → easyrip-4.6.1}/easyrip/easyrip_mlang/lang_en.py +0 -0
  20. {easyrip-4.5.1 → easyrip-4.6.1}/easyrip/easyrip_mlang/translator.py +0 -0
  21. {easyrip-4.5.1 → easyrip-4.6.1}/easyrip/easyrip_prompt.py +0 -0
  22. {easyrip-4.5.1 → easyrip-4.6.1}/easyrip/easyrip_web/__init__.py +0 -0
  23. {easyrip-4.5.1 → easyrip-4.6.1}/easyrip/easyrip_web/http_server.py +0 -0
  24. {easyrip-4.5.1 → easyrip-4.6.1}/easyrip/easyrip_web/third_party_api.py +0 -0
  25. {easyrip-4.5.1 → easyrip-4.6.1}/easyrip/ripper/media_info.py +0 -0
  26. {easyrip-4.5.1 → easyrip-4.6.1}/easyrip/ripper/param.py +0 -0
  27. {easyrip-4.5.1 → easyrip-4.6.1}/easyrip/ripper/ripper.py +0 -0
  28. {easyrip-4.5.1 → easyrip-4.6.1}/easyrip/ripper/sub_and_font/__init__.py +0 -0
  29. {easyrip-4.5.1 → easyrip-4.6.1}/easyrip/ripper/sub_and_font/ass.py +0 -0
  30. {easyrip-4.5.1 → easyrip-4.6.1}/easyrip/ripper/sub_and_font/subset.py +0 -0
  31. {easyrip-4.5.1 → easyrip-4.6.1}/easyrip.egg-info/SOURCES.txt +0 -0
  32. {easyrip-4.5.1 → easyrip-4.6.1}/easyrip.egg-info/dependency_links.txt +0 -0
  33. {easyrip-4.5.1 → easyrip-4.6.1}/easyrip.egg-info/entry_points.txt +0 -0
  34. {easyrip-4.5.1 → easyrip-4.6.1}/easyrip.egg-info/requires.txt +0 -0
  35. {easyrip-4.5.1 → easyrip-4.6.1}/easyrip.egg-info/top_level.txt +0 -0
  36. {easyrip-4.5.1 → easyrip-4.6.1}/pyproject.toml +0 -0
  37. {easyrip-4.5.1 → easyrip-4.6.1}/setup.cfg +0 -0
@@ -1,6 +1,6 @@
1
1
  Metadata-Version: 2.4
2
2
  Name: easyrip
3
- Version: 4.5.1
3
+ Version: 4.6.1
4
4
  Author: op200
5
5
  License-Expression: AGPL-3.0-or-later
6
6
  Project-URL: Homepage, https://github.com/op200/EasyRip
@@ -103,7 +103,7 @@ def run() -> NoReturn:
103
103
  cmd_ctv_tuple = tuple(ct.value for ct in Cmd_type if ct != Cmd_type.Option)
104
104
  prompt_history = (
105
105
  ConfigFileHistory(easyrip_prompt.PROMPT_HISTORY_FILE)
106
- if config.get_user_profile(Config_key.prompt_history_save_file)
106
+ if config.get_user_profile(Config_key.save_prompt_history)
107
107
  else InMemoryHistory()
108
108
  )
109
109
 
@@ -244,7 +244,7 @@ class Cmd_type(enum.Enum):
244
244
  Cmd_type_val(("list",)),
245
245
  Cmd_type_val(
246
246
  ("set",),
247
- childs=tuple(Cmd_type_val((k,)) for k in Config_key._value2member_map_),
247
+ childs=tuple(Cmd_type_val((k,)) for k in Config_key._member_map_),
248
248
  ),
249
249
  ),
250
250
  )
@@ -1,23 +1,30 @@
1
1
  import json
2
2
  import os
3
3
  from pathlib import Path
4
+ from typing import Literal, get_origin, overload
4
5
 
5
6
  from ..easyrip_log import log
6
7
  from ..easyrip_mlang import all_supported_lang_map, gettext
7
8
  from ..global_val import CONFIG_DIR
8
- from .config_key import CONFIG_VERSION, Config_key
9
+ from ..utils import type_match
10
+ from .config_key import CONFIG_TYPE_DICT, CONFIG_VERSION, Config_key
9
11
 
10
- CONFIG_DEFAULT_DICT: dict[Config_key, str | bool] = {
12
+ CONFIG_DEFAULT_DICT: dict[Config_key, str | bool | list[str]] = {
11
13
  Config_key.language: "auto",
12
14
  Config_key.check_update: True,
13
15
  Config_key.check_dependent: True,
14
- Config_key.startup_directory: "",
16
+ Config_key.startup_dir: "",
17
+ Config_key.startup_dir_blacklist: [],
15
18
  Config_key.force_log_file_path: "",
16
19
  Config_key.log_print_level: log.LogLevel.send.name,
17
20
  Config_key.log_write_level: log.LogLevel.send.name,
18
- Config_key.prompt_history_save_file: True,
21
+ Config_key.save_prompt_history: True,
19
22
  }
20
23
 
24
+ assert all(k in CONFIG_DEFAULT_DICT for k in Config_key), [
25
+ k.name for k in Config_key if k not in CONFIG_DEFAULT_DICT
26
+ ]
27
+
21
28
 
22
29
  class config:
23
30
  _config_dir: Path
@@ -32,8 +39,8 @@ class config:
32
39
  if not cls._config_file.is_file():
33
40
  cls._config_dir.mkdir(exist_ok=True)
34
41
  with cls._config_file.open("wt", encoding="utf-8", newline="\n") as f:
35
- config_default_dict: dict[str, str | bool] = {
36
- k.value: v for k, v in CONFIG_DEFAULT_DICT.items()
42
+ config_default_dict: dict[str, str | bool | list[str]] = {
43
+ k.name: v for k, v in CONFIG_DEFAULT_DICT.items()
37
44
  }
38
45
  json.dump(
39
46
  {
@@ -104,7 +111,11 @@ class config:
104
111
  return True
105
112
 
106
113
  @classmethod
107
- def set_user_profile(cls, key: str, val: str | int | float | bool) -> bool:
114
+ def set_user_profile(
115
+ cls,
116
+ key: str,
117
+ val: str | bool | list[str],
118
+ ) -> bool:
108
119
  if cls._config is None and not cls._read_config():
109
120
  return False
110
121
 
@@ -113,21 +124,71 @@ class config:
113
124
  return False
114
125
 
115
126
  if "user_profile" not in cls._config:
116
- log.error("User profile is not found in config")
127
+ log.error("User profile is not found in config file")
117
128
  return False
118
129
 
119
- if key in Config_key:
130
+ if key in Config_key._member_map_:
131
+ need_type = CONFIG_TYPE_DICT[Config_key[key]]
132
+ if not type_match(val, need_type):
133
+ log.error(
134
+ "Type mismatch: need '{}'",
135
+ need_type if get_origin(need_type) else need_type.__name__,
136
+ )
137
+ return False
120
138
  cls._config["user_profile"][key] = val
121
139
  else:
122
140
  log.error("Key '{}' is not found in user profile", key)
123
141
  return False
124
142
  return cls._write_config()
125
143
 
144
+ @overload
145
+ @classmethod
146
+ def get_user_profile(
147
+ cls,
148
+ config_key: Literal[
149
+ Config_key.language,
150
+ Config_key.startup_dir,
151
+ Config_key.force_log_file_path,
152
+ Config_key.log_print_level,
153
+ Config_key.log_write_level,
154
+ ],
155
+ ) -> str | None: ...
156
+
157
+ @overload
158
+ @classmethod
159
+ def get_user_profile(
160
+ cls,
161
+ config_key: Literal[
162
+ Config_key.check_update,
163
+ Config_key.check_dependent,
164
+ Config_key.save_prompt_history,
165
+ ],
166
+ ) -> bool | None: ...
167
+
168
+ @overload
126
169
  @classmethod
127
170
  def get_user_profile(
128
- cls, config_key: Config_key | str
129
- ) -> str | int | float | bool | None:
130
- key = config_key.value if isinstance(config_key, Config_key) else config_key
171
+ cls,
172
+ config_key: Literal[Config_key.startup_dir_blacklist],
173
+ ) -> list[str] | None: ...
174
+
175
+ @overload
176
+ @classmethod
177
+ def get_user_profile(
178
+ cls,
179
+ config_key: str,
180
+ ) -> str | bool | list[str] | None: ...
181
+
182
+ @classmethod
183
+ def get_user_profile(
184
+ cls,
185
+ config_key: Config_key | str,
186
+ ) -> str | bool | list[str] | None:
187
+ key = config_key.name if isinstance(config_key, Config_key) else config_key
188
+
189
+ if key not in Config_key._member_map_:
190
+ log.error("The key '{}' is not a config", key)
191
+ return None
131
192
 
132
193
  if cls._config is None:
133
194
  cls._read_config()
@@ -161,43 +222,47 @@ class config:
161
222
  def _get_config_about(cls, key: str) -> str:
162
223
  return (
163
224
  {
164
- Config_key.language.value: gettext(
225
+ Config_key.language.name: gettext(
165
226
  "Easy Rip's language, support incomplete matching. Default: {}. Supported: {}",
166
227
  CONFIG_DEFAULT_DICT[Config_key.language],
167
228
  ", ".join(("auto", *(str(tag) for tag in all_supported_lang_map))),
168
229
  ),
169
- Config_key.check_update.value: gettext(
230
+ Config_key.check_update.name: gettext(
170
231
  "Auto check the update of Easy Rip. Default: {}",
171
232
  CONFIG_DEFAULT_DICT[Config_key.check_update],
172
233
  ),
173
- Config_key.check_dependent.value: gettext(
234
+ Config_key.check_dependent.name: gettext(
174
235
  "Auto check the versions of all dependent programs. Default: {}",
175
236
  CONFIG_DEFAULT_DICT[Config_key.check_dependent],
176
237
  ),
177
- Config_key.startup_directory.value: gettext(
238
+ Config_key.startup_dir.name: gettext(
178
239
  "Program startup directory, when the value is empty, starts in the working directory. Default: {}",
179
- CONFIG_DEFAULT_DICT[Config_key.startup_directory] or '""',
240
+ CONFIG_DEFAULT_DICT[Config_key.startup_dir] or '""',
241
+ ),
242
+ Config_key.startup_dir_blacklist.name: gettext(
243
+ "Directory list. When the startup directory is a blacklisted directory, rollback to startup directory. Default: {}",
244
+ CONFIG_DEFAULT_DICT[Config_key.startup_dir] or '""',
180
245
  ),
181
- Config_key.force_log_file_path.value: gettext(
246
+ Config_key.force_log_file_path.name: gettext(
182
247
  "Force change of log file path, when the value is empty, it is the working directory. Default: {}",
183
248
  CONFIG_DEFAULT_DICT[Config_key.force_log_file_path] or '""',
184
249
  ),
185
- Config_key.log_print_level.value: gettext(
250
+ Config_key.log_print_level.name: gettext(
186
251
  "Logs this level and above will be printed, and if the value is '{}', they will not be printed. Default: {}. Supported: {}",
187
252
  log.LogLevel.none.name,
188
253
  CONFIG_DEFAULT_DICT[Config_key.log_print_level],
189
254
  ", ".join(log.LogLevel._member_names_),
190
255
  ),
191
- Config_key.log_write_level.value: gettext(
256
+ Config_key.log_write_level.name: gettext(
192
257
  "Logs this level and above will be written, and if the value is '{}', the '{}' only be written when 'server', they will not be written. Default: {}. Supported: {}",
193
258
  log.LogLevel.none.name,
194
259
  log.LogLevel.send.name,
195
260
  CONFIG_DEFAULT_DICT[Config_key.log_write_level],
196
261
  ", ".join(log.LogLevel._member_names_),
197
262
  ),
198
- Config_key.prompt_history_save_file.value: gettext(
263
+ Config_key.save_prompt_history.name: gettext(
199
264
  "Save prompt history to config directory, otherwise save to memory. Take effect after reboot. Default: {}",
200
- CONFIG_DEFAULT_DICT[Config_key.prompt_history_save_file],
265
+ CONFIG_DEFAULT_DICT[Config_key.save_prompt_history],
201
266
  ),
202
267
  }
203
268
  | (cls._config or {})
@@ -0,0 +1,28 @@
1
+ import enum
2
+
3
+ CONFIG_VERSION = "4.6.0"
4
+
5
+
6
+ class Config_key(enum.Enum):
7
+ language = enum.auto()
8
+ check_update = enum.auto()
9
+ check_dependent = enum.auto()
10
+ startup_dir = enum.auto()
11
+ startup_dir_blacklist = enum.auto()
12
+ force_log_file_path = enum.auto()
13
+ log_print_level = enum.auto()
14
+ log_write_level = enum.auto()
15
+ save_prompt_history = enum.auto()
16
+
17
+
18
+ CONFIG_TYPE_DICT: dict[Config_key, type] = {
19
+ Config_key.language: str,
20
+ Config_key.check_update: bool,
21
+ Config_key.check_dependent: bool,
22
+ Config_key.startup_dir: str,
23
+ Config_key.startup_dir_blacklist: list[str],
24
+ Config_key.force_log_file_path: str,
25
+ Config_key.log_print_level: str,
26
+ Config_key.log_write_level: str,
27
+ Config_key.save_prompt_history: bool,
28
+ }
@@ -1,3 +1,4 @@
1
+ import ast
1
2
  import ctypes
2
3
  import itertools
3
4
  import json
@@ -58,16 +59,15 @@ def log_new_ver(
58
59
  log.info(
59
60
  "\n"
60
61
  + gettext(
61
- "{} has new version {}. You can download it: {}",
62
+ "{} has new version ({} -> {}). Suggest upgrading it: {}",
62
63
  program_name,
64
+ old_ver,
63
65
  new_ver,
64
66
  dl_url,
65
67
  )
66
68
  )
67
- # print(get_input_prompt(True), end="")
68
69
  except Exception as e:
69
70
  log.warning(f"\n{e}", is_format=False, deep=True)
70
- # print(get_input_prompt(True), end="")
71
71
 
72
72
 
73
73
  def check_env() -> None:
@@ -185,20 +185,6 @@ def check_env() -> None:
185
185
  _url,
186
186
  )
187
187
 
188
- # _name, _url = "MediaInfo", "https://mediaarea.net/en/MediaInfo/Download"
189
- # if not shutil.which(_name):
190
- # log.warning(
191
- # "\n"
192
- # + gettext(
193
- # "{} not found, download it: {}", _name, f"(CLI ver) {_url}"
194
- # )
195
- # )
196
- # log.print(get_input_prompt(), end="")
197
- # elif not subprocess.run(
198
- # "mediainfo --version", capture_output=True, text=True
199
- # ).stdout:
200
- # log.error("The MediaInfo must be CLI ver")
201
-
202
188
  if config.get_user_profile(Config_key.check_update):
203
189
  log_new_ver(
204
190
  easyrip_web.github.get_latest_release_ver(
@@ -208,18 +194,20 @@ def check_env() -> None:
208
194
  PROJECT_NAME,
209
195
  f"{global_val.PROJECT_URL}\n{
210
196
  gettext(
211
- 'or run this command to update using pip: {}',
212
- f'{sys.executable + " -m " if sys.executable[-10:].lower() == "python.exe" else ""}pip install -U easyrip',
197
+ 'Suggest running the following command to upgrade using pip: {}',
198
+ f'{f'"{sys.executable}" -m ' if sys.executable.lower().endswith("python.exe") else ""}pip install -U easyrip',
213
199
  )
214
200
  }",
215
201
  )
216
202
 
217
- # sys.stdout.flush()
218
- # sys.stderr.flush()
219
203
  change_title(PROJECT_TITLE)
220
204
 
221
205
  except Exception as e:
222
- log.error(f"The def check_env error: {e!r} {e}", deep=True)
206
+ log.error(
207
+ f"The function {check_env.__name__} error: {e!r} {e}",
208
+ is_format=False,
209
+ deep=True,
210
+ )
223
211
 
224
212
 
225
213
  def get_input_prompt(is_color: bool = False) -> str:
@@ -685,25 +673,16 @@ def run_command(command: Iterable[str] | str) -> bool:
685
673
  case "set":
686
674
  _key = cmd_list[2]
687
675
  _val = cmd_list[3]
688
- _old_val = config.get_user_profile(_key)
676
+
677
+ if (_old_val := config.get_user_profile(_key)) is None:
678
+ return False
689
679
 
690
680
  try:
691
- _val = float(_val)
692
- except ValueError:
693
- match _val:
694
- case "true" | "True":
695
- _val = True
696
- case "false" | "False":
697
- _val = False
698
- else:
699
- if (_val_int := int(_val)) == _val:
700
- _val = _val_int
681
+ _val = ast.literal_eval(_val)
682
+ except (ValueError, SyntaxError):
683
+ pass
701
684
 
702
- if (
703
- (_old_val is _val)
704
- if isinstance(_val, bool) or isinstance(_old_val, bool)
705
- else (_old_val == _val)
706
- ):
685
+ if _old_val == _val:
707
686
  log.info(
708
687
  "The new value is the same as the old value, cancel the modification",
709
688
  )
@@ -1102,7 +1081,7 @@ def init(is_first_run: bool = False) -> None:
1102
1081
 
1103
1082
  # 设置日志文件路径名
1104
1083
  log.html_filename = gettext("encoding_log.html")
1105
- if _path := str(config.get_user_profile("force_log_file_path") or ""):
1084
+ if _path := str(config.get_user_profile(Config_key.force_log_file_path) or ""):
1106
1085
  log.html_filename = os.path.join(_path, log.html_filename)
1107
1086
 
1108
1087
  # 设置日志级别
@@ -1119,8 +1098,18 @@ def init(is_first_run: bool = False) -> None:
1119
1098
  if is_first_run:
1120
1099
  # 设置启动目录
1121
1100
  try:
1122
- if _path := str(config.get_user_profile(Config_key.startup_directory)):
1123
- os.chdir(_path)
1101
+ if _startup_dir := config.get_user_profile(Config_key.startup_dir):
1102
+ if _startup_dir_blacklist := config.get_user_profile(
1103
+ Config_key.startup_dir_blacklist
1104
+ ):
1105
+ if any(
1106
+ Path.cwd().samefile(d)
1107
+ for d in map(Path, _startup_dir_blacklist)
1108
+ if d.is_dir()
1109
+ ):
1110
+ os.chdir(_startup_dir)
1111
+ else:
1112
+ os.chdir(_startup_dir)
1124
1113
  except Exception as e:
1125
1114
  log.error(f"{e!r} {e}", deep=True)
1126
1115
 
@@ -273,7 +273,7 @@ LANG_MAP: dict[str, str] = {
273
273
  "例如 -p subset 或 -p copy"
274
274
  ),
275
275
  # utils
276
- "{} has new version {}. You can download it: {}": "检测到 {} 有新版本 {}。可在此下载: {}",
276
+ "{} has new version ({} -> {}). Suggest upgrading it: {}": "检测到 {} 有新版本 ({} -> {})。建议更新: {}",
277
277
  "{} not found, download it: {}": "没找到 {}, 在此下载: {}",
278
278
  "flac ver ({}) must >= 1.5.0": "flac 版本 ({}) 必须 >= 1.5.0",
279
279
  # main
@@ -303,7 +303,7 @@ LANG_MAP: dict[str, str] = {
303
303
  "There is no audio stream in the video, so '-c:a' cannot be used": "视频中没有音频流,所以无法使用 '-c:a'",
304
304
  "Unsupported '{}' param: {}": "'{}' 不支持此参数: {}",
305
305
  "Manually force exit": "手动强制退出",
306
- "or run this command to update using pip: {}": "或运行以下命令以使用 pip 更新: {}",
306
+ "Suggest running the following command to upgrade using pip: {}": "建议运行以下命令以使用 pip 更新: {}",
307
307
  "Wrong sec in -shutdown, change to default 60s": "-shutdown 设定的秒数错误, 改为默认值 60s",
308
308
  "Current work directory has an other Easy Rip is running: {}": "当前工作目录存在其他 Easy Rip 正在运行: {}",
309
309
  "Stop run command": "命令执行终止",
@@ -346,7 +346,8 @@ LANG_MAP: dict[str, str] = {
346
346
  "Config data is not found": "配置文件数据不存在",
347
347
  "User profile is not found, regenerate config": "用户配置文件不存在, 重新生成配置",
348
348
  "User profile is not a valid dictionary": "用户配置文件不是有效的字典",
349
- "User profile is not found": "用户配置文件不存在",
349
+ "User profile is not found in config file": "用户配置文件不存在于配置文件",
350
+ "Type mismatch: need '{}'": "类型不匹配: 需要 '{}'",
350
351
  "Key '{}' is not found in user profile": "用户配置文件中不存在 {}",
351
352
  "Save prompt history to config directory, otherwise save to memory. Take effect after reboot. Default: {}": "将 prompt 历史保存到 config 目录,否则保存到内存。重启后生效。默认: {}",
352
353
  # config about
@@ -3,7 +3,7 @@ import sys
3
3
  from pathlib import Path
4
4
 
5
5
  PROJECT_NAME = "Easy Rip"
6
- PROJECT_VERSION = "4.5.1"
6
+ PROJECT_VERSION = "4.6.1"
7
7
  PROJECT_TITLE = f"{PROJECT_NAME} v{PROJECT_VERSION}"
8
8
  PROJECT_URL = "https://github.com/op200/EasyRip"
9
9
  PROJECT_RELEASE_API = "https://api.github.com/repos/op200/EasyRip/releases/latest"
@@ -55,7 +55,7 @@ def load_fonts(
55
55
  for file in path.iterdir() if path.is_dir() else (path,):
56
56
  if not (
57
57
  file.is_file()
58
- and ((suffix := file.suffix.lower()) in {".ttf", ".otf", ".ttc"})
58
+ and (suffix := file.suffix.lower()) in {".ttf", ".otf", ".ttc"}
59
59
  ):
60
60
  continue
61
61
 
@@ -120,7 +120,7 @@ def load_fonts(
120
120
  if is_regular:
121
121
  if is_bold or is_italic:
122
122
  log.error(
123
- "Font {} is Regular but Bold={} and Italic={}. Skip this font",
123
+ 'Font "{}" is Regular but Bold={} and Italic={}. Skip this font',
124
124
  file,
125
125
  is_bold,
126
126
  is_italic,
@@ -134,15 +134,15 @@ def load_fonts(
134
134
  else:
135
135
  res_font.font_type = Font_type.Regular
136
136
  log.warning(
137
- f"Font {file} does not have an English subfamily name. Defaulting to Regular"
137
+ f'Font "{file}" does not have an English subfamily name. Defaulting to Regular'
138
138
  )
139
139
 
140
140
  res_font_list.append(res_font)
141
141
 
142
142
  except TTLibError as e:
143
- log.error(f'Error loading font file "{file}": {e}')
143
+ log.error(f'Failed to load font file "{file}": {e}')
144
144
  except Exception as e:
145
- log.error(f"Unexpected error for font {file}: {e}")
145
+ log.error(f'Unexpected error when load font "{file}": {e}')
146
146
 
147
147
  return res_font_list
148
148
 
@@ -7,7 +7,7 @@ import sys
7
7
  import time
8
8
  from itertools import zip_longest
9
9
  from pathlib import Path
10
- from typing import Final
10
+ from typing import Any, Final, TypeGuard, get_args, get_origin
11
11
 
12
12
  from Crypto.Cipher import AES as CryptoAES
13
13
  from Crypto.Util.Padding import pad, unpad
@@ -207,3 +207,82 @@ def time_str_to_sec(s: str) -> float:
207
207
  def non_ascii_str_len(s: str) -> int:
208
208
  """非 ASCII 字符算作 2 宽度"""
209
209
  return sum(2 - int(ord(c) < 256) for c in s)
210
+
211
+
212
+ def type_match[T](val: Any, t: type[T]) -> TypeGuard[T]:
213
+ """
214
+ 检查值是否匹配给定的类型(支持泛型)
215
+
216
+ 支持的类型包括:
217
+ - 基本类型: int, str, list, dict, tuple, set
218
+ - 泛型类型: list[str], dict[str, int], tuple[int, ...]
219
+ - 联合类型: int | str, Union[int, str]
220
+ - 可选类型: Optional[str]
221
+ - 嵌套泛型: list[list[str]], dict[str, list[int]]
222
+
223
+ Args:
224
+ val: 要检查的值
225
+ t: 目标类型,可以是普通类型或泛型
226
+
227
+ Returns:
228
+ bool: 值是否匹配目标类型
229
+
230
+ """
231
+ t_org = get_origin(t)
232
+
233
+ # 如果不是泛型类型,直接使用 isinstance
234
+ if t_org is None:
235
+ return isinstance(val, t)
236
+
237
+ # 首先检查是否是 b_org 的实例
238
+ if not isinstance(val, t_org):
239
+ return False
240
+
241
+ # 获取类型参数
242
+ args = get_args(t)
243
+ if not args: # 没有类型参数,如 List
244
+ return True
245
+
246
+ # 根据不同的原始类型进行检查
247
+ if t_org is list:
248
+ # list[T] 检查
249
+ if len(args) == 1:
250
+ elem_type = args[0]
251
+ return all(type_match(item, elem_type) for item in val)
252
+
253
+ elif t_org is tuple:
254
+ # tuple[T1, T2, ...] 或 tuple[T, ...] 检查
255
+ if len(args) == 2 and args[1] is ...: # 可变长度元组
256
+ elem_type = args[0]
257
+ return all(type_match(item, elem_type) for item in val)
258
+ # 固定长度元组
259
+ if len(val) != len(args):
260
+ return False
261
+ return all(type_match(item, t) for item, t in zip(val, args, strict=False))
262
+
263
+ elif t_org is dict:
264
+ # dict[K, V] 检查
265
+ if len(args) == 2:
266
+ key_type, value_type = args
267
+ return all(
268
+ type_match(k, key_type) and type_match(v, value_type)
269
+ for k, v in val.items()
270
+ )
271
+
272
+ elif t_org is set:
273
+ # set[T] 检查
274
+ if len(args) == 1:
275
+ elem_type = args[0]
276
+ return all(type_match(item, elem_type) for item in val)
277
+
278
+ elif t_org is frozenset:
279
+ # frozenset[T] 检查
280
+ if len(args) == 1:
281
+ elem_type = args[0]
282
+ return all(type_match(item, elem_type) for item in val)
283
+
284
+ elif hasattr(t_org, "__name__") and t_org.__name__ == "Union":
285
+ # Union[T1, T2, ...] 或 T1 | T2 检查
286
+ return any(type_match(val, t) for t in args)
287
+
288
+ return True
@@ -1,6 +1,6 @@
1
1
  Metadata-Version: 2.4
2
2
  Name: easyrip
3
- Version: 4.5.1
3
+ Version: 4.6.1
4
4
  Author: op200
5
5
  License-Expression: AGPL-3.0-or-later
6
6
  Project-URL: Homepage, https://github.com/op200/EasyRip
@@ -1,14 +0,0 @@
1
- import enum
2
-
3
- CONFIG_VERSION = "4.2.0"
4
-
5
-
6
- class Config_key(enum.Enum):
7
- language = "language"
8
- check_update = "check_update"
9
- check_dependent = "check_dependent"
10
- startup_directory = "startup_directory"
11
- force_log_file_path = "force_log_file_path"
12
- log_print_level = "log_print_level"
13
- log_write_level = "log_write_level"
14
- prompt_history_save_file = "prompt_history_file"
File without changes
File without changes
File without changes
File without changes
File without changes
File without changes
File without changes