extract-youtube 1.0.2 → 1.0.4

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
Files changed (47) hide show
  1. package/README.md +61 -20
  2. package/dist/archiver-web.es-3EQgnnex.js +2 -0
  3. package/dist/archiver-web.es-3EQgnnex.js.map +1 -0
  4. package/dist/archiver-web.es-CO0IfW41.mjs +2 -0
  5. package/dist/archiver-web.es-CO0IfW41.mjs.map +1 -0
  6. package/dist/archiver-web.es-eh2cNrlm.js +3140 -0
  7. package/dist/archiver-web.es-eh2cNrlm.js.map +1 -0
  8. package/dist/cli.js +455 -56
  9. package/dist/cli.js.map +1 -1
  10. package/dist/index-CLlAnStL-BKslXZFs.mjs +2 -0
  11. package/dist/index-CLlAnStL-BKslXZFs.mjs.map +1 -0
  12. package/dist/index-CLlAnStL-BNm8mEFS.js +6524 -0
  13. package/dist/index-CLlAnStL-BNm8mEFS.js.map +1 -0
  14. package/dist/index-CLlAnStL-DDV_Pgc5.js +2 -0
  15. package/dist/index-CLlAnStL-DDV_Pgc5.js.map +1 -0
  16. package/dist/index.cjs +2 -0
  17. package/dist/index.cjs.map +1 -0
  18. package/dist/index.mjs +2 -0
  19. package/dist/index.mjs.map +1 -0
  20. package/dist/rolldown-runtime-Bd6kNlEP.js +71 -0
  21. package/jest.config.js +0 -0
  22. package/package.json +12 -9
  23. package/src/cli.ts +11 -2
  24. package/src/{errors.ts → errors/index.ts} +1 -1
  25. package/src/fetchers/transcript-list-fetcher.ts +0 -0
  26. package/src/fetchers/youtube-data-extractor.ts +0 -0
  27. package/src/{formatters.ts → formatters/index.ts} +82 -3
  28. package/src/http/fetch-http-client.ts +9 -9
  29. package/src/index.ts +4 -0
  30. package/src/models/fetched-transcript.ts +0 -0
  31. package/src/models/index.ts +0 -0
  32. package/src/models/transcript-list.ts +0 -0
  33. package/src/models/transcript.ts +0 -0
  34. package/src/parsers/transcript-parser.ts +4 -0
  35. package/src/{proxies.ts → proxies/index.ts} +1 -1
  36. package/src/{types.ts → types/index.ts} +0 -2
  37. package/src/utils/http-error-handler.ts +0 -1
  38. package/src/utils/transcript-utils.ts +107 -0
  39. package/src/youtube-transcript-api.ts +0 -0
  40. package/{__tests__ → test}/api.test.ts +0 -0
  41. package/{__tests__/basic-usage.ts → test/basic.test.ts} +0 -0
  42. package/{__tests__ → test}/formatters.test.ts +0 -0
  43. package/{__tests__ → test}/integration.test.ts +0 -0
  44. package/tsconfig.json +5 -5
  45. package/tsconfig.test.json +10 -0
  46. package/vite.config.ts +8 -3
  47. package/CHANGELOG.md +0 -1
@@ -1,13 +1,13 @@
1
1
  /**
2
- * @fileoverview HTTP client implementation using node-fetch
2
+ * @fileoverview HTTP client implementation using grab-url
3
3
  */
4
4
 
5
- import fetch, { Response, RequestInit } from 'node-fetch';
5
+ import grab from 'grab-url';
6
6
  import { HttpsProxyAgent } from 'https-proxy-agent';
7
7
  import { HttpClient, RequestsProxyConfigDict } from '../types';
8
8
 
9
9
  /**
10
- * Simple HTTP client implementation using node-fetch with proxy support.
10
+ * Simple HTTP client implementation using grab-url with proxy support.
11
11
  * This client handles HTTP/HTTPS requests with configurable headers and proxy settings.
12
12
  *
13
13
  * @internal
@@ -52,12 +52,12 @@ export class FetchHttpClient implements HttpClient {
52
52
  */
53
53
  async get(url: string, options?: RequestInit): Promise<Response> {
54
54
  const proxyAgent = this.getProxyAgentForUrl(url);
55
- return fetch(url, {
55
+ return grab(url, {
56
56
  ...options,
57
57
  method: 'GET',
58
- headers: { ...this.requestHeaders, ...options?.headers },
58
+ headers: { ...this.requestHeaders, ...(options?.headers as Record<string, string>) },
59
59
  agent: proxyAgent
60
- });
60
+ } as any);
61
61
  }
62
62
 
63
63
  /**
@@ -69,12 +69,12 @@ export class FetchHttpClient implements HttpClient {
69
69
  */
70
70
  async post(url: string, options?: RequestInit): Promise<Response> {
71
71
  const proxyAgent = this.getProxyAgentForUrl(url);
72
- return fetch(url, {
72
+ return grab(url, {
73
73
  ...options,
74
74
  method: 'POST',
75
- headers: { ...this.requestHeaders, ...options?.headers },
75
+ headers: { ...this.requestHeaders, ...(options?.headers as Record<string, string>) },
76
76
  agent: proxyAgent
77
- });
77
+ } as any);
78
78
  }
79
79
 
80
80
  /**
package/src/index.ts CHANGED
@@ -6,6 +6,9 @@
6
6
  // Main API
7
7
  export { YouTubeTranscriptApi } from './youtube-transcript-api';
8
8
 
9
+ // Transcript utilities
10
+ export { encodeTranscriptSpeeds, getTimestampAtChar, decompressTimestampsArray } from './utils/transcript-utils';
11
+
9
12
  // Transcript model classes
10
13
  export {
11
14
  FetchedTranscript,
@@ -30,6 +33,7 @@ export {
30
33
  JSONFormatter,
31
34
  PrettyPrintFormatter,
32
35
  TextFormatter,
36
+ ArticleFormatter,
33
37
  SRTFormatter,
34
38
  WebVTTFormatter,
35
39
  FormatterLoader,
File without changes
File without changes
File without changes
File without changes
@@ -129,6 +129,10 @@ export class TranscriptParser {
129
129
  * @returns {string} The cleaned text
130
130
  */
131
131
  private cleanTranscriptText(text: string): string {
132
+ // Ensure text is a string
133
+ if (typeof text !== 'string') {
134
+ text = String(text || '');
135
+ }
132
136
  const decodedText = decode(text);
133
137
  return decodedText.replace(this.htmlRemovalRegex, '');
134
138
  }
@@ -2,7 +2,7 @@
2
2
  * @fileoverview Proxy configuration classes for the YouTube Transcript API
3
3
  */
4
4
 
5
- import { RequestsProxyConfigDict } from './types';
5
+ import { RequestsProxyConfigDict } from '../types';
6
6
 
7
7
  /**
8
8
  * Exception for invalid proxy configurations
@@ -2,8 +2,6 @@
2
2
  * @fileoverview Type definitions for the YouTube Transcript API
3
3
  */
4
4
 
5
- import { Response, RequestInit } from 'node-fetch';
6
-
7
5
  /**
8
6
  * Represents a single snippet/cue of a transcript
9
7
  */
@@ -2,7 +2,6 @@
2
2
  * @fileoverview HTTP error handling utilities for YouTube API requests
3
3
  */
4
4
 
5
- import { Response } from 'node-fetch';
6
5
  import { IpBlocked, YouTubeRequestFailed } from '../errors';
7
6
 
8
7
  /**
@@ -0,0 +1,107 @@
1
+ import { FetchedTranscript } from '../models';
2
+
3
+ /**
4
+ * Encodes speech speed from a fetched transcript into a compact run-length string.
5
+ * Optionally prepends a YouTube embed iframe with data-timestamps for player sync.
6
+ * @param transcript - A FetchedTranscript returned by fetchTranscript()
7
+ * @param addPlayer - If true, prepends an iframe embed with data-timestamps attribute
8
+ * @returns html content, word count, run-length encoded speed string, and raw timestamps
9
+ */
10
+ export function encodeTranscriptSpeeds(
11
+ transcript: FetchedTranscript,
12
+ addPlayer = false
13
+ ): {
14
+ html: string;
15
+ word_count: number;
16
+ speeds: string;
17
+ } {
18
+ let charCount = 0;
19
+ const timestamps: [number, number][] = [];
20
+ const textParts: string[] = [];
21
+
22
+ for (const snippet of transcript.snippets) {
23
+ textParts.push(snippet.text);
24
+ charCount += snippet.text.length;
25
+ if (snippet.start > 0) {
26
+ timestamps.push([charCount, snippet.start]);
27
+ }
28
+ }
29
+
30
+ const content = textParts.join(' ').replace(/\s+/g, ' ');
31
+ const word_count = content.split(/\s+/).filter(Boolean).length;
32
+
33
+ const speedValues = timestamps.map(([char, time]) => Math.floor(char / time) - 10);
34
+ if (speedValues.length === 0) {
35
+ return { html: content, word_count, speeds: '' };
36
+ }
37
+
38
+ const compressed: number[] = [];
39
+ const compressedCount: number[] = [];
40
+ let currentNum = speedValues[0];
41
+ let count = 1;
42
+
43
+ for (let i = 1; i < speedValues.length; i++) {
44
+ if (speedValues[i] === currentNum) {
45
+ count++;
46
+ } else {
47
+ compressed.push(currentNum);
48
+ compressedCount.push(count);
49
+ currentNum = speedValues[i];
50
+ count = 1;
51
+ }
52
+ }
53
+ compressed.push(currentNum);
54
+ compressedCount.push(count);
55
+
56
+ const speeds = compressed.map((val, i) => `${val}x${compressedCount[i]}`).join(',');
57
+
58
+ const html = addPlayer
59
+ ? `<iframe width="100%" height="315px" data-timestamps="${speeds}" ` +
60
+ `src="https://www.youtube.com/embed/${transcript.videoId}" frameborder="0" ` +
61
+ `allow="accelerometer; autoplay; clipboard-write; encrypted-media; ` +
62
+ `gyroscope; picture-in-picture" allowfullscreen></iframe>${content}`
63
+ : content;
64
+
65
+ return { html, word_count, speeds };
66
+ }
67
+
68
+ /**
69
+ * Given a character index in the joined transcript text, returns the interpolated
70
+ * video timestamp in seconds. Uses the nearest snippet boundaries to interpolate.
71
+ */
72
+ export function getTimestampAtChar(transcript: FetchedTranscript, charIndex: number): number {
73
+ let charCount = 0;
74
+ const points: [number, number][] = [[0, 0]]; // [cumulativeChars, seconds]
75
+
76
+ for (const snippet of transcript.snippets) {
77
+ charCount += snippet.text.length + 1; // +1 for the join space
78
+ points.push([charCount, snippet.start]);
79
+ }
80
+
81
+ if (charIndex <= 0) return 0;
82
+ if (charIndex >= points[points.length - 1][0]) return points[points.length - 1][1];
83
+
84
+ for (let i = 1; i < points.length; i++) {
85
+ if (points[i][0] >= charIndex) {
86
+ const [c0, t0] = points[i - 1];
87
+ const [c1, t1] = points[i];
88
+ const ratio = (charIndex - c0) / (c1 - c0);
89
+ return t0 + ratio * (t1 - t0);
90
+ }
91
+ }
92
+
93
+ return points[points.length - 1][1];
94
+ }
95
+
96
+ /**
97
+ * Decompresses a speeds string produced by encodeTranscriptSpeeds back into a flat array.
98
+ * Format: "12x3,8x7,15x1" → [12,12,12, 8,8,8,8,8,8,8, 15]
99
+ */
100
+ export function decompressTimestampsArray(speeds: string): number[] {
101
+ const decompressed: number[] = [];
102
+ for (const part of speeds.split(',')) {
103
+ const [num, count] = part.split('x');
104
+ decompressed.push(...Array(parseInt(count)).fill(parseInt(num)));
105
+ }
106
+ return decompressed;
107
+ }
File without changes
File without changes
File without changes
File without changes
File without changes
package/tsconfig.json CHANGED
@@ -2,7 +2,7 @@
2
2
  "compilerOptions": {
3
3
  "target": "ES2020",
4
4
  "module": "commonjs",
5
- "lib": ["ES2020"],
5
+ "lib": ["ES2020", "DOM"],
6
6
  "outDir": "./dist",
7
7
  "rootDir": "./src",
8
8
  "declaration": true,
@@ -12,9 +12,9 @@
12
12
  "esModuleInterop": true,
13
13
  "skipLibCheck": true,
14
14
  "forceConsistentCasingInFileNames": true,
15
- "moduleResolution": "node",
16
- "resolveJsonModule": true
15
+ "resolveJsonModule": true,
16
+ "types": ["node"]
17
17
  },
18
- "include": ["src/**/*", "__tests__"],
19
- "exclude": ["node_modules", "dist", "src/**/*.test.ts", "__tests__"]
18
+ "include": ["src/**/*", "test"],
19
+ "exclude": ["node_modules", "dist", "src/**/*.test.ts", "test"]
20
20
  }
@@ -0,0 +1,10 @@
1
+ {
2
+ "extends": "./tsconfig.json",
3
+ "compilerOptions": {
4
+ "rootDir": ".",
5
+ "types": ["node", "jest"],
6
+ "noEmit": true
7
+ },
8
+ "include": ["src/**/*", "test/**/*"],
9
+ "exclude": ["node_modules", "dist"]
10
+ }
package/vite.config.ts CHANGED
@@ -10,6 +10,8 @@ const buildCli = process.env.BUILD_CLI === 'true';
10
10
 
11
11
  export default defineConfig({
12
12
  build: {
13
+ // Don't empty output dir for CLI build to preserve library files
14
+ emptyOutDir: !buildCli,
13
15
  lib: buildCli ? {
14
16
  entry: resolve(__dirname, 'src/cli.ts'),
15
17
  name: 'ExtractYoutubeCli',
@@ -22,9 +24,12 @@ export default defineConfig({
22
24
  fileName: (format) => `index.${format === 'es' ? 'mjs' : 'cjs'}`,
23
25
  },
24
26
  rollupOptions: {
25
- // Externalize dependencies to avoid bundling them
27
+ // Externalize dependencies to avoid bundling them.
28
+ // grab-url is deliberately bundled: its published CJS entry resolves
29
+ // its internal chunks relative to the consumer's cwd and fails to load
30
+ // under require(); bundling its ESM source avoids the broken entry.
26
31
  external: [
27
- ...Object.keys(pkg.dependencies || {}),
32
+ ...Object.keys(pkg.dependencies || {}).filter((d) => d !== 'grab-url'),
28
33
  'node:http',
29
34
  'node:https',
30
35
  'node:url',
@@ -38,7 +43,7 @@ export default defineConfig({
38
43
  exports: 'named',
39
44
  // Provide global names for UMD/IIFE builds
40
45
  globals: {
41
- 'node-fetch': 'fetch',
46
+ 'grab-url': 'grab',
42
47
  'fast-xml-parser': 'XMLParser',
43
48
  'html-entities': 'htmlEntities',
44
49
  'https-proxy-agent': 'HttpsProxyAgent',
package/CHANGELOG.md DELETED
@@ -1 +0,0 @@
1
-