Skip to content

feat: Motion presets, keyframes and Effect names on Clip - #109

Merged
ankit-v2-3 merged 1 commit into
ankit/editor-lutfrom
ankit/editor-motion
Nov 11, 2026
Merged

ankit-v2-3 merged 1 commit into
ankit/editor-lutfrom
ankit/editor-motion

Conversation

@ankit-v2-3

Copy link
Copy Markdown
Collaborator

Summary

Stacked on #108 (LUT). Base will move to main automatically when #108 merges. Server side: Spext/streaming_service#76 (videodb-server needs no change, the timeline dict passes through).

  • Motion(enter=None, exit=None, keyframes=None) → Clip(motion=...). Emitted only when set.
  • MotionPreset(preset, duration=0.4, amount=0.08, easing=ease_out) with MotionPresetName: fade, slide_up/down/left/right, pop, wipe_left/right. Slides and pop include a fade; an exit runs the entrance backwards.
  • Keyframe(time, value, easing=linear) on KeyframeProperty: offset.x, offset.y, scale, rotation (degrees, clockwise), opacity. Clip-relative times, easing on the destination keyframe, values replace the base and hold outside their range. Easing: linear, ease_in, ease_out, ease_in_out, back_out.
  • Effect enum for the server's defined set: zoom_in, shake, zoom_out, ken_burns, pulse. Clip.effect still accepts a plain string.
  • Light client-side validation (ranges, unknown property, non-increasing times); the full rules (not on audio/caption, exclusive with effect, exclusive with a transition side, fits the clip, 32 keyframes max) are enforced by the service with a public error.
from videodb.editor import Motion, MotionPreset, Keyframe, KeyframeProperty, Easing, Effect

clip = Clip(asset=TextAsset(text="Hello"), duration=4,
            motion=Motion(enter=MotionPreset("slide_up", duration=0.5),
                          exit=MotionPreset("fade", duration=0.3)))

logo = Clip(asset=ImageAsset(id=image.id), duration=6, fit=None, position=Position.top_right,
            motion=Motion(keyframes={KeyframeProperty.scale: [Keyframe(0, 0.6), Keyframe(0.6, 1, Easing.back_out)],
                                     KeyframeProperty.opacity: [Keyframe(0, 0), Keyframe(0.4, 1)]}))

broll = Clip(asset=VideoAsset(id=video.id), duration=8, effect=Effect.ken_burns)

Docstring notes the text-layer caveat: text is drawn on a canvas-sized layer, so scale/rotation act about the canvas centre unless TextAsset.width/height is set.

Test plan

  • pytest — 22 passed (11 new in tests/test_editor_motion.py)
  • Against a dev deployment of Spext/streaming_service#76: generate_stream() with a slide_up/fade text clip, a keyframed logo and a ken_burns clip

🤖 Generated with Claude Code

Adds Motion(enter, exit, keyframes) with MotionPreset (fade, slides,
pop, wipes; duration, amount, easing) and Keyframe (time, value, easing)
on offset.x/y, scale, rotation and opacity, plus the Effect enum for the
server's named effect set. Clip(motion=...) is emitted only when set, so
existing timelines serialize unchanged.

Co-Authored-By: Claude Fable 5.1 <noreply@anthropic.com>
@ankit-v2-3
ankit-v2-3 merged commit e2843ee into ankit/editor-lut Nov 11, 2026
1 check failed
@ankit-v2-3
ankit-v2-3 deleted the ankit/editor-motion branch November 11, 2026 15:58
@ankit-v2-3

Copy link
Copy Markdown
Collaborator Author

Folded into #110 (single SDK PR for LUT + motion, base main).

@ankit-v2-3 ankit-v2-3 mentioned this pull request Nov 11, 2026
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Labels

None yet

Projects

None yet

Development

Successfully merging this pull request may close these issues.

1 participant