A Kdenlive timeline as data, edited by the SAME verbs the scripting fork answers over HTTP, with no Kdenlive running.
The fork's route catalog (hive-kdenlive.kdenlive.routes) names what an agent
can ask of a timeline: import media, add a track, insert a clip at a frame,
open space, extract a zone. apply-verb answers those route ids against a
value instead of a live application, and ->document turns the value into
an MLT document melt renders. That is the :document transport: the same
vocabulary, a different place for the timeline to live.
State {:name "edit" :profile {:width 1920 :height 1080 :fps 25} :next-id 1 :bin {"1" {:id "1" :resource "/clips/a.mkv" :name "a.mkv" :length 250}} :tracks [{:id "2" :name "V1" :audio? false :clips [{:id "3" :bin "1" :position 0 :in 0 :out 249}]}]}
Frames are integers throughout; :out is inclusive, as in MLT. Clips on a track never overlap: a verb that would make them overlap, or that would have to cut a clip in two, is REFUSED with a named error rather than approximated. Ids are strings, as the fork returns them.
Every verb is (fn [state params ctx] -> {:ok {:state s :result r}} | {:error ...}).
ctx carries effects the pure core cannot perform: :probe, a function from
a media path to {:length frames} or {:error ...}.
Portable: clojure.core and clojure.string only. No for (clojurust
mishandles :when/:let in it), booleans compared with =.
A Kdenlive timeline as data, edited by the SAME verbs the scripting fork
answers over HTTP, with no Kdenlive running.
The fork's route catalog (hive-kdenlive.kdenlive.routes) names what an agent
can ask of a timeline: import media, add a track, insert a clip at a frame,
open space, extract a zone. `apply-verb` answers those route ids against a
value instead of a live application, and `->document` turns the value into
an MLT document melt renders. That is the :document transport: the same
vocabulary, a different place for the timeline to live.
State
{:name "edit"
:profile {:width 1920 :height 1080 :fps 25}
:next-id 1
:bin {"1" {:id "1" :resource "/clips/a.mkv" :name "a.mkv" :length 250}}
:tracks [{:id "2" :name "V1" :audio? false
:clips [{:id "3" :bin "1" :position 0 :in 0 :out 249}]}]}
Frames are integers throughout; :out is inclusive, as in MLT. Clips on a
track never overlap: a verb that would make them overlap, or that would have
to cut a clip in two, is REFUSED with a named error rather than approximated.
Ids are strings, as the fork returns them.
Every verb is (fn [state params ctx] -> {:ok {:state s :result r}} | {:error ...}).
`ctx` carries effects the pure core cannot perform: :probe, a function from
a media path to {:length frames} or {:error ...}.
Portable: clojure.core and clojure.string only. No `for` (clojurust
mishandles :when/:let in it), booleans compared with `=`.(->document state)The MLT document melt renders for STATE.
Details that are load-bearing, each measured by sampling rendered pixels or audio levels:
black under mlt_service=color.mix transition. Without it only
the top track's audio is heard.The MLT document melt renders for STATE. Details that are load-bearing, each measured by sampling rendered pixels or audio levels: - a background track (black, the whole duration) sits under every other track, because a blank with nothing below it renders white; the black producer's resource is the bare colour `black` under mlt_service=color. - every video track is composited onto the tracks below by a qtblend transition, as Kdenlive's internal ones do. Without it the top track REPLACES what is under it, alpha and all: a transparent overlay renders on black. - every track is summed into the mix by a `mix` transition. Without it only the top track's audio is heard. - the profile's display aspect is the frame's own, so 1080x1920 is 9:16.
(apply-verb state route-id params ctx)Answer route ROUTE-ID against STATE. {:ok {:state :result}} | {:error ...}.
Answer route ROUTE-ID against STATE. {:ok {:state :result}} | {:error ...}.
(at-project-rate length source-fps project-fps)LENGTH frames counted at SOURCE-FPS ([num den], or nil for already at the project rate), as whole frames at PROJECT-FPS. Rounds down: a clip never claims a frame its source does not have.
A probe counts at the rate melt chose for the file, 25 fps for audio and stills, and a 3 s WAV in a 30 fps project is 90 frames, not 75 (measured 2026-09-15).
LENGTH frames counted at SOURCE-FPS ([num den], or nil for already at the project rate), as whole frames at PROJECT-FPS. Rounds down: a clip never claims a frame its source does not have. A probe counts at the rate melt chose for the file, 25 fps for audio and stills, and a 3 s WAV in a 30 fps project is 90 frames, not 75 (measured 2026-09-15).
(clip-end clip)The first frame AFTER clip on its track.
The first frame AFTER clip on its track.
(duration state)Frames from 0 to the end of the last clip on any track.
Frames from 0 to the end of the last clip on any track.
(effect-spec clip effect-id params)The filter EFFECT-ID puts on CLIP: {:ok {:service :in :out :params}} with in/out relative to the clip, or an error.
Kdenlive's own effect ids keep Kdenlive's meaning (data/effects/*.xml):
fade_from_black / fade_to_black brightness over the first/last
duration frames; with alpha the picture fades to transparent
instead of to black, which is what an overlay wants
fadein / fadeout volume, gain 0->1 / 1->0
Any other id is an MLT service applied to the whole clip, params as given.
The filter EFFECT-ID puts on CLIP: {:ok {:service :in :out :params}} with
in/out relative to the clip, or an error.
Kdenlive's own effect ids keep Kdenlive's meaning (data/effects/*.xml):
fade_from_black / fade_to_black brightness over the first/last
`duration` frames; with `alpha` the picture fades to transparent
instead of to black, which is what an overlay wants
fadein / fadeout volume, gain 0->1 / 1->0
Any other id is an MLT service applied to the whole clip, params as given.Catalog route id -> verb. The ids are hive-kdenlive.kdenlive.routes's, so a caller holding the fork's vocabulary reaches the same operation here.
Catalog route id -> verb. The ids are hive-kdenlive.kdenlive.routes's, so a caller holding the fork's vocabulary reaches the same operation here.
cljdoc builds & hosts documentation for Clojure/Script libraries
| Ctrl+k | Jump to recent docs |
| ← | Move to previous article |
| → | Move to next article |
| Ctrl+/ | Jump to the search field |