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.
- easyrip/__init__.py +5 -1
- easyrip/__main__.py +124 -15
- easyrip/easyrip_command.py +457 -148
- easyrip/easyrip_config/config.py +269 -0
- easyrip/easyrip_config/config_key.py +28 -0
- easyrip/easyrip_log.py +120 -42
- easyrip/easyrip_main.py +509 -259
- easyrip/easyrip_mlang/__init__.py +20 -45
- easyrip/easyrip_mlang/global_lang_val.py +18 -16
- easyrip/easyrip_mlang/lang_en.py +1 -1
- easyrip/easyrip_mlang/lang_zh_Hans_CN.py +101 -77
- easyrip/easyrip_mlang/translator.py +12 -10
- easyrip/easyrip_prompt.py +73 -0
- easyrip/easyrip_web/__init__.py +2 -1
- easyrip/easyrip_web/http_server.py +56 -42
- easyrip/easyrip_web/third_party_api.py +60 -8
- easyrip/global_val.py +21 -1
- easyrip/ripper/media_info.py +10 -3
- easyrip/ripper/param.py +482 -0
- easyrip/ripper/ripper.py +260 -574
- easyrip/ripper/sub_and_font/__init__.py +10 -0
- easyrip/ripper/{font_subset → sub_and_font}/ass.py +95 -84
- easyrip/ripper/{font_subset → sub_and_font}/font.py +72 -79
- easyrip/ripper/{font_subset → sub_and_font}/subset.py +122 -81
- easyrip/utils.py +129 -27
- easyrip-4.9.1.dist-info/METADATA +92 -0
- easyrip-4.9.1.dist-info/RECORD +31 -0
- easyrip/easyrip_config.py +0 -198
- easyrip/ripper/__init__.py +0 -10
- easyrip/ripper/font_subset/__init__.py +0 -7
- easyrip-3.13.2.dist-info/METADATA +0 -89
- easyrip-3.13.2.dist-info/RECORD +0 -29
- {easyrip-3.13.2.dist-info → easyrip-4.9.1.dist-info}/WHEEL +0 -0
- {easyrip-3.13.2.dist-info → easyrip-4.9.1.dist-info}/entry_points.txt +0 -0
- {easyrip-3.13.2.dist-info → easyrip-4.9.1.dist-info}/licenses/LICENSE +0 -0
- {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
|
|
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 .
|
|
18
|
-
from .
|
|
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
|
-
@
|
|
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 |
|
|
41
|
+
option: "Option | Preset_name",
|
|
33
42
|
option_map: dict[str, str],
|
|
34
43
|
):
|
|
35
44
|
try:
|
|
36
|
-
|
|
37
|
-
|
|
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
|
-
|
|
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.
|
|
55
|
+
preset_name: "Ripper.Preset_name"
|
|
123
56
|
encoder_format_str: str
|
|
124
|
-
audio_encoder: "Ripper.
|
|
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:
|
|
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 |
|
|
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.
|
|
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.
|
|
122
|
+
self.preset_name = Ripper.Preset_name.custom
|
|
190
123
|
self.option = option
|
|
191
124
|
|
|
192
|
-
self._progress
|
|
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:
|
|
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
|
|
246
|
-
|
|
247
|
-
|
|
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
|
-
|
|
255
|
-
|
|
256
|
-
|
|
257
|
-
|
|
258
|
-
|
|
259
|
-
|
|
260
|
-
|
|
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.
|
|
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
|
-
|
|
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.
|
|
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
|
|
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("="
|
|
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"
|
|
337
|
+
ffparams_in += f"-ss {_ss} "
|
|
380
338
|
if _t := self.option_map.get("t"):
|
|
381
|
-
ffparams_out += f"
|
|
339
|
+
ffparams_out += f"-t {_t} "
|
|
382
340
|
if _preset := self.option_map.get("v:preset"):
|
|
383
|
-
ffparams_out += f"
|
|
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.
|
|
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.
|
|
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
|
-
|
|
425
|
-
|
|
426
|
-
+
|
|
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.
|
|
406
|
+
case Ripper.Preset_name.flac:
|
|
448
407
|
_ff_encode_str: str = ""
|
|
449
408
|
_flac_encode_str: str = ""
|
|
450
|
-
_mux_flac_input_list
|
|
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
|
-
|
|
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
|
|
520
|
-
|
|
521
|
-
|
|
522
|
-
|
|
523
|
-
|
|
524
|
-
|
|
525
|
-
|
|
526
|
-
|
|
527
|
-
|
|
528
|
-
|
|
529
|
-
|
|
530
|
-
|
|
531
|
-
|
|
532
|
-
|
|
533
|
-
|
|
534
|
-
|
|
535
|
-
|
|
536
|
-
|
|
537
|
-
|
|
538
|
-
|
|
539
|
-
|
|
540
|
-
|
|
541
|
-
|
|
542
|
-
|
|
543
|
-
|
|
544
|
-
|
|
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(
|
|
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
|
-
+
|
|
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'
|
|
607
|
-
+ '
|
|
520
|
+
+ (f'-vf "{",".join(ff_vf_option)}" ' if len(ff_vf_option) else "")
|
|
521
|
+
+ '"{output}"'
|
|
608
522
|
)
|
|
609
523
|
|
|
610
524
|
case (
|
|
611
|
-
Ripper.
|
|
612
|
-
| Ripper.
|
|
613
|
-
| Ripper.
|
|
614
|
-
| Ripper.
|
|
615
|
-
| Ripper.
|
|
616
|
-
| Ripper.
|
|
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
|
-
|
|
619
|
-
|
|
620
|
-
|
|
621
|
-
|
|
622
|
-
|
|
623
|
-
|
|
624
|
-
|
|
625
|
-
|
|
626
|
-
|
|
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
|
-
|
|
717
|
-
|
|
718
|
-
|
|
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(
|
|
886
|
-
|
|
887
|
-
|
|
888
|
-
|
|
889
|
-
|
|
890
|
-
|
|
891
|
-
|
|
892
|
-
|
|
893
|
-
|
|
894
|
-
|
|
895
|
-
|
|
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
|
-
|
|
899
|
-
|
|
900
|
-
|
|
901
|
-
|
|
902
|
-
|
|
903
|
-
|
|
904
|
-
|
|
905
|
-
|
|
906
|
-
|
|
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"
|
|
928
|
-
+ f'
|
|
929
|
-
+ (f'
|
|
930
|
-
+ '
|
|
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.
|
|
935
|
-
| Ripper.
|
|
936
|
-
| Ripper.
|
|
937
|
-
| Ripper.
|
|
938
|
-
| Ripper.
|
|
939
|
-
| Ripper.
|
|
940
|
-
| Ripper.
|
|
941
|
-
| Ripper.
|
|
942
|
-
| Ripper.
|
|
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.
|
|
952
|
-
| Ripper.
|
|
953
|
-
| Ripper.
|
|
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"
|
|
965
|
-
+ f"
|
|
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.
|
|
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(
|
|
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
|
-
+ "
|
|
987
|
-
+ f"
|
|
988
|
-
+ (f'
|
|
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.
|
|
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 =
|
|
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.
|
|
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.
|
|
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.
|
|
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
|
|
1113
|
-
_other_sub_list
|
|
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
|
|
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.
|
|
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.
|
|
1283
|
-
and self.option.audio_encoder == Ripper.
|
|
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.
|
|
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.
|
|
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
|
-
|
|
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
|
|
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
|
|
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.
|
|
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.
|
|
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
|
-
|
|
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
|
|