easyrip 4.5.1__py3-none-any.whl → 4.6.1__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 +1 -1
- easyrip/easyrip_command.py +1 -1
- easyrip/easyrip_config/config.py +87 -22
- easyrip/easyrip_config/config_key.py +23 -9
- easyrip/easyrip_main.py +30 -41
- easyrip/easyrip_mlang/lang_zh_Hans_CN.py +4 -3
- easyrip/global_val.py +1 -1
- easyrip/ripper/sub_and_font/font.py +5 -5
- easyrip/utils.py +80 -1
- {easyrip-4.5.1.dist-info → easyrip-4.6.1.dist-info}/METADATA +1 -1
- {easyrip-4.5.1.dist-info → easyrip-4.6.1.dist-info}/RECORD +15 -15
- {easyrip-4.5.1.dist-info → easyrip-4.6.1.dist-info}/WHEEL +0 -0
- {easyrip-4.5.1.dist-info → easyrip-4.6.1.dist-info}/entry_points.txt +0 -0
- {easyrip-4.5.1.dist-info → easyrip-4.6.1.dist-info}/licenses/LICENSE +0 -0
- {easyrip-4.5.1.dist-info → easyrip-4.6.1.dist-info}/top_level.txt +0 -0
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.
|
|
106
|
+
if config.get_user_profile(Config_key.save_prompt_history)
|
|
107
107
|
else InMemoryHistory()
|
|
108
108
|
)
|
|
109
109
|
|
easyrip/easyrip_command.py
CHANGED
|
@@ -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.
|
|
247
|
+
childs=tuple(Cmd_type_val((k,)) for k in Config_key._member_map_),
|
|
248
248
|
),
|
|
249
249
|
),
|
|
250
250
|
)
|
easyrip/easyrip_config/config.py
CHANGED
|
@@ -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
|
|
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.
|
|
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.
|
|
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.
|
|
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(
|
|
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,
|
|
129
|
-
|
|
130
|
-
|
|
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.
|
|
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.
|
|
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.
|
|
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.
|
|
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.
|
|
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.
|
|
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.
|
|
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.
|
|
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.
|
|
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.
|
|
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.
|
|
3
|
+
CONFIG_VERSION = "4.6.0"
|
|
4
4
|
|
|
5
5
|
|
|
6
6
|
class Config_key(enum.Enum):
|
|
7
|
-
language =
|
|
8
|
-
check_update =
|
|
9
|
-
check_dependent =
|
|
10
|
-
|
|
11
|
-
|
|
12
|
-
|
|
13
|
-
|
|
14
|
-
|
|
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 {}.
|
|
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
|
-
'
|
|
212
|
-
f'{sys.executable
|
|
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(
|
|
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
|
-
|
|
676
|
+
|
|
677
|
+
if (_old_val := config.get_user_profile(_key)) is None:
|
|
678
|
+
return False
|
|
689
679
|
|
|
690
680
|
try:
|
|
691
|
-
_val =
|
|
692
|
-
except ValueError:
|
|
693
|
-
|
|
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(
|
|
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
|
|
1123
|
-
|
|
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 {}.
|
|
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
|
-
"
|
|
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
|
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.
|
|
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 (
|
|
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
|
-
|
|
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
|
|
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'
|
|
143
|
+
log.error(f'Failed to load font file "{file}": {e}')
|
|
144
144
|
except Exception as e:
|
|
145
|
-
log.error(f
|
|
145
|
+
log.error(f'Unexpected error when load font "{file}": {e}')
|
|
146
146
|
|
|
147
147
|
return res_font_list
|
|
148
148
|
|
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 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,17 +1,17 @@
|
|
|
1
1
|
easyrip/__init__.py,sha256=PIvSPDgswsIkWL4dsCe87knnxKmtvcrWYzmqAwZix_M,765
|
|
2
|
-
easyrip/__main__.py,sha256=
|
|
3
|
-
easyrip/easyrip_command.py,sha256=
|
|
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=
|
|
5
|
+
easyrip/easyrip_main.py,sha256=5Bf3_3cc3fYSqXDrAH8eOn6jBHreBLdZphefZQ5Ts9A,43279
|
|
6
6
|
easyrip/easyrip_prompt.py,sha256=A5S7ybeJSGFkCmwdJ9TCQ_-lde7NWgkbFytZ2KnvkWI,2145
|
|
7
|
-
easyrip/global_val.py,sha256=
|
|
8
|
-
easyrip/utils.py,sha256=
|
|
9
|
-
easyrip/easyrip_config/config.py,sha256=
|
|
10
|
-
easyrip/easyrip_config/config_key.py,sha256=
|
|
7
|
+
easyrip/global_val.py,sha256=E3086jmQN8-6tVwiAItklC02f_9cgaUI1ostadE7-_g,773
|
|
8
|
+
easyrip/utils.py,sha256=N1rMF1MyoC-YFBgy10_u29cFoowfhR-5Viea93O7wQ4,8750
|
|
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=
|
|
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
|
|
@@ -21,11 +21,11 @@ easyrip/ripper/param.py,sha256=x11G7AsU-1Ol37-psKNo8AtjKDdD8YPlXeNwJSbhZxw,11082
|
|
|
21
21
|
easyrip/ripper/ripper.py,sha256=Ru3FI7c6toMEm1qgt_5XneeIYHTTv269nS6-Dmv1f6Y,50167
|
|
22
22
|
easyrip/ripper/sub_and_font/__init__.py,sha256=cBT7mxL7RRFaJXFPXuZ7RT-YK6FbnanaU5v6U9BOquw,153
|
|
23
23
|
easyrip/ripper/sub_and_font/ass.py,sha256=eGi1eIDWAV1Ti_BYIAcAMwrAlXJ5f_zGYte3nNu4PeE,25532
|
|
24
|
-
easyrip/ripper/sub_and_font/font.py,sha256=
|
|
24
|
+
easyrip/ripper/sub_and_font/font.py,sha256=DBtTi32deXES1ylZiZ0qzcv9zvRQRIYBAH9Zbwij3G0,8086
|
|
25
25
|
easyrip/ripper/sub_and_font/subset.py,sha256=VsFYTFuVWXW9ltkqWZIiwOIh-nGDKmW1tUv3oi5kpWw,17815
|
|
26
|
-
easyrip-4.
|
|
27
|
-
easyrip-4.
|
|
28
|
-
easyrip-4.
|
|
29
|
-
easyrip-4.
|
|
30
|
-
easyrip-4.
|
|
31
|
-
easyrip-4.
|
|
26
|
+
easyrip-4.6.1.dist-info/licenses/LICENSE,sha256=hIahDEOTzuHCU5J2nd07LWwkLW7Hko4UFO__ffsvB-8,34523
|
|
27
|
+
easyrip-4.6.1.dist-info/METADATA,sha256=8o9ZCmC7xzHWxb-RHItUR3lgvzyLQy-nwyHSwBfHeYY,3506
|
|
28
|
+
easyrip-4.6.1.dist-info/WHEEL,sha256=_zCd3N1l69ArxyTb8rzEoP9TpbYXkqRFSNOD5OuxnTs,91
|
|
29
|
+
easyrip-4.6.1.dist-info/entry_points.txt,sha256=D6GBMMTzZ-apgX76KyZ6jxMmIFqGYwU9neeLLni_qKI,49
|
|
30
|
+
easyrip-4.6.1.dist-info/top_level.txt,sha256=kuEteBXm-Gf90jRQgH3-fTo-Z-Q6czSuUEqY158H4Ww,8
|
|
31
|
+
easyrip-4.6.1.dist-info/RECORD,,
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|