kinescript 1.1.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 (40) hide show
  1. kinescript/__init__.py +6 -0
  2. kinescript/cli.py +167 -0
  3. kinescript/color.py +89 -0
  4. kinescript/components.py +272 -0
  5. kinescript/docs.py +345 -0
  6. kinescript/ease.py +143 -0
  7. kinescript/fonts/IBMPlexMono-Bold.ttf +0 -0
  8. kinescript/fonts/IBMPlexMono-Medium.ttf +0 -0
  9. kinescript/fonts/IBMPlexMono-OFL.txt +93 -0
  10. kinescript/fonts/IBMPlexMono-Regular.ttf +0 -0
  11. kinescript/fonts/IBMPlexMono-SemiBold.ttf +0 -0
  12. kinescript/fonts/SpaceGrotesk-Bold.ttf +0 -0
  13. kinescript/fonts/SpaceGrotesk-Medium.ttf +0 -0
  14. kinescript/fonts/SpaceGrotesk-OFL.txt +93 -0
  15. kinescript/fonts/SpaceGrotesk-Regular.ttf +0 -0
  16. kinescript/fonts/SpaceGrotesk-SemiBold.ttf +0 -0
  17. kinescript/mcp.py +148 -0
  18. kinescript/media.py +220 -0
  19. kinescript/mesh.py +390 -0
  20. kinescript/models.py +339 -0
  21. kinescript/output.py +370 -0
  22. kinescript/presets.py +170 -0
  23. kinescript/props.py +185 -0
  24. kinescript/render.py +1204 -0
  25. kinescript/render3d.py +393 -0
  26. kinescript/scene.py +362 -0
  27. kinescript/schema.py +701 -0
  28. kinescript/shaders.py +79 -0
  29. kinescript/space.py +81 -0
  30. kinescript/svgpath.py +80 -0
  31. kinescript/timing.py +169 -0
  32. kinescript/verify.py +261 -0
  33. kinescript-1.1.0.dist-info/METADATA +192 -0
  34. kinescript-1.1.0.dist-info/RECORD +40 -0
  35. kinescript-1.1.0.dist-info/WHEEL +5 -0
  36. kinescript-1.1.0.dist-info/entry_points.txt +2 -0
  37. kinescript-1.1.0.dist-info/licenses/LICENSE +25 -0
  38. kinescript-1.1.0.dist-info/licenses/kinescript/fonts/IBMPlexMono-OFL.txt +93 -0
  39. kinescript-1.1.0.dist-info/licenses/kinescript/fonts/SpaceGrotesk-OFL.txt +93 -0
  40. kinescript-1.1.0.dist-info/top_level.txt +1 -0
kinescript/props.py ADDED
@@ -0,0 +1,185 @@
1
+ """Animatable property values.
2
+
3
+ A property is one of:
4
+ 42 / "#ffb000" / "50%" / "50%-120" constant (lengths relative to the canvas)
5
+ [[0, 0], [0.6, 1, "outBack"], [-0.4, 1], ["end", 0]]
6
+ keyframes: [time, value, ease into this key].
7
+ Times are seconds from the layer's start;
8
+ negative = from its end; "40%" = of its length.
9
+ {"from": 0, "to": 1, "dur": 0.6, "delay": 0, "ease": "outCubic"} one tween
10
+ "=540 + sin(t*3)*40" expression of t (local s), d (layer length),
11
+ p (t/d), T (scene s), i (unit index)
12
+ """
13
+ from __future__ import annotations
14
+
15
+ import ast
16
+ import math
17
+ import re
18
+ from typing import Any, Callable
19
+
20
+ from . import color as C
21
+ from . import ease as E
22
+
23
+ W_AXIS = {"x", "w", "left", "cx", "dx", "maxWidth", "x1", "x2"}
24
+ H_AXIS = {"y", "h", "top", "cy", "dy", "y1", "y2"}
25
+ NOT_ANIMATABLE = {"points", "trim", "src", "text", "id", "type", "cues", "children", "layers",
26
+ "d", "font", "split", "enter", "exit", "stops", "data", "words", "format",
27
+ "fit", "align", "anchor", "blend", "clip", "loop", "mode", "cap", "join"}
28
+
29
+ _LEN = re.compile(r"^\s*(-?\d+(?:\.\d+)?)\s*(%|vw|vh|vmin|px)?\s*(?:([+-])\s*(\d+(?:\.\d+)?)\s*(px)?)?\s*$")
30
+
31
+
32
+ def noise(x: float, seed: float = 0.0) -> float:
33
+ """Smooth 1-D value noise in [-1, 1]. Pure function of x, so any frame renders alone."""
34
+ def h(n: int) -> float:
35
+ n = (n * 374761393 + int(seed * 668265263)) & 0xFFFFFFFF
36
+ n = ((n ^ (n >> 13)) * 1274126177) & 0xFFFFFFFF
37
+ return ((n ^ (n >> 16)) & 0xFFFF) / 32767.5 - 1.0
38
+ i = math.floor(x)
39
+ f = x - i
40
+ u = f * f * (3 - 2 * f)
41
+ return h(i) * (1 - u) + h(i + 1) * u
42
+
43
+
44
+ EXPR_ENV: dict[str, Any] = {k: getattr(math, k) for k in (
45
+ "sin", "cos", "tan", "pi", "sqrt", "floor", "ceil", "exp", "log", "atan2", "hypot", "tau")}
46
+ EXPR_ENV.update(abs=abs, min=min, max=max, round=round, noise=noise,
47
+ clamp=lambda v, a=0.0, b=1.0: max(a, min(b, v)),
48
+ lerp=lambda a, b, t: a + (b - a) * t,
49
+ wiggle=lambda freq, amp, t, seed=0: noise(t * freq, seed) * amp,
50
+ ease=lambda name, t: E.get(name)(max(0.0, min(1.0, t))))
51
+
52
+
53
+ class Ctx:
54
+ """Reference box for % lengths (the canvas, or a group's w/h)."""
55
+ def __init__(self, w: float, h: float, fps: float):
56
+ self.w, self.h, self.fps = w, h, fps
57
+
58
+
59
+ def length(v, name: str, ctx: Ctx):
60
+ if not isinstance(v, str) or C.is_color(v):
61
+ return v
62
+ m = _LEN.match(v)
63
+ if not m:
64
+ return v
65
+ num, unit, sign, add, _ = m.groups()
66
+ x = float(num)
67
+ if unit in ("%", "vw", "vh", "vmin"):
68
+ if unit == "vw" or (unit == "%" and name in W_AXIS):
69
+ ref = ctx.w
70
+ elif unit == "vh" or (unit == "%" and name in H_AXIS):
71
+ ref = ctx.h
72
+ else:
73
+ ref = min(ctx.w, ctx.h)
74
+ x = x / 100 * ref
75
+ if sign:
76
+ x += float(add) if sign == "+" else -float(add)
77
+ return x
78
+
79
+
80
+ def lerp(a, b, t: float):
81
+ if isinstance(a, (int, float)) and isinstance(b, (int, float)):
82
+ return a + (b - a) * t
83
+ if isinstance(a, tuple) and isinstance(b, tuple) and len(a) == len(b) == 4: # parsed colours
84
+ return C.mix(a, b, t)
85
+ if isinstance(a, (list, tuple)) and isinstance(b, (list, tuple)) and len(a) == len(b):
86
+ return [lerp(x, y, t) for x, y in zip(a, b)]
87
+ return b if t >= 1 else a
88
+
89
+
90
+ def _norm(v):
91
+ """Colours become RGBA tuples once, so draw code never re-parses them."""
92
+ return C.parse(v) if C.is_color(v) else v
93
+
94
+
95
+ def _is_time(k) -> bool:
96
+ if isinstance(k, (int, float)):
97
+ return True
98
+ return isinstance(k, str) and bool(re.fullmatch(r"\s*(-?\d+(\.\d+)?(s|f)?|\d+(\.\d+)?%|end([+-]\d+(\.\d+)?(s|f)?)?)\s*", k))
99
+
100
+
101
+ def is_keyframes(v) -> bool:
102
+ return (isinstance(v, list) and len(v) >= 1
103
+ and all(isinstance(k, (list, tuple)) and 2 <= len(k) <= 3 and _is_time(k[0]) for k in v))
104
+
105
+
106
+ class Prop:
107
+ __slots__ = ("static", "value", "fn")
108
+
109
+ def __init__(self, static: bool, value=None, fn: Callable | None = None):
110
+ self.static, self.value, self.fn = static, value, fn
111
+
112
+ def at(self, t: float, d: float, T: float = 0.0, i: int = 0):
113
+ return self.value if self.static else self.fn(t, d, T, i)
114
+
115
+
116
+ def key_time(k, d: float, fps: float) -> float:
117
+ if isinstance(k, (int, float)):
118
+ return d + k if k < 0 else float(k)
119
+ s = k.strip()
120
+ if s.endswith("%"):
121
+ return float(s[:-1]) / 100 * d
122
+ if s.startswith("end"):
123
+ rest = s[3:].strip() or "+0"
124
+ return d + float(rest.rstrip("sf")) / (fps if rest.endswith("f") else 1)
125
+ v = float(s.rstrip("sf")) / (fps if s.endswith("f") else 1)
126
+ return d + v if v < 0 else v
127
+
128
+
129
+ _SAFE_NODES = (ast.Expression, ast.BinOp, ast.UnaryOp, ast.BoolOp, ast.Compare, ast.IfExp, ast.Call,
130
+ ast.Name, ast.Load, ast.Constant, ast.Tuple, ast.List, ast.operator, ast.unaryop,
131
+ ast.boolop, ast.cmpop)
132
+
133
+
134
+ def compile_expr(src: str, name: str, allowed: set[str]):
135
+ """Compile a scene expression. Only arithmetic, comparisons and calls to the whitelisted
136
+ helpers are accepted (no attributes, subscripts or lambdas), because scene files may come
137
+ from other agents and are evaluated with eval()."""
138
+ tree = ast.parse(src, mode="eval")
139
+ for node in ast.walk(tree):
140
+ if not isinstance(node, _SAFE_NODES):
141
+ raise ValueError(f"'{name}': '{type(node).__name__}' is not allowed in expressions")
142
+ if isinstance(node, ast.Name) and node.id not in allowed:
143
+ raise ValueError(f"'{name}': unknown name '{node.id}' in expression. "
144
+ f"Available: {', '.join(sorted(allowed))}")
145
+ if isinstance(node, ast.Call) and not isinstance(node.func, ast.Name):
146
+ raise ValueError(f"'{name}': only direct helper calls are allowed")
147
+ return compile(tree, f"<{name}>", "eval")
148
+
149
+
150
+ def compile_prop(name: str, raw, ctx: Ctx) -> Prop:
151
+ if name in NOT_ANIMATABLE:
152
+ return Prop(True, raw)
153
+ if isinstance(raw, str) and raw.startswith("="):
154
+ env = dict(EXPR_ENV, W=ctx.w, H=ctx.h, fps=ctx.fps)
155
+ code = compile_expr(raw[1:], name, set(env) | {"t", "d", "p", "T", "i"})
156
+
157
+ def fx(t, d, T, i, code=code, env=env):
158
+ v = eval(code, {"__builtins__": {}}, dict(env, t=t, d=d, p=(t / d if d else 0.0), T=T, i=i))
159
+ return _norm(v)
160
+ return Prop(False, fn=fx)
161
+ if isinstance(raw, dict) and ("to" in raw or "keys" in raw):
162
+ if "keys" in raw:
163
+ return compile_prop(name, raw["keys"], ctx)
164
+ delay = float(raw.get("delay", 0))
165
+ dur = float(raw.get("dur", 0.6))
166
+ keys = [[delay, raw.get("from", raw["to"])], [delay + dur, raw["to"], raw.get("ease", "outCubic")]]
167
+ return compile_prop(name, keys, ctx)
168
+ if is_keyframes(raw):
169
+ keys = [(k[0], _norm(length(k[1], name, ctx)), E.get(k[2] if len(k) > 2 else "inOutCubic")) for k in raw]
170
+ fps = ctx.fps
171
+ cache: dict[float, list] = {}
172
+
173
+ def fk(t, d, T, i):
174
+ pts = cache.get(d)
175
+ if pts is None:
176
+ pts = cache[d] = sorted(((key_time(k, d, fps), v, e) for k, v, e in keys), key=lambda x: x[0])
177
+ if t <= pts[0][0]:
178
+ return pts[0][1]
179
+ for (t0, v0, _), (t1, v1, e1) in zip(pts, pts[1:]):
180
+ if t < t1:
181
+ u = (t - t0) / (t1 - t0) if t1 > t0 else 1.0
182
+ return lerp(v0, v1, e1(u))
183
+ return pts[-1][1]
184
+ return Prop(False, fn=fk)
185
+ return Prop(True, _norm(length(raw, name, ctx)))