auto-editor 26.0.0__tar.gz → 26.0.1__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 (62) hide show
  1. {auto_editor-26.0.0 → auto_editor-26.0.1}/PKG-INFO +1 -2
  2. auto_editor-26.0.1/auto_editor/__init__.py +1 -0
  3. {auto_editor-26.0.0 → auto_editor-26.0.1}/auto_editor/__main__.py +5 -22
  4. {auto_editor-26.0.0 → auto_editor-26.0.1}/auto_editor/edit.py +11 -29
  5. {auto_editor-26.0.0 → auto_editor-26.0.1}/auto_editor/ffwrapper.py +20 -41
  6. {auto_editor-26.0.0 → auto_editor-26.0.1}/auto_editor/formats/fcp7.py +1 -1
  7. {auto_editor-26.0.0 → auto_editor-26.0.1}/auto_editor/lang/palet.py +3 -9
  8. {auto_editor-26.0.0 → auto_editor-26.0.1}/auto_editor/lang/stdenv.py +0 -7
  9. {auto_editor-26.0.0 → auto_editor-26.0.1}/auto_editor/output.py +4 -1
  10. {auto_editor-26.0.0 → auto_editor-26.0.1}/auto_editor/render/audio.py +87 -5
  11. {auto_editor-26.0.0 → auto_editor-26.0.1}/auto_editor/render/subtitle.py +6 -4
  12. {auto_editor-26.0.0 → auto_editor-26.0.1}/auto_editor/render/video.py +1 -2
  13. {auto_editor-26.0.0 → auto_editor-26.0.1}/auto_editor/subcommands/test.py +1 -1
  14. {auto_editor-26.0.0 → auto_editor-26.0.1}/auto_editor/timeline.py +2 -2
  15. {auto_editor-26.0.0 → auto_editor-26.0.1}/auto_editor/utils/cmdkw.py +5 -8
  16. {auto_editor-26.0.0 → auto_editor-26.0.1}/auto_editor/utils/container.py +2 -5
  17. {auto_editor-26.0.0 → auto_editor-26.0.1}/auto_editor/utils/func.py +1 -34
  18. {auto_editor-26.0.0 → auto_editor-26.0.1}/auto_editor/utils/types.py +2 -15
  19. {auto_editor-26.0.0 → auto_editor-26.0.1}/auto_editor.egg-info/PKG-INFO +1 -2
  20. {auto_editor-26.0.0 → auto_editor-26.0.1}/auto_editor.egg-info/SOURCES.txt +0 -1
  21. {auto_editor-26.0.0 → auto_editor-26.0.1}/auto_editor.egg-info/requires.txt +0 -1
  22. {auto_editor-26.0.0 → auto_editor-26.0.1}/pyproject.toml +0 -1
  23. auto_editor-26.0.0/auto_editor/__init__.py +0 -1
  24. auto_editor-26.0.0/auto_editor/utils/encoder.py +0 -135
  25. {auto_editor-26.0.0 → auto_editor-26.0.1}/LICENSE +0 -0
  26. {auto_editor-26.0.0 → auto_editor-26.0.1}/README.md +0 -0
  27. {auto_editor-26.0.0 → auto_editor-26.0.1}/auto_editor/analyze.py +0 -0
  28. {auto_editor-26.0.0 → auto_editor-26.0.1}/auto_editor/formats/__init__.py +0 -0
  29. {auto_editor-26.0.0 → auto_editor-26.0.1}/auto_editor/formats/fcp11.py +0 -0
  30. {auto_editor-26.0.0 → auto_editor-26.0.1}/auto_editor/formats/json.py +0 -0
  31. {auto_editor-26.0.0 → auto_editor-26.0.1}/auto_editor/formats/shotcut.py +0 -0
  32. {auto_editor-26.0.0 → auto_editor-26.0.1}/auto_editor/formats/utils.py +0 -0
  33. {auto_editor-26.0.0 → auto_editor-26.0.1}/auto_editor/help.py +0 -0
  34. {auto_editor-26.0.0 → auto_editor-26.0.1}/auto_editor/lang/__init__.py +0 -0
  35. {auto_editor-26.0.0 → auto_editor-26.0.1}/auto_editor/lang/json.py +0 -0
  36. {auto_editor-26.0.0 → auto_editor-26.0.1}/auto_editor/lang/libintrospection.py +0 -0
  37. {auto_editor-26.0.0 → auto_editor-26.0.1}/auto_editor/lang/libmath.py +0 -0
  38. {auto_editor-26.0.0 → auto_editor-26.0.1}/auto_editor/lib/__init__.py +0 -0
  39. {auto_editor-26.0.0 → auto_editor-26.0.1}/auto_editor/lib/contracts.py +0 -0
  40. {auto_editor-26.0.0 → auto_editor-26.0.1}/auto_editor/lib/data_structs.py +0 -0
  41. {auto_editor-26.0.0 → auto_editor-26.0.1}/auto_editor/lib/err.py +0 -0
  42. {auto_editor-26.0.0 → auto_editor-26.0.1}/auto_editor/make_layers.py +0 -0
  43. {auto_editor-26.0.0 → auto_editor-26.0.1}/auto_editor/preview.py +0 -0
  44. {auto_editor-26.0.0 → auto_editor-26.0.1}/auto_editor/render/__init__.py +0 -0
  45. {auto_editor-26.0.0 → auto_editor-26.0.1}/auto_editor/subcommands/__init__.py +0 -0
  46. {auto_editor-26.0.0 → auto_editor-26.0.1}/auto_editor/subcommands/desc.py +0 -0
  47. {auto_editor-26.0.0 → auto_editor-26.0.1}/auto_editor/subcommands/info.py +0 -0
  48. {auto_editor-26.0.0 → auto_editor-26.0.1}/auto_editor/subcommands/levels.py +0 -0
  49. {auto_editor-26.0.0 → auto_editor-26.0.1}/auto_editor/subcommands/palet.py +0 -0
  50. {auto_editor-26.0.0 → auto_editor-26.0.1}/auto_editor/subcommands/repl.py +0 -0
  51. {auto_editor-26.0.0 → auto_editor-26.0.1}/auto_editor/subcommands/subdump.py +0 -0
  52. {auto_editor-26.0.0 → auto_editor-26.0.1}/auto_editor/utils/__init__.py +0 -0
  53. {auto_editor-26.0.0 → auto_editor-26.0.1}/auto_editor/utils/bar.py +0 -0
  54. {auto_editor-26.0.0 → auto_editor-26.0.1}/auto_editor/utils/chunks.py +0 -0
  55. {auto_editor-26.0.0 → auto_editor-26.0.1}/auto_editor/utils/log.py +0 -0
  56. {auto_editor-26.0.0 → auto_editor-26.0.1}/auto_editor/vanparse.py +0 -0
  57. {auto_editor-26.0.0 → auto_editor-26.0.1}/auto_editor/wavfile.py +0 -0
  58. {auto_editor-26.0.0 → auto_editor-26.0.1}/auto_editor.egg-info/dependency_links.txt +0 -0
  59. {auto_editor-26.0.0 → auto_editor-26.0.1}/auto_editor.egg-info/entry_points.txt +0 -0
  60. {auto_editor-26.0.0 → auto_editor-26.0.1}/auto_editor.egg-info/top_level.txt +0 -0
  61. {auto_editor-26.0.0 → auto_editor-26.0.1}/docs/build.py +0 -0
  62. {auto_editor-26.0.0 → auto_editor-26.0.1}/setup.cfg +0 -0
@@ -1,6 +1,6 @@
1
1
  Metadata-Version: 2.1
2
2
  Name: auto-editor
3
- Version: 26.0.0
3
+ Version: 26.0.1
4
4
  Summary: Auto-Editor: Effort free video editing!
5
5
  Author-email: WyattBlue <wyattblue@auto-editor.com>
6
6
  License: Unlicense
@@ -13,7 +13,6 @@ Description-Content-Type: text/markdown
13
13
  License-File: LICENSE
14
14
  Requires-Dist: numpy<3.0,>=1.23.0
15
15
  Requires-Dist: pyav==13.1.*
16
- Requires-Dist: ae-ffmpeg==1.2.*
17
16
 
18
17
  <p align="center"><img src="https://auto-editor.com/img/auto-editor-banner.webp" title="Auto-Editor" width="700"></p>
19
18
 
@@ -0,0 +1 @@
1
+ __version__ = "26.0.1"
@@ -8,15 +8,15 @@ from subprocess import run
8
8
 
9
9
  import auto_editor
10
10
  from auto_editor.edit import edit_media
11
- from auto_editor.ffwrapper import FFmpeg, initFFmpeg
11
+ from auto_editor.ffwrapper import FFmpeg
12
12
  from auto_editor.utils.func import get_stdout
13
13
  from auto_editor.utils.log import Log
14
14
  from auto_editor.utils.types import (
15
15
  Args,
16
- color,
17
16
  frame_rate,
18
17
  margin,
19
18
  number,
19
+ parse_color,
20
20
  resolution,
21
21
  sample_rate,
22
22
  speed,
@@ -108,7 +108,7 @@ def main_options(parser: ArgumentParser) -> ArgumentParser:
108
108
  parser.add_argument(
109
109
  "--background",
110
110
  "-b",
111
- type=color,
111
+ type=parse_color,
112
112
  metavar="COLOR",
113
113
  help="Set the background as a solid RGB color",
114
114
  )
@@ -166,11 +166,6 @@ def main_options(parser: ArgumentParser) -> ArgumentParser:
166
166
  metavar="PATH",
167
167
  help="Set a custom path to the ffmpeg location",
168
168
  )
169
- parser.add_argument(
170
- "--my-ffmpeg",
171
- flag=True,
172
- help="Use the ffmpeg on your PATH instead of the one packaged",
173
- )
174
169
  parser.add_text("Display Options:")
175
170
  parser.add_argument(
176
171
  "--progress",
@@ -179,12 +174,6 @@ def main_options(parser: ArgumentParser) -> ArgumentParser:
179
174
  help="Set what type of progress bar to use",
180
175
  )
181
176
  parser.add_argument("--debug", flag=True, help="Show debugging messages and values")
182
- parser.add_argument(
183
- "--show-ffmpeg-commands", flag=True, help="Show ffmpeg commands"
184
- )
185
- parser.add_argument(
186
- "--show-ffmpeg-output", flag=True, help="Show ffmpeg stdout and stderr"
187
- )
188
177
  parser.add_argument("--quiet", "-q", flag=True, help="Display less output")
189
178
  parser.add_argument(
190
179
  "--preview",
@@ -285,7 +274,7 @@ def download_video(my_input: str, args: Args, ffmpeg: FFmpeg, log: Log) -> str:
285
274
 
286
275
  yt_dlp_path = args.yt_dlp_location
287
276
 
288
- cmd = ["--ffmpeg-location", ffmpeg.path]
277
+ cmd = ["--ffmpeg-location", ffmpeg.get_path("yt-dlp", log)]
289
278
 
290
279
  if download_format is not None:
291
280
  cmd.extend(["-f", download_format])
@@ -363,13 +352,7 @@ def main() -> None:
363
352
  is_machine = args.progress == "machine"
364
353
  log = Log(args.debug, args.quiet, args.temp_dir, is_machine, no_color)
365
354
 
366
- ffmpeg = initFFmpeg(
367
- log,
368
- args.ffmpeg_location,
369
- args.my_ffmpeg,
370
- args.show_ffmpeg_commands,
371
- args.show_ffmpeg_output,
372
- )
355
+ ffmpeg = FFmpeg(args.ffmpeg_location)
373
356
  paths = []
374
357
  for my_input in args.input:
375
358
  if my_input.startswith("http://") or my_input.startswith("https://"):
@@ -3,6 +3,7 @@ from __future__ import annotations
3
3
  import os
4
4
  import sys
5
5
  from fractions import Fraction
6
+ from os.path import splitext
6
7
  from subprocess import run
7
8
  from typing import Any
8
9
 
@@ -11,7 +12,7 @@ from av import AudioResampler
11
12
 
12
13
  from auto_editor.ffwrapper import FFmpeg, FileInfo, initFileInfo
13
14
  from auto_editor.lib.contracts import is_int, is_str
14
- from auto_editor.make_layers import make_timeline
15
+ from auto_editor.make_layers import clipify, make_av, make_timeline
15
16
  from auto_editor.output import Ensure, parse_bitrate
16
17
  from auto_editor.render.audio import make_new_audio
17
18
  from auto_editor.render.subtitle import make_new_subtitles
@@ -31,7 +32,7 @@ def set_output(
31
32
  if src is None:
32
33
  root, ext = "out", ".mp4"
33
34
  else:
34
- root, ext = os.path.splitext(str(src.path) if out is None else out)
35
+ root, ext = splitext(src.path if out is None else out)
35
36
  if ext == "":
36
37
  ext = src.path.suffix
37
38
 
@@ -164,7 +165,7 @@ def edit_media(paths: list[str], ffmpeg: FFmpeg, args: Args, log: Log) -> None:
164
165
  tl = None
165
166
 
166
167
  if paths:
167
- path_ext = os.path.splitext(paths[0])[1].lower()
168
+ path_ext = splitext(paths[0])[1].lower()
168
169
  if path_ext == ".xml":
169
170
  from auto_editor.formats.fcp7 import fcp7_read_xml
170
171
 
@@ -243,7 +244,7 @@ def edit_media(paths: list[str], ffmpeg: FFmpeg, args: Args, log: Log) -> None:
243
244
  from auto_editor.formats.fcp7 import fcp7_write_xml
244
245
 
245
246
  is_resolve = export.startswith("resolve")
246
- fcp7_write_xml(export_ops["name"], output, is_resolve, tl, log)
247
+ fcp7_write_xml(export_ops["name"], output, is_resolve, tl)
247
248
  return
248
249
 
249
250
  if export == "final-cut-pro":
@@ -267,7 +268,7 @@ def edit_media(paths: list[str], ffmpeg: FFmpeg, args: Args, log: Log) -> None:
267
268
  shotcut_write_mlt(output, tl)
268
269
  return
269
270
 
270
- out_ext = os.path.splitext(output)[1].replace(".", "")
271
+ out_ext = splitext(output)[1].replace(".", "")
271
272
 
272
273
  # Check if export options make sense.
273
274
  ctr = container_constructor(out_ext.lower())
@@ -293,27 +294,7 @@ def edit_media(paths: list[str], ffmpeg: FFmpeg, args: Args, log: Log) -> None:
293
294
 
294
295
  if ctr.default_aud != "none":
295
296
  ensure = Ensure(bar, samplerate, log)
296
- audio_paths = make_new_audio(tl, ensure, args, ffmpeg, bar, log)
297
- if (
298
- not (args.keep_tracks_separate and ctr.max_audios is None)
299
- and len(audio_paths) > 1
300
- ):
301
- # Merge all the audio a_tracks into one.
302
- new_a_file = os.path.join(log.temp, "new_audio.wav")
303
- new_cmd = []
304
- for path in audio_paths:
305
- new_cmd.extend(["-i", path])
306
- new_cmd.extend(
307
- [
308
- "-filter_complex",
309
- f"amix=inputs={len(audio_paths)}:duration=longest",
310
- "-ac",
311
- "2",
312
- new_a_file,
313
- ]
314
- )
315
- ffmpeg.run(new_cmd)
316
- audio_paths = [new_a_file]
297
+ audio_paths = make_new_audio(tl, ctr, ensure, args, ffmpeg, bar, log)
317
298
  else:
318
299
  audio_paths = []
319
300
 
@@ -430,14 +411,15 @@ def edit_media(paths: list[str], ffmpeg: FFmpeg, args: Args, log: Log) -> None:
430
411
  if tl.v1 is None:
431
412
  log.error("Timeline too complex to use clip-sequence export")
432
413
 
433
- from auto_editor.make_layers import clipify, make_av
434
- from auto_editor.utils.func import append_filename
435
-
436
414
  def pad_chunk(chunk: Chunk, total: int) -> Chunks:
437
415
  start = [] if chunk[0] == 0 else [(0, chunk[0], 99999.0)]
438
416
  end = [] if chunk[1] == total else [(chunk[1], total, 99999.0)]
439
417
  return start + [chunk] + end
440
418
 
419
+ def append_filename(path: str, val: str) -> str:
420
+ root, ext = splitext(path)
421
+ return root + val + ext
422
+
441
423
  total_frames = tl.v1.chunks[-1][1] - 1
442
424
  clip_num = 0
443
425
  for chunk in tl.v1.chunks:
@@ -1,61 +1,40 @@
1
1
  from __future__ import annotations
2
2
 
3
- import sys
4
3
  from dataclasses import dataclass
5
4
  from fractions import Fraction
6
5
  from pathlib import Path
7
6
  from shutil import which
8
- from subprocess import PIPE, Popen, run
9
- from typing import Any
7
+ from subprocess import PIPE, Popen
10
8
 
11
9
  import av
12
10
 
13
11
  from auto_editor.utils.log import Log
14
12
 
15
13
 
16
- def initFFmpeg(
17
- log: Log, ff_location: str | None, my_ffmpeg: bool, show_cmd: bool, debug: bool
18
- ) -> FFmpeg:
19
- if ff_location is not None:
20
- program = ff_location
21
- elif my_ffmpeg:
22
- program = "ffmpeg"
23
- else:
24
- try:
25
- import ae_ffmpeg
14
+ def _get_ffmpeg(reason: str, ffloc: str | None, log: Log) -> str:
15
+ program = "ffmpeg" if ffloc is None else ffloc
16
+ if (path := which(program)) is None:
17
+ log.error(f"{reason} needs ffmpeg cli but couldn't find ffmpeg on PATH.")
18
+ return path
26
19
 
27
- program = ae_ffmpeg.get_path()
28
- except ImportError:
29
- program = "ffmpeg"
30
20
 
31
- path: str | None = which(program)
32
- if path is None:
33
- log.error("Did not find ffmpeg on PATH.")
21
+ @dataclass(slots=True)
22
+ class FFmpeg:
23
+ ffmpeg_location: str | None
24
+ path: str | None = None
34
25
 
35
- return FFmpeg(log, path, show_cmd, debug)
26
+ def get_path(self, reason: str, log: Log) -> str:
27
+ if self.path is not None:
28
+ return self.path
36
29
 
30
+ self.path = _get_ffmpeg(reason, self.ffmpeg_location, log)
31
+ return self.path
37
32
 
38
- @dataclass(slots=True)
39
- class FFmpeg:
40
- log: Log
41
- path: str
42
- show_cmd: bool
43
- debug: bool
44
-
45
- def run(self, cmd: list[str]) -> None:
46
- cmd = [self.path, "-hide_banner", "-y"] + cmd
47
- if not self.debug:
48
- cmd.extend(["-nostats", "-loglevel", "error"])
49
- if self.show_cmd:
50
- sys.stderr.write(f"{' '.join(cmd)}\n\n")
51
- run(cmd)
52
-
53
- def Popen(
54
- self, cmd: list[str], stdin: Any = None, stdout: Any = PIPE, stderr: Any = None
55
- ) -> Popen:
56
- if self.show_cmd:
57
- sys.stderr.write(f"{self.path} {' '.join(cmd)}\n\n")
58
- return Popen([self.path] + cmd, stdin=stdin, stdout=stdout, stderr=stderr)
33
+ def Popen(self, reason: str, cmd: list[str], log: Log) -> Popen:
34
+ if self.path is None:
35
+ self.path = _get_ffmpeg(reason, self.ffmpeg_location, log)
36
+
37
+ return Popen([self.path] + cmd, stdout=PIPE, stderr=PIPE)
59
38
 
60
39
 
61
40
  def mux(input: Path, output: Path, stream: int) -> None:
@@ -485,7 +485,7 @@ def premiere_write_audio(audio: Element, make_filedef, src: FileInfo, tl: v3) ->
485
485
  audio.append(track)
486
486
 
487
487
 
488
- def fcp7_write_xml(name: str, output: str, resolve: bool, tl: v3, log: Log) -> None:
488
+ def fcp7_write_xml(name: str, output: str, resolve: bool, tl: v3) -> None:
489
489
  width, height = tl.res
490
490
  timebase, ntsc = set_tb_ntsc(tl.tb)
491
491
 
@@ -353,9 +353,7 @@ class Lexer:
353
353
  if is_method:
354
354
  from auto_editor.utils.cmdkw import parse_method
355
355
 
356
- return Token(
357
- M, parse_method(name, result, env), self.lineno, self.column
358
- )
356
+ return Token(M, parse_method(name, result), self.lineno, self.column)
359
357
 
360
358
  if self.char == ".": # handle `object.method` syntax
361
359
  self.advance()
@@ -635,6 +633,8 @@ def edit_subtitle(pattern, stream=0, **kwargs):
635
633
 
636
634
 
637
635
  class StackTraceManager:
636
+ __slots__ = ("stack",)
637
+
638
638
  def __init__(self) -> None:
639
639
  self.stack: list[Sym] = []
640
640
 
@@ -645,12 +645,6 @@ class StackTraceManager:
645
645
  if self.stack:
646
646
  self.stack.pop()
647
647
 
648
- def get_stacktrace(self) -> str:
649
- return "\n".join(
650
- f" at {sym.val} ({sym.lineno}:{sym.column})"
651
- for sym in reversed(self.stack)
652
- )
653
-
654
648
 
655
649
  stack_trace_manager = StackTraceManager()
656
650
 
@@ -14,7 +14,6 @@ if TYPE_CHECKING:
14
14
  from numpy.typing import NDArray
15
15
 
16
16
  Number = int | float | complex | Fraction
17
- Real = int | float | Fraction
18
17
  BoolList = NDArray[np.bool_]
19
18
  Node = tuple
20
19
 
@@ -831,12 +830,6 @@ def make_standard_env() -> dict[str, Any]:
831
830
  check_args("xor", vals, (2, None), (is_bool,))
832
831
  return reduce(lambda a, b: a ^ b, vals)
833
832
 
834
- def string_ref(s: str, ref: int) -> Char:
835
- try:
836
- return Char(s[ref])
837
- except IndexError:
838
- raise MyError(f"string index {ref} is out of range")
839
-
840
833
  def number_to_string(val: Number) -> str:
841
834
  if isinstance(val, complex):
842
835
  join = "" if val.imag < 0 else "+"
@@ -13,7 +13,10 @@ from auto_editor.utils.types import _split_num_str
13
13
 
14
14
 
15
15
  def parse_bitrate(input_: str, log: Log) -> int:
16
- val, unit = _split_num_str(input_)
16
+ try:
17
+ val, unit = _split_num_str(input_)
18
+ except Exception as e:
19
+ log.error(e)
17
20
 
18
21
  if unit.lower() == "k":
19
22
  return int(val * 1000)
@@ -3,7 +3,6 @@ from __future__ import annotations
3
3
  import io
4
4
  from pathlib import Path
5
5
  from platform import system
6
- from subprocess import PIPE
7
6
 
8
7
  import av
9
8
  import numpy as np
@@ -17,6 +16,7 @@ from auto_editor.output import Ensure
17
16
  from auto_editor.timeline import TlAudio, v3
18
17
  from auto_editor.utils.bar import Bar
19
18
  from auto_editor.utils.cmdkw import ParserError, parse_with_palet, pAttr, pAttrs
19
+ from auto_editor.utils.container import Container
20
20
  from auto_editor.utils.log import Log
21
21
  from auto_editor.utils.types import Args
22
22
  from auto_editor.wavfile import AudioData, read, write
@@ -122,8 +122,7 @@ def apply_audio_normalization(
122
122
  "null",
123
123
  file_null,
124
124
  ]
125
- process = ffmpeg.Popen(cmd, stdin=PIPE, stdout=PIPE, stderr=PIPE)
126
- stderr = process.communicate()[1]
125
+ stderr = ffmpeg.Popen("EBU", cmd, log).communicate()[1]
127
126
  name, filter_args = parse_ebu_bytes(norm, stderr, log)
128
127
  else:
129
128
  assert "t" in norm
@@ -238,12 +237,90 @@ def process_audio_clip(
238
237
  return read(output_bytes)[1]
239
238
 
240
239
 
240
+ def mix_audio_files(sr: int, audio_paths: list[str], output_path: str) -> None:
241
+ mixed_audio = None
242
+ max_length = 0
243
+
244
+ # First pass: determine the maximum length
245
+ for path in audio_paths:
246
+ container = av.open(path)
247
+ stream = container.streams.audio[0]
248
+
249
+ # Calculate duration in samples
250
+ assert stream.duration is not None
251
+ assert stream.time_base is not None
252
+ duration_samples = int(stream.duration * sr / stream.time_base.denominator)
253
+ max_length = max(max_length, duration_samples)
254
+ container.close()
255
+
256
+ # Second pass: read and mix audio
257
+ for path in audio_paths:
258
+ container = av.open(path)
259
+ stream = container.streams.audio[0]
260
+
261
+ resampler = av.audio.resampler.AudioResampler(
262
+ format="s16", layout="mono", rate=sr
263
+ )
264
+
265
+ audio_array: list[np.ndarray] = []
266
+ for frame in container.decode(audio=0):
267
+ frame.pts = None
268
+ resampled = resampler.resample(frame)[0]
269
+ audio_array.extend(resampled.to_ndarray().flatten())
270
+
271
+ # Pad or truncate to max_length
272
+ current_audio = np.array(audio_array[:max_length])
273
+ if len(current_audio) < max_length:
274
+ current_audio = np.pad(
275
+ current_audio, (0, max_length - len(current_audio)), "constant"
276
+ )
277
+
278
+ if mixed_audio is None:
279
+ mixed_audio = current_audio.astype(np.float32)
280
+ else:
281
+ mixed_audio += current_audio.astype(np.float32)
282
+
283
+ container.close()
284
+
285
+ if mixed_audio is None:
286
+ raise ValueError("mixed_audio is None")
287
+
288
+ # Normalize the mixed audio
289
+ max_val = np.max(np.abs(mixed_audio))
290
+ if max_val > 0:
291
+ mixed_audio = mixed_audio * (32767 / max_val)
292
+ mixed_audio = mixed_audio.astype(np.int16) # type: ignore
293
+
294
+ output_container = av.open(output_path, mode="w")
295
+ output_stream = output_container.add_stream("pcm_s16le", rate=sr)
296
+
297
+ chunk_size = sr # Process 1 second at a time
298
+ for i in range(0, len(mixed_audio), chunk_size):
299
+ # Shape becomes (1, samples) for mono
300
+ chunk = np.array([mixed_audio[i : i + chunk_size]])
301
+
302
+ frame = av.AudioFrame.from_ndarray(chunk, format="s16", layout="mono")
303
+ frame.rate = sr
304
+ frame.pts = i # Set presentation timestamp
305
+
306
+ output_container.mux(output_stream.encode(frame))
307
+
308
+ output_container.mux(output_stream.encode(None))
309
+ output_container.close()
310
+
311
+
241
312
  def make_new_audio(
242
- tl: v3, ensure: Ensure, args: Args, ffmpeg: FFmpeg, bar: Bar, log: Log
313
+ tl: v3,
314
+ ctr: Container,
315
+ ensure: Ensure,
316
+ args: Args,
317
+ ffmpeg: FFmpeg,
318
+ bar: Bar,
319
+ log: Log,
243
320
  ) -> list[str]:
244
321
  sr = tl.sr
245
322
  tb = tl.tb
246
- output = []
323
+ output: list[str] = []
247
324
  samples: dict[tuple[FileInfo, int], AudioData] = {}
248
325
 
249
326
  norm = parse_norm(args.audio_normalize, log)
@@ -321,4 +398,9 @@ def make_new_audio(
321
398
  Path(temp, "asdf.map").unlink(missing_ok=True)
322
399
  except PermissionError:
323
400
  pass
401
+
402
+ if not (args.keep_tracks_separate and ctr.max_audios is None) and len(output) > 1:
403
+ new_a_file = f"{Path(temp, 'new_audio.wav')}"
404
+ mix_audio_files(sr, output, new_a_file)
405
+ return [new_a_file]
324
406
  return output
@@ -157,7 +157,7 @@ def make_srt(input_: Input, stream: int) -> str:
157
157
  return output_bytes.getvalue()
158
158
 
159
159
 
160
- def _ensure(input_: Input, format: str, stream: int, log: Log) -> str:
160
+ def _ensure(input_: Input, format: str, stream: int) -> str:
161
161
  output_bytes = io.BytesIO()
162
162
  output = av.open(output_bytes, "w", format=format)
163
163
 
@@ -187,7 +187,9 @@ def make_new_subtitles(tl: v3, log: Log) -> list[str]:
187
187
  continue
188
188
 
189
189
  parser = SubtitleParser(tl.tb)
190
- if sub.codec in ("webvtt", "ass", "ssa"):
190
+ if sub.codec == "ssa":
191
+ format = "ass"
192
+ elif sub.codec in ("webvtt", "ass"):
191
193
  format = sub.codec
192
194
  else:
193
195
  log.error(f"Unknown subtitle codec: {sub.codec}")
@@ -195,8 +197,8 @@ def make_new_subtitles(tl: v3, log: Log) -> list[str]:
195
197
  if sub.codec == "mov_text":
196
198
  ret = make_srt(input_, s)
197
199
  else:
198
- ret = _ensure(input_, format, s, log)
199
- parser.parse(ret, sub.codec)
200
+ ret = _ensure(input_, format, s)
201
+ parser.parse(ret, format)
200
202
  parser.edit(tl.v1.chunks)
201
203
 
202
204
  new_path = os.path.join(log.temp, f"new{s}s.{sub.ext}")
@@ -8,7 +8,6 @@ import numpy as np
8
8
 
9
9
  from auto_editor.output import parse_bitrate
10
10
  from auto_editor.timeline import TlImage, TlRect, TlVideo
11
- from auto_editor.utils.types import color
12
11
 
13
12
  if TYPE_CHECKING:
14
13
  from collections.abc import Iterator
@@ -203,7 +202,7 @@ def render_av(
203
202
 
204
203
  bar.start(tl.end, "Creating new video")
205
204
 
206
- bg = color(args.background)
205
+ bg = args.background
207
206
  null_frame = make_solid(target_width, target_height, target_pix_fmt, bg)
208
207
  frame_index = -1
209
208
 
@@ -360,7 +360,7 @@ def main(sys_args: list[str] | None = None):
360
360
  """{"version": "1", "source": "example.mp4", "chunks": [ [0, 26, 1.0], [26, 34, 0] ]}"""
361
361
  )
362
362
 
363
- return run.main(["v1.json"], [])
363
+ return "v1.json", run.main(["v1.json"], [])
364
364
 
365
365
  def premiere_named_export():
366
366
  run.main(["example.mp4"], ["--export", 'premiere:name="Foo Bar"'])
@@ -6,7 +6,7 @@ from typing import TYPE_CHECKING
6
6
  from auto_editor.ffwrapper import initFileInfo, mux
7
7
  from auto_editor.lib.contracts import *
8
8
  from auto_editor.utils.cmdkw import Required, pAttr, pAttrs
9
- from auto_editor.utils.types import color, natural, number, threshold
9
+ from auto_editor.utils.types import natural, number, parse_color, threshold
10
10
 
11
11
  if TYPE_CHECKING:
12
12
  from collections.abc import Iterator
@@ -165,7 +165,7 @@ rect_builder = pAttrs(
165
165
  pAttr("y", Required, is_int, int),
166
166
  pAttr("width", Required, is_int, int),
167
167
  pAttr("height", Required, is_int, int),
168
- pAttr("fill", "#c4c4c4", is_str, color),
168
+ pAttr("fill", "#c4c4c4", is_str, parse_color),
169
169
  )
170
170
  visual_objects = {
171
171
  "rect": (TlRect, rect_builder),
@@ -35,11 +35,6 @@ class pAttrs:
35
35
  self.attrs = attrs
36
36
 
37
37
 
38
- def _norm_name(s: str) -> str:
39
- # Python does not allow - in variable names
40
- return s.replace("-", "_")
41
-
42
-
43
38
  class PLexer:
44
39
  __slots__ = ("text", "pos", "char")
45
40
 
@@ -101,6 +96,10 @@ def parse_with_palet(
101
96
  KEYWORD_SEP = "="
102
97
  kwargs: dict[str, Any] = {}
103
98
 
99
+ def _norm_name(s: str) -> str:
100
+ # Python does not allow - in variable names
101
+ return s.replace("-", "_")
102
+
104
103
  def go(text: str, c: Any) -> Any:
105
104
  try:
106
105
  env = _env if isinstance(_env, Env) else Env(_env)
@@ -174,9 +173,7 @@ def parse_with_palet(
174
173
  return kwargs
175
174
 
176
175
 
177
- def parse_method(
178
- name: str, text: str, env: Env
179
- ) -> tuple[str, list[Any], dict[str, Any]]:
176
+ def parse_method(name: str, text: str) -> tuple[str, list[Any], dict[str, Any]]:
180
177
  from auto_editor.lang.palet import Lexer, Parser
181
178
 
182
179
  # Positional Arguments
@@ -55,12 +55,9 @@ def codec_type(x: str) -> str:
55
55
  return "subtitle"
56
56
 
57
57
  try:
58
- return Codec(x, "r").type
58
+ return Codec(x, "w").type
59
59
  except Exception:
60
- try:
61
- return Codec(x, "w").type
62
- except Exception:
63
- return ""
60
+ return ""
64
61
 
65
62
 
66
63
  def container_constructor(ext: str) -> Container:
@@ -81,21 +81,10 @@ def mut_margin(arr: BoolList, start_m: int, end_m: int) -> None:
81
81
  arr[max(i + end_m, 0) : i] = False
82
82
 
83
83
 
84
- def merge(start_list: np.ndarray, end_list: np.ndarray) -> BoolList:
85
- result = np.zeros((len(start_list)), dtype=np.bool_)
86
-
87
- for i, item in enumerate(start_list):
88
- if item == True:
89
- where = np.where(end_list[i:])[0]
90
- if len(where) > 0:
91
- result[i : where[0]] = True
92
- return result
93
-
94
-
95
84
  def get_stdout(cmd: list[str]) -> str:
96
85
  from subprocess import DEVNULL, PIPE, Popen
97
86
 
98
- stdout, _ = Popen(cmd, stdin=DEVNULL, stdout=PIPE, stderr=PIPE).communicate()
87
+ stdout = Popen(cmd, stdin=DEVNULL, stdout=PIPE, stderr=PIPE).communicate()[0]
99
88
  return stdout.decode("utf-8", "replace")
100
89
 
101
90
 
@@ -116,25 +105,3 @@ def aspect_ratio(width: int, height: int) -> tuple[int, int]:
116
105
 
117
106
  c = gcd(width, height)
118
107
  return width // c, height // c
119
-
120
-
121
- def human_readable_time(time_in_secs: float) -> str:
122
- units = "seconds"
123
- if time_in_secs >= 3600:
124
- time_in_secs = round(time_in_secs / 3600, 1)
125
- if time_in_secs % 1 == 0:
126
- time_in_secs = round(time_in_secs)
127
- units = "hours"
128
- if time_in_secs >= 60:
129
- time_in_secs = round(time_in_secs / 60, 1)
130
- if time_in_secs >= 10 or time_in_secs % 1 == 0:
131
- time_in_secs = round(time_in_secs)
132
- units = "minutes"
133
- return f"{time_in_secs} {units}"
134
-
135
-
136
- def append_filename(path: str, val: str) -> str:
137
- from os.path import splitext
138
-
139
- root, ext = splitext(path)
140
- return root + val + ext
@@ -3,7 +3,6 @@ from __future__ import annotations
3
3
  import re
4
4
  from dataclasses import dataclass, field
5
5
  from fractions import Fraction
6
- from typing import Literal
7
6
 
8
7
 
9
8
  class CoerceError(Exception):
@@ -156,16 +155,7 @@ def speed_range(val: str) -> tuple[float, str, str]:
156
155
  return number(a[0]), a[1], a[2]
157
156
 
158
157
 
159
- Stream = int | Literal["all"]
160
-
161
-
162
- def stream(val: str) -> Stream:
163
- if val == "all" or val == "'all":
164
- return "all"
165
- return natural(val)
166
-
167
-
168
- def color(val: str) -> str:
158
+ def parse_color(val: str) -> str:
169
159
  """
170
160
  Convert a color str into an RGB tuple
171
161
 
@@ -219,7 +209,7 @@ class Args:
219
209
  frame_rate: Fraction | None = None
220
210
  sample_rate: int | None = None
221
211
  resolution: tuple[int, int] | None = None
222
- background: str = "#000"
212
+ background: str = "#000000"
223
213
  edit_based_on: str = "audio"
224
214
  keep_tracks_separate: bool = False
225
215
  audio_normalize: str = "#f"
@@ -228,13 +218,10 @@ class Args:
228
218
  no_open: bool = False
229
219
  temp_dir: str | None = None
230
220
  ffmpeg_location: str | None = None
231
- my_ffmpeg: bool = False
232
221
  progress: str = "modern"
233
222
  version: bool = False
234
223
  debug: bool = False
235
224
  config: bool = False
236
- show_ffmpeg_commands: bool = False
237
- show_ffmpeg_output: bool = False
238
225
  quiet: bool = False
239
226
  preview: bool = False
240
227
  no_cache: bool = False
@@ -1,6 +1,6 @@
1
1
  Metadata-Version: 2.1
2
2
  Name: auto-editor
3
- Version: 26.0.0
3
+ Version: 26.0.1
4
4
  Summary: Auto-Editor: Effort free video editing!
5
5
  Author-email: WyattBlue <wyattblue@auto-editor.com>
6
6
  License: Unlicense
@@ -13,7 +13,6 @@ Description-Content-Type: text/markdown
13
13
  License-File: LICENSE
14
14
  Requires-Dist: numpy<3.0,>=1.23.0
15
15
  Requires-Dist: pyav==13.1.*
16
- Requires-Dist: ae-ffmpeg==1.2.*
17
16
 
18
17
  <p align="center"><img src="https://auto-editor.com/img/auto-editor-banner.webp" title="Auto-Editor" width="700"></p>
19
18
 
@@ -52,7 +52,6 @@ auto_editor/utils/bar.py
52
52
  auto_editor/utils/chunks.py
53
53
  auto_editor/utils/cmdkw.py
54
54
  auto_editor/utils/container.py
55
- auto_editor/utils/encoder.py
56
55
  auto_editor/utils/func.py
57
56
  auto_editor/utils/log.py
58
57
  auto_editor/utils/types.py
@@ -1,3 +1,2 @@
1
1
  numpy<3.0,>=1.23.0
2
2
  pyav==13.1.*
3
- ae-ffmpeg==1.2.*
@@ -11,7 +11,6 @@ requires-python = ">=3.10,<3.14"
11
11
  dependencies = [
12
12
  "numpy>=1.23.0,<3.0",
13
13
  "pyav==13.1.*",
14
- "ae-ffmpeg==1.2.*",
15
14
  ]
16
15
  keywords = [
17
16
  "video", "audio", "media", "editor", "editing",
@@ -1 +0,0 @@
1
- __version__ = "26.0.0"
@@ -1,135 +0,0 @@
1
- encoders = {
2
- "libx264": (
3
- "yuv420p",
4
- "yuvj420p",
5
- "yuv422p",
6
- "yuvj422p",
7
- "yuv444p",
8
- "yuvj444p",
9
- "nv12",
10
- "nv16",
11
- "nv21",
12
- "yuv420p10le",
13
- "yuv422p10le",
14
- "yuv444p10le",
15
- "nv20le",
16
- "gray",
17
- "gray10le",
18
- ),
19
- "libx264rgb": ("bgr0", "bgr24", "rgb24"),
20
- "h264_videotoolbox": ("videotoolbox_vld", "nv12", "yuv420p"),
21
- "h264": ("videotoolbox_vld", "nv12", "yuv420p"),
22
- "libx265": (
23
- "yuv420p",
24
- "yuvj420p",
25
- "yuv422p",
26
- "yuvj422p",
27
- "yuv444p",
28
- "yuvj444p",
29
- "gbrp",
30
- "yuv420p10le",
31
- "yuv422p10le",
32
- "yuv444p10le",
33
- "gbrp10le",
34
- "yuv420p12le",
35
- "yuv422p12le",
36
- "yuv444p12le",
37
- "gbrp12le",
38
- "gray",
39
- "gray10le",
40
- "gray12le",
41
- ),
42
- "hevc_videotoolbox": ("videotoolbox_vld", "nv12", "yuv420p", "bgra", "p010le"),
43
- "hevc": (
44
- "yuv420p",
45
- "yuvj420p",
46
- "yuv422p",
47
- "yuvj422p",
48
- "yuv444p",
49
- "yuvj444p",
50
- "gbrp",
51
- "yuv420p10le",
52
- "yuv422p10le",
53
- "yuv444p10le",
54
- "gbrp10le",
55
- "yuv420p12le",
56
- "yuv422p12le",
57
- "yuv444p12le",
58
- "gbrp12le",
59
- "gray",
60
- "gray10le",
61
- "gray12le",
62
- ),
63
- "hevc_nvenc": (
64
- "yuv420p",
65
- "nv12",
66
- "p010le",
67
- "yuv444p",
68
- "p016le",
69
- "yuv444p16le",
70
- "bgr0",
71
- "rgb0",
72
- "gbrp",
73
- "gbrp16le",
74
- "cuda",
75
- "d3d11",
76
- ),
77
- "hevc_amf": ("yuv420p", "nv12", "d3d11", "dxva2_vld"),
78
- "h264_nvenc": (
79
- "yuv420p",
80
- "nv12",
81
- "p010le",
82
- "yuv444p",
83
- "p016le",
84
- "yuv444p16le",
85
- "bgr0",
86
- "rgb0",
87
- "gbrp",
88
- "gbrp16le",
89
- "cuda",
90
- "d3d11",
91
- ),
92
- "h264_amf": ("yuv420p", "nv12", "d3d11", "dxva2_vld"),
93
- "hevc_qsv": ("nv12", "p010le", "yuyv422", "y210le", "qsv", "bgra", "x2rgb10le"),
94
- "h264_qsv": ("nv12", "p010le", "qsv"),
95
- "vp9": (
96
- "yuv420p",
97
- "yuva420p",
98
- "yuv422p",
99
- "yuv440p",
100
- "yuv444p",
101
- "yuv420p10le",
102
- "yuv422p10le",
103
- "yuv440p10le",
104
- "yuv444p10le",
105
- "yuv420p12le",
106
- "yuv422p12le",
107
- "yuv440p12le",
108
- "yuv444p12le",
109
- "gbrp",
110
- "gbrp10le",
111
- "gbrp12le",
112
- ),
113
- "vp8": ("yuv420p", "yuva420p"),
114
- "prores": ("yuv422p10le", "yuv444p10le", "yuva444p10le"),
115
- "av1": (
116
- "yuv420p",
117
- "yuv422p",
118
- "yuv444p",
119
- "gbrp",
120
- "yuv420p10le",
121
- "yuv422p10le",
122
- "yuv444p10le",
123
- "yuv420p12le",
124
- "yuv422p12le",
125
- "yuv444p12le",
126
- "gbrp10le",
127
- "gbrp12le",
128
- "gray",
129
- "gray10le",
130
- "gray12le",
131
- ),
132
- "mpeg4": ("yuv420p"),
133
- "mpeg2video": ("yuv420p", "yuv422p"),
134
- "mjpeg": ("yuvj420p", "yuvj422p", "yuvj444p"),
135
- }
File without changes
File without changes
File without changes
File without changes