@irithell-js/yt-play 0.2.8 → 0.3.3
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 +206 -1
- package/dist/index.cjs +5 -4
- package/dist/index.cjs.map +1 -1
- package/dist/index.d.cts +142 -31
- package/dist/index.d.ts +142 -31
- package/dist/index.mjs +5 -4
- package/dist/index.mjs.map +1 -1
- package/package.json +1 -1
- package/scripts/check-ytdlp-update.mjs +10 -12
- package/scripts/setup-binaries.mjs +23 -18
package/dist/index.d.cts
CHANGED
|
@@ -1,3 +1,5 @@
|
|
|
1
|
+
import { Readable } from 'node:stream';
|
|
2
|
+
|
|
1
3
|
type MediaType = "audio" | "video";
|
|
2
4
|
interface PlayMetadata {
|
|
3
5
|
title: string;
|
|
@@ -16,6 +18,11 @@ interface DownloadInfo {
|
|
|
16
18
|
quality: string;
|
|
17
19
|
downloadUrl: string;
|
|
18
20
|
}
|
|
21
|
+
interface StreamInfo {
|
|
22
|
+
stream: Readable;
|
|
23
|
+
quality: string;
|
|
24
|
+
format: string;
|
|
25
|
+
}
|
|
19
26
|
interface CachedFile {
|
|
20
27
|
path: string;
|
|
21
28
|
size: number;
|
|
@@ -54,6 +61,65 @@ interface PlayEngineOptions {
|
|
|
54
61
|
error?: (...args: any[]) => void;
|
|
55
62
|
};
|
|
56
63
|
}
|
|
64
|
+
interface StalkChannelOptions {
|
|
65
|
+
flat?: boolean;
|
|
66
|
+
tab?: "videos" | "shorts" | "streams" | "popular" | "featured";
|
|
67
|
+
startItem?: number;
|
|
68
|
+
endItem?: number;
|
|
69
|
+
playlistItems?: string;
|
|
70
|
+
}
|
|
71
|
+
interface YtDlpThumbnail {
|
|
72
|
+
url: string;
|
|
73
|
+
width?: number;
|
|
74
|
+
height?: number;
|
|
75
|
+
id?: string;
|
|
76
|
+
}
|
|
77
|
+
interface YtDlpVideoMetadata {
|
|
78
|
+
_type: "video";
|
|
79
|
+
id: string;
|
|
80
|
+
title: string;
|
|
81
|
+
description?: string;
|
|
82
|
+
channel: string;
|
|
83
|
+
channel_id: string;
|
|
84
|
+
channel_follower_count?: number;
|
|
85
|
+
duration?: number;
|
|
86
|
+
view_count?: number;
|
|
87
|
+
like_count?: number;
|
|
88
|
+
comment_count?: number;
|
|
89
|
+
tags?: string[];
|
|
90
|
+
categories?: string[];
|
|
91
|
+
upload_date?: string;
|
|
92
|
+
timestamp?: number;
|
|
93
|
+
thumbnails?: YtDlpThumbnail[];
|
|
94
|
+
webpage_url: string;
|
|
95
|
+
is_live?: boolean;
|
|
96
|
+
was_live?: boolean;
|
|
97
|
+
concurrent_view_count?: number;
|
|
98
|
+
live_status?: "is_live" | "was_live" | "is_upcoming" | "not_live";
|
|
99
|
+
release_timestamp?: number;
|
|
100
|
+
}
|
|
101
|
+
interface YtDlpFlatEntry {
|
|
102
|
+
_type: "url" | "url_transparent";
|
|
103
|
+
id: string;
|
|
104
|
+
title: string;
|
|
105
|
+
url: string;
|
|
106
|
+
duration?: number;
|
|
107
|
+
view_count?: number;
|
|
108
|
+
channel?: string;
|
|
109
|
+
}
|
|
110
|
+
interface YtDlpChannelMetadata {
|
|
111
|
+
_type: "playlist";
|
|
112
|
+
id: string;
|
|
113
|
+
title: string;
|
|
114
|
+
channel: string;
|
|
115
|
+
channel_id: string;
|
|
116
|
+
description?: string;
|
|
117
|
+
channel_follower_count?: number;
|
|
118
|
+
tags?: string[];
|
|
119
|
+
thumbnails?: YtDlpThumbnail[];
|
|
120
|
+
playlist_count?: number;
|
|
121
|
+
entries?: (YtDlpFlatEntry | YtDlpVideoMetadata)[];
|
|
122
|
+
}
|
|
57
123
|
|
|
58
124
|
declare class CacheStore {
|
|
59
125
|
private readonly opts;
|
|
@@ -74,35 +140,6 @@ declare class CacheStore {
|
|
|
74
140
|
private cleanupEntry;
|
|
75
141
|
}
|
|
76
142
|
|
|
77
|
-
declare class PlayEngine {
|
|
78
|
-
private readonly opts;
|
|
79
|
-
private readonly paths;
|
|
80
|
-
readonly cache: CacheStore;
|
|
81
|
-
private readonly ytdlp;
|
|
82
|
-
private static lastUpdateCheck;
|
|
83
|
-
private static isUpdating;
|
|
84
|
-
constructor(options?: PlayEngineOptions);
|
|
85
|
-
private backgroundUpdateCheck;
|
|
86
|
-
private forceUpdateCheck;
|
|
87
|
-
generateRequestId(prefix?: string): string;
|
|
88
|
-
search(query: string): Promise<PlayMetadata | null>;
|
|
89
|
-
getFromCache(requestId: string): CacheEntry | undefined;
|
|
90
|
-
preload(metadata: PlayMetadata, requestId: string): Promise<void>;
|
|
91
|
-
getOrDownload(requestId: string, type: MediaType): Promise<{
|
|
92
|
-
metadata: PlayMetadata;
|
|
93
|
-
file: CachedFile;
|
|
94
|
-
direct: boolean;
|
|
95
|
-
}>;
|
|
96
|
-
waitCache(requestId: string, type: MediaType, timeoutMs?: number, intervalMs?: number): Promise<CachedFile | null>;
|
|
97
|
-
cleanup(requestId: string): void;
|
|
98
|
-
private preloadOne;
|
|
99
|
-
private downloadDirect;
|
|
100
|
-
}
|
|
101
|
-
|
|
102
|
-
declare function getYouTubeVideoId(input: string): string | null;
|
|
103
|
-
declare function normalizeYoutubeUrl(input: string): string | null;
|
|
104
|
-
declare function searchBest(query: string): Promise<PlayMetadata | null>;
|
|
105
|
-
|
|
106
143
|
interface YtDlpClientOptions {
|
|
107
144
|
binaryPath?: string;
|
|
108
145
|
ffmpegPath?: string;
|
|
@@ -120,6 +157,29 @@ interface YtDlpVideoInfo {
|
|
|
120
157
|
duration: number;
|
|
121
158
|
thumbnail?: string;
|
|
122
159
|
}
|
|
160
|
+
interface YtDlpPlaylistItem {
|
|
161
|
+
id: string;
|
|
162
|
+
title: string;
|
|
163
|
+
url: string;
|
|
164
|
+
duration?: number;
|
|
165
|
+
uploader?: string;
|
|
166
|
+
directUrl?: string;
|
|
167
|
+
}
|
|
168
|
+
interface YtDlpResolvedItem extends YtDlpPlaylistItem {
|
|
169
|
+
directUrl: string;
|
|
170
|
+
}
|
|
171
|
+
interface YtDlpPlaylistInfo {
|
|
172
|
+
id: string;
|
|
173
|
+
title: string;
|
|
174
|
+
uploader?: string;
|
|
175
|
+
entries: YtDlpPlaylistItem[] | YtDlpResolvedItem[];
|
|
176
|
+
}
|
|
177
|
+
interface YtDlpPlaylistOptions {
|
|
178
|
+
limit?: number;
|
|
179
|
+
resolveLinks?: boolean;
|
|
180
|
+
type?: "audio" | "video" | "both";
|
|
181
|
+
batchSize?: number;
|
|
182
|
+
}
|
|
123
183
|
declare class YtDlpClient {
|
|
124
184
|
private readonly binaryPath;
|
|
125
185
|
private readonly ffmpegPath?;
|
|
@@ -132,12 +192,63 @@ declare class YtDlpClient {
|
|
|
132
192
|
constructor(opts?: YtDlpClientOptions);
|
|
133
193
|
private detectYtDlp;
|
|
134
194
|
private detectAria2c;
|
|
135
|
-
|
|
195
|
+
exec(args: string[]): Promise<string>;
|
|
136
196
|
getInfo(youtubeUrl: string): Promise<YtDlpVideoInfo>;
|
|
197
|
+
getPlaylistInfo(youtubeUrl: string, opts?: YtDlpPlaylistOptions): Promise<YtDlpPlaylistInfo>;
|
|
198
|
+
getDirectUrl(youtubeUrl: string, type?: "audio" | "video" | "both"): Promise<string>;
|
|
199
|
+
resolvePlaylistItems(entries: YtDlpPlaylistItem[], limit: number, type?: "audio" | "video" | "both", batchSize?: number): Promise<YtDlpResolvedItem[]>;
|
|
137
200
|
private buildOptimizationArgs;
|
|
138
201
|
getAudio(youtubeUrl: string, qualityKbps: number, outputPath: string): Promise<DownloadInfo>;
|
|
139
202
|
getVideo(youtubeUrl: string, qualityP: number, outputPath: string): Promise<DownloadInfo>;
|
|
140
203
|
private formatDuration;
|
|
141
204
|
}
|
|
142
205
|
|
|
143
|
-
|
|
206
|
+
declare class StreamEngine {
|
|
207
|
+
private readonly ytdlpClient;
|
|
208
|
+
constructor(ytdlpClient: YtDlpClient);
|
|
209
|
+
private getBinaryPath;
|
|
210
|
+
getAudioStream(youtubeUrl: string, qualityKbps?: number): Promise<StreamInfo>;
|
|
211
|
+
getVideoStream(youtubeUrl: string, qualityP?: number): Promise<StreamInfo>;
|
|
212
|
+
}
|
|
213
|
+
|
|
214
|
+
declare class PlayEngine {
|
|
215
|
+
private readonly opts;
|
|
216
|
+
private readonly paths;
|
|
217
|
+
readonly cache: CacheStore;
|
|
218
|
+
private readonly ytdlp;
|
|
219
|
+
private static lastUpdateCheck;
|
|
220
|
+
private static updatePromise;
|
|
221
|
+
constructor(options?: PlayEngineOptions);
|
|
222
|
+
readonly stream: StreamEngine;
|
|
223
|
+
private backgroundUpdateCheck;
|
|
224
|
+
private forceUpdateCheck;
|
|
225
|
+
generateRequestId(prefix?: string): string;
|
|
226
|
+
search(query: string): Promise<PlayMetadata | null>;
|
|
227
|
+
getFromCache(requestId: string): CacheEntry | undefined;
|
|
228
|
+
preload(metadata: PlayMetadata, requestId: string): Promise<void>;
|
|
229
|
+
getOrDownload(requestId: string, type: MediaType): Promise<{
|
|
230
|
+
metadata: PlayMetadata;
|
|
231
|
+
file: CachedFile;
|
|
232
|
+
direct: boolean;
|
|
233
|
+
}>;
|
|
234
|
+
waitCache(requestId: string, type: MediaType, timeoutMs?: number, intervalMs?: number): Promise<CachedFile | null>;
|
|
235
|
+
cleanup(requestId: string): void;
|
|
236
|
+
private preloadOne;
|
|
237
|
+
private _executePreload;
|
|
238
|
+
private downloadDirect;
|
|
239
|
+
private _executeDownloadDirect;
|
|
240
|
+
}
|
|
241
|
+
|
|
242
|
+
declare class StalkerEngine {
|
|
243
|
+
private readonly ytdlpClient;
|
|
244
|
+
constructor(ytdlpClient: YtDlpClient);
|
|
245
|
+
stalkVideoOrLive(youtubeUrl: string): Promise<YtDlpVideoMetadata>;
|
|
246
|
+
stalkChannel(youtubeUrl: string, opts?: StalkChannelOptions): Promise<YtDlpChannelMetadata>;
|
|
247
|
+
}
|
|
248
|
+
|
|
249
|
+
declare function getYouTubeVideoId(input: string): string | null;
|
|
250
|
+
declare function normalizeYoutubeUrl(input: string): string | null;
|
|
251
|
+
declare function searchBest(query: string): Promise<PlayMetadata | null>;
|
|
252
|
+
declare function searchPlaylistBest(query: string): Promise<PlayMetadata | null>;
|
|
253
|
+
|
|
254
|
+
export { type CacheEntry, type CachedFile, type DownloadInfo, type MediaType, PlayEngine, type PlayEngineOptions, type PlayMetadata, type StalkChannelOptions, StalkerEngine, StreamEngine, type StreamInfo, type YtDlpChannelMetadata, YtDlpClient, type YtDlpFlatEntry, type YtDlpVideoMetadata, getYouTubeVideoId, normalizeYoutubeUrl, searchBest, searchPlaylistBest };
|
package/dist/index.d.ts
CHANGED
|
@@ -1,3 +1,5 @@
|
|
|
1
|
+
import { Readable } from 'node:stream';
|
|
2
|
+
|
|
1
3
|
type MediaType = "audio" | "video";
|
|
2
4
|
interface PlayMetadata {
|
|
3
5
|
title: string;
|
|
@@ -16,6 +18,11 @@ interface DownloadInfo {
|
|
|
16
18
|
quality: string;
|
|
17
19
|
downloadUrl: string;
|
|
18
20
|
}
|
|
21
|
+
interface StreamInfo {
|
|
22
|
+
stream: Readable;
|
|
23
|
+
quality: string;
|
|
24
|
+
format: string;
|
|
25
|
+
}
|
|
19
26
|
interface CachedFile {
|
|
20
27
|
path: string;
|
|
21
28
|
size: number;
|
|
@@ -54,6 +61,65 @@ interface PlayEngineOptions {
|
|
|
54
61
|
error?: (...args: any[]) => void;
|
|
55
62
|
};
|
|
56
63
|
}
|
|
64
|
+
interface StalkChannelOptions {
|
|
65
|
+
flat?: boolean;
|
|
66
|
+
tab?: "videos" | "shorts" | "streams" | "popular" | "featured";
|
|
67
|
+
startItem?: number;
|
|
68
|
+
endItem?: number;
|
|
69
|
+
playlistItems?: string;
|
|
70
|
+
}
|
|
71
|
+
interface YtDlpThumbnail {
|
|
72
|
+
url: string;
|
|
73
|
+
width?: number;
|
|
74
|
+
height?: number;
|
|
75
|
+
id?: string;
|
|
76
|
+
}
|
|
77
|
+
interface YtDlpVideoMetadata {
|
|
78
|
+
_type: "video";
|
|
79
|
+
id: string;
|
|
80
|
+
title: string;
|
|
81
|
+
description?: string;
|
|
82
|
+
channel: string;
|
|
83
|
+
channel_id: string;
|
|
84
|
+
channel_follower_count?: number;
|
|
85
|
+
duration?: number;
|
|
86
|
+
view_count?: number;
|
|
87
|
+
like_count?: number;
|
|
88
|
+
comment_count?: number;
|
|
89
|
+
tags?: string[];
|
|
90
|
+
categories?: string[];
|
|
91
|
+
upload_date?: string;
|
|
92
|
+
timestamp?: number;
|
|
93
|
+
thumbnails?: YtDlpThumbnail[];
|
|
94
|
+
webpage_url: string;
|
|
95
|
+
is_live?: boolean;
|
|
96
|
+
was_live?: boolean;
|
|
97
|
+
concurrent_view_count?: number;
|
|
98
|
+
live_status?: "is_live" | "was_live" | "is_upcoming" | "not_live";
|
|
99
|
+
release_timestamp?: number;
|
|
100
|
+
}
|
|
101
|
+
interface YtDlpFlatEntry {
|
|
102
|
+
_type: "url" | "url_transparent";
|
|
103
|
+
id: string;
|
|
104
|
+
title: string;
|
|
105
|
+
url: string;
|
|
106
|
+
duration?: number;
|
|
107
|
+
view_count?: number;
|
|
108
|
+
channel?: string;
|
|
109
|
+
}
|
|
110
|
+
interface YtDlpChannelMetadata {
|
|
111
|
+
_type: "playlist";
|
|
112
|
+
id: string;
|
|
113
|
+
title: string;
|
|
114
|
+
channel: string;
|
|
115
|
+
channel_id: string;
|
|
116
|
+
description?: string;
|
|
117
|
+
channel_follower_count?: number;
|
|
118
|
+
tags?: string[];
|
|
119
|
+
thumbnails?: YtDlpThumbnail[];
|
|
120
|
+
playlist_count?: number;
|
|
121
|
+
entries?: (YtDlpFlatEntry | YtDlpVideoMetadata)[];
|
|
122
|
+
}
|
|
57
123
|
|
|
58
124
|
declare class CacheStore {
|
|
59
125
|
private readonly opts;
|
|
@@ -74,35 +140,6 @@ declare class CacheStore {
|
|
|
74
140
|
private cleanupEntry;
|
|
75
141
|
}
|
|
76
142
|
|
|
77
|
-
declare class PlayEngine {
|
|
78
|
-
private readonly opts;
|
|
79
|
-
private readonly paths;
|
|
80
|
-
readonly cache: CacheStore;
|
|
81
|
-
private readonly ytdlp;
|
|
82
|
-
private static lastUpdateCheck;
|
|
83
|
-
private static isUpdating;
|
|
84
|
-
constructor(options?: PlayEngineOptions);
|
|
85
|
-
private backgroundUpdateCheck;
|
|
86
|
-
private forceUpdateCheck;
|
|
87
|
-
generateRequestId(prefix?: string): string;
|
|
88
|
-
search(query: string): Promise<PlayMetadata | null>;
|
|
89
|
-
getFromCache(requestId: string): CacheEntry | undefined;
|
|
90
|
-
preload(metadata: PlayMetadata, requestId: string): Promise<void>;
|
|
91
|
-
getOrDownload(requestId: string, type: MediaType): Promise<{
|
|
92
|
-
metadata: PlayMetadata;
|
|
93
|
-
file: CachedFile;
|
|
94
|
-
direct: boolean;
|
|
95
|
-
}>;
|
|
96
|
-
waitCache(requestId: string, type: MediaType, timeoutMs?: number, intervalMs?: number): Promise<CachedFile | null>;
|
|
97
|
-
cleanup(requestId: string): void;
|
|
98
|
-
private preloadOne;
|
|
99
|
-
private downloadDirect;
|
|
100
|
-
}
|
|
101
|
-
|
|
102
|
-
declare function getYouTubeVideoId(input: string): string | null;
|
|
103
|
-
declare function normalizeYoutubeUrl(input: string): string | null;
|
|
104
|
-
declare function searchBest(query: string): Promise<PlayMetadata | null>;
|
|
105
|
-
|
|
106
143
|
interface YtDlpClientOptions {
|
|
107
144
|
binaryPath?: string;
|
|
108
145
|
ffmpegPath?: string;
|
|
@@ -120,6 +157,29 @@ interface YtDlpVideoInfo {
|
|
|
120
157
|
duration: number;
|
|
121
158
|
thumbnail?: string;
|
|
122
159
|
}
|
|
160
|
+
interface YtDlpPlaylistItem {
|
|
161
|
+
id: string;
|
|
162
|
+
title: string;
|
|
163
|
+
url: string;
|
|
164
|
+
duration?: number;
|
|
165
|
+
uploader?: string;
|
|
166
|
+
directUrl?: string;
|
|
167
|
+
}
|
|
168
|
+
interface YtDlpResolvedItem extends YtDlpPlaylistItem {
|
|
169
|
+
directUrl: string;
|
|
170
|
+
}
|
|
171
|
+
interface YtDlpPlaylistInfo {
|
|
172
|
+
id: string;
|
|
173
|
+
title: string;
|
|
174
|
+
uploader?: string;
|
|
175
|
+
entries: YtDlpPlaylistItem[] | YtDlpResolvedItem[];
|
|
176
|
+
}
|
|
177
|
+
interface YtDlpPlaylistOptions {
|
|
178
|
+
limit?: number;
|
|
179
|
+
resolveLinks?: boolean;
|
|
180
|
+
type?: "audio" | "video" | "both";
|
|
181
|
+
batchSize?: number;
|
|
182
|
+
}
|
|
123
183
|
declare class YtDlpClient {
|
|
124
184
|
private readonly binaryPath;
|
|
125
185
|
private readonly ffmpegPath?;
|
|
@@ -132,12 +192,63 @@ declare class YtDlpClient {
|
|
|
132
192
|
constructor(opts?: YtDlpClientOptions);
|
|
133
193
|
private detectYtDlp;
|
|
134
194
|
private detectAria2c;
|
|
135
|
-
|
|
195
|
+
exec(args: string[]): Promise<string>;
|
|
136
196
|
getInfo(youtubeUrl: string): Promise<YtDlpVideoInfo>;
|
|
197
|
+
getPlaylistInfo(youtubeUrl: string, opts?: YtDlpPlaylistOptions): Promise<YtDlpPlaylistInfo>;
|
|
198
|
+
getDirectUrl(youtubeUrl: string, type?: "audio" | "video" | "both"): Promise<string>;
|
|
199
|
+
resolvePlaylistItems(entries: YtDlpPlaylistItem[], limit: number, type?: "audio" | "video" | "both", batchSize?: number): Promise<YtDlpResolvedItem[]>;
|
|
137
200
|
private buildOptimizationArgs;
|
|
138
201
|
getAudio(youtubeUrl: string, qualityKbps: number, outputPath: string): Promise<DownloadInfo>;
|
|
139
202
|
getVideo(youtubeUrl: string, qualityP: number, outputPath: string): Promise<DownloadInfo>;
|
|
140
203
|
private formatDuration;
|
|
141
204
|
}
|
|
142
205
|
|
|
143
|
-
|
|
206
|
+
declare class StreamEngine {
|
|
207
|
+
private readonly ytdlpClient;
|
|
208
|
+
constructor(ytdlpClient: YtDlpClient);
|
|
209
|
+
private getBinaryPath;
|
|
210
|
+
getAudioStream(youtubeUrl: string, qualityKbps?: number): Promise<StreamInfo>;
|
|
211
|
+
getVideoStream(youtubeUrl: string, qualityP?: number): Promise<StreamInfo>;
|
|
212
|
+
}
|
|
213
|
+
|
|
214
|
+
declare class PlayEngine {
|
|
215
|
+
private readonly opts;
|
|
216
|
+
private readonly paths;
|
|
217
|
+
readonly cache: CacheStore;
|
|
218
|
+
private readonly ytdlp;
|
|
219
|
+
private static lastUpdateCheck;
|
|
220
|
+
private static updatePromise;
|
|
221
|
+
constructor(options?: PlayEngineOptions);
|
|
222
|
+
readonly stream: StreamEngine;
|
|
223
|
+
private backgroundUpdateCheck;
|
|
224
|
+
private forceUpdateCheck;
|
|
225
|
+
generateRequestId(prefix?: string): string;
|
|
226
|
+
search(query: string): Promise<PlayMetadata | null>;
|
|
227
|
+
getFromCache(requestId: string): CacheEntry | undefined;
|
|
228
|
+
preload(metadata: PlayMetadata, requestId: string): Promise<void>;
|
|
229
|
+
getOrDownload(requestId: string, type: MediaType): Promise<{
|
|
230
|
+
metadata: PlayMetadata;
|
|
231
|
+
file: CachedFile;
|
|
232
|
+
direct: boolean;
|
|
233
|
+
}>;
|
|
234
|
+
waitCache(requestId: string, type: MediaType, timeoutMs?: number, intervalMs?: number): Promise<CachedFile | null>;
|
|
235
|
+
cleanup(requestId: string): void;
|
|
236
|
+
private preloadOne;
|
|
237
|
+
private _executePreload;
|
|
238
|
+
private downloadDirect;
|
|
239
|
+
private _executeDownloadDirect;
|
|
240
|
+
}
|
|
241
|
+
|
|
242
|
+
declare class StalkerEngine {
|
|
243
|
+
private readonly ytdlpClient;
|
|
244
|
+
constructor(ytdlpClient: YtDlpClient);
|
|
245
|
+
stalkVideoOrLive(youtubeUrl: string): Promise<YtDlpVideoMetadata>;
|
|
246
|
+
stalkChannel(youtubeUrl: string, opts?: StalkChannelOptions): Promise<YtDlpChannelMetadata>;
|
|
247
|
+
}
|
|
248
|
+
|
|
249
|
+
declare function getYouTubeVideoId(input: string): string | null;
|
|
250
|
+
declare function normalizeYoutubeUrl(input: string): string | null;
|
|
251
|
+
declare function searchBest(query: string): Promise<PlayMetadata | null>;
|
|
252
|
+
declare function searchPlaylistBest(query: string): Promise<PlayMetadata | null>;
|
|
253
|
+
|
|
254
|
+
export { type CacheEntry, type CachedFile, type DownloadInfo, type MediaType, PlayEngine, type PlayEngineOptions, type PlayMetadata, type StalkChannelOptions, StalkerEngine, StreamEngine, type StreamInfo, type YtDlpChannelMetadata, YtDlpClient, type YtDlpFlatEntry, type YtDlpVideoMetadata, getYouTubeVideoId, normalizeYoutubeUrl, searchBest, searchPlaylistBest };
|
package/dist/index.mjs
CHANGED
|
@@ -1,6 +1,7 @@
|
|
|
1
|
-
var
|
|
2
|
-
`)[0]}catch{}return"yt-dlp"}detectAria2c(){let t=
|
|
3
|
-
`)[0]}catch{}}async exec(t){return new Promise((r,e)=>{let i=[...t];this.ffmpegPath&&(i=["--ffmpeg-location",this.ffmpegPath,...i]),this.cookiesPath&&
|
|
1
|
+
var T=(a=>typeof require<"u"?require:typeof Proxy<"u"?new Proxy(a,{get:(t,r)=>(typeof require<"u"?require:t)[r]}):a)(function(a){if(typeof require<"u")return require.apply(this,arguments);throw Error('Dynamic require of "'+a+'" is not supported')});import m from"fs";import f from"path";import{execSync as J}from"child_process";import A from"fs";var x=class{constructor(t){this.opts=t}store=new Map;cleanupTimer;get(t){return this.store.get(t)}set(t,r){this.store.set(t,r)}has(t){return this.store.has(t)}delete(t){this.cleanupEntry(t),this.store.delete(t)}markLoading(t,r){let e=this.store.get(t);e&&(e.loading=r)}setFile(t,r,e){let i=this.store.get(t);i&&(i[r]=e)}cleanupExpired(t=Date.now()){let r=0;for(let[e,i]of this.store.entries())t>i.expiresAt&&(this.delete(e),r++);return r}start(){this.cleanupTimer||(this.cleanupTimer=setInterval(()=>{this.cleanupExpired(Date.now())},this.opts.cleanupIntervalMs),this.cleanupTimer.unref())}stop(){this.cleanupTimer&&(clearInterval(this.cleanupTimer),this.cleanupTimer=void 0)}cleanupEntry(t){let r=this.store.get(t);r&&["audio","video"].forEach(e=>{let i=r[e];if(i?.path&&A.existsSync(i.path))try{A.unlinkSync(i.path)}catch{}})}};import b from"fs";import k from"path";import V from"os";function U(a){b.mkdirSync(a,{recursive:!0,mode:511});try{b.chmodSync(a,511)}catch{}b.accessSync(a,b.constants.R_OK|b.constants.W_OK)}function E(a){let t=a?.trim()?a:k.join(V.tmpdir(),"yt-play"),r=k.resolve(t),e=k.join(r);return U(r),U(e),{baseDir:r,cacheDir:e}}import{spawn as z}from"child_process";import p from"path";import u from"fs";var g;try{g=p.dirname(new URL(import.meta.url).pathname)}catch{g=typeof g<"u"?g:process.cwd()}var P=class{binaryPath;ffmpegPath;aria2cPath;timeoutMs;useAria2c;concurrentFragments;cookiesPath;cookiesFromBrowser;constructor(t={}){this.binaryPath=t.binaryPath||this.detectYtDlp(),this.ffmpegPath=t.ffmpegPath,this.timeoutMs=t.timeoutMs??3e5,this.concurrentFragments=t.concurrentFragments??5,this.cookiesPath=t.cookiesPath,this.cookiesFromBrowser=t.cookiesFromBrowser,this.aria2cPath=t.aria2cPath||this.detectAria2c(),this.useAria2c=t.useAria2c??!!this.aria2cPath}detectYtDlp(){let t=p.resolve(g,"../.."),r=[p.join(t,"bin","yt-dlp"),p.join(t,"bin","yt-dlp.exe")];for(let e of r)if(u.existsSync(e))return e;try{let{execSync:e}=T("child_process"),i=process.platform==="win32"?"where yt-dlp":"which yt-dlp",n=e(i,{encoding:"utf-8"}).trim();if(n)return n.split(`
|
|
2
|
+
`)[0]}catch{}return"yt-dlp"}detectAria2c(){let t=p.resolve(g,"../.."),r=[p.join(t,"bin","aria2c"),p.join(t,"bin","aria2c.exe")];for(let e of r)if(u.existsSync(e))return e;try{let{execSync:e}=T("child_process"),i=process.platform==="win32"?"where aria2c":"which aria2c",n=e(i,{encoding:"utf-8"}).trim();if(n)return n.split(`
|
|
3
|
+
`)[0]}catch{}}async exec(t){return new Promise((r,e)=>{let i=[...t];this.ffmpegPath&&(i=["--ffmpeg-location",this.ffmpegPath,...i]),this.cookiesPath&&u.existsSync(this.cookiesPath)&&(i=["--cookies",this.cookiesPath,...i]),this.cookiesFromBrowser&&(i=["--cookies-from-browser",this.cookiesFromBrowser,...i]);let n=z(this.binaryPath,i,{stdio:["ignore","pipe","pipe"]}),o="",s="";n.stdout.on("data",l=>{o+=l.toString()}),n.stderr.on("data",l=>{s+=l.toString()});let c=setTimeout(()=>{n.kill("SIGKILL"),e(new Error(`yt-dlp timeout after ${this.timeoutMs}ms`))},this.timeoutMs);n.on("close",l=>{clearTimeout(c),l===0?r(o):e(new Error(`yt-dlp exited with code ${l}. stderr: ${s.slice(0,500)}`))}),n.on("error",l=>{clearTimeout(c),e(l)})})}async getInfo(t){let r=await this.exec(["-J","--no-warnings","--no-playlist",t]);return JSON.parse(r)}async getPlaylistInfo(t,r={}){let e=await this.exec(["-J","--flat-playlist","--no-warnings",t]),i=JSON.parse(e),n=(i.entries||[]).filter(o=>{let s=o.title||"";return o.id&&s&&!s.includes("[Deleted video]")&&!s.includes("[Private video]")}).map(o=>({id:o.id,title:o.title,url:`https://www.youtube.com/watch?v=${o.id}`,duration:o.duration,uploader:o.uploader||i.uploader}));return r.resolveLinks?n=await this.resolvePlaylistItems(n,r.limit&&r.limit>0?r.limit:n.length,r.type||"audio",r.batchSize||5):r.limit&&r.limit>0&&(n=n.slice(0,r.limit)),{id:i.id,title:i.title,uploader:i.uploader,entries:n}}async getDirectUrl(t,r="audio"){let e=r==="audio"?"bestaudio[ext=m4a]/bestaudio/best":"best[ext=mp4]/best";return(await this.exec(["-f",e,"-g","--no-warnings",t])).trim().split(`
|
|
4
|
+
`)[0]}async resolvePlaylistItems(t,r,e="audio",i=5){let n=[],o=0;for(;n.length<r&&o<t.length;){let s=r-n.length,c=Math.min(i,s,t.length-o),l=t.slice(o,o+c);o+=c;let h=l.map(async d=>{try{let w=await this.getDirectUrl(d.url,e);return{...d,directUrl:w}}catch{return null}}),D=await Promise.all(h);for(let d of D)d&&n.length<r&&n.push(d)}return n}buildOptimizationArgs(){let t=["--no-warnings","--no-playlist","--no-check-certificates","--concurrent-fragments",String(this.concurrentFragments)];return this.useAria2c&&this.aria2cPath&&(t.push("--downloader",this.aria2cPath),t.push("--downloader-args","aria2c:-x 16 -s 16 -k 1M")),t}async getAudio(t,r,e){let i=await this.getInfo(t),o=["-f","bestaudio[ext=m4a]/bestaudio/best","-o",e,...this.buildOptimizationArgs(),t];try{await this.exec(o)}catch(c){throw[e,`${e}.part`,`${e}.ytdl`].forEach(l=>{if(u.existsSync(l))try{u.unlinkSync(l)}catch{}}),c}if(!u.existsSync(e))throw new Error(`yt-dlp failed to create audio file: ${e}`);let s=this.formatDuration(i.duration);return{title:i.title,author:i.uploader,duration:s,quality:`${r}kbps m4a`,filename:p.basename(e),downloadUrl:e}}async getVideo(t,r,e){let i=await this.getInfo(t),o=["-f",`bestvideo[height<=${r}][ext=mp4][vcodec^=avc]+bestaudio[ext=m4a]/bestvideo[height<=${r}][vcodec!=av1][vcodec!=vp9]+bestaudio/best[height<=${r}]`,"--merge-output-format","mp4","-o",e,...this.buildOptimizationArgs(),t];try{await this.exec(o)}catch(c){throw[e,`${e}.part`,`${e}.ytdl`].forEach(l=>{if(u.existsSync(l))try{u.unlinkSync(l)}catch{}}),c}if(!u.existsSync(e))throw new Error(`yt-dlp failed to create video file: ${e}`);let s=this.formatDuration(i.duration);return{title:i.title,author:i.uploader,duration:s,quality:`${r}p H.264`,filename:p.basename(e),downloadUrl:e}}formatDuration(t){if(!t)return"0:00";let r=Math.floor(t/3600),e=Math.floor(t%3600/60),i=Math.floor(t%60);return r>0?`${r}:${e.toString().padStart(2,"0")}:${i.toString().padStart(2,"0")}`:`${e}:${i.toString().padStart(2,"0")}`}};import S from"yt-search";function R(a){let t=(a||"").trim(),r=[...t.matchAll(/\[[^\]]*\]\((https?:\/\/[^)\s]+)\)/gi)];return r.length>0?r[0][1].trim():(t=t.replace(/^<([^>]+)>$/,"$1").trim(),t=t.replace(/^["'`](.*)["'`]$/,"$1").trim(),t)}function M(a){let t=/(?:https?:\/\/)?(?:www\.)?(?:youtube\.com\/(?:[^\/]+\/.+\/|(?:v|e(?:mbed)?)\/|.*[?&]v=|shorts\/)|youtu\.be\/)([a-zA-Z0-9_-]{11})(?:[?&]|$)/i,r=(a||"").match(t);return r?r[1]:null}function L(a){let t=/[?&]list=([a-zA-Z0-9_-]+)/i,r=(a||"").match(t);return r?r[1]:null}function y(a){let t=R(a),r=t.match(/https?:\/\/[^\s)]+/i)?.[0]??t,e=M(r);return e?`https://www.youtube.com/watch?v=${e}`:null}async function $(a){let t=M(a);if(t){let o=await S({videoId:t});if(!o)return null;let s=o.duration?.seconds??0,c=y(o.url)??o.url;return{title:o.title||"Untitled",author:o.author?.name||void 0,duration:o.duration?.timestamp||void 0,thumb:o.image||o.thumbnail||void 0,videoId:o.videoId,url:c,durationSeconds:s}}let e=(await S(a))?.videos?.[0];if(!e)return null;let i=e.duration?.seconds??0,n=y(e.url)??e.url;return{title:e.title||"Untitled",author:e.author?.name||void 0,duration:e.duration?.timestamp||void 0,thumb:e.image||e.thumbnail||void 0,videoId:e.videoId,url:n,durationSeconds:i}}async function K(a){let t=L(a);if(t)try{let i=await S({listId:t});return i?{title:i.title||"Untitled",author:i.author?.name||void 0,duration:void 0,durationSeconds:0,thumb:i.image||i.thumbnail||void 0,videoId:t,url:i.url||`https://www.youtube.com/playlist?list=${t}`}:null}catch{}let e=(await S(a))?.playlists?.[0];return e?{title:e.title||"Untitled",author:e.author?.name||void 0,duration:void 0,durationSeconds:0,thumb:e.image||e.thumbnail||void 0,videoId:e.listId,url:e.url||`https://www.youtube.com/playlist?list=${e.listId}`}:null}import{spawn as F}from"child_process";var v=class{constructor(t){this.ytdlpClient=t}getBinaryPath(){return this.ytdlpClient.binaryPath}async getAudioStream(t,r=128){let i=["-f","bestaudio[ext=m4a]/bestaudio/best","-o","-","--no-warnings","--no-playlist",t],n=F(this.getBinaryPath(),i,{stdio:["ignore","pipe","pipe"]});return n.stderr.on("data",()=>{}),{stream:n.stdout,quality:`${r}kbps`,format:"m4a"}}async getVideoStream(t,r=720){let i=["-f",`best[height<=${r}][ext=mp4]/best`,"-o","-","--no-warnings","--no-playlist",t],n=F(this.getBinaryPath(),i,{stdio:["ignore","pipe","pipe"]});return n.stderr.on("data",()=>{}),{stream:n.stdout,quality:`${r}p`,format:"mp4"}}};var O=[320,256,192,128,96,64],B=[1080,720,480,360],N=36e5;function I(a,t){return t.includes(a)?a:t[0]}function _(a){return(a||"").replace(/[\\/:*?"<>|]/g,"").replace(/[^\w\s-]/gi,"").trim().replace(/\s+/g," ").substring(0,100)}function W(){try{return J("which node",{encoding:"utf-8"}).trim()||null}catch{return process.execPath||null}}function Q(a,t,r){try{let e;if(a)e=f.dirname(a);else try{let s=new URL(import.meta.url);e=f.join(f.dirname(s.pathname),"..","bin")}catch{e=f.join(__dirname,"..","bin")}if(!m.existsSync(e))return;let i=f.join(e,"yt-dlp.conf"),n=[],o=W();o&&n.push(`--js-runtimes node:${o}`),n.push("--remote-components ejs:npm"),t&&m.existsSync(t)?n.push(`--cookies ${t}`):r&&n.push(`--cookies-from-browser ${r}`),m.writeFileSync(i,n.join(`
|
|
4
5
|
`)+`
|
|
5
|
-
`,"utf-8")}catch
|
|
6
|
+
`,"utf-8")}catch{}}var C=class a{opts;paths;cache;ytdlp;static lastUpdateCheck=0;static updatePromise=null;constructor(t={}){this.opts={ttlMs:t.ttlMs??3*6e4,maxPreloadDurationSeconds:t.maxPreloadDurationSeconds??1200,preferredAudioKbps:t.preferredAudioKbps??128,preferredVideoP:t.preferredVideoP??720,preloadBuffer:t.preloadBuffer??!0,cleanupIntervalMs:t.cleanupIntervalMs??3e4,concurrentFragments:t.concurrentFragments??5,useAria2c:t.useAria2c,logger:t.logger},this.paths=E(t.cacheDir),this.cache=new x({cleanupIntervalMs:this.opts.cleanupIntervalMs}),this.cache.start(),Q(t.ytdlpBinaryPath,t.cookiesPath,t.cookiesFromBrowser),this.ytdlp=new P({binaryPath:t.ytdlpBinaryPath,ffmpegPath:t.ffmpegPath,aria2cPath:t.aria2cPath,useAria2c:this.opts.useAria2c,concurrentFragments:this.opts.concurrentFragments,timeoutMs:t.ytdlpTimeoutMs??3e5,cookiesPath:t.cookiesPath,cookiesFromBrowser:t.cookiesFromBrowser}),this.stream=new v(this.ytdlp),this.backgroundUpdateCheck()}stream;backgroundUpdateCheck(){let t=Date.now();t-a.lastUpdateCheck<N||a.updatePromise||(a.updatePromise=(async()=>{try{a.lastUpdateCheck=t;let r=new URL("../scripts/check-ytdlp-update.mjs",import.meta.url),{checkAndUpdate:e}=await import(r.href);await e()&&this.opts.logger?.info?.("\u2713 yt-dlp updated to latest version")}catch{this.opts.logger?.debug?.("Update check failed (will retry later)")}finally{a.updatePromise=null}})())}async forceUpdateCheck(){return a.updatePromise?(this.opts.logger?.info?.("Update already in progress, waiting..."),a.updatePromise):(a.updatePromise=(async()=>{try{this.opts.logger?.warn?.("<!> Download failed. Forcing yt-dlp update check...");let t=new URL("../scripts/check-ytdlp-update.mjs",import.meta.url),{checkAndUpdate:r}=await import(t.href);await r()&&(this.opts.logger?.info?.("\u2713 yt-dlp updated successfully"),a.lastUpdateCheck=Date.now())}catch(t){this.opts.logger?.error?.("Failed to update yt-dlp:",t)}finally{a.updatePromise=null}})(),a.updatePromise)}generateRequestId(t="play"){return`${t}_${Date.now()}_${Math.random().toString(36).slice(2,8)}`}async search(t){return $(t)}getFromCache(t){return this.cache.get(t)}async preload(t,r){let e=y(t.url);if(!e)throw new Error("Invalid YouTube URL.");let i=t.durationSeconds>3600;t.durationSeconds>this.opts.maxPreloadDurationSeconds&&this.opts.logger?.warn?.(`Video too long for preload (${Math.floor(t.durationSeconds/60)}min). Will use direct download with reduced quality.`);let n={...t,url:e};this.cache.set(r,{metadata:n,audio:null,video:null,expiresAt:Date.now()+this.opts.ttlMs,loading:!0});let o=i?96:I(this.opts.preferredAudioKbps,O),s=this.preloadOne(r,"audio",e,o),c=i?[s]:[s,this.preloadOne(r,"video",e,I(this.opts.preferredVideoP,B))];i&&this.opts.logger?.info?.(`Long video detected (${Math.floor(t.durationSeconds/60)}min). Audio only mode (96kbps).`),await Promise.allSettled(c),this.cache.markLoading(r,!1)}async getOrDownload(t,r){let e=this.cache.get(t);if(!e)throw new Error("Request not found (cache miss).");let i=e[r];if(i?.path&&m.existsSync(i.path)&&i.size>0)return{metadata:e.metadata,file:i,direct:!1};let n=y(e.metadata.url);if(!n)throw new Error("Invalid YouTube URL.");let o=await this.downloadDirect(r,n);return{metadata:e.metadata,file:o,direct:!0}}async waitCache(t,r,e=8e3,i=500){let n=Date.now();for(;Date.now()-n<e;){let s=this.cache.get(t)?.[r];if(s?.path&&m.existsSync(s.path)&&s.size>0)return s;await new Promise(c=>setTimeout(c,i))}return null}cleanup(t){this.cache.delete(t)}async preloadOne(t,r,e,i){try{await this._executePreload(t,r,e,i)}catch(n){this.opts.logger?.error?.(`preload ${r} failed, forcing update...`,n),await this.forceUpdateCheck(),this.opts.logger?.info?.(">> Retrying preload after update..."),await this._executePreload(t,r,e,i)}}async _executePreload(t,r,e,i){let n=Math.random().toString(36).slice(2,8),o=_(`temp_${Date.now()}_${n}`),c=`${r}_${t}_${o}.${r==="audio"?"m4a":"mp4"}`,l=f.join(this.paths.cacheDir,c),h=r==="audio"?await this.ytdlp.getAudio(e,i,l):await this.ytdlp.getVideo(e,i,l),d=m.statSync(l).size,w;this.opts.preloadBuffer&&(w=await m.promises.readFile(l));let j={path:l,size:d,info:{quality:h.quality},buffer:w};this.cache.setFile(t,r,j),this.opts.logger?.debug?.(`preloaded ${r} ${d} bytes: ${c}`)}async downloadDirect(t,r){try{return await this._executeDownloadDirect(t,r,!1)}catch(e){return this.opts.logger?.error?.("Direct download failed:",e),await this.forceUpdateCheck(),this.opts.logger?.info?.(">> Retrying download after update..."),await this._executeDownloadDirect(t,r,!0)}}async _executeDownloadDirect(t,r,e){let i=I(this.opts.preferredAudioKbps,O),n=I(this.opts.preferredVideoP,B),o=t==="audio"?"m4a":"mp4",s=e?"direct_retry":"direct",c=Math.random().toString(36).slice(2,8),l=_(`${s}_${Date.now()}_${c}`),h=f.join(this.paths.cacheDir,`${t}_${l}.${o}`),D=t==="audio"?await this.ytdlp.getAudio(r,i,h):await this.ytdlp.getVideo(r,n,h),d=m.statSync(h);return{path:h,size:d.size,info:{quality:D.quality}}}};var Y=class{constructor(t){this.ytdlpClient=t}async stalkVideoOrLive(t){let r=["-J","--no-warnings","--no-playlist",t],e=await this.ytdlpClient.exec(r);return JSON.parse(e)}async stalkChannel(t,r={}){let e=["-J","--no-warnings"];r.flat&&e.push("--flat-playlist"),r.playlistItems?e.push("--playlist-items",r.playlistItems):(r.startItem&&r.startItem>0&&e.push("--playlist-start",r.startItem.toString()),r.endItem&&r.endItem>0&&e.push("--playlist-end",r.endItem.toString()));let i=t.trim().replace(/\/$/,"");r.tab&&(i=i.replace(/\/(videos|shorts|streams|popular|featured)$/i,""),r.tab!=="featured"&&(i=`${i}/${r.tab}`)),e.push(i);try{let n=await this.ytdlpClient.exec(e);return JSON.parse(n)}catch(n){let o=n.message||"";if(o.includes("This channel does not have a")||o.includes("404")||o.includes("does not exist"))return{_type:"playlist",id:i,title:"Tab Not Found",channel:i.split("/").pop()||"Unknown",channel_id:"Unknown",entries:[]};throw n}}};export{C as PlayEngine,Y as StalkerEngine,v as StreamEngine,P as YtDlpClient,M as getYouTubeVideoId,y as normalizeYoutubeUrl,$ as searchBest,K as searchPlaylistBest};
|
|
6
7
|
//# sourceMappingURL=index.mjs.map
|