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.
- kinescript/__init__.py +6 -0
- kinescript/cli.py +167 -0
- kinescript/color.py +89 -0
- kinescript/components.py +272 -0
- kinescript/docs.py +345 -0
- kinescript/ease.py +143 -0
- kinescript/fonts/IBMPlexMono-Bold.ttf +0 -0
- kinescript/fonts/IBMPlexMono-Medium.ttf +0 -0
- kinescript/fonts/IBMPlexMono-OFL.txt +93 -0
- kinescript/fonts/IBMPlexMono-Regular.ttf +0 -0
- kinescript/fonts/IBMPlexMono-SemiBold.ttf +0 -0
- kinescript/fonts/SpaceGrotesk-Bold.ttf +0 -0
- kinescript/fonts/SpaceGrotesk-Medium.ttf +0 -0
- kinescript/fonts/SpaceGrotesk-OFL.txt +93 -0
- kinescript/fonts/SpaceGrotesk-Regular.ttf +0 -0
- kinescript/fonts/SpaceGrotesk-SemiBold.ttf +0 -0
- kinescript/mcp.py +148 -0
- kinescript/media.py +220 -0
- kinescript/mesh.py +390 -0
- kinescript/models.py +339 -0
- kinescript/output.py +370 -0
- kinescript/presets.py +170 -0
- kinescript/props.py +185 -0
- kinescript/render.py +1204 -0
- kinescript/render3d.py +393 -0
- kinescript/scene.py +362 -0
- kinescript/schema.py +701 -0
- kinescript/shaders.py +79 -0
- kinescript/space.py +81 -0
- kinescript/svgpath.py +80 -0
- kinescript/timing.py +169 -0
- kinescript/verify.py +261 -0
- kinescript-1.1.0.dist-info/METADATA +192 -0
- kinescript-1.1.0.dist-info/RECORD +40 -0
- kinescript-1.1.0.dist-info/WHEEL +5 -0
- kinescript-1.1.0.dist-info/entry_points.txt +2 -0
- kinescript-1.1.0.dist-info/licenses/LICENSE +25 -0
- kinescript-1.1.0.dist-info/licenses/kinescript/fonts/IBMPlexMono-OFL.txt +93 -0
- kinescript-1.1.0.dist-info/licenses/kinescript/fonts/SpaceGrotesk-OFL.txt +93 -0
- 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)))
|