auto-editor 27.1.1__py3-none-any.whl → 28.0.1__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 (37) hide show
  1. auto_editor/__init__.py +1 -1
  2. auto_editor/__main__.py +0 -8
  3. auto_editor/cmds/cache.py +1 -1
  4. auto_editor/cmds/desc.py +8 -9
  5. auto_editor/cmds/info.py +4 -17
  6. auto_editor/cmds/palet.py +1 -1
  7. auto_editor/cmds/subdump.py +2 -4
  8. auto_editor/cmds/test.py +50 -31
  9. auto_editor/edit.py +52 -52
  10. auto_editor/{formats → exports}/fcp11.py +11 -7
  11. auto_editor/{formats → exports}/fcp7.py +7 -239
  12. auto_editor/exports/json.py +32 -0
  13. auto_editor/{formats → exports}/shotcut.py +8 -17
  14. auto_editor/ffwrapper.py +1 -3
  15. auto_editor/help.py +6 -17
  16. auto_editor/imports/__init__.py +0 -0
  17. auto_editor/imports/fcp7.py +275 -0
  18. auto_editor/{formats → imports}/json.py +16 -43
  19. auto_editor/json.py +2 -2
  20. auto_editor/lang/palet.py +6 -43
  21. auto_editor/lang/stdenv.py +8 -20
  22. auto_editor/lib/contracts.py +4 -6
  23. auto_editor/lib/data_structs.py +0 -3
  24. auto_editor/make_layers.py +13 -13
  25. auto_editor/preview.py +1 -2
  26. auto_editor/render/audio.py +9 -6
  27. auto_editor/render/video.py +3 -2
  28. auto_editor/timeline.py +35 -52
  29. {auto_editor-27.1.1.dist-info → auto_editor-28.0.1.dist-info}/METADATA +2 -2
  30. auto_editor-28.0.1.dist-info/RECORD +56 -0
  31. {auto_editor-27.1.1.dist-info → auto_editor-28.0.1.dist-info}/WHEEL +1 -1
  32. auto_editor/formats/utils.py +0 -56
  33. auto_editor-27.1.1.dist-info/RECORD +0 -54
  34. /auto_editor/{formats → exports}/__init__.py +0 -0
  35. {auto_editor-27.1.1.dist-info → auto_editor-28.0.1.dist-info}/entry_points.txt +0 -0
  36. {auto_editor-27.1.1.dist-info → auto_editor-28.0.1.dist-info}/licenses/LICENSE +0 -0
  37. {auto_editor-27.1.1.dist-info → auto_editor-28.0.1.dist-info}/top_level.txt +0 -0
@@ -0,0 +1,275 @@
1
+ from __future__ import annotations
2
+
3
+ import xml.etree.ElementTree as ET
4
+ from fractions import Fraction
5
+ from typing import TYPE_CHECKING
6
+ from urllib.parse import unquote
7
+ from xml.etree.ElementTree import Element
8
+
9
+ from auto_editor.ffwrapper import FileInfo
10
+ from auto_editor.timeline import ASpace, Clip, Template, VSpace, v3
11
+
12
+ if TYPE_CHECKING:
13
+ from auto_editor.utils.log import Log
14
+
15
+
16
+ SUPPORTED_EFFECTS = ("timeremap",)
17
+
18
+
19
+ def show(ele: Element, limit: int, depth: int = 0) -> None:
20
+ print(
21
+ f"{' ' * (depth * 4)}<{ele.tag} {ele.attrib}> {ele.text.strip() if ele.text is not None else ''}"
22
+ )
23
+ for child in ele:
24
+ if isinstance(child, Element) and depth < limit:
25
+ show(child, limit, depth + 1)
26
+
27
+
28
+ def read_filters(clipitem: Element, log: Log) -> float:
29
+ for effect_tag in clipitem:
30
+ if effect_tag.tag in {"enabled", "start", "end"}:
31
+ continue
32
+ if len(effect_tag) < 3:
33
+ log.error("<effect> requires: <effectid> <name> and one <parameter>")
34
+ for i, effects in enumerate(effect_tag):
35
+ if i == 0 and effects.tag != "name":
36
+ log.error("<effect>: <name> must be first tag")
37
+ if i == 1 and effects.tag != "effectid":
38
+ log.error("<effect>: <effectid> must be second tag")
39
+ if effects.text not in SUPPORTED_EFFECTS:
40
+ log.error(f"`{effects.text}` is not a supported effect.")
41
+
42
+ if i > 1:
43
+ for j, parms in enumerate(effects):
44
+ if j == 0:
45
+ if parms.tag != "parameterid":
46
+ log.error("<parameter>: <parameterid> must be first tag")
47
+ if parms.text != "speed":
48
+ break
49
+
50
+ if j > 0 and parms.tag == "value":
51
+ if parms.text is None:
52
+ log.error("<value>: number required")
53
+ return float(parms.text) / 100
54
+
55
+ return 1.0
56
+
57
+
58
+ def uri_to_path(uri: str) -> str:
59
+ # Handle inputs like:
60
+ # /Users/wyattblue/projects/auto-editor/example.mp4
61
+ # file:///Users/wyattblue/projects/auto-editor/example.mp4
62
+ # file:///C:/Users/WyattBlue/projects/auto-editor/example.mp4
63
+ # file://localhost/Users/wyattblue/projects/auto-editor/example.mp4
64
+
65
+ if uri.startswith("file://localhost/"):
66
+ uri = uri[16:]
67
+ elif uri.startswith("file://"):
68
+ # Windows-style paths
69
+ uri = uri[8:] if len(uri) > 8 and uri[9] == ":" else uri[7:]
70
+ else:
71
+ return uri
72
+ return unquote(uri)
73
+
74
+
75
+ def read_tb_ntsc(tb: int, ntsc: bool) -> Fraction:
76
+ if ntsc:
77
+ if tb == 24:
78
+ return Fraction(24000, 1001)
79
+ if tb == 30:
80
+ return Fraction(30000, 1001)
81
+ if tb == 60:
82
+ return Fraction(60000, 1001)
83
+ return tb * Fraction(999, 1000)
84
+
85
+ return Fraction(tb)
86
+
87
+
88
+ def fcp7_read_xml(path: str, log: Log) -> v3:
89
+ def xml_bool(val: str) -> bool:
90
+ if val == "TRUE":
91
+ return True
92
+ if val == "FALSE":
93
+ return False
94
+ raise TypeError("Value must be 'TRUE' or 'FALSE'")
95
+
96
+ try:
97
+ tree = ET.parse(path)
98
+ except FileNotFoundError:
99
+ log.error(f"Could not find '{path}'")
100
+
101
+ root = tree.getroot()
102
+
103
+ def parse(ele: Element, schema: dict) -> dict:
104
+ new: dict = {}
105
+ for key, val in schema.items():
106
+ if isinstance(val, dict) and "__arr" in val:
107
+ new[key] = []
108
+
109
+ is_arr = False
110
+ for child in ele:
111
+ if child.tag not in schema:
112
+ continue
113
+
114
+ if schema[child.tag] is None:
115
+ new[child.tag] = child
116
+ continue
117
+
118
+ if isinstance(schema[child.tag], dict):
119
+ val = parse(child, schema[child.tag])
120
+ is_arr = "__arr" in schema[child.tag]
121
+ else:
122
+ val = schema[child.tag](child.text)
123
+
124
+ if child.tag in new:
125
+ if not is_arr:
126
+ log.error(f"<{child.tag}> can only occur once")
127
+ new[child.tag].append(val)
128
+ else:
129
+ new[child.tag] = [val] if is_arr else val
130
+
131
+ return new
132
+
133
+ def check(ele: Element, tag: str) -> None:
134
+ if tag != ele.tag:
135
+ log.error(f"Expected '{tag}' tag, got '{ele.tag}'")
136
+
137
+ check(root, "xmeml")
138
+ check(root[0], "sequence")
139
+ result = parse(
140
+ root[0],
141
+ {
142
+ "name": str,
143
+ "duration": int,
144
+ "rate": {
145
+ "timebase": Fraction,
146
+ "ntsc": xml_bool,
147
+ },
148
+ "media": None,
149
+ },
150
+ )
151
+
152
+ tb = read_tb_ntsc(result["rate"]["timebase"], result["rate"]["ntsc"])
153
+ av = parse(
154
+ result["media"],
155
+ {
156
+ "video": None,
157
+ "audio": None,
158
+ },
159
+ )
160
+
161
+ sources: dict[str, FileInfo] = {}
162
+ vobjs: VSpace = []
163
+ aobjs: ASpace = []
164
+
165
+ vclip_schema = {
166
+ "format": {
167
+ "samplecharacteristics": {
168
+ "width": int,
169
+ "height": int,
170
+ },
171
+ },
172
+ "track": {
173
+ "__arr": "",
174
+ "clipitem": {
175
+ "__arr": "",
176
+ "start": int,
177
+ "end": int,
178
+ "in": int,
179
+ "out": int,
180
+ "file": None,
181
+ "filter": None,
182
+ },
183
+ },
184
+ }
185
+
186
+ aclip_schema = {
187
+ "format": {"samplecharacteristics": {"samplerate": int}},
188
+ "track": {
189
+ "__arr": "",
190
+ "clipitem": {
191
+ "__arr": "",
192
+ "start": int,
193
+ "end": int,
194
+ "in": int,
195
+ "out": int,
196
+ "file": None,
197
+ "filter": None,
198
+ },
199
+ },
200
+ }
201
+
202
+ sr = 48000
203
+ res = (1920, 1080)
204
+
205
+ if "video" in av:
206
+ tracks = parse(av["video"], vclip_schema)
207
+
208
+ if "format" in tracks:
209
+ width = tracks["format"]["samplecharacteristics"]["width"]
210
+ height = tracks["format"]["samplecharacteristics"]["height"]
211
+ res = width, height
212
+
213
+ for t, track in enumerate(tracks["track"]):
214
+ if len(track["clipitem"]) > 0:
215
+ vobjs.append([])
216
+ for clipitem in track["clipitem"]:
217
+ file_id = clipitem["file"].attrib["id"]
218
+ if file_id not in sources:
219
+ fileobj = parse(clipitem["file"], {"pathurl": str})
220
+
221
+ if "pathurl" in fileobj:
222
+ sources[file_id] = FileInfo.init(
223
+ uri_to_path(fileobj["pathurl"]),
224
+ log,
225
+ )
226
+ else:
227
+ show(clipitem["file"], 3)
228
+ log.error(
229
+ f"'pathurl' child element not found in {clipitem['file'].tag}"
230
+ )
231
+
232
+ if "filter" in clipitem:
233
+ speed = read_filters(clipitem["filter"], log)
234
+ else:
235
+ speed = 1.0
236
+
237
+ start = clipitem["start"]
238
+ dur = clipitem["end"] - start
239
+ offset = clipitem["in"]
240
+
241
+ vobjs[t].append(
242
+ Clip(start, dur, sources[file_id], offset, stream=0, speed=speed)
243
+ )
244
+
245
+ if "audio" in av:
246
+ tracks = parse(av["audio"], aclip_schema)
247
+ if "format" in tracks:
248
+ sr = tracks["format"]["samplecharacteristics"]["samplerate"]
249
+
250
+ for t, track in enumerate(tracks["track"]):
251
+ if len(track["clipitem"]) > 0:
252
+ aobjs.append([])
253
+ for clipitem in track["clipitem"]:
254
+ file_id = clipitem["file"].attrib["id"]
255
+ if file_id not in sources:
256
+ fileobj = parse(clipitem["file"], {"pathurl": str})
257
+ sources[file_id] = FileInfo.init(
258
+ uri_to_path(fileobj["pathurl"]), log
259
+ )
260
+
261
+ if "filter" in clipitem:
262
+ speed = read_filters(clipitem["filter"], log)
263
+ else:
264
+ speed = 1.0
265
+
266
+ start = clipitem["start"]
267
+ dur = clipitem["end"] - start
268
+ offset = clipitem["in"]
269
+
270
+ aobjs[t].append(
271
+ Clip(start, dur, sources[file_id], offset, stream=0, speed=speed)
272
+ )
273
+
274
+ T = Template.init(sources[next(iter(sources))], sr, res=res)
275
+ return v3(tb, "#000", T, vobjs, aobjs, v1=None)
@@ -1,32 +1,27 @@
1
1
  from __future__ import annotations
2
2
 
3
3
  import os
4
- import sys
5
4
  from difflib import get_close_matches
6
5
  from fractions import Fraction
7
- from typing import Any
6
+ from typing import TYPE_CHECKING, Any
8
7
 
9
8
  from auto_editor.ffwrapper import FileInfo
10
- from auto_editor.json import dump, load
9
+ from auto_editor.json import load
11
10
  from auto_editor.lib.err import MyError
12
11
  from auto_editor.timeline import (
13
- ASpace,
12
+ Clip,
14
13
  Template,
15
- TlAudio,
16
- TlVideo,
17
- VSpace,
18
14
  audio_builder,
19
15
  v1,
20
16
  v3,
21
17
  visual_objects,
22
18
  )
23
19
  from auto_editor.utils.cmdkw import ParserError, Required, pAttrs
24
- from auto_editor.utils.log import Log
25
20
  from auto_editor.utils.types import CoerceError
26
21
 
27
- """
28
- Make a pre-edited file reference that can be inputted back into auto-editor.
29
- """
22
+ if TYPE_CHECKING:
23
+ from auto_editor.timeline import ASpace, VSpace
24
+ from auto_editor.utils.log import Log
30
25
 
31
26
 
32
27
  def check_attrs(data: object, log: Log, *attrs: str) -> None:
@@ -116,7 +111,7 @@ def read_v3(tl: Any, log: Log) -> v3:
116
111
  tb = Fraction(tl["timebase"])
117
112
 
118
113
  v: Any = []
119
- a: list[list[TlAudio]] = []
114
+ a: list[list[Clip]] = []
120
115
 
121
116
  for vlayers in tl["v"]:
122
117
  if vlayers:
@@ -145,7 +140,7 @@ def read_v3(tl: Any, log: Log) -> v3:
145
140
  log.error(f"Unknown audio object: {adict['name']}")
146
141
 
147
142
  try:
148
- a_out.append(TlAudio(**parse_obj(adict, audio_builder)))
143
+ a_out.append(Clip(**parse_obj(adict, audio_builder)))
149
144
  except ParserError as e:
150
145
  log.error(e)
151
146
 
@@ -202,10 +197,10 @@ def read_v1(tl: Any, log: Log) -> v3:
202
197
  if src.videos:
203
198
  if len(vtl) == 0:
204
199
  vtl.append([])
205
- vtl[0].append(TlVideo(c.start, c.dur, c.src, c.offset, c.speed, 0))
200
+ vtl[0].append(Clip(c.start, c.dur, c.src, c.offset, 0, c.speed))
206
201
 
207
202
  for a in range(len(src.audios)):
208
- atl[a].append(TlAudio(c.start, c.dur, c.src, c.offset, c.speed, 1, a))
203
+ atl[a].append(Clip(c.start, c.dur, c.src, c.offset, a, c.speed))
209
204
 
210
205
  return v3(
211
206
  src.get_fps(),
@@ -218,11 +213,13 @@ def read_v1(tl: Any, log: Log) -> v3:
218
213
 
219
214
 
220
215
  def read_json(path: str, log: Log) -> v3:
221
- with open(path, encoding="utf-8", errors="ignore") as f:
222
- try:
216
+ try:
217
+ with open(path, encoding="utf-8", errors="ignore") as f:
223
218
  tl = load(path, f)
224
- except MyError as e:
225
- log.error(e)
219
+ except FileNotFoundError:
220
+ log.error(f"File not found: {path}")
221
+ except MyError as e:
222
+ log.error(e)
226
223
 
227
224
  check_attrs(tl, log, "version")
228
225
 
@@ -235,27 +232,3 @@ def read_json(path: str, log: Log) -> v3:
235
232
  if type(ver) is not str:
236
233
  log.error("version needs to be a string")
237
234
  log.error(f"Importing version {ver} timelines is not supported.")
238
-
239
-
240
- def make_json_timeline(ver: int, out: str | int, tl: v3, log: Log) -> None:
241
- if ver not in {3, 1}:
242
- log.error(f"Version {ver} is not supported!")
243
-
244
- if isinstance(out, str):
245
- if not out.endswith(".json"):
246
- log.error("Output extension must be .json")
247
- outfile: Any = open(out, "w")
248
- else:
249
- outfile = sys.stdout
250
-
251
- if ver == 3:
252
- dump(tl.as_dict(), outfile, indent=2)
253
- else:
254
- if tl.v1 is None:
255
- log.error("Timeline can't be converted to v1 format")
256
- dump(tl.v1.as_dict(), outfile, indent=2)
257
-
258
- if isinstance(out, str):
259
- outfile.close()
260
- else:
261
- print("") # Flush stdout
auto_editor/json.py CHANGED
@@ -89,7 +89,7 @@ class Lexer:
89
89
 
90
90
  if self.char == "u":
91
91
  buf = ""
92
- for i in range(4):
92
+ for _ in range(4):
93
93
  self.advance()
94
94
  if self.char is None:
95
95
  self.error("\\u escape sequence needs 4 hexs")
@@ -162,7 +162,7 @@ class Lexer:
162
162
  return (key, None)
163
163
 
164
164
  keyword = ""
165
- for i in range(5): # Longest valid keyword length
165
+ for _ in range(5): # Longest valid keyword length
166
166
  if self.char is None or self.char in " \t\n\r\x0b\x0c[]}{,":
167
167
  break
168
168
  keyword += self.char
auto_editor/lang/palet.py CHANGED
@@ -65,20 +65,11 @@ class Token:
65
65
 
66
66
 
67
67
  class Lexer:
68
- __slots__ = (
69
- "filename",
70
- "text",
71
- "allow_lang_prag",
72
- "pos",
73
- "char",
74
- "lineno",
75
- "column",
76
- )
77
-
78
- def __init__(self, filename: str, text: str, langprag: bool = False):
68
+ __slots__ = ("filename", "text", "pos", "char", "lineno", "column")
69
+
70
+ def __init__(self, filename: str, text: str):
79
71
  self.filename = filename
80
72
  self.text = text
81
- self.allow_lang_prag = langprag
82
73
  self.pos: int = 0
83
74
  self.lineno: int = 1
84
75
  self.column: int = 1
@@ -157,7 +148,7 @@ class Lexer:
157
148
  token = SEC
158
149
  elif unit == "dB":
159
150
  token = DB
160
- elif unit != "i" and unit != "%":
151
+ elif unit != "%":
161
152
  return Token(
162
153
  VAL,
163
154
  Sym(result + unit, self.lineno, self.column),
@@ -166,9 +157,7 @@ class Lexer:
166
157
  )
167
158
 
168
159
  try:
169
- if unit == "i":
170
- return Token(VAL, complex(result + "j"), self.lineno, self.column)
171
- elif unit == "%":
160
+ if unit == "%":
172
161
  return Token(VAL, float(result) / 100, self.lineno, self.column)
173
162
  elif "/" in result:
174
163
  return Token(token, Fraction(result), self.lineno, self.column)
@@ -289,32 +278,6 @@ class Lexer:
289
278
  self.advance()
290
279
  if self.char is None or self.char == "\n":
291
280
  continue
292
-
293
- elif self.char == "l" and self.peek() == "a":
294
- buf = StringIO()
295
- while self.char_is_norm():
296
- assert self.char is not None
297
- buf.write(self.char)
298
- self.advance()
299
-
300
- result = buf.getvalue()
301
- if result != "lang":
302
- self.error(f"Unknown hash literal `#{result}`")
303
- if not self.allow_lang_prag:
304
- self.error("#lang pragma is not allowed here")
305
-
306
- self.advance()
307
- buf = StringIO()
308
- while not self.is_whitespace():
309
- assert self.char is not None
310
- buf.write(self.char)
311
- self.advance()
312
-
313
- result = buf.getvalue()
314
- if result != "palet":
315
- self.error(f"Invalid #lang: {result}")
316
- self.allow_lang_prag = False
317
- continue
318
281
  else:
319
282
  return self.hash_literal()
320
283
 
@@ -747,7 +710,7 @@ env.update({
747
710
  # fmt: on
748
711
 
749
712
 
750
- def interpret(env: Env, parser: Parser) -> list:
713
+ def interpret(env: Env, parser: Parser) -> list[object]:
751
714
  result = []
752
715
 
753
716
  try:
@@ -1,5 +1,6 @@
1
1
  from __future__ import annotations
2
2
 
3
+ from dataclasses import dataclass
3
4
  from typing import TYPE_CHECKING
4
5
 
5
6
  import bv
@@ -7,23 +8,24 @@ import bv
7
8
  from auto_editor.analyze import mut_remove_large, mut_remove_small
8
9
  from auto_editor.lib.contracts import *
9
10
  from auto_editor.lib.data_structs import *
11
+ from auto_editor.lib.err import MyError
10
12
 
11
13
  from .palet import Syntax, env, is_boolarr, is_iterable, my_eval, p_slice, raise_, ref
12
14
 
13
15
  if TYPE_CHECKING:
16
+ from fractions import Fraction
14
17
  from typing import Any, Literal
15
18
 
16
19
  import numpy as np
17
20
  from numpy.typing import NDArray
18
21
 
19
- Number = int | float | complex | Fraction
22
+ Number = int | float | Fraction
20
23
  BoolList = NDArray[np.bool_]
21
24
  Node = tuple
22
25
 
23
26
 
24
27
  def make_standard_env() -> dict[str, Any]:
25
28
  import os.path
26
- from cmath import sqrt as complex_sqrt
27
29
  from functools import reduce
28
30
  from operator import add, ge, gt, is_, le, lt, mod, mul
29
31
  from subprocess import run
@@ -509,7 +511,7 @@ def make_standard_env() -> dict[str, Any]:
509
511
  for c in node[2:]:
510
512
  my_eval(env, c)
511
513
 
512
- def syn_quote(env: Env, node: Node) -> Any:
514
+ def syn_quote(_: Env, node: Node) -> Any:
513
515
  guard_term(node, 2, 2)
514
516
  if type(node[1]) is Keyword:
515
517
  return QuotedKeyword(node[1])
@@ -825,14 +827,6 @@ def make_standard_env() -> dict[str, Any]:
825
827
 
826
828
  return reduce(lambda a, b: a // b, m, n)
827
829
 
828
- def _sqrt(v: Number) -> Number:
829
- r = complex_sqrt(v)
830
- if r.imag == 0:
831
- if int(r.real) == r.real:
832
- return int(r.real)
833
- return r.real
834
- return r
835
-
836
830
  def _xor(*vals: Any) -> bool | BoolList:
837
831
  if is_boolarr(vals[0]):
838
832
  check_args("xor", vals, (2, None), (is_boolarr,))
@@ -841,9 +835,6 @@ def make_standard_env() -> dict[str, Any]:
841
835
  return reduce(lambda a, b: a ^ b, vals)
842
836
 
843
837
  def number_to_string(val: Number) -> str:
844
- if isinstance(val, complex):
845
- join = "" if val.imag < 0 else "+"
846
- return f"{val.real}{join}{val.imag}i"
847
838
  return f"{val}"
848
839
 
849
840
  def string_to_number(val) -> float:
@@ -928,7 +919,7 @@ def make_standard_env() -> dict[str, Any]:
928
919
  except Exception:
929
920
  raise MyError("hash-ref: invalid key")
930
921
 
931
- def hash_set(h: dict, k: object, v: object) -> None:
922
+ def hash_set(h: dict[object, object], k: object, v: object) -> None:
932
923
  h[k] = v
933
924
 
934
925
  def hash_remove(h: dict, v: object) -> None:
@@ -953,7 +944,7 @@ def make_standard_env() -> dict[str, Any]:
953
944
  except Exception:
954
945
  return False
955
946
 
956
- def change_file_ext(a, ext) -> str:
947
+ def change_file_ext(a: str, ext: str) -> str:
957
948
  import os.path
958
949
 
959
950
  base_name = os.path.splitext(a)[0]
@@ -968,6 +959,7 @@ def make_standard_env() -> dict[str, Any]:
968
959
  # syntax
969
960
  "lambda": Syntax(syn_lambda),
970
961
  "λ": Syntax(syn_lambda),
962
+ "defn": Syntax(syn_define),
971
963
  "define": Syntax(syn_define),
972
964
  "define/c": Syntax(syn_definec),
973
965
  "set!": Syntax(syn_set),
@@ -994,7 +986,6 @@ def make_standard_env() -> dict[str, Any]:
994
986
  "int?": is_int,
995
987
  "float?": is_float,
996
988
  "frac?": is_frac,
997
- "complex?": Contract("complex?", lambda v: type(v) is complex),
998
989
  "nat?": is_nat,
999
990
  "nat1?": is_nat1,
1000
991
  "threshold?": is_threshold,
@@ -1049,9 +1040,6 @@ def make_standard_env() -> dict[str, Any]:
1049
1040
  "div": Proc("div", int_div, (2, None), is_int),
1050
1041
  "add1": Proc("add1", lambda z: z + 1, (1, 1), is_num),
1051
1042
  "sub1": Proc("sub1", lambda z: z - 1, (1, 1), is_num),
1052
- "sqrt": Proc("sqrt", _sqrt, (1, 1), is_num),
1053
- "real-part": Proc("real-part", lambda v: v.real, (1, 1), is_num),
1054
- "imag-part": Proc("imag-part", lambda v: v.imag, (1, 1), is_num),
1055
1043
  # reals
1056
1044
  "pow": Proc("pow", pow, (2, 2), is_real),
1057
1045
  "abs": Proc("abs", abs, (1, 1), is_real),
@@ -46,7 +46,7 @@ def check_contract(c: object, val: object) -> bool:
46
46
  return val is True
47
47
  if c is False:
48
48
  return val is False
49
- if type(c) in (int, float, float64, Fraction, complex, str, Sym):
49
+ if type(c) in (int, float, float64, Fraction, str, Sym):
50
50
  return val == c
51
51
  raise MyError(f"Invalid contract, got: {print_str(c)}")
52
52
 
@@ -97,7 +97,7 @@ class Proc:
97
97
  self.kw_contracts = None
98
98
  self.contracts = c
99
99
 
100
- def __call__(self, *args: Any, **kwargs: Any):
100
+ def __call__(self, *args: Any, **kwargs: Any) -> Any:
101
101
  lower, upper = self.arity
102
102
  amount = len(args)
103
103
  cont = self.contracts
@@ -164,7 +164,7 @@ def is_contract(c: object) -> bool:
164
164
  return True
165
165
  if c is True or c is False:
166
166
  return True
167
- return type(c) in (int, float, Fraction, complex, str, Sym)
167
+ return type(c) in (int, float, Fraction, str, Sym)
168
168
 
169
169
 
170
170
  is_bool = Contract("bool?", lambda v: type(v) is bool)
@@ -172,10 +172,8 @@ is_int = Contract("int?", lambda v: type(v) is int)
172
172
  is_nat = Contract("nat?", lambda v: type(v) is int and v > -1)
173
173
  is_nat1 = Contract("nat1?", lambda v: type(v) is int and v > 0)
174
174
  int_not_zero = Contract("(or/c (not/c 0) int?)", lambda v: v != 0 and is_int(v))
175
- is_num = Contract(
176
- "number?", lambda v: type(v) in (int, float, float64, Fraction, complex)
177
- )
178
175
  is_real = Contract("real?", lambda v: type(v) in (int, float, float64, Fraction))
176
+ is_num = is_real
179
177
  is_float = Contract("float?", lambda v: type(v) in (float, float64))
180
178
  is_frac = Contract("frac?", lambda v: type(v) is Fraction)
181
179
  is_str = Contract("string?", lambda v: type(v) is str)
@@ -182,9 +182,6 @@ def display_str(val: object) -> str:
182
182
  return f"{val}"
183
183
  if type(val) is range:
184
184
  return "#<range>"
185
- if type(val) is complex:
186
- join = "" if val.imag < 0 else "+"
187
- return f"{val.real}{join}{val.imag}i"
188
185
  if type(val) is np.bool_:
189
186
  return "1" if val else "0"
190
187
  if type(val) is np.float64 or type(val) is np.float32: