auto-editor 28.1.0__py3-none-any.whl → 29.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-28.1.0.dist-info → auto_editor-29.0.0.dist-info}/METADATA +4 -3
- auto_editor-29.0.0.dist-info/RECORD +5 -0
- auto_editor-29.0.0.dist-info/top_level.txt +1 -0
- auto_editor/__init__.py +0 -1
- auto_editor/__main__.py +0 -504
- auto_editor/analyze.py +0 -393
- auto_editor/cmds/__init__.py +0 -0
- auto_editor/cmds/cache.py +0 -69
- auto_editor/cmds/desc.py +0 -32
- auto_editor/cmds/info.py +0 -213
- auto_editor/cmds/levels.py +0 -199
- auto_editor/cmds/palet.py +0 -29
- auto_editor/cmds/repl.py +0 -113
- auto_editor/cmds/subdump.py +0 -72
- auto_editor/cmds/test.py +0 -816
- auto_editor/edit.py +0 -560
- auto_editor/exports/__init__.py +0 -0
- auto_editor/exports/fcp11.py +0 -195
- auto_editor/exports/fcp7.py +0 -313
- auto_editor/exports/json.py +0 -63
- auto_editor/exports/kdenlive.py +0 -322
- auto_editor/exports/shotcut.py +0 -147
- auto_editor/ffwrapper.py +0 -187
- auto_editor/help.py +0 -224
- auto_editor/imports/__init__.py +0 -0
- auto_editor/imports/fcp7.py +0 -275
- auto_editor/imports/json.py +0 -234
- auto_editor/json.py +0 -297
- auto_editor/lang/__init__.py +0 -0
- auto_editor/lang/libintrospection.py +0 -10
- auto_editor/lang/libmath.py +0 -23
- auto_editor/lang/palet.py +0 -724
- auto_editor/lang/stdenv.py +0 -1179
- auto_editor/lib/__init__.py +0 -0
- auto_editor/lib/contracts.py +0 -235
- auto_editor/lib/data_structs.py +0 -278
- auto_editor/lib/err.py +0 -2
- auto_editor/make_layers.py +0 -315
- auto_editor/preview.py +0 -93
- auto_editor/render/__init__.py +0 -0
- auto_editor/render/audio.py +0 -517
- auto_editor/render/subtitle.py +0 -205
- auto_editor/render/video.py +0 -307
- auto_editor/timeline.py +0 -331
- auto_editor/utils/__init__.py +0 -0
- auto_editor/utils/bar.py +0 -142
- auto_editor/utils/chunks.py +0 -2
- auto_editor/utils/cmdkw.py +0 -206
- auto_editor/utils/container.py +0 -101
- auto_editor/utils/func.py +0 -128
- auto_editor/utils/log.py +0 -126
- auto_editor/utils/types.py +0 -277
- auto_editor/vanparse.py +0 -313
- auto_editor-28.1.0.dist-info/RECORD +0 -57
- auto_editor-28.1.0.dist-info/entry_points.txt +0 -6
- auto_editor-28.1.0.dist-info/top_level.txt +0 -2
- docs/build.py +0 -70
- {auto_editor-28.1.0.dist-info → auto_editor-29.0.0.dist-info}/WHEEL +0 -0
- {auto_editor-28.1.0.dist-info → auto_editor-29.0.0.dist-info}/licenses/LICENSE +0 -0
auto_editor/lang/palet.py
DELETED
@@ -1,724 +0,0 @@
|
|
1
|
-
"""
|
2
|
-
Palet is a light-weight scripting languge. It handles `--edit` and the `repl`.
|
3
|
-
The syntax is inspired by the Racket Programming language.
|
4
|
-
"""
|
5
|
-
|
6
|
-
from __future__ import annotations
|
7
|
-
|
8
|
-
from dataclasses import dataclass
|
9
|
-
from difflib import get_close_matches
|
10
|
-
from fractions import Fraction
|
11
|
-
from io import StringIO
|
12
|
-
from typing import TYPE_CHECKING, cast
|
13
|
-
|
14
|
-
import numpy as np
|
15
|
-
|
16
|
-
from auto_editor.analyze import LevelError, Levels, mut_remove_small
|
17
|
-
from auto_editor.lib.contracts import *
|
18
|
-
from auto_editor.lib.data_structs import *
|
19
|
-
from auto_editor.lib.err import MyError
|
20
|
-
from auto_editor.utils.func import boolop
|
21
|
-
|
22
|
-
if TYPE_CHECKING:
|
23
|
-
from collections.abc import Callable
|
24
|
-
from typing import Any, NoReturn, TypeGuard
|
25
|
-
|
26
|
-
from numpy.typing import NDArray
|
27
|
-
|
28
|
-
Node = tuple
|
29
|
-
|
30
|
-
|
31
|
-
class ClosingError(MyError):
|
32
|
-
pass
|
33
|
-
|
34
|
-
|
35
|
-
###############################################################################
|
36
|
-
# #
|
37
|
-
# LEXER #
|
38
|
-
# #
|
39
|
-
###############################################################################
|
40
|
-
|
41
|
-
LPAREN, RPAREN, LBRAC, RBRAC, LCUR, RCUR, EOF = "(", ")", "[", "]", "{", "}", "EOF"
|
42
|
-
VAL, QUOTE, SEC, DB, DOT, VLIT, M = "VAL", "QUOTE", "SEC", "DB", "DOT", "VLIT", "M"
|
43
|
-
SEC_UNITS = ("s", "sec", "secs", "second", "seconds")
|
44
|
-
brac_pairs = {LPAREN: RPAREN, LBRAC: RBRAC, LCUR: RCUR}
|
45
|
-
|
46
|
-
str_escape = {
|
47
|
-
"a": "\a",
|
48
|
-
"b": "\b",
|
49
|
-
"t": "\t",
|
50
|
-
"n": "\n",
|
51
|
-
"v": "\v",
|
52
|
-
"f": "\f",
|
53
|
-
"r": "\r",
|
54
|
-
'"': '"',
|
55
|
-
"\\": "\\",
|
56
|
-
}
|
57
|
-
|
58
|
-
|
59
|
-
@dataclass(slots=True)
|
60
|
-
class Token:
|
61
|
-
type: str
|
62
|
-
value: Any
|
63
|
-
lineno: int
|
64
|
-
column: int
|
65
|
-
|
66
|
-
|
67
|
-
class Lexer:
|
68
|
-
__slots__ = ("filename", "text", "pos", "char", "lineno", "column")
|
69
|
-
|
70
|
-
def __init__(self, filename: str, text: str):
|
71
|
-
self.filename = filename
|
72
|
-
self.text = text
|
73
|
-
self.pos: int = 0
|
74
|
-
self.lineno: int = 1
|
75
|
-
self.column: int = 1
|
76
|
-
self.char: str | None = self.text[self.pos] if text else None
|
77
|
-
|
78
|
-
def error(self, msg: str) -> NoReturn:
|
79
|
-
raise MyError(f"{msg}\n at {self.filename}:{self.lineno}:{self.column}")
|
80
|
-
|
81
|
-
def close_err(self, msg: str) -> NoReturn:
|
82
|
-
raise ClosingError(f"{msg}\n at {self.filename}:{self.lineno}:{self.column}")
|
83
|
-
|
84
|
-
def char_is_norm(self) -> bool:
|
85
|
-
return self.char is not None and self.char not in '()[]{}"; \t\n\r\x0b\x0c'
|
86
|
-
|
87
|
-
def advance(self) -> None:
|
88
|
-
if self.char == "\n":
|
89
|
-
self.lineno += 1
|
90
|
-
self.column = 0
|
91
|
-
|
92
|
-
self.pos += 1
|
93
|
-
|
94
|
-
if self.pos > len(self.text) - 1:
|
95
|
-
self.char = None
|
96
|
-
else:
|
97
|
-
self.char = self.text[self.pos]
|
98
|
-
self.column += 1
|
99
|
-
|
100
|
-
def peek(self) -> str | None:
|
101
|
-
peek_pos = self.pos + 1
|
102
|
-
return None if peek_pos > len(self.text) - 1 else self.text[peek_pos]
|
103
|
-
|
104
|
-
def is_whitespace(self) -> bool:
|
105
|
-
return self.char is None or self.char in " \t\n\r\x0b\x0c"
|
106
|
-
|
107
|
-
def string(self) -> str:
|
108
|
-
result = StringIO()
|
109
|
-
while self.char is not None and self.char != '"':
|
110
|
-
if self.char == "\\":
|
111
|
-
self.advance()
|
112
|
-
if self.char is None:
|
113
|
-
break
|
114
|
-
|
115
|
-
if self.char not in str_escape:
|
116
|
-
self.error(f"Unknown escape sequence `\\{self.char}` in string")
|
117
|
-
|
118
|
-
result.write(str_escape[self.char])
|
119
|
-
else:
|
120
|
-
result.write(self.char)
|
121
|
-
self.advance()
|
122
|
-
|
123
|
-
if self.char is None:
|
124
|
-
self.close_err('Expected a closing `"`')
|
125
|
-
|
126
|
-
self.advance()
|
127
|
-
return result.getvalue()
|
128
|
-
|
129
|
-
def number(self) -> Token:
|
130
|
-
buf = StringIO()
|
131
|
-
token = VAL
|
132
|
-
|
133
|
-
while self.char is not None and self.char in "+-0123456789./":
|
134
|
-
buf.write(self.char)
|
135
|
-
self.advance()
|
136
|
-
|
137
|
-
result = buf.getvalue()
|
138
|
-
del buf
|
139
|
-
|
140
|
-
unit = ""
|
141
|
-
if self.char_is_norm():
|
142
|
-
while self.char_is_norm():
|
143
|
-
assert self.char is not None
|
144
|
-
unit += self.char
|
145
|
-
self.advance()
|
146
|
-
|
147
|
-
if unit in SEC_UNITS:
|
148
|
-
token = SEC
|
149
|
-
elif unit == "dB":
|
150
|
-
token = DB
|
151
|
-
elif unit != "%":
|
152
|
-
return Token(
|
153
|
-
VAL,
|
154
|
-
Sym(result + unit, self.lineno, self.column),
|
155
|
-
self.lineno,
|
156
|
-
self.column,
|
157
|
-
)
|
158
|
-
|
159
|
-
try:
|
160
|
-
if unit == "%":
|
161
|
-
return Token(VAL, float(result) / 100, self.lineno, self.column)
|
162
|
-
elif "/" in result:
|
163
|
-
return Token(token, Fraction(result), self.lineno, self.column)
|
164
|
-
elif "." in result:
|
165
|
-
return Token(token, float(result), self.lineno, self.column)
|
166
|
-
else:
|
167
|
-
return Token(token, int(result), self.lineno, self.column)
|
168
|
-
except ValueError:
|
169
|
-
return Token(
|
170
|
-
VAL,
|
171
|
-
Sym(result + unit, self.lineno, self.column),
|
172
|
-
self.lineno,
|
173
|
-
self.column,
|
174
|
-
)
|
175
|
-
|
176
|
-
def hash_literal(self) -> Token:
|
177
|
-
if self.char == "\\":
|
178
|
-
self.advance()
|
179
|
-
if self.char is None:
|
180
|
-
self.close_err("Expected a character after #\\")
|
181
|
-
|
182
|
-
char = self.char
|
183
|
-
self.advance()
|
184
|
-
return Token(VAL, Char(char), self.lineno, self.column)
|
185
|
-
|
186
|
-
if self.char == ":":
|
187
|
-
self.advance()
|
188
|
-
buf = StringIO()
|
189
|
-
while self.char_is_norm():
|
190
|
-
assert self.char is not None
|
191
|
-
buf.write(self.char)
|
192
|
-
self.advance()
|
193
|
-
|
194
|
-
return Token(VAL, Keyword(buf.getvalue()), self.lineno, self.column)
|
195
|
-
|
196
|
-
if self.char is not None and self.char in "([{":
|
197
|
-
brac_type = self.char
|
198
|
-
self.advance()
|
199
|
-
if self.char is None:
|
200
|
-
self.close_err(f"Expected a character after #{brac_type}")
|
201
|
-
return Token(VLIT, brac_pairs[brac_type], self.lineno, self.column)
|
202
|
-
|
203
|
-
buf = StringIO()
|
204
|
-
while self.char_is_norm():
|
205
|
-
assert self.char is not None
|
206
|
-
buf.write(self.char)
|
207
|
-
self.advance()
|
208
|
-
|
209
|
-
result = buf.getvalue()
|
210
|
-
if result in {"t", "T", "true"}:
|
211
|
-
return Token(VAL, True, self.lineno, self.column)
|
212
|
-
|
213
|
-
if result in {"f", "F", "false"}:
|
214
|
-
return Token(VAL, False, self.lineno, self.column)
|
215
|
-
|
216
|
-
self.error(f"Unknown hash literal `#{result}`")
|
217
|
-
|
218
|
-
def get_next_token(self) -> Token:
|
219
|
-
while self.char is not None:
|
220
|
-
while self.char is not None and self.is_whitespace():
|
221
|
-
self.advance()
|
222
|
-
if self.char is None:
|
223
|
-
continue
|
224
|
-
|
225
|
-
if self.char == ";":
|
226
|
-
while self.char is not None and self.char != "\n":
|
227
|
-
self.advance()
|
228
|
-
continue
|
229
|
-
|
230
|
-
if self.char == '"':
|
231
|
-
self.advance()
|
232
|
-
my_str = self.string()
|
233
|
-
if self.char == ".": # handle `object.method` syntax
|
234
|
-
self.advance()
|
235
|
-
return Token(
|
236
|
-
DOT, (my_str, self.get_next_token()), self.lineno, self.column
|
237
|
-
)
|
238
|
-
return Token(VAL, my_str, self.lineno, self.column)
|
239
|
-
|
240
|
-
if self.char == "'":
|
241
|
-
self.advance()
|
242
|
-
return Token(QUOTE, "'", self.lineno, self.column)
|
243
|
-
|
244
|
-
if self.char in "(){}[]":
|
245
|
-
_par = self.char
|
246
|
-
self.advance()
|
247
|
-
return Token(_par, _par, self.lineno, self.column)
|
248
|
-
|
249
|
-
if self.char in "+-":
|
250
|
-
_peek = self.peek()
|
251
|
-
if _peek is not None and _peek in "0123456789.":
|
252
|
-
return self.number()
|
253
|
-
|
254
|
-
if self.char in "0123456789.":
|
255
|
-
return self.number()
|
256
|
-
|
257
|
-
if self.char == "#":
|
258
|
-
self.advance()
|
259
|
-
if self.char == "|":
|
260
|
-
success = False
|
261
|
-
while self.char is not None:
|
262
|
-
self.advance()
|
263
|
-
|
264
|
-
if self.char == "|" and self.peek() == "#":
|
265
|
-
self.advance()
|
266
|
-
self.advance()
|
267
|
-
success = True
|
268
|
-
break
|
269
|
-
|
270
|
-
if not success and self.char is None:
|
271
|
-
self.close_err("no closing `|#` for `#|` comment")
|
272
|
-
continue
|
273
|
-
|
274
|
-
elif self.char == "!" and self.peek() == "/":
|
275
|
-
self.advance()
|
276
|
-
self.advance()
|
277
|
-
while self.char is not None and self.char != "\n":
|
278
|
-
self.advance()
|
279
|
-
if self.char is None or self.char == "\n":
|
280
|
-
continue
|
281
|
-
else:
|
282
|
-
return self.hash_literal()
|
283
|
-
|
284
|
-
result = ""
|
285
|
-
has_illegal = False
|
286
|
-
|
287
|
-
def normal() -> bool:
|
288
|
-
return (
|
289
|
-
self.char is not None
|
290
|
-
and self.char not in '.()[]{}"; \t\n\r\x0b\x0c'
|
291
|
-
)
|
292
|
-
|
293
|
-
def handle_strings() -> bool:
|
294
|
-
nonlocal result
|
295
|
-
if self.char == '"':
|
296
|
-
self.advance()
|
297
|
-
result = f'{result}"{self.string()}"'
|
298
|
-
return handle_strings()
|
299
|
-
else:
|
300
|
-
return self.char_is_norm()
|
301
|
-
|
302
|
-
is_method = False
|
303
|
-
while normal():
|
304
|
-
if self.char == ":":
|
305
|
-
name = result
|
306
|
-
result = ""
|
307
|
-
is_method = True
|
308
|
-
normal = handle_strings
|
309
|
-
else:
|
310
|
-
result += self.char
|
311
|
-
|
312
|
-
if self.char in "'`|\\":
|
313
|
-
has_illegal = True
|
314
|
-
self.advance()
|
315
|
-
|
316
|
-
if is_method:
|
317
|
-
from auto_editor.utils.cmdkw import parse_method
|
318
|
-
|
319
|
-
return Token(M, parse_method(name, result), self.lineno, self.column)
|
320
|
-
|
321
|
-
if self.char == ".": # handle `object.method` syntax
|
322
|
-
self.advance()
|
323
|
-
return Token(
|
324
|
-
DOT,
|
325
|
-
(Sym(result, self.lineno, self.column), self.get_next_token()),
|
326
|
-
self.lineno,
|
327
|
-
self.column,
|
328
|
-
)
|
329
|
-
|
330
|
-
if has_illegal:
|
331
|
-
self.error(f"Symbol has illegal character(s): {result}")
|
332
|
-
|
333
|
-
return Token(
|
334
|
-
VAL, Sym(result, self.lineno, self.column), self.lineno, self.column
|
335
|
-
)
|
336
|
-
|
337
|
-
return Token(EOF, "EOF", self.lineno, self.column)
|
338
|
-
|
339
|
-
|
340
|
-
###############################################################################
|
341
|
-
# #
|
342
|
-
# PARSER #
|
343
|
-
# #
|
344
|
-
###############################################################################
|
345
|
-
|
346
|
-
|
347
|
-
class Parser:
|
348
|
-
def __init__(self, lexer: Lexer):
|
349
|
-
self.lexer = lexer
|
350
|
-
self.current_token = self.lexer.get_next_token()
|
351
|
-
|
352
|
-
def eat(self) -> None:
|
353
|
-
self.current_token = self.lexer.get_next_token()
|
354
|
-
|
355
|
-
def expr(self) -> Any:
|
356
|
-
token = self.current_token
|
357
|
-
lineno, column = token.lineno, token.column
|
358
|
-
|
359
|
-
if token.type == VAL:
|
360
|
-
self.eat()
|
361
|
-
return token.value
|
362
|
-
|
363
|
-
if token.type == VLIT:
|
364
|
-
self.eat()
|
365
|
-
literal_vec = []
|
366
|
-
while self.current_token.type != token.value:
|
367
|
-
literal_vec.append(self.expr())
|
368
|
-
if self.current_token.type == EOF:
|
369
|
-
raise ClosingError("Unclosed vector literal")
|
370
|
-
self.eat()
|
371
|
-
return literal_vec
|
372
|
-
|
373
|
-
# Handle unhygienic macros in next four cases
|
374
|
-
if token.type == SEC:
|
375
|
-
self.eat()
|
376
|
-
return (Sym("round"), (Sym("*"), token.value, Sym("timebase")))
|
377
|
-
|
378
|
-
if token.type == DB:
|
379
|
-
self.eat()
|
380
|
-
return (Sym("pow"), 10, (Sym("/"), token.value, 20))
|
381
|
-
|
382
|
-
if token.type == M:
|
383
|
-
self.eat()
|
384
|
-
name, args, kwargs = token.value
|
385
|
-
_result = [Sym(name, lineno, column)] + args
|
386
|
-
for key, val in kwargs.items():
|
387
|
-
_result.append(Keyword(key))
|
388
|
-
_result.append(val)
|
389
|
-
|
390
|
-
return tuple(_result)
|
391
|
-
|
392
|
-
if token.type == DOT:
|
393
|
-
self.eat()
|
394
|
-
if type(token.value[1].value) is not Sym:
|
395
|
-
raise MyError(". macro: attribute call needs to be an identifier")
|
396
|
-
|
397
|
-
return (Sym("@r"), token.value[0], token.value[1].value)
|
398
|
-
|
399
|
-
if token.type == QUOTE:
|
400
|
-
self.eat()
|
401
|
-
return (Sym("quote", lineno, column), self.expr())
|
402
|
-
|
403
|
-
if token.type in brac_pairs:
|
404
|
-
self.eat()
|
405
|
-
closing = brac_pairs[token.type]
|
406
|
-
childs = []
|
407
|
-
while self.current_token.type != closing:
|
408
|
-
if self.current_token.type == EOF:
|
409
|
-
raise ClosingError(f"Expected closing `{closing}` before end")
|
410
|
-
childs.append(self.expr())
|
411
|
-
|
412
|
-
self.eat()
|
413
|
-
return tuple(childs)
|
414
|
-
|
415
|
-
self.eat()
|
416
|
-
childs = []
|
417
|
-
while self.current_token.type not in {RPAREN, RBRAC, RCUR, EOF}:
|
418
|
-
childs.append(self.expr())
|
419
|
-
return tuple(childs)
|
420
|
-
|
421
|
-
def __str__(self) -> str:
|
422
|
-
result = str(self.expr())
|
423
|
-
|
424
|
-
self.lexer.pos = 0
|
425
|
-
self.lexer.char = self.lexer.text[0]
|
426
|
-
self.current_token = self.lexer.get_next_token()
|
427
|
-
|
428
|
-
return result
|
429
|
-
|
430
|
-
|
431
|
-
###############################################################################
|
432
|
-
# #
|
433
|
-
# ENVIRONMENT #
|
434
|
-
# #
|
435
|
-
###############################################################################
|
436
|
-
|
437
|
-
|
438
|
-
class Syntax:
|
439
|
-
__slots__ = "syn"
|
440
|
-
|
441
|
-
def __init__(self, syn: Callable[[Env, Node], Any]):
|
442
|
-
self.syn = syn
|
443
|
-
|
444
|
-
def __call__(self, env: Env, node: Node) -> Any:
|
445
|
-
return self.syn(env, node)
|
446
|
-
|
447
|
-
def __str__(self) -> str:
|
448
|
-
return "#<syntax>"
|
449
|
-
|
450
|
-
__repr__ = __str__
|
451
|
-
|
452
|
-
|
453
|
-
def ref(seq: Any, ref: int) -> Any:
|
454
|
-
try:
|
455
|
-
if type(seq) is str:
|
456
|
-
return Char(seq[ref])
|
457
|
-
if isinstance(seq, np.ndarray) and seq.dtype == np.bool_:
|
458
|
-
return int(seq[ref])
|
459
|
-
return seq[ref]
|
460
|
-
except (KeyError, IndexError, TypeError):
|
461
|
-
raise MyError(f"ref: Invalid key: {print_str(ref)}")
|
462
|
-
|
463
|
-
|
464
|
-
def p_slice(
|
465
|
-
seq: str | list | range | NDArray,
|
466
|
-
start: int = 0,
|
467
|
-
end: int | None = None,
|
468
|
-
step: int = 1,
|
469
|
-
) -> Any:
|
470
|
-
if end is None:
|
471
|
-
end = len(seq)
|
472
|
-
|
473
|
-
return seq[start:end:step]
|
474
|
-
|
475
|
-
|
476
|
-
def is_boolean_array(v: object) -> TypeGuard[np.ndarray]:
|
477
|
-
return isinstance(v, np.ndarray) and v.dtype.kind == "b"
|
478
|
-
|
479
|
-
|
480
|
-
is_iterable = Contract(
|
481
|
-
"iterable?",
|
482
|
-
lambda v: type(v) in {str, range, list, tuple, dict, Quoted}
|
483
|
-
or isinstance(v, np.ndarray),
|
484
|
-
)
|
485
|
-
is_boolarr = Contract("bool-array?", is_boolean_array)
|
486
|
-
|
487
|
-
|
488
|
-
def raise_(msg: str | Exception) -> NoReturn:
|
489
|
-
raise MyError(msg)
|
490
|
-
|
491
|
-
|
492
|
-
def edit_none() -> np.ndarray:
|
493
|
-
if "@levels" not in env:
|
494
|
-
raise MyError("Can't use `none` if there's no input media")
|
495
|
-
|
496
|
-
return env["@levels"].none()
|
497
|
-
|
498
|
-
|
499
|
-
def edit_all() -> np.ndarray:
|
500
|
-
if "@levels" not in env:
|
501
|
-
raise MyError("Can't use `all/e` if there's no input media")
|
502
|
-
|
503
|
-
return env["@levels"].all()
|
504
|
-
|
505
|
-
|
506
|
-
def audio_levels(stream: int) -> np.ndarray:
|
507
|
-
if "@levels" not in env:
|
508
|
-
raise MyError("Can't use `audio` if there's no input media")
|
509
|
-
|
510
|
-
try:
|
511
|
-
return env["@levels"].audio(stream)
|
512
|
-
except LevelError as e:
|
513
|
-
raise MyError(e)
|
514
|
-
|
515
|
-
|
516
|
-
def motion_levels(stream: int, blur: int = 9, width: int = 400) -> np.ndarray:
|
517
|
-
if "@levels" not in env:
|
518
|
-
raise MyError("Can't use `motion` if there's no input media")
|
519
|
-
|
520
|
-
try:
|
521
|
-
return env["@levels"].motion(stream, blur, width)
|
522
|
-
except LevelError as e:
|
523
|
-
raise MyError(e)
|
524
|
-
|
525
|
-
|
526
|
-
def edit_audio(
|
527
|
-
threshold: float = 0.04,
|
528
|
-
stream: object = Sym("all"),
|
529
|
-
mincut: int = 6,
|
530
|
-
minclip: int = 3,
|
531
|
-
) -> np.ndarray:
|
532
|
-
if "@levels" not in env:
|
533
|
-
raise MyError("Can't use `audio` if there's no input media")
|
534
|
-
|
535
|
-
levels = cast(Levels, env["@levels"])
|
536
|
-
stream_data: NDArray[np.bool_] | None = None
|
537
|
-
if stream == Sym("all"):
|
538
|
-
stream_range = range(0, len(levels.container.streams.audio))
|
539
|
-
else:
|
540
|
-
assert isinstance(stream, int)
|
541
|
-
stream_range = range(stream, stream + 1)
|
542
|
-
|
543
|
-
try:
|
544
|
-
for s in stream_range:
|
545
|
-
audio_list = levels.audio(s) >= threshold
|
546
|
-
if stream_data is None:
|
547
|
-
stream_data = audio_list
|
548
|
-
else:
|
549
|
-
stream_data = boolop(stream_data, audio_list, np.logical_or)
|
550
|
-
except LevelError:
|
551
|
-
return np.array([], dtype=np.bool_)
|
552
|
-
|
553
|
-
if stream_data is None:
|
554
|
-
return np.array([], dtype=np.bool_)
|
555
|
-
|
556
|
-
mut_remove_small(stream_data, minclip, replace=1, with_=0)
|
557
|
-
mut_remove_small(stream_data, mincut, replace=0, with_=1)
|
558
|
-
return stream_data
|
559
|
-
|
560
|
-
|
561
|
-
def edit_motion(
|
562
|
-
threshold: float = 0.02,
|
563
|
-
stream: int = 0,
|
564
|
-
blur: int = 9,
|
565
|
-
width: int = 400,
|
566
|
-
) -> np.ndarray:
|
567
|
-
if "@levels" not in env:
|
568
|
-
raise MyError("Can't use `motion` if there's no input media")
|
569
|
-
|
570
|
-
levels = cast(Levels, env["@levels"])
|
571
|
-
try:
|
572
|
-
return levels.motion(stream, blur, width) >= threshold
|
573
|
-
except LevelError:
|
574
|
-
return np.array([], dtype=np.bool_)
|
575
|
-
|
576
|
-
|
577
|
-
def edit_subtitle(pattern, stream=0, **kwargs):
|
578
|
-
if "@levels" not in env:
|
579
|
-
raise MyError("Can't use `subtitle` if there's no input media")
|
580
|
-
|
581
|
-
levels = cast(Levels, env["@levels"])
|
582
|
-
if "ignore-case" not in kwargs:
|
583
|
-
kwargs["ignore-case"] = False
|
584
|
-
if "max-count" not in kwargs:
|
585
|
-
kwargs["max-count"] = None
|
586
|
-
ignore_case = kwargs["ignore-case"]
|
587
|
-
max_count = kwargs["max-count"]
|
588
|
-
try:
|
589
|
-
return levels.subtitle(pattern, stream, ignore_case, max_count)
|
590
|
-
except LevelError:
|
591
|
-
return np.array([], dtype=np.bool_)
|
592
|
-
|
593
|
-
|
594
|
-
class StackTraceManager:
|
595
|
-
__slots__ = ("stack",)
|
596
|
-
|
597
|
-
def __init__(self) -> None:
|
598
|
-
self.stack: list[Sym] = []
|
599
|
-
|
600
|
-
def push(self, sym: Sym) -> None:
|
601
|
-
self.stack.append(sym)
|
602
|
-
|
603
|
-
def pop(self) -> None:
|
604
|
-
if self.stack:
|
605
|
-
self.stack.pop()
|
606
|
-
|
607
|
-
|
608
|
-
stack_trace_manager = StackTraceManager()
|
609
|
-
|
610
|
-
|
611
|
-
def my_eval(env: Env, node: object) -> Any:
|
612
|
-
def make_trace(sym: object) -> str:
|
613
|
-
return f" at {sym.val} ({sym.lineno}:{sym.column})" if type(sym) is Sym else ""
|
614
|
-
|
615
|
-
if type(node) is Sym:
|
616
|
-
val = env.get(node.val)
|
617
|
-
if type(val) is NotFound:
|
618
|
-
stacktrace = make_trace(node)
|
619
|
-
if mat := get_close_matches(node.val, env.data):
|
620
|
-
raise MyError(
|
621
|
-
f"variable `{node.val}` not found. Did you mean: {mat[0]}\n{stacktrace}"
|
622
|
-
)
|
623
|
-
raise MyError(
|
624
|
-
f"variable `{node.val}` not found. Did you mean a string literal.\n{stacktrace}"
|
625
|
-
)
|
626
|
-
return val
|
627
|
-
|
628
|
-
if type(node) is list:
|
629
|
-
return [my_eval(env, item) for item in node]
|
630
|
-
|
631
|
-
if type(node) is tuple:
|
632
|
-
if not node:
|
633
|
-
raise MyError("Illegal () expression")
|
634
|
-
|
635
|
-
oper = my_eval(env, node[0])
|
636
|
-
if isinstance(node[0], Sym):
|
637
|
-
stack_trace_manager.push(node[0])
|
638
|
-
|
639
|
-
try:
|
640
|
-
if not callable(oper):
|
641
|
-
"""
|
642
|
-
...No one wants to write (aref a x y) when they could write a[x,y].
|
643
|
-
In this particular case there is a way to finesse our way out of the
|
644
|
-
problem. If we treat data structures as if they were functions on indexes,
|
645
|
-
we could write (a x y) instead, which is even shorter than the Perl form.
|
646
|
-
"""
|
647
|
-
if is_iterable(oper):
|
648
|
-
length = len(node[1:])
|
649
|
-
if length > 3:
|
650
|
-
raise MyError(f"{print_str(node[0])}: slice expects 1 argument")
|
651
|
-
if length in {2, 3}:
|
652
|
-
return p_slice(oper, *(my_eval(env, c) for c in node[1:]))
|
653
|
-
if length == 1:
|
654
|
-
return ref(oper, my_eval(env, node[1]))
|
655
|
-
|
656
|
-
raise MyError(
|
657
|
-
f"{print_str(oper)} is not a function. Tried to run with args: {print_str(node[1:])}"
|
658
|
-
)
|
659
|
-
|
660
|
-
if type(oper) is Syntax:
|
661
|
-
return oper(env, node)
|
662
|
-
|
663
|
-
i = 1
|
664
|
-
args: list[Any] = []
|
665
|
-
kwargs: dict[str, Any] = {}
|
666
|
-
while i < len(node):
|
667
|
-
result = my_eval(env, node[i])
|
668
|
-
if type(result) is Keyword:
|
669
|
-
i += 1
|
670
|
-
if i >= len(node):
|
671
|
-
raise MyError("Keyword need argument")
|
672
|
-
kwargs[result.val] = my_eval(env, node[i])
|
673
|
-
else:
|
674
|
-
args.append(result)
|
675
|
-
i += 1
|
676
|
-
|
677
|
-
return oper(*args, **kwargs)
|
678
|
-
except MyError as e:
|
679
|
-
error_msg = str(e)
|
680
|
-
if not error_msg.endswith(make_trace(node[0])):
|
681
|
-
error_msg += f"\n{make_trace(node[0])}"
|
682
|
-
raise MyError(error_msg)
|
683
|
-
finally:
|
684
|
-
if isinstance(node[0], Sym):
|
685
|
-
stack_trace_manager.pop()
|
686
|
-
|
687
|
-
return node
|
688
|
-
|
689
|
-
|
690
|
-
# fmt: off
|
691
|
-
env = Env({})
|
692
|
-
env.update({
|
693
|
-
"none": Proc("none", edit_none, (0, 0)),
|
694
|
-
"all/e": Proc("all/e", edit_all, (0, 0)),
|
695
|
-
"audio-levels": Proc("audio-levels", audio_levels, (1, 1), is_nat),
|
696
|
-
"audio": Proc("audio", edit_audio, (0, 4),
|
697
|
-
is_threshold, orc(is_nat, Sym("all")), is_nat,
|
698
|
-
{"threshold": 0, "stream": 1, "minclip": 2, "mincut": 2}
|
699
|
-
),
|
700
|
-
"motion-levels": Proc("motion-levels", motion_levels, (1, 3), is_nat, is_nat1, {"blur": 1, "width": 2}),
|
701
|
-
"motion": Proc("motion", edit_motion, (0, 4),
|
702
|
-
is_threshold, is_nat, is_nat1,
|
703
|
-
{"threshold": 0, "stream": 1, "blur": 1, "width": 2}
|
704
|
-
),
|
705
|
-
"subtitle": Proc("subtitle", edit_subtitle, (1, 4),
|
706
|
-
is_str, is_nat, is_bool, orc(is_nat, is_void),
|
707
|
-
{"pattern": 0, "stream": 1, "ignore-case": 2, "max-count": 3}
|
708
|
-
),
|
709
|
-
})
|
710
|
-
# fmt: on
|
711
|
-
|
712
|
-
|
713
|
-
def interpret(env: Env, parser: Parser) -> list[object]:
|
714
|
-
result = []
|
715
|
-
|
716
|
-
try:
|
717
|
-
while parser.current_token.type != EOF:
|
718
|
-
result.append(my_eval(env, parser.expr()))
|
719
|
-
|
720
|
-
if type(result[-1]) is Keyword:
|
721
|
-
raise MyError(f"Keyword misused in expression. `{result[-1]}`")
|
722
|
-
except RecursionError:
|
723
|
-
raise MyError("maximum recursion depth exceeded")
|
724
|
-
return result
|