@brickgale/caption-sync 1.0.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.
Files changed (47) hide show
  1. package/LICENSE +21 -0
  2. package/README.md +261 -0
  3. package/dist/components/Video.d.ts +29 -0
  4. package/dist/components/Video.d.ts.map +1 -0
  5. package/dist/components/Video.js +243 -0
  6. package/dist/components/Video.js.map +1 -0
  7. package/dist/config.d.ts +148 -0
  8. package/dist/config.d.ts.map +1 -0
  9. package/dist/config.js +141 -0
  10. package/dist/config.js.map +1 -0
  11. package/dist/index.d.ts +33 -0
  12. package/dist/index.d.ts.map +1 -0
  13. package/dist/index.js +60 -0
  14. package/dist/index.js.map +1 -0
  15. package/dist/providers/elevenlabs-provider.d.ts +13 -0
  16. package/dist/providers/elevenlabs-provider.d.ts.map +1 -0
  17. package/dist/providers/elevenlabs-provider.js +117 -0
  18. package/dist/providers/elevenlabs-provider.js.map +1 -0
  19. package/dist/providers/index.d.ts +30 -0
  20. package/dist/providers/index.d.ts.map +1 -0
  21. package/dist/providers/index.js +47 -0
  22. package/dist/providers/index.js.map +1 -0
  23. package/dist/providers/openai-provider.d.ts +13 -0
  24. package/dist/providers/openai-provider.d.ts.map +1 -0
  25. package/dist/providers/openai-provider.js +85 -0
  26. package/dist/providers/openai-provider.js.map +1 -0
  27. package/dist/providers/transcription-provider.d.ts +37 -0
  28. package/dist/providers/transcription-provider.d.ts.map +1 -0
  29. package/dist/providers/transcription-provider.js +9 -0
  30. package/dist/providers/transcription-provider.js.map +1 -0
  31. package/dist/types/index.d.ts +106 -0
  32. package/dist/types/index.d.ts.map +1 -0
  33. package/dist/types/index.js +6 -0
  34. package/dist/types/index.js.map +1 -0
  35. package/dist/utils/caption-generator.d.ts +128 -0
  36. package/dist/utils/caption-generator.d.ts.map +1 -0
  37. package/dist/utils/caption-generator.js +400 -0
  38. package/dist/utils/caption-generator.js.map +1 -0
  39. package/dist/utils/transcription.d.ts +96 -0
  40. package/dist/utils/transcription.d.ts.map +1 -0
  41. package/dist/utils/transcription.js +280 -0
  42. package/dist/utils/transcription.js.map +1 -0
  43. package/dist/utils/video-renderer.d.ts +58 -0
  44. package/dist/utils/video-renderer.d.ts.map +1 -0
  45. package/dist/utils/video-renderer.js +153 -0
  46. package/dist/utils/video-renderer.js.map +1 -0
  47. package/package.json +80 -0
package/LICENSE ADDED
@@ -0,0 +1,21 @@
1
+ MIT License
2
+
3
+ Copyright (c) 2026 brickgale
4
+
5
+ Permission is hereby granted, free of charge, to any person obtaining a copy
6
+ of this software and associated documentation files (the "Software"), to deal
7
+ in the Software without restriction, including without limitation the rights
8
+ to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
9
+ copies of the Software, and to permit persons to whom the Software is
10
+ furnished to do so, subject to the following conditions:
11
+
12
+ The above copyright notice and this permission notice shall be included in all
13
+ copies or substantial portions of the Software.
14
+
15
+ THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
16
+ IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
17
+ FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
18
+ AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
19
+ LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
20
+ OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE
21
+ SOFTWARE.
package/README.md ADDED
@@ -0,0 +1,261 @@
1
+ # Caption Sync
2
+
3
+ Automated video generation with intelligent caption timing using Remotion.
4
+
5
+ **Use as:**
6
+
7
+ - 📦 **npm Library** - Import into your automation projects
8
+ - 🎬 **CLI Tool** - Run commands directly
9
+
10
+ ## Features
11
+
12
+ - ⏱️ **Accurate Timing**: Word-level timestamps from Whisper API for perfect sync
13
+ - 🎯 **Automatic Caption Timing**: Fast mode with weighted word-count distribution
14
+ - 📱 **Mobile-First**: Defaults to 1080x1920 vertical format (TikTok, Reels, Shorts)
15
+ - 🎬 **Green Screen Ready**: Pure green background for chroma key compositing
16
+ - 🤖 **Speech-to-Text**: Transcribe audio and generate captions from actual speech
17
+ - 📏 **Smart Character Limits**: Automatically split long captions at word boundaries
18
+ - 🎨 **Customizable Styling**: Configure colors, fonts, letter spacing, and animations
19
+ - 📍 **Flexible Placement**: Position captions at top, bottom, or center
20
+ - ✨ **Smooth Animations**: Fade, subtle slide-up, or no animation options
21
+ - 🎬 **Remotion-based**: Professional video output with React components
22
+ - 📚 **TypeScript Support**: Full type definitions included
23
+
24
+ ## Quick Start
25
+
26
+ ### As npm Library
27
+
28
+ ```bash
29
+ npm install @brickgale/caption-sync
30
+ ```
31
+
32
+ ```typescript
33
+ import { generateVideo } from '@brickgale/caption-sync';
34
+
35
+ generateVideo({
36
+ scriptPath: './script.txt',
37
+ audioPath: './audio.mp3',
38
+ outputPath: './out/video.mp4',
39
+ });
40
+ ```
41
+
42
+ **Requirements:** Node.js v20.6+, FFmpeg installed
43
+
44
+ 👉 **See [docs/LIBRARY.md](docs/LIBRARY.md) for complete API documentation**
45
+
46
+ ---
47
+
48
+ ### As CLI Tool
49
+
50
+ **Requirements:**
51
+
52
+ - Node.js v20.6+ (for built-in .env file support)
53
+ - FFmpeg ([install instructions](https://ffmpeg.org/download.html))
54
+ - Linux users: Chrome dependencies (see below)
55
+
56
+ **Install:**
57
+
58
+ ```bash
59
+ git clone <repository-url>
60
+ cd caption-sync
61
+ npm install
62
+ ```
63
+
64
+ **Install FFmpeg:**
65
+
66
+ ```bash
67
+ # macOS
68
+ brew install ffmpeg
69
+
70
+ # Ubuntu/Debian
71
+ sudo apt-get install ffmpeg
72
+ ```
73
+
74
+ **Linux: Chrome dependencies**
75
+
76
+ ```bash
77
+ sudo apt-get install -y libnss3 libnspr4 libatk1.0-0 libatk-bridge2.0-0 \
78
+ libcups2 libdrm2 libxkbcommon0 libxcomposite1 libxdamage1 libxfixes3 \
79
+ libxrandr2 libgbm1 libpango-1.0-0 libcairo2 libasound2
80
+ ```
81
+
82
+ **Generate video:**
83
+
84
+ ```bash
85
+ npm run generate
86
+ ```
87
+
88
+ **Two timing modes available:**
89
+
90
+ 1. **Fast mode (word-count estimation):**
91
+
92
+ ```bash
93
+ npm run generate-captions -- --max-characters=50
94
+ ```
95
+
96
+ 2. **Accurate mode (word-level timestamps from audio):**
97
+
98
+ ```bash
99
+ export OPENAI_API_KEY=your-api-key
100
+ npm run generate-captions -- --transcribe --max-characters=50
101
+ ```
102
+
103
+ ✨ **Recommended:** Uses Whisper API for perfect sync with actual speech timing
104
+
105
+ **Preview in Remotion Studio:**
106
+
107
+ ```bashASAA
108
+ npm run dev
109
+ ```
110
+
111
+ Output: `out/video.mp4`
112
+
113
+ ## Configuration
114
+
115
+ Caption Sync is highly customizable. You can configure:
116
+
117
+ - 🎨 **Styling**: Colors, fonts, shadows, backgrounds
118
+ - 📍 **Placement**: Top, bottom, or center captions
119
+ - ✨ **Animations**: Fade, slide-up, or none
120
+ - 📐 **Resolution**: Vertical, square, HD, oWAWA FHD presets
121
+ - 📏 **Character Limits**: Control caption length
122
+ - 🎬 **Chroma Key**: Green screen backgrounds for compositing
123
+
124
+ 👉 **See [docs/CONFIGURATION.md](docs/CONFIGURATION.md) for complete configuration guide**
125
+
126
+ ## Speech-to-Text
127
+
128
+ Use OpenAI Whisper or ElevenLabs to transcribe audio and get word-by-word highlighting:
129
+
130
+ ### Option 1: ElevenLabs Speech-to-Text (Default)
131
+
132
+ 1. Get an API key from [elevenlabs.io/app/settings/api-keys](https://elevenlabs.io/app/settings/api-keys)
133
+ 2. Add to `.env` file:
134
+ ```bash
135
+ echo "ELEVENLABS_API_KEY=your-api-key-here" > .env
136
+ ```
137
+ 3. Generate with word highlighting:
138
+ ```bash
139
+ npm run generate -- --transcribe
140
+ ```
141
+
142
+ ### Option 2: OpenAI Whisper
143
+
144
+ > ⚠️ **Note:** OpenAI provider is available but has not been tested yet. Use ElevenLabs for verified functionality.
145
+
146
+ 1. Get an API key from [platform.openai.com](https://platform.openai.com/api-keys)
147
+ 2. Add to `.env` file:
148
+ ```bash
149
+ echo "OPENAI_API_KEY=your-api-key-here" >> .env
150
+ ```
151
+ 3. Generate with OpenAI:
152
+ ```bash
153
+ npm run generate -- --transcribe --provider=openai
154
+ ```
155
+
156
+ 💡 **Node v20.6+ automatically loads `.env` files** - no extra packages needed!
157
+
158
+ ### Programmatic Usage with Providers
159
+
160
+ ```typescript
161
+ import {
162
+ generateCaptions,
163
+ createTranscriptionProvider,
164
+ } from '@brickgale/caption-sync';
165
+
166
+ // Using ElevenLabs (default - tested and verified)
167
+ const captions = await generateCaptions({
168
+ scriptPath: './script.txt',
169
+ audioPath: './audio.mp3',
170
+ useTranscription: true,
171
+ transcriptionProvider: 'elevenlabs',
172
+ elevenLabsApiKey: process.env.ELEVENLABS_API_KEY,
173
+ });
174
+
175
+ // Using OpenAI (available but untested)
176
+ const captions2 = await generateCaptions({
177
+ scriptPath: './script.txt',
178
+ audioPath: './audio.mp3',
179
+ useTranscription: true,
180
+ transcriptionProvider: 'openai',
181
+ openaiApiKey: process.env.OPENAI_API_KEY,
182
+ });
183
+
184
+ // Or use providers directly (Strategy Pattern)
185
+ const provider = createTranscriptionProvider({
186
+ provider: 'elevenlabs',
187
+ apiKey: process.env.ELEVENLABS_API_KEY!,
188
+ });
189
+ const words = await provider.transcribeWithTimestamps('./audio.mp3');
190
+ ```
191
+
192
+ ## How It Works
193
+
194
+ 1. **Text Processing**: Script is sanitized and split into phrases
195
+ 2. **Optional Transcription**: Audio transcribed with Whisper for accurate timing
196
+ 3. **Weighted Timing**: Duration distributed by word count
197
+ 4. **Video Rendering**: Remotion generates frames with animated captions
198
+ 5. **Output**: Final MP4 video with synced captions
199
+
200
+ ## Documentation
201
+
202
+ - **[Configuration Guide](docs/CONFIGURATION.md)** - Styling, placement, animations, and more
203
+ - **[Library API](docs/LIBRARY.md)** - Use as npm package in your projects
204
+ - **[Transcription Providers](docs/PROVIDERS.md)** - OpenAI, ElevenLabs, and adding new providers
205
+ - **[Examples](docs/EXAMPLES.md)** - Code examples and advanced usage
206
+ - **[Animation Guide](docs/ANIMATION_GUIDE.md)** - Caption animation options
207
+
208
+ ## Troubleshooting
209
+
210
+ - **FFmpeg not found**: Ensure FFmpeg is installed and in PATH
211
+ - **OpenAI API errors**: Check that `OPENAI_API_KEY` is set correctly in `.env`
212
+ - **Captions too long**: Reduce `MAX_CHARACTERS_PER_CAPTION` in [config](docs/CONFIGURATION.md)
213
+ - **Linux browser errors**: Install Chrome dependencies (see installation above)
214
+
215
+ ## Release Process
216
+
217
+ Releases are automated with GitHub Actions and publish to npm when a semantic version tag is pushed.
218
+
219
+ 1. Update code and docs, then run local checks:
220
+
221
+ ```bash
222
+ npm ci
223
+ npm run typecheck
224
+ npm run format:check
225
+ npm run compile
226
+ npm pack --dry-run
227
+ ```
228
+
229
+ 2. Bump version using npm:
230
+
231
+ ```bash
232
+ npm version patch
233
+ # or: npm version minor
234
+ # or: npm version major
235
+ ```
236
+
237
+ 3. Push commit and tag:
238
+
239
+ ```bash
240
+ git push origin main --follow-tags
241
+ ```
242
+
243
+ 4. GitHub Actions workflow [publish.yml](.github/workflows/publish.yml) validates the tag/version match and runs:
244
+
245
+ ```bash
246
+ npm publish --provenance --access public
247
+ ```
248
+
249
+ 5. Confirm the published package on npm and verify install in a clean project:
250
+
251
+ ```bash
252
+ npm i @brickgale/caption-sync
253
+ ```
254
+
255
+ ### Required Repository Secrets
256
+
257
+ - `NPM_TOKEN`: npm automation token with publish permissions for `caption-sync`
258
+
259
+ ## License
260
+
261
+ MIT
@@ -0,0 +1,29 @@
1
+ import React from 'react';
2
+ import type { VideoProps } from '../types';
3
+ /**
4
+ * Video component that renders captions with animations
5
+ *
6
+ * This is a Remotion component that displays captions synchronized
7
+ * with audio, featuring configurable animations (fade, slide-up, or none).
8
+ *
9
+ * @param props - Component props
10
+ * @param props.audioPath - Path to audio file
11
+ * @param props.captions - Array of captions with timing
12
+ * @param props.placement - Caption placement (center-top, center-bottom, center)
13
+ * @param props.backgroundColor - Background color for caption box
14
+ * @param props.canvasBackgroundColor - Background color for entire canvas
15
+ * @param props.textColor - Text color
16
+ * @param props.fontSize - Font size in pixels
17
+ * @param props.fontFamily - Font family
18
+ * @param props.googleFontsUrl - Google Fonts URL to load (null to skip)
19
+ * @param props.letterSpacing - Letter spacing
20
+ * @param props.textShadow - CSS text shadow string
21
+ * @param props.textStrokeWidth - Text stroke width (e.g., '2px')
22
+ * @param props.textStrokeColor - Text stroke color (e.g., 'black', '#000')
23
+ * @param props.animation - Animation preset (fade: fade in/out, slide-up: 5px upward movement, none: no animation)
24
+ * @param props.paddingBottom - Bottom padding for caption positioning (moves captions up from bottom)
25
+ * @param props.paddingHorizontal - Left/right padding for caption container (horizontal margins)
26
+ * @param props.maxWidth - Maximum width of caption text (e.g., '90vw', '800px')
27
+ */
28
+ export declare const Video: React.FC<VideoProps>;
29
+ //# sourceMappingURL=Video.d.ts.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"Video.d.ts","sourceRoot":"","sources":["../../src/lib/components/Video.tsx"],"names":[],"mappings":"AAAA,OAAO,KAAK,MAAM,OAAO,CAAC;AAY1B,OAAO,KAAK,EAAW,UAAU,EAAE,MAAM,UAAU,CAAC;AA4EpD;;;;;;;;;;;;;;;;;;;;;;;;GAwBG;AACH,eAAO,MAAM,KAAK,EAAE,KAAK,CAAC,EAAE,CAAC,UAAU,CA6NtC,CAAC"}
@@ -0,0 +1,243 @@
1
+ "use strict";
2
+ var __importDefault = (this && this.__importDefault) || function (mod) {
3
+ return (mod && mod.__esModule) ? mod : { "default": mod };
4
+ };
5
+ Object.defineProperty(exports, "__esModule", { value: true });
6
+ exports.Video = void 0;
7
+ const react_1 = __importDefault(require("react"));
8
+ const remotion_1 = require("remotion");
9
+ const config_1 = require("../config");
10
+ // Font loading state
11
+ let fontLoadedPromise = null;
12
+ let fontLoaded = false;
13
+ /**
14
+ * Initialize font loading
15
+ */
16
+ const initFontLoading = (googleFontsUrl, fontFamily, fontSize) => {
17
+ if (!googleFontsUrl || fontLoaded || fontLoadedPromise)
18
+ return fontLoadedPromise;
19
+ if (typeof document === 'undefined')
20
+ return Promise.resolve();
21
+ fontLoadedPromise = new Promise(resolve => {
22
+ // Add the stylesheet to the document
23
+ const link = document.createElement('link');
24
+ link.rel = 'stylesheet';
25
+ link.href = googleFontsUrl;
26
+ document.head.appendChild(link);
27
+ // Extract font family name from config (first part before comma)
28
+ const fontName = fontFamily.split(',')[0].replace(/['"]/g, '').trim();
29
+ // Wait for the specific font to load
30
+ const loadFont = async () => {
31
+ try {
32
+ // Load the font explicitly
33
+ await document.fonts.load(`${fontSize}px "${fontName}"`);
34
+ // Double-check all fonts are ready
35
+ await document.fonts.ready;
36
+ fontLoaded = true;
37
+ resolve();
38
+ }
39
+ catch (err) {
40
+ console.error('Error loading fonts:', err);
41
+ fontLoaded = true;
42
+ resolve();
43
+ }
44
+ };
45
+ loadFont();
46
+ });
47
+ return fontLoadedPromise;
48
+ };
49
+ /**
50
+ * Find the active caption for the current time
51
+ */
52
+ function getActiveCaption(captions, currentTime) {
53
+ return (captions.find(caption => currentTime >= caption.start && currentTime < caption.end) || null);
54
+ }
55
+ /**
56
+ * Calculate progress within a caption's duration (0 to 1)
57
+ */
58
+ function getCaptionProgress(caption, currentTime) {
59
+ const duration = caption.end - caption.start;
60
+ const elapsed = currentTime - caption.start;
61
+ return Math.max(0, Math.min(1, elapsed / duration));
62
+ }
63
+ /**
64
+ * Video component that renders captions with animations
65
+ *
66
+ * This is a Remotion component that displays captions synchronized
67
+ * with audio, featuring configurable animations (fade, slide-up, or none).
68
+ *
69
+ * @param props - Component props
70
+ * @param props.audioPath - Path to audio file
71
+ * @param props.captions - Array of captions with timing
72
+ * @param props.placement - Caption placement (center-top, center-bottom, center)
73
+ * @param props.backgroundColor - Background color for caption box
74
+ * @param props.canvasBackgroundColor - Background color for entire canvas
75
+ * @param props.textColor - Text color
76
+ * @param props.fontSize - Font size in pixels
77
+ * @param props.fontFamily - Font family
78
+ * @param props.googleFontsUrl - Google Fonts URL to load (null to skip)
79
+ * @param props.letterSpacing - Letter spacing
80
+ * @param props.textShadow - CSS text shadow string
81
+ * @param props.textStrokeWidth - Text stroke width (e.g., '2px')
82
+ * @param props.textStrokeColor - Text stroke color (e.g., 'black', '#000')
83
+ * @param props.animation - Animation preset (fade: fade in/out, slide-up: 5px upward movement, none: no animation)
84
+ * @param props.paddingBottom - Bottom padding for caption positioning (moves captions up from bottom)
85
+ * @param props.paddingHorizontal - Left/right padding for caption container (horizontal margins)
86
+ * @param props.maxWidth - Maximum width of caption text (e.g., '90vw', '800px')
87
+ */
88
+ const Video = ({ audioPath, captions, placement = config_1.CaptionConfig.PLACEMENT.CENTER_BOTTOM, backgroundColor = config_1.CaptionConfig.STYLE.captionBackgroundColor, canvasBackgroundColor = config_1.CaptionConfig.STYLE.canvasBackgroundColor, textColor = config_1.CaptionConfig.STYLE.textColor, fontSize = config_1.CaptionConfig.STYLE.fontSize, fontFamily = config_1.CaptionConfig.STYLE.fontFamily, googleFontsUrl = config_1.CaptionConfig.STYLE.googleFontsUrl, letterSpacing = config_1.CaptionConfig.STYLE.letterSpacing, textShadow = config_1.CaptionConfig.STYLE.textShadow, textStrokeWidth = config_1.CaptionConfig.STYLE.textStrokeWidth, textStrokeColor = config_1.CaptionConfig.STYLE.textStrokeColor, animation = config_1.CaptionConfig.STYLE.animation, paddingBottom = config_1.CaptionConfig.STYLE.paddingBottom, paddingHorizontal = config_1.CaptionConfig.STYLE.paddingHorizontal, maxWidth = config_1.CaptionConfig.STYLE.maxWidth, highlightColor = config_1.CaptionConfig.STYLE.highlightColor, }) => {
89
+ const frame = (0, remotion_1.useCurrentFrame)();
90
+ const { fps } = (0, remotion_1.useVideoConfig)();
91
+ // Wait for fonts to load before rendering - block until stylesheet AND fonts are ready
92
+ const [fontHandle] = react_1.default.useState(() => {
93
+ if (!googleFontsUrl)
94
+ return null;
95
+ const handle = (0, remotion_1.delayRender)('Loading fonts');
96
+ const loadFont = async () => {
97
+ if (typeof document === 'undefined') {
98
+ (0, remotion_1.continueRender)(handle);
99
+ return;
100
+ }
101
+ try {
102
+ // Check if stylesheet already exists
103
+ let link = document.querySelector(`link[href="${googleFontsUrl}"]`);
104
+ // If not, add it and wait for it to load
105
+ if (!link) {
106
+ link = document.createElement('link');
107
+ link.rel = 'stylesheet';
108
+ link.href = googleFontsUrl;
109
+ // Wait for stylesheet to load
110
+ await new Promise((resolve, reject) => {
111
+ link.onload = () => resolve();
112
+ link.onerror = () => reject(new Error('Failed to load stylesheet'));
113
+ document.head.appendChild(link);
114
+ // Timeout after 10 seconds
115
+ setTimeout(() => reject(new Error('Font loading timeout')), 10000);
116
+ });
117
+ }
118
+ // Extract font name and load it explicitly
119
+ const fontName = fontFamily.split(',')[0].replace(/['"]/g, '').trim();
120
+ // Try multiple times to ensure font is loaded
121
+ for (let i = 0; i < 3; i++) {
122
+ await document.fonts.load(`${fontSize}px "${fontName}"`);
123
+ await new Promise(resolve => setTimeout(resolve, 100));
124
+ }
125
+ // Final check: wait for all fonts to be ready
126
+ await document.fonts.ready;
127
+ fontLoaded = true;
128
+ (0, remotion_1.continueRender)(handle);
129
+ }
130
+ catch (err) {
131
+ console.error('Error loading fonts:', err);
132
+ fontLoaded = true;
133
+ (0, remotion_1.continueRender)(handle);
134
+ }
135
+ };
136
+ loadFont();
137
+ return handle;
138
+ });
139
+ // Calculate current time in seconds
140
+ const currentTime = frame / fps;
141
+ // Get active caption
142
+ const activeCaption = getActiveCaption(captions, currentTime);
143
+ // Calculate animation values based on animation preset
144
+ let opacity = 1;
145
+ let translateY = 0;
146
+ if (activeCaption && animation !== config_1.CaptionConfig.ANIMATION.NONE) {
147
+ const captionProgress = getCaptionProgress(activeCaption, currentTime);
148
+ const captionDuration = activeCaption.end - activeCaption.start;
149
+ // Faster animation for slide-up (0.15s), standard for fade (0.3s)
150
+ const animationDuration = animation === config_1.CaptionConfig.ANIMATION.SLIDE_UP ? 0.15 : 0.3;
151
+ const fadeInDuration = Math.min(animationDuration, captionDuration * 0.15);
152
+ const fadeOutDuration = Math.min(animationDuration, captionDuration * 0.15);
153
+ const fadeInProgress = captionDuration * captionProgress;
154
+ const fadeOutProgress = captionDuration - fadeInProgress;
155
+ // Apply fade animation (FADE only, not SLIDE_UP)
156
+ if (animation === config_1.CaptionConfig.ANIMATION.FADE) {
157
+ // Fade in
158
+ if (fadeInProgress < fadeInDuration) {
159
+ opacity = fadeInProgress / fadeInDuration;
160
+ }
161
+ // Fade out
162
+ else if (fadeOutProgress < fadeOutDuration) {
163
+ opacity = fadeOutProgress / fadeOutDuration;
164
+ }
165
+ else {
166
+ opacity = 1;
167
+ }
168
+ }
169
+ // Apply slide up animation (5px movement, no fade)
170
+ if (animation === config_1.CaptionConfig.ANIMATION.SLIDE_UP) {
171
+ if (fadeInProgress < fadeInDuration) {
172
+ // Slide up from 5px below to 0
173
+ translateY = 5 * (1 - fadeInProgress / fadeInDuration);
174
+ }
175
+ else if (fadeOutProgress < fadeOutDuration) {
176
+ // Stay at 0 during fadeout
177
+ translateY = 0;
178
+ }
179
+ else {
180
+ translateY = 0;
181
+ }
182
+ }
183
+ }
184
+ // Determine vertical alignment based on placement
185
+ const getVerticalAlignment = () => {
186
+ switch (placement) {
187
+ case config_1.CaptionConfig.PLACEMENT.CENTER_TOP:
188
+ return 'flex-start';
189
+ case config_1.CaptionConfig.PLACEMENT.CENTER_BOTTOM:
190
+ return 'flex-end';
191
+ case config_1.CaptionConfig.PLACEMENT.CENTER:
192
+ default:
193
+ return 'center';
194
+ }
195
+ };
196
+ return (react_1.default.createElement(remotion_1.AbsoluteFill, { style: { backgroundColor: canvasBackgroundColor } },
197
+ react_1.default.createElement(remotion_1.Audio, { src: audioPath.startsWith('/') || audioPath.includes(':\\')
198
+ ? audioPath
199
+ : (0, remotion_1.staticFile)(audioPath) }),
200
+ activeCaption && (react_1.default.createElement(remotion_1.AbsoluteFill, { style: {
201
+ justifyContent: getVerticalAlignment(),
202
+ alignItems: 'center',
203
+ padding: `40px ${paddingHorizontal} ${paddingBottom} ${paddingHorizontal}`,
204
+ } },
205
+ react_1.default.createElement("div", { style: {
206
+ opacity: opacity,
207
+ transform: `translateY(${translateY}px)`,
208
+ transition: 'none', // Animations handled by frame-by-frame calculations
209
+ } },
210
+ react_1.default.createElement("p", { style: {
211
+ color: textColor,
212
+ fontSize: `${fontSize}px`,
213
+ fontFamily: fontFamily,
214
+ letterSpacing: letterSpacing,
215
+ fontWeight: config_1.CaptionConfig.STYLE.fontWeight,
216
+ textAlign: 'center',
217
+ margin: 0,
218
+ padding: backgroundColor ? config_1.CaptionConfig.STYLE.padding : '0',
219
+ backgroundColor: backgroundColor || 'transparent',
220
+ borderRadius: backgroundColor
221
+ ? config_1.CaptionConfig.STYLE.borderRadius
222
+ : '0',
223
+ textShadow: textShadow,
224
+ WebkitTextStrokeWidth: textStrokeWidth || '0',
225
+ WebkitTextStrokeColor: textStrokeColor || 'transparent',
226
+ maxWidth: maxWidth,
227
+ wordWrap: 'break-word',
228
+ } }, activeCaption.words && activeCaption.words.length > 0
229
+ ? activeCaption.words.map((wordObj, index) => {
230
+ // Check if this word should be highlighted based on current time
231
+ const isActive = currentTime >= wordObj.start && currentTime < wordObj.end;
232
+ return (react_1.default.createElement("span", { key: index, style: {
233
+ color: isActive ? highlightColor : textColor,
234
+ transition: 'none',
235
+ } },
236
+ wordObj.word,
237
+ index < activeCaption.words.length - 1 ? ' ' : ''));
238
+ })
239
+ : // Fallback: render plain text if no word timings
240
+ activeCaption.text))))));
241
+ };
242
+ exports.Video = Video;
243
+ //# sourceMappingURL=Video.js.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"Video.js","sourceRoot":"","sources":["../../src/lib/components/Video.tsx"],"names":[],"mappings":"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"}