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 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)!
@@ -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(/&amp;#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
+ }