agentic-qe 2.4.0 → 2.5.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/.claude/agents/qe-a11y-ally.md +751 -0
- package/.claude/agents/qx-partner.md +120 -4
- package/.claude/skills/testability-scoring/SKILL.md +107 -6
- package/CHANGELOG.md +86 -0
- package/README.md +7 -6
- package/dist/agents/AccessibilityAllyAgent.d.ts +168 -0
- package/dist/agents/AccessibilityAllyAgent.d.ts.map +1 -0
- package/dist/agents/AccessibilityAllyAgent.js +462 -0
- package/dist/agents/AccessibilityAllyAgent.js.map +1 -0
- package/dist/agents/SONAIntegration.d.ts +109 -0
- package/dist/agents/SONAIntegration.d.ts.map +1 -0
- package/dist/agents/SONAIntegration.js +167 -0
- package/dist/agents/SONAIntegration.js.map +1 -0
- package/dist/agents/index.d.ts +3 -0
- package/dist/agents/index.d.ts.map +1 -1
- package/dist/agents/index.js +93 -2
- package/dist/agents/index.js.map +1 -1
- package/dist/cli/init/agents.js +1 -1
- package/dist/cli/init/claude-config.js +2 -2
- package/dist/cli/init/database-init.js +1 -1
- package/dist/core/cache/BinaryCacheImpl.d.ts +161 -0
- package/dist/core/cache/BinaryCacheImpl.d.ts.map +1 -0
- package/dist/core/cache/BinaryCacheImpl.js +685 -0
- package/dist/core/cache/BinaryCacheImpl.js.map +1 -0
- package/dist/core/cache/BinaryMetadataCache.d.ts +244 -0
- package/dist/core/cache/BinaryMetadataCache.d.ts.map +1 -1
- package/dist/core/cache/BinaryMetadataCache.js +63 -1
- package/dist/core/cache/BinaryMetadataCache.js.map +1 -1
- package/dist/core/cache/index.d.ts +1 -0
- package/dist/core/cache/index.d.ts.map +1 -1
- package/dist/core/cache/index.js +10 -1
- package/dist/core/cache/index.js.map +1 -1
- package/dist/core/memory/AgentDBService.d.ts +30 -4
- package/dist/core/memory/AgentDBService.d.ts.map +1 -1
- package/dist/core/memory/AgentDBService.js +122 -12
- package/dist/core/memory/AgentDBService.js.map +1 -1
- package/dist/core/memory/CachedHNSWVectorMemory.d.ts +153 -0
- package/dist/core/memory/CachedHNSWVectorMemory.d.ts.map +1 -0
- package/dist/core/memory/CachedHNSWVectorMemory.js +329 -0
- package/dist/core/memory/CachedHNSWVectorMemory.js.map +1 -0
- package/dist/core/memory/HNSWVectorMemory.js +1 -1
- package/dist/core/memory/RuVectorPatternStore.d.ts.map +1 -1
- package/dist/core/memory/RuVectorPatternStore.js +8 -2
- package/dist/core/memory/RuVectorPatternStore.js.map +1 -1
- package/dist/core/memory/UnifiedMemoryCoordinator.d.ts +50 -0
- package/dist/core/memory/UnifiedMemoryCoordinator.d.ts.map +1 -1
- package/dist/core/memory/UnifiedMemoryCoordinator.js +206 -0
- package/dist/core/memory/UnifiedMemoryCoordinator.js.map +1 -1
- package/dist/core/memory/index.d.ts +2 -0
- package/dist/core/memory/index.d.ts.map +1 -1
- package/dist/core/memory/index.js +8 -1
- package/dist/core/memory/index.js.map +1 -1
- package/dist/core/optimization/RecursiveOptimizer.d.ts +233 -0
- package/dist/core/optimization/RecursiveOptimizer.d.ts.map +1 -0
- package/dist/core/optimization/RecursiveOptimizer.js +509 -0
- package/dist/core/optimization/RecursiveOptimizer.js.map +1 -0
- package/dist/core/strategies/SONALearningStrategy.d.ts +115 -0
- package/dist/core/strategies/SONALearningStrategy.d.ts.map +1 -0
- package/dist/core/strategies/SONALearningStrategy.js +656 -0
- package/dist/core/strategies/SONALearningStrategy.js.map +1 -0
- package/dist/core/strategies/TRMLearningStrategy.d.ts +162 -0
- package/dist/core/strategies/TRMLearningStrategy.d.ts.map +1 -0
- package/dist/core/strategies/TRMLearningStrategy.js +670 -0
- package/dist/core/strategies/TRMLearningStrategy.js.map +1 -0
- package/dist/core/strategies/index.d.ts +10 -1
- package/dist/core/strategies/index.d.ts.map +1 -1
- package/dist/core/strategies/index.js +4 -1
- package/dist/core/strategies/index.js.map +1 -1
- package/dist/learning/SONAFeedbackLoop.d.ts +168 -0
- package/dist/learning/SONAFeedbackLoop.d.ts.map +1 -0
- package/dist/learning/SONAFeedbackLoop.js +344 -0
- package/dist/learning/SONAFeedbackLoop.js.map +1 -0
- package/dist/learning/baselines/BaselineCollector.d.ts +1 -1
- package/dist/learning/baselines/BaselineCollector.js +1 -1
- package/dist/learning/baselines/StandardTaskSuite.d.ts +1 -1
- package/dist/learning/baselines/StandardTaskSuite.js +1 -1
- package/dist/learning/index.d.ts +2 -0
- package/dist/learning/index.d.ts.map +1 -1
- package/dist/learning/index.js +6 -1
- package/dist/learning/index.js.map +1 -1
- package/dist/mcp/server-instructions.d.ts +1 -1
- package/dist/mcp/server-instructions.js +1 -1
- package/dist/mcp/server.d.ts.map +1 -1
- package/dist/mcp/server.js +23 -16
- package/dist/mcp/server.js.map +1 -1
- package/dist/mcp/services/AgentRegistry.d.ts.map +1 -1
- package/dist/mcp/services/AgentRegistry.js +6 -1
- package/dist/mcp/services/AgentRegistry.js.map +1 -1
- package/dist/mcp/tools/qe/accessibility/accname-computation.d.ts +114 -0
- package/dist/mcp/tools/qe/accessibility/accname-computation.d.ts.map +1 -0
- package/dist/mcp/tools/qe/accessibility/accname-computation.js +566 -0
- package/dist/mcp/tools/qe/accessibility/accname-computation.js.map +1 -0
- package/dist/mcp/tools/qe/accessibility/apg-patterns.d.ts +103 -0
- package/dist/mcp/tools/qe/accessibility/apg-patterns.d.ts.map +1 -0
- package/dist/mcp/tools/qe/accessibility/apg-patterns.js +1028 -0
- package/dist/mcp/tools/qe/accessibility/apg-patterns.js.map +1 -0
- package/dist/mcp/tools/qe/accessibility/en-301-549-mapping.d.ts +48 -0
- package/dist/mcp/tools/qe/accessibility/en-301-549-mapping.d.ts.map +1 -0
- package/dist/mcp/tools/qe/accessibility/en-301-549-mapping.js +565 -0
- package/dist/mcp/tools/qe/accessibility/en-301-549-mapping.js.map +1 -0
- package/dist/mcp/tools/qe/accessibility/eu-accessibility-act.d.ts +117 -0
- package/dist/mcp/tools/qe/accessibility/eu-accessibility-act.d.ts.map +1 -0
- package/dist/mcp/tools/qe/accessibility/eu-accessibility-act.js +571 -0
- package/dist/mcp/tools/qe/accessibility/eu-accessibility-act.js.map +1 -0
- package/dist/mcp/tools/qe/accessibility/html-report-generator.d.ts +23 -0
- package/dist/mcp/tools/qe/accessibility/html-report-generator.d.ts.map +1 -0
- package/dist/mcp/tools/qe/accessibility/html-report-generator.js +1152 -0
- package/dist/mcp/tools/qe/accessibility/html-report-generator.js.map +1 -0
- package/dist/mcp/tools/qe/accessibility/index.d.ts +22 -0
- package/dist/mcp/tools/qe/accessibility/index.d.ts.map +1 -0
- package/dist/mcp/tools/qe/accessibility/index.js +38 -0
- package/dist/mcp/tools/qe/accessibility/index.js.map +1 -0
- package/dist/mcp/tools/qe/accessibility/markdown-report-generator.d.ts +18 -0
- package/dist/mcp/tools/qe/accessibility/markdown-report-generator.d.ts.map +1 -0
- package/dist/mcp/tools/qe/accessibility/markdown-report-generator.js +549 -0
- package/dist/mcp/tools/qe/accessibility/markdown-report-generator.js.map +1 -0
- package/dist/mcp/tools/qe/accessibility/remediation-code-generator.d.ts +139 -0
- package/dist/mcp/tools/qe/accessibility/remediation-code-generator.d.ts.map +1 -0
- package/dist/mcp/tools/qe/accessibility/remediation-code-generator.js +1300 -0
- package/dist/mcp/tools/qe/accessibility/remediation-code-generator.js.map +1 -0
- package/dist/mcp/tools/qe/accessibility/scan-comprehensive.d.ts +138 -0
- package/dist/mcp/tools/qe/accessibility/scan-comprehensive.d.ts.map +1 -0
- package/dist/mcp/tools/qe/accessibility/scan-comprehensive.js +1326 -0
- package/dist/mcp/tools/qe/accessibility/scan-comprehensive.js.map +1 -0
- package/dist/mcp/tools/qe/accessibility/video-vision-analyzer.d.ts +50 -0
- package/dist/mcp/tools/qe/accessibility/video-vision-analyzer.d.ts.map +1 -0
- package/dist/mcp/tools/qe/accessibility/video-vision-analyzer.js +469 -0
- package/dist/mcp/tools/qe/accessibility/video-vision-analyzer.js.map +1 -0
- package/dist/mcp/tools/qe/accessibility/webvtt-generator.d.ts +193 -0
- package/dist/mcp/tools/qe/accessibility/webvtt-generator.d.ts.map +1 -0
- package/dist/mcp/tools/qe/accessibility/webvtt-generator.js +511 -0
- package/dist/mcp/tools/qe/accessibility/webvtt-generator.js.map +1 -0
- package/dist/mcp/tools.d.ts +1 -0
- package/dist/mcp/tools.d.ts.map +1 -1
- package/dist/mcp/tools.js +61 -0
- package/dist/mcp/tools.js.map +1 -1
- package/dist/providers/HybridRouter.d.ts +34 -3
- package/dist/providers/HybridRouter.d.ts.map +1 -1
- package/dist/providers/HybridRouter.js +69 -4
- package/dist/providers/HybridRouter.js.map +1 -1
- package/dist/providers/LLMProviderFactory.d.ts +68 -1
- package/dist/providers/LLMProviderFactory.d.ts.map +1 -1
- package/dist/providers/LLMProviderFactory.js +173 -6
- package/dist/providers/LLMProviderFactory.js.map +1 -1
- package/dist/providers/OpenRouterProvider.d.ts +150 -0
- package/dist/providers/OpenRouterProvider.d.ts.map +1 -0
- package/dist/providers/OpenRouterProvider.js +545 -0
- package/dist/providers/OpenRouterProvider.js.map +1 -0
- package/dist/providers/RuvllmProvider.d.ts +130 -16
- package/dist/providers/RuvllmProvider.d.ts.map +1 -1
- package/dist/providers/RuvllmProvider.js +399 -83
- package/dist/providers/RuvllmProvider.js.map +1 -1
- package/dist/providers/index.d.ts +33 -4
- package/dist/providers/index.d.ts.map +1 -1
- package/dist/providers/index.js +72 -21
- package/dist/providers/index.js.map +1 -1
- package/dist/telemetry/instrumentation/agent.d.ts +1 -1
- package/dist/telemetry/instrumentation/agent.js +1 -1
- package/dist/telemetry/instrumentation/index.d.ts +1 -1
- package/dist/telemetry/instrumentation/index.js +1 -1
- package/dist/types/index.d.ts +2 -1
- package/dist/types/index.d.ts.map +1 -1
- package/dist/types/index.js +2 -0
- package/dist/types/index.js.map +1 -1
- package/dist/types/ruvllm.d.ts +97 -0
- package/dist/types/ruvllm.d.ts.map +1 -0
- package/dist/types/ruvllm.js +46 -0
- package/dist/types/ruvllm.js.map +1 -0
- package/dist/utils/ruvllm-loader.d.ts +94 -0
- package/dist/utils/ruvllm-loader.d.ts.map +1 -0
- package/dist/utils/ruvllm-loader.js +87 -0
- package/dist/utils/ruvllm-loader.js.map +1 -0
- package/docs/reference/agents.md +36 -1
- package/package.json +4 -2
|
@@ -0,0 +1,193 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* WebVTT Caption File Generator
|
|
3
|
+
*
|
|
4
|
+
* Generate Web Video Text Tracks (WebVTT) caption files for video accessibility
|
|
5
|
+
* Version: Based on W3C WebVTT Specification (W3C Candidate Recommendation)
|
|
6
|
+
*
|
|
7
|
+
* Purpose: Generate properly formatted caption files with AI-assisted transcription
|
|
8
|
+
* Reference: https://www.w3.org/TR/webvtt1/
|
|
9
|
+
*
|
|
10
|
+
* WCAG Success Criteria:
|
|
11
|
+
* - 1.2.2 Captions (Prerecorded) - Level A
|
|
12
|
+
* - 1.2.4 Captions (Live) - Level AA
|
|
13
|
+
* - 1.2.6 Sign Language (Prerecorded) - Level AAA
|
|
14
|
+
*/
|
|
15
|
+
export interface WebVTTCue {
|
|
16
|
+
/** Unique identifier for the cue (optional) */
|
|
17
|
+
identifier?: string;
|
|
18
|
+
/** Start time in seconds */
|
|
19
|
+
startTime: number;
|
|
20
|
+
/** End time in seconds */
|
|
21
|
+
endTime: number;
|
|
22
|
+
/** Caption text (can include formatting tags) */
|
|
23
|
+
text: string;
|
|
24
|
+
/** Speaker name (optional, for dialogue) */
|
|
25
|
+
speaker?: string;
|
|
26
|
+
/** Cue settings (position, alignment, etc.) */
|
|
27
|
+
settings?: {
|
|
28
|
+
vertical?: 'rl' | 'lr';
|
|
29
|
+
line?: number | 'auto';
|
|
30
|
+
position?: number;
|
|
31
|
+
size?: number;
|
|
32
|
+
align?: 'start' | 'center' | 'end' | 'left' | 'right';
|
|
33
|
+
};
|
|
34
|
+
}
|
|
35
|
+
export interface WebVTTStyle {
|
|
36
|
+
selector: string;
|
|
37
|
+
properties: Record<string, string>;
|
|
38
|
+
}
|
|
39
|
+
export interface WebVTTMetadata {
|
|
40
|
+
title?: string;
|
|
41
|
+
language?: string;
|
|
42
|
+
description?: string;
|
|
43
|
+
[key: string]: string | undefined;
|
|
44
|
+
}
|
|
45
|
+
export interface WebVTTFile {
|
|
46
|
+
metadata?: WebVTTMetadata;
|
|
47
|
+
styles?: WebVTTStyle[];
|
|
48
|
+
cues: WebVTTCue[];
|
|
49
|
+
}
|
|
50
|
+
export interface WebVTTGeneratorOptions {
|
|
51
|
+
/** Include NOTE blocks with metadata */
|
|
52
|
+
includeNotes?: boolean;
|
|
53
|
+
/** Validate caption best practices (length, reading speed) */
|
|
54
|
+
validateBestPractices?: boolean;
|
|
55
|
+
/** Maximum characters per line (default: 37) */
|
|
56
|
+
maxCharsPerLine?: number;
|
|
57
|
+
/** Maximum lines per cue (default: 2) */
|
|
58
|
+
maxLinesPerCue?: number;
|
|
59
|
+
/** Target reading speed in words per minute (default: 160) */
|
|
60
|
+
targetWPM?: number;
|
|
61
|
+
}
|
|
62
|
+
export interface CaptionValidationIssue {
|
|
63
|
+
cueIndex: number;
|
|
64
|
+
severity: 'error' | 'warning';
|
|
65
|
+
issue: string;
|
|
66
|
+
suggestion: string;
|
|
67
|
+
}
|
|
68
|
+
/**
|
|
69
|
+
* Format time in WebVTT timestamp format (HH:MM:SS.mmm)
|
|
70
|
+
*/
|
|
71
|
+
export declare function formatWebVTTTimestamp(seconds: number): string;
|
|
72
|
+
/**
|
|
73
|
+
* Parse WebVTT timestamp to seconds
|
|
74
|
+
*/
|
|
75
|
+
export declare function parseWebVTTTimestamp(timestamp: string): number;
|
|
76
|
+
/**
|
|
77
|
+
* Generate WebVTT file content from cues
|
|
78
|
+
*/
|
|
79
|
+
export declare function generateWebVTT(file: WebVTTFile, options?: WebVTTGeneratorOptions): string;
|
|
80
|
+
/**
|
|
81
|
+
* Validate WebVTT file against best practices
|
|
82
|
+
*/
|
|
83
|
+
export declare function validateWebVTT(file: WebVTTFile, options?: WebVTTGeneratorOptions): CaptionValidationIssue[];
|
|
84
|
+
/**
|
|
85
|
+
* Create a WebVTT file from a transcript
|
|
86
|
+
*
|
|
87
|
+
* @param transcript - Array of transcript segments with timestamps
|
|
88
|
+
* @param options - Generation options
|
|
89
|
+
*/
|
|
90
|
+
export declare function createWebVTTFromTranscript(transcript: Array<{
|
|
91
|
+
text: string;
|
|
92
|
+
startTime: number;
|
|
93
|
+
endTime: number;
|
|
94
|
+
speaker?: string;
|
|
95
|
+
}>, options?: WebVTTGeneratorOptions): WebVTTFile;
|
|
96
|
+
/**
|
|
97
|
+
* Common caption templates and patterns
|
|
98
|
+
*/
|
|
99
|
+
export declare const CAPTION_TEMPLATES: {
|
|
100
|
+
/**
|
|
101
|
+
* Generate sound effect caption
|
|
102
|
+
*/
|
|
103
|
+
soundEffect: (description: string, startTime: number, endTime: number) => WebVTTCue;
|
|
104
|
+
/**
|
|
105
|
+
* Generate music caption
|
|
106
|
+
*/
|
|
107
|
+
music: (description: string, startTime: number, endTime: number) => WebVTTCue;
|
|
108
|
+
/**
|
|
109
|
+
* Generate speaker change caption
|
|
110
|
+
*/
|
|
111
|
+
dialogue: (speaker: string, text: string, startTime: number, endTime: number) => WebVTTCue;
|
|
112
|
+
/**
|
|
113
|
+
* Generate atmosphere/ambient sound caption
|
|
114
|
+
*/
|
|
115
|
+
ambient: (description: string, startTime: number, endTime: number) => WebVTTCue;
|
|
116
|
+
};
|
|
117
|
+
/**
|
|
118
|
+
* WebVTT style presets for common use cases
|
|
119
|
+
*/
|
|
120
|
+
export declare const WEBVTT_STYLE_PRESETS: {
|
|
121
|
+
/**
|
|
122
|
+
* High contrast style for better visibility
|
|
123
|
+
*/
|
|
124
|
+
highContrast: () => WebVTTStyle[];
|
|
125
|
+
/**
|
|
126
|
+
* Speaker differentiation with colors
|
|
127
|
+
*/
|
|
128
|
+
speakerColors: () => WebVTTStyle[];
|
|
129
|
+
/**
|
|
130
|
+
* Sound effect styling
|
|
131
|
+
*/
|
|
132
|
+
soundEffects: () => WebVTTStyle[];
|
|
133
|
+
};
|
|
134
|
+
/**
|
|
135
|
+
* AI-Assisted Caption Generation Helpers
|
|
136
|
+
*/
|
|
137
|
+
export interface AITranscriptionSegment {
|
|
138
|
+
text: string;
|
|
139
|
+
startTime: number;
|
|
140
|
+
endTime: number;
|
|
141
|
+
confidence: number;
|
|
142
|
+
speaker?: string;
|
|
143
|
+
}
|
|
144
|
+
/**
|
|
145
|
+
* Generate caption generation prompt for AI services
|
|
146
|
+
*
|
|
147
|
+
* @param videoMetadata - Video information
|
|
148
|
+
* @returns Prompt for AI transcription services
|
|
149
|
+
*/
|
|
150
|
+
export declare function generateAICaptionPrompt(videoMetadata: {
|
|
151
|
+
title?: string;
|
|
152
|
+
duration?: number;
|
|
153
|
+
topic?: string;
|
|
154
|
+
language?: string;
|
|
155
|
+
}): string;
|
|
156
|
+
/**
|
|
157
|
+
* Convert AI transcription to WebVTT file
|
|
158
|
+
*/
|
|
159
|
+
export declare function convertAITranscriptionToWebVTT(segments: AITranscriptionSegment[], options?: WebVTTGeneratorOptions): WebVTTFile;
|
|
160
|
+
/**
|
|
161
|
+
* Caption quality scoring
|
|
162
|
+
*/
|
|
163
|
+
export declare function scoreCaptionQuality(file: WebVTTFile): {
|
|
164
|
+
score: number;
|
|
165
|
+
breakdown: {
|
|
166
|
+
timing: number;
|
|
167
|
+
readability: number;
|
|
168
|
+
completeness: number;
|
|
169
|
+
formatting: number;
|
|
170
|
+
};
|
|
171
|
+
recommendations: string[];
|
|
172
|
+
};
|
|
173
|
+
/**
|
|
174
|
+
* Export WebVTT file to disk
|
|
175
|
+
*/
|
|
176
|
+
export declare function saveWebVTT(file: WebVTTFile, options?: WebVTTGeneratorOptions): {
|
|
177
|
+
content: string;
|
|
178
|
+
filename: string;
|
|
179
|
+
};
|
|
180
|
+
/**
|
|
181
|
+
* Example usage and best practices
|
|
182
|
+
*/
|
|
183
|
+
export declare const WEBVTT_EXAMPLES: {
|
|
184
|
+
/**
|
|
185
|
+
* Basic video with dialogue
|
|
186
|
+
*/
|
|
187
|
+
simpleDialogue: () => WebVTTFile;
|
|
188
|
+
/**
|
|
189
|
+
* Educational video with sound effects
|
|
190
|
+
*/
|
|
191
|
+
educational: () => WebVTTFile;
|
|
192
|
+
};
|
|
193
|
+
//# sourceMappingURL=webvtt-generator.d.ts.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"webvtt-generator.d.ts","sourceRoot":"","sources":["../../../../../src/mcp/tools/qe/accessibility/webvtt-generator.ts"],"names":[],"mappings":"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"}
|
|
@@ -0,0 +1,511 @@
|
|
|
1
|
+
"use strict";
|
|
2
|
+
/**
|
|
3
|
+
* WebVTT Caption File Generator
|
|
4
|
+
*
|
|
5
|
+
* Generate Web Video Text Tracks (WebVTT) caption files for video accessibility
|
|
6
|
+
* Version: Based on W3C WebVTT Specification (W3C Candidate Recommendation)
|
|
7
|
+
*
|
|
8
|
+
* Purpose: Generate properly formatted caption files with AI-assisted transcription
|
|
9
|
+
* Reference: https://www.w3.org/TR/webvtt1/
|
|
10
|
+
*
|
|
11
|
+
* WCAG Success Criteria:
|
|
12
|
+
* - 1.2.2 Captions (Prerecorded) - Level A
|
|
13
|
+
* - 1.2.4 Captions (Live) - Level AA
|
|
14
|
+
* - 1.2.6 Sign Language (Prerecorded) - Level AAA
|
|
15
|
+
*/
|
|
16
|
+
Object.defineProperty(exports, "__esModule", { value: true });
|
|
17
|
+
exports.WEBVTT_EXAMPLES = exports.WEBVTT_STYLE_PRESETS = exports.CAPTION_TEMPLATES = void 0;
|
|
18
|
+
exports.formatWebVTTTimestamp = formatWebVTTTimestamp;
|
|
19
|
+
exports.parseWebVTTTimestamp = parseWebVTTTimestamp;
|
|
20
|
+
exports.generateWebVTT = generateWebVTT;
|
|
21
|
+
exports.validateWebVTT = validateWebVTT;
|
|
22
|
+
exports.createWebVTTFromTranscript = createWebVTTFromTranscript;
|
|
23
|
+
exports.generateAICaptionPrompt = generateAICaptionPrompt;
|
|
24
|
+
exports.convertAITranscriptionToWebVTT = convertAITranscriptionToWebVTT;
|
|
25
|
+
exports.scoreCaptionQuality = scoreCaptionQuality;
|
|
26
|
+
exports.saveWebVTT = saveWebVTT;
|
|
27
|
+
/**
|
|
28
|
+
* Format time in WebVTT timestamp format (HH:MM:SS.mmm)
|
|
29
|
+
*/
|
|
30
|
+
function formatWebVTTTimestamp(seconds) {
|
|
31
|
+
const hours = Math.floor(seconds / 3600);
|
|
32
|
+
const minutes = Math.floor((seconds % 3600) / 60);
|
|
33
|
+
const secs = Math.floor(seconds % 60);
|
|
34
|
+
const milliseconds = Math.floor((seconds % 1) * 1000);
|
|
35
|
+
return `${String(hours).padStart(2, '0')}:${String(minutes).padStart(2, '0')}:${String(secs).padStart(2, '0')}.${String(milliseconds).padStart(3, '0')}`;
|
|
36
|
+
}
|
|
37
|
+
/**
|
|
38
|
+
* Parse WebVTT timestamp to seconds
|
|
39
|
+
*/
|
|
40
|
+
function parseWebVTTTimestamp(timestamp) {
|
|
41
|
+
const parts = timestamp.split(':');
|
|
42
|
+
const hours = parseInt(parts[0], 10);
|
|
43
|
+
const minutes = parseInt(parts[1], 10);
|
|
44
|
+
const secondsParts = parts[2].split('.');
|
|
45
|
+
const seconds = parseInt(secondsParts[0], 10);
|
|
46
|
+
const milliseconds = parseInt(secondsParts[1] || '0', 10);
|
|
47
|
+
return hours * 3600 + minutes * 60 + seconds + milliseconds / 1000;
|
|
48
|
+
}
|
|
49
|
+
/**
|
|
50
|
+
* Format cue settings object to WebVTT settings string
|
|
51
|
+
*/
|
|
52
|
+
function formatCueSettings(settings) {
|
|
53
|
+
if (!settings)
|
|
54
|
+
return '';
|
|
55
|
+
const parts = [];
|
|
56
|
+
if (settings.vertical) {
|
|
57
|
+
parts.push(`vertical:${settings.vertical}`);
|
|
58
|
+
}
|
|
59
|
+
if (settings.line !== undefined) {
|
|
60
|
+
parts.push(`line:${settings.line}`);
|
|
61
|
+
}
|
|
62
|
+
if (settings.position !== undefined) {
|
|
63
|
+
parts.push(`position:${settings.position}%`);
|
|
64
|
+
}
|
|
65
|
+
if (settings.size !== undefined) {
|
|
66
|
+
parts.push(`size:${settings.size}%`);
|
|
67
|
+
}
|
|
68
|
+
if (settings.align) {
|
|
69
|
+
parts.push(`align:${settings.align}`);
|
|
70
|
+
}
|
|
71
|
+
return parts.length > 0 ? ' ' + parts.join(' ') : '';
|
|
72
|
+
}
|
|
73
|
+
/**
|
|
74
|
+
* Generate WebVTT file content from cues
|
|
75
|
+
*/
|
|
76
|
+
function generateWebVTT(file, options = {}) {
|
|
77
|
+
let content = 'WEBVTT\n';
|
|
78
|
+
// Add metadata as NOTE blocks
|
|
79
|
+
if (options.includeNotes && file.metadata) {
|
|
80
|
+
content += '\n';
|
|
81
|
+
Object.entries(file.metadata).forEach(([key, value]) => {
|
|
82
|
+
if (value) {
|
|
83
|
+
content += `NOTE ${key}\n${value}\n\n`;
|
|
84
|
+
}
|
|
85
|
+
});
|
|
86
|
+
}
|
|
87
|
+
// Add STYLE blocks
|
|
88
|
+
if (file.styles && file.styles.length > 0) {
|
|
89
|
+
content += '\nSTYLE\n';
|
|
90
|
+
file.styles.forEach(style => {
|
|
91
|
+
content += `${style.selector} {\n`;
|
|
92
|
+
Object.entries(style.properties).forEach(([prop, value]) => {
|
|
93
|
+
content += ` ${prop}: ${value};\n`;
|
|
94
|
+
});
|
|
95
|
+
content += '}\n';
|
|
96
|
+
});
|
|
97
|
+
content += '\n';
|
|
98
|
+
}
|
|
99
|
+
// Add cues
|
|
100
|
+
file.cues.forEach((cue, index) => {
|
|
101
|
+
// Optional identifier
|
|
102
|
+
if (cue.identifier) {
|
|
103
|
+
content += `${cue.identifier}\n`;
|
|
104
|
+
}
|
|
105
|
+
// Timestamp line
|
|
106
|
+
const startTimestamp = formatWebVTTTimestamp(cue.startTime);
|
|
107
|
+
const endTimestamp = formatWebVTTTimestamp(cue.endTime);
|
|
108
|
+
const settings = formatCueSettings(cue.settings);
|
|
109
|
+
content += `${startTimestamp} --> ${endTimestamp}${settings}\n`;
|
|
110
|
+
// Caption text
|
|
111
|
+
let text = cue.text;
|
|
112
|
+
// Add speaker identification if provided
|
|
113
|
+
if (cue.speaker) {
|
|
114
|
+
text = `<v ${cue.speaker}>${text}</v>`;
|
|
115
|
+
}
|
|
116
|
+
content += `${text}\n\n`;
|
|
117
|
+
});
|
|
118
|
+
return content.trim();
|
|
119
|
+
}
|
|
120
|
+
/**
|
|
121
|
+
* Validate WebVTT file against best practices
|
|
122
|
+
*/
|
|
123
|
+
function validateWebVTT(file, options = {}) {
|
|
124
|
+
const issues = [];
|
|
125
|
+
const maxCharsPerLine = options.maxCharsPerLine || 37;
|
|
126
|
+
const maxLinesPerCue = options.maxLinesPerCue || 2;
|
|
127
|
+
const targetWPM = options.targetWPM || 160;
|
|
128
|
+
const maxWPM = 180; // Maximum comfortable reading speed
|
|
129
|
+
file.cues.forEach((cue, index) => {
|
|
130
|
+
// Check cue duration
|
|
131
|
+
const duration = cue.endTime - cue.startTime;
|
|
132
|
+
if (duration < 1) {
|
|
133
|
+
issues.push({
|
|
134
|
+
cueIndex: index,
|
|
135
|
+
severity: 'warning',
|
|
136
|
+
issue: 'Cue duration less than 1 second',
|
|
137
|
+
suggestion: 'Captions should remain on screen for at least 1 second for readability'
|
|
138
|
+
});
|
|
139
|
+
}
|
|
140
|
+
// Check overlapping cues
|
|
141
|
+
if (index > 0) {
|
|
142
|
+
const previousCue = file.cues[index - 1];
|
|
143
|
+
if (cue.startTime < previousCue.endTime) {
|
|
144
|
+
issues.push({
|
|
145
|
+
cueIndex: index,
|
|
146
|
+
severity: 'error',
|
|
147
|
+
issue: 'Cue overlaps with previous cue',
|
|
148
|
+
suggestion: `Start time ${formatWebVTTTimestamp(cue.startTime)} is before previous cue end time ${formatWebVTTTimestamp(previousCue.endTime)}`
|
|
149
|
+
});
|
|
150
|
+
}
|
|
151
|
+
}
|
|
152
|
+
// Remove speaker tags and formatting tags for text analysis
|
|
153
|
+
const plainText = cue.text
|
|
154
|
+
.replace(/<v\s+[^>]+>/g, '')
|
|
155
|
+
.replace(/<\/v>/g, '')
|
|
156
|
+
.replace(/<[^>]+>/g, '');
|
|
157
|
+
// Check line length
|
|
158
|
+
const lines = plainText.split('\n');
|
|
159
|
+
if (lines.length > maxLinesPerCue) {
|
|
160
|
+
issues.push({
|
|
161
|
+
cueIndex: index,
|
|
162
|
+
severity: 'warning',
|
|
163
|
+
issue: `Cue has ${lines.length} lines (max recommended: ${maxLinesPerCue})`,
|
|
164
|
+
suggestion: 'Split long captions into multiple cues for better readability'
|
|
165
|
+
});
|
|
166
|
+
}
|
|
167
|
+
lines.forEach((line, lineIndex) => {
|
|
168
|
+
if (line.length > maxCharsPerLine) {
|
|
169
|
+
issues.push({
|
|
170
|
+
cueIndex: index,
|
|
171
|
+
severity: 'warning',
|
|
172
|
+
issue: `Line ${lineIndex + 1} has ${line.length} characters (max recommended: ${maxCharsPerLine})`,
|
|
173
|
+
suggestion: 'Break long lines at natural phrase boundaries'
|
|
174
|
+
});
|
|
175
|
+
}
|
|
176
|
+
});
|
|
177
|
+
// Check reading speed
|
|
178
|
+
const wordCount = plainText.split(/\s+/).length;
|
|
179
|
+
const durationMinutes = duration / 60;
|
|
180
|
+
const wpm = wordCount / durationMinutes;
|
|
181
|
+
if (wpm > maxWPM) {
|
|
182
|
+
issues.push({
|
|
183
|
+
cueIndex: index,
|
|
184
|
+
severity: 'warning',
|
|
185
|
+
issue: `Reading speed is ${Math.round(wpm)} WPM (max comfortable: ${maxWPM} WPM)`,
|
|
186
|
+
suggestion: 'Increase cue duration or split into multiple cues'
|
|
187
|
+
});
|
|
188
|
+
}
|
|
189
|
+
// Check for empty captions
|
|
190
|
+
if (plainText.trim().length === 0) {
|
|
191
|
+
issues.push({
|
|
192
|
+
cueIndex: index,
|
|
193
|
+
severity: 'error',
|
|
194
|
+
issue: 'Empty caption text',
|
|
195
|
+
suggestion: 'Remove empty cue or add caption text'
|
|
196
|
+
});
|
|
197
|
+
}
|
|
198
|
+
});
|
|
199
|
+
return issues;
|
|
200
|
+
}
|
|
201
|
+
/**
|
|
202
|
+
* Create a WebVTT file from a transcript
|
|
203
|
+
*
|
|
204
|
+
* @param transcript - Array of transcript segments with timestamps
|
|
205
|
+
* @param options - Generation options
|
|
206
|
+
*/
|
|
207
|
+
function createWebVTTFromTranscript(transcript, options = {}) {
|
|
208
|
+
const maxCharsPerLine = options.maxCharsPerLine || 37;
|
|
209
|
+
const cues = transcript.map((segment, index) => {
|
|
210
|
+
// Smart line breaking at natural boundaries
|
|
211
|
+
const words = segment.text.split(' ');
|
|
212
|
+
const lines = [];
|
|
213
|
+
let currentLine = '';
|
|
214
|
+
words.forEach(word => {
|
|
215
|
+
const testLine = currentLine ? `${currentLine} ${word}` : word;
|
|
216
|
+
if (testLine.length <= maxCharsPerLine) {
|
|
217
|
+
currentLine = testLine;
|
|
218
|
+
}
|
|
219
|
+
else {
|
|
220
|
+
if (currentLine) {
|
|
221
|
+
lines.push(currentLine);
|
|
222
|
+
}
|
|
223
|
+
currentLine = word;
|
|
224
|
+
}
|
|
225
|
+
});
|
|
226
|
+
if (currentLine) {
|
|
227
|
+
lines.push(currentLine);
|
|
228
|
+
}
|
|
229
|
+
// Join lines (max 2 lines per cue recommended)
|
|
230
|
+
const text = lines.slice(0, options.maxLinesPerCue || 2).join('\n');
|
|
231
|
+
return {
|
|
232
|
+
identifier: `cue-${index + 1}`,
|
|
233
|
+
startTime: segment.startTime,
|
|
234
|
+
endTime: segment.endTime,
|
|
235
|
+
text,
|
|
236
|
+
speaker: segment.speaker
|
|
237
|
+
};
|
|
238
|
+
});
|
|
239
|
+
return { cues };
|
|
240
|
+
}
|
|
241
|
+
/**
|
|
242
|
+
* Common caption templates and patterns
|
|
243
|
+
*/
|
|
244
|
+
exports.CAPTION_TEMPLATES = {
|
|
245
|
+
/**
|
|
246
|
+
* Generate sound effect caption
|
|
247
|
+
*/
|
|
248
|
+
soundEffect: (description, startTime, endTime) => ({
|
|
249
|
+
startTime,
|
|
250
|
+
endTime,
|
|
251
|
+
text: `[${description}]`,
|
|
252
|
+
settings: { align: 'center' }
|
|
253
|
+
}),
|
|
254
|
+
/**
|
|
255
|
+
* Generate music caption
|
|
256
|
+
*/
|
|
257
|
+
music: (description, startTime, endTime) => ({
|
|
258
|
+
startTime,
|
|
259
|
+
endTime,
|
|
260
|
+
text: `♪ ${description} ♪`,
|
|
261
|
+
settings: { align: 'center' }
|
|
262
|
+
}),
|
|
263
|
+
/**
|
|
264
|
+
* Generate speaker change caption
|
|
265
|
+
*/
|
|
266
|
+
dialogue: (speaker, text, startTime, endTime) => ({
|
|
267
|
+
startTime,
|
|
268
|
+
endTime,
|
|
269
|
+
text,
|
|
270
|
+
speaker
|
|
271
|
+
}),
|
|
272
|
+
/**
|
|
273
|
+
* Generate atmosphere/ambient sound caption
|
|
274
|
+
*/
|
|
275
|
+
ambient: (description, startTime, endTime) => ({
|
|
276
|
+
startTime,
|
|
277
|
+
endTime,
|
|
278
|
+
text: `(${description})`,
|
|
279
|
+
settings: { position: 50, align: 'center' }
|
|
280
|
+
})
|
|
281
|
+
};
|
|
282
|
+
/**
|
|
283
|
+
* WebVTT style presets for common use cases
|
|
284
|
+
*/
|
|
285
|
+
exports.WEBVTT_STYLE_PRESETS = {
|
|
286
|
+
/**
|
|
287
|
+
* High contrast style for better visibility
|
|
288
|
+
*/
|
|
289
|
+
highContrast: () => [
|
|
290
|
+
{
|
|
291
|
+
selector: '::cue',
|
|
292
|
+
properties: {
|
|
293
|
+
'background-color': 'rgba(0, 0, 0, 0.9)',
|
|
294
|
+
'color': '#FFFFFF',
|
|
295
|
+
'font-size': '1.5em',
|
|
296
|
+
'font-family': 'Arial, sans-serif'
|
|
297
|
+
}
|
|
298
|
+
}
|
|
299
|
+
],
|
|
300
|
+
/**
|
|
301
|
+
* Speaker differentiation with colors
|
|
302
|
+
*/
|
|
303
|
+
speakerColors: () => [
|
|
304
|
+
{
|
|
305
|
+
selector: '::cue(v[voice="Speaker 1"])',
|
|
306
|
+
properties: {
|
|
307
|
+
'color': '#00CCFF'
|
|
308
|
+
}
|
|
309
|
+
},
|
|
310
|
+
{
|
|
311
|
+
selector: '::cue(v[voice="Speaker 2"])',
|
|
312
|
+
properties: {
|
|
313
|
+
'color': '#FFCC00'
|
|
314
|
+
}
|
|
315
|
+
},
|
|
316
|
+
{
|
|
317
|
+
selector: '::cue(v[voice="Speaker 3"])',
|
|
318
|
+
properties: {
|
|
319
|
+
'color': '#FF66CC'
|
|
320
|
+
}
|
|
321
|
+
}
|
|
322
|
+
],
|
|
323
|
+
/**
|
|
324
|
+
* Sound effect styling
|
|
325
|
+
*/
|
|
326
|
+
soundEffects: () => [
|
|
327
|
+
{
|
|
328
|
+
selector: '::cue(i)',
|
|
329
|
+
properties: {
|
|
330
|
+
'color': '#CCCCCC',
|
|
331
|
+
'font-style': 'italic'
|
|
332
|
+
}
|
|
333
|
+
}
|
|
334
|
+
]
|
|
335
|
+
};
|
|
336
|
+
/**
|
|
337
|
+
* Generate caption generation prompt for AI services
|
|
338
|
+
*
|
|
339
|
+
* @param videoMetadata - Video information
|
|
340
|
+
* @returns Prompt for AI transcription services
|
|
341
|
+
*/
|
|
342
|
+
function generateAICaptionPrompt(videoMetadata) {
|
|
343
|
+
return `Generate accurate captions for a video with the following details:
|
|
344
|
+
|
|
345
|
+
Title: ${videoMetadata.title || 'Untitled Video'}
|
|
346
|
+
Duration: ${videoMetadata.duration ? formatWebVTTTimestamp(videoMetadata.duration) : 'Unknown'}
|
|
347
|
+
Topic: ${videoMetadata.topic || 'General'}
|
|
348
|
+
Language: ${videoMetadata.language || 'English'}
|
|
349
|
+
|
|
350
|
+
Caption Requirements:
|
|
351
|
+
- Maximum 37 characters per line
|
|
352
|
+
- Maximum 2 lines per caption
|
|
353
|
+
- Reading speed: 160-180 words per minute
|
|
354
|
+
- Include speaker identification for dialogue
|
|
355
|
+
- Mark sound effects with [brackets]
|
|
356
|
+
- Mark music with ♪ symbols
|
|
357
|
+
- Mark ambient sounds with (parentheses)
|
|
358
|
+
- Ensure accurate timing synchronization
|
|
359
|
+
- Follow WebVTT format specification
|
|
360
|
+
|
|
361
|
+
Please provide:
|
|
362
|
+
1. Timestamp (HH:MM:SS.mmm format)
|
|
363
|
+
2. Speaker name (if applicable)
|
|
364
|
+
3. Caption text with proper line breaks
|
|
365
|
+
4. Confidence score (0-1)`;
|
|
366
|
+
}
|
|
367
|
+
/**
|
|
368
|
+
* Convert AI transcription to WebVTT file
|
|
369
|
+
*/
|
|
370
|
+
function convertAITranscriptionToWebVTT(segments, options = {}) {
|
|
371
|
+
const cues = segments.map((segment, index) => {
|
|
372
|
+
// Filter out low-confidence segments (you may want manual review)
|
|
373
|
+
if (segment.confidence < 0.8) {
|
|
374
|
+
console.warn(`Low confidence segment at ${formatWebVTTTimestamp(segment.startTime)}: ${segment.confidence}`);
|
|
375
|
+
}
|
|
376
|
+
return {
|
|
377
|
+
identifier: `ai-cue-${index + 1}`,
|
|
378
|
+
startTime: segment.startTime,
|
|
379
|
+
endTime: segment.endTime,
|
|
380
|
+
text: segment.text,
|
|
381
|
+
speaker: segment.speaker
|
|
382
|
+
};
|
|
383
|
+
});
|
|
384
|
+
const file = {
|
|
385
|
+
metadata: {
|
|
386
|
+
title: 'AI-Generated Captions',
|
|
387
|
+
description: 'Generated using AI transcription service'
|
|
388
|
+
},
|
|
389
|
+
cues
|
|
390
|
+
};
|
|
391
|
+
return file;
|
|
392
|
+
}
|
|
393
|
+
/**
|
|
394
|
+
* Caption quality scoring
|
|
395
|
+
*/
|
|
396
|
+
function scoreCaptionQuality(file) {
|
|
397
|
+
const issues = validateWebVTT(file, { validateBestPractices: true });
|
|
398
|
+
const errorCount = issues.filter(i => i.severity === 'error').length;
|
|
399
|
+
const warningCount = issues.filter(i => i.severity === 'warning').length;
|
|
400
|
+
// Scoring components (0-100)
|
|
401
|
+
let timing = 100;
|
|
402
|
+
let readability = 100;
|
|
403
|
+
let completeness = 100;
|
|
404
|
+
let formatting = 100;
|
|
405
|
+
const recommendations = [];
|
|
406
|
+
// Penalize for errors and warnings
|
|
407
|
+
timing -= errorCount * 10;
|
|
408
|
+
readability -= warningCount * 5;
|
|
409
|
+
// Check completeness (minimum 80% of expected cues)
|
|
410
|
+
if (file.cues.length === 0) {
|
|
411
|
+
completeness = 0;
|
|
412
|
+
recommendations.push('Add captions to the video');
|
|
413
|
+
}
|
|
414
|
+
// Check formatting
|
|
415
|
+
const hasSpeakers = file.cues.some(c => c.speaker);
|
|
416
|
+
if (!hasSpeakers && file.cues.length > 5) {
|
|
417
|
+
formatting -= 10;
|
|
418
|
+
recommendations.push('Consider adding speaker identification for dialogue');
|
|
419
|
+
}
|
|
420
|
+
// Overall score (weighted average)
|
|
421
|
+
const score = Math.max(0, Math.round(timing * 0.3 +
|
|
422
|
+
readability * 0.3 +
|
|
423
|
+
completeness * 0.2 +
|
|
424
|
+
formatting * 0.2));
|
|
425
|
+
if (score < 80) {
|
|
426
|
+
recommendations.push('Review and address validation issues for better quality');
|
|
427
|
+
}
|
|
428
|
+
if (errorCount > 0) {
|
|
429
|
+
recommendations.push('Fix critical errors (overlapping cues, empty captions)');
|
|
430
|
+
}
|
|
431
|
+
if (warningCount > 5) {
|
|
432
|
+
recommendations.push('Consider revising captions for better readability');
|
|
433
|
+
}
|
|
434
|
+
return {
|
|
435
|
+
score,
|
|
436
|
+
breakdown: {
|
|
437
|
+
timing: Math.max(0, timing),
|
|
438
|
+
readability: Math.max(0, readability),
|
|
439
|
+
completeness: Math.max(0, completeness),
|
|
440
|
+
formatting: Math.max(0, formatting)
|
|
441
|
+
},
|
|
442
|
+
recommendations
|
|
443
|
+
};
|
|
444
|
+
}
|
|
445
|
+
/**
|
|
446
|
+
* Export WebVTT file to disk
|
|
447
|
+
*/
|
|
448
|
+
function saveWebVTT(file, options = {}) {
|
|
449
|
+
const content = generateWebVTT(file, options);
|
|
450
|
+
const timestamp = new Date().toISOString().replace(/[:.]/g, '-').slice(0, -5);
|
|
451
|
+
const filename = `captions-${timestamp}.vtt`;
|
|
452
|
+
return { content, filename };
|
|
453
|
+
}
|
|
454
|
+
/**
|
|
455
|
+
* Example usage and best practices
|
|
456
|
+
*/
|
|
457
|
+
exports.WEBVTT_EXAMPLES = {
|
|
458
|
+
/**
|
|
459
|
+
* Basic video with dialogue
|
|
460
|
+
*/
|
|
461
|
+
simpleDialogue: () => ({
|
|
462
|
+
metadata: {
|
|
463
|
+
title: 'Product Demo Video',
|
|
464
|
+
language: 'en'
|
|
465
|
+
},
|
|
466
|
+
cues: [
|
|
467
|
+
{
|
|
468
|
+
identifier: 'intro',
|
|
469
|
+
startTime: 0,
|
|
470
|
+
endTime: 3.5,
|
|
471
|
+
text: 'Welcome to our product demo.',
|
|
472
|
+
speaker: 'Narrator'
|
|
473
|
+
},
|
|
474
|
+
{
|
|
475
|
+
identifier: 'feature-1',
|
|
476
|
+
startTime: 3.5,
|
|
477
|
+
endTime: 7.0,
|
|
478
|
+
text: 'Today we\'ll show you\nthree amazing features.',
|
|
479
|
+
speaker: 'Narrator'
|
|
480
|
+
},
|
|
481
|
+
exports.CAPTION_TEMPLATES.music('Upbeat background music', 7.0, 10.0)
|
|
482
|
+
]
|
|
483
|
+
}),
|
|
484
|
+
/**
|
|
485
|
+
* Educational video with sound effects
|
|
486
|
+
*/
|
|
487
|
+
educational: () => ({
|
|
488
|
+
metadata: {
|
|
489
|
+
title: 'Science Experiment',
|
|
490
|
+
language: 'en',
|
|
491
|
+
description: 'Chemistry demonstration with captions'
|
|
492
|
+
},
|
|
493
|
+
styles: exports.WEBVTT_STYLE_PRESETS.highContrast(),
|
|
494
|
+
cues: [
|
|
495
|
+
{
|
|
496
|
+
startTime: 0,
|
|
497
|
+
endTime: 4,
|
|
498
|
+
text: 'First, we add the sodium chloride\nto the beaker.',
|
|
499
|
+
speaker: 'Professor'
|
|
500
|
+
},
|
|
501
|
+
exports.CAPTION_TEMPLATES.soundEffect('Bubbling sounds', 4, 6),
|
|
502
|
+
{
|
|
503
|
+
startTime: 6,
|
|
504
|
+
endTime: 10,
|
|
505
|
+
text: 'Notice the chemical reaction\noccurring.',
|
|
506
|
+
speaker: 'Professor'
|
|
507
|
+
}
|
|
508
|
+
]
|
|
509
|
+
})
|
|
510
|
+
};
|
|
511
|
+
//# sourceMappingURL=webvtt-generator.js.map
|