auto-editor 28.0.2__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.
Files changed (58) hide show
  1. {auto_editor-28.0.2.dist-info → auto_editor-29.0.0.dist-info}/METADATA +5 -4
  2. auto_editor-29.0.0.dist-info/RECORD +5 -0
  3. auto_editor-29.0.0.dist-info/top_level.txt +1 -0
  4. auto_editor/__init__.py +0 -1
  5. auto_editor/__main__.py +0 -503
  6. auto_editor/analyze.py +0 -393
  7. auto_editor/cmds/__init__.py +0 -0
  8. auto_editor/cmds/cache.py +0 -69
  9. auto_editor/cmds/desc.py +0 -32
  10. auto_editor/cmds/info.py +0 -213
  11. auto_editor/cmds/levels.py +0 -199
  12. auto_editor/cmds/palet.py +0 -29
  13. auto_editor/cmds/repl.py +0 -113
  14. auto_editor/cmds/subdump.py +0 -72
  15. auto_editor/cmds/test.py +0 -812
  16. auto_editor/edit.py +0 -548
  17. auto_editor/exports/__init__.py +0 -0
  18. auto_editor/exports/fcp11.py +0 -195
  19. auto_editor/exports/fcp7.py +0 -313
  20. auto_editor/exports/json.py +0 -63
  21. auto_editor/exports/shotcut.py +0 -147
  22. auto_editor/ffwrapper.py +0 -187
  23. auto_editor/help.py +0 -223
  24. auto_editor/imports/__init__.py +0 -0
  25. auto_editor/imports/fcp7.py +0 -275
  26. auto_editor/imports/json.py +0 -234
  27. auto_editor/json.py +0 -297
  28. auto_editor/lang/__init__.py +0 -0
  29. auto_editor/lang/libintrospection.py +0 -10
  30. auto_editor/lang/libmath.py +0 -23
  31. auto_editor/lang/palet.py +0 -724
  32. auto_editor/lang/stdenv.py +0 -1184
  33. auto_editor/lib/__init__.py +0 -0
  34. auto_editor/lib/contracts.py +0 -235
  35. auto_editor/lib/data_structs.py +0 -278
  36. auto_editor/lib/err.py +0 -2
  37. auto_editor/make_layers.py +0 -315
  38. auto_editor/preview.py +0 -93
  39. auto_editor/render/__init__.py +0 -0
  40. auto_editor/render/audio.py +0 -517
  41. auto_editor/render/subtitle.py +0 -205
  42. auto_editor/render/video.py +0 -312
  43. auto_editor/timeline.py +0 -331
  44. auto_editor/utils/__init__.py +0 -0
  45. auto_editor/utils/bar.py +0 -142
  46. auto_editor/utils/chunks.py +0 -2
  47. auto_editor/utils/cmdkw.py +0 -206
  48. auto_editor/utils/container.py +0 -102
  49. auto_editor/utils/func.py +0 -128
  50. auto_editor/utils/log.py +0 -124
  51. auto_editor/utils/types.py +0 -277
  52. auto_editor/vanparse.py +0 -313
  53. auto_editor-28.0.2.dist-info/RECORD +0 -56
  54. auto_editor-28.0.2.dist-info/entry_points.txt +0 -6
  55. auto_editor-28.0.2.dist-info/top_level.txt +0 -2
  56. docs/build.py +0 -70
  57. {auto_editor-28.0.2.dist-info → auto_editor-29.0.0.dist-info}/WHEEL +0 -0
  58. {auto_editor-28.0.2.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