auto-editor 24.31.1__py3-none-any.whl → 25.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/__init__.py CHANGED
@@ -1,2 +1 @@
1
- __version__ = "24.31.1"
2
- version = "24w31a"
1
+ __version__ = "25.0.0"
auto_editor/__main__.py CHANGED
@@ -6,7 +6,6 @@ from os import environ
6
6
  import auto_editor
7
7
  from auto_editor.edit import edit_media
8
8
  from auto_editor.ffwrapper import FFmpeg
9
- from auto_editor.utils.func import setup_tempdir
10
9
  from auto_editor.utils.log import Log
11
10
  from auto_editor.utils.types import (
12
11
  Args,
@@ -140,10 +139,7 @@ def main_options(parser: ArgumentParser) -> ArgumentParser:
140
139
  )
141
140
  parser.add_text("Utility Options:")
142
141
  parser.add_argument(
143
- "--export",
144
- "-ex",
145
- metavar="EXPORT:ATTRS?",
146
- help="Choose the export mode",
142
+ "--export", "-ex", metavar="EXPORT:ATTRS?", help="Choose the export mode"
147
143
  )
148
144
  parser.add_argument(
149
145
  "--output-file",
@@ -153,15 +149,10 @@ def main_options(parser: ArgumentParser) -> ArgumentParser:
153
149
  help="Set the name/path of the new output file.",
154
150
  )
155
151
  parser.add_argument(
156
- "--player",
157
- "-p",
158
- metavar="CMD",
159
- help="Set player to open output media files",
152
+ "--player", "-p", metavar="CMD", help="Set player to open output media files"
160
153
  )
161
154
  parser.add_argument(
162
- "--no-open",
163
- flag=True,
164
- help="Do not open the output file after editing is done",
155
+ "--no-open", flag=True, help="Do not open the output file after editing is done"
165
156
  )
166
157
  parser.add_argument(
167
158
  "--temp-dir",
@@ -255,7 +246,7 @@ def main_options(parser: ArgumentParser) -> ArgumentParser:
255
246
  parser.add_argument(
256
247
  "--audio-normalize",
257
248
  metavar="NORM-TYPE",
258
- help="Apply audio rendering to all audio tracks. Applied right before rendering the output file.",
249
+ help="Apply audio rendering to all audio tracks. Applied right before rendering the output file",
259
250
  )
260
251
  parser.add_text("Miscellaneous:")
261
252
  parser.add_argument(
@@ -268,6 +259,9 @@ def main_options(parser: ArgumentParser) -> ArgumentParser:
268
259
  metavar="CMD",
269
260
  help="Add extra options for ffmpeg. Must be in quotes",
270
261
  )
262
+ parser.add_argument(
263
+ "--no-cache", flag=True, help="Don't look for or write a cache file"
264
+ )
271
265
  parser.add_argument("--version", "-V", flag=True, help="Display version and halt")
272
266
  return parser
273
267
 
@@ -303,7 +297,7 @@ def main() -> None:
303
297
  )
304
298
 
305
299
  if args.version:
306
- print(f"{auto_editor.version} ({auto_editor.__version__})")
300
+ print(auto_editor.__version__)
307
301
  return
308
302
 
309
303
  if args.debug and not args.input:
@@ -314,15 +308,14 @@ def main() -> None:
314
308
  print(f"OS: {plat.system()} {plat.release()} {plat.machine().lower()}")
315
309
  print(f"Python: {plat.python_version()}")
316
310
  print(f"PyAV: {av.__version__}")
317
- print(f"Auto-Editor: {auto_editor.version}")
311
+ print(f"Auto-Editor: {auto_editor.__version__}")
318
312
  return
319
313
 
320
314
  if not args.input:
321
315
  log.error("You need to give auto-editor an input file.")
322
316
 
323
- temp = setup_tempdir(args.temp_dir, log)
324
- log = Log(args.debug, args.quiet, temp, args.progress == "machine", no_color)
325
- log.debug(f"Temp Directory: {temp}")
317
+ is_machine = args.progress == "machine"
318
+ log = Log(args.debug, args.quiet, args.temp_dir, is_machine, no_color)
326
319
 
327
320
  ffmpeg = FFmpeg(
328
321
  args.ffmpeg_location,
@@ -333,7 +326,7 @@ def main() -> None:
333
326
  paths = valid_input(args.input, ffmpeg, args, log)
334
327
 
335
328
  try:
336
- edit_media(paths, ffmpeg, args, temp, log)
329
+ edit_media(paths, ffmpeg, args, log)
337
330
  except KeyboardInterrupt:
338
331
  log.error("Keyboard Interrupt")
339
332
  log.cleanup()
auto_editor/analyze.py CHANGED
@@ -5,6 +5,7 @@ import re
5
5
  from dataclasses import dataclass
6
6
  from fractions import Fraction
7
7
  from math import ceil
8
+ from tempfile import gettempdir
8
9
  from typing import TYPE_CHECKING
9
10
 
10
11
  import av
@@ -12,7 +13,7 @@ import numpy as np
12
13
  from av.audio.fifo import AudioFifo
13
14
  from av.subtitles.subtitle import AssSubtitle
14
15
 
15
- from auto_editor import version
16
+ from auto_editor import __version__
16
17
  from auto_editor.utils.subtitle_tools import convert_ass_to_text
17
18
 
18
19
  if TYPE_CHECKING:
@@ -27,16 +28,6 @@ if TYPE_CHECKING:
27
28
  from auto_editor.utils.log import Log
28
29
 
29
30
 
30
- @dataclass(slots=True)
31
- class FileSetup:
32
- src: FileInfo
33
- strict: bool
34
- tb: Fraction
35
- bar: Bar
36
- temp: str
37
- log: Log
38
-
39
-
40
31
  class LevelError(Exception):
41
32
  pass
42
33
 
@@ -88,7 +79,7 @@ def obj_tag(tag: str, tb: Fraction, obj: dict[str, Any]) -> str:
88
79
  return key
89
80
 
90
81
 
91
- def iter_audio(src, tb: Fraction, stream: int = 0) -> Iterator[float]:
82
+ def iter_audio(src, tb: Fraction, stream: int = 0) -> Iterator[np.float32]:
92
83
  fifo = AudioFifo()
93
84
  try:
94
85
  container = av.open(src.path, "r")
@@ -117,13 +108,13 @@ def iter_audio(src, tb: Fraction, stream: int = 0) -> Iterator[float]:
117
108
  audio_chunk = fifo.read(current_size)
118
109
  assert audio_chunk is not None
119
110
  arr = audio_chunk.to_ndarray().flatten()
120
- yield float(np.max(np.abs(arr)))
111
+ yield np.max(np.abs(arr))
121
112
 
122
113
  finally:
123
114
  container.close()
124
115
 
125
116
 
126
- def iter_motion(src, tb, stream: int, blur: int, width: int) -> Iterator[float]:
117
+ def iter_motion(src, tb, stream: int, blur: int, width: int) -> Iterator[np.float32]:
127
118
  container = av.open(src.path, "r")
128
119
 
129
120
  video = container.streams.video[stream]
@@ -155,11 +146,11 @@ def iter_motion(src, tb, stream: int, blur: int, width: int) -> Iterator[float]:
155
146
 
156
147
  current_frame = frame.to_ndarray()
157
148
  if prev_frame is None:
158
- value = 0.0
149
+ value = np.float32(0.0)
159
150
  else:
160
151
  # Use `int16` to avoid underflow with `uint8` datatype
161
152
  diff = np.abs(prev_frame.astype(np.int16) - current_frame.astype(np.int16))
162
- value = np.count_nonzero(diff) / total_pixels
153
+ value = np.float32(np.count_nonzero(diff) / total_pixels)
163
154
 
164
155
  for _ in range(index - prev_index):
165
156
  yield value
@@ -175,8 +166,9 @@ class Levels:
175
166
  src: FileInfo
176
167
  tb: Fraction
177
168
  bar: Bar
178
- temp: str
169
+ no_cache: bool
179
170
  log: Log
171
+ strict: bool
180
172
 
181
173
  @property
182
174
  def media_length(self) -> int:
@@ -210,9 +202,10 @@ class Levels:
210
202
  return np.zeros(self.media_length, dtype=np.bool_)
211
203
 
212
204
  def read_cache(self, tag: str, obj: dict[str, Any]) -> None | np.ndarray:
213
- workfile = os.path.join(
214
- os.path.dirname(self.temp), f"ae-{version}", "cache.npz"
215
- )
205
+ if self.no_cache:
206
+ return None
207
+
208
+ workfile = os.path.join(gettempdir(), f"ae-{__version__}", "cache.npz")
216
209
 
217
210
  try:
218
211
  npzfile = np.load(workfile, allow_pickle=False)
@@ -227,8 +220,11 @@ class Levels:
227
220
  self.log.debug("Using cache")
228
221
  return npzfile[key]
229
222
 
230
- def cache(self, tag: str, obj: dict[str, Any], arr: np.ndarray) -> np.ndarray:
231
- workdur = os.path.join(os.path.dirname(self.temp), f"ae-{version}")
223
+ def cache(self, arr: np.ndarray, tag: str, obj: dict[str, Any]) -> np.ndarray:
224
+ if self.no_cache:
225
+ return arr
226
+
227
+ workdur = os.path.join(gettempdir(), f"ae-{__version__}")
232
228
  if not os.path.exists(workdur):
233
229
  os.mkdir(workdur)
234
230
 
@@ -237,7 +233,7 @@ class Levels:
237
233
 
238
234
  return arr
239
235
 
240
- def audio(self, stream: int) -> NDArray[np.float64]:
236
+ def audio(self, stream: int) -> NDArray[np.float32]:
241
237
  if stream >= len(self.src.audios):
242
238
  raise LevelError(f"audio: audio stream '{stream}' does not exist.")
243
239
 
@@ -256,21 +252,21 @@ class Levels:
256
252
  bar = self.bar
257
253
  bar.start(inaccurate_dur, "Analyzing audio volume")
258
254
 
259
- result = np.zeros((inaccurate_dur), dtype=np.float64)
255
+ result = np.zeros((inaccurate_dur), dtype=np.float32)
260
256
  index = 0
261
257
  for value in iter_audio(self.src, self.tb, stream):
262
258
  if index > len(result) - 1:
263
259
  result = np.concatenate(
264
- (result, np.zeros((len(result)), dtype=np.float64))
260
+ (result, np.zeros((len(result)), dtype=np.float32))
265
261
  )
266
262
  result[index] = value
267
263
  bar.tick(index)
268
264
  index += 1
269
265
 
270
266
  bar.end()
271
- return self.cache("audio", {"stream": stream}, result[:index])
267
+ return self.cache(result[:index], "audio", {"stream": stream})
272
268
 
273
- def motion(self, stream: int, blur: int, width: int) -> NDArray[np.float64]:
269
+ def motion(self, stream: int, blur: int, width: int) -> NDArray[np.float32]:
274
270
  if stream >= len(self.src.videos):
275
271
  raise LevelError(f"motion: video stream '{stream}' does not exist.")
276
272
 
@@ -289,19 +285,19 @@ class Levels:
289
285
  bar = self.bar
290
286
  bar.start(inaccurate_dur, "Analyzing motion")
291
287
 
292
- result = np.zeros((inaccurate_dur), dtype=np.float64)
288
+ result = np.zeros((inaccurate_dur), dtype=np.float32)
293
289
  index = 0
294
290
  for value in iter_motion(self.src, self.tb, stream, blur, width):
295
291
  if index > len(result) - 1:
296
292
  result = np.concatenate(
297
- (result, np.zeros((len(result)), dtype=np.float64))
293
+ (result, np.zeros((len(result)), dtype=np.float32))
298
294
  )
299
295
  result[index] = value
300
296
  bar.tick(index)
301
297
  index += 1
302
298
 
303
299
  bar.end()
304
- return self.cache("motion", mobj, result[:index])
300
+ return self.cache(result[:index], "motion", mobj)
305
301
 
306
302
  def subtitle(
307
303
  self,
auto_editor/edit.py CHANGED
@@ -142,9 +142,7 @@ def parse_export(export: str, log: Log) -> dict[str, Any]:
142
142
  log.error(f"'{name}': Export must be [{', '.join([s for s in parsing.keys()])}]")
143
143
 
144
144
 
145
- def edit_media(
146
- paths: list[str], ffmpeg: FFmpeg, args: Args, temp: str, log: Log
147
- ) -> None:
145
+ def edit_media(paths: list[str], ffmpeg: FFmpeg, args: Args, log: Log) -> None:
148
146
  bar = Bar(args.progress)
149
147
  tl = None
150
148
 
@@ -203,7 +201,7 @@ def edit_media(
203
201
  samplerate = args.sample_rate
204
202
 
205
203
  if tl is None:
206
- tl = make_timeline(sources, args, samplerate, bar, temp, log)
204
+ tl = make_timeline(sources, args, samplerate, bar, log)
207
205
 
208
206
  if export["export"] == "timeline":
209
207
  from auto_editor.formats.json import make_json_timeline
@@ -214,7 +212,7 @@ def edit_media(
214
212
  if args.preview:
215
213
  from auto_editor.preview import preview
216
214
 
217
- preview(tl, temp, log)
215
+ preview(tl, log)
218
216
  return
219
217
 
220
218
  if export["export"] == "json":
@@ -263,22 +261,22 @@ def edit_media(
263
261
  sub_output = []
264
262
  apply_later = False
265
263
 
266
- ensure = Ensure(ffmpeg, bar, samplerate, temp, log)
264
+ ensure = Ensure(ffmpeg, bar, samplerate, log)
267
265
 
268
266
  if ctr.default_sub != "none" and not args.sn:
269
- sub_output = make_new_subtitles(tl, ensure, temp)
267
+ sub_output = make_new_subtitles(tl, ensure, log.temp)
270
268
 
271
269
  if ctr.default_aud != "none":
272
- audio_output = make_new_audio(tl, ensure, args, ffmpeg, bar, temp, log)
270
+ audio_output = make_new_audio(tl, ensure, args, ffmpeg, bar, log)
273
271
 
274
272
  if ctr.default_vid != "none":
275
273
  if tl.v:
276
- out_path, apply_later = render_av(ffmpeg, tl, args, bar, ctr, temp, log)
274
+ out_path, apply_later = render_av(ffmpeg, tl, args, bar, ctr, log)
277
275
  visual_output.append((True, out_path))
278
276
 
279
277
  for v, vid in enumerate(src.videos, start=1):
280
278
  if ctr.allow_image and vid.codec in ("png", "mjpeg", "webp"):
281
- out_path = os.path.join(temp, f"{v}.{vid.codec}")
279
+ out_path = os.path.join(log.temp, f"{v}.{vid.codec}")
282
280
  # fmt: off
283
281
  ffmpeg.run(["-i", f"{src.path}", "-map", "0:v", "-map", "-0:V",
284
282
  "-c", "copy", out_path])
@@ -297,7 +295,6 @@ def edit_media(
297
295
  tl.tb,
298
296
  args,
299
297
  src,
300
- temp,
301
298
  log,
302
299
  )
303
300
 
auto_editor/ffwrapper.py CHANGED
@@ -232,7 +232,7 @@ def initFileInfo(path: str, log: Log) -> FileInfo:
232
232
  vdur,
233
233
  sar,
234
234
  v.time_base,
235
- cc.pix_fmt,
235
+ getattr(v.format, "name", None),
236
236
  cc.color_range,
237
237
  cc.colorspace,
238
238
  cc.color_primaries,
auto_editor/lang/palet.py CHANGED
@@ -1487,12 +1487,12 @@ def edit_audio(
1487
1487
  mincut: int = 6,
1488
1488
  minclip: int = 3,
1489
1489
  ) -> np.ndarray:
1490
- if "@levels" not in env or "@filesetup" not in env:
1490
+ if "@levels" not in env:
1491
1491
  raise MyError("Can't use `audio` if there's no input media")
1492
1492
 
1493
1493
  levels = env["@levels"]
1494
- src = env["@filesetup"].src
1495
- strict = env["@filesetup"].strict
1494
+ src = levels.src
1495
+ strict = levels.strict
1496
1496
 
1497
1497
  stream_data: NDArray[np.bool_] | None = None
1498
1498
  if stream == Sym("all"):
@@ -1531,11 +1531,10 @@ def edit_motion(
1531
1531
  raise MyError("Can't use `motion` if there's no input media")
1532
1532
 
1533
1533
  levels = env["@levels"]
1534
- strict = env["@filesetup"].strict
1535
1534
  try:
1536
1535
  return levels.motion(stream, blur, width) >= threshold
1537
1536
  except LevelError as e:
1538
- return raise_(e) if strict else levels.all()
1537
+ return raise_(e) if levels.strict else levels.all()
1539
1538
 
1540
1539
 
1541
1540
  def edit_subtitle(pattern, stream=0, **kwargs):
@@ -1543,7 +1542,6 @@ def edit_subtitle(pattern, stream=0, **kwargs):
1543
1542
  raise MyError("Can't use `subtitle` if there's no input media")
1544
1543
 
1545
1544
  levels = env["@levels"]
1546
- strict = env["@filesetup"].strict
1547
1545
  if "ignore-case" not in kwargs:
1548
1546
  kwargs["ignore-case"] = False
1549
1547
  if "max-count" not in kwargs:
@@ -1553,7 +1551,7 @@ def edit_subtitle(pattern, stream=0, **kwargs):
1553
1551
  try:
1554
1552
  return levels.subtitle(pattern, stream, ignore_case, max_count)
1555
1553
  except LevelError as e:
1556
- return raise_(e) if strict else levels.all()
1554
+ return raise_(e) if levels.strict else levels.all()
1557
1555
 
1558
1556
 
1559
1557
  def my_eval(env: Env, node: object) -> Any:
@@ -1,15 +1,18 @@
1
1
  from __future__ import annotations
2
2
 
3
- from collections.abc import Callable
4
3
  from dataclasses import dataclass
5
4
  from fractions import Fraction
6
- from typing import Any
5
+ from typing import TYPE_CHECKING
7
6
 
8
7
  from numpy import float64
9
8
 
10
9
  from .data_structs import Sym, print_str
11
10
  from .err import MyError
12
11
 
12
+ if TYPE_CHECKING:
13
+ from collections.abc import Callable
14
+ from typing import Any
15
+
13
16
 
14
17
  @dataclass(slots=True)
15
18
  class Contract:
@@ -6,7 +6,7 @@ from typing import TYPE_CHECKING, NamedTuple
6
6
 
7
7
  import numpy as np
8
8
 
9
- from auto_editor.analyze import FileSetup, Levels
9
+ from auto_editor.analyze import Levels
10
10
  from auto_editor.ffwrapper import FileInfo
11
11
  from auto_editor.lang.palet import Lexer, Parser, env, interpret, is_boolarr
12
12
  from auto_editor.lib.data_structs import print_str
@@ -71,44 +71,6 @@ def make_av(src: FileInfo, all_clips: list[list[Clip]]) -> tuple[VSpace, ASpace]
71
71
  return vtl, atl
72
72
 
73
73
 
74
- def run_interpreter_for_edit_option(
75
- text: str, filesetup: FileSetup
76
- ) -> NDArray[np.bool_]:
77
- src = filesetup.src
78
- tb = filesetup.tb
79
- bar = filesetup.bar
80
- temp = filesetup.temp
81
- log = filesetup.log
82
-
83
- try:
84
- parser = Parser(Lexer("`--edit`", text))
85
- if log.is_debug:
86
- log.debug(f"edit: {parser}")
87
-
88
- env["timebase"] = tb
89
- env["@levels"] = Levels(src, tb, bar, temp, log)
90
- env["@filesetup"] = filesetup
91
-
92
- results = interpret(env, parser)
93
-
94
- if len(results) == 0:
95
- raise MyError("Expression in --edit must return a bool-array, got nothing")
96
-
97
- result = results[-1]
98
- if callable(result):
99
- result = result()
100
-
101
- if not is_boolarr(result):
102
- raise MyError(
103
- f"Expression in --edit must return a bool-array, got {print_str(result)}"
104
- )
105
- except MyError as e:
106
- log.error(e)
107
-
108
- assert isinstance(result, np.ndarray)
109
- return result
110
-
111
-
112
74
  def make_sane_timebase(fps: Fraction) -> Fraction:
113
75
  tb = round(fps, 2)
114
76
 
@@ -140,7 +102,6 @@ def make_timeline(
140
102
  args: Args,
141
103
  sr: int,
142
104
  bar: Bar,
143
- temp: str,
144
105
  log: Log,
145
106
  ) -> v3:
146
107
  inp = None if not sources else sources[0]
@@ -159,20 +120,40 @@ def make_timeline(
159
120
  except CoerceError as e:
160
121
  log.error(e)
161
122
 
162
- method = args.edit_based_on
163
-
164
123
  has_loud = np.array([], dtype=np.bool_)
165
124
  src_index = np.array([], dtype=np.int32)
166
125
  concat = np.concatenate
167
126
 
168
127
  for i, src in enumerate(sources):
169
- filesetup = FileSetup(src, len(sources) < 2, tb, bar, temp, log)
128
+ try:
129
+ parser = Parser(Lexer("`--edit`", args.edit_based_on))
130
+ if log.is_debug:
131
+ log.debug(f"edit: {parser}")
132
+
133
+ env["timebase"] = tb
134
+ env["@levels"] = Levels(src, tb, bar, args.no_cache, log, len(sources) < 2)
135
+
136
+ results = interpret(env, parser)
137
+
138
+ if len(results) == 0:
139
+ log.error("Expression in --edit must return a bool-array, got nothing")
140
+
141
+ result = results[-1]
142
+ if callable(result):
143
+ result = result()
144
+ except MyError as e:
145
+ log.error(e)
146
+
147
+ if not is_boolarr(result):
148
+ log.error(
149
+ f"Expression in --edit must return a bool-array, got {print_str(result)}"
150
+ )
151
+ assert isinstance(result, np.ndarray)
170
152
 
171
- edit_result = run_interpreter_for_edit_option(method, filesetup)
172
- mut_margin(edit_result, start_margin, end_margin)
153
+ mut_margin(result, start_margin, end_margin)
173
154
 
174
- has_loud = concat((has_loud, edit_result))
175
- src_index = concat((src_index, np.full(len(edit_result), i, dtype=np.int32)))
155
+ has_loud = concat((has_loud, result))
156
+ src_index = concat((src_index, np.full(len(result), i, dtype=np.int32)))
176
157
 
177
158
  # Setup for handling custom speeds
178
159
  speed_index = has_loud.astype(np.uint)
auto_editor/output.py CHANGED
@@ -19,7 +19,6 @@ class Ensure:
19
19
  _ffmpeg: FFmpeg
20
20
  _bar: Bar
21
21
  _sr: int
22
- temp: str
23
22
  log: Log
24
23
  _audios: list[tuple[FileInfo, int]] = field(default_factory=list)
25
24
  _subtitles: list[tuple[FileInfo, int, str]] = field(default_factory=list)
@@ -33,7 +32,7 @@ class Ensure:
33
32
  label = len(self._audios) - 1
34
33
  first_time = True
35
34
 
36
- out_path = os.path.join(self.temp, f"{label:x}.wav")
35
+ out_path = os.path.join(self.log.temp, f"{label:x}.wav")
37
36
 
38
37
  if first_time:
39
38
  sample_rate = self._sr
@@ -83,7 +82,7 @@ class Ensure:
83
82
  self._subtitles.append((src, stream, ext))
84
83
  first_time = True
85
84
 
86
- out_path = os.path.join(self.temp, f"{stream}s.{ext}")
85
+ out_path = os.path.join(self.log.temp, f"{stream}s.{ext}")
87
86
 
88
87
  if first_time:
89
88
  self.log.debug(f"Making external subtitle: {out_path}")
@@ -119,7 +118,6 @@ def mux_quality_media(
119
118
  tb: Fraction,
120
119
  args: Args,
121
120
  src: FileInfo,
122
- temp: str,
123
121
  log: Log,
124
122
  ) -> None:
125
123
  v_tracks = len(visual_output)
@@ -142,7 +140,7 @@ def mux_quality_media(
142
140
  cmd.extend(["-i", path])
143
141
  else:
144
142
  # Merge all the audio a_tracks into one.
145
- new_a_file = os.path.join(temp, "new_audio.wav")
143
+ new_a_file = os.path.join(log.temp, "new_audio.wav")
146
144
  if a_tracks > 1:
147
145
  new_cmd = []
148
146
  for path in audio_output:
auto_editor/preview.py CHANGED
@@ -48,7 +48,7 @@ def all_cuts(tl: v3, in_len: int) -> list[int]:
48
48
  return cut_lens
49
49
 
50
50
 
51
- def preview(tl: v3, temp: str, log: Log) -> None:
51
+ def preview(tl: v3, log: Log) -> None:
52
52
  log.conwrite("")
53
53
  tb = tl.tb
54
54
 
@@ -65,7 +65,7 @@ def preview(tl: v3, temp: str, log: Log) -> None:
65
65
 
66
66
  in_len = 0
67
67
  for src in all_sources:
68
- in_len += Levels(src, tb, Bar("none"), temp, log).media_length
68
+ in_len += Levels(src, tb, Bar("none"), False, log, False).media_length
69
69
 
70
70
  out_len = tl.out_len()
71
71
 
@@ -166,7 +166,7 @@ def apply_audio_normalization(
166
166
 
167
167
 
168
168
  def make_new_audio(
169
- tl: v3, ensure: Ensure, args: Args, ffmpeg: FFmpeg, bar: Bar, temp: str, log: Log
169
+ tl: v3, ensure: Ensure, args: Args, ffmpeg: FFmpeg, bar: Bar, log: Log
170
170
  ) -> list[str]:
171
171
  sr = tl.sr
172
172
  tb = tl.tb
@@ -176,6 +176,7 @@ def make_new_audio(
176
176
  norm = parse_norm(args.audio_normalize, log)
177
177
 
178
178
  af_tick = 0
179
+ temp = log.temp
179
180
 
180
181
  if not tl.a or not tl.a[0]:
181
182
  log.error("Trying to render empty audio timeline")
@@ -105,13 +105,7 @@ def make_image_cache(tl: v3) -> dict[tuple[FileInfo, int], np.ndarray]:
105
105
 
106
106
 
107
107
  def render_av(
108
- ffmpeg: FFmpeg,
109
- tl: v3,
110
- args: Args,
111
- bar: Bar,
112
- ctr: Container,
113
- temp: str,
114
- log: Log,
108
+ ffmpeg: FFmpeg, tl: v3, args: Args, bar: Bar, ctr: Container, log: Log
115
109
  ) -> tuple[str, bool]:
116
110
  src = tl.src
117
111
  cns: dict[FileInfo, av.container.InputContainer] = {}
@@ -121,6 +115,7 @@ def render_av(
121
115
 
122
116
  target_pix_fmt = "yuv420p" # Reasonable default
123
117
  img_cache = make_image_cache(tl)
118
+ temp = log.temp
124
119
 
125
120
  first_src: FileInfo | None = None
126
121
  for src in tl.sources:
@@ -19,7 +19,6 @@ from auto_editor.utils.cmdkw import (
19
19
  pAttr,
20
20
  pAttrs,
21
21
  )
22
- from auto_editor.utils.func import setup_tempdir
23
22
  from auto_editor.utils.log import Log
24
23
  from auto_editor.utils.types import frame_rate
25
24
  from auto_editor.vanparse import ArgumentParser
@@ -72,14 +71,11 @@ def print_arr(arr: NDArray) -> None:
72
71
  print("")
73
72
 
74
73
 
75
- def print_arr_gen(arr: Iterator[int | float]) -> None:
74
+ def print_arr_gen(arr: Iterator[float | np.float32]) -> None:
76
75
  print("")
77
76
  print("@start")
78
77
  for a in arr:
79
- if isinstance(a, float):
80
- print(f"{a:.20f}")
81
- else:
82
- print(a)
78
+ print(f"{a:.20f}")
83
79
  print("")
84
80
 
85
81
 
@@ -88,8 +84,7 @@ def main(sys_args: list[str] = sys.argv[1:]) -> None:
88
84
  args = parser.parse_args(LevelArgs, sys_args)
89
85
 
90
86
  bar = Bar("none")
91
- temp = setup_tempdir(None, Log())
92
- log = Log(quiet=True, temp=temp)
87
+ log = Log(quiet=True)
93
88
 
94
89
  sources = [initFileInfo(path, log) for path in args.input]
95
90
  if len(sources) < 1:
@@ -132,7 +127,7 @@ def main(sys_args: list[str] = sys.argv[1:]) -> None:
132
127
  except ParserError as e:
133
128
  log.error(e)
134
129
 
135
- levels = Levels(src, tb, bar, temp, log)
130
+ levels = Levels(src, tb, bar, False, log, strict=True)
136
131
  try:
137
132
  if method == "audio":
138
133
  print_arr_gen(iter_audio(src, tb, **obj))
@@ -5,13 +5,12 @@ from dataclasses import dataclass, field
5
5
  from fractions import Fraction
6
6
 
7
7
  import auto_editor
8
- from auto_editor.analyze import FileSetup, Levels
8
+ from auto_editor.analyze import Levels
9
9
  from auto_editor.ffwrapper import initFileInfo
10
10
  from auto_editor.lang.palet import ClosingError, Lexer, Parser, env, interpret
11
11
  from auto_editor.lib.data_structs import print_str
12
12
  from auto_editor.lib.err import MyError
13
13
  from auto_editor.utils.bar import Bar
14
- from auto_editor.utils.func import setup_tempdir
15
14
  from auto_editor.utils.log import Log
16
15
  from auto_editor.utils.types import frame_rate
17
16
  from auto_editor.vanparse import ArgumentParser
@@ -59,18 +58,16 @@ def main(sys_args: list[str] = sys.argv[1:]) -> None:
59
58
  args = repl_options(ArgumentParser(None)).parse_args(REPL_Args, sys_args)
60
59
 
61
60
  if args.input:
62
- temp = setup_tempdir(args.temp_dir, Log())
63
- log = Log(quiet=True, temp=temp)
61
+ log = Log(quiet=True, temp_dir=args.temp_dir)
64
62
  strict = len(args.input) < 2
65
63
  sources = [initFileInfo(path, log) for path in args.input]
66
64
  src = sources[0]
67
65
  tb = src.get_fps() if args.timebase is None else args.timebase
68
66
  bar = Bar("modern")
69
67
  env["timebase"] = tb
70
- env["@levels"] = Levels(src, tb, bar, temp, log)
71
- env["@filesetup"] = FileSetup(src, strict, tb, bar, temp, log)
68
+ env["@levels"] = Levels(src, tb, bar, False, log, strict)
72
69
 
73
- print(f"Auto-Editor {auto_editor.version} ({auto_editor.__version__})")
70
+ print(f"Auto-Editor {auto_editor.__version__}")
74
71
  text = None
75
72
 
76
73
  try:
@@ -1,15 +1,13 @@
1
- # type: ignore
2
1
  from __future__ import annotations
3
2
 
4
3
  import os
5
4
  import shutil
6
5
  import subprocess
7
6
  import sys
8
- from collections.abc import Callable
9
7
  from dataclasses import dataclass, field
10
8
  from fractions import Fraction
11
9
  from time import perf_counter
12
- from typing import Any
10
+ from typing import TYPE_CHECKING
13
11
 
14
12
  import numpy as np
15
13
 
@@ -20,6 +18,12 @@ from auto_editor.lib.err import MyError
20
18
  from auto_editor.utils.log import Log
21
19
  from auto_editor.vanparse import ArgumentParser
22
20
 
21
+ if TYPE_CHECKING:
22
+ from collections.abc import Callable
23
+ from typing import Any
24
+
25
+ from auto_editor.vanparse import ArgumentParser
26
+
23
27
 
24
28
  @dataclass(slots=True)
25
29
  class TestArgs:
@@ -29,7 +33,7 @@ class TestArgs:
29
33
  category: str = "cli"
30
34
 
31
35
 
32
- def test_options(parser):
36
+ def test_options(parser: ArgumentParser) -> ArgumentParser:
33
37
  parser.add_argument("--only", "-n", nargs="*")
34
38
  parser.add_argument("--no-fail-fast", flag=True)
35
39
  parser.add_required(
@@ -47,14 +51,6 @@ def pipe_to_console(cmd: list[str]) -> tuple[int, str, str]:
47
51
  return process.returncode, stdout.decode("utf-8"), stderr.decode("utf-8")
48
52
 
49
53
 
50
- class Checker:
51
- def __init__(self, log: Log):
52
- self.log = log
53
-
54
- def check(self, path: str) -> FileInfo:
55
- return initFileInfo(path, self.log)
56
-
57
-
58
54
  class Runner:
59
55
  def __init__(self) -> None:
60
56
  self.program = [sys.executable, "-m", "auto_editor"]
@@ -176,7 +172,10 @@ def main(sys_args: list[str] | None = None):
176
172
  args = test_options(ArgumentParser("test")).parse_args(TestArgs, sys_args)
177
173
 
178
174
  run = Runner()
179
- checker = Checker(Log())
175
+ log = Log()
176
+
177
+ def fileinfo(path: str) -> FileInfo:
178
+ return initFileInfo(path, log)
180
179
 
181
180
  ### Tests ###
182
181
 
@@ -229,7 +228,7 @@ def main(sys_args: list[str] | None = None):
229
228
 
230
229
  def example():
231
230
  out = run.main(inputs=["example.mp4"], cmd=[])
232
- cn = checker.check(out)
231
+ cn = fileinfo(out)
233
232
  video = cn.videos[0]
234
233
 
235
234
  assert video.fps == 30
@@ -295,7 +294,7 @@ def main(sys_args: list[str] | None = None):
295
294
  out = run.main(
296
295
  ["resources/only-video/man-on-green-screen.gif"], ["--edit", "none"]
297
296
  )
298
- assert checker.check(out).videos[0].codec == "gif"
297
+ assert fileinfo(out).videos[0].codec == "gif"
299
298
 
300
299
  return out
301
300
 
@@ -319,11 +318,11 @@ def main(sys_args: list[str] | None = None):
319
318
  out = run.main(inputs=["example.mp4"], cmd=[], output="out")
320
319
 
321
320
  assert out == "out.mp4"
322
- assert checker.check(out).videos[0].codec == "h264"
321
+ assert fileinfo(out).videos[0].codec == "h264"
323
322
 
324
323
  out = run.main(inputs=["resources/testsrc.mkv"], cmd=[], output="out")
325
324
  assert out == "out.mkv"
326
- assert checker.check(out).videos[0].codec == "h264"
325
+ assert fileinfo(out).videos[0].codec == "h264"
327
326
 
328
327
  return "out.mp4", "out.mkv"
329
328
 
@@ -357,14 +356,14 @@ def main(sys_args: list[str] | None = None):
357
356
  run.main(["example.mp4"], ["--export", 'premiere:name="Foo Bar"'])
358
357
 
359
358
  def resolution_and_scale():
360
- cn = checker.check(run.main(["example.mp4"], ["--scale", "1.5"]))
359
+ cn = fileinfo(run.main(["example.mp4"], ["--scale", "1.5"]))
361
360
 
362
361
  assert cn.videos[0].fps == 30
363
362
  assert cn.videos[0].width == 1920
364
363
  assert cn.videos[0].height == 1080
365
364
  assert cn.audios[0].samplerate == 48000
366
365
 
367
- cn = checker.check(run.main(["example.mp4"], ["--scale", "0.2"]))
366
+ cn = fileinfo(run.main(["example.mp4"], ["--scale", "0.2"]))
368
367
 
369
368
  assert cn.videos[0].fps == 30
370
369
  assert cn.videos[0].width == 256
@@ -372,7 +371,7 @@ def main(sys_args: list[str] | None = None):
372
371
  assert cn.audios[0].samplerate == 48000
373
372
 
374
373
  out = run.main(["example.mp4"], ["-res", "700,380", "-b", "darkgreen"])
375
- cn = checker.check(out)
374
+ cn = fileinfo(out)
376
375
 
377
376
  assert cn.videos[0].fps == 30
378
377
  assert cn.videos[0].width == 700
@@ -416,7 +415,7 @@ def main(sys_args: list[str] | None = None):
416
415
  ["--edit", "audio:stream=1"],
417
416
  "out.mov",
418
417
  )
419
- assert len(checker.check(out).audios) == 1
418
+ assert len(fileinfo(out).audios) == 1
420
419
 
421
420
  return out
422
421
 
@@ -427,7 +426,7 @@ def main(sys_args: list[str] | None = None):
427
426
 
428
427
  def concat_mux_tracks():
429
428
  out = run.main(["example.mp4", "resources/multi-track.mov"], [], "out.mov")
430
- assert len(checker.check(out).audios) == 1
429
+ assert len(fileinfo(out).audios) == 1
431
430
 
432
431
  return out
433
432
 
@@ -437,30 +436,30 @@ def main(sys_args: list[str] | None = None):
437
436
  ["--keep-tracks-separate"],
438
437
  "out.mov",
439
438
  )
440
- assert len(checker.check(out).audios) == 2
439
+ assert len(fileinfo(out).audios) == 2
441
440
  out = run.main(
442
441
  ["example.mp4", "resources/multi-track.mov"],
443
442
  ["--keep-tracks-separate"],
444
443
  "out.mov",
445
444
  )
446
- assert len(checker.check(out).audios) == 2
445
+ assert len(fileinfo(out).audios) == 2
447
446
 
448
447
  return out
449
448
 
450
449
  def frame_rate():
451
- cn = checker.check(run.main(["example.mp4"], ["-r", "15", "--no-seek"]))
450
+ cn = fileinfo(run.main(["example.mp4"], ["-r", "15", "--no-seek"]))
452
451
  video = cn.videos[0]
453
452
  assert video.fps == 15
454
453
  assert video.time_base == Fraction(1, 15)
455
454
  assert float(video.duration) - 17.33333333333333333333333 < 3
456
455
 
457
- cn = checker.check(run.main(["example.mp4"], ["-r", "20"]))
456
+ cn = fileinfo(run.main(["example.mp4"], ["-r", "20"]))
458
457
  video = cn.videos[0]
459
458
  assert video.fps == 20
460
459
  assert video.time_base == Fraction(1, 20)
461
460
  assert float(video.duration) - 17.33333333333333333333333 < 2
462
461
 
463
- cn = checker.check(out := run.main(["example.mp4"], ["-r", "60"]))
462
+ cn = fileinfo(out := run.main(["example.mp4"], ["-r", "60"]))
464
463
  video = cn.videos[0]
465
464
 
466
465
  assert video.fps == 60
@@ -471,22 +470,22 @@ def main(sys_args: list[str] | None = None):
471
470
 
472
471
  def embedded_image():
473
472
  out1 = run.main(["resources/embedded-image/h264-png.mp4"], [])
474
- cn = checker.check(out1)
473
+ cn = fileinfo(out1)
475
474
  assert cn.videos[0].codec == "h264"
476
475
  assert cn.videos[1].codec == "png"
477
476
 
478
477
  out2 = run.main(["resources/embedded-image/h264-mjpeg.mp4"], [])
479
- cn = checker.check(out2)
478
+ cn = fileinfo(out2)
480
479
  assert cn.videos[0].codec == "h264"
481
480
  assert cn.videos[1].codec == "mjpeg"
482
481
 
483
482
  out3 = run.main(["resources/embedded-image/h264-png.mkv"], [])
484
- cn = checker.check(out3)
483
+ cn = fileinfo(out3)
485
484
  assert cn.videos[0].codec == "h264"
486
485
  assert cn.videos[1].codec == "png"
487
486
 
488
487
  out4 = run.main(["resources/embedded-image/h264-mjpeg.mkv"], [])
489
- cn = checker.check(out4)
488
+ cn = fileinfo(out4)
490
489
  assert cn.videos[0].codec == "h264"
491
490
  assert cn.videos[1].codec == "mjpeg"
492
491
 
@@ -532,7 +531,7 @@ def main(sys_args: list[str] | None = None):
532
531
  # Issue 280
533
532
  def SAR():
534
533
  out = run.main(["resources/SAR-2by3.mp4"], [])
535
- assert checker.check(out).videos[0].sar == Fraction(2, 3)
534
+ assert fileinfo(out).videos[0].sar == Fraction(2, 3)
536
535
 
537
536
  return out
538
537
 
auto_editor/timeline.py CHANGED
@@ -1,21 +1,19 @@
1
1
  from __future__ import annotations
2
2
 
3
- from collections.abc import Iterator
4
3
  from dataclasses import dataclass
5
- from fractions import Fraction
6
- from typing import Any
4
+ from typing import TYPE_CHECKING
7
5
 
8
- from auto_editor.ffwrapper import FileInfo
9
6
  from auto_editor.lib.contracts import *
10
- from auto_editor.utils.chunks import Chunks
11
7
  from auto_editor.utils.cmdkw import Required, pAttr, pAttrs
12
- from auto_editor.utils.types import (
13
- anchor,
14
- color,
15
- natural,
16
- number,
17
- threshold,
18
- )
8
+ from auto_editor.utils.types import anchor, color, natural, number, threshold
9
+
10
+ if TYPE_CHECKING:
11
+ from collections.abc import Iterator
12
+ from fractions import Fraction
13
+ from typing import Any
14
+
15
+ from auto_editor.ffwrapper import FileInfo
16
+ from auto_editor.utils.chunks import Chunks
19
17
 
20
18
 
21
19
  @dataclass(slots=True)
@@ -83,6 +83,10 @@ def container_constructor(ext: str) -> Container:
83
83
  vcodecs.add(codec)
84
84
  if codec == "h264":
85
85
  vcodecs.add("libx264")
86
+ if codec == "av1":
87
+ vcodecs.add("libsvtav1")
88
+ if codec == "hevc":
89
+ vcodecs.add("hevc_nvenc")
86
90
  if kind == "audio":
87
91
  acodecs.add(codec)
88
92
  if kind == "subtitle":
auto_editor/utils/func.py CHANGED
@@ -1,15 +1,19 @@
1
1
  from __future__ import annotations
2
2
 
3
- from collections.abc import Callable
4
- from fractions import Fraction
3
+ from typing import TYPE_CHECKING
5
4
 
6
5
  import numpy as np
7
- from numpy.typing import NDArray
8
6
 
9
- from auto_editor.utils.log import Log
7
+ if TYPE_CHECKING:
8
+ from collections.abc import Callable
9
+ from fractions import Fraction
10
10
 
11
- BoolList = NDArray[np.bool_]
12
- BoolOperand = Callable[[BoolList, BoolList], BoolList]
11
+ from numpy.typing import NDArray
12
+
13
+ from auto_editor.utils.log import Log
14
+
15
+ BoolList = NDArray[np.bool_]
16
+ BoolOperand = Callable[[BoolList, BoolList], BoolList]
13
17
 
14
18
 
15
19
  def boolop(a: BoolList, b: BoolList, call: BoolOperand) -> BoolList:
@@ -25,26 +29,6 @@ def boolop(a: BoolList, b: BoolList, call: BoolOperand) -> BoolList:
25
29
  return call(a, b)
26
30
 
27
31
 
28
- def setup_tempdir(temp: str | None, log: Log) -> str:
29
- if temp is None:
30
- import tempfile
31
-
32
- return tempfile.mkdtemp()
33
-
34
- import os.path
35
- from os import listdir, mkdir
36
-
37
- if os.path.isfile(temp):
38
- log.error("Temp directory cannot be an already existing file.")
39
- if os.path.isdir(temp):
40
- if len(listdir(temp)) != 0:
41
- log.error("Temp directory should be empty!")
42
- else:
43
- mkdir(temp)
44
-
45
- return temp
46
-
47
-
48
32
  def to_timecode(secs: float | Fraction, fmt: str) -> str:
49
33
  sign = ""
50
34
  if secs < 0:
auto_editor/utils/log.py CHANGED
@@ -3,45 +3,72 @@ from __future__ import annotations
3
3
  import sys
4
4
  from datetime import timedelta
5
5
  from shutil import get_terminal_size, rmtree
6
+ from tempfile import mkdtemp
6
7
  from time import perf_counter, sleep
7
8
  from typing import NoReturn
8
9
 
9
10
 
10
11
  class Log:
11
- __slots__ = ("is_debug", "quiet", "temp", "machine", "start_time", "no_color")
12
+ __slots__ = ("is_debug", "quiet", "machine", "no_color", "_temp", "_ut", "_s")
12
13
 
13
14
  def __init__(
14
15
  self,
15
16
  is_debug: bool = False,
16
17
  quiet: bool = False,
17
- temp: str | None = None,
18
+ temp_dir: str | None = None,
18
19
  machine: bool = False,
19
20
  no_color: bool = True,
20
21
  ):
21
22
  self.is_debug = is_debug
22
23
  self.quiet = quiet
23
- self.temp = temp
24
24
  self.machine = machine
25
25
  self.no_color = no_color
26
- self.start_time = 0 if self.quiet or self.machine else perf_counter()
26
+ self._temp: str | None = None
27
+ self._ut = temp_dir
28
+ self._s = 0 if self.quiet or self.machine else perf_counter()
27
29
 
28
30
  def debug(self, message: object) -> None:
29
31
  if self.is_debug:
30
32
  self.conwrite("")
31
33
  sys.stderr.write(f"Debug: {message}\n")
32
34
 
35
+ @property
36
+ def temp(self) -> str:
37
+ if self._temp is not None:
38
+ return self._temp
39
+
40
+ if self._ut is None:
41
+ result = mkdtemp()
42
+ else:
43
+ import os.path
44
+ from os import listdir, mkdir
45
+
46
+ if os.path.isfile(self._ut):
47
+ self.error("Temp directory cannot be an already existing file.")
48
+
49
+ if os.path.isdir(self._ut):
50
+ if len(listdir(self._ut)) != 0:
51
+ self.error("Temp directory should be empty!")
52
+ else:
53
+ mkdir(self._ut)
54
+ result = self._ut
55
+
56
+ self.debug(f"Temp Directory: {result}")
57
+ self._temp = result
58
+ return result
59
+
33
60
  def cleanup(self) -> None:
34
- if self.temp is None:
61
+ if self._temp is None:
35
62
  return
36
63
  try:
37
- rmtree(self.temp)
64
+ rmtree(self._temp)
38
65
  self.debug("Removed Temp Directory.")
39
66
  except FileNotFoundError:
40
67
  pass
41
68
  except PermissionError:
42
69
  sleep(0.1)
43
70
  try:
44
- rmtree(self.temp)
71
+ rmtree(self._temp)
45
72
  self.debug("Removed Temp Directory.")
46
73
  except Exception as e:
47
74
  self.debug(f"Failed to delete temp dir:\n{e}")
@@ -65,7 +92,7 @@ class Log:
65
92
 
66
93
  def stop_timer(self) -> None:
67
94
  if not self.quiet and not self.machine:
68
- second_len = round(perf_counter() - self.start_time, 2)
95
+ second_len = round(perf_counter() - self._s, 2)
69
96
  minute_len = timedelta(seconds=round(second_len))
70
97
 
71
98
  sys.stdout.write(f"Finished. took {second_len} seconds ({minute_len})\n")
@@ -217,7 +217,7 @@ def resolution(val: str | None) -> tuple[int, int] | None:
217
217
  return natural(vals[0]), natural(vals[1])
218
218
 
219
219
 
220
- @dataclass
220
+ @dataclass(slots=True)
221
221
  class Args:
222
222
  yt_dlp_location: str = "yt-dlp"
223
223
  download_format: str | None = None
@@ -255,6 +255,7 @@ class Args:
255
255
  show_ffmpeg_output: bool = False
256
256
  quiet: bool = False
257
257
  preview: bool = False
258
+ no_cache: bool = False
258
259
  margin: tuple[str, str] = ("0.2s", "0.2s")
259
260
  silent_speed: float = 99999.0
260
261
  video_speed: float = 1.0
auto_editor/vanparse.py CHANGED
@@ -4,7 +4,6 @@ import difflib
4
4
  import re
5
5
  import sys
6
6
  import textwrap
7
- from collections.abc import Iterator
8
7
  from dataclasses import dataclass
9
8
  from io import StringIO
10
9
  from shutil import get_terminal_size
@@ -14,7 +13,7 @@ from auto_editor.utils.log import Log
14
13
  from auto_editor.utils.types import CoerceError
15
14
 
16
15
  if TYPE_CHECKING:
17
- from collections.abc import Callable
16
+ from collections.abc import Callable, Iterator
18
17
  from typing import Any, Literal, TypeVar
19
18
 
20
19
  T = TypeVar("T")
@@ -1,6 +1,6 @@
1
1
  Metadata-Version: 2.1
2
2
  Name: auto-editor
3
- Version: 24.31.1
3
+ Version: 25.0.0
4
4
  Summary: Auto-Editor: Effort free video editing!
5
5
  Author-email: WyattBlue <wyattblue@auto-editor.com>
6
6
  License: Unlicense
@@ -1,15 +1,15 @@
1
- auto_editor/__init__.py,sha256=fE5TG5waO4ycosUA6qLxyCtCX3by5wAgPN6aiaae268,43
2
- auto_editor/__main__.py,sha256=nkb8N_bxF_qld53LWo4c6Y0n9NDRdsPfANpVF1RD1cQ,9863
3
- auto_editor/analyze.py,sha256=6siPLoYwEjvbAzPfKZWDEcqtGXIM8n9qNveyCCYLAgI,11769
4
- auto_editor/edit.py,sha256=yZFLoWqfC2QFEdibp7AlKXMRB-6jHwNDpTW2SlN-4Is,11325
5
- auto_editor/ffwrapper.py,sha256=jga7-HbQnC4w21qZk4aY4kwLT7UPqkGn6NJPFM5Qssc,7811
1
+ auto_editor/__init__.py,sha256=hNpKYQArhnrmhf1u3TNPTwtZw8XS7EeDs-TfBLusM-s,23
2
+ auto_editor/__main__.py,sha256=89pW_zaFB8WEyKTGadkALLuv_MFONIcfEu8t0jKqeWI,9774
3
+ auto_editor/analyze.py,sha256=pHoSZ_-wyV1hLJyJpPg9Ha7oecjdGHGJ0a4hbKnb9NY,11779
4
+ auto_editor/edit.py,sha256=ihyCi9YyIV-WYP81pOnc5jG8n-e8m5aMogg27XVxPVY,11268
5
+ auto_editor/ffwrapper.py,sha256=RU74pfZUMEIVdpvZVVoJt92AILBGkYaPLZbI94Y0i60,7832
6
6
  auto_editor/help.py,sha256=BFiP7vBz42TUzum4-zaQIrV1OY7kHeN0pe0MPE0T5xw,7997
7
- auto_editor/make_layers.py,sha256=AzfxRkm9ZFjeVwdh3jFT_5_YvVaUMtphVKDtIaDI5bo,8900
8
- auto_editor/output.py,sha256=n2WMXS1oRJi3RbtnloYTlPROqU9qy-5BVW3l4ZnPNHo,8060
9
- auto_editor/preview.py,sha256=CVKtSjcX1dNjbBgsD9PTbRBwRV6dxBLkJ_VIkkBuTUY,3032
10
- auto_editor/timeline.py,sha256=JwcS-8AS5vsoTL_m03aosYijScQef4AGa2lyutQ8wbI,7069
7
+ auto_editor/make_layers.py,sha256=ybTxPRD6bDbEX-7e9qu4OYUvYkrdNb4BjnN9hzwkRiQ,8496
8
+ auto_editor/output.py,sha256=D8NCJwwmcjDf5rvoBnWKu5XY7QtxF3thxbnTxKAxGu8,8043
9
+ auto_editor/preview.py,sha256=noWkgyzdE14zwG8ZDYxLDual5iVt6zYTt4HTe-QAgFY,3029
10
+ auto_editor/timeline.py,sha256=d9Qhup2pBwsj7j9kF-Iw22xHbQvGx-keDq2eLI11Mt4,7117
11
11
  auto_editor/validate_input.py,sha256=JQt7J5xOBJDp6lnd2sQptpYhf7Z_hyxNEzLsE9E7LKU,2596
12
- auto_editor/vanparse.py,sha256=p0u1X2gKM_lWB9bg-Lotqq9-bjfLgsVW9-U4Lwbz0aU,10029
12
+ auto_editor/vanparse.py,sha256=f0vViZ-aYtDxEyVrFHJ5X2pPTQAfqtw3N2gZgzn51kU,10002
13
13
  auto_editor/wavfile.py,sha256=7N2LX_WfFVRnoXrKveLvuyTYpIz2htpGqfCD8tR4kJ8,9168
14
14
  auto_editor/formats/__init__.py,sha256=47DEQpj8HBSa-_TImW-5JCeuQeRkm5NMpJWZG3hSuFU,0
15
15
  auto_editor/formats/fcp11.py,sha256=VwJWJs1qNDIVC8-pswipmKCk0e4V3LnE5fAMA0pPWVg,5857
@@ -20,36 +20,36 @@ auto_editor/formats/utils.py,sha256=GIZw28WHuCIaZ_zMI0v6Kxbq0QaIpbLsdSegdYwQxQ8,
20
20
  auto_editor/lang/__init__.py,sha256=47DEQpj8HBSa-_TImW-5JCeuQeRkm5NMpJWZG3hSuFU,0
21
21
  auto_editor/lang/json.py,sha256=OsNcYlfEj8ZLlzLK-gkLcrCCKI7mJz9rpe-6XLr4f9U,9231
22
22
  auto_editor/lang/libmath.py,sha256=z33A161Oe6vYYK7R6pgYjdZZe63dQkN38Qf36TL3prg,847
23
- auto_editor/lang/palet.py,sha256=MIyI0Os0wCgUZaOkyiL-KPb2Bd7JwZtbI26H4m89lwQ,59823
23
+ auto_editor/lang/palet.py,sha256=m22TQnKomUM2quxIH4bz7ya_gM166rbJ5pW7Ei-43IA,59712
24
24
  auto_editor/lib/__init__.py,sha256=47DEQpj8HBSa-_TImW-5JCeuQeRkm5NMpJWZG3hSuFU,0
25
- auto_editor/lib/contracts.py,sha256=HxWWMU63_6nKsdIXCDKnlWaYzS0dpN-H3uwCuzZALaM,7353
25
+ auto_editor/lib/contracts.py,sha256=a3ZT-bGMa3-UjWKKFrEwLayw2Gl-rhqd5Bmvmrj85oE,7413
26
26
  auto_editor/lib/data_structs.py,sha256=xyB6aEcpdB9NNWp_dU3d2ds5Z8zOfHXNX4mNQLh2pNw,6977
27
27
  auto_editor/lib/err.py,sha256=UlszQJdzMZwkbT8x3sY4GkCV_5x9yrd6uVVUzvA8iiI,35
28
28
  auto_editor/render/__init__.py,sha256=47DEQpj8HBSa-_TImW-5JCeuQeRkm5NMpJWZG3hSuFU,0
29
- auto_editor/render/audio.py,sha256=pUhD4rQZfUnyzKgpuxNxl_2CUGwbkAWo2356HUAW7VM,8835
29
+ auto_editor/render/audio.py,sha256=darKvlglNXkknSUaPnH-qEUyccM1Awnv03_Px4yY81I,8844
30
30
  auto_editor/render/subtitle.py,sha256=g195kDN0LcwKlZeQMCflXPH5n_74iwCk1RPLSQ5eP34,4373
31
- auto_editor/render/video.py,sha256=MOhZh72VGe0LVrDE8SzBb-FqXUrnQTIF51zz1qvX2_I,12987
31
+ auto_editor/render/video.py,sha256=gMVcLehC_QpdtIzNLR_7tv2CZmYeEWisS_5as4ceHV0,12971
32
32
  auto_editor/subcommands/__init__.py,sha256=47DEQpj8HBSa-_TImW-5JCeuQeRkm5NMpJWZG3hSuFU,0
33
33
  auto_editor/subcommands/desc.py,sha256=GDrKJYiHMaeTrplZAceXl1JwoqD78XsV2_5lc0Xd7po,869
34
34
  auto_editor/subcommands/info.py,sha256=7Sgt9WR0rWxe7juCRKseMxW6gKv3z3voqFcL8-MOVVM,6927
35
- auto_editor/subcommands/levels.py,sha256=ymHwAp2l7HScmdTtNAPDEeDfRVY_rRPRigUCDYx2F40,4220
35
+ auto_editor/subcommands/levels.py,sha256=ZB8_9jbOA5s1AQUcUNZDiLAjyJOKl7Be2YeVWdkOr0Q,4071
36
36
  auto_editor/subcommands/palet.py,sha256=tbQoRWoT4jR3yu0etGApfprM-oQgXIjC-rIY-QG3nM0,655
37
- auto_editor/subcommands/repl.py,sha256=gEVmZduWwnumMe4GLHi72C_IbmdscYwiz7mjigU97Qk,3300
37
+ auto_editor/subcommands/repl.py,sha256=OfxIOBjE7W12UemfaaxMnzHcmV5cUTt7g5328R7rAYU,3116
38
38
  auto_editor/subcommands/subdump.py,sha256=af_XBf7kaevqHn1A71z8C-7x8pS5WKD9FE_ugkCw6rk,665
39
- auto_editor/subcommands/test.py,sha256=kafifZmZJrxevehVOyFSObmdNGpSg9HIJPhc731Gayk,25050
39
+ auto_editor/subcommands/test.py,sha256=Qg_zY1Cg8Mo7JXsUWFEJxJZAe5pkkXyK4ydnBVpG0I8,24992
40
40
  auto_editor/utils/__init__.py,sha256=47DEQpj8HBSa-_TImW-5JCeuQeRkm5NMpJWZG3hSuFU,0
41
41
  auto_editor/utils/bar.py,sha256=RJqkJ8gNr8op_Z-2hh48ExjSonmTPX-RshctK_itv14,3988
42
42
  auto_editor/utils/chunks.py,sha256=J-eGKtEz68gFtRrj1kOSgH4Tj_Yz6prNQ7Xr-d9NQJw,52
43
43
  auto_editor/utils/cmdkw.py,sha256=XApxw7FZBOEJV9N4LHhdw1GVfHbFfCjr-zCZ1gJsSvY,6002
44
- auto_editor/utils/container.py,sha256=CWB55RRzADBuQmX8lhiPaG8iPayGWaebvtmJjgJ6WRA,2497
44
+ auto_editor/utils/container.py,sha256=qSoS5d8JqLRH4_BIWfnJ-37eCKKe_J290yrUNULGT94,2643
45
45
  auto_editor/utils/encoder.py,sha256=auNYo7HXbcU4iTUCc0LE5lpwFmSvdWvBm6-5KIaRK8w,2983
46
- auto_editor/utils/func.py,sha256=H38xO6Wxg1TZILVrx-nCowCzj_mqBUtJuOFp4DV3Hsc,4843
47
- auto_editor/utils/log.py,sha256=ry-C92PRkJ-c8PQYIs1imk1qigDYfsCoLBYK6CQSP7I,2844
46
+ auto_editor/utils/func.py,sha256=kcxCOqe-tg6k-kxutIran8LpffRiHDjKB6rm-ngFiSU,4460
47
+ auto_editor/utils/log.py,sha256=M2QKeQHMRNLm3HMVUKedZPRprT2u5dipOStiO4miPBk,3613
48
48
  auto_editor/utils/subtitle_tools.py,sha256=TjjVPiT8bWzZJcrZjF7ddpgfIsVkLE4LyxXzBswHAGU,693
49
- auto_editor/utils/types.py,sha256=zWbU_VkcdP4yHHzKyaSiXu560n5U53i0x5SPkUDsCZU,11570
50
- auto_editor-24.31.1.dist-info/LICENSE,sha256=yiq99pWITHfqS0pbZMp7cy2dnbreTuvBwudsU-njvIM,1210
51
- auto_editor-24.31.1.dist-info/METADATA,sha256=QhgrsPDCOesLJsC420BKailNLFnrpJj6ykc3bGXgPmg,6138
52
- auto_editor-24.31.1.dist-info/WHEEL,sha256=R0nc6qTxuoLk7ShA2_Y-UWkN8ZdfDBG2B6Eqpz2WXbs,91
53
- auto_editor-24.31.1.dist-info/entry_points.txt,sha256=-H7zdTw4MqnAcwrN5xTNkGIhzZtJMxS9r6lTMeR9-aA,240
54
- auto_editor-24.31.1.dist-info/top_level.txt,sha256=ky1HUkqq9i034c4CUU_0wBw0xZsxxyGEak1eTbdvpyA,12
55
- auto_editor-24.31.1.dist-info/RECORD,,
49
+ auto_editor/utils/types.py,sha256=JdAwfuT9Ty_FXUm89GUTo0M8FPFrXbqnlk-g4pWP1_k,11609
50
+ auto_editor-25.0.0.dist-info/LICENSE,sha256=yiq99pWITHfqS0pbZMp7cy2dnbreTuvBwudsU-njvIM,1210
51
+ auto_editor-25.0.0.dist-info/METADATA,sha256=ucOoacVK0LCr-DKicxIkO9w6Zx2LWvlfMPgBiXucYkk,6137
52
+ auto_editor-25.0.0.dist-info/WHEEL,sha256=R0nc6qTxuoLk7ShA2_Y-UWkN8ZdfDBG2B6Eqpz2WXbs,91
53
+ auto_editor-25.0.0.dist-info/entry_points.txt,sha256=-H7zdTw4MqnAcwrN5xTNkGIhzZtJMxS9r6lTMeR9-aA,240
54
+ auto_editor-25.0.0.dist-info/top_level.txt,sha256=ky1HUkqq9i034c4CUU_0wBw0xZsxxyGEak1eTbdvpyA,12
55
+ auto_editor-25.0.0.dist-info/RECORD,,