Keyframe
Reading a property's authored keyframes: frame, value, interpolation and handles, exactly as the timeline stores them.
M10 DOM2-2: one keyframe, READ-ONLY - the get_keyframes WIRE SHAPE verbatim per entry (frame, value in the property's wire kind, interpolation "linear" | "bezier" | "hold", bezier in/out {x, y} handles when present, spatial tangents on spatial tracks - any future wire member lands here by the same rule) plus the derived time in seconds. Scripts never WRITE keys (the E25 model: the overlay, never the document).
frame
Type: number (accessor).
The canonical grid frame (integer).
Determinism: pure.
A Keyframe is the get_keyframes wire entry verbatim plus the derived time: frame, value in the property's wire kind, interpolation, bezier in and out handles where the key has them, spatial tangents on spatial tracks. Any future wire member lands here by the same rule, which is how the surface stays open ended. Scripts never write keys: set() drives the overlay, the document's tracks stay untouched.
Worked example: Keys sit on the frame grid
The scene. One pale card near the centre with its rotation keyframed from 0 to 90 degrees across the comp, for scripts that read and reshape keyframed motion.
- The comp Keyed: 640 by 360 at 25 fps, 100 frames (4 seconds).
- Card: a solid layer, at [320, 180], 120 by 80, filled e8e6e3.
- Keyframe transform.rotation at frame 0: 0 (linear).
- Keyframe transform.rotation at frame 96: 90 (linear).
The script. Add a project script named Frame reader and paste:
export const outputs = ["layer_1:transform.opacity"];
export function frame() {
const last = prop("Card:transform.rotation").keyAt(1);
set("layer_1:transform.opacity", last.frame - 26);
}
Line by line:
- Line 4: The second key of the rotation track, as the wire stores it.
- Line 5: Its frame member is the canonical grid integer 96, driving the opacity to 70.
What you see. The card holds 70 percent opacity, read straight off the last key's frame number.
time
Type: number (accessor).
frame / fps, in seconds (derived).
Determinism: pure.
Worked example: Frames into seconds
The scene. One pale card near the centre with its rotation keyframed from 0 to 90 degrees across the comp, for scripts that read and reshape keyframed motion.
- The comp Keyed: 640 by 360 at 25 fps, 100 frames (4 seconds).
- Card: a solid layer, at [320, 180], 120 by 80, filled e8e6e3.
- Keyframe transform.rotation at frame 0: 0 (linear).
- Keyframe transform.rotation at frame 96: 90 (linear).
The script. Add a project script named Time reader and paste:
export const outputs = ["layer_1:transform.opacity"];
export function frame() {
const last = prop("Card:transform.rotation").keyAt(1);
const in_window = last.time > 3.8 && last.time < 3.9;
set("layer_1:transform.opacity", in_window ? 75 : 5);
}
Line by line:
- Lines 4-5: time is the derived member: frame divided by the comp's fps. Frame 96 at 25 fps is 3.84 seconds, comfortably inside the window.
- Line 6: The proof value drives only while the derived seconds agree with the grid.
What you see. The card rests at 75 percent opacity: the last key's derived time landed on 3.84 seconds.
value
Type: T (accessor).
The key's value in the property's wire kind.
Determinism: pure.
Worked example: Reading a key's stored value
The scene. One pale card near the centre with its rotation keyframed from 0 to 90 degrees across the comp, for scripts that read and reshape keyframed motion.
- The comp Keyed: 640 by 360 at 25 fps, 100 frames (4 seconds).
- Card: a solid layer, at [320, 180], 120 by 80, filled e8e6e3.
- Keyframe transform.rotation at frame 0: 0 (linear).
- Keyframe transform.rotation at frame 96: 90 (linear).
The script. Add a project script named Value reader and paste:
export const outputs = ["layer_1:transform.opacity"];
export function frame() {
const last = prop("Card:transform.rotation").keyAt(1);
set("layer_1:transform.opacity", last.value + 5);
}
Line by line:
- Line 4: The key's value arrives in the property's wire kind: a scalar 90 for this rotation key. Points arrive as [x, y], colours as their wire shape, exactly as get_keyframes stores them.
- Line 5: The stored target drives another property: read and react.
What you see. The card brightens to 95 percent opacity, five over the last key's stored 90 degrees.
interpolation
Type: string (accessor).
"linear" | "bezier" | "hold" (the wire spelling).
Determinism: pure.
Worked example: Linear, bezier or hold
The scene. One pale card near the centre with its rotation keyframed from 0 to 90 degrees across the comp, for scripts that read and reshape keyframed motion.
- The comp Keyed: 640 by 360 at 25 fps, 100 frames (4 seconds).
- Card: a solid layer, at [320, 180], 120 by 80, filled e8e6e3.
- Keyframe transform.rotation at frame 0: 0 (linear).
- Keyframe transform.rotation at frame 96: 90 (linear).
The script. Add a project script named Interpolation reader and paste:
export const outputs = ["layer_1:transform.opacity"];
export function frame() {
const first = prop("Card:transform.rotation").keyAt(0);
set("layer_1:transform.opacity", first.interpolation === "linear" ? 80 : 10);
}
Line by line:
- Lines 4-5: interpolation carries the wire spelling: linear, bezier or hold. The fixture's first key is linear, so the proof value drives. A bezier key would also carry its in and out handles on the same object.
What you see. The card sits at 80 percent opacity because the first rotation key reports linear interpolation.