@vosjs/studio-core 0.47.0 → 0.49.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 CHANGED
@@ -397,6 +397,13 @@ interface RecordingMeta {
397
397
  * whole take, so Chrome letterboxes the resized content — surface a notice.
398
398
  */
399
399
  resizedDuringTake?: boolean;
400
+ /**
401
+ * How round the footage's own corners are, in cursor-space units (a recorded
402
+ * window's points). Outside them the footage is opaque: a window recorded
403
+ * alone is black there. A card drawn around the footage is at least this
404
+ * round, or those corners show as notches. Absent or 0 = square footage.
405
+ */
406
+ cornerRadius?: number;
400
407
  /**
401
408
  * Page URL/title at record start (seeds the browser-bar mock's address pill).
402
409
  * Query/hash are stripped at capture for privacy.
@@ -404,12 +411,26 @@ interface RecordingMeta {
404
411
  pageUrl?: string;
405
412
  pageTitle?: string;
406
413
  /**
407
- * What the frame contains. Absent = 'tab' (back-compat). Non-tab surfaces use
408
- * CursorEvent.sx/sy + the geometry rects below to map cursor → capture px
409
- * (see normalizeCaptureSpace); tab-only studio features (browser-bar mock,
410
- * letterbox notice) are gated off for them.
414
+ * The application an `'app'` take recorded, where a page take says
415
+ * `pageUrl`: its name as the OS shows it, and its bundle id or executable
416
+ * name. Never a window title, which carries document and account names.
417
+ */
418
+ app?: {
419
+ name: string;
420
+ id?: string;
421
+ };
422
+ /**
423
+ * What the frame contains. Absent = 'tab' (back-compat). `'window'` and
424
+ * `'monitor'` are a BROWSER's display captures: they use CursorEvent.sx/sy +
425
+ * the geometry rects below to map cursor → capture px (see
426
+ * normalizeCaptureSpace), and tab-only studio features (browser-bar mock,
427
+ * letterbox notice) are gated off for them. `'app'` is one application's
428
+ * window recorded by a desktop producer: the frame IS the window, the cursor
429
+ * is already in the frame's own space (`width × height` are the window's
430
+ * points, `captureWidth × captureHeight` its pixels), and the footage
431
+ * carries the window's own title bar, so no bar is drawn over it.
411
432
  */
412
- captureSurface?: 'tab' | 'window' | 'monitor';
433
+ captureSurface?: 'tab' | 'window' | 'monitor' | 'app';
413
434
  /**
414
435
  * Target-tab browser-window bounds at record start, screen-coord CSS px
415
436
  * (window.screenX/Y + outerWidth/Height). Anchor for 'window' captures.
@@ -463,10 +484,12 @@ interface RecordingMeta {
463
484
  * track from automation (exact coords, fresh rects, coverage 1 by
464
485
  * construction) and encode WebM; `ingest` is someone else's recording
465
486
  * brought in as a take (`vos ingest`, or a file dropped on the studio),
466
- * whose cursor track, if any, came from a trace beside it; absent means
487
+ * whose cursor track, if any, came from a trace beside it; `desktop` is a
488
+ * capture helper recording an application's window, its cursor either
489
+ * sampled from the real pointer or synthesized from a script; absent means
467
490
  * the extension.
468
491
  */
469
- producer?: 'extension' | 'cli' | 'ingest';
492
+ producer?: 'extension' | 'cli' | 'ingest' | 'desktop';
470
493
  /**
471
494
  * The step timeline: when each actions.json step ran, in SOURCE
472
495
  * seconds. This is what makes a cut re-anchorable across re-records — a
@@ -3705,6 +3728,8 @@ interface DigestTakeFacts {
3705
3728
  producer: string;
3706
3729
  pageUrl: string | null;
3707
3730
  pageTitle: string | null;
3731
+ /** The recorded application's name, on an `'app'` take. */
3732
+ app: string | null;
3708
3733
  hasMic: boolean;
3709
3734
  hasSystemAudio: boolean;
3710
3735
  hasCursor: boolean;
package/dist/index.js CHANGED
@@ -506,7 +506,8 @@ function deriveViewportCrop(cursor, meta) {
506
506
  }
507
507
  function normalizeCaptureSpace(cursor, meta) {
508
508
  const surface = meta.captureSurface ?? "tab";
509
- if (surface === "tab") return { cursor, meta, coverage: 1 };
509
+ if (surface === "tab" || surface === "app")
510
+ return { cursor, meta, coverage: 1 };
510
511
  const anchor = surface === "window" ? meta.windowRect : meta.screenRect;
511
512
  const capW = meta.captureWidth ?? 0;
512
513
  const capH = meta.captureHeight ?? 0;
@@ -620,11 +621,12 @@ function projectFromArtifact(artifact, videoUrl, opts = {}) {
620
621
  const norm = normalizeCaptureSpace(artifact.cursor, artifact.meta);
621
622
  const { cursor, meta } = norm;
622
623
  const unfocused = surface === "window" && (artifact.meta.windowFocusedFrac ?? 1) < WINDOW_FOCUS_MIN;
623
- const coverage = surface === "monitor" || artifact.meta.windowMovedDuringTake || unfocused ? 0 : norm.coverage;
624
+ const movedAnchor = surface !== "app" && artifact.meta.windowMovedDuringTake === true;
625
+ const coverage = surface === "monitor" || movedAnchor || unfocused ? 0 : norm.coverage;
624
626
  if (artifact.cursor.length > 0 && coverage < CAPTURE_COVERAGE_MIN) {
625
627
  console.warn(
626
628
  "[studio] dropping cursor track \u2014",
627
- surface === "monitor" ? "entire-screen takes have no cursor effects (untrackable outside browser pages)" : artifact.meta.windowMovedDuringTake ? "window moved/resized during the take" : unfocused ? `browser window focused only ${Math.round((artifact.meta.windowFocusedFrac ?? 0) * 100)}% of the take \u2014 a different window was likely shared` : `coverage ${norm.coverage.toFixed(2)} (wrong surface shared, or unusable capture geometry)`
629
+ surface === "monitor" ? "entire-screen takes have no cursor effects (untrackable outside browser pages)" : movedAnchor ? "window moved/resized during the take" : unfocused ? `browser window focused only ${Math.round((artifact.meta.windowFocusedFrac ?? 0) * 100)}% of the take \u2014 a different window was likely shared` : `coverage ${norm.coverage.toFixed(2)} (wrong surface shared, or unusable capture geometry)`
628
630
  );
629
631
  }
630
632
  const doc = {
@@ -8565,6 +8567,11 @@ var ON_FRAME = `(ctx, content, dt) => {
8565
8567
  cardX = dx; cardY = dy - barH; cardW = dw; cardH = dh + barH
8566
8568
  }
8567
8569
  var radius = (cfr.radius || 0) * s2
8570
+ // Footage of a window is round at its own corners and opaque outside them.
8571
+ // The card is at least as round, or those corners show as notches. Under
8572
+ // cover the card crops the footage and its corners are not the card's.
8573
+ var ftR = (actM ? actM.footageRadius : d.footageRadius) || 0
8574
+ if (ftR > 0 && !fitCover) radius = Math.max(radius, ftR * dw)
8568
8575
  var shadow = cfr.shadow || 0
8569
8576
  // The shadow's colour (a #rrggbb; the strengths are its alpha) and the
8570
8577
  // optional tight CONTACT layer that makes a light card sit on a light
@@ -9562,6 +9569,7 @@ function lowerToComposition(input) {
9562
9569
  })),
9563
9570
  cursorStyle: doc.cursor,
9564
9571
  cursorSpace: { w: doc.source.meta.width, h: doc.source.meta.height },
9572
+ ...footageRadiusData(doc.source.meta, doc.source.crop),
9565
9573
  // The other media (concat): each with its element's source, its crop
9566
9574
  // and its own cursor in its own capture space; absent = one media.
9567
9575
  ...doc.media && doc.media.length ? {
@@ -9586,6 +9594,7 @@ function lowerToComposition(input) {
9586
9594
  y: round9(p.y)
9587
9595
  })),
9588
9596
  cursorSpace: { w: m.meta.width, h: m.meta.height },
9597
+ ...footageRadiusData(m.meta, m.crop),
9589
9598
  // The card this media wears (its own placement, bar, corner,
9590
9599
  // shadow, border, fit and focus over the take's), resolved
9591
9600
  // here so ON_FRAME swaps one object and reads no document.
@@ -9860,6 +9869,11 @@ function pinsDataOf(doc, pins) {
9860
9869
  function round42(v) {
9861
9870
  return Math.round(v * 1e5) / 1e5;
9862
9871
  }
9872
+ function footageRadiusData(meta, crop) {
9873
+ const r = meta.cornerRadius;
9874
+ if (!r || !(r > 0) || crop || !(meta.width > 0)) return {};
9875
+ return { footageRadius: round42(r / meta.width) };
9876
+ }
9863
9877
  function clickFxData(doc, rated) {
9864
9878
  const fx = doc.cursor.clickFx;
9865
9879
  const on = fx.style !== "none" || fx.press;
@@ -10774,6 +10788,7 @@ function buildDigest(input) {
10774
10788
  producer: meta.producer ?? "extension",
10775
10789
  pageUrl: meta.pageUrl ?? null,
10776
10790
  pageTitle: meta.pageTitle ?? null,
10791
+ app: meta.app?.name ?? null,
10777
10792
  hasMic: Boolean(doc.source.micKey) || meta.hasMic === true,
10778
10793
  hasSystemAudio: meta.hasAudio === true,
10779
10794
  hasCursor: doc.source.cursor.length > 0,