auto-editor 25.1.0__tar.gz → 25.2.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 (65) hide show
  1. {auto_editor-25.1.0 → auto_editor-25.2.0}/PKG-INFO +4 -4
  2. auto_editor-25.2.0/auto_editor/__init__.py +1 -0
  3. {auto_editor-25.1.0 → auto_editor-25.2.0}/auto_editor/lang/palet.py +118 -57
  4. {auto_editor-25.1.0 → auto_editor-25.2.0}/auto_editor/lang/stdenv.py +56 -5
  5. {auto_editor-25.1.0 → auto_editor-25.2.0}/auto_editor/lib/contracts.py +4 -0
  6. {auto_editor-25.1.0 → auto_editor-25.2.0}/auto_editor/lib/data_structs.py +4 -2
  7. {auto_editor-25.1.0 → auto_editor-25.2.0}/auto_editor/render/video.py +2 -19
  8. {auto_editor-25.1.0 → auto_editor-25.2.0}/auto_editor/timeline.py +1 -7
  9. {auto_editor-25.1.0 → auto_editor-25.2.0}/auto_editor/utils/cmdkw.py +5 -11
  10. {auto_editor-25.1.0 → auto_editor-25.2.0}/auto_editor/utils/types.py +0 -7
  11. {auto_editor-25.1.0 → auto_editor-25.2.0}/auto_editor.egg-info/PKG-INFO +4 -4
  12. auto_editor-25.2.0/auto_editor.egg-info/requires.txt +3 -0
  13. {auto_editor-25.1.0 → auto_editor-25.2.0}/pyproject.toml +3 -3
  14. auto_editor-25.1.0/auto_editor/__init__.py +0 -1
  15. auto_editor-25.1.0/auto_editor.egg-info/requires.txt +0 -3
  16. {auto_editor-25.1.0 → auto_editor-25.2.0}/LICENSE +0 -0
  17. {auto_editor-25.1.0 → auto_editor-25.2.0}/README.md +0 -0
  18. {auto_editor-25.1.0 → auto_editor-25.2.0}/auto_editor/__main__.py +0 -0
  19. {auto_editor-25.1.0 → auto_editor-25.2.0}/auto_editor/analyze.py +0 -0
  20. {auto_editor-25.1.0 → auto_editor-25.2.0}/auto_editor/edit.py +0 -0
  21. {auto_editor-25.1.0 → auto_editor-25.2.0}/auto_editor/ffwrapper.py +0 -0
  22. {auto_editor-25.1.0 → auto_editor-25.2.0}/auto_editor/formats/__init__.py +0 -0
  23. {auto_editor-25.1.0 → auto_editor-25.2.0}/auto_editor/formats/fcp11.py +0 -0
  24. {auto_editor-25.1.0 → auto_editor-25.2.0}/auto_editor/formats/fcp7.py +0 -0
  25. {auto_editor-25.1.0 → auto_editor-25.2.0}/auto_editor/formats/json.py +0 -0
  26. {auto_editor-25.1.0 → auto_editor-25.2.0}/auto_editor/formats/shotcut.py +0 -0
  27. {auto_editor-25.1.0 → auto_editor-25.2.0}/auto_editor/formats/utils.py +0 -0
  28. {auto_editor-25.1.0 → auto_editor-25.2.0}/auto_editor/help.py +0 -0
  29. {auto_editor-25.1.0 → auto_editor-25.2.0}/auto_editor/lang/__init__.py +0 -0
  30. {auto_editor-25.1.0 → auto_editor-25.2.0}/auto_editor/lang/json.py +0 -0
  31. {auto_editor-25.1.0 → auto_editor-25.2.0}/auto_editor/lang/libintrospection.py +0 -0
  32. {auto_editor-25.1.0 → auto_editor-25.2.0}/auto_editor/lang/libmath.py +0 -0
  33. {auto_editor-25.1.0 → auto_editor-25.2.0}/auto_editor/lib/__init__.py +0 -0
  34. {auto_editor-25.1.0 → auto_editor-25.2.0}/auto_editor/lib/err.py +0 -0
  35. {auto_editor-25.1.0 → auto_editor-25.2.0}/auto_editor/make_layers.py +0 -0
  36. {auto_editor-25.1.0 → auto_editor-25.2.0}/auto_editor/output.py +0 -0
  37. {auto_editor-25.1.0 → auto_editor-25.2.0}/auto_editor/preview.py +0 -0
  38. {auto_editor-25.1.0 → auto_editor-25.2.0}/auto_editor/render/__init__.py +0 -0
  39. {auto_editor-25.1.0 → auto_editor-25.2.0}/auto_editor/render/audio.py +0 -0
  40. {auto_editor-25.1.0 → auto_editor-25.2.0}/auto_editor/render/subtitle.py +0 -0
  41. {auto_editor-25.1.0 → auto_editor-25.2.0}/auto_editor/subcommands/__init__.py +0 -0
  42. {auto_editor-25.1.0 → auto_editor-25.2.0}/auto_editor/subcommands/desc.py +0 -0
  43. {auto_editor-25.1.0 → auto_editor-25.2.0}/auto_editor/subcommands/info.py +0 -0
  44. {auto_editor-25.1.0 → auto_editor-25.2.0}/auto_editor/subcommands/levels.py +0 -0
  45. {auto_editor-25.1.0 → auto_editor-25.2.0}/auto_editor/subcommands/palet.py +0 -0
  46. {auto_editor-25.1.0 → auto_editor-25.2.0}/auto_editor/subcommands/repl.py +0 -0
  47. {auto_editor-25.1.0 → auto_editor-25.2.0}/auto_editor/subcommands/subdump.py +0 -0
  48. {auto_editor-25.1.0 → auto_editor-25.2.0}/auto_editor/subcommands/test.py +0 -0
  49. {auto_editor-25.1.0 → auto_editor-25.2.0}/auto_editor/utils/__init__.py +0 -0
  50. {auto_editor-25.1.0 → auto_editor-25.2.0}/auto_editor/utils/bar.py +0 -0
  51. {auto_editor-25.1.0 → auto_editor-25.2.0}/auto_editor/utils/chunks.py +0 -0
  52. {auto_editor-25.1.0 → auto_editor-25.2.0}/auto_editor/utils/container.py +0 -0
  53. {auto_editor-25.1.0 → auto_editor-25.2.0}/auto_editor/utils/encoder.py +0 -0
  54. {auto_editor-25.1.0 → auto_editor-25.2.0}/auto_editor/utils/func.py +0 -0
  55. {auto_editor-25.1.0 → auto_editor-25.2.0}/auto_editor/utils/log.py +0 -0
  56. {auto_editor-25.1.0 → auto_editor-25.2.0}/auto_editor/utils/subtitle_tools.py +0 -0
  57. {auto_editor-25.1.0 → auto_editor-25.2.0}/auto_editor/validate_input.py +0 -0
  58. {auto_editor-25.1.0 → auto_editor-25.2.0}/auto_editor/vanparse.py +0 -0
  59. {auto_editor-25.1.0 → auto_editor-25.2.0}/auto_editor/wavfile.py +0 -0
  60. {auto_editor-25.1.0 → auto_editor-25.2.0}/auto_editor.egg-info/SOURCES.txt +0 -0
  61. {auto_editor-25.1.0 → auto_editor-25.2.0}/auto_editor.egg-info/dependency_links.txt +0 -0
  62. {auto_editor-25.1.0 → auto_editor-25.2.0}/auto_editor.egg-info/entry_points.txt +0 -0
  63. {auto_editor-25.1.0 → auto_editor-25.2.0}/auto_editor.egg-info/top_level.txt +0 -0
  64. {auto_editor-25.1.0 → auto_editor-25.2.0}/docs/build.py +0 -0
  65. {auto_editor-25.1.0 → auto_editor-25.2.0}/setup.cfg +0 -0
@@ -1,6 +1,6 @@
1
1
  Metadata-Version: 2.1
2
2
  Name: auto-editor
3
- Version: 25.1.0
3
+ Version: 25.2.0
4
4
  Summary: Auto-Editor: Effort free video editing!
5
5
  Author-email: WyattBlue <wyattblue@auto-editor.com>
6
6
  License: Unlicense
@@ -8,11 +8,11 @@ Project-URL: Bug Tracker, https://github.com/WyattBlue/auto-editor/issues
8
8
  Project-URL: Source Code, https://github.com/WyattBlue/auto-editor
9
9
  Project-URL: homepage, https://auto-editor.com
10
10
  Keywords: video,audio,media,editor,editing,processing,nonlinear,automatic,silence-detect,silence-removal,silence-speedup,motion-detection
11
- Requires-Python: >=3.10
11
+ Requires-Python: <3.14,>=3.10
12
12
  Description-Content-Type: text/markdown
13
13
  License-File: LICENSE
14
- Requires-Dist: numpy>=1.23.0
15
- Requires-Dist: pyav==12.3.*
14
+ Requires-Dist: numpy<3.0,>=1.23.0
15
+ Requires-Dist: pyav==13.0.*
16
16
  Requires-Dist: ae-ffmpeg==1.2.*
17
17
 
18
18
  <p align="center"><img src="https://auto-editor.com/img/auto-editor-banner.webp" title="Auto-Editor" width="700"></p>
@@ -0,0 +1 @@
1
+ __version__ = "25.2.0"
@@ -60,6 +60,8 @@ str_escape = {
60
60
  class Token:
61
61
  type: str
62
62
  value: Any
63
+ lineno: int
64
+ column: int
63
65
 
64
66
 
65
67
  class Lexer:
@@ -156,21 +158,31 @@ class Lexer:
156
158
  elif unit == "dB":
157
159
  token = DB
158
160
  elif unit != "i" and unit != "%":
159
- return Token(VAL, Sym(result + unit))
161
+ return Token(
162
+ VAL,
163
+ Sym(result + unit, self.lineno, self.column),
164
+ self.lineno,
165
+ self.column,
166
+ )
160
167
 
161
168
  try:
162
169
  if unit == "i":
163
- return Token(VAL, complex(result + "j"))
170
+ return Token(VAL, complex(result + "j"), self.lineno, self.column)
164
171
  elif unit == "%":
165
- return Token(VAL, float(result) / 100)
172
+ return Token(VAL, float(result) / 100, self.lineno, self.column)
166
173
  elif "/" in result:
167
- return Token(token, Fraction(result))
174
+ return Token(token, Fraction(result), self.lineno, self.column)
168
175
  elif "." in result:
169
- return Token(token, float(result))
176
+ return Token(token, float(result), self.lineno, self.column)
170
177
  else:
171
- return Token(token, int(result))
178
+ return Token(token, int(result), self.lineno, self.column)
172
179
  except ValueError:
173
- return Token(VAL, Sym(result + unit))
180
+ return Token(
181
+ VAL,
182
+ Sym(result + unit, self.lineno, self.column),
183
+ self.lineno,
184
+ self.column,
185
+ )
174
186
 
175
187
  def hash_literal(self) -> Token:
176
188
  if self.char == "\\":
@@ -180,7 +192,7 @@ class Lexer:
180
192
 
181
193
  char = self.char
182
194
  self.advance()
183
- return Token(VAL, Char(char))
195
+ return Token(VAL, Char(char), self.lineno, self.column)
184
196
 
185
197
  if self.char == ":":
186
198
  self.advance()
@@ -190,14 +202,14 @@ class Lexer:
190
202
  buf.write(self.char)
191
203
  self.advance()
192
204
 
193
- return Token(VAL, Keyword(buf.getvalue()))
205
+ return Token(VAL, Keyword(buf.getvalue()), self.lineno, self.column)
194
206
 
195
207
  if self.char is not None and self.char in "([{":
196
208
  brac_type = self.char
197
209
  self.advance()
198
210
  if self.char is None:
199
211
  self.close_err(f"Expected a character after #{brac_type}")
200
- return Token(VLIT, brac_pairs[brac_type])
212
+ return Token(VLIT, brac_pairs[brac_type], self.lineno, self.column)
201
213
 
202
214
  buf = StringIO()
203
215
  while self.char_is_norm():
@@ -207,10 +219,10 @@ class Lexer:
207
219
 
208
220
  result = buf.getvalue()
209
221
  if result in ("t", "T", "true"):
210
- return Token(VAL, True)
222
+ return Token(VAL, True, self.lineno, self.column)
211
223
 
212
224
  if result in ("f", "F", "false"):
213
- return Token(VAL, False)
225
+ return Token(VAL, False, self.lineno, self.column)
214
226
 
215
227
  self.error(f"Unknown hash literal `#{result}`")
216
228
 
@@ -231,17 +243,19 @@ class Lexer:
231
243
  my_str = self.string()
232
244
  if self.char == ".": # handle `object.method` syntax
233
245
  self.advance()
234
- return Token(DOT, (my_str, self.get_next_token()))
235
- return Token(VAL, my_str)
246
+ return Token(
247
+ DOT, (my_str, self.get_next_token()), self.lineno, self.column
248
+ )
249
+ return Token(VAL, my_str, self.lineno, self.column)
236
250
 
237
251
  if self.char == "'":
238
252
  self.advance()
239
- return Token(QUOTE, "'")
253
+ return Token(QUOTE, "'", self.lineno, self.column)
240
254
 
241
255
  if self.char in "(){}[]":
242
256
  _par = self.char
243
257
  self.advance()
244
- return Token(_par, _par)
258
+ return Token(_par, _par, self.lineno, self.column)
245
259
 
246
260
  if self.char in "+-":
247
261
  _peek = self.peek()
@@ -339,18 +353,27 @@ class Lexer:
339
353
  if is_method:
340
354
  from auto_editor.utils.cmdkw import parse_method
341
355
 
342
- return Token(M, parse_method(name, result, env))
356
+ return Token(
357
+ M, parse_method(name, result, env), self.lineno, self.column
358
+ )
343
359
 
344
360
  if self.char == ".": # handle `object.method` syntax
345
361
  self.advance()
346
- return Token(DOT, (Sym(result), self.get_next_token()))
362
+ return Token(
363
+ DOT,
364
+ (Sym(result, self.lineno, self.column), self.get_next_token()),
365
+ self.lineno,
366
+ self.column,
367
+ )
347
368
 
348
369
  if has_illegal:
349
370
  self.error(f"Symbol has illegal character(s): {result}")
350
371
 
351
- return Token(VAL, Sym(result))
372
+ return Token(
373
+ VAL, Sym(result, self.lineno, self.column), self.lineno, self.column
374
+ )
352
375
 
353
- return Token(EOF, "EOF")
376
+ return Token(EOF, "EOF", self.lineno, self.column)
354
377
 
355
378
 
356
379
  ###############################################################################
@@ -370,6 +393,7 @@ class Parser:
370
393
 
371
394
  def expr(self) -> Any:
372
395
  token = self.current_token
396
+ lineno, column = token.lineno, token.column
373
397
 
374
398
  if token.type == VAL:
375
399
  self.eat()
@@ -397,7 +421,7 @@ class Parser:
397
421
  if token.type == M:
398
422
  self.eat()
399
423
  name, args, kwargs = token.value
400
- _result = [Sym(name)] + args
424
+ _result = [Sym(name, lineno, column)] + args
401
425
  for key, val in kwargs.items():
402
426
  _result.append(Keyword(key))
403
427
  _result.append(val)
@@ -413,7 +437,7 @@ class Parser:
413
437
 
414
438
  if token.type == QUOTE:
415
439
  self.eat()
416
- return (Sym("quote"), self.expr())
440
+ return (Sym("quote", lineno, column), self.expr())
417
441
 
418
442
  if token.type in brac_pairs:
419
443
  self.eat()
@@ -610,16 +634,41 @@ def edit_subtitle(pattern, stream=0, **kwargs):
610
634
  return raise_(e) if levels.strict else levels.all()
611
635
 
612
636
 
637
+ class StackTraceManager:
638
+ def __init__(self) -> None:
639
+ self.stack: list[Sym] = []
640
+
641
+ def push(self, sym: Sym) -> None:
642
+ self.stack.append(sym)
643
+
644
+ def pop(self) -> None:
645
+ if self.stack:
646
+ self.stack.pop()
647
+
648
+ def get_stacktrace(self) -> str:
649
+ return "\n".join(
650
+ f" at {sym.val} ({sym.lineno}:{sym.column})"
651
+ for sym in reversed(self.stack)
652
+ )
653
+
654
+
655
+ stack_trace_manager = StackTraceManager()
656
+
657
+
613
658
  def my_eval(env: Env, node: object) -> Any:
659
+ def make_trace(sym: Sym) -> str:
660
+ return f" at {sym.val} ({sym.lineno}:{sym.column})"
661
+
614
662
  if type(node) is Sym:
615
663
  val = env.get(node.val)
616
664
  if type(val) is NotFound:
665
+ stacktrace = make_trace(node)
617
666
  if mat := get_close_matches(node.val, env.data):
618
667
  raise MyError(
619
- f"variable `{node.val}` not found. Did you mean: {mat[0]}"
668
+ f"variable `{node.val}` not found. Did you mean: {mat[0]}\n{stacktrace}"
620
669
  )
621
670
  raise MyError(
622
- f"variable `{node.val}` not found. Did you mean a string literal."
671
+ f"variable `{node.val}` not found. Did you mean a string literal.\n{stacktrace}"
623
672
  )
624
673
  return val
625
674
 
@@ -631,44 +680,56 @@ def my_eval(env: Env, node: object) -> Any:
631
680
  raise MyError("Illegal () expression")
632
681
 
633
682
  oper = my_eval(env, node[0])
634
- if not callable(oper):
635
- """
636
- ...No one wants to write (aref a x y) when they could write a[x,y].
637
- In this particular case there is a way to finesse our way out of the
638
- problem. If we treat data structures as if they were functions on indexes,
639
- we could write (a x y) instead, which is even shorter than the Perl form.
640
- """
641
- if is_iterable(oper):
642
- length = len(node[1:])
643
- if length > 3:
644
- raise MyError(f"{print_str(node[0])}: slice expects 1 argument")
645
- if length in (2, 3):
646
- return p_slice(oper, *(my_eval(env, c) for c in node[1:]))
647
- if length == 1:
648
- return ref(oper, my_eval(env, node[1]))
683
+ if isinstance(node[0], Sym):
684
+ stack_trace_manager.push(node[0])
649
685
 
650
- raise MyError(
651
- f"{print_str(oper)} is not a function. Tried to run with args: {print_str(node[1:])}"
652
- )
686
+ try:
687
+ if not callable(oper):
688
+ """
689
+ ...No one wants to write (aref a x y) when they could write a[x,y].
690
+ In this particular case there is a way to finesse our way out of the
691
+ problem. If we treat data structures as if they were functions on indexes,
692
+ we could write (a x y) instead, which is even shorter than the Perl form.
693
+ """
694
+ if is_iterable(oper):
695
+ length = len(node[1:])
696
+ if length > 3:
697
+ raise MyError(f"{print_str(node[0])}: slice expects 1 argument")
698
+ if length in (2, 3):
699
+ return p_slice(oper, *(my_eval(env, c) for c in node[1:]))
700
+ if length == 1:
701
+ return ref(oper, my_eval(env, node[1]))
653
702
 
654
- if type(oper) is Syntax:
655
- return oper(env, node)
703
+ raise MyError(
704
+ f"{print_str(oper)} is not a function. Tried to run with args: {print_str(node[1:])}"
705
+ )
656
706
 
657
- i = 1
658
- args: list[Any] = []
659
- kwargs: dict[str, Any] = {}
660
- while i < len(node):
661
- result = my_eval(env, node[i])
662
- if type(result) is Keyword:
707
+ if type(oper) is Syntax:
708
+ return oper(env, node)
709
+
710
+ i = 1
711
+ args: list[Any] = []
712
+ kwargs: dict[str, Any] = {}
713
+ while i < len(node):
714
+ result = my_eval(env, node[i])
715
+ if type(result) is Keyword:
716
+ i += 1
717
+ if i >= len(node):
718
+ raise MyError("Keyword need argument")
719
+ kwargs[result.val] = my_eval(env, node[i])
720
+ else:
721
+ args.append(result)
663
722
  i += 1
664
- if i >= len(node):
665
- raise MyError("Keyword need argument")
666
- kwargs[result.val] = my_eval(env, node[i])
667
- else:
668
- args.append(result)
669
- i += 1
670
723
 
671
- return oper(*args, **kwargs)
724
+ return oper(*args, **kwargs)
725
+ except MyError as e:
726
+ error_msg = str(e)
727
+ if not error_msg.endswith(make_trace(node[0])):
728
+ error_msg += f"\n{make_trace(node[0])}"
729
+ raise MyError(error_msg)
730
+ finally:
731
+ if isinstance(node[0], Sym):
732
+ stack_trace_manager.pop()
672
733
 
673
734
  return node
674
735
 
@@ -20,6 +20,7 @@ if TYPE_CHECKING:
20
20
 
21
21
 
22
22
  def make_standard_env() -> dict[str, Any]:
23
+ import os.path
23
24
  from cmath import sqrt as complex_sqrt
24
25
  from functools import reduce
25
26
  from operator import add, ge, gt, is_, le, lt, mod, mul
@@ -135,6 +136,22 @@ def make_standard_env() -> dict[str, Any]:
135
136
  Sym("float64"): np.float64,
136
137
  }
137
138
 
139
+ @dataclass(slots=True)
140
+ class InputPort:
141
+ name: str
142
+ port: Any
143
+ closed: bool
144
+
145
+ def close(self) -> None:
146
+ if not self.closed:
147
+ self.closed = True
148
+ self.port.close()
149
+
150
+ def __str__(self) -> str:
151
+ return f"#<input-port:{self.name}>"
152
+
153
+ __repr__ = __str__
154
+
138
155
  @dataclass(slots=True)
139
156
  class OutputPort:
140
157
  name: str
@@ -152,6 +169,13 @@ def make_standard_env() -> dict[str, Any]:
152
169
 
153
170
  __repr__ = __str__
154
171
 
172
+ def initInPort(name: str) -> InputPort | Literal[False]:
173
+ try:
174
+ port = open(name, encoding="utf-8")
175
+ except Exception:
176
+ return False
177
+ return InputPort(name, port, False)
178
+
155
179
  def initOutPort(name: str) -> OutputPort | Literal[False]:
156
180
  try:
157
181
  port = open(name, "w", encoding="utf-8")
@@ -198,14 +222,20 @@ def make_standard_env() -> dict[str, Any]:
198
222
  parms: list[str] = []
199
223
  for item in node[1]:
200
224
  if type(item) is not Sym:
201
- raise MyError(f"{node[0]}: must be an identifier")
225
+ raise MyError(f"{node[0]}: must be an identifier, got: {item} {type(item)}")
202
226
 
203
227
  parms.append(f"{item}")
204
228
 
205
229
  return UserProc(env, "", parms, (), node[2:])
206
230
 
207
231
  def syn_define(env: Env, node: Node) -> None:
232
+ if len(node) < 2:
233
+ raise MyError(f"{node[0]}: too few terms")
208
234
  if len(node) < 3:
235
+ if type(node[1]) is Sym:
236
+ raise MyError(f"{node[0]}: what should `{node[1]}` be defined as?")
237
+ elif type(node[1]) is tuple and len(node[1]) > 0:
238
+ raise MyError(f"{node[0]}: function `{node[1][0]}` needs a body.")
209
239
  raise MyError(f"{node[0]}: too few terms")
210
240
 
211
241
  if type(node[1]) is tuple:
@@ -437,6 +467,11 @@ def make_standard_env() -> dict[str, Any]:
437
467
  raise MyError(f"{node[0]}: Expected string? got: {print_str(num)}")
438
468
  env[name] += num
439
469
 
470
+ def syn_while(env: Env, node: Node) -> None:
471
+ while my_eval(env, node[1]) == True:
472
+ for c in node[2:]:
473
+ my_eval(env, c)
474
+
440
475
  def syn_for(env: Env, node: Node) -> None:
441
476
  var, my_iter = check_for_syntax(env, node)
442
477
 
@@ -808,6 +843,12 @@ def make_standard_env() -> dict[str, Any]:
808
843
  return f"{val.real}{join}{val.imag}i"
809
844
  return f"{val}"
810
845
 
846
+ def string_to_number(val) -> float:
847
+ try:
848
+ return float(val)
849
+ except Exception:
850
+ raise MyError(f"failed to convert {val} to number")
851
+
811
852
  def palet_join(v: Any, s: str) -> str:
812
853
  try:
813
854
  return s.join(v)
@@ -943,6 +984,7 @@ def make_standard_env() -> dict[str, Any]:
943
984
  # loops
944
985
  "for": Syntax(syn_for),
945
986
  "for-items": Syntax(syn_for_items),
987
+ "while": Syntax(syn_while),
946
988
  # contracts
947
989
  "number?": is_num,
948
990
  "real?": is_real,
@@ -1102,10 +1144,20 @@ def make_standard_env() -> dict[str, Any]:
1102
1144
  ),
1103
1145
  ),
1104
1146
  # i/o
1147
+ "file-exists?": Proc("file-exists", os.path.isfile, (1, 1), is_str),
1148
+ "open-input-file": Proc("open-input-file", initInPort, (1, 1), is_str),
1149
+ "input-port?": (ip := Contract("input-port?", lambda v: type(v) is InputPort)),
1150
+ "read-line": Proc("read-line",
1151
+ lambda f: (r := f.port.readline(), None if r == "" else r.rstrip())[-1],
1152
+ (1, 1), ip),
1105
1153
  "open-output-file": Proc("open-output-file", initOutPort, (1, 1), is_str),
1106
1154
  "output-port?": (op := Contract("output-port?", lambda v: type(v) is OutputPort)),
1107
- "close-port": Proc("close-port", OutputPort.close, (1, 1), op),
1108
- "closed?": Proc("closed?", lambda o: o.closed, (1, 1), op),
1155
+ "port?": (port := Contract("port?", orc(ip, op))),
1156
+ "close-port": Proc("close-port", lambda p: p.close, (1, 1), port),
1157
+ "closed?": Proc("closed?", lambda o: o.closed, (1, 1), port),
1158
+ # subprocess
1159
+ "system": Proc("system", palet_system, (1, 1), is_str),
1160
+ "system*": Proc("system*", palet_system_star, (1, None), is_str),
1109
1161
  # printing
1110
1162
  "display": Proc("display",
1111
1163
  lambda v, f=None: print(display_str(v), end="", file=f), (1, 2), any_p, op),
@@ -1119,10 +1171,9 @@ def make_standard_env() -> dict[str, Any]:
1119
1171
  "assert": Proc("assert", palet_assert, (1, 2), any_p, orc(is_str, False)),
1120
1172
  "error": Proc("error", raise_, (1, 1), is_str),
1121
1173
  "sleep": Proc("sleep", sleep, (1, 1), is_int_or_float),
1122
- "system": Proc("system", palet_system, (1, 1), is_str),
1123
- "system*": Proc("system*", palet_system_star, (1, None), is_str),
1124
1174
  # conversions
1125
1175
  "number->string": Proc("number->string", number_to_string, (1, 1), is_num),
1176
+ "string->number": Proc("string->number", string_to_number, (1, 1), is_str),
1126
1177
  "string->vector": Proc(
1127
1178
  "string->vector", lambda s: [Char(c) for c in s], (1, 1), is_str
1128
1179
  ),
@@ -118,6 +118,10 @@ class Proc:
118
118
 
119
119
  if kws is not None:
120
120
  for key, val in kwargs.items():
121
+ if key not in kws:
122
+ raise MyError(
123
+ f"{self.name} got an unexpected keyword argument: {key}"
124
+ )
121
125
  check = cont[-1] if kws[key] >= len(cont) else cont[kws[key]]
122
126
  if not check_contract(check, val):
123
127
  exp = f"{check}" if callable(check) else print_str(check)
@@ -54,12 +54,14 @@ class Env:
54
54
 
55
55
 
56
56
  class Sym:
57
- __slots__ = ("val", "hash")
57
+ __slots__ = ("val", "hash", "lineno", "column")
58
58
 
59
- def __init__(self, val: str):
59
+ def __init__(self, val: str, lineno: int = -1, column: int = -1):
60
60
  assert isinstance(val, str)
61
61
  self.val = val
62
62
  self.hash = hash(val)
63
+ self.lineno = lineno
64
+ self.column = column
63
65
 
64
66
  def __str__(self) -> str:
65
67
  return self.val
@@ -57,21 +57,6 @@ allowed_pix_fmt = {
57
57
  }
58
58
 
59
59
 
60
- def apply_anchor(x: int, y: int, w: int, h: int, anchor: str) -> tuple[int, int]:
61
- if anchor == "ce":
62
- x = (x * 2 - w) // 2
63
- y = (y * 2 - h) // 2
64
- if anchor == "tr":
65
- x -= w
66
- if anchor == "bl":
67
- y -= h
68
- if anchor == "br":
69
- x -= w
70
- y -= h
71
- # Use 'tl' by default
72
- return x, y
73
-
74
-
75
60
  def make_solid(width: int, height: int, pix_fmt: str, bg: str) -> av.VideoFrame:
76
61
  hex_color = bg.lstrip("#").upper()
77
62
  rgb_color = tuple(int(hex_color[i : i + 2], 16) for i in (0, 2, 4))
@@ -292,7 +277,7 @@ def render_av(
292
277
  frame = graph.vpull()
293
278
  elif isinstance(obj, TlRect):
294
279
  graph = av.filter.Graph()
295
- x, y = apply_anchor(obj.x, obj.y, obj.width, obj.height, obj.anchor)
280
+ x, y = obj.x, obj.y
296
281
  graph.link_nodes(
297
282
  graph.add_buffer(template=my_stream),
298
283
  graph.add(
@@ -307,9 +292,7 @@ def render_av(
307
292
  array = frame.to_ndarray(format="rgb24")
308
293
 
309
294
  overlay_h, overlay_w, _ = img.shape
310
- x_pos, y_pos = apply_anchor(
311
- obj.x, obj.y, overlay_w, overlay_h, obj.anchor
312
- )
295
+ x_pos, y_pos = obj.x, obj.y
313
296
 
314
297
  x_start = max(x_pos, 0)
315
298
  y_start = max(y_pos, 0)
@@ -5,7 +5,7 @@ from typing import TYPE_CHECKING
5
5
 
6
6
  from auto_editor.lib.contracts import *
7
7
  from auto_editor.utils.cmdkw import Required, pAttr, pAttrs
8
- from auto_editor.utils.types import anchor, color, natural, number, threshold
8
+ from auto_editor.utils.types import color, natural, number, threshold
9
9
 
10
10
  if TYPE_CHECKING:
11
11
  from collections.abc import Iterator
@@ -88,7 +88,6 @@ class TlImage:
88
88
  y: int
89
89
  width: int
90
90
  opacity: float
91
- anchor: str
92
91
 
93
92
  def as_dict(self) -> dict:
94
93
  return {
@@ -100,7 +99,6 @@ class TlImage:
100
99
  "y": self.y,
101
100
  "width": self.width,
102
101
  "opacity": self.opacity,
103
- "anchor": self.anchor,
104
102
  }
105
103
 
106
104
 
@@ -112,7 +110,6 @@ class TlRect:
112
110
  y: int
113
111
  width: int
114
112
  height: int
115
- anchor: str
116
113
  fill: str
117
114
 
118
115
  def as_dict(self) -> dict:
@@ -124,7 +121,6 @@ class TlRect:
124
121
  "y": self.y,
125
122
  "width": self.width,
126
123
  "height": self.height,
127
- "anchor": self.anchor,
128
124
  "fill": self.fill,
129
125
  }
130
126
 
@@ -157,7 +153,6 @@ img_builder = pAttrs(
157
153
  pAttr("y", Required, is_int, int),
158
154
  pAttr("width", 0, is_nat, natural),
159
155
  pAttr("opacity", 1, is_threshold, threshold),
160
- pAttr("anchor", "ce", is_str, anchor),
161
156
  )
162
157
  rect_builder = pAttrs(
163
158
  "rect",
@@ -167,7 +162,6 @@ rect_builder = pAttrs(
167
162
  pAttr("y", Required, is_int, int),
168
163
  pAttr("width", Required, is_int, int),
169
164
  pAttr("height", Required, is_int, int),
170
- pAttr("anchor", "ce", is_str, anchor),
171
165
  pAttr("fill", "#c4c4c4", is_str, color),
172
166
  )
173
167
  visual_objects = {
@@ -177,8 +177,7 @@ def parse_with_palet(
177
177
  def parse_method(
178
178
  name: str, text: str, env: Env
179
179
  ) -> tuple[str, list[Any], dict[str, Any]]:
180
- from auto_editor.lang.palet import Lexer, Parser, interpret
181
- from auto_editor.lib.err import MyError
180
+ from auto_editor.lang.palet import Lexer, Parser
182
181
 
183
182
  # Positional Arguments
184
183
  # audio:0.04,0,6,3
@@ -197,18 +196,13 @@ def parse_method(
197
196
  if "=" in arg:
198
197
  key, val = arg.split("=", 1)
199
198
 
200
- results = interpret(env, Parser(Lexer(name, val)))
201
- if not results:
202
- raise MyError("Results must be of length > 0")
203
-
204
- kwargs[key] = results[-1]
199
+ result = Parser(Lexer(name, val)).expr()
200
+ kwargs[key] = result
205
201
  allow_positional_args = False
206
202
 
207
203
  elif allow_positional_args:
208
- results = interpret(env, Parser(Lexer(name, arg)))
209
- if not results:
210
- raise MyError("Results must be of length > 0")
211
- args.append(results[-1])
204
+ result = Parser(Lexer(name, arg)).expr()
205
+ args.append(result)
212
206
  else:
213
207
  raise ParserError(f"{name} positional argument follows keyword argument.")
214
208
 
@@ -148,13 +148,6 @@ def time(val: str, tb: Fraction) -> int:
148
148
  return int(num)
149
149
 
150
150
 
151
- def anchor(val: str) -> str:
152
- allowed = ("tl", "tr", "bl", "br", "ce")
153
- if val not in allowed:
154
- raise CoerceError(f"Anchor must be: {' '.join(allowed)}")
155
- return val
156
-
157
-
158
151
  def margin(val: str) -> tuple[str, str]:
159
152
  vals = val.strip().split(",")
160
153
  if len(vals) == 1:
@@ -1,6 +1,6 @@
1
1
  Metadata-Version: 2.1
2
2
  Name: auto-editor
3
- Version: 25.1.0
3
+ Version: 25.2.0
4
4
  Summary: Auto-Editor: Effort free video editing!
5
5
  Author-email: WyattBlue <wyattblue@auto-editor.com>
6
6
  License: Unlicense
@@ -8,11 +8,11 @@ Project-URL: Bug Tracker, https://github.com/WyattBlue/auto-editor/issues
8
8
  Project-URL: Source Code, https://github.com/WyattBlue/auto-editor
9
9
  Project-URL: homepage, https://auto-editor.com
10
10
  Keywords: video,audio,media,editor,editing,processing,nonlinear,automatic,silence-detect,silence-removal,silence-speedup,motion-detection
11
- Requires-Python: >=3.10
11
+ Requires-Python: <3.14,>=3.10
12
12
  Description-Content-Type: text/markdown
13
13
  License-File: LICENSE
14
- Requires-Dist: numpy>=1.23.0
15
- Requires-Dist: pyav==12.3.*
14
+ Requires-Dist: numpy<3.0,>=1.23.0
15
+ Requires-Dist: pyav==13.0.*
16
16
  Requires-Dist: ae-ffmpeg==1.2.*
17
17
 
18
18
  <p align="center"><img src="https://auto-editor.com/img/auto-editor-banner.webp" title="Auto-Editor" width="700"></p>
@@ -0,0 +1,3 @@
1
+ numpy<3.0,>=1.23.0
2
+ pyav==13.0.*
3
+ ae-ffmpeg==1.2.*
@@ -7,10 +7,10 @@ description = "Auto-Editor: Effort free video editing!"
7
7
  readme = "README.md"
8
8
  license = { text = "Unlicense" }
9
9
  authors = [{ name = "WyattBlue", email = "wyattblue@auto-editor.com" }]
10
- requires-python = ">=3.10"
10
+ requires-python = ">=3.10,<3.14"
11
11
  dependencies = [
12
- "numpy>=1.23.0",
13
- "pyav==12.3.*",
12
+ "numpy>=1.23.0,<3.0",
13
+ "pyav==13.0.*",
14
14
  "ae-ffmpeg==1.2.*",
15
15
  ]
16
16
  keywords = [
@@ -1 +0,0 @@
1
- __version__ = "25.1.0"
@@ -1,3 +0,0 @@
1
- numpy>=1.23.0
2
- pyav==12.3.*
3
- ae-ffmpeg==1.2.*
File without changes
File without changes
File without changes
File without changes