@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.
- package/LICENSE +21 -0
- package/README.md +261 -0
- package/dist/components/Video.d.ts +29 -0
- package/dist/components/Video.d.ts.map +1 -0
- package/dist/components/Video.js +243 -0
- package/dist/components/Video.js.map +1 -0
- package/dist/config.d.ts +148 -0
- package/dist/config.d.ts.map +1 -0
- package/dist/config.js +141 -0
- package/dist/config.js.map +1 -0
- package/dist/index.d.ts +33 -0
- package/dist/index.d.ts.map +1 -0
- package/dist/index.js +60 -0
- package/dist/index.js.map +1 -0
- package/dist/providers/elevenlabs-provider.d.ts +13 -0
- package/dist/providers/elevenlabs-provider.d.ts.map +1 -0
- package/dist/providers/elevenlabs-provider.js +117 -0
- package/dist/providers/elevenlabs-provider.js.map +1 -0
- package/dist/providers/index.d.ts +30 -0
- package/dist/providers/index.d.ts.map +1 -0
- package/dist/providers/index.js +47 -0
- package/dist/providers/index.js.map +1 -0
- package/dist/providers/openai-provider.d.ts +13 -0
- package/dist/providers/openai-provider.d.ts.map +1 -0
- package/dist/providers/openai-provider.js +85 -0
- package/dist/providers/openai-provider.js.map +1 -0
- package/dist/providers/transcription-provider.d.ts +37 -0
- package/dist/providers/transcription-provider.d.ts.map +1 -0
- package/dist/providers/transcription-provider.js +9 -0
- package/dist/providers/transcription-provider.js.map +1 -0
- package/dist/types/index.d.ts +106 -0
- package/dist/types/index.d.ts.map +1 -0
- package/dist/types/index.js +6 -0
- package/dist/types/index.js.map +1 -0
- package/dist/utils/caption-generator.d.ts +128 -0
- package/dist/utils/caption-generator.d.ts.map +1 -0
- package/dist/utils/caption-generator.js +400 -0
- package/dist/utils/caption-generator.js.map +1 -0
- package/dist/utils/transcription.d.ts +96 -0
- package/dist/utils/transcription.d.ts.map +1 -0
- package/dist/utils/transcription.js +280 -0
- package/dist/utils/transcription.js.map +1 -0
- package/dist/utils/video-renderer.d.ts +58 -0
- package/dist/utils/video-renderer.d.ts.map +1 -0
- package/dist/utils/video-renderer.js +153 -0
- package/dist/utils/video-renderer.js.map +1 -0
- 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":";;;;;;AAAA,kDAA0B;AAC1B,uCAUkB;AAElB,sCAA0C;AAE1C,qBAAqB;AACrB,IAAI,iBAAiB,GAAyB,IAAI,CAAC;AACnD,IAAI,UAAU,GAAG,KAAK,CAAC;AAEvB;;GAEG;AACH,MAAM,eAAe,GAAG,CACtB,cAA6B,EAC7B,UAAkB,EAClB,QAAgB,EAChB,EAAE;IACF,IAAI,CAAC,cAAc,IAAI,UAAU,IAAI,iBAAiB;QACpD,OAAO,iBAAiB,CAAC;IAC3B,IAAI,OAAO,QAAQ,KAAK,WAAW;QAAE,OAAO,OAAO,CAAC,OAAO,EAAE,CAAC;IAE9D,iBAAiB,GAAG,IAAI,OAAO,CAAO,OAAO,CAAC,EAAE;QAC9C,qCAAqC;QACrC,MAAM,IAAI,GAAG,QAAQ,CAAC,aAAa,CAAC,MAAM,CAAC,CAAC;QAC5C,IAAI,CAAC,GAAG,GAAG,YAAY,CAAC;QACxB,IAAI,CAAC,IAAI,GAAG,cAAc,CAAC;QAC3B,QAAQ,CAAC,IAAI,CAAC,WAAW,CAAC,IAAI,CAAC,CAAC;QAEhC,iEAAiE;QACjE,MAAM,QAAQ,GAAG,UAAU,CAAC,KAAK,CAAC,GAAG,CAAC,CAAC,CAAC,CAAC,CAAC,OAAO,CAAC,OAAO,EAAE,EAAE,CAAC,CAAC,IAAI,EAAE,CAAC;QAEtE,qCAAqC;QACrC,MAAM,QAAQ,GAAG,KAAK,IAAI,EAAE;YAC1B,IAAI,CAAC;gBACH,2BAA2B;gBAC3B,MAAM,QAAQ,CAAC,KAAK,CAAC,IAAI,CAAC,GAAG,QAAQ,OAAO,QAAQ,GAAG,CAAC,CAAC;gBAEzD,mCAAmC;gBACnC,MAAM,QAAQ,CAAC,KAAK,CAAC,KAAK,CAAC;gBAE3B,UAAU,GAAG,IAAI,CAAC;gBAClB,OAAO,EAAE,CAAC;YACZ,CAAC;YAAC,OAAO,GAAG,EAAE,CAAC;gBACb,OAAO,CAAC,KAAK,CAAC,sBAAsB,EAAE,GAAG,CAAC,CAAC;gBAC3C,UAAU,GAAG,IAAI,CAAC;gBAClB,OAAO,EAAE,CAAC;YACZ,CAAC;QACH,CAAC,CAAC;QAEF,QAAQ,EAAE,CAAC;IACb,CAAC,CAAC,CAAC;IAEH,OAAO,iBAAiB,CAAC;AAC3B,CAAC,CAAC;AAEF;;GAEG;AACH,SAAS,gBAAgB,CACvB,QAAmB,EACnB,WAAmB;IAEnB,OAAO,CACL,QAAQ,CAAC,IAAI,CACX,OAAO,CAAC,EAAE,CAAC,WAAW,IAAI,OAAO,CAAC,KAAK,IAAI,WAAW,GAAG,OAAO,CAAC,GAAG,CACrE,IAAI,IAAI,CACV,CAAC;AACJ,CAAC;AAED;;GAEG;AACH,SAAS,kBAAkB,CAAC,OAAgB,EAAE,WAAmB;IAC/D,MAAM,QAAQ,GAAG,OAAO,CAAC,GAAG,GAAG,OAAO,CAAC,KAAK,CAAC;IAC7C,MAAM,OAAO,GAAG,WAAW,GAAG,OAAO,CAAC,KAAK,CAAC;IAC5C,OAAO,IAAI,CAAC,GAAG,CAAC,CAAC,EAAE,IAAI,CAAC,GAAG,CAAC,CAAC,EAAE,OAAO,GAAG,QAAQ,CAAC,CAAC,CAAC;AACtD,CAAC;AAED;;;;;;;;;;;;;;;;;;;;;;;;GAwBG;AACI,MAAM,KAAK,GAAyB,CAAC,EAC1C,SAAS,EACT,QAAQ,EACR,SAAS,GAAG,sBAAa,CAAC,SAAS,CAAC,aAAa,EACjD,eAAe,GAAG,sBAAa,CAAC,KAAK,CAAC,sBAAsB,EAC5D,qBAAqB,GAAG,sBAAa,CAAC,KAAK,CAAC,qBAAqB,EACjE,SAAS,GAAG,sBAAa,CAAC,KAAK,CAAC,SAAS,EACzC,QAAQ,GAAG,sBAAa,CAAC,KAAK,CAAC,QAAQ,EACvC,UAAU,GAAG,sBAAa,CAAC,KAAK,CAAC,UAAU,EAC3C,cAAc,GAAG,sBAAa,CAAC,KAAK,CAAC,cAAc,EACnD,aAAa,GAAG,sBAAa,CAAC,KAAK,CAAC,aAAa,EACjD,UAAU,GAAG,sBAAa,CAAC,KAAK,CAAC,UAAU,EAC3C,eAAe,GAAG,sBAAa,CAAC,KAAK,CAAC,eAAe,EACrD,eAAe,GAAG,sBAAa,CAAC,KAAK,CAAC,eAAe,EACrD,SAAS,GAAG,sBAAa,CAAC,KAAK,CAAC,SAAS,EACzC,aAAa,GAAG,sBAAa,CAAC,KAAK,CAAC,aAAa,EACjD,iBAAiB,GAAG,sBAAa,CAAC,KAAK,CAAC,iBAAiB,EACzD,QAAQ,GAAG,sBAAa,CAAC,KAAK,CAAC,QAAQ,EACvC,cAAc,GAAG,sBAAa,CAAC,KAAK,CAAC,cAAc,GACpD,EAAE,EAAE;IACH,MAAM,KAAK,GAAG,IAAA,0BAAe,GAAE,CAAC;IAChC,MAAM,EAAE,GAAG,EAAE,GAAG,IAAA,yBAAc,GAAE,CAAC;IAEjC,uFAAuF;IACvF,MAAM,CAAC,UAAU,CAAC,GAAG,eAAK,CAAC,QAAQ,CAAC,GAAG,EAAE;QACvC,IAAI,CAAC,cAAc;YAAE,OAAO,IAAI,CAAC;QAEjC,MAAM,MAAM,GAAG,IAAA,sBAAW,EAAC,eAAe,CAAC,CAAC;QAE5C,MAAM,QAAQ,GAAG,KAAK,IAAI,EAAE;YAC1B,IAAI,OAAO,QAAQ,KAAK,WAAW,EAAE,CAAC;gBACpC,IAAA,yBAAc,EAAC,MAAM,CAAC,CAAC;gBACvB,OAAO;YACT,CAAC;YAED,IAAI,CAAC;gBACH,qCAAqC;gBACrC,IAAI,IAAI,GAAG,QAAQ,CAAC,aAAa,CAC/B,cAAc,cAAc,IAAI,CACd,CAAC;gBAErB,yCAAyC;gBACzC,IAAI,CAAC,IAAI,EAAE,CAAC;oBACV,IAAI,GAAG,QAAQ,CAAC,aAAa,CAAC,MAAM,CAAC,CAAC;oBACtC,IAAI,CAAC,GAAG,GAAG,YAAY,CAAC;oBACxB,IAAI,CAAC,IAAI,GAAG,cAAc,CAAC;oBAE3B,8BAA8B;oBAC9B,MAAM,IAAI,OAAO,CAAO,CAAC,OAAO,EAAE,MAAM,EAAE,EAAE;wBAC1C,IAAI,CAAC,MAAM,GAAG,GAAG,EAAE,CAAC,OAAO,EAAE,CAAC;wBAC9B,IAAI,CAAC,OAAO,GAAG,GAAG,EAAE,CAAC,MAAM,CAAC,IAAI,KAAK,CAAC,2BAA2B,CAAC,CAAC,CAAC;wBACpE,QAAQ,CAAC,IAAI,CAAC,WAAW,CAAC,IAAI,CAAC,CAAC;wBAEhC,2BAA2B;wBAC3B,UAAU,CAAC,GAAG,EAAE,CAAC,MAAM,CAAC,IAAI,KAAK,CAAC,sBAAsB,CAAC,CAAC,EAAE,KAAK,CAAC,CAAC;oBACrE,CAAC,CAAC,CAAC;gBACL,CAAC;gBAED,2CAA2C;gBAC3C,MAAM,QAAQ,GAAG,UAAU,CAAC,KAAK,CAAC,GAAG,CAAC,CAAC,CAAC,CAAC,CAAC,OAAO,CAAC,OAAO,EAAE,EAAE,CAAC,CAAC,IAAI,EAAE,CAAC;gBAEtE,8CAA8C;gBAC9C,KAAK,IAAI,CAAC,GAAG,CAAC,EAAE,CAAC,GAAG,CAAC,EAAE,CAAC,EAAE,EAAE,CAAC;oBAC3B,MAAM,QAAQ,CAAC,KAAK,CAAC,IAAI,CAAC,GAAG,QAAQ,OAAO,QAAQ,GAAG,CAAC,CAAC;oBACzD,MAAM,IAAI,OAAO,CAAC,OAAO,CAAC,EAAE,CAAC,UAAU,CAAC,OAAO,EAAE,GAAG,CAAC,CAAC,CAAC;gBACzD,CAAC;gBAED,8CAA8C;gBAC9C,MAAM,QAAQ,CAAC,KAAK,CAAC,KAAK,CAAC;gBAE3B,UAAU,GAAG,IAAI,CAAC;gBAClB,IAAA,yBAAc,EAAC,MAAM,CAAC,CAAC;YACzB,CAAC;YAAC,OAAO,GAAG,EAAE,CAAC;gBACb,OAAO,CAAC,KAAK,CAAC,sBAAsB,EAAE,GAAG,CAAC,CAAC;gBAC3C,UAAU,GAAG,IAAI,CAAC;gBAClB,IAAA,yBAAc,EAAC,MAAM,CAAC,CAAC;YACzB,CAAC;QACH,CAAC,CAAC;QAEF,QAAQ,EAAE,CAAC;QACX,OAAO,MAAM,CAAC;IAChB,CAAC,CAAC,CAAC;IAEH,oCAAoC;IACpC,MAAM,WAAW,GAAG,KAAK,GAAG,GAAG,CAAC;IAEhC,qBAAqB;IACrB,MAAM,aAAa,GAAG,gBAAgB,CAAC,QAAQ,EAAE,WAAW,CAAC,CAAC;IAE9D,uDAAuD;IACvD,IAAI,OAAO,GAAG,CAAC,CAAC;IAChB,IAAI,UAAU,GAAG,CAAC,CAAC;IAEnB,IAAI,aAAa,IAAI,SAAS,KAAK,sBAAa,CAAC,SAAS,CAAC,IAAI,EAAE,CAAC;QAChE,MAAM,eAAe,GAAG,kBAAkB,CAAC,aAAa,EAAE,WAAW,CAAC,CAAC;QACvE,MAAM,eAAe,GAAG,aAAa,CAAC,GAAG,GAAG,aAAa,CAAC,KAAK,CAAC;QAChE,kEAAkE;QAClE,MAAM,iBAAiB,GACrB,SAAS,KAAK,sBAAa,CAAC,SAAS,CAAC,QAAQ,CAAC,CAAC,CAAC,IAAI,CAAC,CAAC,CAAC,GAAG,CAAC;QAC9D,MAAM,cAAc,GAAG,IAAI,CAAC,GAAG,CAAC,iBAAiB,EAAE,eAAe,GAAG,IAAI,CAAC,CAAC;QAC3E,MAAM,eAAe,GAAG,IAAI,CAAC,GAAG,CAAC,iBAAiB,EAAE,eAAe,GAAG,IAAI,CAAC,CAAC;QAC5E,MAAM,cAAc,GAAG,eAAe,GAAG,eAAe,CAAC;QACzD,MAAM,eAAe,GAAG,eAAe,GAAG,cAAc,CAAC;QAEzD,iDAAiD;QACjD,IAAI,SAAS,KAAK,sBAAa,CAAC,SAAS,CAAC,IAAI,EAAE,CAAC;YAC/C,UAAU;YACV,IAAI,cAAc,GAAG,cAAc,EAAE,CAAC;gBACpC,OAAO,GAAG,cAAc,GAAG,cAAc,CAAC;YAC5C,CAAC;YACD,WAAW;iBACN,IAAI,eAAe,GAAG,eAAe,EAAE,CAAC;gBAC3C,OAAO,GAAG,eAAe,GAAG,eAAe,CAAC;YAC9C,CAAC;iBAAM,CAAC;gBACN,OAAO,GAAG,CAAC,CAAC;YACd,CAAC;QACH,CAAC;QAED,mDAAmD;QACnD,IAAI,SAAS,KAAK,sBAAa,CAAC,SAAS,CAAC,QAAQ,EAAE,CAAC;YACnD,IAAI,cAAc,GAAG,cAAc,EAAE,CAAC;gBACpC,+BAA+B;gBAC/B,UAAU,GAAG,CAAC,GAAG,CAAC,CAAC,GAAG,cAAc,GAAG,cAAc,CAAC,CAAC;YACzD,CAAC;iBAAM,IAAI,eAAe,GAAG,eAAe,EAAE,CAAC;gBAC7C,2BAA2B;gBAC3B,UAAU,GAAG,CAAC,CAAC;YACjB,CAAC;iBAAM,CAAC;gBACN,UAAU,GAAG,CAAC,CAAC;YACjB,CAAC;QACH,CAAC;IACH,CAAC;IAED,kDAAkD;IAClD,MAAM,oBAAoB,GAAG,GAAG,EAAE;QAChC,QAAQ,SAAS,EAAE,CAAC;YAClB,KAAK,sBAAa,CAAC,SAAS,CAAC,UAAU;gBACrC,OAAO,YAAY,CAAC;YACtB,KAAK,sBAAa,CAAC,SAAS,CAAC,aAAa;gBACxC,OAAO,UAAU,CAAC;YACpB,KAAK,sBAAa,CAAC,SAAS,CAAC,MAAM,CAAC;YACpC;gBACE,OAAO,QAAQ,CAAC;QACpB,CAAC;IACH,CAAC,CAAC;IAEF,OAAO,CACL,8BAAC,uBAAY,IAAC,KAAK,EAAE,EAAE,eAAe,EAAE,qBAAqB,EAAE;QAE7D,8BAAC,gBAAK,IACJ,GAAG,EACD,SAAS,CAAC,UAAU,CAAC,GAAG,CAAC,IAAI,SAAS,CAAC,QAAQ,CAAC,KAAK,CAAC;gBACpD,CAAC,CAAC,SAAS;gBACX,CAAC,CAAC,IAAA,qBAAU,EAAC,SAAS,CAAC,GAE3B;QAGD,aAAa,IAAI,CAChB,8BAAC,uBAAY,IACX,KAAK,EAAE;gBACL,cAAc,EAAE,oBAAoB,EAAE;gBACtC,UAAU,EAAE,QAAQ;gBACpB,OAAO,EAAE,QAAQ,iBAAiB,IAAI,aAAa,IAAI,iBAAiB,EAAE;aAC3E;YAED,uCACE,KAAK,EAAE;oBACL,OAAO,EAAE,OAAO;oBAChB,SAAS,EAAE,cAAc,UAAU,KAAK;oBACxC,UAAU,EAAE,MAAM,EAAE,oDAAoD;iBACzE;gBAED,qCACE,KAAK,EAAE;wBACL,KAAK,EAAE,SAAS;wBAChB,QAAQ,EAAE,GAAG,QAAQ,IAAI;wBACzB,UAAU,EAAE,UAAU;wBACtB,aAAa,EAAE,aAAa;wBAC5B,UAAU,EAAE,sBAAa,CAAC,KAAK,CAAC,UAAU;wBAC1C,SAAS,EAAE,QAAQ;wBACnB,MAAM,EAAE,CAAC;wBACT,OAAO,EAAE,eAAe,CAAC,CAAC,CAAC,sBAAa,CAAC,KAAK,CAAC,OAAO,CAAC,CAAC,CAAC,GAAG;wBAC5D,eAAe,EAAE,eAAe,IAAI,aAAa;wBACjD,YAAY,EAAE,eAAe;4BAC3B,CAAC,CAAC,sBAAa,CAAC,KAAK,CAAC,YAAY;4BAClC,CAAC,CAAC,GAAG;wBACP,UAAU,EAAE,UAAU;wBACtB,qBAAqB,EAAE,eAAe,IAAI,GAAG;wBAC7C,qBAAqB,EAAE,eAAe,IAAI,aAAa;wBACvD,QAAQ,EAAE,QAAQ;wBAClB,QAAQ,EAAE,YAAY;qBACvB,IAGA,aAAa,CAAC,KAAK,IAAI,aAAa,CAAC,KAAK,CAAC,MAAM,GAAG,CAAC;oBACpD,CAAC,CAAC,aAAa,CAAC,KAAK,CAAC,GAAG,CAAC,CAAC,OAAO,EAAE,KAAK,EAAE,EAAE;wBACzC,iEAAiE;wBACjE,MAAM,QAAQ,GACZ,WAAW,IAAI,OAAO,CAAC,KAAK,IAAI,WAAW,GAAG,OAAO,CAAC,GAAG,CAAC;wBAE5D,OAAO,CACL,wCACE,GAAG,EAAE,KAAK,EACV,KAAK,EAAE;gCACL,KAAK,EAAE,QAAQ,CAAC,CAAC,CAAC,cAAc,CAAC,CAAC,CAAC,SAAS;gCAC5C,UAAU,EAAE,MAAM;6BACnB;4BAEA,OAAO,CAAC,IAAI;4BACZ,KAAK,GAAG,aAAa,CAAC,KAAM,CAAC,MAAM,GAAG,CAAC,CAAC,CAAC,CAAC,GAAG,CAAC,CAAC,CAAC,EAAE,CAC9C,CACR,CAAC;oBACJ,CAAC,CAAC;oBACJ,CAAC,CAAC,iDAAiD;wBACjD,aAAa,CAAC,IAAI,CACpB,CACA,CACO,CAChB,CACY,CAChB,CAAC;AACJ,CAAC,CAAC;AA7NW,QAAA,KAAK,SA6NhB"}
|