@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,280 @@
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
+ var __importDefault = (this && this.__importDefault) || function (mod) {
36
+ return (mod && mod.__esModule) ? mod : { "default": mod };
37
+ };
38
+ Object.defineProperty(exports, "__esModule", { value: true });
39
+ exports.transcribeAudio = transcribeAudio;
40
+ exports.transcribeAudioWithTimestamps = transcribeAudioWithTimestamps;
41
+ exports.sanitizeText = sanitizeText;
42
+ exports.compareTexts = compareTexts;
43
+ exports.generateCorrections = generateCorrections;
44
+ const openai_1 = __importDefault(require("openai"));
45
+ const fs = __importStar(require("fs"));
46
+ const config_1 = require("../config");
47
+ /**
48
+ * Transcribe audio to text using OpenAI Whisper
49
+ *
50
+ * @param options - Transcription options
51
+ * @returns Transcribed text
52
+ *
53
+ * @example
54
+ * ```typescript
55
+ * const text = await transcribeAudio({
56
+ * audioPath: './audio.mp3',
57
+ * openaiApiKey: process.env.OPENAI_API_KEY!,
58
+ * });
59
+ * console.log('Transcribed:', text);
60
+ * ```
61
+ */
62
+ async function transcribeAudio(options) {
63
+ const { audioPath, openaiApiKey, language = config_1.TranscriptionConfig.LANGUAGE, } = options;
64
+ if (!openaiApiKey) {
65
+ throw new Error('OpenAI API key is required for transcription. Set OPENAI_API_KEY environment variable or pass it in options.');
66
+ }
67
+ if (!fs.existsSync(audioPath)) {
68
+ throw new Error(`Audio file not found: ${audioPath}`);
69
+ }
70
+ const openai = new openai_1.default({ apiKey: openaiApiKey });
71
+ try {
72
+ console.log('🎤 Transcribing audio with Whisper...');
73
+ const transcription = await openai.audio.transcriptions.create({
74
+ file: fs.createReadStream(audioPath),
75
+ model: config_1.TranscriptionConfig.MODEL,
76
+ language: language,
77
+ response_format: 'text',
78
+ });
79
+ console.log('✅ Transcription complete');
80
+ return transcription;
81
+ }
82
+ catch (error) {
83
+ throw new Error(`Failed to transcribe audio: ${error}`);
84
+ }
85
+ }
86
+ /**
87
+ * Transcribe audio with word-level timestamps using OpenAI Whisper
88
+ *
89
+ * @param options - Transcription options
90
+ * @returns Array of words with timestamps
91
+ *
92
+ * @example
93
+ * ```typescript
94
+ * const words = await transcribeAudioWithTimestamps({
95
+ * audioPath: './audio.mp3',
96
+ * openaiApiKey: process.env.OPENAI_API_KEY!,
97
+ * });
98
+ * console.log('First word:', words[0]);
99
+ * // { word: "Hello", start: 0.5, end: 0.8 }
100
+ * ```
101
+ */
102
+ async function transcribeAudioWithTimestamps(options) {
103
+ const { audioPath, openaiApiKey, language = config_1.TranscriptionConfig.LANGUAGE, } = options;
104
+ if (!openaiApiKey) {
105
+ throw new Error('OpenAI API key is required for transcription. Set OPENAI_API_KEY environment variable or pass it in options.');
106
+ }
107
+ if (!fs.existsSync(audioPath)) {
108
+ throw new Error(`Audio file not found: ${audioPath}`);
109
+ }
110
+ const openai = new openai_1.default({ apiKey: openaiApiKey });
111
+ try {
112
+ console.log('🎤 Transcribing audio with word-level timestamps...');
113
+ const transcription = await openai.audio.transcriptions.create({
114
+ file: fs.createReadStream(audioPath),
115
+ model: config_1.TranscriptionConfig.MODEL,
116
+ language: language,
117
+ response_format: 'verbose_json',
118
+ timestamp_granularities: ['word'],
119
+ });
120
+ console.log('✅ Transcription with timestamps complete');
121
+ // Extract word-level timestamps
122
+ const result = transcription;
123
+ if (result.words && Array.isArray(result.words)) {
124
+ return result.words.map((w) => ({
125
+ word: w.word,
126
+ start: w.start,
127
+ end: w.end,
128
+ }));
129
+ }
130
+ throw new Error('No word-level timestamps in response');
131
+ }
132
+ catch (error) {
133
+ throw new Error(`Failed to transcribe audio with timestamps: ${error}`);
134
+ }
135
+ }
136
+ /**
137
+ * Sanitize text by removing unwanted characters
138
+ * Keeps only alphanumeric characters, spaces, and basic punctuation
139
+ *
140
+ * @param text - Text to sanitize
141
+ * @returns Sanitized text
142
+ *
143
+ * @example
144
+ * ```typescript
145
+ * const clean = sanitizeText("Hello—world! [test]");
146
+ * // Returns: "Hello world! test"
147
+ * ```
148
+ */
149
+ function sanitizeText(text) {
150
+ // Remove unwanted characters (keep only alphanumeric and basic punctuation)
151
+ let sanitized = text.replace(config_1.TextSanitizationConfig.ALLOWED_PATTERN, '');
152
+ // Normalize whitespace
153
+ if (config_1.TextSanitizationConfig.NORMALIZE_WHITESPACE) {
154
+ sanitized = sanitized.replace(/\s+/g, ' ').trim();
155
+ }
156
+ return sanitized;
157
+ }
158
+ /**
159
+ * Calculate similarity between two texts (simple word-based comparison)
160
+ *
161
+ * @param text1 - First text
162
+ * @param text2 - Second text
163
+ * @returns Similarity score between 0 and 1
164
+ */
165
+ function calculateSimilarity(text1, text2) {
166
+ const words1 = text1.toLowerCase().split(/\s+/);
167
+ const words2 = text2.toLowerCase().split(/\s+/);
168
+ const set1 = new Set(words1);
169
+ const set2 = new Set(words2);
170
+ const intersection = new Set([...set1].filter(word => set2.has(word)));
171
+ const union = new Set([...set1, ...set2]);
172
+ return union.size > 0 ? intersection.size / union.size : 0;
173
+ }
174
+ /**
175
+ * Find differences between two texts
176
+ *
177
+ * @param original - Original text
178
+ * @param transcribed - Transcribed text
179
+ * @returns Array of difference descriptions
180
+ */
181
+ function findDifferences(original, transcribed) {
182
+ const differences = [];
183
+ const origWords = original.split(/\s+/);
184
+ const transWords = transcribed.split(/\s+/);
185
+ // Word count difference
186
+ if (origWords.length !== transWords.length) {
187
+ differences.push(`Word count: original has ${origWords.length}, transcribed has ${transWords.length}`);
188
+ }
189
+ // Find missing words
190
+ const origSet = new Set(origWords.map(w => w.toLowerCase()));
191
+ const transSet = new Set(transWords.map(w => w.toLowerCase()));
192
+ const missingInTrans = [...origSet].filter(w => !transSet.has(w));
193
+ const extraInTrans = [...transSet].filter(w => !origSet.has(w));
194
+ if (missingInTrans.length > 0) {
195
+ differences.push(`Missing in transcription: ${missingInTrans.slice(0, 5).join(', ')}${missingInTrans.length > 5 ? '...' : ''}`);
196
+ }
197
+ if (extraInTrans.length > 0) {
198
+ differences.push(`Extra in transcription: ${extraInTrans.slice(0, 5).join(', ')}${extraInTrans.length > 5 ? '...' : ''}`);
199
+ }
200
+ return differences;
201
+ }
202
+ /**
203
+ * Compare original script with transcribed audio
204
+ *
205
+ * @param originalText - Original script text
206
+ * @param transcribedText - Transcribed audio text
207
+ * @returns Comparison result with differences and similarity score
208
+ *
209
+ * @example
210
+ * ```typescript
211
+ * const result = compareTexts(originalScript, transcribedText);
212
+ * console.log(`Similarity: ${(result.similarity * 100).toFixed(1)}%`);
213
+ * console.log('Differences:', result.differences);
214
+ * ```
215
+ */
216
+ function compareTexts(originalText, transcribedText) {
217
+ // Sanitize both texts
218
+ const sanitizedOriginal = sanitizeText(originalText);
219
+ const sanitizedTranscribed = sanitizeText(transcribedText);
220
+ // Calculate similarity
221
+ const similarity = calculateSimilarity(sanitizedOriginal, sanitizedTranscribed);
222
+ // Find differences
223
+ const differences = findDifferences(sanitizedOriginal, sanitizedTranscribed);
224
+ return {
225
+ original: originalText,
226
+ transcribed: transcribedText,
227
+ sanitizedOriginal,
228
+ sanitizedTranscribed,
229
+ differences,
230
+ similarity,
231
+ };
232
+ }
233
+ /**
234
+ * Generate corrected script based on transcription
235
+ *
236
+ * @param scriptPath - Path to original script file
237
+ * @param audioPath - Path to audio file
238
+ * @param openaiApiKey - OpenAI API key for transcription
239
+ * @returns Comparison result with corrections
240
+ *
241
+ * @example
242
+ * ```typescript
243
+ * const result = await generateCorrections({
244
+ * scriptPath: './script.txt',
245
+ * audioPath: './audio.mp3',
246
+ * openaiApiKey: process.env.OPENAI_API_KEY!
247
+ * });
248
+ *
249
+ * console.log('Corrections needed:', result.differences);
250
+ * ```
251
+ */
252
+ async function generateCorrections(options) {
253
+ const { scriptPath, audioPath, openaiApiKey } = options;
254
+ // Read original script
255
+ const originalText = fs.readFileSync(scriptPath, 'utf-8');
256
+ // Transcribe audio
257
+ const transcribedText = await transcribeAudio({
258
+ audioPath,
259
+ openaiApiKey,
260
+ });
261
+ // Compare texts
262
+ const comparison = compareTexts(originalText, transcribedText);
263
+ // Print report
264
+ console.log('\n📊 Script Comparison Report');
265
+ console.log('━━━━━━━━━━━━━━━━━━━━━━━━━━━━');
266
+ console.log(`Similarity: ${(comparison.similarity * 100).toFixed(1)}%`);
267
+ console.log(`\nOriginal (sanitized):\n${comparison.sanitizedOriginal}`);
268
+ console.log(`\nTranscribed (sanitized):\n${comparison.sanitizedTranscribed}`);
269
+ if (comparison.differences.length > 0) {
270
+ console.log('\n⚠️ Differences found:');
271
+ comparison.differences.forEach((diff, index) => {
272
+ console.log(` ${index + 1}. ${diff}`);
273
+ });
274
+ }
275
+ else {
276
+ console.log('\n✅ No significant differences found');
277
+ }
278
+ return comparison;
279
+ }
280
+ //# sourceMappingURL=transcription.js.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"transcription.js","sourceRoot":"","sources":["../../src/lib/utils/transcription.ts"],"names":[],"mappings":"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"}
@@ -0,0 +1,58 @@
1
+ import type { Caption, RenderVideoOptions, GenerateVideoOptions } from '../types';
2
+ /**
3
+ * Render video using Remotion
4
+ *
5
+ * This function takes captions and an audio file, then renders them
6
+ * into a video using Remotion. The captions are temporarily saved to
7
+ * a file, then Remotion is invoked via CLI to render the final video.
8
+ *
9
+ * @param options - Rendering configuration
10
+ * @param options.audioPath - Path to audio file
11
+ * @param options.captions - Array of captions with timing
12
+ * @param options.outputPath - Path for output video file
13
+ * @param options.compositionPath - Path to Remotion composition (default: src/index.ts)
14
+ * @param options.width - Video width (default: 1920)
15
+ * @param options.height - Video height (default: 1080)
16
+ * @param options.fps - Frame rate (default: 30)
17
+ *
18
+ * @example
19
+ * ```typescript
20
+ * import { generateCaptions, renderVideo } from 'caption-sync';
21
+ *
22
+ * const captions = generateCaptions({
23
+ * scriptPath: './script.txt',
24
+ * audioPath: './audio.mp3'
25
+ * });
26
+ *
27
+ * await renderVideo({
28
+ * audioPath: './audio.mp3',
29
+ * captions,
30
+ * outputPath: './out/video.mp4'
31
+ * });
32
+ * ```
33
+ */
34
+ export declare function renderVideo(options: RenderVideoOptions): void;
35
+ /**
36
+ * Generate captions and render video in one step
37
+ *
38
+ * This is a convenience function that combines caption generation
39
+ * and video rendering into a single operation.
40
+ *
41
+ * @param options - Combined options for caption generation and rendering
42
+ * @returns Array of generated captions
43
+ *
44
+ * @example
45
+ * ```typescript
46
+ * import { generateVideo } from 'caption-sync';
47
+ *
48
+ * const captions = await generateVideo({
49
+ * scriptPath: './script.txt',
50
+ * audioPath: './audio.mp3',
51
+ * outputPath: './out/video.mp4'
52
+ * });
53
+ *
54
+ * console.log(`Generated ${captions.length} captions`);
55
+ * ```
56
+ */
57
+ export declare function generateVideo(options: GenerateVideoOptions): Promise<Caption[]>;
58
+ //# sourceMappingURL=video-renderer.d.ts.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"video-renderer.d.ts","sourceRoot":"","sources":["../../src/lib/utils/video-renderer.ts"],"names":[],"mappings":"AAGA,OAAO,KAAK,EACV,OAAO,EACP,kBAAkB,EAClB,oBAAoB,EACrB,MAAM,UAAU,CAAC;AAGlB;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;GA+BG;AACH,wBAAgB,WAAW,CAAC,OAAO,EAAE,kBAAkB,GAAG,IAAI,CAkD7D;AAED;;;;;;;;;;;;;;;;;;;;;GAqBG;AACH,wBAAsB,aAAa,CACjC,OAAO,EAAE,oBAAoB,GAC5B,OAAO,CAAC,OAAO,EAAE,CAAC,CAmCpB"}
@@ -0,0 +1,153 @@
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.renderVideo = renderVideo;
37
+ exports.generateVideo = generateVideo;
38
+ const fs = __importStar(require("fs"));
39
+ const path = __importStar(require("path"));
40
+ const child_process_1 = require("child_process");
41
+ const caption_generator_1 = require("./caption-generator");
42
+ /**
43
+ * Render video using Remotion
44
+ *
45
+ * This function takes captions and an audio file, then renders them
46
+ * into a video using Remotion. The captions are temporarily saved to
47
+ * a file, then Remotion is invoked via CLI to render the final video.
48
+ *
49
+ * @param options - Rendering configuration
50
+ * @param options.audioPath - Path to audio file
51
+ * @param options.captions - Array of captions with timing
52
+ * @param options.outputPath - Path for output video file
53
+ * @param options.compositionPath - Path to Remotion composition (default: src/index.ts)
54
+ * @param options.width - Video width (default: 1920)
55
+ * @param options.height - Video height (default: 1080)
56
+ * @param options.fps - Frame rate (default: 30)
57
+ *
58
+ * @example
59
+ * ```typescript
60
+ * import { generateCaptions, renderVideo } from 'caption-sync';
61
+ *
62
+ * const captions = generateCaptions({
63
+ * scriptPath: './script.txt',
64
+ * audioPath: './audio.mp3'
65
+ * });
66
+ *
67
+ * await renderVideo({
68
+ * audioPath: './audio.mp3',
69
+ * captions,
70
+ * outputPath: './out/video.mp4'
71
+ * });
72
+ * ```
73
+ */
74
+ function renderVideo(options) {
75
+ const { audioPath, captions, outputPath, compositionPath = 'src/index.tsx', width = 1080, height = 1920, fps = 30, } = options;
76
+ // Ensure output directory exists
77
+ const outputDir = path.dirname(outputPath);
78
+ if (!fs.existsSync(outputDir)) {
79
+ fs.mkdirSync(outputDir, { recursive: true });
80
+ }
81
+ // Calculate duration from captions
82
+ const duration = captions.length > 0 ? Math.ceil(captions[captions.length - 1].end) : 10;
83
+ // Convert audio path to be relative to public/ directory for Remotion
84
+ // If the path is in public/, use relative path; otherwise use absolute
85
+ const absoluteAudioPath = path.resolve(audioPath);
86
+ const publicDir = path.join(process.cwd(), 'public');
87
+ let remotionAudioPath = absoluteAudioPath;
88
+ // Check if audio is in public/ directory
89
+ if (absoluteAudioPath.startsWith(publicDir + path.sep)) {
90
+ // Make it relative to public/ so staticFile() works
91
+ remotionAudioPath = path.relative(publicDir, absoluteAudioPath);
92
+ }
93
+ // Set environment variables for Remotion composition
94
+ // Pass captions as JSON string to avoid file I/O in composition
95
+ const env = {
96
+ ...process.env,
97
+ REMOTION_AUDIO_PATH: remotionAudioPath,
98
+ REMOTION_CAPTIONS_JSON: JSON.stringify(captions),
99
+ REMOTION_DURATION: duration.toString(),
100
+ };
101
+ // Build remotion render command
102
+ const cmd = `npx remotion render ${compositionPath} Video "${outputPath}" --width=${width} --height=${height} --fps=${fps}`;
103
+ // Execute render
104
+ (0, child_process_1.execSync)(cmd, {
105
+ stdio: 'inherit',
106
+ env,
107
+ });
108
+ }
109
+ /**
110
+ * Generate captions and render video in one step
111
+ *
112
+ * This is a convenience function that combines caption generation
113
+ * and video rendering into a single operation.
114
+ *
115
+ * @param options - Combined options for caption generation and rendering
116
+ * @returns Array of generated captions
117
+ *
118
+ * @example
119
+ * ```typescript
120
+ * import { generateVideo } from 'caption-sync';
121
+ *
122
+ * const captions = await generateVideo({
123
+ * scriptPath: './script.txt',
124
+ * audioPath: './audio.mp3',
125
+ * outputPath: './out/video.mp4'
126
+ * });
127
+ *
128
+ * console.log(`Generated ${captions.length} captions`);
129
+ * ```
130
+ */
131
+ async function generateVideo(options) {
132
+ const { scriptPath, audioPath, outputPath, width, height, fps, placement, maxCharacters, useTranscription, openaiApiKey, } = options;
133
+ // Generate captions
134
+ const captions = await (0, caption_generator_1.generateCaptions)({
135
+ scriptPath,
136
+ audioPath,
137
+ maxCharacters,
138
+ useTranscription,
139
+ openaiApiKey,
140
+ });
141
+ // Render video
142
+ renderVideo({
143
+ audioPath,
144
+ captions,
145
+ outputPath,
146
+ width,
147
+ height,
148
+ fps,
149
+ placement,
150
+ });
151
+ return captions;
152
+ }
153
+ //# sourceMappingURL=video-renderer.js.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"video-renderer.js","sourceRoot":"","sources":["../../src/lib/utils/video-renderer.ts"],"names":[],"mappings":";;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;AA0CA,kCAkDC;AAwBD,sCAqCC;AAzJD,uCAAyB;AACzB,2CAA6B;AAC7B,iDAAyC;AAMzC,2DAAuD;AAEvD;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;GA+BG;AACH,SAAgB,WAAW,CAAC,OAA2B;IACrD,MAAM,EACJ,SAAS,EACT,QAAQ,EACR,UAAU,EACV,eAAe,GAAG,eAAe,EACjC,KAAK,GAAG,IAAI,EACZ,MAAM,GAAG,IAAI,EACb,GAAG,GAAG,EAAE,GACT,GAAG,OAAO,CAAC;IAEZ,iCAAiC;IACjC,MAAM,SAAS,GAAG,IAAI,CAAC,OAAO,CAAC,UAAU,CAAC,CAAC;IAC3C,IAAI,CAAC,EAAE,CAAC,UAAU,CAAC,SAAS,CAAC,EAAE,CAAC;QAC9B,EAAE,CAAC,SAAS,CAAC,SAAS,EAAE,EAAE,SAAS,EAAE,IAAI,EAAE,CAAC,CAAC;IAC/C,CAAC;IAED,mCAAmC;IACnC,MAAM,QAAQ,GACZ,QAAQ,CAAC,MAAM,GAAG,CAAC,CAAC,CAAC,CAAC,IAAI,CAAC,IAAI,CAAC,QAAQ,CAAC,QAAQ,CAAC,MAAM,GAAG,CAAC,CAAC,CAAC,GAAG,CAAC,CAAC,CAAC,CAAC,EAAE,CAAC;IAE1E,sEAAsE;IACtE,uEAAuE;IACvE,MAAM,iBAAiB,GAAG,IAAI,CAAC,OAAO,CAAC,SAAS,CAAC,CAAC;IAClD,MAAM,SAAS,GAAG,IAAI,CAAC,IAAI,CAAC,OAAO,CAAC,GAAG,EAAE,EAAE,QAAQ,CAAC,CAAC;IACrD,IAAI,iBAAiB,GAAG,iBAAiB,CAAC;IAE1C,yCAAyC;IACzC,IAAI,iBAAiB,CAAC,UAAU,CAAC,SAAS,GAAG,IAAI,CAAC,GAAG,CAAC,EAAE,CAAC;QACvD,oDAAoD;QACpD,iBAAiB,GAAG,IAAI,CAAC,QAAQ,CAAC,SAAS,EAAE,iBAAiB,CAAC,CAAC;IAClE,CAAC;IAED,qDAAqD;IACrD,gEAAgE;IAChE,MAAM,GAAG,GAAG;QACV,GAAG,OAAO,CAAC,GAAG;QACd,mBAAmB,EAAE,iBAAiB;QACtC,sBAAsB,EAAE,IAAI,CAAC,SAAS,CAAC,QAAQ,CAAC;QAChD,iBAAiB,EAAE,QAAQ,CAAC,QAAQ,EAAE;KACvC,CAAC;IAEF,gCAAgC;IAChC,MAAM,GAAG,GAAG,uBAAuB,eAAe,WAAW,UAAU,aAAa,KAAK,aAAa,MAAM,UAAU,GAAG,EAAE,CAAC;IAE5H,iBAAiB;IACjB,IAAA,wBAAQ,EAAC,GAAG,EAAE;QACZ,KAAK,EAAE,SAAS;QAChB,GAAG;KACJ,CAAC,CAAC;AACL,CAAC;AAED;;;;;;;;;;;;;;;;;;;;;GAqBG;AACI,KAAK,UAAU,aAAa,CACjC,OAA6B;IAE7B,MAAM,EACJ,UAAU,EACV,SAAS,EACT,UAAU,EACV,KAAK,EACL,MAAM,EACN,GAAG,EACH,SAAS,EACT,aAAa,EACb,gBAAgB,EAChB,YAAY,GACb,GAAG,OAAO,CAAC;IAEZ,oBAAoB;IACpB,MAAM,QAAQ,GAAG,MAAM,IAAA,oCAAgB,EAAC;QACtC,UAAU;QACV,SAAS;QACT,aAAa;QACb,gBAAgB;QAChB,YAAY;KACb,CAAC,CAAC;IAEH,eAAe;IACf,WAAW,CAAC;QACV,SAAS;QACT,QAAQ;QACR,UAAU;QACV,KAAK;QACL,MAAM;QACN,GAAG;QACH,SAAS;KACV,CAAC,CAAC;IAEH,OAAO,QAAQ,CAAC;AAClB,CAAC"}
package/package.json ADDED
@@ -0,0 +1,80 @@
1
+ {
2
+ "name": "@brickgale/caption-sync",
3
+ "version": "1.0.0",
4
+ "description": "Video generation with automatic caption timing using Remotion",
5
+ "main": "dist/index.js",
6
+ "types": "dist/index.d.ts",
7
+ "exports": {
8
+ ".": {
9
+ "types": "./dist/index.d.ts",
10
+ "require": "./dist/index.js",
11
+ "import": "./dist/index.js"
12
+ },
13
+ "./types": {
14
+ "types": "./dist/types.d.ts"
15
+ }
16
+ },
17
+ "files": [
18
+ "dist/",
19
+ "README.md",
20
+ "LICENSE"
21
+ ],
22
+ "keywords": [
23
+ "video-generation",
24
+ "captions",
25
+ "remotion",
26
+ "automation",
27
+ "subtitles",
28
+ "video",
29
+ "react"
30
+ ],
31
+ "author": "brickgale",
32
+ "license": "MIT",
33
+ "repository": {
34
+ "type": "git",
35
+ "url": "git+https://github.com/brickgale/caption-sync.git"
36
+ },
37
+ "homepage": "https://github.com/brickgale/caption-sync#readme",
38
+ "bugs": {
39
+ "url": "https://github.com/brickgale/caption-sync/issues"
40
+ },
41
+ "publishConfig": {
42
+ "access": "public"
43
+ },
44
+ "engines": {
45
+ "node": ">=20.6.0"
46
+ },
47
+ "scripts": {
48
+ "generate": "node --env-file=.env -r ts-node/register src/cli/generate-video.ts",
49
+ "dev": "node --env-file=.env -r ts-node/register src/cli/dev-helper.ts",
50
+ "dev:studio": "remotion studio src/index.tsx",
51
+ "build": "remotion render src/index.tsx Video out/final.mp4",
52
+ "generate-captions": "node --env-file=.env -r ts-node/register src/cli/caption-generator.ts",
53
+ "verify-script": "node --env-file=.env -r ts-node/register src/cli/verify-script.ts",
54
+ "format": "prettier --write .",
55
+ "format:check": "prettier --check .",
56
+ "typecheck": "tsc --noEmit --project tsconfig.lib.json",
57
+ "compile": "tsc --project tsconfig.lib.json",
58
+ "prepublishOnly": "npm run compile"
59
+ },
60
+ "dependencies": {
61
+ "@remotion/bundler": "^4.0.0",
62
+ "@remotion/cli": "^4.0.0",
63
+ "@types/form-data": "^2.2.1",
64
+ "form-data": "^4.0.5",
65
+ "openai": "^6.35.0",
66
+ "react": "^18.2.0",
67
+ "react-dom": "^18.2.0",
68
+ "remotion": "^4.0.0"
69
+ },
70
+ "overrides": {
71
+ "postcss": "^8.5.10"
72
+ },
73
+ "devDependencies": {
74
+ "@types/node": "^20.19.39",
75
+ "@types/react": "^18.2.0",
76
+ "prettier": "^3.2.0",
77
+ "ts-node": "^10.9.0",
78
+ "typescript": "^5.0.0"
79
+ }
80
+ }