venture-studio / studio /compile_24fps.py
AlterProgramming's picture
Initial venture-studio deploy: pixel_cursor + studio + app.py
1e3ce4c verified
Raw
History Blame Contribute Delete
2.51 kB
"""24fps compiler: keyframe cursors + AnimationSegments -> dense 24fps FrameStack.
The on-twos discipline (A1) is realized here. For each segment with stride=N, the
compiler advances to a new sample every N frames; intermediate frames hold the
prior sample. This is what makes the cursor's temporal layer load-bearing for
animation specifically — not decoration around a black-box video model.
This is real, runnable today on numpy alone. It alpha-blends keyframe artifacts
along eased curves; with a backend providing write_motion, the same compile path
fills the same FrameStack from model-generated samples instead.
"""
from __future__ import annotations
from typing import Iterable
import numpy as np
from pixel_cursor import PixelCursor, AnimationSegment, FrameStack, Image, interp_t
from pixel_cursor.artifact import _new_framestack
def compile_keyframes(
keyframe_cursors: Iterable[PixelCursor],
segments: Iterable[AnimationSegment],
target_fps: int = 24,
) -> FrameStack:
kf = list(keyframe_cursors)
segs = list(segments)
if len(kf) < 2:
raise ValueError("Need at least 2 keyframe cursors")
if len(segs) != len(kf) - 1:
raise ValueError(
f"Expected {len(kf) - 1} segments for {len(kf)} keyframes; got {len(segs)}"
)
for cur in kf:
if not isinstance(cur.artifact, Image):
raise TypeError(
f"Keyframe cursors must point at Image artifacts; got {type(cur.artifact).__name__}"
)
total_frames = segs[-1].end_frame
ref_pixels = kf[0].artifact.pixels
h, w, c = ref_pixels.shape
out = np.zeros((total_frames, h, w, c), dtype=np.uint8)
for seg_idx, seg in enumerate(segs):
a = kf[seg_idx].artifact.pixels
b = kf[seg_idx + 1].artifact.pixels
if a.shape != b.shape:
raise ValueError(
f"Segment {seg_idx}: keyframe shapes differ {a.shape} vs {b.shape}"
)
seg_len = seg.end_frame - seg.start_frame
for f in range(seg.start_frame, seg.end_frame):
rel = f - seg.start_frame
sample_rel = (rel // seg.stride) * seg.stride
denom = max(1, seg_len - 1)
t = sample_rel / denom
eased = interp_t(seg.curve_name, t)
blended = (1.0 - eased) * a.astype(np.float32) + eased * b.astype(np.float32)
out[f] = np.clip(blended, 0, 255).astype(np.uint8)
return _new_framestack(out, fps=target_fps, name="compiled-24fps")