@vosjs/studio-core 0.30.3 → 0.31.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 +95 -3
- package/dist/index.js +737 -207
- package/dist/index.js.map +1 -1
- package/package.json +3 -3
package/dist/index.js
CHANGED
|
@@ -665,6 +665,527 @@ function projectFromArtifact(artifact, videoUrl, opts = {}) {
|
|
|
665
665
|
return { doc, videoUrl };
|
|
666
666
|
}
|
|
667
667
|
|
|
668
|
+
// src/ingest/traces.ts
|
|
669
|
+
var CLICK_HOLD_MS = 40;
|
|
670
|
+
function traceKindOf(name) {
|
|
671
|
+
const m = /\.([a-z0-9]+)$/i.exec(name);
|
|
672
|
+
const ext = m ? m[1].toLowerCase() : "";
|
|
673
|
+
if (ext === "zip") return "zip";
|
|
674
|
+
if (ext === "csv") return "csv";
|
|
675
|
+
if (ext === "jsonl" || ext === "ndjson") return "jsonl";
|
|
676
|
+
if (ext === "json") return "json";
|
|
677
|
+
if (ext === "trace") return "trace";
|
|
678
|
+
return null;
|
|
679
|
+
}
|
|
680
|
+
function cursorFromTrace(name, text, opts = {}) {
|
|
681
|
+
const kind = traceKindOf(name);
|
|
682
|
+
if (kind === "csv") return cursorFromCsv(text, opts);
|
|
683
|
+
if (kind === "trace" || looksLikePlaywright(text))
|
|
684
|
+
return cursorFromPlaywrightTrace(text, opts);
|
|
685
|
+
return cursorFromRecords(text, opts);
|
|
686
|
+
}
|
|
687
|
+
function looksLikePlaywright(text) {
|
|
688
|
+
const head = text.slice(0, 4e3);
|
|
689
|
+
return head.includes('"type":"context-options"') || head.includes('"type":"before"') && head.includes('"callId"');
|
|
690
|
+
}
|
|
691
|
+
function sortEvents(events) {
|
|
692
|
+
return events.map((e, i) => ({ e, i })).sort((a, b) => a.e.t - b.e.t || a.i - b.i).map((x) => x.e);
|
|
693
|
+
}
|
|
694
|
+
function round(n) {
|
|
695
|
+
return Math.round(n * 1e3) / 1e3;
|
|
696
|
+
}
|
|
697
|
+
var PW_PRESS = /* @__PURE__ */ new Set([
|
|
698
|
+
"click",
|
|
699
|
+
"dblclick",
|
|
700
|
+
"tap",
|
|
701
|
+
"check",
|
|
702
|
+
"uncheck",
|
|
703
|
+
"setChecked",
|
|
704
|
+
"selectOption"
|
|
705
|
+
]);
|
|
706
|
+
var PW_TYPE = /* @__PURE__ */ new Set(["fill", "type", "press", "pressSequentially"]);
|
|
707
|
+
function cursorFromPlaywrightTrace(text, opts = {}) {
|
|
708
|
+
const befores = [];
|
|
709
|
+
const afters = /* @__PURE__ */ new Map();
|
|
710
|
+
const inputs = /* @__PURE__ */ new Map();
|
|
711
|
+
let viewport;
|
|
712
|
+
let wallTime;
|
|
713
|
+
let monotonicAtOptions;
|
|
714
|
+
for (const line of text.split("\n")) {
|
|
715
|
+
if (!line.trim()) continue;
|
|
716
|
+
let ev;
|
|
717
|
+
try {
|
|
718
|
+
ev = JSON.parse(line);
|
|
719
|
+
} catch {
|
|
720
|
+
continue;
|
|
721
|
+
}
|
|
722
|
+
const type = ev.type;
|
|
723
|
+
if (type === "context-options") {
|
|
724
|
+
const o = ev.options;
|
|
725
|
+
const v = o?.viewport;
|
|
726
|
+
if (v && typeof v.width === "number" && typeof v.height === "number")
|
|
727
|
+
viewport = { width: v.width, height: v.height };
|
|
728
|
+
if (typeof ev.wallTime === "number") wallTime = ev.wallTime;
|
|
729
|
+
if (typeof ev.monotonicTime === "number")
|
|
730
|
+
monotonicAtOptions = ev.monotonicTime;
|
|
731
|
+
} else if (type === "before" && typeof ev.startTime === "number") {
|
|
732
|
+
befores.push({
|
|
733
|
+
callId: String(ev.callId),
|
|
734
|
+
startTime: ev.startTime,
|
|
735
|
+
class: typeof ev.class === "string" ? ev.class : void 0,
|
|
736
|
+
method: typeof ev.method === "string" ? ev.method : void 0,
|
|
737
|
+
params: ev.params && typeof ev.params === "object" ? ev.params : void 0
|
|
738
|
+
});
|
|
739
|
+
} else if (type === "after") {
|
|
740
|
+
afters.set(String(ev.callId), {
|
|
741
|
+
endTime: typeof ev.endTime === "number" ? ev.endTime : void 0,
|
|
742
|
+
point: asPoint(ev.point)
|
|
743
|
+
});
|
|
744
|
+
} else if (type === "input") {
|
|
745
|
+
const p = asPoint(ev.point);
|
|
746
|
+
if (p) inputs.set(String(ev.callId), p);
|
|
747
|
+
}
|
|
748
|
+
}
|
|
749
|
+
const notes = [];
|
|
750
|
+
if (befores.length === 0) {
|
|
751
|
+
return {
|
|
752
|
+
events: [],
|
|
753
|
+
notes: ['not a Playwright trace: no "before" events with a startTime'],
|
|
754
|
+
viewport,
|
|
755
|
+
kind: "playwright",
|
|
756
|
+
placed: 0,
|
|
757
|
+
dropped: 0
|
|
758
|
+
};
|
|
759
|
+
}
|
|
760
|
+
const newPage = befores.find((b) => b.method === "newPage");
|
|
761
|
+
const origin = newPage?.startTime ?? monotonicAtOptions ?? befores[0].startTime;
|
|
762
|
+
const offset = opts.offsetMs ?? 0;
|
|
763
|
+
const t0 = wallTime !== void 0 && monotonicAtOptions !== void 0 ? Math.round(wallTime + (origin - monotonicAtOptions) - offset) : void 0;
|
|
764
|
+
const at = (ms) => round(ms - origin + offset);
|
|
765
|
+
const events = [];
|
|
766
|
+
const cur = { pos: null };
|
|
767
|
+
let placed = 0;
|
|
768
|
+
let dropped = 0;
|
|
769
|
+
const move2 = (t, p) => {
|
|
770
|
+
if (!cur.pos || cur.pos.x !== p.x || cur.pos.y !== p.y) {
|
|
771
|
+
events.push({ t, x: p.x, y: p.y, type: "move" });
|
|
772
|
+
}
|
|
773
|
+
cur.pos = p;
|
|
774
|
+
};
|
|
775
|
+
const press = (start, end, p, times = 1) => {
|
|
776
|
+
move2(start, p);
|
|
777
|
+
let downAt = Math.max(start, end - CLICK_HOLD_MS);
|
|
778
|
+
for (let i = 0; i < times; i++) {
|
|
779
|
+
events.push({ t: round(downAt), x: p.x, y: p.y, type: "down", button: 0 });
|
|
780
|
+
events.push({
|
|
781
|
+
t: round(downAt + CLICK_HOLD_MS),
|
|
782
|
+
x: p.x,
|
|
783
|
+
y: p.y,
|
|
784
|
+
type: "up",
|
|
785
|
+
button: 0
|
|
786
|
+
});
|
|
787
|
+
downAt += CLICK_HOLD_MS * 2;
|
|
788
|
+
}
|
|
789
|
+
};
|
|
790
|
+
for (const b of befores) {
|
|
791
|
+
const after = afters.get(b.callId);
|
|
792
|
+
const end = after?.endTime ?? b.startTime;
|
|
793
|
+
const point = after?.point ?? inputs.get(b.callId);
|
|
794
|
+
const method = b.method ?? "";
|
|
795
|
+
const cls = b.class ?? "";
|
|
796
|
+
const start = at(b.startTime);
|
|
797
|
+
const label = `${cls}.${method}`;
|
|
798
|
+
if (cls === "Page" && method === "mouseMove") {
|
|
799
|
+
const p = asPoint(b.params);
|
|
800
|
+
if (!p) {
|
|
801
|
+
notes.push(`${label} at ${start}ms has no x/y`);
|
|
802
|
+
dropped++;
|
|
803
|
+
continue;
|
|
804
|
+
}
|
|
805
|
+
move2(start, p);
|
|
806
|
+
placed++;
|
|
807
|
+
} else if (cls === "Page" && method === "mouseClick") {
|
|
808
|
+
const p = asPoint(b.params) ?? point;
|
|
809
|
+
if (!p) {
|
|
810
|
+
notes.push(`${label} at ${start}ms has no x/y`);
|
|
811
|
+
dropped++;
|
|
812
|
+
continue;
|
|
813
|
+
}
|
|
814
|
+
press(start, at(end), p, clickCount(b.params));
|
|
815
|
+
placed++;
|
|
816
|
+
} else if (cls === "Page" && (method === "mouseDown" || method === "mouseUp")) {
|
|
817
|
+
if (!cur.pos) {
|
|
818
|
+
notes.push(`${label} at ${start}ms before any pointer position`);
|
|
819
|
+
dropped++;
|
|
820
|
+
continue;
|
|
821
|
+
}
|
|
822
|
+
events.push({
|
|
823
|
+
t: start,
|
|
824
|
+
x: cur.pos.x,
|
|
825
|
+
y: cur.pos.y,
|
|
826
|
+
type: method === "mouseDown" ? "down" : "up",
|
|
827
|
+
button: 0
|
|
828
|
+
});
|
|
829
|
+
placed++;
|
|
830
|
+
} else if (cls === "Page" && method === "mouseWheel") {
|
|
831
|
+
if (!cur.pos) {
|
|
832
|
+
notes.push(`${label} at ${start}ms before any pointer position`);
|
|
833
|
+
dropped++;
|
|
834
|
+
continue;
|
|
835
|
+
}
|
|
836
|
+
events.push({ t: start, x: cur.pos.x, y: cur.pos.y, type: "scroll" });
|
|
837
|
+
placed++;
|
|
838
|
+
} else if (cls === "Frame" && method === "hover") {
|
|
839
|
+
if (!point) {
|
|
840
|
+
notes.push(
|
|
841
|
+
`${label} ${selectorOf(b)} at ${start}ms landed nowhere the trace recorded`
|
|
842
|
+
);
|
|
843
|
+
dropped++;
|
|
844
|
+
continue;
|
|
845
|
+
}
|
|
846
|
+
move2(start, point);
|
|
847
|
+
placed++;
|
|
848
|
+
} else if (cls === "Frame" && PW_PRESS.has(method)) {
|
|
849
|
+
if (!point) {
|
|
850
|
+
notes.push(
|
|
851
|
+
`${label} ${selectorOf(b)} at ${start}ms landed nowhere the trace recorded`
|
|
852
|
+
);
|
|
853
|
+
dropped++;
|
|
854
|
+
continue;
|
|
855
|
+
}
|
|
856
|
+
press(start, at(end), point, method === "dblclick" ? 2 : 1);
|
|
857
|
+
placed++;
|
|
858
|
+
} else if (cls === "Frame" && PW_TYPE.has(method)) {
|
|
859
|
+
if (!point && !cur.pos) {
|
|
860
|
+
notes.push(
|
|
861
|
+
`${label} ${selectorOf(b)} at ${start}ms typed into a field the trace never placed`
|
|
862
|
+
);
|
|
863
|
+
dropped++;
|
|
864
|
+
continue;
|
|
865
|
+
}
|
|
866
|
+
const p = point ?? cur.pos;
|
|
867
|
+
events.push({ t: start, x: p.x, y: p.y, type: "key" });
|
|
868
|
+
placed++;
|
|
869
|
+
} else if (cls === "Frame" && method === "dragAndDrop") {
|
|
870
|
+
notes.push(
|
|
871
|
+
`${label} at ${start}ms: a drag has two points the trace records as one; not placed`
|
|
872
|
+
);
|
|
873
|
+
dropped++;
|
|
874
|
+
}
|
|
875
|
+
}
|
|
876
|
+
const negative = events.filter((e) => e.t < 0).length;
|
|
877
|
+
if (negative)
|
|
878
|
+
notes.push(`${negative} event(s) before the video's start were dropped`);
|
|
879
|
+
return {
|
|
880
|
+
events: sortEvents(events.filter((e) => e.t >= 0)),
|
|
881
|
+
notes,
|
|
882
|
+
viewport,
|
|
883
|
+
t0,
|
|
884
|
+
kind: "playwright",
|
|
885
|
+
placed,
|
|
886
|
+
dropped
|
|
887
|
+
};
|
|
888
|
+
}
|
|
889
|
+
function asPoint(v) {
|
|
890
|
+
if (!v || typeof v !== "object") return void 0;
|
|
891
|
+
const o = v;
|
|
892
|
+
return typeof o.x === "number" && typeof o.y === "number" ? { x: o.x, y: o.y } : void 0;
|
|
893
|
+
}
|
|
894
|
+
function clickCount(params) {
|
|
895
|
+
const n = params?.clickCount;
|
|
896
|
+
return typeof n === "number" && n > 1 ? Math.min(3, Math.floor(n)) : 1;
|
|
897
|
+
}
|
|
898
|
+
function selectorOf(b) {
|
|
899
|
+
const s = b.params?.selector;
|
|
900
|
+
return typeof s === "string" ? s : "";
|
|
901
|
+
}
|
|
902
|
+
var CSV_TYPES = /* @__PURE__ */ new Set(["move", "down", "up", "click", "scroll"]);
|
|
903
|
+
function cursorFromCsv(text, opts = {}) {
|
|
904
|
+
const notes = [];
|
|
905
|
+
const events = [];
|
|
906
|
+
const offset = opts.offsetMs ?? 0;
|
|
907
|
+
let placed = 0;
|
|
908
|
+
let dropped = 0;
|
|
909
|
+
const lines = text.split(/\r?\n/);
|
|
910
|
+
let cols = ["t", "x", "y", "type"];
|
|
911
|
+
let start = 0;
|
|
912
|
+
const first = (lines[0] ?? "").split(",").map((c) => c.trim().toLowerCase());
|
|
913
|
+
if (first.length >= 3 && first.some((c) => c !== "" && Number.isNaN(Number(c)))) {
|
|
914
|
+
cols = first;
|
|
915
|
+
start = 1;
|
|
916
|
+
}
|
|
917
|
+
const ti = cols.indexOf("t");
|
|
918
|
+
const xi = cols.indexOf("x");
|
|
919
|
+
const yi = cols.indexOf("y");
|
|
920
|
+
const ki = cols.indexOf("type");
|
|
921
|
+
if (ti < 0 || xi < 0 || yi < 0) {
|
|
922
|
+
return {
|
|
923
|
+
events: [],
|
|
924
|
+
notes: [
|
|
925
|
+
"the CSV needs t, x and y columns (a header row names them; without one the order is t,x,y,type)"
|
|
926
|
+
],
|
|
927
|
+
kind: "csv",
|
|
928
|
+
placed: 0,
|
|
929
|
+
dropped: 0
|
|
930
|
+
};
|
|
931
|
+
}
|
|
932
|
+
for (let i = start; i < lines.length; i++) {
|
|
933
|
+
const raw = lines[i];
|
|
934
|
+
if (!raw.trim()) continue;
|
|
935
|
+
const cells = raw.split(",").map((c) => c.trim());
|
|
936
|
+
const t = Number(cells[ti]);
|
|
937
|
+
const x = Number(cells[xi]);
|
|
938
|
+
const y = Number(cells[yi]);
|
|
939
|
+
const type = (ki >= 0 ? cells[ki] : "") || "move";
|
|
940
|
+
if (![t, x, y].every(Number.isFinite)) {
|
|
941
|
+
notes.push(`line ${i + 1}: t, x and y must be numbers`);
|
|
942
|
+
dropped++;
|
|
943
|
+
continue;
|
|
944
|
+
}
|
|
945
|
+
if (!CSV_TYPES.has(type)) {
|
|
946
|
+
notes.push(
|
|
947
|
+
`line ${i + 1}: unknown type "${type}" (move, down, up, click, scroll)`
|
|
948
|
+
);
|
|
949
|
+
dropped++;
|
|
950
|
+
continue;
|
|
951
|
+
}
|
|
952
|
+
const at = round(t + offset);
|
|
953
|
+
if (type === "click") {
|
|
954
|
+
events.push({ t: at, x, y, type: "move" });
|
|
955
|
+
events.push({ t: at, x, y, type: "down", button: 0 });
|
|
956
|
+
events.push({ t: round(at + CLICK_HOLD_MS), x, y, type: "up", button: 0 });
|
|
957
|
+
} else if (type === "down" || type === "up") {
|
|
958
|
+
events.push({ t: at, x, y, type, button: 0 });
|
|
959
|
+
} else {
|
|
960
|
+
events.push({ t: at, x, y, type });
|
|
961
|
+
}
|
|
962
|
+
placed++;
|
|
963
|
+
}
|
|
964
|
+
return { events: sortEvents(events), notes, kind: "csv", placed, dropped };
|
|
965
|
+
}
|
|
966
|
+
var RECORD_TYPES = {
|
|
967
|
+
move: "move",
|
|
968
|
+
hover: "move",
|
|
969
|
+
down: "down",
|
|
970
|
+
mousedown: "down",
|
|
971
|
+
up: "up",
|
|
972
|
+
mouseup: "up",
|
|
973
|
+
click: "click",
|
|
974
|
+
press: "click",
|
|
975
|
+
scroll: "scroll",
|
|
976
|
+
wheel: "scroll",
|
|
977
|
+
key: "key",
|
|
978
|
+
type: "key",
|
|
979
|
+
fill: "key",
|
|
980
|
+
focus: "focus"
|
|
981
|
+
};
|
|
982
|
+
function cursorFromRecords(input, opts = {}) {
|
|
983
|
+
const notes = [];
|
|
984
|
+
const events = [];
|
|
985
|
+
const offset = opts.offsetMs ?? 0;
|
|
986
|
+
let placed = 0;
|
|
987
|
+
let dropped = 0;
|
|
988
|
+
let records;
|
|
989
|
+
if (typeof input === "string") {
|
|
990
|
+
const trimmed = input.trim();
|
|
991
|
+
if (trimmed.startsWith("[")) {
|
|
992
|
+
try {
|
|
993
|
+
records = JSON.parse(trimmed);
|
|
994
|
+
} catch {
|
|
995
|
+
return {
|
|
996
|
+
events: [],
|
|
997
|
+
notes: ["not JSON: the records file could not be parsed"],
|
|
998
|
+
kind: "records",
|
|
999
|
+
placed: 0,
|
|
1000
|
+
dropped: 0
|
|
1001
|
+
};
|
|
1002
|
+
}
|
|
1003
|
+
} else {
|
|
1004
|
+
records = [];
|
|
1005
|
+
let n = 0;
|
|
1006
|
+
for (const line of trimmed.split("\n")) {
|
|
1007
|
+
n++;
|
|
1008
|
+
if (!line.trim()) continue;
|
|
1009
|
+
try {
|
|
1010
|
+
records.push(JSON.parse(line));
|
|
1011
|
+
} catch {
|
|
1012
|
+
notes.push(`line ${n}: not JSON`);
|
|
1013
|
+
dropped++;
|
|
1014
|
+
}
|
|
1015
|
+
}
|
|
1016
|
+
}
|
|
1017
|
+
} else records = input;
|
|
1018
|
+
let epochZero = null;
|
|
1019
|
+
let sawNoTime = 0;
|
|
1020
|
+
let sawNoPoint = 0;
|
|
1021
|
+
records.forEach((r, i) => {
|
|
1022
|
+
if (!r || typeof r !== "object") {
|
|
1023
|
+
notes.push(`record ${i + 1}: not an object`);
|
|
1024
|
+
dropped++;
|
|
1025
|
+
return;
|
|
1026
|
+
}
|
|
1027
|
+
const o = r;
|
|
1028
|
+
let t = numberOf(o.t ?? o.ts ?? o.time ?? o.timestamp);
|
|
1029
|
+
if (t !== void 0 && t > 1e12) {
|
|
1030
|
+
if (epochZero === null) epochZero = t;
|
|
1031
|
+
t -= epochZero;
|
|
1032
|
+
}
|
|
1033
|
+
const kind = recordKind(o);
|
|
1034
|
+
if (!kind) return;
|
|
1035
|
+
const p = asPoint(o) ?? asPoint(o.point) ?? centerOf(o.rect);
|
|
1036
|
+
if (t === void 0) {
|
|
1037
|
+
sawNoTime++;
|
|
1038
|
+
dropped++;
|
|
1039
|
+
return;
|
|
1040
|
+
}
|
|
1041
|
+
if (!p) {
|
|
1042
|
+
sawNoPoint++;
|
|
1043
|
+
dropped++;
|
|
1044
|
+
return;
|
|
1045
|
+
}
|
|
1046
|
+
const at = round(t + offset);
|
|
1047
|
+
const rect = asRect(o.rect);
|
|
1048
|
+
if (kind === "click") {
|
|
1049
|
+
events.push({ t: at, x: p.x, y: p.y, type: "move" });
|
|
1050
|
+
events.push({
|
|
1051
|
+
t: at,
|
|
1052
|
+
x: p.x,
|
|
1053
|
+
y: p.y,
|
|
1054
|
+
type: "down",
|
|
1055
|
+
button: 0,
|
|
1056
|
+
...rect ? { rect } : {}
|
|
1057
|
+
});
|
|
1058
|
+
events.push({
|
|
1059
|
+
t: round(at + CLICK_HOLD_MS),
|
|
1060
|
+
x: p.x,
|
|
1061
|
+
y: p.y,
|
|
1062
|
+
type: "up",
|
|
1063
|
+
button: 0
|
|
1064
|
+
});
|
|
1065
|
+
} else if (kind === "down" || kind === "up") {
|
|
1066
|
+
events.push({
|
|
1067
|
+
t: at,
|
|
1068
|
+
x: p.x,
|
|
1069
|
+
y: p.y,
|
|
1070
|
+
type: kind,
|
|
1071
|
+
button: 0,
|
|
1072
|
+
...rect ? { rect } : {}
|
|
1073
|
+
});
|
|
1074
|
+
} else {
|
|
1075
|
+
events.push({
|
|
1076
|
+
t: at,
|
|
1077
|
+
x: p.x,
|
|
1078
|
+
y: p.y,
|
|
1079
|
+
type: kind,
|
|
1080
|
+
...rect ? { rect } : {}
|
|
1081
|
+
});
|
|
1082
|
+
}
|
|
1083
|
+
placed++;
|
|
1084
|
+
});
|
|
1085
|
+
if (sawNoTime)
|
|
1086
|
+
notes.push(
|
|
1087
|
+
`${sawNoTime} gesture record(s) carry no time (t, ts or time, in ms), so they cannot meet the video; agent-browser's own log has none, so stamp one per command when you keep it`
|
|
1088
|
+
);
|
|
1089
|
+
if (sawNoPoint)
|
|
1090
|
+
notes.push(
|
|
1091
|
+
`${sawNoPoint} gesture record(s) carry no point (x/y, point, or rect); a ref alone says which element, not where it was`
|
|
1092
|
+
);
|
|
1093
|
+
return { events: sortEvents(events), notes, kind: "records", placed, dropped };
|
|
1094
|
+
}
|
|
1095
|
+
function recordKind(o) {
|
|
1096
|
+
const explicit = typeof o.type === "string" ? o.type.toLowerCase() : "";
|
|
1097
|
+
if (explicit && explicit in RECORD_TYPES) return RECORD_TYPES[explicit];
|
|
1098
|
+
const cmd = commandWords(o.command ?? o.action ?? o.kind);
|
|
1099
|
+
const verb = cmd[0] === "mouse" ? `${cmd[0]}${cmd[1] ?? ""}` : cmd[0];
|
|
1100
|
+
if (!verb) return null;
|
|
1101
|
+
const key = verb.toLowerCase();
|
|
1102
|
+
if (key in RECORD_TYPES) return RECORD_TYPES[key];
|
|
1103
|
+
if (key === "mousemove") return "move";
|
|
1104
|
+
if (key === "mouseclick") return "click";
|
|
1105
|
+
if (key === "mousedown") return "down";
|
|
1106
|
+
if (key === "mouseup") return "up";
|
|
1107
|
+
if (key === "dblclick") return "click";
|
|
1108
|
+
return null;
|
|
1109
|
+
}
|
|
1110
|
+
function commandWords(v) {
|
|
1111
|
+
if (Array.isArray(v)) return v.map(String);
|
|
1112
|
+
if (typeof v === "string") return v.trim().split(/\s+/);
|
|
1113
|
+
return [];
|
|
1114
|
+
}
|
|
1115
|
+
function numberOf(v) {
|
|
1116
|
+
if (typeof v === "number" && Number.isFinite(v)) return v;
|
|
1117
|
+
if (typeof v === "string" && v.trim() !== "" && Number.isFinite(Number(v)))
|
|
1118
|
+
return Number(v);
|
|
1119
|
+
return void 0;
|
|
1120
|
+
}
|
|
1121
|
+
function asRect(v) {
|
|
1122
|
+
if (!v || typeof v !== "object") return void 0;
|
|
1123
|
+
const o = v;
|
|
1124
|
+
const x = numberOf(o.x);
|
|
1125
|
+
const y = numberOf(o.y);
|
|
1126
|
+
const w = numberOf(o.w ?? o.width);
|
|
1127
|
+
const h = numberOf(o.h ?? o.height);
|
|
1128
|
+
return x !== void 0 && y !== void 0 && w !== void 0 && h !== void 0 ? { x, y, w, h } : void 0;
|
|
1129
|
+
}
|
|
1130
|
+
function centerOf(v) {
|
|
1131
|
+
const r = asRect(v);
|
|
1132
|
+
return r ? { x: r.x + r.w / 2, y: r.y + r.h / 2 } : void 0;
|
|
1133
|
+
}
|
|
1134
|
+
|
|
1135
|
+
// src/ingest/zip.ts
|
|
1136
|
+
var EOCD = 101010256;
|
|
1137
|
+
var CENTRAL = 33639248;
|
|
1138
|
+
var LOCAL = 67324752;
|
|
1139
|
+
function zipEntries(bytes) {
|
|
1140
|
+
const view = new DataView(bytes.buffer, bytes.byteOffset, bytes.byteLength);
|
|
1141
|
+
let eocd = -1;
|
|
1142
|
+
for (let i = bytes.length - 22; i >= Math.max(0, bytes.length - 22 - 65535); i--) {
|
|
1143
|
+
if (view.getUint32(i, true) === EOCD) {
|
|
1144
|
+
eocd = i;
|
|
1145
|
+
break;
|
|
1146
|
+
}
|
|
1147
|
+
}
|
|
1148
|
+
if (eocd < 0) throw new Error("not a zip: no end-of-central-directory record");
|
|
1149
|
+
const count = view.getUint16(eocd + 10, true);
|
|
1150
|
+
let p = view.getUint32(eocd + 16, true);
|
|
1151
|
+
const entries = [];
|
|
1152
|
+
const decoder = new TextDecoder();
|
|
1153
|
+
for (let i = 0; i < count; i++) {
|
|
1154
|
+
if (view.getUint32(p, true) !== CENTRAL)
|
|
1155
|
+
throw new Error("not a zip: central directory entry expected");
|
|
1156
|
+
const method = view.getUint16(p + 10, true);
|
|
1157
|
+
const compressedSize = view.getUint32(p + 20, true);
|
|
1158
|
+
const size = view.getUint32(p + 24, true);
|
|
1159
|
+
const nameLen = view.getUint16(p + 28, true);
|
|
1160
|
+
const extraLen = view.getUint16(p + 30, true);
|
|
1161
|
+
const commentLen = view.getUint16(p + 32, true);
|
|
1162
|
+
const local = view.getUint32(p + 42, true);
|
|
1163
|
+
const name = decoder.decode(bytes.subarray(p + 46, p + 46 + nameLen));
|
|
1164
|
+
if (view.getUint32(local, true) !== LOCAL)
|
|
1165
|
+
throw new Error(`not a zip: local header expected for ${name}`);
|
|
1166
|
+
const localNameLen = view.getUint16(local + 26, true);
|
|
1167
|
+
const localExtraLen = view.getUint16(local + 28, true);
|
|
1168
|
+
entries.push({
|
|
1169
|
+
name,
|
|
1170
|
+
method,
|
|
1171
|
+
compressedSize,
|
|
1172
|
+
size,
|
|
1173
|
+
dataOffset: local + 30 + localNameLen + localExtraLen
|
|
1174
|
+
});
|
|
1175
|
+
p += 46 + nameLen + extraLen + commentLen;
|
|
1176
|
+
}
|
|
1177
|
+
return entries;
|
|
1178
|
+
}
|
|
1179
|
+
function zipEntryBytes(bytes, entry) {
|
|
1180
|
+
return bytes.subarray(
|
|
1181
|
+
entry.dataOffset,
|
|
1182
|
+
entry.dataOffset + entry.compressedSize
|
|
1183
|
+
);
|
|
1184
|
+
}
|
|
1185
|
+
function playwrightTraceEntry(entries) {
|
|
1186
|
+
return entries.find((e) => e.name === "trace.trace") ?? entries.find((e) => /(^|\/)trace\.trace$/.test(e.name)) ?? null;
|
|
1187
|
+
}
|
|
1188
|
+
|
|
668
1189
|
// src/lower/motion.ts
|
|
669
1190
|
import { sortKeyframes } from "@vosjs/timeline";
|
|
670
1191
|
|
|
@@ -1752,7 +2273,7 @@ function planAutoZoom(track, options) {
|
|
|
1752
2273
|
typingMinLevel = style.typingMinLevel,
|
|
1753
2274
|
duration
|
|
1754
2275
|
} = options;
|
|
1755
|
-
const within = (spans2) => duration === void 0 ? spans2 : spans2.map((z) => ({ ...z, out: Math.min(z.out,
|
|
2276
|
+
const within = (spans2) => duration === void 0 ? spans2 : spans2.map((z) => ({ ...z, out: Math.min(z.out, round2(duration)) })).filter((z) => z.out - z.in >= 0.3);
|
|
1756
2277
|
const grouped = groupTrack(track, {
|
|
1757
2278
|
width,
|
|
1758
2279
|
height,
|
|
@@ -1764,17 +2285,17 @@ function planAutoZoom(track, options) {
|
|
|
1764
2285
|
});
|
|
1765
2286
|
const { sessions } = grouped;
|
|
1766
2287
|
const drags = dragsFromTrack(track, width, height);
|
|
1767
|
-
const dragPress = new Set(drags.map((d) =>
|
|
1768
|
-
const withoutDrags = (list) => list.map((cl) => cl.filter((c) => !dragPress.has(
|
|
2288
|
+
const dragPress = new Set(drags.map((d) => round2(d.t0)));
|
|
2289
|
+
const withoutDrags = (list) => list.map((cl) => cl.filter((c) => !dragPress.has(round2(c.t)))).filter((cl) => cl.length > 0);
|
|
1769
2290
|
const clusters = withoutDrags(grouped.clusters);
|
|
1770
2291
|
const surfaces = withoutDrags(grouped.surfaces);
|
|
1771
2292
|
const dragSpans = drags.map((d, i) => ({
|
|
1772
2293
|
id: `g${i}`,
|
|
1773
|
-
in:
|
|
1774
|
-
out:
|
|
2294
|
+
in: round2(Math.max(0, d.t0 - lead)),
|
|
2295
|
+
out: round2(d.t1 + hold),
|
|
1775
2296
|
level: clampZoomLevel(Math.min(Math.max(dragLevel, 1.1), maxLevel)),
|
|
1776
|
-
cx:
|
|
1777
|
-
cy:
|
|
2297
|
+
cx: round2(d.nx),
|
|
2298
|
+
cy: round2(d.ny),
|
|
1778
2299
|
focusMode: "auto",
|
|
1779
2300
|
source: "auto"
|
|
1780
2301
|
}));
|
|
@@ -1851,11 +2372,11 @@ function planAutoZoom(track, options) {
|
|
|
1851
2372
|
typing = typing.filter((t) => t.out - t.in >= 0.3);
|
|
1852
2373
|
const zSpans = clickSpans.filter((s) => !s.dead).sort((a, b) => a.in - b.in).map((s, i) => ({
|
|
1853
2374
|
id: `z${i}`,
|
|
1854
|
-
in:
|
|
1855
|
-
out:
|
|
2375
|
+
in: round2(s.in),
|
|
2376
|
+
out: round2(s.out),
|
|
1856
2377
|
level: clampZoomLevel(s.level),
|
|
1857
|
-
cx:
|
|
1858
|
-
cy:
|
|
2378
|
+
cx: round2(s.cx),
|
|
2379
|
+
cy: round2(s.cy),
|
|
1859
2380
|
// Follow styles ride the cursor through the span (entry + dead-zone
|
|
1860
2381
|
// recenters baked at lowering); typing/dwell spans stay fixed-focus.
|
|
1861
2382
|
...followByDefault ? { focusMode: "auto" } : {},
|
|
@@ -1863,11 +2384,11 @@ function planAutoZoom(track, options) {
|
|
|
1863
2384
|
}));
|
|
1864
2385
|
const kSpans = typing.map((s, i) => ({
|
|
1865
2386
|
id: `k${i}`,
|
|
1866
|
-
in:
|
|
1867
|
-
out:
|
|
2387
|
+
in: round2(s.in),
|
|
2388
|
+
out: round2(s.out),
|
|
1868
2389
|
level: clampZoomLevel(s.level),
|
|
1869
|
-
cx:
|
|
1870
|
-
cy:
|
|
2390
|
+
cx: round2(s.cx),
|
|
2391
|
+
cy: round2(s.cy),
|
|
1871
2392
|
// Never focusMode:'auto': the dot is parked (and fading) while typing —
|
|
1872
2393
|
// the FIELD is the anchor, and a follow would be a no-op at best.
|
|
1873
2394
|
source: "auto"
|
|
@@ -1944,12 +2465,12 @@ function dwellSpans(track, width, height, maxLevel, reserved) {
|
|
|
1944
2465
|
if (taken.some((z) => spanIn < z.out && spanOut > z.in)) continue;
|
|
1945
2466
|
const span = {
|
|
1946
2467
|
id: `d${accepted.length}`,
|
|
1947
|
-
in:
|
|
1948
|
-
out:
|
|
2468
|
+
in: round2(spanIn),
|
|
2469
|
+
out: round2(spanOut),
|
|
1949
2470
|
// No element rect on moves → point zoom at the planner ceiling.
|
|
1950
2471
|
level: clampZoomLevel(maxLevel),
|
|
1951
|
-
cx:
|
|
1952
|
-
cy:
|
|
2472
|
+
cx: round2(clamp012(c.cx)),
|
|
2473
|
+
cy: round2(clamp012(c.cy)),
|
|
1953
2474
|
source: "auto"
|
|
1954
2475
|
};
|
|
1955
2476
|
accepted.push(span);
|
|
@@ -2018,7 +2539,7 @@ function clamp3(v, lo, hi) {
|
|
|
2018
2539
|
function clamp012(v) {
|
|
2019
2540
|
return clamp3(v, 0, 1);
|
|
2020
2541
|
}
|
|
2021
|
-
function
|
|
2542
|
+
function round2(v) {
|
|
2022
2543
|
return Math.round(v * 1e3) / 1e3;
|
|
2023
2544
|
}
|
|
2024
2545
|
|
|
@@ -2858,14 +3379,14 @@ function resolveOverlayFx(clip, clipDuration) {
|
|
|
2858
3379
|
const maxTotal = Math.max(dur, clipDuration * 0.9);
|
|
2859
3380
|
st = Math.min(st, Math.max(0, (maxTotal - dur) / (upperN - 1)));
|
|
2860
3381
|
}
|
|
2861
|
-
const
|
|
3382
|
+
const round14 = (v) => Math.round(v * 1e4) / 1e4;
|
|
2862
3383
|
return {
|
|
2863
3384
|
k: spec.fx,
|
|
2864
3385
|
u: unit,
|
|
2865
3386
|
d: FX_DIR[spec.direction ?? "forward"] ?? 0,
|
|
2866
|
-
st:
|
|
2867
|
-
dur:
|
|
2868
|
-
tt:
|
|
3387
|
+
st: round14(st),
|
|
3388
|
+
dur: round14(dur),
|
|
3389
|
+
tt: round14(st * (upperN - 1) + dur),
|
|
2869
3390
|
units,
|
|
2870
3391
|
n
|
|
2871
3392
|
};
|
|
@@ -3199,10 +3720,10 @@ function followFocusEvents(span, cursor, space, layout, options = {}) {
|
|
|
3199
3720
|
camera
|
|
3200
3721
|
);
|
|
3201
3722
|
pathEvents.push({
|
|
3202
|
-
t:
|
|
3723
|
+
t: round4(at),
|
|
3203
3724
|
path: true,
|
|
3204
|
-
cx:
|
|
3205
|
-
cy:
|
|
3725
|
+
cx: round4(f.cx),
|
|
3726
|
+
cy: round4(f.cy)
|
|
3206
3727
|
});
|
|
3207
3728
|
if (at >= to) break;
|
|
3208
3729
|
}
|
|
@@ -3226,7 +3747,7 @@ function followFocusEvents(span, cursor, space, layout, options = {}) {
|
|
|
3226
3747
|
const target = lookahead > 0 ? sampleAt2(pts, Math.min(p.t + lookahead, span.out)) : p;
|
|
3227
3748
|
const f = clampFocus(target.nx, target.ny, level, layout, camera);
|
|
3228
3749
|
if (Math.abs(f.cx - cx) < 1e-3 && Math.abs(f.cy - cy) < 1e-3) continue;
|
|
3229
|
-
events.push({ t:
|
|
3750
|
+
events.push({ t: round4(p.t), cx: round4(f.cx), cy: round4(f.cy) });
|
|
3230
3751
|
cx = f.cx;
|
|
3231
3752
|
cy = f.cy;
|
|
3232
3753
|
nextAllowed = p.t + recenter;
|
|
@@ -3250,7 +3771,7 @@ function sampleAt2(pts, t) {
|
|
|
3250
3771
|
function clamp014(v) {
|
|
3251
3772
|
return Math.max(0, Math.min(1, v));
|
|
3252
3773
|
}
|
|
3253
|
-
function
|
|
3774
|
+
function round4(v) {
|
|
3254
3775
|
return Math.round(v * 1e3) / 1e3;
|
|
3255
3776
|
}
|
|
3256
3777
|
|
|
@@ -3540,7 +4061,7 @@ function pinPlacement(input) {
|
|
|
3540
4061
|
side,
|
|
3541
4062
|
track: { keyframes: simplify(poses, [W, H, 1, 1, 1]) },
|
|
3542
4063
|
tip: { keyframes: simplify(tips, [W, H]) },
|
|
3543
|
-
clamped:
|
|
4064
|
+
clamped: round5(clamped),
|
|
3544
4065
|
box
|
|
3545
4066
|
};
|
|
3546
4067
|
}
|
|
@@ -3569,13 +4090,13 @@ function simplify(keys, scalePx) {
|
|
|
3569
4090
|
}
|
|
3570
4091
|
}
|
|
3571
4092
|
const last = keys[keys.length - 1];
|
|
3572
|
-
if (out[out.length - 1].t !==
|
|
4093
|
+
if (out[out.length - 1].t !== round5(last.t)) out.push(roundKey(last));
|
|
3573
4094
|
return out;
|
|
3574
4095
|
}
|
|
3575
4096
|
function roundKey(k) {
|
|
3576
|
-
return { t:
|
|
4097
|
+
return { t: round5(k.t), value: k.value.map(round42), ease: k.ease };
|
|
3577
4098
|
}
|
|
3578
|
-
function
|
|
4099
|
+
function round5(v) {
|
|
3579
4100
|
return Math.round(v * 1e3) / 1e3;
|
|
3580
4101
|
}
|
|
3581
4102
|
function round42(v) {
|
|
@@ -3587,7 +4108,7 @@ var CURSOR_IDLE_HOLD = 1;
|
|
|
3587
4108
|
var CURSOR_IDLE_FADE_OUT = 0.35;
|
|
3588
4109
|
var CURSOR_IDLE_FADE_IN = 0.18;
|
|
3589
4110
|
var CURSOR_IDLE_EPS_FRAC = 25e-4;
|
|
3590
|
-
var
|
|
4111
|
+
var round6 = (n) => Math.round(n * 1e3) / 1e3;
|
|
3591
4112
|
function cursorIdleFade(track, o) {
|
|
3592
4113
|
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 }));
|
|
3593
4114
|
if (moves.length === 0) return [];
|
|
@@ -3639,7 +4160,7 @@ function emit(keys, s, e) {
|
|
|
3639
4160
|
push(keys, e + CURSOR_IDLE_FADE_IN, 1);
|
|
3640
4161
|
}
|
|
3641
4162
|
function push(keys, t, a) {
|
|
3642
|
-
const rt =
|
|
4163
|
+
const rt = round6(t);
|
|
3643
4164
|
if (keys.length > 0 && rt <= keys[keys.length - 1].t) {
|
|
3644
4165
|
keys[keys.length - 1].a = a;
|
|
3645
4166
|
return;
|
|
@@ -5147,7 +5668,7 @@ function gatedRect(rect, x, y, space) {
|
|
|
5147
5668
|
return null;
|
|
5148
5669
|
const m = RECT_CONTAIN_MARGIN;
|
|
5149
5670
|
const inside = x >= rect.x - m && x <= rect.x + rect.w + m && y >= rect.y - m && y <= rect.y + rect.h + m;
|
|
5150
|
-
return inside ? [
|
|
5671
|
+
return inside ? [round7(rect.x), round7(rect.y), round7(rect.w), round7(rect.h)] : null;
|
|
5151
5672
|
}
|
|
5152
5673
|
function extractClicks(track, segments, opts) {
|
|
5153
5674
|
const out = [];
|
|
@@ -5172,11 +5693,11 @@ function extractClicks(track, segments, opts) {
|
|
|
5172
5693
|
const upSrc = st + Math.max(pressLen, 0.02);
|
|
5173
5694
|
const up = keptOutputEnd(segments, st, upSrc, opts.media) ?? ot + CLICK_SYNTH_RELEASE;
|
|
5174
5695
|
const click = {
|
|
5175
|
-
ot:
|
|
5176
|
-
up:
|
|
5177
|
-
st:
|
|
5178
|
-
x:
|
|
5179
|
-
y:
|
|
5696
|
+
ot: round7(ot),
|
|
5697
|
+
up: round7(Math.max(up, ot + 0.02)),
|
|
5698
|
+
st: round7(st),
|
|
5699
|
+
x: round7(e.x),
|
|
5700
|
+
y: round7(e.y),
|
|
5180
5701
|
b: button
|
|
5181
5702
|
};
|
|
5182
5703
|
if (opts.rects && e.rect) {
|
|
@@ -5195,7 +5716,7 @@ function hexToRgbTriplet(hex) {
|
|
|
5195
5716
|
const n = parseInt(h, 16);
|
|
5196
5717
|
return [n >> 16, n >> 8 & 255, n & 255];
|
|
5197
5718
|
}
|
|
5198
|
-
function
|
|
5719
|
+
function round7(v) {
|
|
5199
5720
|
return Math.round(v * 1e3) / 1e3;
|
|
5200
5721
|
}
|
|
5201
5722
|
|
|
@@ -5278,7 +5799,7 @@ function trackEmitter() {
|
|
|
5278
5799
|
if (tt <= prev.t + 1e-6 && sameVec(prev.value, value)) return prev.t;
|
|
5279
5800
|
if (tt <= prev.t + 1e-6) tt = prev.t + 1e-3;
|
|
5280
5801
|
}
|
|
5281
|
-
keyframes.push({ t:
|
|
5802
|
+
keyframes.push({ t: round8(tt), value: value.map(round8), ease });
|
|
5282
5803
|
return tt;
|
|
5283
5804
|
};
|
|
5284
5805
|
return { keyframes, push: push2 };
|
|
@@ -7278,14 +7799,14 @@ function studioLayerData(layers, duration, pins) {
|
|
|
7278
7799
|
// needs a decoded recording, which a sync lowering can't produce.
|
|
7279
7800
|
audio: (layers.audio ?? []).map((c) => ({
|
|
7280
7801
|
key: c.key,
|
|
7281
|
-
start:
|
|
7282
|
-
in:
|
|
7283
|
-
out:
|
|
7284
|
-
gain:
|
|
7802
|
+
start: round8(c.start),
|
|
7803
|
+
in: round8(c.in),
|
|
7804
|
+
out: round8(c.out),
|
|
7805
|
+
gain: round8(c.gain),
|
|
7285
7806
|
loop: !!c.loop,
|
|
7286
|
-
len:
|
|
7807
|
+
len: round8(clipLength(c)),
|
|
7287
7808
|
duck: !!c.duck,
|
|
7288
|
-
env: clipEnvelope(c).map((p) => ({ t:
|
|
7809
|
+
env: clipEnvelope(c).map((p) => ({ t: round8(p.t), g: round8(p.g) }))
|
|
7289
7810
|
})),
|
|
7290
7811
|
// Object clips: numbers resolved HERE; shapes are the drafted engine spec.
|
|
7291
7812
|
...layers.objects && layers.objects.length ? {
|
|
@@ -7298,17 +7819,17 @@ function studioLayerData(layers, duration, pins) {
|
|
|
7298
7819
|
} : o.asset.kind === "text3d" ? resolveText3dAsset(o.asset) : { kind: "gltf", key: o.asset.key },
|
|
7299
7820
|
...o.span ? {
|
|
7300
7821
|
span: {
|
|
7301
|
-
start:
|
|
7302
|
-
duration:
|
|
7822
|
+
start: round8(o.span.start),
|
|
7823
|
+
duration: round8(o.span.duration)
|
|
7303
7824
|
}
|
|
7304
7825
|
} : {},
|
|
7305
|
-
x:
|
|
7306
|
-
y:
|
|
7307
|
-
z:
|
|
7308
|
-
rx:
|
|
7309
|
-
ry:
|
|
7310
|
-
rz:
|
|
7311
|
-
scale:
|
|
7826
|
+
x: round8(o.transform3d.x),
|
|
7827
|
+
y: round8(o.transform3d.y),
|
|
7828
|
+
z: round8(o.transform3d.z),
|
|
7829
|
+
rx: round8(o.transform3d.rx),
|
|
7830
|
+
ry: round8(o.transform3d.ry),
|
|
7831
|
+
rz: round8(o.transform3d.rz),
|
|
7832
|
+
scale: round8(o.transform3d.scale || OBJECT_DEFAULT_SCALE),
|
|
7312
7833
|
anim: idleKey(o),
|
|
7313
7834
|
// Pose keyframes: clip-local [x,y,z,rx,ry,rz,scale] track,
|
|
7314
7835
|
// sampled at t − span.start. Omitted when absent — parity.
|
|
@@ -7335,12 +7856,12 @@ function studioLayerData(layers, duration, pins) {
|
|
|
7335
7856
|
const base = {
|
|
7336
7857
|
id: o.id,
|
|
7337
7858
|
kind: o.kind,
|
|
7338
|
-
start:
|
|
7339
|
-
dur:
|
|
7340
|
-
x:
|
|
7341
|
-
y:
|
|
7342
|
-
scale:
|
|
7343
|
-
rot:
|
|
7859
|
+
start: round8(o.start),
|
|
7860
|
+
dur: round8(Math.max(OVERLAY_MIN_DURATION, o.duration)),
|
|
7861
|
+
x: round8(o.transform.x),
|
|
7862
|
+
y: round8(o.transform.y),
|
|
7863
|
+
scale: round8(o.transform.scale || 1),
|
|
7864
|
+
rot: round8(o.transform.rotation || 0),
|
|
7344
7865
|
enter: enterKey(o),
|
|
7345
7866
|
exit: exitKey(o),
|
|
7346
7867
|
// Pose keyframes: a CLIP-LOCAL [x, y, scale, rot, opacity]
|
|
@@ -7365,10 +7886,10 @@ function studioLayerData(layers, duration, pins) {
|
|
|
7365
7886
|
...base,
|
|
7366
7887
|
kind: "image",
|
|
7367
7888
|
key: hl.key,
|
|
7368
|
-
w:
|
|
7889
|
+
w: round8(hl.width),
|
|
7369
7890
|
radius: 0,
|
|
7370
7891
|
shadow: "none",
|
|
7371
|
-
opacity:
|
|
7892
|
+
opacity: round8(o.opacity ?? 1),
|
|
7372
7893
|
html: hl.html
|
|
7373
7894
|
};
|
|
7374
7895
|
}
|
|
@@ -7385,8 +7906,8 @@ function studioLayerData(layers, duration, pins) {
|
|
|
7385
7906
|
...o.frame.browserBar?.theme ? { theme: o.frame.browserBar.theme } : {}
|
|
7386
7907
|
},
|
|
7387
7908
|
lean: [
|
|
7388
|
-
|
|
7389
|
-
|
|
7909
|
+
round8(o.frame.lean?.rx ?? 0),
|
|
7910
|
+
round8(o.frame.lean?.ry ?? 0)
|
|
7390
7911
|
],
|
|
7391
7912
|
shadow: o.frame.shadow ?? LAYER_CARD_SHADOW,
|
|
7392
7913
|
shadowContact: o.frame.shadowContact ?? 0,
|
|
@@ -7402,17 +7923,17 @@ function studioLayerData(layers, duration, pins) {
|
|
|
7402
7923
|
for (const ev of lm.cursor) {
|
|
7403
7924
|
if (ev.type === "down")
|
|
7404
7925
|
clicks.push({
|
|
7405
|
-
t:
|
|
7406
|
-
x:
|
|
7407
|
-
y:
|
|
7926
|
+
t: round8(ev.t / 1e3),
|
|
7927
|
+
x: round8(ev.x),
|
|
7928
|
+
y: round8(ev.y)
|
|
7408
7929
|
});
|
|
7409
7930
|
}
|
|
7410
7931
|
return {
|
|
7411
7932
|
cur: {
|
|
7412
7933
|
pts: sm.map((p) => ({
|
|
7413
|
-
t:
|
|
7414
|
-
x:
|
|
7415
|
-
y:
|
|
7934
|
+
t: round8(p.t),
|
|
7935
|
+
x: round8(p.x),
|
|
7936
|
+
y: round8(p.y)
|
|
7416
7937
|
})),
|
|
7417
7938
|
space: { w: lm.meta.width, h: lm.meta.height },
|
|
7418
7939
|
size: rec.cursor.size,
|
|
@@ -7427,7 +7948,7 @@ function studioLayerData(layers, duration, pins) {
|
|
|
7427
7948
|
...lm && lm.crop ? { crop: lm.crop } : {},
|
|
7428
7949
|
...lmCard ? { card: lmCard } : {},
|
|
7429
7950
|
...lmFacts,
|
|
7430
|
-
w:
|
|
7951
|
+
w: round8(o.width ?? OVERLAY_MEDIA_DEFAULT_WIDTH),
|
|
7431
7952
|
radius: o.radius ?? OVERLAY_MEDIA_DEFAULT_RADIUS,
|
|
7432
7953
|
opacity: o.opacity ?? 1,
|
|
7433
7954
|
loop: !!o.loop,
|
|
@@ -7437,7 +7958,7 @@ function studioLayerData(layers, duration, pins) {
|
|
|
7437
7958
|
...o.shadow ? { shadow: o.shadow } : {},
|
|
7438
7959
|
...o.border && o.border.width > 0 ? {
|
|
7439
7960
|
border: {
|
|
7440
|
-
width:
|
|
7961
|
+
width: round8(o.border.width),
|
|
7441
7962
|
color: o.border.color
|
|
7442
7963
|
}
|
|
7443
7964
|
} : {}
|
|
@@ -7457,11 +7978,11 @@ function studioLayerData(layers, duration, pins) {
|
|
|
7457
7978
|
// Style-v2 fields bake only when non-default (byte parity for
|
|
7458
7979
|
// older docs); ON_FRAME reads them unconditionally.
|
|
7459
7980
|
...st.fontStyle === "italic" ? { sty: "italic" } : {},
|
|
7460
|
-
...o.maxWidth ? { mw:
|
|
7461
|
-
...st.letterSpacing ? { ls:
|
|
7462
|
-
...st.lineHeight !== OVERLAY_LINE_HEIGHT ? { lh:
|
|
7981
|
+
...o.maxWidth ? { mw: round8(o.maxWidth) } : {},
|
|
7982
|
+
...st.letterSpacing ? { ls: round8(st.letterSpacing) } : {},
|
|
7983
|
+
...st.lineHeight !== OVERLAY_LINE_HEIGHT ? { lh: round8(st.lineHeight) } : {},
|
|
7463
7984
|
...st.align !== "center" ? { align: st.align } : {},
|
|
7464
|
-
...st.stroke ? { stroke: { c: st.stroke.color, w:
|
|
7985
|
+
...st.stroke ? { stroke: { c: st.stroke.color, w: round8(st.stroke.width) } } : {},
|
|
7465
7986
|
// Hosted face behind an override: ON_FRAME lazy-loads it so a
|
|
7466
7987
|
// live family/weight edit paints without a LOAD (SETUP only
|
|
7467
7988
|
// runs on cold load).
|
|
@@ -7474,10 +7995,10 @@ function studioLayerData(layers, duration, pins) {
|
|
|
7474
7995
|
...bx ? {
|
|
7475
7996
|
box: {
|
|
7476
7997
|
c: bx.color,
|
|
7477
|
-
o:
|
|
7478
|
-
px:
|
|
7479
|
-
py:
|
|
7480
|
-
r:
|
|
7998
|
+
o: round8(bx.opacity),
|
|
7999
|
+
px: round8(bx.padX),
|
|
8000
|
+
py: round8(bx.padY),
|
|
8001
|
+
r: round8(bx.radius)
|
|
7481
8002
|
}
|
|
7482
8003
|
} : {},
|
|
7483
8004
|
// Entrance animation: segmentation + timing normalized
|
|
@@ -7549,17 +8070,17 @@ function lowerToComposition(input) {
|
|
|
7549
8070
|
// Rated segments (speed spans pre-intersected): ON_FRAME's mapTime and the
|
|
7550
8071
|
// export's audio splice read the rate straight off each segment.
|
|
7551
8072
|
segments: rated.map((seg) => ({
|
|
7552
|
-
in:
|
|
7553
|
-
out:
|
|
7554
|
-
...seg.rate !== void 0 && seg.rate !== 1 ? { rate:
|
|
8073
|
+
in: round8(seg.in),
|
|
8074
|
+
out: round8(seg.out),
|
|
8075
|
+
...seg.rate !== void 0 && seg.rate !== 1 ? { rate: round8(seg.rate) } : {},
|
|
7555
8076
|
...seg.media ? { media: seg.media } : {}
|
|
7556
8077
|
})),
|
|
7557
8078
|
frame: doc.frame,
|
|
7558
8079
|
micGain: doc.micGain ?? 1,
|
|
7559
8080
|
cursor: smoothed.map((p) => ({
|
|
7560
|
-
t:
|
|
7561
|
-
x:
|
|
7562
|
-
y:
|
|
8081
|
+
t: round8(p.t),
|
|
8082
|
+
x: round8(p.x),
|
|
8083
|
+
y: round8(p.y)
|
|
7563
8084
|
})),
|
|
7564
8085
|
cursorStyle: doc.cursor,
|
|
7565
8086
|
cursorSpace: { w: doc.source.meta.width, h: doc.source.meta.height },
|
|
@@ -7582,9 +8103,9 @@ function lowerToComposition(input) {
|
|
|
7582
8103
|
crop: m.crop ?? null,
|
|
7583
8104
|
hasAudio: !!m.meta.hasAudio,
|
|
7584
8105
|
cursor: sm.map((p) => ({
|
|
7585
|
-
t:
|
|
7586
|
-
x:
|
|
7587
|
-
y:
|
|
8106
|
+
t: round8(p.t),
|
|
8107
|
+
x: round8(p.x),
|
|
8108
|
+
y: round8(p.y)
|
|
7588
8109
|
})),
|
|
7589
8110
|
cursorSpace: { w: m.meta.width, h: m.meta.height },
|
|
7590
8111
|
// The card this media wears (its own placement, bar, corner,
|
|
@@ -7766,8 +8287,8 @@ function pinCandidates(doc, clip) {
|
|
|
7766
8287
|
step: s.id ?? s.step,
|
|
7767
8288
|
do: s.do,
|
|
7768
8289
|
...s.selector ? { selector: s.selector } : {},
|
|
7769
|
-
output: { start:
|
|
7770
|
-
overlap:
|
|
8290
|
+
output: { start: round8(ext.start), end: round8(ext.end) },
|
|
8291
|
+
overlap: round8(overlap)
|
|
7771
8292
|
});
|
|
7772
8293
|
}
|
|
7773
8294
|
return out.sort((x, y) => y.overlap - x.overlap);
|
|
@@ -7798,8 +8319,8 @@ function pinsDataOf(doc, pins) {
|
|
|
7798
8319
|
if (!ref) continue;
|
|
7799
8320
|
out.push({
|
|
7800
8321
|
id: o.id,
|
|
7801
|
-
start:
|
|
7802
|
-
dur:
|
|
8322
|
+
start: round8(o.start),
|
|
8323
|
+
dur: round8(Math.max(OVERLAY_MIN_DURATION, o.duration)),
|
|
7803
8324
|
x: round43(ref.rect.x),
|
|
7804
8325
|
y: round43(ref.rect.y),
|
|
7805
8326
|
w: round43(ref.rect.w),
|
|
@@ -7812,8 +8333,8 @@ function pinsDataOf(doc, pins) {
|
|
|
7812
8333
|
// leader's end with the pointer through [t0, t1], source seconds.
|
|
7813
8334
|
...ref.carry ? {
|
|
7814
8335
|
carry: {
|
|
7815
|
-
t0:
|
|
7816
|
-
t1:
|
|
8336
|
+
t0: round8(ref.carry.t0),
|
|
8337
|
+
t1: round8(ref.carry.t1),
|
|
7817
8338
|
x0: round43(ref.carry.x0),
|
|
7818
8339
|
y0: round43(ref.carry.y0)
|
|
7819
8340
|
}
|
|
@@ -7860,7 +8381,7 @@ function clickFxData(doc, rated) {
|
|
|
7860
8381
|
}
|
|
7861
8382
|
};
|
|
7862
8383
|
}
|
|
7863
|
-
function
|
|
8384
|
+
function round8(v) {
|
|
7864
8385
|
return Math.round(v * 1e3) / 1e3;
|
|
7865
8386
|
}
|
|
7866
8387
|
|
|
@@ -7950,8 +8471,8 @@ function planAutoSpeed(track, opts) {
|
|
|
7950
8471
|
accepted.sort((a, b) => a.in - b.in);
|
|
7951
8472
|
return accepted.map((c, i) => ({
|
|
7952
8473
|
id: `s${i}`,
|
|
7953
|
-
in:
|
|
7954
|
-
out:
|
|
8474
|
+
in: round9(c.in),
|
|
8475
|
+
out: round9(c.out),
|
|
7955
8476
|
rate: clampSpeedRate(c.rate),
|
|
7956
8477
|
source: "auto"
|
|
7957
8478
|
}));
|
|
@@ -8004,7 +8525,7 @@ function collectRuns(evs, gap, minLen, emit2) {
|
|
|
8004
8525
|
}
|
|
8005
8526
|
flush();
|
|
8006
8527
|
}
|
|
8007
|
-
function
|
|
8528
|
+
function round9(v) {
|
|
8008
8529
|
return Math.round(v * 1e3) / 1e3;
|
|
8009
8530
|
}
|
|
8010
8531
|
|
|
@@ -8186,16 +8707,16 @@ function momentsFromDoc(doc, plan, opts = {}) {
|
|
|
8186
8707
|
return {
|
|
8187
8708
|
id: `m${String(i + 1).padStart(2, "0")}`,
|
|
8188
8709
|
kind: d.kind,
|
|
8189
|
-
source: { in:
|
|
8190
|
-
output: output ? { in:
|
|
8191
|
-
at: d.at === null ? null :
|
|
8192
|
-
outputAt: outAt ?
|
|
8193
|
-
focus: d.focus ? { cx:
|
|
8710
|
+
source: { in: round10(d.in), out: round10(d.out) },
|
|
8711
|
+
output: output ? { in: round10(output.start), out: round10(output.end) } : null,
|
|
8712
|
+
at: d.at === null ? null : round10(d.at),
|
|
8713
|
+
outputAt: outAt ? round10(outAt.start) : null,
|
|
8714
|
+
focus: d.focus ? { cx: round10(d.focus.cx), cy: round10(d.focus.cy) } : null,
|
|
8194
8715
|
rect: d.rect ? {
|
|
8195
|
-
x:
|
|
8196
|
-
y:
|
|
8197
|
-
w:
|
|
8198
|
-
h:
|
|
8716
|
+
x: round10(d.rect.x),
|
|
8717
|
+
y: round10(d.rect.y),
|
|
8718
|
+
w: round10(d.rect.w),
|
|
8719
|
+
h: round10(d.rect.h)
|
|
8199
8720
|
} : null,
|
|
8200
8721
|
...d.clicks !== void 0 ? { clicks: d.clicks } : {},
|
|
8201
8722
|
...d.surface ? { surface: true } : {},
|
|
@@ -8216,7 +8737,7 @@ function activityOf(bins, d) {
|
|
|
8216
8737
|
sum += bins[i];
|
|
8217
8738
|
n++;
|
|
8218
8739
|
}
|
|
8219
|
-
return n ?
|
|
8740
|
+
return n ? round10(sum / n) : null;
|
|
8220
8741
|
}
|
|
8221
8742
|
function saidOver(transcript, d) {
|
|
8222
8743
|
if (!transcript?.length) return null;
|
|
@@ -8225,7 +8746,7 @@ function saidOver(transcript, d) {
|
|
|
8225
8746
|
const text = transcript.filter((s) => s.start < hi && s.end > lo).map((s) => s.text.trim()).filter(Boolean).join(" ");
|
|
8226
8747
|
return text || null;
|
|
8227
8748
|
}
|
|
8228
|
-
function
|
|
8749
|
+
function round10(v) {
|
|
8229
8750
|
return Math.round(v * 1e3) / 1e3;
|
|
8230
8751
|
}
|
|
8231
8752
|
|
|
@@ -8732,8 +9253,8 @@ function buildDigest(input) {
|
|
|
8732
9253
|
return {
|
|
8733
9254
|
digestVersion: DIGEST_VERSION,
|
|
8734
9255
|
take: {
|
|
8735
|
-
sourceDuration:
|
|
8736
|
-
outputDuration:
|
|
9256
|
+
sourceDuration: round11(meta.durationMs / 1e3),
|
|
9257
|
+
outputDuration: round11(input.outputDuration),
|
|
8737
9258
|
width: meta.width,
|
|
8738
9259
|
height: meta.height,
|
|
8739
9260
|
captureWidth: meta.captureWidth ?? null,
|
|
@@ -8785,7 +9306,7 @@ function buildDigest(input) {
|
|
|
8785
9306
|
function outputDurationOf(doc) {
|
|
8786
9307
|
return docOutputDuration(doc);
|
|
8787
9308
|
}
|
|
8788
|
-
function
|
|
9309
|
+
function round11(v) {
|
|
8789
9310
|
return Math.round(v * 1e3) / 1e3;
|
|
8790
9311
|
}
|
|
8791
9312
|
|
|
@@ -8811,20 +9332,20 @@ function setSpeedInRange(spans, srcIn, srcOut, rate) {
|
|
|
8811
9332
|
continue;
|
|
8812
9333
|
}
|
|
8813
9334
|
if (srcIn - s.in >= MIN_REMAINDER)
|
|
8814
|
-
kept.push({ ...s, out:
|
|
9335
|
+
kept.push({ ...s, out: round12(srcIn), source: "manual" });
|
|
8815
9336
|
if (s.out - srcOut >= MIN_REMAINDER)
|
|
8816
9337
|
kept.push({
|
|
8817
9338
|
...s,
|
|
8818
9339
|
id: mintId(used),
|
|
8819
|
-
in:
|
|
9340
|
+
in: round12(srcOut),
|
|
8820
9341
|
source: "manual"
|
|
8821
9342
|
});
|
|
8822
9343
|
}
|
|
8823
9344
|
if (rate != null)
|
|
8824
9345
|
kept.push({
|
|
8825
9346
|
id: mintId(used),
|
|
8826
|
-
in:
|
|
8827
|
-
out:
|
|
9347
|
+
in: round12(srcIn),
|
|
9348
|
+
out: round12(srcOut),
|
|
8828
9349
|
rate: clampSpeedRate(rate),
|
|
8829
9350
|
source: "manual"
|
|
8830
9351
|
});
|
|
@@ -8840,9 +9361,9 @@ function removeSourceRange(segments, srcIn, srcOut) {
|
|
|
8840
9361
|
next.push(seg);
|
|
8841
9362
|
continue;
|
|
8842
9363
|
}
|
|
8843
|
-
if (srcIn - seg.in >= MIN_SEGMENT) next.push({ ...seg, out:
|
|
9364
|
+
if (srcIn - seg.in >= MIN_SEGMENT) next.push({ ...seg, out: round12(srcIn) });
|
|
8844
9365
|
if (seg.out - srcOut >= MIN_SEGMENT)
|
|
8845
|
-
next.push({ ...seg, in:
|
|
9366
|
+
next.push({ ...seg, in: round12(srcOut) });
|
|
8846
9367
|
}
|
|
8847
9368
|
return next.length ? next : [...segments];
|
|
8848
9369
|
}
|
|
@@ -8860,8 +9381,8 @@ function zoomSpanForRange(zoom, srcIn, srcOut) {
|
|
|
8860
9381
|
while (used.has(`u${n}`)) n++;
|
|
8861
9382
|
return {
|
|
8862
9383
|
id: `u${n}`,
|
|
8863
|
-
in:
|
|
8864
|
-
out:
|
|
9384
|
+
in: round12(start),
|
|
9385
|
+
out: round12(end),
|
|
8865
9386
|
level: DEFAULT_ZOOM_LEVEL,
|
|
8866
9387
|
cx: 0.5,
|
|
8867
9388
|
cy: 0.5,
|
|
@@ -8875,7 +9396,7 @@ function mintId(used) {
|
|
|
8875
9396
|
used.add(id);
|
|
8876
9397
|
return id;
|
|
8877
9398
|
}
|
|
8878
|
-
function
|
|
9399
|
+
function round12(v) {
|
|
8879
9400
|
return Math.round(v * 1e3) / 1e3;
|
|
8880
9401
|
}
|
|
8881
9402
|
|
|
@@ -9586,8 +10107,8 @@ var videoLane = {
|
|
|
9586
10107
|
return segments.map((s, i) => ({
|
|
9587
10108
|
id: `seg-${i}`,
|
|
9588
10109
|
kind: "clip",
|
|
9589
|
-
t:
|
|
9590
|
-
duration:
|
|
10110
|
+
t: round13(starts[i]),
|
|
10111
|
+
duration: round13(
|
|
9591
10112
|
outputLen(s, speeds, freezes, i === 0 || !contiguous(segments, i))
|
|
9592
10113
|
)
|
|
9593
10114
|
}));
|
|
@@ -9670,7 +10191,7 @@ var videoLane = {
|
|
|
9670
10191
|
if (local >= m.t - 1e-9 && local <= m.t + m.duration + 1e-9)
|
|
9671
10192
|
return null;
|
|
9672
10193
|
}
|
|
9673
|
-
const sourceT =
|
|
10194
|
+
const sourceT = round13(
|
|
9674
10195
|
mapTime3(segmentPieces(s, speeds, freezes, leading), local)
|
|
9675
10196
|
);
|
|
9676
10197
|
if (sourceT - s.in < 0.05 || s.out - sourceT < 0.05) return null;
|
|
@@ -9711,7 +10232,7 @@ var videoLane = {
|
|
|
9711
10232
|
n === 1 || !contiguous(segments, n - 1)
|
|
9712
10233
|
)
|
|
9713
10234
|
] : []
|
|
9714
|
-
].map(
|
|
10235
|
+
].map(round13);
|
|
9715
10236
|
}
|
|
9716
10237
|
};
|
|
9717
10238
|
function nextFreezeId(doc) {
|
|
@@ -9738,8 +10259,8 @@ var freezeLane = {
|
|
|
9738
10259
|
{
|
|
9739
10260
|
id: f.id,
|
|
9740
10261
|
kind: "clip",
|
|
9741
|
-
t:
|
|
9742
|
-
duration:
|
|
10262
|
+
t: round13(m.t),
|
|
10263
|
+
duration: round13(m.duration),
|
|
9743
10264
|
label: `${trimZeros(f.seconds)}s`
|
|
9744
10265
|
}
|
|
9745
10266
|
] : [];
|
|
@@ -9761,7 +10282,7 @@ var freezeLane = {
|
|
|
9761
10282
|
if (g.t >= m.t - 1e-9 && g.t <= m.t + m.duration + 1e-9) return null;
|
|
9762
10283
|
}
|
|
9763
10284
|
if (g.t > totalDuration7(rated) + 1e-9) return null;
|
|
9764
|
-
const srcT =
|
|
10285
|
+
const srcT = round13(mapTime3(rated, Math.max(0, g.t)));
|
|
9765
10286
|
if (!kept(srcT) || !clear(srcT)) return null;
|
|
9766
10287
|
const id = nextFreezeId(doc);
|
|
9767
10288
|
const mediaHere = mediaAtOutput(rated, Math.max(0, g.t));
|
|
@@ -9803,7 +10324,7 @@ var freezeLane = {
|
|
|
9803
10324
|
const marks = freezeOutputExtents(splitBySpeed4(segs, speeds), freezes);
|
|
9804
10325
|
const mine = marks.get(f.id);
|
|
9805
10326
|
const endOut = mine ? mine.t + mine.duration : null;
|
|
9806
|
-
const srcT =
|
|
10327
|
+
const srcT = round13(
|
|
9807
10328
|
Math.min(sourceDuration, mapTime3(base, Math.max(0, g.t)))
|
|
9808
10329
|
);
|
|
9809
10330
|
if (!kept(srcT) || !clear(srcT, f.id)) return null;
|
|
@@ -9863,8 +10384,8 @@ var zoomLane = {
|
|
|
9863
10384
|
{
|
|
9864
10385
|
id: z.id,
|
|
9865
10386
|
kind: "clip",
|
|
9866
|
-
t:
|
|
9867
|
-
duration:
|
|
10387
|
+
t: round13(ext.start),
|
|
10388
|
+
duration: round13(ext.end - ext.start),
|
|
9868
10389
|
label: `${z.level.toFixed(2)}\xD7`
|
|
9869
10390
|
}
|
|
9870
10391
|
];
|
|
@@ -9891,8 +10412,8 @@ var zoomLane = {
|
|
|
9891
10412
|
...d.zoom,
|
|
9892
10413
|
{
|
|
9893
10414
|
id,
|
|
9894
|
-
in:
|
|
9895
|
-
out:
|
|
10415
|
+
in: round13(srcT),
|
|
10416
|
+
out: round13(srcT + len),
|
|
9896
10417
|
level: DEFAULT_ZOOM_LEVEL,
|
|
9897
10418
|
cx,
|
|
9898
10419
|
cy,
|
|
@@ -9924,8 +10445,8 @@ var zoomLane = {
|
|
|
9924
10445
|
return (d) => {
|
|
9925
10446
|
const z = d.zoom.find((x) => x.id === g.id);
|
|
9926
10447
|
if (!z) return;
|
|
9927
|
-
z.in =
|
|
9928
|
-
z.out =
|
|
10448
|
+
z.in = round13(newIn);
|
|
10449
|
+
z.out = round13(newIn + len);
|
|
9929
10450
|
z.source = "manual";
|
|
9930
10451
|
d.zoom.sort((a, b) => a.in - b.in);
|
|
9931
10452
|
};
|
|
@@ -9954,8 +10475,8 @@ var zoomLane = {
|
|
|
9954
10475
|
return (d) => {
|
|
9955
10476
|
const z = d.zoom.find((x) => x.id === g.id);
|
|
9956
10477
|
if (!z) return;
|
|
9957
|
-
z.in =
|
|
9958
|
-
z.out =
|
|
10478
|
+
z.in = round13(next.in);
|
|
10479
|
+
z.out = round13(next.out);
|
|
9959
10480
|
z.source = "manual";
|
|
9960
10481
|
};
|
|
9961
10482
|
}
|
|
@@ -9977,8 +10498,8 @@ function cursorFocusAt(doc, srcT) {
|
|
|
9977
10498
|
if (Math.abs(e.t - ms) < Math.abs(best.t - ms)) best = e;
|
|
9978
10499
|
const clamp015 = (v) => Math.max(0, Math.min(1, v));
|
|
9979
10500
|
return {
|
|
9980
|
-
cx:
|
|
9981
|
-
cy:
|
|
10501
|
+
cx: round13(clamp015(best.x / width)),
|
|
10502
|
+
cy: round13(clamp015(best.y / height))
|
|
9982
10503
|
};
|
|
9983
10504
|
}
|
|
9984
10505
|
var tiltLane = {
|
|
@@ -9992,8 +10513,8 @@ var tiltLane = {
|
|
|
9992
10513
|
{
|
|
9993
10514
|
id: z.id,
|
|
9994
10515
|
kind: "clip",
|
|
9995
|
-
t:
|
|
9996
|
-
duration:
|
|
10516
|
+
t: round13(ext.start),
|
|
10517
|
+
duration: round13(ext.end - ext.start),
|
|
9997
10518
|
label: `${formatDeg(z.rx)}\xB0/${formatDeg(z.ry)}\xB0`
|
|
9998
10519
|
}
|
|
9999
10520
|
];
|
|
@@ -10019,8 +10540,8 @@ var tiltLane = {
|
|
|
10019
10540
|
...d.tilt ?? [],
|
|
10020
10541
|
{
|
|
10021
10542
|
id,
|
|
10022
|
-
in:
|
|
10023
|
-
out:
|
|
10543
|
+
in: round13(srcT),
|
|
10544
|
+
out: round13(srcT + len),
|
|
10024
10545
|
rx: DEFAULT_TILT_POSE.rx,
|
|
10025
10546
|
ry: DEFAULT_TILT_POSE.ry,
|
|
10026
10547
|
source: "manual",
|
|
@@ -10051,8 +10572,8 @@ var tiltLane = {
|
|
|
10051
10572
|
return (d) => {
|
|
10052
10573
|
const z = (d.tilt ?? []).find((x) => x.id === g.id);
|
|
10053
10574
|
if (!z || !d.tilt) return;
|
|
10054
|
-
z.in =
|
|
10055
|
-
z.out =
|
|
10575
|
+
z.in = round13(newIn);
|
|
10576
|
+
z.out = round13(newIn + len);
|
|
10056
10577
|
z.source = "manual";
|
|
10057
10578
|
d.tilt.sort((a, b) => a.in - b.in);
|
|
10058
10579
|
};
|
|
@@ -10081,8 +10602,8 @@ var tiltLane = {
|
|
|
10081
10602
|
return (d) => {
|
|
10082
10603
|
const z = (d.tilt ?? []).find((x) => x.id === g.id);
|
|
10083
10604
|
if (!z) return;
|
|
10084
|
-
z.in =
|
|
10085
|
-
z.out =
|
|
10605
|
+
z.in = round13(next.in);
|
|
10606
|
+
z.out = round13(next.out);
|
|
10086
10607
|
z.source = "manual";
|
|
10087
10608
|
};
|
|
10088
10609
|
}
|
|
@@ -10109,8 +10630,8 @@ var camMoveLane = {
|
|
|
10109
10630
|
{
|
|
10110
10631
|
id: z.id,
|
|
10111
10632
|
kind: "clip",
|
|
10112
|
-
t:
|
|
10113
|
-
duration:
|
|
10633
|
+
t: round13(ext.start),
|
|
10634
|
+
duration: round13(ext.end - ext.start),
|
|
10114
10635
|
label: z.size != null ? `${Math.round(z.size * 100)}%` : "Move"
|
|
10115
10636
|
}
|
|
10116
10637
|
];
|
|
@@ -10134,8 +10655,8 @@ var camMoveLane = {
|
|
|
10134
10655
|
...d.camMotion ?? [],
|
|
10135
10656
|
{
|
|
10136
10657
|
id,
|
|
10137
|
-
in:
|
|
10138
|
-
out:
|
|
10658
|
+
in: round13(srcT),
|
|
10659
|
+
out: round13(srcT + len),
|
|
10139
10660
|
...DEFAULT_CAM_POSE,
|
|
10140
10661
|
source: "manual"
|
|
10141
10662
|
}
|
|
@@ -10164,8 +10685,8 @@ var camMoveLane = {
|
|
|
10164
10685
|
return (d) => {
|
|
10165
10686
|
const z = (d.camMotion ?? []).find((x) => x.id === g.id);
|
|
10166
10687
|
if (!z || !d.camMotion) return;
|
|
10167
|
-
z.in =
|
|
10168
|
-
z.out =
|
|
10688
|
+
z.in = round13(newIn);
|
|
10689
|
+
z.out = round13(newIn + len);
|
|
10169
10690
|
z.source = "manual";
|
|
10170
10691
|
d.camMotion.sort((a, b) => a.in - b.in);
|
|
10171
10692
|
};
|
|
@@ -10194,8 +10715,8 @@ var camMoveLane = {
|
|
|
10194
10715
|
return (d) => {
|
|
10195
10716
|
const z = (d.camMotion ?? []).find((x) => x.id === g.id);
|
|
10196
10717
|
if (!z) return;
|
|
10197
|
-
z.in =
|
|
10198
|
-
z.out =
|
|
10718
|
+
z.in = round13(next.in);
|
|
10719
|
+
z.out = round13(next.out);
|
|
10199
10720
|
z.source = "manual";
|
|
10200
10721
|
};
|
|
10201
10722
|
}
|
|
@@ -10228,10 +10749,10 @@ var camLane = {
|
|
|
10228
10749
|
items.push({
|
|
10229
10750
|
id: `cam-${i}`,
|
|
10230
10751
|
kind: "clip",
|
|
10231
|
-
t:
|
|
10752
|
+
t: round13(
|
|
10232
10753
|
starts[i] + outputLen({ in: s.in, out: a }, speeds, freezes, leading)
|
|
10233
10754
|
),
|
|
10234
|
-
duration:
|
|
10755
|
+
duration: round13(
|
|
10235
10756
|
outputLen({ in: a, out: b }, speeds, freezes, a === s.in && leading)
|
|
10236
10757
|
)
|
|
10237
10758
|
});
|
|
@@ -10260,7 +10781,7 @@ var camLane = {
|
|
|
10260
10781
|
Math.max(first.in, last.out - span)
|
|
10261
10782
|
);
|
|
10262
10783
|
return (d) => {
|
|
10263
|
-
d.cam.window = { in:
|
|
10784
|
+
d.cam.window = { in: round13(newIn), out: round13(newIn + span) };
|
|
10264
10785
|
};
|
|
10265
10786
|
}
|
|
10266
10787
|
const overlapping = eff.map((s, i) => ({
|
|
@@ -10273,8 +10794,8 @@ var camLane = {
|
|
|
10273
10794
|
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) };
|
|
10274
10795
|
return (d) => {
|
|
10275
10796
|
d.cam.window = {
|
|
10276
|
-
in:
|
|
10277
|
-
out:
|
|
10797
|
+
in: round13(Math.max(0, next.in)),
|
|
10798
|
+
out: round13(Math.min(sourceDuration, next.out))
|
|
10278
10799
|
};
|
|
10279
10800
|
};
|
|
10280
10801
|
},
|
|
@@ -10294,8 +10815,8 @@ var micLane = {
|
|
|
10294
10815
|
return segments.map((s, i) => ({
|
|
10295
10816
|
id: `mic-${i}`,
|
|
10296
10817
|
kind: "clip",
|
|
10297
|
-
t:
|
|
10298
|
-
duration:
|
|
10818
|
+
t: round13(starts[i]),
|
|
10819
|
+
duration: round13(
|
|
10299
10820
|
outputLen(s, speeds, freezes, i === 0 || !contiguous(segments, i))
|
|
10300
10821
|
)
|
|
10301
10822
|
}));
|
|
@@ -10351,8 +10872,8 @@ var speedLane = {
|
|
|
10351
10872
|
{
|
|
10352
10873
|
id: sp.id,
|
|
10353
10874
|
kind: "clip",
|
|
10354
|
-
t:
|
|
10355
|
-
duration:
|
|
10875
|
+
t: round13(start),
|
|
10876
|
+
duration: round13(end - start),
|
|
10356
10877
|
label: `${sp.rate}\xD7`
|
|
10357
10878
|
}
|
|
10358
10879
|
];
|
|
@@ -10376,8 +10897,8 @@ var speedLane = {
|
|
|
10376
10897
|
...d.speed ?? [],
|
|
10377
10898
|
{
|
|
10378
10899
|
id,
|
|
10379
|
-
in:
|
|
10380
|
-
out:
|
|
10900
|
+
in: round13(srcT),
|
|
10901
|
+
out: round13(srcT + len),
|
|
10381
10902
|
rate: DEFAULT_SPEED_RATE,
|
|
10382
10903
|
source: "manual",
|
|
10383
10904
|
...mediaHere ? { media: mediaHere } : {}
|
|
@@ -10408,8 +10929,8 @@ var speedLane = {
|
|
|
10408
10929
|
return (d) => {
|
|
10409
10930
|
const s = d.speed?.find((x) => x.id === g.id);
|
|
10410
10931
|
if (!s) return;
|
|
10411
|
-
s.in =
|
|
10412
|
-
s.out =
|
|
10932
|
+
s.in = round13(newIn);
|
|
10933
|
+
s.out = round13(newIn + len);
|
|
10413
10934
|
s.source = "manual";
|
|
10414
10935
|
d.speed.sort((a, b) => a.in - b.in);
|
|
10415
10936
|
};
|
|
@@ -10438,8 +10959,8 @@ var speedLane = {
|
|
|
10438
10959
|
return (d) => {
|
|
10439
10960
|
const s = d.speed?.find((x) => x.id === g.id);
|
|
10440
10961
|
if (!s) return;
|
|
10441
|
-
s.in =
|
|
10442
|
-
s.out =
|
|
10962
|
+
s.in = round13(next.in);
|
|
10963
|
+
s.out = round13(next.out);
|
|
10443
10964
|
s.source = "manual";
|
|
10444
10965
|
};
|
|
10445
10966
|
}
|
|
@@ -10458,8 +10979,8 @@ var audioLane = {
|
|
|
10458
10979
|
return doc.audio.map((c) => ({
|
|
10459
10980
|
id: c.id,
|
|
10460
10981
|
kind: "clip",
|
|
10461
|
-
t:
|
|
10462
|
-
duration:
|
|
10982
|
+
t: round13(c.start),
|
|
10983
|
+
duration: round13(clipLength(c)),
|
|
10463
10984
|
label: c.name
|
|
10464
10985
|
}));
|
|
10465
10986
|
},
|
|
@@ -10470,7 +10991,7 @@ var audioLane = {
|
|
|
10470
10991
|
const start = Math.max(0, g.t);
|
|
10471
10992
|
return (d) => {
|
|
10472
10993
|
const c = d.audio.find((x) => x.id === g.id);
|
|
10473
|
-
if (c) c.start =
|
|
10994
|
+
if (c) c.start = round13(start);
|
|
10474
10995
|
};
|
|
10475
10996
|
}
|
|
10476
10997
|
if (g.type === "resize") {
|
|
@@ -10483,8 +11004,8 @@ var audioLane = {
|
|
|
10483
11004
|
return (d) => {
|
|
10484
11005
|
const c = d.audio.find((x) => x.id === g.id);
|
|
10485
11006
|
if (!c) return;
|
|
10486
|
-
c.start =
|
|
10487
|
-
c.loopLen =
|
|
11007
|
+
c.start = round13(newStart2);
|
|
11008
|
+
c.loopLen = round13(end - newStart2);
|
|
10488
11009
|
};
|
|
10489
11010
|
}
|
|
10490
11011
|
const delta = g.t - clip.start;
|
|
@@ -10493,22 +11014,22 @@ var audioLane = {
|
|
|
10493
11014
|
return (d) => {
|
|
10494
11015
|
const c = d.audio.find((x) => x.id === g.id);
|
|
10495
11016
|
if (!c) return;
|
|
10496
|
-
c.in =
|
|
10497
|
-
c.start =
|
|
11017
|
+
c.in = round13(newIn);
|
|
11018
|
+
c.start = round13(newStart);
|
|
10498
11019
|
};
|
|
10499
11020
|
}
|
|
10500
11021
|
if (clip.loop) {
|
|
10501
11022
|
const newLen = Math.max(0.1, g.t - clip.start);
|
|
10502
11023
|
return (d) => {
|
|
10503
11024
|
const c = d.audio.find((x) => x.id === g.id);
|
|
10504
|
-
if (c) c.loopLen =
|
|
11025
|
+
if (c) c.loopLen = round13(newLen);
|
|
10505
11026
|
};
|
|
10506
11027
|
}
|
|
10507
11028
|
const span = Math.max(0.05, g.t - clip.start);
|
|
10508
11029
|
const newOut = Math.min(clip.in + span, clip.duration);
|
|
10509
11030
|
return (d) => {
|
|
10510
11031
|
const c = d.audio.find((x) => x.id === g.id);
|
|
10511
|
-
if (c) c.out =
|
|
11032
|
+
if (c) c.out = round13(Math.max(c.in + 0.05, newOut));
|
|
10512
11033
|
};
|
|
10513
11034
|
}
|
|
10514
11035
|
if (g.type === "remove") {
|
|
@@ -10521,8 +11042,8 @@ var audioLane = {
|
|
|
10521
11042
|
},
|
|
10522
11043
|
magnets(doc) {
|
|
10523
11044
|
return doc.audio.flatMap((c) => [
|
|
10524
|
-
|
|
10525
|
-
|
|
11045
|
+
round13(c.start),
|
|
11046
|
+
round13(c.start + clipLength(c))
|
|
10526
11047
|
]);
|
|
10527
11048
|
}
|
|
10528
11049
|
};
|
|
@@ -10540,7 +11061,7 @@ function poseItems(clipId, start, duration, motion) {
|
|
|
10540
11061
|
return (motion ?? []).map((p, i) => ({
|
|
10541
11062
|
id: `${clipId}::k${i}`,
|
|
10542
11063
|
kind: "keyframe",
|
|
10543
|
-
t:
|
|
11064
|
+
t: round13(start + Math.min(Math.max(0, p.at), duration))
|
|
10544
11065
|
}));
|
|
10545
11066
|
}
|
|
10546
11067
|
var overlaysLane = {
|
|
@@ -10551,8 +11072,8 @@ var overlaysLane = {
|
|
|
10551
11072
|
{
|
|
10552
11073
|
id: o.id,
|
|
10553
11074
|
kind: "clip",
|
|
10554
|
-
t:
|
|
10555
|
-
duration:
|
|
11075
|
+
t: round13(o.start),
|
|
11076
|
+
duration: round13(o.duration),
|
|
10556
11077
|
label: o.kind === "text" ? o.text.split("\n")[0].slice(0, 24) || "Text" : o.kind === "html" ? htmlLayerLabel(o) : o.kind === "image" ? "Image" : "Video"
|
|
10557
11078
|
},
|
|
10558
11079
|
// Pose diamonds render after the clips, so they sit on top.
|
|
@@ -10572,7 +11093,7 @@ var overlaysLane = {
|
|
|
10572
11093
|
return (d) => {
|
|
10573
11094
|
const o = (d.overlays ?? []).find((x) => x.id === kf.clipId);
|
|
10574
11095
|
const p = o?.motion?.[kf.index];
|
|
10575
|
-
if (p) p.at =
|
|
11096
|
+
if (p) p.at = round13(at);
|
|
10576
11097
|
};
|
|
10577
11098
|
}
|
|
10578
11099
|
if (g.type === "remove") {
|
|
@@ -10601,8 +11122,8 @@ var overlaysLane = {
|
|
|
10601
11122
|
{
|
|
10602
11123
|
id,
|
|
10603
11124
|
kind: "text",
|
|
10604
|
-
start:
|
|
10605
|
-
duration:
|
|
11125
|
+
start: round13(start),
|
|
11126
|
+
duration: round13(len),
|
|
10606
11127
|
text: "Title",
|
|
10607
11128
|
preset: "title",
|
|
10608
11129
|
// Frame fractions: centered, lower-third — at any aspect ratio.
|
|
@@ -10617,7 +11138,7 @@ var overlaysLane = {
|
|
|
10617
11138
|
const start = Math.max(0, g.t);
|
|
10618
11139
|
return (d) => {
|
|
10619
11140
|
const o = (d.overlays ?? []).find((x) => x.id === g.id);
|
|
10620
|
-
if (o) o.start =
|
|
11141
|
+
if (o) o.start = round13(start);
|
|
10621
11142
|
};
|
|
10622
11143
|
}
|
|
10623
11144
|
if (g.type === "resize") {
|
|
@@ -10627,14 +11148,14 @@ var overlaysLane = {
|
|
|
10627
11148
|
return (d) => {
|
|
10628
11149
|
const o = (d.overlays ?? []).find((x) => x.id === g.id);
|
|
10629
11150
|
if (!o) return;
|
|
10630
|
-
o.start =
|
|
10631
|
-
o.duration =
|
|
11151
|
+
o.start = round13(newStart);
|
|
11152
|
+
o.duration = round13(end - newStart);
|
|
10632
11153
|
};
|
|
10633
11154
|
}
|
|
10634
11155
|
const newLen = Math.max(OVERLAY_MIN_DURATION, g.t - clip.start);
|
|
10635
11156
|
return (d) => {
|
|
10636
11157
|
const o = (d.overlays ?? []).find((x) => x.id === g.id);
|
|
10637
|
-
if (o) o.duration =
|
|
11158
|
+
if (o) o.duration = round13(newLen);
|
|
10638
11159
|
};
|
|
10639
11160
|
}
|
|
10640
11161
|
return (d) => {
|
|
@@ -10643,8 +11164,8 @@ var overlaysLane = {
|
|
|
10643
11164
|
},
|
|
10644
11165
|
magnets(doc) {
|
|
10645
11166
|
return (doc.overlays ?? []).flatMap((o) => [
|
|
10646
|
-
|
|
10647
|
-
|
|
11167
|
+
round13(o.start),
|
|
11168
|
+
round13(o.start + o.duration)
|
|
10648
11169
|
]);
|
|
10649
11170
|
}
|
|
10650
11171
|
};
|
|
@@ -10657,8 +11178,8 @@ var objectsLane = {
|
|
|
10657
11178
|
{
|
|
10658
11179
|
id: o.id,
|
|
10659
11180
|
kind: "clip",
|
|
10660
|
-
t:
|
|
10661
|
-
duration:
|
|
11181
|
+
t: round13(o.span?.start ?? 0),
|
|
11182
|
+
duration: round13(o.span?.duration ?? outDur),
|
|
10662
11183
|
label: o.asset.kind === "primitive" ? o.asset.shape : o.asset.kind === "text3d" ? o.asset.text : "model"
|
|
10663
11184
|
},
|
|
10664
11185
|
// Pose diamonds: clip-local `at` from the span start (0 span-less).
|
|
@@ -10686,7 +11207,7 @@ var objectsLane = {
|
|
|
10686
11207
|
return (d) => {
|
|
10687
11208
|
const o = (d.objects ?? []).find((x) => x.id === kf.clipId);
|
|
10688
11209
|
const p = o?.motion?.[kf.index];
|
|
10689
|
-
if (p) p.at =
|
|
11210
|
+
if (p) p.at = round13(at);
|
|
10690
11211
|
};
|
|
10691
11212
|
}
|
|
10692
11213
|
if (g.type === "remove") {
|
|
@@ -10707,7 +11228,7 @@ var objectsLane = {
|
|
|
10707
11228
|
const start = Math.max(0, g.t);
|
|
10708
11229
|
return (d) => {
|
|
10709
11230
|
const o = (d.objects ?? []).find((x) => x.id === g.id);
|
|
10710
|
-
if (o?.span) o.span.start =
|
|
11231
|
+
if (o?.span) o.span.start = round13(start);
|
|
10711
11232
|
};
|
|
10712
11233
|
}
|
|
10713
11234
|
if (g.type === "resize") {
|
|
@@ -10718,14 +11239,14 @@ var objectsLane = {
|
|
|
10718
11239
|
return (d) => {
|
|
10719
11240
|
const o = (d.objects ?? []).find((x) => x.id === g.id);
|
|
10720
11241
|
if (!o?.span) return;
|
|
10721
|
-
o.span.start =
|
|
10722
|
-
o.span.duration =
|
|
11242
|
+
o.span.start = round13(newStart);
|
|
11243
|
+
o.span.duration = round13(end - newStart);
|
|
10723
11244
|
};
|
|
10724
11245
|
}
|
|
10725
11246
|
const newLen = Math.max(OVERLAY_MIN_DURATION, g.t - clip.span.start);
|
|
10726
11247
|
return (d) => {
|
|
10727
11248
|
const o = (d.objects ?? []).find((x) => x.id === g.id);
|
|
10728
|
-
if (o?.span) o.span.duration =
|
|
11249
|
+
if (o?.span) o.span.duration = round13(newLen);
|
|
10729
11250
|
};
|
|
10730
11251
|
}
|
|
10731
11252
|
return (d) => {
|
|
@@ -10734,7 +11255,7 @@ var objectsLane = {
|
|
|
10734
11255
|
},
|
|
10735
11256
|
magnets(doc) {
|
|
10736
11257
|
return (doc.objects ?? []).flatMap(
|
|
10737
|
-
(o) => o.span ? [
|
|
11258
|
+
(o) => o.span ? [round13(o.span.start), round13(o.span.start + o.span.duration)] : []
|
|
10738
11259
|
);
|
|
10739
11260
|
}
|
|
10740
11261
|
};
|
|
@@ -10761,7 +11282,7 @@ function nextSpeedId(doc) {
|
|
|
10761
11282
|
function segIndex(id) {
|
|
10762
11283
|
return Number(id.replace("seg-", ""));
|
|
10763
11284
|
}
|
|
10764
|
-
function
|
|
11285
|
+
function round13(v) {
|
|
10765
11286
|
return Math.round(v * 1e3) / 1e3;
|
|
10766
11287
|
}
|
|
10767
11288
|
|
|
@@ -10929,6 +11450,7 @@ export {
|
|
|
10929
11450
|
CLICK_FX_INTENSITY,
|
|
10930
11451
|
CLICK_FX_PRE,
|
|
10931
11452
|
CLICK_HIGHLIGHT_FADE,
|
|
11453
|
+
CLICK_HOLD_MS,
|
|
10932
11454
|
CLICK_PAIR_MAX,
|
|
10933
11455
|
CLICK_PULSE_DUR,
|
|
10934
11456
|
CLICK_RECT_MAX_FRAC,
|
|
@@ -11111,6 +11633,10 @@ export {
|
|
|
11111
11633
|
cropBox,
|
|
11112
11634
|
cssFamilies,
|
|
11113
11635
|
cssWeights,
|
|
11636
|
+
cursorFromCsv,
|
|
11637
|
+
cursorFromPlaywrightTrace,
|
|
11638
|
+
cursorFromRecords,
|
|
11639
|
+
cursorFromTrace,
|
|
11114
11640
|
cursorIdleFade,
|
|
11115
11641
|
deltaE,
|
|
11116
11642
|
deriveViewportCrop,
|
|
@@ -11239,6 +11765,7 @@ export {
|
|
|
11239
11765
|
planForDigest,
|
|
11240
11766
|
planeSizeAtDepth,
|
|
11241
11767
|
platformBarKind,
|
|
11768
|
+
playwrightTraceEntry,
|
|
11242
11769
|
programDuration,
|
|
11243
11770
|
projectFromArtifact,
|
|
11244
11771
|
pumpFreeChainGap,
|
|
@@ -11274,6 +11801,7 @@ export {
|
|
|
11274
11801
|
studioLayerData,
|
|
11275
11802
|
tiltLane,
|
|
11276
11803
|
tiltTrackFromDoc,
|
|
11804
|
+
traceKindOf,
|
|
11277
11805
|
transitionMult,
|
|
11278
11806
|
transitionSeconds,
|
|
11279
11807
|
unionFitLevel,
|
|
@@ -11286,6 +11814,8 @@ export {
|
|
|
11286
11814
|
withHolds,
|
|
11287
11815
|
withoutRejected,
|
|
11288
11816
|
wrapProgramLength,
|
|
11817
|
+
zipEntries,
|
|
11818
|
+
zipEntryBytes,
|
|
11289
11819
|
zoomCoversRect,
|
|
11290
11820
|
zoomLane,
|
|
11291
11821
|
zoomSpanForRange,
|