@vosjs/studio-core 0.43.0 → 0.44.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/README.md +3 -1
- package/dist/index.d.ts +211 -4
- package/dist/index.js +240 -27
- package/dist/index.js.map +1 -1
- package/package.json +1 -1
package/dist/index.js
CHANGED
|
@@ -3531,16 +3531,17 @@ function planAutoZoom(track, options) {
|
|
|
3531
3531
|
const withoutDrags = (list) => list.map((cl) => cl.filter((c) => !dragPress.has(round2(c.t)))).filter((cl) => cl.length > 0);
|
|
3532
3532
|
const clusters = withoutDrags(grouped.clusters);
|
|
3533
3533
|
const surfaces = withoutDrags(grouped.surfaces);
|
|
3534
|
-
const
|
|
3535
|
-
|
|
3536
|
-
|
|
3537
|
-
|
|
3538
|
-
|
|
3539
|
-
|
|
3540
|
-
cy: round2(d.ny),
|
|
3541
|
-
focusMode: "auto",
|
|
3542
|
-
source: "auto"
|
|
3534
|
+
const dragWork = drags.map((d) => ({
|
|
3535
|
+
in: Math.max(0, d.t0 - lead),
|
|
3536
|
+
out: d.t1 + hold,
|
|
3537
|
+
cx: d.nx,
|
|
3538
|
+
cy: d.ny,
|
|
3539
|
+
level: Math.min(Math.max(dragLevel, 1.1), maxLevel)
|
|
3543
3540
|
}));
|
|
3541
|
+
for (let i = 0; i + 1 < dragWork.length; i++) {
|
|
3542
|
+
if (dragWork[i].out > dragWork[i + 1].in)
|
|
3543
|
+
dragWork[i].out = dragWork[i + 1].in;
|
|
3544
|
+
}
|
|
3544
3545
|
const eligible = clusters.filter((c) => c.length >= minClusterClicks);
|
|
3545
3546
|
const clickSpans = [];
|
|
3546
3547
|
const dragReserved = surfaces.map((cluster, i) => {
|
|
@@ -3612,6 +3613,19 @@ function planAutoZoom(track, options) {
|
|
|
3612
3613
|
if (typing[i].out > typing[i + 1].in) typing[i].out = typing[i + 1].in;
|
|
3613
3614
|
}
|
|
3614
3615
|
typing = typing.filter((t) => t.out - t.in >= 0.3);
|
|
3616
|
+
for (const w of [...clickSpans, ...typing]) cedeTo(w, dragWork);
|
|
3617
|
+
typing = typing.filter((t) => !t.dead && t.out - t.in >= 0.3);
|
|
3618
|
+
for (const z of clickSpans) if (z.out - z.in < 0.3) z.dead = true;
|
|
3619
|
+
const dragSpans = dragWork.filter((d) => d.out - d.in >= 0.3).map((d, i) => ({
|
|
3620
|
+
id: `g${i}`,
|
|
3621
|
+
in: round2(d.in),
|
|
3622
|
+
out: round2(d.out),
|
|
3623
|
+
level: clampZoomLevel(d.level),
|
|
3624
|
+
cx: round2(d.cx),
|
|
3625
|
+
cy: round2(d.cy),
|
|
3626
|
+
focusMode: "auto",
|
|
3627
|
+
source: "auto"
|
|
3628
|
+
}));
|
|
3615
3629
|
const zSpans = clickSpans.filter((s) => !s.dead).sort((a, b) => a.in - b.in).map((s, i) => ({
|
|
3616
3630
|
id: `z${i}`,
|
|
3617
3631
|
in: round2(s.in),
|
|
@@ -3645,6 +3659,17 @@ function planAutoZoom(track, options) {
|
|
|
3645
3659
|
]).filter((d) => firstStep === void 0 || d.out > firstStep);
|
|
3646
3660
|
return within([...spans, ...dwells]).sort((a, b) => a.in - b.in);
|
|
3647
3661
|
}
|
|
3662
|
+
function cedeTo(w, keep) {
|
|
3663
|
+
for (const k of keep) {
|
|
3664
|
+
if (w.dead || w.in >= k.out || w.out <= k.in) continue;
|
|
3665
|
+
if (k.in <= w.in && k.out >= w.out) w.dead = true;
|
|
3666
|
+
else if (k.in > w.in && k.out < w.out) {
|
|
3667
|
+
if (k.in - w.in >= w.out - k.out) w.out = k.in;
|
|
3668
|
+
else w.in = k.out;
|
|
3669
|
+
} else if (k.in > w.in) w.out = k.in;
|
|
3670
|
+
else w.in = k.out;
|
|
3671
|
+
}
|
|
3672
|
+
}
|
|
3648
3673
|
function typingSessions(track, width, height, typingGap) {
|
|
3649
3674
|
const pings = track.filter((e) => e.type === "key").map((e) => ({ t: e.t / 1e3, rect: e.rect, x: e.x, y: e.y }));
|
|
3650
3675
|
const all = [];
|
|
@@ -4245,6 +4270,51 @@ function sourceAt(rated, t) {
|
|
|
4245
4270
|
return null;
|
|
4246
4271
|
}
|
|
4247
4272
|
|
|
4273
|
+
// src/lower/loudness.ts
|
|
4274
|
+
var NORMALIZE_MIN_LUFS = -30;
|
|
4275
|
+
var NORMALIZE_MAX_LUFS = -6;
|
|
4276
|
+
var NORMALIZE_PEAK_CEILING_DB = -1;
|
|
4277
|
+
var NORMALIZE_MAX_LIFT_DB = 24;
|
|
4278
|
+
var finite = (v) => typeof v === "number" && Number.isFinite(v);
|
|
4279
|
+
var clamp014 = (v) => Math.max(0, Math.min(1, v));
|
|
4280
|
+
function loudnessMatches(clip) {
|
|
4281
|
+
const l = clip.loudness;
|
|
4282
|
+
return !!l && typeof l === "object" && finite(l.lufs) && typeof l.src === "string" && l.src === clip.key;
|
|
4283
|
+
}
|
|
4284
|
+
function clipGain(clip) {
|
|
4285
|
+
const trim = finite(clip.gain) ? Math.max(0, clip.gain) : 1;
|
|
4286
|
+
if (!finite(clip.normalize)) return { gain: clamp014(trim), basis: "gain" };
|
|
4287
|
+
if (!clip.loudness || typeof clip.loudness !== "object")
|
|
4288
|
+
return { gain: clamp014(trim), basis: "unmeasured" };
|
|
4289
|
+
if (!loudnessMatches(clip)) return { gain: clamp014(trim), basis: "stale" };
|
|
4290
|
+
const l = clip.loudness;
|
|
4291
|
+
const target = Math.max(
|
|
4292
|
+
NORMALIZE_MIN_LUFS,
|
|
4293
|
+
Math.min(NORMALIZE_MAX_LUFS, clip.normalize)
|
|
4294
|
+
);
|
|
4295
|
+
const asked = Math.pow(10, (target - l.lufs) / 20) * trim;
|
|
4296
|
+
let gain = asked;
|
|
4297
|
+
let capped;
|
|
4298
|
+
const lift = Math.pow(10, NORMALIZE_MAX_LIFT_DB / 20);
|
|
4299
|
+
if (gain > lift) {
|
|
4300
|
+
gain = lift;
|
|
4301
|
+
capped = "lift";
|
|
4302
|
+
}
|
|
4303
|
+
if (finite(l.peakDb)) {
|
|
4304
|
+
const peak = Math.pow(10, (NORMALIZE_PEAK_CEILING_DB - l.peakDb) / 20);
|
|
4305
|
+
if (gain > peak) {
|
|
4306
|
+
gain = peak;
|
|
4307
|
+
capped = "peak";
|
|
4308
|
+
}
|
|
4309
|
+
}
|
|
4310
|
+
return {
|
|
4311
|
+
gain: Math.max(0, gain),
|
|
4312
|
+
basis: "normalize",
|
|
4313
|
+
asked,
|
|
4314
|
+
...capped ? { capped } : {}
|
|
4315
|
+
};
|
|
4316
|
+
}
|
|
4317
|
+
|
|
4248
4318
|
// src/lower/audioEnvelope.ts
|
|
4249
4319
|
function clipEnvelope(clip) {
|
|
4250
4320
|
const span = clipLength(clip);
|
|
@@ -4256,7 +4326,7 @@ function clipEnvelope(clip) {
|
|
|
4256
4326
|
fi *= scale;
|
|
4257
4327
|
fo *= scale;
|
|
4258
4328
|
}
|
|
4259
|
-
const g =
|
|
4329
|
+
const g = clipGain(clip).gain;
|
|
4260
4330
|
const pts = [];
|
|
4261
4331
|
pts.push({ t: clip.start, g: fi > 0 ? 0 : g });
|
|
4262
4332
|
if (fi > 0) pts.push({ t: clip.start + fi, g });
|
|
@@ -4293,8 +4363,8 @@ function followFocusEvents(span, cursor, space, layout, options = {}) {
|
|
|
4293
4363
|
}
|
|
4294
4364
|
const pts = cursor.filter((e) => e.type === "move" || e.type === "down" || e.type === "up").map((e) => ({
|
|
4295
4365
|
t: e.t / 1e3,
|
|
4296
|
-
nx:
|
|
4297
|
-
ny:
|
|
4366
|
+
nx: clamp015(e.x / space.w),
|
|
4367
|
+
ny: clamp015(e.y / space.h)
|
|
4298
4368
|
}));
|
|
4299
4369
|
if (!pts.length) return { entry: null, events: [] };
|
|
4300
4370
|
const entry = clampFocus(span.cx, span.cy, level, layout, camera);
|
|
@@ -4372,7 +4442,7 @@ function sampleAt2(pts, t) {
|
|
|
4372
4442
|
}
|
|
4373
4443
|
return pts[pts.length - 1];
|
|
4374
4444
|
}
|
|
4375
|
-
function
|
|
4445
|
+
function clamp015(v) {
|
|
4376
4446
|
return Math.max(0, Math.min(1, v));
|
|
4377
4447
|
}
|
|
4378
4448
|
function round4(v) {
|
|
@@ -8731,17 +8801,22 @@ function studioLayerData(input, duration, pins) {
|
|
|
8731
8801
|
// preview scheduler and the export's offline mix). `duckEnv` (the mic-derived
|
|
8732
8802
|
// duck multiplier curve) is merged in asynchronously by useComposition — it
|
|
8733
8803
|
// needs a decoded recording, which a sync lowering can't produce.
|
|
8734
|
-
|
|
8735
|
-
|
|
8736
|
-
|
|
8737
|
-
|
|
8738
|
-
|
|
8739
|
-
|
|
8740
|
-
|
|
8741
|
-
|
|
8742
|
-
|
|
8743
|
-
|
|
8744
|
-
|
|
8804
|
+
// A measured `normalize` sets the gain (it may exceed 1); a clip with
|
|
8805
|
+
// no target keeps its own, byte for byte.
|
|
8806
|
+
audio: (layers.audio ?? []).map((c) => {
|
|
8807
|
+
const g = clipGain(c);
|
|
8808
|
+
return {
|
|
8809
|
+
key: c.key,
|
|
8810
|
+
start: round8(c.start),
|
|
8811
|
+
in: round8(c.in),
|
|
8812
|
+
out: round8(c.out),
|
|
8813
|
+
gain: round8(g.basis === "normalize" ? g.gain : c.gain),
|
|
8814
|
+
loop: !!c.loop,
|
|
8815
|
+
len: round8(clipLength(c)),
|
|
8816
|
+
duck: !!c.duck,
|
|
8817
|
+
env: clipEnvelope(c).map((p) => ({ t: round8(p.t), g: round8(p.g) }))
|
|
8818
|
+
};
|
|
8819
|
+
}),
|
|
8745
8820
|
// Object clips: numbers resolved HERE; shapes are the drafted engine spec.
|
|
8746
8821
|
...layers.objects && layers.objects.length ? {
|
|
8747
8822
|
objects: layers.objects.map((o) => ({
|
|
@@ -11326,10 +11401,10 @@ function cursorFocusAt(doc, srcT) {
|
|
|
11326
11401
|
let best = track[0];
|
|
11327
11402
|
for (const e of track)
|
|
11328
11403
|
if (Math.abs(e.t - ms) < Math.abs(best.t - ms)) best = e;
|
|
11329
|
-
const
|
|
11404
|
+
const clamp016 = (v) => Math.max(0, Math.min(1, v));
|
|
11330
11405
|
return {
|
|
11331
|
-
cx: round13(
|
|
11332
|
-
cy: round13(
|
|
11406
|
+
cx: round13(clamp016(best.x / width)),
|
|
11407
|
+
cy: round13(clamp016(best.y / height))
|
|
11333
11408
|
};
|
|
11334
11409
|
}
|
|
11335
11410
|
var tiltLane = {
|
|
@@ -12197,6 +12272,133 @@ function duckCurve(rms, segments, durationSec2, opts = DEFAULT_DUCK) {
|
|
|
12197
12272
|
return points;
|
|
12198
12273
|
}
|
|
12199
12274
|
|
|
12275
|
+
// src/docFeatures.ts
|
|
12276
|
+
var overlaysOf = (doc) => Array.isArray(doc.overlays) ? doc.overlays : [];
|
|
12277
|
+
var audioOf = (doc) => Array.isArray(doc.audio) ? doc.audio : [];
|
|
12278
|
+
var anchored = (o) => !!o && !!o.anchor && typeof o.anchor === "object";
|
|
12279
|
+
function bakeAnchors(doc) {
|
|
12280
|
+
const layers = overlaysOf(doc);
|
|
12281
|
+
if (!layers.some(anchored)) return { doc, notes: [] };
|
|
12282
|
+
const notes = [];
|
|
12283
|
+
let resolved = doc;
|
|
12284
|
+
if (isRecordingDoc(doc)) {
|
|
12285
|
+
const rated = ratedSegments(migrateMotion(doc));
|
|
12286
|
+
resolved = resolveOverlayAnchors(doc, rated);
|
|
12287
|
+
for (const o of layers) {
|
|
12288
|
+
if (!anchored(o) || !o.anchor) continue;
|
|
12289
|
+
const r = resolveAnchor(doc, o.anchor, rated);
|
|
12290
|
+
if (r.start === null)
|
|
12291
|
+
notes.push(
|
|
12292
|
+
`overlay ${o.id}: its step ${r.miss === "cut" ? "is cut out of the film" : r.miss === "skipped" ? "was skipped by the recorder" : "is not in the take"}, so it plays at its fallback start (${o.start.toFixed(2)}s)`
|
|
12293
|
+
);
|
|
12294
|
+
}
|
|
12295
|
+
}
|
|
12296
|
+
const overlays = overlaysOf(resolved).map((o) => {
|
|
12297
|
+
if (!anchored(o)) return o;
|
|
12298
|
+
const { anchor: _anchor, ...rest } = o;
|
|
12299
|
+
return rest;
|
|
12300
|
+
});
|
|
12301
|
+
return { doc: { ...resolved, overlays }, notes };
|
|
12302
|
+
}
|
|
12303
|
+
var DOC_FEATURES = [
|
|
12304
|
+
{
|
|
12305
|
+
id: "text-runs",
|
|
12306
|
+
since: "0.38.0",
|
|
12307
|
+
name: "styled text runs",
|
|
12308
|
+
before: "would draw those layers without their words",
|
|
12309
|
+
uses: (doc) => overlaysOf(doc).filter((o) => o?.kind === "text" && Array.isArray(o.text)).length
|
|
12310
|
+
},
|
|
12311
|
+
{
|
|
12312
|
+
id: "overlay-anchor",
|
|
12313
|
+
since: "0.43.0",
|
|
12314
|
+
name: "caption anchors",
|
|
12315
|
+
before: "would show them at their fallback start",
|
|
12316
|
+
uses: (doc) => overlaysOf(doc).filter(anchored).length,
|
|
12317
|
+
bake: bakeAnchors
|
|
12318
|
+
},
|
|
12319
|
+
{
|
|
12320
|
+
id: "text-elapsed",
|
|
12321
|
+
since: "0.43.0",
|
|
12322
|
+
name: "{elapsed} in text",
|
|
12323
|
+
before: "would show the placeholder as it is written",
|
|
12324
|
+
// Filled only on an anchored layer: anywhere else every release shows
|
|
12325
|
+
// the placeholder as written.
|
|
12326
|
+
uses: (doc) => overlaysOf(doc).filter(
|
|
12327
|
+
(o) => anchored(o) && o.kind === "text" && hasElapsed(o.text)
|
|
12328
|
+
).length,
|
|
12329
|
+
// The anchors' bake fills it.
|
|
12330
|
+
bake: bakeAnchors
|
|
12331
|
+
},
|
|
12332
|
+
{
|
|
12333
|
+
id: "audio-normalize",
|
|
12334
|
+
since: "0.44.0",
|
|
12335
|
+
name: "loudness targets on sound",
|
|
12336
|
+
before: "would play those clips at their gain alone",
|
|
12337
|
+
// Only a target that applies: one with no measurement for its key is
|
|
12338
|
+
// ignored by every release alike.
|
|
12339
|
+
uses: (doc) => audioOf(doc).filter((a) => a && clipGain(a).basis === "normalize").length,
|
|
12340
|
+
bake: (doc) => {
|
|
12341
|
+
const notes = [];
|
|
12342
|
+
const audio = audioOf(doc).map((a) => {
|
|
12343
|
+
if (!a || a.normalize === void 0) return a;
|
|
12344
|
+
const { normalize: _normalize, ...rest } = a;
|
|
12345
|
+
const g = clipGain(a);
|
|
12346
|
+
if (g.basis !== "normalize") return rest;
|
|
12347
|
+
const gain = Math.round(Math.min(1, g.gain) * 1e3) / 1e3;
|
|
12348
|
+
if (g.gain > 1)
|
|
12349
|
+
notes.push(
|
|
12350
|
+
`audio ${a.id}: its target asks for gain ${g.gain.toFixed(2)}, which an older renderer cannot play; baked at 1, so it plays ${(20 * Math.log10(g.gain)).toFixed(1)} dB quieter than asked`
|
|
12351
|
+
);
|
|
12352
|
+
return { ...rest, gain };
|
|
12353
|
+
});
|
|
12354
|
+
return { doc: { ...doc, audio }, notes };
|
|
12355
|
+
}
|
|
12356
|
+
}
|
|
12357
|
+
];
|
|
12358
|
+
function compareVersions(a, b) {
|
|
12359
|
+
const parts = (v) => v.trim().replace(/^v/, "").split(/[-+]/)[0].split(".").map((n) => parseInt(n, 10) || 0);
|
|
12360
|
+
const x = parts(a);
|
|
12361
|
+
const y = parts(b);
|
|
12362
|
+
for (let i = 0; i < Math.max(x.length, y.length, 3); i++) {
|
|
12363
|
+
const d = (x[i] ?? 0) - (y[i] ?? 0);
|
|
12364
|
+
if (d) return d < 0 ? -1 : 1;
|
|
12365
|
+
}
|
|
12366
|
+
return 0;
|
|
12367
|
+
}
|
|
12368
|
+
function docFeatures(doc) {
|
|
12369
|
+
const out = [];
|
|
12370
|
+
for (const feature of DOC_FEATURES) {
|
|
12371
|
+
const count = feature.uses(doc);
|
|
12372
|
+
if (count > 0) out.push({ feature, count });
|
|
12373
|
+
}
|
|
12374
|
+
return out;
|
|
12375
|
+
}
|
|
12376
|
+
function featuresNewerThan(doc, version) {
|
|
12377
|
+
return docFeatures(doc).filter(
|
|
12378
|
+
(u) => compareVersions(u.feature.since, version) > 0
|
|
12379
|
+
);
|
|
12380
|
+
}
|
|
12381
|
+
function bakeDocFeatures(doc, version) {
|
|
12382
|
+
let next = structuredClone(doc);
|
|
12383
|
+
const baked = [];
|
|
12384
|
+
const unbakeable = [];
|
|
12385
|
+
const notes = [];
|
|
12386
|
+
const done = /* @__PURE__ */ new Set();
|
|
12387
|
+
for (const { feature } of featuresNewerThan(doc, version)) {
|
|
12388
|
+
if (!feature.bake) {
|
|
12389
|
+
unbakeable.push(feature);
|
|
12390
|
+
continue;
|
|
12391
|
+
}
|
|
12392
|
+
baked.push(feature);
|
|
12393
|
+
if (done.has(feature.bake)) continue;
|
|
12394
|
+
done.add(feature.bake);
|
|
12395
|
+
const r = feature.bake(next);
|
|
12396
|
+
next = r.doc;
|
|
12397
|
+
notes.push(...r.notes);
|
|
12398
|
+
}
|
|
12399
|
+
return { doc: next, baked, unbakeable, notes };
|
|
12400
|
+
}
|
|
12401
|
+
|
|
12200
12402
|
// src/backdrop.ts
|
|
12201
12403
|
function backdropMedia(backdrop) {
|
|
12202
12404
|
return {
|
|
@@ -12317,6 +12519,7 @@ export {
|
|
|
12317
12519
|
DIGEST_CROP_MAX,
|
|
12318
12520
|
DIGEST_FULL_MAX,
|
|
12319
12521
|
DIGEST_VERSION,
|
|
12522
|
+
DOC_FEATURES,
|
|
12320
12523
|
DOC_SCHEMA_VERSION,
|
|
12321
12524
|
DRAG_FIT_LEVEL,
|
|
12322
12525
|
DRAG_MIN_S,
|
|
@@ -12358,6 +12561,10 @@ export {
|
|
|
12358
12561
|
MIXED,
|
|
12359
12562
|
MOTION_DELTA,
|
|
12360
12563
|
MOTION_EASE,
|
|
12564
|
+
NORMALIZE_MAX_LIFT_DB,
|
|
12565
|
+
NORMALIZE_MAX_LUFS,
|
|
12566
|
+
NORMALIZE_MIN_LUFS,
|
|
12567
|
+
NORMALIZE_PEAK_CEILING_DB,
|
|
12361
12568
|
OBJECT_DEFAULT_SCALE,
|
|
12362
12569
|
OVERLAY_FONT_FACES,
|
|
12363
12570
|
OVERLAY_LINE_HEIGHT,
|
|
@@ -12439,6 +12646,7 @@ export {
|
|
|
12439
12646
|
aspectRatioValue,
|
|
12440
12647
|
audioLane,
|
|
12441
12648
|
backdropMedia,
|
|
12649
|
+
bakeDocFeatures,
|
|
12442
12650
|
boldWeightFor,
|
|
12443
12651
|
buildDigest,
|
|
12444
12652
|
calloutBox,
|
|
@@ -12473,9 +12681,11 @@ export {
|
|
|
12473
12681
|
clampZoomLevel,
|
|
12474
12682
|
clearStyle,
|
|
12475
12683
|
clipEnvelope,
|
|
12684
|
+
clipGain,
|
|
12476
12685
|
clipLength,
|
|
12477
12686
|
clipsFrom,
|
|
12478
12687
|
commonStyle,
|
|
12688
|
+
compareVersions,
|
|
12479
12689
|
computeCardLayout,
|
|
12480
12690
|
computeMicRms,
|
|
12481
12691
|
computePeaks,
|
|
@@ -12496,6 +12706,7 @@ export {
|
|
|
12496
12706
|
destinationsForChannel,
|
|
12497
12707
|
diffInput,
|
|
12498
12708
|
docCardLayout,
|
|
12709
|
+
docFeatures,
|
|
12499
12710
|
docFreezes,
|
|
12500
12711
|
docMediaList,
|
|
12501
12712
|
docOutputDuration,
|
|
@@ -12525,6 +12736,7 @@ export {
|
|
|
12525
12736
|
expectedFrameSize,
|
|
12526
12737
|
exportSizeFor,
|
|
12527
12738
|
extractClicks,
|
|
12739
|
+
featuresNewerThan,
|
|
12528
12740
|
findStep,
|
|
12529
12741
|
focusBounds,
|
|
12530
12742
|
focusForViewportCentre,
|
|
@@ -12572,6 +12784,7 @@ export {
|
|
|
12572
12784
|
liveZoomStyleName,
|
|
12573
12785
|
lookFromBrand,
|
|
12574
12786
|
lookKindForGround,
|
|
12787
|
+
loudnessMatches,
|
|
12575
12788
|
lowerProgramDoc,
|
|
12576
12789
|
lowerStudioDoc,
|
|
12577
12790
|
lowerToComposition,
|