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.
- package/README.md +61 -20
- package/dist/archiver-web.es-3EQgnnex.js +2 -0
- package/dist/archiver-web.es-3EQgnnex.js.map +1 -0
- package/dist/archiver-web.es-CO0IfW41.mjs +2 -0
- package/dist/archiver-web.es-CO0IfW41.mjs.map +1 -0
- package/dist/archiver-web.es-eh2cNrlm.js +3140 -0
- package/dist/archiver-web.es-eh2cNrlm.js.map +1 -0
- package/dist/cli.js +455 -56
- package/dist/cli.js.map +1 -1
- package/dist/index-CLlAnStL-BKslXZFs.mjs +2 -0
- package/dist/index-CLlAnStL-BKslXZFs.mjs.map +1 -0
- package/dist/index-CLlAnStL-BNm8mEFS.js +6524 -0
- package/dist/index-CLlAnStL-BNm8mEFS.js.map +1 -0
- package/dist/index-CLlAnStL-DDV_Pgc5.js +2 -0
- package/dist/index-CLlAnStL-DDV_Pgc5.js.map +1 -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/dist/rolldown-runtime-Bd6kNlEP.js +71 -0
- package/jest.config.js +0 -0
- package/package.json +12 -9
- package/src/cli.ts +11 -2
- package/src/{errors.ts → errors/index.ts} +1 -1
- package/src/fetchers/transcript-list-fetcher.ts +0 -0
- package/src/fetchers/youtube-data-extractor.ts +0 -0
- package/src/{formatters.ts → formatters/index.ts} +82 -3
- package/src/http/fetch-http-client.ts +9 -9
- package/src/index.ts +4 -0
- package/src/models/fetched-transcript.ts +0 -0
- package/src/models/index.ts +0 -0
- package/src/models/transcript-list.ts +0 -0
- package/src/models/transcript.ts +0 -0
- package/src/parsers/transcript-parser.ts +4 -0
- package/src/{proxies.ts → proxies/index.ts} +1 -1
- package/src/{types.ts → types/index.ts} +0 -2
- package/src/utils/http-error-handler.ts +0 -1
- package/src/utils/transcript-utils.ts +107 -0
- package/src/youtube-transcript-api.ts +0 -0
- package/{__tests__ → test}/api.test.ts +0 -0
- package/{__tests__/basic-usage.ts → test/basic.test.ts} +0 -0
- package/{__tests__ → test}/formatters.test.ts +0 -0
- package/{__tests__ → test}/integration.test.ts +0 -0
- package/tsconfig.json +5 -5
- package/tsconfig.test.json +10 -0
- package/vite.config.ts +8 -3
- package/CHANGELOG.md +0 -1
|
@@ -1,13 +1,13 @@
|
|
|
1
1
|
/**
|
|
2
|
-
* @fileoverview HTTP client implementation using
|
|
2
|
+
* @fileoverview HTTP client implementation using grab-url
|
|
3
3
|
*/
|
|
4
4
|
|
|
5
|
-
import
|
|
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
|
|
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
|
|
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
|
|
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
|
package/src/models/index.ts
CHANGED
|
File without changes
|
|
File without changes
|
package/src/models/transcript.ts
CHANGED
|
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
|
}
|
|
@@ -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
|
-
"
|
|
16
|
-
"
|
|
15
|
+
"resolveJsonModule": true,
|
|
16
|
+
"types": ["node"]
|
|
17
17
|
},
|
|
18
|
-
"include": ["src/**/*", "
|
|
19
|
-
"exclude": ["node_modules", "dist", "src/**/*.test.ts", "
|
|
18
|
+
"include": ["src/**/*", "test"],
|
|
19
|
+
"exclude": ["node_modules", "dist", "src/**/*.test.ts", "test"]
|
|
20
20
|
}
|
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
|
-
'
|
|
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
|
-
|