Lay root clips at timeline positions, not source positions

The root timeline placed each clip at its media-in (source) frame, which is
fractional (media-offset 50641.358…), so adjacent clips of a contiguous multicam
edit showed ~1-frame gaps; clicking a gap hit no segment and local->source
returned the last frame ("glitch to the end"). The OTIO timeline is gapless —
clips tile by their accumulated timeline :start. Carry :start through from-otio
and lay content-segments at [start, start+len] (src stays the source range), so
the assembled program is contiguous and maps cleanly to scattered source frames.

Co-Authored-By: Claude Opus 4.8 <noreply@anthropic.com>
This commit is contained in:
Your Name 2026-06-29 02:34:11 -04:00
parent 0d73b204e6
commit 8ee2cdf7b9
2 changed files with 18 additions and 10 deletions

View file

@ -170,15 +170,19 @@
"The clip-segments that make up context `ctx` — what you draw and select
against. An annotation's marks reference clips, so that's just `resolve`. The
root timeline doesn't enumerate its clips (they're a parentless pool), so
there it's the pool laid in root's identity coordinate (local = source)."
there it's the pool laid at each clip's TIMELINE position (:start) with :src =
its source range — so the assembled program tiles contiguously even though
the underlying source frames are scattered (and fractional)."
[scene ctx]
(if (= :timeline (:type (grp scene ctx)))
(->> (:groups scene)
(keep (fn [[gid g]]
(when (= :clip (:type g))
(let [seg (first (resolve scene gid))]
(assoc seg :mark gid :local (:src seg)))))) ; ref clip by group id
(sort-by (comp first :src))
(let [seg (first (resolve scene gid))
[sa sb] (:src seg)
st (:start g 0)]
(assoc seg :mark gid :local [st (+ st (- sb sa))])))))
(sort-by (comp first :local))
vec)
(resolve scene ctx)))
@ -278,6 +282,7 @@
clips (into {} (for [t vtracks c (:clips t)]
[(keyword (:id c))
{:type :clip :parent nil :name (:name c)
:start (:start c) ; timeline position (frames)
:marks [{:id (keyword (str (:id c) "-m"))
:start (:media-in c)
:end (+ (:media-in c) (:duration c))

View file

@ -8,9 +8,10 @@
(def base
{:tracks {:t0 {:name "A-roll"} :t1 {:name "B-roll"} :t2 {:name "C-roll"}}
:groups {:root {:type :timeline :parent nil :marks [{:id :m/root :start 0 :end 300}]}
:clip-a {:type :clip :parent nil :marks [{:id :m/a :start 0 :end 100 :track :t0}]}
:clip-b {:type :clip :parent nil :marks [{:id :m/b :start 100 :end 200 :track :t1}]}
:clip-c {:type :clip :parent nil :marks [{:id :m/c :start 200 :end 300 :track :t2}]}}})
;; :start = timeline position; here it equals source so root is identity
:clip-a {:type :clip :parent nil :start 0 :marks [{:id :m/a :start 0 :end 100 :track :t0}]}
:clip-b {:type :clip :parent nil :start 100 :marks [{:id :m/b :start 100 :end 200 :track :t1}]}
:clip-c {:type :clip :parent nil :start 200 :marks [{:id :m/c :start 200 :end 300 :track :t2}]}}})
(defn with-group [scene gid g] (assoc-in scene [:groups gid] g))
(defn refm [id clip a b] {:id id :start {:ref clip :at a} :end {:ref clip :at b}})
@ -177,10 +178,12 @@
(deftest seed-from-otio
(testing "from-otio seeds tracks + clip mark-groups + root; content-segments at root tiles the clips"
;; two clips cut back to back on the timeline (:start 0 then 100), like a
;; multicam program, even though their source frames aren't contiguous
(let [parsed {:fps 24 :duration 200
:tracks [{:index 0 :kind :video :name "Wide" :clips [{:id "t0-c0" :name "a" :media-in 0 :duration 100}]}
{:index 1 :kind :video :name "CU" :clips [{:id "t1-c0" :name "b" :media-in 100 :duration 100}]}
{:index 2 :kind :audio :name "Aud" :clips [{:id "t2-c0" :name "x" :media-in 0 :duration 50}]}]}
:tracks [{:index 0 :kind :video :name "Wide" :clips [{:id "t0-c0" :name "a" :start 0 :media-in 0 :duration 100}]}
{:index 1 :kind :video :name "CU" :clips [{:id "t1-c0" :name "b" :start 100 :media-in 500 :duration 100}]}
{:index 2 :kind :audio :name "Aud" :clips [{:id "t2-c0" :name "x" :start 0 :media-in 0 :duration 50}]}]}
scene (s/from-otio parsed)]
(is (= 2 (count (:tracks scene)))) ; audio track dropped
(is (contains? (:groups scene) :t0-c0))