easyrip 4.5.0__py3-none-any.whl → 4.6.0__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.
easyrip/__main__.py CHANGED
@@ -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
 
@@ -129,8 +129,11 @@ def run() -> NoReturn:
129
129
  log.debug("Manually force exit")
130
130
  sys.exit(0)
131
131
 
132
- if not run_command(command):
133
- log.warning("Stop run command")
132
+ try:
133
+ if not run_command(command):
134
+ log.warning("Stop run command")
135
+ except KeyboardInterrupt:
136
+ log.warning("Manually stop run command")
134
137
 
135
138
 
136
139
  if __name__ == "__main__":
@@ -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
  )
@@ -490,7 +490,7 @@ class Opt_type(enum.Enum):
490
490
  "Audio encoder:\n" # .
491
491
  f"{Audio_codec.to_help_string(' ')}"
492
492
  ),
493
- childs=(Cmd_type_val(("copy", "libopus", "flac")),),
493
+ childs=(Cmd_type_val(tuple(Audio_codec._value2member_map_)),),
494
494
  )
495
495
  _b_a = Cmd_type_val(
496
496
  ("-b:a",),
@@ -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 {})
@@ -1,14 +1,28 @@
1
1
  import enum
2
2
 
3
- CONFIG_VERSION = "4.2.0"
3
+ CONFIG_VERSION = "4.6.0"
4
4
 
5
5
 
6
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"
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
+ }
easyrip/easyrip_main.py CHANGED
@@ -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:
@@ -674,6 +662,7 @@ def run_command(command: Iterable[str] | str) -> bool:
674
662
  easyrip_web.run_server(*_params)
675
663
 
676
664
  case Cmd_type.config:
665
+ log.debug(cmd_list)
677
666
  match cmd_list[1]:
678
667
  case "list" | "":
679
668
  config.show_config_list()
@@ -685,25 +674,16 @@ def run_command(command: Iterable[str] | str) -> bool:
685
674
  case "set":
686
675
  _key = cmd_list[2]
687
676
  _val = cmd_list[3]
688
- _old_val = config.get_user_profile(_key)
677
+
678
+ if (_old_val := config.get_user_profile(_key)) is None:
679
+ return False
689
680
 
690
681
  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
682
+ _val = ast.literal_eval(_val)
683
+ except (ValueError, SyntaxError):
684
+ pass
701
685
 
702
- if (
703
- (_old_val is _val)
704
- if isinstance(_val, bool) or isinstance(_old_val, bool)
705
- else (_old_val == _val)
706
- ):
686
+ if _old_val == _val:
707
687
  log.info(
708
688
  "The new value is the same as the old value, cancel the modification",
709
689
  )
@@ -1102,7 +1082,7 @@ def init(is_first_run: bool = False) -> None:
1102
1082
 
1103
1083
  # 设置日志文件路径名
1104
1084
  log.html_filename = gettext("encoding_log.html")
1105
- if _path := str(config.get_user_profile("force_log_file_path") or ""):
1085
+ if _path := str(config.get_user_profile(Config_key.force_log_file_path) or ""):
1106
1086
  log.html_filename = os.path.join(_path, log.html_filename)
1107
1087
 
1108
1088
  # 设置日志级别
@@ -1119,8 +1099,18 @@ def init(is_first_run: bool = False) -> None:
1119
1099
  if is_first_run:
1120
1100
  # 设置启动目录
1121
1101
  try:
1122
- if _path := str(config.get_user_profile(Config_key.startup_directory)):
1123
- os.chdir(_path)
1102
+ if _startup_dir := config.get_user_profile(Config_key.startup_dir):
1103
+ if _startup_dir_blacklist := config.get_user_profile(
1104
+ Config_key.startup_dir_blacklist
1105
+ ):
1106
+ if any(
1107
+ Path.cwd().samefile(d)
1108
+ for d in map(Path, _startup_dir_blacklist)
1109
+ if d.is_dir()
1110
+ ):
1111
+ os.chdir(_startup_dir)
1112
+ else:
1113
+ os.chdir(_startup_dir)
1124
1114
  except Exception as e:
1125
1115
  log.error(f"{e!r} {e}", deep=True)
1126
1116
 
@@ -1,4 +1,4 @@
1
- from ..easyrip_command import Cmd_type, Opt_type
1
+ from ..easyrip_command import Audio_codec, Cmd_type, Opt_type, Preset_name
2
2
  from .global_lang_val import (
3
3
  Lang_tag,
4
4
  Lang_tag_language,
@@ -135,18 +135,8 @@ LANG_MAP: dict[str, str] = {
135
135
  ),
136
136
  Opt_type._preset.value.description: (
137
137
  "设置预设\n"
138
- "预设名:\n"
139
- " custom\n"
140
- " subset\n"
141
- " copy\n"
142
- " flac\n"
143
- " x264fast x264slow\n"
144
- " x265fast4 x265fast3 x265fast2 x265fast x265slow x265full\n"
145
- " svtav1\n"
146
- " vvenc\n"
147
- " h264_amf h264_nvenc h264_qsv\n"
148
- " hevc_amf hevc_nvenc hevc_qsv\n"
149
- " av1_amf av1_nvenc av1_qsv"
138
+ "预设名:\n" # .
139
+ f"{Preset_name.to_help_string(' ')}"
150
140
  ),
151
141
  Opt_type._pipe.value.description: (
152
142
  "选择一个 vpy 文件作为管道的输入, 这个 vpy 必须有 input 全局变量\n"
@@ -209,11 +199,8 @@ LANG_MAP: dict[str, str] = {
209
199
  ),
210
200
  Opt_type._c_a.value.description: (
211
201
  "设置音频编码器\n"
212
- " \n" # .
213
- "音频编码器:\n"
214
- " copy\n"
215
- " libopus\n"
216
- " flac"
202
+ "音频编码器:\n" # .
203
+ f"{Audio_codec.to_help_string(' ')}"
217
204
  ),
218
205
  Opt_type._b_a.value.description: "设置音频码率。默认值 '160k'",
219
206
  Opt_type._muxer.value.description: (
@@ -286,7 +273,7 @@ LANG_MAP: dict[str, str] = {
286
273
  "例如 -p subset 或 -p copy"
287
274
  ),
288
275
  # utils
289
- "{} has new version {}. You can download it: {}": "检测到 {} 有新版本 {}。可在此下载: {}",
276
+ "{} has new version ({} -> {}). Suggest upgrading it: {}": "检测到 {} 有新版本 ({} -> {})。建议更新: {}",
290
277
  "{} not found, download it: {}": "没找到 {}, 在此下载: {}",
291
278
  "flac ver ({}) must >= 1.5.0": "flac 版本 ({}) 必须 >= 1.5.0",
292
279
  # main
@@ -316,7 +303,7 @@ LANG_MAP: dict[str, str] = {
316
303
  "There is no audio stream in the video, so '-c:a' cannot be used": "视频中没有音频流,所以无法使用 '-c:a'",
317
304
  "Unsupported '{}' param: {}": "'{}' 不支持此参数: {}",
318
305
  "Manually force exit": "手动强制退出",
319
- "or run this command to update using pip: {}": "或运行以下命令以使用 pip 更新: {}",
306
+ "Suggest running the following command to upgrade using pip: {}": "建议运行以下命令以使用 pip 更新: {}",
320
307
  "Wrong sec in -shutdown, change to default 60s": "-shutdown 设定的秒数错误, 改为默认值 60s",
321
308
  "Current work directory has an other Easy Rip is running: {}": "当前工作目录存在其他 Easy Rip 正在运行: {}",
322
309
  "Stop run command": "命令执行终止",
@@ -359,7 +346,8 @@ LANG_MAP: dict[str, str] = {
359
346
  "Config data is not found": "配置文件数据不存在",
360
347
  "User profile is not found, regenerate config": "用户配置文件不存在, 重新生成配置",
361
348
  "User profile is not a valid dictionary": "用户配置文件不是有效的字典",
362
- "User profile is not found": "用户配置文件不存在",
349
+ "User profile is not found in config file": "用户配置文件不存在于配置文件",
350
+ "Type mismatch: need '{}'": "类型不匹配: 需要 '{}'",
363
351
  "Key '{}' is not found in user profile": "用户配置文件中不存在 {}",
364
352
  "Save prompt history to config directory, otherwise save to memory. Take effect after reboot. Default: {}": "将 prompt 历史保存到 config 目录,否则保存到内存。重启后生效。默认: {}",
365
353
  # config about
easyrip/global_val.py CHANGED
@@ -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.0"
6
+ PROJECT_VERSION = "4.6.0"
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"
easyrip/utils.py CHANGED
@@ -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 Final, 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(val, b: type) -> bool:
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
+ b: 目标类型,可以是普通类型或泛型
226
+
227
+ Returns:
228
+ bool: 值是否匹配目标类型
229
+
230
+ """
231
+ b_org = get_origin(b)
232
+
233
+ # 如果不是泛型类型,直接使用 isinstance
234
+ if b_org is None:
235
+ return isinstance(val, b)
236
+
237
+ # 首先检查是否是 b_org 的实例
238
+ if not isinstance(val, b_org):
239
+ return False
240
+
241
+ # 获取类型参数
242
+ args = get_args(b)
243
+ if not args: # 没有类型参数,如 List
244
+ return True
245
+
246
+ # 根据不同的原始类型进行检查
247
+ if b_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 b_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 b_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 b_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 b_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(b_org, "__name__") and b_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.0
3
+ Version: 4.6.0
4
4
  Author: op200
5
5
  License-Expression: AGPL-3.0-or-later
6
6
  Project-URL: Homepage, https://github.com/op200/EasyRip
@@ -1,17 +1,17 @@
1
1
  easyrip/__init__.py,sha256=PIvSPDgswsIkWL4dsCe87knnxKmtvcrWYzmqAwZix_M,765
2
- easyrip/__main__.py,sha256=ndKSOQpWOUtpVf009yqX-9vEiEjOgFDye0re45_PfQQ,4454
3
- easyrip/easyrip_command.py,sha256=YnwtrAh7FtN7Uk8R_Fupn96OTeWtzdCYN0p5rEFe91o,27833
2
+ easyrip/__main__.py,sha256=5t5NjuTSFW6jDp_HH4rTX8EOlf-GJI5HuTSwwndkw_I,4557
3
+ easyrip/easyrip_command.py,sha256=vuT9xg2OCuUXHH58BxcFGbzrzLUzlpynjGzjtDsBnCk,27837
4
4
  easyrip/easyrip_log.py,sha256=0V5wBwbqWt56w_NHoLmQmkTHPnPLkYZCAbhDSYnLnDw,15611
5
- easyrip/easyrip_main.py,sha256=39a1cSnQUnNplX5rVTG1ECoo0_RCpQyylPu1uhArnZc,43886
5
+ easyrip/easyrip_main.py,sha256=qF_u552kkVj8nnKNd46jHfkNwTODQfR297rISRgJkpU,43311
6
6
  easyrip/easyrip_prompt.py,sha256=A5S7ybeJSGFkCmwdJ9TCQ_-lde7NWgkbFytZ2KnvkWI,2145
7
- easyrip/global_val.py,sha256=yzNIoY-_F1mkIsk39lsW7iNFbqBAB88HMg-WbE7Jg-E,773
8
- easyrip/utils.py,sha256=YM8gaKon-9DLt5llF-7-TE0JqDpKZrKGj7oRScLFatU,6322
9
- easyrip/easyrip_config/config.py,sha256=9uskiGIpYLuS-vjzNASZlhdWLWXkEVxtSjODxcZU3pc,7935
10
- easyrip/easyrip_config/config_key.py,sha256=wANVusz9kNqsYJUvXM7DUQn6k_G6EO2VWTHWh9dOifw,394
7
+ easyrip/global_val.py,sha256=dBVsU5OVbawpZ64k7QOrad1gQRa8yVV6Z0wKzAFKMMY,773
8
+ easyrip/utils.py,sha256=A2IuZ8-yxhpjNTIDXH1SqU7qOWXtMhU7llHwebEu8LQ,8715
9
+ easyrip/easyrip_config/config.py,sha256=aj6Vg1rJkvICSTZ0ZONznR_MkvVr5u5ngkX_zfZopvU,9859
10
+ easyrip/easyrip_config/config_key.py,sha256=_jjdKOunskUoG7UUWOz3QZK-s4LF_x6hmM9MKttyS2Q,766
11
11
  easyrip/easyrip_mlang/__init__.py,sha256=QHZt4BYJFkJuaPkN89pt_zkM2grifJakyRZbeyfH8f4,1893
12
12
  easyrip/easyrip_mlang/global_lang_val.py,sha256=Un20KGMVVMQbOaV_7VaHg3E1dvK2Y7kI1cPzq_sFWKY,9984
13
13
  easyrip/easyrip_mlang/lang_en.py,sha256=heUSPeVtY1Nf9eDhrjPM28N3PLsu62op3llbXAfXvAs,140
14
- easyrip/easyrip_mlang/lang_zh_Hans_CN.py,sha256=6oczt-ziN39wIk0c-9JBfV7H7Rth99m3FI0ViHgBQew,18697
14
+ easyrip/easyrip_mlang/lang_zh_Hans_CN.py,sha256=UhDwCofxh7ku5weVzcLV2Xel9_EzgjR8_Rbw9FJ9j0U,18530
15
15
  easyrip/easyrip_mlang/translator.py,sha256=Vg0S0p2rpMNAy3LHTdK7qKFdkPEXlOoCCXcaH7PyAC4,5939
16
16
  easyrip/easyrip_web/__init__.py,sha256=tMyEeaSGeEJjND7MF0MBv9aDiDgaO3MOnppwxA70U2c,177
17
17
  easyrip/easyrip_web/http_server.py,sha256=iyulCAFQrJlz86Lrr-Dm3fhOnNCf79Bp6fVHhr0ephY,8350
@@ -23,9 +23,9 @@ easyrip/ripper/sub_and_font/__init__.py,sha256=cBT7mxL7RRFaJXFPXuZ7RT-YK6FbnanaU
23
23
  easyrip/ripper/sub_and_font/ass.py,sha256=eGi1eIDWAV1Ti_BYIAcAMwrAlXJ5f_zGYte3nNu4PeE,25532
24
24
  easyrip/ripper/sub_and_font/font.py,sha256=pzRtMrcL3ATFOMM5nsJQqSBbZtrnRodIgxLkjHai7kg,8075
25
25
  easyrip/ripper/sub_and_font/subset.py,sha256=VsFYTFuVWXW9ltkqWZIiwOIh-nGDKmW1tUv3oi5kpWw,17815
26
- easyrip-4.5.0.dist-info/licenses/LICENSE,sha256=hIahDEOTzuHCU5J2nd07LWwkLW7Hko4UFO__ffsvB-8,34523
27
- easyrip-4.5.0.dist-info/METADATA,sha256=10d52t-vOhqDgakGdsn0_0QLCrnMSuoMvQ9rUOwsraY,3506
28
- easyrip-4.5.0.dist-info/WHEEL,sha256=_zCd3N1l69ArxyTb8rzEoP9TpbYXkqRFSNOD5OuxnTs,91
29
- easyrip-4.5.0.dist-info/entry_points.txt,sha256=D6GBMMTzZ-apgX76KyZ6jxMmIFqGYwU9neeLLni_qKI,49
30
- easyrip-4.5.0.dist-info/top_level.txt,sha256=kuEteBXm-Gf90jRQgH3-fTo-Z-Q6czSuUEqY158H4Ww,8
31
- easyrip-4.5.0.dist-info/RECORD,,
26
+ easyrip-4.6.0.dist-info/licenses/LICENSE,sha256=hIahDEOTzuHCU5J2nd07LWwkLW7Hko4UFO__ffsvB-8,34523
27
+ easyrip-4.6.0.dist-info/METADATA,sha256=IlgOWhd9fLGMssLGVZzDfZGtsfcHZyA10LhnHAtcxB4,3506
28
+ easyrip-4.6.0.dist-info/WHEEL,sha256=_zCd3N1l69ArxyTb8rzEoP9TpbYXkqRFSNOD5OuxnTs,91
29
+ easyrip-4.6.0.dist-info/entry_points.txt,sha256=D6GBMMTzZ-apgX76KyZ6jxMmIFqGYwU9neeLLni_qKI,49
30
+ easyrip-4.6.0.dist-info/top_level.txt,sha256=kuEteBXm-Gf90jRQgH3-fTo-Z-Q6czSuUEqY158H4Ww,8
31
+ easyrip-4.6.0.dist-info/RECORD,,