@twick/visualizer 0.15.13 → 0.15.15

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/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "@twick/visualizer",
3
- "version": "0.15.13",
3
+ "version": "0.15.15",
4
4
  "license": "https://github.com/ncounterspecialist/twick/blob/main/LICENSE.md",
5
5
  "scripts": {
6
6
  "start": "twick editor --projectFile ./src/live.tsx",
@@ -22,18 +22,18 @@
22
22
  },
23
23
  "dependencies": {
24
24
  "@preact/signals": "^1.2.1",
25
- "@twick/2d": "^0.15.13",
26
- "@twick/core": "^0.15.13",
27
- "@twick/renderer": "^0.15.13",
28
- "@twick/vite-plugin": "^0.15.13",
25
+ "@twick/2d": "^0.15.15",
26
+ "@twick/core": "^0.15.15",
27
+ "@twick/renderer": "^0.15.15",
28
+ "@twick/vite-plugin": "^0.15.15",
29
29
  "date-fns": "^4.1.0",
30
30
  "preact": "^10.19.2",
31
31
  "crelt": "^1.0.6",
32
- "@twick/media-utils": "0.15.13"
32
+ "@twick/media-utils": "0.15.15"
33
33
  },
34
34
  "devDependencies": {
35
- "@twick/cli": "^0.15.13",
36
- "@twick/ui": "^0.15.13",
35
+ "@twick/cli": "^0.15.15",
36
+ "@twick/ui": "^0.15.15",
37
37
  "typescript": "5.4.2",
38
38
  "typedoc": "^0.25.8",
39
39
  "typedoc-plugin-markdown": "^3.17.1",
@@ -74,10 +74,10 @@ import { AnimationParams } from "../helpers/types";
74
74
  * animation: { name: "fade", animate: "enter", duration: 2 }
75
75
  * },
76
76
  * {
77
- * id: "subtitle",
77
+ * id: "caption",
78
78
  * type: "text",
79
79
  * s: 1, e: 8,
80
- * t: "Subtitle",
80
+ * t: "Caption",
81
81
  * animation: { name: "fade", animate: "enter", duration: 2 }
82
82
  * },
83
83
  * {
@@ -3,8 +3,8 @@
3
3
  * including video, audio, captions, scenes, and elements.
4
4
  */
5
5
 
6
- import { Layout, Rect, View2D, Audio } from "@twick/2d";
7
- import { VisualizerTrack } from "../helpers/types";
6
+ import { Layout, Rect, View2D, Audio, Img, Txt } from "@twick/2d";
7
+ import { VisualizerTrack, WatermarkInput } from "../helpers/types";
8
8
  import { all, Color, createRef, ThreadGenerator, waitFor } from "@twick/core";
9
9
  import {
10
10
  CAPTION_STYLE,
@@ -13,6 +13,7 @@ import {
13
13
  } from "../helpers/constants";
14
14
  import { logger } from "../helpers/log.utils";
15
15
  import elementController from "../controllers/element.controller";
16
+ import watermarkController from "../controllers/watermark.controller";
16
17
  import { hexToRGB } from "../helpers/utils";
17
18
 
18
19
  /**
@@ -218,3 +219,28 @@ export function* makeElementTrack({
218
219
  yield* all(...sequence);
219
220
  yield elementTrackRef().remove();
220
221
  }
222
+
223
+ /**
224
+ * Creates a watermark overlay on top of all tracks.
225
+ * Dispatches to the registered watermark renderer by type (text | image).
226
+ * Added last to ensure it renders on top of all content.
227
+ */
228
+ export function* makeWatermarkTrack({
229
+ view,
230
+ watermark,
231
+ duration,
232
+ }: {
233
+ view: View2D;
234
+ watermark: WatermarkInput;
235
+ duration: number;
236
+ }) {
237
+ if (duration <= 0) return;
238
+
239
+ // Add watermark after other tracks have started (ensures it renders on top)
240
+ yield* waitFor(0.001);
241
+
242
+ const renderer = watermarkController.get(watermark.type);
243
+ if (renderer) {
244
+ yield* renderer.render({ view, watermark, duration });
245
+ }
246
+ }
@@ -1,7 +1,6 @@
1
1
  import { AudioElement } from "../elements/audio.element";
2
2
  import { CaptionElement } from "../elements/caption.element";
3
3
  import { CircleElement } from "../elements/circle.element";
4
- import { IconElement } from "../elements/icon.element";
5
4
  import { ImageElement } from "../elements/image.element";
6
5
  import { RectElement } from "../elements/rect.element";
7
6
  import { SceneElement } from "../elements/scene.element";
@@ -33,7 +32,6 @@ export class ElementController {
33
32
  elementController.register(TextElement);
34
33
  elementController.register(AudioElement);
35
34
  elementController.register(CircleElement);
36
- elementController.register(IconElement);
37
35
  elementController.register(RectElement);
38
36
  }
39
37
 
@@ -0,0 +1,35 @@
1
+ import type { WatermarkRendererContract } from "../helpers/watermark.types";
2
+ import { TextWatermarkRenderer } from "../watermark-renderers/text.watermark";
3
+ import { ImageWatermarkRenderer } from "../watermark-renderers/image.watermark";
4
+
5
+ /**
6
+ * Registry for watermark renderers. Enables scalable dispatch by type:
7
+ * watermarkController.get(watermark.type)?.render({ view, watermark, duration }).
8
+ * Add new watermark types (e.g. svg) by registering a new renderer.
9
+ */
10
+ export class WatermarkController {
11
+ private renderers = new Map<string, WatermarkRendererContract>();
12
+
13
+ register(renderer: WatermarkRendererContract) {
14
+ this.renderers.set(renderer.name, renderer);
15
+ }
16
+
17
+ get(name: string): WatermarkRendererContract | undefined {
18
+ return this.renderers.get(name);
19
+ }
20
+
21
+ list(): string[] {
22
+ return Array.from(this.renderers.keys());
23
+ }
24
+ }
25
+
26
+ const watermarkController = new WatermarkController();
27
+
28
+ function registerWatermarkRenderers() {
29
+ watermarkController.register(TextWatermarkRenderer);
30
+ watermarkController.register(ImageWatermarkRenderer);
31
+ }
32
+
33
+ registerWatermarkRenderers();
34
+
35
+ export default watermarkController;
@@ -10,5 +10,4 @@ export { TextElement } from './text.element';
10
10
  export { CaptionElement } from './caption.element';
11
11
  export { RectElement } from './rect.element';
12
12
  export { CircleElement } from './circle.element';
13
- export { IconElement } from './icon.element';
14
13
  export { SceneElement } from './scene.element';
@@ -1,8 +1,8 @@
1
1
  import { ElementParams } from "../helpers/types";
2
- import { all, createRef, waitFor } from "@twick/core";
3
- import { Txt } from "@twick/2d";
2
+ import { all, Color, createRef, waitFor } from "@twick/core";
3
+ import { Rect, Txt } from "@twick/2d";
4
4
  import { addAnimation, addTextEffect } from "../helpers/element.utils";
5
- import { logger } from "../helpers/log.utils";
5
+ import { hexToRGB } from "../helpers/utils";
6
6
 
7
7
  /**
8
8
  * @group TextElement
@@ -79,26 +79,85 @@ export const TextElement = {
79
79
  * });
80
80
  * ```
81
81
  */
82
- *create({ containerRef, element, view }: ElementParams) {
82
+ *create({ containerRef, element, view }: ElementParams) {
83
83
  const elementRef = createRef<any>();
84
+ const hasBackground =
85
+ element.props?.backgroundColor != null &&
86
+ element.props.backgroundColor !== "";
84
87
 
85
88
  yield* waitFor(element?.s);
86
- yield containerRef().add(
87
- <Txt
88
- ref={elementRef}
89
- key={element.id}
90
- text={element.t}
91
- textWrap={element.props?.textWrap ?? true}
92
- textAlign={element.props?.textAlign ?? "center"}
93
- {...element.props}
94
- />
95
- );
96
- yield* all(
97
- addAnimation({ elementRef: elementRef, element: element, view }),
98
- addTextEffect({ elementRef: elementRef, element: element }),
99
- waitFor(Math.max(0, element.e - element.s))
100
- );
101
- yield elementRef().remove();
102
- }
89
+
90
+ if (hasBackground) {
91
+ const textRef = createRef<Txt>();
92
+ const wrapperRef = createRef<any>();
93
+ const innerTextRef = createRef<Txt>();
94
+
95
+ yield containerRef().add(
96
+ <Txt
97
+ ref={textRef}
98
+ key={`${element.id}-measure`}
99
+ text={element.t}
100
+ textWrap={element.props?.textWrap ?? true}
101
+ textAlign={element.props?.textAlign ?? "center"}
102
+ opacity={0}
103
+ {...element.props}
104
+ />
105
+ );
106
+
107
+ const bgColor = new Color({
108
+ ...hexToRGB(element.props!.backgroundColor!),
109
+ a: element.props?.backgroundOpacity ?? 1,
110
+ });
111
+ const paddingX = 20;
112
+ const paddingY = 4;
113
+
114
+ yield containerRef().add(
115
+ <Rect
116
+ ref={wrapperRef}
117
+ key={element.id}
118
+ fill={bgColor}
119
+ width={textRef().width() + paddingX}
120
+ height={textRef().height() + paddingY}
121
+ padding={[0, paddingX / 2]}
122
+ alignItems={"center"}
123
+ justifyContent={"center"}
124
+ layout
125
+ >
126
+ <Txt
127
+ ref={innerTextRef}
128
+ text={element.t}
129
+ textWrap={element.props?.textWrap ?? true}
130
+ textAlign={element.props?.textAlign ?? "center"}
131
+ {...element.props}
132
+ />
133
+ </Rect>
134
+ );
135
+ textRef().remove();
136
+
137
+ yield* all(
138
+ addAnimation({ elementRef: wrapperRef, element: element, view }),
139
+ addTextEffect({ elementRef: innerTextRef, element: element }),
140
+ waitFor(Math.max(0, element.e - element.s))
141
+ );
142
+ yield wrapperRef().remove();
143
+ } else {
144
+ yield containerRef().add(
145
+ <Txt
146
+ ref={elementRef}
147
+ key={element.id}
148
+ text={element.t}
149
+ textWrap={element.props?.textWrap ?? true}
150
+ textAlign={element.props?.textAlign ?? "center"}
151
+ {...element.props}
152
+ />
153
+ );
154
+ yield* all(
155
+ addAnimation({ elementRef: elementRef, element: element, view }),
156
+ addTextEffect({ elementRef: elementRef, element: element }),
157
+ waitFor(Math.max(0, element.e - element.s))
158
+ );
159
+ yield elementRef().remove();
160
+ }
161
+ },
103
162
  }
104
163
 
@@ -1,19 +1,48 @@
1
1
  import { View2D } from "@twick/2d";
2
2
  import { Reference, ThreadGenerator, Vector2 } from "@twick/core";
3
3
 
4
+ /**
5
+ * Watermark configuration for overlay on video.
6
+ * Supports text or image watermarks with position, rotation, and opacity.
7
+ * Compatible with WatermarkJSON from @twick/timeline.
8
+ */
9
+ export type WatermarkInput = {
10
+ type: "text" | "image";
11
+ position?: Position;
12
+ rotation?: number;
13
+ opacity?: number;
14
+ props: {
15
+ text?: string;
16
+ fontSize?: number;
17
+ fontFamily?: string;
18
+ fill?: string;
19
+ stroke?: string;
20
+ strokeWidth?: number;
21
+ textAlign?: string;
22
+ fontWeight?: number;
23
+ lineWidth?: number;
24
+ fontStyle?: string;
25
+ src?: string;
26
+ width?: number;
27
+ height?: number;
28
+ objectFit?: ObjectFit;
29
+ };
30
+ };
31
+
4
32
  /**
5
33
  * Main input configuration for video visualization.
6
- * Contains player settings, background color, dimensions, and track definitions
7
- * for creating complete video visualizations.
34
+ * Contains player settings, background color, dimensions, track definitions,
35
+ * and optional watermark for creating complete video visualizations.
8
36
  */
9
37
  export type VideoInput = {
10
- playerId: string,
38
+ playerId: string;
11
39
  backgroundColor: string;
12
40
  properties: {
13
41
  width: number;
14
42
  height: number;
15
43
  };
16
44
  tracks: VisualizerTrack[];
45
+ watermark?: WatermarkInput;
17
46
  };
18
47
 
19
48
  /**
@@ -0,0 +1,23 @@
1
+ import { View2D } from "@twick/2d";
2
+ import { ThreadGenerator } from "@twick/core";
3
+ import { WatermarkInput } from "./types";
4
+
5
+ /**
6
+ * Parameters passed to a watermark renderer.
7
+ */
8
+ export type WatermarkRendererParams = {
9
+ view: View2D;
10
+ watermark: WatermarkInput;
11
+ duration: number;
12
+ };
13
+
14
+ /**
15
+ * Interface for watermark renderers. Register with WatermarkController
16
+ * to support text, image, or future watermark types (e.g. svg).
17
+ */
18
+ export interface WatermarkRendererContract {
19
+ /** Renderer type: "text" | "image" */
20
+ name: "text" | "image";
21
+ /** Renders the watermark overlay for the given duration */
22
+ render(params: WatermarkRendererParams): ThreadGenerator;
23
+ }
package/src/index.ts CHANGED
@@ -194,3 +194,8 @@ export * from './elements';
194
194
  export * from './text-effects';
195
195
  export * from './frame-effects';
196
196
 
197
+ // Watermark renderer registry (Option B – register custom watermark types)
198
+ export { default as watermarkController } from './controllers/watermark.controller';
199
+ export { WatermarkController } from './controllers/watermark.controller';
200
+ export type { WatermarkRendererContract, WatermarkRendererParams } from './helpers/watermark.types';
201
+
package/src/sample.ts CHANGED
@@ -26,6 +26,18 @@ export const sample = {
26
26
  "orgId": "a251d9971a55"
27
27
  },
28
28
  "basePath": "carlyn",
29
+ "watermark": {
30
+ "id": "e-watermark",
31
+ "type": "text",
32
+ "position": { "x": 0, "y": -200 },
33
+ "opacity": 0.7,
34
+ "props": {
35
+ "text": "© Twick SDK",
36
+ "fontSize": 24,
37
+ "fontFamily": "Arial",
38
+ "fill": "#ffffff"
39
+ }
40
+ },
29
41
  "tracks": [
30
42
  {
31
43
  "id": "t-scene",
@@ -55,7 +55,6 @@ export { CaptionElement } from './elements/caption.element';
55
55
  // Shape and UI elements
56
56
  export { RectElement } from './elements/rect.element';
57
57
  export { CircleElement } from './elements/circle.element';
58
- export { IconElement } from './elements/icon.element';
59
58
 
60
59
  // Special elements
61
60
  export { SceneElement } from './elements/scene.element';
@@ -57,6 +57,7 @@ import {
57
57
  makeElementTrack,
58
58
  makeSceneTrack,
59
59
  makeVideoTrack,
60
+ makeWatermarkTrack,
60
61
  } from "./components/track";
61
62
  import { dispatchWindowEvent } from "./helpers/event.utils";
62
63
 
@@ -133,7 +134,7 @@ export const scene = makeScene2D("scene", function* (view: View2D) {
133
134
  const movie = [];
134
135
  let index = 1;
135
136
 
136
- // Iterate through each track element and create appropriate visualization
137
+ // Iterate through each track. Z-order is determined by track order (first track = back, last = front).
137
138
  for (const track of input.tracks) {
138
139
  switch (track.type) {
139
140
  case TRACK_TYPES.VIDEO:
@@ -159,6 +160,25 @@ export const scene = makeScene2D("scene", function* (view: View2D) {
159
160
  }
160
161
  index++;
161
162
  }
163
+
164
+ // Add watermark on top of all tracks when present
165
+ if (input.watermark) {
166
+ const duration =
167
+ input.tracks?.length > 0
168
+ ? Math.max(
169
+ 0,
170
+ ...input.tracks.flatMap((t) => t.elements || []).map((e) => e.e)
171
+ )
172
+ : 10;
173
+ movie.push(
174
+ makeWatermarkTrack({
175
+ view,
176
+ watermark: input.watermark,
177
+ duration,
178
+ })
179
+ );
180
+ }
181
+
162
182
  // Execute all track animations in parallel
163
183
  yield* all(...movie);
164
184
  dispatchWindowEvent(EVENT_TYPES.PLAYER_UPDATE, {
@@ -0,0 +1,37 @@
1
+ /**
2
+ * Image watermark renderer for the visualizer.
3
+ * Renders image overlay with position, rotation, opacity, and dimensions.
4
+ */
5
+
6
+ import { Img } from "@twick/2d";
7
+ import { createRef, waitFor } from "@twick/core";
8
+ import type { WatermarkRendererContract } from "../helpers/watermark.types";
9
+
10
+ export const ImageWatermarkRenderer: WatermarkRendererContract = {
11
+ name: "image",
12
+
13
+ *render({ view, watermark, duration }) {
14
+ const props = watermark.props;
15
+ if (!props?.src) return;
16
+
17
+ const position = watermark.position ?? { x: 0, y: 0 };
18
+ const rotation = (watermark.rotation ?? 0) * (Math.PI / 180);
19
+ const opacity = watermark.opacity ?? 1;
20
+
21
+ const watermarkRef = createRef<any>();
22
+ view.add(
23
+ <Img
24
+ ref={watermarkRef}
25
+ src={props.src}
26
+ x={position.x}
27
+ y={position.y}
28
+ width={props.width}
29
+ height={props.height}
30
+ rotation={rotation}
31
+ opacity={opacity}
32
+ />
33
+ );
34
+
35
+ yield* waitFor(duration);
36
+ },
37
+ };
@@ -0,0 +1,7 @@
1
+ /**
2
+ * Watermark renderers registry.
3
+ * Export renderers and shared types for the watermark controller.
4
+ */
5
+
6
+ export { TextWatermarkRenderer } from "./text.watermark";
7
+ export { ImageWatermarkRenderer } from "./image.watermark";
@@ -0,0 +1,43 @@
1
+ /**
2
+ * Text watermark renderer for the visualizer.
3
+ * Renders text overlay with position, rotation, opacity, and text styling.
4
+ */
5
+
6
+ import { Txt } from "@twick/2d";
7
+ import { createRef, waitFor } from "@twick/core";
8
+ import type { WatermarkRendererContract } from "../helpers/watermark.types";
9
+
10
+ export const TextWatermarkRenderer: WatermarkRendererContract = {
11
+ name: "text",
12
+
13
+ *render({ view, watermark, duration }) {
14
+ const props = watermark.props;
15
+ if (!props?.text) return;
16
+
17
+ const position = watermark.position ?? { x: 0, y: 0 };
18
+ const rotation = (watermark.rotation ?? 0) * (Math.PI / 180);
19
+ const opacity = watermark.opacity ?? 1;
20
+
21
+ const watermarkRef = createRef<any>();
22
+ view.add(
23
+ <Txt
24
+ ref={watermarkRef}
25
+ text={props.text}
26
+ x={position.x}
27
+ y={position.y}
28
+ rotation={rotation}
29
+ opacity={opacity}
30
+ fontSize={props.fontSize ?? 24}
31
+ fontFamily={props.fontFamily ?? "Arial"}
32
+ fill={props.fill ?? "#ffffff"}
33
+ stroke={props.stroke}
34
+ lineWidth={props.lineWidth ?? props.strokeWidth ?? 0}
35
+ textAlign={(props.textAlign as any) ?? "center"}
36
+ fontWeight={props.fontWeight ?? 400}
37
+ fontStyle={props.fontStyle ?? "normal"}
38
+ />
39
+ );
40
+
41
+ yield* waitFor(duration);
42
+ },
43
+ };
@@ -1,88 +0,0 @@
1
- import { ElementParams } from "../helpers/types";
2
- import { all, createRef, waitFor } from "@twick/core";
3
- import { Icon } from "@twick/2d";
4
- import { addAnimation } from "../helpers/element.utils";
5
-
6
- /**
7
- * @group IconElement
8
- * IconElement creates and manages icon elements in the visualizer scene.
9
- * Handles icon rendering, styling, and animations for UI elements and
10
- * visual design components.
11
- *
12
- * Features:
13
- * - Icon rendering with custom styling
14
- * - Size and position control
15
- * - Color and opacity customization
16
- * - Animation support
17
- *
18
- * @param containerRef - Reference to the container element
19
- * @param element - Icon element configuration and properties
20
- * @param view - The main scene view for rendering
21
- *
22
- * @example
23
- * ```js
24
- * // Basic icon element
25
- * {
26
- * id: "play-icon",
27
- * type: "icon",
28
- * s: 0,
29
- * e: 20,
30
- * props: {
31
- * icon: "play",
32
- * size: 64,
33
- * fill: "#ffffff"
34
- * }
35
- * }
36
- *
37
- * // Icon with animation
38
- * {
39
- * id: "animated-icon",
40
- * type: "icon",
41
- * s: 2,
42
- * e: 15,
43
- * props: {
44
- * icon: "pause",
45
- * size: 48,
46
- * fill: "#ff0000",
47
- * opacity: 0.8
48
- * },
49
- * animation: {
50
- * name: "fade",
51
- * animate: "enter",
52
- * duration: 2
53
- * }
54
- * }
55
- * ```
56
- */
57
- export const IconElement = {
58
- name: "icon",
59
-
60
- /**
61
- * Generator function that creates and manages icon elements in the scene.
62
- * Handles icon creation, styling, and cleanup.
63
- *
64
- * @param params - Element parameters including container reference, element config, and view
65
- * @returns Generator that controls the icon element lifecycle
66
- *
67
- * @example
68
- * ```js
69
- * yield* IconElement.create({
70
- * containerRef: mainContainer,
71
- * element: iconConfig,
72
- * view: sceneView
73
- * });
74
- * ```
75
- */
76
- *create({ containerRef, element, view }: ElementParams) {
77
- const elementRef = createRef<any>();
78
- yield* waitFor(element?.s);
79
- yield containerRef().add(
80
- <Icon ref={elementRef} key={element.id} {...element.props} />
81
- );
82
- yield* all(
83
- addAnimation({ elementRef: elementRef, element: element, view }),
84
- waitFor(Math.max(0, element.e - element.s))
85
- );
86
- yield elementRef().remove();
87
- },
88
- };