Coalesce continuous annotation bars into one jump point

A continuous selection across N clips is N marks → N bars, and OTIO's fractional
media offsets leave ~1-frame gaps between adjacent clips, so the jump control
showed "jump (5)" for a continuous stretch. merge-bars snaps bar endpoints to
whole frames and joins touching ranges; only a real >=1-frame gap splits.

Co-Authored-By: Claude Opus 4.8 <noreply@anthropic.com>
This commit is contained in:
Your Name 2026-06-29 02:25:59 -04:00
parent fcac22fe83
commit 0d73b204e6
3 changed files with 26 additions and 1 deletions

View file

@ -54,6 +54,21 @@
(when (< lo hi) [(+ c (- lo a)) (+ c (- hi a))])))
segs)))
(defn merge-bars
"Coalesce [lo hi) ranges that meet at a frame boundary into single bars, so a
continuous selection spanning several clips reads as one piece. Endpoints snap
to whole frames first, which absorbs the sub-frame gaps OTIO's fractional
media offsets leave between adjacent clips; only a real (≥1 frame) gap splits."
[bars]
(reduce (fn [acc [lo hi]]
(let [lo (js/Math.floor lo) hi (js/Math.ceil hi)
[plo phi] (peek acc)]
(if (and plo (<= lo phi))
(conj (pop acc) [plo (max phi hi)])
(conj acc [lo hi]))))
[]
(sort-by first bars)))
(defn slice
"Sub-segments of `segs` covering local range [la lb), src + local re-cut."
[segs la lb]

View file

@ -72,7 +72,8 @@
(keep (fn [[gid g]]
(when (and (= :annotation (:type g)) (= ctx (:parent g)))
(let [src-segs (scene/resolve scene gid)
bars (vec (mapcat (fn [{[a b] :src}] (scene/pieces segs a b)) src-segs))
bars (scene/merge-bars
(mapcat (fn [{[a b] :src}] (scene/pieces segs a b)) src-segs))
reason (scene/broken-reason scene gid)]
{:id gid :name (:name g) :color (or (:color g) "#4e8fc2")
:content (:content g) :children (count (:marks g))

View file

@ -221,6 +221,15 @@
(is (= 0 (get-in (first marks) [:start :at])))
(is (= 30 (get-in (first marks) [:end :at])))))))
(deftest merge-bars-coalesces-continuous-run
(testing "sub-frame OTIO gaps collapse to one bar; a real gap stays split"
;; foobar's real bars (continuous 5-clip selection, ~0.9-frame source gaps)
(is (= [[128 542]]
(s/merge-bars [[128.87 190.87] [191.80 265.80] [266.73 384.73]
[385.61 413.61] [414.58 541.58]])))
(is (= [[0 50] [200 260]] (s/merge-bars [[0 50] [200 260]]))) ; real gap → two
(is (= [] (s/merge-bars [])))))
(deftest restore-annotations-rekeywordizes-json
(testing "a JSON-roundtripped annotation (string :type/:parent/:ref) is restored + resolves"
(let [json-like {:ann-1 {:type "annotation" :parent "root" :name "x"