auto-editor 24.30.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 +1 -2
- auto_editor/__main__.py +12 -19
- auto_editor/analyze.py +26 -30
- auto_editor/edit.py +8 -11
- auto_editor/ffwrapper.py +1 -1
- auto_editor/lang/palet.py +17 -23
- auto_editor/lib/contracts.py +5 -2
- auto_editor/lib/data_structs.py +2 -0
- auto_editor/make_layers.py +28 -47
- auto_editor/output.py +4 -6
- auto_editor/preview.py +2 -2
- auto_editor/render/audio.py +2 -1
- auto_editor/render/video.py +2 -7
- auto_editor/subcommands/levels.py +4 -9
- auto_editor/subcommands/repl.py +4 -7
- auto_editor/subcommands/test.py +33 -34
- auto_editor/timeline.py +10 -12
- auto_editor/utils/container.py +4 -0
- auto_editor/utils/func.py +10 -26
- auto_editor/utils/log.py +35 -8
- auto_editor/utils/types.py +2 -1
- auto_editor/vanparse.py +1 -2
- {auto_editor-24.30.1.dist-info → auto_editor-25.0.0.dist-info}/METADATA +1 -1
- auto_editor-25.0.0.dist-info/RECORD +55 -0
- {auto_editor-24.30.1.dist-info → auto_editor-25.0.0.dist-info}/WHEEL +1 -1
- auto_editor-24.30.1.dist-info/RECORD +0 -55
- {auto_editor-24.30.1.dist-info → auto_editor-25.0.0.dist-info}/LICENSE +0 -0
- {auto_editor-24.30.1.dist-info → auto_editor-25.0.0.dist-info}/entry_points.txt +0 -0
- {auto_editor-24.30.1.dist-info → auto_editor-25.0.0.dist-info}/top_level.txt +0 -0
auto_editor/__init__.py
CHANGED
@@ -1,2 +1 @@
|
|
1
|
-
__version__ = "
|
2
|
-
version = "24w30a"
|
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(
|
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.
|
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
|
-
|
324
|
-
log = Log(args.debug, args.quiet,
|
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,
|
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
|
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[
|
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
|
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[
|
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
|
-
|
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
|
-
|
214
|
-
|
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]
|
231
|
-
|
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.
|
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.
|
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.
|
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}
|
267
|
+
return self.cache(result[:index], "audio", {"stream": stream})
|
272
268
|
|
273
|
-
def motion(self, stream: int, blur: int, width: int) -> NDArray[np.
|
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.
|
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.
|
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
|
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,
|
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,
|
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,
|
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,
|
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,
|
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
auto_editor/lang/palet.py
CHANGED
@@ -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(
|
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
|
-
|
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
|
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 =
|
1501
|
-
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),
|
auto_editor/lib/contracts.py
CHANGED
@@ -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
|
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:
|
auto_editor/lib/data_structs.py
CHANGED
@@ -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
|
|
auto_editor/make_layers.py
CHANGED
@@ -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
|
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
|
-
|
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
|
-
|
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,
|
175
|
-
src_index = concat((src_index, np.full(len(
|
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:
|
@@ -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
|
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}"])
|
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,
|
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"),
|
68
|
+
in_len += Levels(src, tb, Bar("none"), False, log, False).media_length
|
69
69
|
|
70
70
|
out_len = tl.out_len()
|
71
71
|
|
auto_editor/render/audio.py
CHANGED
@@ -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,
|
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")
|