@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
@@ -0,0 +1,400 @@
1
+ "use strict";
2
+ var __createBinding = (this && this.__createBinding) || (Object.create ? (function(o, m, k, k2) {
3
+ if (k2 === undefined) k2 = k;
4
+ var desc = Object.getOwnPropertyDescriptor(m, k);
5
+ if (!desc || ("get" in desc ? !m.__esModule : desc.writable || desc.configurable)) {
6
+ desc = { enumerable: true, get: function() { return m[k]; } };
7
+ }
8
+ Object.defineProperty(o, k2, desc);
9
+ }) : (function(o, m, k, k2) {
10
+ if (k2 === undefined) k2 = k;
11
+ o[k2] = m[k];
12
+ }));
13
+ var __setModuleDefault = (this && this.__setModuleDefault) || (Object.create ? (function(o, v) {
14
+ Object.defineProperty(o, "default", { enumerable: true, value: v });
15
+ }) : function(o, v) {
16
+ o["default"] = v;
17
+ });
18
+ var __importStar = (this && this.__importStar) || (function () {
19
+ var ownKeys = function(o) {
20
+ ownKeys = Object.getOwnPropertyNames || function (o) {
21
+ var ar = [];
22
+ for (var k in o) if (Object.prototype.hasOwnProperty.call(o, k)) ar[ar.length] = k;
23
+ return ar;
24
+ };
25
+ return ownKeys(o);
26
+ };
27
+ return function (mod) {
28
+ if (mod && mod.__esModule) return mod;
29
+ var result = {};
30
+ if (mod != null) for (var k = ownKeys(mod), i = 0; i < k.length; i++) if (k[i] !== "default") __createBinding(result, mod, k[i]);
31
+ __setModuleDefault(result, mod);
32
+ return result;
33
+ };
34
+ })();
35
+ Object.defineProperty(exports, "__esModule", { value: true });
36
+ exports.getAudioDuration = getAudioDuration;
37
+ exports.splitIntoPhrases = splitIntoPhrases;
38
+ exports.countWords = countWords;
39
+ exports.splitByCharacterLimit = splitByCharacterLimit;
40
+ exports.generateCaptionsFromWordTimestamps = generateCaptionsFromWordTimestamps;
41
+ exports.generateCaptions = generateCaptions;
42
+ exports.generateCaptionsFromText = generateCaptionsFromText;
43
+ const fs = __importStar(require("fs"));
44
+ const child_process_1 = require("child_process");
45
+ const config_1 = require("../config");
46
+ const transcription_1 = require("./transcription");
47
+ const providers_1 = require("../providers");
48
+ /**
49
+ * Get audio duration using ffmpeg
50
+ *
51
+ * @param audioPath - Path to audio file
52
+ * @returns Duration in seconds
53
+ *
54
+ * @example
55
+ * ```typescript
56
+ * const duration = getAudioDuration('./audio.mp3');
57
+ * console.log(`Audio is ${duration} seconds long`);
58
+ * ```
59
+ */
60
+ function getAudioDuration(audioPath) {
61
+ try {
62
+ const output = (0, child_process_1.execSync)(`ffmpeg -i "${audioPath}" 2>&1 | grep "Duration" | cut -d ' ' -f 4 | sed s/,//`, { encoding: 'utf8' });
63
+ // Parse duration format HH:MM:SS.ms
64
+ const [hours, minutes, seconds] = output.trim().split(':');
65
+ const totalSeconds = parseInt(hours) * 3600 + parseInt(minutes) * 60 + parseFloat(seconds);
66
+ return totalSeconds;
67
+ }
68
+ catch (error) {
69
+ throw new Error(`Failed to get audio duration: ${error}`);
70
+ }
71
+ }
72
+ /**
73
+ * Split text into phrases based on punctuation and natural breaks
74
+ *
75
+ * @param text - Text to split into phrases
76
+ * @returns Array of phrases
77
+ *
78
+ * @example
79
+ * ```typescript
80
+ * const phrases = splitIntoPhrases("Hello world. This is a test.");
81
+ * // Returns: ["Hello world", "This is a test"]
82
+ * ```
83
+ */
84
+ function splitIntoPhrases(text) {
85
+ const phrases = text
86
+ .split(/([.!?;]\s+|\n+)/)
87
+ .filter(phrase => phrase.trim().length > 0)
88
+ .map(phrase => phrase.trim())
89
+ .filter(phrase => phrase !== '.' && phrase !== '!' && phrase !== '?' && phrase !== ';');
90
+ return phrases;
91
+ }
92
+ /**
93
+ * Count words in a phrase
94
+ *
95
+ * @param phrase - Phrase to count words in
96
+ * @returns Number of words
97
+ *
98
+ * @example
99
+ * ```typescript
100
+ * const count = countWords("Hello world");
101
+ * // Returns: 2
102
+ * ```
103
+ */
104
+ function countWords(phrase) {
105
+ return phrase.split(/\s+/).filter(word => word.length > 0).length;
106
+ }
107
+ /**
108
+ * Split long phrases into smaller chunks based on character limit
109
+ *
110
+ * @param phrase - Phrase to split
111
+ * @param maxCharacters - Maximum characters per chunk
112
+ * @returns Array of smaller phrases
113
+ *
114
+ * @example
115
+ * ```typescript
116
+ * const chunks = splitByCharacterLimit("This is a very long sentence that needs to be split", 20);
117
+ * // Returns: ["This is a very long", "sentence that needs", "to be split"]
118
+ * ```
119
+ */
120
+ function splitByCharacterLimit(phrase, maxCharacters) {
121
+ if (phrase.length <= maxCharacters) {
122
+ return [phrase];
123
+ }
124
+ const words = phrase.split(/\s+/);
125
+ const chunks = [];
126
+ let currentChunk = '';
127
+ for (const word of words) {
128
+ const testChunk = currentChunk ? `${currentChunk} ${word}` : word;
129
+ if (testChunk.length <= maxCharacters) {
130
+ currentChunk = testChunk;
131
+ }
132
+ else {
133
+ if (currentChunk) {
134
+ chunks.push(currentChunk);
135
+ }
136
+ currentChunk = word;
137
+ }
138
+ }
139
+ if (currentChunk) {
140
+ chunks.push(currentChunk);
141
+ }
142
+ return chunks;
143
+ }
144
+ /**
145
+ * Check if a word is likely a proper noun (name, place, etc.)
146
+ * Uses simple heuristics: capitalized mid-sentence, common name patterns
147
+ */
148
+ function isLikelyProperNoun(word, index) {
149
+ // Skip first word (always capitalized)
150
+ if (index === 0)
151
+ return false;
152
+ // Check if word starts with capital letter
153
+ const firstChar = word.charAt(0);
154
+ return firstChar === firstChar.toUpperCase() && /[A-Z]/.test(firstChar);
155
+ }
156
+ /**
157
+ * Check if a word is a semantic break point (conjunctions, transition words)
158
+ */
159
+ function isSemanticBreak(word) {
160
+ const breakWords = new Set([
161
+ 'and',
162
+ 'but',
163
+ 'or',
164
+ 'so',
165
+ 'then',
166
+ 'now',
167
+ 'because',
168
+ 'however',
169
+ 'therefore',
170
+ 'meanwhile',
171
+ 'finally',
172
+ 'also',
173
+ 'plus',
174
+ 'yet',
175
+ ]);
176
+ return breakWords.has(word.toLowerCase());
177
+ }
178
+ /**
179
+ * Calculate pause duration between two consecutive words
180
+ */
181
+ function getPauseBetween(prevWord, nextWord) {
182
+ return nextWord.start - prevWord.end;
183
+ }
184
+ /**
185
+ * Generate captions from word-level timestamps with intelligent splitting
186
+ *
187
+ * Groups words into captions based on:
188
+ * - Character limits (hard constraint)
189
+ * - Natural pauses in speech (preferred break points)
190
+ * - Semantic boundaries (proper nouns, conjunctions)
191
+ * - Minimum words per caption (avoid too-short captions)
192
+ *
193
+ * @param words - Array of words with timestamps
194
+ * @param maxCharacters - Maximum characters per caption
195
+ * @returns Array of captions with accurate timing
196
+ *
197
+ * @example
198
+ * ```typescript
199
+ * const words = [
200
+ * { word: "Hello", start: 0.0, end: 0.5 },
201
+ * { word: "world", start: 0.6, end: 1.0 }
202
+ * ];
203
+ * const captions = generateCaptionsFromWordTimestamps(words, 50);
204
+ * ```
205
+ */
206
+ function generateCaptionsFromWordTimestamps(words, maxCharacters) {
207
+ const captions = [];
208
+ let currentWords = [];
209
+ let currentText = '';
210
+ const minPauseDuration = config_1.CaptionConfig.MIN_PAUSE_DURATION;
211
+ const minWordsPerCaption = config_1.CaptionConfig.MIN_WORDS_PER_CAPTION;
212
+ for (let i = 0; i < words.length; i++) {
213
+ const wordObj = words[i];
214
+ const word = wordObj.word.trim();
215
+ const testText = currentText ? `${currentText} ${word}` : word;
216
+ // Calculate pause before this word
217
+ const pauseBefore = currentWords.length > 0
218
+ ? getPauseBetween(currentWords[currentWords.length - 1], wordObj)
219
+ : 0;
220
+ // Check various break conditions
221
+ const hasLongPause = pauseBefore >= minPauseDuration;
222
+ const isProperNoun = isLikelyProperNoun(word, i);
223
+ const isSemanticBoundary = isSemanticBreak(word);
224
+ const hasMinWords = currentWords.length >= minWordsPerCaption;
225
+ const wouldExceedLimit = testText.length > maxCharacters;
226
+ // Decide whether to break here
227
+ const shouldBreak = wouldExceedLimit ||
228
+ (hasMinWords && hasLongPause) ||
229
+ (hasMinWords &&
230
+ isProperNoun &&
231
+ currentText.length > maxCharacters * 0.5) ||
232
+ (hasMinWords &&
233
+ isSemanticBoundary &&
234
+ currentText.length > maxCharacters * 0.6);
235
+ if (shouldBreak && currentWords.length > 0) {
236
+ // Save current caption with word timings for highlighting
237
+ captions.push({
238
+ text: currentText,
239
+ start: currentWords[0].start,
240
+ end: currentWords[currentWords.length - 1].end,
241
+ words: [...currentWords],
242
+ });
243
+ // Start new caption with current word
244
+ currentText = word;
245
+ currentWords = [wordObj];
246
+ }
247
+ else {
248
+ // Add word to current caption
249
+ currentText = testText;
250
+ currentWords.push(wordObj);
251
+ }
252
+ }
253
+ // Add the last caption with word timings
254
+ if (currentWords.length > 0) {
255
+ captions.push({
256
+ text: currentText,
257
+ start: currentWords[0].start,
258
+ end: currentWords[currentWords.length - 1].end,
259
+ words: [...currentWords],
260
+ });
261
+ }
262
+ return captions;
263
+ }
264
+ /**
265
+ * Generate caption timing based on weighted distribution
266
+ *
267
+ * This function reads a script file and audio file, then generates
268
+ * caption timing by distributing the audio duration proportionally
269
+ * based on the word count in each phrase.
270
+ *
271
+ * @param options - Configuration options
272
+ * @param options.scriptPath - Path to script text file
273
+ * @param options.audioPath - Path to audio file
274
+ * @param options.maxCharacters - Maximum characters per caption (default: from config)
275
+ * @param options.useTranscription - Use speech-to-text to verify/correct script (default: false)
276
+ * @param options.openaiApiKey - OpenAI API key for transcription
277
+ * @returns Array of captions with timing
278
+ *
279
+ * @example
280
+ * ```typescript
281
+ * import { generateCaptions } from 'caption-sync';
282
+ *
283
+ * const captions = generateCaptions({
284
+ * scriptPath: './script.txt',
285
+ * audioPath: './audio.mp3',
286
+ * maxCharacters: 50
287
+ * });
288
+ *
289
+ * console.log(captions);
290
+ * // [{ start: 0, end: 2.5, text: "Hello world." }, ...]
291
+ * ```
292
+ */
293
+ async function generateCaptions(options) {
294
+ const { scriptPath, audioPath, maxCharacters = config_1.CaptionConfig.MAX_CHARACTERS_PER_CAPTION, useTranscription = false, openaiApiKey, transcriptionProvider = 'elevenlabs', elevenLabsApiKey, } = options;
295
+ // Use transcription with word-level timestamps for accurate timing
296
+ if (useTranscription) {
297
+ // Determine which API key to use
298
+ const apiKey = transcriptionProvider === 'elevenlabs' ? elevenLabsApiKey : openaiApiKey;
299
+ if (!apiKey) {
300
+ throw new Error(`API key required for ${transcriptionProvider} transcription. ` +
301
+ `Set ${transcriptionProvider === 'elevenlabs' ? 'ELEVENLABS_API_KEY' : 'OPENAI_API_KEY'} ` +
302
+ `environment variable or pass the appropriate apiKey option.`);
303
+ }
304
+ console.log(`🎤 Transcribing audio with word-level timestamps using ${transcriptionProvider}...`);
305
+ // Create provider and get word timestamps
306
+ const provider = (0, providers_1.createTranscriptionProvider)({
307
+ provider: transcriptionProvider,
308
+ apiKey,
309
+ });
310
+ const words = await provider.transcribeWithTimestamps(audioPath);
311
+ console.log(`✅ Got ${words.length} words with timestamps`);
312
+ // Generate captions from word timestamps (accurate timing)
313
+ const captions = generateCaptionsFromWordTimestamps(words, maxCharacters);
314
+ console.log(`📝 Generated ${captions.length} captions with accurate timing`);
315
+ return captions;
316
+ }
317
+ // Fallback: Use script file with weighted word-count distribution
318
+ let scriptText = fs.readFileSync(scriptPath, 'utf-8');
319
+ scriptText = (0, transcription_1.sanitizeText)(scriptText);
320
+ // Get audio duration
321
+ const totalDuration = getAudioDuration(audioPath);
322
+ // Split into phrases
323
+ let phrases = splitIntoPhrases(scriptText);
324
+ if (phrases.length === 0) {
325
+ throw new Error('No phrases found in script');
326
+ }
327
+ // Apply character limit by splitting long phrases
328
+ const splitPhrases = [];
329
+ for (const phrase of phrases) {
330
+ const chunks = splitByCharacterLimit(phrase, maxCharacters);
331
+ splitPhrases.push(...chunks);
332
+ }
333
+ phrases = splitPhrases;
334
+ // Calculate word counts for weighted timing
335
+ const wordCounts = phrases.map(countWords);
336
+ const totalWords = wordCounts.reduce((sum, count) => sum + count, 0);
337
+ // Generate captions with weighted timing
338
+ const captions = [];
339
+ let currentTime = 0;
340
+ for (let i = 0; i < phrases.length; i++) {
341
+ const phrase = phrases[i];
342
+ const wordCount = wordCounts[i];
343
+ // Calculate duration based on word count proportion
344
+ const duration = (wordCount / totalWords) * totalDuration;
345
+ captions.push({
346
+ start: currentTime,
347
+ end: currentTime + duration,
348
+ text: phrase,
349
+ });
350
+ currentTime += duration;
351
+ }
352
+ return captions;
353
+ }
354
+ /**
355
+ * Generate captions from text content directly (no file I/O)
356
+ *
357
+ * Useful when you already have the script content in memory
358
+ * and don't want to write it to a file first.
359
+ *
360
+ * @param scriptText - The script text content
361
+ * @param audioPath - Path to audio file
362
+ * @returns Array of captions with timing
363
+ *
364
+ * @example
365
+ * ```typescript
366
+ * import { generateCaptionsFromText } from 'caption-sync';
367
+ *
368
+ * const script = "Hello world. This is a test.";
369
+ * const captions = generateCaptionsFromText(script, "./audio.mp3");
370
+ * ```
371
+ */
372
+ function generateCaptionsFromText(scriptText, audioPath) {
373
+ // Get audio duration
374
+ const totalDuration = getAudioDuration(audioPath);
375
+ // Split into phrases
376
+ const phrases = splitIntoPhrases(scriptText);
377
+ if (phrases.length === 0) {
378
+ throw new Error('No phrases found in script');
379
+ }
380
+ // Calculate word counts for weighted timing
381
+ const wordCounts = phrases.map(countWords);
382
+ const totalWords = wordCounts.reduce((sum, count) => sum + count, 0);
383
+ // Generate captions with weighted timing
384
+ const captions = [];
385
+ let currentTime = 0;
386
+ for (let i = 0; i < phrases.length; i++) {
387
+ const phrase = phrases[i];
388
+ const wordCount = wordCounts[i];
389
+ // Calculate duration based on word count proportion
390
+ const duration = (wordCount / totalWords) * totalDuration;
391
+ captions.push({
392
+ start: currentTime,
393
+ end: currentTime + duration,
394
+ text: phrase,
395
+ });
396
+ currentTime += duration;
397
+ }
398
+ return captions;
399
+ }
400
+ //# sourceMappingURL=caption-generator.js.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"caption-generator.js","sourceRoot":"","sources":["../../src/lib/utils/caption-generator.ts"],"names":[],"mappings":"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"}
@@ -0,0 +1,96 @@
1
+ import type { TranscribeAudioOptions, TextComparisonResult } from '../types';
2
+ /**
3
+ * Word with timestamp information
4
+ */
5
+ export interface WordTimestamp {
6
+ word: string;
7
+ start: number;
8
+ end: number;
9
+ }
10
+ /**
11
+ * Transcribe audio to text using OpenAI Whisper
12
+ *
13
+ * @param options - Transcription options
14
+ * @returns Transcribed text
15
+ *
16
+ * @example
17
+ * ```typescript
18
+ * const text = await transcribeAudio({
19
+ * audioPath: './audio.mp3',
20
+ * openaiApiKey: process.env.OPENAI_API_KEY!,
21
+ * });
22
+ * console.log('Transcribed:', text);
23
+ * ```
24
+ */
25
+ export declare function transcribeAudio(options: TranscribeAudioOptions): Promise<string>;
26
+ /**
27
+ * Transcribe audio with word-level timestamps using OpenAI Whisper
28
+ *
29
+ * @param options - Transcription options
30
+ * @returns Array of words with timestamps
31
+ *
32
+ * @example
33
+ * ```typescript
34
+ * const words = await transcribeAudioWithTimestamps({
35
+ * audioPath: './audio.mp3',
36
+ * openaiApiKey: process.env.OPENAI_API_KEY!,
37
+ * });
38
+ * console.log('First word:', words[0]);
39
+ * // { word: "Hello", start: 0.5, end: 0.8 }
40
+ * ```
41
+ */
42
+ export declare function transcribeAudioWithTimestamps(options: TranscribeAudioOptions): Promise<WordTimestamp[]>;
43
+ /**
44
+ * Sanitize text by removing unwanted characters
45
+ * Keeps only alphanumeric characters, spaces, and basic punctuation
46
+ *
47
+ * @param text - Text to sanitize
48
+ * @returns Sanitized text
49
+ *
50
+ * @example
51
+ * ```typescript
52
+ * const clean = sanitizeText("Hello—world! [test]");
53
+ * // Returns: "Hello world! test"
54
+ * ```
55
+ */
56
+ export declare function sanitizeText(text: string): string;
57
+ /**
58
+ * Compare original script with transcribed audio
59
+ *
60
+ * @param originalText - Original script text
61
+ * @param transcribedText - Transcribed audio text
62
+ * @returns Comparison result with differences and similarity score
63
+ *
64
+ * @example
65
+ * ```typescript
66
+ * const result = compareTexts(originalScript, transcribedText);
67
+ * console.log(`Similarity: ${(result.similarity * 100).toFixed(1)}%`);
68
+ * console.log('Differences:', result.differences);
69
+ * ```
70
+ */
71
+ export declare function compareTexts(originalText: string, transcribedText: string): TextComparisonResult;
72
+ /**
73
+ * Generate corrected script based on transcription
74
+ *
75
+ * @param scriptPath - Path to original script file
76
+ * @param audioPath - Path to audio file
77
+ * @param openaiApiKey - OpenAI API key for transcription
78
+ * @returns Comparison result with corrections
79
+ *
80
+ * @example
81
+ * ```typescript
82
+ * const result = await generateCorrections({
83
+ * scriptPath: './script.txt',
84
+ * audioPath: './audio.mp3',
85
+ * openaiApiKey: process.env.OPENAI_API_KEY!
86
+ * });
87
+ *
88
+ * console.log('Corrections needed:', result.differences);
89
+ * ```
90
+ */
91
+ export declare function generateCorrections(options: {
92
+ scriptPath: string;
93
+ audioPath: string;
94
+ openaiApiKey: string;
95
+ }): Promise<TextComparisonResult>;
96
+ //# sourceMappingURL=transcription.d.ts.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"transcription.d.ts","sourceRoot":"","sources":["../../src/lib/utils/transcription.ts"],"names":[],"mappings":"AAEA,OAAO,KAAK,EAAE,sBAAsB,EAAE,oBAAoB,EAAE,MAAM,UAAU,CAAC;AAG7E;;GAEG;AACH,MAAM,WAAW,aAAa;IAC5B,IAAI,EAAE,MAAM,CAAC;IACb,KAAK,EAAE,MAAM,CAAC;IACd,GAAG,EAAE,MAAM,CAAC;CACb;AAED;;;;;;;;;;;;;;GAcG;AACH,wBAAsB,eAAe,CACnC,OAAO,EAAE,sBAAsB,GAC9B,OAAO,CAAC,MAAM,CAAC,CAkCjB;AAED;;;;;;;;;;;;;;;GAeG;AACH,wBAAsB,6BAA6B,CACjD,OAAO,EAAE,sBAAsB,GAC9B,OAAO,CAAC,aAAa,EAAE,CAAC,CA8C1B;AAED;;;;;;;;;;;;GAYG;AACH,wBAAgB,YAAY,CAAC,IAAI,EAAE,MAAM,GAAG,MAAM,CAUjD;AAgED;;;;;;;;;;;;;GAaG;AACH,wBAAgB,YAAY,CAC1B,YAAY,EAAE,MAAM,EACpB,eAAe,EAAE,MAAM,GACtB,oBAAoB,CAsBtB;AAED;;;;;;;;;;;;;;;;;;GAkBG;AACH,wBAAsB,mBAAmB,CAAC,OAAO,EAAE;IACjD,UAAU,EAAE,MAAM,CAAC;IACnB,SAAS,EAAE,MAAM,CAAC;IAClB,YAAY,EAAE,MAAM,CAAC;CACtB,GAAG,OAAO,CAAC,oBAAoB,CAAC,CAgChC"}