easyrip 4.11.2__py3-none-any.whl → 4.12.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.
@@ -1,15 +1,10 @@
1
1
  from ..easyrip_command import Audio_codec, Cmd_type, Opt_type, Preset_name
2
- from .global_lang_val import (
3
- Lang_tag,
4
- Lang_tag_language,
5
- Lang_tag_region,
6
- Lang_tag_script,
7
- )
2
+ from .global_lang_val import Lang_tag
8
3
 
9
4
  LANG_TAG = Lang_tag(
10
- language=Lang_tag_language.zh,
11
- script=Lang_tag_script.Hans,
12
- region=Lang_tag_region.CN,
5
+ language=Lang_tag.Language.zh,
6
+ script=Lang_tag.Script.Hans,
7
+ region=Lang_tag.Region.CN,
13
8
  )
14
9
 
15
10
  LANG_MAP: dict[str, str] = {
@@ -135,7 +130,8 @@ LANG_MAP: dict[str, str] = {
135
130
  ),
136
131
  Opt_type._preset.value.description: (
137
132
  "设置预设\n"
138
- "预设名:\n" # .
133
+ "\n" # .
134
+ "预设名:\n"
139
135
  f"{Preset_name.to_help_string(' ')}"
140
136
  ),
141
137
  Opt_type._pipe.value.description: (
@@ -199,13 +195,14 @@ LANG_MAP: dict[str, str] = {
199
195
  ),
200
196
  Opt_type._c_a.value.description: (
201
197
  "设置音频编码器\n"
202
- "音频编码器:\n" # .
198
+ "\n" # .
199
+ "音频编码器:\n"
203
200
  f"{Audio_codec.to_help_string(' ')}"
204
201
  ),
205
202
  Opt_type._b_a.value.description: "设置音频码率。默认值 '160k'",
206
203
  Opt_type._muxer.value.description: (
207
204
  "设置复用器\n"
208
- " \n" # .
205
+ "\n" # .
209
206
  "可用的复用器:\n"
210
207
  " mp4\n"
211
208
  " mkv"
@@ -323,6 +320,9 @@ LANG_MAP: dict[str, str] = {
323
320
  "Failed to add Ripper: {}": "添加 Ripper 失败: {}",
324
321
  "'{}' is not a valid '{}', set to default value '{}'. Valid options are: {}": "'{}' 不存在于 '{}', 已设为默认值 '{}'。有以下值可用: {}",
325
322
  "The preset custom must have custom:format or custom:template": "custom 预设必须要有 custom:format 或 custom:template",
323
+ "Run the following commands in order:\n{}": "按顺序执行以下命令:\n{}",
324
+ "Run the command {}": "执行命令 {}",
325
+ "Command run failed: status code {}\n Failed command: {}": "命令执行失败: 状态码 {}\n 失败的命令: {}",
326
326
  "There have error in running": "执行时出错",
327
327
  "{} param illegal": "{} 参数非法",
328
328
  'The file "{}" already exists, skip translating it': '文件 "{}" 已存在, 跳过翻译',
@@ -5,12 +5,7 @@ from time import sleep
5
5
  from typing import Final
6
6
 
7
7
  from ..easyrip_web.third_party_api import zhconvert
8
- from .global_lang_val import (
9
- Lang_tag,
10
- Lang_tag_language,
11
- Lang_tag_region,
12
- Lang_tag_script,
13
- )
8
+ from .global_lang_val import Lang_tag
14
9
 
15
10
 
16
11
  def translate_subtitles(
@@ -64,52 +59,52 @@ def translate_subtitles(
64
59
  match Lang_tag.from_str(infix):
65
60
  case (
66
61
  Lang_tag(
67
- language=Lang_tag_language.zh,
68
- script=Lang_tag_script.Hans,
62
+ language=Lang_tag.Language.zh,
63
+ script=Lang_tag.Script.Hans,
69
64
  region=_,
70
65
  )
71
66
  | Lang_tag(
72
- language=Lang_tag_language.zh,
67
+ language=Lang_tag.Language.zh,
73
68
  script=_,
74
- region=Lang_tag_region.CN,
69
+ region=Lang_tag.Region.CN,
75
70
  )
76
71
  ):
77
72
  # 简体 -> 繁体 or 非 CN 简体 CN 化
78
73
  match target_lang_tag:
79
74
  case Lang_tag(
80
- language=Lang_tag_language.zh,
75
+ language=Lang_tag.Language.zh,
81
76
  script=_,
82
- region=Lang_tag_region.HK,
77
+ region=Lang_tag.Region.HK,
83
78
  ):
84
79
  zhconvert_target_lang = zhconvert.Target_lang.HK
85
80
 
86
81
  case Lang_tag(
87
- language=Lang_tag_language.zh,
82
+ language=Lang_tag.Language.zh,
88
83
  script=_,
89
- region=Lang_tag_region.TW,
84
+ region=Lang_tag.Region.TW,
90
85
  ):
91
86
  zhconvert_target_lang = zhconvert.Target_lang.TW
92
87
 
93
88
  case Lang_tag(
94
- language=Lang_tag_language.zh,
95
- script=Lang_tag_script.Hant,
89
+ language=Lang_tag.Language.zh,
90
+ script=Lang_tag.Script.Hant,
96
91
  region=_,
97
92
  ):
98
93
  zhconvert_target_lang = zhconvert.Target_lang.Hant
99
94
 
100
95
  case Lang_tag(
101
- language=Lang_tag_language.zh,
102
- script=Lang_tag_script.Hant,
103
- region=Lang_tag_region.CN,
96
+ language=Lang_tag.Language.zh,
97
+ script=Lang_tag.Script.Hant,
98
+ region=Lang_tag.Region.CN,
104
99
  ): # 特殊情况
105
100
  raise Exception(
106
101
  gettext("Unsupported language tag: {}", target_lang_tag)
107
102
  )
108
103
 
109
104
  case Lang_tag(
110
- language=Lang_tag_language.zh,
105
+ language=Lang_tag.Language.zh,
111
106
  script=_,
112
- region=Lang_tag_region.CN,
107
+ region=Lang_tag.Region.CN,
113
108
  ):
114
109
  zhconvert_target_lang = zhconvert.Target_lang.CN
115
110
 
@@ -120,28 +115,28 @@ def translate_subtitles(
120
115
 
121
116
  case (
122
117
  Lang_tag(
123
- language=Lang_tag_language.zh,
124
- script=Lang_tag_script.Hant,
118
+ language=Lang_tag.Language.zh,
119
+ script=Lang_tag.Script.Hant,
125
120
  region=_,
126
121
  )
127
122
  | Lang_tag(
128
- language=Lang_tag_language.zh,
123
+ language=Lang_tag.Language.zh,
129
124
  script=_,
130
- region=Lang_tag_region.HK | Lang_tag_region.TW,
125
+ region=Lang_tag.Region.HK | Lang_tag.Region.TW,
131
126
  )
132
127
  ):
133
128
  # 繁体 -> 简体
134
129
  match target_lang_tag:
135
130
  case Lang_tag(
136
- language=Lang_tag_language.zh,
131
+ language=Lang_tag.Language.zh,
137
132
  script=_,
138
- region=Lang_tag_region.CN,
133
+ region=Lang_tag.Region.CN,
139
134
  ):
140
135
  zhconvert_target_lang = zhconvert.Target_lang.CN
141
136
 
142
137
  case Lang_tag(
143
- language=Lang_tag_language.zh,
144
- script=Lang_tag_script.Hans,
138
+ language=Lang_tag.Language.zh,
139
+ script=Lang_tag.Script.Hans,
145
140
  region=_,
146
141
  ):
147
142
  zhconvert_target_lang = zhconvert.Target_lang.Hans
easyrip/easyrip_prompt.py CHANGED
@@ -1,8 +1,10 @@
1
1
  import os
2
+ import re
2
3
  from collections.abc import Iterable
3
4
 
4
5
  from prompt_toolkit.completion import CompleteEvent, Completer, Completion
5
6
  from prompt_toolkit.document import Document
7
+ from prompt_toolkit.formatted_text import StyleAndTextTuples
6
8
  from prompt_toolkit.history import FileHistory
7
9
 
8
10
  from .global_val import C_Z, get_CONFIG_DIR
@@ -26,47 +28,123 @@ class SmartPathCompleter(Completer):
26
28
  def __init__(self) -> None:
27
29
  pass
28
30
 
31
+ def _highlight_fuzzy_match(
32
+ self,
33
+ suggestion: str,
34
+ user_input: str,
35
+ style_config: dict | None = None,
36
+ ) -> StyleAndTextTuples:
37
+ """
38
+ 高亮显示模糊匹配结果
39
+
40
+ Args:
41
+ suggestion: 建议的完整字符串
42
+ user_input: 用户输入的匹配字符
43
+ style_config: 样式配置字典
44
+
45
+ Returns:
46
+ 包含样式信息的格式化文本
47
+
48
+ """
49
+ if style_config is None:
50
+ style_config = {
51
+ "match_char": "class:fuzzymatch.inside.character",
52
+ "match_section": "class:fuzzymatch.inside",
53
+ "non_match": "class:fuzzymatch.outside",
54
+ }
55
+
56
+ if not user_input:
57
+ # 用户没有输入,返回原始字符串
58
+ return [(style_config["non_match"], suggestion)]
59
+
60
+ # 找到最佳匹配位置
61
+ result = []
62
+
63
+ # 简化的模糊匹配算法
64
+ pattern = ".*?".join(map(re.escape, user_input))
65
+ regex = re.compile(pattern, re.IGNORECASE)
66
+
67
+ match = regex.search(suggestion)
68
+ if not match:
69
+ # 没有匹配,返回原始字符串
70
+ return [(style_config["non_match"], suggestion)]
71
+
72
+ start, end = match.span()
73
+ match_text = suggestion[start:end]
74
+
75
+ # 匹配段之前的文本
76
+ if start > 0:
77
+ result.append((style_config["non_match"], suggestion[:start]))
78
+
79
+ # 匹配段内部的字符
80
+ input_chars = list(user_input)
81
+ for char in match_text:
82
+ if input_chars and char.lower() == input_chars[0].lower():
83
+ result.append((style_config["match_char"], char))
84
+ input_chars.pop(0)
85
+ else:
86
+ result.append((style_config["match_section"], char))
87
+
88
+ # 匹配段之后的文本
89
+ if end < len(suggestion):
90
+ result.append((style_config["non_match"], suggestion[end:]))
91
+
92
+ return result
93
+
94
+ def _fuzzy_filter_and_sort(self, filenames: list[str], match_str: str) -> list[str]:
95
+ """模糊过滤和排序"""
96
+ if not match_str:
97
+ return sorted(filenames)
98
+
99
+ # 构建模糊匹配模式
100
+ pattern = ".*?".join(map(re.escape, match_str))
101
+ regex = re.compile(f"(?=({pattern}))", re.IGNORECASE)
102
+
103
+ matches = []
104
+ for filename in filenames:
105
+ regex_matches = list(regex.finditer(filename))
106
+ if regex_matches:
107
+ # 找到最佳匹配(最左、最短)
108
+ best = min(regex_matches, key=lambda m: (m.start(), len(m.group(1))))
109
+ matches.append((best.start(), len(best.group(1)), filename))
110
+
111
+ # 按匹配质量排序:先按匹配位置,再按匹配长度
112
+ matches.sort(key=lambda x: (x[0], x[1]))
113
+ return [item[2] for item in matches]
114
+
29
115
  def get_completions(
30
116
  self,
31
117
  document: Document,
32
118
  complete_event: CompleteEvent, # noqa: ARG002
33
119
  ) -> Iterable[Completion]:
34
- text = document.text_before_cursor.strip("\"'")
120
+ text = document.text_before_cursor
121
+ input_path = text.strip("\"'")
35
122
 
36
123
  try:
37
- directory = (
38
- os.path.dirname(os.path.join(".", text))
39
- if os.path.dirname(text)
40
- else "."
41
- )
42
-
43
- prefix = os.path.basename(text)
124
+ directory = os.path.dirname(input_path) or "."
125
+ basename = os.path.basename(input_path)
44
126
 
45
- filenames: list[tuple[str, str]] = (
46
- [
47
- (directory, filename)
48
- for filename in os.listdir(directory)
49
- if filename.startswith(prefix)
50
- ]
51
- if os.path.isdir(directory)
52
- else []
127
+ filenames: list[str] = (
128
+ os.listdir(directory) if os.path.isdir(directory) else []
53
129
  )
54
130
 
55
- for directory, filename in sorted(filenames, key=lambda k: k[1]):
56
- completion = filename[len(prefix) :]
57
- full_name = os.path.join(directory, filename)
131
+ for filename in self._fuzzy_filter_and_sort(filenames, basename):
132
+ full_name = (
133
+ filename if directory == "." else os.path.join(directory, filename)
134
+ )
58
135
 
59
136
  if os.path.isdir(full_name):
60
137
  filename += "/"
61
138
 
139
+ completion = full_name
140
+
141
+ if any(c in r""" !$%&()*:;<=>?[]^`{|}~""" for c in completion):
142
+ completion = f'"{completion}"'
143
+
62
144
  yield Completion(
63
- text=(
64
- f'{"" if any(c in text for c in "\\/") else '"'}{completion}"'
65
- if any(c in r"""!$%&()*:;<=>?[]^`{|}~""" for c in completion)
66
- else completion
67
- ),
68
- start_position=0,
69
- display=filename,
145
+ text=completion,
146
+ start_position=-len(text),
147
+ display=self._highlight_fuzzy_match(filename, basename),
70
148
  )
71
149
 
72
150
  except OSError:
easyrip/global_val.py CHANGED
@@ -4,7 +4,7 @@ from functools import cache
4
4
  from pathlib import Path
5
5
 
6
6
  PROJECT_NAME = "Easy Rip"
7
- PROJECT_VERSION = "4.11.2"
7
+ PROJECT_VERSION = "4.12.0"
8
8
  PROJECT_TITLE = f"{PROJECT_NAME} v{PROJECT_VERSION}"
9
9
  PROJECT_URL = "https://github.com/op200/EasyRip"
10
10
  PROJECT_RELEASE_API = "https://api.github.com/repos/op200/EasyRip/releases/latest"
easyrip/ripper/param.py CHANGED
@@ -29,6 +29,8 @@ class Preset_name(enum.Enum):
29
29
 
30
30
  vvenc = "vvenc"
31
31
 
32
+ ffv1 = "ffv1"
33
+
32
34
  h264_amf = "h264_amf"
33
35
  h264_nvenc = "h264_nvenc"
34
36
  h264_qsv = "h264_qsv"
@@ -66,6 +68,16 @@ class Preset_name(enum.Enum):
66
68
  prefix=prefix,
67
69
  )
68
70
 
71
+ def get_param_name_set[T: set[LiteralString] | None](
72
+ self, default: T = None, /
73
+ ) -> set[LiteralString] | T:
74
+ return _PRESET__PARAM_NAME_SET.get(self, default)
75
+
76
+ def get_param_default_dict[T: dict[LiteralString, LiteralString] | None](
77
+ self, default: T = None, /
78
+ ) -> dict[LiteralString, LiteralString] | T:
79
+ return _PRESET__DEFAULT_PARAMS.get(self, default)
80
+
69
81
 
70
82
  class Audio_codec(enum.Enum):
71
83
  copy = "copy"
@@ -119,8 +131,19 @@ class Muxer(enum.Enum):
119
131
  )
120
132
  return DEFAULT
121
133
 
134
+ @classmethod
135
+ def to_help_string(cls, prefix: str = ""):
136
+ return textwrap.indent(
137
+ reduce(
138
+ lambda acc,
139
+ add: f"{acc}{' ' if add.endswith(acc.split()[-1][-4:]) else '\n'}{add}",
140
+ tuple[str](cls._member_map_),
141
+ ),
142
+ prefix=prefix,
143
+ )
122
144
 
123
- X265_PARAMS_NAME: Final[tuple[LiteralString, ...]] = (
145
+
146
+ _X265_PARAM_NAME_SET: Final[set[LiteralString]] = {
124
147
  "crf",
125
148
  "qpmin",
126
149
  "qpmax",
@@ -148,6 +171,7 @@ X265_PARAMS_NAME: Final[tuple[LiteralString, ...]] = (
148
171
  "open-gop",
149
172
  "gop-lookahead",
150
173
  "rc-lookahead",
174
+ "lookahead-slices",
151
175
  "rect",
152
176
  "amp",
153
177
  "cbqpoffs",
@@ -160,8 +184,13 @@ X265_PARAMS_NAME: Final[tuple[LiteralString, ...]] = (
160
184
  "max-tu-size",
161
185
  "level-idc",
162
186
  "sao",
163
- )
164
- X264_PARAMS_NAME: Final[tuple[LiteralString, ...]] = (
187
+ # 性能
188
+ "lookahead-threads",
189
+ "asm",
190
+ "frame-threads",
191
+ "pools",
192
+ }
193
+ _X264_PARAM_NAME_SET: Final[set[LiteralString]] = {
165
194
  "threads",
166
195
  "crf",
167
196
  "psy-rd",
@@ -182,8 +211,29 @@ X264_PARAMS_NAME: Final[tuple[LiteralString, ...]] = (
182
211
  "fast-pskip",
183
212
  "partitions",
184
213
  "direct",
185
- )
214
+ }
215
+ _FFV1_PARAM_NAME_SET: Final[set[LiteralString]] = {
216
+ "slicecrc",
217
+ "coder",
218
+ "context",
219
+ "qtable",
220
+ "remap_mode",
221
+ "remap_optimizer",
222
+ }
186
223
 
224
+ _PRESET__PARAM_NAME_SET: Final[dict[Preset_name, set[LiteralString]]] = {
225
+ Preset_name.x264: _X264_PARAM_NAME_SET,
226
+ Preset_name.x264fast: _X264_PARAM_NAME_SET,
227
+ Preset_name.x264slow: _X264_PARAM_NAME_SET,
228
+ Preset_name.x265: _X265_PARAM_NAME_SET,
229
+ Preset_name.x265fast4: _X265_PARAM_NAME_SET,
230
+ Preset_name.x265fast3: _X265_PARAM_NAME_SET,
231
+ Preset_name.x265fast2: _X265_PARAM_NAME_SET,
232
+ Preset_name.x265fast: _X265_PARAM_NAME_SET,
233
+ Preset_name.x265slow: _X265_PARAM_NAME_SET,
234
+ Preset_name.x265full: _X265_PARAM_NAME_SET,
235
+ Preset_name.ffv1: _FFV1_PARAM_NAME_SET,
236
+ }
187
237
 
188
238
  _DEFAULT_X265_PARAMS: Final[dict[LiteralString, LiteralString]] = {
189
239
  "crf": "20",
@@ -213,6 +263,7 @@ _DEFAULT_X265_PARAMS: Final[dict[LiteralString, LiteralString]] = {
213
263
  "open-gop": "1",
214
264
  "gop-lookahead": "0",
215
265
  "rc-lookahead": "20",
266
+ "lookahead-slices": "8",
216
267
  "rect": "0",
217
268
  "amp": "0",
218
269
  "cbqpoffs": "0",
@@ -227,22 +278,17 @@ _DEFAULT_X265_PARAMS: Final[dict[LiteralString, LiteralString]] = {
227
278
  "sao": "0",
228
279
  "weightb": "1",
229
280
  "info": "1",
281
+ # 性能
282
+ "lookahead-threads": "0",
283
+ "asm": "auto",
284
+ "frame-threads": "0",
285
+ "pools": "*",
230
286
  }
231
287
 
232
288
 
233
- PRESET_OPT_NAME: Final[dict[Preset_name, tuple[LiteralString, ...]]] = {
234
- Preset_name.x264: X264_PARAMS_NAME,
235
- Preset_name.x264fast: X264_PARAMS_NAME,
236
- Preset_name.x264slow: X264_PARAMS_NAME,
237
- Preset_name.x265: X265_PARAMS_NAME,
238
- Preset_name.x265fast4: X265_PARAMS_NAME,
239
- Preset_name.x265fast3: X265_PARAMS_NAME,
240
- Preset_name.x265fast2: X265_PARAMS_NAME,
241
- Preset_name.x265fast: X265_PARAMS_NAME,
242
- Preset_name.x265slow: X265_PARAMS_NAME,
243
- Preset_name.x265full: X265_PARAMS_NAME,
244
- }
245
- DEFAULT_PRESET_PARAMS: Final[dict[Preset_name, dict[LiteralString, LiteralString]]] = {
289
+ _PRESET__DEFAULT_PARAMS: Final[
290
+ dict[Preset_name, dict[LiteralString, LiteralString]]
291
+ ] = {
246
292
  Preset_name.x264fast: {
247
293
  "threads": "auto",
248
294
  "crf": "20",
@@ -391,6 +437,7 @@ DEFAULT_PRESET_PARAMS: Final[dict[Preset_name, dict[LiteralString, LiteralString
391
437
  "subme": "5",
392
438
  "gop-lookahead": "8",
393
439
  "rc-lookahead": "216",
440
+ "lookahead-slices": "4",
394
441
  "cbqpoffs": "-2",
395
442
  "crqpoffs": "-2",
396
443
  "pbratio": "1.2",
@@ -421,6 +468,7 @@ DEFAULT_PRESET_PARAMS: Final[dict[Preset_name, dict[LiteralString, LiteralString
421
468
  "subme": "6",
422
469
  "gop-lookahead": "14",
423
470
  "rc-lookahead": "250",
471
+ "lookahead-slices": "2",
424
472
  "rect": "1",
425
473
  "min-keyint": "2",
426
474
  "cbqpoffs": "-2",
@@ -456,6 +504,7 @@ DEFAULT_PRESET_PARAMS: Final[dict[Preset_name, dict[LiteralString, LiteralString
456
504
  "open-gop": "1",
457
505
  "gop-lookahead": "14",
458
506
  "rc-lookahead": "250",
507
+ "lookahead-slices": "1",
459
508
  "rect": "1",
460
509
  "amp": "1",
461
510
  "cbqpoffs": "-3",