@twick/visualizer 0.15.19 → 0.15.20
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/project.js +61 -61
- package/package.json +8 -8
- package/src/caption-styles/highlight-bg.handler.tsx +60 -0
- package/src/caption-styles/index.ts +32 -0
- package/src/caption-styles/karaoke.handler.tsx +58 -0
- package/src/caption-styles/lower-third.handler.tsx +44 -0
- package/src/caption-styles/outline-only.handler.tsx +29 -0
- package/src/caption-styles/pop-scale.handler.tsx +35 -0
- package/src/caption-styles/registry.ts +32 -0
- package/src/caption-styles/soft-box.handler.tsx +40 -0
- package/src/caption-styles/types.ts +77 -0
- package/src/caption-styles/typewriter.handler.tsx +33 -0
- package/src/caption-styles/word-by-word-with-bg.handler.tsx +39 -0
- package/src/caption-styles/word-by-word.handler.tsx +29 -0
- package/src/components/track.tsx +38 -18
- package/src/elements/caption.element.tsx +23 -153
- package/src/helpers/constants.ts +119 -3
- package/src/helpers/types.ts +5 -0
- package/src/visualizer-grouped.ts +12 -0
|
@@ -1,176 +1,46 @@
|
|
|
1
1
|
import { CaptionProps, ElementParams } from "../helpers/types";
|
|
2
|
-
import {
|
|
3
|
-
import { Rect, Txt } from "@twick/2d";
|
|
2
|
+
import { ThreadGenerator } from "@twick/core";
|
|
4
3
|
import { splitPhraseTiming } from "../helpers/caption.utils";
|
|
5
|
-
import {
|
|
6
|
-
import { hexToRGB } from "../helpers/utils";
|
|
4
|
+
import { getCaptionStyleHandler, getDefaultCaptionStyleHandler } from "../caption-styles";
|
|
7
5
|
|
|
8
6
|
/**
|
|
9
7
|
* @group CaptionElement
|
|
10
8
|
* CaptionElement creates and manages styled text overlays in the visualizer scene.
|
|
11
|
-
*
|
|
12
|
-
* for professional video presentations and content creation.
|
|
13
|
-
*
|
|
14
|
-
* Features:
|
|
15
|
-
* - Styled text with custom fonts, colors, and backgrounds
|
|
16
|
-
* - Word-by-word timing and animation
|
|
17
|
-
* - Background highlighting and styling options
|
|
18
|
-
* - Text effects and animations
|
|
19
|
-
* - Automatic timing and synchronization
|
|
9
|
+
* Delegates rendering and animation to registered caption style handlers.
|
|
20
10
|
*
|
|
21
11
|
* @param containerRef - Reference to the container element
|
|
22
12
|
* @param caption - Caption configuration including text, styling, and timing
|
|
23
|
-
*
|
|
24
|
-
* @example
|
|
25
|
-
* ```js
|
|
26
|
-
* // Basic caption
|
|
27
|
-
* {
|
|
28
|
-
* id: "welcome-caption",
|
|
29
|
-
* type: "caption",
|
|
30
|
-
* s: 2,
|
|
31
|
-
* e: 8,
|
|
32
|
-
* t: "Welcome to our presentation!",
|
|
33
|
-
* props: {
|
|
34
|
-
* colors: {
|
|
35
|
-
* text: "#ffffff",
|
|
36
|
-
* background: "rgba(0,0,0,0.7)"
|
|
37
|
-
* },
|
|
38
|
-
* font: {
|
|
39
|
-
* family: "Arial",
|
|
40
|
-
* size: 48,
|
|
41
|
-
* weight: 600
|
|
42
|
-
* }
|
|
43
|
-
* }
|
|
44
|
-
* }
|
|
45
|
-
*
|
|
46
|
-
* // Caption with background highlighting
|
|
47
|
-
* {
|
|
48
|
-
* id: "highlighted-caption",
|
|
49
|
-
* type: "caption",
|
|
50
|
-
* s: 3,
|
|
51
|
-
* e: 10,
|
|
52
|
-
* t: "Important information",
|
|
53
|
-
* capStyle: "highlight_bg",
|
|
54
|
-
* props: {
|
|
55
|
-
* colors: {
|
|
56
|
-
* text: "#ffffff",
|
|
57
|
-
* background: "rgba(255,0,0,0.8)"
|
|
58
|
-
* },
|
|
59
|
-
* font: {
|
|
60
|
-
* family: "Helvetica",
|
|
61
|
-
* size: 36,
|
|
62
|
-
* weight: 700
|
|
63
|
-
* },
|
|
64
|
-
* bgOpacity: 0.9,
|
|
65
|
-
* bgOffsetWidth: 20,
|
|
66
|
-
* bgOffsetHeight: 10,
|
|
67
|
-
* bgMargin: [10, 5],
|
|
68
|
-
* bgRadius: 15,
|
|
69
|
-
* bgPadding: [20, 15]
|
|
70
|
-
* }
|
|
71
|
-
* }
|
|
72
|
-
* ```
|
|
73
13
|
*/
|
|
74
14
|
export const CaptionElement = {
|
|
75
15
|
name: "caption",
|
|
76
|
-
|
|
77
|
-
|
|
78
|
-
* Generator function that creates and manages caption elements in the scene.
|
|
79
|
-
* Handles caption creation, word timing, styling, and text effects.
|
|
80
|
-
*
|
|
81
|
-
* @param params - Element parameters including container reference and caption config
|
|
82
|
-
* @returns Generator that controls the caption element lifecycle
|
|
83
|
-
*
|
|
84
|
-
* @example
|
|
85
|
-
* ```js
|
|
86
|
-
* yield* CaptionElement.create({
|
|
87
|
-
* containerRef: mainContainer,
|
|
88
|
-
* caption: captionConfig
|
|
89
|
-
* });
|
|
90
|
-
* ```
|
|
91
|
-
*/
|
|
92
|
-
*create({ containerRef, caption, containerProps }: ElementParams) {
|
|
16
|
+
|
|
17
|
+
*create({ containerRef, caption, containerProps }: ElementParams): ThreadGenerator {
|
|
93
18
|
const words = splitPhraseTiming(caption);
|
|
94
|
-
|
|
95
|
-
|
|
96
|
-
|
|
97
|
-
|
|
98
|
-
let wordsState: {
|
|
99
|
-
refs: Array<{ bgRef?: Reference<any>; textRef: Reference<any> }>;
|
|
100
|
-
props: CaptionProps[];
|
|
101
|
-
idx: number;
|
|
102
|
-
prevTime: number;
|
|
103
|
-
} = {
|
|
104
|
-
refs: [],
|
|
105
|
-
props: [],
|
|
106
|
-
idx: 0,
|
|
107
|
-
prevTime: phraseStart,
|
|
108
|
-
};
|
|
19
|
+
if (!words?.length) return;
|
|
20
|
+
|
|
21
|
+
const handler =
|
|
22
|
+
getCaptionStyleHandler(caption.capStyle ?? "") ?? getDefaultCaptionStyleHandler();
|
|
109
23
|
|
|
110
|
-
// Set container properties
|
|
111
24
|
containerRef().maxWidth(containerProps?.maxWidth ?? "95%");
|
|
112
25
|
containerRef().wrap(containerProps?.wrap ?? "wrap");
|
|
113
26
|
containerRef().justifyContent(containerProps?.justifyContent ?? "center");
|
|
114
27
|
containerRef().alignItems(containerProps?.alignItems ?? "center");
|
|
115
28
|
|
|
116
|
-
|
|
117
|
-
|
|
118
|
-
|
|
119
|
-
|
|
120
|
-
containerRef().add(
|
|
121
|
-
<Txt ref={textRef} {...captionProps} text={word.t} opacity={0} />
|
|
122
|
-
);
|
|
123
|
-
if (caption.capStyle == "highlight_bg") {
|
|
124
|
-
const bgContainerRef = createRef();
|
|
125
|
-
const childTextRef = createRef();
|
|
126
|
-
const _color = new Color({...hexToRGB(captionProps.colors.bgColor), a: captionProps?.bgOpacity ?? 1});
|
|
127
|
-
containerRef().add(
|
|
128
|
-
<Rect
|
|
129
|
-
ref={bgContainerRef}
|
|
130
|
-
fill={_color}
|
|
131
|
-
width={textRef().width() + (captionProps.bgOffsetWidth ?? 30)}
|
|
132
|
-
height={textRef().height() + (captionProps.bgOffsetHeight ?? 10)}
|
|
133
|
-
margin={captionProps.bgMargin ?? [0, -5]}
|
|
134
|
-
radius={captionProps.bgRadius ?? 10}
|
|
135
|
-
padding={captionProps.bgPadding ?? [0, 15]}
|
|
136
|
-
opacity={0}
|
|
137
|
-
alignItems={"center"}
|
|
138
|
-
justifyContent={"center"}
|
|
139
|
-
layout
|
|
140
|
-
>
|
|
141
|
-
<Txt ref={childTextRef} {...captionProps} text={word.t} />
|
|
142
|
-
</Rect>
|
|
143
|
-
);
|
|
144
|
-
textRef().remove();
|
|
145
|
-
wordsState.refs.push({
|
|
146
|
-
bgRef: bgContainerRef,
|
|
147
|
-
textRef: childTextRef,
|
|
148
|
-
});
|
|
149
|
-
} else {
|
|
150
|
-
wordsState.refs.push({
|
|
151
|
-
textRef: textRef,
|
|
152
|
-
});
|
|
153
|
-
}
|
|
154
|
-
wordsState.prevTime = word.e;
|
|
155
|
-
wordsState.idx = wordsState.idx + 1;
|
|
156
|
-
}
|
|
29
|
+
const captionWithProps = {
|
|
30
|
+
...caption,
|
|
31
|
+
props: caption.props ?? ({} as CaptionProps),
|
|
32
|
+
};
|
|
157
33
|
|
|
158
|
-
|
|
159
|
-
|
|
34
|
+
const refs = handler.renderWords({
|
|
35
|
+
containerRef,
|
|
36
|
+
words,
|
|
37
|
+
caption: captionWithProps,
|
|
38
|
+
});
|
|
160
39
|
|
|
161
|
-
|
|
162
|
-
|
|
163
|
-
|
|
164
|
-
|
|
165
|
-
|
|
166
|
-
?.bgRef?.()
|
|
167
|
-
.fill(TRANSPARENT_COLOR, 0);
|
|
168
|
-
} else {
|
|
169
|
-
yield* wordsState.refs[wordsState.idx]?.textRef?.().opacity(1, 0);
|
|
170
|
-
yield* waitFor(Math.max(0, word.e - word.s));
|
|
171
|
-
}
|
|
172
|
-
wordsState.prevTime = word.e;
|
|
173
|
-
wordsState.idx = wordsState.idx + 1;
|
|
174
|
-
}
|
|
40
|
+
yield* handler.animateWords({
|
|
41
|
+
words,
|
|
42
|
+
refs,
|
|
43
|
+
caption: captionWithProps,
|
|
44
|
+
});
|
|
175
45
|
},
|
|
176
46
|
};
|
package/src/helpers/constants.ts
CHANGED
|
@@ -106,6 +106,122 @@ export const CAPTION_STYLE: Record<string, CaptionStyle> = {
|
|
|
106
106
|
fontSize: 46,
|
|
107
107
|
},
|
|
108
108
|
},
|
|
109
|
+
outline_only: {
|
|
110
|
+
rect: {
|
|
111
|
+
alignItems: "center",
|
|
112
|
+
justifyContent: "center",
|
|
113
|
+
gap: 8,
|
|
114
|
+
},
|
|
115
|
+
word: {
|
|
116
|
+
lineWidth: 0.5,
|
|
117
|
+
stroke: "#000000",
|
|
118
|
+
fontWeight: 600,
|
|
119
|
+
strokeFirst: true,
|
|
120
|
+
shadowOffset: [0, 0],
|
|
121
|
+
shadowBlur: 0,
|
|
122
|
+
fontFamily: "Arial",
|
|
123
|
+
fill: "#FFFFFF",
|
|
124
|
+
fontSize: 42,
|
|
125
|
+
},
|
|
126
|
+
},
|
|
127
|
+
soft_box: {
|
|
128
|
+
rect: {
|
|
129
|
+
alignItems: "center",
|
|
130
|
+
justifyContent: "center",
|
|
131
|
+
gap: 8,
|
|
132
|
+
padding: [12, 24],
|
|
133
|
+
radius: 12,
|
|
134
|
+
},
|
|
135
|
+
word: {
|
|
136
|
+
lineWidth: 0.2,
|
|
137
|
+
stroke: "#000000",
|
|
138
|
+
fontWeight: 600,
|
|
139
|
+
strokeFirst: true,
|
|
140
|
+
shadowOffset: [-1, 1],
|
|
141
|
+
shadowColor: "rgba(0,0,0,0.3)",
|
|
142
|
+
shadowBlur: 3,
|
|
143
|
+
fontFamily: "Montserrat",
|
|
144
|
+
fill: "#FFFFFF",
|
|
145
|
+
fontSize: 40,
|
|
146
|
+
},
|
|
147
|
+
},
|
|
148
|
+
lower_third: {
|
|
149
|
+
rect: {
|
|
150
|
+
alignItems: "center",
|
|
151
|
+
justifyContent: "center",
|
|
152
|
+
gap: 8,
|
|
153
|
+
padding: [14, 32],
|
|
154
|
+
radius: 0,
|
|
155
|
+
},
|
|
156
|
+
word: {
|
|
157
|
+
lineWidth: 0.2,
|
|
158
|
+
stroke: "#000000",
|
|
159
|
+
fontWeight: 600,
|
|
160
|
+
strokeFirst: true,
|
|
161
|
+
shadowOffset: [0, 1],
|
|
162
|
+
shadowColor: "rgba(0,0,0,0.5)",
|
|
163
|
+
shadowBlur: 2,
|
|
164
|
+
fontFamily: "Arial",
|
|
165
|
+
fill: "#FFFFFF",
|
|
166
|
+
fontSize: 38,
|
|
167
|
+
},
|
|
168
|
+
},
|
|
169
|
+
typewriter: {
|
|
170
|
+
rect: {
|
|
171
|
+
alignItems: "center",
|
|
172
|
+
justifyContent: "center",
|
|
173
|
+
gap: 8,
|
|
174
|
+
},
|
|
175
|
+
word: {
|
|
176
|
+
lineWidth: 0.3,
|
|
177
|
+
stroke: "#000000",
|
|
178
|
+
fontWeight: 600,
|
|
179
|
+
strokeFirst: true,
|
|
180
|
+
shadowOffset: [0, 0],
|
|
181
|
+
shadowBlur: 0,
|
|
182
|
+
fontFamily: "Monaco",
|
|
183
|
+
fill: "#FFFFFF",
|
|
184
|
+
fontSize: 40,
|
|
185
|
+
},
|
|
186
|
+
},
|
|
187
|
+
karaoke: {
|
|
188
|
+
rect: {
|
|
189
|
+
alignItems: "center",
|
|
190
|
+
justifyContent: "center",
|
|
191
|
+
gap: 8,
|
|
192
|
+
},
|
|
193
|
+
word: {
|
|
194
|
+
lineWidth: 0.35,
|
|
195
|
+
stroke: "#000000",
|
|
196
|
+
fontWeight: 700,
|
|
197
|
+
strokeFirst: true,
|
|
198
|
+
shadowOffset: [-2, 2],
|
|
199
|
+
shadowColor: "#000000",
|
|
200
|
+
shadowBlur: 4,
|
|
201
|
+
fontFamily: "Bangers",
|
|
202
|
+
fill: "#FFFFFF",
|
|
203
|
+
fontSize: 46,
|
|
204
|
+
},
|
|
205
|
+
},
|
|
206
|
+
pop_scale: {
|
|
207
|
+
rect: {
|
|
208
|
+
alignItems: "center",
|
|
209
|
+
justifyContent: "center",
|
|
210
|
+
gap: 8,
|
|
211
|
+
},
|
|
212
|
+
word: {
|
|
213
|
+
lineWidth: 0.35,
|
|
214
|
+
stroke: "#000000",
|
|
215
|
+
fontWeight: 700,
|
|
216
|
+
strokeFirst: true,
|
|
217
|
+
shadowOffset: [-2, 2],
|
|
218
|
+
shadowColor: "#000000",
|
|
219
|
+
shadowBlur: 5,
|
|
220
|
+
fontFamily: "Bangers",
|
|
221
|
+
fill: "#FFFFFF",
|
|
222
|
+
fontSize: 46,
|
|
223
|
+
},
|
|
224
|
+
},
|
|
109
225
|
};
|
|
110
226
|
|
|
111
227
|
export const DEFAULT_CAPTION_COLORS = {
|
|
@@ -114,9 +230,9 @@ export const DEFAULT_CAPTION_COLORS = {
|
|
|
114
230
|
};
|
|
115
231
|
|
|
116
232
|
export const DEFAULT_CAPTION_FONT = {
|
|
117
|
-
family: "
|
|
118
|
-
size:
|
|
119
|
-
weight:
|
|
233
|
+
family: "Bangers",
|
|
234
|
+
size: 46,
|
|
235
|
+
weight: 700,
|
|
120
236
|
};
|
|
121
237
|
|
|
122
238
|
export const TRANSPARENT_COLOR = "#FFFFFF00";
|
package/src/helpers/types.ts
CHANGED
|
@@ -180,9 +180,14 @@ export type CaptionProps = {
|
|
|
180
180
|
* Defines text color, background color, and highlight colors.
|
|
181
181
|
*/
|
|
182
182
|
export type CaptionColors = {
|
|
183
|
+
/** Main text fill color */
|
|
183
184
|
text?: string;
|
|
185
|
+
/** Background color behind words/boxes */
|
|
184
186
|
bgColor?: string;
|
|
187
|
+
/** Color used for per-word highlights (e.g. karaoke-style) */
|
|
185
188
|
highlight?: string;
|
|
189
|
+
/** Stroke/outline color around text (e.g. classic outline style) */
|
|
190
|
+
outlineColor?: string;
|
|
186
191
|
};
|
|
187
192
|
|
|
188
193
|
/**
|
|
@@ -52,6 +52,18 @@ export { AudioElement } from './elements/audio.element';
|
|
|
52
52
|
export { TextElement } from './elements/text.element';
|
|
53
53
|
export { CaptionElement } from './elements/caption.element';
|
|
54
54
|
|
|
55
|
+
/**
|
|
56
|
+
* @group Caption Styles
|
|
57
|
+
* @description Plugin-based caption style handlers for extensible caption rendering
|
|
58
|
+
*/
|
|
59
|
+
export {
|
|
60
|
+
registerCaptionStyle,
|
|
61
|
+
registerCaptionStyles,
|
|
62
|
+
getCaptionStyleHandler,
|
|
63
|
+
getDefaultCaptionStyleHandler,
|
|
64
|
+
} from './caption-styles';
|
|
65
|
+
export type { CaptionStyleHandler, WordRefs, WordTiming } from './caption-styles';
|
|
66
|
+
|
|
55
67
|
// Shape and UI elements
|
|
56
68
|
export { RectElement } from './elements/rect.element';
|
|
57
69
|
export { CircleElement } from './elements/circle.element';
|