ytgrab 0.1.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/LICENSE +21 -0
- package/README.md +142 -0
- package/bin/ytgrab.js +194 -0
- package/dist/downloader/common.d.ts +22 -0
- package/dist/downloader/common.d.ts.map +1 -0
- package/dist/downloader/common.js +125 -0
- package/dist/downloader/common.js.map +1 -0
- package/dist/downloader/hls.d.ts +11 -0
- package/dist/downloader/hls.d.ts.map +1 -0
- package/dist/downloader/hls.js +134 -0
- package/dist/downloader/hls.js.map +1 -0
- package/dist/downloader/http.d.ts +10 -0
- package/dist/downloader/http.d.ts.map +1 -0
- package/dist/downloader/http.js +132 -0
- package/dist/downloader/http.js.map +1 -0
- package/dist/downloader/index.d.ts +10 -0
- package/dist/downloader/index.d.ts.map +1 -0
- package/dist/downloader/index.js +24 -0
- package/dist/downloader/index.js.map +1 -0
- package/dist/extractor/common.d.ts +48 -0
- package/dist/extractor/common.d.ts.map +1 -0
- package/dist/extractor/common.js +324 -0
- package/dist/extractor/common.js.map +1 -0
- package/dist/extractor/nsig.d.ts +17 -0
- package/dist/extractor/nsig.d.ts.map +1 -0
- package/dist/extractor/nsig.js +200 -0
- package/dist/extractor/nsig.js.map +1 -0
- package/dist/extractor/youtube.d.ts +51 -0
- package/dist/extractor/youtube.d.ts.map +1 -0
- package/dist/extractor/youtube.js +1113 -0
- package/dist/extractor/youtube.js.map +1 -0
- package/dist/index.d.ts +36 -0
- package/dist/index.d.ts.map +1 -0
- package/dist/index.js +72 -0
- package/dist/index.js.map +1 -0
- package/dist/networking/index.d.ts +33 -0
- package/dist/networking/index.d.ts.map +1 -0
- package/dist/networking/index.js +171 -0
- package/dist/networking/index.js.map +1 -0
- package/dist/postprocessor/common.d.ts +21 -0
- package/dist/postprocessor/common.d.ts.map +1 -0
- package/dist/postprocessor/common.js +42 -0
- package/dist/postprocessor/common.js.map +1 -0
- package/dist/postprocessor/ffmpeg.d.ts +44 -0
- package/dist/postprocessor/ffmpeg.d.ts.map +1 -0
- package/dist/postprocessor/ffmpeg.js +286 -0
- package/dist/postprocessor/ffmpeg.js.map +1 -0
- package/dist/types.d.ts +157 -0
- package/dist/types.d.ts.map +1 -0
- package/dist/types.js +3 -0
- package/dist/types.js.map +1 -0
- package/dist/utils/index.d.ts +57 -0
- package/dist/utils/index.d.ts.map +1 -0
- package/dist/utils/index.js +403 -0
- package/dist/utils/index.js.map +1 -0
- package/dist/utils/traversal.d.ts +22 -0
- package/dist/utils/traversal.d.ts.map +1 -0
- package/dist/utils/traversal.js +112 -0
- package/dist/utils/traversal.js.map +1 -0
- package/dist/ytgrab.d.ts +48 -0
- package/dist/ytgrab.d.ts.map +1 -0
- package/dist/ytgrab.js +450 -0
- package/dist/ytgrab.js.map +1 -0
- package/package.json +45 -0
package/LICENSE
ADDED
|
@@ -0,0 +1,21 @@
|
|
|
1
|
+
MIT License
|
|
2
|
+
|
|
3
|
+
Copyright (c) 2026 Harry Wang
|
|
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,142 @@
|
|
|
1
|
+
# ytgrab
|
|
2
|
+
|
|
3
|
+
A Node.js YouTube video downloader ported from [yt-dlp](https://github.com/yt-dlp/yt-dlp).
|
|
4
|
+
|
|
5
|
+
## Features
|
|
6
|
+
|
|
7
|
+
- Download YouTube videos in available formats
|
|
8
|
+
- Download auto-generated and manual subtitles/captions
|
|
9
|
+
- Extract video metadata (title, description, thumbnails, chapters, etc.)
|
|
10
|
+
- N-parameter challenge solver (uses yt-dlp's EJS solver scripts)
|
|
11
|
+
- InnerTube API integration (android_vr, web_safari clients)
|
|
12
|
+
- Audio extraction with FFmpeg
|
|
13
|
+
- HLS/M3U8 stream downloading
|
|
14
|
+
- Playlist and search support
|
|
15
|
+
- CLI and programmatic API
|
|
16
|
+
|
|
17
|
+
## Requirements
|
|
18
|
+
|
|
19
|
+
- Node.js >= 18
|
|
20
|
+
- [yt-dlp](https://github.com/yt-dlp/yt-dlp) installed (for the EJS challenge solver scripts)
|
|
21
|
+
- [FFmpeg](https://ffmpeg.org/) (optional, for audio extraction and format conversion)
|
|
22
|
+
|
|
23
|
+
## Installation
|
|
24
|
+
|
|
25
|
+
```bash
|
|
26
|
+
npm install
|
|
27
|
+
npm run build
|
|
28
|
+
```
|
|
29
|
+
|
|
30
|
+
## CLI Usage
|
|
31
|
+
|
|
32
|
+
```bash
|
|
33
|
+
# Download a video
|
|
34
|
+
node bin/ytgrab.js "https://www.youtube.com/watch?v=VIDEO_ID"
|
|
35
|
+
|
|
36
|
+
# Download with subtitles
|
|
37
|
+
node bin/ytgrab.js --write-auto-subs --sub-langs en "https://www.youtube.com/watch?v=VIDEO_ID"
|
|
38
|
+
|
|
39
|
+
# List available formats
|
|
40
|
+
node bin/ytgrab.js -F "https://www.youtube.com/watch?v=VIDEO_ID"
|
|
41
|
+
|
|
42
|
+
# Download to a specific directory
|
|
43
|
+
node bin/ytgrab.js -P /path/to/output "https://www.youtube.com/watch?v=VIDEO_ID"
|
|
44
|
+
|
|
45
|
+
# Extract audio as MP3
|
|
46
|
+
node bin/ytgrab.js -x --audio-format mp3 "https://www.youtube.com/watch?v=VIDEO_ID"
|
|
47
|
+
|
|
48
|
+
# Print video info as JSON
|
|
49
|
+
node bin/ytgrab.js -j "https://www.youtube.com/watch?v=VIDEO_ID"
|
|
50
|
+
|
|
51
|
+
# Download with custom format
|
|
52
|
+
node bin/ytgrab.js -f 720p "https://www.youtube.com/watch?v=VIDEO_ID"
|
|
53
|
+
|
|
54
|
+
# Write metadata files
|
|
55
|
+
node bin/ytgrab.js --write-info-json --write-thumbnail --write-description "URL"
|
|
56
|
+
```
|
|
57
|
+
|
|
58
|
+
Run `node bin/ytgrab.js -h` for all options.
|
|
59
|
+
|
|
60
|
+
## Programmatic API
|
|
61
|
+
|
|
62
|
+
```typescript
|
|
63
|
+
import { YtGrab } from 'ytgrab';
|
|
64
|
+
|
|
65
|
+
// Get video info without downloading
|
|
66
|
+
const yt = new YtGrab();
|
|
67
|
+
const info = await yt.getInfo('https://www.youtube.com/watch?v=VIDEO_ID');
|
|
68
|
+
console.log(info.title);
|
|
69
|
+
console.log(info.formats);
|
|
70
|
+
console.log(info.automatic_captions);
|
|
71
|
+
|
|
72
|
+
// Download a video
|
|
73
|
+
await yt.download('https://www.youtube.com/watch?v=VIDEO_ID');
|
|
74
|
+
|
|
75
|
+
// Download with options
|
|
76
|
+
const ytWithOpts = new YtGrab({
|
|
77
|
+
format: 'best',
|
|
78
|
+
output: '%(title)s.%(ext)s',
|
|
79
|
+
writeSubtitles: true,
|
|
80
|
+
subtitleLanguages: ['en'],
|
|
81
|
+
paths: { home: './downloads' },
|
|
82
|
+
progressHooks: [(progress) => {
|
|
83
|
+
console.log(`${progress.status}: ${progress.downloaded_bytes} bytes`);
|
|
84
|
+
}],
|
|
85
|
+
});
|
|
86
|
+
await ytWithOpts.download('https://www.youtube.com/watch?v=VIDEO_ID');
|
|
87
|
+
|
|
88
|
+
// Extract audio
|
|
89
|
+
const ytAudio = new YtGrab({
|
|
90
|
+
extractAudio: true,
|
|
91
|
+
audioFormat: 'mp3',
|
|
92
|
+
});
|
|
93
|
+
await ytAudio.download('https://www.youtube.com/watch?v=VIDEO_ID');
|
|
94
|
+
|
|
95
|
+
// List formats
|
|
96
|
+
const formats = await yt.listFormats('https://www.youtube.com/watch?v=VIDEO_ID');
|
|
97
|
+
|
|
98
|
+
// List subtitles
|
|
99
|
+
const subs = await yt.listSubtitles('https://www.youtube.com/watch?v=VIDEO_ID');
|
|
100
|
+
```
|
|
101
|
+
|
|
102
|
+
## Project Structure
|
|
103
|
+
|
|
104
|
+
```
|
|
105
|
+
ytgrab/
|
|
106
|
+
├── bin/ytgrab.js # CLI entry point
|
|
107
|
+
├── src/
|
|
108
|
+
│ ├── index.ts # Public API exports
|
|
109
|
+
│ ├── types.ts # TypeScript interfaces
|
|
110
|
+
│ ├── ytgrab.ts # Main orchestrator (YtGrab class)
|
|
111
|
+
│ ├── utils/
|
|
112
|
+
│ │ ├── index.ts # Utility functions
|
|
113
|
+
│ │ └── traversal.ts # traverse_obj / tryGet
|
|
114
|
+
│ ├── networking/
|
|
115
|
+
│ │ └── index.ts # HTTP client
|
|
116
|
+
│ ├── extractor/
|
|
117
|
+
│ │ ├── common.ts # Base InfoExtractor
|
|
118
|
+
│ │ ├── youtube.ts # YouTube extractor
|
|
119
|
+
│ │ └── nsig.ts # N-parameter challenge solver
|
|
120
|
+
│ ├── downloader/
|
|
121
|
+
│ │ ├── common.ts # Base downloader
|
|
122
|
+
│ │ ├── http.ts # HTTP downloader
|
|
123
|
+
│ │ ├── hls.ts # HLS downloader
|
|
124
|
+
│ │ └── index.ts # Downloader registry
|
|
125
|
+
│ └── postprocessor/
|
|
126
|
+
│ ├── common.ts # Base post-processor
|
|
127
|
+
│ └── ffmpeg.ts # FFmpeg post-processors
|
|
128
|
+
└── dist/ # Compiled output
|
|
129
|
+
```
|
|
130
|
+
|
|
131
|
+
## How It Works
|
|
132
|
+
|
|
133
|
+
1. **Webpage fetch** — Downloads the YouTube watch page to extract the initial player response and player JS URL
|
|
134
|
+
2. **InnerTube API** — Calls YouTube's internal API with `android_vr` and `web_safari` clients to get video formats and captions
|
|
135
|
+
3. **N-parameter solving** — Uses yt-dlp's EJS challenge solver (meriyah + astring) to transform the `n` throttle parameter in format URLs
|
|
136
|
+
4. **Format selection** — Picks the best available format based on user preferences
|
|
137
|
+
5. **Download** — Downloads the video via HTTP with resume support, or HLS fragment downloading
|
|
138
|
+
6. **Post-processing** — Optional FFmpeg operations (audio extraction, format conversion, subtitle embedding)
|
|
139
|
+
|
|
140
|
+
## License
|
|
141
|
+
|
|
142
|
+
MIT
|
package/bin/ytgrab.js
ADDED
|
@@ -0,0 +1,194 @@
|
|
|
1
|
+
#!/usr/bin/env node
|
|
2
|
+
|
|
3
|
+
/**
|
|
4
|
+
* ytgrab CLI - YouTube video downloader
|
|
5
|
+
* Usage: ytgrab [OPTIONS] URL [URL...]
|
|
6
|
+
*/
|
|
7
|
+
|
|
8
|
+
import { parseArgs } from 'node:util';
|
|
9
|
+
import { YtGrab } from '../dist/index.js';
|
|
10
|
+
|
|
11
|
+
const { values: opts, positionals: urls } = parseArgs({
|
|
12
|
+
allowPositionals: true,
|
|
13
|
+
options: {
|
|
14
|
+
// Output
|
|
15
|
+
output: { type: 'string', short: 'o', default: '%(title)s [%(id)s].%(ext)s' },
|
|
16
|
+
paths: { type: 'string', short: 'P' },
|
|
17
|
+
|
|
18
|
+
// Format selection
|
|
19
|
+
format: { type: 'string', short: 'f', default: 'best' },
|
|
20
|
+
'merge-output-format': { type: 'string' },
|
|
21
|
+
|
|
22
|
+
// Info
|
|
23
|
+
'list-formats': { type: 'boolean', short: 'F', default: false },
|
|
24
|
+
'list-subs': { type: 'boolean', default: false },
|
|
25
|
+
'print-json': { type: 'boolean', short: 'j', default: false },
|
|
26
|
+
simulate: { type: 'boolean', short: 's', default: false },
|
|
27
|
+
'skip-download': { type: 'boolean', default: false },
|
|
28
|
+
|
|
29
|
+
// Subtitles
|
|
30
|
+
'write-subs': { type: 'boolean', default: false },
|
|
31
|
+
'write-auto-subs': { type: 'boolean', default: false },
|
|
32
|
+
'sub-langs': { type: 'string', default: 'en' },
|
|
33
|
+
'sub-format': { type: 'string', default: 'srt' },
|
|
34
|
+
|
|
35
|
+
// Thumbnails & metadata
|
|
36
|
+
'write-thumbnail': { type: 'boolean', default: false },
|
|
37
|
+
'write-info-json': { type: 'boolean', default: false },
|
|
38
|
+
'write-description': { type: 'boolean', default: false },
|
|
39
|
+
|
|
40
|
+
// Audio
|
|
41
|
+
'extract-audio': { type: 'boolean', short: 'x', default: false },
|
|
42
|
+
'audio-format': { type: 'string', default: 'mp3' },
|
|
43
|
+
'audio-quality': { type: 'string', default: '5' },
|
|
44
|
+
|
|
45
|
+
// Embedding
|
|
46
|
+
'embed-subs': { type: 'boolean', default: false },
|
|
47
|
+
'embed-thumbnail': { type: 'boolean', default: false },
|
|
48
|
+
'embed-metadata': { type: 'boolean', default: false },
|
|
49
|
+
|
|
50
|
+
// Network
|
|
51
|
+
proxy: { type: 'string' },
|
|
52
|
+
retries: { type: 'string', short: 'R', default: '10' },
|
|
53
|
+
|
|
54
|
+
// FFmpeg
|
|
55
|
+
'ffmpeg-location': { type: 'string' },
|
|
56
|
+
|
|
57
|
+
// Verbosity
|
|
58
|
+
quiet: { type: 'boolean', short: 'q', default: false },
|
|
59
|
+
verbose: { type: 'boolean', short: 'v', default: false },
|
|
60
|
+
'no-progress': { type: 'boolean', default: false },
|
|
61
|
+
|
|
62
|
+
// Help
|
|
63
|
+
help: { type: 'boolean', short: 'h', default: false },
|
|
64
|
+
version: { type: 'boolean', short: 'V', default: false },
|
|
65
|
+
},
|
|
66
|
+
});
|
|
67
|
+
|
|
68
|
+
if (opts.help) {
|
|
69
|
+
console.log(`
|
|
70
|
+
ytgrab - YouTube video downloader for Node.js (ported from yt-dlp)
|
|
71
|
+
|
|
72
|
+
Usage: ytgrab [OPTIONS] URL [URL...]
|
|
73
|
+
|
|
74
|
+
Options:
|
|
75
|
+
-o, --output TEMPLATE Output filename template (default: %(title)s [%(id)s].%(ext)s)
|
|
76
|
+
-P, --paths PATH Output directory
|
|
77
|
+
-f, --format FORMAT Video format (default: best)
|
|
78
|
+
Examples: best, worst, bestaudio, 720p, 1080p, 137
|
|
79
|
+
-F, --list-formats List available formats and exit
|
|
80
|
+
--list-subs List available subtitles and exit
|
|
81
|
+
-j, --print-json Print info JSON and exit
|
|
82
|
+
-s, --simulate Do not download, just print info
|
|
83
|
+
|
|
84
|
+
--write-subs Download subtitles
|
|
85
|
+
--write-auto-subs Download auto-generated subtitles
|
|
86
|
+
--sub-langs LANGS Subtitle languages (comma-separated, default: en)
|
|
87
|
+
--sub-format FMT Subtitle format (default: srt)
|
|
88
|
+
|
|
89
|
+
--write-thumbnail Download thumbnail
|
|
90
|
+
--write-info-json Write video info to .info.json
|
|
91
|
+
--write-description Write description to .description
|
|
92
|
+
|
|
93
|
+
-x, --extract-audio Extract audio (requires FFmpeg)
|
|
94
|
+
--audio-format FMT Audio format: mp3, aac, opus, flac, wav (default: mp3)
|
|
95
|
+
--audio-quality Q Audio quality: 0 (best) to 9 (worst) (default: 5)
|
|
96
|
+
|
|
97
|
+
--embed-subs Embed subtitles (requires FFmpeg)
|
|
98
|
+
--embed-thumbnail Embed thumbnail (requires FFmpeg)
|
|
99
|
+
--embed-metadata Embed metadata (requires FFmpeg)
|
|
100
|
+
--merge-output-format FMT Merge format: mp4, mkv, webm
|
|
101
|
+
|
|
102
|
+
--proxy URL Use proxy
|
|
103
|
+
-R, --retries N Number of retries (default: 10)
|
|
104
|
+
--ffmpeg-location PATH FFmpeg binary path
|
|
105
|
+
|
|
106
|
+
-q, --quiet Suppress output
|
|
107
|
+
-v, --verbose Verbose output
|
|
108
|
+
--no-progress Hide progress bar
|
|
109
|
+
|
|
110
|
+
-h, --help Show this help
|
|
111
|
+
-V, --version Show version
|
|
112
|
+
`);
|
|
113
|
+
process.exit(0);
|
|
114
|
+
}
|
|
115
|
+
|
|
116
|
+
if (opts.version) {
|
|
117
|
+
console.log('ytgrab 0.1.0');
|
|
118
|
+
process.exit(0);
|
|
119
|
+
}
|
|
120
|
+
|
|
121
|
+
if (urls.length === 0) {
|
|
122
|
+
console.error('Error: No URL provided. Use -h for help.');
|
|
123
|
+
process.exit(1);
|
|
124
|
+
}
|
|
125
|
+
|
|
126
|
+
const ytgrab = new YtGrab({
|
|
127
|
+
format: opts.format,
|
|
128
|
+
output: opts.output,
|
|
129
|
+
paths: opts.paths ? { home: opts.paths } : undefined,
|
|
130
|
+
quiet: opts.quiet,
|
|
131
|
+
verbose: opts.verbose,
|
|
132
|
+
noProgress: opts['no-progress'],
|
|
133
|
+
simulate: opts.simulate || opts['print-json'],
|
|
134
|
+
skipDownload: opts['skip-download'],
|
|
135
|
+
listFormats: opts['list-formats'],
|
|
136
|
+
listSubtitles: opts['list-subs'],
|
|
137
|
+
writeSubtitles: opts['write-subs'],
|
|
138
|
+
writeAutoSubtitles: opts['write-auto-subs'],
|
|
139
|
+
subtitleLanguages: opts['sub-langs']?.split(','),
|
|
140
|
+
subtitleFormat: opts['sub-format'],
|
|
141
|
+
writeThumbnail: opts['write-thumbnail'],
|
|
142
|
+
writeInfoJson: opts['write-info-json'],
|
|
143
|
+
writeDescription: opts['write-description'],
|
|
144
|
+
extractAudio: opts['extract-audio'],
|
|
145
|
+
audioFormat: opts['audio-format'],
|
|
146
|
+
audioQuality: opts['audio-quality'],
|
|
147
|
+
embedSubtitles: opts['embed-subs'],
|
|
148
|
+
embedThumbnail: opts['embed-thumbnail'],
|
|
149
|
+
embedMetadata: opts['embed-metadata'],
|
|
150
|
+
mergeOutputFormat: opts['merge-output-format'],
|
|
151
|
+
proxy: opts.proxy,
|
|
152
|
+
retries: parseInt(opts.retries || '10'),
|
|
153
|
+
ffmpegLocation: opts['ffmpeg-location'],
|
|
154
|
+
progressHooks: opts.quiet ? [] : [
|
|
155
|
+
(progress) => {
|
|
156
|
+
if (opts['no-progress']) return;
|
|
157
|
+
if (progress.status === 'downloading') {
|
|
158
|
+
const pct = progress.total_bytes
|
|
159
|
+
? ((progress.downloaded_bytes || 0) / progress.total_bytes * 100).toFixed(1)
|
|
160
|
+
: '?';
|
|
161
|
+
const speed = progress.speed
|
|
162
|
+
? `${(progress.speed / 1024 / 1024).toFixed(2)} MiB/s`
|
|
163
|
+
: '? MiB/s';
|
|
164
|
+
const eta = progress.eta ? `ETA ${progress.eta}s` : '';
|
|
165
|
+
const frag = progress.fragment_count
|
|
166
|
+
? `frag ${progress.fragment_index}/${progress.fragment_count}`
|
|
167
|
+
: '';
|
|
168
|
+
process.stdout.write(
|
|
169
|
+
`\r[download] ${pct}% ${speed} ${eta} ${frag}`.padEnd(80)
|
|
170
|
+
);
|
|
171
|
+
} else if (progress.status === 'finished') {
|
|
172
|
+
process.stdout.write('\n');
|
|
173
|
+
}
|
|
174
|
+
},
|
|
175
|
+
],
|
|
176
|
+
});
|
|
177
|
+
|
|
178
|
+
async function main() {
|
|
179
|
+
try {
|
|
180
|
+
if (opts['print-json']) {
|
|
181
|
+
for (const url of urls) {
|
|
182
|
+
const info = await ytgrab.getInfo(url);
|
|
183
|
+
console.log(JSON.stringify(info, null, 2));
|
|
184
|
+
}
|
|
185
|
+
} else {
|
|
186
|
+
await ytgrab.download(urls);
|
|
187
|
+
}
|
|
188
|
+
} catch (err) {
|
|
189
|
+
console.error(`ERROR: ${(err).message}`);
|
|
190
|
+
process.exit(1);
|
|
191
|
+
}
|
|
192
|
+
}
|
|
193
|
+
|
|
194
|
+
main();
|
|
@@ -0,0 +1,22 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* Base FileDownloader - ported from yt_dlp/downloader/common.py
|
|
3
|
+
*/
|
|
4
|
+
import type { InfoDict, DownloadProgress, ProgressHook } from '../types.js';
|
|
5
|
+
export declare abstract class FileDownloader {
|
|
6
|
+
protected _ydl: any;
|
|
7
|
+
protected _progressHooks: ProgressHook[];
|
|
8
|
+
constructor(ydl: any);
|
|
9
|
+
abstract realDownload(filename: string, infoDict: InfoDict): Promise<boolean>;
|
|
10
|
+
addProgressHook(hook: ProgressHook): void;
|
|
11
|
+
protected _hookProgress(progress: DownloadProgress): void;
|
|
12
|
+
protected tempName(filename: string): string;
|
|
13
|
+
protected undoTempName(filename: string): string;
|
|
14
|
+
protected reportDestination(filename: string): void;
|
|
15
|
+
protected reportProgress(downloaded: number, total: number | null, startTime: number): void;
|
|
16
|
+
protected reportFinished(filename: string, filesize: number): void;
|
|
17
|
+
protected calcSpeed(start: number, now: number, bytes: number): number | null;
|
|
18
|
+
protected calcEta(start: number, now: number, total: number, current: number): number | null;
|
|
19
|
+
protected ensureDir(filepath: string): void;
|
|
20
|
+
protected _log(msg: string): void;
|
|
21
|
+
}
|
|
22
|
+
//# sourceMappingURL=common.d.ts.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"common.d.ts","sourceRoot":"","sources":["../../src/downloader/common.ts"],"names":[],"mappings":"AAAA;;GAEG;AAIH,OAAO,KAAK,EAAE,QAAQ,EAAE,gBAAgB,EAAE,YAAY,EAAE,MAAM,aAAa,CAAC;AAG5E,8BAAsB,cAAc;IAClC,SAAS,CAAC,IAAI,EAAE,GAAG,CAAC;IACpB,SAAS,CAAC,cAAc,EAAE,YAAY,EAAE,CAAM;gBAElC,GAAG,EAAE,GAAG;IAOpB,QAAQ,CAAC,YAAY,CAAC,QAAQ,EAAE,MAAM,EAAE,QAAQ,EAAE,QAAQ,GAAG,OAAO,CAAC,OAAO,CAAC;IAE7E,eAAe,CAAC,IAAI,EAAE,YAAY,GAAG,IAAI;IAIzC,SAAS,CAAC,aAAa,CAAC,QAAQ,EAAE,gBAAgB,GAAG,IAAI;IAMzD,SAAS,CAAC,QAAQ,CAAC,QAAQ,EAAE,MAAM,GAAG,MAAM;IAI5C,SAAS,CAAC,YAAY,CAAC,QAAQ,EAAE,MAAM,GAAG,MAAM;IAIhD,SAAS,CAAC,iBAAiB,CAAC,QAAQ,EAAE,MAAM,GAAG,IAAI;IAInD,SAAS,CAAC,cAAc,CAAC,UAAU,EAAE,MAAM,EAAE,KAAK,EAAE,MAAM,GAAG,IAAI,EAAE,SAAS,EAAE,MAAM,GAAG,IAAI;IAe3F,SAAS,CAAC,cAAc,CAAC,QAAQ,EAAE,MAAM,EAAE,QAAQ,EAAE,MAAM,GAAG,IAAI;IAUlE,SAAS,CAAC,SAAS,CAAC,KAAK,EAAE,MAAM,EAAE,GAAG,EAAE,MAAM,EAAE,KAAK,EAAE,MAAM,GAAG,MAAM,GAAG,IAAI;IAM7E,SAAS,CAAC,OAAO,CAAC,KAAK,EAAE,MAAM,EAAE,GAAG,EAAE,MAAM,EAAE,KAAK,EAAE,MAAM,EAAE,OAAO,EAAE,MAAM,GAAG,MAAM,GAAG,IAAI;IAO5F,SAAS,CAAC,SAAS,CAAC,QAAQ,EAAE,MAAM,GAAG,IAAI;IAO3C,SAAS,CAAC,IAAI,CAAC,GAAG,EAAE,MAAM,GAAG,IAAI;CAQlC"}
|
|
@@ -0,0 +1,125 @@
|
|
|
1
|
+
"use strict";
|
|
2
|
+
/**
|
|
3
|
+
* Base FileDownloader - ported from yt_dlp/downloader/common.py
|
|
4
|
+
*/
|
|
5
|
+
var __createBinding = (this && this.__createBinding) || (Object.create ? (function(o, m, k, k2) {
|
|
6
|
+
if (k2 === undefined) k2 = k;
|
|
7
|
+
var desc = Object.getOwnPropertyDescriptor(m, k);
|
|
8
|
+
if (!desc || ("get" in desc ? !m.__esModule : desc.writable || desc.configurable)) {
|
|
9
|
+
desc = { enumerable: true, get: function() { return m[k]; } };
|
|
10
|
+
}
|
|
11
|
+
Object.defineProperty(o, k2, desc);
|
|
12
|
+
}) : (function(o, m, k, k2) {
|
|
13
|
+
if (k2 === undefined) k2 = k;
|
|
14
|
+
o[k2] = m[k];
|
|
15
|
+
}));
|
|
16
|
+
var __setModuleDefault = (this && this.__setModuleDefault) || (Object.create ? (function(o, v) {
|
|
17
|
+
Object.defineProperty(o, "default", { enumerable: true, value: v });
|
|
18
|
+
}) : function(o, v) {
|
|
19
|
+
o["default"] = v;
|
|
20
|
+
});
|
|
21
|
+
var __importStar = (this && this.__importStar) || (function () {
|
|
22
|
+
var ownKeys = function(o) {
|
|
23
|
+
ownKeys = Object.getOwnPropertyNames || function (o) {
|
|
24
|
+
var ar = [];
|
|
25
|
+
for (var k in o) if (Object.prototype.hasOwnProperty.call(o, k)) ar[ar.length] = k;
|
|
26
|
+
return ar;
|
|
27
|
+
};
|
|
28
|
+
return ownKeys(o);
|
|
29
|
+
};
|
|
30
|
+
return function (mod) {
|
|
31
|
+
if (mod && mod.__esModule) return mod;
|
|
32
|
+
var result = {};
|
|
33
|
+
if (mod != null) for (var k = ownKeys(mod), i = 0; i < k.length; i++) if (k[i] !== "default") __createBinding(result, mod, k[i]);
|
|
34
|
+
__setModuleDefault(result, mod);
|
|
35
|
+
return result;
|
|
36
|
+
};
|
|
37
|
+
})();
|
|
38
|
+
Object.defineProperty(exports, "__esModule", { value: true });
|
|
39
|
+
exports.FileDownloader = void 0;
|
|
40
|
+
const fs = __importStar(require("node:fs"));
|
|
41
|
+
const path = __importStar(require("node:path"));
|
|
42
|
+
const index_js_1 = require("../utils/index.js");
|
|
43
|
+
class FileDownloader {
|
|
44
|
+
_ydl;
|
|
45
|
+
_progressHooks = [];
|
|
46
|
+
constructor(ydl) {
|
|
47
|
+
this._ydl = ydl;
|
|
48
|
+
if (ydl?.params?.progressHooks) {
|
|
49
|
+
this._progressHooks.push(...ydl.params.progressHooks);
|
|
50
|
+
}
|
|
51
|
+
}
|
|
52
|
+
addProgressHook(hook) {
|
|
53
|
+
this._progressHooks.push(hook);
|
|
54
|
+
}
|
|
55
|
+
_hookProgress(progress) {
|
|
56
|
+
for (const hook of this._progressHooks) {
|
|
57
|
+
try {
|
|
58
|
+
hook(progress);
|
|
59
|
+
}
|
|
60
|
+
catch { /* ignore hook errors */ }
|
|
61
|
+
}
|
|
62
|
+
}
|
|
63
|
+
tempName(filename) {
|
|
64
|
+
return `${filename}.part`;
|
|
65
|
+
}
|
|
66
|
+
undoTempName(filename) {
|
|
67
|
+
return filename.replace(/\.part$/, '');
|
|
68
|
+
}
|
|
69
|
+
reportDestination(filename) {
|
|
70
|
+
this._log(`Destination: ${filename}`);
|
|
71
|
+
}
|
|
72
|
+
reportProgress(downloaded, total, startTime) {
|
|
73
|
+
const elapsed = (Date.now() - startTime) / 1000;
|
|
74
|
+
const speed = elapsed > 0 ? downloaded / elapsed : 0;
|
|
75
|
+
const eta = total && speed > 0 ? Math.round((total - downloaded) / speed) : undefined;
|
|
76
|
+
this._hookProgress({
|
|
77
|
+
status: 'downloading',
|
|
78
|
+
downloaded_bytes: downloaded,
|
|
79
|
+
total_bytes: total ?? undefined,
|
|
80
|
+
elapsed,
|
|
81
|
+
speed,
|
|
82
|
+
eta,
|
|
83
|
+
});
|
|
84
|
+
}
|
|
85
|
+
reportFinished(filename, filesize) {
|
|
86
|
+
this._log(`Download completed: ${filename} (${(0, index_js_1.formatBytes)(filesize)})`);
|
|
87
|
+
this._hookProgress({
|
|
88
|
+
status: 'finished',
|
|
89
|
+
filename,
|
|
90
|
+
downloaded_bytes: filesize,
|
|
91
|
+
total_bytes: filesize,
|
|
92
|
+
});
|
|
93
|
+
}
|
|
94
|
+
calcSpeed(start, now, bytes) {
|
|
95
|
+
const elapsed = (now - start) / 1000;
|
|
96
|
+
if (elapsed <= 0)
|
|
97
|
+
return null;
|
|
98
|
+
return bytes / elapsed;
|
|
99
|
+
}
|
|
100
|
+
calcEta(start, now, total, current) {
|
|
101
|
+
const elapsed = (now - start) / 1000;
|
|
102
|
+
if (elapsed <= 0 || current <= 0)
|
|
103
|
+
return null;
|
|
104
|
+
const rate = current / elapsed;
|
|
105
|
+
return Math.round((total - current) / rate);
|
|
106
|
+
}
|
|
107
|
+
ensureDir(filepath) {
|
|
108
|
+
const dir = path.dirname(filepath);
|
|
109
|
+
if (!fs.existsSync(dir)) {
|
|
110
|
+
fs.mkdirSync(dir, { recursive: true });
|
|
111
|
+
}
|
|
112
|
+
}
|
|
113
|
+
_log(msg) {
|
|
114
|
+
if (this._ydl?.params?.quiet)
|
|
115
|
+
return;
|
|
116
|
+
if (this._ydl) {
|
|
117
|
+
this._ydl.toScreen(`[download] ${msg}`);
|
|
118
|
+
}
|
|
119
|
+
else {
|
|
120
|
+
console.log(`[download] ${msg}`);
|
|
121
|
+
}
|
|
122
|
+
}
|
|
123
|
+
}
|
|
124
|
+
exports.FileDownloader = FileDownloader;
|
|
125
|
+
//# sourceMappingURL=common.js.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"common.js","sourceRoot":"","sources":["../../src/downloader/common.ts"],"names":[],"mappings":"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"}
|
|
@@ -0,0 +1,11 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* HLS (M3U8) Downloader - ported from yt_dlp/downloader/hls.py
|
|
3
|
+
* Downloads HTTP Live Streaming content by fetching fragments.
|
|
4
|
+
*/
|
|
5
|
+
import { FileDownloader } from './common.js';
|
|
6
|
+
import type { InfoDict } from '../types.js';
|
|
7
|
+
export declare class HlsFD extends FileDownloader {
|
|
8
|
+
realDownload(filename: string, infoDict: InfoDict): Promise<boolean>;
|
|
9
|
+
private _parseMediaPlaylist;
|
|
10
|
+
}
|
|
11
|
+
//# sourceMappingURL=hls.d.ts.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"hls.d.ts","sourceRoot":"","sources":["../../src/downloader/hls.ts"],"names":[],"mappings":"AAAA;;;GAGG;AAIH,OAAO,EAAE,cAAc,EAAE,MAAM,aAAa,CAAC;AAE7C,OAAO,KAAK,EAAE,QAAQ,EAAE,MAAM,aAAa,CAAC;AAG5C,qBAAa,KAAM,SAAQ,cAAc;IACjC,YAAY,CAAC,QAAQ,EAAE,MAAM,EAAE,QAAQ,EAAE,QAAQ,GAAG,OAAO,CAAC,OAAO,CAAC;IA0E1E,OAAO,CAAC,mBAAmB;CAoB5B"}
|
|
@@ -0,0 +1,134 @@
|
|
|
1
|
+
"use strict";
|
|
2
|
+
/**
|
|
3
|
+
* HLS (M3U8) Downloader - ported from yt_dlp/downloader/hls.py
|
|
4
|
+
* Downloads HTTP Live Streaming content by fetching fragments.
|
|
5
|
+
*/
|
|
6
|
+
var __createBinding = (this && this.__createBinding) || (Object.create ? (function(o, m, k, k2) {
|
|
7
|
+
if (k2 === undefined) k2 = k;
|
|
8
|
+
var desc = Object.getOwnPropertyDescriptor(m, k);
|
|
9
|
+
if (!desc || ("get" in desc ? !m.__esModule : desc.writable || desc.configurable)) {
|
|
10
|
+
desc = { enumerable: true, get: function() { return m[k]; } };
|
|
11
|
+
}
|
|
12
|
+
Object.defineProperty(o, k2, desc);
|
|
13
|
+
}) : (function(o, m, k, k2) {
|
|
14
|
+
if (k2 === undefined) k2 = k;
|
|
15
|
+
o[k2] = m[k];
|
|
16
|
+
}));
|
|
17
|
+
var __setModuleDefault = (this && this.__setModuleDefault) || (Object.create ? (function(o, v) {
|
|
18
|
+
Object.defineProperty(o, "default", { enumerable: true, value: v });
|
|
19
|
+
}) : function(o, v) {
|
|
20
|
+
o["default"] = v;
|
|
21
|
+
});
|
|
22
|
+
var __importStar = (this && this.__importStar) || (function () {
|
|
23
|
+
var ownKeys = function(o) {
|
|
24
|
+
ownKeys = Object.getOwnPropertyNames || function (o) {
|
|
25
|
+
var ar = [];
|
|
26
|
+
for (var k in o) if (Object.prototype.hasOwnProperty.call(o, k)) ar[ar.length] = k;
|
|
27
|
+
return ar;
|
|
28
|
+
};
|
|
29
|
+
return ownKeys(o);
|
|
30
|
+
};
|
|
31
|
+
return function (mod) {
|
|
32
|
+
if (mod && mod.__esModule) return mod;
|
|
33
|
+
var result = {};
|
|
34
|
+
if (mod != null) for (var k = ownKeys(mod), i = 0; i < k.length; i++) if (k[i] !== "default") __createBinding(result, mod, k[i]);
|
|
35
|
+
__setModuleDefault(result, mod);
|
|
36
|
+
return result;
|
|
37
|
+
};
|
|
38
|
+
})();
|
|
39
|
+
Object.defineProperty(exports, "__esModule", { value: true });
|
|
40
|
+
exports.HlsFD = void 0;
|
|
41
|
+
const fs = __importStar(require("node:fs"));
|
|
42
|
+
const common_js_1 = require("./common.js");
|
|
43
|
+
const index_js_1 = require("../networking/index.js");
|
|
44
|
+
class HlsFD extends common_js_1.FileDownloader {
|
|
45
|
+
async realDownload(filename, infoDict) {
|
|
46
|
+
const manifestUrl = (infoDict.url || infoDict.manifest_url);
|
|
47
|
+
if (!manifestUrl)
|
|
48
|
+
throw new Error('No HLS manifest URL');
|
|
49
|
+
this.ensureDir(filename);
|
|
50
|
+
this.reportDestination(filename);
|
|
51
|
+
// Download manifest
|
|
52
|
+
this._log('Downloading HLS manifest');
|
|
53
|
+
const manifestResp = await (0, index_js_1.makeRequest)(manifestUrl, {
|
|
54
|
+
headers: infoDict.http_headers ?? {},
|
|
55
|
+
});
|
|
56
|
+
const manifest = manifestResp.text();
|
|
57
|
+
// Parse fragments
|
|
58
|
+
const fragments = this._parseMediaPlaylist(manifest, manifestUrl);
|
|
59
|
+
if (fragments.length === 0) {
|
|
60
|
+
throw new Error('No fragments found in HLS manifest');
|
|
61
|
+
}
|
|
62
|
+
this._log(`Downloading ${fragments.length} fragments`);
|
|
63
|
+
const tmpFilename = this.tempName(filename);
|
|
64
|
+
const stream = fs.createWriteStream(tmpFilename);
|
|
65
|
+
const startTime = Date.now();
|
|
66
|
+
let downloadedBytes = 0;
|
|
67
|
+
for (let i = 0; i < fragments.length; i++) {
|
|
68
|
+
const frag = fragments[i];
|
|
69
|
+
try {
|
|
70
|
+
const resp = await (0, index_js_1.makeRequest)(frag.url, {
|
|
71
|
+
headers: infoDict.http_headers ?? {},
|
|
72
|
+
timeout: 60000,
|
|
73
|
+
});
|
|
74
|
+
stream.write(resp.body);
|
|
75
|
+
downloadedBytes += resp.body.length;
|
|
76
|
+
this._hookProgress({
|
|
77
|
+
status: 'downloading',
|
|
78
|
+
downloaded_bytes: downloadedBytes,
|
|
79
|
+
fragment_index: i + 1,
|
|
80
|
+
fragment_count: fragments.length,
|
|
81
|
+
elapsed: (Date.now() - startTime) / 1000,
|
|
82
|
+
speed: this.calcSpeed(startTime, Date.now(), downloadedBytes) ?? undefined,
|
|
83
|
+
});
|
|
84
|
+
}
|
|
85
|
+
catch (err) {
|
|
86
|
+
this._log(`Fragment ${i + 1}/${fragments.length} failed: ${err.message}`);
|
|
87
|
+
// Continue with next fragment
|
|
88
|
+
}
|
|
89
|
+
}
|
|
90
|
+
await new Promise((resolve, reject) => {
|
|
91
|
+
stream.end(() => {
|
|
92
|
+
try {
|
|
93
|
+
if (fs.existsSync(filename))
|
|
94
|
+
fs.unlinkSync(filename);
|
|
95
|
+
fs.renameSync(tmpFilename, filename);
|
|
96
|
+
resolve();
|
|
97
|
+
}
|
|
98
|
+
catch {
|
|
99
|
+
try {
|
|
100
|
+
fs.copyFileSync(tmpFilename, filename);
|
|
101
|
+
fs.unlinkSync(tmpFilename);
|
|
102
|
+
resolve();
|
|
103
|
+
}
|
|
104
|
+
catch (err) {
|
|
105
|
+
reject(err);
|
|
106
|
+
}
|
|
107
|
+
}
|
|
108
|
+
});
|
|
109
|
+
});
|
|
110
|
+
this.reportFinished(filename, downloadedBytes);
|
|
111
|
+
return true;
|
|
112
|
+
}
|
|
113
|
+
_parseMediaPlaylist(manifest, baseUrl) {
|
|
114
|
+
const fragments = [];
|
|
115
|
+
const lines = manifest.split('\n');
|
|
116
|
+
let duration = 0;
|
|
117
|
+
for (let i = 0; i < lines.length; i++) {
|
|
118
|
+
const line = lines[i].trim();
|
|
119
|
+
if (line.startsWith('#EXTINF:')) {
|
|
120
|
+
const match = line.match(/#EXTINF:([\d.]+)/);
|
|
121
|
+
if (match)
|
|
122
|
+
duration = parseFloat(match[1]);
|
|
123
|
+
}
|
|
124
|
+
else if (line && !line.startsWith('#')) {
|
|
125
|
+
const fragUrl = line.startsWith('http') ? line : new URL(line, baseUrl).toString();
|
|
126
|
+
fragments.push({ url: fragUrl, duration });
|
|
127
|
+
duration = 0;
|
|
128
|
+
}
|
|
129
|
+
}
|
|
130
|
+
return fragments;
|
|
131
|
+
}
|
|
132
|
+
}
|
|
133
|
+
exports.HlsFD = HlsFD;
|
|
134
|
+
//# sourceMappingURL=hls.js.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"hls.js","sourceRoot":"","sources":["../../src/downloader/hls.ts"],"names":[],"mappings":"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"}
|