diff --git a/tl/src/tl/scene.cljs b/tl/src/tl/scene.cljs index b46e4af..62ac014 100644 --- a/tl/src/tl/scene.cljs +++ b/tl/src/tl/scene.cljs @@ -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] diff --git a/tl/src/tl/subs.cljs b/tl/src/tl/subs.cljs index 422c3eb..50a7c9f 100644 --- a/tl/src/tl/subs.cljs +++ b/tl/src/tl/subs.cljs @@ -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)) diff --git a/tl/test/tl/scene_test.cljs b/tl/test/tl/scene_test.cljs index 808adea..9f3b95c 100644 --- a/tl/test/tl/scene_test.cljs +++ b/tl/test/tl/scene_test.cljs @@ -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"