auto-editor 24.30.1__tar.gz → 25.0.0__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 (61) hide show
  1. {auto_editor-24.30.1 → auto_editor-25.0.0}/PKG-INFO +1 -1
  2. auto_editor-25.0.0/auto_editor/__init__.py +1 -0
  3. {auto_editor-24.30.1 → auto_editor-25.0.0}/auto_editor/__main__.py +12 -19
  4. {auto_editor-24.30.1 → auto_editor-25.0.0}/auto_editor/analyze.py +26 -30
  5. {auto_editor-24.30.1 → auto_editor-25.0.0}/auto_editor/edit.py +8 -11
  6. {auto_editor-24.30.1 → auto_editor-25.0.0}/auto_editor/ffwrapper.py +1 -1
  7. {auto_editor-24.30.1 → auto_editor-25.0.0}/auto_editor/lang/palet.py +17 -23
  8. {auto_editor-24.30.1 → auto_editor-25.0.0}/auto_editor/lib/contracts.py +5 -2
  9. {auto_editor-24.30.1 → auto_editor-25.0.0}/auto_editor/lib/data_structs.py +2 -0
  10. {auto_editor-24.30.1 → auto_editor-25.0.0}/auto_editor/make_layers.py +28 -47
  11. {auto_editor-24.30.1 → auto_editor-25.0.0}/auto_editor/output.py +4 -6
  12. {auto_editor-24.30.1 → auto_editor-25.0.0}/auto_editor/preview.py +2 -2
  13. {auto_editor-24.30.1 → auto_editor-25.0.0}/auto_editor/render/audio.py +2 -1
  14. {auto_editor-24.30.1 → auto_editor-25.0.0}/auto_editor/render/video.py +2 -7
  15. {auto_editor-24.30.1 → auto_editor-25.0.0}/auto_editor/subcommands/levels.py +4 -9
  16. {auto_editor-24.30.1 → auto_editor-25.0.0}/auto_editor/subcommands/repl.py +4 -7
  17. {auto_editor-24.30.1 → auto_editor-25.0.0}/auto_editor/subcommands/test.py +33 -34
  18. {auto_editor-24.30.1 → auto_editor-25.0.0}/auto_editor/timeline.py +10 -12
  19. {auto_editor-24.30.1 → auto_editor-25.0.0}/auto_editor/utils/container.py +4 -0
  20. {auto_editor-24.30.1 → auto_editor-25.0.0}/auto_editor/utils/func.py +10 -26
  21. {auto_editor-24.30.1 → auto_editor-25.0.0}/auto_editor/utils/log.py +35 -8
  22. {auto_editor-24.30.1 → auto_editor-25.0.0}/auto_editor/utils/types.py +2 -1
  23. {auto_editor-24.30.1 → auto_editor-25.0.0}/auto_editor/vanparse.py +1 -2
  24. {auto_editor-24.30.1 → auto_editor-25.0.0}/auto_editor.egg-info/PKG-INFO +1 -1
  25. auto_editor-24.30.1/auto_editor/__init__.py +0 -2
  26. {auto_editor-24.30.1 → auto_editor-25.0.0}/LICENSE +0 -0
  27. {auto_editor-24.30.1 → auto_editor-25.0.0}/README.md +0 -0
  28. {auto_editor-24.30.1 → auto_editor-25.0.0}/auto_editor/formats/__init__.py +0 -0
  29. {auto_editor-24.30.1 → auto_editor-25.0.0}/auto_editor/formats/fcp11.py +0 -0
  30. {auto_editor-24.30.1 → auto_editor-25.0.0}/auto_editor/formats/fcp7.py +0 -0
  31. {auto_editor-24.30.1 → auto_editor-25.0.0}/auto_editor/formats/json.py +0 -0
  32. {auto_editor-24.30.1 → auto_editor-25.0.0}/auto_editor/formats/shotcut.py +0 -0
  33. {auto_editor-24.30.1 → auto_editor-25.0.0}/auto_editor/formats/utils.py +0 -0
  34. {auto_editor-24.30.1 → auto_editor-25.0.0}/auto_editor/help.py +0 -0
  35. {auto_editor-24.30.1 → auto_editor-25.0.0}/auto_editor/lang/__init__.py +0 -0
  36. {auto_editor-24.30.1 → auto_editor-25.0.0}/auto_editor/lang/json.py +0 -0
  37. {auto_editor-24.30.1 → auto_editor-25.0.0}/auto_editor/lang/libmath.py +0 -0
  38. {auto_editor-24.30.1 → auto_editor-25.0.0}/auto_editor/lib/__init__.py +0 -0
  39. {auto_editor-24.30.1 → auto_editor-25.0.0}/auto_editor/lib/err.py +0 -0
  40. {auto_editor-24.30.1 → auto_editor-25.0.0}/auto_editor/render/__init__.py +0 -0
  41. {auto_editor-24.30.1 → auto_editor-25.0.0}/auto_editor/render/subtitle.py +0 -0
  42. {auto_editor-24.30.1 → auto_editor-25.0.0}/auto_editor/subcommands/__init__.py +0 -0
  43. {auto_editor-24.30.1 → auto_editor-25.0.0}/auto_editor/subcommands/desc.py +0 -0
  44. {auto_editor-24.30.1 → auto_editor-25.0.0}/auto_editor/subcommands/info.py +0 -0
  45. {auto_editor-24.30.1 → auto_editor-25.0.0}/auto_editor/subcommands/palet.py +0 -0
  46. {auto_editor-24.30.1 → auto_editor-25.0.0}/auto_editor/subcommands/subdump.py +0 -0
  47. {auto_editor-24.30.1 → auto_editor-25.0.0}/auto_editor/utils/__init__.py +0 -0
  48. {auto_editor-24.30.1 → auto_editor-25.0.0}/auto_editor/utils/bar.py +0 -0
  49. {auto_editor-24.30.1 → auto_editor-25.0.0}/auto_editor/utils/chunks.py +0 -0
  50. {auto_editor-24.30.1 → auto_editor-25.0.0}/auto_editor/utils/cmdkw.py +0 -0
  51. {auto_editor-24.30.1 → auto_editor-25.0.0}/auto_editor/utils/encoder.py +0 -0
  52. {auto_editor-24.30.1 → auto_editor-25.0.0}/auto_editor/utils/subtitle_tools.py +0 -0
  53. {auto_editor-24.30.1 → auto_editor-25.0.0}/auto_editor/validate_input.py +0 -0
  54. {auto_editor-24.30.1 → auto_editor-25.0.0}/auto_editor/wavfile.py +0 -0
  55. {auto_editor-24.30.1 → auto_editor-25.0.0}/auto_editor.egg-info/SOURCES.txt +0 -0
  56. {auto_editor-24.30.1 → auto_editor-25.0.0}/auto_editor.egg-info/dependency_links.txt +0 -0
  57. {auto_editor-24.30.1 → auto_editor-25.0.0}/auto_editor.egg-info/entry_points.txt +0 -0
  58. {auto_editor-24.30.1 → auto_editor-25.0.0}/auto_editor.egg-info/requires.txt +0 -0
  59. {auto_editor-24.30.1 → auto_editor-25.0.0}/auto_editor.egg-info/top_level.txt +0 -0
  60. {auto_editor-24.30.1 → auto_editor-25.0.0}/pyproject.toml +0 -0
  61. {auto_editor-24.30.1 → auto_editor-25.0.0}/setup.cfg +0 -0
@@ -1,6 +1,6 @@
1
1
  Metadata-Version: 2.1
2
2
  Name: auto-editor
3
- Version: 24.30.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
@@ -0,0 +1 @@
1
+ __version__ = "25.0.0"
@@ -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()
@@ -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,
@@ -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
 
@@ -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,
@@ -620,42 +620,36 @@ def make_array(dtype: Sym, size: int, v: int = 0) -> np.ndarray:
620
620
  raise MyError(f"number too large to be converted to {dtype}")
621
621
 
622
622
 
623
- def minclip(oarr: BoolList, _min: int) -> BoolList:
623
+ def minclip(oarr: BoolList, _min: int, /) -> BoolList:
624
624
  arr = np.copy(oarr)
625
625
  mut_remove_small(arr, _min, replace=1, with_=0)
626
626
  return arr
627
627
 
628
628
 
629
- def mincut(oarr: BoolList, _min: int) -> BoolList:
629
+ def mincut(oarr: BoolList, _min: int, /) -> BoolList:
630
630
  arr = np.copy(oarr)
631
631
  mut_remove_small(arr, _min, replace=0, with_=1)
632
632
  return arr
633
633
 
634
634
 
635
- def maxclip(oarr: BoolList, _min: int) -> BoolList:
635
+ def maxclip(oarr: BoolList, _min: int, /) -> BoolList:
636
636
  arr = np.copy(oarr)
637
637
  mut_remove_large(arr, _min, replace=1, with_=0)
638
638
  return arr
639
639
 
640
640
 
641
- def maxcut(oarr: BoolList, _min: int) -> BoolList:
641
+ def maxcut(oarr: BoolList, _min: int, /) -> BoolList:
642
642
  arr = np.copy(oarr)
643
643
  mut_remove_large(arr, _min, replace=0, with_=1)
644
644
  return arr
645
645
 
646
646
 
647
- def margin(a: int, b: Any, c: Any = None) -> BoolList:
648
- if c is None:
649
- check_args("margin", [a, b], (2, 2), (is_int, is_boolarr))
650
- oarr = b
651
- start, end = a, a
652
- else:
653
- check_args("margin", [a, b, c], (3, 3), (is_int, is_int, is_boolarr))
654
- oarr = c
655
- start, end = a, b
656
-
647
+ def margin(oarr: BoolList, start: int, end: int | None = None, /) -> BoolList:
657
648
  arr = np.copy(oarr)
658
- mut_margin(arr, start, end)
649
+ if end is None:
650
+ mut_margin(arr, start, start)
651
+ else:
652
+ mut_margin(arr, start, end)
659
653
  return arr
660
654
 
661
655
 
@@ -1493,12 +1487,12 @@ def edit_audio(
1493
1487
  mincut: int = 6,
1494
1488
  minclip: int = 3,
1495
1489
  ) -> np.ndarray:
1496
- if "@levels" not in env or "@filesetup" not in env:
1490
+ if "@levels" not in env:
1497
1491
  raise MyError("Can't use `audio` if there's no input media")
1498
1492
 
1499
1493
  levels = env["@levels"]
1500
- src = env["@filesetup"].src
1501
- strict = env["@filesetup"].strict
1494
+ src = levels.src
1495
+ strict = levels.strict
1502
1496
 
1503
1497
  stream_data: NDArray[np.bool_] | None = None
1504
1498
  if stream == Sym("all"):
@@ -1537,11 +1531,10 @@ def edit_motion(
1537
1531
  raise MyError("Can't use `motion` if there's no input media")
1538
1532
 
1539
1533
  levels = env["@levels"]
1540
- strict = env["@filesetup"].strict
1541
1534
  try:
1542
1535
  return levels.motion(stream, blur, width) >= threshold
1543
1536
  except LevelError as e:
1544
- return raise_(e) if strict else levels.all()
1537
+ return raise_(e) if levels.strict else levels.all()
1545
1538
 
1546
1539
 
1547
1540
  def edit_subtitle(pattern, stream=0, **kwargs):
@@ -1549,7 +1542,6 @@ def edit_subtitle(pattern, stream=0, **kwargs):
1549
1542
  raise MyError("Can't use `subtitle` if there's no input media")
1550
1543
 
1551
1544
  levels = env["@levels"]
1552
- strict = env["@filesetup"].strict
1553
1545
  if "ignore-case" not in kwargs:
1554
1546
  kwargs["ignore-case"] = False
1555
1547
  if "max-count" not in kwargs:
@@ -1559,7 +1551,7 @@ def edit_subtitle(pattern, stream=0, **kwargs):
1559
1551
  try:
1560
1552
  return levels.subtitle(pattern, stream, ignore_case, max_count)
1561
1553
  except LevelError as e:
1562
- return raise_(e) if strict else levels.all()
1554
+ return raise_(e) if levels.strict else levels.all()
1563
1555
 
1564
1556
 
1565
1557
  def my_eval(env: Env, node: object) -> Any:
@@ -1741,6 +1733,8 @@ env.update({
1741
1733
  "round": Proc("round", round, (1, 1), is_real),
1742
1734
  "max": Proc("max", lambda *v: max(v), (1, None), is_real),
1743
1735
  "min": Proc("min", lambda *v: min(v), (1, None), is_real),
1736
+ "max-seq": Proc("max-seq", max, (1, 1), is_sequence),
1737
+ "min-seq": Proc("min-seq", min, (1, 1), is_sequence),
1744
1738
  "mod": Proc("mod", mod, (2, 2), is_int),
1745
1739
  "modulo": Proc("modulo", mod, (2, 2), is_int),
1746
1740
  # symbols
@@ -1796,7 +1790,7 @@ env.update({
1796
1790
  "bool-array": Proc(
1797
1791
  "bool-array", lambda *a: np.array(a, dtype=np.bool_), (1, None), is_nat
1798
1792
  ),
1799
- "margin": Proc("margin", margin, (2, 3)),
1793
+ "margin": Proc("margin", margin, (2, 3), is_boolarr, is_int),
1800
1794
  "mincut": Proc("mincut", mincut, (2, 2), is_boolarr, is_nat),
1801
1795
  "minclip": Proc("minclip", minclip, (2, 2), is_boolarr, is_nat),
1802
1796
  "maxcut": Proc("maxcut", maxcut, (2, 2), is_boolarr, is_nat),
@@ -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:
@@ -185,6 +185,8 @@ def display_str(val: object) -> str:
185
185
  return f"{val.real}{join}{val.imag}i"
186
186
  if type(val) is np.bool_:
187
187
  return "1" if val else "0"
188
+ if type(val) is np.float64 or type(val) is np.float32:
189
+ return f"{float(val)}"
188
190
  if type(val) is Fraction:
189
191
  return f"{val.numerator}/{val.denominator}"
190
192
 
@@ -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)
@@ -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:
@@ -227,7 +225,7 @@ def mux_quality_media(
227
225
  cmd.extend(["-color_range", f"{color_range}"])
228
226
  if colorspace in (0, 1) or (colorspace >= 3 and colorspace < 16):
229
227
  cmd.extend(["-colorspace", f"{colorspace}"])
230
- if color_prim in (0, 1) or (color_prim >= 4 and color_prim < 17):
228
+ if color_prim == 1 or (color_prim >= 4 and color_prim < 17):
231
229
  cmd.extend(["-color_primaries", f"{color_prim}"])
232
230
  if color_trc == 1 or (color_trc >= 4 and color_trc < 22):
233
231
  cmd.extend(["-color_trc", f"{color_trc}"])
@@ -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