@irithell-js/yt-play 0.3.2 → 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 +100 -1
- package/dist/index.cjs +5 -5
- package/dist/index.cjs.map +1 -1
- package/dist/index.d.cts +116 -34
- package/dist/index.d.ts +116 -34
- package/dist/index.mjs +5 -5
- package/dist/index.mjs.map +1 -1
- package/package.json +1 -1
package/README.md
CHANGED
|
@@ -9,6 +9,8 @@ High-performance YouTube audio/video download engine with intelligent caching, b
|
|
|
9
9
|
- **Ultra Fast Downloads** - aria2c acceleration (up to 5x faster)
|
|
10
10
|
- **Playlist Support** - Search, extract, and resolve direct media URLs from entire playlists in batches
|
|
11
11
|
- **Shorts Support** - Download and convertion for more compatibility
|
|
12
|
+
- **Metadata Stalking** - Deep extraction of channels, videos, and live streams (real-time viewers, hidden tags, etc).
|
|
13
|
+
- **Native Streaming** - Get raw `Readable` streams for audio or video without saving to disk first, perfect for Discord bots or streaming directly to HTTP responses.
|
|
12
14
|
- **Intelligent Caching** - TTL-based cache with automatic cleanup
|
|
13
15
|
- **Smart Quality** - Auto-reduces quality for long videos (>1h)
|
|
14
16
|
- **Container Ready** - Works in Docker/isolated environments with cookie support
|
|
@@ -362,6 +364,95 @@ if (entry) {
|
|
|
362
364
|
|
|
363
365
|
Fetches all entries from a playlist efficiently. If `resolveLinks: true` is provided in options, it will batch-resolve the direct playable streaming URLs for each valid track up to the provided `limit`.
|
|
364
366
|
|
|
367
|
+
### StalkerEngine Methods
|
|
368
|
+
|
|
369
|
+
The `StalkerEngine` allows you to deeply inspect channels, videos, and live streams to extract valuable metadata like hidden tags, exact subscriber counts, and real-time live viewers.
|
|
370
|
+
|
|
371
|
+
```typescript
|
|
372
|
+
import { YtDlpClient, StalkerEngine } from "@irithell-js/yt-play";
|
|
373
|
+
|
|
374
|
+
const client = new YtDlpClient({ cookiesFromBrowser: "firefox" });
|
|
375
|
+
const stalker = new StalkerEngine(client);
|
|
376
|
+
```
|
|
377
|
+
|
|
378
|
+
#### `stalkVideoOrLive(url: string): Promise<YtDlpVideoMetadata>`
|
|
379
|
+
|
|
380
|
+
Extracts complete metadata from a single video or live stream.
|
|
381
|
+
|
|
382
|
+
```typescript
|
|
383
|
+
const live = await stalker.stalkVideoOrLive(
|
|
384
|
+
"https://youtube.com/watch?v=LIVE_ID",
|
|
385
|
+
);
|
|
386
|
+
|
|
387
|
+
console.log(live.title);
|
|
388
|
+
console.log(live.view_count);
|
|
389
|
+
|
|
390
|
+
if (live.is_live) {
|
|
391
|
+
console.log(`Live right now with ${live.concurrent_view_count} viewers!`);
|
|
392
|
+
}
|
|
393
|
+
```
|
|
394
|
+
|
|
395
|
+
#### `stalkChannel(url: string, opts?: StalkChannelOptions): Promise<YtDlpChannelMetadata>`
|
|
396
|
+
|
|
397
|
+
Scrapes channel data. You can target specific tabs (videos, shorts, streams, popular) and set specific ranges.
|
|
398
|
+
|
|
399
|
+
```typescript
|
|
400
|
+
// Get the 5 most recent videos from a channel
|
|
401
|
+
const recentVideos = await stalker.stalkChannel(
|
|
402
|
+
"https://youtube.com/@MrBeast",
|
|
403
|
+
{
|
|
404
|
+
flat: true, // Faster extraction (basic info only)
|
|
405
|
+
tab: "videos", // "videos" | "shorts" | "streams" | "popular" | "featured"
|
|
406
|
+
endItem: 5, // Get items 1 to 5
|
|
407
|
+
},
|
|
408
|
+
);
|
|
409
|
+
|
|
410
|
+
// Get videos from index 500 to 510
|
|
411
|
+
const olderVideos = await stalker.stalkChannel("https://youtube.com/@MrBeast", {
|
|
412
|
+
flat: true,
|
|
413
|
+
tab: "videos",
|
|
414
|
+
startItem: 500,
|
|
415
|
+
endItem: 510,
|
|
416
|
+
});
|
|
417
|
+
```
|
|
418
|
+
|
|
419
|
+
### StreamEngine Methods
|
|
420
|
+
|
|
421
|
+
The `StreamEngine` bypasses disk caching completely and returns a Node.js `Readable` stream. This is ideal for Discord music bots (passing the stream directly to the voice connection) or proxying the download via an HTTP API.
|
|
422
|
+
|
|
423
|
+
It is automatically available inside the `PlayEngine` or can be initialized separately.
|
|
424
|
+
|
|
425
|
+
#### `getAudioStream(url: string, qualityKbps?: number): Promise<StreamInfo>`
|
|
426
|
+
|
|
427
|
+
Returns a readable audio stream (defaults to 128kbps M4A).
|
|
428
|
+
|
|
429
|
+
```typescript
|
|
430
|
+
import fs from "node:fs";
|
|
431
|
+
|
|
432
|
+
const { stream, quality, format } = await engine.stream.getAudioStream(
|
|
433
|
+
"https://youtube.com/watch?v=VIDEO_ID",
|
|
434
|
+
128, // preferred kbps
|
|
435
|
+
);
|
|
436
|
+
|
|
437
|
+
console.log(`Streaming ${quality} in ${format} format...`);
|
|
438
|
+
|
|
439
|
+
// Pipe directly to a file, an HTTP response, or Discord voice
|
|
440
|
+
stream.pipe(fs.createWriteStream("output.m4a"));
|
|
441
|
+
```
|
|
442
|
+
|
|
443
|
+
#### `getVideoStream(url: string, qualityP?: number): Promise<StreamInfo>`
|
|
444
|
+
|
|
445
|
+
Returns a readable video stream (defaults to 720p MP4).
|
|
446
|
+
|
|
447
|
+
```typescript
|
|
448
|
+
const { stream, quality, format } = await engine.stream.getVideoStream(
|
|
449
|
+
"https://youtube.com/watch?v=VIDEO_ID",
|
|
450
|
+
1080, // preferred height (1080p, 720p, etc)
|
|
451
|
+
);
|
|
452
|
+
|
|
453
|
+
stream.pipe(fs.createWriteStream("output.mp4"));
|
|
454
|
+
```
|
|
455
|
+
|
|
365
456
|
## Auto-Update System
|
|
366
457
|
|
|
367
458
|
The engine includes an intelligent auto-update system for yt-dlp:
|
|
@@ -565,7 +656,15 @@ Issues and PRs welcome!
|
|
|
565
656
|
|
|
566
657
|
Deprecated versions have been removed to prevent errors during use.
|
|
567
658
|
|
|
568
|
-
### 0.3.
|
|
659
|
+
### 0.3.3 (Latest)
|
|
660
|
+
|
|
661
|
+
- Added `StalkerEngine` for deep metadata extraction from videos, live streams, and entire channels.
|
|
662
|
+
- Added advanced channel scraping with support for specific tabs (`videos`, `shorts`, `streams`, `popular`) and precise item ranges.
|
|
663
|
+
- Added real-time live stream metadata detection (`is_live`, `concurrent_view_count`, etc.).
|
|
664
|
+
- Added `StreamEngine` to return raw Node.js `Readable` streams for audio and video, bypassing disk storage entirely (ideal for Discord bots and HTTP streaming).
|
|
665
|
+
- Improved error handling to gracefully return empty arrays when a requested channel tab does not exist instead of crashing.
|
|
666
|
+
|
|
667
|
+
### 0.3.2
|
|
569
668
|
|
|
570
669
|
- Added full support for YouTube Playlists (search by name or URL via `searchPlaylistBest`)
|
|
571
670
|
- Added fast batch processing to resolve direct media URLs from playlists (`resolveLinks`) with auto-replenishment
|
package/dist/index.cjs
CHANGED
|
@@ -1,7 +1,7 @@
|
|
|
1
|
-
"use strict";var
|
|
2
|
-
`)[0]}catch{}return"yt-dlp"}detectAria2c(){let t=p.default.resolve(
|
|
3
|
-
`)[0]}catch{}}async exec(t){return new Promise((r
|
|
4
|
-
`)[0]}async resolvePlaylistItems(t,r
|
|
1
|
+
"use strict";var W=Object.create;var I=Object.defineProperty;var Q=Object.getOwnPropertyDescriptor;var q=Object.getOwnPropertyNames;var H=Object.getPrototypeOf,Z=Object.prototype.hasOwnProperty;var G=(n,t)=>{for(var e in t)I(n,e,{get:t[e],enumerable:!0})},O=(n,t,e,r)=>{if(t&&typeof t=="object"||typeof t=="function")for(let i of q(t))!Z.call(n,i)&&i!==e&&I(n,i,{get:()=>t[i],enumerable:!(r=Q(t,i))||r.enumerable});return n};var h=(n,t,e)=>(e=n!=null?W(H(n)):{},O(t||!n||!n.__esModule?I(e,"default",{value:n,enumerable:!0}):e,n)),X=n=>O(I({},"__esModule",{value:!0}),n);var ot={};G(ot,{PlayEngine:()=>T,StalkerEngine:()=>A,StreamEngine:()=>v,YtDlpClient:()=>P,getYouTubeVideoId:()=>$,normalizeYoutubeUrl:()=>y,searchBest:()=>C,searchPlaylistBest:()=>z});module.exports=X(ot);var m=h(require("fs"),1),g=h(require("path"),1),J=require("child_process");var U=h(require("fs"),1),k=class{constructor(t){this.opts=t}store=new Map;cleanupTimer;get(t){return this.store.get(t)}set(t,e){this.store.set(t,e)}has(t){return this.store.has(t)}delete(t){this.cleanupEntry(t),this.store.delete(t)}markLoading(t,e){let r=this.store.get(t);r&&(r.loading=e)}setFile(t,e,r){let i=this.store.get(t);i&&(i[e]=r)}cleanupExpired(t=Date.now()){let e=0;for(let[r,i]of this.store.entries())t>i.expiresAt&&(this.delete(r),e++);return e}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 e=this.store.get(t);e&&["audio","video"].forEach(r=>{let i=e[r];if(i?.path&&U.default.existsSync(i.path))try{U.default.unlinkSync(i.path)}catch{}})}};var w=h(require("fs"),1),M=h(require("path"),1),_=h(require("os"),1);function B(n){w.default.mkdirSync(n,{recursive:!0,mode:511});try{w.default.chmodSync(n,511)}catch{}w.default.accessSync(n,w.default.constants.R_OK|w.default.constants.W_OK)}function j(n){let t=n?.trim()?n:M.default.join(_.default.tmpdir(),"yt-play"),e=M.default.resolve(t),r=M.default.join(e);return B(e),B(r),{baseDir:e,cacheDir:r}}var V=require("child_process"),p=h(require("path"),1),u=h(require("fs"),1),tt={},b;try{b=p.default.dirname(new URL(tt.url).pathname)}catch{b=typeof b<"u"?b: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.default.resolve(b,"../.."),e=[p.default.join(t,"bin","yt-dlp"),p.default.join(t,"bin","yt-dlp.exe")];for(let r of e)if(u.default.existsSync(r))return r;try{let{execSync:r}=require("child_process"),i=process.platform==="win32"?"where yt-dlp":"which yt-dlp",a=r(i,{encoding:"utf-8"}).trim();if(a)return a.split(`
|
|
2
|
+
`)[0]}catch{}return"yt-dlp"}detectAria2c(){let t=p.default.resolve(b,"../.."),e=[p.default.join(t,"bin","aria2c"),p.default.join(t,"bin","aria2c.exe")];for(let r of e)if(u.default.existsSync(r))return r;try{let{execSync:r}=require("child_process"),i=process.platform==="win32"?"where aria2c":"which aria2c",a=r(i,{encoding:"utf-8"}).trim();if(a)return a.split(`
|
|
3
|
+
`)[0]}catch{}}async exec(t){return new Promise((e,r)=>{let i=[...t];this.ffmpegPath&&(i=["--ffmpeg-location",this.ffmpegPath,...i]),this.cookiesPath&&u.default.existsSync(this.cookiesPath)&&(i=["--cookies",this.cookiesPath,...i]),this.cookiesFromBrowser&&(i=["--cookies-from-browser",this.cookiesFromBrowser,...i]);let a=(0,V.spawn)(this.binaryPath,i,{stdio:["ignore","pipe","pipe"]}),o="",s="";a.stdout.on("data",l=>{o+=l.toString()}),a.stderr.on("data",l=>{s+=l.toString()});let c=setTimeout(()=>{a.kill("SIGKILL"),r(new Error(`yt-dlp timeout after ${this.timeoutMs}ms`))},this.timeoutMs);a.on("close",l=>{clearTimeout(c),l===0?e(o):r(new Error(`yt-dlp exited with code ${l}. stderr: ${s.slice(0,500)}`))}),a.on("error",l=>{clearTimeout(c),r(l)})})}async getInfo(t){let e=await this.exec(["-J","--no-warnings","--no-playlist",t]);return JSON.parse(e)}async getPlaylistInfo(t,e={}){let r=await this.exec(["-J","--flat-playlist","--no-warnings",t]),i=JSON.parse(r),a=(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 e.resolveLinks?a=await this.resolvePlaylistItems(a,e.limit&&e.limit>0?e.limit:a.length,e.type||"audio",e.batchSize||5):e.limit&&e.limit>0&&(a=a.slice(0,e.limit)),{id:i.id,title:i.title,uploader:i.uploader,entries:a}}async getDirectUrl(t,e="audio"){let r=e==="audio"?"bestaudio[ext=m4a]/bestaudio/best":"best[ext=mp4]/best";return(await this.exec(["-f",r,"-g","--no-warnings",t])).trim().split(`
|
|
4
|
+
`)[0]}async resolvePlaylistItems(t,e,r="audio",i=5){let a=[],o=0;for(;a.length<e&&o<t.length;){let s=e-a.length,c=Math.min(i,s,t.length-o),l=t.slice(o,o+c);o+=c;let f=l.map(async d=>{try{let D=await this.getDirectUrl(d.url,r);return{...d,directUrl:D}}catch{return null}}),S=await Promise.all(f);for(let d of S)d&&a.length<e&&a.push(d)}return a}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,e,r){let i=await this.getInfo(t),o=["-f","bestaudio[ext=m4a]/bestaudio/best","-o",r,...this.buildOptimizationArgs(),t];try{await this.exec(o)}catch(c){throw[r,`${r}.part`,`${r}.ytdl`].forEach(l=>{if(u.default.existsSync(l))try{u.default.unlinkSync(l)}catch{}}),c}if(!u.default.existsSync(r))throw new Error(`yt-dlp failed to create audio file: ${r}`);let s=this.formatDuration(i.duration);return{title:i.title,author:i.uploader,duration:s,quality:`${e}kbps m4a`,filename:p.default.basename(r),downloadUrl:r}}async getVideo(t,e,r){let i=await this.getInfo(t),o=["-f",`bestvideo[height<=${e}][ext=mp4][vcodec^=avc]+bestaudio[ext=m4a]/bestvideo[height<=${e}][vcodec!=av1][vcodec!=vp9]+bestaudio/best[height<=${e}]`,"--merge-output-format","mp4","-o",r,...this.buildOptimizationArgs(),t];try{await this.exec(o)}catch(c){throw[r,`${r}.part`,`${r}.ytdl`].forEach(l=>{if(u.default.existsSync(l))try{u.default.unlinkSync(l)}catch{}}),c}if(!u.default.existsSync(r))throw new Error(`yt-dlp failed to create video file: ${r}`);let s=this.formatDuration(i.duration);return{title:i.title,author:i.uploader,duration:s,quality:`${e}p H.264`,filename:p.default.basename(r),downloadUrl:r}}formatDuration(t){if(!t)return"0:00";let e=Math.floor(t/3600),r=Math.floor(t%3600/60),i=Math.floor(t%60);return e>0?`${e}:${r.toString().padStart(2,"0")}:${i.toString().padStart(2,"0")}`:`${r}:${i.toString().padStart(2,"0")}`}};var x=h(require("yt-search"),1);function et(n){let t=(n||"").trim(),e=[...t.matchAll(/\[[^\]]*\]\((https?:\/\/[^)\s]+)\)/gi)];return e.length>0?e[0][1].trim():(t=t.replace(/^<([^>]+)>$/,"$1").trim(),t=t.replace(/^["'`](.*)["'`]$/,"$1").trim(),t)}function $(n){let t=/(?:https?:\/\/)?(?:www\.)?(?:youtube\.com\/(?:[^\/]+\/.+\/|(?:v|e(?:mbed)?)\/|.*[?&]v=|shorts\/)|youtu\.be\/)([a-zA-Z0-9_-]{11})(?:[?&]|$)/i,e=(n||"").match(t);return e?e[1]:null}function rt(n){let t=/[?&]list=([a-zA-Z0-9_-]+)/i,e=(n||"").match(t);return e?e[1]:null}function y(n){let t=et(n),e=t.match(/https?:\/\/[^\s)]+/i)?.[0]??t,r=$(e);return r?`https://www.youtube.com/watch?v=${r}`:null}async function C(n){let t=$(n);if(t){let o=await(0,x.default)({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 r=(await(0,x.default)(n))?.videos?.[0];if(!r)return null;let i=r.duration?.seconds??0,a=y(r.url)??r.url;return{title:r.title||"Untitled",author:r.author?.name||void 0,duration:r.duration?.timestamp||void 0,thumb:r.image||r.thumbnail||void 0,videoId:r.videoId,url:a,durationSeconds:i}}async function z(n){let t=rt(n);if(t)try{let i=await(0,x.default)({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 r=(await(0,x.default)(n))?.playlists?.[0];return r?{title:r.title||"Untitled",author:r.author?.name||void 0,duration:void 0,durationSeconds:0,thumb:r.image||r.thumbnail||void 0,videoId:r.listId,url:r.url||`https://www.youtube.com/playlist?list=${r.listId}`}:null}var E=require("child_process"),v=class{constructor(t){this.ytdlpClient=t}getBinaryPath(){return this.ytdlpClient.binaryPath}async getAudioStream(t,e=128){let i=["-f","bestaudio[ext=m4a]/bestaudio/best","-o","-","--no-warnings","--no-playlist",t],a=(0,E.spawn)(this.getBinaryPath(),i,{stdio:["ignore","pipe","pipe"]});return a.stderr.on("data",()=>{}),{stream:a.stdout,quality:`${e}kbps`,format:"m4a"}}async getVideoStream(t,e=720){let i=["-f",`best[height<=${e}][ext=mp4]/best`,"-o","-","--no-warnings","--no-playlist",t],a=(0,E.spawn)(this.getBinaryPath(),i,{stdio:["ignore","pipe","pipe"]});return a.stderr.on("data",()=>{}),{stream:a.stdout,quality:`${e}p`,format:"mp4"}}};var F={},R=[320,256,192,128,96,64],L=[1080,720,480,360],it=36e5;function Y(n,t){return t.includes(n)?n:t[0]}function K(n){return(n||"").replace(/[\\/:*?"<>|]/g,"").replace(/[^\w\s-]/gi,"").trim().replace(/\s+/g," ").substring(0,100)}function nt(){try{return(0,J.execSync)("which node",{encoding:"utf-8"}).trim()||null}catch{return process.execPath||null}}function at(n,t,e){try{let r;if(n)r=g.default.dirname(n);else try{let s=new URL(F.url);r=g.default.join(g.default.dirname(s.pathname),"..","bin")}catch{r=g.default.join(__dirname,"..","bin")}if(!m.default.existsSync(r))return;let i=g.default.join(r,"yt-dlp.conf"),a=[],o=nt();o&&a.push(`--js-runtimes node:${o}`),a.push("--remote-components ejs:npm"),t&&m.default.existsSync(t)?a.push(`--cookies ${t}`):e&&a.push(`--cookies-from-browser ${e}`),m.default.writeFileSync(i,a.join(`
|
|
5
5
|
`)+`
|
|
6
|
-
`,"utf-8")}catch{}}var
|
|
6
|
+
`,"utf-8")}catch{}}var T=class n{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=j(t.cacheDir),this.cache=new k({cleanupIntervalMs:this.opts.cleanupIntervalMs}),this.cache.start(),at(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-n.lastUpdateCheck<it||n.updatePromise||(n.updatePromise=(async()=>{try{n.lastUpdateCheck=t;let e=new URL("../scripts/check-ytdlp-update.mjs",F.url),{checkAndUpdate:r}=await import(e.href);await r()&&this.opts.logger?.info?.("\u2713 yt-dlp updated to latest version")}catch{this.opts.logger?.debug?.("Update check failed (will retry later)")}finally{n.updatePromise=null}})())}async forceUpdateCheck(){return n.updatePromise?(this.opts.logger?.info?.("Update already in progress, waiting..."),n.updatePromise):(n.updatePromise=(async()=>{try{this.opts.logger?.warn?.("<!> Download failed. Forcing yt-dlp update check...");let t=new URL("../scripts/check-ytdlp-update.mjs",F.url),{checkAndUpdate:e}=await import(t.href);await e()&&(this.opts.logger?.info?.("\u2713 yt-dlp updated successfully"),n.lastUpdateCheck=Date.now())}catch(t){this.opts.logger?.error?.("Failed to update yt-dlp:",t)}finally{n.updatePromise=null}})(),n.updatePromise)}generateRequestId(t="play"){return`${t}_${Date.now()}_${Math.random().toString(36).slice(2,8)}`}async search(t){return C(t)}getFromCache(t){return this.cache.get(t)}async preload(t,e){let r=y(t.url);if(!r)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 a={...t,url:r};this.cache.set(e,{metadata:a,audio:null,video:null,expiresAt:Date.now()+this.opts.ttlMs,loading:!0});let o=i?96:Y(this.opts.preferredAudioKbps,R),s=this.preloadOne(e,"audio",r,o),c=i?[s]:[s,this.preloadOne(e,"video",r,Y(this.opts.preferredVideoP,L))];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(e,!1)}async getOrDownload(t,e){let r=this.cache.get(t);if(!r)throw new Error("Request not found (cache miss).");let i=r[e];if(i?.path&&m.default.existsSync(i.path)&&i.size>0)return{metadata:r.metadata,file:i,direct:!1};let a=y(r.metadata.url);if(!a)throw new Error("Invalid YouTube URL.");let o=await this.downloadDirect(e,a);return{metadata:r.metadata,file:o,direct:!0}}async waitCache(t,e,r=8e3,i=500){let a=Date.now();for(;Date.now()-a<r;){let s=this.cache.get(t)?.[e];if(s?.path&&m.default.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,e,r,i){try{await this._executePreload(t,e,r,i)}catch(a){this.opts.logger?.error?.(`preload ${e} failed, forcing update...`,a),await this.forceUpdateCheck(),this.opts.logger?.info?.(">> Retrying preload after update..."),await this._executePreload(t,e,r,i)}}async _executePreload(t,e,r,i){let a=Math.random().toString(36).slice(2,8),o=K(`temp_${Date.now()}_${a}`),c=`${e}_${t}_${o}.${e==="audio"?"m4a":"mp4"}`,l=g.default.join(this.paths.cacheDir,c),f=e==="audio"?await this.ytdlp.getAudio(r,i,l):await this.ytdlp.getVideo(r,i,l),d=m.default.statSync(l).size,D;this.opts.preloadBuffer&&(D=await m.default.promises.readFile(l));let N={path:l,size:d,info:{quality:f.quality},buffer:D};this.cache.setFile(t,e,N),this.opts.logger?.debug?.(`preloaded ${e} ${d} bytes: ${c}`)}async downloadDirect(t,e){try{return await this._executeDownloadDirect(t,e,!1)}catch(r){return this.opts.logger?.error?.("Direct download failed:",r),await this.forceUpdateCheck(),this.opts.logger?.info?.(">> Retrying download after update..."),await this._executeDownloadDirect(t,e,!0)}}async _executeDownloadDirect(t,e,r){let i=Y(this.opts.preferredAudioKbps,R),a=Y(this.opts.preferredVideoP,L),o=t==="audio"?"m4a":"mp4",s=r?"direct_retry":"direct",c=Math.random().toString(36).slice(2,8),l=K(`${s}_${Date.now()}_${c}`),f=g.default.join(this.paths.cacheDir,`${t}_${l}.${o}`),S=t==="audio"?await this.ytdlp.getAudio(e,i,f):await this.ytdlp.getVideo(e,a,f),d=m.default.statSync(f);return{path:f,size:d.size,info:{quality:S.quality}}}};var A=class{constructor(t){this.ytdlpClient=t}async stalkVideoOrLive(t){let e=["-J","--no-warnings","--no-playlist",t],r=await this.ytdlpClient.exec(e);return JSON.parse(r)}async stalkChannel(t,e={}){let r=["-J","--no-warnings"];e.flat&&r.push("--flat-playlist"),e.playlistItems?r.push("--playlist-items",e.playlistItems):(e.startItem&&e.startItem>0&&r.push("--playlist-start",e.startItem.toString()),e.endItem&&e.endItem>0&&r.push("--playlist-end",e.endItem.toString()));let i=t.trim().replace(/\/$/,"");e.tab&&(i=i.replace(/\/(videos|shorts|streams|popular|featured)$/i,""),e.tab!=="featured"&&(i=`${i}/${e.tab}`)),r.push(i);try{let a=await this.ytdlpClient.exec(r);return JSON.parse(a)}catch(a){let o=a.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 a}}};0&&(module.exports={PlayEngine,StalkerEngine,StreamEngine,YtDlpClient,getYouTubeVideoId,normalizeYoutubeUrl,searchBest,searchPlaylistBest});
|
|
7
7
|
//# sourceMappingURL=index.cjs.map
|
package/dist/index.cjs.map
CHANGED
|
@@ -1 +1 @@
|
|
|
1
|
-
{"version":3,"sources":["../src/index.ts","../src/core/play-engine.ts","../src/core/cache.ts","../src/core/paths.ts","../src/core/ytdlp-client.ts","../src/core/youtube.ts"],"sourcesContent":["export type {\n PlayMetadata,\n CachedFile,\n PlayEngineOptions,\n MediaType,\n DownloadInfo,\n CacheEntry,\n} from \"./core/types.js\";\n\nexport { PlayEngine } from \"./core/play-engine.js\";\nexport {\n searchBest,\n searchPlaylistBest,\n normalizeYoutubeUrl,\n getYouTubeVideoId,\n} from \"./core/youtube.js\";\nexport { YtDlpClient } from \"./core/ytdlp-client.js\";\n","import fs from \"node:fs\";\nimport path from \"node:path\";\nimport { execSync } from \"node:child_process\";\nimport type {\n CacheEntry,\n CachedFile,\n DownloadInfo,\n MediaType,\n PlayEngineOptions,\n PlayMetadata,\n} from \"./types.js\";\nimport { CacheStore } from \"./cache.js\";\nimport { resolvePaths } from \"./paths.js\";\nimport { YtDlpClient } from \"./ytdlp-client.js\";\nimport { normalizeYoutubeUrl, searchBest } from \"./youtube.js\";\n\nconst AUDIO_QUALITIES = [320, 256, 192, 128, 96, 64] as const;\nconst VIDEO_QUALITIES = [1080, 720, 480, 360] as const;\nconst UPDATE_CHECK_INTERVAL = 3600000;\n\nfunction pickQuality<T extends number>(\n requested: T,\n available: readonly T[],\n): T {\n return (available as readonly number[]).includes(requested)\n ? requested\n : available[0];\n}\n\nfunction sanitizeFilename(filename: string): string {\n return (filename || \"\")\n .replace(/[\\\\/:*?\"<>|]/g, \"\")\n .replace(/[^\\w\\s-]/gi, \"\")\n .trim()\n .replace(/\\s+/g, \" \")\n .substring(0, 100);\n}\n\nfunction getNodeBinaryPath(): string | null {\n try {\n const nodePath = execSync(\"which node\", { encoding: \"utf-8\" }).trim();\n return nodePath || null;\n } catch {\n return process.execPath || null;\n }\n}\n\nfunction setupYtDlpConfig(\n ytdlpBinaryPath: string | undefined,\n cookiesPath: string | undefined,\n cookiesFromBrowser: string | undefined,\n): void {\n try {\n let binaryDir: string;\n\n if (ytdlpBinaryPath) {\n binaryDir = path.dirname(ytdlpBinaryPath);\n } else {\n try {\n const moduleUrl = new URL(import.meta.url);\n binaryDir = path.join(path.dirname(moduleUrl.pathname), \"..\", \"bin\");\n } catch {\n binaryDir = path.join(__dirname, \"..\", \"bin\");\n }\n }\n\n if (!fs.existsSync(binaryDir)) {\n return;\n }\n\n const configPath = path.join(binaryDir, \"yt-dlp.conf\");\n const configLines: string[] = [];\n const nodePath = getNodeBinaryPath();\n\n if (nodePath) {\n configLines.push(`--js-runtimes node:${nodePath}`);\n }\n\n configLines.push(\"--remote-components ejs:npm\");\n\n if (cookiesPath && fs.existsSync(cookiesPath)) {\n configLines.push(`--cookies ${cookiesPath}`);\n } else if (cookiesFromBrowser) {\n configLines.push(`--cookies-from-browser ${cookiesFromBrowser}`);\n }\n\n fs.writeFileSync(configPath, configLines.join(\"\\n\") + \"\\n\", \"utf-8\");\n } catch (error) {}\n}\n\nexport class PlayEngine {\n private readonly opts: Required<\n Pick<\n PlayEngineOptions,\n | \"ttlMs\"\n | \"maxPreloadDurationSeconds\"\n | \"preferredAudioKbps\"\n | \"preferredVideoP\"\n | \"preloadBuffer\"\n | \"cleanupIntervalMs\"\n | \"concurrentFragments\"\n >\n > &\n Pick<PlayEngineOptions, \"useAria2c\" | \"logger\">;\n\n private readonly paths: { baseDir: string; cacheDir: string };\n readonly cache: CacheStore;\n private readonly ytdlp: YtDlpClient;\n\n private static lastUpdateCheck: number = 0;\n private static updatePromise: Promise<void> | null = null;\n\n constructor(options: PlayEngineOptions = {}) {\n this.opts = {\n ttlMs: options.ttlMs ?? 3 * 60_000,\n maxPreloadDurationSeconds: options.maxPreloadDurationSeconds ?? 20 * 60,\n preferredAudioKbps: options.preferredAudioKbps ?? 128,\n preferredVideoP: options.preferredVideoP ?? 720,\n preloadBuffer: options.preloadBuffer ?? true,\n cleanupIntervalMs: options.cleanupIntervalMs ?? 30_000,\n concurrentFragments: options.concurrentFragments ?? 5,\n useAria2c: options.useAria2c,\n logger: options.logger,\n };\n\n this.paths = resolvePaths(options.cacheDir);\n this.cache = new CacheStore({\n cleanupIntervalMs: this.opts.cleanupIntervalMs,\n });\n this.cache.start();\n\n setupYtDlpConfig(\n options.ytdlpBinaryPath,\n options.cookiesPath,\n options.cookiesFromBrowser,\n );\n\n this.ytdlp = new YtDlpClient({\n binaryPath: options.ytdlpBinaryPath,\n ffmpegPath: options.ffmpegPath,\n aria2cPath: options.aria2cPath,\n useAria2c: this.opts.useAria2c,\n concurrentFragments: this.opts.concurrentFragments,\n timeoutMs: options.ytdlpTimeoutMs ?? 300_000,\n cookiesPath: options.cookiesPath,\n cookiesFromBrowser: options.cookiesFromBrowser,\n });\n\n this.backgroundUpdateCheck();\n }\n\n private backgroundUpdateCheck(): void {\n const now = Date.now();\n\n if (now - PlayEngine.lastUpdateCheck < UPDATE_CHECK_INTERVAL) {\n return;\n }\n\n if (PlayEngine.updatePromise) {\n return;\n }\n\n PlayEngine.updatePromise = (async () => {\n try {\n PlayEngine.lastUpdateCheck = now;\n const scriptPath = new URL(\n \"../scripts/check-ytdlp-update.mjs\",\n import.meta.url,\n );\n const { checkAndUpdate } = await import(scriptPath.href);\n const updated = await checkAndUpdate();\n\n if (updated) {\n this.opts.logger?.info?.(\"✓ yt-dlp updated to latest version\");\n }\n } catch (error) {\n this.opts.logger?.debug?.(\"Update check failed (will retry later)\");\n } finally {\n PlayEngine.updatePromise = null;\n }\n })();\n }\n\n private async forceUpdateCheck(): Promise<void> {\n if (PlayEngine.updatePromise) {\n this.opts.logger?.info?.(\"Update already in progress, waiting...\");\n return PlayEngine.updatePromise;\n }\n\n PlayEngine.updatePromise = (async () => {\n try {\n this.opts.logger?.warn?.(\n \"<!> Download failed. Forcing yt-dlp update check...\",\n );\n\n const scriptPath = new URL(\n \"../scripts/check-ytdlp-update.mjs\",\n import.meta.url,\n );\n const { checkAndUpdate } = await import(scriptPath.href);\n const updated = await checkAndUpdate();\n\n if (updated) {\n this.opts.logger?.info?.(\"✓ yt-dlp updated successfully\");\n PlayEngine.lastUpdateCheck = Date.now();\n }\n } catch (error) {\n this.opts.logger?.error?.(\"Failed to update yt-dlp:\", error);\n } finally {\n PlayEngine.updatePromise = null;\n }\n })();\n\n return PlayEngine.updatePromise;\n }\n\n generateRequestId(prefix = \"play\"): string {\n return `${prefix}_${Date.now()}_${Math.random().toString(36).slice(2, 8)}`;\n }\n\n async search(query: string): Promise<PlayMetadata | null> {\n return searchBest(query);\n }\n\n getFromCache(requestId: string): CacheEntry | undefined {\n return this.cache.get(requestId);\n }\n\n async preload(metadata: PlayMetadata, requestId: string): Promise<void> {\n const normalized = normalizeYoutubeUrl(metadata.url);\n if (!normalized) throw new Error(\"Invalid YouTube URL.\");\n\n const isLongVideo = metadata.durationSeconds > 3600;\n\n if (metadata.durationSeconds > this.opts.maxPreloadDurationSeconds) {\n this.opts.logger?.warn?.(\n `Video too long for preload (${Math.floor(metadata.durationSeconds / 60)}min). Will use direct download with reduced quality.`,\n );\n }\n\n const normalizedMeta: PlayMetadata = { ...metadata, url: normalized };\n\n this.cache.set(requestId, {\n metadata: normalizedMeta,\n audio: null,\n video: null,\n expiresAt: Date.now() + this.opts.ttlMs,\n loading: true,\n });\n\n const audioKbps = isLongVideo\n ? 96\n : pickQuality(this.opts.preferredAudioKbps, AUDIO_QUALITIES);\n\n const audioTask = this.preloadOne(\n requestId,\n \"audio\",\n normalized,\n audioKbps,\n );\n\n const tasks = isLongVideo\n ? [audioTask]\n : [\n audioTask,\n this.preloadOne(\n requestId,\n \"video\",\n normalized,\n pickQuality(this.opts.preferredVideoP, VIDEO_QUALITIES),\n ),\n ];\n\n if (isLongVideo) {\n this.opts.logger?.info?.(\n `Long video detected (${Math.floor(metadata.durationSeconds / 60)}min). Audio only mode (96kbps).`,\n );\n }\n\n await Promise.allSettled(tasks);\n this.cache.markLoading(requestId, false);\n }\n\n async getOrDownload(\n requestId: string,\n type: MediaType,\n ): Promise<{ metadata: PlayMetadata; file: CachedFile; direct: boolean }> {\n const entry = this.cache.get(requestId);\n if (!entry) throw new Error(\"Request not found (cache miss).\");\n\n const cached = entry[type];\n if (cached?.path && fs.existsSync(cached.path) && cached.size > 0) {\n return { metadata: entry.metadata, file: cached, direct: false };\n }\n\n const normalized = normalizeYoutubeUrl(entry.metadata.url);\n if (!normalized) throw new Error(\"Invalid YouTube URL.\");\n\n const directFile = await this.downloadDirect(type, normalized);\n return { metadata: entry.metadata, file: directFile, direct: true };\n }\n\n async waitCache(\n requestId: string,\n type: MediaType,\n timeoutMs = 8_000,\n intervalMs = 500,\n ): Promise<CachedFile | null> {\n const started = Date.now();\n while (Date.now() - started < timeoutMs) {\n const entry = this.cache.get(requestId);\n const f = entry?.[type];\n if (f?.path && fs.existsSync(f.path) && f.size > 0) return f;\n await new Promise((r) => setTimeout(r, intervalMs));\n }\n return null;\n }\n\n cleanup(requestId: string): void {\n this.cache.delete(requestId);\n }\n\n private async preloadOne(\n requestId: string,\n type: MediaType,\n youtubeUrl: string,\n quality: number,\n ): Promise<void> {\n try {\n await this._executePreload(requestId, type, youtubeUrl, quality);\n } catch (err) {\n this.opts.logger?.error?.(\n `preload ${type} failed, forcing update...`,\n err,\n );\n await this.forceUpdateCheck();\n\n this.opts.logger?.info?.(\">> Retrying preload after update...\");\n await this._executePreload(requestId, type, youtubeUrl, quality);\n }\n }\n\n private async _executePreload(\n requestId: string,\n type: MediaType,\n youtubeUrl: string,\n quality: number,\n ): Promise<void> {\n const uniqueSuffix = Math.random().toString(36).slice(2, 8);\n const safeTitle = sanitizeFilename(`temp_${Date.now()}_${uniqueSuffix}`);\n const ext = type === \"audio\" ? \"m4a\" : \"mp4\";\n const filename = `${type}_${requestId}_${safeTitle}.${ext}`;\n const filePath = path.join(this.paths.cacheDir, filename);\n\n const info: DownloadInfo =\n type === \"audio\"\n ? await this.ytdlp.getAudio(youtubeUrl, quality, filePath)\n : await this.ytdlp.getVideo(youtubeUrl, quality, filePath);\n\n const stats = fs.statSync(filePath);\n const size = stats.size;\n\n let buffer: Buffer | undefined;\n if (this.opts.preloadBuffer) {\n buffer = await fs.promises.readFile(filePath);\n }\n\n const cached: CachedFile = {\n path: filePath,\n size,\n info: { quality: info.quality },\n buffer,\n };\n\n this.cache.setFile(requestId, type, cached);\n this.opts.logger?.debug?.(`preloaded ${type} ${size} bytes: ${filename}`);\n }\n\n private async downloadDirect(\n type: MediaType,\n youtubeUrl: string,\n ): Promise<CachedFile> {\n try {\n return await this._executeDownloadDirect(type, youtubeUrl, false);\n } catch (err) {\n this.opts.logger?.error?.(\"Direct download failed:\", err);\n await this.forceUpdateCheck();\n\n this.opts.logger?.info?.(\">> Retrying download after update...\");\n return await this._executeDownloadDirect(type, youtubeUrl, true);\n }\n }\n\n private async _executeDownloadDirect(\n type: MediaType,\n youtubeUrl: string,\n isRetry: boolean,\n ): Promise<CachedFile> {\n const audioKbps = pickQuality(\n this.opts.preferredAudioKbps,\n AUDIO_QUALITIES,\n );\n const videoP = pickQuality(this.opts.preferredVideoP, VIDEO_QUALITIES);\n const ext = type === \"audio\" ? \"m4a\" : \"mp4\";\n const prefix = isRetry ? \"direct_retry\" : \"direct\";\n const uniqueSuffix = Math.random().toString(36).slice(2, 8);\n const safeTitle = sanitizeFilename(\n `${prefix}_${Date.now()}_${uniqueSuffix}`,\n );\n const filePath = path.join(\n this.paths.cacheDir,\n `${type}_${safeTitle}.${ext}`,\n );\n\n const info =\n type === \"audio\"\n ? await this.ytdlp.getAudio(youtubeUrl, audioKbps, filePath)\n : await this.ytdlp.getVideo(youtubeUrl, videoP, filePath);\n\n const stats = fs.statSync(filePath);\n return {\n path: filePath,\n size: stats.size,\n info: { quality: info.quality },\n };\n }\n}\n","import fs from \"node:fs\";\n\nimport type { CacheEntry, MediaType } from \"./types.js\";\n\nexport class CacheStore {\n private readonly store = new Map<string, CacheEntry>();\n private cleanupTimer?: NodeJS.Timeout;\n\n constructor(\n private readonly opts: {\n cleanupIntervalMs: number;\n }\n ) {}\n\n get(requestId: string): CacheEntry | undefined {\n return this.store.get(requestId);\n }\n\n set(requestId: string, entry: CacheEntry): void {\n this.store.set(requestId, entry);\n }\n\n has(requestId: string): boolean {\n return this.store.has(requestId);\n }\n\n delete(requestId: string): void {\n this.cleanupEntry(requestId);\n this.store.delete(requestId);\n }\n\n markLoading(requestId: string, loading: boolean): void {\n const e = this.store.get(requestId);\n if (e) e.loading = loading;\n }\n\n setFile(\n requestId: string,\n type: MediaType,\n file: CacheEntry[MediaType]\n ): void {\n const e = this.store.get(requestId);\n if (!e) return;\n e[type] = file as any;\n }\n\n cleanupExpired(now = Date.now()): number {\n let removed = 0;\n for (const [requestId, entry] of this.store.entries()) {\n if (now > entry.expiresAt) {\n this.delete(requestId);\n removed++;\n }\n }\n return removed;\n }\n\n start(): void {\n if (this.cleanupTimer) return;\n\n this.cleanupTimer = setInterval(() => {\n this.cleanupExpired(Date.now());\n }, this.opts.cleanupIntervalMs);\n\n this.cleanupTimer.unref();\n }\n\n stop(): void {\n if (!this.cleanupTimer) return;\n clearInterval(this.cleanupTimer);\n this.cleanupTimer = undefined;\n }\n\n private cleanupEntry(requestId: string) {\n const entry = this.store.get(requestId);\n if (!entry) return;\n\n ([\"audio\", \"video\"] as const).forEach((type) => {\n const f = entry[type];\n if (f?.path && fs.existsSync(f.path)) {\n try {\n fs.unlinkSync(f.path);\n } catch {\n // ignore\n }\n }\n });\n }\n}\n","import fs from \"node:fs\";\nimport path from \"node:path\";\nimport os from \"node:os\";\n\nexport interface ResolvedPaths {\n baseDir: string;\n cacheDir: string;\n}\n\nexport function ensureDirSync(dirPath: string) {\n fs.mkdirSync(dirPath, { recursive: true, mode: 0o777 });\n\n try {\n fs.chmodSync(dirPath, 0o777);\n } catch {\n // ignore\n }\n\n fs.accessSync(dirPath, fs.constants.R_OK | fs.constants.W_OK);\n}\n\nexport function resolvePaths(cacheDir?: string): ResolvedPaths {\n const baseDir = cacheDir?.trim()\n ? cacheDir\n : path.join(os.tmpdir(), \"yt-play\");\n const resolvedBase = path.resolve(baseDir);\n\n const resolvedCache = path.join(resolvedBase);\n\n ensureDirSync(resolvedBase);\n ensureDirSync(resolvedCache);\n\n return {\n baseDir: resolvedBase,\n cacheDir: resolvedCache,\n };\n}\n","import { spawn } from \"node:child_process\";\nimport path from \"node:path\";\nimport fs from \"node:fs\";\nimport type { DownloadInfo } from \"./types.js\";\n\nlet __dirname: string;\ntry {\n // @ts-ignore\n __dirname = path.dirname(new URL(import.meta.url).pathname);\n} catch {\n // @ts-ignore\n __dirname = typeof __dirname !== \"undefined\" ? __dirname : process.cwd();\n}\n\nexport interface YtDlpClientOptions {\n binaryPath?: string;\n ffmpegPath?: string;\n aria2cPath?: string;\n timeoutMs?: number;\n useAria2c?: boolean;\n concurrentFragments?: number;\n cookiesPath?: string;\n cookiesFromBrowser?: string;\n}\n\nexport interface YtDlpVideoInfo {\n id: string;\n title: string;\n uploader?: string;\n duration: number;\n thumbnail?: string;\n}\n\nexport interface YtDlpPlaylistItem {\n id: string;\n title: string;\n url: string;\n duration?: number;\n uploader?: string;\n directUrl?: string;\n}\n\nexport interface YtDlpResolvedItem extends YtDlpPlaylistItem {\n directUrl: string;\n}\n\nexport interface YtDlpPlaylistInfo {\n id: string;\n title: string;\n uploader?: string;\n entries: YtDlpPlaylistItem[] | YtDlpResolvedItem[];\n}\n\nexport interface YtDlpPlaylistOptions {\n limit?: number;\n resolveLinks?: boolean;\n type?: \"audio\" | \"video\" | \"both\";\n batchSize?: number;\n}\n\nexport class YtDlpClient {\n private readonly binaryPath: string;\n private readonly ffmpegPath?: string;\n private readonly aria2cPath?: string;\n private readonly timeoutMs: number;\n private readonly useAria2c: boolean;\n private readonly concurrentFragments: number;\n private readonly cookiesPath?: string;\n private readonly cookiesFromBrowser?: string;\n\n constructor(opts: YtDlpClientOptions = {}) {\n this.binaryPath = opts.binaryPath || this.detectYtDlp();\n this.ffmpegPath = opts.ffmpegPath;\n this.timeoutMs = opts.timeoutMs ?? 300_000;\n this.concurrentFragments = opts.concurrentFragments ?? 5;\n this.cookiesPath = opts.cookiesPath;\n this.cookiesFromBrowser = opts.cookiesFromBrowser;\n\n this.aria2cPath = opts.aria2cPath || this.detectAria2c();\n this.useAria2c = opts.useAria2c ?? !!this.aria2cPath;\n }\n\n private detectYtDlp(): string {\n const packageRoot = path.resolve(__dirname, \"../..\");\n const bundledPaths = [\n path.join(packageRoot, \"bin\", \"yt-dlp\"),\n path.join(packageRoot, \"bin\", \"yt-dlp.exe\"),\n ];\n\n for (const p of bundledPaths) {\n if (fs.existsSync(p)) {\n return p;\n }\n }\n\n try {\n const { execSync } = require(\"node:child_process\");\n const cmd =\n process.platform === \"win32\" ? \"where yt-dlp\" : \"which yt-dlp\";\n const result = execSync(cmd, { encoding: \"utf-8\" }).trim();\n if (result) return result.split(\"\\n\")[0];\n } catch {}\n\n return \"yt-dlp\";\n }\n\n private detectAria2c(): string | undefined {\n const packageRoot = path.resolve(__dirname, \"../..\");\n const bundledPaths = [\n path.join(packageRoot, \"bin\", \"aria2c\"),\n path.join(packageRoot, \"bin\", \"aria2c.exe\"),\n ];\n\n for (const p of bundledPaths) {\n if (fs.existsSync(p)) {\n return p;\n }\n }\n\n try {\n const { execSync } = require(\"node:child_process\");\n const cmd =\n process.platform === \"win32\" ? \"where aria2c\" : \"which aria2c\";\n const result = execSync(cmd, { encoding: \"utf-8\" }).trim();\n if (result) return result.split(\"\\n\")[0];\n } catch {}\n\n return undefined;\n }\n\n private async exec(args: string[]): Promise<string> {\n return new Promise((resolve, reject) => {\n let allArgs = [...args];\n\n if (this.ffmpegPath) {\n allArgs = [\"--ffmpeg-location\", this.ffmpegPath, ...allArgs];\n }\n\n if (this.cookiesPath && fs.existsSync(this.cookiesPath)) {\n allArgs = [\"--cookies\", this.cookiesPath, ...allArgs];\n }\n\n if (this.cookiesFromBrowser) {\n allArgs = [\n \"--cookies-from-browser\",\n this.cookiesFromBrowser,\n ...allArgs,\n ];\n }\n\n const proc = spawn(this.binaryPath, allArgs, {\n stdio: [\"ignore\", \"pipe\", \"pipe\"],\n });\n\n let stdout = \"\";\n let stderr = \"\";\n\n proc.stdout.on(\"data\", (chunk) => {\n stdout += chunk.toString();\n });\n\n proc.stderr.on(\"data\", (chunk) => {\n stderr += chunk.toString();\n });\n\n const timer = setTimeout(() => {\n proc.kill(\"SIGKILL\");\n reject(new Error(`yt-dlp timeout after ${this.timeoutMs}ms`));\n }, this.timeoutMs);\n\n proc.on(\"close\", (code) => {\n clearTimeout(timer);\n if (code === 0) {\n resolve(stdout);\n } else {\n reject(\n new Error(\n `yt-dlp exited with code ${code}. stderr: ${stderr.slice(0, 500)}`,\n ),\n );\n }\n });\n\n proc.on(\"error\", (err) => {\n clearTimeout(timer);\n reject(err);\n });\n });\n }\n\n async getInfo(youtubeUrl: string): Promise<YtDlpVideoInfo> {\n const stdout = await this.exec([\n \"-J\",\n \"--no-warnings\",\n \"--no-playlist\",\n youtubeUrl,\n ]);\n const info = JSON.parse(stdout) as YtDlpVideoInfo;\n return info;\n }\n\n async getPlaylistInfo(\n youtubeUrl: string,\n opts: YtDlpPlaylistOptions = {},\n ): Promise<YtDlpPlaylistInfo> {\n const stdout = await this.exec([\n \"-J\",\n \"--flat-playlist\",\n \"--no-warnings\",\n youtubeUrl,\n ]);\n\n const info = JSON.parse(stdout);\n\n let entries: YtDlpPlaylistItem[] = (info.entries || [])\n .filter((entry: any) => {\n const t = entry.title || \"\";\n return (\n entry.id &&\n t &&\n !t.includes(\"[Deleted video]\") &&\n !t.includes(\"[Private video]\")\n );\n })\n .map((entry: any) => ({\n id: entry.id,\n title: entry.title,\n url: `https://www.youtube.com/watch?v=${entry.id}`,\n duration: entry.duration,\n uploader: entry.uploader || info.uploader,\n }));\n\n if (opts.resolveLinks) {\n entries = await this.resolvePlaylistItems(\n entries,\n opts.limit && opts.limit > 0 ? opts.limit : entries.length,\n opts.type || \"audio\",\n opts.batchSize || 5,\n );\n } else if (opts.limit && opts.limit > 0) {\n entries = entries.slice(0, opts.limit);\n }\n\n return {\n id: info.id,\n title: info.title,\n uploader: info.uploader,\n entries,\n };\n }\n\n async getDirectUrl(\n youtubeUrl: string,\n type: \"audio\" | \"video\" | \"both\" = \"audio\",\n ): Promise<string> {\n const format =\n type === \"audio\"\n ? \"bestaudio[ext=m4a]/bestaudio/best\"\n : \"best[ext=mp4]/best\";\n\n const stdout = await this.exec([\n \"-f\",\n format,\n \"-g\",\n \"--no-warnings\",\n youtubeUrl,\n ]);\n\n return stdout.trim().split(\"\\n\")[0];\n }\n\n async resolvePlaylistItems(\n entries: YtDlpPlaylistItem[],\n limit: number,\n type: \"audio\" | \"video\" | \"both\" = \"audio\",\n batchSize: number = 5,\n ): Promise<YtDlpResolvedItem[]> {\n const resolved: YtDlpResolvedItem[] = [];\n let currentIndex = 0;\n\n // Continua processando até atingir o limite solicitado ou a playlist acabar\n while (resolved.length < limit && currentIndex < entries.length) {\n const needed = limit - resolved.length;\n const takeCount = Math.min(\n batchSize,\n needed,\n entries.length - currentIndex,\n );\n\n const batch = entries.slice(currentIndex, currentIndex + takeCount);\n currentIndex += takeCount;\n\n const promises = batch.map(async (item) => {\n try {\n const directUrl = await this.getDirectUrl(item.url, type);\n return { ...item, directUrl } as YtDlpResolvedItem;\n } catch (err) {\n return null; // Ignora os que falharem\n }\n });\n\n const results = await Promise.all(promises);\n\n for (const res of results) {\n if (res && resolved.length < limit) {\n resolved.push(res);\n }\n }\n }\n\n return resolved;\n }\n\n private buildOptimizationArgs(): string[] {\n const args: string[] = [\n \"--no-warnings\",\n \"--no-playlist\",\n \"--no-check-certificates\",\n \"--concurrent-fragments\",\n String(this.concurrentFragments),\n ];\n\n if (this.useAria2c && this.aria2cPath) {\n args.push(\"--downloader\", this.aria2cPath);\n args.push(\"--downloader-args\", \"aria2c:-x 16 -s 16 -k 1M\");\n }\n\n return args;\n }\n\n async getAudio(\n youtubeUrl: string,\n qualityKbps: number,\n outputPath: string,\n ): Promise<DownloadInfo> {\n const info = await this.getInfo(youtubeUrl);\n const format = \"bestaudio[ext=m4a]/bestaudio/best\";\n\n const args = [\n \"-f\",\n format,\n \"-o\",\n outputPath,\n ...this.buildOptimizationArgs(),\n youtubeUrl,\n ];\n\n try {\n await this.exec(args);\n } catch (err) {\n [outputPath, `${outputPath}.part`, `${outputPath}.ytdl`].forEach((f) => {\n if (fs.existsSync(f)) {\n try {\n fs.unlinkSync(f);\n } catch {}\n }\n });\n throw err;\n }\n\n if (!fs.existsSync(outputPath)) {\n throw new Error(`yt-dlp failed to create audio file: ${outputPath}`);\n }\n\n const duration = this.formatDuration(info.duration);\n\n return {\n title: info.title,\n author: info.uploader,\n duration,\n quality: `${qualityKbps}kbps m4a`,\n filename: path.basename(outputPath),\n downloadUrl: outputPath,\n };\n }\n\n async getVideo(\n youtubeUrl: string,\n qualityP: number,\n outputPath: string,\n ): Promise<DownloadInfo> {\n const info = await this.getInfo(youtubeUrl);\n\n const format = `bestvideo[height<=${qualityP}][ext=mp4][vcodec^=avc]+bestaudio[ext=m4a]/bestvideo[height<=${qualityP}][vcodec!=av1][vcodec!=vp9]+bestaudio/best[height<=${qualityP}]`;\n\n const args = [\n \"-f\",\n format,\n \"--merge-output-format\",\n \"mp4\",\n \"-o\",\n outputPath,\n ...this.buildOptimizationArgs(),\n youtubeUrl,\n ];\n\n try {\n await this.exec(args);\n } catch (err) {\n [outputPath, `${outputPath}.part`, `${outputPath}.ytdl`].forEach((f) => {\n if (fs.existsSync(f)) {\n try {\n fs.unlinkSync(f);\n } catch {}\n }\n });\n throw err;\n }\n\n if (!fs.existsSync(outputPath)) {\n throw new Error(`yt-dlp failed to create video file: ${outputPath}`);\n }\n\n const duration = this.formatDuration(info.duration);\n\n return {\n title: info.title,\n author: info.uploader,\n duration,\n quality: `${qualityP}p H.264`,\n filename: path.basename(outputPath),\n downloadUrl: outputPath,\n };\n }\n\n private formatDuration(seconds: number): string {\n if (!seconds) return \"0:00\";\n const h = Math.floor(seconds / 3600);\n const m = Math.floor((seconds % 3600) / 60);\n const s = Math.floor(seconds % 60);\n if (h > 0) {\n return `${h}:${m.toString().padStart(2, \"0\")}:${s.toString().padStart(2, \"0\")}`;\n }\n return `${m}:${s.toString().padStart(2, \"0\")}`;\n }\n}\n","import yts from \"yt-search\";\n\nimport type { PlayMetadata } from \"./types.js\";\n\nexport function stripWeirdUrlWrappers(input: string): string {\n let s = (input || \"\").trim();\n const mdAll = [...s.matchAll(/\\[[^\\]]*\\]\\((https?:\\/\\/[^)\\s]+)\\)/gi)];\n if (mdAll.length > 0) return mdAll[0][1].trim();\n s = s.replace(/^<([^>]+)>$/, \"$1\").trim();\n s = s.replace(/^[\"'`](.*)[\"'`]$/, \"$1\").trim();\n\n return s;\n}\n\nexport function getYouTubeVideoId(input: string): string | null {\n const regex =\n /(?:https?:\\/\\/)?(?:www\\.)?(?:youtube\\.com\\/(?:[^\\/]+\\/.+\\/|(?:v|e(?:mbed)?)\\/|.*[?&]v=|shorts\\/)|youtu\\.be\\/)([a-zA-Z0-9_-]{11})(?:[?&]|$)/i;\n\n const match = (input || \"\").match(regex);\n return match ? match[1] : null;\n}\n\nexport function getYouTubePlaylistId(input: string): string | null {\n const regex = /[?&]list=([a-zA-Z0-9_-]+)/i;\n const match = (input || \"\").match(regex);\n return match ? match[1] : null;\n}\n\nexport function normalizeYoutubeUrl(input: string): string | null {\n const cleaned0 = stripWeirdUrlWrappers(input);\n\n const firstUrl = cleaned0.match(/https?:\\/\\/[^\\s)]+/i)?.[0] ?? cleaned0;\n\n const id = getYouTubeVideoId(firstUrl);\n if (!id) return null;\n\n return `https://www.youtube.com/watch?v=${id}`;\n}\n\nexport async function searchBest(query: string): Promise<PlayMetadata | null> {\n const videoId = getYouTubeVideoId(query);\n\n if (videoId) {\n const video = await yts({ videoId });\n\n if (!video) return null;\n\n const durationSeconds = video.duration?.seconds ?? 0;\n const normalizedUrl = normalizeYoutubeUrl(video.url) ?? video.url;\n\n return {\n title: video.title || \"Untitled\",\n author: video.author?.name || undefined,\n duration: video.duration?.timestamp || undefined,\n thumb: video.image || video.thumbnail || undefined,\n videoId: video.videoId,\n url: normalizedUrl,\n durationSeconds,\n };\n }\n\n const result = await yts(query);\n const v = result?.videos?.[0];\n if (!v) return null;\n\n const durationSeconds = v.duration?.seconds ?? 0;\n const normalizedUrl = normalizeYoutubeUrl(v.url) ?? v.url;\n\n return {\n title: v.title || \"Untitled\",\n author: v.author?.name || undefined,\n duration: v.duration?.timestamp || undefined,\n thumb: v.image || v.thumbnail || undefined,\n videoId: v.videoId,\n url: normalizedUrl,\n durationSeconds,\n };\n}\n\nexport async function searchPlaylistBest(\n query: string,\n): Promise<PlayMetadata | null> {\n const listId = getYouTubePlaylistId(query);\n\n if (listId) {\n try {\n const list = await yts({ listId });\n\n if (!list) return null;\n\n return {\n title: list.title || \"Untitled\",\n author: list.author?.name || undefined,\n duration: undefined,\n durationSeconds: 0,\n thumb: list.image || list.thumbnail || undefined,\n videoId: listId,\n url: list.url || `https://www.youtube.com/playlist?list=${listId}`,\n };\n } catch (err) {}\n }\n\n const result = await yts(query);\n const p = result?.playlists?.[0];\n if (!p) return null;\n\n return {\n title: p.title || \"Untitled\",\n author: p.author?.name || undefined,\n duration: undefined,\n durationSeconds: 0,\n thumb: p.image || p.thumbnail || undefined,\n videoId: p.listId,\n url: p.url || `https://www.youtube.com/playlist?list=${p.listId}`,\n };\n}\n"],"mappings":"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","names":["index_exports","__export","PlayEngine","YtDlpClient","getYouTubeVideoId","normalizeYoutubeUrl","searchBest","searchPlaylistBest","__toCommonJS","import_node_fs","import_node_path","import_node_child_process","import_node_fs","CacheStore","opts","requestId","entry","loading","type","file","e","now","removed","f","fs","import_node_fs","import_node_path","import_node_os","ensureDirSync","dirPath","fs","resolvePaths","cacheDir","baseDir","path","os","resolvedBase","resolvedCache","import_node_child_process","import_node_path","import_node_fs","import_meta","__dirname","path","YtDlpClient","opts","packageRoot","bundledPaths","p","fs","execSync","cmd","result","args","resolve","reject","allArgs","proc","stdout","stderr","chunk","timer","code","err","youtubeUrl","info","entries","entry","t","type","format","limit","batchSize","resolved","currentIndex","needed","takeCount","batch","promises","item","directUrl","results","res","qualityKbps","outputPath","f","duration","qualityP","seconds","h","m","s","import_yt_search","stripWeirdUrlWrappers","input","s","mdAll","getYouTubeVideoId","regex","match","getYouTubePlaylistId","normalizeYoutubeUrl","cleaned0","firstUrl","id","searchBest","query","videoId","video","yts","durationSeconds","normalizedUrl","v","searchPlaylistBest","listId","list","p","import_meta","AUDIO_QUALITIES","VIDEO_QUALITIES","UPDATE_CHECK_INTERVAL","pickQuality","requested","available","sanitizeFilename","filename","getNodeBinaryPath","setupYtDlpConfig","ytdlpBinaryPath","cookiesPath","cookiesFromBrowser","binaryDir","path","moduleUrl","fs","configPath","configLines","nodePath","PlayEngine","_PlayEngine","options","resolvePaths","CacheStore","YtDlpClient","now","scriptPath","checkAndUpdate","error","prefix","query","searchBest","requestId","metadata","normalized","normalizeYoutubeUrl","isLongVideo","normalizedMeta","audioKbps","audioTask","tasks","type","entry","cached","directFile","timeoutMs","intervalMs","started","f","r","youtubeUrl","quality","err","uniqueSuffix","safeTitle","filePath","info","size","buffer","isRetry","videoP","ext","stats"]}
|
|
1
|
+
{"version":3,"sources":["../src/index.ts","../src/core/play-engine.ts","../src/core/cache.ts","../src/core/paths.ts","../src/core/ytdlp-client.ts","../src/core/youtube.ts","../src/core/stream.ts","../src/core/stalker.ts"],"sourcesContent":["export type {\n PlayMetadata,\n CachedFile,\n PlayEngineOptions,\n MediaType,\n DownloadInfo,\n CacheEntry,\n StreamInfo,\n YtDlpVideoMetadata,\n YtDlpChannelMetadata,\n YtDlpFlatEntry,\n StalkChannelOptions,\n} from \"./core/types.js\";\n\nexport { PlayEngine } from \"./core/play-engine.js\";\nexport { StalkerEngine } from \"./core/stalker.js\";\nexport { StreamEngine } from \"./core/stream.js\";\nexport {\n searchBest,\n searchPlaylistBest,\n normalizeYoutubeUrl,\n getYouTubeVideoId,\n} from \"./core/youtube.js\";\nexport { YtDlpClient } from \"./core/ytdlp-client.js\";\n","import fs from \"node:fs\";\nimport path from \"node:path\";\nimport { execSync } from \"node:child_process\";\nimport type {\n CacheEntry,\n CachedFile,\n DownloadInfo,\n MediaType,\n PlayEngineOptions,\n PlayMetadata,\n} from \"./types.js\";\nimport { CacheStore } from \"./cache.js\";\nimport { resolvePaths } from \"./paths.js\";\nimport { YtDlpClient } from \"./ytdlp-client.js\";\nimport { normalizeYoutubeUrl, searchBest } from \"./youtube.js\";\nimport { StreamEngine } from \"./stream.js\";\n\nconst AUDIO_QUALITIES = [320, 256, 192, 128, 96, 64] as const;\nconst VIDEO_QUALITIES = [1080, 720, 480, 360] as const;\nconst UPDATE_CHECK_INTERVAL = 3600000;\n\nfunction pickQuality<T extends number>(\n requested: T,\n available: readonly T[],\n): T {\n return (available as readonly number[]).includes(requested)\n ? requested\n : available[0];\n}\n\nfunction sanitizeFilename(filename: string): string {\n return (filename || \"\")\n .replace(/[\\\\/:*?\"<>|]/g, \"\")\n .replace(/[^\\w\\s-]/gi, \"\")\n .trim()\n .replace(/\\s+/g, \" \")\n .substring(0, 100);\n}\n\nfunction getNodeBinaryPath(): string | null {\n try {\n const nodePath = execSync(\"which node\", { encoding: \"utf-8\" }).trim();\n return nodePath || null;\n } catch {\n return process.execPath || null;\n }\n}\n\nfunction setupYtDlpConfig(\n ytdlpBinaryPath: string | undefined,\n cookiesPath: string | undefined,\n cookiesFromBrowser: string | undefined,\n): void {\n try {\n let binaryDir: string;\n\n if (ytdlpBinaryPath) {\n binaryDir = path.dirname(ytdlpBinaryPath);\n } else {\n try {\n const moduleUrl = new URL(import.meta.url);\n binaryDir = path.join(path.dirname(moduleUrl.pathname), \"..\", \"bin\");\n } catch {\n binaryDir = path.join(__dirname, \"..\", \"bin\");\n }\n }\n\n if (!fs.existsSync(binaryDir)) {\n return;\n }\n\n const configPath = path.join(binaryDir, \"yt-dlp.conf\");\n const configLines: string[] = [];\n const nodePath = getNodeBinaryPath();\n\n if (nodePath) {\n configLines.push(`--js-runtimes node:${nodePath}`);\n }\n\n configLines.push(\"--remote-components ejs:npm\");\n\n if (cookiesPath && fs.existsSync(cookiesPath)) {\n configLines.push(`--cookies ${cookiesPath}`);\n } else if (cookiesFromBrowser) {\n configLines.push(`--cookies-from-browser ${cookiesFromBrowser}`);\n }\n\n fs.writeFileSync(configPath, configLines.join(\"\\n\") + \"\\n\", \"utf-8\");\n } catch (error) {}\n}\n\nexport class PlayEngine {\n private readonly opts: Required<\n Pick<\n PlayEngineOptions,\n | \"ttlMs\"\n | \"maxPreloadDurationSeconds\"\n | \"preferredAudioKbps\"\n | \"preferredVideoP\"\n | \"preloadBuffer\"\n | \"cleanupIntervalMs\"\n | \"concurrentFragments\"\n >\n > &\n Pick<PlayEngineOptions, \"useAria2c\" | \"logger\">;\n\n private readonly paths: { baseDir: string; cacheDir: string };\n readonly cache: CacheStore;\n private readonly ytdlp: YtDlpClient;\n\n private static lastUpdateCheck: number = 0;\n private static updatePromise: Promise<void> | null = null;\n\n constructor(options: PlayEngineOptions = {}) {\n this.opts = {\n ttlMs: options.ttlMs ?? 3 * 60_000,\n maxPreloadDurationSeconds: options.maxPreloadDurationSeconds ?? 20 * 60,\n preferredAudioKbps: options.preferredAudioKbps ?? 128,\n preferredVideoP: options.preferredVideoP ?? 720,\n preloadBuffer: options.preloadBuffer ?? true,\n cleanupIntervalMs: options.cleanupIntervalMs ?? 30_000,\n concurrentFragments: options.concurrentFragments ?? 5,\n useAria2c: options.useAria2c,\n logger: options.logger,\n };\n\n this.paths = resolvePaths(options.cacheDir);\n this.cache = new CacheStore({\n cleanupIntervalMs: this.opts.cleanupIntervalMs,\n });\n this.cache.start();\n\n setupYtDlpConfig(\n options.ytdlpBinaryPath,\n options.cookiesPath,\n options.cookiesFromBrowser,\n );\n\n this.ytdlp = new YtDlpClient({\n binaryPath: options.ytdlpBinaryPath,\n ffmpegPath: options.ffmpegPath,\n aria2cPath: options.aria2cPath,\n useAria2c: this.opts.useAria2c,\n concurrentFragments: this.opts.concurrentFragments,\n timeoutMs: options.ytdlpTimeoutMs ?? 300_000,\n cookiesPath: options.cookiesPath,\n cookiesFromBrowser: options.cookiesFromBrowser,\n });\n\n this.stream = new StreamEngine(this.ytdlp);\n\n this.backgroundUpdateCheck();\n }\n\n public readonly stream: StreamEngine;\n\n private backgroundUpdateCheck(): void {\n const now = Date.now();\n\n if (now - PlayEngine.lastUpdateCheck < UPDATE_CHECK_INTERVAL) {\n return;\n }\n\n if (PlayEngine.updatePromise) {\n return;\n }\n\n PlayEngine.updatePromise = (async () => {\n try {\n PlayEngine.lastUpdateCheck = now;\n const scriptPath = new URL(\n \"../scripts/check-ytdlp-update.mjs\",\n import.meta.url,\n );\n const { checkAndUpdate } = await import(scriptPath.href);\n const updated = await checkAndUpdate();\n\n if (updated) {\n this.opts.logger?.info?.(\"✓ yt-dlp updated to latest version\");\n }\n } catch (error) {\n this.opts.logger?.debug?.(\"Update check failed (will retry later)\");\n } finally {\n PlayEngine.updatePromise = null;\n }\n })();\n }\n\n private async forceUpdateCheck(): Promise<void> {\n if (PlayEngine.updatePromise) {\n this.opts.logger?.info?.(\"Update already in progress, waiting...\");\n return PlayEngine.updatePromise;\n }\n\n PlayEngine.updatePromise = (async () => {\n try {\n this.opts.logger?.warn?.(\n \"<!> Download failed. Forcing yt-dlp update check...\",\n );\n\n const scriptPath = new URL(\n \"../scripts/check-ytdlp-update.mjs\",\n import.meta.url,\n );\n const { checkAndUpdate } = await import(scriptPath.href);\n const updated = await checkAndUpdate();\n\n if (updated) {\n this.opts.logger?.info?.(\"✓ yt-dlp updated successfully\");\n PlayEngine.lastUpdateCheck = Date.now();\n }\n } catch (error) {\n this.opts.logger?.error?.(\"Failed to update yt-dlp:\", error);\n } finally {\n PlayEngine.updatePromise = null;\n }\n })();\n\n return PlayEngine.updatePromise;\n }\n\n generateRequestId(prefix = \"play\"): string {\n return `${prefix}_${Date.now()}_${Math.random().toString(36).slice(2, 8)}`;\n }\n\n async search(query: string): Promise<PlayMetadata | null> {\n return searchBest(query);\n }\n\n getFromCache(requestId: string): CacheEntry | undefined {\n return this.cache.get(requestId);\n }\n\n async preload(metadata: PlayMetadata, requestId: string): Promise<void> {\n const normalized = normalizeYoutubeUrl(metadata.url);\n if (!normalized) throw new Error(\"Invalid YouTube URL.\");\n\n const isLongVideo = metadata.durationSeconds > 3600;\n\n if (metadata.durationSeconds > this.opts.maxPreloadDurationSeconds) {\n this.opts.logger?.warn?.(\n `Video too long for preload (${Math.floor(metadata.durationSeconds / 60)}min). Will use direct download with reduced quality.`,\n );\n }\n\n const normalizedMeta: PlayMetadata = { ...metadata, url: normalized };\n\n this.cache.set(requestId, {\n metadata: normalizedMeta,\n audio: null,\n video: null,\n expiresAt: Date.now() + this.opts.ttlMs,\n loading: true,\n });\n\n const audioKbps = isLongVideo\n ? 96\n : pickQuality(this.opts.preferredAudioKbps, AUDIO_QUALITIES);\n\n const audioTask = this.preloadOne(\n requestId,\n \"audio\",\n normalized,\n audioKbps,\n );\n\n const tasks = isLongVideo\n ? [audioTask]\n : [\n audioTask,\n this.preloadOne(\n requestId,\n \"video\",\n normalized,\n pickQuality(this.opts.preferredVideoP, VIDEO_QUALITIES),\n ),\n ];\n\n if (isLongVideo) {\n this.opts.logger?.info?.(\n `Long video detected (${Math.floor(metadata.durationSeconds / 60)}min). Audio only mode (96kbps).`,\n );\n }\n\n await Promise.allSettled(tasks);\n this.cache.markLoading(requestId, false);\n }\n\n async getOrDownload(\n requestId: string,\n type: MediaType,\n ): Promise<{ metadata: PlayMetadata; file: CachedFile; direct: boolean }> {\n const entry = this.cache.get(requestId);\n if (!entry) throw new Error(\"Request not found (cache miss).\");\n\n const cached = entry[type];\n if (cached?.path && fs.existsSync(cached.path) && cached.size > 0) {\n return { metadata: entry.metadata, file: cached, direct: false };\n }\n\n const normalized = normalizeYoutubeUrl(entry.metadata.url);\n if (!normalized) throw new Error(\"Invalid YouTube URL.\");\n\n const directFile = await this.downloadDirect(type, normalized);\n return { metadata: entry.metadata, file: directFile, direct: true };\n }\n\n async waitCache(\n requestId: string,\n type: MediaType,\n timeoutMs = 8_000,\n intervalMs = 500,\n ): Promise<CachedFile | null> {\n const started = Date.now();\n while (Date.now() - started < timeoutMs) {\n const entry = this.cache.get(requestId);\n const f = entry?.[type];\n if (f?.path && fs.existsSync(f.path) && f.size > 0) return f;\n await new Promise((r) => setTimeout(r, intervalMs));\n }\n return null;\n }\n\n cleanup(requestId: string): void {\n this.cache.delete(requestId);\n }\n\n private async preloadOne(\n requestId: string,\n type: MediaType,\n youtubeUrl: string,\n quality: number,\n ): Promise<void> {\n try {\n await this._executePreload(requestId, type, youtubeUrl, quality);\n } catch (err) {\n this.opts.logger?.error?.(\n `preload ${type} failed, forcing update...`,\n err,\n );\n await this.forceUpdateCheck();\n\n this.opts.logger?.info?.(\">> Retrying preload after update...\");\n await this._executePreload(requestId, type, youtubeUrl, quality);\n }\n }\n\n private async _executePreload(\n requestId: string,\n type: MediaType,\n youtubeUrl: string,\n quality: number,\n ): Promise<void> {\n const uniqueSuffix = Math.random().toString(36).slice(2, 8);\n const safeTitle = sanitizeFilename(`temp_${Date.now()}_${uniqueSuffix}`);\n const ext = type === \"audio\" ? \"m4a\" : \"mp4\";\n const filename = `${type}_${requestId}_${safeTitle}.${ext}`;\n const filePath = path.join(this.paths.cacheDir, filename);\n\n const info: DownloadInfo =\n type === \"audio\"\n ? await this.ytdlp.getAudio(youtubeUrl, quality, filePath)\n : await this.ytdlp.getVideo(youtubeUrl, quality, filePath);\n\n const stats = fs.statSync(filePath);\n const size = stats.size;\n\n let buffer: Buffer | undefined;\n if (this.opts.preloadBuffer) {\n buffer = await fs.promises.readFile(filePath);\n }\n\n const cached: CachedFile = {\n path: filePath,\n size,\n info: { quality: info.quality },\n buffer,\n };\n\n this.cache.setFile(requestId, type, cached);\n this.opts.logger?.debug?.(`preloaded ${type} ${size} bytes: ${filename}`);\n }\n\n private async downloadDirect(\n type: MediaType,\n youtubeUrl: string,\n ): Promise<CachedFile> {\n try {\n return await this._executeDownloadDirect(type, youtubeUrl, false);\n } catch (err) {\n this.opts.logger?.error?.(\"Direct download failed:\", err);\n await this.forceUpdateCheck();\n\n this.opts.logger?.info?.(\">> Retrying download after update...\");\n return await this._executeDownloadDirect(type, youtubeUrl, true);\n }\n }\n\n private async _executeDownloadDirect(\n type: MediaType,\n youtubeUrl: string,\n isRetry: boolean,\n ): Promise<CachedFile> {\n const audioKbps = pickQuality(\n this.opts.preferredAudioKbps,\n AUDIO_QUALITIES,\n );\n const videoP = pickQuality(this.opts.preferredVideoP, VIDEO_QUALITIES);\n const ext = type === \"audio\" ? \"m4a\" : \"mp4\";\n const prefix = isRetry ? \"direct_retry\" : \"direct\";\n const uniqueSuffix = Math.random().toString(36).slice(2, 8);\n const safeTitle = sanitizeFilename(\n `${prefix}_${Date.now()}_${uniqueSuffix}`,\n );\n const filePath = path.join(\n this.paths.cacheDir,\n `${type}_${safeTitle}.${ext}`,\n );\n\n const info =\n type === \"audio\"\n ? await this.ytdlp.getAudio(youtubeUrl, audioKbps, filePath)\n : await this.ytdlp.getVideo(youtubeUrl, videoP, filePath);\n\n const stats = fs.statSync(filePath);\n return {\n path: filePath,\n size: stats.size,\n info: { quality: info.quality },\n };\n }\n}\n","import fs from \"node:fs\";\n\nimport type { CacheEntry, MediaType } from \"./types.js\";\n\nexport class CacheStore {\n private readonly store = new Map<string, CacheEntry>();\n private cleanupTimer?: NodeJS.Timeout;\n\n constructor(\n private readonly opts: {\n cleanupIntervalMs: number;\n }\n ) {}\n\n get(requestId: string): CacheEntry | undefined {\n return this.store.get(requestId);\n }\n\n set(requestId: string, entry: CacheEntry): void {\n this.store.set(requestId, entry);\n }\n\n has(requestId: string): boolean {\n return this.store.has(requestId);\n }\n\n delete(requestId: string): void {\n this.cleanupEntry(requestId);\n this.store.delete(requestId);\n }\n\n markLoading(requestId: string, loading: boolean): void {\n const e = this.store.get(requestId);\n if (e) e.loading = loading;\n }\n\n setFile(\n requestId: string,\n type: MediaType,\n file: CacheEntry[MediaType]\n ): void {\n const e = this.store.get(requestId);\n if (!e) return;\n e[type] = file as any;\n }\n\n cleanupExpired(now = Date.now()): number {\n let removed = 0;\n for (const [requestId, entry] of this.store.entries()) {\n if (now > entry.expiresAt) {\n this.delete(requestId);\n removed++;\n }\n }\n return removed;\n }\n\n start(): void {\n if (this.cleanupTimer) return;\n\n this.cleanupTimer = setInterval(() => {\n this.cleanupExpired(Date.now());\n }, this.opts.cleanupIntervalMs);\n\n this.cleanupTimer.unref();\n }\n\n stop(): void {\n if (!this.cleanupTimer) return;\n clearInterval(this.cleanupTimer);\n this.cleanupTimer = undefined;\n }\n\n private cleanupEntry(requestId: string) {\n const entry = this.store.get(requestId);\n if (!entry) return;\n\n ([\"audio\", \"video\"] as const).forEach((type) => {\n const f = entry[type];\n if (f?.path && fs.existsSync(f.path)) {\n try {\n fs.unlinkSync(f.path);\n } catch {\n // ignore\n }\n }\n });\n }\n}\n","import fs from \"node:fs\";\nimport path from \"node:path\";\nimport os from \"node:os\";\n\nexport interface ResolvedPaths {\n baseDir: string;\n cacheDir: string;\n}\n\nexport function ensureDirSync(dirPath: string) {\n fs.mkdirSync(dirPath, { recursive: true, mode: 0o777 });\n\n try {\n fs.chmodSync(dirPath, 0o777);\n } catch {\n // ignore\n }\n\n fs.accessSync(dirPath, fs.constants.R_OK | fs.constants.W_OK);\n}\n\nexport function resolvePaths(cacheDir?: string): ResolvedPaths {\n const baseDir = cacheDir?.trim()\n ? cacheDir\n : path.join(os.tmpdir(), \"yt-play\");\n const resolvedBase = path.resolve(baseDir);\n\n const resolvedCache = path.join(resolvedBase);\n\n ensureDirSync(resolvedBase);\n ensureDirSync(resolvedCache);\n\n return {\n baseDir: resolvedBase,\n cacheDir: resolvedCache,\n };\n}\n","import { spawn } from \"node:child_process\";\nimport path from \"node:path\";\nimport fs from \"node:fs\";\nimport type { DownloadInfo } from \"./types.js\";\n\nlet __dirname: string;\ntry {\n // @ts-ignore\n __dirname = path.dirname(new URL(import.meta.url).pathname);\n} catch {\n // @ts-ignore\n __dirname = typeof __dirname !== \"undefined\" ? __dirname : process.cwd();\n}\n\nexport interface YtDlpClientOptions {\n binaryPath?: string;\n ffmpegPath?: string;\n aria2cPath?: string;\n timeoutMs?: number;\n useAria2c?: boolean;\n concurrentFragments?: number;\n cookiesPath?: string;\n cookiesFromBrowser?: string;\n}\n\nexport interface YtDlpVideoInfo {\n id: string;\n title: string;\n uploader?: string;\n duration: number;\n thumbnail?: string;\n}\n\nexport interface YtDlpPlaylistItem {\n id: string;\n title: string;\n url: string;\n duration?: number;\n uploader?: string;\n directUrl?: string;\n}\n\nexport interface YtDlpResolvedItem extends YtDlpPlaylistItem {\n directUrl: string;\n}\n\nexport interface YtDlpPlaylistInfo {\n id: string;\n title: string;\n uploader?: string;\n entries: YtDlpPlaylistItem[] | YtDlpResolvedItem[];\n}\n\nexport interface YtDlpPlaylistOptions {\n limit?: number;\n resolveLinks?: boolean;\n type?: \"audio\" | \"video\" | \"both\";\n batchSize?: number;\n}\n\nexport class YtDlpClient {\n private readonly binaryPath: string;\n private readonly ffmpegPath?: string;\n private readonly aria2cPath?: string;\n private readonly timeoutMs: number;\n private readonly useAria2c: boolean;\n private readonly concurrentFragments: number;\n private readonly cookiesPath?: string;\n private readonly cookiesFromBrowser?: string;\n\n constructor(opts: YtDlpClientOptions = {}) {\n this.binaryPath = opts.binaryPath || this.detectYtDlp();\n this.ffmpegPath = opts.ffmpegPath;\n this.timeoutMs = opts.timeoutMs ?? 300_000;\n this.concurrentFragments = opts.concurrentFragments ?? 5;\n this.cookiesPath = opts.cookiesPath;\n this.cookiesFromBrowser = opts.cookiesFromBrowser;\n\n this.aria2cPath = opts.aria2cPath || this.detectAria2c();\n this.useAria2c = opts.useAria2c ?? !!this.aria2cPath;\n }\n\n private detectYtDlp(): string {\n const packageRoot = path.resolve(__dirname, \"../..\");\n const bundledPaths = [\n path.join(packageRoot, \"bin\", \"yt-dlp\"),\n path.join(packageRoot, \"bin\", \"yt-dlp.exe\"),\n ];\n\n for (const p of bundledPaths) {\n if (fs.existsSync(p)) {\n return p;\n }\n }\n\n try {\n const { execSync } = require(\"node:child_process\");\n const cmd =\n process.platform === \"win32\" ? \"where yt-dlp\" : \"which yt-dlp\";\n const result = execSync(cmd, { encoding: \"utf-8\" }).trim();\n if (result) return result.split(\"\\n\")[0];\n } catch {}\n\n return \"yt-dlp\";\n }\n\n private detectAria2c(): string | undefined {\n const packageRoot = path.resolve(__dirname, \"../..\");\n const bundledPaths = [\n path.join(packageRoot, \"bin\", \"aria2c\"),\n path.join(packageRoot, \"bin\", \"aria2c.exe\"),\n ];\n\n for (const p of bundledPaths) {\n if (fs.existsSync(p)) {\n return p;\n }\n }\n\n try {\n const { execSync } = require(\"node:child_process\");\n const cmd =\n process.platform === \"win32\" ? \"where aria2c\" : \"which aria2c\";\n const result = execSync(cmd, { encoding: \"utf-8\" }).trim();\n if (result) return result.split(\"\\n\")[0];\n } catch {}\n\n return undefined;\n }\n\n public async exec(args: string[]): Promise<string> {\n return new Promise((resolve, reject) => {\n let allArgs = [...args];\n\n if (this.ffmpegPath) {\n allArgs = [\"--ffmpeg-location\", this.ffmpegPath, ...allArgs];\n }\n\n if (this.cookiesPath && fs.existsSync(this.cookiesPath)) {\n allArgs = [\"--cookies\", this.cookiesPath, ...allArgs];\n }\n\n if (this.cookiesFromBrowser) {\n allArgs = [\n \"--cookies-from-browser\",\n this.cookiesFromBrowser,\n ...allArgs,\n ];\n }\n\n const proc = spawn(this.binaryPath, allArgs, {\n stdio: [\"ignore\", \"pipe\", \"pipe\"],\n });\n\n let stdout = \"\";\n let stderr = \"\";\n\n proc.stdout.on(\"data\", (chunk) => {\n stdout += chunk.toString();\n });\n\n proc.stderr.on(\"data\", (chunk) => {\n stderr += chunk.toString();\n });\n\n const timer = setTimeout(() => {\n proc.kill(\"SIGKILL\");\n reject(new Error(`yt-dlp timeout after ${this.timeoutMs}ms`));\n }, this.timeoutMs);\n\n proc.on(\"close\", (code) => {\n clearTimeout(timer);\n if (code === 0) {\n resolve(stdout);\n } else {\n reject(\n new Error(\n `yt-dlp exited with code ${code}. stderr: ${stderr.slice(0, 500)}`,\n ),\n );\n }\n });\n\n proc.on(\"error\", (err) => {\n clearTimeout(timer);\n reject(err);\n });\n });\n }\n\n async getInfo(youtubeUrl: string): Promise<YtDlpVideoInfo> {\n const stdout = await this.exec([\n \"-J\",\n \"--no-warnings\",\n \"--no-playlist\",\n youtubeUrl,\n ]);\n const info = JSON.parse(stdout) as YtDlpVideoInfo;\n return info;\n }\n\n async getPlaylistInfo(\n youtubeUrl: string,\n opts: YtDlpPlaylistOptions = {},\n ): Promise<YtDlpPlaylistInfo> {\n const stdout = await this.exec([\n \"-J\",\n \"--flat-playlist\",\n \"--no-warnings\",\n youtubeUrl,\n ]);\n\n const info = JSON.parse(stdout);\n\n let entries: YtDlpPlaylistItem[] = (info.entries || [])\n .filter((entry: any) => {\n const t = entry.title || \"\";\n return (\n entry.id &&\n t &&\n !t.includes(\"[Deleted video]\") &&\n !t.includes(\"[Private video]\")\n );\n })\n .map((entry: any) => ({\n id: entry.id,\n title: entry.title,\n url: `https://www.youtube.com/watch?v=${entry.id}`,\n duration: entry.duration,\n uploader: entry.uploader || info.uploader,\n }));\n\n if (opts.resolveLinks) {\n entries = await this.resolvePlaylistItems(\n entries,\n opts.limit && opts.limit > 0 ? opts.limit : entries.length,\n opts.type || \"audio\",\n opts.batchSize || 5,\n );\n } else if (opts.limit && opts.limit > 0) {\n entries = entries.slice(0, opts.limit);\n }\n\n return {\n id: info.id,\n title: info.title,\n uploader: info.uploader,\n entries,\n };\n }\n\n async getDirectUrl(\n youtubeUrl: string,\n type: \"audio\" | \"video\" | \"both\" = \"audio\",\n ): Promise<string> {\n const format =\n type === \"audio\"\n ? \"bestaudio[ext=m4a]/bestaudio/best\"\n : \"best[ext=mp4]/best\";\n\n const stdout = await this.exec([\n \"-f\",\n format,\n \"-g\",\n \"--no-warnings\",\n youtubeUrl,\n ]);\n\n return stdout.trim().split(\"\\n\")[0];\n }\n\n async resolvePlaylistItems(\n entries: YtDlpPlaylistItem[],\n limit: number,\n type: \"audio\" | \"video\" | \"both\" = \"audio\",\n batchSize: number = 5,\n ): Promise<YtDlpResolvedItem[]> {\n const resolved: YtDlpResolvedItem[] = [];\n let currentIndex = 0;\n\n while (resolved.length < limit && currentIndex < entries.length) {\n const needed = limit - resolved.length;\n const takeCount = Math.min(\n batchSize,\n needed,\n entries.length - currentIndex,\n );\n\n const batch = entries.slice(currentIndex, currentIndex + takeCount);\n currentIndex += takeCount;\n\n const promises = batch.map(async (item) => {\n try {\n const directUrl = await this.getDirectUrl(item.url, type);\n return { ...item, directUrl } as YtDlpResolvedItem;\n } catch (err) {\n return null;\n }\n });\n\n const results = await Promise.all(promises);\n\n for (const res of results) {\n if (res && resolved.length < limit) {\n resolved.push(res);\n }\n }\n }\n\n return resolved;\n }\n\n private buildOptimizationArgs(): string[] {\n const args: string[] = [\n \"--no-warnings\",\n \"--no-playlist\",\n \"--no-check-certificates\",\n \"--concurrent-fragments\",\n String(this.concurrentFragments),\n ];\n\n if (this.useAria2c && this.aria2cPath) {\n args.push(\"--downloader\", this.aria2cPath);\n args.push(\"--downloader-args\", \"aria2c:-x 16 -s 16 -k 1M\");\n }\n\n return args;\n }\n\n async getAudio(\n youtubeUrl: string,\n qualityKbps: number,\n outputPath: string,\n ): Promise<DownloadInfo> {\n const info = await this.getInfo(youtubeUrl);\n const format = \"bestaudio[ext=m4a]/bestaudio/best\";\n\n const args = [\n \"-f\",\n format,\n \"-o\",\n outputPath,\n ...this.buildOptimizationArgs(),\n youtubeUrl,\n ];\n\n try {\n await this.exec(args);\n } catch (err) {\n [outputPath, `${outputPath}.part`, `${outputPath}.ytdl`].forEach((f) => {\n if (fs.existsSync(f)) {\n try {\n fs.unlinkSync(f);\n } catch {}\n }\n });\n throw err;\n }\n\n if (!fs.existsSync(outputPath)) {\n throw new Error(`yt-dlp failed to create audio file: ${outputPath}`);\n }\n\n const duration = this.formatDuration(info.duration);\n\n return {\n title: info.title,\n author: info.uploader,\n duration,\n quality: `${qualityKbps}kbps m4a`,\n filename: path.basename(outputPath),\n downloadUrl: outputPath,\n };\n }\n\n async getVideo(\n youtubeUrl: string,\n qualityP: number,\n outputPath: string,\n ): Promise<DownloadInfo> {\n const info = await this.getInfo(youtubeUrl);\n\n const format = `bestvideo[height<=${qualityP}][ext=mp4][vcodec^=avc]+bestaudio[ext=m4a]/bestvideo[height<=${qualityP}][vcodec!=av1][vcodec!=vp9]+bestaudio/best[height<=${qualityP}]`;\n\n const args = [\n \"-f\",\n format,\n \"--merge-output-format\",\n \"mp4\",\n \"-o\",\n outputPath,\n ...this.buildOptimizationArgs(),\n youtubeUrl,\n ];\n\n try {\n await this.exec(args);\n } catch (err) {\n [outputPath, `${outputPath}.part`, `${outputPath}.ytdl`].forEach((f) => {\n if (fs.existsSync(f)) {\n try {\n fs.unlinkSync(f);\n } catch {}\n }\n });\n throw err;\n }\n\n if (!fs.existsSync(outputPath)) {\n throw new Error(`yt-dlp failed to create video file: ${outputPath}`);\n }\n\n const duration = this.formatDuration(info.duration);\n\n return {\n title: info.title,\n author: info.uploader,\n duration,\n quality: `${qualityP}p H.264`,\n filename: path.basename(outputPath),\n downloadUrl: outputPath,\n };\n }\n\n private formatDuration(seconds: number): string {\n if (!seconds) return \"0:00\";\n const h = Math.floor(seconds / 3600);\n const m = Math.floor((seconds % 3600) / 60);\n const s = Math.floor(seconds % 60);\n if (h > 0) {\n return `${h}:${m.toString().padStart(2, \"0\")}:${s.toString().padStart(2, \"0\")}`;\n }\n return `${m}:${s.toString().padStart(2, \"0\")}`;\n }\n}\n","import yts from \"yt-search\";\n\nimport type { PlayMetadata } from \"./types.js\";\n\nexport function stripWeirdUrlWrappers(input: string): string {\n let s = (input || \"\").trim();\n const mdAll = [...s.matchAll(/\\[[^\\]]*\\]\\((https?:\\/\\/[^)\\s]+)\\)/gi)];\n if (mdAll.length > 0) return mdAll[0][1].trim();\n s = s.replace(/^<([^>]+)>$/, \"$1\").trim();\n s = s.replace(/^[\"'`](.*)[\"'`]$/, \"$1\").trim();\n\n return s;\n}\n\nexport function getYouTubeVideoId(input: string): string | null {\n const regex =\n /(?:https?:\\/\\/)?(?:www\\.)?(?:youtube\\.com\\/(?:[^\\/]+\\/.+\\/|(?:v|e(?:mbed)?)\\/|.*[?&]v=|shorts\\/)|youtu\\.be\\/)([a-zA-Z0-9_-]{11})(?:[?&]|$)/i;\n\n const match = (input || \"\").match(regex);\n return match ? match[1] : null;\n}\n\nexport function getYouTubePlaylistId(input: string): string | null {\n const regex = /[?&]list=([a-zA-Z0-9_-]+)/i;\n const match = (input || \"\").match(regex);\n return match ? match[1] : null;\n}\n\nexport function normalizeYoutubeUrl(input: string): string | null {\n const cleaned0 = stripWeirdUrlWrappers(input);\n\n const firstUrl = cleaned0.match(/https?:\\/\\/[^\\s)]+/i)?.[0] ?? cleaned0;\n\n const id = getYouTubeVideoId(firstUrl);\n if (!id) return null;\n\n return `https://www.youtube.com/watch?v=${id}`;\n}\n\nexport async function searchBest(query: string): Promise<PlayMetadata | null> {\n const videoId = getYouTubeVideoId(query);\n\n if (videoId) {\n const video = await yts({ videoId });\n\n if (!video) return null;\n\n const durationSeconds = video.duration?.seconds ?? 0;\n const normalizedUrl = normalizeYoutubeUrl(video.url) ?? video.url;\n\n return {\n title: video.title || \"Untitled\",\n author: video.author?.name || undefined,\n duration: video.duration?.timestamp || undefined,\n thumb: video.image || video.thumbnail || undefined,\n videoId: video.videoId,\n url: normalizedUrl,\n durationSeconds,\n };\n }\n\n const result = await yts(query);\n const v = result?.videos?.[0];\n if (!v) return null;\n\n const durationSeconds = v.duration?.seconds ?? 0;\n const normalizedUrl = normalizeYoutubeUrl(v.url) ?? v.url;\n\n return {\n title: v.title || \"Untitled\",\n author: v.author?.name || undefined,\n duration: v.duration?.timestamp || undefined,\n thumb: v.image || v.thumbnail || undefined,\n videoId: v.videoId,\n url: normalizedUrl,\n durationSeconds,\n };\n}\n\nexport async function searchPlaylistBest(\n query: string,\n): Promise<PlayMetadata | null> {\n const listId = getYouTubePlaylistId(query);\n\n if (listId) {\n try {\n const list = await yts({ listId });\n\n if (!list) return null;\n\n return {\n title: list.title || \"Untitled\",\n author: list.author?.name || undefined,\n duration: undefined,\n durationSeconds: 0,\n thumb: list.image || list.thumbnail || undefined,\n videoId: listId,\n url: list.url || `https://www.youtube.com/playlist?list=${listId}`,\n };\n } catch (err) {}\n }\n\n const result = await yts(query);\n const p = result?.playlists?.[0];\n if (!p) return null;\n\n return {\n title: p.title || \"Untitled\",\n author: p.author?.name || undefined,\n duration: undefined,\n durationSeconds: 0,\n thumb: p.image || p.thumbnail || undefined,\n videoId: p.listId,\n url: p.url || `https://www.youtube.com/playlist?list=${p.listId}`,\n };\n}\n","import { spawn } from \"node:child_process\";\nimport type { StreamInfo } from \"./types.js\";\nimport type { YtDlpClient } from \"./ytdlp-client.js\";\n\nexport class StreamEngine {\n constructor(private readonly ytdlpClient: YtDlpClient) {}\n\n private getBinaryPath(): string {\n return (this.ytdlpClient as any).binaryPath;\n }\n\n async getAudioStream(\n youtubeUrl: string,\n qualityKbps: number = 128,\n ): Promise<StreamInfo> {\n const format = \"bestaudio[ext=m4a]/bestaudio/best\";\n\n const args = [\n \"-f\",\n format,\n \"-o\",\n \"-\",\n \"--no-warnings\",\n \"--no-playlist\",\n youtubeUrl,\n ];\n\n const proc = spawn(this.getBinaryPath(), args, {\n stdio: [\"ignore\", \"pipe\", \"pipe\"],\n });\n\n proc.stderr.on(\"data\", () => {});\n\n return {\n stream: proc.stdout,\n quality: `${qualityKbps}kbps`,\n format: \"m4a\",\n };\n }\n\n async getVideoStream(\n youtubeUrl: string,\n qualityP: number = 720,\n ): Promise<StreamInfo> {\n const format = `best[height<=${qualityP}][ext=mp4]/best`;\n\n const args = [\n \"-f\",\n format,\n \"-o\",\n \"-\",\n \"--no-warnings\",\n \"--no-playlist\",\n youtubeUrl,\n ];\n\n const proc = spawn(this.getBinaryPath(), args, {\n stdio: [\"ignore\", \"pipe\", \"pipe\"],\n });\n\n proc.stderr.on(\"data\", () => {});\n\n return {\n stream: proc.stdout,\n quality: `${qualityP}p`,\n format: \"mp4\",\n };\n }\n}\n","import type { YtDlpClient } from \"./ytdlp-client.js\";\nimport type {\n YtDlpVideoMetadata,\n YtDlpChannelMetadata,\n StalkChannelOptions,\n} from \"./types.js\";\n\nexport class StalkerEngine {\n constructor(private readonly ytdlpClient: YtDlpClient) {}\n\n async stalkVideoOrLive(youtubeUrl: string): Promise<YtDlpVideoMetadata> {\n const args = [\"-J\", \"--no-warnings\", \"--no-playlist\", youtubeUrl];\n\n const stdout = await this.ytdlpClient.exec(args);\n return JSON.parse(stdout) as YtDlpVideoMetadata;\n }\n\n async stalkChannel(\n youtubeUrl: string,\n opts: StalkChannelOptions = {},\n ): Promise<YtDlpChannelMetadata> {\n const args = [\"-J\", \"--no-warnings\"];\n\n if (opts.flat) {\n args.push(\"--flat-playlist\");\n }\n\n if (opts.playlistItems) {\n args.push(\"--playlist-items\", opts.playlistItems);\n } else {\n if (opts.startItem && opts.startItem > 0) {\n args.push(\"--playlist-start\", opts.startItem.toString());\n }\n if (opts.endItem && opts.endItem > 0) {\n args.push(\"--playlist-end\", opts.endItem.toString());\n }\n }\n\n let targetUrl = youtubeUrl.trim().replace(/\\/$/, \"\");\n if (opts.tab) {\n targetUrl = targetUrl.replace(\n /\\/(videos|shorts|streams|popular|featured)$/i,\n \"\",\n );\n if (opts.tab !== \"featured\") {\n targetUrl = `${targetUrl}/${opts.tab}`;\n }\n }\n\n args.push(targetUrl);\n\n try {\n const stdout = await this.ytdlpClient.exec(args);\n return JSON.parse(stdout) as YtDlpChannelMetadata;\n } catch (error: any) {\n const msg = error.message || \"\";\n\n if (\n msg.includes(\"This channel does not have a\") ||\n msg.includes(\"404\") ||\n msg.includes(\"does not exist\")\n ) {\n return {\n _type: \"playlist\",\n id: targetUrl,\n title: \"Tab Not Found\",\n channel: targetUrl.split(\"/\").pop() || \"Unknown\",\n channel_id: \"Unknown\",\n entries: [],\n } as YtDlpChannelMetadata;\n }\n\n throw error;\n }\n }\n}\n"],"mappings":"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","names":["index_exports","__export","PlayEngine","StalkerEngine","StreamEngine","YtDlpClient","getYouTubeVideoId","normalizeYoutubeUrl","searchBest","searchPlaylistBest","__toCommonJS","import_node_fs","import_node_path","import_node_child_process","import_node_fs","CacheStore","opts","requestId","entry","loading","e","type","file","now","removed","f","fs","import_node_fs","import_node_path","import_node_os","ensureDirSync","dirPath","fs","resolvePaths","cacheDir","baseDir","path","os","resolvedBase","resolvedCache","import_node_child_process","import_node_path","import_node_fs","import_meta","__dirname","path","YtDlpClient","opts","packageRoot","bundledPaths","p","fs","execSync","cmd","result","args","resolve","reject","allArgs","proc","stdout","stderr","chunk","timer","code","err","youtubeUrl","info","entries","entry","t","type","format","limit","batchSize","resolved","currentIndex","needed","takeCount","batch","promises","item","directUrl","results","res","qualityKbps","outputPath","f","duration","qualityP","seconds","h","m","s","import_yt_search","stripWeirdUrlWrappers","input","s","mdAll","getYouTubeVideoId","regex","match","getYouTubePlaylistId","normalizeYoutubeUrl","cleaned0","firstUrl","id","searchBest","query","videoId","video","yts","durationSeconds","normalizedUrl","v","searchPlaylistBest","listId","list","p","import_node_child_process","StreamEngine","ytdlpClient","youtubeUrl","qualityKbps","args","proc","qualityP","import_meta","AUDIO_QUALITIES","VIDEO_QUALITIES","UPDATE_CHECK_INTERVAL","pickQuality","requested","available","sanitizeFilename","filename","getNodeBinaryPath","setupYtDlpConfig","ytdlpBinaryPath","cookiesPath","cookiesFromBrowser","binaryDir","path","moduleUrl","fs","configPath","configLines","nodePath","PlayEngine","_PlayEngine","options","resolvePaths","CacheStore","YtDlpClient","StreamEngine","now","scriptPath","checkAndUpdate","error","prefix","query","searchBest","requestId","metadata","normalized","normalizeYoutubeUrl","isLongVideo","normalizedMeta","audioKbps","audioTask","tasks","type","entry","cached","directFile","timeoutMs","intervalMs","started","f","r","youtubeUrl","quality","err","uniqueSuffix","safeTitle","filePath","info","size","buffer","isRetry","videoP","ext","stats","StalkerEngine","ytdlpClient","youtubeUrl","args","stdout","opts","targetUrl","error","msg"]}
|
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,38 +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 updatePromise;
|
|
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 _executePreload;
|
|
100
|
-
private downloadDirect;
|
|
101
|
-
private _executeDownloadDirect;
|
|
102
|
-
}
|
|
103
|
-
|
|
104
|
-
declare function getYouTubeVideoId(input: string): string | null;
|
|
105
|
-
declare function normalizeYoutubeUrl(input: string): string | null;
|
|
106
|
-
declare function searchBest(query: string): Promise<PlayMetadata | null>;
|
|
107
|
-
declare function searchPlaylistBest(query: string): Promise<PlayMetadata | null>;
|
|
108
|
-
|
|
109
143
|
interface YtDlpClientOptions {
|
|
110
144
|
binaryPath?: string;
|
|
111
145
|
ffmpegPath?: string;
|
|
@@ -158,7 +192,7 @@ declare class YtDlpClient {
|
|
|
158
192
|
constructor(opts?: YtDlpClientOptions);
|
|
159
193
|
private detectYtDlp;
|
|
160
194
|
private detectAria2c;
|
|
161
|
-
|
|
195
|
+
exec(args: string[]): Promise<string>;
|
|
162
196
|
getInfo(youtubeUrl: string): Promise<YtDlpVideoInfo>;
|
|
163
197
|
getPlaylistInfo(youtubeUrl: string, opts?: YtDlpPlaylistOptions): Promise<YtDlpPlaylistInfo>;
|
|
164
198
|
getDirectUrl(youtubeUrl: string, type?: "audio" | "video" | "both"): Promise<string>;
|
|
@@ -169,4 +203,52 @@ declare class YtDlpClient {
|
|
|
169
203
|
private formatDuration;
|
|
170
204
|
}
|
|
171
205
|
|
|
172
|
-
|
|
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,38 +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 updatePromise;
|
|
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 _executePreload;
|
|
100
|
-
private downloadDirect;
|
|
101
|
-
private _executeDownloadDirect;
|
|
102
|
-
}
|
|
103
|
-
|
|
104
|
-
declare function getYouTubeVideoId(input: string): string | null;
|
|
105
|
-
declare function normalizeYoutubeUrl(input: string): string | null;
|
|
106
|
-
declare function searchBest(query: string): Promise<PlayMetadata | null>;
|
|
107
|
-
declare function searchPlaylistBest(query: string): Promise<PlayMetadata | null>;
|
|
108
|
-
|
|
109
143
|
interface YtDlpClientOptions {
|
|
110
144
|
binaryPath?: string;
|
|
111
145
|
ffmpegPath?: string;
|
|
@@ -158,7 +192,7 @@ declare class YtDlpClient {
|
|
|
158
192
|
constructor(opts?: YtDlpClientOptions);
|
|
159
193
|
private detectYtDlp;
|
|
160
194
|
private detectAria2c;
|
|
161
|
-
|
|
195
|
+
exec(args: string[]): Promise<string>;
|
|
162
196
|
getInfo(youtubeUrl: string): Promise<YtDlpVideoInfo>;
|
|
163
197
|
getPlaylistInfo(youtubeUrl: string, opts?: YtDlpPlaylistOptions): Promise<YtDlpPlaylistInfo>;
|
|
164
198
|
getDirectUrl(youtubeUrl: string, type?: "audio" | "video" | "both"): Promise<string>;
|
|
@@ -169,4 +203,52 @@ declare class YtDlpClient {
|
|
|
169
203
|
private formatDuration;
|
|
170
204
|
}
|
|
171
205
|
|
|
172
|
-
|
|
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,7 +1,7 @@
|
|
|
1
|
-
var T=(
|
|
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",
|
|
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
|
|
4
|
-
`)[0]}async resolvePlaylistItems(t,r,e="audio",i=5){let
|
|
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(`
|
|
5
5
|
`)+`
|
|
6
|
-
`,"utf-8")}catch{}}var
|
|
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};
|
|
7
7
|
//# sourceMappingURL=index.mjs.map
|
package/dist/index.mjs.map
CHANGED
|
@@ -1 +1 @@
|
|
|
1
|
-
{"version":3,"sources":["../src/core/play-engine.ts","../src/core/cache.ts","../src/core/paths.ts","../src/core/ytdlp-client.ts","../src/core/youtube.ts"],"sourcesContent":["import fs from \"node:fs\";\nimport path from \"node:path\";\nimport { execSync } from \"node:child_process\";\nimport type {\n CacheEntry,\n CachedFile,\n DownloadInfo,\n MediaType,\n PlayEngineOptions,\n PlayMetadata,\n} from \"./types.js\";\nimport { CacheStore } from \"./cache.js\";\nimport { resolvePaths } from \"./paths.js\";\nimport { YtDlpClient } from \"./ytdlp-client.js\";\nimport { normalizeYoutubeUrl, searchBest } from \"./youtube.js\";\n\nconst AUDIO_QUALITIES = [320, 256, 192, 128, 96, 64] as const;\nconst VIDEO_QUALITIES = [1080, 720, 480, 360] as const;\nconst UPDATE_CHECK_INTERVAL = 3600000;\n\nfunction pickQuality<T extends number>(\n requested: T,\n available: readonly T[],\n): T {\n return (available as readonly number[]).includes(requested)\n ? requested\n : available[0];\n}\n\nfunction sanitizeFilename(filename: string): string {\n return (filename || \"\")\n .replace(/[\\\\/:*?\"<>|]/g, \"\")\n .replace(/[^\\w\\s-]/gi, \"\")\n .trim()\n .replace(/\\s+/g, \" \")\n .substring(0, 100);\n}\n\nfunction getNodeBinaryPath(): string | null {\n try {\n const nodePath = execSync(\"which node\", { encoding: \"utf-8\" }).trim();\n return nodePath || null;\n } catch {\n return process.execPath || null;\n }\n}\n\nfunction setupYtDlpConfig(\n ytdlpBinaryPath: string | undefined,\n cookiesPath: string | undefined,\n cookiesFromBrowser: string | undefined,\n): void {\n try {\n let binaryDir: string;\n\n if (ytdlpBinaryPath) {\n binaryDir = path.dirname(ytdlpBinaryPath);\n } else {\n try {\n const moduleUrl = new URL(import.meta.url);\n binaryDir = path.join(path.dirname(moduleUrl.pathname), \"..\", \"bin\");\n } catch {\n binaryDir = path.join(__dirname, \"..\", \"bin\");\n }\n }\n\n if (!fs.existsSync(binaryDir)) {\n return;\n }\n\n const configPath = path.join(binaryDir, \"yt-dlp.conf\");\n const configLines: string[] = [];\n const nodePath = getNodeBinaryPath();\n\n if (nodePath) {\n configLines.push(`--js-runtimes node:${nodePath}`);\n }\n\n configLines.push(\"--remote-components ejs:npm\");\n\n if (cookiesPath && fs.existsSync(cookiesPath)) {\n configLines.push(`--cookies ${cookiesPath}`);\n } else if (cookiesFromBrowser) {\n configLines.push(`--cookies-from-browser ${cookiesFromBrowser}`);\n }\n\n fs.writeFileSync(configPath, configLines.join(\"\\n\") + \"\\n\", \"utf-8\");\n } catch (error) {}\n}\n\nexport class PlayEngine {\n private readonly opts: Required<\n Pick<\n PlayEngineOptions,\n | \"ttlMs\"\n | \"maxPreloadDurationSeconds\"\n | \"preferredAudioKbps\"\n | \"preferredVideoP\"\n | \"preloadBuffer\"\n | \"cleanupIntervalMs\"\n | \"concurrentFragments\"\n >\n > &\n Pick<PlayEngineOptions, \"useAria2c\" | \"logger\">;\n\n private readonly paths: { baseDir: string; cacheDir: string };\n readonly cache: CacheStore;\n private readonly ytdlp: YtDlpClient;\n\n private static lastUpdateCheck: number = 0;\n private static updatePromise: Promise<void> | null = null;\n\n constructor(options: PlayEngineOptions = {}) {\n this.opts = {\n ttlMs: options.ttlMs ?? 3 * 60_000,\n maxPreloadDurationSeconds: options.maxPreloadDurationSeconds ?? 20 * 60,\n preferredAudioKbps: options.preferredAudioKbps ?? 128,\n preferredVideoP: options.preferredVideoP ?? 720,\n preloadBuffer: options.preloadBuffer ?? true,\n cleanupIntervalMs: options.cleanupIntervalMs ?? 30_000,\n concurrentFragments: options.concurrentFragments ?? 5,\n useAria2c: options.useAria2c,\n logger: options.logger,\n };\n\n this.paths = resolvePaths(options.cacheDir);\n this.cache = new CacheStore({\n cleanupIntervalMs: this.opts.cleanupIntervalMs,\n });\n this.cache.start();\n\n setupYtDlpConfig(\n options.ytdlpBinaryPath,\n options.cookiesPath,\n options.cookiesFromBrowser,\n );\n\n this.ytdlp = new YtDlpClient({\n binaryPath: options.ytdlpBinaryPath,\n ffmpegPath: options.ffmpegPath,\n aria2cPath: options.aria2cPath,\n useAria2c: this.opts.useAria2c,\n concurrentFragments: this.opts.concurrentFragments,\n timeoutMs: options.ytdlpTimeoutMs ?? 300_000,\n cookiesPath: options.cookiesPath,\n cookiesFromBrowser: options.cookiesFromBrowser,\n });\n\n this.backgroundUpdateCheck();\n }\n\n private backgroundUpdateCheck(): void {\n const now = Date.now();\n\n if (now - PlayEngine.lastUpdateCheck < UPDATE_CHECK_INTERVAL) {\n return;\n }\n\n if (PlayEngine.updatePromise) {\n return;\n }\n\n PlayEngine.updatePromise = (async () => {\n try {\n PlayEngine.lastUpdateCheck = now;\n const scriptPath = new URL(\n \"../scripts/check-ytdlp-update.mjs\",\n import.meta.url,\n );\n const { checkAndUpdate } = await import(scriptPath.href);\n const updated = await checkAndUpdate();\n\n if (updated) {\n this.opts.logger?.info?.(\"✓ yt-dlp updated to latest version\");\n }\n } catch (error) {\n this.opts.logger?.debug?.(\"Update check failed (will retry later)\");\n } finally {\n PlayEngine.updatePromise = null;\n }\n })();\n }\n\n private async forceUpdateCheck(): Promise<void> {\n if (PlayEngine.updatePromise) {\n this.opts.logger?.info?.(\"Update already in progress, waiting...\");\n return PlayEngine.updatePromise;\n }\n\n PlayEngine.updatePromise = (async () => {\n try {\n this.opts.logger?.warn?.(\n \"<!> Download failed. Forcing yt-dlp update check...\",\n );\n\n const scriptPath = new URL(\n \"../scripts/check-ytdlp-update.mjs\",\n import.meta.url,\n );\n const { checkAndUpdate } = await import(scriptPath.href);\n const updated = await checkAndUpdate();\n\n if (updated) {\n this.opts.logger?.info?.(\"✓ yt-dlp updated successfully\");\n PlayEngine.lastUpdateCheck = Date.now();\n }\n } catch (error) {\n this.opts.logger?.error?.(\"Failed to update yt-dlp:\", error);\n } finally {\n PlayEngine.updatePromise = null;\n }\n })();\n\n return PlayEngine.updatePromise;\n }\n\n generateRequestId(prefix = \"play\"): string {\n return `${prefix}_${Date.now()}_${Math.random().toString(36).slice(2, 8)}`;\n }\n\n async search(query: string): Promise<PlayMetadata | null> {\n return searchBest(query);\n }\n\n getFromCache(requestId: string): CacheEntry | undefined {\n return this.cache.get(requestId);\n }\n\n async preload(metadata: PlayMetadata, requestId: string): Promise<void> {\n const normalized = normalizeYoutubeUrl(metadata.url);\n if (!normalized) throw new Error(\"Invalid YouTube URL.\");\n\n const isLongVideo = metadata.durationSeconds > 3600;\n\n if (metadata.durationSeconds > this.opts.maxPreloadDurationSeconds) {\n this.opts.logger?.warn?.(\n `Video too long for preload (${Math.floor(metadata.durationSeconds / 60)}min). Will use direct download with reduced quality.`,\n );\n }\n\n const normalizedMeta: PlayMetadata = { ...metadata, url: normalized };\n\n this.cache.set(requestId, {\n metadata: normalizedMeta,\n audio: null,\n video: null,\n expiresAt: Date.now() + this.opts.ttlMs,\n loading: true,\n });\n\n const audioKbps = isLongVideo\n ? 96\n : pickQuality(this.opts.preferredAudioKbps, AUDIO_QUALITIES);\n\n const audioTask = this.preloadOne(\n requestId,\n \"audio\",\n normalized,\n audioKbps,\n );\n\n const tasks = isLongVideo\n ? [audioTask]\n : [\n audioTask,\n this.preloadOne(\n requestId,\n \"video\",\n normalized,\n pickQuality(this.opts.preferredVideoP, VIDEO_QUALITIES),\n ),\n ];\n\n if (isLongVideo) {\n this.opts.logger?.info?.(\n `Long video detected (${Math.floor(metadata.durationSeconds / 60)}min). Audio only mode (96kbps).`,\n );\n }\n\n await Promise.allSettled(tasks);\n this.cache.markLoading(requestId, false);\n }\n\n async getOrDownload(\n requestId: string,\n type: MediaType,\n ): Promise<{ metadata: PlayMetadata; file: CachedFile; direct: boolean }> {\n const entry = this.cache.get(requestId);\n if (!entry) throw new Error(\"Request not found (cache miss).\");\n\n const cached = entry[type];\n if (cached?.path && fs.existsSync(cached.path) && cached.size > 0) {\n return { metadata: entry.metadata, file: cached, direct: false };\n }\n\n const normalized = normalizeYoutubeUrl(entry.metadata.url);\n if (!normalized) throw new Error(\"Invalid YouTube URL.\");\n\n const directFile = await this.downloadDirect(type, normalized);\n return { metadata: entry.metadata, file: directFile, direct: true };\n }\n\n async waitCache(\n requestId: string,\n type: MediaType,\n timeoutMs = 8_000,\n intervalMs = 500,\n ): Promise<CachedFile | null> {\n const started = Date.now();\n while (Date.now() - started < timeoutMs) {\n const entry = this.cache.get(requestId);\n const f = entry?.[type];\n if (f?.path && fs.existsSync(f.path) && f.size > 0) return f;\n await new Promise((r) => setTimeout(r, intervalMs));\n }\n return null;\n }\n\n cleanup(requestId: string): void {\n this.cache.delete(requestId);\n }\n\n private async preloadOne(\n requestId: string,\n type: MediaType,\n youtubeUrl: string,\n quality: number,\n ): Promise<void> {\n try {\n await this._executePreload(requestId, type, youtubeUrl, quality);\n } catch (err) {\n this.opts.logger?.error?.(\n `preload ${type} failed, forcing update...`,\n err,\n );\n await this.forceUpdateCheck();\n\n this.opts.logger?.info?.(\">> Retrying preload after update...\");\n await this._executePreload(requestId, type, youtubeUrl, quality);\n }\n }\n\n private async _executePreload(\n requestId: string,\n type: MediaType,\n youtubeUrl: string,\n quality: number,\n ): Promise<void> {\n const uniqueSuffix = Math.random().toString(36).slice(2, 8);\n const safeTitle = sanitizeFilename(`temp_${Date.now()}_${uniqueSuffix}`);\n const ext = type === \"audio\" ? \"m4a\" : \"mp4\";\n const filename = `${type}_${requestId}_${safeTitle}.${ext}`;\n const filePath = path.join(this.paths.cacheDir, filename);\n\n const info: DownloadInfo =\n type === \"audio\"\n ? await this.ytdlp.getAudio(youtubeUrl, quality, filePath)\n : await this.ytdlp.getVideo(youtubeUrl, quality, filePath);\n\n const stats = fs.statSync(filePath);\n const size = stats.size;\n\n let buffer: Buffer | undefined;\n if (this.opts.preloadBuffer) {\n buffer = await fs.promises.readFile(filePath);\n }\n\n const cached: CachedFile = {\n path: filePath,\n size,\n info: { quality: info.quality },\n buffer,\n };\n\n this.cache.setFile(requestId, type, cached);\n this.opts.logger?.debug?.(`preloaded ${type} ${size} bytes: ${filename}`);\n }\n\n private async downloadDirect(\n type: MediaType,\n youtubeUrl: string,\n ): Promise<CachedFile> {\n try {\n return await this._executeDownloadDirect(type, youtubeUrl, false);\n } catch (err) {\n this.opts.logger?.error?.(\"Direct download failed:\", err);\n await this.forceUpdateCheck();\n\n this.opts.logger?.info?.(\">> Retrying download after update...\");\n return await this._executeDownloadDirect(type, youtubeUrl, true);\n }\n }\n\n private async _executeDownloadDirect(\n type: MediaType,\n youtubeUrl: string,\n isRetry: boolean,\n ): Promise<CachedFile> {\n const audioKbps = pickQuality(\n this.opts.preferredAudioKbps,\n AUDIO_QUALITIES,\n );\n const videoP = pickQuality(this.opts.preferredVideoP, VIDEO_QUALITIES);\n const ext = type === \"audio\" ? \"m4a\" : \"mp4\";\n const prefix = isRetry ? \"direct_retry\" : \"direct\";\n const uniqueSuffix = Math.random().toString(36).slice(2, 8);\n const safeTitle = sanitizeFilename(\n `${prefix}_${Date.now()}_${uniqueSuffix}`,\n );\n const filePath = path.join(\n this.paths.cacheDir,\n `${type}_${safeTitle}.${ext}`,\n );\n\n const info =\n type === \"audio\"\n ? await this.ytdlp.getAudio(youtubeUrl, audioKbps, filePath)\n : await this.ytdlp.getVideo(youtubeUrl, videoP, filePath);\n\n const stats = fs.statSync(filePath);\n return {\n path: filePath,\n size: stats.size,\n info: { quality: info.quality },\n };\n }\n}\n","import fs from \"node:fs\";\n\nimport type { CacheEntry, MediaType } from \"./types.js\";\n\nexport class CacheStore {\n private readonly store = new Map<string, CacheEntry>();\n private cleanupTimer?: NodeJS.Timeout;\n\n constructor(\n private readonly opts: {\n cleanupIntervalMs: number;\n }\n ) {}\n\n get(requestId: string): CacheEntry | undefined {\n return this.store.get(requestId);\n }\n\n set(requestId: string, entry: CacheEntry): void {\n this.store.set(requestId, entry);\n }\n\n has(requestId: string): boolean {\n return this.store.has(requestId);\n }\n\n delete(requestId: string): void {\n this.cleanupEntry(requestId);\n this.store.delete(requestId);\n }\n\n markLoading(requestId: string, loading: boolean): void {\n const e = this.store.get(requestId);\n if (e) e.loading = loading;\n }\n\n setFile(\n requestId: string,\n type: MediaType,\n file: CacheEntry[MediaType]\n ): void {\n const e = this.store.get(requestId);\n if (!e) return;\n e[type] = file as any;\n }\n\n cleanupExpired(now = Date.now()): number {\n let removed = 0;\n for (const [requestId, entry] of this.store.entries()) {\n if (now > entry.expiresAt) {\n this.delete(requestId);\n removed++;\n }\n }\n return removed;\n }\n\n start(): void {\n if (this.cleanupTimer) return;\n\n this.cleanupTimer = setInterval(() => {\n this.cleanupExpired(Date.now());\n }, this.opts.cleanupIntervalMs);\n\n this.cleanupTimer.unref();\n }\n\n stop(): void {\n if (!this.cleanupTimer) return;\n clearInterval(this.cleanupTimer);\n this.cleanupTimer = undefined;\n }\n\n private cleanupEntry(requestId: string) {\n const entry = this.store.get(requestId);\n if (!entry) return;\n\n ([\"audio\", \"video\"] as const).forEach((type) => {\n const f = entry[type];\n if (f?.path && fs.existsSync(f.path)) {\n try {\n fs.unlinkSync(f.path);\n } catch {\n // ignore\n }\n }\n });\n }\n}\n","import fs from \"node:fs\";\nimport path from \"node:path\";\nimport os from \"node:os\";\n\nexport interface ResolvedPaths {\n baseDir: string;\n cacheDir: string;\n}\n\nexport function ensureDirSync(dirPath: string) {\n fs.mkdirSync(dirPath, { recursive: true, mode: 0o777 });\n\n try {\n fs.chmodSync(dirPath, 0o777);\n } catch {\n // ignore\n }\n\n fs.accessSync(dirPath, fs.constants.R_OK | fs.constants.W_OK);\n}\n\nexport function resolvePaths(cacheDir?: string): ResolvedPaths {\n const baseDir = cacheDir?.trim()\n ? cacheDir\n : path.join(os.tmpdir(), \"yt-play\");\n const resolvedBase = path.resolve(baseDir);\n\n const resolvedCache = path.join(resolvedBase);\n\n ensureDirSync(resolvedBase);\n ensureDirSync(resolvedCache);\n\n return {\n baseDir: resolvedBase,\n cacheDir: resolvedCache,\n };\n}\n","import { spawn } from \"node:child_process\";\nimport path from \"node:path\";\nimport fs from \"node:fs\";\nimport type { DownloadInfo } from \"./types.js\";\n\nlet __dirname: string;\ntry {\n // @ts-ignore\n __dirname = path.dirname(new URL(import.meta.url).pathname);\n} catch {\n // @ts-ignore\n __dirname = typeof __dirname !== \"undefined\" ? __dirname : process.cwd();\n}\n\nexport interface YtDlpClientOptions {\n binaryPath?: string;\n ffmpegPath?: string;\n aria2cPath?: string;\n timeoutMs?: number;\n useAria2c?: boolean;\n concurrentFragments?: number;\n cookiesPath?: string;\n cookiesFromBrowser?: string;\n}\n\nexport interface YtDlpVideoInfo {\n id: string;\n title: string;\n uploader?: string;\n duration: number;\n thumbnail?: string;\n}\n\nexport interface YtDlpPlaylistItem {\n id: string;\n title: string;\n url: string;\n duration?: number;\n uploader?: string;\n directUrl?: string;\n}\n\nexport interface YtDlpResolvedItem extends YtDlpPlaylistItem {\n directUrl: string;\n}\n\nexport interface YtDlpPlaylistInfo {\n id: string;\n title: string;\n uploader?: string;\n entries: YtDlpPlaylistItem[] | YtDlpResolvedItem[];\n}\n\nexport interface YtDlpPlaylistOptions {\n limit?: number;\n resolveLinks?: boolean;\n type?: \"audio\" | \"video\" | \"both\";\n batchSize?: number;\n}\n\nexport class YtDlpClient {\n private readonly binaryPath: string;\n private readonly ffmpegPath?: string;\n private readonly aria2cPath?: string;\n private readonly timeoutMs: number;\n private readonly useAria2c: boolean;\n private readonly concurrentFragments: number;\n private readonly cookiesPath?: string;\n private readonly cookiesFromBrowser?: string;\n\n constructor(opts: YtDlpClientOptions = {}) {\n this.binaryPath = opts.binaryPath || this.detectYtDlp();\n this.ffmpegPath = opts.ffmpegPath;\n this.timeoutMs = opts.timeoutMs ?? 300_000;\n this.concurrentFragments = opts.concurrentFragments ?? 5;\n this.cookiesPath = opts.cookiesPath;\n this.cookiesFromBrowser = opts.cookiesFromBrowser;\n\n this.aria2cPath = opts.aria2cPath || this.detectAria2c();\n this.useAria2c = opts.useAria2c ?? !!this.aria2cPath;\n }\n\n private detectYtDlp(): string {\n const packageRoot = path.resolve(__dirname, \"../..\");\n const bundledPaths = [\n path.join(packageRoot, \"bin\", \"yt-dlp\"),\n path.join(packageRoot, \"bin\", \"yt-dlp.exe\"),\n ];\n\n for (const p of bundledPaths) {\n if (fs.existsSync(p)) {\n return p;\n }\n }\n\n try {\n const { execSync } = require(\"node:child_process\");\n const cmd =\n process.platform === \"win32\" ? \"where yt-dlp\" : \"which yt-dlp\";\n const result = execSync(cmd, { encoding: \"utf-8\" }).trim();\n if (result) return result.split(\"\\n\")[0];\n } catch {}\n\n return \"yt-dlp\";\n }\n\n private detectAria2c(): string | undefined {\n const packageRoot = path.resolve(__dirname, \"../..\");\n const bundledPaths = [\n path.join(packageRoot, \"bin\", \"aria2c\"),\n path.join(packageRoot, \"bin\", \"aria2c.exe\"),\n ];\n\n for (const p of bundledPaths) {\n if (fs.existsSync(p)) {\n return p;\n }\n }\n\n try {\n const { execSync } = require(\"node:child_process\");\n const cmd =\n process.platform === \"win32\" ? \"where aria2c\" : \"which aria2c\";\n const result = execSync(cmd, { encoding: \"utf-8\" }).trim();\n if (result) return result.split(\"\\n\")[0];\n } catch {}\n\n return undefined;\n }\n\n private async exec(args: string[]): Promise<string> {\n return new Promise((resolve, reject) => {\n let allArgs = [...args];\n\n if (this.ffmpegPath) {\n allArgs = [\"--ffmpeg-location\", this.ffmpegPath, ...allArgs];\n }\n\n if (this.cookiesPath && fs.existsSync(this.cookiesPath)) {\n allArgs = [\"--cookies\", this.cookiesPath, ...allArgs];\n }\n\n if (this.cookiesFromBrowser) {\n allArgs = [\n \"--cookies-from-browser\",\n this.cookiesFromBrowser,\n ...allArgs,\n ];\n }\n\n const proc = spawn(this.binaryPath, allArgs, {\n stdio: [\"ignore\", \"pipe\", \"pipe\"],\n });\n\n let stdout = \"\";\n let stderr = \"\";\n\n proc.stdout.on(\"data\", (chunk) => {\n stdout += chunk.toString();\n });\n\n proc.stderr.on(\"data\", (chunk) => {\n stderr += chunk.toString();\n });\n\n const timer = setTimeout(() => {\n proc.kill(\"SIGKILL\");\n reject(new Error(`yt-dlp timeout after ${this.timeoutMs}ms`));\n }, this.timeoutMs);\n\n proc.on(\"close\", (code) => {\n clearTimeout(timer);\n if (code === 0) {\n resolve(stdout);\n } else {\n reject(\n new Error(\n `yt-dlp exited with code ${code}. stderr: ${stderr.slice(0, 500)}`,\n ),\n );\n }\n });\n\n proc.on(\"error\", (err) => {\n clearTimeout(timer);\n reject(err);\n });\n });\n }\n\n async getInfo(youtubeUrl: string): Promise<YtDlpVideoInfo> {\n const stdout = await this.exec([\n \"-J\",\n \"--no-warnings\",\n \"--no-playlist\",\n youtubeUrl,\n ]);\n const info = JSON.parse(stdout) as YtDlpVideoInfo;\n return info;\n }\n\n async getPlaylistInfo(\n youtubeUrl: string,\n opts: YtDlpPlaylistOptions = {},\n ): Promise<YtDlpPlaylistInfo> {\n const stdout = await this.exec([\n \"-J\",\n \"--flat-playlist\",\n \"--no-warnings\",\n youtubeUrl,\n ]);\n\n const info = JSON.parse(stdout);\n\n let entries: YtDlpPlaylistItem[] = (info.entries || [])\n .filter((entry: any) => {\n const t = entry.title || \"\";\n return (\n entry.id &&\n t &&\n !t.includes(\"[Deleted video]\") &&\n !t.includes(\"[Private video]\")\n );\n })\n .map((entry: any) => ({\n id: entry.id,\n title: entry.title,\n url: `https://www.youtube.com/watch?v=${entry.id}`,\n duration: entry.duration,\n uploader: entry.uploader || info.uploader,\n }));\n\n if (opts.resolveLinks) {\n entries = await this.resolvePlaylistItems(\n entries,\n opts.limit && opts.limit > 0 ? opts.limit : entries.length,\n opts.type || \"audio\",\n opts.batchSize || 5,\n );\n } else if (opts.limit && opts.limit > 0) {\n entries = entries.slice(0, opts.limit);\n }\n\n return {\n id: info.id,\n title: info.title,\n uploader: info.uploader,\n entries,\n };\n }\n\n async getDirectUrl(\n youtubeUrl: string,\n type: \"audio\" | \"video\" | \"both\" = \"audio\",\n ): Promise<string> {\n const format =\n type === \"audio\"\n ? \"bestaudio[ext=m4a]/bestaudio/best\"\n : \"best[ext=mp4]/best\";\n\n const stdout = await this.exec([\n \"-f\",\n format,\n \"-g\",\n \"--no-warnings\",\n youtubeUrl,\n ]);\n\n return stdout.trim().split(\"\\n\")[0];\n }\n\n async resolvePlaylistItems(\n entries: YtDlpPlaylistItem[],\n limit: number,\n type: \"audio\" | \"video\" | \"both\" = \"audio\",\n batchSize: number = 5,\n ): Promise<YtDlpResolvedItem[]> {\n const resolved: YtDlpResolvedItem[] = [];\n let currentIndex = 0;\n\n // Continua processando até atingir o limite solicitado ou a playlist acabar\n while (resolved.length < limit && currentIndex < entries.length) {\n const needed = limit - resolved.length;\n const takeCount = Math.min(\n batchSize,\n needed,\n entries.length - currentIndex,\n );\n\n const batch = entries.slice(currentIndex, currentIndex + takeCount);\n currentIndex += takeCount;\n\n const promises = batch.map(async (item) => {\n try {\n const directUrl = await this.getDirectUrl(item.url, type);\n return { ...item, directUrl } as YtDlpResolvedItem;\n } catch (err) {\n return null; // Ignora os que falharem\n }\n });\n\n const results = await Promise.all(promises);\n\n for (const res of results) {\n if (res && resolved.length < limit) {\n resolved.push(res);\n }\n }\n }\n\n return resolved;\n }\n\n private buildOptimizationArgs(): string[] {\n const args: string[] = [\n \"--no-warnings\",\n \"--no-playlist\",\n \"--no-check-certificates\",\n \"--concurrent-fragments\",\n String(this.concurrentFragments),\n ];\n\n if (this.useAria2c && this.aria2cPath) {\n args.push(\"--downloader\", this.aria2cPath);\n args.push(\"--downloader-args\", \"aria2c:-x 16 -s 16 -k 1M\");\n }\n\n return args;\n }\n\n async getAudio(\n youtubeUrl: string,\n qualityKbps: number,\n outputPath: string,\n ): Promise<DownloadInfo> {\n const info = await this.getInfo(youtubeUrl);\n const format = \"bestaudio[ext=m4a]/bestaudio/best\";\n\n const args = [\n \"-f\",\n format,\n \"-o\",\n outputPath,\n ...this.buildOptimizationArgs(),\n youtubeUrl,\n ];\n\n try {\n await this.exec(args);\n } catch (err) {\n [outputPath, `${outputPath}.part`, `${outputPath}.ytdl`].forEach((f) => {\n if (fs.existsSync(f)) {\n try {\n fs.unlinkSync(f);\n } catch {}\n }\n });\n throw err;\n }\n\n if (!fs.existsSync(outputPath)) {\n throw new Error(`yt-dlp failed to create audio file: ${outputPath}`);\n }\n\n const duration = this.formatDuration(info.duration);\n\n return {\n title: info.title,\n author: info.uploader,\n duration,\n quality: `${qualityKbps}kbps m4a`,\n filename: path.basename(outputPath),\n downloadUrl: outputPath,\n };\n }\n\n async getVideo(\n youtubeUrl: string,\n qualityP: number,\n outputPath: string,\n ): Promise<DownloadInfo> {\n const info = await this.getInfo(youtubeUrl);\n\n const format = `bestvideo[height<=${qualityP}][ext=mp4][vcodec^=avc]+bestaudio[ext=m4a]/bestvideo[height<=${qualityP}][vcodec!=av1][vcodec!=vp9]+bestaudio/best[height<=${qualityP}]`;\n\n const args = [\n \"-f\",\n format,\n \"--merge-output-format\",\n \"mp4\",\n \"-o\",\n outputPath,\n ...this.buildOptimizationArgs(),\n youtubeUrl,\n ];\n\n try {\n await this.exec(args);\n } catch (err) {\n [outputPath, `${outputPath}.part`, `${outputPath}.ytdl`].forEach((f) => {\n if (fs.existsSync(f)) {\n try {\n fs.unlinkSync(f);\n } catch {}\n }\n });\n throw err;\n }\n\n if (!fs.existsSync(outputPath)) {\n throw new Error(`yt-dlp failed to create video file: ${outputPath}`);\n }\n\n const duration = this.formatDuration(info.duration);\n\n return {\n title: info.title,\n author: info.uploader,\n duration,\n quality: `${qualityP}p H.264`,\n filename: path.basename(outputPath),\n downloadUrl: outputPath,\n };\n }\n\n private formatDuration(seconds: number): string {\n if (!seconds) return \"0:00\";\n const h = Math.floor(seconds / 3600);\n const m = Math.floor((seconds % 3600) / 60);\n const s = Math.floor(seconds % 60);\n if (h > 0) {\n return `${h}:${m.toString().padStart(2, \"0\")}:${s.toString().padStart(2, \"0\")}`;\n }\n return `${m}:${s.toString().padStart(2, \"0\")}`;\n }\n}\n","import yts from \"yt-search\";\n\nimport type { PlayMetadata } from \"./types.js\";\n\nexport function stripWeirdUrlWrappers(input: string): string {\n let s = (input || \"\").trim();\n const mdAll = [...s.matchAll(/\\[[^\\]]*\\]\\((https?:\\/\\/[^)\\s]+)\\)/gi)];\n if (mdAll.length > 0) return mdAll[0][1].trim();\n s = s.replace(/^<([^>]+)>$/, \"$1\").trim();\n s = s.replace(/^[\"'`](.*)[\"'`]$/, \"$1\").trim();\n\n return s;\n}\n\nexport function getYouTubeVideoId(input: string): string | null {\n const regex =\n /(?:https?:\\/\\/)?(?:www\\.)?(?:youtube\\.com\\/(?:[^\\/]+\\/.+\\/|(?:v|e(?:mbed)?)\\/|.*[?&]v=|shorts\\/)|youtu\\.be\\/)([a-zA-Z0-9_-]{11})(?:[?&]|$)/i;\n\n const match = (input || \"\").match(regex);\n return match ? match[1] : null;\n}\n\nexport function getYouTubePlaylistId(input: string): string | null {\n const regex = /[?&]list=([a-zA-Z0-9_-]+)/i;\n const match = (input || \"\").match(regex);\n return match ? match[1] : null;\n}\n\nexport function normalizeYoutubeUrl(input: string): string | null {\n const cleaned0 = stripWeirdUrlWrappers(input);\n\n const firstUrl = cleaned0.match(/https?:\\/\\/[^\\s)]+/i)?.[0] ?? cleaned0;\n\n const id = getYouTubeVideoId(firstUrl);\n if (!id) return null;\n\n return `https://www.youtube.com/watch?v=${id}`;\n}\n\nexport async function searchBest(query: string): Promise<PlayMetadata | null> {\n const videoId = getYouTubeVideoId(query);\n\n if (videoId) {\n const video = await yts({ videoId });\n\n if (!video) return null;\n\n const durationSeconds = video.duration?.seconds ?? 0;\n const normalizedUrl = normalizeYoutubeUrl(video.url) ?? video.url;\n\n return {\n title: video.title || \"Untitled\",\n author: video.author?.name || undefined,\n duration: video.duration?.timestamp || undefined,\n thumb: video.image || video.thumbnail || undefined,\n videoId: video.videoId,\n url: normalizedUrl,\n durationSeconds,\n };\n }\n\n const result = await yts(query);\n const v = result?.videos?.[0];\n if (!v) return null;\n\n const durationSeconds = v.duration?.seconds ?? 0;\n const normalizedUrl = normalizeYoutubeUrl(v.url) ?? v.url;\n\n return {\n title: v.title || \"Untitled\",\n author: v.author?.name || undefined,\n duration: v.duration?.timestamp || undefined,\n thumb: v.image || v.thumbnail || undefined,\n videoId: v.videoId,\n url: normalizedUrl,\n durationSeconds,\n };\n}\n\nexport async function searchPlaylistBest(\n query: string,\n): Promise<PlayMetadata | null> {\n const listId = getYouTubePlaylistId(query);\n\n if (listId) {\n try {\n const list = await yts({ listId });\n\n if (!list) return null;\n\n return {\n title: list.title || \"Untitled\",\n author: list.author?.name || undefined,\n duration: undefined,\n durationSeconds: 0,\n thumb: list.image || list.thumbnail || undefined,\n videoId: listId,\n url: list.url || `https://www.youtube.com/playlist?list=${listId}`,\n };\n } catch (err) {}\n }\n\n const result = await yts(query);\n const p = result?.playlists?.[0];\n if (!p) return null;\n\n return {\n title: p.title || \"Untitled\",\n author: p.author?.name || undefined,\n duration: undefined,\n durationSeconds: 0,\n thumb: p.image || p.thumbnail || undefined,\n videoId: p.listId,\n url: p.url || `https://www.youtube.com/playlist?list=${p.listId}`,\n };\n}\n"],"mappings":"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","names":["fs","path","execSync","fs","CacheStore","opts","requestId","entry","loading","type","file","e","now","removed","f","fs","path","os","ensureDirSync","dirPath","resolvePaths","cacheDir","baseDir","resolvedBase","resolvedCache","spawn","path","fs","__dirname","YtDlpClient","opts","packageRoot","bundledPaths","p","execSync","cmd","result","args","resolve","reject","allArgs","proc","stdout","stderr","chunk","timer","code","err","youtubeUrl","info","entries","entry","t","type","format","limit","batchSize","resolved","currentIndex","needed","takeCount","batch","promises","item","directUrl","results","res","qualityKbps","outputPath","f","duration","qualityP","seconds","h","m","s","yts","stripWeirdUrlWrappers","input","s","mdAll","getYouTubeVideoId","regex","match","getYouTubePlaylistId","normalizeYoutubeUrl","cleaned0","firstUrl","id","searchBest","query","videoId","video","durationSeconds","normalizedUrl","v","searchPlaylistBest","listId","list","p","AUDIO_QUALITIES","VIDEO_QUALITIES","UPDATE_CHECK_INTERVAL","pickQuality","requested","available","sanitizeFilename","filename","getNodeBinaryPath","execSync","setupYtDlpConfig","ytdlpBinaryPath","cookiesPath","cookiesFromBrowser","binaryDir","path","moduleUrl","fs","configPath","configLines","nodePath","PlayEngine","_PlayEngine","options","resolvePaths","CacheStore","YtDlpClient","now","scriptPath","checkAndUpdate","error","prefix","query","searchBest","requestId","metadata","normalized","normalizeYoutubeUrl","isLongVideo","normalizedMeta","audioKbps","audioTask","tasks","type","entry","cached","directFile","timeoutMs","intervalMs","started","f","r","youtubeUrl","quality","err","uniqueSuffix","safeTitle","filePath","info","size","buffer","isRetry","videoP","ext","stats"]}
|
|
1
|
+
{"version":3,"sources":["../src/core/play-engine.ts","../src/core/cache.ts","../src/core/paths.ts","../src/core/ytdlp-client.ts","../src/core/youtube.ts","../src/core/stream.ts","../src/core/stalker.ts"],"sourcesContent":["import fs from \"node:fs\";\nimport path from \"node:path\";\nimport { execSync } from \"node:child_process\";\nimport type {\n CacheEntry,\n CachedFile,\n DownloadInfo,\n MediaType,\n PlayEngineOptions,\n PlayMetadata,\n} from \"./types.js\";\nimport { CacheStore } from \"./cache.js\";\nimport { resolvePaths } from \"./paths.js\";\nimport { YtDlpClient } from \"./ytdlp-client.js\";\nimport { normalizeYoutubeUrl, searchBest } from \"./youtube.js\";\nimport { StreamEngine } from \"./stream.js\";\n\nconst AUDIO_QUALITIES = [320, 256, 192, 128, 96, 64] as const;\nconst VIDEO_QUALITIES = [1080, 720, 480, 360] as const;\nconst UPDATE_CHECK_INTERVAL = 3600000;\n\nfunction pickQuality<T extends number>(\n requested: T,\n available: readonly T[],\n): T {\n return (available as readonly number[]).includes(requested)\n ? requested\n : available[0];\n}\n\nfunction sanitizeFilename(filename: string): string {\n return (filename || \"\")\n .replace(/[\\\\/:*?\"<>|]/g, \"\")\n .replace(/[^\\w\\s-]/gi, \"\")\n .trim()\n .replace(/\\s+/g, \" \")\n .substring(0, 100);\n}\n\nfunction getNodeBinaryPath(): string | null {\n try {\n const nodePath = execSync(\"which node\", { encoding: \"utf-8\" }).trim();\n return nodePath || null;\n } catch {\n return process.execPath || null;\n }\n}\n\nfunction setupYtDlpConfig(\n ytdlpBinaryPath: string | undefined,\n cookiesPath: string | undefined,\n cookiesFromBrowser: string | undefined,\n): void {\n try {\n let binaryDir: string;\n\n if (ytdlpBinaryPath) {\n binaryDir = path.dirname(ytdlpBinaryPath);\n } else {\n try {\n const moduleUrl = new URL(import.meta.url);\n binaryDir = path.join(path.dirname(moduleUrl.pathname), \"..\", \"bin\");\n } catch {\n binaryDir = path.join(__dirname, \"..\", \"bin\");\n }\n }\n\n if (!fs.existsSync(binaryDir)) {\n return;\n }\n\n const configPath = path.join(binaryDir, \"yt-dlp.conf\");\n const configLines: string[] = [];\n const nodePath = getNodeBinaryPath();\n\n if (nodePath) {\n configLines.push(`--js-runtimes node:${nodePath}`);\n }\n\n configLines.push(\"--remote-components ejs:npm\");\n\n if (cookiesPath && fs.existsSync(cookiesPath)) {\n configLines.push(`--cookies ${cookiesPath}`);\n } else if (cookiesFromBrowser) {\n configLines.push(`--cookies-from-browser ${cookiesFromBrowser}`);\n }\n\n fs.writeFileSync(configPath, configLines.join(\"\\n\") + \"\\n\", \"utf-8\");\n } catch (error) {}\n}\n\nexport class PlayEngine {\n private readonly opts: Required<\n Pick<\n PlayEngineOptions,\n | \"ttlMs\"\n | \"maxPreloadDurationSeconds\"\n | \"preferredAudioKbps\"\n | \"preferredVideoP\"\n | \"preloadBuffer\"\n | \"cleanupIntervalMs\"\n | \"concurrentFragments\"\n >\n > &\n Pick<PlayEngineOptions, \"useAria2c\" | \"logger\">;\n\n private readonly paths: { baseDir: string; cacheDir: string };\n readonly cache: CacheStore;\n private readonly ytdlp: YtDlpClient;\n\n private static lastUpdateCheck: number = 0;\n private static updatePromise: Promise<void> | null = null;\n\n constructor(options: PlayEngineOptions = {}) {\n this.opts = {\n ttlMs: options.ttlMs ?? 3 * 60_000,\n maxPreloadDurationSeconds: options.maxPreloadDurationSeconds ?? 20 * 60,\n preferredAudioKbps: options.preferredAudioKbps ?? 128,\n preferredVideoP: options.preferredVideoP ?? 720,\n preloadBuffer: options.preloadBuffer ?? true,\n cleanupIntervalMs: options.cleanupIntervalMs ?? 30_000,\n concurrentFragments: options.concurrentFragments ?? 5,\n useAria2c: options.useAria2c,\n logger: options.logger,\n };\n\n this.paths = resolvePaths(options.cacheDir);\n this.cache = new CacheStore({\n cleanupIntervalMs: this.opts.cleanupIntervalMs,\n });\n this.cache.start();\n\n setupYtDlpConfig(\n options.ytdlpBinaryPath,\n options.cookiesPath,\n options.cookiesFromBrowser,\n );\n\n this.ytdlp = new YtDlpClient({\n binaryPath: options.ytdlpBinaryPath,\n ffmpegPath: options.ffmpegPath,\n aria2cPath: options.aria2cPath,\n useAria2c: this.opts.useAria2c,\n concurrentFragments: this.opts.concurrentFragments,\n timeoutMs: options.ytdlpTimeoutMs ?? 300_000,\n cookiesPath: options.cookiesPath,\n cookiesFromBrowser: options.cookiesFromBrowser,\n });\n\n this.stream = new StreamEngine(this.ytdlp);\n\n this.backgroundUpdateCheck();\n }\n\n public readonly stream: StreamEngine;\n\n private backgroundUpdateCheck(): void {\n const now = Date.now();\n\n if (now - PlayEngine.lastUpdateCheck < UPDATE_CHECK_INTERVAL) {\n return;\n }\n\n if (PlayEngine.updatePromise) {\n return;\n }\n\n PlayEngine.updatePromise = (async () => {\n try {\n PlayEngine.lastUpdateCheck = now;\n const scriptPath = new URL(\n \"../scripts/check-ytdlp-update.mjs\",\n import.meta.url,\n );\n const { checkAndUpdate } = await import(scriptPath.href);\n const updated = await checkAndUpdate();\n\n if (updated) {\n this.opts.logger?.info?.(\"✓ yt-dlp updated to latest version\");\n }\n } catch (error) {\n this.opts.logger?.debug?.(\"Update check failed (will retry later)\");\n } finally {\n PlayEngine.updatePromise = null;\n }\n })();\n }\n\n private async forceUpdateCheck(): Promise<void> {\n if (PlayEngine.updatePromise) {\n this.opts.logger?.info?.(\"Update already in progress, waiting...\");\n return PlayEngine.updatePromise;\n }\n\n PlayEngine.updatePromise = (async () => {\n try {\n this.opts.logger?.warn?.(\n \"<!> Download failed. Forcing yt-dlp update check...\",\n );\n\n const scriptPath = new URL(\n \"../scripts/check-ytdlp-update.mjs\",\n import.meta.url,\n );\n const { checkAndUpdate } = await import(scriptPath.href);\n const updated = await checkAndUpdate();\n\n if (updated) {\n this.opts.logger?.info?.(\"✓ yt-dlp updated successfully\");\n PlayEngine.lastUpdateCheck = Date.now();\n }\n } catch (error) {\n this.opts.logger?.error?.(\"Failed to update yt-dlp:\", error);\n } finally {\n PlayEngine.updatePromise = null;\n }\n })();\n\n return PlayEngine.updatePromise;\n }\n\n generateRequestId(prefix = \"play\"): string {\n return `${prefix}_${Date.now()}_${Math.random().toString(36).slice(2, 8)}`;\n }\n\n async search(query: string): Promise<PlayMetadata | null> {\n return searchBest(query);\n }\n\n getFromCache(requestId: string): CacheEntry | undefined {\n return this.cache.get(requestId);\n }\n\n async preload(metadata: PlayMetadata, requestId: string): Promise<void> {\n const normalized = normalizeYoutubeUrl(metadata.url);\n if (!normalized) throw new Error(\"Invalid YouTube URL.\");\n\n const isLongVideo = metadata.durationSeconds > 3600;\n\n if (metadata.durationSeconds > this.opts.maxPreloadDurationSeconds) {\n this.opts.logger?.warn?.(\n `Video too long for preload (${Math.floor(metadata.durationSeconds / 60)}min). Will use direct download with reduced quality.`,\n );\n }\n\n const normalizedMeta: PlayMetadata = { ...metadata, url: normalized };\n\n this.cache.set(requestId, {\n metadata: normalizedMeta,\n audio: null,\n video: null,\n expiresAt: Date.now() + this.opts.ttlMs,\n loading: true,\n });\n\n const audioKbps = isLongVideo\n ? 96\n : pickQuality(this.opts.preferredAudioKbps, AUDIO_QUALITIES);\n\n const audioTask = this.preloadOne(\n requestId,\n \"audio\",\n normalized,\n audioKbps,\n );\n\n const tasks = isLongVideo\n ? [audioTask]\n : [\n audioTask,\n this.preloadOne(\n requestId,\n \"video\",\n normalized,\n pickQuality(this.opts.preferredVideoP, VIDEO_QUALITIES),\n ),\n ];\n\n if (isLongVideo) {\n this.opts.logger?.info?.(\n `Long video detected (${Math.floor(metadata.durationSeconds / 60)}min). Audio only mode (96kbps).`,\n );\n }\n\n await Promise.allSettled(tasks);\n this.cache.markLoading(requestId, false);\n }\n\n async getOrDownload(\n requestId: string,\n type: MediaType,\n ): Promise<{ metadata: PlayMetadata; file: CachedFile; direct: boolean }> {\n const entry = this.cache.get(requestId);\n if (!entry) throw new Error(\"Request not found (cache miss).\");\n\n const cached = entry[type];\n if (cached?.path && fs.existsSync(cached.path) && cached.size > 0) {\n return { metadata: entry.metadata, file: cached, direct: false };\n }\n\n const normalized = normalizeYoutubeUrl(entry.metadata.url);\n if (!normalized) throw new Error(\"Invalid YouTube URL.\");\n\n const directFile = await this.downloadDirect(type, normalized);\n return { metadata: entry.metadata, file: directFile, direct: true };\n }\n\n async waitCache(\n requestId: string,\n type: MediaType,\n timeoutMs = 8_000,\n intervalMs = 500,\n ): Promise<CachedFile | null> {\n const started = Date.now();\n while (Date.now() - started < timeoutMs) {\n const entry = this.cache.get(requestId);\n const f = entry?.[type];\n if (f?.path && fs.existsSync(f.path) && f.size > 0) return f;\n await new Promise((r) => setTimeout(r, intervalMs));\n }\n return null;\n }\n\n cleanup(requestId: string): void {\n this.cache.delete(requestId);\n }\n\n private async preloadOne(\n requestId: string,\n type: MediaType,\n youtubeUrl: string,\n quality: number,\n ): Promise<void> {\n try {\n await this._executePreload(requestId, type, youtubeUrl, quality);\n } catch (err) {\n this.opts.logger?.error?.(\n `preload ${type} failed, forcing update...`,\n err,\n );\n await this.forceUpdateCheck();\n\n this.opts.logger?.info?.(\">> Retrying preload after update...\");\n await this._executePreload(requestId, type, youtubeUrl, quality);\n }\n }\n\n private async _executePreload(\n requestId: string,\n type: MediaType,\n youtubeUrl: string,\n quality: number,\n ): Promise<void> {\n const uniqueSuffix = Math.random().toString(36).slice(2, 8);\n const safeTitle = sanitizeFilename(`temp_${Date.now()}_${uniqueSuffix}`);\n const ext = type === \"audio\" ? \"m4a\" : \"mp4\";\n const filename = `${type}_${requestId}_${safeTitle}.${ext}`;\n const filePath = path.join(this.paths.cacheDir, filename);\n\n const info: DownloadInfo =\n type === \"audio\"\n ? await this.ytdlp.getAudio(youtubeUrl, quality, filePath)\n : await this.ytdlp.getVideo(youtubeUrl, quality, filePath);\n\n const stats = fs.statSync(filePath);\n const size = stats.size;\n\n let buffer: Buffer | undefined;\n if (this.opts.preloadBuffer) {\n buffer = await fs.promises.readFile(filePath);\n }\n\n const cached: CachedFile = {\n path: filePath,\n size,\n info: { quality: info.quality },\n buffer,\n };\n\n this.cache.setFile(requestId, type, cached);\n this.opts.logger?.debug?.(`preloaded ${type} ${size} bytes: ${filename}`);\n }\n\n private async downloadDirect(\n type: MediaType,\n youtubeUrl: string,\n ): Promise<CachedFile> {\n try {\n return await this._executeDownloadDirect(type, youtubeUrl, false);\n } catch (err) {\n this.opts.logger?.error?.(\"Direct download failed:\", err);\n await this.forceUpdateCheck();\n\n this.opts.logger?.info?.(\">> Retrying download after update...\");\n return await this._executeDownloadDirect(type, youtubeUrl, true);\n }\n }\n\n private async _executeDownloadDirect(\n type: MediaType,\n youtubeUrl: string,\n isRetry: boolean,\n ): Promise<CachedFile> {\n const audioKbps = pickQuality(\n this.opts.preferredAudioKbps,\n AUDIO_QUALITIES,\n );\n const videoP = pickQuality(this.opts.preferredVideoP, VIDEO_QUALITIES);\n const ext = type === \"audio\" ? \"m4a\" : \"mp4\";\n const prefix = isRetry ? \"direct_retry\" : \"direct\";\n const uniqueSuffix = Math.random().toString(36).slice(2, 8);\n const safeTitle = sanitizeFilename(\n `${prefix}_${Date.now()}_${uniqueSuffix}`,\n );\n const filePath = path.join(\n this.paths.cacheDir,\n `${type}_${safeTitle}.${ext}`,\n );\n\n const info =\n type === \"audio\"\n ? await this.ytdlp.getAudio(youtubeUrl, audioKbps, filePath)\n : await this.ytdlp.getVideo(youtubeUrl, videoP, filePath);\n\n const stats = fs.statSync(filePath);\n return {\n path: filePath,\n size: stats.size,\n info: { quality: info.quality },\n };\n }\n}\n","import fs from \"node:fs\";\n\nimport type { CacheEntry, MediaType } from \"./types.js\";\n\nexport class CacheStore {\n private readonly store = new Map<string, CacheEntry>();\n private cleanupTimer?: NodeJS.Timeout;\n\n constructor(\n private readonly opts: {\n cleanupIntervalMs: number;\n }\n ) {}\n\n get(requestId: string): CacheEntry | undefined {\n return this.store.get(requestId);\n }\n\n set(requestId: string, entry: CacheEntry): void {\n this.store.set(requestId, entry);\n }\n\n has(requestId: string): boolean {\n return this.store.has(requestId);\n }\n\n delete(requestId: string): void {\n this.cleanupEntry(requestId);\n this.store.delete(requestId);\n }\n\n markLoading(requestId: string, loading: boolean): void {\n const e = this.store.get(requestId);\n if (e) e.loading = loading;\n }\n\n setFile(\n requestId: string,\n type: MediaType,\n file: CacheEntry[MediaType]\n ): void {\n const e = this.store.get(requestId);\n if (!e) return;\n e[type] = file as any;\n }\n\n cleanupExpired(now = Date.now()): number {\n let removed = 0;\n for (const [requestId, entry] of this.store.entries()) {\n if (now > entry.expiresAt) {\n this.delete(requestId);\n removed++;\n }\n }\n return removed;\n }\n\n start(): void {\n if (this.cleanupTimer) return;\n\n this.cleanupTimer = setInterval(() => {\n this.cleanupExpired(Date.now());\n }, this.opts.cleanupIntervalMs);\n\n this.cleanupTimer.unref();\n }\n\n stop(): void {\n if (!this.cleanupTimer) return;\n clearInterval(this.cleanupTimer);\n this.cleanupTimer = undefined;\n }\n\n private cleanupEntry(requestId: string) {\n const entry = this.store.get(requestId);\n if (!entry) return;\n\n ([\"audio\", \"video\"] as const).forEach((type) => {\n const f = entry[type];\n if (f?.path && fs.existsSync(f.path)) {\n try {\n fs.unlinkSync(f.path);\n } catch {\n // ignore\n }\n }\n });\n }\n}\n","import fs from \"node:fs\";\nimport path from \"node:path\";\nimport os from \"node:os\";\n\nexport interface ResolvedPaths {\n baseDir: string;\n cacheDir: string;\n}\n\nexport function ensureDirSync(dirPath: string) {\n fs.mkdirSync(dirPath, { recursive: true, mode: 0o777 });\n\n try {\n fs.chmodSync(dirPath, 0o777);\n } catch {\n // ignore\n }\n\n fs.accessSync(dirPath, fs.constants.R_OK | fs.constants.W_OK);\n}\n\nexport function resolvePaths(cacheDir?: string): ResolvedPaths {\n const baseDir = cacheDir?.trim()\n ? cacheDir\n : path.join(os.tmpdir(), \"yt-play\");\n const resolvedBase = path.resolve(baseDir);\n\n const resolvedCache = path.join(resolvedBase);\n\n ensureDirSync(resolvedBase);\n ensureDirSync(resolvedCache);\n\n return {\n baseDir: resolvedBase,\n cacheDir: resolvedCache,\n };\n}\n","import { spawn } from \"node:child_process\";\nimport path from \"node:path\";\nimport fs from \"node:fs\";\nimport type { DownloadInfo } from \"./types.js\";\n\nlet __dirname: string;\ntry {\n // @ts-ignore\n __dirname = path.dirname(new URL(import.meta.url).pathname);\n} catch {\n // @ts-ignore\n __dirname = typeof __dirname !== \"undefined\" ? __dirname : process.cwd();\n}\n\nexport interface YtDlpClientOptions {\n binaryPath?: string;\n ffmpegPath?: string;\n aria2cPath?: string;\n timeoutMs?: number;\n useAria2c?: boolean;\n concurrentFragments?: number;\n cookiesPath?: string;\n cookiesFromBrowser?: string;\n}\n\nexport interface YtDlpVideoInfo {\n id: string;\n title: string;\n uploader?: string;\n duration: number;\n thumbnail?: string;\n}\n\nexport interface YtDlpPlaylistItem {\n id: string;\n title: string;\n url: string;\n duration?: number;\n uploader?: string;\n directUrl?: string;\n}\n\nexport interface YtDlpResolvedItem extends YtDlpPlaylistItem {\n directUrl: string;\n}\n\nexport interface YtDlpPlaylistInfo {\n id: string;\n title: string;\n uploader?: string;\n entries: YtDlpPlaylistItem[] | YtDlpResolvedItem[];\n}\n\nexport interface YtDlpPlaylistOptions {\n limit?: number;\n resolveLinks?: boolean;\n type?: \"audio\" | \"video\" | \"both\";\n batchSize?: number;\n}\n\nexport class YtDlpClient {\n private readonly binaryPath: string;\n private readonly ffmpegPath?: string;\n private readonly aria2cPath?: string;\n private readonly timeoutMs: number;\n private readonly useAria2c: boolean;\n private readonly concurrentFragments: number;\n private readonly cookiesPath?: string;\n private readonly cookiesFromBrowser?: string;\n\n constructor(opts: YtDlpClientOptions = {}) {\n this.binaryPath = opts.binaryPath || this.detectYtDlp();\n this.ffmpegPath = opts.ffmpegPath;\n this.timeoutMs = opts.timeoutMs ?? 300_000;\n this.concurrentFragments = opts.concurrentFragments ?? 5;\n this.cookiesPath = opts.cookiesPath;\n this.cookiesFromBrowser = opts.cookiesFromBrowser;\n\n this.aria2cPath = opts.aria2cPath || this.detectAria2c();\n this.useAria2c = opts.useAria2c ?? !!this.aria2cPath;\n }\n\n private detectYtDlp(): string {\n const packageRoot = path.resolve(__dirname, \"../..\");\n const bundledPaths = [\n path.join(packageRoot, \"bin\", \"yt-dlp\"),\n path.join(packageRoot, \"bin\", \"yt-dlp.exe\"),\n ];\n\n for (const p of bundledPaths) {\n if (fs.existsSync(p)) {\n return p;\n }\n }\n\n try {\n const { execSync } = require(\"node:child_process\");\n const cmd =\n process.platform === \"win32\" ? \"where yt-dlp\" : \"which yt-dlp\";\n const result = execSync(cmd, { encoding: \"utf-8\" }).trim();\n if (result) return result.split(\"\\n\")[0];\n } catch {}\n\n return \"yt-dlp\";\n }\n\n private detectAria2c(): string | undefined {\n const packageRoot = path.resolve(__dirname, \"../..\");\n const bundledPaths = [\n path.join(packageRoot, \"bin\", \"aria2c\"),\n path.join(packageRoot, \"bin\", \"aria2c.exe\"),\n ];\n\n for (const p of bundledPaths) {\n if (fs.existsSync(p)) {\n return p;\n }\n }\n\n try {\n const { execSync } = require(\"node:child_process\");\n const cmd =\n process.platform === \"win32\" ? \"where aria2c\" : \"which aria2c\";\n const result = execSync(cmd, { encoding: \"utf-8\" }).trim();\n if (result) return result.split(\"\\n\")[0];\n } catch {}\n\n return undefined;\n }\n\n public async exec(args: string[]): Promise<string> {\n return new Promise((resolve, reject) => {\n let allArgs = [...args];\n\n if (this.ffmpegPath) {\n allArgs = [\"--ffmpeg-location\", this.ffmpegPath, ...allArgs];\n }\n\n if (this.cookiesPath && fs.existsSync(this.cookiesPath)) {\n allArgs = [\"--cookies\", this.cookiesPath, ...allArgs];\n }\n\n if (this.cookiesFromBrowser) {\n allArgs = [\n \"--cookies-from-browser\",\n this.cookiesFromBrowser,\n ...allArgs,\n ];\n }\n\n const proc = spawn(this.binaryPath, allArgs, {\n stdio: [\"ignore\", \"pipe\", \"pipe\"],\n });\n\n let stdout = \"\";\n let stderr = \"\";\n\n proc.stdout.on(\"data\", (chunk) => {\n stdout += chunk.toString();\n });\n\n proc.stderr.on(\"data\", (chunk) => {\n stderr += chunk.toString();\n });\n\n const timer = setTimeout(() => {\n proc.kill(\"SIGKILL\");\n reject(new Error(`yt-dlp timeout after ${this.timeoutMs}ms`));\n }, this.timeoutMs);\n\n proc.on(\"close\", (code) => {\n clearTimeout(timer);\n if (code === 0) {\n resolve(stdout);\n } else {\n reject(\n new Error(\n `yt-dlp exited with code ${code}. stderr: ${stderr.slice(0, 500)}`,\n ),\n );\n }\n });\n\n proc.on(\"error\", (err) => {\n clearTimeout(timer);\n reject(err);\n });\n });\n }\n\n async getInfo(youtubeUrl: string): Promise<YtDlpVideoInfo> {\n const stdout = await this.exec([\n \"-J\",\n \"--no-warnings\",\n \"--no-playlist\",\n youtubeUrl,\n ]);\n const info = JSON.parse(stdout) as YtDlpVideoInfo;\n return info;\n }\n\n async getPlaylistInfo(\n youtubeUrl: string,\n opts: YtDlpPlaylistOptions = {},\n ): Promise<YtDlpPlaylistInfo> {\n const stdout = await this.exec([\n \"-J\",\n \"--flat-playlist\",\n \"--no-warnings\",\n youtubeUrl,\n ]);\n\n const info = JSON.parse(stdout);\n\n let entries: YtDlpPlaylistItem[] = (info.entries || [])\n .filter((entry: any) => {\n const t = entry.title || \"\";\n return (\n entry.id &&\n t &&\n !t.includes(\"[Deleted video]\") &&\n !t.includes(\"[Private video]\")\n );\n })\n .map((entry: any) => ({\n id: entry.id,\n title: entry.title,\n url: `https://www.youtube.com/watch?v=${entry.id}`,\n duration: entry.duration,\n uploader: entry.uploader || info.uploader,\n }));\n\n if (opts.resolveLinks) {\n entries = await this.resolvePlaylistItems(\n entries,\n opts.limit && opts.limit > 0 ? opts.limit : entries.length,\n opts.type || \"audio\",\n opts.batchSize || 5,\n );\n } else if (opts.limit && opts.limit > 0) {\n entries = entries.slice(0, opts.limit);\n }\n\n return {\n id: info.id,\n title: info.title,\n uploader: info.uploader,\n entries,\n };\n }\n\n async getDirectUrl(\n youtubeUrl: string,\n type: \"audio\" | \"video\" | \"both\" = \"audio\",\n ): Promise<string> {\n const format =\n type === \"audio\"\n ? \"bestaudio[ext=m4a]/bestaudio/best\"\n : \"best[ext=mp4]/best\";\n\n const stdout = await this.exec([\n \"-f\",\n format,\n \"-g\",\n \"--no-warnings\",\n youtubeUrl,\n ]);\n\n return stdout.trim().split(\"\\n\")[0];\n }\n\n async resolvePlaylistItems(\n entries: YtDlpPlaylistItem[],\n limit: number,\n type: \"audio\" | \"video\" | \"both\" = \"audio\",\n batchSize: number = 5,\n ): Promise<YtDlpResolvedItem[]> {\n const resolved: YtDlpResolvedItem[] = [];\n let currentIndex = 0;\n\n while (resolved.length < limit && currentIndex < entries.length) {\n const needed = limit - resolved.length;\n const takeCount = Math.min(\n batchSize,\n needed,\n entries.length - currentIndex,\n );\n\n const batch = entries.slice(currentIndex, currentIndex + takeCount);\n currentIndex += takeCount;\n\n const promises = batch.map(async (item) => {\n try {\n const directUrl = await this.getDirectUrl(item.url, type);\n return { ...item, directUrl } as YtDlpResolvedItem;\n } catch (err) {\n return null;\n }\n });\n\n const results = await Promise.all(promises);\n\n for (const res of results) {\n if (res && resolved.length < limit) {\n resolved.push(res);\n }\n }\n }\n\n return resolved;\n }\n\n private buildOptimizationArgs(): string[] {\n const args: string[] = [\n \"--no-warnings\",\n \"--no-playlist\",\n \"--no-check-certificates\",\n \"--concurrent-fragments\",\n String(this.concurrentFragments),\n ];\n\n if (this.useAria2c && this.aria2cPath) {\n args.push(\"--downloader\", this.aria2cPath);\n args.push(\"--downloader-args\", \"aria2c:-x 16 -s 16 -k 1M\");\n }\n\n return args;\n }\n\n async getAudio(\n youtubeUrl: string,\n qualityKbps: number,\n outputPath: string,\n ): Promise<DownloadInfo> {\n const info = await this.getInfo(youtubeUrl);\n const format = \"bestaudio[ext=m4a]/bestaudio/best\";\n\n const args = [\n \"-f\",\n format,\n \"-o\",\n outputPath,\n ...this.buildOptimizationArgs(),\n youtubeUrl,\n ];\n\n try {\n await this.exec(args);\n } catch (err) {\n [outputPath, `${outputPath}.part`, `${outputPath}.ytdl`].forEach((f) => {\n if (fs.existsSync(f)) {\n try {\n fs.unlinkSync(f);\n } catch {}\n }\n });\n throw err;\n }\n\n if (!fs.existsSync(outputPath)) {\n throw new Error(`yt-dlp failed to create audio file: ${outputPath}`);\n }\n\n const duration = this.formatDuration(info.duration);\n\n return {\n title: info.title,\n author: info.uploader,\n duration,\n quality: `${qualityKbps}kbps m4a`,\n filename: path.basename(outputPath),\n downloadUrl: outputPath,\n };\n }\n\n async getVideo(\n youtubeUrl: string,\n qualityP: number,\n outputPath: string,\n ): Promise<DownloadInfo> {\n const info = await this.getInfo(youtubeUrl);\n\n const format = `bestvideo[height<=${qualityP}][ext=mp4][vcodec^=avc]+bestaudio[ext=m4a]/bestvideo[height<=${qualityP}][vcodec!=av1][vcodec!=vp9]+bestaudio/best[height<=${qualityP}]`;\n\n const args = [\n \"-f\",\n format,\n \"--merge-output-format\",\n \"mp4\",\n \"-o\",\n outputPath,\n ...this.buildOptimizationArgs(),\n youtubeUrl,\n ];\n\n try {\n await this.exec(args);\n } catch (err) {\n [outputPath, `${outputPath}.part`, `${outputPath}.ytdl`].forEach((f) => {\n if (fs.existsSync(f)) {\n try {\n fs.unlinkSync(f);\n } catch {}\n }\n });\n throw err;\n }\n\n if (!fs.existsSync(outputPath)) {\n throw new Error(`yt-dlp failed to create video file: ${outputPath}`);\n }\n\n const duration = this.formatDuration(info.duration);\n\n return {\n title: info.title,\n author: info.uploader,\n duration,\n quality: `${qualityP}p H.264`,\n filename: path.basename(outputPath),\n downloadUrl: outputPath,\n };\n }\n\n private formatDuration(seconds: number): string {\n if (!seconds) return \"0:00\";\n const h = Math.floor(seconds / 3600);\n const m = Math.floor((seconds % 3600) / 60);\n const s = Math.floor(seconds % 60);\n if (h > 0) {\n return `${h}:${m.toString().padStart(2, \"0\")}:${s.toString().padStart(2, \"0\")}`;\n }\n return `${m}:${s.toString().padStart(2, \"0\")}`;\n }\n}\n","import yts from \"yt-search\";\n\nimport type { PlayMetadata } from \"./types.js\";\n\nexport function stripWeirdUrlWrappers(input: string): string {\n let s = (input || \"\").trim();\n const mdAll = [...s.matchAll(/\\[[^\\]]*\\]\\((https?:\\/\\/[^)\\s]+)\\)/gi)];\n if (mdAll.length > 0) return mdAll[0][1].trim();\n s = s.replace(/^<([^>]+)>$/, \"$1\").trim();\n s = s.replace(/^[\"'`](.*)[\"'`]$/, \"$1\").trim();\n\n return s;\n}\n\nexport function getYouTubeVideoId(input: string): string | null {\n const regex =\n /(?:https?:\\/\\/)?(?:www\\.)?(?:youtube\\.com\\/(?:[^\\/]+\\/.+\\/|(?:v|e(?:mbed)?)\\/|.*[?&]v=|shorts\\/)|youtu\\.be\\/)([a-zA-Z0-9_-]{11})(?:[?&]|$)/i;\n\n const match = (input || \"\").match(regex);\n return match ? match[1] : null;\n}\n\nexport function getYouTubePlaylistId(input: string): string | null {\n const regex = /[?&]list=([a-zA-Z0-9_-]+)/i;\n const match = (input || \"\").match(regex);\n return match ? match[1] : null;\n}\n\nexport function normalizeYoutubeUrl(input: string): string | null {\n const cleaned0 = stripWeirdUrlWrappers(input);\n\n const firstUrl = cleaned0.match(/https?:\\/\\/[^\\s)]+/i)?.[0] ?? cleaned0;\n\n const id = getYouTubeVideoId(firstUrl);\n if (!id) return null;\n\n return `https://www.youtube.com/watch?v=${id}`;\n}\n\nexport async function searchBest(query: string): Promise<PlayMetadata | null> {\n const videoId = getYouTubeVideoId(query);\n\n if (videoId) {\n const video = await yts({ videoId });\n\n if (!video) return null;\n\n const durationSeconds = video.duration?.seconds ?? 0;\n const normalizedUrl = normalizeYoutubeUrl(video.url) ?? video.url;\n\n return {\n title: video.title || \"Untitled\",\n author: video.author?.name || undefined,\n duration: video.duration?.timestamp || undefined,\n thumb: video.image || video.thumbnail || undefined,\n videoId: video.videoId,\n url: normalizedUrl,\n durationSeconds,\n };\n }\n\n const result = await yts(query);\n const v = result?.videos?.[0];\n if (!v) return null;\n\n const durationSeconds = v.duration?.seconds ?? 0;\n const normalizedUrl = normalizeYoutubeUrl(v.url) ?? v.url;\n\n return {\n title: v.title || \"Untitled\",\n author: v.author?.name || undefined,\n duration: v.duration?.timestamp || undefined,\n thumb: v.image || v.thumbnail || undefined,\n videoId: v.videoId,\n url: normalizedUrl,\n durationSeconds,\n };\n}\n\nexport async function searchPlaylistBest(\n query: string,\n): Promise<PlayMetadata | null> {\n const listId = getYouTubePlaylistId(query);\n\n if (listId) {\n try {\n const list = await yts({ listId });\n\n if (!list) return null;\n\n return {\n title: list.title || \"Untitled\",\n author: list.author?.name || undefined,\n duration: undefined,\n durationSeconds: 0,\n thumb: list.image || list.thumbnail || undefined,\n videoId: listId,\n url: list.url || `https://www.youtube.com/playlist?list=${listId}`,\n };\n } catch (err) {}\n }\n\n const result = await yts(query);\n const p = result?.playlists?.[0];\n if (!p) return null;\n\n return {\n title: p.title || \"Untitled\",\n author: p.author?.name || undefined,\n duration: undefined,\n durationSeconds: 0,\n thumb: p.image || p.thumbnail || undefined,\n videoId: p.listId,\n url: p.url || `https://www.youtube.com/playlist?list=${p.listId}`,\n };\n}\n","import { spawn } from \"node:child_process\";\nimport type { StreamInfo } from \"./types.js\";\nimport type { YtDlpClient } from \"./ytdlp-client.js\";\n\nexport class StreamEngine {\n constructor(private readonly ytdlpClient: YtDlpClient) {}\n\n private getBinaryPath(): string {\n return (this.ytdlpClient as any).binaryPath;\n }\n\n async getAudioStream(\n youtubeUrl: string,\n qualityKbps: number = 128,\n ): Promise<StreamInfo> {\n const format = \"bestaudio[ext=m4a]/bestaudio/best\";\n\n const args = [\n \"-f\",\n format,\n \"-o\",\n \"-\",\n \"--no-warnings\",\n \"--no-playlist\",\n youtubeUrl,\n ];\n\n const proc = spawn(this.getBinaryPath(), args, {\n stdio: [\"ignore\", \"pipe\", \"pipe\"],\n });\n\n proc.stderr.on(\"data\", () => {});\n\n return {\n stream: proc.stdout,\n quality: `${qualityKbps}kbps`,\n format: \"m4a\",\n };\n }\n\n async getVideoStream(\n youtubeUrl: string,\n qualityP: number = 720,\n ): Promise<StreamInfo> {\n const format = `best[height<=${qualityP}][ext=mp4]/best`;\n\n const args = [\n \"-f\",\n format,\n \"-o\",\n \"-\",\n \"--no-warnings\",\n \"--no-playlist\",\n youtubeUrl,\n ];\n\n const proc = spawn(this.getBinaryPath(), args, {\n stdio: [\"ignore\", \"pipe\", \"pipe\"],\n });\n\n proc.stderr.on(\"data\", () => {});\n\n return {\n stream: proc.stdout,\n quality: `${qualityP}p`,\n format: \"mp4\",\n };\n }\n}\n","import type { YtDlpClient } from \"./ytdlp-client.js\";\nimport type {\n YtDlpVideoMetadata,\n YtDlpChannelMetadata,\n StalkChannelOptions,\n} from \"./types.js\";\n\nexport class StalkerEngine {\n constructor(private readonly ytdlpClient: YtDlpClient) {}\n\n async stalkVideoOrLive(youtubeUrl: string): Promise<YtDlpVideoMetadata> {\n const args = [\"-J\", \"--no-warnings\", \"--no-playlist\", youtubeUrl];\n\n const stdout = await this.ytdlpClient.exec(args);\n return JSON.parse(stdout) as YtDlpVideoMetadata;\n }\n\n async stalkChannel(\n youtubeUrl: string,\n opts: StalkChannelOptions = {},\n ): Promise<YtDlpChannelMetadata> {\n const args = [\"-J\", \"--no-warnings\"];\n\n if (opts.flat) {\n args.push(\"--flat-playlist\");\n }\n\n if (opts.playlistItems) {\n args.push(\"--playlist-items\", opts.playlistItems);\n } else {\n if (opts.startItem && opts.startItem > 0) {\n args.push(\"--playlist-start\", opts.startItem.toString());\n }\n if (opts.endItem && opts.endItem > 0) {\n args.push(\"--playlist-end\", opts.endItem.toString());\n }\n }\n\n let targetUrl = youtubeUrl.trim().replace(/\\/$/, \"\");\n if (opts.tab) {\n targetUrl = targetUrl.replace(\n /\\/(videos|shorts|streams|popular|featured)$/i,\n \"\",\n );\n if (opts.tab !== \"featured\") {\n targetUrl = `${targetUrl}/${opts.tab}`;\n }\n }\n\n args.push(targetUrl);\n\n try {\n const stdout = await this.ytdlpClient.exec(args);\n return JSON.parse(stdout) as YtDlpChannelMetadata;\n } catch (error: any) {\n const msg = error.message || \"\";\n\n if (\n msg.includes(\"This channel does not have a\") ||\n msg.includes(\"404\") ||\n msg.includes(\"does not exist\")\n ) {\n return {\n _type: \"playlist\",\n id: targetUrl,\n title: \"Tab Not Found\",\n channel: targetUrl.split(\"/\").pop() || \"Unknown\",\n channel_id: \"Unknown\",\n entries: [],\n } as YtDlpChannelMetadata;\n }\n\n throw error;\n }\n }\n}\n"],"mappings":"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","names":["fs","path","execSync","fs","CacheStore","opts","requestId","entry","loading","type","file","e","now","removed","f","fs","path","os","ensureDirSync","dirPath","resolvePaths","cacheDir","baseDir","resolvedBase","resolvedCache","spawn","path","fs","__dirname","YtDlpClient","opts","packageRoot","bundledPaths","p","execSync","cmd","result","args","resolve","reject","allArgs","proc","stdout","stderr","chunk","timer","code","err","youtubeUrl","info","entries","entry","t","type","format","limit","batchSize","resolved","currentIndex","needed","takeCount","batch","promises","item","directUrl","results","res","qualityKbps","outputPath","f","duration","qualityP","seconds","h","m","s","yts","stripWeirdUrlWrappers","input","s","mdAll","getYouTubeVideoId","regex","match","getYouTubePlaylistId","normalizeYoutubeUrl","cleaned0","firstUrl","id","searchBest","query","videoId","video","durationSeconds","normalizedUrl","v","searchPlaylistBest","listId","list","p","spawn","StreamEngine","ytdlpClient","youtubeUrl","qualityKbps","args","proc","qualityP","AUDIO_QUALITIES","VIDEO_QUALITIES","UPDATE_CHECK_INTERVAL","pickQuality","requested","available","sanitizeFilename","filename","getNodeBinaryPath","execSync","setupYtDlpConfig","ytdlpBinaryPath","cookiesPath","cookiesFromBrowser","binaryDir","path","moduleUrl","fs","configPath","configLines","nodePath","PlayEngine","_PlayEngine","options","resolvePaths","CacheStore","YtDlpClient","StreamEngine","now","scriptPath","checkAndUpdate","error","prefix","query","searchBest","requestId","metadata","normalized","normalizeYoutubeUrl","isLongVideo","normalizedMeta","audioKbps","audioTask","tasks","type","entry","cached","directFile","timeoutMs","intervalMs","started","f","r","youtubeUrl","quality","err","uniqueSuffix","safeTitle","filePath","info","size","buffer","isRetry","videoP","ext","stats","StalkerEngine","ytdlpClient","youtubeUrl","args","stdout","opts","targetUrl","error","msg"]}
|