@laplace.live/persona-sdk 1.22.0 → 1.23.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.
@@ -147,11 +147,12 @@ export const EFFECT_SPECS = {
147
147
  blur: {
148
148
  radius: { default: 8, min: 0, max: 64, step: 0.5, unit: 'px' },
149
149
  },
150
+ // Intensity and Threshold take Warudo's Camera Bloom units; with Radius, the defaults are its look at 1080p.
150
151
  bloom: {
151
- intensity: { default: 0, min: 0, max: 10, step: 0.01 },
152
- threshold: { default: 0.5, min: 0, max: 2, step: 0.01 },
152
+ intensity: { default: 0.1, min: 0, max: 10, step: 0.01 },
153
+ threshold: { default: 0.75, min: 0, max: 5, step: 0.01 },
153
154
  thresholdSmooth: { default: 0, min: 0, max: 1, step: 0.01 },
154
- radius: { default: 1, min: 0, max: 6, step: 0.01 },
155
+ radius: { default: 3, min: 0, max: 6, step: 0.01 },
155
156
  saturation: { default: 0, min: -1, max: 1, step: 0.01 },
156
157
  opacity: { default: 1, min: 0, max: 1, step: 0.01 },
157
158
  starRays: { default: 3, min: 1, max: 6, step: 1 },
@@ -308,7 +309,7 @@ export const EFFECT_COLOR_SPECS = {
308
309
  color5: { default: '#ffffff' },
309
310
  },
310
311
  rim: {
311
- color: { default: '#ffffff' },
312
+ color: { default: '#ffeb74' },
312
313
  },
313
314
  outline: {
314
315
  color: { default: '#000000' },
@@ -394,13 +395,7 @@ export const EFFECT_ENUM_SPECS = {
394
395
  },
395
396
  blur: { mode: { default: 'gaussian', values: ['gaussian', 'bokeh'] } },
396
397
  bloom: { mode: { default: 'normal', values: ['normal', 'streak', 'star'] } },
397
- rim: {
398
- mode: { default: 'single', values: ['single', 'double', 'sharpenSingle', 'sharpenDouble'] },
399
- blendMode: {
400
- default: 'overlay',
401
- values: EFFECT_BLEND_MODES,
402
- },
403
- },
398
+ rim: { mode: { default: 'single', values: ['single', 'double', 'sharpenSingle', 'sharpenDouble'] } },
404
399
  outline: { quality: { default: 'medium', values: ['low', 'medium', 'high'] } },
405
400
  colorWheels: { mode: { default: 'liftGammaGain', values: ['liftGammaGain', 'shadowsMidtonesHighlights'] } },
406
401
  };
@@ -1,12 +1,14 @@
1
- export declare const ITEM_TRANSITION_STYLES: readonly ["glitch", "dither", "pop", "cut"];
1
+ export declare const ITEM_TRANSITION_STYLES: readonly ["glitch", "fade", "pop", "cut"];
2
2
  export type ItemTransitionStyle = (typeof ITEM_TRANSITION_STYLES)[number];
3
+ /** What `fade` was named while it was a dithered dissolve; hosts still accept it, as `fade`. */
4
+ export declare const LEGACY_DITHER_STYLE = "dither";
3
5
  /** One ramp length for a model switch, an object fade, or a body's entrance and exit. */
4
6
  export declare const ITEM_TRANSITION_DEFAULT_MS = 300;
5
7
  export declare const ITEM_TRANSITION_MAX_MS = 5000;
6
8
  export interface ItemTransition {
7
9
  /**
8
- * `glitch` tears and burns through a dithered dissolve, `dither` is the plain screen-door,
9
- * `pop` scales in and out, `cut` switches.
10
+ * `glitch` tears and burns through a dithered dissolve, `fade` fades each item by alpha as one
11
+ * layer, `pop` scales in and out, `cut` switches.
10
12
  */
11
13
  style: ItemTransitionStyle;
12
14
  /** Length of the ramp either way, 0–`ITEM_TRANSITION_MAX_MS`; 0 behaves as `cut`. */
@@ -16,8 +18,8 @@ export declare function defaultItemTransition(): ItemTransition;
16
18
  /** A per-call departure from the scene's item transition, for `stage.spawn`. */
17
19
  export interface SpawnTransition {
18
20
  style?: ItemTransitionStyle;
19
- /** Entrance ramp, 0–`ITEM_TRANSITION_MAX_MS`; defaults to the scene's duration. */
21
+ /** Entrance ramp, 0–`ITEM_TRANSITION_MAX_MS`; defaults to the scene's duration, and a `cut` ignores it. */
20
22
  inMs?: number;
21
- /** Exit ramp — expiry, eviction and clears — 0–`ITEM_TRANSITION_MAX_MS`; defaults to the scene's duration. */
23
+ /** Exit ramp — expiry, eviction and clears — 0–`ITEM_TRANSITION_MAX_MS`; as `inMs`. */
22
24
  outMs?: number;
23
25
  }
@@ -1,6 +1,8 @@
1
1
  // How stage items appear and disappear — models, objects and spawned bodies alike. Per scene,
2
2
  // on `SceneEnvironment.itemTransition`; `stage.spawn` may override it per call.
3
- export const ITEM_TRANSITION_STYLES = ['glitch', 'dither', 'pop', 'cut'];
3
+ export const ITEM_TRANSITION_STYLES = ['glitch', 'fade', 'pop', 'cut'];
4
+ /** What `fade` was named while it was a dithered dissolve; hosts still accept it, as `fade`. */
5
+ export const LEGACY_DITHER_STYLE = 'dither';
4
6
  /** One ramp length for a model switch, an object fade, or a body's entrance and exit. */
5
7
  export const ITEM_TRANSITION_DEFAULT_MS = 300;
6
8
  export const ITEM_TRANSITION_MAX_MS = 5000;
@@ -1,4 +1,4 @@
1
- import type { AssetRef, Attach, AttachDepth, CameraAnchor, CameraFollowDamping, CameraTrackTarget, EnvironmentLook, ModelFormat, MToonTuning, ObjectContent, ObjectSpace, SceneCameraAim, SceneCameraAimSettings, SceneCameraFocus, SceneCameraFocusSettings, SceneCameraFollow, SceneCameraFollowSettings, SceneCameraHandheld, SceneEnvironment, SceneLight, SceneLightCameraFollowOptions, SceneLightType, ScreenPlacement, VrmPlacement } from '../wire/types.ts';
1
+ import type { AssetRef, Attach, AttachDepth, CameraAnchor, CameraFollowDamping, CameraTrackTarget, ChromaKey, EnvironmentLook, KeyableContent, ModelFormat, MToonTuning, ObjectContent, ObjectSpace, SceneCameraAim, SceneCameraAimSettings, SceneCameraFocus, SceneCameraFocusSettings, SceneCameraFollow, SceneCameraFollowSettings, SceneCameraHandheld, SceneEnvironment, SceneLight, SceneLightCameraFollowOptions, SceneLightType, ScreenPlacement, VrmPlacement } from '../wire/types.ts';
2
2
  export declare function clamp(v: number, min: number, max: number): number;
3
3
  /** Clamp to the unit interval. */
4
4
  export declare function clamp01(v: number): number;
@@ -232,6 +232,13 @@ export declare const WEB_SIZE_MAX = 7680;
232
232
  /** Web object paint-rate bounds, frames per second. */
233
233
  export declare const WEB_FPS_MIN = 1;
234
234
  export declare const WEB_FPS_MAX = 60;
235
+ /** Chroma key tolerance bounds, OBS's 1–1000 ÷ 1000; the floor keeps the edge ramps from dividing by zero. */
236
+ export declare const CHROMA_KEY_TOLERANCE_MIN = 0.001;
237
+ export declare const CHROMA_KEY_TOLERANCE_MAX = 1;
238
+ /** OBS's Chroma Key defaults: green, similarity 400, smoothness 80, spill reduction 100. */
239
+ export declare const DEFAULT_CHROMA_KEY: ChromaKey;
240
+ /** Whether a chroma key can reach the content's pixels: image, video and webpage quads. */
241
+ export declare function isKeyableContent(content: ObjectContent): content is KeyableContent;
235
242
  /** Text object bounds: characters, em size in px, and the box and paint extents in px. */
236
243
  export declare const TEXT_LENGTH_MAX = 10000;
237
244
  export declare const TEXT_SIZE_MIN = 4;
@@ -364,6 +364,21 @@ export const WEB_SIZE_MAX = 7680;
364
364
  /** Web object paint-rate bounds, frames per second. */
365
365
  export const WEB_FPS_MIN = 1;
366
366
  export const WEB_FPS_MAX = 60;
367
+ /** Chroma key tolerance bounds, OBS's 1–1000 ÷ 1000; the floor keeps the edge ramps from dividing by zero. */
368
+ export const CHROMA_KEY_TOLERANCE_MIN = 0.001;
369
+ export const CHROMA_KEY_TOLERANCE_MAX = 1;
370
+ /** OBS's Chroma Key defaults: green, similarity 400, smoothness 80, spill reduction 100. */
371
+ export const DEFAULT_CHROMA_KEY = {
372
+ enabled: false,
373
+ color: '#00ff00',
374
+ similarity: 0.4,
375
+ smoothness: 0.08,
376
+ spill: 0.1,
377
+ };
378
+ /** Whether a chroma key can reach the content's pixels: image, video and webpage quads. */
379
+ export function isKeyableContent(content) {
380
+ return content.kind === 'image' || content.kind === 'video' || content.kind === 'web';
381
+ }
367
382
  /** Text object bounds: characters, em size in px, and the box and paint extents in px. */
368
383
  export const TEXT_LENGTH_MAX = 10_000;
369
384
  export const TEXT_SIZE_MIN = 4;
@@ -7,8 +7,8 @@ export declare const SceneTransitionSchema: z.ZodObject<{
7
7
  image: "image";
8
8
  video: "video";
9
9
  circle: "circle";
10
- cut: "cut";
11
10
  fade: "fade";
11
+ cut: "cut";
12
12
  wipe: "wipe";
13
13
  }>;
14
14
  durationMs: z.ZodNumber;
@@ -329,12 +329,13 @@ export declare const requestSchemas: {
329
329
  scale: z.ZodOptional<z.ZodNumber>;
330
330
  ttlMs: z.ZodOptional<z.ZodNumber>;
331
331
  transition: z.ZodOptional<z.ZodObject<{
332
- style: z.ZodOptional<z.ZodEnum<{
332
+ style: z.ZodOptional<z.ZodPipe<z.ZodEnum<{
333
333
  pop: "pop";
334
334
  glitch: "glitch";
335
- dither: "dither";
335
+ fade: "fade";
336
336
  cut: "cut";
337
- }>>;
337
+ dither: "dither";
338
+ }>, z.ZodTransform<"pop" | "glitch" | "fade" | "cut", "pop" | "glitch" | "fade" | "cut" | "dither">>>;
338
339
  inMs: z.ZodOptional<z.ZodNumber>;
339
340
  outMs: z.ZodOptional<z.ZodNumber>;
340
341
  }, z.core.$strip>>;
@@ -1,7 +1,7 @@
1
1
  import * as z from 'zod';
2
2
  import { CONTROLLER_DEAD_ZONE_MAX } from "../../values/controller.js";
3
3
  import { isRecord } from "../../values/guards.js";
4
- import { ITEM_TRANSITION_STYLES } from "../../values/item-transition.js";
4
+ import { ITEM_TRANSITION_STYLES, LEGACY_DITHER_STYLE } from "../../values/item-transition.js";
5
5
  import { OBJECT_ADD_MANY_MAX, SCENE_COLOR_RE, SPEECH_URL_MAX_LENGTH, STAGE_CAPTURE_EDGE_MAX, STAGE_CAPTURE_EDGE_MIN, STORAGE_KEY_MAX_LENGTH, STORAGE_VALUE_MAX_LENGTH, } from "../../values/limits.js";
6
6
  import { LIP_SYNC_CALIBRATION_ACTIONS, LIP_SYNC_MODES, LIP_SYNC_PHONEMES, } from "../../values/lipsync.js";
7
7
  import { SCENE_TRANSITION_DURATION_MAX_MS, SCENE_TRANSITION_DURATION_MIN_MS, SCENE_TRANSITION_FADE_DEFAULT_MS, SCENE_TRANSITION_SWITCH_POINT_MAX, SCENE_TRANSITION_SWITCH_POINT_MIN, SCENE_TRANSITION_TYPES, } from "../../values/scene-transition.js";
@@ -168,7 +168,11 @@ export const requestSchemas = {
168
168
  ttlMs: z.number().optional(),
169
169
  transition: z
170
170
  .object({
171
- style: z.enum(ITEM_TRANSITION_STYLES).optional(),
171
+ // Older clients still send `fade` by its old name.
172
+ style: z
173
+ .enum([...ITEM_TRANSITION_STYLES, LEGACY_DITHER_STYLE])
174
+ .transform(style => (style === LEGACY_DITHER_STYLE ? 'fade' : style))
175
+ .optional(),
172
176
  inMs: z.number().optional(),
173
177
  outMs: z.number().optional(),
174
178
  })
@@ -239,10 +239,28 @@ export interface TextFont {
239
239
  /** CSS `font-stretch` percent; 100 is normal. */
240
240
  stretch: number;
241
241
  }
242
+ /**
243
+ * Keys one color out of an image, video or webpage, as OBS's Chroma Key filter does. Distance is
244
+ * BT.709 chroma at studio swing, so brightness never counts, and the three tolerances are OBS's
245
+ * slider values ÷ 1000.
246
+ */
247
+ export interface ChromaKey {
248
+ enabled: boolean;
249
+ /** Hex color made transparent. */
250
+ color: string;
251
+ /** Chroma distance from `color` that turns fully transparent. */
252
+ similarity: number;
253
+ /** Distance past `similarity` over which the edge fades back to opaque. */
254
+ smoothness: number;
255
+ /** Distance past `similarity` over which the key's tint desaturates out of the edge. */
256
+ spill: number;
257
+ }
242
258
  /** What an object renders. Mirrors VTube Studio's items and Warudo's screen/prop assets. */
243
259
  export type ObjectContent = {
244
260
  kind: 'image';
245
261
  assetId: string;
262
+ /** Absent keys nothing, as on older hosts. */
263
+ chromaKey?: ChromaKey;
246
264
  } | {
247
265
  kind: 'video';
248
266
  assetId: string;
@@ -251,6 +269,7 @@ export type ObjectContent = {
251
269
  volume: number;
252
270
  /** Held rather than played while shown, through a restart too; the playback position is not saved. */
253
271
  paused: boolean;
272
+ chromaKey?: ChromaKey;
254
273
  } | {
255
274
  kind: 'prop';
256
275
  assetId: string;
@@ -269,6 +288,7 @@ export type ObjectContent = {
269
288
  css: string;
270
289
  /** Close the page while the object is hidden and reload it on show, as an OBS browser source can. */
271
290
  shutdownWhenHidden: boolean;
291
+ chromaKey?: ChromaKey;
272
292
  } | {
273
293
  kind: 'capture';
274
294
  source: CaptureKind;
@@ -332,6 +352,10 @@ export type ObjectContent = {
332
352
  /** 3D: turn the text to face the camera every frame, ignoring the placement's rotation. */
333
353
  faceCamera: boolean;
334
354
  };
355
+ /** The variants of {@link ObjectContent} a chroma key applies to: the flat media quads. */
356
+ export type KeyableContent = Extract<ObjectContent, {
357
+ kind: 'image' | 'video' | 'web';
358
+ }>;
335
359
  /** The video variant of {@link ObjectContent}. */
336
360
  export type VideoContent = Extract<ObjectContent, {
337
361
  kind: 'video';
@@ -716,8 +740,9 @@ export type SceneToneMapping = 'none' | 'neutral' | 'aces' | 'agx';
716
740
  export interface SceneBloom {
717
741
  enabled: boolean;
718
742
  mode: 'normal' | 'streak' | 'star';
719
- /** UI offset: the glow's brightness multiplier is 1 + intensity. */
743
+ /** The glow's brightness multiplier, as Warudo's; 0 adds no glow. */
720
744
  intensity: number;
745
+ /** Gamma-space brightness a channel must pass; squared into linear light, as Warudo's. */
721
746
  threshold: number;
722
747
  thresholdSmooth: number;
723
748
  radius: number;
@@ -986,7 +1011,7 @@ export interface SceneDroplets {
986
1011
  /** Specular glint on drops where the frame is transparent, so rain reads over the desktop (0 to 1). */
987
1012
  glints: number;
988
1013
  }
989
- /** Blend modes shared by Shoost's rim light and gradient. */
1014
+ /** Shoost's blend modes, as Gradient offers them. */
990
1015
  export type EffectBlendMode = (typeof EFFECT_BLEND_MODES)[number];
991
1016
  /** Solid, linear, or radial color overlay in scene or source coordinates. */
992
1017
  export interface EffectGradient {
@@ -1030,11 +1055,10 @@ export interface SceneFlare {
1030
1055
  raysIntensity: number;
1031
1056
  raysOpacity: number;
1032
1057
  }
1033
- /** Directional silhouette lighting, optionally sharpened or applied to both sides. */
1058
+ /** Directional silhouette light, added like VTube Studio's backlight; optionally sharpened or two-sided. */
1034
1059
  export interface SceneRim {
1035
1060
  enabled: boolean;
1036
1061
  mode: 'single' | 'double' | 'sharpenSingle' | 'sharpenDouble';
1037
- blendMode: EffectBlendMode;
1038
1062
  /** Rim tint (hex). */
1039
1063
  color: string;
1040
1064
  /** Normalized rim width, 0..1. */
@@ -1287,7 +1311,7 @@ export interface ScenePatch {
1287
1311
  * App-level features a client gates on (never version-sniff): `hello` and
1288
1312
  * `app.info` report them — the per-app mirror of {@link InstanceRuntime.capabilities}.
1289
1313
  */
1290
- export declare const APP_CAPABILITIES: readonly ["storage", "speech", "automations", "controllers", "model-editing", "asset-inspection", "layer-effects", "scene-transitions", "area-lights", "spot-lights", "camera-follow-lights", "shadow-filters", "environment-map-model", "spawn", "tracking-lost", "motion-stop", "stage-capture", "model-movement", "object-pin-depth", "text-objects", "camera-follow", "camera-aim", "camera-focus", "camera-handheld", "video-sound"];
1314
+ export declare const APP_CAPABILITIES: readonly ["storage", "speech", "automations", "controllers", "model-editing", "asset-inspection", "layer-effects", "scene-transitions", "area-lights", "spot-lights", "camera-follow-lights", "shadow-filters", "environment-map-model", "spawn", "tracking-lost", "motion-stop", "stage-capture", "model-movement", "object-pin-depth", "text-objects", "camera-follow", "camera-aim", "camera-focus", "camera-handheld", "video-sound", "object-chroma-key"];
1291
1315
  export type AppCapability = (typeof APP_CAPABILITIES)[number];
1292
1316
  export declare function isAppCapability(v: unknown): v is AppCapability;
1293
1317
  /** What a loaded model instance can do; absent capabilities answer `unsupported-for-format`. */
@@ -95,6 +95,8 @@ export const APP_CAPABILITIES = [
95
95
  'camera-handheld',
96
96
  /** `object.setContent` edits a video's `loop`, `muted` and `volume` in place; its sound follows the output device. */
97
97
  'video-sound',
98
+ /** Image, video and webpage content takes `chromaKey`, which `object.setContent` edits in place. */
99
+ 'object-chroma-key',
98
100
  ];
99
101
  export function isAppCapability(v) {
100
102
  return isOneOf(v, APP_CAPABILITIES);
package/package.json CHANGED
@@ -1,10 +1,10 @@
1
1
  {
2
2
  "name": "@laplace.live/persona-sdk",
3
- "version": "1.22.0",
3
+ "version": "1.23.0",
4
4
  "description": "TypeScript SDK and wire schema for the LAPLACE Persona plugin API",
5
5
  "license": "MIT",
6
6
  "type": "module",
7
- "author": "LAPLACE <s@laplace.live>",
7
+ "author": "LAPLACE Live! <s@laplace.live>",
8
8
  "repository": {
9
9
  "type": "git",
10
10
  "url": "https://github.com/laplace-live/persona",