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:
parent
0d73b204e6
commit
8ee2cdf7b9
2 changed files with 18 additions and 10 deletions
|
|
@ -170,15 +170,19 @@
|
||||||
"The clip-segments that make up context `ctx` — what you draw and select
|
"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
|
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
|
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]
|
[scene ctx]
|
||||||
(if (= :timeline (:type (grp scene ctx)))
|
(if (= :timeline (:type (grp scene ctx)))
|
||||||
(->> (:groups scene)
|
(->> (:groups scene)
|
||||||
(keep (fn [[gid g]]
|
(keep (fn [[gid g]]
|
||||||
(when (= :clip (:type g))
|
(when (= :clip (:type g))
|
||||||
(let [seg (first (resolve scene gid))]
|
(let [seg (first (resolve scene gid))
|
||||||
(assoc seg :mark gid :local (:src seg)))))) ; ref clip by group id
|
[sa sb] (:src seg)
|
||||||
(sort-by (comp first :src))
|
st (:start g 0)]
|
||||||
|
(assoc seg :mark gid :local [st (+ st (- sb sa))])))))
|
||||||
|
(sort-by (comp first :local))
|
||||||
vec)
|
vec)
|
||||||
(resolve scene ctx)))
|
(resolve scene ctx)))
|
||||||
|
|
||||||
|
|
@ -278,6 +282,7 @@
|
||||||
clips (into {} (for [t vtracks c (:clips t)]
|
clips (into {} (for [t vtracks c (:clips t)]
|
||||||
[(keyword (:id c))
|
[(keyword (:id c))
|
||||||
{:type :clip :parent nil :name (:name c)
|
{:type :clip :parent nil :name (:name c)
|
||||||
|
:start (:start c) ; timeline position (frames)
|
||||||
:marks [{:id (keyword (str (:id c) "-m"))
|
:marks [{:id (keyword (str (:id c) "-m"))
|
||||||
:start (:media-in c)
|
:start (:media-in c)
|
||||||
:end (+ (:media-in c) (:duration c))
|
:end (+ (:media-in c) (:duration c))
|
||||||
|
|
|
||||||
|
|
@ -8,9 +8,10 @@
|
||||||
(def base
|
(def base
|
||||||
{:tracks {:t0 {:name "A-roll"} :t1 {:name "B-roll"} :t2 {:name "C-roll"}}
|
{: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}]}
|
: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}]}
|
;; :start = timeline position; here it equals source so root is identity
|
||||||
:clip-b {:type :clip :parent nil :marks [{:id :m/b :start 100 :end 200 :track :t1}]}
|
:clip-a {:type :clip :parent nil :start 0 :marks [{:id :m/a :start 0 :end 100 :track :t0}]}
|
||||||
:clip-c {:type :clip :parent nil :marks [{:id :m/c :start 200 :end 300 :track :t2}]}}})
|
: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 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}})
|
(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
|
(deftest seed-from-otio
|
||||||
(testing "from-otio seeds tracks + clip mark-groups + root; content-segments at root tiles the clips"
|
(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
|
(let [parsed {:fps 24 :duration 200
|
||||||
:tracks [{:index 0 :kind :video :name "Wide" :clips [{:id "t0-c0" :name "a" :media-in 0 :duration 100}]}
|
: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" :media-in 100 :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" :media-in 0 :duration 50}]}]}
|
{:index 2 :kind :audio :name "Aud" :clips [{:id "t2-c0" :name "x" :start 0 :media-in 0 :duration 50}]}]}
|
||||||
scene (s/from-otio parsed)]
|
scene (s/from-otio parsed)]
|
||||||
(is (= 2 (count (:tracks scene)))) ; audio track dropped
|
(is (= 2 (count (:tracks scene)))) ; audio track dropped
|
||||||
(is (contains? (:groups scene) :t0-c0))
|
(is (contains? (:groups scene) :t0-c0))
|
||||||
|
|
|
||||||
Loading…
Add table
Add a link
Reference in a new issue