easyrip 4.3.3__py3-none-any.whl → 4.5.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/__init__.py CHANGED
@@ -14,10 +14,13 @@ from .easyrip_mlang import (
14
14
  Lang_tag_region,
15
15
  Lang_tag_script,
16
16
  )
17
- from .ripper import Ass, Media_info, Ripper
17
+ from .ripper.media_info import Media_info
18
+ from .ripper.ripper import Ripper
19
+ from .ripper.sub_and_font import Ass, Font, load_fonts
18
20
 
19
21
  __all__ = [
20
22
  "Ass",
23
+ "Font",
21
24
  "Global_lang_val",
22
25
  "Lang_tag",
23
26
  "Lang_tag_language",
@@ -30,6 +33,7 @@ __all__ = [
30
33
  "gettext",
31
34
  "global_val",
32
35
  "init",
36
+ "load_fonts",
33
37
  "log",
34
38
  "run_command",
35
39
  ]
easyrip/__main__.py CHANGED
@@ -64,6 +64,7 @@ def run() -> NoReturn:
64
64
  in {
65
65
  *Cmd_type.cd.value.names,
66
66
  *Cmd_type.mediainfo.value.names,
67
+ *Cmd_type.fontinfo.value.names,
67
68
  }
68
69
  else ()
69
70
  ),
@@ -19,6 +19,7 @@ from prompt_toolkit.document import Document
19
19
 
20
20
  from . import global_val
21
21
  from .easyrip_config.config_key import Config_key
22
+ from .ripper.param import Audio_codec, Preset_name
22
23
 
23
24
 
24
25
  @final
@@ -279,6 +280,15 @@ class Cmd_type(enum.Enum):
279
280
  Cmd_type_val(("cfd",)),
280
281
  ),
281
282
  )
283
+ fontinfo = Cmd_type_val(
284
+ ("fontinfo",),
285
+ param="<<path> | 'fd' | 'cfd'>",
286
+ description="Get the font info by the Font class",
287
+ childs=(
288
+ Cmd_type_val(("fd",)),
289
+ Cmd_type_val(("cfd",)),
290
+ ),
291
+ )
282
292
  Option = Cmd_type_val(
283
293
  ("Option",),
284
294
  param="...",
@@ -344,48 +354,10 @@ class Opt_type(enum.Enum):
344
354
  param="<string>",
345
355
  description=(
346
356
  "Setting preset\n"
347
- "Preset name:\n"
348
- " custom\n"
349
- " subset\n"
350
- " copy\n"
351
- " flac\n"
352
- " x264fast x264slow\n"
353
- " x265fast4 x265fast3 x265fast2 x265fast x265slow x265full\n"
354
- " svtav1\n"
355
- " vvenc\n"
356
- " h264_qsv h264_nvenc h264_amf\n"
357
- " hevc_qsv hevc_nvenc hevc_amf\n"
358
- " av1_qsv av1_nvenc av1_amf"
359
- ),
360
- childs=(
361
- Cmd_type_val(
362
- (
363
- "custom",
364
- "subset",
365
- "copy",
366
- "flac",
367
- "x264fast",
368
- "x264slow",
369
- "x265fast4",
370
- "x265fast3",
371
- "x265fast2",
372
- "x265fast",
373
- "x265slow",
374
- "x265full",
375
- "svtav1",
376
- "vvenc",
377
- "h264_qsv",
378
- "h264_nvenc",
379
- "h264_amf",
380
- "hevc_qsv",
381
- "hevc_nvenc",
382
- "hevc_amf",
383
- "av1_qsv",
384
- "av1_nvenc",
385
- "av1_amf",
386
- )
387
- ),
357
+ "Preset name:\n" # .
358
+ f"{Preset_name.to_help_string(' ')}"
388
359
  ),
360
+ childs=(Cmd_type_val(tuple(Preset_name._value2member_map_)),),
389
361
  )
390
362
  _pipe = Cmd_type_val(
391
363
  ("-pipe",),
@@ -515,11 +487,8 @@ class Opt_type(enum.Enum):
515
487
  param="<string>",
516
488
  description=(
517
489
  "Setting audio encoder\n"
518
- " \n" # .
519
- "Audio encoder:\n"
520
- " copy\n"
521
- " libopus\n"
522
- " flac"
490
+ "Audio encoder:\n" # .
491
+ f"{Audio_codec.to_help_string(' ')}"
523
492
  ),
524
493
  childs=(Cmd_type_val(("copy", "libopus", "flac")),),
525
494
  )
easyrip/easyrip_main.py CHANGED
@@ -34,8 +34,9 @@ from .easyrip_mlang import (
34
34
  translate_subtitles,
35
35
  )
36
36
  from .easyrip_prompt import easyrip_prompt
37
- from .ripper import Media_info, Ripper
38
- from .ripper.ripper import DEFAULT_PRESET_PARAMS
37
+ from .ripper.media_info import Media_info
38
+ from .ripper.ripper import DEFAULT_PRESET_PARAMS, Ripper
39
+ from .ripper.sub_and_font import load_fonts
39
40
  from .utils import change_title, check_ver, read_text
40
41
 
41
42
  __all__ = ["init", "run_command"]
@@ -433,8 +434,8 @@ def run_command(command: Iterable[str] | str) -> bool:
433
434
  case Opt_type._preset:
434
435
  if not cmd_list[2]:
435
436
  log.send(_want_doc_cmd_type.value.to_doc(), is_format=False)
436
- elif cmd_list[2] in Ripper.PresetName._value2member_map_:
437
- _preset = Ripper.PresetName(cmd_list[2])
437
+ elif cmd_list[2] in Ripper.Preset_name._value2member_map_:
438
+ _preset = Ripper.Preset_name(cmd_list[2])
438
439
  if _preset in DEFAULT_PRESET_PARAMS:
439
440
  log.send(
440
441
  json.dumps(
@@ -488,7 +489,7 @@ def run_command(command: Iterable[str] | str) -> bool:
488
489
  case Cmd_type.exit:
489
490
  sys.exit(0)
490
491
 
491
- case Cmd_type.cd | Cmd_type.mediainfo:
492
+ case Cmd_type.cd | Cmd_type.mediainfo | Cmd_type.fontinfo:
492
493
  _path_tuple: tuple[str, ...] | None = None
493
494
 
494
495
  match cmd_list[1]:
@@ -521,6 +522,13 @@ def run_command(command: Iterable[str] | str) -> bool:
521
522
  case Cmd_type.mediainfo:
522
523
  for _path in _path_tuple:
523
524
  log.send(f"{_path}: {Media_info.from_path(_path)}")
525
+ case Cmd_type.fontinfo:
526
+ for _font in itertools.chain.from_iterable(
527
+ load_fonts(_path) for _path in _path_tuple
528
+ ):
529
+ log.send(
530
+ f"{_font.pathname}: {_font.familys} / {_font.font_type.name}"
531
+ )
524
532
 
525
533
  case Cmd_type.dir:
526
534
  files = os.listdir(os.getcwd())
@@ -781,7 +789,7 @@ def run_command(command: Iterable[str] | str) -> bool:
781
789
  input_pathname_org_list: list[str] = []
782
790
  output_basename: str | None = None
783
791
  output_dir: str | None = None
784
- preset_name: str | Ripper.PresetName | None = None
792
+ preset_name: str | Ripper.Preset_name | None = None
785
793
  option_map: dict[str, str] = {}
786
794
  is_run: bool = False
787
795
  web_server_params = None
@@ -882,19 +890,19 @@ def run_command(command: Iterable[str] | str) -> bool:
882
890
  if not preset_name:
883
891
  log.warning("Missing '-preset' option, set to default value 'custom'")
884
892
  preset_name = "custom"
885
- if preset_name not in Ripper.PresetName._value2member_map_:
893
+ if preset_name not in Ripper.Preset_name._value2member_map_:
886
894
  log.error("'{}' is not a member of preset", preset_name)
887
895
  return False
888
896
 
889
- preset_name = Ripper.PresetName(preset_name)
897
+ preset_name = Ripper.Preset_name(preset_name)
890
898
 
891
899
  if (
892
- preset_name is Ripper.PresetName.custom
900
+ preset_name is Ripper.Preset_name.custom
893
901
  and easyrip_web.http_server.Event.is_run_command
894
902
  ):
895
903
  log.error(
896
904
  "Disable the use of '{}' on the web",
897
- f"-preset {Ripper.PresetName.custom}",
905
+ f"-preset {Ripper.Preset_name.custom}",
898
906
  )
899
907
  return False
900
908
 
easyrip/global_val.py CHANGED
@@ -3,7 +3,7 @@ import sys
3
3
  from pathlib import Path
4
4
 
5
5
  PROJECT_NAME = "Easy Rip"
6
- PROJECT_VERSION = "4.3.3"
6
+ PROJECT_VERSION = "4.5.0"
7
7
  PROJECT_TITLE = f"{PROJECT_NAME} v{PROJECT_VERSION}"
8
8
  PROJECT_URL = "https://github.com/op200/EasyRip"
9
9
  PROJECT_RELEASE_API = "https://api.github.com/repos/op200/EasyRip/releases/latest"
@@ -0,0 +1,462 @@
1
+ import enum
2
+ import textwrap
3
+ from functools import reduce
4
+ from typing import Final, LiteralString
5
+
6
+
7
+ class Preset_name(enum.Enum):
8
+ custom = "custom"
9
+
10
+ subset = "subset"
11
+
12
+ flac = "flac"
13
+
14
+ copy = "copy"
15
+
16
+ x264 = "x264"
17
+ x264fast = "x264fast"
18
+ x264slow = "x264slow"
19
+
20
+ x265 = "x265"
21
+ x265fast4 = "x265fast4"
22
+ x265fast3 = "x265fast3"
23
+ x265fast2 = "x265fast2"
24
+ x265fast = "x265fast"
25
+ x265slow = "x265slow"
26
+ x265full = "x265full"
27
+
28
+ svtav1 = "svtav1"
29
+
30
+ vvenc = "vvenc"
31
+
32
+ h264_amf = "h264_amf"
33
+ h264_nvenc = "h264_nvenc"
34
+ h264_qsv = "h264_qsv"
35
+
36
+ hevc_amf = "hevc_amf"
37
+ hevc_nvenc = "hevc_nvenc"
38
+ hevc_qsv = "hevc_qsv"
39
+
40
+ av1_amf = "av1_amf"
41
+ av1_nvenc = "av1_nvenc"
42
+ av1_qsv = "av1_qsv"
43
+
44
+ @classmethod
45
+ def _missing_(cls, value: object):
46
+ from ..easyrip_log import log
47
+
48
+ DEFAULT = cls.custom
49
+ log.error(
50
+ "'{}' is not a valid '{}', set to default value '{}'. Valid options are: {}",
51
+ value,
52
+ cls.__name__,
53
+ DEFAULT.name,
54
+ list(cls.__members__.values()),
55
+ )
56
+ return DEFAULT
57
+
58
+ @classmethod
59
+ def to_help_string(cls, prefix: str = ""):
60
+ return textwrap.indent(
61
+ reduce(
62
+ lambda acc,
63
+ add: f"{acc}{' ' if add.startswith(acc.split()[-1][:4]) else '\n'}{add}",
64
+ tuple[str](cls._value2member_map_),
65
+ ),
66
+ prefix=prefix,
67
+ )
68
+
69
+
70
+ class Audio_codec(enum.Enum):
71
+ copy = "copy"
72
+ libopus = "libopus"
73
+ flac = "flac"
74
+
75
+ # 别名
76
+ opus = libopus
77
+
78
+ @classmethod
79
+ def _missing_(cls, value: object):
80
+ from ..easyrip_log import log
81
+
82
+ DEFAULT = cls.copy
83
+ log.error(
84
+ "'{}' is not a valid '{}', set to default value '{}'. Valid options are: {}",
85
+ value,
86
+ cls.__name__,
87
+ DEFAULT.name,
88
+ list(cls.__members__.values()),
89
+ )
90
+ return DEFAULT
91
+
92
+ @classmethod
93
+ def to_help_string(cls, prefix: str = ""):
94
+ return textwrap.indent(
95
+ reduce(
96
+ lambda acc,
97
+ add: f"{acc}{' ' if add.endswith(acc.split()[-1][-4:]) else '\n'}{add}",
98
+ tuple[str](cls._member_map_),
99
+ ),
100
+ prefix=prefix,
101
+ )
102
+
103
+
104
+ class Muxer(enum.Enum):
105
+ mp4 = "mp4"
106
+ mkv = "mkv"
107
+
108
+ @classmethod
109
+ def _missing_(cls, value: object):
110
+ from ..easyrip_log import log
111
+
112
+ DEFAULT = cls.mkv
113
+ log.error(
114
+ "'{}' is not a valid '{}', set to default value '{}'. Valid options are: {}",
115
+ value,
116
+ cls.__name__,
117
+ DEFAULT.name,
118
+ list(cls.__members__.values()),
119
+ )
120
+ return DEFAULT
121
+
122
+
123
+ _DEFAULT_X265_PARAMS: Final[dict[LiteralString, LiteralString]] = {
124
+ "crf": "20",
125
+ "qpmin": "6",
126
+ "qpmax": "32",
127
+ "rd": "3",
128
+ "psy-rd": "2",
129
+ "rdoq-level": "0",
130
+ "psy-rdoq": "0",
131
+ "qcomp": "0.68",
132
+ "keyint": "250",
133
+ "min-keyint": "2",
134
+ "deblock": "0,0",
135
+ "me": "umh",
136
+ "merange": "57",
137
+ "hme": "1",
138
+ "hme-search": "hex,hex,hex",
139
+ "hme-range": "16,57,92",
140
+ "aq-mode": "2",
141
+ "aq-strength": "1",
142
+ "tu-intra-depth": "1",
143
+ "tu-inter-depth": "1",
144
+ "limit-tu": "0",
145
+ "bframes": "16",
146
+ "ref": "8",
147
+ "subme": "2",
148
+ "open-gop": "1",
149
+ "gop-lookahead": "0",
150
+ "rc-lookahead": "20",
151
+ "rect": "0",
152
+ "amp": "0",
153
+ "cbqpoffs": "0",
154
+ "crqpoffs": "0",
155
+ "ipratio": "1.4",
156
+ "pbratio": "1.3",
157
+ "early-skip": "1",
158
+ "ctu": "64",
159
+ "min-cu-size": "8",
160
+ "max-tu-size": "32",
161
+ "level-idc": "0",
162
+ "sao": "0",
163
+ "weightb": "1",
164
+ "info": "1",
165
+ }
166
+
167
+ X265_PARAMS_NAME: Final[tuple[LiteralString, ...]] = (
168
+ "crf",
169
+ "qpmin",
170
+ "qpmax",
171
+ "psy-rd",
172
+ "rd",
173
+ "rdoq-level",
174
+ "psy-rdoq",
175
+ "qcomp",
176
+ "keyint",
177
+ "min-keyint",
178
+ "deblock",
179
+ "me",
180
+ "merange",
181
+ "hme",
182
+ "hme-search",
183
+ "hme-range",
184
+ "aq-mode",
185
+ "aq-strength",
186
+ "tu-intra-depth",
187
+ "tu-inter-depth",
188
+ "limit-tu",
189
+ "bframes",
190
+ "ref",
191
+ "subme",
192
+ "open-gop",
193
+ "gop-lookahead",
194
+ "rc-lookahead",
195
+ "rect",
196
+ "amp",
197
+ "cbqpoffs",
198
+ "crqpoffs",
199
+ "ipratio",
200
+ "pbratio",
201
+ "early-skip",
202
+ "ctu",
203
+ "min-cu-size",
204
+ "max-tu-size",
205
+ "level-idc",
206
+ "sao",
207
+ )
208
+
209
+ X264_PARAMS_NAME: Final[tuple[LiteralString, ...]] = (
210
+ "threads",
211
+ "crf",
212
+ "psy-rd",
213
+ "qcomp",
214
+ "keyint",
215
+ "deblock",
216
+ "qpmin",
217
+ "qpmax",
218
+ "bframes",
219
+ "ref",
220
+ "subme",
221
+ "me",
222
+ "merange",
223
+ "aq-mode",
224
+ "rc-lookahead",
225
+ "min-keyint",
226
+ "trellis",
227
+ "fast-pskip",
228
+ )
229
+
230
+ DEFAULT_PRESET_PARAMS: Final[dict[Preset_name, dict[LiteralString, LiteralString]]] = {
231
+ Preset_name.x264fast: {
232
+ "threads": "auto",
233
+ "crf": "20",
234
+ "psy-rd": "0.6,0.15",
235
+ "qcomp": "0.66",
236
+ "keyint": "250",
237
+ "deblock": "0,0",
238
+ "qpmin": "8",
239
+ "qpmax": "32",
240
+ "bframes": "8",
241
+ "ref": "4",
242
+ "subme": "5",
243
+ "me": "hex",
244
+ "merange": "16",
245
+ "aq-mode": "1",
246
+ "rc-lookahead": "60",
247
+ "min-keyint": "2",
248
+ "trellis": "1",
249
+ "fast-pskip": "1",
250
+ "weightb": "1",
251
+ },
252
+ Preset_name.x264slow: {
253
+ "threads": "auto",
254
+ "crf": "21",
255
+ "psy-rd": "0.6,0.15",
256
+ "qcomp": "0.66",
257
+ "keyint": "250",
258
+ "deblock": "-1,-1",
259
+ "qpmin": "8",
260
+ "qpmax": "32",
261
+ "bframes": "16",
262
+ "ref": "8",
263
+ "subme": "7",
264
+ "me": "umh",
265
+ "merange": "24",
266
+ "aq-mode": "3",
267
+ "rc-lookahead": "120",
268
+ "min-keyint": "2",
269
+ "trellis": "2",
270
+ "fast-pskip": "0",
271
+ "weightb": "1",
272
+ },
273
+ Preset_name.x265fast4: _DEFAULT_X265_PARAMS
274
+ | dict[LiteralString, LiteralString](
275
+ {
276
+ "crf": "18",
277
+ "qpmin": "12",
278
+ "qpmax": "28",
279
+ "rd": "2",
280
+ "rdoq-level": "1",
281
+ "me": "hex",
282
+ "merange": "57",
283
+ "hme-search": "hex,hex,hex",
284
+ "hme-range": "16,32,48",
285
+ "aq-mode": "1",
286
+ "tu-intra-depth": "1",
287
+ "tu-inter-depth": "1",
288
+ "limit-tu": "4",
289
+ "bframes": "8",
290
+ "ref": "6",
291
+ "subme": "3",
292
+ "open-gop": "0",
293
+ "gop-lookahead": "0",
294
+ "rc-lookahead": "48",
295
+ "cbqpoffs": "-1",
296
+ "crqpoffs": "-1",
297
+ "pbratio": "1.28",
298
+ }
299
+ ),
300
+ Preset_name.x265fast3: _DEFAULT_X265_PARAMS
301
+ | dict[LiteralString, LiteralString](
302
+ {
303
+ "crf": "18",
304
+ "qpmin": "12",
305
+ "qpmax": "28",
306
+ "rdoq-level": "1",
307
+ "deblock": "-0.5,-0.5",
308
+ "me": "hex",
309
+ "merange": "57",
310
+ "hme-search": "hex,hex,hex",
311
+ "hme-range": "16,32,57",
312
+ "aq-mode": "3",
313
+ "tu-intra-depth": "2",
314
+ "tu-inter-depth": "2",
315
+ "limit-tu": "4",
316
+ "bframes": "12",
317
+ "ref": "6",
318
+ "subme": "3",
319
+ "open-gop": "0",
320
+ "gop-lookahead": "0",
321
+ "rc-lookahead": "120",
322
+ "cbqpoffs": "-1",
323
+ "crqpoffs": "-1",
324
+ "pbratio": "1.27",
325
+ }
326
+ ),
327
+ Preset_name.x265fast2: _DEFAULT_X265_PARAMS
328
+ | dict[LiteralString, LiteralString](
329
+ {
330
+ "crf": "18",
331
+ "qpmin": "12",
332
+ "qpmax": "28",
333
+ "rdoq-level": "2",
334
+ "deblock": "-1,-1",
335
+ "me": "hex",
336
+ "merange": "57",
337
+ "hme-search": "hex,hex,hex",
338
+ "hme-range": "16,57,92",
339
+ "aq-mode": "3",
340
+ "tu-intra-depth": "3",
341
+ "tu-inter-depth": "2",
342
+ "limit-tu": "4",
343
+ "ref": "6",
344
+ "subme": "4",
345
+ "open-gop": "0",
346
+ "gop-lookahead": "0",
347
+ "rc-lookahead": "192",
348
+ "cbqpoffs": "-1",
349
+ "crqpoffs": "-1",
350
+ "pbratio": "1.25",
351
+ }
352
+ ),
353
+ Preset_name.x265fast: _DEFAULT_X265_PARAMS
354
+ | dict[LiteralString, LiteralString](
355
+ {
356
+ "crf": "18",
357
+ "qpmin": "12",
358
+ "qpmax": "28",
359
+ "psy-rd": "1.8",
360
+ "rdoq-level": "2",
361
+ "psy-rdoq": "0.4",
362
+ "keyint": "312",
363
+ "deblock": "-1,-1",
364
+ "me": "umh",
365
+ "merange": "57",
366
+ "hme-search": "umh,hex,hex",
367
+ "hme-range": "16,57,92",
368
+ "aq-mode": "4",
369
+ "tu-intra-depth": "4",
370
+ "tu-inter-depth": "3",
371
+ "limit-tu": "4",
372
+ "subme": "5",
373
+ "gop-lookahead": "8",
374
+ "rc-lookahead": "216",
375
+ "cbqpoffs": "-2",
376
+ "crqpoffs": "-2",
377
+ "pbratio": "1.2",
378
+ }
379
+ ),
380
+ Preset_name.x265slow: _DEFAULT_X265_PARAMS
381
+ | dict[LiteralString, LiteralString](
382
+ {
383
+ "crf": "17.5",
384
+ "qpmin": "12",
385
+ "qpmax": "28",
386
+ "rd": "5",
387
+ "psy-rd": "1.8",
388
+ "rdoq-level": "2",
389
+ "psy-rdoq": "0.4",
390
+ "qcomp": "0.7",
391
+ "keyint": "312",
392
+ "deblock": "-1,-1",
393
+ "me": "umh",
394
+ "merange": "57",
395
+ "hme-search": "umh,hex,hex",
396
+ "hme-range": "16,57,184",
397
+ "aq-mode": "4",
398
+ "aq-strength": "1",
399
+ "tu-intra-depth": "4",
400
+ "tu-inter-depth": "3",
401
+ "limit-tu": "2",
402
+ "subme": "6",
403
+ "gop-lookahead": "14",
404
+ "rc-lookahead": "250",
405
+ "rect": "1",
406
+ "min-keyint": "2",
407
+ "cbqpoffs": "-2",
408
+ "crqpoffs": "-2",
409
+ "pbratio": "1.2",
410
+ "early-skip": "0",
411
+ }
412
+ ),
413
+ Preset_name.x265full: _DEFAULT_X265_PARAMS
414
+ | dict[LiteralString, LiteralString](
415
+ {
416
+ "crf": "17",
417
+ "qpmin": "3",
418
+ "qpmax": "21.5",
419
+ "psy-rd": "2.2",
420
+ "rd": "5",
421
+ "rdoq-level": "2",
422
+ "psy-rdoq": "1.6",
423
+ "qcomp": "0.72",
424
+ "keyint": "266",
425
+ "min-keyint": "2",
426
+ "deblock": "-1,-1",
427
+ "me": "umh",
428
+ "merange": "160",
429
+ "hme-search": "full,umh,hex",
430
+ "hme-range": "16,92,320",
431
+ "aq-mode": "4",
432
+ "aq-strength": "1.2",
433
+ "tu-intra-depth": "4",
434
+ "tu-inter-depth": "4",
435
+ "limit-tu": "2",
436
+ "subme": "7",
437
+ "open-gop": "1",
438
+ "gop-lookahead": "14",
439
+ "rc-lookahead": "250",
440
+ "rect": "1",
441
+ "amp": "1",
442
+ "cbqpoffs": "-3",
443
+ "crqpoffs": "-3",
444
+ "ipratio": "1.43",
445
+ "pbratio": "1.2",
446
+ "early-skip": "0",
447
+ }
448
+ ),
449
+ }
450
+
451
+ SUBTITLE_SUFFIX_SET: Final[set[LiteralString]] = {
452
+ ".srt",
453
+ ".ass",
454
+ ".ssa",
455
+ ".sup",
456
+ ".idx",
457
+ }
458
+ FONT_SUFFIX_SET: Final[set[LiteralString]] = {
459
+ ".otf",
460
+ ".ttf",
461
+ ".ttc",
462
+ }