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/timeline.py DELETED
@@ -1,331 +0,0 @@
1
- from __future__ import annotations
2
-
3
- from dataclasses import dataclass
4
- from typing import TYPE_CHECKING
5
-
6
- from auto_editor.ffwrapper import FileInfo, mux
7
- from auto_editor.lib.contracts import *
8
- from auto_editor.utils.cmdkw import Required, pAttr, pAttrs
9
- from auto_editor.utils.types import CoerceError, natural, number, parse_color
10
-
11
- if TYPE_CHECKING:
12
- from collections.abc import Iterator
13
- from fractions import Fraction
14
- from pathlib import Path
15
-
16
- from auto_editor.ffwrapper import FileInfo
17
- from auto_editor.utils.chunks import Chunks
18
- from auto_editor.utils.log import Log
19
-
20
-
21
- @dataclass(slots=True)
22
- class v1:
23
- """
24
- v1 timeline constructor
25
- timebase is always the source's average fps
26
-
27
- """
28
-
29
- source: FileInfo
30
- chunks: Chunks
31
-
32
- def as_dict(self) -> dict:
33
- return {
34
- "version": "1",
35
- "source": f"{self.source.path.resolve()}",
36
- "chunks": self.chunks,
37
- }
38
-
39
-
40
- @dataclass(slots=True)
41
- class Clip:
42
- start: int
43
- dur: int
44
- src: FileInfo
45
- offset: int
46
- stream: int
47
-
48
- speed: float = 1.0
49
- volume: float = 1.0
50
-
51
- def as_dict(self) -> dict:
52
- return {
53
- "name": "video",
54
- "src": self.src,
55
- "start": self.start,
56
- "dur": self.dur,
57
- "offset": self.offset,
58
- "speed": self.speed,
59
- "stream": self.stream,
60
- }
61
-
62
-
63
- @dataclass(slots=True)
64
- class TlImage:
65
- start: int
66
- dur: int
67
- src: FileInfo
68
- x: int
69
- y: int
70
- width: int
71
- opacity: float
72
-
73
- def as_dict(self) -> dict:
74
- return {
75
- "name": "image",
76
- "src": self.src,
77
- "start": self.start,
78
- "dur": self.dur,
79
- "x": self.x,
80
- "y": self.y,
81
- "width": self.width,
82
- "opacity": self.opacity,
83
- }
84
-
85
-
86
- @dataclass(slots=True)
87
- class TlRect:
88
- start: int
89
- dur: int
90
- x: int
91
- y: int
92
- width: int
93
- height: int
94
- fill: str
95
-
96
- def as_dict(self) -> dict:
97
- return {
98
- "name": "rect",
99
- "start": self.start,
100
- "dur": self.dur,
101
- "x": self.x,
102
- "y": self.y,
103
- "width": self.width,
104
- "height": self.height,
105
- "fill": self.fill,
106
- }
107
-
108
-
109
- def threshold(val: str | float) -> float:
110
- num = number(val)
111
- if num > 1 or num < 0:
112
- raise CoerceError(f"'{val}': Threshold must be between 0 and 1 (0%-100%)")
113
- return num
114
-
115
-
116
- video_builder = pAttrs(
117
- "video",
118
- pAttr("start", Required, is_nat, natural),
119
- pAttr("dur", Required, is_nat, natural),
120
- pAttr("src", Required, is_str, "source"),
121
- pAttr("offset", 0, is_int, natural),
122
- pAttr("speed", 1, is_real, number),
123
- pAttr("stream", 0, is_nat, natural),
124
- )
125
- audio_builder = pAttrs(
126
- "audio",
127
- pAttr("start", Required, is_nat, natural),
128
- pAttr("dur", Required, is_nat, natural),
129
- pAttr("src", Required, is_str, "source"),
130
- pAttr("offset", 0, is_int, natural),
131
- pAttr("speed", 1, is_real, number),
132
- pAttr("volume", 1, is_threshold, threshold),
133
- pAttr("stream", 0, is_nat, natural),
134
- )
135
- img_builder = pAttrs(
136
- "image",
137
- pAttr("start", Required, is_nat, natural),
138
- pAttr("dur", Required, is_nat, natural),
139
- pAttr("src", Required, is_str, "source"),
140
- pAttr("x", Required, is_int, int),
141
- pAttr("y", Required, is_int, int),
142
- pAttr("width", 0, is_nat, natural),
143
- pAttr("opacity", 1, is_threshold, threshold),
144
- )
145
- rect_builder = pAttrs(
146
- "rect",
147
- pAttr("start", Required, is_nat, natural),
148
- pAttr("dur", Required, is_nat, natural),
149
- pAttr("x", Required, is_int, int),
150
- pAttr("y", Required, is_int, int),
151
- pAttr("width", Required, is_int, int),
152
- pAttr("height", Required, is_int, int),
153
- pAttr("fill", "#c4c4c4", is_str, parse_color),
154
- )
155
- visual_objects = {
156
- "rect": (TlRect, rect_builder),
157
- "image": (TlImage, img_builder),
158
- "video": (Clip, video_builder),
159
- }
160
-
161
- VLayer = list[Clip | TlImage | TlRect]
162
- VSpace = list[VLayer]
163
- ASpace = list[list[Clip]]
164
-
165
-
166
- @dataclass(slots=True)
167
- class AudioTemplate:
168
- lang: str | None
169
-
170
-
171
- @dataclass(slots=True)
172
- class SubtitleTemplate:
173
- lang: str | None
174
-
175
-
176
- @dataclass(slots=True)
177
- class Template:
178
- sr: int
179
- layout: str
180
- res: tuple[int, int]
181
- audios: list[AudioTemplate]
182
- subtitles: list[SubtitleTemplate]
183
-
184
- @classmethod
185
- def init(
186
- self,
187
- src: FileInfo,
188
- sr: int | None = None,
189
- layout: str | None = None,
190
- res: tuple[int, int] | None = None,
191
- ) -> Template:
192
- alist = [AudioTemplate(x.lang) for x in src.audios]
193
- slist = [SubtitleTemplate(x.lang) for x in src.subtitles]
194
-
195
- if sr is None:
196
- sr = src.get_sr()
197
-
198
- if layout is None:
199
- layout = "stereo" if not src.audios else src.audios[0].layout
200
-
201
- if res is None:
202
- res = src.get_res()
203
-
204
- return Template(sr, layout, res, alist, slist)
205
-
206
-
207
- @dataclass
208
- class v3:
209
- tb: Fraction
210
- background: str
211
- template: Template
212
- v: VSpace
213
- a: ASpace
214
- v1: v1 | None # Is it v1 compatible (linear and only one source)?
215
-
216
- def __str__(self) -> str:
217
- result = f"""
218
- global
219
- timebase {self.tb}
220
- samplerate {self.sr}
221
- res {self.res[0]}x{self.res[1]}
222
-
223
- video\n"""
224
-
225
- for i, layer in enumerate(self.v):
226
- result += f" v{i} "
227
- for obj in layer:
228
- if isinstance(obj, Clip):
229
- result += (
230
- f"[#:start {obj.start} #:dur {obj.dur} #:off {obj.offset}] "
231
- )
232
- else:
233
- result += f"[#:start {obj.start} #:dur {obj.dur}] "
234
- result += "\n"
235
-
236
- result += "\naudio\n"
237
- for i, alayer in enumerate(self.a):
238
- result += f" a{i} "
239
- for abj in alayer:
240
- result += f"[#:start {abj.start} #:dur {abj.dur} #:off {abj.offset}] "
241
- result += "\n"
242
- return result
243
-
244
- @property
245
- def end(self) -> int:
246
- end = 0
247
- for vclips in self.v:
248
- if vclips:
249
- v = vclips[-1]
250
- end = max(end, v.start + v.dur)
251
-
252
- for aclips in self.a:
253
- if aclips:
254
- a = aclips[-1]
255
- end = max(end, a.start + a.dur)
256
-
257
- return end
258
-
259
- @property
260
- def sources(self) -> Iterator[FileInfo]:
261
- for vclips in self.v:
262
- for v in vclips:
263
- if isinstance(v, Clip):
264
- yield v.src
265
- for aclips in self.a:
266
- for a in aclips:
267
- yield a.src
268
-
269
- def unique_sources(self) -> Iterator[FileInfo]:
270
- seen = set()
271
- for source in self.sources:
272
- if source.path not in seen:
273
- seen.add(source.path)
274
- yield source
275
-
276
- def __len__(self) -> int:
277
- result = 0
278
- for clips in self.v + self.a:
279
- if len(clips) > 0:
280
- lastClip = clips[-1]
281
- result = max(result, lastClip.start + lastClip.dur)
282
-
283
- return result
284
-
285
- @property
286
- def T(self) -> Template:
287
- return self.template
288
-
289
- @property
290
- def res(self) -> tuple[int, int]:
291
- return self.T.res
292
-
293
- @property
294
- def sr(self) -> int:
295
- return self.T.sr
296
-
297
-
298
- def make_tracks_dir(tracks_dir: Path) -> None:
299
- from os import mkdir
300
- from shutil import rmtree
301
-
302
- try:
303
- mkdir(tracks_dir)
304
- except OSError:
305
- rmtree(tracks_dir)
306
- mkdir(tracks_dir)
307
-
308
-
309
- def set_stream_to_0(tl: v3, log: Log) -> None:
310
- dir_exists = False
311
- cache: dict[Path, FileInfo] = {}
312
-
313
- def make_track(i: int, path: Path) -> FileInfo:
314
- nonlocal dir_exists
315
-
316
- fold = path.parent / f"{path.stem}_tracks"
317
- if not dir_exists:
318
- make_tracks_dir(fold)
319
- dir_exists = True
320
-
321
- newtrack = fold / f"{path.stem}_{i}.wav"
322
- if newtrack not in cache:
323
- mux(path, output=newtrack, stream=i)
324
- cache[newtrack] = FileInfo.init(f"{newtrack}", log)
325
- return cache[newtrack]
326
-
327
- for alayer in tl.a:
328
- for aobj in alayer:
329
- if aobj.stream > 0:
330
- aobj.src = make_track(aobj.stream, aobj.src.path)
331
- aobj.stream = 0
File without changes
auto_editor/utils/bar.py DELETED
@@ -1,142 +0,0 @@
1
- from __future__ import annotations
2
-
3
- import sys
4
- from dataclasses import dataclass
5
- from math import floor
6
- from shutil import get_terminal_size
7
- from time import localtime, time
8
-
9
- from .func import get_stdout_bytes
10
-
11
-
12
- def initBar(bar_type: str) -> Bar:
13
- icon = "⏳"
14
- chars: tuple[str, ...] = (" ", "▏", "▎", "▍", "▌", "▋", "▊", "▉", "█")
15
- brackets = ("|", "|")
16
- machine = hide = False
17
-
18
- if bar_type == "classic":
19
- icon = "⏳"
20
- chars = ("░", "█")
21
- brackets = ("[", "]")
22
- if bar_type == "ascii":
23
- icon = "& "
24
- chars = ("-", "#")
25
- brackets = ("[", "]")
26
- if bar_type == "machine":
27
- machine = True
28
- if bar_type == "none":
29
- hide = True
30
-
31
- part_width = len(chars) - 1
32
-
33
- ampm = True
34
- if sys.platform == "darwin" and bar_type in {"modern", "classic", "ascii"}:
35
- try:
36
- date_format = get_stdout_bytes(
37
- ["defaults", "read", "com.apple.menuextra.clock", "Show24Hour"]
38
- )
39
- ampm = date_format == b"0\n"
40
- except FileNotFoundError:
41
- pass
42
-
43
- return Bar(icon, chars, brackets, machine, hide, part_width, ampm, [])
44
-
45
-
46
- @dataclass(slots=True)
47
- class Bar:
48
- icon: str
49
- chars: tuple[str, ...]
50
- brackets: tuple[str, str]
51
- machine: bool
52
- hide: bool
53
- part_width: int
54
- ampm: bool
55
- stack: list[tuple[str, int, float, float]]
56
-
57
- @staticmethod
58
- def pretty_time(my_time: float, ampm: bool) -> str:
59
- new_time = localtime(my_time)
60
-
61
- hours = new_time.tm_hour
62
- minutes = new_time.tm_min
63
-
64
- if ampm:
65
- if hours == 0:
66
- hours = 12
67
- if hours > 12:
68
- hours -= 12
69
- ampm_marker = "PM" if new_time.tm_hour >= 12 else "AM"
70
- return f"{hours:02}:{minutes:02} {ampm_marker}"
71
- return f"{hours:02}:{minutes:02}"
72
-
73
- def tick(self, index: float) -> None:
74
- if self.hide:
75
- return
76
-
77
- title, len_title, total, begin = self.stack[-1]
78
- progress = 0.0 if total == 0 else min(1, max(0, index / total))
79
- rate = 0.0 if progress == 0 else (time() - begin) / progress
80
-
81
- if self.machine:
82
- index = min(index, total)
83
- secs_til_eta = round(begin + rate - time(), 2)
84
- print(f"{title}~{index}~{total}~{secs_til_eta}", end="\r", flush=True)
85
- return
86
-
87
- new_time = self.pretty_time(begin + rate, self.ampm)
88
-
89
- percent = round(progress * 100, 1)
90
- p_pad = " " * (4 - len(str(percent)))
91
- columns = get_terminal_size().columns
92
- bar_len = max(1, columns - len_title - 35)
93
- bar_str = self._bar_str(progress, bar_len)
94
-
95
- bar = f" {self.icon}{title} {bar_str} {p_pad}{percent}% ETA {new_time} \r"
96
- sys.stdout.write(bar)
97
-
98
- def start(self, total: float, title: str = "Please wait") -> None:
99
- len_title = 0
100
- in_escape = False
101
-
102
- for char in title:
103
- if not in_escape:
104
- if char == "\033":
105
- in_escape = True
106
- else:
107
- len_title += 1
108
- elif char == "m":
109
- in_escape = False
110
-
111
- self.stack.append((title, len_title, total, time()))
112
-
113
- try:
114
- self.tick(0)
115
- except UnicodeEncodeError:
116
- self.icon = "& "
117
- self.chars = ("-", "#")
118
- self.brackets = ("[", "]")
119
- self.part_width = 1
120
-
121
- def _bar_str(self, progress: float, width: int) -> str:
122
- whole_width = floor(progress * width)
123
- remainder_width = (progress * width) % 1
124
- part_width = floor(remainder_width * self.part_width)
125
- part_char = self.chars[part_width]
126
-
127
- if width - whole_width - 1 < 0:
128
- part_char = ""
129
-
130
- line = (
131
- self.brackets[0]
132
- + self.chars[-1] * whole_width
133
- + part_char
134
- + self.chars[0] * (width - whole_width - 1)
135
- + self.brackets[1]
136
- )
137
- return line
138
-
139
- def end(self) -> None:
140
- sys.stdout.write(" " * (get_terminal_size().columns - 2) + "\r")
141
- if self.stack:
142
- self.stack.pop()
@@ -1,2 +0,0 @@
1
- Chunk = tuple[int, int, float]
2
- Chunks = list[Chunk]
@@ -1,206 +0,0 @@
1
- from __future__ import annotations
2
-
3
- from dataclasses import dataclass
4
- from difflib import get_close_matches
5
- from typing import TYPE_CHECKING
6
-
7
- from auto_editor.lib.data_structs import Env
8
-
9
- if TYPE_CHECKING:
10
- from collections.abc import Callable
11
- from typing import Any, Literal
12
-
13
-
14
- class ParserError(Exception):
15
- pass
16
-
17
-
18
- class Required:
19
- pass
20
-
21
-
22
- @dataclass(slots=True)
23
- class pAttr:
24
- n: str
25
- default: Any
26
- contract: Any
27
- coerce: Callable[[Any], Any] | Literal["source"] | None = None
28
-
29
-
30
- class pAttrs:
31
- __slots__ = ("name", "attrs")
32
-
33
- def __init__(self, name: str, *attrs: pAttr):
34
- self.name = name
35
- self.attrs = attrs
36
-
37
-
38
- class PLexer:
39
- __slots__ = ("text", "pos", "char")
40
-
41
- def __init__(self, text: str):
42
- self.text = text
43
- self.pos: int = 0
44
- self.char: str | None = self.text[self.pos] if text else None
45
-
46
- def advance(self) -> None:
47
- self.pos += 1
48
- self.char = None if self.pos > len(self.text) - 1 else self.text[self.pos]
49
-
50
- def string(self) -> str:
51
- result = ""
52
- while self.char is not None and self.char != '"':
53
- if self.char == "\\":
54
- self.advance()
55
- if self.char is None:
56
- raise ParserError(
57
- "Expected character for escape sequence, got end of file."
58
- )
59
- result += f"\\{self.char}"
60
- self.advance()
61
- else:
62
- result += self.char
63
- self.advance()
64
-
65
- self.advance()
66
- return f'"{result}"'
67
-
68
- def get_next_token(self) -> str | None:
69
- while self.char is not None:
70
- if self.char == '"':
71
- self.advance()
72
- return self.string()
73
-
74
- result = ""
75
- while self.char is not None and self.char not in ",":
76
- result += self.char
77
- self.advance()
78
-
79
- self.advance()
80
- return result
81
- return None
82
-
83
-
84
- def parse_with_palet(
85
- text: str, build: pAttrs, _env: Env | dict[str, Any]
86
- ) -> dict[str, Any]:
87
- from auto_editor.lang.palet import Lexer, Parser, interpret
88
- from auto_editor.lib.data_structs import print_str
89
- from auto_editor.lib.err import MyError
90
-
91
- # Positional Arguments
92
- # --option 0,end,10,20,20,30,#000, ...
93
- # Keyword Arguments
94
- # --option start=0,dur=end,x1=10, ...
95
-
96
- KEYWORD_SEP = "="
97
- kwargs: dict[str, Any] = {}
98
-
99
- def _norm_name(s: str) -> str:
100
- # Python does not allow - in variable names
101
- return s.replace("-", "_")
102
-
103
- def go(text: str, c: Any) -> Any:
104
- try:
105
- env = _env if isinstance(_env, Env) else Env(_env)
106
- results = interpret(env, Parser(Lexer(build.name, text)))
107
- except MyError as e:
108
- raise ParserError(e)
109
-
110
- if not results:
111
- raise ParserError("Results must be of length > 0")
112
-
113
- if c(results[-1]) is not True:
114
- raise ParserError(
115
- f"{build.name}: Expected {c.name}, got {print_str(results[-1])}"
116
- )
117
-
118
- return results[-1]
119
-
120
- for attr in build.attrs:
121
- kwargs[_norm_name(attr.n)] = attr.default
122
-
123
- allow_positional_args = True
124
-
125
- lexer = PLexer(text)
126
- i = 0
127
- while (arg := lexer.get_next_token()) is not None:
128
- if not arg:
129
- continue
130
-
131
- if i + 1 > len(build.attrs):
132
- raise ParserError(
133
- f"{build.name} has too many arguments, starting with '{arg}'."
134
- )
135
-
136
- if KEYWORD_SEP in arg:
137
- key, val = arg.split(KEYWORD_SEP, 1)
138
-
139
- allow_positional_args = False
140
- found = False
141
-
142
- for attr in build.attrs:
143
- if key == attr.n:
144
- kwargs[_norm_name(attr.n)] = go(val, attr.contract)
145
- found = True
146
- break
147
-
148
- if not found:
149
- all_names = {attr.n for attr in build.attrs}
150
- if matches := get_close_matches(key, all_names):
151
- more = f"\n Did you mean:\n {', '.join(matches)}"
152
- else:
153
- more = (
154
- f"\n attributes available:\n {', '.join(all_names)}"
155
- )
156
-
157
- raise ParserError(
158
- f"{build.name} got an unexpected attribute '{key}'\n{more}"
159
- )
160
-
161
- elif allow_positional_args:
162
- kwargs[_norm_name(build.attrs[i].n)] = go(arg, build.attrs[i].contract)
163
- else:
164
- raise ParserError(
165
- f"{build.name} positional argument follows keyword argument."
166
- )
167
- i += 1
168
-
169
- for k, v in kwargs.items():
170
- if v is Required:
171
- raise ParserError(f"'{k}' must be specified.")
172
-
173
- return kwargs
174
-
175
-
176
- def parse_method(name: str, text: str) -> tuple[str, list[Any], dict[str, Any]]:
177
- from auto_editor.lang.palet import Lexer, Parser
178
-
179
- # Positional Arguments
180
- # audio:0.04,0,6,3
181
- # Keyword Arguments
182
- # audio:threshold=0.04,stream=0,mincut=6,minclip=3
183
-
184
- args: list[Any] = []
185
- kwargs: dict[str, Any] = {}
186
-
187
- allow_positional_args = True
188
- lexer = PLexer(text)
189
- while (arg := lexer.get_next_token()) is not None:
190
- if not arg:
191
- continue
192
-
193
- if "=" in arg:
194
- key, val = arg.split("=", 1)
195
-
196
- result = Parser(Lexer(name, val)).expr()
197
- kwargs[key] = result
198
- allow_positional_args = False
199
-
200
- elif allow_positional_args:
201
- result = Parser(Lexer(name, arg)).expr()
202
- args.append(result)
203
- else:
204
- raise ParserError(f"{name} positional argument follows keyword argument.")
205
-
206
- return name, args, kwargs