muxtools 0.2.2__tar.gz → 0.2.3__tar.gz

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 (49) hide show
  1. {muxtools-0.2.2 → muxtools-0.2.3}/PKG-INFO +2 -2
  2. {muxtools-0.2.2 → muxtools-0.2.3}/muxtools/audio/encoders.py +1 -1
  3. {muxtools-0.2.2 → muxtools-0.2.3}/muxtools/functions.py +19 -5
  4. {muxtools-0.2.2 → muxtools-0.2.3}/muxtools/misc/chapters.py +2 -2
  5. {muxtools-0.2.2 → muxtools-0.2.3}/muxtools/subtitle/sub.py +6 -4
  6. {muxtools-0.2.2 → muxtools-0.2.3}/muxtools/utils/download.py +17 -10
  7. {muxtools-0.2.2 → muxtools-0.2.3}/muxtools.egg-info/PKG-INFO +2 -2
  8. {muxtools-0.2.2 → muxtools-0.2.3}/pyproject.toml +3 -2
  9. {muxtools-0.2.2 → muxtools-0.2.3}/LICENSE +0 -0
  10. {muxtools-0.2.2 → muxtools-0.2.3}/README.md +0 -0
  11. {muxtools-0.2.2 → muxtools-0.2.3}/muxtools/__init__.py +0 -0
  12. {muxtools-0.2.2 → muxtools-0.2.3}/muxtools/__main__.py +0 -0
  13. {muxtools-0.2.2 → muxtools-0.2.3}/muxtools/audio/__init__.py +0 -0
  14. {muxtools-0.2.2 → muxtools-0.2.3}/muxtools/audio/audioutils.py +0 -0
  15. {muxtools-0.2.2 → muxtools-0.2.3}/muxtools/audio/extractors.py +0 -0
  16. {muxtools-0.2.2 → muxtools-0.2.3}/muxtools/audio/memecoders.py +0 -0
  17. {muxtools-0.2.2 → muxtools-0.2.3}/muxtools/audio/preprocess.py +0 -0
  18. {muxtools-0.2.2 → muxtools-0.2.3}/muxtools/audio/tools.py +0 -0
  19. {muxtools-0.2.2 → muxtools-0.2.3}/muxtools/cli.py +0 -0
  20. {muxtools-0.2.2 → muxtools-0.2.3}/muxtools/main.py +0 -0
  21. {muxtools-0.2.2 → muxtools-0.2.3}/muxtools/misc/__init__.py +0 -0
  22. {muxtools-0.2.2 → muxtools-0.2.3}/muxtools/muxing/__init__.py +0 -0
  23. {muxtools-0.2.2 → muxtools-0.2.3}/muxtools/muxing/mux.py +0 -0
  24. {muxtools-0.2.2 → muxtools-0.2.3}/muxtools/muxing/muxfiles.py +0 -0
  25. {muxtools-0.2.2 → muxtools-0.2.3}/muxtools/muxing/tmdb.py +0 -0
  26. {muxtools-0.2.2 → muxtools-0.2.3}/muxtools/muxing/tracks.py +0 -0
  27. {muxtools-0.2.2 → muxtools-0.2.3}/muxtools/py.typed +0 -0
  28. {muxtools-0.2.2 → muxtools-0.2.3}/muxtools/subtitle/__init__.py +0 -0
  29. {muxtools-0.2.2 → muxtools-0.2.3}/muxtools/subtitle/basesub.py +0 -0
  30. {muxtools-0.2.2 → muxtools-0.2.3}/muxtools/subtitle/font.py +0 -0
  31. {muxtools-0.2.2 → muxtools-0.2.3}/muxtools/subtitle/styles.py +0 -0
  32. {muxtools-0.2.2 → muxtools-0.2.3}/muxtools/subtitle/subutils.py +0 -0
  33. {muxtools-0.2.2 → muxtools-0.2.3}/muxtools/utils/__init__.py +0 -0
  34. {muxtools-0.2.2 → muxtools-0.2.3}/muxtools/utils/convert.py +0 -0
  35. {muxtools-0.2.2 → muxtools-0.2.3}/muxtools/utils/dataclass.py +0 -0
  36. {muxtools-0.2.2 → muxtools-0.2.3}/muxtools/utils/env.py +0 -0
  37. {muxtools-0.2.2 → muxtools-0.2.3}/muxtools/utils/files.py +0 -0
  38. {muxtools-0.2.2 → muxtools-0.2.3}/muxtools/utils/format.py +0 -0
  39. {muxtools-0.2.2 → muxtools-0.2.3}/muxtools/utils/glob.py +0 -0
  40. {muxtools-0.2.2 → muxtools-0.2.3}/muxtools/utils/log.py +0 -0
  41. {muxtools-0.2.2 → muxtools-0.2.3}/muxtools/utils/parsing.py +0 -0
  42. {muxtools-0.2.2 → muxtools-0.2.3}/muxtools/utils/subprogress.py +0 -0
  43. {muxtools-0.2.2 → muxtools-0.2.3}/muxtools/utils/types.py +0 -0
  44. {muxtools-0.2.2 → muxtools-0.2.3}/muxtools.egg-info/SOURCES.txt +0 -0
  45. {muxtools-0.2.2 → muxtools-0.2.3}/muxtools.egg-info/dependency_links.txt +0 -0
  46. {muxtools-0.2.2 → muxtools-0.2.3}/muxtools.egg-info/entry_points.txt +0 -0
  47. {muxtools-0.2.2 → muxtools-0.2.3}/muxtools.egg-info/requires.txt +0 -0
  48. {muxtools-0.2.2 → muxtools-0.2.3}/muxtools.egg-info/top_level.txt +0 -0
  49. {muxtools-0.2.2 → muxtools-0.2.3}/setup.cfg +0 -0
@@ -1,6 +1,6 @@
1
- Metadata-Version: 2.1
1
+ Metadata-Version: 2.2
2
2
  Name: muxtools
3
- Version: 0.2.2
3
+ Version: 0.2.3
4
4
  Summary: A library for various muxing and automation tools for anything and everything fansubbing related
5
5
  Author-email: Vodes <vodes.imp@gmail.com>
6
6
  License: MPL 2.0
@@ -304,7 +304,7 @@ class FDK_AAC(Encoder):
304
304
  exe = get_executable("ffmpeg") if not self.use_binary else get_executable("fdkaac")
305
305
 
306
306
  if os.name == "nt":
307
- warn("It is strongly recommended to use qAAC on windows. See docs.", self, 5)
307
+ warn("It is strongly recommended to use qAAC on windows. See docs.", self, 1)
308
308
  info(f"Encoding '{fileIn.file.stem}' to AAC using libFDK...", self)
309
309
 
310
310
  tags = dict[str, str]()
@@ -5,12 +5,13 @@ from collections.abc import Sequence
5
5
  from .utils.log import warn, error, info, danger
6
6
  from .muxing.muxfiles import AudioFile
7
7
  from .audio.audioutils import is_fancy_codec
8
- from .audio.encoders import Opus
8
+ from .audio.encoders import Opus, qAAC, FDK_AAC
9
9
  from .utils.types import PathLike, Trim
10
10
  from .audio.extractors import FFMpeg, Sox
11
11
  from .utils.files import ensure_path, ensure_path_exists
12
12
  from .audio.tools import AutoEncoder, AutoTrimmer, Encoder, Trimmer, Extractor
13
13
  from .utils.convert import format_timedelta
14
+ from .utils.download import get_executable
14
15
 
15
16
  __all__ = ["do_audio"]
16
17
 
@@ -21,7 +22,7 @@ def do_audio(
21
22
  trims: Trim | list[Trim] | None = None,
22
23
  fps: Fraction | PathLike | Sequence[int] = Fraction(24000, 1001),
23
24
  num_frames: int = 0,
24
- extractor: Extractor = FFMpeg.Extractor(),
25
+ extractor: Extractor | None = FFMpeg.Extractor(),
25
26
  trimmer: Trimmer | None = AutoTrimmer(),
26
27
  encoder: Encoder | None = AutoEncoder(),
27
28
  quiet: bool = True,
@@ -40,7 +41,7 @@ def do_audio(
40
41
  AutoTrimmer means it will choose ffmpeg for lossy and Sox for lossless
41
42
 
42
43
  :param encoder: Tool used to encode the audio
43
- AutoEncoder means it won't reencode lossy and choose opus otherwise
44
+ AutoEncoder means it won't reencode lossy and choose opus (for 2.0) or qAAC/FDKAAC (for >2.0) otherwise
44
45
 
45
46
  :param quiet: Whether the tool output should be visible
46
47
  :param output: Custom output file or directory, extensions will be automatically added
@@ -77,6 +78,8 @@ def do_audio(
77
78
  audio = FFMpeg.Concat(extracted).concat_audio()
78
79
  else:
79
80
  audio = extractor.extract_audio(fileIn, quiet)
81
+ else:
82
+ audio = ensure_path_exists(fileIn, do_audio)
80
83
 
81
84
  if not isinstance(audio, AudioFile):
82
85
  audio = AudioFile.from_file(audio, do_audio)
@@ -89,14 +92,25 @@ def do_audio(
89
92
  else:
90
93
  trimmer = Sox()
91
94
 
95
+ mediainfo = audio.get_mediainfo()
96
+
92
97
  if isinstance(encoder, AutoEncoder):
93
98
  if lossy:
94
99
  encoder = None
95
- elif is_fancy_codec(audio.get_mediainfo()):
100
+ elif is_fancy_codec(mediainfo):
96
101
  encoder = None
97
102
  warn("Audio will not be reencoded due to having Atmos or special DTS features.", do_audio, 2)
98
103
  else:
99
- encoder = Opus()
104
+ channels = getattr(mediainfo, "channel_s", None) or 2
105
+ if channels <= 2:
106
+ encoder = Opus()
107
+ else:
108
+ has_qaac = bool(get_executable("qaac", False, False))
109
+ if has_qaac:
110
+ encoder = qAAC(100, lowpass=20000)
111
+ else:
112
+ warn("Attempting to fall back to FDK_AAC because of a lack of qAAC in current PATH.", do_audio, 1)
113
+ encoder = FDK_AAC()
100
114
 
101
115
  if trimmer and trims:
102
116
  if not isinstance(fps, str) and isinstance(fps, Sequence):
@@ -21,7 +21,7 @@ __all__ = ["Chapters"]
21
21
 
22
22
 
23
23
  class Chapters:
24
- chapters: list[Chapter] = []
24
+ chapters: list[Chapter]
25
25
  fps: Fraction | PathLike
26
26
 
27
27
  def __init__(
@@ -190,7 +190,7 @@ class Chapters:
190
190
  with open(out_file, "w", encoding="UTF-8") as f:
191
191
  f.writelines(
192
192
  [
193
- f"CHAPTER{i:02d}={format_timedelta(chapter[0])}\nCHAPTER{i:02d}NAME=" f'{chapter[1] if chapter[1] else ""}\n'
193
+ f"CHAPTER{i:02d}={format_timedelta(chapter[0])}\nCHAPTER{i:02d}NAME={chapter[1] if chapter[1] else ''}\n"
194
194
  for i, chapter in enumerate(self.chapters)
195
195
  ]
196
196
  )
@@ -222,10 +222,12 @@ class SubFile(BaseSubFile):
222
222
 
223
223
  marker = re.escape(inline_marker)
224
224
 
225
- ab_swap_regex = re.compile(rf"{{{marker}}}(.*?){{{marker}([^}}*]+)}}")
225
+ ab_swap_regex = re.compile(rf"{{{marker}}}(.*?){{{marker}([^}}]+)}}")
226
226
  show_word_regex = re.compile(rf"{{{marker}{marker}([^}}]+)}}")
227
227
  hide_word_regex = re.compile(rf"{{{marker}}}(.*?){{{marker} *}}")
228
228
 
229
+ backslash = "\\" # This is to ensure support for previous python versions that don't allow backslashes in f-strings.
230
+
229
231
  def _do_autoswap(lines: LINES):
230
232
  for i, line in enumerate(lines):
231
233
  if not allowed_styles or str(line.style).casefold() in {style.casefold() for style in allowed_styles}:
@@ -235,7 +237,7 @@ class SubFile(BaseSubFile):
235
237
  if inline_tag_markers:
236
238
  to_swap.update(
237
239
  {
238
- f"{match.group(0)}": f"{{{inline_marker}}}{match.group(2).replace(inline_tag_markers[0], '{').replace(inline_tag_markers[1], '}').replace('/', '\\')}{{{inline_marker}{match.group(1).replace('{', inline_tag_markers[0]).replace('}', inline_tag_markers[1], ).replace('\\', '/')}}}"
240
+ f"{match.group(0)}": f"{{{inline_marker}}}{match.group(2).replace(inline_tag_markers[0], '{').replace(inline_tag_markers[1], '}').replace('/', backslash)}{{{inline_marker}{match.group(1).replace('{', inline_tag_markers[0]).replace('}', inline_tag_markers[1], ).replace(backslash, '/')}}}"
239
241
  }
240
242
  )
241
243
  else:
@@ -250,7 +252,7 @@ class SubFile(BaseSubFile):
250
252
  if inline_tag_markers:
251
253
  to_swap.update(
252
254
  {
253
- f"{match.group(0)}": f"{{{inline_marker}}}{match.group(1).replace(inline_tag_markers[0], '{').replace(inline_tag_markers[1], '}').replace('/', '\\')}{{{inline_marker}}}"
255
+ f"{match.group(0)}": f"{{{inline_marker}}}{match.group(1).replace(inline_tag_markers[0], '{').replace(inline_tag_markers[1], '}').replace('/', backslash)}{{{inline_marker}}}"
254
256
  }
255
257
  )
256
258
  else:
@@ -261,7 +263,7 @@ class SubFile(BaseSubFile):
261
263
  if inline_tag_markers:
262
264
  to_swap.update(
263
265
  {
264
- f"{match.group(0)}": f"{{{inline_marker*2}{match.group(1).replace('{', inline_tag_markers[0]).replace('}', inline_tag_markers[1]).replace('\\', '/')}}}"
266
+ f"{match.group(0)}": f"{{{inline_marker*2}{match.group(1).replace('{', inline_tag_markers[0]).replace('}', inline_tag_markers[1]).replace(backslash, '/')}}}"
265
267
  }
266
268
  )
267
269
  else:
@@ -59,8 +59,12 @@ def get_executable(type: str, can_download: bool | None = None, can_error: bool
59
59
 
60
60
  if path is None:
61
61
  if not can_download or can_download is False:
62
+ if os.name == "nt" and (exe := _find_downloaded_binary(type)):
63
+ return str(exe)
64
+
62
65
  if not can_error:
63
66
  return None
67
+
64
68
  raise crit(f"{type.lower()} executable not found in path!", get_executable)
65
69
  else:
66
70
  path = download_binary(type.lower())
@@ -68,21 +72,28 @@ def get_executable(type: str, can_download: bool | None = None, can_error: bool
68
72
  return str(path)
69
73
 
70
74
 
71
- def download_binary(type: str) -> str:
72
- if os.name != "nt":
73
- raise EnvironmentError("Of course only Windows is supported for downloading of binaries!")
74
-
75
+ def _find_downloaded_binary(type: str) -> Path | None:
75
76
  binary_dir = Path(os.path.join(os.getcwd(), "_binaries"))
76
77
  binary_dir.mkdir(exist_ok=True)
77
78
 
78
79
  type = type.lower()
79
80
 
80
- executable: Path = None
81
81
  executables = binary_dir.rglob(type + "*.exe")
82
82
 
83
83
  for exe in sorted(executables):
84
84
  if exe.is_file():
85
85
  return exe.resolve()
86
+ return None
87
+
88
+
89
+ def download_binary(type: str) -> str:
90
+ if os.name != "nt":
91
+ raise EnvironmentError("Of course only Windows is supported for downloading of binaries!")
92
+
93
+ if exe := _find_downloaded_binary(type):
94
+ return str(exe)
95
+
96
+ binary_dir = Path(os.path.join(os.getcwd(), "_binaries"))
86
97
 
87
98
  info(f"Downloading {type} executables...", get_executable)
88
99
  url = None
@@ -103,11 +114,7 @@ def download_binary(type: str) -> str:
103
114
  info("Done.", get_executable)
104
115
  unpack_all(binary_dir)
105
116
 
106
- executables = binary_dir.rglob(type.lower() + "*.exe")
107
-
108
- for exe in sorted(executables):
109
- if exe.is_file():
110
- executable = exe
117
+ executable = _find_downloaded_binary(type)
111
118
 
112
119
  if executable is None:
113
120
  raise error(f"Binary for '{type}' could not have been found!", get_executable)
@@ -1,6 +1,6 @@
1
- Metadata-Version: 2.1
1
+ Metadata-Version: 2.2
2
2
  Name: muxtools
3
- Version: 0.2.2
3
+ Version: 0.2.3
4
4
  Summary: A library for various muxing and automation tools for anything and everything fansubbing related
5
5
  Author-email: Vodes <vodes.imp@gmail.com>
6
6
  License: MPL 2.0
@@ -1,7 +1,7 @@
1
1
 
2
2
  [project]
3
3
  name = "muxtools"
4
- version = "0.2.2"
4
+ version = "0.2.3"
5
5
  description = "A library for various muxing and automation tools for anything and everything fansubbing related"
6
6
  authors = [{ name = "Vodes", email = "vodes.imp@gmail.com" }]
7
7
  dependencies = [
@@ -49,7 +49,8 @@ indent-width = 4
49
49
  target-version = "py310"
50
50
 
51
51
  [tool.ruff.lint]
52
- ignore = ["E722"]
52
+ select = ["F", "E", "RUF012"]
53
+ ignore = ["E722", "E501"]
53
54
  fixable = ["F541", "F401", "E712", "E711"]
54
55
  exclude = ["__init__.py"]
55
56
 
File without changes
File without changes
File without changes
File without changes
File without changes
File without changes
File without changes
File without changes
File without changes
File without changes