@vosjs/studio-core 0.11.1 → 0.13.0
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- package/dist/index.d.ts +112 -29
- package/dist/index.js +573 -217
- package/dist/index.js.map +1 -1
- package/package.json +1 -1
package/dist/index.js
CHANGED
|
@@ -8,6 +8,13 @@ var TRANSITION_SPEED_MULT = {
|
|
|
8
8
|
function transitionMult(t) {
|
|
9
9
|
return t !== void 0 && t in TRANSITION_SPEED_MULT ? TRANSITION_SPEED_MULT[t] : 1;
|
|
10
10
|
}
|
|
11
|
+
var FREEZE_SECONDS_MIN = 0.25;
|
|
12
|
+
var FREEZE_SECONDS_MAX = 30;
|
|
13
|
+
var FREEZE_DEFAULT_SECONDS = 2;
|
|
14
|
+
function clampFreezeSeconds(seconds) {
|
|
15
|
+
const s = Math.min(FREEZE_SECONDS_MAX, Math.max(FREEZE_SECONDS_MIN, seconds));
|
|
16
|
+
return Math.round(s * 1e3) / 1e3;
|
|
17
|
+
}
|
|
11
18
|
var SPEED_RATE_MIN = 0.1;
|
|
12
19
|
var SPEED_RATE_MAX = 16;
|
|
13
20
|
var SPEED_SPAN_MIN = 0.25;
|
|
@@ -506,17 +513,43 @@ function lastLayerEnd(doc) {
|
|
|
506
513
|
import { splitBySpeed, totalDuration } from "@vosjs/timeline";
|
|
507
514
|
function docRestTime(doc) {
|
|
508
515
|
if (doc.endCard) return null;
|
|
509
|
-
const
|
|
510
|
-
|
|
511
|
-
|
|
512
|
-
const
|
|
513
|
-
|
|
514
|
-
|
|
516
|
+
const freezes = docFreezes(doc);
|
|
517
|
+
if (!freezes.length) return null;
|
|
518
|
+
const segs = docSegmentsOf(doc);
|
|
519
|
+
const marks = freezeOutputExtents(
|
|
520
|
+
splitBySpeed(segs, doc.speed ?? []),
|
|
521
|
+
freezes
|
|
515
522
|
);
|
|
516
|
-
|
|
517
|
-
if (
|
|
523
|
+
let rest = null;
|
|
524
|
+
for (const m of marks.values()) if (rest === null || m.t > rest) rest = m.t;
|
|
525
|
+
if (rest === null || !(rest >= 0)) return null;
|
|
518
526
|
return Math.round(rest * 1e3) / 1e3;
|
|
519
527
|
}
|
|
528
|
+
function docSegmentsOf(doc) {
|
|
529
|
+
const segs = Array.isArray(doc.segments) ? doc.segments : [];
|
|
530
|
+
if (segs.length) return segs;
|
|
531
|
+
const ms = doc.source?.meta?.durationMs;
|
|
532
|
+
return [{ in: 0, out: typeof ms === "number" ? ms / 1e3 : 0 }];
|
|
533
|
+
}
|
|
534
|
+
function docFreezes(doc) {
|
|
535
|
+
const own = Array.isArray(doc.freeze) ? doc.freeze : [];
|
|
536
|
+
const legacy = legacyHoldFreezes(
|
|
537
|
+
Array.isArray(doc.segments) ? doc.segments : []
|
|
538
|
+
);
|
|
539
|
+
const all = [...own, ...legacy].filter(
|
|
540
|
+
(f) => typeof f.at === "number" && Number.isFinite(f.at) && typeof f.seconds === "number" && f.seconds > 0
|
|
541
|
+
);
|
|
542
|
+
return all.sort((a, b) => a.at - b.at);
|
|
543
|
+
}
|
|
544
|
+
function legacyHoldFreezes(segments) {
|
|
545
|
+
const out = [];
|
|
546
|
+
segments.forEach((s, i) => {
|
|
547
|
+
const hold = s.hold;
|
|
548
|
+
if (typeof hold === "number" && hold > 0)
|
|
549
|
+
out.push({ id: `h${i}`, at: s.out, seconds: hold });
|
|
550
|
+
});
|
|
551
|
+
return out;
|
|
552
|
+
}
|
|
520
553
|
var ENTRANCE_SECONDS = 1.2;
|
|
521
554
|
var END_CARD_SECONDS = 2.5;
|
|
522
555
|
var END_CARD_RECEDE = 0.7;
|
|
@@ -538,30 +571,69 @@ function entranceSeconds(e) {
|
|
|
538
571
|
function exitSeconds(e) {
|
|
539
572
|
if (!e || e.kind === "none") return 0;
|
|
540
573
|
const house = e.kind === "fade" ? CARD_FADE_SECONDS : END_CARD_RECEDE;
|
|
541
|
-
return Math.max(0.1, Math.min(
|
|
574
|
+
return Math.max(0.1, Math.min(8, e.seconds ?? house));
|
|
542
575
|
}
|
|
543
|
-
function
|
|
544
|
-
const
|
|
545
|
-
const pending =
|
|
576
|
+
function placeFreezes(rated, freezes) {
|
|
577
|
+
const marks = /* @__PURE__ */ new Map();
|
|
578
|
+
const pending = [...freezes].filter((f) => f.seconds > 0).sort((a, b) => a.at - b.at);
|
|
579
|
+
const pieces = [];
|
|
580
|
+
let acc = 0;
|
|
581
|
+
const freezePiece = (f) => {
|
|
582
|
+
const at = Math.max(HOLD_SOURCE_SPAN, f.at);
|
|
583
|
+
marks.set(f.id, { t: acc, duration: f.seconds });
|
|
584
|
+
pieces.push({
|
|
585
|
+
in: at - HOLD_SOURCE_SPAN,
|
|
586
|
+
out: at,
|
|
587
|
+
rate: HOLD_SOURCE_SPAN / f.seconds
|
|
588
|
+
});
|
|
589
|
+
acc += f.seconds;
|
|
590
|
+
};
|
|
591
|
+
const push2 = (p) => {
|
|
592
|
+
pieces.push(p);
|
|
593
|
+
acc += Math.max(0, p.out - p.in) / (p.rate ?? 1);
|
|
594
|
+
};
|
|
546
595
|
for (let i = 0; i < rated.length; i++) {
|
|
547
|
-
|
|
548
|
-
out.push(piece);
|
|
596
|
+
let piece = rated[i];
|
|
549
597
|
const next = rated[i + 1];
|
|
550
|
-
|
|
551
|
-
|
|
552
|
-
|
|
553
|
-
|
|
554
|
-
|
|
555
|
-
|
|
556
|
-
in: p.out - HOLD_SOURCE_SPAN,
|
|
557
|
-
out: p.out,
|
|
558
|
-
rate: HOLD_SOURCE_SPAN / p.hold
|
|
559
|
-
});
|
|
560
|
-
p.used = true;
|
|
598
|
+
while (pending.length) {
|
|
599
|
+
const f = pending[0];
|
|
600
|
+
if (f.at > piece.out + 1e-9) break;
|
|
601
|
+
if (f.at < piece.in - 1e-9) {
|
|
602
|
+
pending.shift();
|
|
603
|
+
continue;
|
|
561
604
|
}
|
|
605
|
+
if (f.at <= piece.in + 1e-9) {
|
|
606
|
+
pending.shift();
|
|
607
|
+
freezePiece(f);
|
|
608
|
+
continue;
|
|
609
|
+
}
|
|
610
|
+
if (f.at < piece.out - 1e-9) {
|
|
611
|
+
push2({ ...piece, out: f.at });
|
|
612
|
+
pending.shift();
|
|
613
|
+
freezePiece(f);
|
|
614
|
+
piece = { ...piece, in: f.at };
|
|
615
|
+
continue;
|
|
616
|
+
}
|
|
617
|
+
const continues = next && Math.abs(next.in - piece.out) < 1e-9 && next.out > piece.out;
|
|
618
|
+
if (continues) break;
|
|
619
|
+
push2(piece);
|
|
620
|
+
piece = null;
|
|
621
|
+
pending.shift();
|
|
622
|
+
freezePiece(f);
|
|
623
|
+
break;
|
|
562
624
|
}
|
|
625
|
+
if (piece) push2(piece);
|
|
563
626
|
}
|
|
564
|
-
return
|
|
627
|
+
return { pieces, marks };
|
|
628
|
+
}
|
|
629
|
+
function withFreezes(rated, freezes) {
|
|
630
|
+
return placeFreezes(rated, freezes).pieces;
|
|
631
|
+
}
|
|
632
|
+
function freezeOutputExtents(rated, freezes) {
|
|
633
|
+
return placeFreezes(rated, freezes).marks;
|
|
634
|
+
}
|
|
635
|
+
function withHolds(docSegments, rated) {
|
|
636
|
+
return withFreezes(rated, legacyHoldFreezes(docSegments));
|
|
565
637
|
}
|
|
566
638
|
function entranceTiltKeyframes(e) {
|
|
567
639
|
const s = entranceSeconds(e);
|
|
@@ -591,8 +663,9 @@ function prependEntrance(track, head) {
|
|
|
591
663
|
return { keyframes: sortKeyframes([...head, ...rest]) };
|
|
592
664
|
}
|
|
593
665
|
function exitPose(e) {
|
|
594
|
-
return e?.kind === "fade" ? [1, 0, 0] : [0.9, -0.02, 0
|
|
666
|
+
return e?.kind === "fade" ? [1, 0, 0] : [0.9, -0.02, 0];
|
|
595
667
|
}
|
|
668
|
+
var RECEDE_MID = [0.91, -0.018, 0.8];
|
|
596
669
|
function cardPoseTrack(enter, exit, footageEnd, outputEnd2 = footageEnd) {
|
|
597
670
|
const keyframes = [];
|
|
598
671
|
const s = entranceSeconds(enter);
|
|
@@ -602,16 +675,36 @@ function cardPoseTrack(enter, exit, footageEnd, outputEnd2 = footageEnd) {
|
|
|
602
675
|
keyframes.push({ t: s, value: [1, 0, 1], ease: "power3.out" });
|
|
603
676
|
}
|
|
604
677
|
const x = exitSeconds(exit);
|
|
678
|
+
let last = [1, 0, 1];
|
|
605
679
|
if (x > 0 && footageEnd > 0) {
|
|
606
|
-
const
|
|
607
|
-
const
|
|
608
|
-
|
|
680
|
+
const t0 = round3(Math.max(s, footageEnd - x));
|
|
681
|
+
const t1 = round3(Math.max(t0 + 0.05, footageEnd));
|
|
682
|
+
last = exitPose(exit);
|
|
609
683
|
keyframes.push({ t: t0, value: [1, 0, 1], ease: "none" });
|
|
610
|
-
|
|
684
|
+
if (exit?.kind !== "fade") {
|
|
685
|
+
keyframes.push({
|
|
686
|
+
t: round3(t0 + (t1 - t0) * 0.45),
|
|
687
|
+
value: [...RECEDE_MID],
|
|
688
|
+
ease: "power2.out"
|
|
689
|
+
});
|
|
690
|
+
keyframes.push({ t: t1, value: last, ease: "power1.in" });
|
|
691
|
+
} else {
|
|
692
|
+
keyframes.push({ t: t1, value: last, ease: "power2.out" });
|
|
693
|
+
}
|
|
694
|
+
}
|
|
695
|
+
if (outputEnd2 > footageEnd + 1e-6 && footageEnd > 0) {
|
|
696
|
+
if (!keyframes.some((k) => Math.abs(k.t - footageEnd) < 1e-9))
|
|
697
|
+
keyframes.push({ t: footageEnd, value: [...last], ease: "none" });
|
|
698
|
+
keyframes.push({
|
|
699
|
+
t: round3(footageEnd + 1e-3),
|
|
700
|
+
value: [last[0], last[1], 0],
|
|
701
|
+
ease: "none"
|
|
702
|
+
});
|
|
611
703
|
}
|
|
612
704
|
if (!keyframes.length) return void 0;
|
|
613
705
|
return { keyframes: sortKeyframes(keyframes) };
|
|
614
706
|
}
|
|
707
|
+
var round3 = (v) => Math.round(v * 1e3) / 1e3;
|
|
615
708
|
function endCardClips(card, doc, endStart) {
|
|
616
709
|
const seconds = Math.max(1, Math.min(8, card.seconds ?? END_CARD_SECONDS));
|
|
617
710
|
const anim = { enter: "rise", exit: "none" };
|
|
@@ -704,9 +797,29 @@ function migrateMotion(doc) {
|
|
|
704
797
|
const segments = Array.isArray(doc.segments) ? doc.segments : [];
|
|
705
798
|
const overlays = Array.isArray(doc.overlays) ? doc.overlays : [];
|
|
706
799
|
const objects = Array.isArray(doc.objects) ? doc.objects : [];
|
|
707
|
-
const
|
|
800
|
+
const heldSegments = segments.some((s) => s.hold !== void 0);
|
|
801
|
+
const legacy = frame.entrance !== void 0 || doc.endCard !== void 0 || heldSegments || overlays.some(hasLegacyClipMotion) || objects.some((o) => o.animation !== void 0);
|
|
708
802
|
if (!legacy) return doc;
|
|
709
803
|
const out = { ...doc };
|
|
804
|
+
if (heldSegments) {
|
|
805
|
+
const holds = legacyHoldFreezes(segments);
|
|
806
|
+
const own = Array.isArray(doc.freeze) ? doc.freeze : [];
|
|
807
|
+
const taken = new Set(own.map((f) => f.id));
|
|
808
|
+
let n = own.length;
|
|
809
|
+
const migrated = holds.map((h) => {
|
|
810
|
+
let id = `f${n++}`;
|
|
811
|
+
while (taken.has(id)) id = `f${n++}`;
|
|
812
|
+
taken.add(id);
|
|
813
|
+
return { id, at: h.at, seconds: h.seconds };
|
|
814
|
+
});
|
|
815
|
+
out.freeze = [...own, ...migrated].sort((a, b) => a.at - b.at);
|
|
816
|
+
out.segments = segments.map((s) => {
|
|
817
|
+
if (s.hold === void 0) return s;
|
|
818
|
+
const next = { ...s };
|
|
819
|
+
delete next.hold;
|
|
820
|
+
return next;
|
|
821
|
+
});
|
|
822
|
+
}
|
|
710
823
|
const nextFrame = { ...frame };
|
|
711
824
|
if (frame.entrance !== void 0) {
|
|
712
825
|
const e = frame.entrance;
|
|
@@ -730,15 +843,25 @@ function migrateMotion(doc) {
|
|
|
730
843
|
const card = doc.endCard;
|
|
731
844
|
if (card && segments.length) {
|
|
732
845
|
const seconds = Math.max(1, Math.min(8, card.seconds ?? END_CARD_SECONDS));
|
|
733
|
-
const
|
|
846
|
+
const segsNow = out.segments ?? segments;
|
|
847
|
+
const lastSeg = segsNow.at(-1);
|
|
848
|
+
const freezesNow = docFreezes({ segments: segsNow, freeze: out.freeze });
|
|
849
|
+
const rated = withFreezes(
|
|
850
|
+
splitBySpeed(segsNow, doc.speed ?? []),
|
|
851
|
+
freezesNow
|
|
852
|
+
);
|
|
734
853
|
const endStart = totalDuration(rated);
|
|
854
|
+
const taken = new Set(freezesNow.map((f) => f.id));
|
|
855
|
+
let n = 0;
|
|
856
|
+
while (taken.has(`f${n}`)) n++;
|
|
857
|
+
out.freeze = [
|
|
858
|
+
...freezesNow,
|
|
859
|
+
{ id: `f${n}`, at: lastSeg.out, seconds, from: END_CARD_FROM }
|
|
860
|
+
].sort((a, b) => a.at - b.at);
|
|
735
861
|
nextOverlays = [...nextOverlays, ...endCardClips(card, doc, endStart)];
|
|
736
862
|
nextFrame.anim = {
|
|
737
863
|
...nextFrame.anim ?? {},
|
|
738
|
-
exit: {
|
|
739
|
-
kind: "recede",
|
|
740
|
-
seconds: Math.min(END_CARD_RECEDE, seconds * 0.4)
|
|
741
|
-
}
|
|
864
|
+
exit: { kind: "recede", seconds }
|
|
742
865
|
};
|
|
743
866
|
}
|
|
744
867
|
delete out.endCard;
|
|
@@ -767,12 +890,12 @@ function outputEnd(doc, footageEnd) {
|
|
|
767
890
|
}
|
|
768
891
|
|
|
769
892
|
// src/docVersion.ts
|
|
770
|
-
var DOC_SCHEMA_VERSION =
|
|
893
|
+
var DOC_SCHEMA_VERSION = 4;
|
|
771
894
|
function migrateHostedDoc(raw) {
|
|
772
895
|
const version = typeof raw.docSchemaVersion === "number" ? raw.docSchemaVersion : 0;
|
|
773
896
|
if (version >= DOC_SCHEMA_VERSION) return raw;
|
|
774
897
|
let doc = raw;
|
|
775
|
-
if (version <
|
|
898
|
+
if (version < 4 && doc.source && typeof doc.source === "object") {
|
|
776
899
|
doc = migrateMotion(doc);
|
|
777
900
|
}
|
|
778
901
|
return { ...doc, docSchemaVersion: DOC_SCHEMA_VERSION };
|
|
@@ -1815,14 +1938,14 @@ function resolveOverlayFx(clip, clipDuration) {
|
|
|
1815
1938
|
const maxTotal = Math.max(dur, clipDuration * 0.9);
|
|
1816
1939
|
st = Math.min(st, Math.max(0, (maxTotal - dur) / (upperN - 1)));
|
|
1817
1940
|
}
|
|
1818
|
-
const
|
|
1941
|
+
const round12 = (v) => Math.round(v * 1e4) / 1e4;
|
|
1819
1942
|
return {
|
|
1820
1943
|
k: spec.fx,
|
|
1821
1944
|
u: unit,
|
|
1822
1945
|
d: FX_DIR[spec.direction ?? "forward"] ?? 0,
|
|
1823
|
-
st:
|
|
1824
|
-
dur:
|
|
1825
|
-
tt:
|
|
1946
|
+
st: round12(st),
|
|
1947
|
+
dur: round12(dur),
|
|
1948
|
+
tt: round12(st * (upperN - 1) + dur),
|
|
1826
1949
|
units,
|
|
1827
1950
|
n
|
|
1828
1951
|
};
|
|
@@ -2037,7 +2160,7 @@ var CURSOR_IDLE_HOLD = 1;
|
|
|
2037
2160
|
var CURSOR_IDLE_FADE_OUT = 0.35;
|
|
2038
2161
|
var CURSOR_IDLE_FADE_IN = 0.18;
|
|
2039
2162
|
var CURSOR_IDLE_EPS_FRAC = 25e-4;
|
|
2040
|
-
var
|
|
2163
|
+
var round4 = (n) => Math.round(n * 1e3) / 1e3;
|
|
2041
2164
|
function cursorIdleFade(track, o) {
|
|
2042
2165
|
const moves = track.filter((e) => e.type === "move" || e.type === "down" || e.type === "up").map((e) => ({ t: e.t / 1e3, x: e.x, y: e.y }));
|
|
2043
2166
|
if (moves.length === 0) return [];
|
|
@@ -2089,7 +2212,7 @@ function emit(keys, s, e) {
|
|
|
2089
2212
|
push(keys, e + CURSOR_IDLE_FADE_IN, 1);
|
|
2090
2213
|
}
|
|
2091
2214
|
function push(keys, t, a) {
|
|
2092
|
-
const rt =
|
|
2215
|
+
const rt = round4(t);
|
|
2093
2216
|
if (keys.length > 0 && rt <= keys[keys.length - 1].t) {
|
|
2094
2217
|
keys[keys.length - 1].a = a;
|
|
2095
2218
|
return;
|
|
@@ -3147,7 +3270,7 @@ function gatedRect(rect, x, y, space) {
|
|
|
3147
3270
|
return null;
|
|
3148
3271
|
const m = RECT_CONTAIN_MARGIN;
|
|
3149
3272
|
const inside = x >= rect.x - m && x <= rect.x + rect.w + m && y >= rect.y - m && y <= rect.y + rect.h + m;
|
|
3150
|
-
return inside ? [
|
|
3273
|
+
return inside ? [round5(rect.x), round5(rect.y), round5(rect.w), round5(rect.h)] : null;
|
|
3151
3274
|
}
|
|
3152
3275
|
function extractClicks(track, segments, opts) {
|
|
3153
3276
|
const out = [];
|
|
@@ -3172,11 +3295,11 @@ function extractClicks(track, segments, opts) {
|
|
|
3172
3295
|
const upSrc = st + Math.max(pressLen, 0.02);
|
|
3173
3296
|
const up = keptOutputEnd(segments, st, upSrc) ?? ot + CLICK_SYNTH_RELEASE;
|
|
3174
3297
|
const click = {
|
|
3175
|
-
ot:
|
|
3176
|
-
up:
|
|
3177
|
-
st:
|
|
3178
|
-
x:
|
|
3179
|
-
y:
|
|
3298
|
+
ot: round5(ot),
|
|
3299
|
+
up: round5(Math.max(up, ot + 0.02)),
|
|
3300
|
+
st: round5(st),
|
|
3301
|
+
x: round5(e.x),
|
|
3302
|
+
y: round5(e.y),
|
|
3180
3303
|
b: button
|
|
3181
3304
|
};
|
|
3182
3305
|
if (opts.rects && e.rect) {
|
|
@@ -3195,7 +3318,7 @@ function hexToRgbTriplet(hex) {
|
|
|
3195
3318
|
const n = parseInt(h, 16);
|
|
3196
3319
|
return [n >> 16, n >> 8 & 255, n & 255];
|
|
3197
3320
|
}
|
|
3198
|
-
function
|
|
3321
|
+
function round5(v) {
|
|
3199
3322
|
return Math.round(v * 1e3) / 1e3;
|
|
3200
3323
|
}
|
|
3201
3324
|
|
|
@@ -3222,7 +3345,8 @@ var CAM_PAN_EASE = ZOOM_PAN_EASE;
|
|
|
3222
3345
|
var PROGRAM_DURATION = 1;
|
|
3223
3346
|
function ratedSegments(doc) {
|
|
3224
3347
|
const segs = isRecordingDoc(doc) ? doc.segments.length ? doc.segments : [{ in: 0, out: doc.source.meta.durationMs / 1e3 }] : [{ in: 0, out: programDuration(doc) }];
|
|
3225
|
-
|
|
3348
|
+
const freezes = isRecordingDoc(doc) ? docFreezes(doc) : [];
|
|
3349
|
+
return withFreezes(splitBySpeed2(segs, doc.speed ?? []), freezes);
|
|
3226
3350
|
}
|
|
3227
3351
|
function durationSec(doc, rated) {
|
|
3228
3352
|
const trimmed = totalDuration2(rated);
|
|
@@ -3257,7 +3381,7 @@ function trackEmitter() {
|
|
|
3257
3381
|
if (tt <= prev.t + 1e-6 && sameVec(prev.value, value)) return prev.t;
|
|
3258
3382
|
if (tt <= prev.t + 1e-6) tt = prev.t + 1e-3;
|
|
3259
3383
|
}
|
|
3260
|
-
keyframes.push({ t:
|
|
3384
|
+
keyframes.push({ t: round6(tt), value: value.map(round6), ease });
|
|
3261
3385
|
return tt;
|
|
3262
3386
|
};
|
|
3263
3387
|
return { keyframes, push: push2 };
|
|
@@ -4753,10 +4877,13 @@ var ON_FRAME = `(ctx, content, dt) => {
|
|
|
4753
4877
|
card.mesh.scale.x = cpv[0]; card.mesh.scale.y = cpv[0]
|
|
4754
4878
|
card.mesh.position.y = cpv[1] * cpH
|
|
4755
4879
|
if (card.mesh.material) card.mesh.material.opacity = cpv[2]
|
|
4756
|
-
|
|
4880
|
+
// Gone past its clip: the mesh leaves the scene, not just its paint.
|
|
4881
|
+
card.mesh.visible = cpv[2] > 0.001
|
|
4882
|
+
} else if (card.mesh.scale && card.mesh.position && ((card.mesh.scale.x !== undefined && card.mesh.scale.x !== 1) || (card.mesh.position.y !== undefined && card.mesh.position.y !== 0) || card.mesh.visible === false)) {
|
|
4757
4883
|
card.mesh.scale.x = 1; card.mesh.scale.y = 1
|
|
4758
4884
|
card.mesh.position.y = 0
|
|
4759
4885
|
if (card.mesh.material) card.mesh.material.opacity = 1
|
|
4886
|
+
card.mesh.visible = true
|
|
4760
4887
|
}
|
|
4761
4888
|
var tilted = rx * rx + ry * ry > 1e-6
|
|
4762
4889
|
if (card.texture && card.texture.generateMipmaps !== tilted) {
|
|
@@ -4788,14 +4915,14 @@ function studioLayerData(layers, duration) {
|
|
|
4788
4915
|
// needs a decoded recording, which a sync lowering can't produce.
|
|
4789
4916
|
audio: (layers.audio ?? []).map((c) => ({
|
|
4790
4917
|
key: c.key,
|
|
4791
|
-
start:
|
|
4792
|
-
in:
|
|
4793
|
-
out:
|
|
4794
|
-
gain:
|
|
4918
|
+
start: round6(c.start),
|
|
4919
|
+
in: round6(c.in),
|
|
4920
|
+
out: round6(c.out),
|
|
4921
|
+
gain: round6(c.gain),
|
|
4795
4922
|
loop: !!c.loop,
|
|
4796
|
-
len:
|
|
4923
|
+
len: round6(clipLength(c)),
|
|
4797
4924
|
duck: !!c.duck,
|
|
4798
|
-
env: clipEnvelope(c).map((p) => ({ t:
|
|
4925
|
+
env: clipEnvelope(c).map((p) => ({ t: round6(p.t), g: round6(p.g) }))
|
|
4799
4926
|
})),
|
|
4800
4927
|
// Object clips: numbers resolved HERE; shapes are the drafted engine spec.
|
|
4801
4928
|
...layers.objects && layers.objects.length ? {
|
|
@@ -4808,17 +4935,17 @@ function studioLayerData(layers, duration) {
|
|
|
4808
4935
|
} : o.asset.kind === "text3d" ? resolveText3dAsset(o.asset) : { kind: "gltf", key: o.asset.key },
|
|
4809
4936
|
...o.span ? {
|
|
4810
4937
|
span: {
|
|
4811
|
-
start:
|
|
4812
|
-
duration:
|
|
4938
|
+
start: round6(o.span.start),
|
|
4939
|
+
duration: round6(o.span.duration)
|
|
4813
4940
|
}
|
|
4814
4941
|
} : {},
|
|
4815
|
-
x:
|
|
4816
|
-
y:
|
|
4817
|
-
z:
|
|
4818
|
-
rx:
|
|
4819
|
-
ry:
|
|
4820
|
-
rz:
|
|
4821
|
-
scale:
|
|
4942
|
+
x: round6(o.transform3d.x),
|
|
4943
|
+
y: round6(o.transform3d.y),
|
|
4944
|
+
z: round6(o.transform3d.z),
|
|
4945
|
+
rx: round6(o.transform3d.rx),
|
|
4946
|
+
ry: round6(o.transform3d.ry),
|
|
4947
|
+
rz: round6(o.transform3d.rz),
|
|
4948
|
+
scale: round6(o.transform3d.scale || OBJECT_DEFAULT_SCALE),
|
|
4822
4949
|
anim: idleKey(o),
|
|
4823
4950
|
// Pose keyframes: clip-local [x,y,z,rx,ry,rz,scale] track,
|
|
4824
4951
|
// sampled at t − span.start. Omitted when absent — parity.
|
|
@@ -4845,12 +4972,12 @@ function studioLayerData(layers, duration) {
|
|
|
4845
4972
|
const base = {
|
|
4846
4973
|
id: o.id,
|
|
4847
4974
|
kind: o.kind,
|
|
4848
|
-
start:
|
|
4849
|
-
dur:
|
|
4850
|
-
x:
|
|
4851
|
-
y:
|
|
4852
|
-
scale:
|
|
4853
|
-
rot:
|
|
4975
|
+
start: round6(o.start),
|
|
4976
|
+
dur: round6(Math.max(OVERLAY_MIN_DURATION, o.duration)),
|
|
4977
|
+
x: round6(o.transform.x),
|
|
4978
|
+
y: round6(o.transform.y),
|
|
4979
|
+
scale: round6(o.transform.scale || 1),
|
|
4980
|
+
rot: round6(o.transform.rotation || 0),
|
|
4854
4981
|
enter: enterKey(o),
|
|
4855
4982
|
exit: exitKey(o),
|
|
4856
4983
|
// Pose keyframes: a CLIP-LOCAL [x, y, scale, rot, opacity]
|
|
@@ -4871,7 +4998,7 @@ function studioLayerData(layers, duration) {
|
|
|
4871
4998
|
return {
|
|
4872
4999
|
...base,
|
|
4873
5000
|
key: o.key,
|
|
4874
|
-
w:
|
|
5001
|
+
w: round6(o.width ?? OVERLAY_MEDIA_DEFAULT_WIDTH),
|
|
4875
5002
|
radius: o.radius ?? OVERLAY_MEDIA_DEFAULT_RADIUS,
|
|
4876
5003
|
opacity: o.opacity ?? 1,
|
|
4877
5004
|
loop: !!o.loop,
|
|
@@ -4881,7 +5008,7 @@ function studioLayerData(layers, duration) {
|
|
|
4881
5008
|
...o.shadow ? { shadow: o.shadow } : {},
|
|
4882
5009
|
...o.border && o.border.width > 0 ? {
|
|
4883
5010
|
border: {
|
|
4884
|
-
width:
|
|
5011
|
+
width: round6(o.border.width),
|
|
4885
5012
|
color: o.border.color
|
|
4886
5013
|
}
|
|
4887
5014
|
} : {}
|
|
@@ -4901,11 +5028,11 @@ function studioLayerData(layers, duration) {
|
|
|
4901
5028
|
// Style-v2 fields bake only when non-default (byte parity for
|
|
4902
5029
|
// older docs); ON_FRAME reads them unconditionally.
|
|
4903
5030
|
...st.fontStyle === "italic" ? { sty: "italic" } : {},
|
|
4904
|
-
...o.maxWidth ? { mw:
|
|
4905
|
-
...st.letterSpacing ? { ls:
|
|
4906
|
-
...st.lineHeight !== OVERLAY_LINE_HEIGHT ? { lh:
|
|
5031
|
+
...o.maxWidth ? { mw: round6(o.maxWidth) } : {},
|
|
5032
|
+
...st.letterSpacing ? { ls: round6(st.letterSpacing) } : {},
|
|
5033
|
+
...st.lineHeight !== OVERLAY_LINE_HEIGHT ? { lh: round6(st.lineHeight) } : {},
|
|
4907
5034
|
...st.align !== "center" ? { align: st.align } : {},
|
|
4908
|
-
...st.stroke ? { stroke: { c: st.stroke.color, w:
|
|
5035
|
+
...st.stroke ? { stroke: { c: st.stroke.color, w: round6(st.stroke.width) } } : {},
|
|
4909
5036
|
// Hosted face behind an override: ON_FRAME lazy-loads it so a
|
|
4910
5037
|
// live family/weight edit paints without a LOAD (SETUP only
|
|
4911
5038
|
// runs on cold load).
|
|
@@ -4918,10 +5045,10 @@ function studioLayerData(layers, duration) {
|
|
|
4918
5045
|
...bx ? {
|
|
4919
5046
|
box: {
|
|
4920
5047
|
c: bx.color,
|
|
4921
|
-
o:
|
|
4922
|
-
px:
|
|
4923
|
-
py:
|
|
4924
|
-
r:
|
|
5048
|
+
o: round6(bx.opacity),
|
|
5049
|
+
px: round6(bx.padX),
|
|
5050
|
+
py: round6(bx.padY),
|
|
5051
|
+
r: round6(bx.radius)
|
|
4925
5052
|
}
|
|
4926
5053
|
} : {},
|
|
4927
5054
|
// Entrance animation: segmentation + timing normalized
|
|
@@ -4996,16 +5123,16 @@ function lowerToComposition(input) {
|
|
|
4996
5123
|
// Rated segments (speed spans pre-intersected): ON_FRAME's mapTime and the
|
|
4997
5124
|
// export's audio splice read the rate straight off each segment.
|
|
4998
5125
|
segments: rated.map((seg) => ({
|
|
4999
|
-
in:
|
|
5000
|
-
out:
|
|
5001
|
-
...seg.rate !== void 0 && seg.rate !== 1 ? { rate:
|
|
5126
|
+
in: round6(seg.in),
|
|
5127
|
+
out: round6(seg.out),
|
|
5128
|
+
...seg.rate !== void 0 && seg.rate !== 1 ? { rate: round6(seg.rate) } : {}
|
|
5002
5129
|
})),
|
|
5003
5130
|
frame: doc.frame,
|
|
5004
5131
|
micGain: doc.micGain ?? 1,
|
|
5005
5132
|
cursor: smoothed.map((p) => ({
|
|
5006
|
-
t:
|
|
5007
|
-
x:
|
|
5008
|
-
y:
|
|
5133
|
+
t: round6(p.t),
|
|
5134
|
+
x: round6(p.x),
|
|
5135
|
+
y: round6(p.y)
|
|
5009
5136
|
})),
|
|
5010
5137
|
cursorStyle: doc.cursor,
|
|
5011
5138
|
cursorSpace: { w: doc.source.meta.width, h: doc.source.meta.height },
|
|
@@ -5090,7 +5217,7 @@ function clickFxData(doc, rated) {
|
|
|
5090
5217
|
}
|
|
5091
5218
|
};
|
|
5092
5219
|
}
|
|
5093
|
-
function
|
|
5220
|
+
function round6(v) {
|
|
5094
5221
|
return Math.round(v * 1e3) / 1e3;
|
|
5095
5222
|
}
|
|
5096
5223
|
|
|
@@ -5180,8 +5307,8 @@ function planAutoSpeed(track, opts) {
|
|
|
5180
5307
|
accepted.sort((a, b) => a.in - b.in);
|
|
5181
5308
|
return accepted.map((c, i) => ({
|
|
5182
5309
|
id: `s${i}`,
|
|
5183
|
-
in:
|
|
5184
|
-
out:
|
|
5310
|
+
in: round7(c.in),
|
|
5311
|
+
out: round7(c.out),
|
|
5185
5312
|
rate: clampSpeedRate(c.rate),
|
|
5186
5313
|
source: "auto"
|
|
5187
5314
|
}));
|
|
@@ -5234,7 +5361,7 @@ function collectRuns(evs, gap, minLen, emit2) {
|
|
|
5234
5361
|
}
|
|
5235
5362
|
flush();
|
|
5236
5363
|
}
|
|
5237
|
-
function
|
|
5364
|
+
function round7(v) {
|
|
5238
5365
|
return Math.round(v * 1e3) / 1e3;
|
|
5239
5366
|
}
|
|
5240
5367
|
|
|
@@ -5411,16 +5538,16 @@ function momentsFromDoc(doc, plan, opts = {}) {
|
|
|
5411
5538
|
return {
|
|
5412
5539
|
id: `m${String(i + 1).padStart(2, "0")}`,
|
|
5413
5540
|
kind: d.kind,
|
|
5414
|
-
source: { in:
|
|
5415
|
-
output: output ? { in:
|
|
5416
|
-
at: d.at === null ? null :
|
|
5417
|
-
outputAt: outAt ?
|
|
5418
|
-
focus: d.focus ? { cx:
|
|
5541
|
+
source: { in: round8(d.in), out: round8(d.out) },
|
|
5542
|
+
output: output ? { in: round8(output.start), out: round8(output.end) } : null,
|
|
5543
|
+
at: d.at === null ? null : round8(d.at),
|
|
5544
|
+
outputAt: outAt ? round8(outAt.start) : null,
|
|
5545
|
+
focus: d.focus ? { cx: round8(d.focus.cx), cy: round8(d.focus.cy) } : null,
|
|
5419
5546
|
rect: d.rect ? {
|
|
5420
|
-
x:
|
|
5421
|
-
y:
|
|
5422
|
-
w:
|
|
5423
|
-
h:
|
|
5547
|
+
x: round8(d.rect.x),
|
|
5548
|
+
y: round8(d.rect.y),
|
|
5549
|
+
w: round8(d.rect.w),
|
|
5550
|
+
h: round8(d.rect.h)
|
|
5424
5551
|
} : null,
|
|
5425
5552
|
...d.clicks !== void 0 ? { clicks: d.clicks } : {},
|
|
5426
5553
|
...d.pings !== void 0 ? { pings: d.pings } : {},
|
|
@@ -5440,7 +5567,7 @@ function activityOf(bins, d) {
|
|
|
5440
5567
|
sum += bins[i];
|
|
5441
5568
|
n++;
|
|
5442
5569
|
}
|
|
5443
|
-
return n ?
|
|
5570
|
+
return n ? round8(sum / n) : null;
|
|
5444
5571
|
}
|
|
5445
5572
|
function saidOver(transcript, d) {
|
|
5446
5573
|
if (!transcript?.length) return null;
|
|
@@ -5449,7 +5576,7 @@ function saidOver(transcript, d) {
|
|
|
5449
5576
|
const text = transcript.filter((s) => s.start < hi && s.end > lo).map((s) => s.text.trim()).filter(Boolean).join(" ");
|
|
5450
5577
|
return text || null;
|
|
5451
5578
|
}
|
|
5452
|
-
function
|
|
5579
|
+
function round8(v) {
|
|
5453
5580
|
return Math.round(v * 1e3) / 1e3;
|
|
5454
5581
|
}
|
|
5455
5582
|
|
|
@@ -5638,7 +5765,7 @@ function layoutOf(doc) {
|
|
|
5638
5765
|
frame: LAYOUT_FRAME_FIELDS.filter((k) => frame[k] !== void 0),
|
|
5639
5766
|
clips: (doc.overlays ?? []).filter(isStageClip).map((o) => o.id),
|
|
5640
5767
|
lean: restLeanOf(doc) !== null,
|
|
5641
|
-
|
|
5768
|
+
freeze: trailingFreeze(doc) !== null
|
|
5642
5769
|
};
|
|
5643
5770
|
}
|
|
5644
5771
|
function copyLayout(from, to, opts = {}) {
|
|
@@ -5700,11 +5827,33 @@ function copyLayout(from, to, opts = {}) {
|
|
|
5700
5827
|
notes.push("lean kept: this take has tilt spans of its own");
|
|
5701
5828
|
}
|
|
5702
5829
|
}
|
|
5703
|
-
const
|
|
5830
|
+
const freeze = trailingFreeze(from);
|
|
5704
5831
|
const last = doc.segments.at(-1);
|
|
5705
|
-
if (
|
|
5832
|
+
if (freeze && last) {
|
|
5833
|
+
const own2 = (doc.freeze ?? []).filter(
|
|
5834
|
+
(f) => Math.abs(f.at - last.out) > 1e-9
|
|
5835
|
+
);
|
|
5836
|
+
const taken = new Set(own2.map((f) => f.id));
|
|
5837
|
+
let n = 0;
|
|
5838
|
+
while (taken.has(`f${n}`)) n++;
|
|
5839
|
+
doc.freeze = [
|
|
5840
|
+
...own2,
|
|
5841
|
+
{ id: `f${n}`, at: last.out, seconds: freeze.seconds }
|
|
5842
|
+
].sort((a, b) => a.at - b.at);
|
|
5843
|
+
doc.segments = doc.segments.map((s) => {
|
|
5844
|
+
if (s.hold === void 0) return s;
|
|
5845
|
+
const next = { ...s };
|
|
5846
|
+
delete next.hold;
|
|
5847
|
+
return next;
|
|
5848
|
+
});
|
|
5849
|
+
}
|
|
5706
5850
|
return { doc, notes };
|
|
5707
5851
|
}
|
|
5852
|
+
function trailingFreeze(doc) {
|
|
5853
|
+
const last = doc.segments.at(-1);
|
|
5854
|
+
if (!last) return null;
|
|
5855
|
+
return docFreezes(doc).find((f) => Math.abs(f.at - last.out) < 1e-9) ?? null;
|
|
5856
|
+
}
|
|
5708
5857
|
var shiftBy = (at, templateEnd, takeEnd) => at === "start" ? 0 : at === "end" ? takeEnd - templateEnd : at;
|
|
5709
5858
|
function clipsFrom(doc, from) {
|
|
5710
5859
|
return {
|
|
@@ -5715,12 +5864,14 @@ function clipsFrom(doc, from) {
|
|
|
5715
5864
|
}
|
|
5716
5865
|
function dropTemplate(doc, from) {
|
|
5717
5866
|
const has = clipsFrom(doc, from);
|
|
5718
|
-
|
|
5867
|
+
const freezes = (doc.freeze ?? []).some((f) => f.from === from);
|
|
5868
|
+
if (!has.overlays.length && !has.objects.length && !has.audio.length && !freezes)
|
|
5719
5869
|
return doc;
|
|
5720
5870
|
const out = { ...doc };
|
|
5721
5871
|
if (doc.overlays) out.overlays = doc.overlays.filter((o) => o.from !== from);
|
|
5722
5872
|
if (doc.objects) out.objects = doc.objects.filter((o) => o.from !== from);
|
|
5723
5873
|
out.audio = doc.audio.filter((a) => a.from !== from);
|
|
5874
|
+
if (doc.freeze) out.freeze = doc.freeze.filter((f) => f.from !== from);
|
|
5724
5875
|
return out;
|
|
5725
5876
|
}
|
|
5726
5877
|
function applyTemplate(template, take, opts) {
|
|
@@ -5728,8 +5879,22 @@ function applyTemplate(template, take, opts) {
|
|
|
5728
5879
|
const notes = [];
|
|
5729
5880
|
const base = dropTemplate(take, from);
|
|
5730
5881
|
const doc = structuredClone(base);
|
|
5882
|
+
const tail = trailingFreeze(template);
|
|
5883
|
+
const lastSeg = doc.segments.at(-1);
|
|
5884
|
+
if (tail && opts.at === "end" && lastSeg) {
|
|
5885
|
+
const own2 = (doc.freeze ?? []).filter(
|
|
5886
|
+
(f) => Math.abs(f.at - lastSeg.out) > 1e-9
|
|
5887
|
+
);
|
|
5888
|
+
const taken = new Set(own2.map((f) => f.id));
|
|
5889
|
+
let n = 0;
|
|
5890
|
+
while (taken.has(`f${n}`)) n++;
|
|
5891
|
+
doc.freeze = [
|
|
5892
|
+
...own2,
|
|
5893
|
+
{ id: `f${n}`, at: lastSeg.out, seconds: tail.seconds, from }
|
|
5894
|
+
].sort((a, b) => a.at - b.at);
|
|
5895
|
+
}
|
|
5731
5896
|
const templateEnd = totalDuration4(ratedSegments(template));
|
|
5732
|
-
const takeEnd = totalDuration4(ratedSegments(
|
|
5897
|
+
const takeEnd = totalDuration4(ratedSegments(doc));
|
|
5733
5898
|
const shift = shiftBy(opts.at, templateEnd, takeEnd);
|
|
5734
5899
|
const own = new Map((take.overlays ?? []).map((o) => [o.id, o]));
|
|
5735
5900
|
const overlays = (doc.overlays ?? []).filter(
|
|
@@ -5893,8 +6058,8 @@ function buildDigest(input) {
|
|
|
5893
6058
|
return {
|
|
5894
6059
|
digestVersion: DIGEST_VERSION,
|
|
5895
6060
|
take: {
|
|
5896
|
-
sourceDuration:
|
|
5897
|
-
outputDuration:
|
|
6061
|
+
sourceDuration: round9(meta.durationMs / 1e3),
|
|
6062
|
+
outputDuration: round9(input.outputDuration),
|
|
5898
6063
|
width: meta.width,
|
|
5899
6064
|
height: meta.height,
|
|
5900
6065
|
captureWidth: meta.captureWidth ?? null,
|
|
@@ -5943,7 +6108,7 @@ function buildDigest(input) {
|
|
|
5943
6108
|
function outputDurationOf(doc) {
|
|
5944
6109
|
return docOutputDuration(doc);
|
|
5945
6110
|
}
|
|
5946
|
-
function
|
|
6111
|
+
function round9(v) {
|
|
5947
6112
|
return Math.round(v * 1e3) / 1e3;
|
|
5948
6113
|
}
|
|
5949
6114
|
|
|
@@ -5969,20 +6134,20 @@ function setSpeedInRange(spans, srcIn, srcOut, rate) {
|
|
|
5969
6134
|
continue;
|
|
5970
6135
|
}
|
|
5971
6136
|
if (srcIn - s.in >= MIN_REMAINDER)
|
|
5972
|
-
kept.push({ ...s, out:
|
|
6137
|
+
kept.push({ ...s, out: round10(srcIn), source: "manual" });
|
|
5973
6138
|
if (s.out - srcOut >= MIN_REMAINDER)
|
|
5974
6139
|
kept.push({
|
|
5975
6140
|
...s,
|
|
5976
6141
|
id: mintId(used),
|
|
5977
|
-
in:
|
|
6142
|
+
in: round10(srcOut),
|
|
5978
6143
|
source: "manual"
|
|
5979
6144
|
});
|
|
5980
6145
|
}
|
|
5981
6146
|
if (rate != null)
|
|
5982
6147
|
kept.push({
|
|
5983
6148
|
id: mintId(used),
|
|
5984
|
-
in:
|
|
5985
|
-
out:
|
|
6149
|
+
in: round10(srcIn),
|
|
6150
|
+
out: round10(srcOut),
|
|
5986
6151
|
rate: clampSpeedRate(rate),
|
|
5987
6152
|
source: "manual"
|
|
5988
6153
|
});
|
|
@@ -5998,9 +6163,9 @@ function removeSourceRange(segments, srcIn, srcOut) {
|
|
|
5998
6163
|
next.push(seg);
|
|
5999
6164
|
continue;
|
|
6000
6165
|
}
|
|
6001
|
-
if (srcIn - seg.in >= MIN_SEGMENT) next.push({ ...seg, out:
|
|
6166
|
+
if (srcIn - seg.in >= MIN_SEGMENT) next.push({ ...seg, out: round10(srcIn) });
|
|
6002
6167
|
if (seg.out - srcOut >= MIN_SEGMENT)
|
|
6003
|
-
next.push({ ...seg, in:
|
|
6168
|
+
next.push({ ...seg, in: round10(srcOut) });
|
|
6004
6169
|
}
|
|
6005
6170
|
return next.length ? next : [...segments];
|
|
6006
6171
|
}
|
|
@@ -6018,8 +6183,8 @@ function zoomSpanForRange(zoom, srcIn, srcOut) {
|
|
|
6018
6183
|
while (used.has(`u${n}`)) n++;
|
|
6019
6184
|
return {
|
|
6020
6185
|
id: `u${n}`,
|
|
6021
|
-
in:
|
|
6022
|
-
out:
|
|
6186
|
+
in: round10(start),
|
|
6187
|
+
out: round10(end),
|
|
6023
6188
|
level: DEFAULT_ZOOM_LEVEL,
|
|
6024
6189
|
cx: 0.5,
|
|
6025
6190
|
cy: 0.5,
|
|
@@ -6033,7 +6198,7 @@ function mintId(used) {
|
|
|
6033
6198
|
used.add(id);
|
|
6034
6199
|
return id;
|
|
6035
6200
|
}
|
|
6036
|
-
function
|
|
6201
|
+
function round10(v) {
|
|
6037
6202
|
return Math.round(v * 1e3) / 1e3;
|
|
6038
6203
|
}
|
|
6039
6204
|
|
|
@@ -6676,33 +6841,44 @@ function effectiveSegments(doc) {
|
|
|
6676
6841
|
if (isRecordingDoc(doc) && doc.segments.length) return doc.segments;
|
|
6677
6842
|
return [{ in: 0, out: anchorSourceDuration(doc) }];
|
|
6678
6843
|
}
|
|
6679
|
-
var
|
|
6680
|
-
|
|
6844
|
+
var segmentPieces = (seg, speeds, freezes, leading) => {
|
|
6845
|
+
const own = freezes.filter(
|
|
6846
|
+
(f) => f.at <= seg.out + 1e-9 && (leading ? f.at >= seg.in - 1e-9 : f.at > seg.in + 1e-9)
|
|
6847
|
+
);
|
|
6848
|
+
return withFreezes(splitBySpeed3([seg], speeds), own);
|
|
6849
|
+
};
|
|
6850
|
+
var outputLen = (seg, speeds, freezes = [], leading = true) => totalDuration6(segmentPieces(seg, speeds, freezes, leading));
|
|
6851
|
+
var segmentStarts = (segments, speeds, freezes = []) => {
|
|
6681
6852
|
const starts = [];
|
|
6682
6853
|
let acc = 0;
|
|
6683
|
-
|
|
6854
|
+
segments.forEach((s, i) => {
|
|
6684
6855
|
starts.push(acc);
|
|
6685
|
-
acc += outputLen(s, speeds);
|
|
6686
|
-
}
|
|
6856
|
+
acc += outputLen(s, speeds, freezes, i === 0 || !contiguous(segments, i));
|
|
6857
|
+
});
|
|
6687
6858
|
return starts;
|
|
6688
6859
|
};
|
|
6860
|
+
var contiguous = (segments, i) => i > 0 && Math.abs(segments[i - 1].out - segments[i].in) < 1e-9;
|
|
6689
6861
|
var videoLane = {
|
|
6690
6862
|
id: "video",
|
|
6691
6863
|
label: "Video",
|
|
6692
6864
|
items(doc) {
|
|
6693
6865
|
const segments = effectiveSegments(doc);
|
|
6694
6866
|
const speeds = doc.speed ?? [];
|
|
6695
|
-
const
|
|
6867
|
+
const freezes = docFreezes(doc);
|
|
6868
|
+
const starts = segmentStarts(segments, speeds, freezes);
|
|
6696
6869
|
return segments.map((s, i) => ({
|
|
6697
6870
|
id: `seg-${i}`,
|
|
6698
6871
|
kind: "clip",
|
|
6699
|
-
t: starts[i],
|
|
6700
|
-
duration:
|
|
6872
|
+
t: round11(starts[i]),
|
|
6873
|
+
duration: round11(
|
|
6874
|
+
outputLen(s, speeds, freezes, i === 0 || !contiguous(segments, i))
|
|
6875
|
+
)
|
|
6701
6876
|
}));
|
|
6702
6877
|
},
|
|
6703
6878
|
gesture(doc, g) {
|
|
6704
6879
|
const segments = effectiveSegments(doc);
|
|
6705
6880
|
const speeds = doc.speed ?? [];
|
|
6881
|
+
const freezes = docFreezes(doc);
|
|
6706
6882
|
const sourceDuration = anchorSourceDuration(doc);
|
|
6707
6883
|
switch (g.type) {
|
|
6708
6884
|
case "move": {
|
|
@@ -6714,7 +6890,7 @@ var videoLane = {
|
|
|
6714
6890
|
let acc = 0;
|
|
6715
6891
|
let insert = others.length;
|
|
6716
6892
|
for (let i = 0; i < others.length; i++) {
|
|
6717
|
-
const dur = outputLen(others[i], speeds);
|
|
6893
|
+
const dur = outputLen(others[i], speeds, freezes);
|
|
6718
6894
|
if (g.t < acc + dur / 2) {
|
|
6719
6895
|
insert = i;
|
|
6720
6896
|
break;
|
|
@@ -6732,10 +6908,15 @@ var videoLane = {
|
|
|
6732
6908
|
const index = segIndex(g.id);
|
|
6733
6909
|
if (index < 0 || index >= segments.length) return null;
|
|
6734
6910
|
const seg = segments[index];
|
|
6735
|
-
const starts = segmentStarts(segments, speeds);
|
|
6911
|
+
const starts = segmentStarts(segments, speeds, freezes);
|
|
6736
6912
|
const fullMap = splitBySpeed3([{ in: 0, out: sourceDuration }], speeds);
|
|
6737
6913
|
const edgeSrc = g.edge === "start" ? seg.in : seg.out;
|
|
6738
|
-
const edgeOutNow = g.edge === "start" ? starts[index] : starts[index] + outputLen(
|
|
6914
|
+
const edgeOutNow = g.edge === "start" ? starts[index] : starts[index] + outputLen(
|
|
6915
|
+
seg,
|
|
6916
|
+
speeds,
|
|
6917
|
+
freezes.filter((f) => f.at < seg.out - 1e-9),
|
|
6918
|
+
index === 0 || !contiguous(segments, index)
|
|
6919
|
+
);
|
|
6739
6920
|
const anchorOut = sourceToTimeline3(fullMap, Math.min(edgeSrc, sourceDuration)) ?? edgeSrc;
|
|
6740
6921
|
const sourceT = mapTime2(fullMap, anchorOut + (g.t - edgeOutNow));
|
|
6741
6922
|
const next = trimSegment(
|
|
@@ -6750,13 +6931,31 @@ var videoLane = {
|
|
|
6750
6931
|
};
|
|
6751
6932
|
}
|
|
6752
6933
|
case "create": {
|
|
6753
|
-
const starts = segmentStarts(segments, speeds);
|
|
6934
|
+
const starts = segmentStarts(segments, speeds, freezes);
|
|
6754
6935
|
const index = segments.findIndex(
|
|
6755
|
-
(s2, i) => g.t >= starts[i] && g.t < starts[i] + outputLen(
|
|
6936
|
+
(s2, i) => g.t >= starts[i] && g.t < starts[i] + outputLen(
|
|
6937
|
+
s2,
|
|
6938
|
+
speeds,
|
|
6939
|
+
freezes,
|
|
6940
|
+
i === 0 || !contiguous(segments, i)
|
|
6941
|
+
)
|
|
6756
6942
|
);
|
|
6757
6943
|
if (index < 0) return null;
|
|
6758
6944
|
const s = segments[index];
|
|
6759
|
-
const
|
|
6945
|
+
const leading = index === 0 || !contiguous(segments, index);
|
|
6946
|
+
const local = g.t - starts[index];
|
|
6947
|
+
for (const m of freezeOutputExtents(
|
|
6948
|
+
splitBySpeed3([s], speeds),
|
|
6949
|
+
freezes.filter(
|
|
6950
|
+
(f) => f.at <= s.out + 1e-9 && (leading ? f.at >= s.in - 1e-9 : f.at > s.in + 1e-9)
|
|
6951
|
+
)
|
|
6952
|
+
).values()) {
|
|
6953
|
+
if (local >= m.t - 1e-9 && local <= m.t + m.duration + 1e-9)
|
|
6954
|
+
return null;
|
|
6955
|
+
}
|
|
6956
|
+
const sourceT = round11(
|
|
6957
|
+
mapTime2(segmentPieces(s, speeds, freezes, leading), local)
|
|
6958
|
+
);
|
|
6760
6959
|
if (sourceT - s.in < 0.05 || s.out - sourceT < 0.05) return null;
|
|
6761
6960
|
const next = [
|
|
6762
6961
|
...segments.slice(0, index),
|
|
@@ -6782,15 +6981,154 @@ var videoLane = {
|
|
|
6782
6981
|
magnets(doc) {
|
|
6783
6982
|
const segments = effectiveSegments(doc);
|
|
6784
6983
|
const speeds = doc.speed ?? [];
|
|
6785
|
-
const
|
|
6984
|
+
const freezes = docFreezes(doc);
|
|
6985
|
+
const starts = segmentStarts(segments, speeds, freezes);
|
|
6986
|
+
const n = segments.length;
|
|
6786
6987
|
return [
|
|
6787
6988
|
...starts,
|
|
6788
|
-
...
|
|
6789
|
-
starts[
|
|
6989
|
+
...n ? [
|
|
6990
|
+
starts[n - 1] + outputLen(
|
|
6991
|
+
segments[n - 1],
|
|
6992
|
+
speeds,
|
|
6993
|
+
freezes,
|
|
6994
|
+
n === 1 || !contiguous(segments, n - 1)
|
|
6995
|
+
)
|
|
6790
6996
|
] : []
|
|
6791
|
-
];
|
|
6997
|
+
].map(round11);
|
|
6998
|
+
}
|
|
6999
|
+
};
|
|
7000
|
+
function nextFreezeId(doc) {
|
|
7001
|
+
const taken = new Set((doc.freeze ?? []).map((f) => f.id));
|
|
7002
|
+
let n = 0;
|
|
7003
|
+
while (taken.has(`f${n}`)) n++;
|
|
7004
|
+
return `f${n}`;
|
|
7005
|
+
}
|
|
7006
|
+
var FREEZE_GAP = 0.05;
|
|
7007
|
+
var freezeLane = {
|
|
7008
|
+
id: "freeze",
|
|
7009
|
+
label: "Freeze",
|
|
7010
|
+
items(doc) {
|
|
7011
|
+
const freezes = docFreezes(doc);
|
|
7012
|
+
if (!freezes.length) return [];
|
|
7013
|
+
const segs = effectiveSegments(doc);
|
|
7014
|
+
const marks = freezeOutputExtents(
|
|
7015
|
+
splitBySpeed3(segs, doc.speed ?? []),
|
|
7016
|
+
freezes
|
|
7017
|
+
);
|
|
7018
|
+
return freezes.flatMap((f) => {
|
|
7019
|
+
const m = marks.get(f.id);
|
|
7020
|
+
return m ? [
|
|
7021
|
+
{
|
|
7022
|
+
id: f.id,
|
|
7023
|
+
kind: "clip",
|
|
7024
|
+
t: round11(m.t),
|
|
7025
|
+
duration: round11(m.duration),
|
|
7026
|
+
label: `${trimZeros(f.seconds)}s`
|
|
7027
|
+
}
|
|
7028
|
+
] : [];
|
|
7029
|
+
});
|
|
7030
|
+
},
|
|
7031
|
+
gesture(doc, g) {
|
|
7032
|
+
const freezes = docFreezes(doc);
|
|
7033
|
+
const segs = effectiveSegments(doc);
|
|
7034
|
+
const speeds = doc.speed ?? [];
|
|
7035
|
+
const sourceDuration = anchorSourceDuration(doc);
|
|
7036
|
+
const kept = (t) => segs.some((s) => t >= s.in - 1e-9 && t <= s.out + 1e-9);
|
|
7037
|
+
const clear = (t, except) => !freezes.some((f2) => f2.id !== except && Math.abs(f2.at - t) < FREEZE_GAP);
|
|
7038
|
+
if (g.type === "create") {
|
|
7039
|
+
const rated = ratedSegments(doc);
|
|
7040
|
+
for (const m of freezeOutputExtents(
|
|
7041
|
+
splitBySpeed3(segs, speeds),
|
|
7042
|
+
freezes
|
|
7043
|
+
).values()) {
|
|
7044
|
+
if (g.t >= m.t - 1e-9 && g.t <= m.t + m.duration + 1e-9) return null;
|
|
7045
|
+
}
|
|
7046
|
+
if (g.t > totalDuration6(rated) + 1e-9) return null;
|
|
7047
|
+
const srcT = round11(mapTime2(rated, Math.max(0, g.t)));
|
|
7048
|
+
if (!kept(srcT) || !clear(srcT)) return null;
|
|
7049
|
+
const id = nextFreezeId(doc);
|
|
7050
|
+
return (d) => {
|
|
7051
|
+
d.freeze = [
|
|
7052
|
+
...d.freeze ?? [],
|
|
7053
|
+
{ id, at: srcT, seconds: FREEZE_DEFAULT_SECONDS }
|
|
7054
|
+
].sort((a, b) => a.at - b.at);
|
|
7055
|
+
};
|
|
7056
|
+
}
|
|
7057
|
+
const f = freezes.find((x) => x.id === g.id);
|
|
7058
|
+
if (!f) return null;
|
|
7059
|
+
const own = (d) => {
|
|
7060
|
+
const list = d.freeze ?? [];
|
|
7061
|
+
let mine = list.find((x) => x.id === f.id);
|
|
7062
|
+
if (!mine) {
|
|
7063
|
+
const id = nextFreezeId(d);
|
|
7064
|
+
mine = { id, at: f.at, seconds: f.seconds };
|
|
7065
|
+
d.freeze = [...list, mine];
|
|
7066
|
+
d.segments = d.segments.map((sg) => {
|
|
7067
|
+
if (sg.hold === void 0 || Math.abs(sg.out - f.at) > 1e-9) return sg;
|
|
7068
|
+
const next = { ...sg };
|
|
7069
|
+
delete next.hold;
|
|
7070
|
+
return next;
|
|
7071
|
+
});
|
|
7072
|
+
}
|
|
7073
|
+
return mine;
|
|
7074
|
+
};
|
|
7075
|
+
const base = withFreezes(
|
|
7076
|
+
splitBySpeed3(segs, speeds),
|
|
7077
|
+
freezes.filter((x) => x.id !== f.id)
|
|
7078
|
+
);
|
|
7079
|
+
if (g.type === "move" || g.type === "resize" && g.edge === "start") {
|
|
7080
|
+
const marks = freezeOutputExtents(splitBySpeed3(segs, speeds), freezes);
|
|
7081
|
+
const mine = marks.get(f.id);
|
|
7082
|
+
const endOut = mine ? mine.t + mine.duration : null;
|
|
7083
|
+
const srcT = round11(
|
|
7084
|
+
Math.min(sourceDuration, mapTime2(base, Math.max(0, g.t)))
|
|
7085
|
+
);
|
|
7086
|
+
if (!kept(srcT) || !clear(srcT, f.id)) return null;
|
|
7087
|
+
return (d) => {
|
|
7088
|
+
const x = own(d);
|
|
7089
|
+
if (!x) return;
|
|
7090
|
+
x.at = srcT;
|
|
7091
|
+
if (g.type === "resize" && endOut !== null) {
|
|
7092
|
+
const after = freezeOutputExtents(
|
|
7093
|
+
splitBySpeed3(effectiveSegments(d), d.speed ?? []),
|
|
7094
|
+
docFreezes({
|
|
7095
|
+
...d,
|
|
7096
|
+
freeze: (d.freeze ?? []).map(
|
|
7097
|
+
(y) => y.id === x.id ? { ...y, seconds: FREEZE_SECONDS_MIN } : y
|
|
7098
|
+
)
|
|
7099
|
+
})
|
|
7100
|
+
).get(x.id);
|
|
7101
|
+
if (after) x.seconds = clampFreezeSeconds(endOut - after.t);
|
|
7102
|
+
}
|
|
7103
|
+
d.freeze.sort((a, b) => a.at - b.at);
|
|
7104
|
+
};
|
|
7105
|
+
}
|
|
7106
|
+
if (g.type === "resize") {
|
|
7107
|
+
const marks = freezeOutputExtents(splitBySpeed3(segs, speeds), freezes);
|
|
7108
|
+
const mine = marks.get(f.id);
|
|
7109
|
+
if (!mine) return null;
|
|
7110
|
+
const seconds = clampFreezeSeconds(g.t - mine.t);
|
|
7111
|
+
if (Math.abs(seconds - f.seconds) < 1e-6) return null;
|
|
7112
|
+
return (d) => {
|
|
7113
|
+
const x = own(d);
|
|
7114
|
+
if (x) x.seconds = seconds;
|
|
7115
|
+
};
|
|
7116
|
+
}
|
|
7117
|
+
if (g.type === "remove") {
|
|
7118
|
+
return (d) => {
|
|
7119
|
+
const x = own(d);
|
|
7120
|
+
d.freeze = (d.freeze ?? []).filter((y) => y !== x);
|
|
7121
|
+
};
|
|
7122
|
+
}
|
|
7123
|
+
return null;
|
|
7124
|
+
},
|
|
7125
|
+
magnets(doc) {
|
|
7126
|
+
return freezeLane.items(doc).flatMap((it) => [it.t, it.t + (it.duration ?? 0)]);
|
|
6792
7127
|
}
|
|
6793
7128
|
};
|
|
7129
|
+
function trimZeros(n) {
|
|
7130
|
+
return String(Math.round(n * 100) / 100);
|
|
7131
|
+
}
|
|
6794
7132
|
var zoomLane = {
|
|
6795
7133
|
id: "zoom",
|
|
6796
7134
|
label: "Zoom",
|
|
@@ -6802,8 +7140,8 @@ var zoomLane = {
|
|
|
6802
7140
|
{
|
|
6803
7141
|
id: z.id,
|
|
6804
7142
|
kind: "clip",
|
|
6805
|
-
t:
|
|
6806
|
-
duration:
|
|
7143
|
+
t: round11(ext.start),
|
|
7144
|
+
duration: round11(ext.end - ext.start),
|
|
6807
7145
|
label: `${z.level.toFixed(2)}\xD7`
|
|
6808
7146
|
}
|
|
6809
7147
|
];
|
|
@@ -6827,8 +7165,8 @@ var zoomLane = {
|
|
|
6827
7165
|
...d.zoom,
|
|
6828
7166
|
{
|
|
6829
7167
|
id,
|
|
6830
|
-
in:
|
|
6831
|
-
out:
|
|
7168
|
+
in: round11(srcT),
|
|
7169
|
+
out: round11(srcT + len),
|
|
6832
7170
|
level: DEFAULT_ZOOM_LEVEL,
|
|
6833
7171
|
cx,
|
|
6834
7172
|
cy,
|
|
@@ -6859,8 +7197,8 @@ var zoomLane = {
|
|
|
6859
7197
|
return (d) => {
|
|
6860
7198
|
const z = d.zoom.find((x) => x.id === g.id);
|
|
6861
7199
|
if (!z) return;
|
|
6862
|
-
z.in =
|
|
6863
|
-
z.out =
|
|
7200
|
+
z.in = round11(newIn);
|
|
7201
|
+
z.out = round11(newIn + len);
|
|
6864
7202
|
z.source = "manual";
|
|
6865
7203
|
d.zoom.sort((a, b) => a.in - b.in);
|
|
6866
7204
|
};
|
|
@@ -6889,8 +7227,8 @@ var zoomLane = {
|
|
|
6889
7227
|
return (d) => {
|
|
6890
7228
|
const z = d.zoom.find((x) => x.id === g.id);
|
|
6891
7229
|
if (!z) return;
|
|
6892
|
-
z.in =
|
|
6893
|
-
z.out =
|
|
7230
|
+
z.in = round11(next.in);
|
|
7231
|
+
z.out = round11(next.out);
|
|
6894
7232
|
z.source = "manual";
|
|
6895
7233
|
};
|
|
6896
7234
|
}
|
|
@@ -6912,8 +7250,8 @@ function cursorFocusAt(doc, srcT) {
|
|
|
6912
7250
|
if (Math.abs(e.t - ms) < Math.abs(best.t - ms)) best = e;
|
|
6913
7251
|
const clamp015 = (v) => Math.max(0, Math.min(1, v));
|
|
6914
7252
|
return {
|
|
6915
|
-
cx:
|
|
6916
|
-
cy:
|
|
7253
|
+
cx: round11(clamp015(best.x / width)),
|
|
7254
|
+
cy: round11(clamp015(best.y / height))
|
|
6917
7255
|
};
|
|
6918
7256
|
}
|
|
6919
7257
|
var tiltLane = {
|
|
@@ -6927,8 +7265,8 @@ var tiltLane = {
|
|
|
6927
7265
|
{
|
|
6928
7266
|
id: z.id,
|
|
6929
7267
|
kind: "clip",
|
|
6930
|
-
t:
|
|
6931
|
-
duration:
|
|
7268
|
+
t: round11(ext.start),
|
|
7269
|
+
duration: round11(ext.end - ext.start),
|
|
6932
7270
|
label: `${formatDeg(z.rx)}\xB0/${formatDeg(z.ry)}\xB0`
|
|
6933
7271
|
}
|
|
6934
7272
|
];
|
|
@@ -6951,8 +7289,8 @@ var tiltLane = {
|
|
|
6951
7289
|
...d.tilt ?? [],
|
|
6952
7290
|
{
|
|
6953
7291
|
id,
|
|
6954
|
-
in:
|
|
6955
|
-
out:
|
|
7292
|
+
in: round11(srcT),
|
|
7293
|
+
out: round11(srcT + len),
|
|
6956
7294
|
rx: DEFAULT_TILT_POSE.rx,
|
|
6957
7295
|
ry: DEFAULT_TILT_POSE.ry,
|
|
6958
7296
|
source: "manual"
|
|
@@ -6982,8 +7320,8 @@ var tiltLane = {
|
|
|
6982
7320
|
return (d) => {
|
|
6983
7321
|
const z = (d.tilt ?? []).find((x) => x.id === g.id);
|
|
6984
7322
|
if (!z || !d.tilt) return;
|
|
6985
|
-
z.in =
|
|
6986
|
-
z.out =
|
|
7323
|
+
z.in = round11(newIn);
|
|
7324
|
+
z.out = round11(newIn + len);
|
|
6987
7325
|
z.source = "manual";
|
|
6988
7326
|
d.tilt.sort((a, b) => a.in - b.in);
|
|
6989
7327
|
};
|
|
@@ -7012,8 +7350,8 @@ var tiltLane = {
|
|
|
7012
7350
|
return (d) => {
|
|
7013
7351
|
const z = (d.tilt ?? []).find((x) => x.id === g.id);
|
|
7014
7352
|
if (!z) return;
|
|
7015
|
-
z.in =
|
|
7016
|
-
z.out =
|
|
7353
|
+
z.in = round11(next.in);
|
|
7354
|
+
z.out = round11(next.out);
|
|
7017
7355
|
z.source = "manual";
|
|
7018
7356
|
};
|
|
7019
7357
|
}
|
|
@@ -7040,8 +7378,8 @@ var camMoveLane = {
|
|
|
7040
7378
|
{
|
|
7041
7379
|
id: z.id,
|
|
7042
7380
|
kind: "clip",
|
|
7043
|
-
t:
|
|
7044
|
-
duration:
|
|
7381
|
+
t: round11(ext.start),
|
|
7382
|
+
duration: round11(ext.end - ext.start),
|
|
7045
7383
|
label: z.size != null ? `${Math.round(z.size * 100)}%` : "Move"
|
|
7046
7384
|
}
|
|
7047
7385
|
];
|
|
@@ -7065,8 +7403,8 @@ var camMoveLane = {
|
|
|
7065
7403
|
...d.camMotion ?? [],
|
|
7066
7404
|
{
|
|
7067
7405
|
id,
|
|
7068
|
-
in:
|
|
7069
|
-
out:
|
|
7406
|
+
in: round11(srcT),
|
|
7407
|
+
out: round11(srcT + len),
|
|
7070
7408
|
...DEFAULT_CAM_POSE,
|
|
7071
7409
|
source: "manual"
|
|
7072
7410
|
}
|
|
@@ -7095,8 +7433,8 @@ var camMoveLane = {
|
|
|
7095
7433
|
return (d) => {
|
|
7096
7434
|
const z = (d.camMotion ?? []).find((x) => x.id === g.id);
|
|
7097
7435
|
if (!z || !d.camMotion) return;
|
|
7098
|
-
z.in =
|
|
7099
|
-
z.out =
|
|
7436
|
+
z.in = round11(newIn);
|
|
7437
|
+
z.out = round11(newIn + len);
|
|
7100
7438
|
z.source = "manual";
|
|
7101
7439
|
d.camMotion.sort((a, b) => a.in - b.in);
|
|
7102
7440
|
};
|
|
@@ -7125,8 +7463,8 @@ var camMoveLane = {
|
|
|
7125
7463
|
return (d) => {
|
|
7126
7464
|
const z = (d.camMotion ?? []).find((x) => x.id === g.id);
|
|
7127
7465
|
if (!z) return;
|
|
7128
|
-
z.in =
|
|
7129
|
-
z.out =
|
|
7466
|
+
z.in = round11(next.in);
|
|
7467
|
+
z.out = round11(next.out);
|
|
7130
7468
|
z.source = "manual";
|
|
7131
7469
|
};
|
|
7132
7470
|
}
|
|
@@ -7145,7 +7483,8 @@ var camLane = {
|
|
|
7145
7483
|
if (!doc.source.camKey || !doc.cam.visible) return [];
|
|
7146
7484
|
const segments = effectiveSegments(doc);
|
|
7147
7485
|
const speeds = doc.speed ?? [];
|
|
7148
|
-
const
|
|
7486
|
+
const freezes = docFreezes(doc);
|
|
7487
|
+
const starts = segmentStarts(segments, speeds, freezes);
|
|
7149
7488
|
const first = segments[0];
|
|
7150
7489
|
const last = segments[segments.length - 1];
|
|
7151
7490
|
const win = doc.cam.window ?? { in: first.in, out: last.out };
|
|
@@ -7154,11 +7493,16 @@ var camLane = {
|
|
|
7154
7493
|
const a = Math.max(s.in, win.in);
|
|
7155
7494
|
const b = Math.min(s.out, win.out);
|
|
7156
7495
|
if (b - a <= 1e-6) return;
|
|
7496
|
+
const leading = i === 0 || !contiguous(segments, i);
|
|
7157
7497
|
items.push({
|
|
7158
7498
|
id: `cam-${i}`,
|
|
7159
7499
|
kind: "clip",
|
|
7160
|
-
t:
|
|
7161
|
-
|
|
7500
|
+
t: round11(
|
|
7501
|
+
starts[i] + outputLen({ in: s.in, out: a }, speeds, freezes, leading)
|
|
7502
|
+
),
|
|
7503
|
+
duration: round11(
|
|
7504
|
+
outputLen({ in: a, out: b }, speeds, freezes, a === s.in && leading)
|
|
7505
|
+
)
|
|
7162
7506
|
});
|
|
7163
7507
|
});
|
|
7164
7508
|
return items;
|
|
@@ -7185,7 +7529,7 @@ var camLane = {
|
|
|
7185
7529
|
Math.max(first.in, last.out - span)
|
|
7186
7530
|
);
|
|
7187
7531
|
return (d) => {
|
|
7188
|
-
d.cam.window = { in:
|
|
7532
|
+
d.cam.window = { in: round11(newIn), out: round11(newIn + span) };
|
|
7189
7533
|
};
|
|
7190
7534
|
}
|
|
7191
7535
|
const overlapping = eff.map((s, i) => ({
|
|
@@ -7198,8 +7542,8 @@ var camLane = {
|
|
|
7198
7542
|
const next = g.edge === "start" ? { in: Math.min(sourceT, current.out - 0.05), out: current.out } : { in: current.in, out: Math.max(sourceT, current.in + 0.05) };
|
|
7199
7543
|
return (d) => {
|
|
7200
7544
|
d.cam.window = {
|
|
7201
|
-
in:
|
|
7202
|
-
out:
|
|
7545
|
+
in: round11(Math.max(0, next.in)),
|
|
7546
|
+
out: round11(Math.min(sourceDuration, next.out))
|
|
7203
7547
|
};
|
|
7204
7548
|
};
|
|
7205
7549
|
},
|
|
@@ -7214,12 +7558,15 @@ var micLane = {
|
|
|
7214
7558
|
if (!voiceKey(doc)) return [];
|
|
7215
7559
|
const segments = effectiveSegments(doc);
|
|
7216
7560
|
const speeds = doc.speed ?? [];
|
|
7217
|
-
const
|
|
7561
|
+
const freezes = docFreezes(doc);
|
|
7562
|
+
const starts = segmentStarts(segments, speeds, freezes);
|
|
7218
7563
|
return segments.map((s, i) => ({
|
|
7219
7564
|
id: `mic-${i}`,
|
|
7220
7565
|
kind: "clip",
|
|
7221
|
-
t: starts[i],
|
|
7222
|
-
duration:
|
|
7566
|
+
t: round11(starts[i]),
|
|
7567
|
+
duration: round11(
|
|
7568
|
+
outputLen(s, speeds, freezes, i === 0 || !contiguous(segments, i))
|
|
7569
|
+
)
|
|
7223
7570
|
}));
|
|
7224
7571
|
},
|
|
7225
7572
|
gesture() {
|
|
@@ -7273,8 +7620,8 @@ var speedLane = {
|
|
|
7273
7620
|
{
|
|
7274
7621
|
id: sp.id,
|
|
7275
7622
|
kind: "clip",
|
|
7276
|
-
t:
|
|
7277
|
-
duration:
|
|
7623
|
+
t: round11(start),
|
|
7624
|
+
duration: round11(end - start),
|
|
7278
7625
|
label: `${sp.rate}\xD7`
|
|
7279
7626
|
}
|
|
7280
7627
|
];
|
|
@@ -7297,8 +7644,8 @@ var speedLane = {
|
|
|
7297
7644
|
...d.speed ?? [],
|
|
7298
7645
|
{
|
|
7299
7646
|
id,
|
|
7300
|
-
in:
|
|
7301
|
-
out:
|
|
7647
|
+
in: round11(srcT),
|
|
7648
|
+
out: round11(srcT + len),
|
|
7302
7649
|
rate: DEFAULT_SPEED_RATE,
|
|
7303
7650
|
source: "manual"
|
|
7304
7651
|
}
|
|
@@ -7328,8 +7675,8 @@ var speedLane = {
|
|
|
7328
7675
|
return (d) => {
|
|
7329
7676
|
const s = d.speed?.find((x) => x.id === g.id);
|
|
7330
7677
|
if (!s) return;
|
|
7331
|
-
s.in =
|
|
7332
|
-
s.out =
|
|
7678
|
+
s.in = round11(newIn);
|
|
7679
|
+
s.out = round11(newIn + len);
|
|
7333
7680
|
s.source = "manual";
|
|
7334
7681
|
d.speed.sort((a, b) => a.in - b.in);
|
|
7335
7682
|
};
|
|
@@ -7358,8 +7705,8 @@ var speedLane = {
|
|
|
7358
7705
|
return (d) => {
|
|
7359
7706
|
const s = d.speed?.find((x) => x.id === g.id);
|
|
7360
7707
|
if (!s) return;
|
|
7361
|
-
s.in =
|
|
7362
|
-
s.out =
|
|
7708
|
+
s.in = round11(next.in);
|
|
7709
|
+
s.out = round11(next.out);
|
|
7363
7710
|
s.source = "manual";
|
|
7364
7711
|
};
|
|
7365
7712
|
}
|
|
@@ -7378,8 +7725,8 @@ var audioLane = {
|
|
|
7378
7725
|
return doc.audio.map((c) => ({
|
|
7379
7726
|
id: c.id,
|
|
7380
7727
|
kind: "clip",
|
|
7381
|
-
t:
|
|
7382
|
-
duration:
|
|
7728
|
+
t: round11(c.start),
|
|
7729
|
+
duration: round11(clipLength(c)),
|
|
7383
7730
|
label: c.name
|
|
7384
7731
|
}));
|
|
7385
7732
|
},
|
|
@@ -7390,7 +7737,7 @@ var audioLane = {
|
|
|
7390
7737
|
const start = Math.max(0, g.t);
|
|
7391
7738
|
return (d) => {
|
|
7392
7739
|
const c = d.audio.find((x) => x.id === g.id);
|
|
7393
|
-
if (c) c.start =
|
|
7740
|
+
if (c) c.start = round11(start);
|
|
7394
7741
|
};
|
|
7395
7742
|
}
|
|
7396
7743
|
if (g.type === "resize") {
|
|
@@ -7403,8 +7750,8 @@ var audioLane = {
|
|
|
7403
7750
|
return (d) => {
|
|
7404
7751
|
const c = d.audio.find((x) => x.id === g.id);
|
|
7405
7752
|
if (!c) return;
|
|
7406
|
-
c.start =
|
|
7407
|
-
c.loopLen =
|
|
7753
|
+
c.start = round11(newStart2);
|
|
7754
|
+
c.loopLen = round11(end - newStart2);
|
|
7408
7755
|
};
|
|
7409
7756
|
}
|
|
7410
7757
|
const delta = g.t - clip.start;
|
|
@@ -7413,22 +7760,22 @@ var audioLane = {
|
|
|
7413
7760
|
return (d) => {
|
|
7414
7761
|
const c = d.audio.find((x) => x.id === g.id);
|
|
7415
7762
|
if (!c) return;
|
|
7416
|
-
c.in =
|
|
7417
|
-
c.start =
|
|
7763
|
+
c.in = round11(newIn);
|
|
7764
|
+
c.start = round11(newStart);
|
|
7418
7765
|
};
|
|
7419
7766
|
}
|
|
7420
7767
|
if (clip.loop) {
|
|
7421
7768
|
const newLen = Math.max(0.1, g.t - clip.start);
|
|
7422
7769
|
return (d) => {
|
|
7423
7770
|
const c = d.audio.find((x) => x.id === g.id);
|
|
7424
|
-
if (c) c.loopLen =
|
|
7771
|
+
if (c) c.loopLen = round11(newLen);
|
|
7425
7772
|
};
|
|
7426
7773
|
}
|
|
7427
7774
|
const span = Math.max(0.05, g.t - clip.start);
|
|
7428
7775
|
const newOut = Math.min(clip.in + span, clip.duration);
|
|
7429
7776
|
return (d) => {
|
|
7430
7777
|
const c = d.audio.find((x) => x.id === g.id);
|
|
7431
|
-
if (c) c.out =
|
|
7778
|
+
if (c) c.out = round11(Math.max(c.in + 0.05, newOut));
|
|
7432
7779
|
};
|
|
7433
7780
|
}
|
|
7434
7781
|
if (g.type === "remove") {
|
|
@@ -7441,8 +7788,8 @@ var audioLane = {
|
|
|
7441
7788
|
},
|
|
7442
7789
|
magnets(doc) {
|
|
7443
7790
|
return doc.audio.flatMap((c) => [
|
|
7444
|
-
|
|
7445
|
-
|
|
7791
|
+
round11(c.start),
|
|
7792
|
+
round11(c.start + clipLength(c))
|
|
7446
7793
|
]);
|
|
7447
7794
|
}
|
|
7448
7795
|
};
|
|
@@ -7460,7 +7807,7 @@ function poseItems(clipId, start, duration, motion) {
|
|
|
7460
7807
|
return (motion ?? []).map((p, i) => ({
|
|
7461
7808
|
id: `${clipId}::k${i}`,
|
|
7462
7809
|
kind: "keyframe",
|
|
7463
|
-
t:
|
|
7810
|
+
t: round11(start + Math.min(Math.max(0, p.at), duration))
|
|
7464
7811
|
}));
|
|
7465
7812
|
}
|
|
7466
7813
|
var overlaysLane = {
|
|
@@ -7471,8 +7818,8 @@ var overlaysLane = {
|
|
|
7471
7818
|
{
|
|
7472
7819
|
id: o.id,
|
|
7473
7820
|
kind: "clip",
|
|
7474
|
-
t:
|
|
7475
|
-
duration:
|
|
7821
|
+
t: round11(o.start),
|
|
7822
|
+
duration: round11(o.duration),
|
|
7476
7823
|
label: o.kind === "text" ? o.text.split("\n")[0].slice(0, 24) || "Text" : o.kind === "image" ? "Image" : "Video"
|
|
7477
7824
|
},
|
|
7478
7825
|
// Pose diamonds render after the clips, so they sit on top.
|
|
@@ -7492,7 +7839,7 @@ var overlaysLane = {
|
|
|
7492
7839
|
return (d) => {
|
|
7493
7840
|
const o = (d.overlays ?? []).find((x) => x.id === kf.clipId);
|
|
7494
7841
|
const p = o?.motion?.[kf.index];
|
|
7495
|
-
if (p) p.at =
|
|
7842
|
+
if (p) p.at = round11(at);
|
|
7496
7843
|
};
|
|
7497
7844
|
}
|
|
7498
7845
|
if (g.type === "remove") {
|
|
@@ -7521,8 +7868,8 @@ var overlaysLane = {
|
|
|
7521
7868
|
{
|
|
7522
7869
|
id,
|
|
7523
7870
|
kind: "text",
|
|
7524
|
-
start:
|
|
7525
|
-
duration:
|
|
7871
|
+
start: round11(start),
|
|
7872
|
+
duration: round11(len),
|
|
7526
7873
|
text: "Title",
|
|
7527
7874
|
preset: "title",
|
|
7528
7875
|
// Frame fractions: centered, lower-third — at any aspect ratio.
|
|
@@ -7537,7 +7884,7 @@ var overlaysLane = {
|
|
|
7537
7884
|
const start = Math.max(0, g.t);
|
|
7538
7885
|
return (d) => {
|
|
7539
7886
|
const o = (d.overlays ?? []).find((x) => x.id === g.id);
|
|
7540
|
-
if (o) o.start =
|
|
7887
|
+
if (o) o.start = round11(start);
|
|
7541
7888
|
};
|
|
7542
7889
|
}
|
|
7543
7890
|
if (g.type === "resize") {
|
|
@@ -7547,14 +7894,14 @@ var overlaysLane = {
|
|
|
7547
7894
|
return (d) => {
|
|
7548
7895
|
const o = (d.overlays ?? []).find((x) => x.id === g.id);
|
|
7549
7896
|
if (!o) return;
|
|
7550
|
-
o.start =
|
|
7551
|
-
o.duration =
|
|
7897
|
+
o.start = round11(newStart);
|
|
7898
|
+
o.duration = round11(end - newStart);
|
|
7552
7899
|
};
|
|
7553
7900
|
}
|
|
7554
7901
|
const newLen = Math.max(OVERLAY_MIN_DURATION, g.t - clip.start);
|
|
7555
7902
|
return (d) => {
|
|
7556
7903
|
const o = (d.overlays ?? []).find((x) => x.id === g.id);
|
|
7557
|
-
if (o) o.duration =
|
|
7904
|
+
if (o) o.duration = round11(newLen);
|
|
7558
7905
|
};
|
|
7559
7906
|
}
|
|
7560
7907
|
return (d) => {
|
|
@@ -7563,8 +7910,8 @@ var overlaysLane = {
|
|
|
7563
7910
|
},
|
|
7564
7911
|
magnets(doc) {
|
|
7565
7912
|
return (doc.overlays ?? []).flatMap((o) => [
|
|
7566
|
-
|
|
7567
|
-
|
|
7913
|
+
round11(o.start),
|
|
7914
|
+
round11(o.start + o.duration)
|
|
7568
7915
|
]);
|
|
7569
7916
|
}
|
|
7570
7917
|
};
|
|
@@ -7577,8 +7924,8 @@ var objectsLane = {
|
|
|
7577
7924
|
{
|
|
7578
7925
|
id: o.id,
|
|
7579
7926
|
kind: "clip",
|
|
7580
|
-
t:
|
|
7581
|
-
duration:
|
|
7927
|
+
t: round11(o.span?.start ?? 0),
|
|
7928
|
+
duration: round11(o.span?.duration ?? outDur),
|
|
7582
7929
|
label: o.asset.kind === "primitive" ? o.asset.shape : o.asset.kind === "text3d" ? o.asset.text : "model"
|
|
7583
7930
|
},
|
|
7584
7931
|
// Pose diamonds: clip-local `at` from the span start (0 span-less).
|
|
@@ -7606,7 +7953,7 @@ var objectsLane = {
|
|
|
7606
7953
|
return (d) => {
|
|
7607
7954
|
const o = (d.objects ?? []).find((x) => x.id === kf.clipId);
|
|
7608
7955
|
const p = o?.motion?.[kf.index];
|
|
7609
|
-
if (p) p.at =
|
|
7956
|
+
if (p) p.at = round11(at);
|
|
7610
7957
|
};
|
|
7611
7958
|
}
|
|
7612
7959
|
if (g.type === "remove") {
|
|
@@ -7627,7 +7974,7 @@ var objectsLane = {
|
|
|
7627
7974
|
const start = Math.max(0, g.t);
|
|
7628
7975
|
return (d) => {
|
|
7629
7976
|
const o = (d.objects ?? []).find((x) => x.id === g.id);
|
|
7630
|
-
if (o?.span) o.span.start =
|
|
7977
|
+
if (o?.span) o.span.start = round11(start);
|
|
7631
7978
|
};
|
|
7632
7979
|
}
|
|
7633
7980
|
if (g.type === "resize") {
|
|
@@ -7638,14 +7985,14 @@ var objectsLane = {
|
|
|
7638
7985
|
return (d) => {
|
|
7639
7986
|
const o = (d.objects ?? []).find((x) => x.id === g.id);
|
|
7640
7987
|
if (!o?.span) return;
|
|
7641
|
-
o.span.start =
|
|
7642
|
-
o.span.duration =
|
|
7988
|
+
o.span.start = round11(newStart);
|
|
7989
|
+
o.span.duration = round11(end - newStart);
|
|
7643
7990
|
};
|
|
7644
7991
|
}
|
|
7645
7992
|
const newLen = Math.max(OVERLAY_MIN_DURATION, g.t - clip.span.start);
|
|
7646
7993
|
return (d) => {
|
|
7647
7994
|
const o = (d.objects ?? []).find((x) => x.id === g.id);
|
|
7648
|
-
if (o?.span) o.span.duration =
|
|
7995
|
+
if (o?.span) o.span.duration = round11(newLen);
|
|
7649
7996
|
};
|
|
7650
7997
|
}
|
|
7651
7998
|
return (d) => {
|
|
@@ -7654,7 +8001,7 @@ var objectsLane = {
|
|
|
7654
8001
|
},
|
|
7655
8002
|
magnets(doc) {
|
|
7656
8003
|
return (doc.objects ?? []).flatMap(
|
|
7657
|
-
(o) => o.span ? [
|
|
8004
|
+
(o) => o.span ? [round11(o.span.start), round11(o.span.start + o.span.duration)] : []
|
|
7658
8005
|
);
|
|
7659
8006
|
}
|
|
7660
8007
|
};
|
|
@@ -7681,7 +8028,7 @@ function nextSpeedId(doc) {
|
|
|
7681
8028
|
function segIndex(id) {
|
|
7682
8029
|
return Number(id.replace("seg-", ""));
|
|
7683
8030
|
}
|
|
7684
|
-
function
|
|
8031
|
+
function round11(v) {
|
|
7685
8032
|
return Math.round(v * 1e3) / 1e3;
|
|
7686
8033
|
}
|
|
7687
8034
|
|
|
@@ -7886,6 +8233,9 @@ export {
|
|
|
7886
8233
|
FRAME_BORDER_COLOR_DEFAULT,
|
|
7887
8234
|
FRAME_BORDER_DEFAULT,
|
|
7888
8235
|
FRAME_BORDER_WIDTH_DEFAULT,
|
|
8236
|
+
FREEZE_DEFAULT_SECONDS,
|
|
8237
|
+
FREEZE_SECONDS_MAX,
|
|
8238
|
+
FREEZE_SECONDS_MIN,
|
|
7889
8239
|
HOUSE_GRADIENT,
|
|
7890
8240
|
IDLE_KINDS,
|
|
7891
8241
|
LAYOUT_FRAME_FIELDS,
|
|
@@ -7976,6 +8326,7 @@ export {
|
|
|
7976
8326
|
clampCamFrac,
|
|
7977
8327
|
clampCamSize,
|
|
7978
8328
|
clampFocus,
|
|
8329
|
+
clampFreezeSeconds,
|
|
7979
8330
|
clampSpeedRate,
|
|
7980
8331
|
clampTiltDeg,
|
|
7981
8332
|
clampZoomLevel,
|
|
@@ -7993,6 +8344,7 @@ export {
|
|
|
7993
8344
|
destinationById,
|
|
7994
8345
|
destinationsForChannel,
|
|
7995
8346
|
docCardLayout,
|
|
8347
|
+
docFreezes,
|
|
7996
8348
|
docOutputDuration,
|
|
7997
8349
|
docRestTime,
|
|
7998
8350
|
docToCropSpace,
|
|
@@ -8016,6 +8368,8 @@ export {
|
|
|
8016
8368
|
focusBounds,
|
|
8017
8369
|
followFocusEvents,
|
|
8018
8370
|
frameGeometry,
|
|
8371
|
+
freezeLane,
|
|
8372
|
+
freezeOutputExtents,
|
|
8019
8373
|
hexToRgbTriplet,
|
|
8020
8374
|
houseLook,
|
|
8021
8375
|
idleGaps,
|
|
@@ -8063,6 +8417,7 @@ export {
|
|
|
8063
8417
|
pageDisplayUrl,
|
|
8064
8418
|
parsePoseId,
|
|
8065
8419
|
pickStyle,
|
|
8420
|
+
placeFreezes,
|
|
8066
8421
|
planAutoSpeed,
|
|
8067
8422
|
planAutoTilt,
|
|
8068
8423
|
planAutoZoom,
|
|
@@ -8098,6 +8453,7 @@ export {
|
|
|
8098
8453
|
videoLane,
|
|
8099
8454
|
voiceKey,
|
|
8100
8455
|
withBackdrop,
|
|
8456
|
+
withFreezes,
|
|
8101
8457
|
withHolds,
|
|
8102
8458
|
withoutRejected,
|
|
8103
8459
|
wrapProgramLength,
|