auto-editor 26.3.3__py3-none-any.whl → 27.0.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.
auto_editor/edit.py CHANGED
@@ -8,8 +8,8 @@ from os.path import splitext
8
8
  from subprocess import run
9
9
  from typing import TYPE_CHECKING, Any
10
10
 
11
- import av
12
- from av import AudioResampler, Codec
11
+ import bv
12
+ from bv import AudioResampler, Codec
13
13
 
14
14
  from auto_editor.ffwrapper import FileInfo, initFileInfo
15
15
  from auto_editor.lib.contracts import is_int, is_str
@@ -83,18 +83,10 @@ def set_video_codec(
83
83
  return ctr.default_vid
84
84
  return codec
85
85
 
86
- if codec == "copy":
87
- log.deprecated("The `copy` codec is deprecated. auto-editor always re-encodes")
88
- if src is None:
89
- log.error("No input to copy its codec from.")
90
- if not src.videos:
91
- log.error("Input file does not have a video stream to copy codec from.")
92
- codec = src.videos[0].codec
93
-
94
86
  if ctr.vcodecs is not None and codec not in ctr.vcodecs:
95
87
  try:
96
88
  cobj = Codec(codec, "w")
97
- except av.codec.codec.UnknownCodecError:
89
+ except bv.codec.codec.UnknownCodecError:
98
90
  log.error(f"Unknown encoder: {codec}")
99
91
  # Normalize encoder names
100
92
  if cobj.id not in (Codec(x, "w").id for x in ctr.vcodecs):
@@ -111,7 +103,7 @@ def set_audio_codec(
111
103
  codec = "aac"
112
104
  else:
113
105
  codec = src.audios[0].codec
114
- if av.Codec(codec, "w").audio_formats is None:
106
+ if bv.Codec(codec, "w").audio_formats is None:
115
107
  codec = "aac"
116
108
  if codec not in ctr.acodecs and ctr.default_aud != "none":
117
109
  codec = ctr.default_aud
@@ -119,18 +111,10 @@ def set_audio_codec(
119
111
  codec = "aac"
120
112
  return codec
121
113
 
122
- if codec == "copy":
123
- log.deprecated("The `copy` codec is deprecated. auto-editor always re-encodes")
124
- if src is None:
125
- log.error("No input to copy its codec from.")
126
- if not src.audios:
127
- log.error("Input file does not have an audio stream to copy codec from.")
128
- codec = src.audios[0].codec
129
-
130
114
  if ctr.acodecs is None or codec not in ctr.acodecs:
131
115
  try:
132
116
  cobj = Codec(codec, "w")
133
- except av.codec.codec.UnknownCodecError:
117
+ except bv.codec.codec.UnknownCodecError:
134
118
  log.error(f"Unknown encoder: {codec}")
135
119
  # Normalize encoder names
136
120
  if cobj.id not in (Codec(x, "w").id for x in ctr.acodecs):
@@ -178,6 +162,10 @@ def edit_media(paths: list[str], args: Args, log: Log) -> None:
178
162
  bar = initBar(args.progress)
179
163
  tl = None
180
164
 
165
+ if args.keep_tracks_separate:
166
+ log.deprecated("--keep-tracks-separate is deprecated.")
167
+ args.keep_tracks_separate = False
168
+
181
169
  if paths:
182
170
  path_ext = splitext(paths[0])[1].lower()
183
171
  if path_ext == ".xml":
@@ -289,9 +277,6 @@ def edit_media(paths: list[str], args: Args, log: Log) -> None:
289
277
  args.video_codec = set_video_codec(args.video_codec, src, out_ext, ctr, log)
290
278
  args.audio_codec = set_audio_codec(args.audio_codec, src, out_ext, ctr, log)
291
279
 
292
- if args.keep_tracks_separate and ctr.max_audios == 1:
293
- log.warning(f"'{out_ext}' container doesn't support multiple audio tracks.")
294
-
295
280
  def make_media(tl: v3, output_path: str) -> None:
296
281
  assert src is not None
297
282
 
@@ -307,38 +292,38 @@ def edit_media(paths: list[str], args: Args, log: Log) -> None:
307
292
  if mov_flags:
308
293
  options["movflags"] = "+".join(mov_flags)
309
294
 
310
- output = av.open(output_path, "w", container_options=options)
295
+ output = bv.open(output_path, "w", container_options=options)
311
296
 
312
- if ctr.default_sub != "none" and not args.sn:
313
- sub_paths = make_new_subtitles(tl, log)
297
+ # Setup video
298
+ if ctr.default_vid != "none" and tl.v:
299
+ vframes = render_av(output, tl, args, log)
300
+ output_stream: bv.VideoStream | None
301
+ output_stream = next(vframes) # type: ignore
314
302
  else:
315
- sub_paths = []
303
+ output_stream, vframes = None, iter([])
316
304
 
305
+ # Setup audio
317
306
  if ctr.default_aud != "none":
318
307
  ensure = Ensure(bar, samplerate, log)
319
308
  audio_paths = make_new_audio(tl, ctr, ensure, args, bar, log)
320
309
  else:
321
310
  audio_paths = []
322
311
 
323
- # Setup video
324
- if ctr.default_vid != "none" and tl.v:
325
- vframes = render_av(output, tl, args, log)
326
- output_stream = next(vframes)
327
- else:
328
- output_stream, vframes = None, iter([])
312
+ if len(audio_paths) > 1 and ctr.max_audios == 1:
313
+ log.warning("Dropping extra audio streams (container only allows one)")
314
+ audio_paths = audio_paths[0:1]
329
315
 
330
- # Setup audio
331
316
  if audio_paths:
332
317
  try:
333
- audio_encoder = av.Codec(args.audio_codec, "w")
334
- except av.FFmpegError as e:
318
+ audio_encoder = bv.Codec(args.audio_codec, "w")
319
+ except bv.FFmpegError as e:
335
320
  log.error(e)
336
321
  if audio_encoder.audio_formats is None:
337
322
  log.error(f"{args.audio_codec}: No known audio formats avail.")
338
323
  audio_format = audio_encoder.audio_formats[0]
339
324
  resampler = AudioResampler(format=audio_format, layout="stereo", rate=tl.sr)
340
325
 
341
- audio_streams: list[av.AudioStream] = []
326
+ audio_streams: list[bv.AudioStream] = []
342
327
  audio_inputs = []
343
328
  audio_gen_frames = []
344
329
  for i, audio_path in enumerate(audio_paths):
@@ -348,7 +333,7 @@ def edit_media(paths: list[str], args: Args, log: Log) -> None:
348
333
  rate=tl.sr,
349
334
  time_base=Fraction(1, tl.sr),
350
335
  )
351
- if not isinstance(audio_stream, av.AudioStream):
336
+ if not isinstance(audio_stream, bv.AudioStream):
352
337
  log.error(f"Not a known audio codec: {args.audio_codec}")
353
338
 
354
339
  if args.audio_bitrate != "auto":
@@ -360,17 +345,22 @@ def edit_media(paths: list[str], args: Args, log: Log) -> None:
360
345
  audio_stream.metadata["language"] = src.audios[i].lang # type: ignore
361
346
 
362
347
  audio_streams.append(audio_stream)
363
- audio_input = av.open(audio_path)
348
+ audio_input = bv.open(audio_path)
364
349
  audio_inputs.append(audio_input)
365
350
  audio_gen_frames.append(audio_input.decode(audio=0))
366
351
 
367
352
  # Setup subtitles
353
+ if ctr.default_sub != "none" and not args.sn:
354
+ sub_paths = make_new_subtitles(tl, log)
355
+ else:
356
+ sub_paths = []
357
+
368
358
  subtitle_streams = []
369
359
  subtitle_inputs = []
370
360
  sub_gen_frames = []
371
361
 
372
362
  for i, sub_path in enumerate(sub_paths):
373
- subtitle_input = av.open(sub_path)
363
+ subtitle_input = bv.open(sub_path)
374
364
  subtitle_inputs.append(subtitle_input)
375
365
  subtitle_stream = output.add_stream_from_template(
376
366
  subtitle_input.streams.subtitles[0]
@@ -499,14 +489,14 @@ def edit_media(paths: list[str], args: Args, log: Log) -> None:
499
489
  output.mux(item.stream.encode(item.frame))
500
490
  elif frame_type == "subtitle":
501
491
  output.mux(item.frame)
502
- except av.error.ExternalError:
492
+ except bv.error.ExternalError:
503
493
  log.error(
504
494
  f"Generic error for encoder: {item.stream.name}\n"
505
495
  f"at {item.index} time_base\nPerhaps video quality settings are too low?"
506
496
  )
507
- except av.FileNotFoundError:
497
+ except bv.FileNotFoundError:
508
498
  log.error(f"File not found: {output_path}")
509
- except av.FFmpegError as e:
499
+ except bv.FFmpegError as e:
510
500
  log.error(e)
511
501
 
512
502
  if bar_index:
auto_editor/ffwrapper.py CHANGED
@@ -4,14 +4,14 @@ from dataclasses import dataclass
4
4
  from fractions import Fraction
5
5
  from pathlib import Path
6
6
 
7
- import av
7
+ import bv
8
8
 
9
9
  from auto_editor.utils.log import Log
10
10
 
11
11
 
12
12
  def mux(input: Path, output: Path, stream: int) -> None:
13
- input_container = av.open(input, "r")
14
- output_container = av.open(output, "w")
13
+ input_container = bv.open(input, "r")
14
+ output_container = bv.open(output, "w")
15
15
 
16
16
  input_audio_stream = input_container.streams.audio[stream]
17
17
  output_audio_stream = output_container.add_stream("pcm_s16le")
@@ -92,12 +92,12 @@ class FileInfo:
92
92
 
93
93
  def initFileInfo(path: str, log: Log) -> FileInfo:
94
94
  try:
95
- cont = av.open(path, "r")
96
- except av.error.FileNotFoundError:
95
+ cont = bv.open(path, "r")
96
+ except bv.error.FileNotFoundError:
97
97
  log.error(f"Input file doesn't exist: {path}")
98
- except av.error.IsADirectoryError:
98
+ except bv.error.IsADirectoryError:
99
99
  log.error(f"Expected a media file, but got a directory: {path}")
100
- except av.error.InvalidDataError:
100
+ except bv.error.InvalidDataError:
101
101
  log.error(f"Invalid data when processing: {path}")
102
102
 
103
103
  videos: tuple[VideoStream, ...] = ()
@@ -126,7 +126,7 @@ def initFileInfo(path: str, log: Log) -> FileInfo:
126
126
  VideoStream(
127
127
  v.width,
128
128
  v.height,
129
- v.name,
129
+ v.codec.canonical_name,
130
130
  fps,
131
131
  vdur,
132
132
  sar,
@@ -167,7 +167,7 @@ def initFileInfo(path: str, log: Log) -> FileInfo:
167
167
 
168
168
  desc = cont.metadata.get("description", None)
169
169
  bitrate = 0 if cont.bit_rate is None else cont.bit_rate
170
- dur = 0 if cont.duration is None else cont.duration / av.time_base
170
+ dur = 0 if cont.duration is None else cont.duration / bv.time_base
171
171
 
172
172
  cont.close()
173
173
 
@@ -7,7 +7,7 @@ from fractions import Fraction
7
7
  from typing import Any
8
8
 
9
9
  from auto_editor.ffwrapper import FileInfo, initFileInfo
10
- from auto_editor.lang.json import Lexer, Parser, dump
10
+ from auto_editor.json import dump, load
11
11
  from auto_editor.lib.err import MyError
12
12
  from auto_editor.timeline import (
13
13
  ASpace,
@@ -221,7 +221,7 @@ def read_v1(tl: Any, log: Log) -> v3:
221
221
  def read_json(path: str, log: Log) -> v3:
222
222
  with open(path, encoding="utf-8", errors="ignore") as f:
223
223
  try:
224
- tl = Parser(Lexer(path, f)).expr()
224
+ tl = load(path, f)
225
225
  except MyError as e:
226
226
  log.error(e)
227
227
 
@@ -11,29 +11,10 @@ if TYPE_CHECKING:
11
11
 
12
12
  from _typeshed import SupportsWrite
13
13
 
14
-
15
- class Token:
16
- __slots__ = ("type", "value")
17
-
18
- def __init__(self, type: int, value: object):
19
- self.type = type
20
- self.value = value
21
-
22
- def __str__(self) -> str:
23
- return f"{self.type=} {self.value=}"
24
-
25
- __repr__ = __str__
14
+ Token = tuple[int, object]
26
15
 
27
16
 
28
17
  EOF, LCUR, RCUR, LBRAC, RBRAC, COL, COMMA, STR, VAL = range(9)
29
- table = {
30
- "{": LCUR,
31
- "}": RCUR,
32
- "[": LBRAC,
33
- "]": RBRAC,
34
- ":": COL,
35
- ",": COMMA,
36
- }
37
18
  str_escape = {
38
19
  "\\": "\\",
39
20
  "/": "/",
@@ -88,6 +69,12 @@ class Lexer:
88
69
  self.char = self.text[self.pos]
89
70
  self.column += 1
90
71
 
72
+ def rewind(self) -> None:
73
+ self.pos = 0
74
+ self.lineno = 1
75
+ self.column = 1
76
+ self.char = self.text[self.pos] if self.text else None
77
+
91
78
  def peek(self) -> str | None:
92
79
  peek_pos = self.pos + 1
93
80
  return None if peek_pos > len(self.text) - 1 else self.text[peek_pos]
@@ -142,7 +129,7 @@ class Lexer:
142
129
  result = buf.getvalue()
143
130
 
144
131
  try:
145
- return Token(VAL, float(result) if has_dot else int(result))
132
+ return (VAL, float(result) if has_dot else int(result))
146
133
  except ValueError:
147
134
  self.error(f"`{result}` is not a valid JSON Number")
148
135
 
@@ -158,7 +145,7 @@ class Lexer:
158
145
 
159
146
  if self.char == '"':
160
147
  self.advance()
161
- return Token(STR, self.string())
148
+ return (STR, self.string())
162
149
 
163
150
  if self.char == "-":
164
151
  _peek = self.peek()
@@ -168,10 +155,11 @@ class Lexer:
168
155
  if self.char in "0123456789.":
169
156
  return self.number()
170
157
 
158
+ table = {"{": LCUR, "}": RCUR, "[": LBRAC, "]": RBRAC, ":": COL, ",": COMMA}
171
159
  if self.char in table:
172
160
  key = table[self.char]
173
161
  self.advance()
174
- return Token(key, None)
162
+ return (key, None)
175
163
 
176
164
  keyword = ""
177
165
  for i in range(5): # Longest valid keyword length
@@ -181,14 +169,14 @@ class Lexer:
181
169
  self.advance()
182
170
 
183
171
  if keyword == "true":
184
- return Token(VAL, True)
172
+ return (VAL, True)
185
173
  if keyword == "false":
186
- return Token(VAL, False)
174
+ return (VAL, False)
187
175
  if keyword == "null":
188
- return Token(VAL, None)
176
+ return (VAL, None)
189
177
 
190
178
  self.error(f"Invalid keyword: `{keyword}`")
191
- return Token(EOF, None)
179
+ return (EOF, None)
192
180
 
193
181
 
194
182
  class Parser:
@@ -204,49 +192,49 @@ class Parser:
204
192
  def expr(self) -> Any:
205
193
  self.current_token
206
194
 
207
- if self.current_token.type in {STR, VAL}:
208
- val = self.current_token.value
195
+ if self.current_token[0] in {STR, VAL}:
196
+ val = self.current_token[1]
209
197
  self.eat()
210
198
  return val
211
199
 
212
- if self.current_token.type == LCUR:
200
+ if self.current_token[0] == LCUR:
213
201
  self.eat()
214
202
 
215
203
  my_dic = {}
216
- while self.current_token.type != RCUR:
217
- if self.current_token.type != STR:
218
- if self.current_token.type in {LBRAC, VAL}:
204
+ while self.current_token[0] != RCUR:
205
+ if self.current_token[0] != STR:
206
+ if self.current_token[0] in {LBRAC, VAL}:
219
207
  self.lexer.error("JSON Objects only allow strings as keys")
220
208
  self.lexer.error("Expected closing `}`")
221
- key = self.current_token.value
209
+ key = self.current_token[1]
222
210
  if key in my_dic:
223
211
  self.lexer.error(f"Object has repeated key `{key}`")
224
212
  self.eat()
225
- if self.current_token.type != COL:
213
+ if self.current_token[0] != COL:
226
214
  self.lexer.error("Expected `:`")
227
215
  self.eat()
228
216
 
229
217
  my_dic[key] = self.expr()
230
- if self.current_token.type != RCUR:
231
- if self.current_token.type != COMMA:
218
+ if self.current_token[0] != RCUR:
219
+ if self.current_token[0] != COMMA:
232
220
  self.lexer.error("Expected `,` between Object entries")
233
221
  self.eat()
234
- if self.current_token.type == RCUR:
222
+ if self.current_token[0] == RCUR:
235
223
  self.lexer.error("Trailing `,` in Object")
236
224
 
237
225
  self.eat()
238
226
  return my_dic
239
227
 
240
- if self.current_token.type == LBRAC:
228
+ if self.current_token[0] == LBRAC:
241
229
  self.eat()
242
230
  my_arr = []
243
- while self.current_token.type != RBRAC:
231
+ while self.current_token[0] != RBRAC:
244
232
  my_arr.append(self.expr())
245
- if self.current_token.type != RBRAC:
246
- if self.current_token.type != COMMA:
233
+ if self.current_token[0] != RBRAC:
234
+ if self.current_token[0] != COMMA:
247
235
  self.lexer.error("Expected `,` between array entries")
248
236
  self.eat()
249
- if self.current_token.type == RBRAC:
237
+ if self.current_token[0] == RBRAC:
250
238
  self.lexer.error("Trailing `,` in array")
251
239
  self.eat()
252
240
  return my_arr
@@ -254,6 +242,14 @@ class Parser:
254
242
  raise MyError(f"Unknown token: {self.current_token}")
255
243
 
256
244
 
245
+ def load(path: str, f: str | bytes | TextIOWrapper) -> dict[str, object]:
246
+ lexer = Lexer(path, f)
247
+ if lexer.get_next_token()[0] != LCUR:
248
+ raise MyError("Expected JSON Object")
249
+ lexer.rewind()
250
+ return Parser(lexer).expr()
251
+
252
+
257
253
  def dump(
258
254
  data: object, file: SupportsWrite[str], indent: int | None = None, level: int = 0
259
255
  ) -> None:
auto_editor/lang/palet.py CHANGED
@@ -646,8 +646,8 @@ stack_trace_manager = StackTraceManager()
646
646
 
647
647
 
648
648
  def my_eval(env: Env, node: object) -> Any:
649
- def make_trace(sym: Sym) -> str:
650
- return f" at {sym.val} ({sym.lineno}:{sym.column})"
649
+ def make_trace(sym: object) -> str:
650
+ return f" at {sym.val} ({sym.lineno}:{sym.column})" if type(sym) is Sym else ""
651
651
 
652
652
  if type(node) is Sym:
653
653
  val = env.get(node.val)
@@ -2,6 +2,8 @@ from __future__ import annotations
2
2
 
3
3
  from typing import TYPE_CHECKING
4
4
 
5
+ import bv
6
+
5
7
  from auto_editor.analyze import mut_remove_large, mut_remove_small
6
8
  from auto_editor.lib.contracts import *
7
9
  from auto_editor.lib.data_structs import *
@@ -749,6 +751,13 @@ def make_standard_env() -> dict[str, Any]:
749
751
  raise MyError("@r: attribute must be an identifier")
750
752
 
751
753
  base = my_eval(env, node[1])
754
+
755
+ if hasattr(base, "__pyx_vtable__"):
756
+ try:
757
+ return getattr(base, node[2].val)
758
+ except AttributeError as e:
759
+ raise MyError(e)
760
+
752
761
  if type(base) is PaletClass:
753
762
  if type(name := node[2]) is not Sym:
754
763
  raise MyError("@r: class attribute must be an identifier")
@@ -1171,6 +1180,9 @@ def make_standard_env() -> dict[str, Any]:
1171
1180
  "string->vector", lambda s: [Char(c) for c in s], (1, 1), is_str
1172
1181
  ),
1173
1182
  "range->vector": Proc("range->vector", list, (1, 1), is_range),
1183
+ # av
1184
+ "encoder": Proc("encoder", lambda x: bv.Codec(x, "w"), (1, 1), is_str),
1185
+ "decoder": Proc("decoder", lambda x: bv.Codec(x), (1, 1), is_str),
1174
1186
  # reflexion
1175
1187
  "var-exists?": Proc("var-exists?", lambda sym: sym.val in env, (1, 1), is_symbol),
1176
1188
  "rename": Syntax(syn_rename),
auto_editor/output.py CHANGED
@@ -3,8 +3,8 @@ from __future__ import annotations
3
3
  import os.path
4
4
  from dataclasses import dataclass, field
5
5
 
6
- import av
7
- from av.audio.resampler import AudioResampler
6
+ import bv
7
+ from bv.audio.resampler import AudioResampler
8
8
 
9
9
  from auto_editor.ffwrapper import FileInfo
10
10
  from auto_editor.utils.bar import Bar
@@ -53,8 +53,8 @@ class Ensure:
53
53
  bar = self._bar
54
54
  self.log.debug(f"Making external audio: {out_path}")
55
55
 
56
- in_container = av.open(src.path, "r")
57
- out_container = av.open(
56
+ in_container = bv.open(src.path, "r")
57
+ out_container = bv.open(
58
58
  out_path, "w", format="wav", options={"rf64": "always"}
59
59
  )
60
60
  astream = in_container.streams.audio[stream]
@@ -4,12 +4,12 @@ import io
4
4
  from pathlib import Path
5
5
  from typing import TYPE_CHECKING
6
6
 
7
- import av
7
+ import bv
8
8
  import numpy as np
9
- from av.filter.loudnorm import stats
9
+ from bv.filter.loudnorm import stats
10
10
 
11
11
  from auto_editor.ffwrapper import FileInfo
12
- from auto_editor.lang.json import Lexer, Parser
12
+ from auto_editor.json import load
13
13
  from auto_editor.lang.palet import env
14
14
  from auto_editor.lib.contracts import andc, between_c, is_int_or_float
15
15
  from auto_editor.lib.err import MyError
@@ -61,12 +61,14 @@ def parse_norm(norm: str, log: Log) -> dict | None:
61
61
 
62
62
  def parse_ebu_bytes(norm: dict, stat: bytes, log: Log) -> tuple[str, str]:
63
63
  try:
64
- parsed = Parser(Lexer("loudnorm", stat)).expr()
64
+ parsed = load("loudnorm", stat)
65
65
  except MyError:
66
66
  log.error(f"Invalid loudnorm stats.\n{stat!r}")
67
67
 
68
68
  for key in {"input_i", "input_tp", "input_lra", "input_thresh", "target_offset"}:
69
- val = float(parsed[key])
69
+ val_ = parsed[key]
70
+ assert isinstance(val_, int | float | str | bytes)
71
+ val = float(val_)
70
72
  if val == float("-inf"):
71
73
  parsed[key] = -99
72
74
  elif val == float("inf"):
@@ -97,14 +99,14 @@ def apply_audio_normalization(
97
99
  f"i={norm['i']}:lra={norm['lra']}:tp={norm['tp']}:offset={norm['gain']}"
98
100
  )
99
101
  log.debug(f"audio norm first pass: {first_pass}")
100
- with av.open(f"{pre_master}") as container:
102
+ with bv.open(f"{pre_master}") as container:
101
103
  stats_ = stats(first_pass, container.streams.audio[0])
102
104
 
103
105
  name, filter_args = parse_ebu_bytes(norm, stats_, log)
104
106
  else:
105
107
  assert "t" in norm
106
108
 
107
- def get_peak_level(frame: av.AudioFrame) -> float:
109
+ def get_peak_level(frame: bv.AudioFrame) -> float:
108
110
  # Calculate peak level in dB
109
111
  # Should be equivalent to: -af astats=measure_overall=Peak_level:measure_perchannel=0
110
112
  max_amplitude = np.abs(frame.to_ndarray()).max()
@@ -112,7 +114,7 @@ def apply_audio_normalization(
112
114
  return -20.0 * np.log10(max_amplitude)
113
115
  return -99.0
114
116
 
115
- with av.open(pre_master) as container:
117
+ with bv.open(pre_master) as container:
116
118
  max_peak_level = -99.0
117
119
  assert len(container.streams.video) == 0
118
120
  for frame in container.decode(audio=0):
@@ -124,13 +126,13 @@ def apply_audio_normalization(
124
126
  log.print(f"peak adjustment: {adjustment:.3f}dB")
125
127
  name, filter_args = "volume", f"{adjustment}"
126
128
 
127
- with av.open(pre_master) as container:
129
+ with bv.open(pre_master) as container:
128
130
  input_stream = container.streams.audio[0]
129
131
 
130
- output_file = av.open(path, mode="w")
132
+ output_file = bv.open(path, mode="w")
131
133
  output_stream = output_file.add_stream("pcm_s16le", rate=input_stream.rate)
132
134
 
133
- graph = av.filter.Graph()
135
+ graph = bv.filter.Graph()
134
136
  graph.link_nodes(
135
137
  graph.add_abuffer(template=input_stream),
136
138
  graph.add(name, filter_args),
@@ -141,9 +143,9 @@ def apply_audio_normalization(
141
143
  while True:
142
144
  try:
143
145
  aframe = graph.pull()
144
- assert isinstance(aframe, av.AudioFrame)
146
+ assert isinstance(aframe, bv.AudioFrame)
145
147
  output_file.mux(output_stream.encode(aframe))
146
- except (av.BlockingIOError, av.EOFError):
148
+ except (bv.BlockingIOError, bv.EOFError):
147
149
  break
148
150
 
149
151
  output_file.mux(output_stream.encode(None))
@@ -157,14 +159,14 @@ def process_audio_clip(
157
159
  write(input_buffer, sr, samp_list[samp_start:samp_end])
158
160
  input_buffer.seek(0)
159
161
 
160
- input_file = av.open(input_buffer, "r")
162
+ input_file = bv.open(input_buffer, "r")
161
163
  input_stream = input_file.streams.audio[0]
162
164
 
163
165
  output_bytes = io.BytesIO()
164
- output_file = av.open(output_bytes, mode="w", format="wav")
166
+ output_file = bv.open(output_bytes, mode="w", format="wav")
165
167
  output_stream = output_file.add_stream("pcm_s16le", rate=sr)
166
168
 
167
- graph = av.filter.Graph()
169
+ graph = bv.filter.Graph()
168
170
  args = [graph.add_abuffer(template=input_stream)]
169
171
 
170
172
  if clip.speed != 1:
@@ -194,9 +196,9 @@ def process_audio_clip(
194
196
  while True:
195
197
  try:
196
198
  aframe = graph.pull()
197
- assert isinstance(aframe, av.AudioFrame)
199
+ assert isinstance(aframe, bv.AudioFrame)
198
200
  output_file.mux(output_stream.encode(aframe))
199
- except (av.BlockingIOError, av.EOFError):
201
+ except (bv.BlockingIOError, bv.EOFError):
200
202
  break
201
203
 
202
204
  # Flush the stream
@@ -220,7 +222,7 @@ def mix_audio_files(sr: int, audio_paths: list[str], output_path: str) -> None:
220
222
 
221
223
  # First pass: determine the maximum length
222
224
  for path in audio_paths:
223
- container = av.open(path)
225
+ container = bv.open(path)
224
226
  stream = container.streams.audio[0]
225
227
 
226
228
  # Calculate duration in samples
@@ -232,10 +234,10 @@ def mix_audio_files(sr: int, audio_paths: list[str], output_path: str) -> None:
232
234
 
233
235
  # Second pass: read and mix audio
234
236
  for path in audio_paths:
235
- container = av.open(path)
237
+ container = bv.open(path)
236
238
  stream = container.streams.audio[0]
237
239
 
238
- resampler = av.audio.resampler.AudioResampler(
240
+ resampler = bv.audio.resampler.AudioResampler(
239
241
  format="s16", layout="mono", rate=sr
240
242
  )
241
243
 
@@ -268,7 +270,7 @@ def mix_audio_files(sr: int, audio_paths: list[str], output_path: str) -> None:
268
270
  mixed_audio = mixed_audio * (32767 / max_val)
269
271
  mixed_audio = mixed_audio.astype(np.int16) # type: ignore
270
272
 
271
- output_container = av.open(output_path, mode="w")
273
+ output_container = bv.open(output_path, mode="w")
272
274
  output_stream = output_container.add_stream("pcm_s16le", rate=sr)
273
275
 
274
276
  chunk_size = sr # Process 1 second at a time
@@ -276,7 +278,7 @@ def mix_audio_files(sr: int, audio_paths: list[str], output_path: str) -> None:
276
278
  # Shape becomes (1, samples) for mono
277
279
  chunk = np.array([mixed_audio[i : i + chunk_size]])
278
280
 
279
- frame = av.AudioFrame.from_ndarray(chunk, format="s16", layout="mono")
281
+ frame = bv.AudioFrame.from_ndarray(chunk, format="s16", layout="mono")
280
282
  frame.rate = sr
281
283
  frame.pts = i # Set presentation timestamp
282
284
 
@@ -370,7 +372,7 @@ def make_new_audio(
370
372
  except PermissionError:
371
373
  pass
372
374
 
373
- if not (args.keep_tracks_separate and ctr.max_audios is None) and len(output) > 1:
375
+ if args.mix_audio_streams and len(output) > 1:
374
376
  new_a_file = f"{Path(temp, 'new_audio.wav')}"
375
377
  mix_audio_files(sr, output, new_a_file)
376
378
  return [new_a_file]