greenlight-motion 0.3.2 → 0.3.4
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.
- package/README.md +9 -9
- package/dist/greenlight-motion-script.mjs +180 -4
- package/dist/greenlight-motion.js +615 -615
- package/dist/greenlight-motion.mjs +32295 -32211
- package/package.json +1 -1
- package/skill/SKILL.md +10 -10
package/README.md
CHANGED
|
@@ -17,16 +17,16 @@ The agent writes the video as data: a `project.json` and its files. The engine t
|
|
|
17
17
|
## Quick start
|
|
18
18
|
|
|
19
19
|
```bash
|
|
20
|
-
npx greenlight-motion@0.3.
|
|
21
|
-
npx greenlight-motion@0.3.
|
|
20
|
+
npx greenlight-motion@0.3.4 doctor # Node 18+, Chrome or Edge, WebGPU, H.264: what's missing and how to fix it
|
|
21
|
+
npx greenlight-motion@0.3.4 init my-video # a working starter: index.html, project.json, assets/
|
|
22
22
|
# edit my-video/project.json, put pictures / video / sound in my-video/assets/
|
|
23
23
|
# or: write my-video/script.json (a scene script, skill/references/scene-script.md), then
|
|
24
|
-
npx greenlight-motion@0.3.
|
|
25
|
-
npx greenlight-motion@0.3.
|
|
26
|
-
npx greenlight-motion@0.3.
|
|
27
|
-
npx greenlight-motion@0.3.
|
|
28
|
-
npx greenlight-motion@0.3.
|
|
29
|
-
npx greenlight-motion@0.3.
|
|
24
|
+
npx greenlight-motion@0.3.4 script my-video # script.json → project.json with the Video Editor's own presets
|
|
25
|
+
npx greenlight-motion@0.3.4 check my-video # what to fix: missing files, template mistakes, blank frames, broken plugins
|
|
26
|
+
npx greenlight-motion@0.3.4 stills my-video # frames + a labelled contact sheet, to look at
|
|
27
|
+
npx greenlight-motion@0.3.4 render my-video --fps 60 --motion-blur
|
|
28
|
+
npx greenlight-motion@0.3.4 pack my-video # everything in ONE html file
|
|
29
|
+
npx greenlight-motion@0.3.4 pack my-video --glvideo # ONE .glvideo: double-clicked, it opens in GreenLight Dash
|
|
30
30
|
```
|
|
31
31
|
|
|
32
32
|
Also: `trace` (every animated layer's values frame by frame, plus the layer's moments), `set key=value`
|
|
@@ -72,7 +72,7 @@ The engine is one classic script, so it runs from a double-clicked file, a CDN o
|
|
|
72
72
|
|
|
73
73
|
```html
|
|
74
74
|
<div id="app"></div>
|
|
75
|
-
<script src="https://cdn.jsdelivr.net/npm/greenlight-motion@0.3.
|
|
75
|
+
<script src="https://cdn.jsdelivr.net/npm/greenlight-motion@0.3.4/dist/greenlight-motion.js"></script>
|
|
76
76
|
<script>
|
|
77
77
|
GreenLightMotion.mountStudio(document.getElementById('app'), {
|
|
78
78
|
project: { /* project.json */ },
|
|
@@ -14804,8 +14804,18 @@ var num3 = (value, fallback = 0) => {
|
|
|
14804
14804
|
const parsed = Number(value);
|
|
14805
14805
|
return Number.isFinite(parsed) ? parsed : fallback;
|
|
14806
14806
|
};
|
|
14807
|
+
var SLOW_SHARE = 0.25;
|
|
14807
14808
|
var LOG_SLOW = -Math.log(SPEED_MIN);
|
|
14808
14809
|
var LOG_FAST = Math.log(SPEED_MAX);
|
|
14810
|
+
var speedToNorm = (speed) => {
|
|
14811
|
+
const v = clamp2(num3(speed, 1), SPEED_MIN, SPEED_MAX);
|
|
14812
|
+
return v <= 1 ? SLOW_SHARE * (1 + Math.log(v) / LOG_SLOW) : SLOW_SHARE + (1 - SLOW_SHARE) * (Math.log(v) / LOG_FAST);
|
|
14813
|
+
};
|
|
14814
|
+
var normToSpeed = (norm) => {
|
|
14815
|
+
const n2 = clamp2(num3(norm, 0), 0, 1);
|
|
14816
|
+
const v = n2 <= SLOW_SHARE ? Math.exp((n2 / SLOW_SHARE - 1) * LOG_SLOW) : Math.exp((n2 - SLOW_SHARE) / (1 - SLOW_SHARE) * LOG_FAST);
|
|
14817
|
+
return clamp2(v, SPEED_MIN, SPEED_MAX);
|
|
14818
|
+
};
|
|
14809
14819
|
var cubic = (a, b2, c, d, t) => {
|
|
14810
14820
|
const u = 1 - t;
|
|
14811
14821
|
return u * u * u * a + 3 * u * u * t * b2 + 3 * u * t * t * c + t * t * t * d;
|
|
@@ -14815,10 +14825,154 @@ var cubicDeriv = (a, b2, c, d, t) => {
|
|
|
14815
14825
|
return 3 * u * u * (b2 - a) + 6 * u * t * (c - b2) + 3 * t * t * (d - c);
|
|
14816
14826
|
};
|
|
14817
14827
|
var speedCurveCache = /* @__PURE__ */ new WeakMap();
|
|
14828
|
+
var TANGENT_MODES = ["auto", "linked", "separate", "flat"];
|
|
14829
|
+
var TANGENT_SET = new Set(TANGENT_MODES);
|
|
14830
|
+
var isPlotSpeedCurve = (points) => Array.isArray(points) && points.some((p) => p && TANGENT_SET.has(p.tan));
|
|
14831
|
+
function speedAutoSlopes(xs, ys) {
|
|
14832
|
+
const n2 = xs.length;
|
|
14833
|
+
const m = new Array(n2).fill(0);
|
|
14834
|
+
if (n2 < 2) return m;
|
|
14835
|
+
const h = [];
|
|
14836
|
+
const d = [];
|
|
14837
|
+
for (let i = 0; i < n2 - 1; i += 1) {
|
|
14838
|
+
h[i] = xs[i + 1] - xs[i];
|
|
14839
|
+
d[i] = h[i] > 1e-9 ? (ys[i + 1] - ys[i]) / h[i] : 0;
|
|
14840
|
+
}
|
|
14841
|
+
if (n2 === 2) {
|
|
14842
|
+
m[0] = d[0];
|
|
14843
|
+
m[1] = d[0];
|
|
14844
|
+
return m;
|
|
14845
|
+
}
|
|
14846
|
+
for (let i = 1; i < n2 - 1; i += 1) {
|
|
14847
|
+
if (d[i - 1] * d[i] <= 0) m[i] = 0;
|
|
14848
|
+
else {
|
|
14849
|
+
const w1 = 2 * h[i] + h[i - 1];
|
|
14850
|
+
const w2 = h[i] + 2 * h[i - 1];
|
|
14851
|
+
m[i] = (w1 + w2) / (w1 / d[i - 1] + w2 / d[i]);
|
|
14852
|
+
}
|
|
14853
|
+
}
|
|
14854
|
+
const end = (h0, h1, d0, d1) => {
|
|
14855
|
+
let s = ((2 * h0 + h1) * d0 - h0 * d1) / (h0 + h1);
|
|
14856
|
+
if (Math.sign(s) !== Math.sign(d0)) s = 0;
|
|
14857
|
+
else if (Math.sign(d0) !== Math.sign(d1) && Math.abs(s) > Math.abs(3 * d0)) s = 3 * d0;
|
|
14858
|
+
return s;
|
|
14859
|
+
};
|
|
14860
|
+
m[0] = end(h[0], h[1], d[0], d[1]);
|
|
14861
|
+
m[n2 - 1] = end(h[n2 - 2], h[n2 - 3], d[n2 - 2], d[n2 - 3]);
|
|
14862
|
+
return m;
|
|
14863
|
+
}
|
|
14864
|
+
function fitHandleK(n0, m, k, dx) {
|
|
14865
|
+
const dn = m * k * dx;
|
|
14866
|
+
const y = n0 + dn;
|
|
14867
|
+
if (y > 1 && dn > 0) return k * ((1 - n0) / dn);
|
|
14868
|
+
if (y < 0 && dn < 0) return k * ((0 - n0) / dn);
|
|
14869
|
+
return k;
|
|
14870
|
+
}
|
|
14871
|
+
function plotSegmentKinks(seg) {
|
|
14872
|
+
const ys = [seg.y0, seg.y1, seg.y2, seg.y3];
|
|
14873
|
+
if (Math.min(...ys) >= SLOW_SHARE || Math.max(...ys) <= SLOW_SHARE) return [];
|
|
14874
|
+
const f2 = (t) => cubic(seg.y0, seg.y1, seg.y2, seg.y3, t) - SLOW_SHARE;
|
|
14875
|
+
const out = [];
|
|
14876
|
+
const N = 24;
|
|
14877
|
+
for (let j = 0; j < N; j += 1) {
|
|
14878
|
+
let lo = j / N;
|
|
14879
|
+
let hi = (j + 1) / N;
|
|
14880
|
+
if (f2(lo) * f2(hi) >= 0) continue;
|
|
14881
|
+
for (let i = 0; i < 40; i += 1) {
|
|
14882
|
+
const mid = (lo + hi) * 0.5;
|
|
14883
|
+
if (f2(lo) * f2(mid) <= 0) hi = mid;
|
|
14884
|
+
else lo = mid;
|
|
14885
|
+
}
|
|
14886
|
+
out.push((lo + hi) * 0.5);
|
|
14887
|
+
}
|
|
14888
|
+
return out;
|
|
14889
|
+
}
|
|
14890
|
+
function resolvePlotCurve(points) {
|
|
14891
|
+
const sorted = points.filter((p) => p && Number.isFinite(Number(p.x)) && Number.isFinite(Number(p.y))).map((p) => {
|
|
14892
|
+
const y = clamp2(num3(p.y, 1), SPEED_MIN, SPEED_MAX);
|
|
14893
|
+
return {
|
|
14894
|
+
x: num3(p.x),
|
|
14895
|
+
y,
|
|
14896
|
+
n: speedToNorm(y),
|
|
14897
|
+
tan: TANGENT_SET.has(p.tan) ? p.tan : "auto",
|
|
14898
|
+
mIn: num3(p.mIn),
|
|
14899
|
+
mOut: num3(p.mOut),
|
|
14900
|
+
kIn: clamp2(num3(p.kIn, AUTO_HANDLE), 0, 1),
|
|
14901
|
+
kOut: clamp2(num3(p.kOut, AUTO_HANDLE), 0, 1)
|
|
14902
|
+
};
|
|
14903
|
+
}).sort((a, b2) => a.x - b2.x);
|
|
14904
|
+
if (sorted.length === 0) return null;
|
|
14905
|
+
const auto = speedAutoSlopes(sorted.map((p) => p.x), sorted.map((p) => p.n));
|
|
14906
|
+
const anchors = sorted.map((p, i) => {
|
|
14907
|
+
let { mIn, mOut, kIn, kOut } = p;
|
|
14908
|
+
if (p.tan === "auto") {
|
|
14909
|
+
mIn = auto[i];
|
|
14910
|
+
mOut = auto[i];
|
|
14911
|
+
kIn = AUTO_HANDLE;
|
|
14912
|
+
kOut = AUTO_HANDLE;
|
|
14913
|
+
} else if (p.tan === "flat") {
|
|
14914
|
+
mIn = 0;
|
|
14915
|
+
mOut = 0;
|
|
14916
|
+
}
|
|
14917
|
+
const prev = sorted[i - 1];
|
|
14918
|
+
const next = sorted[i + 1];
|
|
14919
|
+
const dxL = prev ? p.x - prev.x : 0;
|
|
14920
|
+
const dxR = next ? next.x - p.x : 0;
|
|
14921
|
+
const kInE = prev ? fitHandleK(p.n, mIn, kIn, -dxL) : 0;
|
|
14922
|
+
const kOutE = next ? fitHandleK(p.n, mOut, kOut, dxR) : 0;
|
|
14923
|
+
return {
|
|
14924
|
+
x: p.x,
|
|
14925
|
+
y: p.y,
|
|
14926
|
+
n: p.n,
|
|
14927
|
+
tan: p.tan,
|
|
14928
|
+
mIn,
|
|
14929
|
+
mOut,
|
|
14930
|
+
kIn: kInE,
|
|
14931
|
+
kOut: kOutE,
|
|
14932
|
+
// The handles as plot-space offsets — what the editor draws and drags.
|
|
14933
|
+
pIn: prev ? { x: -kInE * dxL, y: -mIn * kInE * dxL } : null,
|
|
14934
|
+
pOut: next ? { x: kOutE * dxR, y: mOut * kOutE * dxR } : null
|
|
14935
|
+
};
|
|
14936
|
+
});
|
|
14937
|
+
const segments = [];
|
|
14938
|
+
for (let i = 0; i < anchors.length - 1; i += 1) {
|
|
14939
|
+
const a = anchors[i];
|
|
14940
|
+
const b2 = anchors[i + 1];
|
|
14941
|
+
const seg = {
|
|
14942
|
+
space: "norm",
|
|
14943
|
+
linear: false,
|
|
14944
|
+
x0: a.x,
|
|
14945
|
+
x1: a.x + a.pOut.x,
|
|
14946
|
+
x2: b2.x + b2.pIn.x,
|
|
14947
|
+
x3: b2.x,
|
|
14948
|
+
y0: a.n,
|
|
14949
|
+
y1: a.n + a.pOut.y,
|
|
14950
|
+
y2: b2.n + b2.pIn.y,
|
|
14951
|
+
y3: b2.n
|
|
14952
|
+
};
|
|
14953
|
+
seg.flat = Math.abs(seg.y1 - seg.y0) < 1e-12 && Math.abs(seg.y2 - seg.y0) < 1e-12 && Math.abs(seg.y3 - seg.y0) < 1e-12;
|
|
14954
|
+
seg.kinks = seg.flat ? [] : plotSegmentKinks(seg);
|
|
14955
|
+
seg.area = segmentArea(seg, 1);
|
|
14956
|
+
segments.push(seg);
|
|
14957
|
+
}
|
|
14958
|
+
return {
|
|
14959
|
+
space: "norm",
|
|
14960
|
+
anchors,
|
|
14961
|
+
segments,
|
|
14962
|
+
area: segments.reduce((sum, s) => sum + s.area, 0),
|
|
14963
|
+
first: anchors[0],
|
|
14964
|
+
last: anchors[anchors.length - 1]
|
|
14965
|
+
};
|
|
14966
|
+
}
|
|
14818
14967
|
function resolveSpeedCurve(points) {
|
|
14819
14968
|
if (!Array.isArray(points) || points.length === 0) return null;
|
|
14820
14969
|
const cached = speedCurveCache.get(points);
|
|
14821
14970
|
if (cached) return cached;
|
|
14971
|
+
if (isPlotSpeedCurve(points)) {
|
|
14972
|
+
const plot = resolvePlotCurve(points);
|
|
14973
|
+
if (plot) speedCurveCache.set(points, plot);
|
|
14974
|
+
return plot;
|
|
14975
|
+
}
|
|
14822
14976
|
const sorted = points.filter((p) => p && Number.isFinite(Number(p.x)) && Number.isFinite(Number(p.y))).map((p) => ({
|
|
14823
14977
|
x: num3(p.x),
|
|
14824
14978
|
y: clamp2(num3(p.y, 1), SPEED_MIN, SPEED_MAX),
|
|
@@ -14886,16 +15040,18 @@ function solveSegmentT(seg, x) {
|
|
|
14886
15040
|
if (span <= 1e-9) return 0;
|
|
14887
15041
|
let t = clamp2((x - seg.x0) / span, 0, 1);
|
|
14888
15042
|
if (seg.linear) return t;
|
|
14889
|
-
|
|
15043
|
+
const plot = seg.space === "norm";
|
|
15044
|
+
const tol = plot ? 1e-14 : 1e-7;
|
|
15045
|
+
for (let i = 0; i < (plot ? 30 : 8); i += 1) {
|
|
14890
15046
|
const err = cubic(seg.x0, seg.x1, seg.x2, seg.x3, t) - x;
|
|
14891
|
-
if (Math.abs(err) <
|
|
15047
|
+
if (Math.abs(err) < tol) return t;
|
|
14892
15048
|
const d = cubicDeriv(seg.x0, seg.x1, seg.x2, seg.x3, t);
|
|
14893
|
-
if (Math.abs(d) < 1e-7) break;
|
|
15049
|
+
if (Math.abs(d) < (plot ? 1e-12 : 1e-7)) break;
|
|
14894
15050
|
t = clamp2(t - err / d, 0, 1);
|
|
14895
15051
|
}
|
|
14896
15052
|
let lo = 0;
|
|
14897
15053
|
let hi = 1;
|
|
14898
|
-
for (let i = 0; i < 24; i += 1) {
|
|
15054
|
+
for (let i = 0; i < (plot ? 60 : 24); i += 1) {
|
|
14899
15055
|
const mid = (lo + hi) * 0.5;
|
|
14900
15056
|
if (cubic(seg.x0, seg.x1, seg.x2, seg.x3, mid) < x) lo = mid;
|
|
14901
15057
|
else hi = mid;
|
|
@@ -14910,6 +15066,7 @@ var GAUSS = [
|
|
|
14910
15066
|
];
|
|
14911
15067
|
function segmentArea(seg, tEnd) {
|
|
14912
15068
|
if (tEnd <= 0) return 0;
|
|
15069
|
+
if (seg.space === "norm") return plotSegmentArea(seg, tEnd);
|
|
14913
15070
|
if (seg.linear) {
|
|
14914
15071
|
const width = (seg.x3 - seg.x0) * tEnd;
|
|
14915
15072
|
const yAtT = seg.y0 + (seg.y3 - seg.y0) * tEnd;
|
|
@@ -14923,6 +15080,25 @@ function segmentArea(seg, tEnd) {
|
|
|
14923
15080
|
}
|
|
14924
15081
|
return sum * half;
|
|
14925
15082
|
}
|
|
15083
|
+
function plotSegmentArea(seg, tEnd) {
|
|
15084
|
+
const xAt = (t) => cubic(seg.x0, seg.x1, seg.x2, seg.x3, t);
|
|
15085
|
+
if (seg.flat) return (xAt(tEnd) - seg.x0) * normToSpeed(seg.y0);
|
|
15086
|
+
const cuts = [0, ...(seg.kinks || []).filter((t) => t < tEnd), tEnd];
|
|
15087
|
+
const SUB = 8;
|
|
15088
|
+
let sum = 0;
|
|
15089
|
+
for (let c = 0; c < cuts.length - 1; c += 1) {
|
|
15090
|
+
const step = (cuts[c + 1] - cuts[c]) / SUB;
|
|
15091
|
+
for (let s = 0; s < SUB; s += 1) {
|
|
15092
|
+
const half = step * 0.5;
|
|
15093
|
+
const mid = cuts[c] + s * step + half;
|
|
15094
|
+
for (const [node, weight] of GAUSS) {
|
|
15095
|
+
const t = mid + half * node;
|
|
15096
|
+
sum += weight * half * normToSpeed(cubic(seg.y0, seg.y1, seg.y2, seg.y3, t)) * cubicDeriv(seg.x0, seg.x1, seg.x2, seg.x3, t);
|
|
15097
|
+
}
|
|
15098
|
+
}
|
|
15099
|
+
}
|
|
15100
|
+
return sum;
|
|
15101
|
+
}
|
|
14926
15102
|
var getAverageSpeed = (points) => {
|
|
14927
15103
|
const curve = resolveSpeedCurve(points);
|
|
14928
15104
|
if (!curve) return 1;
|