easyrip 4.14.0__py3-none-any.whl → 4.15.0__py3-none-any.whl

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
easyrip/__main__.py CHANGED
@@ -6,6 +6,8 @@ import Crypto
6
6
  import fontTools
7
7
  import prompt_toolkit
8
8
  from prompt_toolkit import ANSI, prompt
9
+ from prompt_toolkit.application import get_app
10
+ from prompt_toolkit.clipboard.pyperclip import PyperclipClipboard
9
11
  from prompt_toolkit.completion import merge_completers
10
12
  from prompt_toolkit.history import InMemoryHistory
11
13
  from prompt_toolkit.input.ansi_escape_sequences import ANSI_SEQUENCES
@@ -49,8 +51,22 @@ def run() -> NoReturn:
49
51
 
50
52
  @key_bindings.add(Keys.ControlC)
51
53
  def _(event: KeyPressEvent) -> None:
54
+ buffer = event.app.current_buffer
55
+
56
+ # 检查是否有选中的文本
57
+ if buffer.selection_state is not None:
58
+ get_app().clipboard.set_data(buffer.copy_selection())
59
+ return
60
+
52
61
  event.app.exit(exception=KeyboardInterrupt, style="class:exiting")
53
62
 
63
+ @key_bindings.add(Keys.ControlA)
64
+ def _(event: KeyPressEvent) -> None:
65
+ buff = event.app.current_buffer
66
+ buff.cursor_position = 0
67
+ buff.start_selection()
68
+ buff.cursor_position = len(buff.text)
69
+
54
70
  @key_bindings.add(Keys.ControlD)
55
71
  def _(event: KeyPressEvent) -> None:
56
72
  event.app.current_buffer.insert_text(C_D)
@@ -64,6 +80,7 @@ def run() -> NoReturn:
64
80
  return named_commands.get_by_name("unix-word-rubout").handler(event)
65
81
 
66
82
  path_completer = SmartPathCompleter()
83
+ clipboard = PyperclipClipboard()
67
84
 
68
85
  def _ctv_to_nc(ctvs: Iterable[Cmd_type_val]) -> CmdCompleter:
69
86
  return CmdCompleter(
@@ -138,6 +155,7 @@ def run() -> NoReturn:
138
155
  ),
139
156
  history=prompt_history,
140
157
  complete_while_typing=True,
158
+ clipboard=clipboard,
141
159
  )
142
160
  if command.startswith(C_Z):
143
161
  raise EOFError
@@ -664,6 +664,15 @@ class Opt_type(enum.Enum):
664
664
  ),
665
665
  childs=(Cmd_type_val(("0", "1")),),
666
666
  )
667
+ _quality_detection = Cmd_type_val(
668
+ ("-quality-detection",),
669
+ param="<algorithm>[:<threshold>]",
670
+ description=(
671
+ "Comparison of quality between detection and source after encoding is completed\n"
672
+ "Algorithm: ssim psnr vmaf"
673
+ ),
674
+ childs=(Cmd_type_val(("ssim", "psnr", "vmaf")),),
675
+ )
667
676
 
668
677
  @classmethod
669
678
  def from_str(cls, s: str) -> Self | None:
@@ -328,7 +328,7 @@ LANG_MAP: dict[str, str] = {
328
328
  "There have error in running": "执行时出错",
329
329
  "{} param illegal": "{} 参数非法",
330
330
  'The file "{}" already exists, skip translating it': '文件 "{}" 已存在, 跳过翻译',
331
- "Subset faild, cancel mux": "子集化失败, 取消混流",
331
+ "Subset failed, cancel mux": "子集化失败, 取消混流",
332
332
  "FFmpeg report: {}": "FFmpeg 报告: {}",
333
333
  "{} not found. Skip it": "没找到 {}。默认跳过",
334
334
  "{} not found. Skip it. Perhaps you want the {}": "没找到 {}。默认跳过。或许你想要的是 {}",
@@ -120,7 +120,7 @@ class mkvtoolnix:
120
120
  log.debug(
121
121
  "'{}' execution failed: {}",
122
122
  f"{cls.__name__}.{cls.get_latest_release_ver.__name__}",
123
- f"XML parse faild: {xml.etree.ElementTree.tostring(xml_tree)}",
123
+ f"XML parse failed: {xml.etree.ElementTree.tostring(xml_tree)}",
124
124
  print_level=log.LogLevel._detail,
125
125
  )
126
126
  return None
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.14.0"
7
+ PROJECT_VERSION = "4.15.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"
@@ -120,7 +120,7 @@ class Media_info:
120
120
  sample_fmt = _audio_info_dict.get("sample_fmt")
121
121
  if sample_fmt is None:
122
122
  log.debug(
123
- 'Faild to get audio info: {}. file "{}" track index {}',
123
+ 'Failed to get audio info: {}. file "{}" track index {}',
124
124
  "sample_fmt",
125
125
  path,
126
126
  index,
@@ -130,7 +130,7 @@ class Media_info:
130
130
  sample_rate = _audio_info_dict.get("sample_rate")
131
131
  if sample_rate is None:
132
132
  log.debug(
133
- 'Faild to get audio info: {}. file "{}" track index {}',
133
+ 'Failed to get audio info: {}. file "{}" track index {}',
134
134
  "sample_rate",
135
135
  path,
136
136
  index,
@@ -140,7 +140,7 @@ class Media_info:
140
140
  bits_per_sample = _audio_info_dict.get("bits_per_sample")
141
141
  if bits_per_sample is None:
142
142
  log.debug(
143
- 'Faild to get audio info: {}. file "{}" track index {}',
143
+ 'Failed to get audio info: {}. file "{}" track index {}',
144
144
  "bits_per_sample",
145
145
  path,
146
146
  index,
@@ -150,7 +150,7 @@ class Media_info:
150
150
  bits_per_raw_sample = _audio_info_dict.get("bits_per_raw_sample")
151
151
  if bits_per_raw_sample is None:
152
152
  log.debug(
153
- 'Faild to get audio info: {}. file "{}" track index {}',
153
+ 'Failed to get audio info: {}. file "{}" track index {}',
154
154
  "bits_per_raw_sample",
155
155
  path,
156
156
  index,
easyrip/ripper/ripper.py CHANGED
@@ -1,3 +1,4 @@
1
+ import csv
1
2
  import os
2
3
  import re
3
4
  import shutil
@@ -14,7 +15,7 @@ from typing import Final, Self, final
14
15
  from .. import easyrip_web
15
16
  from ..easyrip_log import log
16
17
  from ..easyrip_mlang import Global_lang_val, gettext, translate_subtitles
17
- from ..utils import get_base62_time
18
+ from ..utils import get_base62_time, read_text
18
19
  from .media_info import Media_info, Stream_error
19
20
  from .param import (
20
21
  FONT_SUFFIX_SET,
@@ -1088,9 +1089,15 @@ class Ripper:
1088
1089
  f'{gettext("Encoding speed")}: <span style="color:darkcyan;">{speed}</span><br>'
1089
1090
  )
1090
1091
 
1092
+ # 获取 ffmpeg report 中的报错
1093
+ if FF_REPORT_LOG_FILE.is_file():
1094
+ with FF_REPORT_LOG_FILE.open("rt", encoding="utf-8") as file:
1095
+ for line in file.readlines()[2:]:
1096
+ log.warning("FFmpeg report: {}", line)
1097
+
1091
1098
  if is_cmd_run_failed:
1092
1099
  log.error("There have error in running")
1093
- else: # 多文件合成
1100
+ else: # 多文件合成 or 后处理
1094
1101
  # flac 音频轨合成
1095
1102
  if (
1096
1103
  self.preset_name != Ripper.Preset_name.flac
@@ -1245,16 +1252,133 @@ class Ripper:
1245
1252
  ).run() and os.path.exists(new_full_name):
1246
1253
  os.remove(new_full_name)
1247
1254
  else:
1248
- log.error("Subset faild, cancel mux")
1255
+ log.error("Subset failed, cancel mux")
1249
1256
 
1250
1257
  # 清理临时文件
1251
1258
  shutil.rmtree(subset_folder)
1252
1259
 
1253
- # 获取 ffmpeg report 中的报错
1254
- if FF_REPORT_LOG_FILE.is_file():
1255
- with FF_REPORT_LOG_FILE.open("rt", encoding="utf-8") as file:
1256
- for line in file.readlines()[2:]:
1257
- log.warning("FFmpeg report: {}", line)
1260
+ # 画质检测
1261
+ if quality_detection := self.option_map.get("quality-detection"):
1262
+ quality_detection = quality_detection.split(":")
1263
+ quality_detection_th: float
1264
+ quality_detection_filter: str
1265
+ while True:
1266
+ match quality_detection[0]:
1267
+ case "ssim":
1268
+ quality_detection_th = 0.9
1269
+ quality_detection_filter = "ssim=f="
1270
+
1271
+ def quality_detection_cmp(
1272
+ text: str, threshold: float
1273
+ ) -> None:
1274
+ for line in text.splitlines():
1275
+ values = tuple(
1276
+ s.split(":")[1] for s in line.split()[:-1]
1277
+ )
1278
+ ssim_all = float(values[-1])
1279
+ n = values[0]
1280
+ log.debug(
1281
+ f"{n}: {ssim_all}",
1282
+ is_format=False,
1283
+ print_level=log.LogLevel._detail,
1284
+ )
1285
+ if ssim_all < threshold:
1286
+ log.error(
1287
+ "SSIM {} < threshold {} in frame {}",
1288
+ ssim_all,
1289
+ threshold,
1290
+ n,
1291
+ )
1292
+
1293
+ break
1294
+ case "psnr":
1295
+ quality_detection_th = 30
1296
+ quality_detection_filter = "psnr=f="
1297
+
1298
+ def quality_detection_cmp(
1299
+ text: str, threshold: float
1300
+ ) -> None:
1301
+ for line in text.splitlines():
1302
+ values = tuple(
1303
+ s.split(":")[1] for s in line.split()
1304
+ )
1305
+ psnr_avg_all = float(values[-4])
1306
+ n = values[0]
1307
+ log.debug(
1308
+ f"{n}: {psnr_avg_all}",
1309
+ is_format=False,
1310
+ print_level=log.LogLevel._detail,
1311
+ )
1312
+ if psnr_avg_all < threshold:
1313
+ log.error(
1314
+ "PSNR {} < threshold {} in frame {}",
1315
+ psnr_avg_all,
1316
+ threshold,
1317
+ n,
1318
+ )
1319
+
1320
+ break
1321
+ case "vmaf":
1322
+ quality_detection_th = 80
1323
+ quality_detection_filter = "libvmaf=log_fmt=csv:log_path="
1324
+
1325
+ def quality_detection_cmp(
1326
+ text: str, threshold: float
1327
+ ) -> None:
1328
+ for line in tuple(csv.reader(text))[1:]:
1329
+ vmaf = float(line[-1])
1330
+ n = int(line[0]) + 1
1331
+ log.debug(
1332
+ f"{n}: {vmaf}",
1333
+ is_format=False,
1334
+ print_level=log.LogLevel._detail,
1335
+ )
1336
+ if vmaf < threshold:
1337
+ log.error(
1338
+ "VMAF {} < threshold {} in frame {}",
1339
+ vmaf,
1340
+ threshold,
1341
+ n,
1342
+ )
1343
+
1344
+ break
1345
+ case _:
1346
+ log.error(
1347
+ "Param error from '{}': {}",
1348
+ "-quality-detection",
1349
+ f"{quality_detection[0]} -> ssim",
1350
+ )
1351
+ quality_detection[0] = "ssim"
1352
+
1353
+ if len(quality_detection) > 1:
1354
+ try:
1355
+ quality_detection_th = float(quality_detection[1])
1356
+ except ValueError as e:
1357
+ log.error("Param error from '{}': {}", "-quality-detection", e)
1358
+
1359
+ quality_detection_data_file: Path = Path(
1360
+ self.output_dir, "quality_detection_data.log"
1361
+ )
1362
+ quality_detection_data_file_filter_str: str = (
1363
+ str(quality_detection_data_file)
1364
+ .replace("\\", "/")
1365
+ .replace(":", "\\\\:")
1366
+ )
1367
+ if os.system(
1368
+ f'ffmpeg -i "{self.input_path_list[0]}" -i "{os.path.join(self.output_dir, temp_name)}" -lavfi "{quality_detection_filter}{quality_detection_data_file_filter_str}" -f null -'
1369
+ ):
1370
+ log.error("Run {} failed", "-quality-detection")
1371
+ else:
1372
+ log.debug(
1373
+ "'{}' start: {}",
1374
+ "-quality-detection",
1375
+ f"{quality_detection[0]}:{quality_detection_th}",
1376
+ )
1377
+ quality_detection_cmp(
1378
+ read_text(quality_detection_data_file), quality_detection_th
1379
+ )
1380
+ log.debug("'{}' end", "-quality-detection")
1381
+ quality_detection_data_file.unlink(missing_ok=True)
1258
1382
 
1259
1383
  # 获取体积
1260
1384
  temp_name_full = os.path.join(self.output_dir, temp_name)
@@ -1,6 +1,6 @@
1
1
  Metadata-Version: 2.4
2
2
  Name: easyrip
3
- Version: 4.14.0
3
+ Version: 4.15.0
4
4
  Author: op200
5
5
  License-Expression: AGPL-3.0-or-later
6
6
  Project-URL: Homepage, https://github.com/op200/EasyRip
@@ -20,6 +20,7 @@ Classifier: Typing :: Typed
20
20
  Requires-Python: >=3.12
21
21
  Description-Content-Type: text/markdown
22
22
  License-File: LICENSE
23
+ Requires-Dist: pyperclip>=1.11.0
23
24
  Requires-Dist: prompt-toolkit>=3.0.52
24
25
  Requires-Dist: fonttools>=4.61.1
25
26
  Requires-Dist: pycryptodome>=3.23.0
@@ -1,10 +1,10 @@
1
1
  easyrip/__init__.py,sha256=DULQoFEAEHYk7dS8Zxky56so7qDPqHm7jUc_Zop1eXw,616
2
- easyrip/__main__.py,sha256=AEYyrxi1iUR7nEU7zln8JEHmetjhLqOue4VhFZFR5gI,5158
3
- easyrip/easyrip_command.py,sha256=1_CbICn_4U6xKEt0TCU753Oa2wC7_1966IoKNIdJoWI,30303
2
+ easyrip/__main__.py,sha256=RmJ2_bFzJmyymKnJpTqrbQc64tjK-ziGEen8sGsMm8c,5791
3
+ easyrip/easyrip_command.py,sha256=ZEQ8v8FHaSB7tKKMv0HY0NXOftR1S3gRaPTslfxc9tk,30650
4
4
  easyrip/easyrip_log.py,sha256=R-dM3CWUBFITtG7GSD1zy4X4MhZqxkoiBPjlIpI76cY,15573
5
5
  easyrip/easyrip_main.py,sha256=9KMpKUal8ApWZr80AQ7UwO1n6HUSWeVo2SuJ4pMW1f8,48130
6
6
  easyrip/easyrip_prompt.py,sha256=TOmRJDigGRz7wRWFNakJdfNI1tn9vGekq6FH5ypmQfA,7068
7
- easyrip/global_val.py,sha256=DRe2CSZP2HEBCcvEUVQRQLqR9_AfEQx-rQiYyjp7-NM,866
7
+ easyrip/global_val.py,sha256=Q6PBQ8iB8H7cJN5KRaLBAj8mHdGCj47EAiLzW62cSrg,866
8
8
  easyrip/utils.py,sha256=N1rMF1MyoC-YFBgy10_u29cFoowfhR-5Viea93O7wQ4,8750
9
9
  easyrip/easyrip_config/config.py,sha256=KWXZMEYxdXYUGLQ-MR0A7nnOwR6QZdVrWBopfb2QZSA,9869
10
10
  easyrip/easyrip_config/config_key.py,sha256=_jjdKOunskUoG7UUWOz3QZK-s4LF_x6hmM9MKttyS2Q,766
@@ -12,21 +12,21 @@ easyrip/easyrip_mlang/__init__.py,sha256=QqnL0kgV_trGPeLF5gawP1qAlj0GXUadLNhMSdK
12
12
  easyrip/easyrip_mlang/global_lang_val.py,sha256=pG9DxPl6vUOZoFHYQKCM-AM0TYWbd8L4S65CUQFPRh4,4998
13
13
  easyrip/easyrip_mlang/lang_en.py,sha256=fTM9ejuPW6gEfSMbnMEW-LzlUfvj0YGfoUfmHZpRzms,121
14
14
  easyrip/easyrip_mlang/lang_tag_val.py,sha256=Ec-U0XglpSYvmkHlcEBueSj8ocTLSTH3cacElAkmYVU,5184
15
- easyrip/easyrip_mlang/lang_zh_Hans_CN.py,sha256=uW6esedtSYi6Y4R4MA4Xkd2y3f-2O5-ywJFgogk1O7Y,19031
15
+ easyrip/easyrip_mlang/lang_zh_Hans_CN.py,sha256=-GyAtjhSHqfR2x1mDgW3CdMz7odd58uJzONK67wow8g,19032
16
16
  easyrip/easyrip_mlang/translator.py,sha256=jlgZYSPHvwv1Pps3akKkSgVsGcLtV2psKaXyZH4QCbA,5870
17
17
  easyrip/easyrip_web/__init__.py,sha256=tMyEeaSGeEJjND7MF0MBv9aDiDgaO3MOnppwxA70U2c,177
18
18
  easyrip/easyrip_web/http_server.py,sha256=iyulCAFQrJlz86Lrr-Dm3fhOnNCf79Bp6fVHhr0ephY,8350
19
- easyrip/easyrip_web/third_party_api.py,sha256=GhP6LmR1sVMeLLbnj82r-QYjoUdSnyaU9xp0LRnRLsw,4623
20
- easyrip/ripper/media_info.py,sha256=mQq_vbQ7S9fWpb39HLkoZlAL-pqNfwxewv6X776Nf50,5078
19
+ easyrip/easyrip_web/third_party_api.py,sha256=E-60yoY6D0pPUfYW1VIh0763htyV5z6getzlLtLAdQc,4624
20
+ easyrip/ripper/media_info.py,sha256=KdSodS6nIp2BWEer5y4mD5xwyhP15_PgNRhz2fnHmw0,5082
21
21
  easyrip/ripper/param.py,sha256=PfJzJz9LPCB5hAM9G4GjPxdn_EZRgAz-vxYzuHGQLp8,13084
22
- easyrip/ripper/ripper.py,sha256=bymJVnEqQa3lTEiLJwRQpmfVctvP-2tLprCQ2XfH9Lk,52756
22
+ easyrip/ripper/ripper.py,sha256=FkbOi1H_IQtcOmr1sMnRgZvPEnxkMvINV2EiFIjSIqI,58898
23
23
  easyrip/ripper/sub_and_font/__init__.py,sha256=cBT7mxL7RRFaJXFPXuZ7RT-YK6FbnanaU5v6U9BOquw,153
24
24
  easyrip/ripper/sub_and_font/ass.py,sha256=EhDkVY5JXU77euWPId7H2v85j444m8ZLm7wUid7TYd8,35307
25
25
  easyrip/ripper/sub_and_font/font.py,sha256=X2dPcPzbwQf3fv_g_mxO-zY7puVAX9Nv-9QHn88q4oA,7745
26
26
  easyrip/ripper/sub_and_font/subset.py,sha256=--rAA3VH1rm_jBOC3yMs3rOJpn3tPuvfXqkimbBtx3s,18653
27
- easyrip-4.14.0.dist-info/licenses/LICENSE,sha256=hIahDEOTzuHCU5J2nd07LWwkLW7Hko4UFO__ffsvB-8,34523
28
- easyrip-4.14.0.dist-info/METADATA,sha256=NiCk9-cdBcFFvcSrKArJmsZKCZheat4MnfPKmLkJ7zo,3507
29
- easyrip-4.14.0.dist-info/WHEEL,sha256=qELbo2s1Yzl39ZmrAibXA2jjPLUYfnVhUNTlyF1rq0Y,92
30
- easyrip-4.14.0.dist-info/entry_points.txt,sha256=D6GBMMTzZ-apgX76KyZ6jxMmIFqGYwU9neeLLni_qKI,49
31
- easyrip-4.14.0.dist-info/top_level.txt,sha256=kuEteBXm-Gf90jRQgH3-fTo-Z-Q6czSuUEqY158H4Ww,8
32
- easyrip-4.14.0.dist-info/RECORD,,
27
+ easyrip-4.15.0.dist-info/licenses/LICENSE,sha256=hIahDEOTzuHCU5J2nd07LWwkLW7Hko4UFO__ffsvB-8,34523
28
+ easyrip-4.15.0.dist-info/METADATA,sha256=EeTyI6jF0S972Q706cZggO-ffPg8Y7hUMUIF0uJWf00,3540
29
+ easyrip-4.15.0.dist-info/WHEEL,sha256=wUyA8OaulRlbfwMtmQsvNngGrxQHAvkKcvRmdizlJi0,92
30
+ easyrip-4.15.0.dist-info/entry_points.txt,sha256=D6GBMMTzZ-apgX76KyZ6jxMmIFqGYwU9neeLLni_qKI,49
31
+ easyrip-4.15.0.dist-info/top_level.txt,sha256=kuEteBXm-Gf90jRQgH3-fTo-Z-Q6czSuUEqY158H4Ww,8
32
+ easyrip-4.15.0.dist-info/RECORD,,
@@ -1,5 +1,5 @@
1
1
  Wheel-Version: 1.0
2
- Generator: setuptools (80.10.1)
2
+ Generator: setuptools (80.10.2)
3
3
  Root-Is-Purelib: true
4
4
  Tag: py3-none-any
5
5