3v324v23 commited on
Commit
1e8c195
·
1 Parent(s): 569f90a

fix caption and text render

Browse files
Files changed (1) hide show
  1. src/player/items/caption-word.tsx +265 -17
src/player/items/caption-word.tsx CHANGED
@@ -1,13 +1,103 @@
1
- /**
2
- * CaptionWord - SSR-friendly version
3
- *
4
- * Simplified version for SSR rendering without client-side hooks.
5
- */
6
-
7
  import React from "react";
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
8
 
9
  interface CaptionWordProps {
10
- word: { word: string; start: number; end: number; is_keyword?: boolean };
11
  offsetFrom: number;
12
  activeColor: string;
13
  activeFillColor: string;
@@ -26,24 +116,182 @@ interface CaptionWordProps {
26
 
27
  export const CaptionWord: React.FC<CaptionWordProps> = ({
28
  word,
 
 
 
 
29
  color,
 
 
 
 
30
  scaleFactor,
 
 
 
 
31
  }) => {
32
- // SSR version - simple text rendering without animations
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
33
  return (
34
- <span
 
 
 
 
 
 
35
  style={{
36
- position: "relative",
37
- display: "inline-block",
38
- padding: "0 0.2em",
39
- color: color,
40
- borderRadius: `${scaleFactor * 16}px`,
41
- zIndex: 99,
42
  }}
 
 
 
43
  >
44
- {word.word}
45
- </span>
46
  );
47
  };
48
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
49
  export default CaptionWord;
 
 
 
 
 
 
 
1
  import React from "react";
2
+ import styled from "@emotion/styled";
3
+ import { css, keyframes } from "@emotion/react";
4
+ import { useCurrentFrame } from "remotion";
5
+ import { ANIMATION_CAPTION_LIST } from "./caption-animations";
6
+ import {
7
+ createAnimationFunctions,
8
+ ANIMATION_CONFIGS,
9
+ ANIMATION_FUNCTIONS,
10
+ WordAnimationState
11
+ } from "./caption-word-animations";
12
+
13
+ const scalePulse = keyframes`
14
+ 0% { transform: scale(1); }
15
+ 50% { transform: scale(1.2); }
16
+ 100% { transform: scale(1); }
17
+ `;
18
+
19
+ interface WordSpanProps {
20
+ isActive: boolean;
21
+ activeFillColor: string;
22
+ wordColor: string;
23
+ scale: number;
24
+ animation: string;
25
+ isAppeared: boolean;
26
+ scaleFactor: number;
27
+ animationNoneCaption: boolean;
28
+ showObject: string;
29
+ }
30
+
31
+ const WordSpan = styled.span<WordSpanProps>`
32
+ position: relative;
33
+ display: inline-block;
34
+ padding: 0 0.2em;
35
+ color: ${(props) => props.wordColor};
36
+ scale: ${(props) => props.scale};
37
+ border-radius: 16px;
38
+ z-index: 99;
39
+ transition: opacity 0.2s ease;
40
+
41
+ ${(props) => {
42
+ if (props.isActive && props.animation.includes("underline-effect")) {
43
+ return `
44
+ text-decoration: underline;
45
+ text-decoration-color: #9238ef;
46
+ text-decoration-thickness: 0.2em;
47
+ `;
48
+ }
49
+
50
+ if (!props.isActive && props.animationNoneCaption) {
51
+ return `display: none;`;
52
+ }
53
+
54
+ if (
55
+ !props.isAppeared &&
56
+ (ANIMATION_CAPTION_LIST.includes(props.animation) ||
57
+ props.showObject === "word")
58
+ ) {
59
+ return `display: none;`;
60
+ }
61
+
62
+ if (!props.isActive && props.animation === "customAnimation1") {
63
+ return `display: none;`;
64
+ }
65
+
66
+ return "";
67
+ }}
68
+
69
+ &::before {
70
+ content: "";
71
+ position: absolute;
72
+ z-index: -1;
73
+ left: -0.2em;
74
+ right: -0.2em;
75
+ top: 0;
76
+ bottom: 0;
77
+ transition: background-color 0.2s ease;
78
+ border-radius: ${(props) => `${props.scaleFactor * 16}px`};
79
+ }
80
+
81
+ ${(props) =>
82
+ props.isActive &&
83
+ css`
84
+ &::before {
85
+ background-color: ${props.activeFillColor};
86
+
87
+ ${props.animation === "captionAnimation10" ||
88
+ props.animation === "captionAnimationKeyword42" ||
89
+ props.animation === "captionAnimationKeyword57" ||
90
+ (props.animation === "captionAnimationKeyword48" &&
91
+ css`
92
+ animation: ${scalePulse} 0.4s ease-in-out;
93
+ transform-origin: center;
94
+ `)}
95
+ }
96
+ `}
97
+ `;
98
 
99
  interface CaptionWordProps {
100
+ word: any;
101
  offsetFrom: number;
102
  activeColor: string;
103
  activeFillColor: string;
 
116
 
117
  export const CaptionWord: React.FC<CaptionWordProps> = ({
118
  word,
119
+ offsetFrom,
120
+ activeColor,
121
+ activeFillColor,
122
+ appearedColor,
123
  color,
124
+ animation,
125
+ globalOpacity,
126
+ isKeywordColor,
127
+ preservedColorKeyWord,
128
  scaleFactor,
129
+ animationNoneCaption,
130
+ showObject,
131
+ lineIndex,
132
+ currentLine
133
  }) => {
134
+ const fps = 30;
135
+ // Use Remotion's useCurrentFrame for SSR compatibility
136
+ const currentFrame = useCurrentFrame();
137
+ const { start, end } = word;
138
+ const startAtFrame = ((start + offsetFrom) / 1000) * fps;
139
+ const endAtFrame = ((end + offsetFrom) / 1000) * fps;
140
+ const isActive = currentFrame > startAtFrame && currentFrame < endAtFrame;
141
+ const isAppeared = currentFrame > startAtFrame;
142
+
143
+ // Handle line-based visibility
144
+ if (
145
+ showObject === "line" &&
146
+ lineIndex !== undefined &&
147
+ currentLine !== undefined
148
+ ) {
149
+ if (lineIndex > currentLine) {
150
+ return null;
151
+ }
152
+ }
153
+
154
+ // Word color logic
155
+ const getWordColor = () => {
156
+ let baseColor = isActive ? activeColor : isAppeared ? appearedColor : color;
157
+
158
+ if (word.is_keyword && isKeywordColor !== "transparent") {
159
+ if (isActive || (preservedColorKeyWord && isAppeared)) {
160
+ return isKeywordColor;
161
+ }
162
+ }
163
+
164
+ return baseColor;
165
+ };
166
+
167
+ const wordColor = getWordColor();
168
+
169
+ // Calculate animation state
170
+ const animationState = calculateAnimationState(
171
+ currentFrame,
172
+ startAtFrame,
173
+ endAtFrame,
174
+ animation,
175
+ word,
176
+ globalOpacity
177
+ );
178
+
179
+ // Display text logic
180
+ const getDisplayText = () => {
181
+ if (animation.includes("typewriter-effect")) {
182
+ const totalLetters = word.word.length;
183
+ const animationDuration = endAtFrame - startAtFrame;
184
+ const lettersToShow = Math.min(
185
+ totalLetters,
186
+ Math.floor(
187
+ ((currentFrame - startAtFrame) / animationDuration) * totalLetters
188
+ )
189
+ );
190
+ return word.word.slice(0, lettersToShow);
191
+ }
192
+ return word.word;
193
+ };
194
+
195
+ const displayText = getDisplayText();
196
+
197
+ // Transform style helper
198
+ const getTransformStyle = () => {
199
+ const transforms = [];
200
+ if (animationState.translateX !== 0 || animationState.translateY !== 0) {
201
+ transforms.push(
202
+ `translate(${animationState.translateX}px, ${animationState.translateY}px)`
203
+ );
204
+ }
205
+ return transforms.length > 0 ? transforms.join(" ") : undefined;
206
+ };
207
+
208
  return (
209
+ <WordSpan
210
+ isActive={isActive}
211
+ wordColor={wordColor}
212
+ activeFillColor={activeFillColor}
213
+ scale={animationState.scale}
214
+ animation={animation}
215
+ animationNoneCaption={animationNoneCaption}
216
  style={{
217
+ opacity: animationState.opacity,
218
+ ...(getTransformStyle() && { transform: getTransformStyle() })
 
 
 
 
219
  }}
220
+ isAppeared={isAppeared}
221
+ scaleFactor={scaleFactor}
222
+ showObject={showObject}
223
  >
224
+ {displayText}
225
+ </WordSpan>
226
  );
227
  };
228
 
229
+ function calculateAnimationState(
230
+ currentFrame: number,
231
+ startAtFrame: number,
232
+ endAtFrame: number,
233
+ animation: string,
234
+ word: any,
235
+ globalOpacity?: number
236
+ ): WordAnimationState {
237
+ const initialState: WordAnimationState = {
238
+ opacity: 1,
239
+ scale: 1,
240
+ translateX: 0,
241
+ translateY: 0
242
+ };
243
+
244
+ // Apply basic animation effects
245
+ const basicEffects = {
246
+ scaleAnimationLetterEffect: () => ({
247
+ scale:
248
+ currentFrame > startAtFrame && currentFrame < endAtFrame ? 1.4 : 0.9
249
+ }),
250
+ animationScaleMinEffect: () => ({ scale: 0.8 }),
251
+ animationScaleDinamicEffect: () => ({ scale: word.is_keyword ? 1.4 : 0.9 }),
252
+ captionAnimation26: () => ({
253
+ opacity:
254
+ currentFrame > startAtFrame && currentFrame < endAtFrame ? 1 : 0.6
255
+ })
256
+ };
257
+
258
+ // Apply basic effects
259
+ Object.entries(basicEffects).forEach(([effect, handler]) => {
260
+ if (animation.includes(effect) || animation === effect) {
261
+ Object.assign(initialState, handler());
262
+ }
263
+ });
264
+
265
+ // Create animation helpers
266
+ const animationHelpers = createAnimationFunctions(
267
+ currentFrame,
268
+ startAtFrame,
269
+ endAtFrame
270
+ );
271
+
272
+ // Apply animation configurations
273
+ const animationConfig = ANIMATION_CONFIGS[animation];
274
+ if (animationConfig) {
275
+ const configResult = animationConfig(animationHelpers);
276
+ Object.assign(initialState, configResult);
277
+ }
278
+
279
+ // Apply animation functions from slash-separated animations
280
+ const selectedAnimations = animation.split("/") || [];
281
+ selectedAnimations.forEach((anim) => {
282
+ const animationFn = ANIMATION_FUNCTIONS[anim];
283
+ if (animationFn) {
284
+ const result = animationFn(animationHelpers);
285
+ Object.assign(initialState, result);
286
+ }
287
+ });
288
+
289
+ // Handle global opacity
290
+ if (globalOpacity !== undefined) {
291
+ initialState.opacity = globalOpacity;
292
+ }
293
+
294
+ return initialState;
295
+ }
296
+
297
  export default CaptionWord;