extract-youtube 1.0.1
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/CHANGELOG.md +1 -0
- package/README.md +510 -0
- package/__tests__/api.test.ts +163 -0
- package/__tests__/basic-usage.ts +298 -0
- package/__tests__/formatters.test.ts +239 -0
- package/__tests__/integration.test.ts +283 -0
- package/dist/index.cjs +2 -0
- package/dist/index.cjs.map +1 -0
- package/dist/index.mjs +2 -0
- package/dist/index.mjs.map +1 -0
- package/jest.config.js +17 -0
- package/package.json +71 -0
- package/src/cli.ts +197 -0
- package/src/errors.ts +364 -0
- package/src/fetchers/transcript-list-fetcher.ts +252 -0
- package/src/fetchers/youtube-data-extractor.ts +151 -0
- package/src/formatters.ts +163 -0
- package/src/http/fetch-http-client.ts +102 -0
- package/src/index.ts +77 -0
- package/src/models/fetched-transcript.ts +110 -0
- package/src/models/index.ts +7 -0
- package/src/models/transcript-list.ts +252 -0
- package/src/models/transcript.ts +187 -0
- package/src/parsers/transcript-parser.ts +135 -0
- package/src/proxies.ts +222 -0
- package/src/types.ts +119 -0
- package/src/utils/http-error-handler.ts +32 -0
- package/src/youtube-transcript-api.ts +254 -0
- package/tsconfig.json +20 -0
- package/vite.config.ts +64 -0
|
@@ -0,0 +1,298 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* @fileoverview Basic usage examples for YouTube Transcript API
|
|
3
|
+
*/
|
|
4
|
+
|
|
5
|
+
import {
|
|
6
|
+
YouTubeTranscriptApi,
|
|
7
|
+
SRTFormatter,
|
|
8
|
+
WebVTTFormatter,
|
|
9
|
+
JSONFormatter,
|
|
10
|
+
NoTranscriptFound,
|
|
11
|
+
TranscriptsDisabled,
|
|
12
|
+
} from "../src";
|
|
13
|
+
|
|
14
|
+
/**
|
|
15
|
+
* Example 1: Fetch a transcript with default settings
|
|
16
|
+
*/
|
|
17
|
+
async function example1() {
|
|
18
|
+
console.log("\n=== Example 1: Basic Fetch ===");
|
|
19
|
+
|
|
20
|
+
const api = new YouTubeTranscriptApi();
|
|
21
|
+
|
|
22
|
+
try {
|
|
23
|
+
const transcript = await api.fetch("xz-DTHwRMDo");
|
|
24
|
+
|
|
25
|
+
console.log(`Video ID: ${transcript.videoId}`);
|
|
26
|
+
console.log(
|
|
27
|
+
`Language: ${transcript.language} (${transcript.languageCode})`,
|
|
28
|
+
);
|
|
29
|
+
console.log(`Auto-generated: ${transcript.isGenerated}`);
|
|
30
|
+
console.log(`Snippets: ${transcript.length}\n`);
|
|
31
|
+
|
|
32
|
+
// Print first 3 snippets
|
|
33
|
+
for (let i = 0; i < Math.min(40, transcript.length); i++) {
|
|
34
|
+
const snippet = transcript.get(i);
|
|
35
|
+
console.log(`[${snippet.start.toFixed(2)}s] ${snippet.text}`);
|
|
36
|
+
}
|
|
37
|
+
} catch (error: any) {
|
|
38
|
+
console.error("Error type:", error.constructor?.name || typeof error);
|
|
39
|
+
console.error("Error message:", error.message);
|
|
40
|
+
console.error("Full error:", error);
|
|
41
|
+
if (error.stack) {
|
|
42
|
+
console.error("\nStack trace:");
|
|
43
|
+
console.error(error.stack);
|
|
44
|
+
}
|
|
45
|
+
}
|
|
46
|
+
}
|
|
47
|
+
|
|
48
|
+
/**
|
|
49
|
+
* Example 2: List available transcripts
|
|
50
|
+
*/
|
|
51
|
+
async function example2() {
|
|
52
|
+
console.log("\n=== Example 2: List Available Transcripts ===");
|
|
53
|
+
|
|
54
|
+
const api = new YouTubeTranscriptApi();
|
|
55
|
+
|
|
56
|
+
try {
|
|
57
|
+
const transcriptList = await api.list("dQw4w9WgXcQ");
|
|
58
|
+
|
|
59
|
+
console.log("Available transcripts:");
|
|
60
|
+
for (const transcript of transcriptList) {
|
|
61
|
+
console.log(
|
|
62
|
+
`- ${transcript.language} (${transcript.languageCode}) ` +
|
|
63
|
+
`[${transcript.isGenerated ? "Auto" : "Manual"}]` +
|
|
64
|
+
`${transcript.isTranslatable ? " [Translatable]" : ""}`,
|
|
65
|
+
);
|
|
66
|
+
}
|
|
67
|
+
} catch (error: any) {
|
|
68
|
+
console.error("Error:", error.message);
|
|
69
|
+
}
|
|
70
|
+
}
|
|
71
|
+
|
|
72
|
+
/**
|
|
73
|
+
* Example 3: Fetch with language preference
|
|
74
|
+
*/
|
|
75
|
+
async function example3() {
|
|
76
|
+
console.log("\n=== Example 3: Language Preference ===");
|
|
77
|
+
|
|
78
|
+
const api = new YouTubeTranscriptApi();
|
|
79
|
+
|
|
80
|
+
try {
|
|
81
|
+
// Try German first, then English
|
|
82
|
+
const transcript = await api.fetch("dQw4w9WgXcQ", {
|
|
83
|
+
languages: ["de", "en"],
|
|
84
|
+
});
|
|
85
|
+
|
|
86
|
+
console.log(`Found: ${transcript.language} (${transcript.languageCode})`);
|
|
87
|
+
} catch (error: any) {
|
|
88
|
+
if (error instanceof NoTranscriptFound) {
|
|
89
|
+
console.error("No transcript found for requested languages");
|
|
90
|
+
} else {
|
|
91
|
+
console.error("Error:", error.message);
|
|
92
|
+
}
|
|
93
|
+
}
|
|
94
|
+
}
|
|
95
|
+
|
|
96
|
+
/**
|
|
97
|
+
* Example 4: Translate a transcript
|
|
98
|
+
*/
|
|
99
|
+
async function example4() {
|
|
100
|
+
console.log("\n=== Example 4: Translate Transcript ===");
|
|
101
|
+
|
|
102
|
+
const api = new YouTubeTranscriptApi();
|
|
103
|
+
|
|
104
|
+
try {
|
|
105
|
+
const transcriptList = await api.list("dQw4w9WgXcQ");
|
|
106
|
+
|
|
107
|
+
// Find English transcript
|
|
108
|
+
const englishTranscript = transcriptList.findTranscript(["en"]);
|
|
109
|
+
console.log(`Original: ${englishTranscript.language}`);
|
|
110
|
+
|
|
111
|
+
if (englishTranscript.isTranslatable) {
|
|
112
|
+
// Translate to German
|
|
113
|
+
const germanTranscript = englishTranscript.translate("de");
|
|
114
|
+
const fetched = await germanTranscript.fetch();
|
|
115
|
+
|
|
116
|
+
console.log(
|
|
117
|
+
`Translated to: ${fetched.language} (${fetched.languageCode})`,
|
|
118
|
+
);
|
|
119
|
+
console.log(`First snippet: ${fetched.get(0).text}`);
|
|
120
|
+
}
|
|
121
|
+
} catch (error: any) {
|
|
122
|
+
console.error("Error:", error.message);
|
|
123
|
+
}
|
|
124
|
+
}
|
|
125
|
+
|
|
126
|
+
/**
|
|
127
|
+
* Example 5: Format as SRT
|
|
128
|
+
*/
|
|
129
|
+
async function example5() {
|
|
130
|
+
console.log("\n=== Example 5: Format as SRT ===");
|
|
131
|
+
|
|
132
|
+
const api = new YouTubeTranscriptApi();
|
|
133
|
+
|
|
134
|
+
try {
|
|
135
|
+
const transcript = await api.fetch("dQw4w9WgXcQ");
|
|
136
|
+
|
|
137
|
+
const formatter = new SRTFormatter();
|
|
138
|
+
const srt = formatter.formatTranscript(transcript);
|
|
139
|
+
|
|
140
|
+
// Print first 200 characters
|
|
141
|
+
console.log(srt.substring(0, 200) + "...\n");
|
|
142
|
+
} catch (error: any) {
|
|
143
|
+
console.error("Error:", error.message);
|
|
144
|
+
}
|
|
145
|
+
}
|
|
146
|
+
|
|
147
|
+
/**
|
|
148
|
+
* Example 6: Format as WebVTT
|
|
149
|
+
*/
|
|
150
|
+
async function example6() {
|
|
151
|
+
console.log("\n=== Example 6: Format as WebVTT ===");
|
|
152
|
+
|
|
153
|
+
const api = new YouTubeTranscriptApi();
|
|
154
|
+
|
|
155
|
+
try {
|
|
156
|
+
const transcript = await api.fetch("dQw4w9WgXcQ");
|
|
157
|
+
|
|
158
|
+
const formatter = new WebVTTFormatter();
|
|
159
|
+
const webvtt = formatter.formatTranscript(transcript);
|
|
160
|
+
|
|
161
|
+
// Print first 200 characters
|
|
162
|
+
console.log(webvtt.substring(0, 200) + "...\n");
|
|
163
|
+
} catch (error: any) {
|
|
164
|
+
console.error("Error:", error.message);
|
|
165
|
+
}
|
|
166
|
+
}
|
|
167
|
+
|
|
168
|
+
/**
|
|
169
|
+
* Example 7: Format as JSON
|
|
170
|
+
*/
|
|
171
|
+
async function example7() {
|
|
172
|
+
console.log("\n=== Example 7: Format as JSON ===");
|
|
173
|
+
|
|
174
|
+
const api = new YouTubeTranscriptApi();
|
|
175
|
+
|
|
176
|
+
try {
|
|
177
|
+
const transcript = await api.fetch("dQw4w9WgXcQ");
|
|
178
|
+
|
|
179
|
+
const formatter = new JSONFormatter();
|
|
180
|
+
const json = formatter.formatTranscript(transcript, { indent: 2 });
|
|
181
|
+
|
|
182
|
+
// Print first 300 characters
|
|
183
|
+
console.log(json.substring(0, 300) + "...\n");
|
|
184
|
+
} catch (error: any) {
|
|
185
|
+
console.error("Error:", error.message);
|
|
186
|
+
}
|
|
187
|
+
}
|
|
188
|
+
|
|
189
|
+
/**
|
|
190
|
+
* Example 8: Error handling
|
|
191
|
+
*/
|
|
192
|
+
async function example8() {
|
|
193
|
+
console.log("\n=== Example 8: Error Handling ===");
|
|
194
|
+
|
|
195
|
+
const api = new YouTubeTranscriptApi();
|
|
196
|
+
|
|
197
|
+
try {
|
|
198
|
+
// Try to fetch transcript for a video without subtitles
|
|
199
|
+
const transcript = await api.fetch("invalid-video-id");
|
|
200
|
+
} catch (error: any) {
|
|
201
|
+
if (error instanceof TranscriptsDisabled) {
|
|
202
|
+
console.error("Subtitles are disabled for this video");
|
|
203
|
+
} else if (error instanceof NoTranscriptFound) {
|
|
204
|
+
console.error("No transcript found for requested languages");
|
|
205
|
+
} else {
|
|
206
|
+
console.error(`Error type: ${error.constructor.name}`);
|
|
207
|
+
console.error(`Message: ${error.message}`);
|
|
208
|
+
}
|
|
209
|
+
}
|
|
210
|
+
}
|
|
211
|
+
|
|
212
|
+
/**
|
|
213
|
+
* Example 9: Iterate and filter
|
|
214
|
+
*/
|
|
215
|
+
async function example9() {
|
|
216
|
+
console.log("\n=== Example 9: Iterate and Filter ===");
|
|
217
|
+
|
|
218
|
+
const api = new YouTubeTranscriptApi();
|
|
219
|
+
|
|
220
|
+
try {
|
|
221
|
+
const transcript = await api.fetch("dQw4w9WgXcQ");
|
|
222
|
+
|
|
223
|
+
// Find snippets containing a specific word
|
|
224
|
+
const searchWord = "you";
|
|
225
|
+
let count = 0;
|
|
226
|
+
|
|
227
|
+
for (const snippet of transcript) {
|
|
228
|
+
if (snippet.text.toLowerCase().includes(searchWord)) {
|
|
229
|
+
count++;
|
|
230
|
+
if (count <= 3) {
|
|
231
|
+
console.log(`[${snippet.start.toFixed(2)}s] ${snippet.text}`);
|
|
232
|
+
}
|
|
233
|
+
}
|
|
234
|
+
}
|
|
235
|
+
|
|
236
|
+
console.log(`\nFound "${searchWord}" in ${count} snippets`);
|
|
237
|
+
} catch (error: any) {
|
|
238
|
+
console.error("Error:", error.message);
|
|
239
|
+
}
|
|
240
|
+
}
|
|
241
|
+
|
|
242
|
+
/**
|
|
243
|
+
* Example 10: Get raw data
|
|
244
|
+
*/
|
|
245
|
+
async function example10() {
|
|
246
|
+
console.log("\n=== Example 10: Raw Data ===");
|
|
247
|
+
|
|
248
|
+
const api = new YouTubeTranscriptApi();
|
|
249
|
+
|
|
250
|
+
try {
|
|
251
|
+
const transcript = await api.fetch("dQw4w9WgXcQ");
|
|
252
|
+
|
|
253
|
+
// Get raw data as plain objects
|
|
254
|
+
const rawData = transcript.toRawData();
|
|
255
|
+
|
|
256
|
+
console.log("Raw data (first 3 items):");
|
|
257
|
+
console.log(JSON.stringify(rawData.slice(0, 3), null, 2));
|
|
258
|
+
} catch (error: any) {
|
|
259
|
+
console.error("Error:", error.message);
|
|
260
|
+
}
|
|
261
|
+
}
|
|
262
|
+
|
|
263
|
+
// Run all examples
|
|
264
|
+
async function main() {
|
|
265
|
+
console.log("YouTube Transcript API - Usage Examples");
|
|
266
|
+
console.log("======================================");
|
|
267
|
+
|
|
268
|
+
await example1();
|
|
269
|
+
// await example2();
|
|
270
|
+
// await example3();
|
|
271
|
+
// await example4();
|
|
272
|
+
// await example5();
|
|
273
|
+
// await example6();
|
|
274
|
+
// await example7();
|
|
275
|
+
// await example8();
|
|
276
|
+
// await example9();
|
|
277
|
+
// await example10();
|
|
278
|
+
|
|
279
|
+
console.log("\n=== All examples completed ===\n");
|
|
280
|
+
}
|
|
281
|
+
|
|
282
|
+
// Run if executed directly
|
|
283
|
+
if (require.main === module) {
|
|
284
|
+
main().catch(console.error);
|
|
285
|
+
}
|
|
286
|
+
|
|
287
|
+
export {
|
|
288
|
+
example1,
|
|
289
|
+
example2,
|
|
290
|
+
example3,
|
|
291
|
+
example4,
|
|
292
|
+
example5,
|
|
293
|
+
example6,
|
|
294
|
+
example7,
|
|
295
|
+
example8,
|
|
296
|
+
example9,
|
|
297
|
+
example10,
|
|
298
|
+
};
|
|
@@ -0,0 +1,239 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* @fileoverview Unit tests for Formatter classes
|
|
3
|
+
*/
|
|
4
|
+
|
|
5
|
+
import {
|
|
6
|
+
Formatter,
|
|
7
|
+
JSONFormatter,
|
|
8
|
+
TextFormatter,
|
|
9
|
+
SRTFormatter,
|
|
10
|
+
WebVTTFormatter,
|
|
11
|
+
PrettyPrintFormatter,
|
|
12
|
+
FormatterLoader,
|
|
13
|
+
UnknownFormatterType
|
|
14
|
+
} from '../src/formatters';
|
|
15
|
+
import { FetchedTranscript } from '../src/models';
|
|
16
|
+
|
|
17
|
+
describe('Formatters', () => {
|
|
18
|
+
const mockSnippets = [
|
|
19
|
+
{ text: 'Hello there', start: 0.0, duration: 1.5 },
|
|
20
|
+
{ text: 'How are you', start: 1.5, duration: 2.0 },
|
|
21
|
+
{ text: 'Goodbye', start: 3.5, duration: 1.0 }
|
|
22
|
+
];
|
|
23
|
+
|
|
24
|
+
let transcript: FetchedTranscript;
|
|
25
|
+
|
|
26
|
+
beforeEach(() => {
|
|
27
|
+
transcript = new FetchedTranscript(
|
|
28
|
+
mockSnippets,
|
|
29
|
+
'test-video-id',
|
|
30
|
+
'English',
|
|
31
|
+
'en',
|
|
32
|
+
false
|
|
33
|
+
);
|
|
34
|
+
});
|
|
35
|
+
|
|
36
|
+
describe('JSONFormatter', () => {
|
|
37
|
+
it('should format transcript as JSON', () => {
|
|
38
|
+
const formatter = new JSONFormatter();
|
|
39
|
+
const result = formatter.formatTranscript(transcript);
|
|
40
|
+
|
|
41
|
+
expect(typeof result).toBe('string');
|
|
42
|
+
const parsed = JSON.parse(result);
|
|
43
|
+
expect(parsed).toHaveLength(3);
|
|
44
|
+
expect(parsed[0].text).toBe('Hello there');
|
|
45
|
+
expect(parsed[0].start).toBe(0.0);
|
|
46
|
+
expect(parsed[0].duration).toBe(1.5);
|
|
47
|
+
});
|
|
48
|
+
|
|
49
|
+
it('should format with indentation', () => {
|
|
50
|
+
const formatter = new JSONFormatter();
|
|
51
|
+
const result = formatter.formatTranscript(transcript, { indent: 2 });
|
|
52
|
+
|
|
53
|
+
expect(result).toContain(' "text"');
|
|
54
|
+
expect(result).toContain('\n');
|
|
55
|
+
});
|
|
56
|
+
|
|
57
|
+
it('should format multiple transcripts', () => {
|
|
58
|
+
const formatter = new JSONFormatter();
|
|
59
|
+
const result = formatter.formatTranscripts([transcript, transcript]);
|
|
60
|
+
|
|
61
|
+
const parsed = JSON.parse(result);
|
|
62
|
+
expect(parsed).toHaveLength(2);
|
|
63
|
+
});
|
|
64
|
+
});
|
|
65
|
+
|
|
66
|
+
describe('TextFormatter', () => {
|
|
67
|
+
it('should format transcript as plain text', () => {
|
|
68
|
+
const formatter = new TextFormatter();
|
|
69
|
+
const result = formatter.formatTranscript(transcript);
|
|
70
|
+
|
|
71
|
+
expect(result).toBe('Hello there\nHow are you\nGoodbye');
|
|
72
|
+
expect(result).not.toContain('0.0');
|
|
73
|
+
expect(result).not.toContain('duration');
|
|
74
|
+
});
|
|
75
|
+
|
|
76
|
+
it('should format multiple transcripts', () => {
|
|
77
|
+
const formatter = new TextFormatter();
|
|
78
|
+
const result = formatter.formatTranscripts([transcript, transcript]);
|
|
79
|
+
|
|
80
|
+
expect(result).toContain('\n\n\n');
|
|
81
|
+
});
|
|
82
|
+
});
|
|
83
|
+
|
|
84
|
+
describe('SRTFormatter', () => {
|
|
85
|
+
it('should format transcript as SRT', () => {
|
|
86
|
+
const formatter = new SRTFormatter();
|
|
87
|
+
const result = formatter.formatTranscript(transcript);
|
|
88
|
+
|
|
89
|
+
expect(result).toContain('1\n');
|
|
90
|
+
expect(result).toContain('00:00:00,000 --> 00:00:01,500');
|
|
91
|
+
expect(result).toContain('Hello there');
|
|
92
|
+
expect(result).toContain('2\n');
|
|
93
|
+
expect(result).toContain('00:00:01,500 --> 00:00:03,500');
|
|
94
|
+
expect(result).toContain('How are you');
|
|
95
|
+
});
|
|
96
|
+
|
|
97
|
+
it('should use comma as millisecond separator', () => {
|
|
98
|
+
const formatter = new SRTFormatter();
|
|
99
|
+
const result = formatter.formatTranscript(transcript);
|
|
100
|
+
|
|
101
|
+
expect(result).toMatch(/\d{2}:\d{2}:\d{2},\d{3}/);
|
|
102
|
+
});
|
|
103
|
+
|
|
104
|
+
it('should end with newline', () => {
|
|
105
|
+
const formatter = new SRTFormatter();
|
|
106
|
+
const result = formatter.formatTranscript(transcript);
|
|
107
|
+
|
|
108
|
+
expect(result.endsWith('\n')).toBe(true);
|
|
109
|
+
});
|
|
110
|
+
});
|
|
111
|
+
|
|
112
|
+
describe('WebVTTFormatter', () => {
|
|
113
|
+
it('should format transcript as WebVTT', () => {
|
|
114
|
+
const formatter = new WebVTTFormatter();
|
|
115
|
+
const result = formatter.formatTranscript(transcript);
|
|
116
|
+
|
|
117
|
+
expect(result).toContain('WEBVTT\n');
|
|
118
|
+
expect(result).toContain('00:00:00.000 --> 00:00:01.500');
|
|
119
|
+
expect(result).toContain('Hello there');
|
|
120
|
+
expect(result).toContain('00:00:01.500 --> 00:00:03.500');
|
|
121
|
+
expect(result).toContain('How are you');
|
|
122
|
+
});
|
|
123
|
+
|
|
124
|
+
it('should use period as millisecond separator', () => {
|
|
125
|
+
const formatter = new WebVTTFormatter();
|
|
126
|
+
const result = formatter.formatTranscript(transcript);
|
|
127
|
+
|
|
128
|
+
expect(result).toMatch(/\d{2}:\d{2}:\d{2}\.\d{3}/);
|
|
129
|
+
});
|
|
130
|
+
|
|
131
|
+
it('should start with WEBVTT header', () => {
|
|
132
|
+
const formatter = new WebVTTFormatter();
|
|
133
|
+
const result = formatter.formatTranscript(transcript);
|
|
134
|
+
|
|
135
|
+
expect(result.startsWith('WEBVTT\n')).toBe(true);
|
|
136
|
+
});
|
|
137
|
+
});
|
|
138
|
+
|
|
139
|
+
describe('PrettyPrintFormatter', () => {
|
|
140
|
+
it('should format transcript in pretty format', () => {
|
|
141
|
+
const formatter = new PrettyPrintFormatter();
|
|
142
|
+
const result = formatter.formatTranscript(transcript);
|
|
143
|
+
|
|
144
|
+
const parsed = JSON.parse(result);
|
|
145
|
+
expect(parsed).toHaveLength(3);
|
|
146
|
+
expect(result).toContain('\n');
|
|
147
|
+
});
|
|
148
|
+
});
|
|
149
|
+
|
|
150
|
+
describe('FormatterLoader', () => {
|
|
151
|
+
let loader: FormatterLoader;
|
|
152
|
+
|
|
153
|
+
beforeEach(() => {
|
|
154
|
+
loader = new FormatterLoader();
|
|
155
|
+
});
|
|
156
|
+
|
|
157
|
+
it('should load json formatter', () => {
|
|
158
|
+
const formatter = loader.load('json');
|
|
159
|
+
expect(formatter).toBeInstanceOf(JSONFormatter);
|
|
160
|
+
});
|
|
161
|
+
|
|
162
|
+
it('should load text formatter', () => {
|
|
163
|
+
const formatter = loader.load('text');
|
|
164
|
+
expect(formatter).toBeInstanceOf(TextFormatter);
|
|
165
|
+
});
|
|
166
|
+
|
|
167
|
+
it('should load srt formatter', () => {
|
|
168
|
+
const formatter = loader.load('srt');
|
|
169
|
+
expect(formatter).toBeInstanceOf(SRTFormatter);
|
|
170
|
+
});
|
|
171
|
+
|
|
172
|
+
it('should load webvtt formatter', () => {
|
|
173
|
+
const formatter = loader.load('webvtt');
|
|
174
|
+
expect(formatter).toBeInstanceOf(WebVTTFormatter);
|
|
175
|
+
});
|
|
176
|
+
|
|
177
|
+
it('should load pretty formatter', () => {
|
|
178
|
+
const formatter = loader.load('pretty');
|
|
179
|
+
expect(formatter).toBeInstanceOf(PrettyPrintFormatter);
|
|
180
|
+
});
|
|
181
|
+
|
|
182
|
+
it('should default to pretty formatter', () => {
|
|
183
|
+
const formatter = loader.load();
|
|
184
|
+
expect(formatter).toBeInstanceOf(PrettyPrintFormatter);
|
|
185
|
+
});
|
|
186
|
+
|
|
187
|
+
it('should throw error for unknown formatter type', () => {
|
|
188
|
+
expect(() => {
|
|
189
|
+
loader.load('unknown');
|
|
190
|
+
}).toThrow(UnknownFormatterType);
|
|
191
|
+
});
|
|
192
|
+
|
|
193
|
+
it('should list available types in error', () => {
|
|
194
|
+
try {
|
|
195
|
+
loader.load('invalid');
|
|
196
|
+
fail('Should have thrown error');
|
|
197
|
+
} catch (error) {
|
|
198
|
+
expect(error).toBeInstanceOf(UnknownFormatterType);
|
|
199
|
+
expect((error as Error).message).toContain('json');
|
|
200
|
+
expect((error as Error).message).toContain('text');
|
|
201
|
+
expect((error as Error).message).toContain('srt');
|
|
202
|
+
}
|
|
203
|
+
});
|
|
204
|
+
});
|
|
205
|
+
|
|
206
|
+
describe('Timestamp Formatting', () => {
|
|
207
|
+
it('should format hours correctly', () => {
|
|
208
|
+
const longTranscript = new FetchedTranscript(
|
|
209
|
+
[{ text: 'Test', start: 3661.5, duration: 1.0 }],
|
|
210
|
+
'test',
|
|
211
|
+
'English',
|
|
212
|
+
'en',
|
|
213
|
+
false
|
|
214
|
+
);
|
|
215
|
+
|
|
216
|
+
const srtFormatter = new SRTFormatter();
|
|
217
|
+
const srt = srtFormatter.formatTranscript(longTranscript);
|
|
218
|
+
expect(srt).toContain('01:01:01,500');
|
|
219
|
+
|
|
220
|
+
const webvttFormatter = new WebVTTFormatter();
|
|
221
|
+
const webvtt = webvttFormatter.formatTranscript(longTranscript);
|
|
222
|
+
expect(webvtt).toContain('01:01:01.500');
|
|
223
|
+
});
|
|
224
|
+
|
|
225
|
+
it('should pad numbers with zeros', () => {
|
|
226
|
+
const transcript = new FetchedTranscript(
|
|
227
|
+
[{ text: 'Test', start: 5.5, duration: 1.0 }],
|
|
228
|
+
'test',
|
|
229
|
+
'English',
|
|
230
|
+
'en',
|
|
231
|
+
false
|
|
232
|
+
);
|
|
233
|
+
|
|
234
|
+
const formatter = new SRTFormatter();
|
|
235
|
+
const result = formatter.formatTranscript(transcript);
|
|
236
|
+
expect(result).toContain('00:00:05,500');
|
|
237
|
+
});
|
|
238
|
+
});
|
|
239
|
+
});
|