@blagoja/ts-dlp 0.1.2 → 0.1.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 +105 -74
- package/dist/index.cjs +121 -26
- package/dist/index.d.cts +58 -8
- package/dist/index.d.ts +58 -8
- package/dist/index.js +119 -26
- package/package.json +53 -51
- package/dist/download.d.ts +0 -43
- package/dist/download.d.ts.map +0 -1
- package/dist/download.js +0 -55
- package/dist/download.js.map +0 -1
- package/dist/index.d.ts.map +0 -1
- package/dist/index.js.map +0 -1
- package/dist/internal/runner.d.ts +0 -3
- package/dist/internal/runner.d.ts.map +0 -1
- package/dist/internal/runner.js +0 -13
- package/dist/internal/runner.js.map +0 -1
- package/dist/types.d.ts +0 -2
- package/dist/types.d.ts.map +0 -1
- package/dist/types.js +0 -2
- package/dist/types.js.map +0 -1
- package/dist/version.d.ts +0 -2
- package/dist/version.d.ts.map +0 -1
- package/dist/version.js +0 -2
- package/dist/version.js.map +0 -1
package/README.md
CHANGED
|
@@ -1,8 +1,22 @@
|
|
|
1
|
+
<div align="center">
|
|
2
|
+
|
|
3
|
+
[](#readme)
|
|
4
|
+
|
|
5
|
+
</div>
|
|
6
|
+
|
|
1
7
|
# ts-dlp
|
|
2
8
|
|
|
3
|
-
A lightweight, fluent TypeScript wrapper for yt-dlp made for humans.
|
|
9
|
+
A lightweight, fluent TypeScript wrapper for [yt-dlp](https://github.com/yt-dlp/yt-dlp) made for humans.
|
|
10
|
+
|
|
11
|
+
`ts-dlp` makes using yt-dlp feel natural in Node.js and TypeScript projects through a chainable, strongly-typed API.
|
|
12
|
+
|
|
13
|
+
---
|
|
14
|
+
|
|
15
|
+
## Why ts-dlp?
|
|
16
|
+
|
|
17
|
+
yt-dlp is extremely powerful, but constructing CLI arguments manually from Node.js can become difficult to maintain and hard to type safely.
|
|
4
18
|
|
|
5
|
-
`ts-dlp`
|
|
19
|
+
`ts-dlp` provides a fluent TypeScript API that keeps you close to native yt-dlp functionality while improving developer experience.
|
|
6
20
|
|
|
7
21
|
---
|
|
8
22
|
|
|
@@ -11,9 +25,9 @@ A lightweight, fluent TypeScript wrapper for yt-dlp made for humans.
|
|
|
11
25
|
- Fluent builder-style API
|
|
12
26
|
- Fully typed with TypeScript
|
|
13
27
|
- Uses your local yt-dlp installation
|
|
14
|
-
- Promise-based
|
|
28
|
+
- Promise-based
|
|
15
29
|
- Easy process handling
|
|
16
|
-
- Cross-platform
|
|
30
|
+
- Cross-platform
|
|
17
31
|
|
|
18
32
|
---
|
|
19
33
|
|
|
@@ -21,79 +35,111 @@ A lightweight, fluent TypeScript wrapper for yt-dlp made for humans.
|
|
|
21
35
|
|
|
22
36
|
> Early development / proof of concept
|
|
23
37
|
|
|
24
|
-
Current functionality:
|
|
25
|
-
|
|
26
|
-
- Detects whether `yt-dlp` is installed and available in PATH
|
|
27
|
-
|
|
28
38
|
---
|
|
29
39
|
|
|
30
|
-
|
|
40
|
+
## Installation
|
|
31
41
|
|
|
32
42
|
```bash
|
|
33
43
|
npm install ts-dlp
|
|
34
44
|
```
|
|
35
45
|
|
|
36
|
-
|
|
46
|
+
`ts-dlp` requires a local yt-dlp installation available in your system PATH.
|
|
37
47
|
|
|
38
|
-
|
|
48
|
+
### Install yt-dlp
|
|
39
49
|
|
|
40
|
-
|
|
50
|
+
**Windows**
|
|
41
51
|
|
|
42
52
|
```bash
|
|
43
53
|
winget install yt-dlp.yt-dlp
|
|
44
54
|
```
|
|
45
55
|
|
|
46
|
-
|
|
56
|
+
**macOS**
|
|
47
57
|
|
|
48
58
|
```bash
|
|
49
59
|
brew install yt-dlp
|
|
50
60
|
```
|
|
51
61
|
|
|
52
|
-
|
|
62
|
+
**Linux**
|
|
53
63
|
|
|
54
64
|
```bash
|
|
55
65
|
pip install -U yt-dlp
|
|
56
66
|
```
|
|
57
67
|
|
|
58
|
-
Official project:
|
|
59
|
-
|
|
60
|
-
https://www.npmjs.com/package/@blagoja/ts-dlp
|
|
68
|
+
Official yt-dlp project: [https://github.com/yt-dlp/yt-dlp](https://github.com/yt-dlp/yt-dlp)
|
|
61
69
|
|
|
62
70
|
---
|
|
63
71
|
|
|
64
|
-
|
|
72
|
+
## Quick Start
|
|
65
73
|
|
|
66
74
|
Instead of writing:
|
|
67
75
|
|
|
68
76
|
```bash
|
|
69
|
-
yt-dlp -f "bestvideo[height<=1080]+bestaudio" URL
|
|
77
|
+
yt-dlp -f "bestvideo[height<=1080]+bestaudio" -o "downloads/%(title)s.%(ext)s" URL
|
|
70
78
|
```
|
|
71
79
|
|
|
72
80
|
You write:
|
|
73
81
|
|
|
74
82
|
```ts
|
|
75
|
-
|
|
83
|
+
import {download} from "ts-dlp";
|
|
84
|
+
|
|
85
|
+
await download(url).resolution("1080p").output("downloads").run();
|
|
76
86
|
```
|
|
77
87
|
|
|
78
88
|
---
|
|
79
89
|
|
|
80
|
-
|
|
90
|
+
## API
|
|
81
91
|
|
|
82
92
|
```ts
|
|
83
|
-
import {
|
|
93
|
+
import {download} from "ts-dlp";
|
|
84
94
|
|
|
85
|
-
await
|
|
86
|
-
.download("https://youtube.com/watch?v=...")
|
|
95
|
+
await download(url)
|
|
87
96
|
.resolution("1080p")
|
|
88
97
|
.fps(60)
|
|
89
98
|
.format("mp4")
|
|
99
|
+
.audio("aac")
|
|
90
100
|
.output("./downloads")
|
|
101
|
+
.filename(({title, ext}) => `${title}.${ext}`)
|
|
102
|
+
.run();
|
|
103
|
+
```
|
|
104
|
+
|
|
105
|
+
### Methods
|
|
106
|
+
|
|
107
|
+
| Method | Description |
|
|
108
|
+
| --------------------- | -------------------------------------------------- |
|
|
109
|
+
| `.resolution(res)` | Max video resolution (e.g. `"1080p"`, `"720p"`) |
|
|
110
|
+
| `.format(ext)` | Preferred video container (e.g. `"mp4"`, `"webm"`) |
|
|
111
|
+
| `.fps(fps)` | Max frame rate (e.g. `60`) |
|
|
112
|
+
| `.audio(codec?)` | Audio codec preference (e.g. `"aac"`, `"mp3"`) |
|
|
113
|
+
| `.output(dir)` | Output directory |
|
|
114
|
+
| `.filename(template)` | Filename template string or callback |
|
|
115
|
+
| `.run()` | Execute the download |
|
|
116
|
+
|
|
117
|
+
---
|
|
118
|
+
|
|
119
|
+
## Philosophy
|
|
120
|
+
|
|
121
|
+
`ts-dlp` aims to stay thin and predictable.
|
|
122
|
+
|
|
123
|
+
The project does not attempt to hide yt-dlp or replace its ecosystem. Instead, it focuses on:
|
|
124
|
+
|
|
125
|
+
- Better TypeScript ergonomics
|
|
126
|
+
- Safer command construction
|
|
127
|
+
- Cleaner process handling
|
|
128
|
+
- Fluent APIs
|
|
129
|
+
|
|
130
|
+
`ts-dlp` will not try to model every yt-dlp flag as a typed method. Instead, future versions will expose escape hatches for advanced usage:
|
|
131
|
+
|
|
132
|
+
```ts
|
|
133
|
+
// Planned
|
|
134
|
+
await download(url)
|
|
135
|
+
.customFormat("bestvideo[height<=1080][vcodec^=avc]+bestaudio[ext=m4a]")
|
|
136
|
+
.args(["--no-playlist", "--write-subs"])
|
|
91
137
|
.run();
|
|
92
138
|
```
|
|
93
139
|
|
|
94
140
|
---
|
|
95
141
|
|
|
96
|
-
|
|
142
|
+
## Goals
|
|
97
143
|
|
|
98
144
|
- Make yt-dlp easier to use from Node.js
|
|
99
145
|
- Avoid manually constructing CLI arguments
|
|
@@ -103,94 +149,79 @@ await ytdlp
|
|
|
103
149
|
|
|
104
150
|
---
|
|
105
151
|
|
|
106
|
-
|
|
152
|
+
## Roadmap
|
|
107
153
|
|
|
108
|
-
|
|
154
|
+
### v0.1.0 - Foundation
|
|
109
155
|
|
|
110
156
|
- [x] Detect yt-dlp installation
|
|
111
|
-
- [x] Execute
|
|
112
|
-
- [
|
|
113
|
-
- [ ] Error handling
|
|
114
|
-
- [ ] Capture stdout/stderr
|
|
115
|
-
- [ ] TypeScript support
|
|
116
|
-
- [ ] Unit test setup
|
|
117
|
-
- [x] `.download(url)`
|
|
118
|
-
- [x] `.run()`
|
|
157
|
+
- [x] Execute yt-dlp commands
|
|
158
|
+
- [x] `.download(url)` / `.run()`
|
|
119
159
|
- [x] `.output(path)`
|
|
120
|
-
- [
|
|
121
|
-
- [x] `.audio()`
|
|
122
|
-
- [
|
|
123
|
-
- [x] `.
|
|
124
|
-
- [
|
|
125
|
-
|
|
126
|
-
|
|
127
|
-
|
|
128
|
-
|
|
129
|
-
|
|
130
|
-
|
|
131
|
-
|
|
132
|
-
---
|
|
133
|
-
|
|
134
|
-
## v0.2.0 - Metadata & Information
|
|
135
|
-
|
|
136
|
-
- [ ] `.info()`
|
|
137
|
-
- [ ] Video metadata parsing
|
|
160
|
+
- [x] `.format(ext)`
|
|
161
|
+
- [x] `.audio(codec?)`
|
|
162
|
+
- [x] `.resolution(res)`
|
|
163
|
+
- [x] `.fps(fps)`
|
|
164
|
+
- [x] `.filename(template)`
|
|
165
|
+
- [x] Error handling
|
|
166
|
+
- [x] Capture stdout/stderr
|
|
167
|
+
- [x] Unit test setup
|
|
168
|
+
|
|
169
|
+
### v0.2.0 - Metadata & Information
|
|
170
|
+
|
|
171
|
+
- [ ] `.info(url)` - fetch video metadata
|
|
138
172
|
- [ ] JSON output support
|
|
139
173
|
- [ ] Playlist support
|
|
140
174
|
- [ ] Progress events
|
|
141
175
|
|
|
142
|
-
|
|
143
|
-
|
|
144
|
-
```ts
|
|
145
|
-
const info = await ytdlp.info(url);
|
|
146
|
-
```
|
|
147
|
-
|
|
148
|
-
---
|
|
149
|
-
|
|
150
|
-
## v0.3.0 - Advanced Features
|
|
176
|
+
### v0.3.0 - Advanced Features
|
|
151
177
|
|
|
152
178
|
- [ ] Subtitle support
|
|
153
179
|
- [ ] Thumbnail downloads
|
|
154
180
|
- [ ] Playlist filtering
|
|
155
181
|
- [ ] Cookies support
|
|
156
182
|
- [ ] Proxy support
|
|
157
|
-
- [ ]
|
|
158
|
-
|
|
159
|
-
---
|
|
183
|
+
- [ ] `.customFormat(selector)` escape hatch
|
|
184
|
+
- [ ] `.args(flags[])` escape hatch
|
|
160
185
|
|
|
161
|
-
|
|
186
|
+
### v0.4.0 - Developer Experience
|
|
162
187
|
|
|
163
|
-
- [ ] Better typed builders
|
|
164
188
|
- [ ] Presets
|
|
165
|
-
- [ ] Config
|
|
189
|
+
- [ ] Config file support
|
|
166
190
|
- [ ] Improved documentation
|
|
167
|
-
- [ ] More examples
|
|
168
191
|
- [ ] CI/CD pipeline
|
|
169
192
|
- [ ] Automated tests
|
|
170
193
|
|
|
171
194
|
---
|
|
172
195
|
|
|
173
|
-
|
|
196
|
+
## Non-Goals
|
|
174
197
|
|
|
175
198
|
At least for now, `ts-dlp` will NOT:
|
|
176
199
|
|
|
177
200
|
- Bundle yt-dlp binaries
|
|
178
201
|
- Replace yt-dlp functionality
|
|
179
|
-
- Abstract every yt-dlp flag
|
|
202
|
+
- Abstract every yt-dlp flag into typed methods
|
|
180
203
|
- Become a GUI application
|
|
181
204
|
|
|
182
205
|
The goal is to remain a lightweight developer wrapper.
|
|
183
206
|
|
|
184
207
|
---
|
|
185
208
|
|
|
186
|
-
|
|
209
|
+
## Contributing
|
|
187
210
|
|
|
188
211
|
Contributions, suggestions, and issue reports are welcome.
|
|
189
212
|
|
|
190
|
-
If you have
|
|
213
|
+
If you have ideas for the API design or developer experience, feel free to open an issue.
|
|
214
|
+
|
|
215
|
+
This project follows [Conventional Commits](https://www.conventionalcommits.org/) and maintains a [CHANGELOG](./CHANGELOG.md).
|
|
216
|
+
|
|
217
|
+
---
|
|
218
|
+
|
|
219
|
+
## Disclaimer
|
|
220
|
+
|
|
221
|
+
`ts-dlp` is a developer wrapper around yt-dlp. Users are responsible for complying with the terms of service and applicable laws of the media sources they access.
|
|
191
222
|
|
|
192
223
|
---
|
|
193
224
|
|
|
194
|
-
|
|
225
|
+
## License
|
|
195
226
|
|
|
196
227
|
MIT
|
package/dist/index.cjs
CHANGED
|
@@ -20,21 +20,56 @@ var __toCommonJS = (mod) => __copyProps(__defProp({}, "__esModule", { value: tru
|
|
|
20
20
|
// src/index.ts
|
|
21
21
|
var index_exports = {};
|
|
22
22
|
__export(index_exports, {
|
|
23
|
+
YtDlpError: () => YtDlpError,
|
|
24
|
+
YtDlpNotFoundError: () => YtDlpNotFoundError,
|
|
23
25
|
download: () => download
|
|
24
26
|
});
|
|
25
27
|
module.exports = __toCommonJS(index_exports);
|
|
26
28
|
|
|
27
29
|
// src/internal/runner.ts
|
|
28
30
|
var import_execa = require("execa");
|
|
29
|
-
|
|
30
|
-
|
|
31
|
-
|
|
32
|
-
|
|
31
|
+
|
|
32
|
+
// src/errors.ts
|
|
33
|
+
var YtDlpNotFoundError = class extends Error {
|
|
34
|
+
constructor() {
|
|
35
|
+
super(
|
|
36
|
+
"yt-dlp is not installed or not in PATH. Refer to https://github.com/yt-dlp/yt-dlp#installation"
|
|
37
|
+
);
|
|
38
|
+
this.name = "YtDlpNotFoundError";
|
|
39
|
+
}
|
|
40
|
+
};
|
|
41
|
+
var YtDlpError = class extends Error {
|
|
42
|
+
constructor(message, exitCode, stderr) {
|
|
43
|
+
super(message);
|
|
44
|
+
this.exitCode = exitCode;
|
|
45
|
+
this.stderr = stderr;
|
|
46
|
+
this.name = "YtDlpError";
|
|
47
|
+
}
|
|
48
|
+
exitCode;
|
|
49
|
+
stderr;
|
|
50
|
+
};
|
|
51
|
+
|
|
52
|
+
// src/internal/runner.ts
|
|
53
|
+
async function runYtDlp(args, pipe = false) {
|
|
54
|
+
const result = await (0, import_execa.execa)(
|
|
55
|
+
"yt-dlp",
|
|
56
|
+
["--js-runtimes", "node", ...args],
|
|
57
|
+
{ stdio: pipe ? "pipe" : "inherit" }
|
|
58
|
+
).catch(handleError);
|
|
59
|
+
return {
|
|
60
|
+
stdout: result.stdout ?? "",
|
|
61
|
+
stderr: result.stderr ?? ""
|
|
62
|
+
};
|
|
33
63
|
}
|
|
34
|
-
function
|
|
35
|
-
if (error instanceof import_execa.ExecaError
|
|
36
|
-
|
|
37
|
-
|
|
64
|
+
function handleError(error) {
|
|
65
|
+
if (error instanceof import_execa.ExecaError) {
|
|
66
|
+
if (error.code === "ENOENT") {
|
|
67
|
+
throw new YtDlpNotFoundError();
|
|
68
|
+
}
|
|
69
|
+
throw new YtDlpError(
|
|
70
|
+
`yt-dlp exited with code ${error.exitCode}`,
|
|
71
|
+
error.exitCode ?? 1,
|
|
72
|
+
error.stderr ?? ""
|
|
38
73
|
);
|
|
39
74
|
}
|
|
40
75
|
throw error;
|
|
@@ -44,6 +79,11 @@ function handleEnoent(error) {
|
|
|
44
79
|
var DownloadBuilder = class {
|
|
45
80
|
constructor(url) {
|
|
46
81
|
this.url = url;
|
|
82
|
+
try {
|
|
83
|
+
new URL(url);
|
|
84
|
+
} catch {
|
|
85
|
+
throw new TypeError(`Invalid URL: ${url}`);
|
|
86
|
+
}
|
|
47
87
|
}
|
|
48
88
|
url;
|
|
49
89
|
args = [];
|
|
@@ -51,6 +91,32 @@ var DownloadBuilder = class {
|
|
|
51
91
|
audioFormat;
|
|
52
92
|
outputDir;
|
|
53
93
|
filenameTemplate;
|
|
94
|
+
videoExt;
|
|
95
|
+
videoFps;
|
|
96
|
+
videoHeight;
|
|
97
|
+
/**
|
|
98
|
+
* A private method to build the command-line arguments for yt-dlp based on the options set by the user.
|
|
99
|
+
* This method constructs the appropriate format filters and output template arguments according to the options set by the user.
|
|
100
|
+
*
|
|
101
|
+
* @returns The command-line arguments to be passed to yt-dlp based on the specified options.
|
|
102
|
+
*/
|
|
103
|
+
buildArgs() {
|
|
104
|
+
const args = [...this.args];
|
|
105
|
+
if (this.videoHeight || this.videoFps || this.videoExt || this.audioFormat) {
|
|
106
|
+
const heightFilter = this.videoHeight ? `[height<=${this.videoHeight}]` : "";
|
|
107
|
+
const fpsFilter = this.videoFps ? `[fps<=${this.videoFps}]` : "";
|
|
108
|
+
const extFilter = this.videoExt ? `[ext=${this.videoExt}]` : "";
|
|
109
|
+
const video = `bestvideo${heightFilter}${fpsFilter}${extFilter}`;
|
|
110
|
+
const audio = this.audioFormat ?? "bestaudio";
|
|
111
|
+
args.push("-f", `${video}+${audio}/best`);
|
|
112
|
+
}
|
|
113
|
+
if (this.outputDir || this.filenameTemplate) {
|
|
114
|
+
const dir = this.outputDir ?? "";
|
|
115
|
+
const file = this.filenameTemplate ?? "%(title)s.%(ext)s";
|
|
116
|
+
args.push("-o", dir ? `${dir}/${file}` : file);
|
|
117
|
+
}
|
|
118
|
+
return args;
|
|
119
|
+
}
|
|
54
120
|
/**
|
|
55
121
|
* Set the desired resolution for the video download.
|
|
56
122
|
* The method will automatically select the best video format that is less than or equal to the specified resolution.
|
|
@@ -60,8 +126,7 @@ var DownloadBuilder = class {
|
|
|
60
126
|
* @returns The current instance of DownloadBuilder for method chaining.
|
|
61
127
|
*/
|
|
62
128
|
resolution(res) {
|
|
63
|
-
|
|
64
|
-
this.videoFormat = `bestvideo[height<=${height}]`;
|
|
129
|
+
this.videoHeight = res.replace("p", "");
|
|
65
130
|
return this;
|
|
66
131
|
}
|
|
67
132
|
/**
|
|
@@ -69,7 +134,7 @@ var DownloadBuilder = class {
|
|
|
69
134
|
* If not specified, the file will be saved in the current working directory.
|
|
70
135
|
*
|
|
71
136
|
* @example
|
|
72
|
-
* download("https://www.
|
|
137
|
+
* download("https://www.example.com/video")
|
|
73
138
|
* .output("downloads")
|
|
74
139
|
* .run();
|
|
75
140
|
*
|
|
@@ -87,7 +152,7 @@ var DownloadBuilder = class {
|
|
|
87
152
|
* If no codec is specified, it will select the best available audio format regardless of codec.
|
|
88
153
|
*
|
|
89
154
|
* @example // Specifying an audio codec
|
|
90
|
-
* download("https://www.
|
|
155
|
+
* download("https://www.example.com/video")
|
|
91
156
|
* .audio("mp3")
|
|
92
157
|
* .run();
|
|
93
158
|
*
|
|
@@ -98,18 +163,52 @@ var DownloadBuilder = class {
|
|
|
98
163
|
this.audioFormat = codec ? `bestaudio[ext=${codec}]` : "bestaudio";
|
|
99
164
|
return this;
|
|
100
165
|
}
|
|
166
|
+
/**
|
|
167
|
+
* Set the desired video file extension for the download. You can specify common video extensions like "mp4", "mkv", "webm", etc.
|
|
168
|
+
* If specified, the method will select the best available video format that matches the specified extension.
|
|
169
|
+
* If not specified, it will select the best available video format regardless of extension.
|
|
170
|
+
*
|
|
171
|
+
* @example
|
|
172
|
+
* download("https://www.example.com/video")
|
|
173
|
+
* .format("mp4")
|
|
174
|
+
* .run();
|
|
175
|
+
*
|
|
176
|
+
* @param ext The desired video file extension for the download. You can specify common video extensions like "mp4", "mkv", "webm", etc. If specified, the method will select the best available video format that matches the specified extension. If not specified, it will select the best available video format regardless of extension.
|
|
177
|
+
* @returns The current instance of DownloadBuilder for method chaining.
|
|
178
|
+
*/
|
|
179
|
+
format(ext) {
|
|
180
|
+
this.videoExt = ext;
|
|
181
|
+
return this;
|
|
182
|
+
}
|
|
183
|
+
/**
|
|
184
|
+
* Set the desired frames per second (FPS) for the video download. You can specify a positive integer value for FPS, such as 30, 60, etc.
|
|
185
|
+
* The method will select the best available video format that has an FPS less than or equal to the specified value.
|
|
186
|
+
*
|
|
187
|
+
* @param fps The desired frames per second (FPS) for the video download.
|
|
188
|
+
* You can specify a positive integer value for FPS, such as 30, 60, etc.
|
|
189
|
+
* @returns The current instance of DownloadBuilder for method chaining.
|
|
190
|
+
*/
|
|
191
|
+
fps(fps) {
|
|
192
|
+
if (!Number.isInteger(fps) || fps <= 0) {
|
|
193
|
+
throw new RangeError(
|
|
194
|
+
`Invalid FPS value: ${fps}. FPS must be a positive integer.`
|
|
195
|
+
);
|
|
196
|
+
}
|
|
197
|
+
this.videoFps = fps;
|
|
198
|
+
return this;
|
|
199
|
+
}
|
|
101
200
|
/**
|
|
102
201
|
* Set the filename template for the downloaded file.
|
|
103
202
|
* The callback receives common output fields as named parameters which are
|
|
104
203
|
* automatically mapped to their values at download time.
|
|
105
204
|
*
|
|
106
205
|
* @example // Using a string template
|
|
107
|
-
* download("https://www.
|
|
206
|
+
* download("https://www.example.com/video")
|
|
108
207
|
* .output("downloads")
|
|
109
208
|
* .filename("%(title)s.%(ext)s")
|
|
110
209
|
*
|
|
111
210
|
* @example // Using a callback to specify a custom filename template
|
|
112
|
-
* download("https://www.
|
|
211
|
+
* download("https://www.example.com/video")
|
|
113
212
|
* .output("downloads")
|
|
114
213
|
* .filename(({ title, ext }) => `${title}.${ext}`)
|
|
115
214
|
* .run();
|
|
@@ -138,26 +237,20 @@ var DownloadBuilder = class {
|
|
|
138
237
|
* @throws Will throw an error if yt-dlp is not installed or not in PATH, or if any other error occurs during the execution of yt-dlp.
|
|
139
238
|
*
|
|
140
239
|
* @example
|
|
141
|
-
* download("https://www.
|
|
240
|
+
* download("https://www.example.com/video")
|
|
142
241
|
* .resolution("720p")
|
|
143
242
|
* .output("downloads/%(title)s.%(ext)s")
|
|
144
243
|
* .run();
|
|
145
244
|
*
|
|
146
245
|
*/
|
|
147
246
|
async run() {
|
|
148
|
-
const args =
|
|
149
|
-
if (this.videoFormat || this.audioFormat) {
|
|
150
|
-
const video = this.videoFormat ?? "bestvideo";
|
|
151
|
-
const audio = this.audioFormat ?? "bestaudio";
|
|
152
|
-
args.push("-f", `${video}+${audio}/best`);
|
|
153
|
-
}
|
|
154
|
-
if (this.outputDir || this.filenameTemplate) {
|
|
155
|
-
const dir = this.outputDir ?? "";
|
|
156
|
-
const file = this.filenameTemplate ?? "%(title)s.%(ext)s";
|
|
157
|
-
args.push("-o", dir ? `${dir}/${file}` : file);
|
|
158
|
-
}
|
|
247
|
+
const args = this.buildArgs();
|
|
159
248
|
await runYtDlp([...args, this.url]);
|
|
160
249
|
}
|
|
250
|
+
async capture() {
|
|
251
|
+
const args = this.buildArgs();
|
|
252
|
+
return runYtDlp([...args, this.url], true);
|
|
253
|
+
}
|
|
161
254
|
};
|
|
162
255
|
|
|
163
256
|
// src/index.ts
|
|
@@ -166,5 +259,7 @@ function download(url) {
|
|
|
166
259
|
}
|
|
167
260
|
// Annotate the CommonJS export names for ESM import in node:
|
|
168
261
|
0 && (module.exports = {
|
|
262
|
+
YtDlpError,
|
|
263
|
+
YtDlpNotFoundError,
|
|
169
264
|
download
|
|
170
265
|
});
|
package/dist/index.d.cts
CHANGED
|
@@ -1,4 +1,12 @@
|
|
|
1
|
-
|
|
1
|
+
interface RunResult {
|
|
2
|
+
stdout: string;
|
|
3
|
+
stderr: string;
|
|
4
|
+
}
|
|
5
|
+
|
|
6
|
+
type LiteralUnion<T extends string> = T | (string & {});
|
|
7
|
+
type Resolution = LiteralUnion<"144p" | "240p" | "360p" | "480p" | "720p" | "1080p" | "1440p" | "2160p">;
|
|
8
|
+
type AudioCodec = LiteralUnion<"opus" | "aac" | "m4a" | "mp3" | "flac" | "wav">;
|
|
9
|
+
type VideoExt = LiteralUnion<"mp4" | "mov" | "webm" | "flv" | "mkv" | "avi">;
|
|
2
10
|
type OutputFields = {
|
|
3
11
|
title: string;
|
|
4
12
|
ext: string;
|
|
@@ -20,7 +28,6 @@ type OutputFields = {
|
|
|
20
28
|
playlist_index: string;
|
|
21
29
|
autonumber: string;
|
|
22
30
|
};
|
|
23
|
-
type AudioCodec = "opus" | "aac" | "m4a" | "mp3" | "flac" | "wav" | (string & {});
|
|
24
31
|
|
|
25
32
|
declare class DownloadBuilder {
|
|
26
33
|
private readonly url;
|
|
@@ -29,7 +36,17 @@ declare class DownloadBuilder {
|
|
|
29
36
|
private audioFormat?;
|
|
30
37
|
private outputDir?;
|
|
31
38
|
private filenameTemplate?;
|
|
39
|
+
private videoExt?;
|
|
40
|
+
private videoFps?;
|
|
41
|
+
private videoHeight?;
|
|
32
42
|
constructor(url: string);
|
|
43
|
+
/**
|
|
44
|
+
* A private method to build the command-line arguments for yt-dlp based on the options set by the user.
|
|
45
|
+
* This method constructs the appropriate format filters and output template arguments according to the options set by the user.
|
|
46
|
+
*
|
|
47
|
+
* @returns The command-line arguments to be passed to yt-dlp based on the specified options.
|
|
48
|
+
*/
|
|
49
|
+
private buildArgs;
|
|
33
50
|
/**
|
|
34
51
|
* Set the desired resolution for the video download.
|
|
35
52
|
* The method will automatically select the best video format that is less than or equal to the specified resolution.
|
|
@@ -44,7 +61,7 @@ declare class DownloadBuilder {
|
|
|
44
61
|
* If not specified, the file will be saved in the current working directory.
|
|
45
62
|
*
|
|
46
63
|
* @example
|
|
47
|
-
* download("https://www.
|
|
64
|
+
* download("https://www.example.com/video")
|
|
48
65
|
* .output("downloads")
|
|
49
66
|
* .run();
|
|
50
67
|
*
|
|
@@ -59,7 +76,7 @@ declare class DownloadBuilder {
|
|
|
59
76
|
* If no codec is specified, it will select the best available audio format regardless of codec.
|
|
60
77
|
*
|
|
61
78
|
* @example // Specifying an audio codec
|
|
62
|
-
* download("https://www.
|
|
79
|
+
* download("https://www.example.com/video")
|
|
63
80
|
* .audio("mp3")
|
|
64
81
|
* .run();
|
|
65
82
|
*
|
|
@@ -67,18 +84,41 @@ declare class DownloadBuilder {
|
|
|
67
84
|
* @returns The current instance of DownloadBuilder for method chaining.
|
|
68
85
|
*/
|
|
69
86
|
audio(codec?: AudioCodec): this;
|
|
87
|
+
/**
|
|
88
|
+
* Set the desired video file extension for the download. You can specify common video extensions like "mp4", "mkv", "webm", etc.
|
|
89
|
+
* If specified, the method will select the best available video format that matches the specified extension.
|
|
90
|
+
* If not specified, it will select the best available video format regardless of extension.
|
|
91
|
+
*
|
|
92
|
+
* @example
|
|
93
|
+
* download("https://www.example.com/video")
|
|
94
|
+
* .format("mp4")
|
|
95
|
+
* .run();
|
|
96
|
+
*
|
|
97
|
+
* @param ext The desired video file extension for the download. You can specify common video extensions like "mp4", "mkv", "webm", etc. If specified, the method will select the best available video format that matches the specified extension. If not specified, it will select the best available video format regardless of extension.
|
|
98
|
+
* @returns The current instance of DownloadBuilder for method chaining.
|
|
99
|
+
*/
|
|
100
|
+
format(ext: VideoExt): this;
|
|
101
|
+
/**
|
|
102
|
+
* Set the desired frames per second (FPS) for the video download. You can specify a positive integer value for FPS, such as 30, 60, etc.
|
|
103
|
+
* The method will select the best available video format that has an FPS less than or equal to the specified value.
|
|
104
|
+
*
|
|
105
|
+
* @param fps The desired frames per second (FPS) for the video download.
|
|
106
|
+
* You can specify a positive integer value for FPS, such as 30, 60, etc.
|
|
107
|
+
* @returns The current instance of DownloadBuilder for method chaining.
|
|
108
|
+
*/
|
|
109
|
+
fps(fps: number): this;
|
|
70
110
|
/**
|
|
71
111
|
* Set the filename template for the downloaded file.
|
|
72
112
|
* The callback receives common output fields as named parameters which are
|
|
73
113
|
* automatically mapped to their values at download time.
|
|
74
114
|
*
|
|
75
115
|
* @example // Using a string template
|
|
76
|
-
* download("https://www.
|
|
116
|
+
* download("https://www.example.com/video")
|
|
77
117
|
* .output("downloads")
|
|
78
118
|
* .filename("%(title)s.%(ext)s")
|
|
79
119
|
*
|
|
80
120
|
* @example // Using a callback to specify a custom filename template
|
|
81
|
-
* download("https://www.
|
|
121
|
+
* download("https://www.example.com/video")
|
|
82
122
|
* .output("downloads")
|
|
83
123
|
* .filename(({ title, ext }) => `${title}.${ext}`)
|
|
84
124
|
* .run();
|
|
@@ -95,15 +135,25 @@ declare class DownloadBuilder {
|
|
|
95
135
|
* @throws Will throw an error if yt-dlp is not installed or not in PATH, or if any other error occurs during the execution of yt-dlp.
|
|
96
136
|
*
|
|
97
137
|
* @example
|
|
98
|
-
* download("https://www.
|
|
138
|
+
* download("https://www.example.com/video")
|
|
99
139
|
* .resolution("720p")
|
|
100
140
|
* .output("downloads/%(title)s.%(ext)s")
|
|
101
141
|
* .run();
|
|
102
142
|
*
|
|
103
143
|
*/
|
|
104
144
|
run(): Promise<void>;
|
|
145
|
+
capture(): Promise<RunResult>;
|
|
146
|
+
}
|
|
147
|
+
|
|
148
|
+
declare class YtDlpNotFoundError extends Error {
|
|
149
|
+
constructor();
|
|
150
|
+
}
|
|
151
|
+
declare class YtDlpError extends Error {
|
|
152
|
+
readonly exitCode: number;
|
|
153
|
+
readonly stderr: string;
|
|
154
|
+
constructor(message: string, exitCode: number, stderr: string);
|
|
105
155
|
}
|
|
106
156
|
|
|
107
157
|
declare function download(url: string): DownloadBuilder;
|
|
108
158
|
|
|
109
|
-
export { download };
|
|
159
|
+
export { type AudioCodec, type OutputFields, type Resolution, type RunResult, type VideoExt, YtDlpError, YtDlpNotFoundError, download };
|
package/dist/index.d.ts
CHANGED
|
@@ -1,4 +1,12 @@
|
|
|
1
|
-
|
|
1
|
+
interface RunResult {
|
|
2
|
+
stdout: string;
|
|
3
|
+
stderr: string;
|
|
4
|
+
}
|
|
5
|
+
|
|
6
|
+
type LiteralUnion<T extends string> = T | (string & {});
|
|
7
|
+
type Resolution = LiteralUnion<"144p" | "240p" | "360p" | "480p" | "720p" | "1080p" | "1440p" | "2160p">;
|
|
8
|
+
type AudioCodec = LiteralUnion<"opus" | "aac" | "m4a" | "mp3" | "flac" | "wav">;
|
|
9
|
+
type VideoExt = LiteralUnion<"mp4" | "mov" | "webm" | "flv" | "mkv" | "avi">;
|
|
2
10
|
type OutputFields = {
|
|
3
11
|
title: string;
|
|
4
12
|
ext: string;
|
|
@@ -20,7 +28,6 @@ type OutputFields = {
|
|
|
20
28
|
playlist_index: string;
|
|
21
29
|
autonumber: string;
|
|
22
30
|
};
|
|
23
|
-
type AudioCodec = "opus" | "aac" | "m4a" | "mp3" | "flac" | "wav" | (string & {});
|
|
24
31
|
|
|
25
32
|
declare class DownloadBuilder {
|
|
26
33
|
private readonly url;
|
|
@@ -29,7 +36,17 @@ declare class DownloadBuilder {
|
|
|
29
36
|
private audioFormat?;
|
|
30
37
|
private outputDir?;
|
|
31
38
|
private filenameTemplate?;
|
|
39
|
+
private videoExt?;
|
|
40
|
+
private videoFps?;
|
|
41
|
+
private videoHeight?;
|
|
32
42
|
constructor(url: string);
|
|
43
|
+
/**
|
|
44
|
+
* A private method to build the command-line arguments for yt-dlp based on the options set by the user.
|
|
45
|
+
* This method constructs the appropriate format filters and output template arguments according to the options set by the user.
|
|
46
|
+
*
|
|
47
|
+
* @returns The command-line arguments to be passed to yt-dlp based on the specified options.
|
|
48
|
+
*/
|
|
49
|
+
private buildArgs;
|
|
33
50
|
/**
|
|
34
51
|
* Set the desired resolution for the video download.
|
|
35
52
|
* The method will automatically select the best video format that is less than or equal to the specified resolution.
|
|
@@ -44,7 +61,7 @@ declare class DownloadBuilder {
|
|
|
44
61
|
* If not specified, the file will be saved in the current working directory.
|
|
45
62
|
*
|
|
46
63
|
* @example
|
|
47
|
-
* download("https://www.
|
|
64
|
+
* download("https://www.example.com/video")
|
|
48
65
|
* .output("downloads")
|
|
49
66
|
* .run();
|
|
50
67
|
*
|
|
@@ -59,7 +76,7 @@ declare class DownloadBuilder {
|
|
|
59
76
|
* If no codec is specified, it will select the best available audio format regardless of codec.
|
|
60
77
|
*
|
|
61
78
|
* @example // Specifying an audio codec
|
|
62
|
-
* download("https://www.
|
|
79
|
+
* download("https://www.example.com/video")
|
|
63
80
|
* .audio("mp3")
|
|
64
81
|
* .run();
|
|
65
82
|
*
|
|
@@ -67,18 +84,41 @@ declare class DownloadBuilder {
|
|
|
67
84
|
* @returns The current instance of DownloadBuilder for method chaining.
|
|
68
85
|
*/
|
|
69
86
|
audio(codec?: AudioCodec): this;
|
|
87
|
+
/**
|
|
88
|
+
* Set the desired video file extension for the download. You can specify common video extensions like "mp4", "mkv", "webm", etc.
|
|
89
|
+
* If specified, the method will select the best available video format that matches the specified extension.
|
|
90
|
+
* If not specified, it will select the best available video format regardless of extension.
|
|
91
|
+
*
|
|
92
|
+
* @example
|
|
93
|
+
* download("https://www.example.com/video")
|
|
94
|
+
* .format("mp4")
|
|
95
|
+
* .run();
|
|
96
|
+
*
|
|
97
|
+
* @param ext The desired video file extension for the download. You can specify common video extensions like "mp4", "mkv", "webm", etc. If specified, the method will select the best available video format that matches the specified extension. If not specified, it will select the best available video format regardless of extension.
|
|
98
|
+
* @returns The current instance of DownloadBuilder for method chaining.
|
|
99
|
+
*/
|
|
100
|
+
format(ext: VideoExt): this;
|
|
101
|
+
/**
|
|
102
|
+
* Set the desired frames per second (FPS) for the video download. You can specify a positive integer value for FPS, such as 30, 60, etc.
|
|
103
|
+
* The method will select the best available video format that has an FPS less than or equal to the specified value.
|
|
104
|
+
*
|
|
105
|
+
* @param fps The desired frames per second (FPS) for the video download.
|
|
106
|
+
* You can specify a positive integer value for FPS, such as 30, 60, etc.
|
|
107
|
+
* @returns The current instance of DownloadBuilder for method chaining.
|
|
108
|
+
*/
|
|
109
|
+
fps(fps: number): this;
|
|
70
110
|
/**
|
|
71
111
|
* Set the filename template for the downloaded file.
|
|
72
112
|
* The callback receives common output fields as named parameters which are
|
|
73
113
|
* automatically mapped to their values at download time.
|
|
74
114
|
*
|
|
75
115
|
* @example // Using a string template
|
|
76
|
-
* download("https://www.
|
|
116
|
+
* download("https://www.example.com/video")
|
|
77
117
|
* .output("downloads")
|
|
78
118
|
* .filename("%(title)s.%(ext)s")
|
|
79
119
|
*
|
|
80
120
|
* @example // Using a callback to specify a custom filename template
|
|
81
|
-
* download("https://www.
|
|
121
|
+
* download("https://www.example.com/video")
|
|
82
122
|
* .output("downloads")
|
|
83
123
|
* .filename(({ title, ext }) => `${title}.${ext}`)
|
|
84
124
|
* .run();
|
|
@@ -95,15 +135,25 @@ declare class DownloadBuilder {
|
|
|
95
135
|
* @throws Will throw an error if yt-dlp is not installed or not in PATH, or if any other error occurs during the execution of yt-dlp.
|
|
96
136
|
*
|
|
97
137
|
* @example
|
|
98
|
-
* download("https://www.
|
|
138
|
+
* download("https://www.example.com/video")
|
|
99
139
|
* .resolution("720p")
|
|
100
140
|
* .output("downloads/%(title)s.%(ext)s")
|
|
101
141
|
* .run();
|
|
102
142
|
*
|
|
103
143
|
*/
|
|
104
144
|
run(): Promise<void>;
|
|
145
|
+
capture(): Promise<RunResult>;
|
|
146
|
+
}
|
|
147
|
+
|
|
148
|
+
declare class YtDlpNotFoundError extends Error {
|
|
149
|
+
constructor();
|
|
150
|
+
}
|
|
151
|
+
declare class YtDlpError extends Error {
|
|
152
|
+
readonly exitCode: number;
|
|
153
|
+
readonly stderr: string;
|
|
154
|
+
constructor(message: string, exitCode: number, stderr: string);
|
|
105
155
|
}
|
|
106
156
|
|
|
107
157
|
declare function download(url: string): DownloadBuilder;
|
|
108
158
|
|
|
109
|
-
export { download };
|
|
159
|
+
export { type AudioCodec, type OutputFields, type Resolution, type RunResult, type VideoExt, YtDlpError, YtDlpNotFoundError, download };
|
package/dist/index.js
CHANGED
|
@@ -1,14 +1,47 @@
|
|
|
1
1
|
// src/internal/runner.ts
|
|
2
2
|
import { execa, ExecaError } from "execa";
|
|
3
|
-
|
|
4
|
-
|
|
5
|
-
|
|
6
|
-
|
|
3
|
+
|
|
4
|
+
// src/errors.ts
|
|
5
|
+
var YtDlpNotFoundError = class extends Error {
|
|
6
|
+
constructor() {
|
|
7
|
+
super(
|
|
8
|
+
"yt-dlp is not installed or not in PATH. Refer to https://github.com/yt-dlp/yt-dlp#installation"
|
|
9
|
+
);
|
|
10
|
+
this.name = "YtDlpNotFoundError";
|
|
11
|
+
}
|
|
12
|
+
};
|
|
13
|
+
var YtDlpError = class extends Error {
|
|
14
|
+
constructor(message, exitCode, stderr) {
|
|
15
|
+
super(message);
|
|
16
|
+
this.exitCode = exitCode;
|
|
17
|
+
this.stderr = stderr;
|
|
18
|
+
this.name = "YtDlpError";
|
|
19
|
+
}
|
|
20
|
+
exitCode;
|
|
21
|
+
stderr;
|
|
22
|
+
};
|
|
23
|
+
|
|
24
|
+
// src/internal/runner.ts
|
|
25
|
+
async function runYtDlp(args, pipe = false) {
|
|
26
|
+
const result = await execa(
|
|
27
|
+
"yt-dlp",
|
|
28
|
+
["--js-runtimes", "node", ...args],
|
|
29
|
+
{ stdio: pipe ? "pipe" : "inherit" }
|
|
30
|
+
).catch(handleError);
|
|
31
|
+
return {
|
|
32
|
+
stdout: result.stdout ?? "",
|
|
33
|
+
stderr: result.stderr ?? ""
|
|
34
|
+
};
|
|
7
35
|
}
|
|
8
|
-
function
|
|
9
|
-
if (error instanceof ExecaError
|
|
10
|
-
|
|
11
|
-
|
|
36
|
+
function handleError(error) {
|
|
37
|
+
if (error instanceof ExecaError) {
|
|
38
|
+
if (error.code === "ENOENT") {
|
|
39
|
+
throw new YtDlpNotFoundError();
|
|
40
|
+
}
|
|
41
|
+
throw new YtDlpError(
|
|
42
|
+
`yt-dlp exited with code ${error.exitCode}`,
|
|
43
|
+
error.exitCode ?? 1,
|
|
44
|
+
error.stderr ?? ""
|
|
12
45
|
);
|
|
13
46
|
}
|
|
14
47
|
throw error;
|
|
@@ -18,6 +51,11 @@ function handleEnoent(error) {
|
|
|
18
51
|
var DownloadBuilder = class {
|
|
19
52
|
constructor(url) {
|
|
20
53
|
this.url = url;
|
|
54
|
+
try {
|
|
55
|
+
new URL(url);
|
|
56
|
+
} catch {
|
|
57
|
+
throw new TypeError(`Invalid URL: ${url}`);
|
|
58
|
+
}
|
|
21
59
|
}
|
|
22
60
|
url;
|
|
23
61
|
args = [];
|
|
@@ -25,6 +63,32 @@ var DownloadBuilder = class {
|
|
|
25
63
|
audioFormat;
|
|
26
64
|
outputDir;
|
|
27
65
|
filenameTemplate;
|
|
66
|
+
videoExt;
|
|
67
|
+
videoFps;
|
|
68
|
+
videoHeight;
|
|
69
|
+
/**
|
|
70
|
+
* A private method to build the command-line arguments for yt-dlp based on the options set by the user.
|
|
71
|
+
* This method constructs the appropriate format filters and output template arguments according to the options set by the user.
|
|
72
|
+
*
|
|
73
|
+
* @returns The command-line arguments to be passed to yt-dlp based on the specified options.
|
|
74
|
+
*/
|
|
75
|
+
buildArgs() {
|
|
76
|
+
const args = [...this.args];
|
|
77
|
+
if (this.videoHeight || this.videoFps || this.videoExt || this.audioFormat) {
|
|
78
|
+
const heightFilter = this.videoHeight ? `[height<=${this.videoHeight}]` : "";
|
|
79
|
+
const fpsFilter = this.videoFps ? `[fps<=${this.videoFps}]` : "";
|
|
80
|
+
const extFilter = this.videoExt ? `[ext=${this.videoExt}]` : "";
|
|
81
|
+
const video = `bestvideo${heightFilter}${fpsFilter}${extFilter}`;
|
|
82
|
+
const audio = this.audioFormat ?? "bestaudio";
|
|
83
|
+
args.push("-f", `${video}+${audio}/best`);
|
|
84
|
+
}
|
|
85
|
+
if (this.outputDir || this.filenameTemplate) {
|
|
86
|
+
const dir = this.outputDir ?? "";
|
|
87
|
+
const file = this.filenameTemplate ?? "%(title)s.%(ext)s";
|
|
88
|
+
args.push("-o", dir ? `${dir}/${file}` : file);
|
|
89
|
+
}
|
|
90
|
+
return args;
|
|
91
|
+
}
|
|
28
92
|
/**
|
|
29
93
|
* Set the desired resolution for the video download.
|
|
30
94
|
* The method will automatically select the best video format that is less than or equal to the specified resolution.
|
|
@@ -34,8 +98,7 @@ var DownloadBuilder = class {
|
|
|
34
98
|
* @returns The current instance of DownloadBuilder for method chaining.
|
|
35
99
|
*/
|
|
36
100
|
resolution(res) {
|
|
37
|
-
|
|
38
|
-
this.videoFormat = `bestvideo[height<=${height}]`;
|
|
101
|
+
this.videoHeight = res.replace("p", "");
|
|
39
102
|
return this;
|
|
40
103
|
}
|
|
41
104
|
/**
|
|
@@ -43,7 +106,7 @@ var DownloadBuilder = class {
|
|
|
43
106
|
* If not specified, the file will be saved in the current working directory.
|
|
44
107
|
*
|
|
45
108
|
* @example
|
|
46
|
-
* download("https://www.
|
|
109
|
+
* download("https://www.example.com/video")
|
|
47
110
|
* .output("downloads")
|
|
48
111
|
* .run();
|
|
49
112
|
*
|
|
@@ -61,7 +124,7 @@ var DownloadBuilder = class {
|
|
|
61
124
|
* If no codec is specified, it will select the best available audio format regardless of codec.
|
|
62
125
|
*
|
|
63
126
|
* @example // Specifying an audio codec
|
|
64
|
-
* download("https://www.
|
|
127
|
+
* download("https://www.example.com/video")
|
|
65
128
|
* .audio("mp3")
|
|
66
129
|
* .run();
|
|
67
130
|
*
|
|
@@ -72,18 +135,52 @@ var DownloadBuilder = class {
|
|
|
72
135
|
this.audioFormat = codec ? `bestaudio[ext=${codec}]` : "bestaudio";
|
|
73
136
|
return this;
|
|
74
137
|
}
|
|
138
|
+
/**
|
|
139
|
+
* Set the desired video file extension for the download. You can specify common video extensions like "mp4", "mkv", "webm", etc.
|
|
140
|
+
* If specified, the method will select the best available video format that matches the specified extension.
|
|
141
|
+
* If not specified, it will select the best available video format regardless of extension.
|
|
142
|
+
*
|
|
143
|
+
* @example
|
|
144
|
+
* download("https://www.example.com/video")
|
|
145
|
+
* .format("mp4")
|
|
146
|
+
* .run();
|
|
147
|
+
*
|
|
148
|
+
* @param ext The desired video file extension for the download. You can specify common video extensions like "mp4", "mkv", "webm", etc. If specified, the method will select the best available video format that matches the specified extension. If not specified, it will select the best available video format regardless of extension.
|
|
149
|
+
* @returns The current instance of DownloadBuilder for method chaining.
|
|
150
|
+
*/
|
|
151
|
+
format(ext) {
|
|
152
|
+
this.videoExt = ext;
|
|
153
|
+
return this;
|
|
154
|
+
}
|
|
155
|
+
/**
|
|
156
|
+
* Set the desired frames per second (FPS) for the video download. You can specify a positive integer value for FPS, such as 30, 60, etc.
|
|
157
|
+
* The method will select the best available video format that has an FPS less than or equal to the specified value.
|
|
158
|
+
*
|
|
159
|
+
* @param fps The desired frames per second (FPS) for the video download.
|
|
160
|
+
* You can specify a positive integer value for FPS, such as 30, 60, etc.
|
|
161
|
+
* @returns The current instance of DownloadBuilder for method chaining.
|
|
162
|
+
*/
|
|
163
|
+
fps(fps) {
|
|
164
|
+
if (!Number.isInteger(fps) || fps <= 0) {
|
|
165
|
+
throw new RangeError(
|
|
166
|
+
`Invalid FPS value: ${fps}. FPS must be a positive integer.`
|
|
167
|
+
);
|
|
168
|
+
}
|
|
169
|
+
this.videoFps = fps;
|
|
170
|
+
return this;
|
|
171
|
+
}
|
|
75
172
|
/**
|
|
76
173
|
* Set the filename template for the downloaded file.
|
|
77
174
|
* The callback receives common output fields as named parameters which are
|
|
78
175
|
* automatically mapped to their values at download time.
|
|
79
176
|
*
|
|
80
177
|
* @example // Using a string template
|
|
81
|
-
* download("https://www.
|
|
178
|
+
* download("https://www.example.com/video")
|
|
82
179
|
* .output("downloads")
|
|
83
180
|
* .filename("%(title)s.%(ext)s")
|
|
84
181
|
*
|
|
85
182
|
* @example // Using a callback to specify a custom filename template
|
|
86
|
-
* download("https://www.
|
|
183
|
+
* download("https://www.example.com/video")
|
|
87
184
|
* .output("downloads")
|
|
88
185
|
* .filename(({ title, ext }) => `${title}.${ext}`)
|
|
89
186
|
* .run();
|
|
@@ -112,26 +209,20 @@ var DownloadBuilder = class {
|
|
|
112
209
|
* @throws Will throw an error if yt-dlp is not installed or not in PATH, or if any other error occurs during the execution of yt-dlp.
|
|
113
210
|
*
|
|
114
211
|
* @example
|
|
115
|
-
* download("https://www.
|
|
212
|
+
* download("https://www.example.com/video")
|
|
116
213
|
* .resolution("720p")
|
|
117
214
|
* .output("downloads/%(title)s.%(ext)s")
|
|
118
215
|
* .run();
|
|
119
216
|
*
|
|
120
217
|
*/
|
|
121
218
|
async run() {
|
|
122
|
-
const args =
|
|
123
|
-
if (this.videoFormat || this.audioFormat) {
|
|
124
|
-
const video = this.videoFormat ?? "bestvideo";
|
|
125
|
-
const audio = this.audioFormat ?? "bestaudio";
|
|
126
|
-
args.push("-f", `${video}+${audio}/best`);
|
|
127
|
-
}
|
|
128
|
-
if (this.outputDir || this.filenameTemplate) {
|
|
129
|
-
const dir = this.outputDir ?? "";
|
|
130
|
-
const file = this.filenameTemplate ?? "%(title)s.%(ext)s";
|
|
131
|
-
args.push("-o", dir ? `${dir}/${file}` : file);
|
|
132
|
-
}
|
|
219
|
+
const args = this.buildArgs();
|
|
133
220
|
await runYtDlp([...args, this.url]);
|
|
134
221
|
}
|
|
222
|
+
async capture() {
|
|
223
|
+
const args = this.buildArgs();
|
|
224
|
+
return runYtDlp([...args, this.url], true);
|
|
225
|
+
}
|
|
135
226
|
};
|
|
136
227
|
|
|
137
228
|
// src/index.ts
|
|
@@ -139,5 +230,7 @@ function download(url) {
|
|
|
139
230
|
return new DownloadBuilder(url);
|
|
140
231
|
}
|
|
141
232
|
export {
|
|
233
|
+
YtDlpError,
|
|
234
|
+
YtDlpNotFoundError,
|
|
142
235
|
download
|
|
143
236
|
};
|
package/package.json
CHANGED
|
@@ -1,51 +1,53 @@
|
|
|
1
|
-
{
|
|
2
|
-
"name": "@blagoja/ts-dlp",
|
|
3
|
-
"version": "0.1.
|
|
4
|
-
"description": "A human readable TypeScript wrapper for yt-dlp",
|
|
5
|
-
"main": "dist/index.js",
|
|
6
|
-
"types": "dist/index.d.ts",
|
|
7
|
-
"exports": {
|
|
8
|
-
".": {
|
|
9
|
-
"import": "./dist/index.js",
|
|
10
|
-
"types": "./dist/index.d.ts",
|
|
11
|
-
"require": "./dist/index.cjs"
|
|
12
|
-
}
|
|
13
|
-
},
|
|
14
|
-
"scripts": {
|
|
15
|
-
"build": "tsup src/index.ts --format esm,cjs --dts",
|
|
16
|
-
"dev": "tsx src/index.ts",
|
|
17
|
-
"lint": "eslint src",
|
|
18
|
-
"prepublishOnly": "npm run build"
|
|
19
|
-
|
|
20
|
-
|
|
21
|
-
|
|
22
|
-
"
|
|
23
|
-
"
|
|
24
|
-
"
|
|
25
|
-
|
|
26
|
-
|
|
27
|
-
|
|
28
|
-
"
|
|
29
|
-
|
|
30
|
-
|
|
31
|
-
"
|
|
32
|
-
|
|
33
|
-
|
|
34
|
-
|
|
35
|
-
|
|
36
|
-
|
|
37
|
-
|
|
38
|
-
"
|
|
39
|
-
"
|
|
40
|
-
|
|
41
|
-
"
|
|
42
|
-
"
|
|
43
|
-
"
|
|
44
|
-
"
|
|
45
|
-
"
|
|
46
|
-
"typescript
|
|
47
|
-
|
|
48
|
-
|
|
49
|
-
|
|
50
|
-
|
|
51
|
-
|
|
1
|
+
{
|
|
2
|
+
"name": "@blagoja/ts-dlp",
|
|
3
|
+
"version": "0.1.4",
|
|
4
|
+
"description": "A human readable TypeScript wrapper for yt-dlp",
|
|
5
|
+
"main": "dist/index.js",
|
|
6
|
+
"types": "dist/index.d.ts",
|
|
7
|
+
"exports": {
|
|
8
|
+
".": {
|
|
9
|
+
"import": "./dist/index.js",
|
|
10
|
+
"types": "./dist/index.d.ts",
|
|
11
|
+
"require": "./dist/index.cjs"
|
|
12
|
+
}
|
|
13
|
+
},
|
|
14
|
+
"scripts": {
|
|
15
|
+
"build": "tsup src/index.ts --format esm,cjs --dts",
|
|
16
|
+
"dev": "tsx src/index.ts",
|
|
17
|
+
"lint": "eslint src",
|
|
18
|
+
"prepublishOnly": "npm run build",
|
|
19
|
+
"test": "vitest run"
|
|
20
|
+
},
|
|
21
|
+
"keywords": [
|
|
22
|
+
"yt-dlp",
|
|
23
|
+
"youtube-dl",
|
|
24
|
+
"downloader",
|
|
25
|
+
"wrapper"
|
|
26
|
+
],
|
|
27
|
+
"repository": {
|
|
28
|
+
"type": "git",
|
|
29
|
+
"url": "https://github.com/blagojablazhevski/ts-dlp.git"
|
|
30
|
+
},
|
|
31
|
+
"author": "Blagoja Blazhevski",
|
|
32
|
+
"files": [
|
|
33
|
+
"dist"
|
|
34
|
+
],
|
|
35
|
+
"engines": {
|
|
36
|
+
"node": ">=18"
|
|
37
|
+
},
|
|
38
|
+
"license": "MIT",
|
|
39
|
+
"type": "module",
|
|
40
|
+
"devDependencies": {
|
|
41
|
+
"@types/node": "^25.9.1",
|
|
42
|
+
"eslint": "^10.4.0",
|
|
43
|
+
"jiti": "^2.7.0",
|
|
44
|
+
"tsup": "^8.5.1",
|
|
45
|
+
"tsx": "^4.22.3",
|
|
46
|
+
"typescript": "^6.0.3",
|
|
47
|
+
"typescript-eslint": "^8.60.0",
|
|
48
|
+
"vitest": "^4.1.7"
|
|
49
|
+
},
|
|
50
|
+
"dependencies": {
|
|
51
|
+
"execa": "^9.6.1"
|
|
52
|
+
}
|
|
53
|
+
}
|
package/dist/download.d.ts
DELETED
|
@@ -1,43 +0,0 @@
|
|
|
1
|
-
export declare class DownloadBuilder {
|
|
2
|
-
private readonly url;
|
|
3
|
-
private readonly args;
|
|
4
|
-
constructor(url: string);
|
|
5
|
-
/**
|
|
6
|
-
* Set the desired resolution for the video to be downloaded.
|
|
7
|
-
* This will instruct yt-dlp to download the best video and audio streams that are less than or equal to the specified height.
|
|
8
|
-
* For example, if you specify "720p", yt-dlp will download the best video and audio streams that are 720 pixels in height or less.
|
|
9
|
-
* If you want to download the best available quality regardless of resolution, you can omit this option or set it to a very high value like "9999p".
|
|
10
|
-
*
|
|
11
|
-
* @example
|
|
12
|
-
* download("https://www.youtube.com/watch?v=dQw4w9WgXcQ")
|
|
13
|
-
* .resolution("720p")
|
|
14
|
-
* .output("downloads/%(title)s.%(ext)s")
|
|
15
|
-
*
|
|
16
|
-
* @param res The desired resolution for the video. For example: "720p", "1080p", etc. This will tell yt-dlp to download the best video and audio streams that are less than or equal to the specified height. If you want to download the best available quality, you can omit this option or set it to a very high value like "9999p".
|
|
17
|
-
* @returns The current instance of DownloadBuilder for method chaining.
|
|
18
|
-
*/
|
|
19
|
-
resolution(res: string): this;
|
|
20
|
-
/**
|
|
21
|
-
* Set the output directory and filename template for the downloaded file.
|
|
22
|
-
*
|
|
23
|
-
* @param template The output template for the downloaded file. You can use placeholders like %(title)s, %(ext)s, etc. For example: "downloads/%(title)s.%(ext)s"
|
|
24
|
-
* @returns The current instance of DownloadBuilder for method chaining.
|
|
25
|
-
*/
|
|
26
|
-
output(template: string): this;
|
|
27
|
-
/**
|
|
28
|
-
* Execute the download with the specified options.
|
|
29
|
-
* This will run yt-dlp with the accumulated arguments and the provided URL.
|
|
30
|
-
* The output will be displayed in the console.
|
|
31
|
-
*
|
|
32
|
-
* @throws Will throw an error if yt-dlp is not installed or not in PATH, or if any other error occurs during the execution of yt-dlp.
|
|
33
|
-
*
|
|
34
|
-
* @example
|
|
35
|
-
* download("https://www.youtube.com/watch?v=dQw4w9WgXcQ")
|
|
36
|
-
* .resolution("720p")
|
|
37
|
-
* .output("downloads/%(title)s.%(ext)s")
|
|
38
|
-
* .run();
|
|
39
|
-
*
|
|
40
|
-
*/
|
|
41
|
-
run(): Promise<void>;
|
|
42
|
-
}
|
|
43
|
-
//# sourceMappingURL=download.d.ts.map
|
package/dist/download.d.ts.map
DELETED
|
@@ -1 +0,0 @@
|
|
|
1
|
-
{"version":3,"file":"download.d.ts","sourceRoot":"","sources":["../src/download.ts"],"names":[],"mappings":"AAEA,qBAAa,eAAe;IAGf,OAAO,CAAC,QAAQ,CAAC,GAAG;IAFhC,OAAO,CAAC,QAAQ,CAAC,IAAI,CAAgB;gBAER,GAAG,EAAE,MAAM;IAExC;;;;;;;;;;;;;OAaG;IACH,UAAU,CAAC,GAAG,EAAE,MAAM,GAAG,IAAI;IAS7B;;;;;OAKG;IACH,MAAM,CAAC,QAAQ,EAAE,MAAM,GAAG,IAAI;IAK9B;;;;;;;;;;;;;OAaG;IACG,GAAG,IAAI,OAAO,CAAC,IAAI,CAAC;CAG1B"}
|
package/dist/download.js
DELETED
|
@@ -1,55 +0,0 @@
|
|
|
1
|
-
import { runYtDlp } from "./internal/runner.js";
|
|
2
|
-
export class DownloadBuilder {
|
|
3
|
-
url;
|
|
4
|
-
args = [];
|
|
5
|
-
constructor(url) {
|
|
6
|
-
this.url = url;
|
|
7
|
-
}
|
|
8
|
-
/**
|
|
9
|
-
* Set the desired resolution for the video to be downloaded.
|
|
10
|
-
* This will instruct yt-dlp to download the best video and audio streams that are less than or equal to the specified height.
|
|
11
|
-
* For example, if you specify "720p", yt-dlp will download the best video and audio streams that are 720 pixels in height or less.
|
|
12
|
-
* If you want to download the best available quality regardless of resolution, you can omit this option or set it to a very high value like "9999p".
|
|
13
|
-
*
|
|
14
|
-
* @example
|
|
15
|
-
* download("https://www.youtube.com/watch?v=dQw4w9WgXcQ")
|
|
16
|
-
* .resolution("720p")
|
|
17
|
-
* .output("downloads/%(title)s.%(ext)s")
|
|
18
|
-
*
|
|
19
|
-
* @param res The desired resolution for the video. For example: "720p", "1080p", etc. This will tell yt-dlp to download the best video and audio streams that are less than or equal to the specified height. If you want to download the best available quality, you can omit this option or set it to a very high value like "9999p".
|
|
20
|
-
* @returns The current instance of DownloadBuilder for method chaining.
|
|
21
|
-
*/
|
|
22
|
-
resolution(res) {
|
|
23
|
-
const height = res.replace("p", "");
|
|
24
|
-
this.args.push("-f", `bestvideo[height<=${height}]+bestaudio/best[height<=${height}]`);
|
|
25
|
-
return this;
|
|
26
|
-
}
|
|
27
|
-
/**
|
|
28
|
-
* Set the output directory and filename template for the downloaded file.
|
|
29
|
-
*
|
|
30
|
-
* @param template The output template for the downloaded file. You can use placeholders like %(title)s, %(ext)s, etc. For example: "downloads/%(title)s.%(ext)s"
|
|
31
|
-
* @returns The current instance of DownloadBuilder for method chaining.
|
|
32
|
-
*/
|
|
33
|
-
output(template) {
|
|
34
|
-
this.args.push("-o", template);
|
|
35
|
-
return this;
|
|
36
|
-
}
|
|
37
|
-
/**
|
|
38
|
-
* Execute the download with the specified options.
|
|
39
|
-
* This will run yt-dlp with the accumulated arguments and the provided URL.
|
|
40
|
-
* The output will be displayed in the console.
|
|
41
|
-
*
|
|
42
|
-
* @throws Will throw an error if yt-dlp is not installed or not in PATH, or if any other error occurs during the execution of yt-dlp.
|
|
43
|
-
*
|
|
44
|
-
* @example
|
|
45
|
-
* download("https://www.youtube.com/watch?v=dQw4w9WgXcQ")
|
|
46
|
-
* .resolution("720p")
|
|
47
|
-
* .output("downloads/%(title)s.%(ext)s")
|
|
48
|
-
* .run();
|
|
49
|
-
*
|
|
50
|
-
*/
|
|
51
|
-
async run() {
|
|
52
|
-
await runYtDlp([...this.args, this.url]);
|
|
53
|
-
}
|
|
54
|
-
}
|
|
55
|
-
//# sourceMappingURL=download.js.map
|
package/dist/download.js.map
DELETED
|
@@ -1 +0,0 @@
|
|
|
1
|
-
{"version":3,"file":"download.js","sourceRoot":"","sources":["../src/download.ts"],"names":[],"mappings":"AAAA,OAAO,EAAC,QAAQ,EAAC,MAAM,sBAAsB,CAAC;AAE9C,MAAM,OAAO,eAAe;IAGE;IAFZ,IAAI,GAAa,EAAE,CAAC;IAErC,YAA6B,GAAW;QAAX,QAAG,GAAH,GAAG,CAAQ;IAAG,CAAC;IAE5C;;;;;;;;;;;;;OAaG;IACH,UAAU,CAAC,GAAW;QACrB,MAAM,MAAM,GAAG,GAAG,CAAC,OAAO,CAAC,GAAG,EAAE,EAAE,CAAC,CAAC;QACpC,IAAI,CAAC,IAAI,CAAC,IAAI,CACb,IAAI,EACJ,qBAAqB,MAAM,4BAA4B,MAAM,GAAG,CAChE,CAAC;QACF,OAAO,IAAI,CAAC;IACb,CAAC;IAED;;;;;OAKG;IACH,MAAM,CAAC,QAAgB;QACtB,IAAI,CAAC,IAAI,CAAC,IAAI,CAAC,IAAI,EAAE,QAAQ,CAAC,CAAC;QAC/B,OAAO,IAAI,CAAC;IACb,CAAC;IAED;;;;;;;;;;;;;OAaG;IACH,KAAK,CAAC,GAAG;QACR,MAAM,QAAQ,CAAC,CAAC,GAAG,IAAI,CAAC,IAAI,EAAE,IAAI,CAAC,GAAG,CAAC,CAAC,CAAC;IAC1C,CAAC;CACD"}
|
package/dist/index.d.ts.map
DELETED
|
@@ -1 +0,0 @@
|
|
|
1
|
-
{"version":3,"file":"index.d.ts","sourceRoot":"","sources":["../src/index.ts"],"names":[],"mappings":"AAAA,OAAO,EAAC,eAAe,EAAC,MAAM,eAAe,CAAC;AAE9C,wBAAgB,QAAQ,CAAC,GAAG,EAAE,MAAM,GAAG,eAAe,CAErD"}
|
package/dist/index.js.map
DELETED
|
@@ -1 +0,0 @@
|
|
|
1
|
-
{"version":3,"file":"index.js","sourceRoot":"","sources":["../src/index.ts"],"names":[],"mappings":"AAAA,OAAO,EAAC,eAAe,EAAC,MAAM,eAAe,CAAC;AAE9C,MAAM,UAAU,QAAQ,CAAC,GAAW;IACnC,OAAO,IAAI,eAAe,CAAC,GAAG,CAAC,CAAC;AACjC,CAAC"}
|
|
@@ -1 +0,0 @@
|
|
|
1
|
-
{"version":3,"file":"runner.d.ts","sourceRoot":"","sources":["../../src/internal/runner.ts"],"names":[],"mappings":"AAEA,wBAAsB,QAAQ,CAAC,IAAI,EAAE,MAAM,EAAE,GAAG,OAAO,CAAC,IAAI,CAAC,CAI5D;AAED,wBAAgB,YAAY,CAAC,KAAK,EAAE,OAAO,GAAG,KAAK,CAOlD"}
|
package/dist/internal/runner.js
DELETED
|
@@ -1,13 +0,0 @@
|
|
|
1
|
-
import { execa, ExecaError } from "execa";
|
|
2
|
-
export async function runYtDlp(args) {
|
|
3
|
-
await execa("yt-dlp", ["--js-runtimes", "node", ...args], {
|
|
4
|
-
stdio: "inherit",
|
|
5
|
-
}).catch(handleEnoent);
|
|
6
|
-
}
|
|
7
|
-
export function handleEnoent(error) {
|
|
8
|
-
if (error instanceof ExecaError && error.code === "ENOENT") {
|
|
9
|
-
throw new Error("yt-dlp is not installed or not in PATH. Please refer to https://github.com/yt-dlp/yt-dlp#installation");
|
|
10
|
-
}
|
|
11
|
-
throw error;
|
|
12
|
-
}
|
|
13
|
-
//# sourceMappingURL=runner.js.map
|
|
@@ -1 +0,0 @@
|
|
|
1
|
-
{"version":3,"file":"runner.js","sourceRoot":"","sources":["../../src/internal/runner.ts"],"names":[],"mappings":"AAAA,OAAO,EAAC,KAAK,EAAE,UAAU,EAAC,MAAM,OAAO,CAAC;AAExC,MAAM,CAAC,KAAK,UAAU,QAAQ,CAAC,IAAc;IAC5C,MAAM,KAAK,CAAC,QAAQ,EAAE,CAAC,eAAe,EAAE,MAAM,EAAE,GAAG,IAAI,CAAC,EAAE;QACzD,KAAK,EAAE,SAAS;KAChB,CAAC,CAAC,KAAK,CAAC,YAAY,CAAC,CAAC;AACxB,CAAC;AAED,MAAM,UAAU,YAAY,CAAC,KAAc;IAC1C,IAAI,KAAK,YAAY,UAAU,IAAI,KAAK,CAAC,IAAI,KAAK,QAAQ,EAAE,CAAC;QAC5D,MAAM,IAAI,KAAK,CACd,uGAAuG,CACvG,CAAC;IACH,CAAC;IACD,MAAM,KAAK,CAAC;AACb,CAAC"}
|
package/dist/types.d.ts
DELETED
package/dist/types.d.ts.map
DELETED
|
@@ -1 +0,0 @@
|
|
|
1
|
-
{"version":3,"file":"types.d.ts","sourceRoot":"","sources":["../src/types.ts"],"names":[],"mappings":"AAAA,OAAO,EAAE,CAAC"}
|
package/dist/types.js
DELETED
package/dist/types.js.map
DELETED
|
@@ -1 +0,0 @@
|
|
|
1
|
-
{"version":3,"file":"types.js","sourceRoot":"","sources":["../src/types.ts"],"names":[],"mappings":"AAAA,OAAO,EAAE,CAAC"}
|
package/dist/version.d.ts
DELETED
package/dist/version.d.ts.map
DELETED
|
@@ -1 +0,0 @@
|
|
|
1
|
-
{"version":3,"file":"version.d.ts","sourceRoot":"","sources":["../src/version.ts"],"names":[],"mappings":"AAAA,OAAO,EAAE,CAAC"}
|
package/dist/version.js
DELETED
package/dist/version.js.map
DELETED
|
@@ -1 +0,0 @@
|
|
|
1
|
-
{"version":3,"file":"version.js","sourceRoot":"","sources":["../src/version.ts"],"names":[],"mappings":"AAAA,OAAO,EAAE,CAAC"}
|