muxtools 0.2.0__tar.gz → 0.2.2__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.0 → muxtools-0.2.2}/PKG-INFO +1 -1
  2. {muxtools-0.2.0 → muxtools-0.2.2}/muxtools/audio/encoders.py +1 -1
  3. {muxtools-0.2.0 → muxtools-0.2.2}/muxtools/muxing/mux.py +1 -1
  4. {muxtools-0.2.0 → muxtools-0.2.2}/muxtools/muxing/tmdb.py +10 -2
  5. {muxtools-0.2.0 → muxtools-0.2.2}/muxtools/subtitle/sub.py +81 -24
  6. {muxtools-0.2.0 → muxtools-0.2.2}/muxtools/utils/download.py +6 -7
  7. {muxtools-0.2.0 → muxtools-0.2.2}/muxtools.egg-info/PKG-INFO +1 -1
  8. {muxtools-0.2.0 → muxtools-0.2.2}/pyproject.toml +1 -1
  9. {muxtools-0.2.0 → muxtools-0.2.2}/LICENSE +0 -0
  10. {muxtools-0.2.0 → muxtools-0.2.2}/README.md +0 -0
  11. {muxtools-0.2.0 → muxtools-0.2.2}/muxtools/__init__.py +0 -0
  12. {muxtools-0.2.0 → muxtools-0.2.2}/muxtools/__main__.py +0 -0
  13. {muxtools-0.2.0 → muxtools-0.2.2}/muxtools/audio/__init__.py +0 -0
  14. {muxtools-0.2.0 → muxtools-0.2.2}/muxtools/audio/audioutils.py +0 -0
  15. {muxtools-0.2.0 → muxtools-0.2.2}/muxtools/audio/extractors.py +0 -0
  16. {muxtools-0.2.0 → muxtools-0.2.2}/muxtools/audio/memecoders.py +0 -0
  17. {muxtools-0.2.0 → muxtools-0.2.2}/muxtools/audio/preprocess.py +0 -0
  18. {muxtools-0.2.0 → muxtools-0.2.2}/muxtools/audio/tools.py +0 -0
  19. {muxtools-0.2.0 → muxtools-0.2.2}/muxtools/cli.py +0 -0
  20. {muxtools-0.2.0 → muxtools-0.2.2}/muxtools/functions.py +0 -0
  21. {muxtools-0.2.0 → muxtools-0.2.2}/muxtools/main.py +0 -0
  22. {muxtools-0.2.0 → muxtools-0.2.2}/muxtools/misc/__init__.py +0 -0
  23. {muxtools-0.2.0 → muxtools-0.2.2}/muxtools/misc/chapters.py +0 -0
  24. {muxtools-0.2.0 → muxtools-0.2.2}/muxtools/muxing/__init__.py +0 -0
  25. {muxtools-0.2.0 → muxtools-0.2.2}/muxtools/muxing/muxfiles.py +0 -0
  26. {muxtools-0.2.0 → muxtools-0.2.2}/muxtools/muxing/tracks.py +0 -0
  27. {muxtools-0.2.0 → muxtools-0.2.2}/muxtools/py.typed +0 -0
  28. {muxtools-0.2.0 → muxtools-0.2.2}/muxtools/subtitle/__init__.py +0 -0
  29. {muxtools-0.2.0 → muxtools-0.2.2}/muxtools/subtitle/basesub.py +0 -0
  30. {muxtools-0.2.0 → muxtools-0.2.2}/muxtools/subtitle/font.py +0 -0
  31. {muxtools-0.2.0 → muxtools-0.2.2}/muxtools/subtitle/styles.py +0 -0
  32. {muxtools-0.2.0 → muxtools-0.2.2}/muxtools/subtitle/subutils.py +0 -0
  33. {muxtools-0.2.0 → muxtools-0.2.2}/muxtools/utils/__init__.py +0 -0
  34. {muxtools-0.2.0 → muxtools-0.2.2}/muxtools/utils/convert.py +0 -0
  35. {muxtools-0.2.0 → muxtools-0.2.2}/muxtools/utils/dataclass.py +0 -0
  36. {muxtools-0.2.0 → muxtools-0.2.2}/muxtools/utils/env.py +0 -0
  37. {muxtools-0.2.0 → muxtools-0.2.2}/muxtools/utils/files.py +0 -0
  38. {muxtools-0.2.0 → muxtools-0.2.2}/muxtools/utils/format.py +0 -0
  39. {muxtools-0.2.0 → muxtools-0.2.2}/muxtools/utils/glob.py +0 -0
  40. {muxtools-0.2.0 → muxtools-0.2.2}/muxtools/utils/log.py +0 -0
  41. {muxtools-0.2.0 → muxtools-0.2.2}/muxtools/utils/parsing.py +0 -0
  42. {muxtools-0.2.0 → muxtools-0.2.2}/muxtools/utils/subprogress.py +0 -0
  43. {muxtools-0.2.0 → muxtools-0.2.2}/muxtools/utils/types.py +0 -0
  44. {muxtools-0.2.0 → muxtools-0.2.2}/muxtools.egg-info/SOURCES.txt +0 -0
  45. {muxtools-0.2.0 → muxtools-0.2.2}/muxtools.egg-info/dependency_links.txt +0 -0
  46. {muxtools-0.2.0 → muxtools-0.2.2}/muxtools.egg-info/entry_points.txt +0 -0
  47. {muxtools-0.2.0 → muxtools-0.2.2}/muxtools.egg-info/requires.txt +0 -0
  48. {muxtools-0.2.0 → muxtools-0.2.2}/muxtools.egg-info/top_level.txt +0 -0
  49. {muxtools-0.2.0 → muxtools-0.2.2}/setup.cfg +0 -0
@@ -1,6 +1,6 @@
1
1
  Metadata-Version: 2.1
2
2
  Name: muxtools
3
- Version: 0.2.0
3
+ Version: 0.2.2
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
@@ -206,7 +206,7 @@ class Opus(Encoder):
206
206
 
207
207
  if not run_cmd_pb(args, quiet, config, shell=False, stdin=stdin):
208
208
  tags = version_settings_dict(
209
- self.get_mediainfo_settings(args), exe, r"opusenc (opus-tools .+?\(using libopus \d+\.\d+(?:\.\d)?.+?\)?)", ["-V"], prepend="opusenc"
209
+ self.get_mediainfo_settings(args), exe, r"opusenc (opus-tools .+?\(using libopus \d+\.\d+(?:\.\d)?.+\)?)", ["-V"], prepend="opusenc"
210
210
  )
211
211
  clean_temp_files()
212
212
  return AudioFile(output, fileIn.container_delay, fileIn.source, tags=tags)
@@ -131,7 +131,7 @@ def clean_name(name: str) -> str:
131
131
  stripped = name.strip()
132
132
 
133
133
  dont_match = [R"$show$", R"$ep$", "$crc32$"]
134
- for match in re.findall(r"\$[^ ]+\$", name):
134
+ for match in re.findall(r"\$[^ ]+?\$", name):
135
135
  if match not in dont_match:
136
136
  stripped = stripped.replace(match, "").strip()
137
137
  warn(f"Unknown token '{match}' was removed.", "Mux")
@@ -85,6 +85,7 @@ class TmdbConfig:
85
85
  :param write_cover: Download episode thumbnail from TMDB to use as cover art attachment for the MKV.
86
86
  :param write_summary: Writes the series summary/synopsis to the `SUMMARY` mkv tag if True
87
87
  :param write_synopsis: Writes the individual episode synopsis to the `SYNOPSIS` mkv tag if True
88
+ :param replace_spaces: Replaces spaces in titles with dots if True and with whatever string you passed if a string.
88
89
  """
89
90
 
90
91
  id: int
@@ -100,6 +101,7 @@ class TmdbConfig:
100
101
  write_cover: bool = False
101
102
  write_summary: bool = False
102
103
  write_synopsis: bool = False
104
+ replace_spaces: str | bool = False
103
105
 
104
106
  def needs_xml(self) -> bool:
105
107
  return self.write_ids or self.write_date or self.write_title or self.write_summary or self.write_synopsis
@@ -172,12 +174,18 @@ class TmdbConfig:
172
174
  self.episodes = json_resp["episodes"]
173
175
 
174
176
  try:
175
- episode = self.episodes[(num + self.offset) - 1]
177
+ episode: dict = self.episodes[(num + self.offset) - 1]
176
178
  except:
177
179
  raise error(f"Failed to find or parse episode {num:02}!", self)
178
180
 
181
+ title: str = episode.get("name", "")
182
+ if self.replace_spaces is True:
183
+ title = title.replace(" ", ".")
184
+ elif isinstance(self.replace_spaces, str):
185
+ title = title.replace(" ", self.replace_spaces)
186
+
179
187
  return EpisodeMetadata(
180
- episode.get("name", ""),
188
+ title,
181
189
  episode.get("air_date", ""),
182
190
  episode.get("overview", ""),
183
191
  f"https://image.tmdb.org/t/p/w780{episode.get('still_path')}" if self.write_cover else "",
@@ -16,7 +16,7 @@ from .subutils import create_document, dummy_video, has_arch_resampler
16
16
  from ..utils.glob import GlobSearch
17
17
  from ..utils.download import get_executable
18
18
  from ..utils.types import PathLike, TrackType
19
- from ..utils.log import debug, error, info, warn
19
+ from ..utils.log import debug, error, info, warn, log_escape
20
20
  from ..utils.convert import frame_to_timedelta, timedelta_to_frame
21
21
  from ..utils.env import get_temp_workdir, get_workdir, run_commandline
22
22
  from ..utils.files import ensure_path_exists, get_absolute_track, make_output, clean_temp_files, uniquify_path
@@ -131,7 +131,7 @@ class SubFile(BaseSubFile):
131
131
  """
132
132
  doc = self._read_doc()
133
133
  used_styles = {line.style for line in doc.events if line.TYPE == "Dialogue"}
134
- regex = re.compile(r"\{.*?\\r([^\\]+)\}")
134
+ regex = re.compile(r"\{[^}]*\\r([^\\}]+)[^}]*\}")
135
135
  for line in [line for line in doc.events if line.TYPE == "Dialogue"]:
136
136
  for match in regex.finditer(line.text):
137
137
  used_styles.add(match.group(1))
@@ -169,19 +169,43 @@ class SubFile(BaseSubFile):
169
169
  print_swaps: bool = False,
170
170
  inline_marker: str = "*",
171
171
  line_marker: str = "***",
172
+ inline_tag_markers: str | None = None,
172
173
  ) -> SubFileSelf:
173
- """
174
- autoswapper does the swapping.
175
- Too lazy to explain
174
+ r"""
175
+ autoswapper allows replacing text in the script with a different text.
176
+ Useful for creating honorific tracks.
177
+
178
+ Assuming the markers are as default:
179
+
180
+ - `{*}abc{*def}` becomes `{*}def{*abc}` (AB Swap)
181
+ - `abc{**def}` becomes `abc{*}def{*}` (Show Word)
182
+ - `abc{*}def{*}` becomes `abc{**def}` (Hide Word)
183
+
184
+ Note: AB Swap and Hide Word will remove `{}` from the swapped text, to ensure the comment isn't broken.
185
+
186
+ You can also comment in or out entire lines by using the line marker in either the `effect` or `name` field.
187
+ - A dialogue line with effect or name set to `***` will be commented.
188
+ - A comment line with effect or name set to `***` will be set to a dialogue.
189
+
176
190
 
177
- :param allowed_styles: List of allowed styles to do the swapping on
178
- Will run on every line if passed `None`
179
- :param print_swaps: Prints the swaps
180
- :param inline_marker: Marker to use for inline swaps.
181
- Should be one character. Default `*`
182
- :param line_marker: Marker to use for full-line swaps. Default `***`
191
+ `inline_tag_markers` can be used to swap ASS tags as well. Keep in mind that "\" and "/" will be swapped, so override tags aren't applied.
183
192
 
184
- :return: This SubTrack
193
+ Assuming the markers are as default, except inline_tag_markers = `[]`:
194
+ - `{*}{\i1}abc{*[/b1]def}` becomes `{*}{\b1}def{*[/i1]abc}` (AB Swap)
195
+ - `abc{**[/b1]def}` becomes `abc{*}{\b1}def{*}` (Show Word)
196
+ - `abc{*}{\b1}def{*}` becomes `abc{**[/b1]def}` (Hide Word)
197
+
198
+
199
+ :param allowed_styles: List of allowed styles to do the swapping on
200
+ Will run on every line if passed `None`
201
+ :param print_swaps: Prints the swaps
202
+ :param inline_marker: Marker to use for inline swaps.
203
+ Should be one character. Default `*`
204
+ :param line_marker: Marker to use for full-line swaps. Default `***`
205
+ :param inline_tag_markers: Two characters that will be replaced with `{}` respectively in the inline swaps.
206
+ Defaults to `None`, which will just remove the `{}` from the swapped text.
207
+
208
+ :return: This SubTrack
185
209
  """
186
210
  if not isinstance(inline_marker, str) or not inline_marker.strip():
187
211
  warn("Given invalid inline marker. Using default '*'.", self)
@@ -191,11 +215,16 @@ class SubFile(BaseSubFile):
191
215
  warn("Given invalid line marker. Using default '***'.", self)
192
216
  line_marker = "***"
193
217
 
218
+ if inline_tag_markers:
219
+ if len(inline_tag_markers) != 2 or inline_tag_markers[0] == inline_tag_markers[1] or any([m in "{}" for m in inline_tag_markers]):
220
+ warn("Given invalid inline comment markers. Using default 'None'.", self)
221
+ inline_tag_markers = None
222
+
194
223
  marker = re.escape(inline_marker)
195
224
 
196
- ab_swap_regex = re.compile(rf"{{{marker}}}([^{{]*){{{marker}([^}}*]+)}}")
225
+ ab_swap_regex = re.compile(rf"{{{marker}}}(.*?){{{marker}([^}}*]+)}}")
197
226
  show_word_regex = re.compile(rf"{{{marker}{marker}([^}}]+)}}")
198
- hide_word_regex = re.compile(rf"{{{marker}}}([^{{]*){{{marker} *}}")
227
+ hide_word_regex = re.compile(rf"{{{marker}}}(.*?){{{marker} *}}")
199
228
 
200
229
  def _do_autoswap(lines: LINES):
201
230
  for i, line in enumerate(lines):
@@ -203,30 +232,55 @@ class SubFile(BaseSubFile):
203
232
  to_swap: dict = {}
204
233
  # {*}This will be replaced{*With this}
205
234
  for match in re.finditer(ab_swap_regex, line.text):
206
- to_swap.update({f"{match.group(0)}": f"{{{inline_marker}}}{match.group(2)}{{{inline_marker}{match.group(1)}}}"})
235
+ if inline_tag_markers:
236
+ to_swap.update(
237
+ {
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('\\', '/')}}}"
239
+ }
240
+ )
241
+ else:
242
+ to_swap.update(
243
+ {
244
+ f"{match.group(0)}": f"{{{inline_marker}}}{match.group(2)}{{{inline_marker}{match.group(1).replace('{', '').replace('}', '')}}}"
245
+ }
246
+ )
207
247
 
208
248
  # This sentence is no longer{** incomplete}
209
249
  for match in re.finditer(show_word_regex, line.text):
210
- to_swap.update({f"{match.group(0)}": f"{{{inline_marker}}}{match.group(1)}{{{inline_marker}}}"})
211
-
212
- # This sentence is no longer{*} incomplete{*}
250
+ if inline_tag_markers:
251
+ to_swap.update(
252
+ {
253
+ f"{match.group(0)}": f"{{{inline_marker}}}{match.group(1).replace(inline_tag_markers[0], '{').replace(inline_tag_markers[1], '}').replace('/', '\\')}{{{inline_marker}}}"
254
+ }
255
+ )
256
+ else:
257
+ to_swap.update({f"{match.group(0)}": f"{{{inline_marker}}}{match.group(1)}{{{inline_marker}}}"})
258
+
259
+ # This sentence is no longer{*} complete{*}
213
260
  for match in re.finditer(hide_word_regex, line.text):
214
- to_swap.update({f"{match.group(0)}": f"{{{inline_marker*2}{match.group(1)}}}"})
215
- # print(to_swap)
261
+ if inline_tag_markers:
262
+ to_swap.update(
263
+ {
264
+ f"{match.group(0)}": f"{{{inline_marker*2}{match.group(1).replace('{', inline_tag_markers[0]).replace('}', inline_tag_markers[1]).replace('\\', '/')}}}"
265
+ }
266
+ )
267
+ else:
268
+ to_swap.update({f"{match.group(0)}": f"{{{inline_marker*2}{match.group(1).replace('{', '').replace('}', '')}}}"})
269
+
216
270
  for key, val in to_swap.items():
217
271
  if print_swaps:
218
- info(f'autoswapper: Swapped "{key}" for "{val}" on line {i}', self)
272
+ info(f'autoswapper: Swapped "{log_escape(key)}" for "{log_escape(val)}" on line {i}', self)
219
273
  line.text = line.text.replace(key, val)
220
274
 
221
275
  if line.effect.strip() == line_marker or line.name.strip() == line_marker:
222
276
  if isinstance(line, Comment):
223
277
  line.TYPE = "Dialogue"
224
278
  if print_swaps:
225
- info(f'autoswapper: Uncommented Line {i} - "{line.text}"', self)
279
+ info(f'autoswapper: Uncommented Line {i} - "{log_escape(line.text)}"', self)
226
280
  elif isinstance(line, Dialogue):
227
281
  line.TYPE = "Comment"
228
282
  if print_swaps:
229
- info(f'autoswapper: Commented Line {i} - "{line.text}"', self)
283
+ info(f'autoswapper: Commented Line {i} - "{log_escape(line.text)}"', self)
230
284
 
231
285
  self.manipulate_lines(_do_autoswap)
232
286
  return self
@@ -318,6 +372,7 @@ class SubFile(BaseSubFile):
318
372
  fps: Fraction | PathLike = Fraction(24000, 1001),
319
373
  use_actor_field: bool = False,
320
374
  no_error: bool = False,
375
+ sort_lines: bool = False,
321
376
  ) -> SubFileSelf:
322
377
  """
323
378
  Merge another subtitle file with syncing if needed.
@@ -329,6 +384,8 @@ class SubFile(BaseSubFile):
329
384
  :param fps: The fps used for time calculations. Also accepts a timecode (v2) file.
330
385
  :param use_actor_field: Checks the actor field instead of effect for the names if True.
331
386
  :param no_error: Don't error and warn instead if syncpoint not found.
387
+ :param sort_lines: Sort the lines by the starting timestamp.
388
+ This was done by default before but may cause issues with subtitles relying on implicit layering.
332
389
  """
333
390
 
334
391
  file = ensure_path_exists(file, self)
@@ -377,7 +434,7 @@ class SubFile(BaseSubFile):
377
434
  break
378
435
 
379
436
  # Merge lines from file
380
- for line in sorted_lines:
437
+ for line in sorted_lines if sort_lines else mergedoc.events:
381
438
  # Don't apply any offset if sync=None for plain merging or if target == source
382
439
  if target is None or target == second_sync:
383
440
  tomerge.append(line)
@@ -28,13 +28,13 @@ class Tool:
28
28
  tools = [
29
29
  Tool("CUETools.FLACCL.cmd", "https://github.com/gchudov/cuetools.net/releases/download/v2.2.6/CUETools_2.2.6.zip", ["flaccl"]),
30
30
  # non-free builds for libfdk_aac if one so desires
31
- Tool("ffmpeg", "https://github.com/Vodes/FFmpeg-Builds/releases/download/latest/ffmpeg-n7.0-latest-win64-nonfree-7.0.zip", ["ffprobe"]),
32
- Tool("mkvmerge", "https://mkvtoolnix.download/windows/releases/87.0/mkvtoolnix-64-bit-87.0.7z", ['mkvextract', 'mkvinfo', 'mkvpropedit']),
31
+ Tool("ffmpeg", "https://github.com/Vodes/FFmpeg-Builds/releases/download/latest/ffmpeg-n7.1-latest-win64-nonfree-7.1.zip", ["ffprobe"]),
32
+ Tool("mkvmerge", "https://mkvtoolnix.download/windows/releases/89.0/mkvtoolnix-64-bit-89.0.7z", ['mkvextract', 'mkvinfo', 'mkvpropedit']),
33
33
  Tool("eac3to", "https://files.catbox.moe/hn9oms.7z"), # Custom package because of removed sounds and updated libFlac
34
34
  Tool("x264", "https://github.com/DJATOM/x264-aMod/releases/download/r3101+20/x264-aMod-x64-core164-r3101+20.7z"),
35
35
  Tool("x265", "https://github.com/DJATOM/x265-aMod/releases/download/3.5+67/x265-x64-v3.5+67-aMod-gcc12.2.1+opt.7z"),
36
36
  Tool("qaac", "https://files.catbox.moe/z9q796.7z"), # 2.79 with flac, w64 and iTunes libraries included
37
- Tool("opusenc", "https://www.rarewares.org/files/opus/opustools-0.2-20230419-x64.zip"),
37
+ Tool("opusenc", "https://www.rarewares.org/files/opus/opus-tools%200.2-34-g98f3ddc-x64.zip"),
38
38
  Tool("flac", "https://github.com/xiph/flac/releases/download/1.4.3/flac-1.4.3-win.zip"),
39
39
  Tool("wavpack", "https://github.com/dbry/WavPack/releases/download/5.7.0/wavpack-5.7.0-x64.zip")
40
40
  ]
@@ -46,9 +46,8 @@ tools = [
46
46
  def get_executable(type: str, can_download: bool | None = None, can_error: bool = True) -> str:
47
47
  if can_download is None:
48
48
  can_download = download_allowed()
49
- type = type.lower()
50
49
  path = sh.which(type)
51
- env = os.environ.get(f"vof_exe_{type}", None)
50
+ env = os.environ.get(f"vof_exe_{type.lower()}", None)
52
51
  if env:
53
52
  path = Path(env)
54
53
  if path.exists():
@@ -64,9 +63,9 @@ def get_executable(type: str, can_download: bool | None = None, can_error: bool
64
63
  return None
65
64
  raise crit(f"{type.lower()} executable not found in path!", get_executable)
66
65
  else:
67
- path = download_binary(type)
66
+ path = download_binary(type.lower())
68
67
 
69
- return path
68
+ return str(path)
70
69
 
71
70
 
72
71
  def download_binary(type: str) -> str:
@@ -1,6 +1,6 @@
1
1
  Metadata-Version: 2.1
2
2
  Name: muxtools
3
- Version: 0.2.0
3
+ Version: 0.2.2
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.0"
4
+ version = "0.2.2"
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 = [
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