@vosjs/studio-core 0.11.0 → 0.12.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 +106 -25
- package/dist/index.js +376 -58
- 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;
|
|
@@ -540,28 +573,67 @@ function exitSeconds(e) {
|
|
|
540
573
|
const house = e.kind === "fade" ? CARD_FADE_SECONDS : END_CARD_RECEDE;
|
|
541
574
|
return Math.max(0.1, Math.min(3, 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
|
-
|
|
557
|
-
|
|
558
|
-
|
|
559
|
-
|
|
560
|
-
|
|
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;
|
|
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;
|
|
561
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);
|
|
@@ -704,9 +776,29 @@ function migrateMotion(doc) {
|
|
|
704
776
|
const segments = Array.isArray(doc.segments) ? doc.segments : [];
|
|
705
777
|
const overlays = Array.isArray(doc.overlays) ? doc.overlays : [];
|
|
706
778
|
const objects = Array.isArray(doc.objects) ? doc.objects : [];
|
|
707
|
-
const
|
|
779
|
+
const heldSegments = segments.some((s) => s.hold !== void 0);
|
|
780
|
+
const legacy = frame.entrance !== void 0 || doc.endCard !== void 0 || heldSegments || overlays.some(hasLegacyClipMotion) || objects.some((o) => o.animation !== void 0);
|
|
708
781
|
if (!legacy) return doc;
|
|
709
782
|
const out = { ...doc };
|
|
783
|
+
if (heldSegments) {
|
|
784
|
+
const holds = legacyHoldFreezes(segments);
|
|
785
|
+
const own = Array.isArray(doc.freeze) ? doc.freeze : [];
|
|
786
|
+
const taken = new Set(own.map((f) => f.id));
|
|
787
|
+
let n = own.length;
|
|
788
|
+
const migrated = holds.map((h) => {
|
|
789
|
+
let id = `f${n++}`;
|
|
790
|
+
while (taken.has(id)) id = `f${n++}`;
|
|
791
|
+
taken.add(id);
|
|
792
|
+
return { id, at: h.at, seconds: h.seconds };
|
|
793
|
+
});
|
|
794
|
+
out.freeze = [...own, ...migrated].sort((a, b) => a.at - b.at);
|
|
795
|
+
out.segments = segments.map((s) => {
|
|
796
|
+
if (s.hold === void 0) return s;
|
|
797
|
+
const next = { ...s };
|
|
798
|
+
delete next.hold;
|
|
799
|
+
return next;
|
|
800
|
+
});
|
|
801
|
+
}
|
|
710
802
|
const nextFrame = { ...frame };
|
|
711
803
|
if (frame.entrance !== void 0) {
|
|
712
804
|
const e = frame.entrance;
|
|
@@ -730,7 +822,10 @@ function migrateMotion(doc) {
|
|
|
730
822
|
const card = doc.endCard;
|
|
731
823
|
if (card && segments.length) {
|
|
732
824
|
const seconds = Math.max(1, Math.min(8, card.seconds ?? END_CARD_SECONDS));
|
|
733
|
-
const rated =
|
|
825
|
+
const rated = withFreezes(
|
|
826
|
+
splitBySpeed(segments, doc.speed ?? []),
|
|
827
|
+
docFreezes(doc)
|
|
828
|
+
);
|
|
734
829
|
const endStart = totalDuration(rated);
|
|
735
830
|
nextOverlays = [...nextOverlays, ...endCardClips(card, doc, endStart)];
|
|
736
831
|
nextFrame.anim = {
|
|
@@ -767,12 +862,12 @@ function outputEnd(doc, footageEnd) {
|
|
|
767
862
|
}
|
|
768
863
|
|
|
769
864
|
// src/docVersion.ts
|
|
770
|
-
var DOC_SCHEMA_VERSION =
|
|
865
|
+
var DOC_SCHEMA_VERSION = 4;
|
|
771
866
|
function migrateHostedDoc(raw) {
|
|
772
867
|
const version = typeof raw.docSchemaVersion === "number" ? raw.docSchemaVersion : 0;
|
|
773
868
|
if (version >= DOC_SCHEMA_VERSION) return raw;
|
|
774
869
|
let doc = raw;
|
|
775
|
-
if (version <
|
|
870
|
+
if (version < 4 && doc.source && typeof doc.source === "object") {
|
|
776
871
|
doc = migrateMotion(doc);
|
|
777
872
|
}
|
|
778
873
|
return { ...doc, docSchemaVersion: DOC_SCHEMA_VERSION };
|
|
@@ -3222,7 +3317,8 @@ var CAM_PAN_EASE = ZOOM_PAN_EASE;
|
|
|
3222
3317
|
var PROGRAM_DURATION = 1;
|
|
3223
3318
|
function ratedSegments(doc) {
|
|
3224
3319
|
const segs = isRecordingDoc(doc) ? doc.segments.length ? doc.segments : [{ in: 0, out: doc.source.meta.durationMs / 1e3 }] : [{ in: 0, out: programDuration(doc) }];
|
|
3225
|
-
|
|
3320
|
+
const freezes = isRecordingDoc(doc) ? docFreezes(doc) : [];
|
|
3321
|
+
return withFreezes(splitBySpeed2(segs, doc.speed ?? []), freezes);
|
|
3226
3322
|
}
|
|
3227
3323
|
function durationSec(doc, rated) {
|
|
3228
3324
|
const trimmed = totalDuration2(rated);
|
|
@@ -3960,6 +4056,15 @@ var ON_FRAME = `(ctx, content, dt) => {
|
|
|
3960
4056
|
// range); the paused/seek path needs nothing \u2014 mapTime already lands srcT.
|
|
3961
4057
|
var srcRate = TL.rateAt ? TL.rateAt(d.segments || [], t) : 1
|
|
3962
4058
|
var playRate = Math.min(16, Math.max(0.0625, srcRate))
|
|
4059
|
+
// Where the FOOTAGE ends in output seconds. The output may outlast it
|
|
4060
|
+
// (clips placed after the footage), and a hold is a freeze piece whose
|
|
4061
|
+
// rate is tiny: in both the element must stay PAUSED on its frame. An
|
|
4062
|
+
// ended element's play() rewinds it to zero and the drift guard then
|
|
4063
|
+
// seeks it back, which reads as frames jumping back and forth; a hold
|
|
4064
|
+
// crawling at the clamped 1/16 speed toward that same end was the same
|
|
4065
|
+
// bug in slow motion.
|
|
4066
|
+
var footEnd = TL.totalDuration ? TL.totalDuration(d.segments || []) : Infinity
|
|
4067
|
+
var frozen = srcRate < 0.01 || t >= footEnd - 1e-3
|
|
3963
4068
|
// Drive a <video> to the on-screen SOURCE moment: play natively while playing (drift
|
|
3964
4069
|
// correction covers cut-boundary jumps), else step to the exact frame (registering a
|
|
3965
4070
|
// decode promise so the deterministic export awaits it). Shared by the screen video
|
|
@@ -3967,7 +4072,7 @@ var ON_FRAME = `(ctx, content, dt) => {
|
|
|
3967
4072
|
${FRAME_STEP_SRC}
|
|
3968
4073
|
function syncVid(vid) {
|
|
3969
4074
|
try {
|
|
3970
|
-
if (playing) {
|
|
4075
|
+
if (playing && !frozen) {
|
|
3971
4076
|
if (vid.playbackRate !== playRate) {
|
|
3972
4077
|
vid.playbackRate = playRate
|
|
3973
4078
|
// Resampled (tape-style) speed, matching the export's offline mix.
|
|
@@ -5629,7 +5734,7 @@ function layoutOf(doc) {
|
|
|
5629
5734
|
frame: LAYOUT_FRAME_FIELDS.filter((k) => frame[k] !== void 0),
|
|
5630
5735
|
clips: (doc.overlays ?? []).filter(isStageClip).map((o) => o.id),
|
|
5631
5736
|
lean: restLeanOf(doc) !== null,
|
|
5632
|
-
|
|
5737
|
+
freeze: trailingFreeze(doc) !== null
|
|
5633
5738
|
};
|
|
5634
5739
|
}
|
|
5635
5740
|
function copyLayout(from, to, opts = {}) {
|
|
@@ -5691,11 +5796,33 @@ function copyLayout(from, to, opts = {}) {
|
|
|
5691
5796
|
notes.push("lean kept: this take has tilt spans of its own");
|
|
5692
5797
|
}
|
|
5693
5798
|
}
|
|
5694
|
-
const
|
|
5799
|
+
const freeze = trailingFreeze(from);
|
|
5695
5800
|
const last = doc.segments.at(-1);
|
|
5696
|
-
if (
|
|
5801
|
+
if (freeze && last) {
|
|
5802
|
+
const own2 = (doc.freeze ?? []).filter(
|
|
5803
|
+
(f) => Math.abs(f.at - last.out) > 1e-9
|
|
5804
|
+
);
|
|
5805
|
+
const taken = new Set(own2.map((f) => f.id));
|
|
5806
|
+
let n = 0;
|
|
5807
|
+
while (taken.has(`f${n}`)) n++;
|
|
5808
|
+
doc.freeze = [
|
|
5809
|
+
...own2,
|
|
5810
|
+
{ id: `f${n}`, at: last.out, seconds: freeze.seconds }
|
|
5811
|
+
].sort((a, b) => a.at - b.at);
|
|
5812
|
+
doc.segments = doc.segments.map((s) => {
|
|
5813
|
+
if (s.hold === void 0) return s;
|
|
5814
|
+
const next = { ...s };
|
|
5815
|
+
delete next.hold;
|
|
5816
|
+
return next;
|
|
5817
|
+
});
|
|
5818
|
+
}
|
|
5697
5819
|
return { doc, notes };
|
|
5698
5820
|
}
|
|
5821
|
+
function trailingFreeze(doc) {
|
|
5822
|
+
const last = doc.segments.at(-1);
|
|
5823
|
+
if (!last) return null;
|
|
5824
|
+
return docFreezes(doc).find((f) => Math.abs(f.at - last.out) < 1e-9) ?? null;
|
|
5825
|
+
}
|
|
5699
5826
|
var shiftBy = (at, templateEnd, takeEnd) => at === "start" ? 0 : at === "end" ? takeEnd - templateEnd : at;
|
|
5700
5827
|
function clipsFrom(doc, from) {
|
|
5701
5828
|
return {
|
|
@@ -6667,33 +6794,44 @@ function effectiveSegments(doc) {
|
|
|
6667
6794
|
if (isRecordingDoc(doc) && doc.segments.length) return doc.segments;
|
|
6668
6795
|
return [{ in: 0, out: anchorSourceDuration(doc) }];
|
|
6669
6796
|
}
|
|
6670
|
-
var
|
|
6671
|
-
|
|
6797
|
+
var segmentPieces = (seg, speeds, freezes, leading) => {
|
|
6798
|
+
const own = freezes.filter(
|
|
6799
|
+
(f) => f.at <= seg.out + 1e-9 && (leading ? f.at >= seg.in - 1e-9 : f.at > seg.in + 1e-9)
|
|
6800
|
+
);
|
|
6801
|
+
return withFreezes(splitBySpeed3([seg], speeds), own);
|
|
6802
|
+
};
|
|
6803
|
+
var outputLen = (seg, speeds, freezes = [], leading = true) => totalDuration6(segmentPieces(seg, speeds, freezes, leading));
|
|
6804
|
+
var segmentStarts = (segments, speeds, freezes = []) => {
|
|
6672
6805
|
const starts = [];
|
|
6673
6806
|
let acc = 0;
|
|
6674
|
-
|
|
6807
|
+
segments.forEach((s, i) => {
|
|
6675
6808
|
starts.push(acc);
|
|
6676
|
-
acc += outputLen(s, speeds);
|
|
6677
|
-
}
|
|
6809
|
+
acc += outputLen(s, speeds, freezes, i === 0 || !contiguous(segments, i));
|
|
6810
|
+
});
|
|
6678
6811
|
return starts;
|
|
6679
6812
|
};
|
|
6813
|
+
var contiguous = (segments, i) => i > 0 && Math.abs(segments[i - 1].out - segments[i].in) < 1e-9;
|
|
6680
6814
|
var videoLane = {
|
|
6681
6815
|
id: "video",
|
|
6682
6816
|
label: "Video",
|
|
6683
6817
|
items(doc) {
|
|
6684
6818
|
const segments = effectiveSegments(doc);
|
|
6685
6819
|
const speeds = doc.speed ?? [];
|
|
6686
|
-
const
|
|
6820
|
+
const freezes = docFreezes(doc);
|
|
6821
|
+
const starts = segmentStarts(segments, speeds, freezes);
|
|
6687
6822
|
return segments.map((s, i) => ({
|
|
6688
6823
|
id: `seg-${i}`,
|
|
6689
6824
|
kind: "clip",
|
|
6690
|
-
t: starts[i],
|
|
6691
|
-
duration:
|
|
6825
|
+
t: round10(starts[i]),
|
|
6826
|
+
duration: round10(
|
|
6827
|
+
outputLen(s, speeds, freezes, i === 0 || !contiguous(segments, i))
|
|
6828
|
+
)
|
|
6692
6829
|
}));
|
|
6693
6830
|
},
|
|
6694
6831
|
gesture(doc, g) {
|
|
6695
6832
|
const segments = effectiveSegments(doc);
|
|
6696
6833
|
const speeds = doc.speed ?? [];
|
|
6834
|
+
const freezes = docFreezes(doc);
|
|
6697
6835
|
const sourceDuration = anchorSourceDuration(doc);
|
|
6698
6836
|
switch (g.type) {
|
|
6699
6837
|
case "move": {
|
|
@@ -6705,7 +6843,7 @@ var videoLane = {
|
|
|
6705
6843
|
let acc = 0;
|
|
6706
6844
|
let insert = others.length;
|
|
6707
6845
|
for (let i = 0; i < others.length; i++) {
|
|
6708
|
-
const dur = outputLen(others[i], speeds);
|
|
6846
|
+
const dur = outputLen(others[i], speeds, freezes);
|
|
6709
6847
|
if (g.t < acc + dur / 2) {
|
|
6710
6848
|
insert = i;
|
|
6711
6849
|
break;
|
|
@@ -6723,10 +6861,15 @@ var videoLane = {
|
|
|
6723
6861
|
const index = segIndex(g.id);
|
|
6724
6862
|
if (index < 0 || index >= segments.length) return null;
|
|
6725
6863
|
const seg = segments[index];
|
|
6726
|
-
const starts = segmentStarts(segments, speeds);
|
|
6864
|
+
const starts = segmentStarts(segments, speeds, freezes);
|
|
6727
6865
|
const fullMap = splitBySpeed3([{ in: 0, out: sourceDuration }], speeds);
|
|
6728
6866
|
const edgeSrc = g.edge === "start" ? seg.in : seg.out;
|
|
6729
|
-
const edgeOutNow = g.edge === "start" ? starts[index] : starts[index] + outputLen(
|
|
6867
|
+
const edgeOutNow = g.edge === "start" ? starts[index] : starts[index] + outputLen(
|
|
6868
|
+
seg,
|
|
6869
|
+
speeds,
|
|
6870
|
+
freezes.filter((f) => f.at < seg.out - 1e-9),
|
|
6871
|
+
index === 0 || !contiguous(segments, index)
|
|
6872
|
+
);
|
|
6730
6873
|
const anchorOut = sourceToTimeline3(fullMap, Math.min(edgeSrc, sourceDuration)) ?? edgeSrc;
|
|
6731
6874
|
const sourceT = mapTime2(fullMap, anchorOut + (g.t - edgeOutNow));
|
|
6732
6875
|
const next = trimSegment(
|
|
@@ -6741,13 +6884,31 @@ var videoLane = {
|
|
|
6741
6884
|
};
|
|
6742
6885
|
}
|
|
6743
6886
|
case "create": {
|
|
6744
|
-
const starts = segmentStarts(segments, speeds);
|
|
6887
|
+
const starts = segmentStarts(segments, speeds, freezes);
|
|
6745
6888
|
const index = segments.findIndex(
|
|
6746
|
-
(s2, i) => g.t >= starts[i] && g.t < starts[i] + outputLen(
|
|
6889
|
+
(s2, i) => g.t >= starts[i] && g.t < starts[i] + outputLen(
|
|
6890
|
+
s2,
|
|
6891
|
+
speeds,
|
|
6892
|
+
freezes,
|
|
6893
|
+
i === 0 || !contiguous(segments, i)
|
|
6894
|
+
)
|
|
6747
6895
|
);
|
|
6748
6896
|
if (index < 0) return null;
|
|
6749
6897
|
const s = segments[index];
|
|
6750
|
-
const
|
|
6898
|
+
const leading = index === 0 || !contiguous(segments, index);
|
|
6899
|
+
const local = g.t - starts[index];
|
|
6900
|
+
for (const m of freezeOutputExtents(
|
|
6901
|
+
splitBySpeed3([s], speeds),
|
|
6902
|
+
freezes.filter(
|
|
6903
|
+
(f) => f.at <= s.out + 1e-9 && (leading ? f.at >= s.in - 1e-9 : f.at > s.in + 1e-9)
|
|
6904
|
+
)
|
|
6905
|
+
).values()) {
|
|
6906
|
+
if (local >= m.t - 1e-9 && local <= m.t + m.duration + 1e-9)
|
|
6907
|
+
return null;
|
|
6908
|
+
}
|
|
6909
|
+
const sourceT = round10(
|
|
6910
|
+
mapTime2(segmentPieces(s, speeds, freezes, leading), local)
|
|
6911
|
+
);
|
|
6751
6912
|
if (sourceT - s.in < 0.05 || s.out - sourceT < 0.05) return null;
|
|
6752
6913
|
const next = [
|
|
6753
6914
|
...segments.slice(0, index),
|
|
@@ -6773,15 +6934,154 @@ var videoLane = {
|
|
|
6773
6934
|
magnets(doc) {
|
|
6774
6935
|
const segments = effectiveSegments(doc);
|
|
6775
6936
|
const speeds = doc.speed ?? [];
|
|
6776
|
-
const
|
|
6937
|
+
const freezes = docFreezes(doc);
|
|
6938
|
+
const starts = segmentStarts(segments, speeds, freezes);
|
|
6939
|
+
const n = segments.length;
|
|
6777
6940
|
return [
|
|
6778
6941
|
...starts,
|
|
6779
|
-
...
|
|
6780
|
-
starts[
|
|
6942
|
+
...n ? [
|
|
6943
|
+
starts[n - 1] + outputLen(
|
|
6944
|
+
segments[n - 1],
|
|
6945
|
+
speeds,
|
|
6946
|
+
freezes,
|
|
6947
|
+
n === 1 || !contiguous(segments, n - 1)
|
|
6948
|
+
)
|
|
6781
6949
|
] : []
|
|
6782
|
-
];
|
|
6950
|
+
].map(round10);
|
|
6951
|
+
}
|
|
6952
|
+
};
|
|
6953
|
+
function nextFreezeId(doc) {
|
|
6954
|
+
const taken = new Set((doc.freeze ?? []).map((f) => f.id));
|
|
6955
|
+
let n = 0;
|
|
6956
|
+
while (taken.has(`f${n}`)) n++;
|
|
6957
|
+
return `f${n}`;
|
|
6958
|
+
}
|
|
6959
|
+
var FREEZE_GAP = 0.05;
|
|
6960
|
+
var freezeLane = {
|
|
6961
|
+
id: "freeze",
|
|
6962
|
+
label: "Freeze",
|
|
6963
|
+
items(doc) {
|
|
6964
|
+
const freezes = docFreezes(doc);
|
|
6965
|
+
if (!freezes.length) return [];
|
|
6966
|
+
const segs = effectiveSegments(doc);
|
|
6967
|
+
const marks = freezeOutputExtents(
|
|
6968
|
+
splitBySpeed3(segs, doc.speed ?? []),
|
|
6969
|
+
freezes
|
|
6970
|
+
);
|
|
6971
|
+
return freezes.flatMap((f) => {
|
|
6972
|
+
const m = marks.get(f.id);
|
|
6973
|
+
return m ? [
|
|
6974
|
+
{
|
|
6975
|
+
id: f.id,
|
|
6976
|
+
kind: "clip",
|
|
6977
|
+
t: round10(m.t),
|
|
6978
|
+
duration: round10(m.duration),
|
|
6979
|
+
label: `${trimZeros(f.seconds)}s`
|
|
6980
|
+
}
|
|
6981
|
+
] : [];
|
|
6982
|
+
});
|
|
6983
|
+
},
|
|
6984
|
+
gesture(doc, g) {
|
|
6985
|
+
const freezes = docFreezes(doc);
|
|
6986
|
+
const segs = effectiveSegments(doc);
|
|
6987
|
+
const speeds = doc.speed ?? [];
|
|
6988
|
+
const sourceDuration = anchorSourceDuration(doc);
|
|
6989
|
+
const kept = (t) => segs.some((s) => t >= s.in - 1e-9 && t <= s.out + 1e-9);
|
|
6990
|
+
const clear = (t, except) => !freezes.some((f2) => f2.id !== except && Math.abs(f2.at - t) < FREEZE_GAP);
|
|
6991
|
+
if (g.type === "create") {
|
|
6992
|
+
const rated = ratedSegments(doc);
|
|
6993
|
+
for (const m of freezeOutputExtents(
|
|
6994
|
+
splitBySpeed3(segs, speeds),
|
|
6995
|
+
freezes
|
|
6996
|
+
).values()) {
|
|
6997
|
+
if (g.t >= m.t - 1e-9 && g.t <= m.t + m.duration + 1e-9) return null;
|
|
6998
|
+
}
|
|
6999
|
+
if (g.t > totalDuration6(rated) + 1e-9) return null;
|
|
7000
|
+
const srcT = round10(mapTime2(rated, Math.max(0, g.t)));
|
|
7001
|
+
if (!kept(srcT) || !clear(srcT)) return null;
|
|
7002
|
+
const id = nextFreezeId(doc);
|
|
7003
|
+
return (d) => {
|
|
7004
|
+
d.freeze = [
|
|
7005
|
+
...d.freeze ?? [],
|
|
7006
|
+
{ id, at: srcT, seconds: FREEZE_DEFAULT_SECONDS }
|
|
7007
|
+
].sort((a, b) => a.at - b.at);
|
|
7008
|
+
};
|
|
7009
|
+
}
|
|
7010
|
+
const f = freezes.find((x) => x.id === g.id);
|
|
7011
|
+
if (!f) return null;
|
|
7012
|
+
const own = (d) => {
|
|
7013
|
+
const list = d.freeze ?? [];
|
|
7014
|
+
let mine = list.find((x) => x.id === f.id);
|
|
7015
|
+
if (!mine) {
|
|
7016
|
+
const id = nextFreezeId(d);
|
|
7017
|
+
mine = { id, at: f.at, seconds: f.seconds };
|
|
7018
|
+
d.freeze = [...list, mine];
|
|
7019
|
+
d.segments = d.segments.map((sg) => {
|
|
7020
|
+
if (sg.hold === void 0 || Math.abs(sg.out - f.at) > 1e-9) return sg;
|
|
7021
|
+
const next = { ...sg };
|
|
7022
|
+
delete next.hold;
|
|
7023
|
+
return next;
|
|
7024
|
+
});
|
|
7025
|
+
}
|
|
7026
|
+
return mine;
|
|
7027
|
+
};
|
|
7028
|
+
const base = withFreezes(
|
|
7029
|
+
splitBySpeed3(segs, speeds),
|
|
7030
|
+
freezes.filter((x) => x.id !== f.id)
|
|
7031
|
+
);
|
|
7032
|
+
if (g.type === "move" || g.type === "resize" && g.edge === "start") {
|
|
7033
|
+
const marks = freezeOutputExtents(splitBySpeed3(segs, speeds), freezes);
|
|
7034
|
+
const mine = marks.get(f.id);
|
|
7035
|
+
const endOut = mine ? mine.t + mine.duration : null;
|
|
7036
|
+
const srcT = round10(
|
|
7037
|
+
Math.min(sourceDuration, mapTime2(base, Math.max(0, g.t)))
|
|
7038
|
+
);
|
|
7039
|
+
if (!kept(srcT) || !clear(srcT, f.id)) return null;
|
|
7040
|
+
return (d) => {
|
|
7041
|
+
const x = own(d);
|
|
7042
|
+
if (!x) return;
|
|
7043
|
+
x.at = srcT;
|
|
7044
|
+
if (g.type === "resize" && endOut !== null) {
|
|
7045
|
+
const after = freezeOutputExtents(
|
|
7046
|
+
splitBySpeed3(effectiveSegments(d), d.speed ?? []),
|
|
7047
|
+
docFreezes({
|
|
7048
|
+
...d,
|
|
7049
|
+
freeze: (d.freeze ?? []).map(
|
|
7050
|
+
(y) => y.id === x.id ? { ...y, seconds: FREEZE_SECONDS_MIN } : y
|
|
7051
|
+
)
|
|
7052
|
+
})
|
|
7053
|
+
).get(x.id);
|
|
7054
|
+
if (after) x.seconds = clampFreezeSeconds(endOut - after.t);
|
|
7055
|
+
}
|
|
7056
|
+
d.freeze.sort((a, b) => a.at - b.at);
|
|
7057
|
+
};
|
|
7058
|
+
}
|
|
7059
|
+
if (g.type === "resize") {
|
|
7060
|
+
const marks = freezeOutputExtents(splitBySpeed3(segs, speeds), freezes);
|
|
7061
|
+
const mine = marks.get(f.id);
|
|
7062
|
+
if (!mine) return null;
|
|
7063
|
+
const seconds = clampFreezeSeconds(g.t - mine.t);
|
|
7064
|
+
if (Math.abs(seconds - f.seconds) < 1e-6) return null;
|
|
7065
|
+
return (d) => {
|
|
7066
|
+
const x = own(d);
|
|
7067
|
+
if (x) x.seconds = seconds;
|
|
7068
|
+
};
|
|
7069
|
+
}
|
|
7070
|
+
if (g.type === "remove") {
|
|
7071
|
+
return (d) => {
|
|
7072
|
+
const x = own(d);
|
|
7073
|
+
d.freeze = (d.freeze ?? []).filter((y) => y !== x);
|
|
7074
|
+
};
|
|
7075
|
+
}
|
|
7076
|
+
return null;
|
|
7077
|
+
},
|
|
7078
|
+
magnets(doc) {
|
|
7079
|
+
return freezeLane.items(doc).flatMap((it) => [it.t, it.t + (it.duration ?? 0)]);
|
|
6783
7080
|
}
|
|
6784
7081
|
};
|
|
7082
|
+
function trimZeros(n) {
|
|
7083
|
+
return String(Math.round(n * 100) / 100);
|
|
7084
|
+
}
|
|
6785
7085
|
var zoomLane = {
|
|
6786
7086
|
id: "zoom",
|
|
6787
7087
|
label: "Zoom",
|
|
@@ -7136,7 +7436,8 @@ var camLane = {
|
|
|
7136
7436
|
if (!doc.source.camKey || !doc.cam.visible) return [];
|
|
7137
7437
|
const segments = effectiveSegments(doc);
|
|
7138
7438
|
const speeds = doc.speed ?? [];
|
|
7139
|
-
const
|
|
7439
|
+
const freezes = docFreezes(doc);
|
|
7440
|
+
const starts = segmentStarts(segments, speeds, freezes);
|
|
7140
7441
|
const first = segments[0];
|
|
7141
7442
|
const last = segments[segments.length - 1];
|
|
7142
7443
|
const win = doc.cam.window ?? { in: first.in, out: last.out };
|
|
@@ -7145,11 +7446,16 @@ var camLane = {
|
|
|
7145
7446
|
const a = Math.max(s.in, win.in);
|
|
7146
7447
|
const b = Math.min(s.out, win.out);
|
|
7147
7448
|
if (b - a <= 1e-6) return;
|
|
7449
|
+
const leading = i === 0 || !contiguous(segments, i);
|
|
7148
7450
|
items.push({
|
|
7149
7451
|
id: `cam-${i}`,
|
|
7150
7452
|
kind: "clip",
|
|
7151
|
-
t: round10(
|
|
7152
|
-
|
|
7453
|
+
t: round10(
|
|
7454
|
+
starts[i] + outputLen({ in: s.in, out: a }, speeds, freezes, leading)
|
|
7455
|
+
),
|
|
7456
|
+
duration: round10(
|
|
7457
|
+
outputLen({ in: a, out: b }, speeds, freezes, a === s.in && leading)
|
|
7458
|
+
)
|
|
7153
7459
|
});
|
|
7154
7460
|
});
|
|
7155
7461
|
return items;
|
|
@@ -7205,12 +7511,15 @@ var micLane = {
|
|
|
7205
7511
|
if (!voiceKey(doc)) return [];
|
|
7206
7512
|
const segments = effectiveSegments(doc);
|
|
7207
7513
|
const speeds = doc.speed ?? [];
|
|
7208
|
-
const
|
|
7514
|
+
const freezes = docFreezes(doc);
|
|
7515
|
+
const starts = segmentStarts(segments, speeds, freezes);
|
|
7209
7516
|
return segments.map((s, i) => ({
|
|
7210
7517
|
id: `mic-${i}`,
|
|
7211
7518
|
kind: "clip",
|
|
7212
|
-
t: starts[i],
|
|
7213
|
-
duration:
|
|
7519
|
+
t: round10(starts[i]),
|
|
7520
|
+
duration: round10(
|
|
7521
|
+
outputLen(s, speeds, freezes, i === 0 || !contiguous(segments, i))
|
|
7522
|
+
)
|
|
7214
7523
|
}));
|
|
7215
7524
|
},
|
|
7216
7525
|
gesture() {
|
|
@@ -7877,6 +8186,9 @@ export {
|
|
|
7877
8186
|
FRAME_BORDER_COLOR_DEFAULT,
|
|
7878
8187
|
FRAME_BORDER_DEFAULT,
|
|
7879
8188
|
FRAME_BORDER_WIDTH_DEFAULT,
|
|
8189
|
+
FREEZE_DEFAULT_SECONDS,
|
|
8190
|
+
FREEZE_SECONDS_MAX,
|
|
8191
|
+
FREEZE_SECONDS_MIN,
|
|
7880
8192
|
HOUSE_GRADIENT,
|
|
7881
8193
|
IDLE_KINDS,
|
|
7882
8194
|
LAYOUT_FRAME_FIELDS,
|
|
@@ -7967,6 +8279,7 @@ export {
|
|
|
7967
8279
|
clampCamFrac,
|
|
7968
8280
|
clampCamSize,
|
|
7969
8281
|
clampFocus,
|
|
8282
|
+
clampFreezeSeconds,
|
|
7970
8283
|
clampSpeedRate,
|
|
7971
8284
|
clampTiltDeg,
|
|
7972
8285
|
clampZoomLevel,
|
|
@@ -7984,6 +8297,7 @@ export {
|
|
|
7984
8297
|
destinationById,
|
|
7985
8298
|
destinationsForChannel,
|
|
7986
8299
|
docCardLayout,
|
|
8300
|
+
docFreezes,
|
|
7987
8301
|
docOutputDuration,
|
|
7988
8302
|
docRestTime,
|
|
7989
8303
|
docToCropSpace,
|
|
@@ -8007,6 +8321,8 @@ export {
|
|
|
8007
8321
|
focusBounds,
|
|
8008
8322
|
followFocusEvents,
|
|
8009
8323
|
frameGeometry,
|
|
8324
|
+
freezeLane,
|
|
8325
|
+
freezeOutputExtents,
|
|
8010
8326
|
hexToRgbTriplet,
|
|
8011
8327
|
houseLook,
|
|
8012
8328
|
idleGaps,
|
|
@@ -8054,6 +8370,7 @@ export {
|
|
|
8054
8370
|
pageDisplayUrl,
|
|
8055
8371
|
parsePoseId,
|
|
8056
8372
|
pickStyle,
|
|
8373
|
+
placeFreezes,
|
|
8057
8374
|
planAutoSpeed,
|
|
8058
8375
|
planAutoTilt,
|
|
8059
8376
|
planAutoZoom,
|
|
@@ -8089,6 +8406,7 @@ export {
|
|
|
8089
8406
|
videoLane,
|
|
8090
8407
|
voiceKey,
|
|
8091
8408
|
withBackdrop,
|
|
8409
|
+
withFreezes,
|
|
8092
8410
|
withHolds,
|
|
8093
8411
|
withoutRejected,
|
|
8094
8412
|
wrapProgramLength,
|