easyrip 3.13.2__py3-none-any.whl → 4.9.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.
Files changed (36) hide show
  1. easyrip/__init__.py +5 -1
  2. easyrip/__main__.py +124 -15
  3. easyrip/easyrip_command.py +457 -148
  4. easyrip/easyrip_config/config.py +269 -0
  5. easyrip/easyrip_config/config_key.py +28 -0
  6. easyrip/easyrip_log.py +120 -42
  7. easyrip/easyrip_main.py +509 -259
  8. easyrip/easyrip_mlang/__init__.py +20 -45
  9. easyrip/easyrip_mlang/global_lang_val.py +18 -16
  10. easyrip/easyrip_mlang/lang_en.py +1 -1
  11. easyrip/easyrip_mlang/lang_zh_Hans_CN.py +101 -77
  12. easyrip/easyrip_mlang/translator.py +12 -10
  13. easyrip/easyrip_prompt.py +73 -0
  14. easyrip/easyrip_web/__init__.py +2 -1
  15. easyrip/easyrip_web/http_server.py +56 -42
  16. easyrip/easyrip_web/third_party_api.py +60 -8
  17. easyrip/global_val.py +21 -1
  18. easyrip/ripper/media_info.py +10 -3
  19. easyrip/ripper/param.py +482 -0
  20. easyrip/ripper/ripper.py +260 -574
  21. easyrip/ripper/sub_and_font/__init__.py +10 -0
  22. easyrip/ripper/{font_subset → sub_and_font}/ass.py +95 -84
  23. easyrip/ripper/{font_subset → sub_and_font}/font.py +72 -79
  24. easyrip/ripper/{font_subset → sub_and_font}/subset.py +122 -81
  25. easyrip/utils.py +129 -27
  26. easyrip-4.9.1.dist-info/METADATA +92 -0
  27. easyrip-4.9.1.dist-info/RECORD +31 -0
  28. easyrip/easyrip_config.py +0 -198
  29. easyrip/ripper/__init__.py +0 -10
  30. easyrip/ripper/font_subset/__init__.py +0 -7
  31. easyrip-3.13.2.dist-info/METADATA +0 -89
  32. easyrip-3.13.2.dist-info/RECORD +0 -29
  33. {easyrip-3.13.2.dist-info → easyrip-4.9.1.dist-info}/WHEEL +0 -0
  34. {easyrip-3.13.2.dist-info → easyrip-4.9.1.dist-info}/entry_points.txt +0 -0
  35. {easyrip-3.13.2.dist-info → easyrip-4.9.1.dist-info}/licenses/LICENSE +0 -0
  36. {easyrip-3.13.2.dist-info → easyrip-4.9.1.dist-info}/top_level.txt +0 -0
easyrip/ripper/ripper.py CHANGED
@@ -1,131 +1,64 @@
1
- import enum
2
1
  import os
3
2
  import re
4
3
  import shutil
4
+ from collections.abc import Callable, Iterable
5
5
  from dataclasses import dataclass
6
6
  from datetime import datetime
7
7
  from itertools import zip_longest
8
8
  from pathlib import Path
9
9
  from threading import Thread
10
10
  from time import sleep
11
- from typing import Callable, Iterable, Self
11
+ from typing import Final, Self, final
12
12
 
13
13
  from .. import easyrip_web
14
14
  from ..easyrip_log import log
15
15
  from ..easyrip_mlang import Global_lang_val, gettext, translate_subtitles
16
16
  from ..utils import get_base62_time
17
- from .font_subset import subset
18
- from .media_info import Media_info
17
+ from .media_info import Media_info, Stream_error
18
+ from .param import (
19
+ DEFAULT_PRESET_PARAMS,
20
+ FONT_SUFFIX_SET,
21
+ SUBTITLE_SUFFIX_SET,
22
+ X264_PARAMS_NAME,
23
+ X265_PARAMS_NAME,
24
+ )
25
+ from .sub_and_font import subset
19
26
 
20
27
  FF_PROGRESS_LOG_FILE = Path("FFProgress.log")
21
28
  FF_REPORT_LOG_FILE = Path("FFReport.log")
22
29
 
23
30
 
31
+ @final
24
32
  class Ripper:
25
- ripper_list: list["Ripper"] = []
33
+ ripper_list: Final[list["Ripper"]] = []
26
34
 
27
- @staticmethod
35
+ @classmethod
28
36
  def add_ripper(
37
+ cls: type["Ripper"],
29
38
  input_path: Iterable[str | Path],
30
39
  output_prefix: Iterable[str | None],
31
40
  output_dir: str | None,
32
- option: "Option | PresetName",
41
+ option: "Option | Preset_name",
33
42
  option_map: dict[str, str],
34
43
  ):
35
44
  try:
36
- Ripper.ripper_list.append(
37
- Ripper(input_path, output_prefix, output_dir, option, option_map)
45
+ cls.ripper_list.append(
46
+ cls(input_path, output_prefix, output_dir, option, option_map)
38
47
  )
39
48
  except Exception as e:
40
49
  log.error("Failed to add Ripper: {}", e, deep=True)
41
50
 
42
- class PresetName(enum.Enum):
43
- custom = "custom"
44
-
45
- copy = "copy"
46
-
47
- subset = "subset"
48
-
49
- flac = "flac"
50
-
51
- x264fast = "x264fast"
52
- x264slow = "x264slow"
53
-
54
- x265fast4 = "x265fast4"
55
- x265fast3 = "x265fast3"
56
- x265fast2 = "x265fast2"
57
- x265fast = "x265fast"
58
- x265slow = "x265slow"
59
- x265full = "x265full"
60
-
61
- svtav1 = "svtav1"
62
-
63
- h264_amf = "h264_amf"
64
- h264_nvenc = "h264_nvenc"
65
- h264_qsv = "h264_qsv"
66
-
67
- hevc_amf = "hevc_amf"
68
- hevc_nvenc = "hevc_nvenc"
69
- hevc_qsv = "hevc_qsv"
70
-
71
- av1_amf = "av1_amf"
72
- av1_nvenc = "av1_nvenc"
73
- av1_qsv = "av1_qsv"
74
-
75
- @classmethod
76
- def _missing_(cls, value: object):
77
- default = cls.custom
78
- log.error(
79
- "'{}' is not a valid '{}', set to default value '{}'. Valid options are: {}",
80
- value,
81
- cls.__name__,
82
- default,
83
- list(cls.__members__.keys()),
84
- )
85
- return default
86
-
87
- class AudioCodec(enum.Enum):
88
- copy = "copy"
89
- libopus = "libopus"
90
- flac = "flac"
91
-
92
- @classmethod
93
- def _missing_(cls, value: object):
94
- default = cls.copy
95
- log.error(
96
- "'{}' is not a valid '{}', set to default value '{}'. Valid options are: {}",
97
- value,
98
- cls.__name__,
99
- default,
100
- list(cls.__members__.keys()),
101
- )
102
- return default
103
-
104
- class Muxer(enum.Enum):
105
- mp4 = "mp4"
106
- mkv = "mkv"
107
-
108
- @classmethod
109
- def _missing_(cls, value: object):
110
- default = cls.mkv
111
- log.error(
112
- "'{}' is not a valid '{}', set to default value '{}'. Valid options are: {}",
113
- value,
114
- cls.__name__,
115
- default,
116
- list(cls.__members__.keys()),
117
- )
118
- return default
51
+ from .param import Audio_codec, Muxer, Preset_name
119
52
 
120
53
  @dataclass(slots=True)
121
54
  class Option:
122
- preset_name: "Ripper.PresetName"
55
+ preset_name: "Ripper.Preset_name"
123
56
  encoder_format_str: str
124
- audio_encoder: "Ripper.AudioCodec | None"
57
+ audio_encoder: "Ripper.Audio_codec | None"
125
58
  muxer: "Ripper.Muxer | None"
126
59
  muxer_format_str: str
127
60
 
128
- def __str__(self):
61
+ def __str__(self) -> str:
129
62
  return f" preset_name = {self.preset_name}\n option_format = {self.encoder_format_str}"
130
63
 
131
64
  input_path_list: list[Path]
@@ -134,7 +67,7 @@ class Ripper:
134
67
  option: Option
135
68
  option_map: dict[str, str]
136
69
 
137
- preset_name: PresetName
70
+ preset_name: Preset_name
138
71
 
139
72
  media_info: Media_info
140
73
 
@@ -157,9 +90,9 @@ class Ripper:
157
90
  input_path: Iterable[str | Path],
158
91
  output_prefix: Iterable[str | None],
159
92
  output_dir: str | None,
160
- option: Option | PresetName,
93
+ option: Option | Preset_name,
161
94
  option_map: dict[str, str],
162
- ):
95
+ ) -> None:
163
96
  self.input_path_list = [Path(path) for path in input_path]
164
97
 
165
98
  self.media_info = Media_info.from_path(self.input_path_list[0])
@@ -182,16 +115,16 @@ class Ripper:
182
115
  "The muxer must be 'mkv' when mux subtitle and font. Auto modified"
183
116
  )
184
117
 
185
- if isinstance(option, Ripper.PresetName):
118
+ if isinstance(option, Ripper.Preset_name):
186
119
  self.preset_name = option
187
120
  self.option = self.preset_name_to_option(option)
188
121
  else:
189
- self.preset_name = Ripper.PresetName.custom
122
+ self.preset_name = Ripper.Preset_name.custom
190
123
  self.option = option
191
124
 
192
- self._progress = dict[str, int | float]()
125
+ self._progress: dict[str, int | float] = {}
193
126
 
194
- def __str__(self):
127
+ def __str__(self) -> str:
195
128
  return (
196
129
  f"-i {self.input_path_list[0]} -o {self.output_prefix_list[0]} -o:dir {self.output_dir} -preset {self.option.preset_name.value} {' '.join((f'-{key} {val}' for key, val in self.option_map.items()))}\n"
197
130
  " option: {\n"
@@ -200,7 +133,7 @@ class Ripper:
200
133
  f" option_map: {self.option_map}"
201
134
  )
202
135
 
203
- def preset_name_to_option(self, preset_name: PresetName) -> Option:
136
+ def preset_name_to_option(self, preset_name: Preset_name) -> Option:
204
137
  if (
205
138
  force_fps := self.option_map.get("r") or self.option_map.get("fps")
206
139
  ) == "auto":
@@ -228,10 +161,7 @@ class Ripper:
228
161
  is_pipe_input = bool(self.input_path_list[0].suffix == ".vpy" or vpy_pathname)
229
162
 
230
163
  ff_input_option: list[str]
231
- if is_pipe_input:
232
- ff_input_option = ["-"]
233
- else:
234
- ff_input_option = ['"{input}"']
164
+ ff_input_option = ["-"] if is_pipe_input else ['"{input}"']
235
165
  ff_stream_option: list[str] = ["0:v"]
236
166
  ff_vf_option: list[str] = (
237
167
  s.split(",") if (s := self.option_map.get("vf")) else []
@@ -242,29 +172,66 @@ class Ripper:
242
172
  ff_vf_option.append(f"ass={sub_pathname}")
243
173
 
244
174
  # Audio
245
- if audio_encoder := self.option_map.get("c:a"):
246
- _audio_encoder_str = audio_encoder
247
- audio_encoder = Ripper.AudioCodec(audio_encoder)
175
+ if audio_encoder_str := self.option_map.get("c:a"):
176
+ if (
177
+ not self.media_info.audio_info
178
+ and self.preset_name != Ripper.Preset_name.subset
179
+ ):
180
+ raise Stream_error(
181
+ "There is no audio stream in the video, so '-c:a' cannot be used"
182
+ )
183
+
184
+ if audio_encoder_str not in Ripper.Audio_codec._member_map_:
185
+ raise ValueError(
186
+ gettext("Unsupported '{}' param: {}", "-c:a", audio_encoder_str)
187
+ )
188
+
189
+ audio_encoder = Ripper.Audio_codec[audio_encoder_str]
190
+
191
+ # 通知别名映射
192
+ if audio_encoder_str not in Ripper.Audio_codec._member_names_:
193
+ log.info(
194
+ "Auto mapping encoder name: {} -> {}",
195
+ audio_encoder_str,
196
+ audio_encoder.name,
197
+ )
198
+
248
199
  if is_pipe_input:
249
200
  ff_input_option.append('"{input}"')
250
201
  ff_stream_option.append("1:a")
251
202
  else:
252
203
  ff_stream_option.append("0:a")
253
204
 
254
- _encoder_str = (
255
- ""
256
- if audio_encoder == Ripper.AudioCodec.copy
257
- and self.preset_name == Ripper.PresetName.copy
258
- else "-an "
259
- if audio_encoder == Ripper.AudioCodec.flac
260
- else f"-c:a {_audio_encoder_str} "
261
- )
205
+ match audio_encoder:
206
+ case Ripper.Audio_codec.copy:
207
+ _encoder_str = (
208
+ ""
209
+ if self.preset_name == Ripper.Preset_name.copy
210
+ else "-c:a copy "
211
+ )
212
+ case Ripper.Audio_codec.flac:
213
+ _encoder_str = "-an "
214
+ case Ripper.Audio_codec.libopus:
215
+ _encoder_str = "-c:a libopus "
216
+ for opt in (
217
+ "application",
218
+ "frame_duration",
219
+ "packet_loss",
220
+ "fec",
221
+ "vbr",
222
+ "mapping_family",
223
+ "apply_phase_inv",
224
+ ):
225
+ if (val := self.option_map.get(opt)) is not None:
226
+ _encoder_str += f"-{opt} {val} "
227
+
262
228
  _bitrate_str = (
263
229
  ""
264
- if audio_encoder in {Ripper.AudioCodec.copy, Ripper.AudioCodec.flac}
265
- else f"-b:a {self.option_map.get('b:a') or '160k'}"
230
+ if audio_encoder in {Ripper.Audio_codec.copy, Ripper.Audio_codec.flac}
231
+ else f"-b:a {self.option_map.get('b:a') or '160k'} "
266
232
  )
267
- audio_option = f"{_encoder_str}{_bitrate_str} "
233
+
234
+ audio_option = _encoder_str + _bitrate_str
268
235
 
269
236
  else:
270
237
  audio_encoder = None
@@ -285,7 +252,7 @@ class Ripper:
285
252
  )
286
253
  + (
287
254
  ""
288
- if self.preset_name == Ripper.PresetName.flac
255
+ if self.preset_name == Ripper.Preset_name.flac
289
256
  else (
290
257
  "&& mp4fpsmod "
291
258
  + (f"-r 0:{force_fps}" if force_fps else "")
@@ -324,22 +291,13 @@ class Ripper:
324
291
  )
325
292
  == 1
326
293
  else "--attach-file "
327
- if _file.suffix in {".otf", ".ttf", ".ttc"}
294
+ if _file.suffix in FONT_SUFFIX_SET
328
295
  else f"--language 0:{affixes[1]} --track-name 0:{Global_lang_val.language_tag_to_local_str(affixes[1])} "
329
296
  )
330
297
  + f'"{_file.absolute()}"'
331
298
  for _file in only_mux_sub_path.iterdir()
332
299
  if _file.suffix
333
- in {
334
- ".srt",
335
- ".ass",
336
- ".ssa",
337
- ".sup",
338
- ".idx",
339
- ".otf",
340
- ".ttf",
341
- ".ttc",
342
- }
300
+ in (SUBTITLE_SUFFIX_SET | FONT_SUFFIX_SET)
343
301
  )
344
302
  if only_mux_sub_path
345
303
  else ""
@@ -355,9 +313,9 @@ class Ripper:
355
313
  pipe_gvar_list = [
356
314
  s for s in self.option_map.get("pipe:gvar", "").split(":") if s
357
315
  ]
358
- pipe_gvar_dict = {
359
- s.split("=")[0]: s.split("=")[1] for s in pipe_gvar_list if "=" in s
360
- }
316
+ pipe_gvar_dict = dict(
317
+ s.split("=", maxsplit=1) for s in pipe_gvar_list if "=" in s
318
+ )
361
319
  if sub_pathname:
362
320
  pipe_gvar_dict["subtitle"] = sub_pathname
363
321
 
@@ -373,19 +331,19 @@ class Ripper:
373
331
  ffparams_ff = self.option_map.get("ff-params:ff") or self.option_map.get(
374
332
  "ff-params", ""
375
333
  )
376
- ffparams_in = self.option_map.get("ff-params:in", "")
377
- ffparams_out = self.option_map.get("ff-params:out", "")
334
+ ffparams_in = self.option_map.get("ff-params:in", "") + " "
335
+ ffparams_out = self.option_map.get("ff-params:out", "") + " "
378
336
  if _ss := self.option_map.get("ss"):
379
- ffparams_in += f" -ss {_ss}"
337
+ ffparams_in += f"-ss {_ss} "
380
338
  if _t := self.option_map.get("t"):
381
- ffparams_out += f" -t {_t}"
339
+ ffparams_out += f"-t {_t} "
382
340
  if _preset := self.option_map.get("v:preset"):
383
- ffparams_out += f" -preset {_preset}"
341
+ ffparams_out += f"-preset {_preset} "
384
342
 
385
343
  FFMPEG_HEADER = f"ffmpeg {'-hide_banner ' if self.option_map.get('_sub_ripper_num') else ''}-progress {FF_PROGRESS_LOG_FILE} -report {ffparams_ff} {ffparams_in}"
386
344
 
387
345
  match preset_name:
388
- case Ripper.PresetName.custom:
346
+ case Ripper.Preset_name.custom:
389
347
  if not (
390
348
  encoder_format_str := self.option_map.get(
391
349
  "custom:format",
@@ -412,7 +370,7 @@ class Ripper:
412
370
  )
413
371
  )
414
372
 
415
- case Ripper.PresetName.copy:
373
+ case Ripper.Preset_name.copy:
416
374
  hwaccel = (
417
375
  f"-hwaccel {hwaccel}"
418
376
  if (hwaccel := self.option_map.get("hwaccel"))
@@ -421,9 +379,10 @@ class Ripper:
421
379
 
422
380
  encoder_format_str = (
423
381
  f"{FFMPEG_HEADER} {hwaccel} "
424
- + '-i "{input}" -c copy '
425
- + f"{' '.join(f'-map {s}' for s in ff_stream_option)} "
426
- + f"{audio_option} {ffparams_out} "
382
+ '-i "{input}" -c copy '
383
+ f"{' '.join(f'-map {s}' for s in ff_stream_option)} "
384
+ + audio_option
385
+ + ffparams_out
427
386
  + '"{output}"'
428
387
  )
429
388
 
@@ -444,10 +403,10 @@ class Ripper:
444
403
  else 'mkvmerge -o "{output}" "{input}"'
445
404
  )
446
405
 
447
- case Ripper.PresetName.flac:
406
+ case Ripper.Preset_name.flac:
448
407
  _ff_encode_str: str = ""
449
408
  _flac_encode_str: str = ""
450
- _mux_flac_input_list = list[str]()
409
+ _mux_flac_input_list: list[str] = []
451
410
  # _mux_flac_map_str: str = ""
452
411
  _del_flac_str: str = ""
453
412
 
@@ -505,7 +464,7 @@ class Ripper:
505
464
  case _:
506
465
  _mux_str = (
507
466
  f"mp4box -add {' -add '.join(_mux_flac_input_list)}"
508
- + ' -new "{output}" '
467
+ ' -new "{output}" '
509
468
  if muxer == Ripper.Muxer.mp4
510
469
  else 'mkvmerge -o "{output}" '
511
470
  + " ".join(_mux_flac_input_list)
@@ -516,395 +475,108 @@ class Ripper:
516
475
  f"&& {_mux_str} " + _del_flac_str
517
476
  )
518
477
 
519
- case Ripper.PresetName.x264fast | Ripper.PresetName.x264slow:
520
- match preset_name:
521
- case Ripper.PresetName.x264fast:
522
- _option_map = {
523
- "threads": self.option_map.get("threads", "auto"),
524
- # Select
525
- "crf": self.option_map.get("crf", "20"),
526
- "psy-rd": self.option_map.get("psy-rd", "0.6,0.15"),
527
- "qcomp": self.option_map.get("qcomp", "0.66"),
528
- "keyint": self.option_map.get("keyint", "250"),
529
- "deblock": self.option_map.get("deblock", "0,0"),
530
- # Default
531
- "qpmin": self.option_map.get("qpmin", "8"),
532
- "qpmax": self.option_map.get("qpmax", "32"),
533
- "bframes": self.option_map.get("bframes", "8"),
534
- "ref": self.option_map.get("ref", "4"),
535
- "subme": self.option_map.get("subme", "5"),
536
- "me": self.option_map.get("me", "hex"),
537
- "merange": self.option_map.get("merange", "16"),
538
- "aq-mode": self.option_map.get("aq-mode", "1"),
539
- "rc-lookahead": self.option_map.get("rc-lookahead", "60"),
540
- "min-keyint": self.option_map.get("min-keyint", "2"),
541
- "trellis": self.option_map.get("trellis", "1"),
542
- "fast-pskip": self.option_map.get("fast-pskip", "1"),
543
- # No change
544
- "weightb": "1",
545
- **{
546
- k: v
547
- for k, v in [
548
- s.split("=")
549
- for s in str(
550
- self.option_map.get("x264-params", "")
551
- ).split(":")
552
- if s != ""
553
- ]
554
- },
555
- }
556
- case Ripper.PresetName.x264slow:
557
- _option_map = {
558
- "threads": self.option_map.get("threads", "auto"),
559
- # Select
560
- "crf": self.option_map.get("crf", "21"),
561
- "psy-rd": self.option_map.get("psy-rd", "0.6,0.15"),
562
- "qcomp": self.option_map.get("qcomp", "0.66"),
563
- "keyint": self.option_map.get("keyint", "250"),
564
- "deblock": self.option_map.get("deblock", "-1,-1"),
565
- # Default
566
- "qpmin": self.option_map.get("qpmin", "8"),
567
- "qpmax": self.option_map.get("qpmax", "32"),
568
- "bframes": self.option_map.get("bframes", "16"),
569
- "ref": self.option_map.get("ref", "8"),
570
- "subme": self.option_map.get("subme", "7"),
571
- "me": self.option_map.get("me", "umh"),
572
- "merange": self.option_map.get("merange", "24"),
573
- "aq-mode": self.option_map.get("aq-mode", "3"),
574
- "rc-lookahead": self.option_map.get("rc-lookahead", "120"),
575
- "min-keyint": self.option_map.get("min-keyint", "2"),
576
- "trellis": self.option_map.get("trellis", "2"),
577
- "fast-pskip": self.option_map.get("fast-pskip", "0"),
578
- # No change
579
- "weightb": "1",
580
- **{
581
- k: v
582
- for k, v in [
583
- s.split("=")
584
- for s in str(
585
- self.option_map.get("x264-params", "")
586
- ).split(":")
587
- if s != ""
588
- ]
589
- },
590
- }
478
+ case (
479
+ Ripper.Preset_name.x264
480
+ | Ripper.Preset_name.x264fast
481
+ | Ripper.Preset_name.x264slow
482
+ ):
483
+ _custom_option_map: dict[str, str] = {
484
+ k: v
485
+ for k, v in {
486
+ **{
487
+ _param_name: self.option_map.get(_param_name)
488
+ for _param_name in X264_PARAMS_NAME
489
+ },
490
+ **dict(
491
+ s.split("=", maxsplit=1)
492
+ for s in str(self.option_map.get("x264-params", "")).split(
493
+ ":"
494
+ )
495
+ if s
496
+ ),
497
+ }.items()
498
+ if v is not None
499
+ }
500
+
501
+ _option_map = (
502
+ DEFAULT_PRESET_PARAMS.get(preset_name, {}) | _custom_option_map
503
+ )
591
504
 
592
505
  if (
593
506
  (_crf := _option_map.get("crf"))
594
507
  and (_qpmin := _option_map.get("qpmin"))
595
508
  and (_qpmax := _option_map.get("qpmax"))
596
- and not (_qpmin <= _crf <= _qpmax)
509
+ and not (float(_qpmin) <= float(_crf) <= float(_qpmax))
597
510
  ):
598
511
  log.warning("The CRF is not between QPmin and QPmax")
599
512
 
600
- _param = ":".join((f"{key}={val}" for key, val in _option_map.items()))
513
+ _param = ":".join(f"{key}={val}" for key, val in _option_map.items())
601
514
 
602
515
  encoder_format_str = (
603
516
  f"{vspipe_input} {FFMPEG_HEADER} {hwaccel} {' '.join(f'-i {s}' for s in ff_input_option)} {' '.join(f'-map {s}' for s in ff_stream_option)} "
604
- + f"{audio_option} -c:v libx264 {'' if is_pipe_input else '-pix_fmt yuv420p'} -x264-params "
517
+ + audio_option
518
+ + f"-c:v libx264 {'' if is_pipe_input else '-pix_fmt yuv420p'} -x264-params "
605
519
  + f'"{_param}" {ffparams_out} '
606
- + (f' -vf "{",".join(ff_vf_option)}" ' if len(ff_vf_option) else "")
607
- + ' "{output}"'
520
+ + (f'-vf "{",".join(ff_vf_option)}" ' if len(ff_vf_option) else "")
521
+ + '"{output}"'
608
522
  )
609
523
 
610
524
  case (
611
- Ripper.PresetName.x265fast4
612
- | Ripper.PresetName.x265fast3
613
- | Ripper.PresetName.x265fast2
614
- | Ripper.PresetName.x265fast
615
- | Ripper.PresetName.x265slow
616
- | Ripper.PresetName.x265full
525
+ Ripper.Preset_name.x265
526
+ | Ripper.Preset_name.x265fast4
527
+ | Ripper.Preset_name.x265fast3
528
+ | Ripper.Preset_name.x265fast2
529
+ | Ripper.Preset_name.x265fast
530
+ | Ripper.Preset_name.x265slow
531
+ | Ripper.Preset_name.x265full
617
532
  ):
618
- _default_option_map = {
619
- "crf": "20",
620
- "qpmin": "6",
621
- "qpmax": "32",
622
- "rd": "3",
623
- "psy-rd": "2",
624
- "rdoq-level": "0",
625
- "psy-rdoq": "0",
626
- "qcomp": "0.68",
627
- "keyint": "250",
628
- "min-keyint": "2",
629
- "deblock": "0,0",
630
- "me": "umh",
631
- "merange": "57",
632
- "hme": "1",
633
- "hme-search": "hex,hex,hex",
634
- "hme-range": "16,57,92",
635
- "aq-mode": "2",
636
- "aq-strength": "1",
637
- "tu-intra-depth": "1",
638
- "tu-inter-depth": "1",
639
- "limit-tu": "0",
640
- "bframes": "16",
641
- "ref": "8",
642
- "subme": "2",
643
- "open-gop": "1",
644
- "gop-lookahead": "0",
645
- "rc-lookahead": "20",
646
- "rect": "0",
647
- "amp": "0",
648
- "cbqpoffs": "0",
649
- "crqpoffs": "0",
650
- "ipratio": "1.4",
651
- "pbratio": "1.3",
652
- "early-skip": "1",
653
- "ctu": "64",
654
- "min-cu-size": "8",
655
- "max-tu-size": "32",
656
- "level-idc": "0",
657
- "sao": "0",
658
- "weightb": "1",
659
- "info": "1",
660
- }
661
- _custom_option_map: dict[str, str | None] = {
662
- "crf": self.option_map.get("crf"),
663
- "qpmin": self.option_map.get("qpmin"),
664
- "qpmax": self.option_map.get("qpmax"),
665
- "psy-rd": self.option_map.get("psy-rd"),
666
- "rd": self.option_map.get("rd"),
667
- "rdoq-level": self.option_map.get("rdoq-level"),
668
- "psy-rdoq": self.option_map.get("psy-rdoq"),
669
- "qcomp": self.option_map.get("qcomp"),
670
- "keyint": self.option_map.get("keyint"),
671
- "min-keyint": self.option_map.get("min-keyint"),
672
- "deblock": self.option_map.get("deblock"),
673
- "me": self.option_map.get("me"),
674
- "merange": self.option_map.get("merange"),
675
- "hme": self.option_map.get("hme"),
676
- "hme-search": self.option_map.get("hme-search"),
677
- "hme-range": self.option_map.get("hme-range"),
678
- "aq-mode": self.option_map.get("aq-mode"),
679
- "aq-strength": self.option_map.get("aq-strength"),
680
- "tu-intra-depth": self.option_map.get("tu-intra-depth"),
681
- "tu-inter-depth": self.option_map.get("tu-inter-depth"),
682
- "limit-tu": self.option_map.get("limit-tu"),
683
- "bframes": self.option_map.get("bframes"),
684
- "ref": self.option_map.get("ref"),
685
- "subme": self.option_map.get("subme"),
686
- "open-gop": self.option_map.get("open-gop"),
687
- "gop-lookahead": self.option_map.get("gop-lookahead"),
688
- "rc-lookahead": self.option_map.get("rc-lookahead"),
689
- "rect": self.option_map.get("rect"),
690
- "amp": self.option_map.get("amp"),
691
- "cbqpoffs": self.option_map.get("cbqpoffs"),
692
- "crqpoffs": self.option_map.get("crqpoffs"),
693
- "ipratio": self.option_map.get("ipratio"),
694
- "pbratio": self.option_map.get("pbratio"),
695
- "early-skip": self.option_map.get("early-skip"),
696
- "ctu": self.option_map.get("ctu"),
697
- "min-cu-size": self.option_map.get("min-cu-size"),
698
- "max-tu-size": self.option_map.get("max-tu-size"),
699
- "level-idc": self.option_map.get("level-idc"),
700
- "sao": self.option_map.get("sao"),
701
- **{
702
- k: v
703
- for k, v in [
704
- s.split("=")
533
+ _custom_option_map: dict[str, str] = {
534
+ k: v
535
+ for k, v in {
536
+ **{
537
+ _param_name: self.option_map.get(_param_name)
538
+ for _param_name in X265_PARAMS_NAME
539
+ },
540
+ **dict(
541
+ s.split("=", maxsplit=1)
705
542
  for s in str(self.option_map.get("x265-params", "")).split(
706
543
  ":"
707
544
  )
708
- if s != ""
709
- ]
710
- },
711
- }
712
- _custom_option_map = {
713
- k: v for k, v in _custom_option_map.items() if v is not None
545
+ if s
546
+ ),
547
+ }.items()
548
+ if v is not None
714
549
  }
715
550
 
716
- match preset_name:
717
- case Ripper.PresetName.x265fast4:
718
- _option_map = {
719
- "crf": "18",
720
- "qpmin": "12",
721
- "qpmax": "28",
722
- "rd": "2",
723
- "rdoq-level": "1",
724
- "me": "hex",
725
- "merange": "57",
726
- "hme-search": "hex,hex,hex",
727
- "hme-range": "16,32,48",
728
- "aq-mode": "1",
729
- "tu-intra-depth": "1",
730
- "tu-inter-depth": "1",
731
- "limit-tu": "4",
732
- "bframes": "8",
733
- "ref": "6",
734
- "subme": "3",
735
- "open-gop": "0",
736
- "gop-lookahead": "0",
737
- "rc-lookahead": "48",
738
- "cbqpoffs": "-1",
739
- "crqpoffs": "-1",
740
- "pbratio": "1.28",
741
- }
742
- case Ripper.PresetName.x265fast3:
743
- _option_map = {
744
- "crf": "18",
745
- "qpmin": "12",
746
- "qpmax": "28",
747
- "rdoq-level": "1",
748
- "deblock": "-0.5,-0.5",
749
- "me": "hex",
750
- "merange": "57",
751
- "hme-search": "hex,hex,hex",
752
- "hme-range": "16,32,57",
753
- "aq-mode": "3",
754
- "tu-intra-depth": "2",
755
- "tu-inter-depth": "2",
756
- "limit-tu": "4",
757
- "bframes": "12",
758
- "ref": "6",
759
- "subme": "3",
760
- "open-gop": "0",
761
- "gop-lookahead": "0",
762
- "rc-lookahead": "120",
763
- "cbqpoffs": "-1",
764
- "crqpoffs": "-1",
765
- "pbratio": "1.27",
766
- }
767
- case Ripper.PresetName.x265fast2:
768
- _option_map = {
769
- "crf": "18",
770
- "qpmin": "12",
771
- "qpmax": "28",
772
- "rdoq-level": "2",
773
- "deblock": "-1,-1",
774
- "me": "hex",
775
- "merange": "57",
776
- "hme-search": "hex,hex,hex",
777
- "hme-range": "16,57,92",
778
- "aq-mode": "3",
779
- "tu-intra-depth": "3",
780
- "tu-inter-depth": "2",
781
- "limit-tu": "4",
782
- "ref": "6",
783
- "subme": "4",
784
- "open-gop": "0",
785
- "gop-lookahead": "0",
786
- "rc-lookahead": "192",
787
- "cbqpoffs": "-1",
788
- "crqpoffs": "-1",
789
- "pbratio": "1.25",
790
- }
791
- case Ripper.PresetName.x265fast:
792
- _option_map = {
793
- "crf": "18",
794
- "qpmin": "12",
795
- "qpmax": "28",
796
- "psy-rd": "1.8",
797
- "rdoq-level": "2",
798
- "psy-rdoq": "0.4",
799
- "keyint": "312",
800
- "deblock": "-1,-1",
801
- "me": "umh",
802
- "merange": "57",
803
- "hme-search": "umh,hex,hex",
804
- "hme-range": "16,57,92",
805
- "aq-mode": "4",
806
- "tu-intra-depth": "4",
807
- "tu-inter-depth": "3",
808
- "limit-tu": "4",
809
- "subme": "5",
810
- "gop-lookahead": "8",
811
- "rc-lookahead": "216",
812
- "cbqpoffs": "-2",
813
- "crqpoffs": "-2",
814
- "pbratio": "1.2",
815
- }
816
- case Ripper.PresetName.x265slow:
817
- _option_map = {
818
- "crf": "17.5",
819
- "qpmin": "12",
820
- "qpmax": "28",
821
- "rd": "5",
822
- "psy-rd": "1.8",
823
- "rdoq-level": "2",
824
- "psy-rdoq": "0.4",
825
- "qcomp": "0.7",
826
- "keyint": "312",
827
- "deblock": "-1,-1",
828
- "me": "umh",
829
- "merange": "57",
830
- "hme-search": "umh,hex,hex",
831
- "hme-range": "16,57,184",
832
- "aq-mode": "4",
833
- "aq-strength": "1",
834
- "tu-intra-depth": "4",
835
- "tu-inter-depth": "3",
836
- "limit-tu": "2",
837
- "subme": "6",
838
- "gop-lookahead": "14",
839
- "rc-lookahead": "250",
840
- "rect": "1",
841
- "min-keyint": "2",
842
- "cbqpoffs": "-2",
843
- "crqpoffs": "-2",
844
- "pbratio": "1.2",
845
- "early-skip": "0",
846
- }
847
- case Ripper.PresetName.x265full:
848
- _option_map = {
849
- "crf": "17",
850
- "qpmin": "3",
851
- "qpmax": "21.5",
852
- "psy-rd": "2.2",
853
- "rd": "5",
854
- "rdoq-level": "2",
855
- "psy-rdoq": "1.6",
856
- "qcomp": "0.72",
857
- "keyint": "266",
858
- "min-keyint": "2",
859
- "deblock": "-1,-1",
860
- "me": "umh",
861
- "merange": "160",
862
- "hme-search": "full,umh,hex",
863
- "hme-range": "16,92,320",
864
- "aq-mode": "4",
865
- "aq-strength": "1.2",
866
- "tu-intra-depth": "4",
867
- "tu-inter-depth": "4",
868
- "limit-tu": "2",
869
- "subme": "7",
870
- "open-gop": "1",
871
- "gop-lookahead": "14",
872
- "rc-lookahead": "250",
873
- "rect": "1",
874
- "amp": "1",
875
- "cbqpoffs": "-3",
876
- "crqpoffs": "-3",
877
- "ipratio": "1.43",
878
- "pbratio": "1.2",
879
- "early-skip": "0",
880
- }
881
-
882
- _option_map = _default_option_map | _option_map | _custom_option_map
551
+ _option_map = (
552
+ DEFAULT_PRESET_PARAMS.get(preset_name, {}) | _custom_option_map
553
+ )
883
554
 
884
555
  # HEVC 规范
885
- if self.option_map.get("hevc-strict", "1") == "1":
886
- if self.media_info.width * self.media_info.height >= (
887
- _RESOLUTION := 1920 * 1080 * 4
888
- ):
889
- if _option_map.get("hme", "0") == "1":
890
- _option_map["hme"] = "0"
891
- log.warning(
892
- "The resolution {} * {} >= {}, auto close HME",
893
- self.media_info.width,
894
- self.media_info.height,
895
- _RESOLUTION,
896
- )
556
+ if self.option_map.get(
557
+ "hevc-strict", "1"
558
+ ) != "0" and self.media_info.width * self.media_info.height >= (
559
+ _RESOLUTION := 1920 * 1080 * 4
560
+ ):
561
+ if _option_map.get("hme", "0") == "1":
562
+ _option_map["hme"] = "0"
563
+ log.warning(
564
+ "The resolution {} * {} >= {}, auto close HME",
565
+ self.media_info.width,
566
+ self.media_info.height,
567
+ _RESOLUTION,
568
+ )
897
569
 
898
- if int(_option_map.get("ref") or "3") > (_NEW_REF := 6):
899
- _option_map["ref"] = str(_NEW_REF)
900
- log.warning(
901
- "The resolution {} * {} >= {}, auto reduce {} to {}",
902
- self.media_info.width,
903
- self.media_info.height,
904
- _RESOLUTION,
905
- _option_map.get("ref"),
906
- _NEW_REF,
907
- )
570
+ if int(_option_map.get("ref") or "3") > (_NEW_REF := 6):
571
+ _option_map["ref"] = str(_NEW_REF)
572
+ log.warning(
573
+ "The resolution {} * {} >= {}, auto reduce {} to {}",
574
+ self.media_info.width,
575
+ self.media_info.height,
576
+ _RESOLUTION,
577
+ _option_map.get("ref"),
578
+ _NEW_REF,
579
+ )
908
580
 
909
581
  # 低版本 x265 不支持 -hme 0 主动关闭 HME
910
582
  if _option_map.get("hme", "0") == "0":
@@ -915,7 +587,7 @@ class Ripper:
915
587
  (_crf := _option_map.get("crf"))
916
588
  and (_qpmin := _option_map.get("qpmin"))
917
589
  and (_qpmax := _option_map.get("qpmax"))
918
- and not (_qpmin <= _crf <= _qpmax)
590
+ and not (float(_qpmin) <= float(_crf) <= float(_qpmax))
919
591
  ):
920
592
  log.warning("The CRF is not between QPmin and QPmax")
921
593
 
@@ -924,22 +596,22 @@ class Ripper:
924
596
  encoder_format_str = (
925
597
  f"{vspipe_input} {FFMPEG_HEADER} {hwaccel} {' '.join(f'-i {s}' for s in ff_input_option)} {' '.join(f'-map {s}' for s in ff_stream_option)} "
926
598
  + audio_option
927
- + f" -c:v libx265 {'' if is_pipe_input else '-pix_fmt yuv420p10le'} -x265-params "
928
- + f' "{_param}" {ffparams_out} '
929
- + (f' -vf "{",".join(ff_vf_option)}" ' if len(ff_vf_option) else "")
930
- + ' "{output}"'
599
+ + f"-c:v libx265 {'' if is_pipe_input else '-pix_fmt yuv420p10le'} -x265-params "
600
+ + f'"{_param}" {ffparams_out} '
601
+ + (f'-vf "{",".join(ff_vf_option)}" ' if len(ff_vf_option) else "")
602
+ + '"{output}"'
931
603
  )
932
604
 
933
605
  case (
934
- Ripper.PresetName.h264_amf
935
- | Ripper.PresetName.h264_nvenc
936
- | Ripper.PresetName.h264_qsv
937
- | Ripper.PresetName.hevc_amf
938
- | Ripper.PresetName.hevc_nvenc
939
- | Ripper.PresetName.hevc_qsv
940
- | Ripper.PresetName.av1_amf
941
- | Ripper.PresetName.av1_nvenc
942
- | Ripper.PresetName.av1_qsv
606
+ Ripper.Preset_name.h264_amf
607
+ | Ripper.Preset_name.h264_nvenc
608
+ | Ripper.Preset_name.h264_qsv
609
+ | Ripper.Preset_name.hevc_amf
610
+ | Ripper.Preset_name.hevc_nvenc
611
+ | Ripper.Preset_name.hevc_qsv
612
+ | Ripper.Preset_name.av1_amf
613
+ | Ripper.Preset_name.av1_nvenc
614
+ | Ripper.Preset_name.av1_qsv
943
615
  ):
944
616
  _option_map = {
945
617
  "q:v": self.option_map.get("q:v"),
@@ -948,9 +620,9 @@ class Ripper:
948
620
  }
949
621
  match preset_name:
950
622
  case (
951
- Ripper.PresetName.h264_qsv
952
- | Ripper.PresetName.hevc_qsv
953
- | Ripper.PresetName.av1_qsv
623
+ Ripper.Preset_name.h264_qsv
624
+ | Ripper.Preset_name.hevc_qsv
625
+ | Ripper.Preset_name.av1_qsv
954
626
  ):
955
627
  _option_map["qsv_params"] = self.option_map.get("qsv_params")
956
628
 
@@ -961,17 +633,19 @@ class Ripper:
961
633
  encoder_format_str = (
962
634
  f"{vspipe_input} {FFMPEG_HEADER} {hwaccel} {' '.join(f'-i {s}' for s in ff_input_option)} {' '.join(f'-map {s}' for s in ff_stream_option)} "
963
635
  + audio_option
964
- + f" -c:v {preset_name.value} "
965
- + f" {_param} {ffparams_out} "
636
+ + f"-c:v {preset_name.value} "
637
+ + f"{_param} {ffparams_out} "
966
638
  + (f' -vf "{",".join(ff_vf_option)}" ' if len(ff_vf_option) else "")
967
639
  + ' "{output}"'
968
640
  )
969
641
 
970
- case Ripper.PresetName.svtav1:
642
+ case Ripper.Preset_name.svtav1:
971
643
  _option_map = {
972
644
  "crf": self.option_map.get("crf"),
973
645
  "qp": self.option_map.get("qp"),
974
- "pix_fmt": self.option_map.get("pix_fmt"),
646
+ "pix_fmt": self.option_map.get(
647
+ "pix_fmt", None if is_pipe_input else "yuv420p10le"
648
+ ),
975
649
  "preset:v": self.option_map.get("preset:v"),
976
650
  "svtav1-params": self.option_map.get("svtav1-params"),
977
651
  }
@@ -983,13 +657,36 @@ class Ripper:
983
657
  encoder_format_str = (
984
658
  f"{vspipe_input} {FFMPEG_HEADER} {hwaccel} {' '.join(f'-i {s}' for s in ff_input_option)} {' '.join(f'-map {s}' for s in ff_stream_option)} "
985
659
  + audio_option
986
- + " -c:v libsvtav1 "
987
- + f" {_param} {ffparams_out} "
988
- + (f' -vf "{",".join(ff_vf_option)}" ' if len(ff_vf_option) else "")
660
+ + "-c:v libsvtav1 "
661
+ + f"{_param} {ffparams_out} "
662
+ + (f'-vf "{",".join(ff_vf_option)}" ' if len(ff_vf_option) else "")
989
663
  + ' "{output}"'
990
664
  )
991
665
 
992
- case Ripper.PresetName.subset:
666
+ case Ripper.Preset_name.vvenc:
667
+ _option_map = {
668
+ "qp": self.option_map.get("qp"),
669
+ "pix_fmt": self.option_map.get(
670
+ "pix_fmt", None if is_pipe_input else "yuv420p10le"
671
+ ),
672
+ "preset:v": self.option_map.get("preset:v"),
673
+ "vvenc-params": self.option_map.get("vvenc-params"),
674
+ }
675
+
676
+ _param = " ".join(
677
+ (f"-{key} {val}" for key, val in _option_map.items() if val)
678
+ )
679
+
680
+ encoder_format_str = (
681
+ f"{vspipe_input} {FFMPEG_HEADER} {hwaccel} {' '.join(f'-i {s}' for s in ff_input_option)} {' '.join(f'-map {s}' for s in ff_stream_option)} "
682
+ + audio_option
683
+ + "-c:v libvvenc "
684
+ + f"{_param} {ffparams_out} "
685
+ + (f'-vf "{",".join(ff_vf_option)}" ' if len(ff_vf_option) else "")
686
+ + ' "{output}"'
687
+ )
688
+
689
+ case Ripper.Preset_name.subset:
993
690
  encoder_format_str = ""
994
691
 
995
692
  return Ripper.Option(
@@ -1026,17 +723,14 @@ class Ripper:
1026
723
  log.error(e)
1027
724
  continue
1028
725
 
1029
- res = {
1030
- line[0]: line[1]
1031
- for line in (line.strip().split("=") for line in buffer[-12:])
1032
- }
726
+ res = dict(line.strip().split("=", maxsplit=1) for line in buffer[-12:])
1033
727
 
1034
728
  if p := res.get("progress"):
1035
- out_time_us = res.get("out_time_us", "")
1036
- speed = res.get("speed", "").rstrip("x")
729
+ out_time_us = res.get("out_time_us", -1)
730
+ speed = res.get("speed", "-1").rstrip("x")
1037
731
 
1038
- self._progress["frame"] = int(res.get("frame", ""))
1039
- self._progress["fps"] = float(res.get("fps", ""))
732
+ self._progress["frame"] = int(res.get("frame", -1))
733
+ self._progress["fps"] = float(res.get("fps", -1))
1040
734
  self._progress["out_time_us"] = (
1041
735
  int(out_time_us) if out_time_us != "N/A" else 0
1042
736
  )
@@ -1066,12 +760,14 @@ class Ripper:
1066
760
 
1067
761
  # 生成临时名
1068
762
  basename = self.output_prefix_list[0]
1069
- temp_name = f"{basename}-{datetime.now().strftime('%Y-%m-%d_%H:%M:%S')}"
763
+ temp_name = (
764
+ f"{basename}-{datetime.now().strftime('%Y-%m-%d_%H:%M:%S.%f')[:-4]}"
765
+ )
1070
766
  suffix: str
1071
767
 
1072
768
  # 根据格式判断
1073
769
  match self.option.preset_name:
1074
- case Ripper.PresetName.custom:
770
+ case Ripper.Preset_name.custom:
1075
771
  suffix = (
1076
772
  f".{_suffix}"
1077
773
  if (_suffix := self.option_map.get("custom:suffix"))
@@ -1085,7 +781,7 @@ class Ripper:
1085
781
  }
1086
782
  )
1087
783
 
1088
- case Ripper.PresetName.flac:
784
+ case Ripper.Preset_name.flac:
1089
785
  if self.option.muxer is not None or len(self.media_info.audio_info) > 1:
1090
786
  suffix = f".flac.{'mp4' if self.option.muxer == Ripper.Muxer.mp4 else 'mkv'}"
1091
787
  temp_name = temp_name + suffix
@@ -1105,12 +801,12 @@ class Ripper:
1105
801
  }
1106
802
  )
1107
803
 
1108
- case Ripper.PresetName.subset:
804
+ case Ripper.Preset_name.subset:
1109
805
  _output_dir = Path(self.output_dir) / basename
1110
806
  _output_dir.mkdir(parents=True, exist_ok=True)
1111
807
 
1112
- _ass_list = list[Path]()
1113
- _other_sub_list = list[Path]()
808
+ _ass_list: list[Path] = []
809
+ _other_sub_list: list[Path] = []
1114
810
 
1115
811
  for path in self.input_path_list:
1116
812
  if path.suffix == ".ass":
@@ -1249,13 +945,13 @@ class Ripper:
1249
945
 
1250
946
  self._progress["frame_count"] = 0
1251
947
  self._progress["duration"] = 0
1252
- if not self.input_path_list[0].suffix == ".vpy":
948
+ if self.input_path_list[0].suffix != ".vpy":
1253
949
  self._progress["frame_count"] = self.media_info.nb_frames
1254
950
  self._progress["duration"] = self.media_info.duration
1255
951
 
1256
952
  Thread(target=self._flush_progress, args=(1,), daemon=True).start()
1257
953
 
1258
- if self.preset_name is not Ripper.PresetName.custom:
954
+ if self.preset_name is not Ripper.Preset_name.custom:
1259
955
  os.environ["FFREPORT"] = f"file={FF_REPORT_LOG_FILE}:level=31"
1260
956
 
1261
957
  log.info(cmd)
@@ -1279,8 +975,8 @@ class Ripper:
1279
975
  else: # 多文件合成
1280
976
  # flac 音频轨合成
1281
977
  if (
1282
- self.preset_name != Ripper.PresetName.flac
1283
- and self.option.audio_encoder == Ripper.AudioCodec.flac
978
+ self.preset_name != Ripper.Preset_name.flac
979
+ and self.option.audio_encoder == Ripper.Audio_codec.flac
1284
980
  ):
1285
981
  _flac_basename = f"flac_temp_{get_base62_time()}"
1286
982
  _flac_fullname = _flac_basename + ".flac.mkv"
@@ -1288,7 +984,7 @@ class Ripper:
1288
984
  [self.input_path_list[0]],
1289
985
  [_flac_basename],
1290
986
  self.output_dir,
1291
- Ripper.PresetName.flac,
987
+ Ripper.Preset_name.flac,
1292
988
  {
1293
989
  k: v
1294
990
  for k, v in (
@@ -1324,7 +1020,7 @@ class Ripper:
1324
1020
  (_mux_temp_name,),
1325
1021
  (Path(temp_name).stem,),
1326
1022
  self.output_dir,
1327
- Ripper.PresetName.copy,
1023
+ Ripper.Preset_name.copy,
1328
1024
  {
1329
1025
  k: v
1330
1026
  for k, v in dict[str, str | None](
@@ -1355,6 +1051,7 @@ class Ripper:
1355
1051
  # 处理 soft-sub
1356
1052
  soft_sub_list: list[Path]
1357
1053
  soft_sub_map_list: list[str] = soft_sub.split(":")
1054
+
1358
1055
  if soft_sub_map_list[0] == "auto":
1359
1056
  soft_sub_list = []
1360
1057
 
@@ -1368,14 +1065,7 @@ class Ripper:
1368
1065
  for _file_basename in os.listdir(self.output_dir):
1369
1066
  _file_basename_list = os.path.splitext(_file_basename)
1370
1067
  if (
1371
- _file_basename_list[1]
1372
- in {
1373
- ".srt",
1374
- ".ass",
1375
- ".ssa",
1376
- ".sup",
1377
- ".idx",
1378
- }
1068
+ _file_basename_list[1] in SUBTITLE_SUFFIX_SET
1379
1069
  and _file_basename_list[0].startswith(_input_prefix)
1380
1070
  and (
1381
1071
  len(soft_sub_map_list) == 1
@@ -1385,9 +1075,8 @@ class Ripper:
1385
1075
  in soft_sub_map_list[1:]
1386
1076
  )
1387
1077
  ):
1388
- soft_sub_list.append(
1389
- Path(os.path.join(self.output_dir, _file_basename))
1390
- )
1078
+ soft_sub_list.append(Path(self.output_dir) / _file_basename)
1079
+
1391
1080
  else:
1392
1081
  soft_sub_list = [Path(s) for s in soft_sub.split("?")]
1393
1082
 
@@ -1395,10 +1084,10 @@ class Ripper:
1395
1084
  log.info("-soft-sub list = {}", soft_sub_list)
1396
1085
 
1397
1086
  # 临时翻译
1398
- add_tr_files = list[Path]()
1087
+ add_tr_files: Final[list[Path]] = []
1399
1088
  if translate_sub := self.option_map.get("translate-sub"):
1400
1089
  _tr = translate_sub.split(":")
1401
- if not len(_tr) == 2:
1090
+ if len(_tr) != 2:
1402
1091
  log.error("{} param illegal", "-translate-sub")
1403
1092
  else:
1404
1093
  try:
@@ -1406,9 +1095,7 @@ class Ripper:
1406
1095
  Path(self.output_dir),
1407
1096
  _tr[0],
1408
1097
  _tr[1],
1409
- file_intersection_selector=(
1410
- Path(s) for s in soft_sub_list
1411
- ),
1098
+ file_intersection_selector=soft_sub_list,
1412
1099
  )
1413
1100
  except Exception as e:
1414
1101
  log.error(e, is_format=False)
@@ -1431,7 +1118,7 @@ class Ripper:
1431
1118
  soft_sub_list + add_tr_files,
1432
1119
  (subset_folder.name,),
1433
1120
  self.output_dir,
1434
- Ripper.PresetName.subset,
1121
+ Ripper.Preset_name.subset,
1435
1122
  self.option_map,
1436
1123
  ).run():
1437
1124
  # 合成 MKV
@@ -1445,7 +1132,7 @@ class Ripper:
1445
1132
  [new_full_name],
1446
1133
  [os.path.splitext(org_full_name)[0]],
1447
1134
  self.output_dir,
1448
- Ripper.PresetName.copy,
1135
+ Ripper.Preset_name.copy,
1449
1136
  {
1450
1137
  k: v
1451
1138
  for k, v in dict[str, str | None](
@@ -1465,9 +1152,8 @@ class Ripper:
1465
1152
  ).items()
1466
1153
  if v
1467
1154
  },
1468
- ).run():
1469
- if os.path.exists(new_full_name):
1470
- os.remove(new_full_name)
1155
+ ).run() and os.path.exists(new_full_name):
1156
+ os.remove(new_full_name)
1471
1157
  else:
1472
1158
  log.error("Subset faild, cancel mux")
1473
1159