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.
- {muxtools-0.2.2 → muxtools-0.2.3}/PKG-INFO +2 -2
- {muxtools-0.2.2 → muxtools-0.2.3}/muxtools/audio/encoders.py +1 -1
- {muxtools-0.2.2 → muxtools-0.2.3}/muxtools/functions.py +19 -5
- {muxtools-0.2.2 → muxtools-0.2.3}/muxtools/misc/chapters.py +2 -2
- {muxtools-0.2.2 → muxtools-0.2.3}/muxtools/subtitle/sub.py +6 -4
- {muxtools-0.2.2 → muxtools-0.2.3}/muxtools/utils/download.py +17 -10
- {muxtools-0.2.2 → muxtools-0.2.3}/muxtools.egg-info/PKG-INFO +2 -2
- {muxtools-0.2.2 → muxtools-0.2.3}/pyproject.toml +3 -2
- {muxtools-0.2.2 → muxtools-0.2.3}/LICENSE +0 -0
- {muxtools-0.2.2 → muxtools-0.2.3}/README.md +0 -0
- {muxtools-0.2.2 → muxtools-0.2.3}/muxtools/__init__.py +0 -0
- {muxtools-0.2.2 → muxtools-0.2.3}/muxtools/__main__.py +0 -0
- {muxtools-0.2.2 → muxtools-0.2.3}/muxtools/audio/__init__.py +0 -0
- {muxtools-0.2.2 → muxtools-0.2.3}/muxtools/audio/audioutils.py +0 -0
- {muxtools-0.2.2 → muxtools-0.2.3}/muxtools/audio/extractors.py +0 -0
- {muxtools-0.2.2 → muxtools-0.2.3}/muxtools/audio/memecoders.py +0 -0
- {muxtools-0.2.2 → muxtools-0.2.3}/muxtools/audio/preprocess.py +0 -0
- {muxtools-0.2.2 → muxtools-0.2.3}/muxtools/audio/tools.py +0 -0
- {muxtools-0.2.2 → muxtools-0.2.3}/muxtools/cli.py +0 -0
- {muxtools-0.2.2 → muxtools-0.2.3}/muxtools/main.py +0 -0
- {muxtools-0.2.2 → muxtools-0.2.3}/muxtools/misc/__init__.py +0 -0
- {muxtools-0.2.2 → muxtools-0.2.3}/muxtools/muxing/__init__.py +0 -0
- {muxtools-0.2.2 → muxtools-0.2.3}/muxtools/muxing/mux.py +0 -0
- {muxtools-0.2.2 → muxtools-0.2.3}/muxtools/muxing/muxfiles.py +0 -0
- {muxtools-0.2.2 → muxtools-0.2.3}/muxtools/muxing/tmdb.py +0 -0
- {muxtools-0.2.2 → muxtools-0.2.3}/muxtools/muxing/tracks.py +0 -0
- {muxtools-0.2.2 → muxtools-0.2.3}/muxtools/py.typed +0 -0
- {muxtools-0.2.2 → muxtools-0.2.3}/muxtools/subtitle/__init__.py +0 -0
- {muxtools-0.2.2 → muxtools-0.2.3}/muxtools/subtitle/basesub.py +0 -0
- {muxtools-0.2.2 → muxtools-0.2.3}/muxtools/subtitle/font.py +0 -0
- {muxtools-0.2.2 → muxtools-0.2.3}/muxtools/subtitle/styles.py +0 -0
- {muxtools-0.2.2 → muxtools-0.2.3}/muxtools/subtitle/subutils.py +0 -0
- {muxtools-0.2.2 → muxtools-0.2.3}/muxtools/utils/__init__.py +0 -0
- {muxtools-0.2.2 → muxtools-0.2.3}/muxtools/utils/convert.py +0 -0
- {muxtools-0.2.2 → muxtools-0.2.3}/muxtools/utils/dataclass.py +0 -0
- {muxtools-0.2.2 → muxtools-0.2.3}/muxtools/utils/env.py +0 -0
- {muxtools-0.2.2 → muxtools-0.2.3}/muxtools/utils/files.py +0 -0
- {muxtools-0.2.2 → muxtools-0.2.3}/muxtools/utils/format.py +0 -0
- {muxtools-0.2.2 → muxtools-0.2.3}/muxtools/utils/glob.py +0 -0
- {muxtools-0.2.2 → muxtools-0.2.3}/muxtools/utils/log.py +0 -0
- {muxtools-0.2.2 → muxtools-0.2.3}/muxtools/utils/parsing.py +0 -0
- {muxtools-0.2.2 → muxtools-0.2.3}/muxtools/utils/subprogress.py +0 -0
- {muxtools-0.2.2 → muxtools-0.2.3}/muxtools/utils/types.py +0 -0
- {muxtools-0.2.2 → muxtools-0.2.3}/muxtools.egg-info/SOURCES.txt +0 -0
- {muxtools-0.2.2 → muxtools-0.2.3}/muxtools.egg-info/dependency_links.txt +0 -0
- {muxtools-0.2.2 → muxtools-0.2.3}/muxtools.egg-info/entry_points.txt +0 -0
- {muxtools-0.2.2 → muxtools-0.2.3}/muxtools.egg-info/requires.txt +0 -0
- {muxtools-0.2.2 → muxtools-0.2.3}/muxtools.egg-info/top_level.txt +0 -0
- {muxtools-0.2.2 → muxtools-0.2.3}/setup.cfg +0 -0
|
@@ -1,6 +1,6 @@
|
|
|
1
|
-
Metadata-Version: 2.
|
|
1
|
+
Metadata-Version: 2.2
|
|
2
2
|
Name: muxtools
|
|
3
|
-
Version: 0.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,
|
|
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(
|
|
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
|
-
|
|
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=
|
|
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('/',
|
|
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('/',
|
|
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
|
|
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
|
-
|
|
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
|
+
Metadata-Version: 2.2
|
|
2
2
|
Name: muxtools
|
|
3
|
-
Version: 0.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.
|
|
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
|
-
|
|
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
|
|
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
|
|
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
|
|
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
|
|
File without changes
|