Skip to content

feat: Lut colour grade, Motion presets/keyframes and Effect names on Clip - #110

Open
ankit-v2-3 wants to merge 2 commits into
mainfrom
ankit/editor-lut-motion
Open

ankit-v2-3 wants to merge 2 commits into
mainfrom
ankit/editor-lut-motion

Conversation

@ankit-v2-3

Copy link
Copy Markdown
Collaborator

Summary

Replaces #108 and #109 (closed when the branch was renamed). Two editor features matching the server-side contract in Spext/streaming_service#75 (LUT, merged), Spext/streaming_service#76 (motion) and Spext/videodb-server#1052 (LUT URL pass-through).

Lut colour grade

  • Lut(src, intensity=1.0): src is the URL of a 3D .cube file; intensity blends from untouched (0) to fully graded (1).
  • Lut.from_file(connection, path, intensity) uploads a local .cube through the collection upload URL (the same upload_bytes path large timelines use). The server rewrites that URL to a readable one before compile.
  • Clip(lut=...) grades one clip before its filter; Timeline.lut grades the whole frame under captions. Not valid on audio or caption clips.

Motion and effects

  • Motion(enter=None, exit=None, keyframes=None) → Clip(motion=...).
  • 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; the server now rejects unknown names.
  • Light client-side validation; the full rules (not on audio/caption, motion exclusive with effect and with a transition side, fits the clip, 32 keyframes max, ≥ 1 ms spacing) are enforced by the service with a public error.

All new fields are emitted only when set, so existing timelines serialize exactly as before. Older services ignore them silently, so deploy the service first.

from videodb.editor import Lut, Motion, MotionPreset, Keyframe, KeyframeProperty, Easing, Effect

timeline.lut = Lut.from_file(conn, "teal-orange.cube", intensity=0.8)

title = 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, lut=Lut(src=url))

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 (6 new in tests/test_editor_lut.py, 11 new in tests/test_editor_motion.py)
  • Against a dev deployment of Spext/streaming_service#76: generate_stream() with a timeline LUT, a slide_up/fade text clip, a keyframed logo and a ken_burns clip

🤖 Generated with Claude Code

ankit-v2-3 and others added 2 commits November 10, 2026 18:36
Adds Lut(src, intensity) for 3D .cube lookup tables. Clip(lut=...) grades
one clip before its filter; Timeline.lut grades the whole composited
frame under captions. Lut.from_file uploads a local .cube through the
collection upload URL, the same path large timelines use. The field is
emitted only when set, so existing timelines serialize unchanged.

Co-Authored-By: Claude Fable 5.1 <noreply@anthropic.com>
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>

This branch has not been deployed

No deployments
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