youtube-captions-api 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/LICENSE +21 -0
- package/README.md +88 -0
- package/dist/index.d.ts +37 -0
- package/dist/index.d.ts.map +1 -0
- package/dist/index.js +156 -0
- package/dist/index.js.map +1 -0
- package/package.json +41 -0
package/LICENSE
ADDED
|
@@ -0,0 +1,21 @@
|
|
|
1
|
+
MIT License
|
|
2
|
+
|
|
3
|
+
Copyright (c) 2025 Andrew Njoo
|
|
4
|
+
|
|
5
|
+
Permission is hereby granted, free of charge, to any person obtaining a copy
|
|
6
|
+
of this software and associated documentation files (the "Software"), to deal
|
|
7
|
+
in the Software without restriction, including without limitation the rights
|
|
8
|
+
to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
|
|
9
|
+
copies of the Software, and to permit persons to whom the Software is
|
|
10
|
+
furnished to do so, subject to the following conditions:
|
|
11
|
+
|
|
12
|
+
The above copyright notice and this permission notice shall be included in all
|
|
13
|
+
copies or substantial portions of the Software.
|
|
14
|
+
|
|
15
|
+
THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
|
|
16
|
+
IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
|
|
17
|
+
FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
|
|
18
|
+
AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
|
|
19
|
+
LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
|
|
20
|
+
OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE
|
|
21
|
+
SOFTWARE.
|
package/README.md
ADDED
|
@@ -0,0 +1,88 @@
|
|
|
1
|
+
# youtube-captions-api
|
|
2
|
+
|
|
3
|
+
A reliable, self-contained Node.js/TypeScript port of the popular Python [youtube-transcript-api](https://github.com/jdepoix/youtube-transcript-api) (as of December 2025).
|
|
4
|
+
|
|
5
|
+
This is **mostly an AI-generated port** (created with assistance from Grok by xAI), faithfully replicating the Python library's core logic, error handling, consent handling, and reliability.
|
|
6
|
+
|
|
7
|
+
Fetches **timed transcripts** (manual or auto-generated) using YouTube's internal Innertube API — no official API key required.
|
|
8
|
+
|
|
9
|
+
> **Warning**: This uses undocumented endpoints. It may break if YouTube changes their internals.
|
|
10
|
+
|
|
11
|
+
## Features
|
|
12
|
+
|
|
13
|
+
- Instance-based API (`new YouTubeTranscriptApi()`)
|
|
14
|
+
- `fetch(videoIdOrUrl, { languages })` → timed segments with start/duration
|
|
15
|
+
- Language priority (prefers manual captions over auto-generated)
|
|
16
|
+
- Consent cookie handling
|
|
17
|
+
- Proxy support for production/cloud use
|
|
18
|
+
- Full concatenated text via `.getText()`
|
|
19
|
+
- Debug logging of transcript URL
|
|
20
|
+
- Minimal dependencies (only `undici`)
|
|
21
|
+
|
|
22
|
+
## Installation
|
|
23
|
+
|
|
24
|
+
```bash
|
|
25
|
+
npm install youtube-captions-api
|
|
26
|
+
```
|
|
27
|
+
|
|
28
|
+
## Usage
|
|
29
|
+
|
|
30
|
+
```ts
|
|
31
|
+
import YouTubeTranscriptApi from 'youtube-captions-api';
|
|
32
|
+
|
|
33
|
+
(async () => {
|
|
34
|
+
try {
|
|
35
|
+
// Optional: use a residential proxy for cloud deployments (highly recommended)
|
|
36
|
+
// const api = new YouTubeTranscriptApi('http://user:pass@residential-ip:port');
|
|
37
|
+
|
|
38
|
+
const api = new YouTubeTranscriptApi();
|
|
39
|
+
|
|
40
|
+
const transcript = await api.fetch('dQw4w9WgXcQ', {
|
|
41
|
+
languages: ['en'], // priority list – falls back automatically
|
|
42
|
+
});
|
|
43
|
+
|
|
44
|
+
// First timed segment
|
|
45
|
+
console.log(transcript.snippets[0]);
|
|
46
|
+
// → { text: "We're no strangers to love", start: 12.5, duration: 3.2 }
|
|
47
|
+
|
|
48
|
+
// Full concatenated text
|
|
49
|
+
console.log(transcript.getText());
|
|
50
|
+
|
|
51
|
+
// Metadata
|
|
52
|
+
console.log('Language:', transcript.language_code);
|
|
53
|
+
console.log('Auto-generated:', transcript.is_generated);
|
|
54
|
+
console.log('Segments count:', transcript.snippets.length);
|
|
55
|
+
} catch (err) {
|
|
56
|
+
console.error(err instanceof Error ? err.message : err);
|
|
57
|
+
}
|
|
58
|
+
})();
|
|
59
|
+
```
|
|
60
|
+
|
|
61
|
+
## Express Server Example
|
|
62
|
+
|
|
63
|
+
See `server.js` in the repo for a ready-to-run API endpoint:
|
|
64
|
+
|
|
65
|
+
```
|
|
66
|
+
GET /transcript?id=dQw4w9WgXcQ
|
|
67
|
+
→ { video_id, text, language, timed_segments: [{ timestamp, seconds, text }, ...] }
|
|
68
|
+
```
|
|
69
|
+
|
|
70
|
+
Run:
|
|
71
|
+
```bash
|
|
72
|
+
npm run build
|
|
73
|
+
node server.js
|
|
74
|
+
```
|
|
75
|
+
|
|
76
|
+
Test: `http://localhost:3000/transcript?id=dQw4w9WgXcQ`
|
|
77
|
+
|
|
78
|
+
## Production Notes
|
|
79
|
+
|
|
80
|
+
- Works reliably **locally** on residential IPs.
|
|
81
|
+
- In cloud/server environments, YouTube frequently blocks datacenter IPs → use **rotating residential proxies** (e.g., Webshare, Bright Data, Oxylabs).
|
|
82
|
+
- Simply pass the proxy URL to the constructor when deploying.
|
|
83
|
+
|
|
84
|
+
## License
|
|
85
|
+
|
|
86
|
+
MIT
|
|
87
|
+
|
|
88
|
+
Inspired by the excellent Python original — huge thanks to [jdepoix](https://github.com/jdepoix)!
|
package/dist/index.d.ts
ADDED
|
@@ -0,0 +1,37 @@
|
|
|
1
|
+
export interface TranscriptSnippet {
|
|
2
|
+
text: string;
|
|
3
|
+
start: number;
|
|
4
|
+
duration: number;
|
|
5
|
+
}
|
|
6
|
+
export type FetchedTranscript = {
|
|
7
|
+
snippets: TranscriptSnippet[];
|
|
8
|
+
video_id: string;
|
|
9
|
+
language: string;
|
|
10
|
+
language_code: string;
|
|
11
|
+
is_generated: boolean;
|
|
12
|
+
getText(): string;
|
|
13
|
+
toRawData(): Array<{
|
|
14
|
+
text: string;
|
|
15
|
+
start: number;
|
|
16
|
+
duration: number;
|
|
17
|
+
}>;
|
|
18
|
+
};
|
|
19
|
+
export interface TranscriptOptions {
|
|
20
|
+
languages?: string[];
|
|
21
|
+
proxy?: string;
|
|
22
|
+
}
|
|
23
|
+
export declare class NoTranscriptFound extends Error {
|
|
24
|
+
constructor(message?: string);
|
|
25
|
+
}
|
|
26
|
+
export declare class TranscriptsDisabled extends Error {
|
|
27
|
+
constructor();
|
|
28
|
+
}
|
|
29
|
+
export declare class YouTubeTranscriptApi {
|
|
30
|
+
private dispatcher?;
|
|
31
|
+
private headers;
|
|
32
|
+
constructor(proxy?: string);
|
|
33
|
+
private static extractVideoId;
|
|
34
|
+
fetch(videoIdOrUrl: string, options?: TranscriptOptions): Promise<FetchedTranscript>;
|
|
35
|
+
}
|
|
36
|
+
export default YouTubeTranscriptApi;
|
|
37
|
+
//# sourceMappingURL=index.d.ts.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"index.d.ts","sourceRoot":"","sources":["../src/index.ts"],"names":[],"mappings":"AAGA,MAAM,WAAW,iBAAiB;IAChC,IAAI,EAAE,MAAM,CAAC;IACb,KAAK,EAAE,MAAM,CAAC;IACd,QAAQ,EAAE,MAAM,CAAC;CAClB;AAED,MAAM,MAAM,iBAAiB,GAAG;IAC9B,QAAQ,EAAE,iBAAiB,EAAE,CAAC;IAC9B,QAAQ,EAAE,MAAM,CAAC;IACjB,QAAQ,EAAE,MAAM,CAAC;IACjB,aAAa,EAAE,MAAM,CAAC;IACtB,YAAY,EAAE,OAAO,CAAC;IAEtB,OAAO,IAAI,MAAM,CAAC;IAClB,SAAS,IAAI,KAAK,CAAC;QAAE,IAAI,EAAE,MAAM,CAAC;QAAC,KAAK,EAAE,MAAM,CAAC;QAAC,QAAQ,EAAE,MAAM,CAAA;KAAE,CAAC,CAAC;CACvE,CAAC;AAEF,MAAM,WAAW,iBAAiB;IAChC,SAAS,CAAC,EAAE,MAAM,EAAE,CAAC;IACrB,KAAK,CAAC,EAAE,MAAM,CAAC;CAChB;AAED,qBAAa,iBAAkB,SAAQ,KAAK;gBAC9B,OAAO,SAAwB;CAI5C;AAED,qBAAa,mBAAoB,SAAQ,KAAK;;CAK7C;AAED,qBAAa,oBAAoB;IAC/B,OAAO,CAAC,UAAU,CAAC,CAAa;IAChC,OAAO,CAAC,OAAO,CAIb;gBAEU,KAAK,CAAC,EAAE,MAAM;IAM1B,OAAO,CAAC,MAAM,CAAC,cAAc;IAcvB,KAAK,CAAC,YAAY,EAAE,MAAM,EAAE,OAAO,GAAE,iBAAsB,GAAG,OAAO,CAAC,iBAAiB,CAAC;CAgI/F;AAED,eAAe,oBAAoB,CAAC"}
|
package/dist/index.js
ADDED
|
@@ -0,0 +1,156 @@
|
|
|
1
|
+
// src/index.ts
|
|
2
|
+
import { fetch, ProxyAgent } from "undici";
|
|
3
|
+
export class NoTranscriptFound extends Error {
|
|
4
|
+
constructor(message = "No transcript found") {
|
|
5
|
+
super(message);
|
|
6
|
+
this.name = "NoTranscriptFound";
|
|
7
|
+
}
|
|
8
|
+
}
|
|
9
|
+
export class TranscriptsDisabled extends Error {
|
|
10
|
+
constructor() {
|
|
11
|
+
super("Transcripts are disabled for this video");
|
|
12
|
+
this.name = "TranscriptsDisabled";
|
|
13
|
+
}
|
|
14
|
+
}
|
|
15
|
+
export class YouTubeTranscriptApi {
|
|
16
|
+
dispatcher;
|
|
17
|
+
headers = {
|
|
18
|
+
"User-Agent": "Mozilla/5.0 (Windows NT 10.0; Win64; x64) AppleWebKit/537.36 (KHTML, like Gecko) Chrome/131.0.0.0 Safari/537.36",
|
|
19
|
+
"Accept-Language": "en-US,en;q=0.9",
|
|
20
|
+
};
|
|
21
|
+
constructor(proxy) {
|
|
22
|
+
if (proxy) {
|
|
23
|
+
this.dispatcher = new ProxyAgent(proxy);
|
|
24
|
+
}
|
|
25
|
+
}
|
|
26
|
+
static extractVideoId(idOrUrl) {
|
|
27
|
+
const patterns = [
|
|
28
|
+
/[?&]v=([^&]+)/,
|
|
29
|
+
/youtube\.com\/shorts\/([^/?]+)/,
|
|
30
|
+
/youtu\.be\/([^/?]+)/,
|
|
31
|
+
/embed\/([^/?]+)/,
|
|
32
|
+
];
|
|
33
|
+
for (const pat of patterns) {
|
|
34
|
+
const match = pat.exec(idOrUrl);
|
|
35
|
+
if (match)
|
|
36
|
+
return match[1];
|
|
37
|
+
}
|
|
38
|
+
return idOrUrl.length === 11 ? idOrUrl : "";
|
|
39
|
+
}
|
|
40
|
+
async fetch(videoIdOrUrl, options = {}) {
|
|
41
|
+
const videoId = YouTubeTranscriptApi.extractVideoId(videoIdOrUrl);
|
|
42
|
+
if (!videoId)
|
|
43
|
+
throw new Error("Invalid YouTube video ID or URL");
|
|
44
|
+
const languages = options.languages?.length ? options.languages : ["en"];
|
|
45
|
+
// 1. Fetch watch page
|
|
46
|
+
const watchUrl = `https://www.youtube.com/watch?v=${videoId}&hl=en`;
|
|
47
|
+
const watchRes = await fetch(watchUrl, {
|
|
48
|
+
headers: this.headers,
|
|
49
|
+
dispatcher: this.dispatcher,
|
|
50
|
+
});
|
|
51
|
+
if (!watchRes.ok)
|
|
52
|
+
throw new Error(`Failed to load watch page: ${watchRes.status}`);
|
|
53
|
+
const html = await watchRes.text();
|
|
54
|
+
// Handle consent cookie
|
|
55
|
+
if (html.includes('action="https://consent.youtube.com/s"')) {
|
|
56
|
+
const consentMatch = html.match(/name="v" value="(.*?)"'/);
|
|
57
|
+
if (consentMatch) {
|
|
58
|
+
this.headers["Cookie"] = `CONSENT=YES+${consentMatch[1]}`;
|
|
59
|
+
}
|
|
60
|
+
}
|
|
61
|
+
// 2. Extract Innertube API key
|
|
62
|
+
const apiKeyMatch = html.match(/"INNERTUBE_API_KEY":"([^"]+)"/);
|
|
63
|
+
if (!apiKeyMatch)
|
|
64
|
+
throw new Error("Could not extract Innertube API key");
|
|
65
|
+
const apiKey = apiKeyMatch[1];
|
|
66
|
+
// 3. POST to Innertube /player (WEB client)
|
|
67
|
+
const playerRes = await fetch(`https://www.youtube.com/youtubei/v1/player?key=${apiKey}`, {
|
|
68
|
+
method: "POST",
|
|
69
|
+
headers: { ...this.headers, "Content-Type": "application/json" },
|
|
70
|
+
body: JSON.stringify({
|
|
71
|
+
context: {
|
|
72
|
+
client: {
|
|
73
|
+
clientName: "WEB",
|
|
74
|
+
clientVersion: "2.20241222.01.00",
|
|
75
|
+
},
|
|
76
|
+
},
|
|
77
|
+
videoId,
|
|
78
|
+
}),
|
|
79
|
+
dispatcher: this.dispatcher,
|
|
80
|
+
});
|
|
81
|
+
if (!playerRes.ok)
|
|
82
|
+
throw new Error(`Innertube request failed: ${playerRes.status}`);
|
|
83
|
+
const data = await playerRes.json();
|
|
84
|
+
// 4. Check playability
|
|
85
|
+
const playability = data.playabilityStatus;
|
|
86
|
+
if (playability?.status !== "OK") {
|
|
87
|
+
throw new Error(playability?.reason || "Video unplayable");
|
|
88
|
+
}
|
|
89
|
+
// 5. Get caption tracks
|
|
90
|
+
const captionTracks = data.captions?.playerCaptionsTracklistRenderer?.captionTracks;
|
|
91
|
+
if (!captionTracks || captionTracks.length === 0) {
|
|
92
|
+
throw new TranscriptsDisabled();
|
|
93
|
+
}
|
|
94
|
+
// 6. Select best track
|
|
95
|
+
let selectedTrack = null;
|
|
96
|
+
for (const lang of languages) {
|
|
97
|
+
selectedTrack =
|
|
98
|
+
captionTracks.find((t) => t.languageCode === lang && t.kind !== "asr") ||
|
|
99
|
+
captionTracks.find((t) => t.languageCode === lang);
|
|
100
|
+
if (selectedTrack)
|
|
101
|
+
break;
|
|
102
|
+
}
|
|
103
|
+
if (!selectedTrack) {
|
|
104
|
+
throw new NoTranscriptFound(`No transcript found for languages: ${languages.join(", ")}`);
|
|
105
|
+
}
|
|
106
|
+
// 7. Clean baseUrl
|
|
107
|
+
let captionUrl = selectedTrack.baseUrl
|
|
108
|
+
.replace(/&fmt=srv\d*/g, "")
|
|
109
|
+
.replace(/&fmt=\w+$/, "")
|
|
110
|
+
.trim();
|
|
111
|
+
console.log(`[DEBUG] Transcript URL: ${captionUrl}`);
|
|
112
|
+
// 8. Fetch transcript XML
|
|
113
|
+
const captionRes = await fetch(captionUrl, {
|
|
114
|
+
headers: this.headers,
|
|
115
|
+
dispatcher: this.dispatcher,
|
|
116
|
+
});
|
|
117
|
+
if (!captionRes.ok) {
|
|
118
|
+
throw new Error(`Failed to fetch transcript: ${captionRes.status}`);
|
|
119
|
+
}
|
|
120
|
+
const xml = await captionRes.text();
|
|
121
|
+
if (!xml.trim() || !xml.includes("<text")) {
|
|
122
|
+
throw new NoTranscriptFound("Transcript data is empty");
|
|
123
|
+
}
|
|
124
|
+
// 9. Parse XML with regex (compatible with all TS targets)
|
|
125
|
+
const regex = /<text start="([^"]+)" dur="([^"]+)">([^<]+)<\/text>/g;
|
|
126
|
+
const snippets = [];
|
|
127
|
+
let match;
|
|
128
|
+
while ((match = regex.exec(xml)) !== null) {
|
|
129
|
+
snippets.push({
|
|
130
|
+
text: match[3].replace(/&#39;/g, "'").trim(),
|
|
131
|
+
start: parseFloat(match[1]),
|
|
132
|
+
duration: parseFloat(match[2]),
|
|
133
|
+
});
|
|
134
|
+
}
|
|
135
|
+
if (snippets.length === 0) {
|
|
136
|
+
throw new NoTranscriptFound("No segments found after parsing");
|
|
137
|
+
}
|
|
138
|
+
// 10. Return transcript object
|
|
139
|
+
const transcript = {
|
|
140
|
+
snippets,
|
|
141
|
+
video_id: videoId,
|
|
142
|
+
language: selectedTrack.name?.runs?.[0]?.text || selectedTrack.languageCode,
|
|
143
|
+
language_code: selectedTrack.languageCode,
|
|
144
|
+
is_generated: selectedTrack.kind === "asr",
|
|
145
|
+
getText() {
|
|
146
|
+
return this.snippets.map((s) => s.text).join(" ");
|
|
147
|
+
},
|
|
148
|
+
toRawData() {
|
|
149
|
+
return this.snippets.map((s) => ({ text: s.text, start: s.start, duration: s.duration }));
|
|
150
|
+
},
|
|
151
|
+
};
|
|
152
|
+
return transcript;
|
|
153
|
+
}
|
|
154
|
+
}
|
|
155
|
+
export default YouTubeTranscriptApi;
|
|
156
|
+
//# sourceMappingURL=index.js.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"index.js","sourceRoot":"","sources":["../src/index.ts"],"names":[],"mappings":"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"}
|
package/package.json
ADDED
|
@@ -0,0 +1,41 @@
|
|
|
1
|
+
{
|
|
2
|
+
"name": "youtube-captions-api",
|
|
3
|
+
"version": "1.0.1",
|
|
4
|
+
"description": "Reliable YouTube transcript fetcher for Node.js — port of the popular Python youtube-transcript-api (2025 edition)",
|
|
5
|
+
"main": "dist/index.js",
|
|
6
|
+
"types": "dist/index.d.ts",
|
|
7
|
+
"type": "module",
|
|
8
|
+
"files": [
|
|
9
|
+
"dist"
|
|
10
|
+
],
|
|
11
|
+
"scripts": {
|
|
12
|
+
"build": "tsc",
|
|
13
|
+
"prepublishOnly": "npm run build"
|
|
14
|
+
},
|
|
15
|
+
"keywords": [
|
|
16
|
+
"youtube",
|
|
17
|
+
"transcript",
|
|
18
|
+
"captions",
|
|
19
|
+
"subtitles",
|
|
20
|
+
"innertube",
|
|
21
|
+
"youtube-transcript-api"
|
|
22
|
+
],
|
|
23
|
+
"author": "Your Name <your.email@example.com>",
|
|
24
|
+
"license": "MIT",
|
|
25
|
+
"repository": {
|
|
26
|
+
"type": "git",
|
|
27
|
+
"url": "git+https://github.com/adnjoo/youtube-transcript-api-node.git"
|
|
28
|
+
},
|
|
29
|
+
"bugs": {
|
|
30
|
+
"url": "https://github.com/adnjoo/youtube-transcript-api-node/issues"
|
|
31
|
+
},
|
|
32
|
+
"homepage": "https://github.com/adnjoo/youtube-transcript-api-node#readme",
|
|
33
|
+
"dependencies": {
|
|
34
|
+
"undici": "^7.16.0"
|
|
35
|
+
},
|
|
36
|
+
"devDependencies": {
|
|
37
|
+
"@types/node": "^22.0.0",
|
|
38
|
+
"typescript": "^5.5.0",
|
|
39
|
+
"vitest": "^2.0.0"
|
|
40
|
+
}
|
|
41
|
+
}
|