youtube-audio-transcript-api 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/README.md +258 -0
- package/dist/index.d.mts +208 -0
- package/dist/index.d.ts +208 -0
- package/dist/index.js +271 -0
- package/dist/index.mjs +238 -0
- package/package.json +62 -0
package/README.md
ADDED
|
@@ -0,0 +1,258 @@
|
|
|
1
|
+
<p align="center">
|
|
2
|
+
<img src="https://youtubetranscript.dev/logo.svg" alt="YouTubeTranscript.dev" width="60" />
|
|
3
|
+
</p>
|
|
4
|
+
|
|
5
|
+
<h1 align="center">youtube-transcript-api</h1>
|
|
6
|
+
|
|
7
|
+
<p align="center">
|
|
8
|
+
Official Node.js / TypeScript SDK for the <a href="https://youtubetranscript.dev">YouTubeTranscript.dev</a> API (V2).
|
|
9
|
+
</p>
|
|
10
|
+
|
|
11
|
+
<p align="center">
|
|
12
|
+
<a href="https://www.npmjs.com/package/youtube-transcript-api"><img src="https://img.shields.io/npm/v/youtube-transcript-api" alt="npm" /></a>
|
|
13
|
+
<a href="https://youtubetranscript.dev"><img src="https://img.shields.io/badge/API-v2-brightgreen" alt="API Version" /></a>
|
|
14
|
+
<a href="https://github.com/youtubetranscript/youtube-transcript-api/blob/main/LICENSE"><img src="https://img.shields.io/npm/l/youtube-transcript-api" alt="License" /></a>
|
|
15
|
+
</p>
|
|
16
|
+
|
|
17
|
+
---
|
|
18
|
+
|
|
19
|
+
## Install
|
|
20
|
+
|
|
21
|
+
```bash
|
|
22
|
+
npm install youtube-transcript-api
|
|
23
|
+
```
|
|
24
|
+
|
|
25
|
+
```bash
|
|
26
|
+
pnpm add youtube-transcript-api
|
|
27
|
+
```
|
|
28
|
+
|
|
29
|
+
```bash
|
|
30
|
+
yarn add youtube-transcript-api
|
|
31
|
+
```
|
|
32
|
+
|
|
33
|
+
## Quick Start
|
|
34
|
+
|
|
35
|
+
```typescript
|
|
36
|
+
import { YouTubeTranscript } from "youtube-transcript-api";
|
|
37
|
+
|
|
38
|
+
const yt = new YouTubeTranscript({ apiKey: "your_api_key" });
|
|
39
|
+
|
|
40
|
+
// Simple — just pass a video ID or URL
|
|
41
|
+
const result = await yt.getTranscript("dQw4w9WgXcQ");
|
|
42
|
+
console.log(result.data?.transcript.text);
|
|
43
|
+
```
|
|
44
|
+
|
|
45
|
+
Get your API key at **[youtubetranscript.dev](https://youtubetranscript.dev)**
|
|
46
|
+
|
|
47
|
+
## Features
|
|
48
|
+
|
|
49
|
+
- ✅ Full V2 API coverage — transcribe, batch, jobs, polling
|
|
50
|
+
- ✅ TypeScript-first with complete type definitions
|
|
51
|
+
- ✅ Zero dependencies — uses native `fetch` (Node 18+)
|
|
52
|
+
- ✅ Typed errors for every API error code
|
|
53
|
+
- ✅ Built-in polling helpers for async ASR jobs
|
|
54
|
+
- ✅ ESM and CommonJS support
|
|
55
|
+
|
|
56
|
+
## Usage
|
|
57
|
+
|
|
58
|
+
### Basic Transcription
|
|
59
|
+
|
|
60
|
+
```typescript
|
|
61
|
+
import { YouTubeTranscript } from "youtube-transcript-api";
|
|
62
|
+
|
|
63
|
+
const yt = new YouTubeTranscript({ apiKey: "your_api_key" });
|
|
64
|
+
|
|
65
|
+
// By video ID
|
|
66
|
+
const result = await yt.getTranscript("dQw4w9WgXcQ");
|
|
67
|
+
|
|
68
|
+
// By URL
|
|
69
|
+
const result = await yt.getTranscript("https://www.youtube.com/watch?v=dQw4w9WgXcQ");
|
|
70
|
+
|
|
71
|
+
// With translation
|
|
72
|
+
const result = await yt.getTranscript("dQw4w9WgXcQ", "es");
|
|
73
|
+
```
|
|
74
|
+
|
|
75
|
+
### Full Options
|
|
76
|
+
|
|
77
|
+
```typescript
|
|
78
|
+
const result = await yt.transcribe({
|
|
79
|
+
video: "dQw4w9WgXcQ",
|
|
80
|
+
language: "fr",
|
|
81
|
+
source: "manual",
|
|
82
|
+
format: {
|
|
83
|
+
timestamp: true,
|
|
84
|
+
paragraphs: true,
|
|
85
|
+
words: false,
|
|
86
|
+
},
|
|
87
|
+
});
|
|
88
|
+
|
|
89
|
+
console.log(result.status); // "completed"
|
|
90
|
+
console.log(result.data?.transcript.text); // Full transcript text
|
|
91
|
+
console.log(result.data?.transcript.language); // "fr"
|
|
92
|
+
console.log(result.data?.transcript.segments); // Timestamped segments
|
|
93
|
+
console.log(result.credits_used); // Credits consumed
|
|
94
|
+
```
|
|
95
|
+
|
|
96
|
+
### Batch Processing (up to 100 videos)
|
|
97
|
+
|
|
98
|
+
```typescript
|
|
99
|
+
const result = await yt.batch({
|
|
100
|
+
video_ids: [
|
|
101
|
+
"dQw4w9WgXcQ",
|
|
102
|
+
"jNQXAC9IVRw",
|
|
103
|
+
"9bZkp7q19f0",
|
|
104
|
+
],
|
|
105
|
+
format: { timestamp: true },
|
|
106
|
+
});
|
|
107
|
+
|
|
108
|
+
console.log(result.summary);
|
|
109
|
+
// { total: 3, succeeded: 3, failed: 0, processing: 0 }
|
|
110
|
+
|
|
111
|
+
for (const item of result.results) {
|
|
112
|
+
console.log(`${item.data?.video_id}: ${item.data?.transcript.text.slice(0, 100)}...`);
|
|
113
|
+
}
|
|
114
|
+
```
|
|
115
|
+
|
|
116
|
+
### ASR Audio Transcription (Async)
|
|
117
|
+
|
|
118
|
+
For videos without captions, use ASR with a webhook or polling:
|
|
119
|
+
|
|
120
|
+
```typescript
|
|
121
|
+
// Option 1: Webhook (recommended for production)
|
|
122
|
+
const result = await yt.transcribe({
|
|
123
|
+
video: "VIDEO_ID",
|
|
124
|
+
source: "asr",
|
|
125
|
+
allow_asr: true,
|
|
126
|
+
webhook_url: "https://yoursite.com/webhook",
|
|
127
|
+
});
|
|
128
|
+
// result.status === "processing"
|
|
129
|
+
// result.job_id === "job_abc123"
|
|
130
|
+
|
|
131
|
+
// Option 2: Poll until complete
|
|
132
|
+
const result = await yt.transcribe({
|
|
133
|
+
video: "VIDEO_ID",
|
|
134
|
+
source: "asr",
|
|
135
|
+
allow_asr: true,
|
|
136
|
+
});
|
|
137
|
+
|
|
138
|
+
if (result.job_id) {
|
|
139
|
+
const final = await yt.waitForJob(result.job_id, {
|
|
140
|
+
interval: 5000, // poll every 5s
|
|
141
|
+
maxAttempts: 60, // give up after 5 minutes
|
|
142
|
+
});
|
|
143
|
+
console.log(final.data?.transcript.text);
|
|
144
|
+
}
|
|
145
|
+
```
|
|
146
|
+
|
|
147
|
+
### ASR Confirmation Flow
|
|
148
|
+
|
|
149
|
+
V2 requires explicit confirmation before ASR charges. If you don't set `allow_asr: true` and captions aren't available:
|
|
150
|
+
|
|
151
|
+
```typescript
|
|
152
|
+
const result = await yt.transcribe({
|
|
153
|
+
video: "VIDEO_WITHOUT_CAPTIONS",
|
|
154
|
+
source: "asr",
|
|
155
|
+
// allow_asr not set
|
|
156
|
+
});
|
|
157
|
+
|
|
158
|
+
if (result.status === "requires_asr_confirmation") {
|
|
159
|
+
console.log(result.estimated_credits); // e.g. 5
|
|
160
|
+
console.log(result.duration_minutes); // e.g. 7.5
|
|
161
|
+
console.log(result.suggestion); // "Set allow_asr=true to proceed"
|
|
162
|
+
|
|
163
|
+
// User confirms → retry with allow_asr
|
|
164
|
+
const confirmed = await yt.transcribe({
|
|
165
|
+
video: "VIDEO_WITHOUT_CAPTIONS",
|
|
166
|
+
source: "asr",
|
|
167
|
+
allow_asr: true,
|
|
168
|
+
webhook_url: "https://yoursite.com/webhook",
|
|
169
|
+
});
|
|
170
|
+
}
|
|
171
|
+
```
|
|
172
|
+
|
|
173
|
+
### Job & Batch Polling
|
|
174
|
+
|
|
175
|
+
```typescript
|
|
176
|
+
// Poll a single job
|
|
177
|
+
const job = await yt.getJob("job_abc123");
|
|
178
|
+
|
|
179
|
+
// Poll with format options
|
|
180
|
+
const job = await yt.getJob("job_abc123", {
|
|
181
|
+
include_segments: true,
|
|
182
|
+
include_paragraphs: true,
|
|
183
|
+
});
|
|
184
|
+
|
|
185
|
+
// Poll a batch
|
|
186
|
+
const batch = await yt.getBatch("batch_abc123");
|
|
187
|
+
|
|
188
|
+
// Auto-poll until done
|
|
189
|
+
const completed = await yt.waitForJob("job_abc123");
|
|
190
|
+
const completedBatch = await yt.waitForBatch("batch_abc123");
|
|
191
|
+
```
|
|
192
|
+
|
|
193
|
+
## Error Handling
|
|
194
|
+
|
|
195
|
+
Every API error maps to a typed exception:
|
|
196
|
+
|
|
197
|
+
```typescript
|
|
198
|
+
import {
|
|
199
|
+
YouTubeTranscript,
|
|
200
|
+
InvalidRequestError,
|
|
201
|
+
AuthenticationError,
|
|
202
|
+
InsufficientCreditsError,
|
|
203
|
+
NoCaptionsError,
|
|
204
|
+
RateLimitError,
|
|
205
|
+
} from "youtube-transcript-api";
|
|
206
|
+
|
|
207
|
+
try {
|
|
208
|
+
await yt.getTranscript("invalid");
|
|
209
|
+
} catch (error) {
|
|
210
|
+
if (error instanceof AuthenticationError) {
|
|
211
|
+
console.log("Bad API key");
|
|
212
|
+
} else if (error instanceof InsufficientCreditsError) {
|
|
213
|
+
console.log("Top up at https://youtubetranscript.dev/pricing");
|
|
214
|
+
} else if (error instanceof NoCaptionsError) {
|
|
215
|
+
console.log("No captions — try source: 'asr' with allow_asr: true");
|
|
216
|
+
} else if (error instanceof RateLimitError) {
|
|
217
|
+
console.log(`Rate limited. Retry after ${error.retryAfter}s`);
|
|
218
|
+
} else if (error instanceof InvalidRequestError) {
|
|
219
|
+
console.log(`Bad request: ${error.message}`);
|
|
220
|
+
}
|
|
221
|
+
}
|
|
222
|
+
```
|
|
223
|
+
|
|
224
|
+
| Error Class | HTTP Status | When |
|
|
225
|
+
|---|---|---|
|
|
226
|
+
| `InvalidRequestError` | 400 | Invalid JSON, missing fields, bad video ID |
|
|
227
|
+
| `AuthenticationError` | 401 | Missing or invalid API key |
|
|
228
|
+
| `InsufficientCreditsError` | 402 | Not enough credits |
|
|
229
|
+
| `NoCaptionsError` | 404 | No captions and ASR not used |
|
|
230
|
+
| `RateLimitError` | 429 | Too many requests |
|
|
231
|
+
| `YouTubeTranscriptError` | Other | Server errors, unexpected responses |
|
|
232
|
+
|
|
233
|
+
## Configuration
|
|
234
|
+
|
|
235
|
+
```typescript
|
|
236
|
+
const yt = new YouTubeTranscript({
|
|
237
|
+
apiKey: "your_api_key", // Required
|
|
238
|
+
baseUrl: "https://...", // Override API base URL
|
|
239
|
+
timeout: 60_000, // Request timeout in ms (default: 30s)
|
|
240
|
+
});
|
|
241
|
+
```
|
|
242
|
+
|
|
243
|
+
## Requirements
|
|
244
|
+
|
|
245
|
+
- Node.js 18+ (uses native `fetch`)
|
|
246
|
+
- API key from [youtubetranscript.dev](https://youtubetranscript.dev)
|
|
247
|
+
|
|
248
|
+
## Links
|
|
249
|
+
|
|
250
|
+
- 🌐 [Website](https://youtubetranscript.dev)
|
|
251
|
+
- 📖 [Full API Docs](https://youtubetranscript.dev/api-docs)
|
|
252
|
+
- 📐 [OpenAPI Spec](https://youtubetranscript.dev/api-docs#openapi)
|
|
253
|
+
- 💰 [Pricing](https://youtubetranscript.dev/pricing)
|
|
254
|
+
- 🐛 [Issues](https://github.com/youtubetranscript/youtube-transcript-api/issues)
|
|
255
|
+
|
|
256
|
+
## License
|
|
257
|
+
|
|
258
|
+
MIT — see [LICENSE](./LICENSE) for details.
|
package/dist/index.d.mts
ADDED
|
@@ -0,0 +1,208 @@
|
|
|
1
|
+
/** Control which extra fields are included in the response. */
|
|
2
|
+
interface FormatOptions {
|
|
3
|
+
/** Include timestamped segments */
|
|
4
|
+
timestamp?: boolean;
|
|
5
|
+
/** Include paragraph groupings */
|
|
6
|
+
paragraphs?: boolean;
|
|
7
|
+
/** Include word-level timestamps */
|
|
8
|
+
words?: boolean;
|
|
9
|
+
}
|
|
10
|
+
/** Caption source preference. */
|
|
11
|
+
type Source = "auto" | "manual" | "asr";
|
|
12
|
+
interface TranscribeRequest {
|
|
13
|
+
/** YouTube URL or 11-character video ID */
|
|
14
|
+
video: string;
|
|
15
|
+
/** ISO 639-1 language code (e.g. "es", "fr"). Omit for best available. */
|
|
16
|
+
language?: string;
|
|
17
|
+
/** Caption source: "auto" (default), "manual", or "asr" */
|
|
18
|
+
source?: Source;
|
|
19
|
+
/** Explicitly confirm ASR usage when captions are unavailable */
|
|
20
|
+
allow_asr?: boolean;
|
|
21
|
+
/** Control extra output fields */
|
|
22
|
+
format?: FormatOptions;
|
|
23
|
+
/** URL to receive async results. Required for source="asr". */
|
|
24
|
+
webhook_url?: string;
|
|
25
|
+
}
|
|
26
|
+
interface BatchRequest {
|
|
27
|
+
/** Array of YouTube URLs or 11-character video IDs (max 100) */
|
|
28
|
+
video_ids: string[];
|
|
29
|
+
/** ISO 639-1 language code */
|
|
30
|
+
language?: string;
|
|
31
|
+
/** Caption source preference */
|
|
32
|
+
source?: Source;
|
|
33
|
+
/** Explicitly confirm ASR usage */
|
|
34
|
+
allow_asr?: boolean;
|
|
35
|
+
/** Control extra output fields */
|
|
36
|
+
format?: FormatOptions;
|
|
37
|
+
/** URL to receive async results */
|
|
38
|
+
webhook_url?: string;
|
|
39
|
+
}
|
|
40
|
+
interface TranscriptPayload {
|
|
41
|
+
/** Full transcript text */
|
|
42
|
+
text: string;
|
|
43
|
+
/** Detected / returned language */
|
|
44
|
+
language: string;
|
|
45
|
+
/** Source of the transcript: manual captions, auto, or asr */
|
|
46
|
+
source: string;
|
|
47
|
+
/** Timestamped segments (when format.timestamp = true) */
|
|
48
|
+
segments?: Record<string, unknown>[];
|
|
49
|
+
/** Paragraph groupings (when format.paragraphs = true) */
|
|
50
|
+
paragraphs?: Record<string, unknown>[];
|
|
51
|
+
/** Word-level timestamps (when format.words = true) */
|
|
52
|
+
words?: Record<string, unknown>[];
|
|
53
|
+
}
|
|
54
|
+
type TranscribeStatus = "completed" | "processing" | "failed" | "requires_asr_confirmation";
|
|
55
|
+
interface TranscribeResponse {
|
|
56
|
+
request_id: string;
|
|
57
|
+
status: TranscribeStatus;
|
|
58
|
+
data?: {
|
|
59
|
+
video_id: string;
|
|
60
|
+
transcript: TranscriptPayload;
|
|
61
|
+
video_title?: string | null;
|
|
62
|
+
};
|
|
63
|
+
error?: string;
|
|
64
|
+
job_id?: string | null;
|
|
65
|
+
estimated_credits?: number;
|
|
66
|
+
duration_minutes?: number | null;
|
|
67
|
+
suggestion?: string;
|
|
68
|
+
credits_used?: number;
|
|
69
|
+
}
|
|
70
|
+
type BatchStatus = "completed" | "partial" | "processing";
|
|
71
|
+
interface BatchResponse {
|
|
72
|
+
batch_id: string;
|
|
73
|
+
status: BatchStatus;
|
|
74
|
+
results: TranscribeResponse[];
|
|
75
|
+
summary?: {
|
|
76
|
+
total: number;
|
|
77
|
+
succeeded: number;
|
|
78
|
+
failed: number;
|
|
79
|
+
processing: number;
|
|
80
|
+
};
|
|
81
|
+
credits_used?: number;
|
|
82
|
+
}
|
|
83
|
+
interface ErrorResponse {
|
|
84
|
+
error: string;
|
|
85
|
+
message: string;
|
|
86
|
+
details?: Record<string, unknown>;
|
|
87
|
+
}
|
|
88
|
+
interface YouTubeTranscriptOptions {
|
|
89
|
+
/** Your API key from https://youtubetranscript.dev/dashboard */
|
|
90
|
+
apiKey: string;
|
|
91
|
+
/** Base URL override (default: https://youtubetranscript.dev/api/v2) */
|
|
92
|
+
baseUrl?: string;
|
|
93
|
+
/** Request timeout in ms (default: 30000) */
|
|
94
|
+
timeout?: number;
|
|
95
|
+
}
|
|
96
|
+
|
|
97
|
+
declare class YouTubeTranscript {
|
|
98
|
+
private readonly apiKey;
|
|
99
|
+
private readonly baseUrl;
|
|
100
|
+
private readonly timeout;
|
|
101
|
+
/**
|
|
102
|
+
* Create a new YouTubeTranscript client.
|
|
103
|
+
*
|
|
104
|
+
* ```ts
|
|
105
|
+
* const yt = new YouTubeTranscript({ apiKey: "your_api_key" });
|
|
106
|
+
* ```
|
|
107
|
+
*
|
|
108
|
+
* Get your API key at https://youtubetranscript.dev
|
|
109
|
+
*/
|
|
110
|
+
constructor(options: YouTubeTranscriptOptions);
|
|
111
|
+
/**
|
|
112
|
+
* Transcribe a single YouTube video.
|
|
113
|
+
*
|
|
114
|
+
* ```ts
|
|
115
|
+
* const result = await yt.transcribe({ video: "dQw4w9WgXcQ" });
|
|
116
|
+
* console.log(result.data?.transcript.text);
|
|
117
|
+
* ```
|
|
118
|
+
*/
|
|
119
|
+
transcribe(request: TranscribeRequest): Promise<TranscribeResponse>;
|
|
120
|
+
/**
|
|
121
|
+
* Shorthand: transcribe a video by URL or ID with minimal config.
|
|
122
|
+
*
|
|
123
|
+
* ```ts
|
|
124
|
+
* const result = await yt.getTranscript("dQw4w9WgXcQ");
|
|
125
|
+
* ```
|
|
126
|
+
*/
|
|
127
|
+
getTranscript(video: string, language?: string): Promise<TranscribeResponse>;
|
|
128
|
+
/**
|
|
129
|
+
* Transcribe multiple videos in a single request (up to 100).
|
|
130
|
+
*
|
|
131
|
+
* ```ts
|
|
132
|
+
* const result = await yt.batch({
|
|
133
|
+
* video_ids: ["dQw4w9WgXcQ", "jNQXAC9IVRw"],
|
|
134
|
+
* });
|
|
135
|
+
* ```
|
|
136
|
+
*/
|
|
137
|
+
batch(request: BatchRequest): Promise<BatchResponse>;
|
|
138
|
+
/**
|
|
139
|
+
* Poll an async transcription job (ASR).
|
|
140
|
+
*
|
|
141
|
+
* ```ts
|
|
142
|
+
* const status = await yt.getJob("job_abc123");
|
|
143
|
+
* ```
|
|
144
|
+
*/
|
|
145
|
+
getJob(jobId: string, options?: {
|
|
146
|
+
include_segments?: boolean;
|
|
147
|
+
include_paragraphs?: boolean;
|
|
148
|
+
include_words?: boolean;
|
|
149
|
+
}): Promise<TranscribeResponse>;
|
|
150
|
+
/**
|
|
151
|
+
* Poll a batch job.
|
|
152
|
+
*
|
|
153
|
+
* ```ts
|
|
154
|
+
* const status = await yt.getBatch("batch_abc123");
|
|
155
|
+
* ```
|
|
156
|
+
*/
|
|
157
|
+
getBatch(batchId: string): Promise<BatchResponse>;
|
|
158
|
+
/**
|
|
159
|
+
* Poll a job until it completes or fails.
|
|
160
|
+
*
|
|
161
|
+
* ```ts
|
|
162
|
+
* const result = await yt.waitForJob("job_abc123", {
|
|
163
|
+
* interval: 5000,
|
|
164
|
+
* maxAttempts: 60,
|
|
165
|
+
* });
|
|
166
|
+
* ```
|
|
167
|
+
*/
|
|
168
|
+
waitForJob(jobId: string, options?: {
|
|
169
|
+
interval?: number;
|
|
170
|
+
maxAttempts?: number;
|
|
171
|
+
}): Promise<TranscribeResponse>;
|
|
172
|
+
/**
|
|
173
|
+
* Poll a batch until it completes.
|
|
174
|
+
*/
|
|
175
|
+
waitForBatch(batchId: string, options?: {
|
|
176
|
+
interval?: number;
|
|
177
|
+
maxAttempts?: number;
|
|
178
|
+
}): Promise<BatchResponse>;
|
|
179
|
+
private post;
|
|
180
|
+
private get;
|
|
181
|
+
private request;
|
|
182
|
+
private createError;
|
|
183
|
+
}
|
|
184
|
+
|
|
185
|
+
declare class YouTubeTranscriptError extends Error {
|
|
186
|
+
readonly status: number;
|
|
187
|
+
readonly errorCode: string;
|
|
188
|
+
readonly details?: Record<string, unknown>;
|
|
189
|
+
constructor(status: number, body: ErrorResponse);
|
|
190
|
+
}
|
|
191
|
+
declare class InvalidRequestError extends YouTubeTranscriptError {
|
|
192
|
+
constructor(body: ErrorResponse);
|
|
193
|
+
}
|
|
194
|
+
declare class AuthenticationError extends YouTubeTranscriptError {
|
|
195
|
+
constructor(body: ErrorResponse);
|
|
196
|
+
}
|
|
197
|
+
declare class InsufficientCreditsError extends YouTubeTranscriptError {
|
|
198
|
+
constructor(body: ErrorResponse);
|
|
199
|
+
}
|
|
200
|
+
declare class NoCaptionsError extends YouTubeTranscriptError {
|
|
201
|
+
constructor(body: ErrorResponse);
|
|
202
|
+
}
|
|
203
|
+
declare class RateLimitError extends YouTubeTranscriptError {
|
|
204
|
+
readonly retryAfter?: number;
|
|
205
|
+
constructor(body: ErrorResponse, retryAfter?: number);
|
|
206
|
+
}
|
|
207
|
+
|
|
208
|
+
export { AuthenticationError, type BatchRequest, type BatchResponse, type BatchStatus, type ErrorResponse, type FormatOptions, InsufficientCreditsError, InvalidRequestError, NoCaptionsError, RateLimitError, type Source, type TranscribeRequest, type TranscribeResponse, type TranscribeStatus, type TranscriptPayload, YouTubeTranscript, YouTubeTranscriptError, type YouTubeTranscriptOptions };
|
package/dist/index.d.ts
ADDED
|
@@ -0,0 +1,208 @@
|
|
|
1
|
+
/** Control which extra fields are included in the response. */
|
|
2
|
+
interface FormatOptions {
|
|
3
|
+
/** Include timestamped segments */
|
|
4
|
+
timestamp?: boolean;
|
|
5
|
+
/** Include paragraph groupings */
|
|
6
|
+
paragraphs?: boolean;
|
|
7
|
+
/** Include word-level timestamps */
|
|
8
|
+
words?: boolean;
|
|
9
|
+
}
|
|
10
|
+
/** Caption source preference. */
|
|
11
|
+
type Source = "auto" | "manual" | "asr";
|
|
12
|
+
interface TranscribeRequest {
|
|
13
|
+
/** YouTube URL or 11-character video ID */
|
|
14
|
+
video: string;
|
|
15
|
+
/** ISO 639-1 language code (e.g. "es", "fr"). Omit for best available. */
|
|
16
|
+
language?: string;
|
|
17
|
+
/** Caption source: "auto" (default), "manual", or "asr" */
|
|
18
|
+
source?: Source;
|
|
19
|
+
/** Explicitly confirm ASR usage when captions are unavailable */
|
|
20
|
+
allow_asr?: boolean;
|
|
21
|
+
/** Control extra output fields */
|
|
22
|
+
format?: FormatOptions;
|
|
23
|
+
/** URL to receive async results. Required for source="asr". */
|
|
24
|
+
webhook_url?: string;
|
|
25
|
+
}
|
|
26
|
+
interface BatchRequest {
|
|
27
|
+
/** Array of YouTube URLs or 11-character video IDs (max 100) */
|
|
28
|
+
video_ids: string[];
|
|
29
|
+
/** ISO 639-1 language code */
|
|
30
|
+
language?: string;
|
|
31
|
+
/** Caption source preference */
|
|
32
|
+
source?: Source;
|
|
33
|
+
/** Explicitly confirm ASR usage */
|
|
34
|
+
allow_asr?: boolean;
|
|
35
|
+
/** Control extra output fields */
|
|
36
|
+
format?: FormatOptions;
|
|
37
|
+
/** URL to receive async results */
|
|
38
|
+
webhook_url?: string;
|
|
39
|
+
}
|
|
40
|
+
interface TranscriptPayload {
|
|
41
|
+
/** Full transcript text */
|
|
42
|
+
text: string;
|
|
43
|
+
/** Detected / returned language */
|
|
44
|
+
language: string;
|
|
45
|
+
/** Source of the transcript: manual captions, auto, or asr */
|
|
46
|
+
source: string;
|
|
47
|
+
/** Timestamped segments (when format.timestamp = true) */
|
|
48
|
+
segments?: Record<string, unknown>[];
|
|
49
|
+
/** Paragraph groupings (when format.paragraphs = true) */
|
|
50
|
+
paragraphs?: Record<string, unknown>[];
|
|
51
|
+
/** Word-level timestamps (when format.words = true) */
|
|
52
|
+
words?: Record<string, unknown>[];
|
|
53
|
+
}
|
|
54
|
+
type TranscribeStatus = "completed" | "processing" | "failed" | "requires_asr_confirmation";
|
|
55
|
+
interface TranscribeResponse {
|
|
56
|
+
request_id: string;
|
|
57
|
+
status: TranscribeStatus;
|
|
58
|
+
data?: {
|
|
59
|
+
video_id: string;
|
|
60
|
+
transcript: TranscriptPayload;
|
|
61
|
+
video_title?: string | null;
|
|
62
|
+
};
|
|
63
|
+
error?: string;
|
|
64
|
+
job_id?: string | null;
|
|
65
|
+
estimated_credits?: number;
|
|
66
|
+
duration_minutes?: number | null;
|
|
67
|
+
suggestion?: string;
|
|
68
|
+
credits_used?: number;
|
|
69
|
+
}
|
|
70
|
+
type BatchStatus = "completed" | "partial" | "processing";
|
|
71
|
+
interface BatchResponse {
|
|
72
|
+
batch_id: string;
|
|
73
|
+
status: BatchStatus;
|
|
74
|
+
results: TranscribeResponse[];
|
|
75
|
+
summary?: {
|
|
76
|
+
total: number;
|
|
77
|
+
succeeded: number;
|
|
78
|
+
failed: number;
|
|
79
|
+
processing: number;
|
|
80
|
+
};
|
|
81
|
+
credits_used?: number;
|
|
82
|
+
}
|
|
83
|
+
interface ErrorResponse {
|
|
84
|
+
error: string;
|
|
85
|
+
message: string;
|
|
86
|
+
details?: Record<string, unknown>;
|
|
87
|
+
}
|
|
88
|
+
interface YouTubeTranscriptOptions {
|
|
89
|
+
/** Your API key from https://youtubetranscript.dev/dashboard */
|
|
90
|
+
apiKey: string;
|
|
91
|
+
/** Base URL override (default: https://youtubetranscript.dev/api/v2) */
|
|
92
|
+
baseUrl?: string;
|
|
93
|
+
/** Request timeout in ms (default: 30000) */
|
|
94
|
+
timeout?: number;
|
|
95
|
+
}
|
|
96
|
+
|
|
97
|
+
declare class YouTubeTranscript {
|
|
98
|
+
private readonly apiKey;
|
|
99
|
+
private readonly baseUrl;
|
|
100
|
+
private readonly timeout;
|
|
101
|
+
/**
|
|
102
|
+
* Create a new YouTubeTranscript client.
|
|
103
|
+
*
|
|
104
|
+
* ```ts
|
|
105
|
+
* const yt = new YouTubeTranscript({ apiKey: "your_api_key" });
|
|
106
|
+
* ```
|
|
107
|
+
*
|
|
108
|
+
* Get your API key at https://youtubetranscript.dev
|
|
109
|
+
*/
|
|
110
|
+
constructor(options: YouTubeTranscriptOptions);
|
|
111
|
+
/**
|
|
112
|
+
* Transcribe a single YouTube video.
|
|
113
|
+
*
|
|
114
|
+
* ```ts
|
|
115
|
+
* const result = await yt.transcribe({ video: "dQw4w9WgXcQ" });
|
|
116
|
+
* console.log(result.data?.transcript.text);
|
|
117
|
+
* ```
|
|
118
|
+
*/
|
|
119
|
+
transcribe(request: TranscribeRequest): Promise<TranscribeResponse>;
|
|
120
|
+
/**
|
|
121
|
+
* Shorthand: transcribe a video by URL or ID with minimal config.
|
|
122
|
+
*
|
|
123
|
+
* ```ts
|
|
124
|
+
* const result = await yt.getTranscript("dQw4w9WgXcQ");
|
|
125
|
+
* ```
|
|
126
|
+
*/
|
|
127
|
+
getTranscript(video: string, language?: string): Promise<TranscribeResponse>;
|
|
128
|
+
/**
|
|
129
|
+
* Transcribe multiple videos in a single request (up to 100).
|
|
130
|
+
*
|
|
131
|
+
* ```ts
|
|
132
|
+
* const result = await yt.batch({
|
|
133
|
+
* video_ids: ["dQw4w9WgXcQ", "jNQXAC9IVRw"],
|
|
134
|
+
* });
|
|
135
|
+
* ```
|
|
136
|
+
*/
|
|
137
|
+
batch(request: BatchRequest): Promise<BatchResponse>;
|
|
138
|
+
/**
|
|
139
|
+
* Poll an async transcription job (ASR).
|
|
140
|
+
*
|
|
141
|
+
* ```ts
|
|
142
|
+
* const status = await yt.getJob("job_abc123");
|
|
143
|
+
* ```
|
|
144
|
+
*/
|
|
145
|
+
getJob(jobId: string, options?: {
|
|
146
|
+
include_segments?: boolean;
|
|
147
|
+
include_paragraphs?: boolean;
|
|
148
|
+
include_words?: boolean;
|
|
149
|
+
}): Promise<TranscribeResponse>;
|
|
150
|
+
/**
|
|
151
|
+
* Poll a batch job.
|
|
152
|
+
*
|
|
153
|
+
* ```ts
|
|
154
|
+
* const status = await yt.getBatch("batch_abc123");
|
|
155
|
+
* ```
|
|
156
|
+
*/
|
|
157
|
+
getBatch(batchId: string): Promise<BatchResponse>;
|
|
158
|
+
/**
|
|
159
|
+
* Poll a job until it completes or fails.
|
|
160
|
+
*
|
|
161
|
+
* ```ts
|
|
162
|
+
* const result = await yt.waitForJob("job_abc123", {
|
|
163
|
+
* interval: 5000,
|
|
164
|
+
* maxAttempts: 60,
|
|
165
|
+
* });
|
|
166
|
+
* ```
|
|
167
|
+
*/
|
|
168
|
+
waitForJob(jobId: string, options?: {
|
|
169
|
+
interval?: number;
|
|
170
|
+
maxAttempts?: number;
|
|
171
|
+
}): Promise<TranscribeResponse>;
|
|
172
|
+
/**
|
|
173
|
+
* Poll a batch until it completes.
|
|
174
|
+
*/
|
|
175
|
+
waitForBatch(batchId: string, options?: {
|
|
176
|
+
interval?: number;
|
|
177
|
+
maxAttempts?: number;
|
|
178
|
+
}): Promise<BatchResponse>;
|
|
179
|
+
private post;
|
|
180
|
+
private get;
|
|
181
|
+
private request;
|
|
182
|
+
private createError;
|
|
183
|
+
}
|
|
184
|
+
|
|
185
|
+
declare class YouTubeTranscriptError extends Error {
|
|
186
|
+
readonly status: number;
|
|
187
|
+
readonly errorCode: string;
|
|
188
|
+
readonly details?: Record<string, unknown>;
|
|
189
|
+
constructor(status: number, body: ErrorResponse);
|
|
190
|
+
}
|
|
191
|
+
declare class InvalidRequestError extends YouTubeTranscriptError {
|
|
192
|
+
constructor(body: ErrorResponse);
|
|
193
|
+
}
|
|
194
|
+
declare class AuthenticationError extends YouTubeTranscriptError {
|
|
195
|
+
constructor(body: ErrorResponse);
|
|
196
|
+
}
|
|
197
|
+
declare class InsufficientCreditsError extends YouTubeTranscriptError {
|
|
198
|
+
constructor(body: ErrorResponse);
|
|
199
|
+
}
|
|
200
|
+
declare class NoCaptionsError extends YouTubeTranscriptError {
|
|
201
|
+
constructor(body: ErrorResponse);
|
|
202
|
+
}
|
|
203
|
+
declare class RateLimitError extends YouTubeTranscriptError {
|
|
204
|
+
readonly retryAfter?: number;
|
|
205
|
+
constructor(body: ErrorResponse, retryAfter?: number);
|
|
206
|
+
}
|
|
207
|
+
|
|
208
|
+
export { AuthenticationError, type BatchRequest, type BatchResponse, type BatchStatus, type ErrorResponse, type FormatOptions, InsufficientCreditsError, InvalidRequestError, NoCaptionsError, RateLimitError, type Source, type TranscribeRequest, type TranscribeResponse, type TranscribeStatus, type TranscriptPayload, YouTubeTranscript, YouTubeTranscriptError, type YouTubeTranscriptOptions };
|
package/dist/index.js
ADDED
|
@@ -0,0 +1,271 @@
|
|
|
1
|
+
"use strict";
|
|
2
|
+
var __defProp = Object.defineProperty;
|
|
3
|
+
var __getOwnPropDesc = Object.getOwnPropertyDescriptor;
|
|
4
|
+
var __getOwnPropNames = Object.getOwnPropertyNames;
|
|
5
|
+
var __hasOwnProp = Object.prototype.hasOwnProperty;
|
|
6
|
+
var __export = (target, all) => {
|
|
7
|
+
for (var name in all)
|
|
8
|
+
__defProp(target, name, { get: all[name], enumerable: true });
|
|
9
|
+
};
|
|
10
|
+
var __copyProps = (to, from, except, desc) => {
|
|
11
|
+
if (from && typeof from === "object" || typeof from === "function") {
|
|
12
|
+
for (let key of __getOwnPropNames(from))
|
|
13
|
+
if (!__hasOwnProp.call(to, key) && key !== except)
|
|
14
|
+
__defProp(to, key, { get: () => from[key], enumerable: !(desc = __getOwnPropDesc(from, key)) || desc.enumerable });
|
|
15
|
+
}
|
|
16
|
+
return to;
|
|
17
|
+
};
|
|
18
|
+
var __toCommonJS = (mod) => __copyProps(__defProp({}, "__esModule", { value: true }), mod);
|
|
19
|
+
|
|
20
|
+
// src/index.ts
|
|
21
|
+
var index_exports = {};
|
|
22
|
+
__export(index_exports, {
|
|
23
|
+
AuthenticationError: () => AuthenticationError,
|
|
24
|
+
InsufficientCreditsError: () => InsufficientCreditsError,
|
|
25
|
+
InvalidRequestError: () => InvalidRequestError,
|
|
26
|
+
NoCaptionsError: () => NoCaptionsError,
|
|
27
|
+
RateLimitError: () => RateLimitError,
|
|
28
|
+
YouTubeTranscript: () => YouTubeTranscript,
|
|
29
|
+
YouTubeTranscriptError: () => YouTubeTranscriptError
|
|
30
|
+
});
|
|
31
|
+
module.exports = __toCommonJS(index_exports);
|
|
32
|
+
|
|
33
|
+
// src/errors.ts
|
|
34
|
+
var YouTubeTranscriptError = class extends Error {
|
|
35
|
+
constructor(status, body) {
|
|
36
|
+
super(body.message || body.error);
|
|
37
|
+
this.name = "YouTubeTranscriptError";
|
|
38
|
+
this.status = status;
|
|
39
|
+
this.errorCode = body.error;
|
|
40
|
+
this.details = body.details;
|
|
41
|
+
}
|
|
42
|
+
};
|
|
43
|
+
var InvalidRequestError = class extends YouTubeTranscriptError {
|
|
44
|
+
constructor(body) {
|
|
45
|
+
super(400, body);
|
|
46
|
+
this.name = "InvalidRequestError";
|
|
47
|
+
}
|
|
48
|
+
};
|
|
49
|
+
var AuthenticationError = class extends YouTubeTranscriptError {
|
|
50
|
+
constructor(body) {
|
|
51
|
+
super(401, body);
|
|
52
|
+
this.name = "AuthenticationError";
|
|
53
|
+
}
|
|
54
|
+
};
|
|
55
|
+
var InsufficientCreditsError = class extends YouTubeTranscriptError {
|
|
56
|
+
constructor(body) {
|
|
57
|
+
super(402, body);
|
|
58
|
+
this.name = "InsufficientCreditsError";
|
|
59
|
+
}
|
|
60
|
+
};
|
|
61
|
+
var NoCaptionsError = class extends YouTubeTranscriptError {
|
|
62
|
+
constructor(body) {
|
|
63
|
+
super(404, body);
|
|
64
|
+
this.name = "NoCaptionsError";
|
|
65
|
+
}
|
|
66
|
+
};
|
|
67
|
+
var RateLimitError = class extends YouTubeTranscriptError {
|
|
68
|
+
constructor(body, retryAfter) {
|
|
69
|
+
super(429, body);
|
|
70
|
+
this.name = "RateLimitError";
|
|
71
|
+
this.retryAfter = retryAfter;
|
|
72
|
+
}
|
|
73
|
+
};
|
|
74
|
+
|
|
75
|
+
// src/client.ts
|
|
76
|
+
var DEFAULT_BASE_URL = "https://youtubetranscript.dev/api/v2";
|
|
77
|
+
var DEFAULT_TIMEOUT = 3e4;
|
|
78
|
+
var YouTubeTranscript = class {
|
|
79
|
+
/**
|
|
80
|
+
* Create a new YouTubeTranscript client.
|
|
81
|
+
*
|
|
82
|
+
* ```ts
|
|
83
|
+
* const yt = new YouTubeTranscript({ apiKey: "your_api_key" });
|
|
84
|
+
* ```
|
|
85
|
+
*
|
|
86
|
+
* Get your API key at https://youtubetranscript.dev
|
|
87
|
+
*/
|
|
88
|
+
constructor(options) {
|
|
89
|
+
if (!options.apiKey) {
|
|
90
|
+
throw new Error(
|
|
91
|
+
"API key is required. Get one at https://youtubetranscript.dev"
|
|
92
|
+
);
|
|
93
|
+
}
|
|
94
|
+
this.apiKey = options.apiKey;
|
|
95
|
+
this.baseUrl = (options.baseUrl || DEFAULT_BASE_URL).replace(/\/+$/, "");
|
|
96
|
+
this.timeout = options.timeout || DEFAULT_TIMEOUT;
|
|
97
|
+
}
|
|
98
|
+
// ---------------------------------------------------
|
|
99
|
+
// Public methods
|
|
100
|
+
// ---------------------------------------------------
|
|
101
|
+
/**
|
|
102
|
+
* Transcribe a single YouTube video.
|
|
103
|
+
*
|
|
104
|
+
* ```ts
|
|
105
|
+
* const result = await yt.transcribe({ video: "dQw4w9WgXcQ" });
|
|
106
|
+
* console.log(result.data?.transcript.text);
|
|
107
|
+
* ```
|
|
108
|
+
*/
|
|
109
|
+
async transcribe(request) {
|
|
110
|
+
return this.post("/transcribe", request);
|
|
111
|
+
}
|
|
112
|
+
/**
|
|
113
|
+
* Shorthand: transcribe a video by URL or ID with minimal config.
|
|
114
|
+
*
|
|
115
|
+
* ```ts
|
|
116
|
+
* const result = await yt.getTranscript("dQw4w9WgXcQ");
|
|
117
|
+
* ```
|
|
118
|
+
*/
|
|
119
|
+
async getTranscript(video, language) {
|
|
120
|
+
return this.transcribe({ video, language });
|
|
121
|
+
}
|
|
122
|
+
/**
|
|
123
|
+
* Transcribe multiple videos in a single request (up to 100).
|
|
124
|
+
*
|
|
125
|
+
* ```ts
|
|
126
|
+
* const result = await yt.batch({
|
|
127
|
+
* video_ids: ["dQw4w9WgXcQ", "jNQXAC9IVRw"],
|
|
128
|
+
* });
|
|
129
|
+
* ```
|
|
130
|
+
*/
|
|
131
|
+
async batch(request) {
|
|
132
|
+
return this.post("/batch", request);
|
|
133
|
+
}
|
|
134
|
+
/**
|
|
135
|
+
* Poll an async transcription job (ASR).
|
|
136
|
+
*
|
|
137
|
+
* ```ts
|
|
138
|
+
* const status = await yt.getJob("job_abc123");
|
|
139
|
+
* ```
|
|
140
|
+
*/
|
|
141
|
+
async getJob(jobId, options) {
|
|
142
|
+
const params = new URLSearchParams();
|
|
143
|
+
if (options?.include_segments) params.set("include_segments", "true");
|
|
144
|
+
if (options?.include_paragraphs) params.set("include_paragraphs", "true");
|
|
145
|
+
if (options?.include_words) params.set("include_words", "true");
|
|
146
|
+
const query = params.toString();
|
|
147
|
+
const path = `/jobs/${jobId}${query ? `?${query}` : ""}`;
|
|
148
|
+
return this.get(path);
|
|
149
|
+
}
|
|
150
|
+
/**
|
|
151
|
+
* Poll a batch job.
|
|
152
|
+
*
|
|
153
|
+
* ```ts
|
|
154
|
+
* const status = await yt.getBatch("batch_abc123");
|
|
155
|
+
* ```
|
|
156
|
+
*/
|
|
157
|
+
async getBatch(batchId) {
|
|
158
|
+
return this.get(`/batch/${batchId}`);
|
|
159
|
+
}
|
|
160
|
+
/**
|
|
161
|
+
* Poll a job until it completes or fails.
|
|
162
|
+
*
|
|
163
|
+
* ```ts
|
|
164
|
+
* const result = await yt.waitForJob("job_abc123", {
|
|
165
|
+
* interval: 5000,
|
|
166
|
+
* maxAttempts: 60,
|
|
167
|
+
* });
|
|
168
|
+
* ```
|
|
169
|
+
*/
|
|
170
|
+
async waitForJob(jobId, options) {
|
|
171
|
+
const interval = options?.interval || 5e3;
|
|
172
|
+
const maxAttempts = options?.maxAttempts || 60;
|
|
173
|
+
for (let i = 0; i < maxAttempts; i++) {
|
|
174
|
+
const result = await this.getJob(jobId);
|
|
175
|
+
if (result.status === "completed" || result.status === "failed") {
|
|
176
|
+
return result;
|
|
177
|
+
}
|
|
178
|
+
await new Promise((resolve) => setTimeout(resolve, interval));
|
|
179
|
+
}
|
|
180
|
+
throw new Error(
|
|
181
|
+
`Job ${jobId} did not complete after ${maxAttempts} attempts`
|
|
182
|
+
);
|
|
183
|
+
}
|
|
184
|
+
/**
|
|
185
|
+
* Poll a batch until it completes.
|
|
186
|
+
*/
|
|
187
|
+
async waitForBatch(batchId, options) {
|
|
188
|
+
const interval = options?.interval || 5e3;
|
|
189
|
+
const maxAttempts = options?.maxAttempts || 60;
|
|
190
|
+
for (let i = 0; i < maxAttempts; i++) {
|
|
191
|
+
const result = await this.getBatch(batchId);
|
|
192
|
+
if (result.status === "completed") {
|
|
193
|
+
return result;
|
|
194
|
+
}
|
|
195
|
+
await new Promise((resolve) => setTimeout(resolve, interval));
|
|
196
|
+
}
|
|
197
|
+
throw new Error(
|
|
198
|
+
`Batch ${batchId} did not complete after ${maxAttempts} attempts`
|
|
199
|
+
);
|
|
200
|
+
}
|
|
201
|
+
// ---------------------------------------------------
|
|
202
|
+
// HTTP helpers
|
|
203
|
+
// ---------------------------------------------------
|
|
204
|
+
async post(path, body) {
|
|
205
|
+
return this.request("POST", path, body);
|
|
206
|
+
}
|
|
207
|
+
async get(path) {
|
|
208
|
+
return this.request("GET", path);
|
|
209
|
+
}
|
|
210
|
+
async request(method, path, body) {
|
|
211
|
+
const url = `${this.baseUrl}${path}`;
|
|
212
|
+
const controller = new AbortController();
|
|
213
|
+
const timeoutId = setTimeout(() => controller.abort(), this.timeout);
|
|
214
|
+
try {
|
|
215
|
+
const response = await fetch(url, {
|
|
216
|
+
method,
|
|
217
|
+
headers: {
|
|
218
|
+
Authorization: `Bearer ${this.apiKey}`,
|
|
219
|
+
"Content-Type": "application/json",
|
|
220
|
+
"User-Agent": "youtube-transcript-sdk/1.0.0"
|
|
221
|
+
},
|
|
222
|
+
body: body ? JSON.stringify(body) : void 0,
|
|
223
|
+
signal: controller.signal
|
|
224
|
+
});
|
|
225
|
+
const data = await response.json();
|
|
226
|
+
if (!response.ok) {
|
|
227
|
+
throw this.createError(response.status, data, response.headers);
|
|
228
|
+
}
|
|
229
|
+
return data;
|
|
230
|
+
} catch (error) {
|
|
231
|
+
if (error instanceof YouTubeTranscriptError) throw error;
|
|
232
|
+
if (error.name === "AbortError") {
|
|
233
|
+
throw new Error(`Request to ${path} timed out after ${this.timeout}ms`);
|
|
234
|
+
}
|
|
235
|
+
throw error;
|
|
236
|
+
} finally {
|
|
237
|
+
clearTimeout(timeoutId);
|
|
238
|
+
}
|
|
239
|
+
}
|
|
240
|
+
createError(status, body, headers) {
|
|
241
|
+
switch (status) {
|
|
242
|
+
case 400:
|
|
243
|
+
return new InvalidRequestError(body);
|
|
244
|
+
case 401:
|
|
245
|
+
return new AuthenticationError(body);
|
|
246
|
+
case 402:
|
|
247
|
+
return new InsufficientCreditsError(body);
|
|
248
|
+
case 404:
|
|
249
|
+
return new NoCaptionsError(body);
|
|
250
|
+
case 429: {
|
|
251
|
+
const retryAfter = headers.get("Retry-After");
|
|
252
|
+
return new RateLimitError(
|
|
253
|
+
body,
|
|
254
|
+
retryAfter ? parseInt(retryAfter, 10) : void 0
|
|
255
|
+
);
|
|
256
|
+
}
|
|
257
|
+
default:
|
|
258
|
+
return new YouTubeTranscriptError(status, body);
|
|
259
|
+
}
|
|
260
|
+
}
|
|
261
|
+
};
|
|
262
|
+
// Annotate the CommonJS export names for ESM import in node:
|
|
263
|
+
0 && (module.exports = {
|
|
264
|
+
AuthenticationError,
|
|
265
|
+
InsufficientCreditsError,
|
|
266
|
+
InvalidRequestError,
|
|
267
|
+
NoCaptionsError,
|
|
268
|
+
RateLimitError,
|
|
269
|
+
YouTubeTranscript,
|
|
270
|
+
YouTubeTranscriptError
|
|
271
|
+
});
|
package/dist/index.mjs
ADDED
|
@@ -0,0 +1,238 @@
|
|
|
1
|
+
// src/errors.ts
|
|
2
|
+
var YouTubeTranscriptError = class extends Error {
|
|
3
|
+
constructor(status, body) {
|
|
4
|
+
super(body.message || body.error);
|
|
5
|
+
this.name = "YouTubeTranscriptError";
|
|
6
|
+
this.status = status;
|
|
7
|
+
this.errorCode = body.error;
|
|
8
|
+
this.details = body.details;
|
|
9
|
+
}
|
|
10
|
+
};
|
|
11
|
+
var InvalidRequestError = class extends YouTubeTranscriptError {
|
|
12
|
+
constructor(body) {
|
|
13
|
+
super(400, body);
|
|
14
|
+
this.name = "InvalidRequestError";
|
|
15
|
+
}
|
|
16
|
+
};
|
|
17
|
+
var AuthenticationError = class extends YouTubeTranscriptError {
|
|
18
|
+
constructor(body) {
|
|
19
|
+
super(401, body);
|
|
20
|
+
this.name = "AuthenticationError";
|
|
21
|
+
}
|
|
22
|
+
};
|
|
23
|
+
var InsufficientCreditsError = class extends YouTubeTranscriptError {
|
|
24
|
+
constructor(body) {
|
|
25
|
+
super(402, body);
|
|
26
|
+
this.name = "InsufficientCreditsError";
|
|
27
|
+
}
|
|
28
|
+
};
|
|
29
|
+
var NoCaptionsError = class extends YouTubeTranscriptError {
|
|
30
|
+
constructor(body) {
|
|
31
|
+
super(404, body);
|
|
32
|
+
this.name = "NoCaptionsError";
|
|
33
|
+
}
|
|
34
|
+
};
|
|
35
|
+
var RateLimitError = class extends YouTubeTranscriptError {
|
|
36
|
+
constructor(body, retryAfter) {
|
|
37
|
+
super(429, body);
|
|
38
|
+
this.name = "RateLimitError";
|
|
39
|
+
this.retryAfter = retryAfter;
|
|
40
|
+
}
|
|
41
|
+
};
|
|
42
|
+
|
|
43
|
+
// src/client.ts
|
|
44
|
+
var DEFAULT_BASE_URL = "https://youtubetranscript.dev/api/v2";
|
|
45
|
+
var DEFAULT_TIMEOUT = 3e4;
|
|
46
|
+
var YouTubeTranscript = class {
|
|
47
|
+
/**
|
|
48
|
+
* Create a new YouTubeTranscript client.
|
|
49
|
+
*
|
|
50
|
+
* ```ts
|
|
51
|
+
* const yt = new YouTubeTranscript({ apiKey: "your_api_key" });
|
|
52
|
+
* ```
|
|
53
|
+
*
|
|
54
|
+
* Get your API key at https://youtubetranscript.dev
|
|
55
|
+
*/
|
|
56
|
+
constructor(options) {
|
|
57
|
+
if (!options.apiKey) {
|
|
58
|
+
throw new Error(
|
|
59
|
+
"API key is required. Get one at https://youtubetranscript.dev"
|
|
60
|
+
);
|
|
61
|
+
}
|
|
62
|
+
this.apiKey = options.apiKey;
|
|
63
|
+
this.baseUrl = (options.baseUrl || DEFAULT_BASE_URL).replace(/\/+$/, "");
|
|
64
|
+
this.timeout = options.timeout || DEFAULT_TIMEOUT;
|
|
65
|
+
}
|
|
66
|
+
// ---------------------------------------------------
|
|
67
|
+
// Public methods
|
|
68
|
+
// ---------------------------------------------------
|
|
69
|
+
/**
|
|
70
|
+
* Transcribe a single YouTube video.
|
|
71
|
+
*
|
|
72
|
+
* ```ts
|
|
73
|
+
* const result = await yt.transcribe({ video: "dQw4w9WgXcQ" });
|
|
74
|
+
* console.log(result.data?.transcript.text);
|
|
75
|
+
* ```
|
|
76
|
+
*/
|
|
77
|
+
async transcribe(request) {
|
|
78
|
+
return this.post("/transcribe", request);
|
|
79
|
+
}
|
|
80
|
+
/**
|
|
81
|
+
* Shorthand: transcribe a video by URL or ID with minimal config.
|
|
82
|
+
*
|
|
83
|
+
* ```ts
|
|
84
|
+
* const result = await yt.getTranscript("dQw4w9WgXcQ");
|
|
85
|
+
* ```
|
|
86
|
+
*/
|
|
87
|
+
async getTranscript(video, language) {
|
|
88
|
+
return this.transcribe({ video, language });
|
|
89
|
+
}
|
|
90
|
+
/**
|
|
91
|
+
* Transcribe multiple videos in a single request (up to 100).
|
|
92
|
+
*
|
|
93
|
+
* ```ts
|
|
94
|
+
* const result = await yt.batch({
|
|
95
|
+
* video_ids: ["dQw4w9WgXcQ", "jNQXAC9IVRw"],
|
|
96
|
+
* });
|
|
97
|
+
* ```
|
|
98
|
+
*/
|
|
99
|
+
async batch(request) {
|
|
100
|
+
return this.post("/batch", request);
|
|
101
|
+
}
|
|
102
|
+
/**
|
|
103
|
+
* Poll an async transcription job (ASR).
|
|
104
|
+
*
|
|
105
|
+
* ```ts
|
|
106
|
+
* const status = await yt.getJob("job_abc123");
|
|
107
|
+
* ```
|
|
108
|
+
*/
|
|
109
|
+
async getJob(jobId, options) {
|
|
110
|
+
const params = new URLSearchParams();
|
|
111
|
+
if (options?.include_segments) params.set("include_segments", "true");
|
|
112
|
+
if (options?.include_paragraphs) params.set("include_paragraphs", "true");
|
|
113
|
+
if (options?.include_words) params.set("include_words", "true");
|
|
114
|
+
const query = params.toString();
|
|
115
|
+
const path = `/jobs/${jobId}${query ? `?${query}` : ""}`;
|
|
116
|
+
return this.get(path);
|
|
117
|
+
}
|
|
118
|
+
/**
|
|
119
|
+
* Poll a batch job.
|
|
120
|
+
*
|
|
121
|
+
* ```ts
|
|
122
|
+
* const status = await yt.getBatch("batch_abc123");
|
|
123
|
+
* ```
|
|
124
|
+
*/
|
|
125
|
+
async getBatch(batchId) {
|
|
126
|
+
return this.get(`/batch/${batchId}`);
|
|
127
|
+
}
|
|
128
|
+
/**
|
|
129
|
+
* Poll a job until it completes or fails.
|
|
130
|
+
*
|
|
131
|
+
* ```ts
|
|
132
|
+
* const result = await yt.waitForJob("job_abc123", {
|
|
133
|
+
* interval: 5000,
|
|
134
|
+
* maxAttempts: 60,
|
|
135
|
+
* });
|
|
136
|
+
* ```
|
|
137
|
+
*/
|
|
138
|
+
async waitForJob(jobId, options) {
|
|
139
|
+
const interval = options?.interval || 5e3;
|
|
140
|
+
const maxAttempts = options?.maxAttempts || 60;
|
|
141
|
+
for (let i = 0; i < maxAttempts; i++) {
|
|
142
|
+
const result = await this.getJob(jobId);
|
|
143
|
+
if (result.status === "completed" || result.status === "failed") {
|
|
144
|
+
return result;
|
|
145
|
+
}
|
|
146
|
+
await new Promise((resolve) => setTimeout(resolve, interval));
|
|
147
|
+
}
|
|
148
|
+
throw new Error(
|
|
149
|
+
`Job ${jobId} did not complete after ${maxAttempts} attempts`
|
|
150
|
+
);
|
|
151
|
+
}
|
|
152
|
+
/**
|
|
153
|
+
* Poll a batch until it completes.
|
|
154
|
+
*/
|
|
155
|
+
async waitForBatch(batchId, options) {
|
|
156
|
+
const interval = options?.interval || 5e3;
|
|
157
|
+
const maxAttempts = options?.maxAttempts || 60;
|
|
158
|
+
for (let i = 0; i < maxAttempts; i++) {
|
|
159
|
+
const result = await this.getBatch(batchId);
|
|
160
|
+
if (result.status === "completed") {
|
|
161
|
+
return result;
|
|
162
|
+
}
|
|
163
|
+
await new Promise((resolve) => setTimeout(resolve, interval));
|
|
164
|
+
}
|
|
165
|
+
throw new Error(
|
|
166
|
+
`Batch ${batchId} did not complete after ${maxAttempts} attempts`
|
|
167
|
+
);
|
|
168
|
+
}
|
|
169
|
+
// ---------------------------------------------------
|
|
170
|
+
// HTTP helpers
|
|
171
|
+
// ---------------------------------------------------
|
|
172
|
+
async post(path, body) {
|
|
173
|
+
return this.request("POST", path, body);
|
|
174
|
+
}
|
|
175
|
+
async get(path) {
|
|
176
|
+
return this.request("GET", path);
|
|
177
|
+
}
|
|
178
|
+
async request(method, path, body) {
|
|
179
|
+
const url = `${this.baseUrl}${path}`;
|
|
180
|
+
const controller = new AbortController();
|
|
181
|
+
const timeoutId = setTimeout(() => controller.abort(), this.timeout);
|
|
182
|
+
try {
|
|
183
|
+
const response = await fetch(url, {
|
|
184
|
+
method,
|
|
185
|
+
headers: {
|
|
186
|
+
Authorization: `Bearer ${this.apiKey}`,
|
|
187
|
+
"Content-Type": "application/json",
|
|
188
|
+
"User-Agent": "youtube-transcript-sdk/1.0.0"
|
|
189
|
+
},
|
|
190
|
+
body: body ? JSON.stringify(body) : void 0,
|
|
191
|
+
signal: controller.signal
|
|
192
|
+
});
|
|
193
|
+
const data = await response.json();
|
|
194
|
+
if (!response.ok) {
|
|
195
|
+
throw this.createError(response.status, data, response.headers);
|
|
196
|
+
}
|
|
197
|
+
return data;
|
|
198
|
+
} catch (error) {
|
|
199
|
+
if (error instanceof YouTubeTranscriptError) throw error;
|
|
200
|
+
if (error.name === "AbortError") {
|
|
201
|
+
throw new Error(`Request to ${path} timed out after ${this.timeout}ms`);
|
|
202
|
+
}
|
|
203
|
+
throw error;
|
|
204
|
+
} finally {
|
|
205
|
+
clearTimeout(timeoutId);
|
|
206
|
+
}
|
|
207
|
+
}
|
|
208
|
+
createError(status, body, headers) {
|
|
209
|
+
switch (status) {
|
|
210
|
+
case 400:
|
|
211
|
+
return new InvalidRequestError(body);
|
|
212
|
+
case 401:
|
|
213
|
+
return new AuthenticationError(body);
|
|
214
|
+
case 402:
|
|
215
|
+
return new InsufficientCreditsError(body);
|
|
216
|
+
case 404:
|
|
217
|
+
return new NoCaptionsError(body);
|
|
218
|
+
case 429: {
|
|
219
|
+
const retryAfter = headers.get("Retry-After");
|
|
220
|
+
return new RateLimitError(
|
|
221
|
+
body,
|
|
222
|
+
retryAfter ? parseInt(retryAfter, 10) : void 0
|
|
223
|
+
);
|
|
224
|
+
}
|
|
225
|
+
default:
|
|
226
|
+
return new YouTubeTranscriptError(status, body);
|
|
227
|
+
}
|
|
228
|
+
}
|
|
229
|
+
};
|
|
230
|
+
export {
|
|
231
|
+
AuthenticationError,
|
|
232
|
+
InsufficientCreditsError,
|
|
233
|
+
InvalidRequestError,
|
|
234
|
+
NoCaptionsError,
|
|
235
|
+
RateLimitError,
|
|
236
|
+
YouTubeTranscript,
|
|
237
|
+
YouTubeTranscriptError
|
|
238
|
+
};
|
package/package.json
ADDED
|
@@ -0,0 +1,62 @@
|
|
|
1
|
+
{
|
|
2
|
+
"name": "youtube-audio-transcript-api",
|
|
3
|
+
"version": "1.0.0",
|
|
4
|
+
"description": "Official Node.js/TypeScript SDK for the YouTubeTranscript.dev API — extract, transcribe, and translate YouTube videos at scale.",
|
|
5
|
+
"main": "dist/index.js",
|
|
6
|
+
"module": "dist/index.mjs",
|
|
7
|
+
"types": "dist/index.d.ts",
|
|
8
|
+
"exports": {
|
|
9
|
+
".": {
|
|
10
|
+
"types": "./dist/index.d.ts",
|
|
11
|
+
"import": "./dist/index.mjs",
|
|
12
|
+
"require": "./dist/index.js"
|
|
13
|
+
}
|
|
14
|
+
},
|
|
15
|
+
"files": [
|
|
16
|
+
"dist",
|
|
17
|
+
"README.md",
|
|
18
|
+
"LICENSE"
|
|
19
|
+
],
|
|
20
|
+
"scripts": {
|
|
21
|
+
"build": "tsup src/index.ts --format cjs,esm --dts --clean",
|
|
22
|
+
"dev": "tsup src/index.ts --format cjs,esm --dts --watch",
|
|
23
|
+
"test": "vitest run",
|
|
24
|
+
"test:watch": "vitest",
|
|
25
|
+
"lint": "tsc --noEmit",
|
|
26
|
+
"prepublishOnly": "npm run build"
|
|
27
|
+
},
|
|
28
|
+
"keywords": [
|
|
29
|
+
"youtube",
|
|
30
|
+
"transcript",
|
|
31
|
+
"youtube-transcript",
|
|
32
|
+
"youtube-api",
|
|
33
|
+
"subtitles",
|
|
34
|
+
"captions",
|
|
35
|
+
"speech-to-text",
|
|
36
|
+
"asr",
|
|
37
|
+
"transcription",
|
|
38
|
+
"video-to-text",
|
|
39
|
+
"youtube-transcript-api",
|
|
40
|
+
"ai",
|
|
41
|
+
"batch",
|
|
42
|
+
"webhook"
|
|
43
|
+
],
|
|
44
|
+
"author": "YouTubeTranscript.dev",
|
|
45
|
+
"license": "MIT",
|
|
46
|
+
"homepage": "https://youtubetranscript.dev",
|
|
47
|
+
"repository": {
|
|
48
|
+
"type": "git",
|
|
49
|
+
"url": "https://github.com/youtubetranscript/youtube-transcript-api"
|
|
50
|
+
},
|
|
51
|
+
"bugs": {
|
|
52
|
+
"url": "https://github.com/youtubetranscript/youtube-transcript-api/issues"
|
|
53
|
+
},
|
|
54
|
+
"engines": {
|
|
55
|
+
"node": ">=18.0.0"
|
|
56
|
+
},
|
|
57
|
+
"devDependencies": {
|
|
58
|
+
"tsup": "^8.0.0",
|
|
59
|
+
"typescript": "^5.4.0",
|
|
60
|
+
"vitest": "^2.0.0"
|
|
61
|
+
}
|
|
62
|
+
}
|