@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
|
@@ -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"}
|